cloudflare-artifacts 0.1.0
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 +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +185 -0
- data/Rakefile +12 -0
- data/lib/cloudflare/artifacts/client.rb +101 -0
- data/lib/cloudflare/artifacts/configuration.rb +100 -0
- data/lib/cloudflare/artifacts/error.rb +54 -0
- data/lib/cloudflare/artifacts/repos.rb +39 -0
- data/lib/cloudflare/artifacts/response.rb +31 -0
- data/lib/cloudflare/artifacts/tokens.rb +19 -0
- data/lib/cloudflare/artifacts/version.rb +7 -0
- data/lib/cloudflare/artifacts.rb +32 -0
- metadata +71 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e001f93d20a3cf60111cea996305c7bc401684ef024cac193dd695023ee2857a
|
|
4
|
+
data.tar.gz: b3c0ee6a77510a5f9235996419bfe30bdd0feee632ccbc71c5a000c6d72f8b44
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 603c1d9c7b1b4f192baa60ba3e48fe2b1cd13f8b8395c09f6388af271f90467c5e92be9a8c0a77b19d7a978c1665aa17c7b9143bb64808921252035a47422765
|
|
7
|
+
data.tar.gz: 8693ccd430f7db54de68c7bcdb9721daca763947e772927f335d52607f22de9690fcdad542f605055618c12c14e80f3532c6f5e48f9231cb81c649786eccb5a1
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 songji.zeng
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# Cloudflare::Artifacts
|
|
2
|
+
|
|
3
|
+
A Ruby client for the [Cloudflare Artifacts](https://developers.cloudflare.com/artifacts/) REST API, built on [Faraday](https://github.com/lostisland/faraday).
|
|
4
|
+
|
|
5
|
+
Manage Artifacts repositories and tokens from Ruby — create, list, fork, import, and mint short-lived Git credentials.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Add to your Gemfile:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem "cloudflare-artifacts"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or install directly:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
gem install cloudflare-artifacts
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
### Configure once, use everywhere
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
require "cloudflare/artifacts"
|
|
27
|
+
|
|
28
|
+
Cloudflare::Artifacts.configure do |c|
|
|
29
|
+
c.account_id = ENV.fetch("CLOUDFLARE_ACCOUNT_ID")
|
|
30
|
+
c.api_token = ENV.fetch("CLOUDFLARE_API_TOKEN")
|
|
31
|
+
c.namespace = "default" # optional, defaults to "default"
|
|
32
|
+
c.timeout = 30 # optional
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
client = Cloudflare::Artifacts::Client.new
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Or pass options per client
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
client = Cloudflare::Artifacts::Client.new(
|
|
42
|
+
account_id: ENV.fetch("CLOUDFLARE_ACCOUNT_ID"),
|
|
43
|
+
api_token: ENV.fetch("CLOUDFLARE_API_TOKEN"),
|
|
44
|
+
namespace: "ci"
|
|
45
|
+
)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Or supply an explicit Configuration
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
config = Cloudflare::Artifacts::Configuration.new(
|
|
52
|
+
account_id: "...",
|
|
53
|
+
api_token: "...",
|
|
54
|
+
namespace: "ci"
|
|
55
|
+
)
|
|
56
|
+
client = Cloudflare::Artifacts::Client.new(config)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Or load from environment / file
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
# From ENV — reads:
|
|
63
|
+
# CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_API_TOKEN,
|
|
64
|
+
# CLOUDFLARE_ARTIFACTS_NAMESPACE, CLOUDFLARE_API_BASE_URL,
|
|
65
|
+
# CLOUDFLARE_ARTIFACTS_TIMEOUT, CLOUDFLARE_ARTIFACTS_USER_AGENT
|
|
66
|
+
Cloudflare::Artifacts.configuration = Cloudflare::Artifacts::Configuration.from_env
|
|
67
|
+
|
|
68
|
+
# From a YAML or JSON file
|
|
69
|
+
Cloudflare::Artifacts.configuration =
|
|
70
|
+
Cloudflare::Artifacts::Configuration.from_file("config/cloudflare.yml")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Keyword overrides also work when a `Configuration` is passed — they're merged, leaving the original untouched:
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
Cloudflare::Artifacts::Client.new(config, namespace: "staging")
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Every call returns a `Cloudflare::Artifacts::Response` (a `Data` object) exposing `result`, `errors`, `messages`, and `result_info` from the standard Cloudflare v4 envelope.
|
|
80
|
+
|
|
81
|
+
### Repos
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
# Create a repo
|
|
85
|
+
resp = client.repos.create(
|
|
86
|
+
name: "starter-repo",
|
|
87
|
+
description: "Repository for automation experiments",
|
|
88
|
+
default_branch: "main"
|
|
89
|
+
)
|
|
90
|
+
remote = resp.result["remote"]
|
|
91
|
+
token = resp.result["token"]
|
|
92
|
+
|
|
93
|
+
# List repos (supports limit, cursor, search, sort, direction)
|
|
94
|
+
client.repos.list(limit: 50, sort: "updated_at", direction: "desc")
|
|
95
|
+
|
|
96
|
+
# Get a repo
|
|
97
|
+
client.repos.get("starter-repo")
|
|
98
|
+
|
|
99
|
+
# Delete a repo
|
|
100
|
+
client.repos.delete("starter-repo")
|
|
101
|
+
|
|
102
|
+
# Fork a repo
|
|
103
|
+
client.repos.fork("starter-repo",
|
|
104
|
+
name: "starter-repo-copy",
|
|
105
|
+
default_branch_only: true)
|
|
106
|
+
|
|
107
|
+
# Import a public HTTPS remote
|
|
108
|
+
client.repos.import("react-mirror",
|
|
109
|
+
url: "https://github.com/facebook/react",
|
|
110
|
+
branch: "main",
|
|
111
|
+
depth: 100)
|
|
112
|
+
|
|
113
|
+
# List tokens for a repo
|
|
114
|
+
client.repos.tokens("starter-repo", state: "active")
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Tokens
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
# Mint a short-lived token (TTL in seconds, 60..31_536_000)
|
|
121
|
+
resp = client.tokens.create(repo: "starter-repo", scope: "read", ttl: 3600)
|
|
122
|
+
plaintext = resp.result["plaintext"]
|
|
123
|
+
|
|
124
|
+
# Revoke a token
|
|
125
|
+
client.tokens.revoke("tok_123")
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Per-call namespace override
|
|
129
|
+
|
|
130
|
+
Override the default namespace per call by passing `namespace:`:
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
client.repos.get("starter-repo", namespace: "ci")
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Error handling
|
|
137
|
+
|
|
138
|
+
Non-2xx responses raise typed errors. All inherit from `Cloudflare::Artifacts::APIError`:
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
begin
|
|
142
|
+
client.repos.get("missing")
|
|
143
|
+
rescue Cloudflare::Artifacts::NotFoundError => e
|
|
144
|
+
e.status # => 404
|
|
145
|
+
e.errors # => [{ "code" => 1000, "message" => "Repo not found" }]
|
|
146
|
+
end
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Available subclasses: `BadRequestError`, `AuthenticationError`, `ForbiddenError`, `NotFoundError`, `ConflictError`, `UnprocessableEntityError`, `RateLimitError`, `ServerError`.
|
|
150
|
+
|
|
151
|
+
### Customizing Faraday
|
|
152
|
+
|
|
153
|
+
`Configuration` exposes `adapter`, `timeout`, `base_url`, and `user_agent`:
|
|
154
|
+
|
|
155
|
+
```ruby
|
|
156
|
+
Cloudflare::Artifacts.configure do |c|
|
|
157
|
+
c.adapter = :net_http_persistent
|
|
158
|
+
c.timeout = 60
|
|
159
|
+
c.user_agent = "myapp/1.0"
|
|
160
|
+
end
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
For tests, use the Faraday test adapter — the adapter value can be `[symbol, *args]`:
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
stubs = Faraday::Adapter::Test::Stubs.new
|
|
167
|
+
client = Cloudflare::Artifacts::Client.new(
|
|
168
|
+
account_id: "acct", api_token: "tok",
|
|
169
|
+
adapter: [:test, stubs]
|
|
170
|
+
)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Development
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
bundle exec rake # run tests + rubocop
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Contributing
|
|
180
|
+
|
|
181
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/songjiz/cloudflare-artifacts.
|
|
182
|
+
|
|
183
|
+
## License
|
|
184
|
+
|
|
185
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
|
|
5
|
+
module Cloudflare
|
|
6
|
+
module Artifacts
|
|
7
|
+
class Client
|
|
8
|
+
attr_reader :config, :connection
|
|
9
|
+
|
|
10
|
+
def initialize(config = nil, **opts)
|
|
11
|
+
@config = resolve_config(config, opts)
|
|
12
|
+
@config.validate!
|
|
13
|
+
@connection = build_connection
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def account_id
|
|
17
|
+
@config.account_id
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def api_token
|
|
21
|
+
@config.api_token
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def namespace
|
|
25
|
+
@config.namespace
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def repos
|
|
29
|
+
@repos ||= Repos.new(self)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def tokens
|
|
33
|
+
@tokens ||= Tokens.new(self)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def get(path, params: nil, namespace: nil)
|
|
37
|
+
request(:get, path, params: params, namespace: namespace)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def post(path, body: nil, params: nil, namespace: nil)
|
|
41
|
+
request(:post, path, body: body, params: params, namespace: namespace)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def delete(path, params: nil, namespace: nil)
|
|
45
|
+
request(:delete, path, params: params, namespace: namespace)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def request(method, path, params: nil, body: nil, namespace: nil)
|
|
49
|
+
ns = namespace || @config.namespace
|
|
50
|
+
raise ArgumentError, "namespace is required" if ns.nil? || ns.empty?
|
|
51
|
+
|
|
52
|
+
full_path = "/client/v4/accounts/#{@config.account_id}/artifacts/namespaces/#{ns}#{path}"
|
|
53
|
+
response = @connection.public_send(method, full_path) do |req|
|
|
54
|
+
req.params.update(params) if params && !params.empty?
|
|
55
|
+
req.body = body if body
|
|
56
|
+
end
|
|
57
|
+
handle_response(response)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
def resolve_config(config, opts)
|
|
62
|
+
case config
|
|
63
|
+
when Configuration then opts.empty? ? config : config.merge(**opts)
|
|
64
|
+
when nil then Artifacts.configuration.merge(**opts)
|
|
65
|
+
else raise ArgumentError, "expected Cloudflare::Artifacts::Configuration, got #{config.class}"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def build_connection
|
|
70
|
+
Faraday.new(url: @config.base_url) do |f|
|
|
71
|
+
apply_middleware(f)
|
|
72
|
+
apply_adapter(f)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def apply_middleware(faraday)
|
|
77
|
+
faraday.request :authorization, "Bearer", @config.api_token
|
|
78
|
+
faraday.request :json
|
|
79
|
+
faraday.response :json, content_type: /\bjson$/
|
|
80
|
+
faraday.headers["User-Agent"] = @config.user_agent
|
|
81
|
+
faraday.options.timeout = @config.timeout if @config.timeout
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def apply_adapter(faraday)
|
|
85
|
+
if @config.adapter.is_a?(Array)
|
|
86
|
+
faraday.adapter(*@config.adapter)
|
|
87
|
+
else
|
|
88
|
+
faraday.adapter @config.adapter
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def handle_response(response)
|
|
93
|
+
body = response.body
|
|
94
|
+
ok = response.success? && body.is_a?(Hash) && body["success"]
|
|
95
|
+
raise Artifacts.error_for(response.status, body) unless ok
|
|
96
|
+
|
|
97
|
+
Response.new(status: response.status, body: body)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cloudflare
|
|
4
|
+
module Artifacts
|
|
5
|
+
class Configuration
|
|
6
|
+
DEFAULT_BASE_URL = "https://api.cloudflare.com"
|
|
7
|
+
DEFAULT_NAMESPACE = "default"
|
|
8
|
+
DEFAULT_TIMEOUT = 30
|
|
9
|
+
|
|
10
|
+
attr_accessor :account_id, :api_token, :namespace, :base_url,
|
|
11
|
+
:adapter, :timeout, :user_agent
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
def from_env
|
|
15
|
+
new(
|
|
16
|
+
base_url: ENV.fetch("CLOUDFLARE_API_BASE_URL", DEFAULT_BASE_URL),
|
|
17
|
+
namespace: ENV.fetch("CLOUDFLARE_ARTIFACTS_NAMESPACE", DEFAULT_NAMESPACE),
|
|
18
|
+
timeout: ENV.fetch("CLOUDFLARE_ARTIFACTS_TIMEOUT", DEFAULT_TIMEOUT).to_i,
|
|
19
|
+
account_id: ENV.fetch("CLOUDFLARE_ACCOUNT_ID", nil),
|
|
20
|
+
api_token: ENV.fetch("CLOUDFLARE_API_TOKEN", nil)
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def from_file(path)
|
|
25
|
+
data = load_file(path)
|
|
26
|
+
raise ArgumentError, "expected a hash in #{path}, got #{data.class}" unless data.is_a?(Hash)
|
|
27
|
+
|
|
28
|
+
new(**symbolize_keys(data))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
def load_file(path)
|
|
33
|
+
case File.extname(path).downcase
|
|
34
|
+
when ".yml", ".yaml"
|
|
35
|
+
require "yaml"
|
|
36
|
+
YAML.safe_load_file(path, permitted_classes: [Symbol], aliases: true)
|
|
37
|
+
when ".json"
|
|
38
|
+
require "json"
|
|
39
|
+
JSON.parse(File.read(path))
|
|
40
|
+
else
|
|
41
|
+
raise ArgumentError, "unsupported file format: #{path} (expected .yml, .yaml, or .json)"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def symbolize_keys(hash)
|
|
46
|
+
hash.transform_keys(&:to_sym)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def initialize(base_url: DEFAULT_BASE_URL, namespace: DEFAULT_NAMESPACE, timeout: DEFAULT_TIMEOUT, **options)
|
|
51
|
+
@base_url = base_url
|
|
52
|
+
@namespace = namespace
|
|
53
|
+
@timeout = timeout
|
|
54
|
+
@adapter = Faraday.default_adapter
|
|
55
|
+
@user_agent = "cloudflare-artifacts-ruby/#{VERSION}"
|
|
56
|
+
assign(options)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def assign(options)
|
|
60
|
+
options.each do |key, value|
|
|
61
|
+
setter = "#{key}="
|
|
62
|
+
raise ArgumentError, "unknown configuration option: #{key}" unless respond_to?(setter)
|
|
63
|
+
|
|
64
|
+
public_send(setter, value)
|
|
65
|
+
end
|
|
66
|
+
self
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def merge(**options)
|
|
70
|
+
dup.assign(options)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def to_h
|
|
74
|
+
{
|
|
75
|
+
account_id: account_id,
|
|
76
|
+
api_token: api_token,
|
|
77
|
+
namespace: namespace,
|
|
78
|
+
base_url: base_url,
|
|
79
|
+
adapter: adapter,
|
|
80
|
+
timeout: timeout,
|
|
81
|
+
user_agent: user_agent
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def validate!
|
|
86
|
+
raise ArgumentError, "account_id is required" if blank?(account_id)
|
|
87
|
+
raise ArgumentError, "api_token is required" if blank?(api_token)
|
|
88
|
+
raise ArgumentError, "namespace is required" if blank?(namespace)
|
|
89
|
+
raise ArgumentError, "timeout must be positive" unless timeout.is_a?(Numeric) && timeout.positive?
|
|
90
|
+
|
|
91
|
+
self
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
def blank?(value)
|
|
96
|
+
value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cloudflare
|
|
4
|
+
module Artifacts
|
|
5
|
+
class Error < StandardError; end
|
|
6
|
+
|
|
7
|
+
class APIError < Error
|
|
8
|
+
attr_reader :status, :body, :errors
|
|
9
|
+
|
|
10
|
+
def initialize(message = nil, status: nil, body: nil)
|
|
11
|
+
super(message)
|
|
12
|
+
@status = status
|
|
13
|
+
@body = body
|
|
14
|
+
@errors = (body.is_a?(Hash) && body["errors"]) || []
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class BadRequestError < APIError; end
|
|
19
|
+
class AuthenticationError < APIError; end
|
|
20
|
+
class ForbiddenError < APIError; end
|
|
21
|
+
class NotFoundError < APIError; end
|
|
22
|
+
class ConflictError < APIError; end
|
|
23
|
+
class UnprocessableEntityError < APIError; end
|
|
24
|
+
class RateLimitError < APIError; end
|
|
25
|
+
class ServerError < APIError; end
|
|
26
|
+
|
|
27
|
+
STATUS_ERRORS = {
|
|
28
|
+
400 => BadRequestError,
|
|
29
|
+
401 => AuthenticationError,
|
|
30
|
+
403 => ForbiddenError,
|
|
31
|
+
404 => NotFoundError,
|
|
32
|
+
409 => ConflictError,
|
|
33
|
+
422 => UnprocessableEntityError,
|
|
34
|
+
429 => RateLimitError
|
|
35
|
+
}.freeze
|
|
36
|
+
|
|
37
|
+
def self.error_for(status, body)
|
|
38
|
+
klass = STATUS_ERRORS[status] || (status >= 500 ? ServerError : APIError)
|
|
39
|
+
message = extract_message(body) || "HTTP #{status}"
|
|
40
|
+
klass.new(message, status: status, body: body)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.extract_message(body)
|
|
44
|
+
return unless body.is_a?(Hash)
|
|
45
|
+
|
|
46
|
+
errors = body["errors"]
|
|
47
|
+
return unless errors.is_a?(Array) && !errors.empty?
|
|
48
|
+
|
|
49
|
+
errors.filter_map { |e| e.is_a?(Hash) ? e["message"] : nil }.join("; ").then do |msg|
|
|
50
|
+
msg.empty? ? nil : msg
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cloudflare
|
|
4
|
+
module Artifacts
|
|
5
|
+
class Repos
|
|
6
|
+
def initialize(client)
|
|
7
|
+
@client = client
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def create(name:, namespace: nil, **attrs)
|
|
11
|
+
@client.post("/repos", body: { name: name, **attrs }, namespace: namespace)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def list(namespace: nil, **params)
|
|
15
|
+
@client.get("/repos", params: params, namespace: namespace)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def get(name, namespace: nil)
|
|
19
|
+
@client.get("/repos/#{name}", namespace: namespace)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def delete(name, namespace: nil)
|
|
23
|
+
@client.delete("/repos/#{name}", namespace: namespace)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def fork(name, namespace: nil, **attrs)
|
|
27
|
+
@client.post("/repos/#{name}/fork", body: attrs, namespace: namespace)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def import(name, url:, namespace: nil, **attrs)
|
|
31
|
+
@client.post("/repos/#{name}/import", body: { url: url, **attrs }, namespace: namespace)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def tokens(name, namespace: nil, **params)
|
|
35
|
+
@client.get("/repos/#{name}/tokens", params: params, namespace: namespace)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cloudflare
|
|
4
|
+
module Artifacts
|
|
5
|
+
Response = Data.define(:status, :body) do
|
|
6
|
+
def initialize(status:, body:)
|
|
7
|
+
super(status: status, body: body.is_a?(Hash) ? body : {})
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def result
|
|
11
|
+
body["result"]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def errors
|
|
15
|
+
body["errors"] || []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def messages
|
|
19
|
+
body["messages"] || []
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def result_info
|
|
23
|
+
body["result_info"]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def success?
|
|
27
|
+
body["success"] == true
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cloudflare
|
|
4
|
+
module Artifacts
|
|
5
|
+
class Tokens
|
|
6
|
+
def initialize(client)
|
|
7
|
+
@client = client
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def create(repo:, scope:, ttl:, namespace: nil)
|
|
11
|
+
@client.post("/tokens", body: { repo: repo, scope: scope, ttl: ttl }, namespace: namespace)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def revoke(id, namespace: nil)
|
|
15
|
+
@client.delete("/tokens/#{id}", namespace: namespace)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
|
|
5
|
+
require_relative "artifacts/version"
|
|
6
|
+
require_relative "artifacts/error"
|
|
7
|
+
require_relative "artifacts/response"
|
|
8
|
+
require_relative "artifacts/configuration"
|
|
9
|
+
require_relative "artifacts/repos"
|
|
10
|
+
require_relative "artifacts/tokens"
|
|
11
|
+
require_relative "artifacts/client"
|
|
12
|
+
|
|
13
|
+
module Cloudflare
|
|
14
|
+
module Artifacts
|
|
15
|
+
class << self
|
|
16
|
+
attr_writer :configuration
|
|
17
|
+
|
|
18
|
+
def configuration
|
|
19
|
+
@configuration ||= Configuration.new
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def configure
|
|
23
|
+
yield configuration
|
|
24
|
+
configuration
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def reset_configuration!
|
|
28
|
+
@configuration = Configuration.new
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: cloudflare-artifacts
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- songji.zeng
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: faraday
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.0'
|
|
26
|
+
description: A Faraday-based Ruby client for managing Cloudflare Artifacts repositories
|
|
27
|
+
and tokens.
|
|
28
|
+
email:
|
|
29
|
+
- songji.zeng@outlook.com
|
|
30
|
+
executables: []
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- CHANGELOG.md
|
|
35
|
+
- LICENSE.txt
|
|
36
|
+
- README.md
|
|
37
|
+
- Rakefile
|
|
38
|
+
- lib/cloudflare/artifacts.rb
|
|
39
|
+
- lib/cloudflare/artifacts/client.rb
|
|
40
|
+
- lib/cloudflare/artifacts/configuration.rb
|
|
41
|
+
- lib/cloudflare/artifacts/error.rb
|
|
42
|
+
- lib/cloudflare/artifacts/repos.rb
|
|
43
|
+
- lib/cloudflare/artifacts/response.rb
|
|
44
|
+
- lib/cloudflare/artifacts/tokens.rb
|
|
45
|
+
- lib/cloudflare/artifacts/version.rb
|
|
46
|
+
homepage: https://github.com/songjiz/cloudflare-artifacts
|
|
47
|
+
licenses:
|
|
48
|
+
- MIT
|
|
49
|
+
metadata:
|
|
50
|
+
homepage_uri: https://github.com/songjiz/cloudflare-artifacts
|
|
51
|
+
source_code_uri: https://github.com/songjiz/cloudflare-artifacts
|
|
52
|
+
changelog_uri: https://github.com/songjiz/cloudflare-artifacts/blob/main/CHANGELOG.md
|
|
53
|
+
rubygems_mfa_required: 'true'
|
|
54
|
+
rdoc_options: []
|
|
55
|
+
require_paths:
|
|
56
|
+
- lib
|
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: 3.2.0
|
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '0'
|
|
67
|
+
requirements: []
|
|
68
|
+
rubygems_version: 3.6.9
|
|
69
|
+
specification_version: 4
|
|
70
|
+
summary: Ruby client for the Cloudflare Artifacts REST API.
|
|
71
|
+
test_files: []
|