artifactory 2.2.1 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eb987fd18b2034340daf17e7f5d6d06b7289966f
4
- data.tar.gz: 5315895f5ab0badfee1351eecfd7d80a701d509c
3
+ metadata.gz: f36b33be0b821d20e678a3f104b7fd24a41ebd93
4
+ data.tar.gz: 144d1a14596dd383816dbb47158249a578cf31af
5
5
  SHA512:
6
- metadata.gz: 56040d7854457cbd0d86712e1ad7e096e539409012ea8e904fe4eda9b8d18ee7f4ad4d31f5f45ec12636c7f8f9c734b40fcbf9e45555d50660b9689db801f993
7
- data.tar.gz: 93bdb1b294a58cb73d6bfbcb118090828b25e1dd7ee0cf5e92a00850b6850f16bdf22248a900a62a6338e03b5e3425da78500b047a60157b61d1b106d4fa2402
6
+ metadata.gz: d85c333c34fa8fcb273ccf4a8fee6598f8e748c310c6f8de1b75c57c793994e5046ff98d37983ecd771a311d81c6c65e92df25b493a82e273a20e237cf7b1720
7
+ data.tar.gz: 9ea0a6be40e91885a8a1ea63b62a4fa2032c4e751f8536b325e60e7eea783464e2930fdd5821e37110de890952fc685f659ea13b800827dae4d2360ad2bc8cf0
@@ -3,6 +3,10 @@ Artifactory Client CHANGELOG
3
3
  This file is used to document the changes between releases of the Artifactory
4
4
  Ruby client.
5
5
 
6
+ v2.3.0 (08-04-2015)
7
+ -------------------
8
+ - Support for Build endpoints
9
+
6
10
  v2.2.1 (12-16-2014)
7
11
  -------------------
8
12
  - provide data to post in `Artifact#copy_or_move`
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  Artifactory Client
2
2
  ==================
3
- [![Build Status](https://secure.travis-ci.org/opscode/artifactory-client.png?branch=master)](http://travis-ci.org/opscode/artifactory-client)
3
+ [![Build Status](https://secure.travis-ci.org/chef/artifactory-client.png?branch=master)](http://travis-ci.org/chef/artifactory-client)
4
4
 
5
5
  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.
6
6
 
@@ -121,8 +121,36 @@ artifact.delete #=> true
121
121
 
122
122
  #### Builds
123
123
  ```ruby
124
- # Show all builds
125
- Build.all #=> [#<Build ...>]
124
+ # Show all components
125
+ BuildComponent.all #=> [#<BuildComponent ...>]
126
+
127
+ # Show all builds for a components
128
+ Build.all('wicket') #=> [#<Build ...>]
129
+
130
+ # Find a build component by name
131
+ component = BuildComponent.find('wicket')
132
+
133
+ # Delete some builds for a component
134
+ component.delete(build_numbers: %w( 51 52)) #=> true
135
+
136
+ # Delete all builds for a component
137
+ component.delete(delete_all: true) #=> true
138
+
139
+ # Delete a component and all of its associated data (including artifacts)
140
+ component.delete(artifacts: true, delete_all: true) #=> true
141
+
142
+ # Get a list of all buld records for a component
143
+ component.builds #=> #=> [#<Artifactory::Resource::Build ...>, ...]
144
+
145
+ # Create a new build record
146
+ build = Build.new(name: 'fricket', number: '51', properties: {...}, modules: [...])
147
+ build.save
148
+
149
+ # Find a build
150
+ build = Build.find('wicket', '51')
151
+
152
+ # Promote a build
153
+ build.promote('libs-release-local', status: 'staged', comment: 'Tested on all target platforms.')
126
154
  ```
127
155
 
128
156
  #### Plugins
data/Rakefile CHANGED
@@ -1,8 +1,12 @@
1
1
  require 'bundler/gem_tasks'
2
2
 
3
3
  require 'rspec/core/rake_task'
4
- RSpec::Core::RakeTask.new(:integration)
5
- RSpec::Core::RakeTask.new(:unit)
4
+ RSpec::Core::RakeTask.new(:integration) do |t|
5
+ t.rspec_opts = "--tag integration"
6
+ end
7
+ RSpec::Core::RakeTask.new(:unit) do |t|
8
+ t.rspec_opts = "--tag ~integration"
9
+ end
6
10
 
7
11
  namespace :travis do
8
12
  desc 'Run tests on Travis'
@@ -27,6 +27,7 @@ module Artifactory
27
27
  module Collection
28
28
  autoload :Artifact, 'artifactory/collections/artifact'
29
29
  autoload :Base, 'artifactory/collections/base'
30
+ autoload :Build, 'artifactory/collections/build'
30
31
  end
31
32
 
32
33
  module Resource
@@ -34,6 +35,7 @@ module Artifactory
34
35
  autoload :Backup, 'artifactory/resources/backup'
35
36
  autoload :Base, 'artifactory/resources/base'
36
37
  autoload :Build, 'artifactory/resources/build'
38
+ autoload :BuildComponent, 'artifactory/resources/build_component'
37
39
  autoload :Group, 'artifactory/resources/group'
38
40
  autoload :Layout, 'artifactory/resources/layout'
39
41
  autoload :LDAPSetting, 'artifactory/resources/ldap_setting'
@@ -0,0 +1,28 @@
1
+ #
2
+ # Copyright 2015 Chef Software, Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ module Artifactory
18
+ class Collection::Build < Collection::Base
19
+ #
20
+ # Create a new build collection.
21
+ #
22
+ # @param (see Collection::Base#initialize)
23
+ #
24
+ def initialize(parent, options = {}, &block)
25
+ super(Resource::Build, parent, options, &block)
26
+ end
27
+ end
28
+ end
@@ -40,5 +40,19 @@ module Artifactory
40
40
  "running an that your authentication information is correct."
41
41
  end
42
42
  end
43
+
44
+ # A general connection error with a more informative message
45
+ class InvalidBuildType < ArtifactoryError
46
+ def initialize(given_type)
47
+ super <<-EOH
48
+ '#{given_type}' is not a valid build type.
49
+
50
+ Valid build types include:
51
+
52
+ #{Resource::Build::BUILD_TYPES.join("\n ")}"
53
+
54
+ EOH
55
+ end
56
+ end
43
57
  end
44
58
  end
@@ -663,18 +663,7 @@ module Artifactory
663
663
  param[:dry] = 1 if options[:dry_run]
664
664
  end
665
665
 
666
- # Okay, seriously, WTF Artifactory? Are you fucking serious? You want me
667
- # to make a POST request, but you don't actually read the contents of the
668
- # POST request, you read the URL-params. Sigh, whoever claimed this was a
669
- # RESTful API should seriously consider a new occupation.
670
- params = params.map do |k, v|
671
- key = URI.escape(k.to_s)
672
- value = URI.escape(v.to_s)
673
-
674
- "#{key}=#{value}"
675
- end
676
-
677
- endpoint = File.join('/api', action.to_s, relative_path) + '?' + params.join('&')
666
+ endpoint = File.join('/api', action.to_s, relative_path) + "?#{to_query_string_parameters(params)}"
678
667
 
679
668
  client.post(endpoint, {})
680
669
  end
@@ -297,7 +297,7 @@ module Artifactory
297
297
  def to_hash
298
298
  attributes.inject({}) do |hash, (key, value)|
299
299
  unless Resource::Base.has_attribute?(key)
300
- hash[Util.camelize(key, true)] = value
300
+ hash[Util.camelize(key, true)] = send(key.to_sym)
301
301
  end
302
302
 
303
303
  hash
@@ -316,7 +316,7 @@ module Artifactory
316
316
  end
317
317
 
318
318
  #
319
- # Create URI-escaped string from matrix properties
319
+ # Create CGI-escaped string from matrix properties
320
320
  #
321
321
  # @see http://bit.ly/1qeVYQl
322
322
  #
@@ -335,6 +335,26 @@ module Artifactory
335
335
  end
336
336
  end
337
337
 
338
+ #
339
+ # Create URI-escaped querystring parameters
340
+ #
341
+ # @see http://bit.ly/1qeVYQl
342
+ #
343
+ def to_query_string_parameters(hash = {})
344
+ properties = hash.map do |k, v|
345
+ key = URI.escape(k.to_s)
346
+ value = URI.escape(v.to_s)
347
+
348
+ "#{key}=#{value}"
349
+ end
350
+
351
+ if properties.empty?
352
+ nil
353
+ else
354
+ properties.join('&')
355
+ end
356
+ end
357
+
338
358
  # @private
339
359
  def to_s
340
360
  "#<#{short_classname}>"
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright 2014 Chef Software, Inc.
2
+ # Copyright 2015 Chef Software, Inc.
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
5
5
  # you may not use this file except in compliance with the License.
@@ -14,12 +14,20 @@
14
14
  # limitations under the License.
15
15
  #
16
16
 
17
+ require 'time'
18
+
17
19
  module Artifactory
18
20
  class Resource::Build < Resource::Base
21
+ BUILD_SCHEMA_VERSION = '1.0.1'.freeze
22
+ # valid build types as dictated by the Artifactory API
23
+ BUILD_TYPES = %w( ANT IVY MAVEN GENERIC GRADLE )
24
+
19
25
  class << self
20
26
  #
21
27
  # Search for all builds in the system.
22
28
  #
29
+ # @param [String] name
30
+ # the name of the build component
23
31
  # @param [Hash] options
24
32
  # the list of options
25
33
  #
@@ -29,9 +37,13 @@ module Artifactory
29
37
  # @return [Array<Resource::Build>]
30
38
  # the list of builds
31
39
  #
32
- def all(options = {})
40
+ def all(name, options = {})
33
41
  client = extract_client!(options)
34
- client.get('/api/build')
42
+ client.get("/api/build/#{url_safe(name)}")['buildsNumbers'].map do |build_number|
43
+ # Remove the leading / from the `uri` value. Converts `/484` to `484`.
44
+ number = build_number['uri'].slice(1..-1)
45
+ find(name, number, client: client)
46
+ end.compact.flatten
35
47
  rescue Error::HTTPError => e
36
48
  # Artifactory returns a 404 instead of an empty list when there are no
37
49
  # builds. Whoever decided that was a good idea clearly doesn't
@@ -39,6 +51,179 @@ module Artifactory
39
51
  raise unless e.code == 404
40
52
  []
41
53
  end
54
+
55
+ #
56
+ # Find (fetch) data for a particular build of a component
57
+ #
58
+ # @example Find data for a build of a component
59
+ # Build.find('wicket', 25) #=> #<Build name: 'wicket' ...>
60
+ #
61
+ # @param [String] name
62
+ # the name of the build component
63
+ # @param [String] number
64
+ # the number of the build
65
+ # @param [Hash] options
66
+ # the list of options
67
+ #
68
+ # @option options [Artifactory::Client] :client
69
+ # the client object to make the request with
70
+ #
71
+ # @return [Resource::Build, nil]
72
+ # an instance of the build that matches the given name/number
73
+ # combination, or +nil+ if one does not exist
74
+ #
75
+ def find(name, number, options = {})
76
+ client = extract_client!(options)
77
+ response = client.get("/api/build/#{url_safe(name)}/#{url_safe(number)}")
78
+ from_hash(response['buildInfo'], client: client)
79
+ rescue Error::HTTPError => e
80
+ raise unless e.code == 404
81
+ nil
82
+ end
83
+
84
+ #
85
+ # @see Artifactory::Resource::Base.from_hash
86
+ #
87
+ def from_hash(hash, options = {})
88
+ super.tap do |instance|
89
+ instance.started = Time.parse(instance.started) rescue nil
90
+ instance.duration_millis = instance.duration_millis.to_i
91
+ end
92
+ end
93
+ end
94
+
95
+ # Based on https://github.com/JFrogDev/build-info/blob/master/README.md#build-info-json-format
96
+ attribute :properties, {}
97
+ attribute :version, BUILD_SCHEMA_VERSION
98
+ attribute :name, ->{ raise 'Build component missing!' }
99
+ attribute :number, ->{ raise 'Build number missing!' }
100
+ attribute :type, 'GENERIC'
101
+ attribute :build_agent, {}
102
+ attribute :agent, {}
103
+ attribute :started, Time.now.utc.iso8601(3)
104
+ attribute :duration_millis
105
+ attribute :artifactory_principal
106
+ attribute :url
107
+ attribute :vcs_revision
108
+ attribute :vcs_url
109
+ attribute :license_control, {}
110
+ attribute :build_retention, {}
111
+ attribute :modules, []
112
+ attribute :governance
113
+
114
+ #
115
+ # Compare a build artifacts/dependencies/environment with an older
116
+ # build to see what has changed (new artifacts added, old dependencies
117
+ # deleted etc).
118
+ #
119
+ # @example List all properties for an artifact
120
+ # build.diff(35) #=> { 'artifacts'=>{}, 'dependencies'=>{}, 'properties'=>{} }
121
+ #
122
+ # @param [String] previous_build_number
123
+ # the number of the previous build to compare against
124
+ #
125
+ # @return [Hash<String, Hash>]
126
+ # the list of properties
127
+ #
128
+ def diff(previous_build_number)
129
+ endpoint = api_path + '?' "diff=#{url_safe(previous_build_number)}"
130
+ client.get(endpoint, {})
131
+ end
132
+
133
+ #
134
+ # Move a build's artifacts to a new repository optionally moving or
135
+ # copying the build's dependencies to the target repository
136
+ # and setting properties on promoted artifacts.
137
+ #
138
+ # @example promote the build to 'omnibus-stable-local'
139
+ # build.promote('omnibus-stable-local')
140
+ # @example promote a build attaching some new properites
141
+ # build.promote('omnibus-stable-local'
142
+ # properties: {
143
+ # 'promoted_by' => 'hipchat:schisamo@chef.io'
144
+ # }
145
+ # )
146
+ #
147
+ # @param [String] target_repo
148
+ # repository to move or copy the build's artifacts and/or dependencies
149
+ # @param [Hash] options
150
+ # the list of options to pass
151
+ #
152
+ # @option options [String] :status (default: 'promoted')
153
+ # new build status (any string)
154
+ # @option options [String] :comment (default: '')
155
+ # an optional comment describing the reason for promotion
156
+ # @option options [String] :user (default: +Artifactory.username+)
157
+ # the user that invoked promotion
158
+ # @option options [Boolean] :dry_run (default: +false+)
159
+ # pretend to do the promotion
160
+ # @option options [Boolean] :copy (default: +false+)
161
+ # whether to copy instead of move
162
+ # @option options [Boolean] :dependencies (default: +false+)
163
+ # whether to move/copy the build's dependencies
164
+ # @option options [Array] :scopes (default: [])
165
+ # an array of dependency scopes to include when "dependencies" is true
166
+ # @option options [Hash<String, Array<String>>] :properties (default: [])
167
+ # a list of properties to attach to the build's artifacts
168
+ # @option options [Boolean] :fail_fast (default: +true+)
169
+ # fail and abort the operation upon receiving an error
170
+ #
171
+ # @return [Hash]
172
+ # the parsed JSON response from the server
173
+ #
174
+ def promote(target_repo, options = {})
175
+ request_body = {}.tap do |body|
176
+ body[:status] = options[:status] || 'promoted'
177
+ body[:comment] = options[:comment] || ''
178
+ body[:ciUser] = options[:user] || Artifactory.username
179
+ body[:dryRun] = options[:dry_run] || false
180
+ body[:targetRepo] = target_repo
181
+ body[:copy] = options[:copy] || false
182
+ body[:artifacts] = true # always move/copy the build's artifacts
183
+ body[:dependencies] = options[:dependencies] || false
184
+ body[:scopes] = options[:scopes] || []
185
+ body[:properties] = options[:properties] || {}
186
+ body[:failFast] = options[:fail_fast] || true
187
+ end
188
+
189
+ endpoint = "/api/build/promote/#{url_safe(name)}/#{url_safe(number)}"
190
+ client.post(endpoint, JSON.fast_generate(request_body),
191
+ 'Content-Type' => 'application/json'
192
+ )
193
+ end
194
+
195
+ #
196
+ # Creates data about a build.
197
+ #
198
+ # @return [Boolean]
199
+ #
200
+ def save
201
+ raise Error::InvalidBuildType.new(type) unless BUILD_TYPES.include?(type)
202
+
203
+ file = Tempfile.new("build.json")
204
+ file.write(to_json)
205
+ file.rewind
206
+
207
+ client.put('/api/build', file,
208
+ 'Content-Type' => 'application/json'
209
+ )
210
+ true
211
+ ensure
212
+ if file
213
+ file.close
214
+ file.unlink
215
+ end
216
+ end
217
+
218
+ private
219
+
220
+ #
221
+ # The path to this build on the server.
222
+ #
223
+ # @return [String]
224
+ #
225
+ def api_path
226
+ "/api/build/#{url_safe(name)}/#{url_safe(number)}"
42
227
  end
43
228
  end
44
229
  end
@@ -0,0 +1,160 @@
1
+ #
2
+ # Copyright 2015 Chef Software, Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'time'
18
+
19
+ module Artifactory
20
+ class Resource::BuildComponent < Resource::Base
21
+ class << self
22
+ #
23
+ # Search for all compoenents for which build data exists.
24
+ #
25
+ # @param [Hash] options
26
+ # the list of options
27
+ #
28
+ # @option options [Artifactory::Client] :client
29
+ # the client object to make the request with
30
+ #
31
+ # @return [Array<Resource::BuildComponent>]
32
+ # the list of builds
33
+ #
34
+ def all(options = {})
35
+ client = extract_client!(options)
36
+ client.get('/api/build')['builds'].map do |component|
37
+ from_hash(component, client: client)
38
+ end.compact.flatten
39
+ rescue Error::HTTPError => e
40
+ # Artifactory returns a 404 instead of an empty list when there are no
41
+ # builds. Whoever decided that was a good idea clearly doesn't
42
+ # understand the point of REST interfaces...
43
+ raise unless e.code == 404
44
+ []
45
+ end
46
+
47
+ #
48
+ # Find (fetch) data for a particular build component
49
+ #
50
+ # @example Find a particular build component
51
+ # BuildComponent.find('wicket') #=> #<BuildComponent name: 'wicket' ...>
52
+ #
53
+ # @param [String] name
54
+ # the name of the build component
55
+ # @param [Hash] options
56
+ # the list of options
57
+ #
58
+ # @option options [Artifactory::Client] :client
59
+ # the client object to make the request with
60
+ #
61
+ # @return [Resource::BuildComponent, nil]
62
+ # an instance of the build component that matches the given name,
63
+ # or +nil+ if one does not exist
64
+ #
65
+ def find(name, options = {})
66
+ client = extract_client!(options)
67
+ all.find do |component|
68
+ component.name == name
69
+ end
70
+ end
71
+
72
+ #
73
+ # @see Artifactory::Resource::Base.from_hash
74
+ #
75
+ def from_hash(hash, options = {})
76
+ super.tap do |instance|
77
+ # Remove the leading / from the `uri` value. Converts `/foo` to `foo`.
78
+ instance.name = instance.uri.slice(1..-1)
79
+ instance.last_started = Time.parse(instance.last_started) rescue nil
80
+ end
81
+ end
82
+ end
83
+
84
+ attribute :uri
85
+ attribute :name, ->{ raise 'Name missing!' }
86
+ attribute :last_started
87
+
88
+ #
89
+ # The list of build data for this component.
90
+ #
91
+ # @example Get the list of artifacts for a repository
92
+ # component = BuildComponent.new(name: 'wicket')
93
+ # component.builds #=> [#<Resource::Build>, ...]
94
+ #
95
+ # @return [Collection::Build]
96
+ # the list of builds
97
+ #
98
+ def builds
99
+ @builds ||= Collection::Build.new(self, name: name) do
100
+ Resource::Build.all(name)
101
+ end
102
+ end
103
+
104
+ #
105
+ # Remove this component's build data stored in Artifactory
106
+ #
107
+ # @option options [Array<String>] :build_numbers (default: nil)
108
+ # an array of build numbers that should be deleted; if not given
109
+ # all builds (for this component) are deleted
110
+ # @option options [Boolean] :artifacts (default: +false+)
111
+ # if true the component's artifacts are also removed
112
+ # @option options [Boolean] :delete_all (default: +false+)
113
+ # if true the entire component is removed
114
+ #
115
+ # @return [Boolean]
116
+ # true if the object was deleted successfully, false otherwise
117
+ #
118
+ def delete(options = {})
119
+ params = {}.tap do |param|
120
+ param[:buildNumbers] = options[:build_numbers].join(',') if options[:build_numbers]
121
+ param[:artifacts] = 1 if options[:artifacts]
122
+ param[:deleteAll] = 1 if options[:delete_all]
123
+ end
124
+
125
+ endpoint = api_path + "?#{to_query_string_parameters(params)}"
126
+ client.delete(endpoint, {})
127
+ true
128
+ rescue Error::HTTPError => e
129
+ false
130
+ end
131
+
132
+ #
133
+ # Rename a build component.
134
+ #
135
+ # @param [String] new_name
136
+ # new name for the component
137
+ #
138
+ # @return [Boolean]
139
+ # true if the object was renamed successfully, false otherwise
140
+ #
141
+ def rename(new_name, options = {})
142
+ endpoint = "/api/build/rename/#{url_safe(name)}" + "?to=#{new_name}"
143
+ client.post(endpoint, {})
144
+ true
145
+ rescue Error::HTTPError => e
146
+ false
147
+ end
148
+
149
+ private
150
+
151
+ #
152
+ # The path to this build component on the server.
153
+ #
154
+ # @return [String]
155
+ #
156
+ def api_path
157
+ "/api/build/#{url_safe(name)}"
158
+ end
159
+ end
160
+ end