diff --git a/scripts/build/copy.go b/scripts/build/copy.go new file mode 100644 index 00000000..c04024e1 --- /dev/null +++ b/scripts/build/copy.go @@ -0,0 +1,65 @@ +package main + +import ( + "context" + "flag" + "fmt" + "strings" +) + +// runCopy implements the `copy --plan ` command. +// It prints all services which have the action "block" in the generation-plan. These services should be copied +// during the generation, to prevent that they will be removed. +func runCopy(_ context.Context, args []string, env Environment) error { + copyFlags := flag.NewFlagSet("copy", flag.ContinueOnError) + copyFlags.SetOutput(env.Err) + + var planPath string + copyFlags.StringVar(&planPath, "plan", "", "path to the generation plan") + if err := copyFlags.Parse(args); err != nil { + return fmt.Errorf("parsing copy flags: %w", err) + } + if len(copyFlags.Args()) != 0 { + return fmt.Errorf("unexpected arguments: %s", strings.Join(copyFlags.Args(), " ")) + } + if planPath == "" { + return fmt.Errorf("--plan is required") + } + + plan, err := readPlan(env.FS, planPath) + if err != nil { + return fmt.Errorf("read generation plan: %w", err) + } + + blockedServices := findBlockedServices(plan) + + return outputResult(env, blockedServices) +} + +// findBlockedServices finds all services in `Plan.Service` which are blocked and returns the service name in a string slice. +func findBlockedServices(plan Plan) []string { + var blockedServices []string + for _, service := range plan.Services { + if service.Action == ActionBlock { + blockedServices = append(blockedServices, service.Service) + } + } + + return blockedServices +} + +// outputResult takes a list of blockedServices and writes service in a separate line into `env.Out`. +func outputResult(env Environment, blockedServices []string) error { + if len(blockedServices) == 0 { + return nil + } + + result := fmt.Sprintf("%s\n", strings.Join(blockedServices, "\n")) + + _, err := fmt.Fprint(env.Out, result) + if err != nil { + return fmt.Errorf("writing result: %w", err) + } + + return nil +} diff --git a/scripts/build/copy_test.go b/scripts/build/copy_test.go new file mode 100644 index 00000000..abf95626 --- /dev/null +++ b/scripts/build/copy_test.go @@ -0,0 +1,266 @@ +package main + +import ( + "bytes" + "context" + "reflect" + "testing" + "testing/fstest" +) + +func TestRunCopy(t *testing.T) { + fixtureFlags := []string{"--plan", "generation-plan.json"} + fixtureGenerationPlan := []byte(`{ + "services": [ + {"service": "remain", "oas_service": "remain-oas", "action": "block"}, + {"service": "foo", "oas_service": "foo-bar", "action": "generate"}, + {"service": "obsolete", "action": "delete"}, + {"service": "bar", "oas_service": "bar-oas", "action": "block"}, + {"service": "service2", "action": "delete"} + ] +}`) + + type args struct { + flags []string + generationPlan []byte + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "success", + args: args{ + flags: fixtureFlags, + generationPlan: fixtureGenerationPlan, + }, + want: "remain\nbar\n", + wantErr: false, + }, + { + name: "no blocked services", + args: args{ + flags: fixtureFlags, + generationPlan: []byte(`{ + "services": [ + {"service": "remain", "oas_service": "remain-oas", "action": "generate"}, + {"service": "foo", "oas_service": "foo-bar", "action": "generate"}, + {"service": "obsolete", "action": "delete"}, + {"service": "bar", "oas_service": "bar-oas", "action": "generate"}, + {"service": "service2", "action": "delete"} + ] +}`), + }, + want: "", + wantErr: false, + }, + { + name: "missing flags", + args: args{ + flags: []string{}, + generationPlan: fixtureGenerationPlan, + }, + want: "", + wantErr: true, + }, + { + name: "flag to invalid generationPlan", + args: args{ + flags: []string{"--plan", "not-existing-file.json"}, + generationPlan: fixtureGenerationPlan, + }, + want: "", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + filesystem := fstest.MapFS{ + "generation-plan.json": { + Data: tt.args.generationPlan, + }, + } + + var stdout bytes.Buffer + env := Environment{ + Out: &stdout, + FS: readOnlyFileSystem{FileSystem: filesystem}, + } + + err := runCopy(context.Background(), tt.args.flags, env) + if (err != nil) != tt.wantErr { + t.Errorf("runCopy() error = %v, wantErr %v", err, tt.wantErr) + } + if got := stdout.String(); got != tt.want { + t.Errorf("runCopy() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFindBlockedServices(t *testing.T) { + type args struct { + plan Plan + } + tests := []struct { + name string + args args + want []string + }{ + { + name: "finds blocked services", + args: args{ + plan: Plan{ + Services: []PlannedService{ + { + Service: "remain", + OASService: "remain-oas", + Action: ActionBlock, + }, + { + Service: "obsolete", + Action: ActionDelete, + }, + { + Service: "foo", + OASService: "foo-bar", + Action: ActionGenerate, + }, + { + Service: "bar", + OASService: "bar-oas", + Action: ActionBlock, + }, + }, + }, + }, + want: []string{"remain", "bar"}, + }, + { + name: "no blocked services", + args: args{ + plan: Plan{ + Services: []PlannedService{ + { + Service: "remain", + OASService: "remain-oas", + Action: ActionGenerate, + }, + { + Service: "obsolete", + OASService: "obsolete-oas", + Action: ActionDelete, + }, + { + Service: "foo", + OASService: "foo-bar", + Action: ActionGenerate, + }, + { + Service: "bar", + OASService: "bar-oas", + Action: ActionDelete, + }, + }, + }, + }, + want: nil, + }, + { + name: "all services are blocked", + args: args{ + plan: Plan{ + Services: []PlannedService{ + { + Service: "remain", + OASService: "remain-oas", + Action: ActionBlock, + }, + { + Service: "obsolete", + OASService: "remain-oas", + Action: ActionBlock, + }, + { + Service: "foo", + OASService: "foo-bar", + Action: ActionBlock, + }, + { + Service: "bar", + OASService: "bar-oas", + Action: ActionBlock, + }, + }, + }, + }, + want: []string{"remain", "obsolete", "foo", "bar"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := findBlockedServices(tt.args.plan); !reflect.DeepEqual(got, tt.want) { + t.Errorf("findBlockedServices() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + blockedServices []string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "outputs blocked services", + args: args{ + blockedServices: []string{ + "remain", + "obsolete", + }, + }, + want: "remain\nobsolete\n", + wantErr: false, + }, + { + name: "empty blocked services", + args: args{ + blockedServices: []string{}, + }, + want: "", + wantErr: false, + }, + { + name: "nil blocked services", + args: args{ + blockedServices: nil, + }, + want: "", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var stdout bytes.Buffer + env := Environment{ + Out: &stdout, + } + + if err := outputResult(env, tt.args.blockedServices); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + + got := stdout.String() + if got != tt.want { + t.Errorf("stdout = %q, want %q", got, tt.want) + } + }) + } +} diff --git a/scripts/build/go.sum b/scripts/build/go.sum new file mode 100644 index 00000000..40e761ae --- /dev/null +++ b/scripts/build/go.sum @@ -0,0 +1,2 @@ +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= diff --git a/scripts/build/main.go b/scripts/build/main.go index 537584a4..57373a27 100644 --- a/scripts/build/main.go +++ b/scripts/build/main.go @@ -111,6 +111,8 @@ func run(ctx context.Context, env Environment) bool { err = runGenerate(ctx, args[1:], env) case "delete": err = runDelete(ctx, args[1:], env) + case "copy": + err = runCopy(ctx, args[1:], env) default: fmt.Fprintf(env.Err, "unknown command %q\n", args[0]) return false diff --git a/scripts/generate-sdk/languages/go.sh b/scripts/generate-sdk/languages/go.sh index b3db5692..0db19126 100644 --- a/scripts/generate-sdk/languages/go.sh +++ b/scripts/generate-sdk/languages/go.sh @@ -121,6 +121,24 @@ generate_go_sdk() { warning="" + # Copy services from the blocklist to prevent missing dependencies in the examples or removal of the service + mkdir -p "${SERVICES_FOLDER}" + while IFS= read -r service; do + echo "Skipping blocklisted service ${service}" + warning+="Skipping blocklisted service ${service}\n" + # If the service exists in the repo, move them inside the service folder + if [ -d "${sdk_services_backup_dir}/${service}" ]; then + echo "Copy existing ${service} package" + cp -r "${sdk_services_backup_dir}/${service}" "${SERVICES_FOLDER}/${service}" + + if [ -f "${SERVICES_FOLDER}/${service}/go.mod" ]; then + (cd "${SERVICES_FOLDER}/${service}" && go work use .) + fi + fi + done < <( + "${ROOT_DIR}/scripts/bin/build" copy --plan "generation-plan.json" + ) + while IFS= read -r -d '' oas_service && IFS= read -r service; do generate_go_service "${ROOT_DIR}/oas/services/${oas_service}" "${service}" done < <( @@ -164,6 +182,14 @@ generate_go_service() { if grep -E "^${service}-${version}$" "${ROOT_DIR}/languages/golang/blocklist.txt"; then echo "Skipping blocklisted API version ${version} of service ${service}" warning+="Skipping blocklisted API version ${version} of service ${service}\n" + + # If the service version exists in the repo, move them inside the service folder + if [ -d "${sdk_services_backup_dir}/${service}/${version}api" ]; then + echo "Copy existing ${service}/${version}api package" + mkdir -p "${SERVICES_FOLDER}/${service}" + cp -r "${sdk_services_backup_dir}/${service}/${version}api" "${SERVICES_FOLDER}/${service}/${version}api" + fi + continue fi diff --git a/scripts/generate-sdk/languages/java.sh b/scripts/generate-sdk/languages/java.sh index 95b82375..f9cc528d 100644 --- a/scripts/generate-sdk/languages/java.sh +++ b/scripts/generate-sdk/languages/java.sh @@ -96,6 +96,20 @@ generate_java_sdk() { warning="" + # Copy services from the blocklist to prevent missing dependencies in the examples or removal of the service + mkdir -p "${SERVICES_FOLDER}" + while IFS= read -r service; do + echo "Skipping blocklisted service ${service}" + warning+="Skipping blocklisted service ${service}\n" + # If the service exists in the repo, move them inside the service folder + if [ -d "${sdk_services_backup_dir}/${service}" ]; then + echo "Copy existing ${service} package" + cp -r "${sdk_services_backup_dir}/${service}" "${SERVICES_FOLDER}/${service}" + fi + done < <( + "${ROOT_DIR}/scripts/bin/build" copy --plan "generation-plan.json" + ) + while IFS= read -r -d '' oas_service && IFS= read -r service; do generate_java_service \ "${ROOT_DIR}/oas/services/${oas_service}" \ @@ -139,6 +153,30 @@ generate_java_service(){ if grep -E "^${service}-${version}$" "${ROOT_DIR}/languages/java/blocklist.txt"; then echo "Skipping blocklisted API version ${version} of service ${service}" warning+="Skipping blocklisted API version ${version} of service ${service}\n" + + # If the service version exists in the repo, move them inside the service folder + if [ -d "${sdk_services_backup_dir}/${service}/src/main/java/cloud/stackit/sdk/${service}/${version}api" ]; then + echo "Copy existing ${service}/src/main/java/cloud/stackit/sdk/${service}/${version}api package" + mkdir -p "${SERVICES_FOLDER}/${service}/src/main/java/cloud/stackit/sdk/${service}" + cp -r "${sdk_services_backup_dir}/${service}/src/main/java/cloud/stackit/sdk/${service}/${version}api" "${SERVICES_FOLDER}/${service}/src/main/java/cloud/stackit/sdk/${service}/${version}api" + fi + + # Copy test files for the blocklisted service version + if [ -d "${sdk_services_backup_dir}/${service}/src/test/java/cloud/stackit/sdk/${service}/${version}api" ]; then + mkdir -p "${SERVICES_FOLDER}/${service}/src/test/java/cloud/stackit/sdk/${service}" + cp -r "${sdk_services_backup_dir}/${service}/src/test/java/cloud/stackit/sdk/${service}/${version}api" "${SERVICES_FOLDER}/${service}/src/test/java/cloud/stackit/sdk/${service}/${version}api" + fi + + # Copy the existing build.gradle file if exists and not already in the services folder + if [[ -f "${sdk_services_backup_dir}/${service}/build.gradle" && ! -f "${SERVICES_FOLDER}/${service}/build.gradle" ]]; then + cp "${sdk_services_backup_dir}/${service}/build.gradle" "${SERVICES_FOLDER}/${service}/build.gradle" + fi + + # Copy the existing README.md file if exists and not already in the services folder + if [[ -f "${sdk_services_backup_dir}/${service}/README.md" && ! -f "${SERVICES_FOLDER}/${service}/README.md" ]]; then + cp "${sdk_services_backup_dir}/${service}/README.md" "${SERVICES_FOLDER}/${service}/README.md" + fi + continue fi diff --git a/scripts/generate-sdk/languages/python.sh b/scripts/generate-sdk/languages/python.sh index a4ef4ee3..b131a435 100644 --- a/scripts/generate-sdk/languages/python.sh +++ b/scripts/generate-sdk/languages/python.sh @@ -92,6 +92,22 @@ generate_python_sdk() { extra_props_internal="" fi + warning="" + + # Copy services from the blocklist to prevent missing dependencies in the examples or removal of the service + mkdir -p "${SERVICES_FOLDER}" + while IFS= read -r service; do + echo "Skipping blocklisted service ${service}" + warning+="Skipping blocklisted service ${service}\n" + # If the service exists in the repo, move them inside the service folder + if [ -d "${sdk_services_backup_dir}/${service}" ]; then + echo "Copy existing ${service} package" + cp -r "${sdk_services_backup_dir}/${service}" "${SERVICES_FOLDER}/${service}" + fi + done < <( + "${ROOT_DIR}/scripts/bin/build" copy --plan "generation-plan.json" + ) + # TODO: add to generator below when adding multi-API-version support: # --inline-schema-options "SKIP_SCHEMA_REUSE=true" @@ -106,6 +122,10 @@ generate_python_sdk() { done < <( "${ROOT_DIR}/scripts/bin/build" generate --plan "generation-plan.json" ) + + if [[ -n "$warning" ]]; then + echo -e "\nSome of the services were skipped during creation!\n$warning" + fi } generate_python_service() {