/artifactory-client

A simple, lightweight Ruby client for interacting with the Artifactory API.

Primary LanguageRubyApache License 2.0Apache-2.0

Artifactory Client

Build status Gem Version

A Ruby client and interface to the Artifactory API. The majority of API endpoints are only exposed for Artifactory Pro customers! As such, many of the resources and actions exposed by this gem also require Artifactory Pro.

The Artifactory gem offers a convienent interface for managing various parts of the Artifactory API. It is not a complete API implementation, and should still be considered a work in progress.

Quick start

Install via Rubygems:

$ gem install artifactory

or add it to your Gemfile if you're using Bundler:

gem 'artifactory', '~> 3.0.5'

In your library or project, you wil likely want to include the Artifactory::Resource namespace:

include Artifactory::Resource

This will given you "Rails-like" access to the top-level Artifactory resources like:

System.info
Repository.all

If you choose not to include the module (for namespacing reasons), you will need to specify the full module path to access resources:

Artifactory::Resource::System.info
Artifactory::Resource::Repository.all

Create a connection

Before you can make a request, you must give Artifactory your connection information.

Artifactory.configure do |config|
  # The endpoint for the Artifactory server. If you are running the "default"
  # Artifactory installation using tomcat, don't forget to include the
  # +/artifactoy+ part of the URL.
  config.endpoint = 'https://my.storage.server/artifactory'

  # The basic authentication information. Since this uses HTTP Basic Auth, it
  # is highly recommended that you run Artifactory over SSL.
  config.username = 'admin'
  config.password = 'password'

  # You can also use an API key for authentication, username and password
  # take precedence so leave them off if you are using an API key.
  config.api_key = 'XXXXXXXXXXXXXXXXXX'

  # Speaking of SSL, you can specify the path to a pem file with your custom
  # certificates and the gem will wire it all up for you (NOTE: it must be a
  # valid PEM file).
  config.ssl_pem_file = '/path/to/my.pem'

  # Or if you are feelying frisky, you can always disable SSL verification
  config.ssl_verify = false

  # You can specify any proxy information, including any authentication
  # information in the URL.
  config.proxy_username = 'user'
  config.proxy_password = 'password'
  config.proxy_address  = 'my.proxy.server'
  config.proxy_port     = '8080'
end

All of these parameters are also configurable via the top-level Artifactory object. For example:

Artifactory.endpoint = '...'

Or, if you want to be really Unixy, these parameters are all configurable via environment variables:

# Artifactory will use these values for the defaults
export ARTIFACTORY_ENDPOINT=http://my.storage.server/artifactory
export ARTIFACTORY_USERNAME=admin
export ARTIFACTORY_PASSWORD=password
export ARTIFACTORY_API_KEY=XXXXXXXXXXXXXXXXXX
export ARTIFACTORY_SSL_PEM_FILE=/path/to/my.pem

You can also create a full Client object with hash parameters:

client = Artifactory::Client.new(endpoint: '...', username: '...')

Making requests

The Artifactory gem attempts to make the Artifactory API as object-oriented and Ruby-like as possible. All of the methods and API calls are heavily documented with examples inline using YARD. In order to keep the examples versioned with the code, the README only lists a few examples for using the Artifactory gem. Please see the inline documentation for the full API documentation. The tests in the 'spec' directory are an additional source of examples.

Artifacts

# Upload an artifact to a repository whose key is 'repo_key'
artifact.upload('/local/path/to/file', 'repo_key', param_1: 'foo')

# Search for an artifact by name
artifact = Artifact.search(name: 'package.deb').first
artifact #=> "#<Artifactory::Resource::Artifact md5: 'ABCD1234'>"

# Get the properties of an artifact
artifact.md5 #=> "ABCD1234"
artifact.properties #=> { ... }
# Set the properties of an artifact
artifact.properties({prop1: 'value1', 'prop2': 'value2'}) #=> { ... }

# Download the artifact to disk
artifact.download #=> /tmp/folders-a38b0decf038201/package.deb
artifact.download('~/Desktop', filename: 'software.deb') #=> /Users/you/Desktop/software.deb

# Delete the artifact from the Artifactory server
artifact.delete #=> true

Builds

# Show all components
BuildComponent.all #=> [#<BuildComponent ...>]

# Show all builds for a components
Build.all('wicket') #=> [#<Build ...>]

# Find a build component by name
component = BuildComponent.find('wicket')

# Delete some builds for a component
component.delete(build_numbers: %w( 51 52)) #=> true

# Delete all builds for a component
component.delete(delete_all: true) #=> true

# Delete a component and all of its associated data (including artifacts)
component.delete(artifacts: true, delete_all: true) #=> true

# Get a list of all buld records for a component
component.builds #=> #=> [#<Artifactory::Resource::Build ...>, ...]

# Create a new build record
build = Build.new(name: 'fricket', number: '51', properties: {...}, modules: [...])
build.save

# Find a build
build = Build.find('wicket', '51')

# Promote a build
build.promote('libs-release-local', status: 'staged', comment: 'Tested on all target platforms.')

Plugins

# Show all plugins
Plugin.all #=> [#<Plugin ...>]

Repository

# Find a repository by name
repo = Repository.find(name: 'libs-release-local')
repo #=> #<Artifactory::Resource::Repository ...>

# Get information about the repository
repo.description => "The default storage mechanism for..."

# Change the repository
repo.description = "This is a new description"
repo.save

# Upload an artifact to the repo
repo.upload('/local/path/to/file', param_1: 'foo', param_2: 'bar')

# Get a list of artifacts in this repository
repo.artifacts #=> [#<Artifactory::Resource::Artifact ...>, ...]

System

# Get the system information
System.info #=> "..."

# See if artifactory is running
System.ping #=> true

# Get the Artifactory server version and other information
System.version #=> { ... }

Raw requests

If there's a specific endpoint or path you need to hit that is not implemented by this gem, you can execute a "raw" request:

# Using the top-level Artifactory module
Artifactory.get('/some/special/path', param_1: 'foo', param_2: 'bar')

# Using an Artifactory::Client object
client.get('/some/special/path', param_1: 'foo', param_2: 'bar')

For more information on the methods available, please see the Artifactory::Client class.

Threadsafety

If you plan to use the Artifactory gem in a library, you should be aware that certain pathways for accessing resources are not threadsafe. In order to deliver a "Rails-like" experience, accessing a resource without a client object uses a global shared state. Other threads may modify this state, and therefore we do not recommend using the Rails-like approach if you are concerned about threadsafety. The following code snippet may better explain the differences:

# In our current thread...
Artifactory.endpoint = 'http://foo.com/artifactory'

# Meanwhile, in another thread...
Thread.new do
  Artifactory.endpoint = 'http://bar.com/artifactory'
end

# You have a 50/50 chance of which endpoint is used, depending on the order in
# which the threads execute on the CPU.
Artifactory.endpoint #=> 'http://foo.com/artifactory'
Artifactory.endpoint #=> 'http://bar.com/artifactory'

To avoid this potential headache, the Artifactory gem offers a less Rails-like API in which the Artifactory::Client object becomes the pivot for all resources. First, you must create a client object (you cannot use the global namespace):

client = Artifactory::Client.new(endpoint: 'http://foo.com/artifactory')

And then execute all requests using this client object, with the general pattern resource_method:

# Search for artifacts
client.artifact_search(name: '...') #=> [...]

# Get all plugins
client.all_plugins #=> [...]

This pattern is slightly less eye-appealing, but it will ensure that your code is threadsafe.

Development

  1. Clone the project on GitHub
  2. Create a feature branch
  3. Submit a Pull Request

Artifactory uses a built-in Sinatra server that "acts like" a real Artifactory Pro server. Since we cannot bundle a full Artifactory Pro server with the gem, we have re-implemented various pieces of their API. If you are writing a feature that accesses a new endpoint, you will likely need to add that endpoint to the vendored Sinatra app, using the API documentation for Artifactory.

Important Notes:

  • All new features must include test coverage. At a bare minimum, Unit tests are required. It is preferred if you include acceptance tests as well.
  • The tests must be be idempotent. The HTTP calls made during a test should be able to be run over and over.
  • Tests are order independent. The default RSpec configuration randomizes the test order, so this should not be a problem.

Maintainer

This project is maintained by Chef's Release Engineering Team (releng@chef.io).

License

Copyright 2013-2019 Chef Software, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.