This module is intended to serve as a logical descendant of pathlib, a Python 3 module for object-oriented path manipulations. As such, it implements everything as closely as possible to the origin with few exceptions, such as stat().
dohq-artifactory
is a live python package for Jfrog Artifactory. It was forked from outdated parallels/artifactory and supports all functionality from the original package.
Upgrade/install to the newest available version:
pip install dohq-artifactory --upgrade
Install latest development version (Warning! It may contains some errors!):
pip install dohq-artifactory --upgrade --pre
Or specify version, e.g.:
pip install dohq-artifactory==0.5.dev243
dohq-artifactory
supports these ways of authentication:
- Username and password (or API KEY) to access restricted resources, you can pass
auth
parameter to ArtifactoryPath. - API KEY can pass with
apikey
parameter.
from artifactory import ArtifactoryPath
# API_KEY
path = ArtifactoryPath(
"http://my-artifactory/artifactory/myrepo/restricted-path", apikey="MY_API_KEY"
)
# User and password OR API_KEY
path = ArtifactoryPath(
"http://my-artifactory/artifactory/myrepo/restricted-path",
auth=("USERNAME", "PASSWORD or API_KEY"),
)
# Other authentication types
from requests.auth import HTTPDigestAuth
path = ArtifactoryPath(
"http://my-artifactory/artifactory/myrepo/restricted-path",
auth=("USERNAME", "PASSWORD"),
auth_type=HTTPDigestAuth,
)
from requests.auth import HTTPBasicAuth
path = ArtifactoryPath(
"http://my-artifactory/artifactory/myrepo/restricted-path",
auth=("USERNAME", "PASSWORD"),
auth_type=HTTPBasicAuth,
)
# Load username, password from global config if exist:
path = ArtifactoryPath(
"http://my-artifactory/artifactory/myrepo/restricted-path", auth_type=HTTPBasicAuth,
)
path.touch()
If you use Artifactory SaaS solution - use ArtifactorySaaSPath
class
from artifactory import ArtifactorySaaSPath
# API_KEY
path = ArtifactorySaaSPath(
"https://myartifactorysaas.jfrog.io/myartifactorysaas/folder/path.xml",
apikey="MY_API_KEY",
)
We have to use other class, because as a SaaS service, the URL is different from an on-prem installation and the REST API endpoints.
Get directory listing:
from artifactory import ArtifactoryPath
path = ArtifactoryPath("http://repo.jfrog.org/artifactory/gradle-ivy-local")
for p in path:
print(p)
Find all .gz
files in current dir, recursively:
from artifactory import ArtifactoryPath
path = ArtifactoryPath("http://repo.jfrog.org/artifactory/distributions/org/")
for p in path.glob("**/*.gz"):
print(p)
Download artifact to a local filesystem:
from artifactory import ArtifactoryPath
path = ArtifactoryPath(
"http://repo.jfrog.org/artifactory/distributions/org/apache/tomcat/apache-tomcat-7.0.11.tar.gz"
)
with path.open() as fd, ("tomcat.tar.gz", "wb") as out:
out.write(fd.read())
Download artifact folder to a local filesystem as archive (supports zip/tar/tar.gz/tgz) Allows to specify archive type and request checksum for the folder Note: Archiving should be enabled on the server!
from artifactory import ArtifactoryPath
path = ArtifactoryPath(
"http://my_url:8080/artifactory/my_repo/winx64/aas", auth=("user", "password")
)
with path.download_folder_archive(archive_type="zip", check_sum=False) as archive:
with open(r"D:\target.zip", "wb") as out:
out.write(archive.read())
Deploy a regular file myapp-1.0.tar.gz
from artifactory import ArtifactoryPath
path = ArtifactoryPath(
"http://my-artifactory/artifactory/libs-snapshot-local/myapp/1.0"
)
path.mkdir()
path.deploy_file("./myapp-1.0.tar.gz")
Deploy a debian package myapp-1.0.deb
from artifactory import ArtifactoryPath
path = ArtifactoryPath("http://my-artifactory/artifactory/ubuntu-local/pool")
path.deploy_deb(
"./myapp-1.0.deb", distribution="trusty", component="main", architecture="amd64"
)
Copy artifact from this path to destination. If files are on the same instance of artifactory, lightweight (local) copying will be attempted.
The suppress_layouts parameter, when set to True
, will allow artifacts
from one path to be copied directly into another path without enforcing
repository layouts. The default behaviour is to copy to the repository
root, but remap the [org], [module], [baseVer], etc. structure to the
target repository.
For example, we have a builds repository using the default maven2 repository where we publish our builds, and we also have a published repository where a directory for production and a directory for staging environments should hold the current promoted builds. How do we copy the contents of a build over to the production folder?
from artifactory import ArtifactoryPath
source = ArtifactoryPath("http://example.com/artifactory/builds/product/product/1.0.0/")
dest = ArtifactoryPath("http://example.com/artifactory/published/production/")
"""
Using copy with the default, suppress_layouts=False, the artifacts inside
builds/product/product/1.0.0/ will not end up in the published/production
path as we intended, but rather the entire structure product/product/1.0.0
is placed in the destination repo.
"""
source.copy(dest)
for p in dest:
print (p)
# http://example.com/artifactory/published/production/foo-0.0.1.gz
# http://example.com/artifactory/published/production/foo-0.0.1.pom
for p in ArtifactoryPath(
"http://example.com/artifactory/published/product/product/1.0.0.tar"
):
print p
# http://example.com/artifactory/published/product/product/1.0.0/product-1.0.0.tar.gz
# http://example.com/artifactory/published/product/product/1.0.0/product-1.0.0.tar.pom
"""
Using copy with suppress_layouts=True, the contents inside our source are copied
directly inside our dest as we intended.
"""
source.copy(dest, suppress_layouts=True)
for p in dest:
print (p)
"""
http://example.com/artifactory/published/production/foo-0.0.1.gz
http://example.com/artifactory/published/production/foo-0.0.1.pom
http://example.com/artifactory/published/production/product-1.0.0.tar.gz
http://example.com/artifactory/published/production/product-1.0.0.tar.pom
"""
Move artifact from this path to destination.
from artifactory import ArtifactoryPath
source = ArtifactoryPath("http://example.com/artifactory/builds/product/product/1.0.0/")
dest = ArtifactoryPath("http://example.com/artifactory/published/production/")
source.move(dest)
from artifactory import ArtifactoryPath
path = ArtifactoryPath(
"http://repo.jfrog.org/artifactory/distributions/org/apache/tomcat/apache-tomcat-7.0.11.tar.gz"
)
if path.exists():
path.unlink()
You can get and set (or remove) properties from artifact:
from artifactory import ArtifactoryPath
path = ArtifactoryPath(
"http://repo.jfrog.org/artifactory/distributions/org/apache/tomcat/apache-tomcat-7.0.11.tar.gz"
)
# Get properties
properties = path.properties
print(properties)
# Update one properties or add if does not exist
properties["qa"] = "tested"
path.properties = properties
# Remove properties
properties.pop("release")
path.properties = properties
You can use Artifactory Query Language in python.
from artifactory import ArtifactoryPath
aql = ArtifactoryPath(
"http://my-artifactory/artifactory"
) # path to artifactory, NO repo
# dict support
# Send query:
# items.find({"repo": "myrepo"})
artifacts = aql.aql("items.find", {"repo": "myrepo"})
# list support.
# Send query:
# items.find().include("name", "repo")
artifacts = aql.aql("items.find()", ".include", ["name", "repo"])
# support complex query
# items.find({"$and": [{"repo": {"$eq": "repo"}}, {"$or": [{"path": {"$match": "*path1"}}, {"path": {"$match": "*path2"}}]}]})
args = [
"items.find",
{
"$and": [
{"repo": {"$eq": "repo"}},
{"$or": [{"path": {"$match": "*path1"}}, {"path": {"$match": "*path2"}},]},
]
},
]
# artifacts_list contains raw data (list of dict)
# Send query:
# items.find({"$and": [{"repo": {"$eq": "repo"}}, {"$or": [{"path": {"$match": "*path1"}}, {"path": {"$match": "*path2"}}]}]})
artifacts_list = aql.aql(*args)
# You can convert to pathlib object:
artifact_pathlib = map(aql.from_aql, artifacts_list)
artifact_pathlib_list = list(map(aql.from_aql, artifacts_list))
You can get hash (md5
, sha1
, sha256
), create date, and change date:
from artifactory import ArtifactoryPath
path = ArtifactoryPath(
"http://repo.jfrog.org/artifactory/distributions/org/apache/tomcat/apache-tomcat-7.0.11.tar.gz"
)
# Get FileStat
stat = ArtifactoryPath.stat(path)
print(stat)
print(stat.md5)
print(stat.sha1)
print(stat.sha256)
print(stat.ctime)
print(stat.is_dir)
print(stat.size)
You can manipulate with user\group\repository and permission. First, create ArtifactoryPath
object without a repository
from artifactory import ArtifactoryPath
artifactory_ = ArtifactoryPath(
"https://artifactory.example.com/artifactory", auth=("user", "password")
)
You can see detailed use of AdminObject
in file .\tests\integration\test_admin.py
# Find or create first way
from dohq_artifactory import generate_password, User
user = artifactory_.find_user("username")
if user is None:
# User does not exist
user = User(
artifactory_, "username", "username@example.com", password=generate_password()
)
user.create()
# Find or create - second way
user = User(artifactory_, "username")
if not user.read(): # Return True if user exist
# User does not exist
user = User(
artifactory_, "username", "username@example.com", password=generate_password()
)
user.create()
# Add to group
user.add_to_group("byname")
group = artifactory_.find_group("groupname")
user.add_to_group(group)
user.update() # Don't forget update :)
enc_pwd = user.encryptedPassword
# You can re-read from Artifactory
user.read()
user.delete()
# Find
from dohq_artifactory import generate_password, Group
group = artifactory_.find_group("groupname")
# Create
if group is None:
group = Group(artifactory_, "groupname")
group.create()
# You can re-read from Artifactory
group.read()
group.delete()
https://www.jfrog.com/confluence/display/RTF/LDAP+Groups#LDAPGroups-UsingtheRESTAPI
# Full DN path in artifactory
dn = "cn=R.DevOps.TestArtifactory,ou=Groups,dc=example,dc=com"
attr = "ldapGroupName=r.devops.testartifactory;groupsStrategy=STATIC;groupDn={}".format(
dn
)
test_group = GroupLDAP(
artifactory=artifactory_, name="r.devops.testartifactory", realmAttributes=attr
)
test_group.create()
# Find
from dohq_artifactory import generate_password, RepositoryLocal
repo = artifactory_.find_repository_local("reponame")
# Create
if repo is None:
# or RepositoryLocal.PYPI, RepositoryLocal.NUGET, etc
repo = RepositoryLocal(artifactory_, "reponame", packageType=RepositoryLocal.DEBIAN)
repo.create()
# You can re-read from Artifactory
repo.read()
repo.delete()
# Find
from dohq_artifactory import RepositoryVirtual
repo = artifactory_.find_repository_virtual("pypi.all")
# Create
if repo is None:
# or RepositoryVirtual.PYPI, RepositoryLocal.NUGET, etc
repo = RepositoryVirtual(
artifactory_,
"pypi.all",
repositories=["pypi.snapshot", "pypi.release"],
packageType=RepositoryVirtual.PYPI,
)
repo.create()
# You can re-read from Artifactory
repo.read()
local_repos = repo.repositories # return List<RepositiryLocal>
repo.delete()
# Find
from dohq_artifactory import RepositoryRemote
repo = artifactory_.find_repository_virtual("pypi.all")
# Create
if repo is None:
# or RepositoryRemote.PYPI, RepositoryRemote.NUGET, etc
repo = RepositoryRemote(
artifactory_,
"pypi.all",
url="https://files.pythonhosted.org",
packageType=RepositoryVirtual.PYPI,
)
repo.create()
# You can re-read from Artifactory
repo.read()
repo.delete()
Docs: https://www.jfrog.com/confluence/display/RTF/Managing+Permissions
Supports these roles:
- PermissionTarget.ROLE_ADMIN =
ADMIN + DELETE + DEPLOY + ANNOTATE + READ
- PermissionTarget.ROLE_DELETE =
DELETE + DEPLOY + ANNOTATE + READ
- PermissionTarget.ROLE_DEPLOY =
DEPLOY + ANNOTATE + READ
- PermissionTarget.ROLE_ANNOTATE =
ANNOTATE + READ
- PermissionTarget.ROLE_READ =
READ
And for more modular control:
PermissionTarget.ADMIN
- Allows changing the permission settings for other users on this permission targetPermissionTarget.DELETE
- Allows deletion or overwriting of artifactsPermissionTarget.DEPLOY
- Allows deploying artifacts and deploying to caches (i.e. populating caches with remote artifacts)PermissionTarget.ANNOTATE
- Allows annotating artifacts and folders with metadata and propertiesPermissionTarget.READ
- Allows reading and downloading of artifacts
from dohq_artifactory import PermissionTarget
permission = artifactory_.find_permission_target("rule")
# Add repo as string or RepositoryLocal object
permission.add_repository("repo1", "repo2")
# Add group or user with permission
permission.add_user(user_object, PermissionTarget.ROLE_ADMIN)
permission.add_group("groupname", PermissionTarget.ROLE_READ)
permission.update() # Update!!
https://www.jfrog.com/confluence/display/RTF5X/Access+Tokens#AccessTokens-RESTAPI
from requests.auth import HTTPBasicAuth
from artifactory import ArtifactoryPath
from dohq_artifactory import Token
session = ArtifactoryPath(
"https://artifactory_dns/artifactory",
auth=("admin", "admin_password"),
auth_type=HTTPBasicAuth,
verify=False,
)
# Read token for readers group
group_name = "readers"
scope = "api:* member-of-groups:" + group_name
token = Token(session, scope=scope)
token.read()
# Create token for member of the readers
group_name = "readers"
scope = "api:* member-of-groups:" + group_name
subject = group_name
token = Token(
session, scope=scope, username=subject, expires_in=31557600, refreshable=True
)
response = token.create()
print("Readonly token:")
print("Username: " + token.username)
print("Token: " + token.token["access_token"])
All AdminObject
support:
user = artifactory_.find_user("username")
print(user.raw) # JSON response from Artifactory
new_repo = RepositoryLocal(artifactory, "reponame")
# If some key you can't find in object, you can use this:
new_repo.additional_params["property_sets"] = ["my", "properties_sets"]
new_repo.create()
# All object support CRUD operations:
obj.read() # Return True if user exist (and read from Artifactory), else return False
obj.create()
obj.update()
obj.delete()
# ArtifactoryPath have different find_ method:
artifactory_.find_user("name")
artifactory_.find_group("name")
artifactory_.find_repository_local("name")
artifactory_.find_permission_target("name")
To re-use the established connection, you can pass session
parameter to ArtifactoryPath:
from artifactory import ArtifactoryPath
import requests
ses = requests.Session()
ses.auth = ("username", "password")
path = ArtifactoryPath(
"http://my-artifactory/artifactory/myrepo/my-path-1", sesssion=ses
)
path.touch()
path = ArtifactoryPath(
"http://my-artifactory/artifactory/myrepo/my-path-2", sesssion=ses
)
path.touch()
See Requests - SSL verification for more details.
from artifactory import ArtifactoryPath
path = ArtifactoryPath(
"http://my-artifactory/artifactory/libs-snapshot-local/myapp/1.0"
)
... is the same as
from artifactory import ArtifactoryPath
path = ArtifactoryPath(
"http://my-artifactory/artifactory/libs-snapshot-local/myapp/1.0", verify=True
)
Specify a local cert to use as client side certificate
from artifactory import ArtifactoryPath
path = ArtifactoryPath(
"http://my-artifactory/artifactory/libs-snapshot-local/myapp/1.0",
cert="/path_to_file/server.pem",
)
Disable host cert verification
from artifactory import ArtifactoryPath
path = ArtifactoryPath(
"http://my-artifactory/artifactory/libs-snapshot-local/myapp/1.0", verify=False
)
Note: If host cert verification is disabled, urllib3
will throw a InsecureRequestWarning.
To disable these warning, one needs to call urllib3.disable_warnings()
.
import requests.packages.urllib3 as urllib3
urllib3.disable_warnings()
Use logging for debug:
def init_logging():
logger_format_string = "%(thread)5s %(module)-20s %(levelname)-8s %(message)s"
logging.basicConfig(
level=logging.DEBUG, format=logger_format_string, stream=sys.stdout
)
init_logging()
path = ArtifactoryPath(
"http://my-artifactory/artifactory/myrepo/restricted-path",
auth=("USERNAME", "PASSWORD or API_KEY"),
)
path.touch()
Artifactory Python module also can specify all connection-related settings in a central file, ~/.artifactory_python.cfg
that is read upon the creation of first ArtifactoryPath
object and is stored globally. For instance, you can specify per-instance settings of authentication tokens, so that you won't need to explicitly pass auth
parameter to ArtifactoryPath
.
Example:
[http://artifactory-instance.com/artifactory]
username = deployer
password = ilikerandompasswords
verify = false
[another-artifactory-instance.com/artifactory]
username = foo
password = @dmin
cert = ~/mycert
Whether or not you specify http://
or https://
, the prefix is not essential. The module will first try to locate the best match and then try to match URLs without prefixes. So in the config, if you specify https://my-instance.local
and call ArtifactoryPath
with http://my-instance.local
, it will still do the right thing.
About contributing and testing
- artifactory-du - estimate file space usage. Summarize disk usage in JFrog Artifactory of the set of FILEs, recursively for directories.
- artifactory-cleanup-rules - python-script for Artifactory intelligence cleanup rules with config.