-
Notifications
You must be signed in to change notification settings - Fork 800
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Convert shell script to Go #3413
Changes from 6 commits
236ba26
4192499
59584f8
78e6ec5
e17aa7d
d48fd7e
fb19864
32865db
b410245
a3e64a8
0bb1f7b
fa74a28
8117f5e
da222d3
6b96c71
b678e7e
04794df
5ef1384
98c6e0c
5f9f4db
ec09c3d
5e7ea72
597745d
7f12569
cebc3e2
720452e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,284 @@ | ||
// Copyright 2023 Google LLC All Rights Reserved. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
// Package main implements a program to convert from export-openapi.sh to Go script. | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"io" | ||
"log" | ||
"net/http" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"strings" | ||
) | ||
|
||
func main() { | ||
tmpDir := "../tmp" | ||
|
||
if _, err := os.Stat(tmpDir); err == nil { | ||
err = os.RemoveAll(tmpDir) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
} | ||
|
||
err := os.MkdirAll(tmpDir, os.ModePerm) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
// Start the kubectl proxy. | ||
cmd := exec.Command("kubectl", "proxy") | ||
err = cmd.Start() | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
// Make an HTTP request to fetch the OpenAPI JSON. | ||
resp, err := http.Get("http://127.0.0.1:8001/openapi/v2") | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
defer resp.Body.Close() | ||
|
||
// Create a file to store the OpenAPI JSON in the `tmp` directory. | ||
openapiFile := filepath.Join(tmpDir, "openapi.json") | ||
file, err := os.Create(openapiFile) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
defer file.Close() | ||
|
||
// Copy the HTTP response body to the file. | ||
_, err = io.Copy(file, resp.Body) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
// Remove specified fields from the OpenAPI JSON. | ||
err = removeFields(openapiFile) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
// Read the modified OpenAPI JSON file. | ||
file, err = os.Open(openapiFile) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
defer file.Close() | ||
|
||
var openAPI map[string]interface{} | ||
err = json.NewDecoder(file).Decode(&openAPI) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
doExpand("io.k8s.api.core.v1.PodTemplateSpec", tmpDir, openAPI) | ||
|
||
objectMetaPath := filepath.Join(tmpDir, "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta.json") | ||
if err := makeCreationTimestampNullable(objectMetaPath); err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
intOrStringPath := filepath.Join(tmpDir, "io.k8s.apimachinery.pkg.util.intstr.IntOrString.json") | ||
if err := modifyIntOrString(intOrStringPath); err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
podSpecPath := filepath.Join(tmpDir, "io.k8s.api.core.v1.PodSpec.json") | ||
if err := escapeDoubleBackslashes(podSpecPath); err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
} | ||
|
||
func removeFields(filename string) error { | ||
file, err := os.Open(filename) | ||
if err != nil { | ||
return err | ||
} | ||
defer file.Close() | ||
|
||
var openAPI map[string]interface{} | ||
err = json.NewDecoder(file).Decode(&openAPI) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
keysToRemove := []string{ | ||
"x-kubernetes-patch-strategy", | ||
"x-kubernetes-patch-merge-key", | ||
"x-kubernetes-list-type", // ******DOUBLE CHECK - this key is present in two places on openapi.json file | ||
"x-kubernetes-group-version-kind", | ||
"x-kubernetes-list-map-keys", | ||
"x-kubernetes-unions", | ||
} | ||
|
||
removeKeysRecursively(openAPI, keysToRemove) | ||
|
||
file, err = os.Create(filename) | ||
if err != nil { | ||
return err | ||
} | ||
defer file.Close() | ||
|
||
err = json.NewEncoder(file).Encode(openAPI) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func removeKeysRecursively(obj interface{}, keysToRemove []string) { | ||
switch v := obj.(type) { | ||
case map[string]interface{}: | ||
for _, key := range keysToRemove { | ||
delete(v, key) | ||
} | ||
for _, value := range v { | ||
removeKeysRecursively(value, keysToRemove) | ||
} | ||
case []interface{}: | ||
for _, item := range v { | ||
removeKeysRecursively(item, keysToRemove) | ||
} | ||
} | ||
} | ||
|
||
func doExpand(key, tmpDir string, openapi map[string]interface{}) { | ||
log.Println("Processing", key) | ||
|
||
// Extract and save the definitions | ||
definitions := openapi["definitions"].(map[string]interface{}) | ||
section, exists := definitions[key] | ||
if !exists { | ||
log.Printf("Key %s not found in definitions\n", key) | ||
return | ||
} | ||
|
||
sectionJSON, err := json.MarshalIndent(section, "", " ") | ||
if err != nil { | ||
log.Println("Error marshaling section:", err) | ||
return | ||
} | ||
|
||
err = os.WriteFile(filepath.Join(tmpDir, key+".json"), sectionJSON, 0o644) | ||
if err != nil { | ||
log.Println("Error writing to file:", err) | ||
return | ||
} | ||
|
||
// Recursively search for $ref values | ||
var children []string | ||
err = searchForRefs(section, &children) | ||
if err != nil { | ||
log.Println("Error searching for references:", err) | ||
return | ||
} | ||
|
||
for _, child := range children { | ||
doExpand(child, tmpDir, openapi) | ||
} | ||
} | ||
|
||
func searchForRefs(obj interface{}, refs *[]string) error { | ||
switch v := obj.(type) { | ||
case map[string]interface{}: | ||
if ref, ok := v["$ref"]; ok { | ||
refStr := ref.(string) | ||
refStr = strings.TrimPrefix(refStr, "#/definitions/") | ||
*refs = append(*refs, refStr) | ||
} | ||
|
||
for _, value := range v { | ||
err := searchForRefs(value, refs) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
case []interface{}: | ||
for _, item := range v { | ||
err := searchForRefs(item, refs) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func makeCreationTimestampNullable(filePath string) error { | ||
var obj map[string]interface{} | ||
|
||
data, err := os.ReadFile(filePath) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if err := json.Unmarshal(data, &obj); err != nil { | ||
return err | ||
} | ||
|
||
if properties, ok := obj["properties"].(map[string]interface{}); ok { | ||
if timestamp, ok := properties["creationTimestamp"].(map[string]interface{}); ok { | ||
timestamp["nullable"] = true | ||
} | ||
} | ||
|
||
modifiedData, err := json.MarshalIndent(obj, "", " ") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return os.WriteFile(filePath, modifiedData, 0o644) | ||
} | ||
|
||
func modifyIntOrString(filePath string) error { | ||
var obj map[string]interface{} | ||
|
||
data, err := os.ReadFile(filePath) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if err := json.Unmarshal(data, &obj); err != nil { | ||
return err | ||
} | ||
|
||
obj["x-kubernetes-int-or-string"] = true | ||
delete(obj, "type") | ||
|
||
modifiedData, err := json.MarshalIndent(obj, "", " ") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return os.WriteFile(filePath, modifiedData, 0o644) | ||
} | ||
|
||
// DOUBLE CHECK | ||
func escapeDoubleBackslashes(filePath string) error { | ||
data, err := os.ReadFile(filePath) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
modifiedData := strings.ReplaceAll(string(data), "\\\\", "\\\\\\\\") | ||
return os.WriteFile(filePath, []byte(modifiedData), 0o644) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module github.com/agones/agones/build/exp-openapi | ||
|
||
go 1.22 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -73,6 +73,12 @@ mkdir orig | |
cp *.json orig | ||
rm openapi.json | ||
|
||
echo "Compiling replaceRef.go..." | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know you aren't there yet, but once we have a comprehensive go script, we can delete export-openapi.sh |
||
go build -o /go/src/agones.dev/agones/build/replaceRef /go/src/agones.dev/agones/build/replaceRef.go | ||
if [[ ! -x "/go/src/agones.dev/agones/build/replaceRef" ]]; then | ||
echo "Failed to compile replaceRef.go" | ||
exit 1 | ||
fi | ||
for f in *.json; do | ||
echo "Expanding $f" | ||
# remove description, because there is usually another description when at its replacement point | ||
|
@@ -84,7 +90,12 @@ for f in *.json; do | |
contents=$(cat "$f" | jq 'del(.description)' | sed 's/\\n/\\\\n/g' | sed '$d' | sed '1d' | sed ':a;N;$!ba;s/\n/\\n/g' | sed 's/\$/\\$/g' | sed 's/\&/\\&/g' | sed 's@\"@\\"@g') | ||
ref=$(basename "$f" .json) | ||
|
||
find -maxdepth 1 -name '*.json' | xargs sed -i 's@"$ref": "#/definitions/'"$ref"'"@'"$contents"'@g' | ||
echo "Ref: $ref" | ||
echo "Contents: $contents" | ||
|
||
echo "Processing file: $f" | ||
/go/src/agones.dev/agones/build/replaceRef "$f" "$ref" "$contents" | ||
|
||
done | ||
|
||
# convert the ones you want to include via helm to yaml | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to commit the binary to the repository? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's change .gitignore. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. build/replaceRef is a piece of Bash script conversion into Go code, but our focus is on converting the whole file to Go code. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// Copyright 2023 Google LLC All Rights Reserved. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
// Package main implements a program that replaces ref with contents in the given json files | ||
package main | ||
|
||
import ( | ||
"log" | ||
markmandel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"os" | ||
"strings" | ||
) | ||
|
||
func main() { | ||
if len(os.Args) != 4 { | ||
log.Println("Usage: replaceRef <jsonFilePath> <ref> <contents>") | ||
return | ||
} | ||
|
||
jsonFilePath := os.Args[1] | ||
ref := os.Args[2] | ||
contents := os.Args[3] | ||
|
||
processJSON(jsonFilePath, ref, contents) | ||
markmandel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
func processJSON(filename, ref, contents string) { | ||
log.Println("Processing JSON file:", filename) | ||
|
||
file, err := os.ReadFile(filename) | ||
if err != nil { | ||
log.Println("Error reading:", filename, err) | ||
return | ||
} | ||
|
||
searchPattern := "\"" + ref + "\": \"#/definitions/" + ref + "\"" | ||
markmandel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
modifiedData := strings.ReplaceAll(string(file), searchPattern, contents) | ||
|
||
err = os.WriteFile(filename, []byte(modifiedData), 0o644) | ||
if err != nil { | ||
log.Println("Error writing:", filename, err) | ||
return | ||
} | ||
|
||
log.Println("JSON file processed successfully.") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason we can't use
ioutil.TempFile
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I am correct
lint
isn't happy with ioutil package