fly_io 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 +15 -0
- data/CONTRIBUTING.md +14 -0
- data/LICENSE +21 -0
- data/README.md +239 -0
- data/Rakefile +30 -0
- data/contracts/additional_rest/network_policies.json +94 -0
- data/contracts/additional_rest/prometheus.json +103 -0
- data/contracts/graphql/fly_go_operations.json +948 -0
- data/contracts/graphql/flyctl_named_operations.graphql +316 -0
- data/contracts/graphql/flyctl_operations.json +336 -0
- data/contracts/graphql/official_examples.json +75 -0
- data/contracts/graphql/schema.graphql +10995 -0
- data/contracts/graphql/source.json +29 -0
- data/contracts/machines/openapi.headers +9 -0
- data/contracts/machines/openapi.json +1 -0
- data/contracts/machines/source.json +20 -0
- data/contracts/public_surface_inventory.json +164 -0
- data/contracts/research_metadata.json +17 -0
- data/contracts/sources/metrics.html.md +474 -0
- data/contracts/sources/network-policies.html.markerb +142 -0
- data/docs/API.md +149 -0
- data/docs/SURFACES.md +60 -0
- data/lib/fly_io/client.rb +52 -0
- data/lib/fly_io/configuration.rb +115 -0
- data/lib/fly_io/errors.rb +45 -0
- data/lib/fly_io/generated/additional_operations.json +123 -0
- data/lib/fly_io/generated/graphql_operations.json +1658 -0
- data/lib/fly_io/generated/operations.json +6130 -0
- data/lib/fly_io/generated/prometheus_operations.json +503 -0
- data/lib/fly_io/generated/schemas.json +4237 -0
- data/lib/fly_io/graphql_client.rb +103 -0
- data/lib/fly_io/model.rb +65 -0
- data/lib/fly_io/operation_registry.rb +79 -0
- data/lib/fly_io/redactor.rb +34 -0
- data/lib/fly_io/resources/base.rb +68 -0
- data/lib/fly_io/resources.rb +30 -0
- data/lib/fly_io/response.rb +45 -0
- data/lib/fly_io/schema_registry.rb +89 -0
- data/lib/fly_io/schema_validator.rb +56 -0
- data/lib/fly_io/transport.rb +282 -0
- data/lib/fly_io/version.rb +5 -0
- data/lib/fly_io.rb +23 -0
- data/script/check_api_coverage +60 -0
- data/script/fetch_openapi +18 -0
- data/script/generate_api +302 -0
- metadata +197 -0
data/script/generate_api
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "digest"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
require "json"
|
|
7
|
+
|
|
8
|
+
ROOT = File.expand_path("..", __dir__)
|
|
9
|
+
SPEC_PATH = File.join(ROOT, "contracts/machines/openapi.json")
|
|
10
|
+
SOURCE_PATH = File.join(ROOT, "contracts/machines/source.json")
|
|
11
|
+
ADDITIONAL_PATH = File.join(ROOT, "contracts/additional_rest/network_policies.json")
|
|
12
|
+
PROMETHEUS_PATH = File.join(ROOT, "contracts/additional_rest/prometheus.json")
|
|
13
|
+
HTTP_METHODS = %w[get put post delete patch options head trace].freeze
|
|
14
|
+
|
|
15
|
+
def constant_name(name, used)
|
|
16
|
+
words = name.gsub(/([a-z0-9])([A-Z])/, "\\1_\\2").split(/[^A-Za-z0-9]+/).reject(&:empty?)
|
|
17
|
+
candidate = words.map { |word| word[0].upcase + word[1..].to_s }.join
|
|
18
|
+
candidate = "Schema#{candidate}" if candidate.empty? || candidate.match?(/\A\d/)
|
|
19
|
+
candidate += Digest::SHA256.hexdigest(name)[0, 8] if used.key?(candidate) && used[candidate] != name
|
|
20
|
+
used[candidate] = name
|
|
21
|
+
candidate
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def ruby_resource(tag)
|
|
25
|
+
{
|
|
26
|
+
"Apps" => "apps", "TLS Certificates" => "certificates", "Machines" => "machines",
|
|
27
|
+
"Secrets" => "secrets", "Volumes" => "volumes", "Organizations" => "organizations",
|
|
28
|
+
"Platform" => "platform", "Postgres Clusters" => "postgres_clusters", "Tokens" => "tokens"
|
|
29
|
+
}.fetch(tag)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def ruby_method(operation_id, method)
|
|
33
|
+
exact = {
|
|
34
|
+
"Apps_list" => "list", "Apps_create" => "create", "Apps_show" => "get", "Apps_delete" => "destroy",
|
|
35
|
+
"App_create_deploy_token" => "create_deploy_token",
|
|
36
|
+
"App_IPAssignments_list" => "list_ip_assignments", "App_IPAssignments_create" => "create_ip_assignment",
|
|
37
|
+
"App_IPAssignments_delete" => "delete_ip_assignment",
|
|
38
|
+
"App_Certificates_list" => "list", "App_Certificates_acme_create" => "request_acme",
|
|
39
|
+
"App_Certificates_custom_create" => "upload_custom", "App_Certificates_show" => "get",
|
|
40
|
+
"App_Certificates_delete" => "remove", "App_Certificates_acme_delete" => "remove_acme",
|
|
41
|
+
"App_Certificates_check" => "check", "App_Certificates_custom_delete" => "remove_custom",
|
|
42
|
+
"Machines_show" => "get", "Machines_delete" => "destroy", "Machines_list_events" => "events",
|
|
43
|
+
"Machines_exec" => "execute", "Machines_show_lease" => "lease", "Machines_get_memory" => "memory",
|
|
44
|
+
"Machines_show_metadata" => "metadata", "Machines_get_metadata_key" => "get_metadata",
|
|
45
|
+
"Machines_upsert_metadata" => "set_metadata", "Machines_delete_metadata" => "delete_metadata",
|
|
46
|
+
"Machines_list_processes" => "processes", "Machines_list_versions" => "versions",
|
|
47
|
+
"Volumes_get_by_id" => "get", "Volume_delete" => "destroy", "createVolumeSnapshot" => "create_snapshot",
|
|
48
|
+
"Machines_org_list" => "list_machines", "Volumes_org_list" => "list_volumes",
|
|
49
|
+
"Platform_placements_post" => "placements", "Platform_regions_get" => "regions",
|
|
50
|
+
"CurrentToken_show" => "current", "Tokens_request_Kms" => "request_kms", "Tokens_request_OIDC" => "request_oidc"
|
|
51
|
+
}
|
|
52
|
+
return method == "put" ? "replace_metadata" : "update_metadata" if operation_id == "Machines_update_metadata"
|
|
53
|
+
return exact.fetch(operation_id) if exact.key?(operation_id)
|
|
54
|
+
return "list_keys" if operation_id == "Secretkeys_list"
|
|
55
|
+
if operation_id.start_with?("Secretkey_")
|
|
56
|
+
return operation_id.delete_prefix("Secretkey_").then do |suffix|
|
|
57
|
+
"#{suffix == 'get' ? 'get' : suffix}_key"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
return operation_id.delete_prefix("Secret_").sub("create", "set") if operation_id.start_with?("Secret_")
|
|
61
|
+
return operation_id.delete_prefix("Secrets_") if operation_id.start_with?("Secrets_")
|
|
62
|
+
|
|
63
|
+
%w[Machines_ Volumes_ Postgres_ Tokens_].each do |prefix|
|
|
64
|
+
return operation_id.delete_prefix(prefix).downcase if operation_id.start_with?(prefix)
|
|
65
|
+
end
|
|
66
|
+
operation_id.gsub(/([a-z0-9])([A-Z])/, "\\1_\\2").downcase
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def refs_in(value, result = [])
|
|
70
|
+
case value
|
|
71
|
+
when Hash
|
|
72
|
+
value.each do |key, child|
|
|
73
|
+
result << child if key == "$ref"
|
|
74
|
+
refs_in(child, result)
|
|
75
|
+
end
|
|
76
|
+
when Array
|
|
77
|
+
value.each { |child| refs_in(child, result) }
|
|
78
|
+
end
|
|
79
|
+
result
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def operation_rows(spec)
|
|
83
|
+
spec.fetch("paths").flat_map do |path, path_item|
|
|
84
|
+
path_item.filter_map do |method, operation|
|
|
85
|
+
next unless HTTP_METHODS.include?(method)
|
|
86
|
+
|
|
87
|
+
tag = operation.fetch("tags").first
|
|
88
|
+
parameters = (path_item.fetch("parameters", []) + operation.fetch("parameters", [])).map do |parameter|
|
|
89
|
+
parameter.merge("ruby_name" => parameter.fetch("name").tr("-", "_").downcase)
|
|
90
|
+
end
|
|
91
|
+
body = operation["requestBody"]
|
|
92
|
+
body_metadata = if body
|
|
93
|
+
content = body.fetch("content")
|
|
94
|
+
json = content["application/json"] || content.values.first
|
|
95
|
+
{"required" => body.fetch("required", false), "content_types" => content.keys,
|
|
96
|
+
"schema" => json["schema"]}
|
|
97
|
+
end
|
|
98
|
+
responses = operation.fetch("responses").transform_values do |response|
|
|
99
|
+
{
|
|
100
|
+
"description" => response["description"],
|
|
101
|
+
"content" => response.fetch("content", {}).transform_values { |entry| {"schema" => entry["schema"]} }
|
|
102
|
+
}
|
|
103
|
+
end
|
|
104
|
+
operation_id = operation.fetch("operationId")
|
|
105
|
+
resource = ruby_resource(tag)
|
|
106
|
+
{
|
|
107
|
+
"identity" => "#{method.upcase} #{path} #{operation_id}",
|
|
108
|
+
"surface" => "machines_rest", "stability" => "stable", "base_url" => spec.fetch("servers").first.fetch("url"),
|
|
109
|
+
"operation_id" => operation_id, "method" => method, "path" => path, "tags" => operation.fetch("tags"),
|
|
110
|
+
"summary" => operation["summary"], "parameters" => parameters, "request_body" => body_metadata,
|
|
111
|
+
"responses" => responses, "ruby_resource" => resource, "ruby_method" => ruby_method(operation_id, method),
|
|
112
|
+
"public_ruby_method" => "FlyIO::Client##{resource}.#{ruby_method(operation_id, method)}"
|
|
113
|
+
}
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def pretty(value)
|
|
119
|
+
"#{JSON.pretty_generate(value)}\n"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
spec_bytes = File.binread(SPEC_PATH)
|
|
123
|
+
spec = JSON.parse(spec_bytes)
|
|
124
|
+
source = JSON.parse(File.read(SOURCE_PATH))
|
|
125
|
+
additional = JSON.parse(File.read(ADDITIONAL_PATH))
|
|
126
|
+
prometheus = JSON.parse(File.read(PROMETHEUS_PATH))
|
|
127
|
+
abort "OpenAPI 3.x required" unless spec.fetch("openapi").start_with?("3.")
|
|
128
|
+
actual_sha = Digest::SHA256.hexdigest(spec_bytes)
|
|
129
|
+
abort "snapshot hash drift: expected #{source.fetch('sha256')}, got #{actual_sha}" unless actual_sha == source.fetch("sha256")
|
|
130
|
+
|
|
131
|
+
schemas = spec.fetch("components").fetch("schemas")
|
|
132
|
+
refs = refs_in(spec)
|
|
133
|
+
invalid_refs = refs.reject do |ref|
|
|
134
|
+
ref.start_with?("#/components/schemas/") && schemas.key?(ref.delete_prefix("#/components/schemas/"))
|
|
135
|
+
end
|
|
136
|
+
abort "unresolved or non-local refs: #{invalid_refs.uniq.join(', ')}" unless invalid_refs.empty?
|
|
137
|
+
|
|
138
|
+
used_constants = {}
|
|
139
|
+
all_schemas = schemas.merge(additional.fetch("schemas")).merge(prometheus.fetch("schemas"))
|
|
140
|
+
additional_refs = refs_in(additional) + refs_in(prometheus)
|
|
141
|
+
invalid_additional_refs = additional_refs.reject do |ref|
|
|
142
|
+
ref.start_with?("#/additional/schemas/") && all_schemas.key?(ref.delete_prefix("#/additional/schemas/"))
|
|
143
|
+
end
|
|
144
|
+
abort "unresolved additional-surface refs: #{invalid_additional_refs.uniq.join(', ')}" unless invalid_additional_refs.empty?
|
|
145
|
+
generated_schemas = all_schemas.to_h do |name, schema|
|
|
146
|
+
[name, schema.merge("x-ruby-class" => constant_name(name, used_constants))]
|
|
147
|
+
end
|
|
148
|
+
operations = operation_rows(spec)
|
|
149
|
+
additional_operations = additional.fetch("operations").map do |operation|
|
|
150
|
+
operation.merge(
|
|
151
|
+
"surface" => additional.fetch("surface"), "stability" => additional.fetch("stability"),
|
|
152
|
+
"base_url" => additional.fetch("base_url"), "tags" => ["Network Policies"],
|
|
153
|
+
"ruby_resource" => "network_policies",
|
|
154
|
+
"ruby_method" => {"NetworkPolicies_upsert" => "upsert", "NetworkPolicies_list" => "list", "NetworkPolicies_delete" => "delete"}.fetch(operation.fetch("operation_id")),
|
|
155
|
+
"public_ruby_method" => "FlyIO::Client#network_policies.#{{'NetworkPolicies_upsert' => 'upsert', 'NetworkPolicies_list' => 'list', 'NetworkPolicies_delete' => 'delete'}.fetch(operation.fetch('operation_id'))}"
|
|
156
|
+
)
|
|
157
|
+
end
|
|
158
|
+
prometheus_operations = prometheus.fetch("operations").map do |operation|
|
|
159
|
+
content_type = operation.fetch("response_content_type", "application/json")
|
|
160
|
+
response_schema = content_type == "application/json" ? {"$ref" => "#/additional/schemas/PrometheusAPIResponse"} : {"type" => "string"}
|
|
161
|
+
method_name = operation.fetch("operation_id").delete_prefix("Prometheus_")
|
|
162
|
+
operation.merge(
|
|
163
|
+
"identity" => "#{operation.fetch('method').upcase} #{operation.fetch('path')} #{operation.fetch('operation_id')}",
|
|
164
|
+
"surface" => prometheus.fetch("surface"), "stability" => prometheus.fetch("stability"),
|
|
165
|
+
"base_url" => prometheus.fetch("base_url"), "tags" => ["Metrics"], "request_body" => nil,
|
|
166
|
+
"responses" => {"200" => {"description" => "Prometheus API response", "content" => {content_type => {"schema" => response_schema}}}},
|
|
167
|
+
"ruby_resource" => "metrics", "ruby_method" => method_name,
|
|
168
|
+
"public_ruby_method" => "FlyIO::Client#metrics.#{method_name}"
|
|
169
|
+
).except("response_content_type")
|
|
170
|
+
end
|
|
171
|
+
identities = (operations + additional_operations + prometheus_operations).map { |operation| operation.fetch("identity") }
|
|
172
|
+
abort "duplicate operation identities" unless identities.uniq.length == identities.length
|
|
173
|
+
public_methods = (operations + additional_operations + prometheus_operations).map do |operation|
|
|
174
|
+
[operation.fetch("ruby_resource"), operation.fetch("ruby_method")]
|
|
175
|
+
end
|
|
176
|
+
duplicates = public_methods.group_by(&:itself).select { |_, entries| entries.length > 1 }
|
|
177
|
+
abort "duplicate public methods: #{duplicates.keys.inspect}" unless duplicates.empty?
|
|
178
|
+
|
|
179
|
+
counts = {
|
|
180
|
+
"paths" => spec.fetch("paths").length, "operations" => operations.length,
|
|
181
|
+
"unique_operation_ids" => operations.map { |operation| operation.fetch("operation_id") }.uniq.length,
|
|
182
|
+
"schemas" => schemas.length,
|
|
183
|
+
"tags" => operations.flat_map { |operation| operation.fetch("tags") }.tally.sort.to_h
|
|
184
|
+
}
|
|
185
|
+
%w[paths operations unique_operation_ids schemas].each do |key|
|
|
186
|
+
abort "#{key} drift: expected #{source.fetch(key)}, got #{counts.fetch(key)}" unless counts.fetch(key) == source.fetch(key)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
manifest = {
|
|
190
|
+
"generated_from" => source.slice("contract_url", "retrieved_at", "etag", "last_modified", "sha256"),
|
|
191
|
+
"counts" => counts, "operations" => operations
|
|
192
|
+
}
|
|
193
|
+
api_lines = (operations + additional_operations + prometheus_operations).group_by do |operation|
|
|
194
|
+
operation.fetch("tags").first
|
|
195
|
+
end.sort.flat_map do |tag, tagged|
|
|
196
|
+
["## #{tag}", ""] + tagged.map do |operation|
|
|
197
|
+
"- `#{operation.fetch('method').upcase} #{operation.fetch('path')}` → `client.#{operation.fetch('ruby_resource')}.#{operation.fetch('ruby_method')}` (`#{operation.fetch('operation_id')}`)"
|
|
198
|
+
end + [""]
|
|
199
|
+
end
|
|
200
|
+
api_doc = <<~MARKDOWN
|
|
201
|
+
# Generated Fly API reference
|
|
202
|
+
|
|
203
|
+
Machines metadata is generated from `#{source.fetch('contract_url')}` (SHA-256 `#{source.fetch('sha256')}`).
|
|
204
|
+
This file lists #{operations.length} Machines OpenAPI operations, #{additional_operations.length} Network Policies
|
|
205
|
+
documentation-derived operations, and #{prometheus_operations.length} Prometheus endpoint families. GraphQL's
|
|
206
|
+
experimental generated-operation adapter is inventoried separately in `generated/graphql_operations.json`.
|
|
207
|
+
Do not edit this file by hand.
|
|
208
|
+
|
|
209
|
+
#{api_lines.join("\n")}
|
|
210
|
+
MARKDOWN
|
|
211
|
+
|
|
212
|
+
flyctl_graphql = JSON.parse(File.read(File.join(ROOT, "contracts/graphql/flyctl_operations.json")))
|
|
213
|
+
fly_go_graphql = JSON.parse(File.read(File.join(ROOT, "contracts/graphql/fly_go_operations.json")))
|
|
214
|
+
official_graphql = JSON.parse(File.read(File.join(ROOT, "contracts/graphql/official_examples.json")))
|
|
215
|
+
graphql_operations = flyctl_graphql.fetch("named_operations").map do |operation|
|
|
216
|
+
{
|
|
217
|
+
"name" => "flyctl.#{operation.fetch('name')}", "operation_name" => operation["operation_name"],
|
|
218
|
+
"operation_type" => operation.fetch("operation_type"), "root_fields" => operation.fetch("root_fields"),
|
|
219
|
+
"document" => operation.fetch("document"), "stability" => "experimental",
|
|
220
|
+
"observed_in" => "flyctl", "source_revision" => flyctl_graphql.fetch("revision"),
|
|
221
|
+
"source_path" => operation.fetch("source_path"), "source_line" => operation.fetch("source_line")
|
|
222
|
+
}
|
|
223
|
+
end
|
|
224
|
+
flyctl_graphql.fetch("ad_hoc_operations").each do |operation|
|
|
225
|
+
graphql_operations << {
|
|
226
|
+
"name" => "flyctl.#{operation.fetch('adapter')}", "operation_name" => operation["operation_name"],
|
|
227
|
+
"operation_type" => operation.fetch("operation_type"), "root_fields" => operation.fetch("root_fields"),
|
|
228
|
+
"document" => operation.fetch("document"), "stability" => "experimental",
|
|
229
|
+
"observed_in" => "flyctl", "source_revision" => flyctl_graphql.fetch("revision"),
|
|
230
|
+
"source_path" => operation.fetch("source_path"), "source_line" => operation.fetch("source_line")
|
|
231
|
+
}
|
|
232
|
+
end
|
|
233
|
+
fly_go_graphql.fetch("documents").each do |operation|
|
|
234
|
+
graphql_operations << {
|
|
235
|
+
"name" => "fly_go.#{operation.fetch('adapter')}", "operation_name" => operation["operation_name"],
|
|
236
|
+
"operation_type" => operation.fetch("operation_type"), "root_fields" => operation.fetch("root_fields"),
|
|
237
|
+
"document" => operation.fetch("document"), "stability" => "experimental",
|
|
238
|
+
"observed_in" => "fly-go", "source_revision" => fly_go_graphql.fetch("revision"),
|
|
239
|
+
"source_path" => operation.fetch("source_path"), "source_line" => operation.fetch("source_line")
|
|
240
|
+
}
|
|
241
|
+
end
|
|
242
|
+
official_graphql.fetch("examples").each do |operation|
|
|
243
|
+
graphql_operations << {
|
|
244
|
+
"name" => "official.#{operation.fetch('id')}", "operation_name" => operation["operation_name"],
|
|
245
|
+
"operation_type" => operation.fetch("operation_type"), "root_fields" => operation.fetch("root_fields"),
|
|
246
|
+
"document" => operation.fetch("document"), "stability" => "experimental",
|
|
247
|
+
"observed_in" => "official_documentation", "source_revision" => official_graphql.fetch("docs_revision"),
|
|
248
|
+
"source_path" => operation.fetch("source_path"), "source_line" => operation.fetch("source_lines").first
|
|
249
|
+
}
|
|
250
|
+
end
|
|
251
|
+
abort "duplicate generated GraphQL operation names" unless graphql_operations.map do |entry|
|
|
252
|
+
entry.fetch("name")
|
|
253
|
+
end.uniq.length == graphql_operations.length
|
|
254
|
+
graphql_manifest = {
|
|
255
|
+
"surface" => "control_plane_graphql", "endpoint" => "https://api.fly.io/graphql",
|
|
256
|
+
"stability" => "experimental_internal_no_stability_guarantee",
|
|
257
|
+
"schema" => flyctl_graphql.fetch("schema"),
|
|
258
|
+
"counts" => {
|
|
259
|
+
"flyctl_named" => flyctl_graphql.fetch("named_operation_count"),
|
|
260
|
+
"flyctl_ad_hoc" => flyctl_graphql.fetch("ad_hoc_operation_count"),
|
|
261
|
+
"fly_go" => fly_go_graphql.fetch("document_count"),
|
|
262
|
+
"official_examples" => official_graphql.fetch("examples").length,
|
|
263
|
+
"total" => graphql_operations.length
|
|
264
|
+
},
|
|
265
|
+
"operations" => graphql_operations
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
outputs = {
|
|
269
|
+
File.join(ROOT, "lib/fly_io/generated/schemas.json") => pretty(generated_schemas),
|
|
270
|
+
File.join(ROOT, "lib/fly_io/generated/operations.json") => pretty(manifest),
|
|
271
|
+
File.join(ROOT, "lib/fly_io/generated/additional_operations.json") => pretty({
|
|
272
|
+
"generated_from" => additional.slice("documentation_url", "source_revision", "source_path", "source_sha256", "retrieved_at"),
|
|
273
|
+
"counts" => {"operations" => additional_operations.length, "schemas" => additional.fetch("schemas").length},
|
|
274
|
+
"limitations" => additional.fetch("limitations"), "operations" => additional_operations
|
|
275
|
+
}),
|
|
276
|
+
File.join(ROOT, "lib/fly_io/generated/prometheus_operations.json") => pretty({
|
|
277
|
+
"generated_from" => prometheus.slice("documentation_url", "source_revision", "source_path", "source_sha256", "retrieved_at"),
|
|
278
|
+
"counts" => {"endpoint_families" => prometheus_operations.length, "schemas" => prometheus.fetch("schemas").length},
|
|
279
|
+
"limitations" => prometheus.fetch("limitations"), "operations" => prometheus_operations
|
|
280
|
+
}),
|
|
281
|
+
File.join(ROOT, "lib/fly_io/generated/graphql_operations.json") => pretty(graphql_manifest),
|
|
282
|
+
File.join(ROOT, "docs/API.md") => api_doc
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if ARGV.include?("--check")
|
|
286
|
+
failures = outputs.filter_map do |path, expected|
|
|
287
|
+
actual = File.exist?(path) ? File.read(path, encoding: "UTF-8") : nil
|
|
288
|
+
path unless actual == expected
|
|
289
|
+
end
|
|
290
|
+
unless failures.empty?
|
|
291
|
+
abort "generated API drift: #{failures.map do |path|
|
|
292
|
+
path.delete_prefix("#{ROOT}/")
|
|
293
|
+
end.join(', ')}"
|
|
294
|
+
end
|
|
295
|
+
puts "Machines REST API coverage OK: #{counts.fetch('paths')} paths, #{counts.fetch('operations')} operations, #{counts.fetch('schemas')} schemas, #{refs.length} resolved refs"
|
|
296
|
+
else
|
|
297
|
+
outputs.each do |path, contents|
|
|
298
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
299
|
+
File.write(path, contents)
|
|
300
|
+
end
|
|
301
|
+
puts "Generated #{operations.length} operations and #{schemas.length} schemas"
|
|
302
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: fly_io
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Vito Botta
|
|
8
|
+
bindir: exe
|
|
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.9'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '3'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '2.9'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '3'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: bundler-audit
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - "~>"
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '0.9'
|
|
39
|
+
type: :development
|
|
40
|
+
prerelease: false
|
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - "~>"
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '0.9'
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: rake
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
requirements:
|
|
50
|
+
- - "~>"
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '13.2'
|
|
53
|
+
type: :development
|
|
54
|
+
prerelease: false
|
|
55
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - "~>"
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '13.2'
|
|
60
|
+
- !ruby/object:Gem::Dependency
|
|
61
|
+
name: rspec
|
|
62
|
+
requirement: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - "~>"
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '3.13'
|
|
67
|
+
type: :development
|
|
68
|
+
prerelease: false
|
|
69
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
70
|
+
requirements:
|
|
71
|
+
- - "~>"
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: '3.13'
|
|
74
|
+
- !ruby/object:Gem::Dependency
|
|
75
|
+
name: rubocop
|
|
76
|
+
requirement: !ruby/object:Gem::Requirement
|
|
77
|
+
requirements:
|
|
78
|
+
- - "~>"
|
|
79
|
+
- !ruby/object:Gem::Version
|
|
80
|
+
version: '1.80'
|
|
81
|
+
type: :development
|
|
82
|
+
prerelease: false
|
|
83
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - "~>"
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: '1.80'
|
|
88
|
+
- !ruby/object:Gem::Dependency
|
|
89
|
+
name: simplecov
|
|
90
|
+
requirement: !ruby/object:Gem::Requirement
|
|
91
|
+
requirements:
|
|
92
|
+
- - "~>"
|
|
93
|
+
- !ruby/object:Gem::Version
|
|
94
|
+
version: '0.22'
|
|
95
|
+
type: :development
|
|
96
|
+
prerelease: false
|
|
97
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
98
|
+
requirements:
|
|
99
|
+
- - "~>"
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '0.22'
|
|
102
|
+
- !ruby/object:Gem::Dependency
|
|
103
|
+
name: webmock
|
|
104
|
+
requirement: !ruby/object:Gem::Requirement
|
|
105
|
+
requirements:
|
|
106
|
+
- - "~>"
|
|
107
|
+
- !ruby/object:Gem::Version
|
|
108
|
+
version: '3.25'
|
|
109
|
+
type: :development
|
|
110
|
+
prerelease: false
|
|
111
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
112
|
+
requirements:
|
|
113
|
+
- - "~>"
|
|
114
|
+
- !ruby/object:Gem::Version
|
|
115
|
+
version: '3.25'
|
|
116
|
+
description: |
|
|
117
|
+
A typed, generated-contract Ruby client for the complete Fly Machines REST
|
|
118
|
+
API, plus a clearly isolated experimental client for Fly's unstable GraphQL
|
|
119
|
+
control-plane interface.
|
|
120
|
+
email:
|
|
121
|
+
- vito@botta.me
|
|
122
|
+
executables: []
|
|
123
|
+
extensions: []
|
|
124
|
+
extra_rdoc_files: []
|
|
125
|
+
files:
|
|
126
|
+
- CHANGELOG.md
|
|
127
|
+
- CONTRIBUTING.md
|
|
128
|
+
- LICENSE
|
|
129
|
+
- README.md
|
|
130
|
+
- Rakefile
|
|
131
|
+
- contracts/additional_rest/network_policies.json
|
|
132
|
+
- contracts/additional_rest/prometheus.json
|
|
133
|
+
- contracts/graphql/fly_go_operations.json
|
|
134
|
+
- contracts/graphql/flyctl_named_operations.graphql
|
|
135
|
+
- contracts/graphql/flyctl_operations.json
|
|
136
|
+
- contracts/graphql/official_examples.json
|
|
137
|
+
- contracts/graphql/schema.graphql
|
|
138
|
+
- contracts/graphql/source.json
|
|
139
|
+
- contracts/machines/openapi.headers
|
|
140
|
+
- contracts/machines/openapi.json
|
|
141
|
+
- contracts/machines/source.json
|
|
142
|
+
- contracts/public_surface_inventory.json
|
|
143
|
+
- contracts/research_metadata.json
|
|
144
|
+
- contracts/sources/metrics.html.md
|
|
145
|
+
- contracts/sources/network-policies.html.markerb
|
|
146
|
+
- docs/API.md
|
|
147
|
+
- docs/SURFACES.md
|
|
148
|
+
- lib/fly_io.rb
|
|
149
|
+
- lib/fly_io/client.rb
|
|
150
|
+
- lib/fly_io/configuration.rb
|
|
151
|
+
- lib/fly_io/errors.rb
|
|
152
|
+
- lib/fly_io/generated/additional_operations.json
|
|
153
|
+
- lib/fly_io/generated/graphql_operations.json
|
|
154
|
+
- lib/fly_io/generated/operations.json
|
|
155
|
+
- lib/fly_io/generated/prometheus_operations.json
|
|
156
|
+
- lib/fly_io/generated/schemas.json
|
|
157
|
+
- lib/fly_io/graphql_client.rb
|
|
158
|
+
- lib/fly_io/model.rb
|
|
159
|
+
- lib/fly_io/operation_registry.rb
|
|
160
|
+
- lib/fly_io/redactor.rb
|
|
161
|
+
- lib/fly_io/resources.rb
|
|
162
|
+
- lib/fly_io/resources/base.rb
|
|
163
|
+
- lib/fly_io/response.rb
|
|
164
|
+
- lib/fly_io/schema_registry.rb
|
|
165
|
+
- lib/fly_io/schema_validator.rb
|
|
166
|
+
- lib/fly_io/transport.rb
|
|
167
|
+
- lib/fly_io/version.rb
|
|
168
|
+
- script/check_api_coverage
|
|
169
|
+
- script/fetch_openapi
|
|
170
|
+
- script/generate_api
|
|
171
|
+
homepage: https://github.com/vitobotta/fly_io
|
|
172
|
+
licenses:
|
|
173
|
+
- MIT
|
|
174
|
+
metadata:
|
|
175
|
+
homepage_uri: https://github.com/vitobotta/fly_io
|
|
176
|
+
source_code_uri: https://github.com/vitobotta/fly_io
|
|
177
|
+
changelog_uri: https://github.com/vitobotta/fly_io/blob/main/CHANGELOG.md
|
|
178
|
+
documentation_uri: https://github.com/vitobotta/fly_io/blob/main/docs/API.md
|
|
179
|
+
rubygems_mfa_required: 'true'
|
|
180
|
+
rdoc_options: []
|
|
181
|
+
require_paths:
|
|
182
|
+
- lib
|
|
183
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
184
|
+
requirements:
|
|
185
|
+
- - ">="
|
|
186
|
+
- !ruby/object:Gem::Version
|
|
187
|
+
version: '3.2'
|
|
188
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
189
|
+
requirements:
|
|
190
|
+
- - ">="
|
|
191
|
+
- !ruby/object:Gem::Version
|
|
192
|
+
version: '0'
|
|
193
|
+
requirements: []
|
|
194
|
+
rubygems_version: 4.0.16
|
|
195
|
+
specification_version: 4
|
|
196
|
+
summary: A contract-driven Ruby client for Fly.io control-plane APIs
|
|
197
|
+
test_files: []
|