Skip to content

Commit 4a702bb

Browse files
authored
migrate to OpenAPI.jl from Swagger.jl (#55)
* migrate to OpenAPI.jl from Swagger.jl * changes for IntOrString support * regenerate with const disambiguation * update julia lowerbound for github ci
1 parent f910fff commit 4a702bb

File tree

1,562 files changed

+98265
-89485
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

1,562 files changed

+98265
-89485
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
fail-fast: false
1313
matrix:
1414
version:
15-
- '1.3'
15+
- '1.6'
1616
- '1' # automatically expands to the latest stable 1.x release of Julia
1717
- nightly
1818
os:

Project.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,21 @@ authors = ["Tanmay Mohapatra <[email protected]>"]
44
keywords = ["kubernetes", "client"]
55
license = "MIT"
66
desc = "Julia Kubernetes Client"
7-
version = "0.6.2"
7+
version = "0.7.0"
88

99
[deps]
1010
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
1111
Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
1212
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
1313
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
14-
Swagger = "2d69052b-6a58-5cd9-a030-a48559c324ac"
14+
OpenAPI = "d5e62ea6-ddf3-4d43-8e4c-ad5e6c8bfd7d"
15+
TimeZones = "f269a46b-ccf7-5d73-abea-4c690281aa53"
1516

1617
[compat]
1718
Downloads = "1"
18-
Swagger = "0.3"
19+
OpenAPI = "0.1"
1920
JSON = "0.21"
21+
TimeZones = "1"
2022
julia = "1"
2123

2224
[extras]

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ end
6464
E.g.:
6565

6666
```julia
67-
watch(ctx, list, :Pod; resourceVersion=19451) do stream
67+
watch(ctx, list, :Pod; resource_version=19451) do stream
6868
for event in stream
6969
@info("got event", event)
7070
end

gen/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
## Generate OpenAPI Client
2+
3+
Use the bundled `generate.sh` script to generate OpenAPI client implementations.
4+
5+
- Fetch the OpenAPI v2 (Swagger) specification from the `/openapi/v2` endpoint of a running k8s api server
6+
- Ensure the k8s server has all the required CRDs installed
7+
- The specification file must be named `swagger.json`. It can be stored in any location, but store it in the `spec` folder if you wish to update the Kuber.jl package itself
8+
- The k8s OpenAPI spec uses a custom `int-or-string` format, that needs to be tweaked in the specification to be able to generate it correctly (see: https://github.com/kubernetes/kube-openapi/issues/52)
9+
- Open the downloaded spec and change the type of `io.k8s.apimachinery.pkg.util.intstr.IntOrString` from `string` to `object`
10+
- Ensure:
11+
- `julia` is in `PATH` or set in environment variable `JULIA`
12+
- `java` is in `PATH` or set in environment variable `JAVA_CMD`
13+
- `openapi-generator-cli.jar` is in `CLASSPATH` or set in environment variable `JAVA_CMD`
14+
- package directory is writable
15+
- Run `generate.sh <path to kubernetes specifications folder> [output path]`
16+
- output path is optional, if not specified `../src/ApiImpl/api` folder relative to this script will be used
17+
- Note:
18+
- the `api` folder in the output path will be renamed to `api_bak`
19+
- existing `api_bak` folder if any in output folder will be deleted

gen/config.json

Lines changed: 0 additions & 3 deletions
This file was deleted.

gen/detect_apis_and_types.jl

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
const rx_apiname = r"^struct ([a-zA-Z0-9_]*) <: OpenAPI.APIClientImpl$"
2+
const rx_apipath = r"^_ctx = OpenAPI\.Clients\.Ctx\(_api\.client, \"[A-Z]+\", [_a-zA-Z0-9]+, .*\"([0-9a-z\/\._{}]+)\", .*\)$"
3+
const rx_validapipath = r"^\/apis\/.+\/.+\/.*$"
4+
const rx_coreapipath = r"^\/api\/(v[1-9])\/.*$"
5+
const rx_apisapipath = r"^\/(apis)\/$"
6+
const rx_methodexports = r"^function _oacinternal_([a-zA-Z0-9_-]+)\(_api::.*\)$"
7+
8+
const rx_model_api_returntype = r"^Regex\(.*\) => ([A-Za-z0-9]+),$"
9+
const rx_model_api_bodytype = r"^function _oacinternal_.*\(_api::.*, body::([A-Za-z0-9]+).*\)$"
10+
11+
const rx_model_spec_name_from_docstring = r"^@doc raw\"\"\"([a-zA-Z\.0-9-_]+)$"
12+
const rx_model_name_from_filename = r"model_([a-zA-Z0-9]+).jl"
13+
const rx_model_name_from_modelfile = r"^.* # spec type: Union{ Nothing, ([A-Za-z0-9]+) }$"
14+
const rx_model_name_from_modelfile_vector = r"^.* # spec type: Union{ Nothing, Vector{([A-Za-z0-9]+)} }$"
15+
16+
# function to_snake_case(camel_case_str::String)
17+
# s = replace(camel_case_str, r"([A-Z]+)([A-Z][a-z])" => s"\1_\2")
18+
# s = replace(s, r"([a-z\d])([A-Z])" => s"\1_\2")
19+
# replace(lowercase(s), r"[_\s]+" => "_")
20+
# end
21+
22+
function to_snake_case(camel_case_str::String)
23+
iob = IOBuffer()
24+
for c in camel_case_str
25+
if isuppercase(c)
26+
(iob.size > 0) && write(iob, '_')
27+
write(iob, lowercase(c))
28+
else
29+
write(iob, c)
30+
end
31+
end
32+
String(take!(iob))
33+
end
34+
35+
function detect_api_map(api_file::String)
36+
modeldir = abspath(joinpath(dirname(api_file), "..", "models"))
37+
apiname = ""
38+
apipaths = String[]
39+
methods = String[]
40+
models = String[]
41+
for line in eachline(api_file)
42+
line = strip(line)
43+
match_apiname = match(rx_apiname, line)
44+
match_apipath = match(rx_apipath, line)
45+
match_methodexports = match(rx_methodexports, line)
46+
match_model_api_returntype = match(rx_model_api_returntype, line)
47+
match_model_api_bodytype = match(rx_model_api_bodytype, line)
48+
if !isnothing(match_apiname)
49+
apiname = match_apiname.captures[1]
50+
elseif !isnothing(match_apipath)
51+
apipath = match_apipath.captures[1]
52+
if !isnothing(match(rx_validapipath, apipath))
53+
pathparts = split(apipath, '/'; keepempty=false)[2:3]
54+
push!(apipaths, join(pathparts, '/'))
55+
elseif !isnothing(match(rx_coreapipath, apipath))
56+
push!(apipaths, match(rx_coreapipath, apipath).captures[1])
57+
elseif !isnothing(match(rx_apisapipath, apipath))
58+
push!(apipaths, match(rx_apisapipath, apipath).captures[1])
59+
end
60+
elseif !isnothing(match_methodexports)
61+
push!(methods, match_methodexports.captures[1])
62+
elseif !isnothing(match_model_api_returntype)
63+
push!(models, match_model_api_returntype.captures[1])
64+
elseif !isnothing(match_model_api_bodytype)
65+
push!(models, match_model_api_bodytype.captures[1])
66+
end
67+
end
68+
unique!(apipaths)
69+
filter!(!isempty, apipaths)
70+
unique!(methods)
71+
filter!(!isempty, methods)
72+
unique!(models)
73+
filter!(!isempty, models)
74+
filter!(x->isfile(joinpath(modeldir, string("model_", x, ".jl"))), models)
75+
@debug("detect_api_map", apiname, apipaths, methods, models)
76+
return apiname, apipaths, methods, models
77+
end
78+
79+
function detect_model_map(model_file::String)
80+
model_spec_name = ""
81+
dependent_models = String[]
82+
for line in eachline(model_file)
83+
line = strip(line)
84+
match_modelname = match(rx_model_spec_name_from_docstring, line)
85+
match_dependent_modelname = match(rx_model_name_from_modelfile, line)
86+
match_dependent_modelname_from_vector = match(rx_model_name_from_modelfile_vector, line)
87+
if !isnothing(match_modelname)
88+
model_spec_name = match_modelname.captures[1]
89+
elseif !isnothing(match_dependent_modelname_from_vector)
90+
push!(dependent_models, match_dependent_modelname_from_vector.captures[1])
91+
elseif !isnothing(match_dependent_modelname)
92+
push!(dependent_models, match_dependent_modelname.captures[1])
93+
end
94+
end
95+
96+
return model_spec_name, dependent_models
97+
end
98+
99+
function detect_api_and_type_maps(apiimpl_dir::String)
100+
apis_path = joinpath(apiimpl_dir, "api", "apis")
101+
models_path = joinpath(apiimpl_dir, "api", "models")
102+
103+
# maps api spec names to generated names, e.g. "settings.k8s.io/v1alpha1" => "SettingsV1alpha1Api"
104+
api_names = Dict{String,String}()
105+
# maps model generated names to spec names, e.g. "IoK8sApiCoreV1Pod" => "io.k8s.api.core.v1.Pod"
106+
model_names = Dict{String, String}()
107+
api_method_map = Dict{String, Vector{Pair{String,String}}}()
108+
api_models_map = Dict{String, Vector{String}}()
109+
model_models_map = Dict{String, Vector{String}}()
110+
111+
for file in readdir(models_path)
112+
@debug("detect_api_and_type_maps", model_file=file)
113+
if endswith(file, ".jl")
114+
modelname = match(rx_model_name_from_filename, file).captures[1]
115+
model_spec_name, dependent_models = detect_model_map(joinpath(models_path, file))
116+
if !isempty(model_spec_name)
117+
model_names[modelname] = model_spec_name
118+
end
119+
if !isempty(dependent_models)
120+
deps = get!(model_models_map, modelname, String[])
121+
append!(deps, dependent_models)
122+
end
123+
end
124+
end
125+
126+
for file in readdir(apis_path)
127+
@debug("detect_api_and_type_maps", api_file=file)
128+
if endswith(file, ".jl")
129+
apiname, apipaths, methods, models = detect_api_map(joinpath(apis_path, file))
130+
apiname_snake_case = to_snake_case(replace(apiname, r"Api$" => ""))
131+
if !isempty(apiname) && !isempty(apipaths) && length(apipaths) == 1
132+
apipath = apipaths[1]
133+
if !isempty(apipath)
134+
api_names[apipath] = apiname
135+
end
136+
for method in methods
137+
args = string("(_api::Kubernetes.", apiname, ", args...; kwargs...)")
138+
to_name = replace(method, string("_", apiname_snake_case) => "")
139+
to_method = string(replace(method, string("_", apiname_snake_case) => ""), args)
140+
from_method = string("Kubernetes.", method, args)
141+
map_for_name = get!(api_method_map, to_name) do
142+
Pair{String,String}[]
143+
end
144+
push!(map_for_name, to_method => from_method)
145+
end
146+
map_for_models = get!(api_models_map, apiname) do
147+
String[]
148+
end
149+
append!(map_for_models, models)
150+
end
151+
end
152+
end
153+
154+
# add models of the unversioned ApisApi to all api-model maps
155+
apisapi_models = get(api_models_map, "ApisApi", String[])
156+
for (_, models) in api_models_map
157+
union!(models, apisapi_models)
158+
end
159+
160+
# add all dependent models to the api-model map
161+
for (_, models) in api_models_map
162+
all_resolved = false
163+
while !all_resolved
164+
modelset = Set(models)
165+
initial_model_count = length(modelset)
166+
for model in models
167+
dependent_models = get(model_models_map, model, String[])
168+
union!(modelset, dependent_models)
169+
end
170+
empty!(models)
171+
append!(models, collect(modelset))
172+
if length(modelset) == initial_model_count
173+
all_resolved = true
174+
end
175+
end
176+
end
177+
178+
open(joinpath(apiimpl_dir, "api_versions.jl"), "w") do f
179+
println(f, "const APIVersionMap = Dict(")
180+
for (n,v) in api_names
181+
println(f, " \"$n\" => \"$v\",")
182+
end
183+
println(f, ")")
184+
for (to_name, map_for_name) in api_method_map
185+
println(f, "")
186+
println(f, "# ", to_name)
187+
for pair in map_for_name
188+
println(f, pair[1], " = ", pair[2])
189+
end
190+
end
191+
end
192+
193+
open(joinpath(apiimpl_dir, "api_typemap.jl"), "w") do f
194+
println(f, "module Typedefs")
195+
println(f, " using ..Kubernetes")
196+
for (apiname, models) in api_models_map
197+
println(f, " module ", replace(apiname, r"Api$" => ""))
198+
println(f, " using ..Kubernetes")
199+
for model in models
200+
model_spec_name = last(split(model_names[model], '.'))
201+
println(f, " const ", model_spec_name, " = Kubernetes.", model)
202+
end
203+
println(f, " end")
204+
end
205+
println(f, "end")
206+
end
207+
end
208+
209+
@assert length(ARGS) == 1
210+
detect_api_and_type_maps(ARGS[1])

0 commit comments

Comments
 (0)