lib/mixlib/install/backend/artifactory.rb in mixlib-install-0.8.0.alpha.2 vs lib/mixlib/install/backend/artifactory.rb in mixlib-install-0.8.0.alpha.3

- old
+ new

@@ -23,67 +23,141 @@ module Mixlib class Install class Backend class Artifactory - ARTIFACTORY_ENDPOINT = "http://artifactory.chef.co".freeze + class ConnectionError < StandardError; end + class AuthenticationError < StandardError; end - attr_reader :options - attr_reader :client + ENDPOINT = "http://artifactory.chef.co".freeze + attr_accessor :options + def initialize(options) @options = options - @client = ::Artifactory::Client.new(endpoint: ARTIFACTORY_ENDPOINT) end + # Create filtered list of artifacts + # + # @return [Array<ArtifactInfo>] list of artifacts for the configured + # channel, product name, and product version. + # @return [ArtifactInfo] arifact info for the configured + # channel, product name, product version and platform info + # def info - begin - results = client.get("/api/search/prop", params, headers)["results"] - rescue Errno::ETIMEDOUT => e - raise e, "unstable channel uses endpoint #{ARTIFACTORY_ENDPOINT} \ -which is currently only accessible through Chef's internal network." + artifacts = artifactory_info.collect { |a| create_artifact(a) } + + artifacts_for_version = artifacts.find_all do |a| + a.version == options.resolved_version(artifacts) end if options.platform - artifact(results.first) - else - results.collect do |result| - artifact(result) + artifacts_for_version.find do |a| + a.platform == options.platform && + a.platform_version == options.platform_version && + a.architecture == options.architecture end + else + artifacts_for_version end end - private + # Fetches all artifacts from the configured Artifactory repository using + # channel and product name as search criteria + # + # @return [Array<Hash>] list of artifactory hash data + # + # Hash data: + # download_uri: The full url download path + # <property_name>: The names of the properties associcated to the artifact + # + def artifactory_info + query = <<-QUERY +items.find( + {"repo": "omnibus-#{options.channel}-local"}, + {"@omnibus.project": "#{options.product_name}"} +).include("repo", "path", "name", "property") + QUERY - def artifact(result) + results = artifactory_request do + client.post("/api/search/aql", query, "Content-Type" => "text/plain") + end + + # Merge artifactory properties and downloadUri to a flat Hash + results["results"].collect do |result| + { "downloadUri" => generate_download_uri(result) }.merge( + map_properties(result["properties"]) + ) + end + end + + def create_artifact(artifact_map) ArtifactInfo.new( - md5: result["properties"]["omnibus.md5"].first, - sha256: result["properties"]["omnibus.sha256"].first, - version: result["properties"]["omnibus.version"].first, - platform: result["properties"]["omnibus.platform"].first, - platform_version: result["properties"]["omnibus.platform_version"].first, - architecture: result["properties"]["omnibus.architecture"].first, - url: result["uri"] + md5: artifact_map["omnibus.md5"], + sha256: artifact_map["omnibus.sha256"], + version: artifact_map["omnibus.version"], + platform: artifact_map["omnibus.platform"], + platform_version: artifact_map["omnibus.platform_version"], + architecture: artifact_map["omnibus.architecture"], + url: artifact_map["downloadUri"] ) end - def params - params = { - "repos" => "omnibus-current-local", - "omnibus.version" => options.product_version - } + private - if options.platform - params["omnibus.platform"] = options.platform - params["omnibus.platform_version"] = options.platform_version - params["omnibus.architecture"] = options.architecture + # Converts Array<Hash> where the Hash is a key pair and + # value pair to a simplifed key/pair Hash + # + def map_properties(properties) + return {} if properties.nil? + properties.each_with_object({}) do |prop, h| + h[prop["key"]] = prop["value"] end + end - params + # Construct the downloadUri from raw artifactory data + # + def generate_download_uri(result) + uri = [] + uri << endpoint.sub(/\/$/, "") + uri << result["repo"] + uri << result["path"] + uri << result["name"] + uri.join("/") end - def headers - { "X-Result-Detail" => "properties" } + def client + @client ||= ::Artifactory::Client.new( + endpoint: endpoint, + username: ENV["ARTIFACTORY_USERNAME"], + password: ENV["ARTIFACTORY_PASSWORD"] + ) + end + + def endpoint + @endpoint ||= ENV.fetch("ARTIFACTORY_ENDPOINT", ENDPOINT) + end + + def artifactory_request + begin + results = yield + rescue Errno::ETIMEDOUT, ::Artifactory::Error::ConnectionError + raise ConnectionError, <<-EOS +Artifactory endpoint '#{::Artifactory.endpoint}' is unreachable. Check that +the endpoint is correct and there is an open connection to Chef's private network. + EOS + rescue ::Artifactory::Error::HTTPError => e + if e.code == 401 && e.message =~ /Bad credentials/ + raise AuthenticationError, <<-EOS +Artifactory server denied credentials. Verify ARTIFACTORY_USERNAME and +ARTIFACTORY_PASSWORD environment variables are configured properly. + EOS + else + raise e + end + end + + results end end end end end