Skip to content
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

Merged
merged 26 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
236ba26
Change to awk
Kalaiselvi84 Oct 4, 2023
4192499
excluded print and -F
Kalaiselvi84 Oct 5, 2023
59584f8
xargs variation
Kalaiselvi84 Oct 6, 2023
78e6ec5
Merge branch 'main' into sed-to-awk
Kalaiselvi84 Oct 6, 2023
e17aa7d
go-script to replace ref
Kalaiselvi84 Oct 11, 2023
d48fd7e
Part 1 - convert sh into go
Kalaiselvi84 Oct 17, 2023
fb19864
Go script
Kalaiselvi84 Oct 18, 2023
32865db
del exp-openapi
Kalaiselvi84 Oct 18, 2023
b410245
bin file in gitignore
Kalaiselvi84 Oct 18, 2023
a3e64a8
exclude bin file
Kalaiselvi84 Oct 18, 2023
0bb1f7b
Revert yaml files
Kalaiselvi84 Oct 19, 2023
fa74a28
code change for yaml file
Kalaiselvi84 Oct 19, 2023
8117f5e
Merge branch 'main' into sed-to-awk
Kalaiselvi84 Oct 19, 2023
da222d3
mv go-script to scripts dir
Kalaiselvi84 Oct 19, 2023
6b96c71
required changes
Kalaiselvi84 Oct 19, 2023
b678e7e
Merge branch 'main' into sed-to-awk
Kalaiselvi84 Oct 19, 2023
04794df
delete export-openapi.sh
Kalaiselvi84 Oct 19, 2023
5ef1384
delete binary file main
Kalaiselvi84 Oct 19, 2023
98c6e0c
kubectl proxy - log, sleep time, graceful termination
Kalaiselvi84 Oct 20, 2023
5f9f4db
gen-embedded-openapi target for go script, run make gen-install
Kalaiselvi84 Oct 20, 2023
ec09c3d
agones - go mod vendor
Kalaiselvi84 Oct 20, 2023
5e7ea72
tmp dir path change
Kalaiselvi84 Oct 20, 2023
597745d
fix modifyJSONFiles(), make gen-install
Kalaiselvi84 Oct 20, 2023
7f12569
revert go.mod, go.sum and vendor/modules.txt
Kalaiselvi84 Oct 20, 2023
cebc3e2
change path
Kalaiselvi84 Oct 20, 2023
720452e
Merge branch 'main' into sed-to-awk
Kalaiselvi84 Oct 20, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
284 changes: 284 additions & 0 deletions build/exp-openapi/exp-openapi.go
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"
Copy link
Member

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

Copy link
Contributor Author

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


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)
}
3 changes: 3 additions & 0 deletions build/exp-openapi/go.mod
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
13 changes: 12 additions & 1 deletion build/export-openapi.sh
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ mkdir orig
cp *.json orig
rm openapi.json

echo "Compiling replaceRef.go..."
Copy link
Member

Choose a reason for hiding this comment

The 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
Expand All @@ -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
Expand Down
Binary file added build/replaceRef
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to commit the binary to the repository?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's change .gitignore.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Binary file not shown.
56 changes: 56 additions & 0 deletions build/replaceRef.go
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.")
}