# # Copyright 2014-2018 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. # require "uri" unless defined?(URI) require "benchmark" unless defined?(Benchmark) module Omnibus class ArtifactoryPublisher < Publisher def publish(&block) log.info(log_key) { "Starting artifactory publisher" } safe_require("artifactory") packages.each do |package| # Make sure the package is good to go! log.debug(log_key) { "Validating '#{package.name}'" } package.validate! retries = Config.publish_retries begin upload_time = Benchmark.realtime do remote_path = remote_path_for(package) properties = default_properties.merge(metadata_properties_for(package)) # Upload the package log.info(log_key) { "Uploading '#{package.name}'" } artifact_for(package).upload( repository, remote_path, properties ) # Upload the package's assoacited `*.metadata.json` file log.info(log_key) { "Uploading '#{package.name}.metadata.json'" } artifact_for(package.metadata).upload( repository, "#{remote_path}.metadata.json", # *.metadata.json files should not include # the checksum properties properties.dup.delete_if { |k, v| k =~ /^omnibus\.(md5|sha)/ } ) end rescue Artifactory::Error::HTTPError => e if (retries -= 1) != 0 log.info(log_key) { "Upload failed with exception: #{e}" } log.info(log_key) { "Retrying failed publish #{retries} more time(s)..." } retry else raise e end end log.debug(log_key) { "Elapsed time to publish #{package.name}: #{1000 * upload_time} ms" } # If a block was given, "yield" the package to the caller yield(package) if block end if build_record? if packages.empty? log.warn(log_key) { "No packages were uploaded, build record will not be created." } else build_for(packages).save end end end private # # The artifact object that corresponds to this package. # # @param [Package,Metadata] artifact # the package or metadata file to create the artifact from # # @return [Artifactory::Resource::Artifact] # def artifact_for(artifact) md5 = artifact.respond_to?(:metadata) ? artifact.metadata[:md5] : digest(artifact.path, :md5) sha1 = artifact.respond_to?(:metadata) ? artifact.metadata[:sha1] : digest(artifact.path, :sha1) Artifactory::Resource::Artifact.new( local_path: artifact.path, client: client, checksums: { "md5" => md5, "sha1" => sha1, } ) end # # The build object that corresponds to this package. # # @param [Array] packages # the packages to create the build from # # @return [Artifactory::Resource::Build] # def build_for(packages) metadata = packages.first.metadata name = metadata[:name] # Attempt to load the version manifest data from the packages metadata manifest = if version_manifest = metadata[:version_manifest] Manifest.from_hash(version_manifest) else Manifest.new( metadata[:version], # we already know the `version_manifest` entry is # missing so we can't pull in the `build_git_revision` nil, metadata[:license] ) end # Upload the actual package log.info(log_key) { "Saving build info for #{name}, Build ##{manifest.build_version}" } Artifactory::Resource::Build.new( client: client, name: name, number: manifest.build_version, vcs_revision: manifest.build_git_revision, build_agent: { name: "omnibus", version: Omnibus::VERSION, }, modules: [ { # com.getchef:chef-server:12.0.0 id: [ Config.artifactory_base_path.tr("/", "."), name, manifest.build_version, ].join(":"), artifacts: packages.map do |package| [ { type: File.extname(package.path).split(".").last, sha1: package.metadata[:sha1], md5: package.metadata[:md5], name: package.metadata[:basename], }, { type: File.extname(package.metadata.path).split(".").last, sha1: digest(package.metadata.path, :sha1), md5: digest(package.metadata.path, :md5), name: File.basename(package.metadata.path), }, ] end.flatten, }, ] ) end # # Indicates if an Artifactory build record should be created for the # published set of packages. # # @return [Boolean] # def build_record? # We want to create a build record by default if @options[:build_record].nil? true else @options[:build_record] end end # # The Artifactory client object to communicate with the Artifactory API. # # @return [Artifactory::Client] # def client @client ||= Artifactory::Client.new( endpoint: Config.artifactory_endpoint, username: Config.artifactory_username, password: Config.artifactory_password, ssl_pem_file: Config.artifactory_ssl_pem_file, ssl_verify: Config.artifactory_ssl_verify, proxy_username: Config.artifactory_proxy_username, proxy_password: Config.artifactory_proxy_password, proxy_address: Config.artifactory_proxy_address, proxy_port: Config.artifactory_proxy_port ) end # # The metadata for this package. # # @param [Package] package # the package to generate the metadata for # # @return [Hash] # def metadata_properties_for(package) metadata = { "omnibus.project" => package.metadata[:name], "omnibus.platform" => package.metadata[:platform], "omnibus.platform_version" => package.metadata[:platform_version], "omnibus.architecture" => package.metadata[:arch], "omnibus.version" => package.metadata[:version], "omnibus.iteration" => package.metadata[:iteration], "omnibus.license" => package.metadata[:license], "omnibus.md5" => package.metadata[:md5], "omnibus.sha1" => package.metadata[:sha1], "omnibus.sha256" => package.metadata[:sha256], "omnibus.sha512" => package.metadata[:sha512], "md5" => package.metadata[:md5], "sha1" => package.metadata[:sha1], "sha256" => package.metadata[:sha256], "sha512" => package.metadata[:sha512], }.tap do |h| if build_record? h["build.name"] = package.metadata[:name] h["build.number"] = package.metadata[:version] end end metadata end # # Properties to attach to published artifacts (and build record). # # @return [Hash] # def default_properties @properties ||= @options[:properties] || {} end # # The name of the Artifactory repository (as supplied as an option). # # @return [String] # def repository @options[:repository] end # # The path where the package will live inside of the Artifactory repository. # This is dynamically computed from the values in the project definition # and the package metadata. # # @example # com/getchef/chef/11.6.0/ubuntu/14.04/chef-11.6.0-1.el6.x86_64.rpm # # @param [Package] package # the package to generate the remote path for # # @return [String] # def remote_path_for(package) File.join( Config.artifactory_base_path, Config.artifactory_publish_pattern % package.metadata ) end end end