buildkit 1.6.1 → 1.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e0e8f1334155df0287e1c1ea8b99bd03234f10681f87bea60ddf471474952a9
4
- data.tar.gz: 0cc70aa70b57a0f6754460257dbc127f936d11f6a35e01bcaa8cd9b677223078
3
+ metadata.gz: 6e44ba52bf1e6ebeb791cc1848cd06853bc84422d5e3a4a667dc478d33159fe6
4
+ data.tar.gz: a5e41995469ed1617f67a34c4a534bfcbf33dc4754fe117cbbf3fca9bceb1ec3
5
5
  SHA512:
6
- metadata.gz: f48d7763085376ec590cb490ec8eca0c3b971aead37e3e9bc12e037627af4ff4122fdb242ebf20a3d4030c59f01ec7f4333078e8e33a4da097e628dccf91632e
7
- data.tar.gz: 2f738d7d0437b8f6c2d37a68a240dfde0e37ac912de2bda9e74d8dba9e75f113b1c5c61705dddccd9cf050871afe3c8d622fdb17bb14e3a08757d002990f575c
6
+ metadata.gz: eeac7f5ea7ed18cc5f1fb1b9f7a4b4479693430a2aeff9c0a77845082cf7fe82877a65b2d5154ec53ca2adc3ce448f6f2edc059755f7d372d6ca0d8e8048c45b
7
+ data.tar.gz: da54f50f06508673aa93a8af9f29e709a23e7c5697f2e29a2149d525d19b2b7b30b3ef57e8322063ae4eb9b88f31e5d1821ed5db4b3595c45f55fed714095895
data/README.md CHANGED
@@ -26,6 +26,8 @@ organization = client.organization('my-great-org')
26
26
  agents = organization.rels[:agents].get.data
27
27
  ```
28
28
 
29
+ Identifier arguments (`org`, `pipeline`, `build`, `job`, `id`) are interpolated into the request path, so each must be a single URL path segment: letters, digits, `-`, `_`, `.`, `~`. Values containing `/`, `?`, `%`, or that are `.`, `..`, or empty raise `Buildkit::InvalidRouteSegment` (an `ArgumentError`) before any request is made. Validate or reject user-supplied identifiers at your application boundary rather than rescuing this error.
30
+
29
31
  ## Development
30
32
 
31
33
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -4,6 +4,8 @@ module Buildkit
4
4
  class Client
5
5
  # Methods for the Agents API
6
6
  #
7
+ # Identifier arguments must be single URL path segments; see {Buildkit::InvalidRouteSegment}.
8
+ #
7
9
  # @see https://buildkite.com/docs/api/agents
8
10
  module Agents
9
11
  # List agents
@@ -13,7 +15,7 @@ module Buildkit
13
15
  # @example
14
16
  # Buildkit.agents('my-great-org')
15
17
  def agents(org, options = {})
16
- get("/v2/organizations/#{org}/agents", options)
18
+ get("/v2/organizations/#{route_segment(org, :org)}/agents", options)
17
19
  end
18
20
 
19
21
  # Get an agent
@@ -25,7 +27,7 @@ module Buildkit
25
27
  # @example
26
28
  # Buildkit.agent('my-great-org', '0b461f65-e7be-4c80-888a-ef11d81fd971')
27
29
  def agent(org, id, options = {})
28
- get("/v2/organizations/#{org}/agents/#{id}", options)
30
+ get("/v2/organizations/#{route_segment(org, :org)}/agents/#{route_segment(id, :id)}", options)
29
31
  end
30
32
 
31
33
  # Stop an agent
@@ -36,7 +38,7 @@ module Buildkit
36
38
  # @example Stop an agent
37
39
  # Buildkit.stop_agent('my-great-org', '16940c91-f12d-4122-8154-0edf6c0978c2')
38
40
  def stop_agent(org, id, options = {})
39
- put("/v2/organizations/#{org}/agents/#{id}/stop", options)
41
+ put("/v2/organizations/#{route_segment(org, :org)}/agents/#{route_segment(id, :id)}/stop", options)
40
42
  end
41
43
  end
42
44
  end
@@ -4,6 +4,8 @@ module Buildkit
4
4
  class Client
5
5
  # Methods for the Artifacts API
6
6
  #
7
+ # Identifier arguments must be single URL path segments; see {Buildkit::InvalidRouteSegment}.
8
+ #
7
9
  # @see https://buildkite.com/docs/api/artifacts
8
10
  module Artifacts
9
11
  # List all artifacts for a build
@@ -13,7 +15,8 @@ module Buildkit
13
15
  # @example
14
16
  # Buildkit.artifacts('my-great-org', 'great-pipeline', 42)
15
17
  def artifacts(org, pipeline, build, options = {})
16
- get("/v2/organizations/#{org}/pipelines/#{pipeline}/builds/#{build}/artifacts", options)
18
+ get("/v2/organizations/#{route_segment(org, :org)}/pipelines/#{route_segment(pipeline, :pipeline)}" \
19
+ "/builds/#{route_segment(build, :build)}/artifacts", options)
17
20
  end
18
21
 
19
22
  # List all artifacts for a job
@@ -23,7 +26,8 @@ module Buildkit
23
26
  # @example
24
27
  # Buildkit.job_artifacts('my-great-org', 'great-pipeline', 42, '76365070-34d5-4104-8b91-952780f8029f')
25
28
  def job_artifacts(org, pipeline, build, job, options = {})
26
- get("/v2/organizations/#{org}/pipelines/#{pipeline}/builds/#{build}/jobs/#{job}/artifacts", options)
29
+ get("/v2/organizations/#{route_segment(org, :org)}/pipelines/#{route_segment(pipeline, :pipeline)}" \
30
+ "/builds/#{route_segment(build, :build)}/jobs/#{route_segment(job, :job)}/artifacts", options)
27
31
  end
28
32
  end
29
33
  end
@@ -4,6 +4,8 @@ module Buildkit
4
4
  class Client
5
5
  # Methods for the Builds API
6
6
  #
7
+ # Identifier arguments must be single URL path segments; see {Buildkit::InvalidRouteSegment}.
8
+ #
7
9
  # @see https://buildkite.com/docs/api/builds
8
10
  module Builds
9
11
  # List all builds
@@ -24,7 +26,7 @@ module Buildkit
24
26
  # @example
25
27
  # Buildkit.organization_builds('my-great-org'))
26
28
  def organization_builds(org, options = {})
27
- get("/v2/organizations/#{org}/builds", options)
29
+ get("/v2/organizations/#{route_segment(org, :org)}/builds", options)
28
30
  end
29
31
 
30
32
  # List builds for a pipeline
@@ -36,7 +38,7 @@ module Buildkit
36
38
  # @example
37
39
  # Buildkit.pipeline_builds('my-great-org', 'great-pipeline')
38
40
  def pipeline_builds(org, pipeline, options = {})
39
- get("/v2/organizations/#{org}/pipelines/#{pipeline}/builds", options)
41
+ get(builds_path(org, pipeline), options)
40
42
  end
41
43
 
42
44
  # Get a build
@@ -49,7 +51,7 @@ module Buildkit
49
51
  # @example
50
52
  # Buildkit.build('my-great-org', 'great-pipeline', 42)
51
53
  def build(org, pipeline, number, options = {})
52
- get("/v2/organizations/#{org}/pipelines/#{pipeline}/builds/#{number}", options)
54
+ get("#{builds_path(org, pipeline)}/#{route_segment(number, :number)}", options)
53
55
  end
54
56
 
55
57
  # Rebuild a build
@@ -61,7 +63,7 @@ module Buildkit
61
63
  # @example
62
64
  # Buildkit.rebuild('my-great-org', 'great-pipeline', 42)
63
65
  def rebuild(org, pipeline, number, options = {})
64
- put("/v2/organizations/#{org}/pipelines/#{pipeline}/builds/#{number}/rebuild", options)
66
+ put("#{builds_path(org, pipeline)}/#{route_segment(number, :number)}/rebuild", options)
65
67
  end
66
68
 
67
69
  # Create a build
@@ -81,7 +83,7 @@ module Buildkit
81
83
  # })
82
84
  #
83
85
  def create_build(org, pipeline, options = {})
84
- post("/v2/organizations/#{org}/pipelines/#{pipeline}/builds", options)
86
+ post(builds_path(org, pipeline), options)
85
87
  end
86
88
 
87
89
  # Cancel a build
@@ -93,7 +95,13 @@ module Buildkit
93
95
  # @example
94
96
  # Buildkit.cancel_build('my-great-org', 'great-pipeline', 42)
95
97
  def cancel_build(org, pipeline, number, options = {})
96
- put("/v2/organizations/#{org}/pipelines/#{pipeline}/builds/#{number}/cancel", options)
98
+ put("#{builds_path(org, pipeline)}/#{route_segment(number, :number)}/cancel", options)
99
+ end
100
+
101
+ private
102
+
103
+ def builds_path(org, pipeline)
104
+ "/v2/organizations/#{route_segment(org, :org)}/pipelines/#{route_segment(pipeline, :pipeline)}/builds"
97
105
  end
98
106
  end
99
107
  end
@@ -4,6 +4,8 @@ module Buildkit
4
4
  class Client
5
5
  # Methods for the Jobs API
6
6
  #
7
+ # Identifier arguments must be single URL path segments; see {Buildkit::InvalidRouteSegment}.
8
+ #
7
9
  # @see https://buildkite.com/docs/rest-api/jobs
8
10
  module Jobs
9
11
  # Retry a job
@@ -17,7 +19,7 @@ module Buildkit
17
19
  # @example
18
20
  # Buildkit.retry_job('my-great-org', 'great-pipeline', 123, 'my-job-id')
19
21
  def retry_job(org, pipeline, build, job, options = {})
20
- put("/v2/organizations/#{org}/pipelines/#{pipeline}/builds/#{build}/jobs/#{job}/retry", options)
22
+ put("#{job_path(org, pipeline, build, job)}/retry", options)
21
23
  end
22
24
 
23
25
  # Get a job's environment variables
@@ -31,7 +33,7 @@ module Buildkit
31
33
  # @example
32
34
  # Buildkit.job_env('my-great-org', 'great-pipeline', 123, 'my-job-id')
33
35
  def job_env(org, pipeline, build, job, options = {})
34
- get("/v2/organizations/#{org}/pipelines/#{pipeline}/builds/#{build}/jobs/#{job}/env", options)
36
+ get("#{job_path(org, pipeline, build, job)}/env", options)
35
37
  end
36
38
 
37
39
  # Get a job's log output
@@ -45,7 +47,7 @@ module Buildkit
45
47
  # @example
46
48
  # Buildkit.job_log('my-great-org', 'great-pipeline', 123, 'my-job-id')
47
49
  def job_log(org, pipeline, build, job, options = {})
48
- get("/v2/organizations/#{org}/pipelines/#{pipeline}/builds/#{build}/jobs/#{job}/log", options)
50
+ get("#{job_path(org, pipeline, build, job)}/log", options)
49
51
  end
50
52
 
51
53
  # Unblock a job
@@ -65,7 +67,14 @@ module Buildkit
65
67
  # }
66
68
  # })
67
69
  def unblock(org, pipeline, build, job, options = {})
68
- put("/v2/organizations/#{org}/pipelines/#{pipeline}/builds/#{build}/jobs/#{job}/unblock", options)
70
+ put("#{job_path(org, pipeline, build, job)}/unblock", options)
71
+ end
72
+
73
+ private
74
+
75
+ def job_path(org, pipeline, build, job)
76
+ "/v2/organizations/#{route_segment(org, :org)}/pipelines/#{route_segment(pipeline, :pipeline)}" \
77
+ "/builds/#{route_segment(build, :build)}/jobs/#{route_segment(job, :job)}"
69
78
  end
70
79
  end
71
80
  end
@@ -4,6 +4,8 @@ module Buildkit
4
4
  class Client
5
5
  # Methods for the Organizations API
6
6
  #
7
+ # Identifier arguments must be single URL path segments; see {Buildkit::InvalidRouteSegment}.
8
+ #
7
9
  # @see https://buildkite.com/docs/api/organizations
8
10
  module Organizations
9
11
  # List organizations
@@ -24,7 +26,7 @@ module Buildkit
24
26
  # @example
25
27
  # Buildkit.organization('my-great-org')
26
28
  def organization(org, options = {})
27
- get("/v2/organizations/#{org}", options)
29
+ get("/v2/organizations/#{route_segment(org, :org)}", options)
28
30
  end
29
31
  end
30
32
  end
@@ -4,6 +4,8 @@ module Buildkit
4
4
  class Client
5
5
  # Methods for the pipelines API
6
6
  #
7
+ # Identifier arguments must be single URL path segments; see {Buildkit::InvalidRouteSegment}.
8
+ #
7
9
  # @see https://buildkite.com/docs/api/pipelines
8
10
  module Pipelines
9
11
  # List pipelines
@@ -13,7 +15,7 @@ module Buildkit
13
15
  # @example
14
16
  # Buildkit.pipelines('my-great-org')
15
17
  def pipelines(org, options = {})
16
- get("/v2/organizations/#{org}/pipelines", options)
18
+ get(pipelines_path(org), options)
17
19
  end
18
20
 
19
21
  # Get a pipeline
@@ -25,7 +27,7 @@ module Buildkit
25
27
  # @example
26
28
  # Buildkit.pipeline('my-great-org', 'great-pipeline')
27
29
  def pipeline(org, pipeline, options = {})
28
- get("/v2/organizations/#{org}/pipelines/#{pipeline}", options)
30
+ get("#{pipelines_path(org)}/#{route_segment(pipeline, :pipeline)}", options)
29
31
  end
30
32
 
31
33
  # Create a pipeline
@@ -48,7 +50,7 @@ module Buildkit
48
50
  # })
49
51
  #
50
52
  def create_pipeline(org, options = {})
51
- post("/v2/organizations/#{org}/pipelines", options)
53
+ post(pipelines_path(org), options)
52
54
  end
53
55
 
54
56
  # Update a pipeline
@@ -63,7 +65,7 @@ module Buildkit
63
65
  # })
64
66
  #
65
67
  def update_pipeline(org, pipeline, options = {})
66
- patch("/v2/organizations/#{org}/pipelines/#{pipeline}", options)
68
+ patch("#{pipelines_path(org)}/#{route_segment(pipeline, :pipeline)}", options)
67
69
  end
68
70
 
69
71
  # Archive a pipeline
@@ -76,7 +78,7 @@ module Buildkit
76
78
  # Buildkit.archive_pipeline('my-great-org', 'great-pipeline')
77
79
  #
78
80
  def archive_pipeline(org, pipeline)
79
- post("/v2/organizations/#{org}/pipelines/#{pipeline}/archive")
81
+ post("#{pipelines_path(org)}/#{route_segment(pipeline, :pipeline)}/archive")
80
82
  end
81
83
 
82
84
  # Unarchive a pipeline
@@ -89,7 +91,7 @@ module Buildkit
89
91
  # Buildkit.unarchive_pipeline('my-great-org', 'great-pipeline')
90
92
  #
91
93
  def unarchive_pipeline(org, pipeline)
92
- post("/v2/organizations/#{org}/pipelines/#{pipeline}/unarchive")
94
+ post("#{pipelines_path(org)}/#{route_segment(pipeline, :pipeline)}/unarchive")
93
95
  end
94
96
 
95
97
  # Delete a pipeline
@@ -101,7 +103,13 @@ module Buildkit
101
103
  # Buildkit.delete_pipeline('my-great-org', 'great-pipeline')
102
104
  #
103
105
  def delete_pipeline(org, pipeline)
104
- delete("/v2/organizations/#{org}/pipelines/#{pipeline}")
106
+ delete("#{pipelines_path(org)}/#{route_segment(pipeline, :pipeline)}")
107
+ end
108
+
109
+ private
110
+
111
+ def pipelines_path(org)
112
+ "/v2/organizations/#{route_segment(org, :org)}/pipelines"
105
113
  end
106
114
  end
107
115
  end
@@ -25,6 +25,11 @@ module Buildkit
25
25
  # Header keys that can be passed in options hash to {#get},{#head}
26
26
  CONVENIENCE_HEADERS = Set.new(%i[accept content_type])
27
27
 
28
+ # Characters permitted in a route identifier: RFC 3986 unreserved set.
29
+ # Buildkite identifiers are slugs ([a-z0-9-]), UUIDs, or integers, so this is
30
+ # not restrictive in practice; it rejects every path/query delimiter and `%`.
31
+ ROUTE_SEGMENT = /\A[A-Za-z0-9\-._~]+\z/.freeze
32
+
28
33
  # In Faraday 0.9, Faraday::Builder was renamed to Faraday::RackBuilder
29
34
  RACK_BUILDER_CLASS = defined?(Faraday::RackBuilder) ? Faraday::RackBuilder : Faraday::Builder
30
35
 
@@ -118,6 +123,25 @@ module Buildkit
118
123
 
119
124
  private
120
125
 
126
+ # Validate that a caller-supplied identifier is exactly one URL path segment.
127
+ #
128
+ # Named helpers interpolate identifiers between fixed route parts; a value
129
+ # containing `/`, `?` or a dot-segment would otherwise re-route the request
130
+ # to a different Buildkite action.
131
+ #
132
+ # @param value [String, Integer]
133
+ # @param name [Symbol] parameter name, for the error message
134
+ # @return [String]
135
+ # @raise [Buildkit::InvalidRouteSegment]
136
+ def route_segment(value, name)
137
+ segment = value.to_s
138
+ if segment.match?(ROUTE_SEGMENT) && segment != '.' && segment != '..'
139
+ segment
140
+ else
141
+ raise InvalidRouteSegment, "#{name} must be a single URL path segment, got #{value.inspect}"
142
+ end
143
+ end
144
+
121
145
  def request(method, path, data, options = {})
122
146
  if data.is_a?(Hash)
123
147
  options = extract_query_and_headers_from data
@@ -166,4 +166,7 @@ module Buildkit
166
166
 
167
167
  # Raised when client fails to provide valid Content-Type
168
168
  class MissingContentType < ArgumentError; end
169
+
170
+ # Raised when a route identifier (org, pipeline, build, job, agent id) is not a single URL path segment
171
+ class InvalidRouteSegment < ArgumentError; end
169
172
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Buildkit
4
- VERSION = '1.6.1'
4
+ VERSION = '1.6.2'
5
5
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: buildkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.1
4
+ version: 1.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Boussier
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-09-20 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: sawyer
@@ -38,7 +37,6 @@ dependencies:
38
37
  - - ">="
39
38
  - !ruby/object:Gem::Version
40
39
  version: '0'
41
- description:
42
40
  email:
43
41
  - jean.boussier@shopify.com
44
42
  executables: []
@@ -78,7 +76,6 @@ licenses:
78
76
  - MIT
79
77
  metadata:
80
78
  allowed_push_host: https://rubygems.org
81
- post_install_message:
82
79
  rdoc_options: []
83
80
  require_paths:
84
81
  - lib
@@ -93,8 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
93
90
  - !ruby/object:Gem::Version
94
91
  version: '0'
95
92
  requirements: []
96
- rubygems_version: 3.5.18
97
- signing_key:
93
+ rubygems_version: 4.0.21
98
94
  specification_version: 4
99
95
  summary: Ruby toolkit for working with the Buildkite API
100
96
  test_files: []