lib/artifactory/resources/build.rb
# # Copyright 2015 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 'time' module Artifactory class Resource::Build < Resource::Base BUILD_SCHEMA_VERSION = '1.0.1'.freeze # valid build types as dictated by the Artifactory API BUILD_TYPES = %w( ANT IVY MAVEN GENERIC GRADLE ) class << self # # Search for all builds in the system. # # @param [String] name # the name of the build component # @param [Hash] options # the list of options # # @option options [Artifactory::Client] :client # the client object to make the request with # # @return [Array<Resource::Build>] # the list of builds # def all(name, options = {}) client = extract_client!(options) client.get("/api/build/#{url_safe(name)}")['buildsNumbers'].map do |build_number| # Remove the leading / from the `uri` value. Converts `/484` to `484`. number = build_number['uri'].slice(1..-1) find(name, number, client: client) end.compact.flatten rescue Error::HTTPError => e # Artifactory returns a 404 instead of an empty list when there are no # builds. Whoever decided that was a good idea clearly doesn't # understand the point of REST interfaces... raise unless e.code == 404 [] end # # Find (fetch) data for a particular build of a component # # @example Find data for a build of a component # Build.find('wicket', 25) #=> #<Build name: 'wicket' ...> # # @param [String] name # the name of the build component # @param [String] number # the number of the build # @param [Hash] options # the list of options # # @option options [Artifactory::Client] :client # the client object to make the request with # # @return [Resource::Build, nil] # an instance of the build that matches the given name/number # combination, or +nil+ if one does not exist # def find(name, number, options = {}) client = extract_client!(options) response = client.get("/api/build/#{url_safe(name)}/#{url_safe(number)}") from_hash(response['buildInfo'], client: client) rescue Error::HTTPError => e raise unless e.code == 404 nil end # # @see Artifactory::Resource::Base.from_hash # def from_hash(hash, options = {}) super.tap do |instance| instance.started = Time.parse(instance.started) rescue nil instance.duration_millis = instance.duration_millis.to_i end end end # Based on https://github.com/JFrogDev/build-info/blob/master/README.md#build-info-json-format attribute :properties, {} attribute :version, BUILD_SCHEMA_VERSION attribute :name, ->{ raise 'Build component missing!' } attribute :number, ->{ raise 'Build number missing!' } attribute :type, 'GENERIC' attribute :build_agent, {} attribute :agent, {} attribute :started, Time.now.utc.iso8601(3) attribute :duration_millis attribute :artifactory_principal attribute :url attribute :vcs_revision attribute :vcs_url attribute :license_control, {} attribute :build_retention, {} attribute :modules, [] attribute :governance # # Compare a build artifacts/dependencies/environment with an older # build to see what has changed (new artifacts added, old dependencies # deleted etc). # # @example List all properties for an artifact # build.diff(35) #=> { 'artifacts'=>{}, 'dependencies'=>{}, 'properties'=>{} } # # @param [String] previous_build_number # the number of the previous build to compare against # # @return [Hash<String, Hash>] # the list of properties # def diff(previous_build_number) endpoint = api_path + '?' "diff=#{url_safe(previous_build_number)}" client.get(endpoint, {}) end # # Move a build's artifacts to a new repository optionally moving or # copying the build's dependencies to the target repository # and setting properties on promoted artifacts. # # @example promote the build to 'omnibus-stable-local' # build.promote('omnibus-stable-local') # @example promote a build attaching some new properites # build.promote('omnibus-stable-local' # properties: { # 'promoted_by' => 'hipchat:schisamo@chef.io' # } # ) # # @param [String] target_repo # repository to move or copy the build's artifacts and/or dependencies # @param [Hash] options # the list of options to pass # # @option options [String] :status (default: 'promoted') # new build status (any string) # @option options [String] :comment (default: '') # an optional comment describing the reason for promotion # @option options [String] :user (default: +Artifactory.username+) # the user that invoked promotion # @option options [Boolean] :dry_run (default: +false+) # pretend to do the promotion # @option options [Boolean] :copy (default: +false+) # whether to copy instead of move # @option options [Boolean] :dependencies (default: +false+) # whether to move/copy the build's dependencies # @option options [Array] :scopes (default: []) # an array of dependency scopes to include when "dependencies" is true # @option options [Hash<String, Array<String>>] :properties (default: []) # a list of properties to attach to the build's artifacts # @option options [Boolean] :fail_fast (default: +true+) # fail and abort the operation upon receiving an error # # @return [Hash] # the parsed JSON response from the server # def promote(target_repo, options = {}) request_body = {}.tap do |body| body[:status] = options[:status] || 'promoted' body[:comment] = options[:comment] || '' body[:ciUser] = options[:user] || Artifactory.username body[:dryRun] = options[:dry_run] || false body[:targetRepo] = target_repo body[:copy] = options[:copy] || false body[:artifacts] = true # always move/copy the build's artifacts body[:dependencies] = options[:dependencies] || false body[:scopes] = options[:scopes] || [] body[:properties] = options[:properties] || {} body[:failFast] = options[:fail_fast] || true end endpoint = "/api/build/promote/#{url_safe(name)}/#{url_safe(number)}" client.post(endpoint, JSON.fast_generate(request_body), 'Content-Type' => 'application/json' ) end # # Creates data about a build. # # @return [Boolean] # def save raise Error::InvalidBuildType.new(type) unless BUILD_TYPES.include?(type) file = Tempfile.new("build.json") file.write(to_json) file.rewind client.put('/api/build', file, 'Content-Type' => 'application/json' ) true ensure if file file.close file.unlink end end private # # The path to this build on the server. # # @return [String] # def api_path "/api/build/#{url_safe(name)}/#{url_safe(number)}" end end end