From be01dd3869a5f7439f25e425711653b08895886e Mon Sep 17 00:00:00 2001 From: Daniel Kurzynski Date: Thu, 18 Jun 2020 14:50:46 +0200 Subject: [PATCH] Add schema patch step in go (#1683) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Stephan Aßmus --- cmd/jsonApplyPatch.go | 59 ++++++++++++ cmd/jsonApplyPatch_generated.go | 125 +++++++++++++++++++++++++ cmd/jsonApplyPatch_generated_test.go | 16 ++++ cmd/jsonApplyPatch_test.go | 82 ++++++++++++++++ cmd/piper.go | 1 + go.mod | 1 + go.sum | 2 + resources/metadata/jsonApplyPatch.yaml | 27 ++++++ 8 files changed, 313 insertions(+) create mode 100644 cmd/jsonApplyPatch.go create mode 100644 cmd/jsonApplyPatch_generated.go create mode 100644 cmd/jsonApplyPatch_generated_test.go create mode 100644 cmd/jsonApplyPatch_test.go create mode 100644 resources/metadata/jsonApplyPatch.yaml diff --git a/cmd/jsonApplyPatch.go b/cmd/jsonApplyPatch.go new file mode 100644 index 000000000..28db3dc7a --- /dev/null +++ b/cmd/jsonApplyPatch.go @@ -0,0 +1,59 @@ +package cmd + +import ( + "bytes" + "encoding/json" + "github.com/SAP/jenkins-library/pkg/log" + "github.com/SAP/jenkins-library/pkg/piperutils" + "github.com/SAP/jenkins-library/pkg/telemetry" + "github.com/evanphx/json-patch" +) + +func jsonApplyPatch(config jsonApplyPatchOptions, telemetryData *telemetry.CustomData) { + err := runJsonApplyPatch(&config, &piperutils.Files{}) + if err != nil { + log.Entry().WithError(err).Fatal("step execution failed") + } +} + +func runJsonApplyPatch(config *jsonApplyPatchOptions, fileUtils piperutils.FileUtils) error { + schema, err := fileUtils.FileRead(config.Input) + if err != nil { + return nil + } + + patchFile, err := fileUtils.FileRead(config.Patch) + if err != nil { + return nil + } + patcher, err := jsonpatch.DecodePatch(patchFile) + if err != nil { + return err + } + + patchedSchema, err := patcher.Apply(schema) + if err != nil { + return err + } + + formattedJson, err := formatJson(patchedSchema) + if err != nil { + // Ignore error and just use original result. + formattedJson = patchedSchema + } + + err = fileUtils.FileWrite(config.Output, formattedJson, 0644) + if err != nil { + return err + } + return nil +} + +func formatJson(input []byte) ([]byte, error) { + var output bytes.Buffer + err := json.Indent(&output, input, "", " ") + if err != nil { + return nil, err + } + return output.Bytes(), nil +} diff --git a/cmd/jsonApplyPatch_generated.go b/cmd/jsonApplyPatch_generated.go new file mode 100644 index 000000000..98a1b40b1 --- /dev/null +++ b/cmd/jsonApplyPatch_generated.go @@ -0,0 +1,125 @@ +// Code generated by piper's step-generator. DO NOT EDIT. + +package cmd + +import ( + "fmt" + "os" + "time" + + "github.com/SAP/jenkins-library/pkg/config" + "github.com/SAP/jenkins-library/pkg/log" + "github.com/SAP/jenkins-library/pkg/telemetry" + "github.com/spf13/cobra" +) + +type jsonApplyPatchOptions struct { + Input string `json:"input,omitempty"` + Patch string `json:"patch,omitempty"` + Output string `json:"output,omitempty"` +} + +// JsonApplyPatchCommand Patches a json with a patch file +func JsonApplyPatchCommand() *cobra.Command { + const STEP_NAME = "jsonApplyPatch" + + metadata := jsonApplyPatchMetadata() + var stepConfig jsonApplyPatchOptions + var startTime time.Time + + var createJsonApplyPatchCmd = &cobra.Command{ + Use: STEP_NAME, + Short: "Patches a json with a patch file", + Long: `This steps patches a json file with patch file using the json patch standard. +This step can, e.g., be used if there is a json schema which needs to be patched.`, + PreRunE: func(cmd *cobra.Command, args []string) error { + startTime = time.Now() + log.SetStepName(STEP_NAME) + log.SetVerbose(GeneralConfig.Verbose) + + path, _ := os.Getwd() + fatalHook := &log.FatalHook{CorrelationID: GeneralConfig.CorrelationID, Path: path} + log.RegisterHook(fatalHook) + + err := PrepareConfig(cmd, &metadata, STEP_NAME, &stepConfig, config.OpenPiperFile) + if err != nil { + return err + } + + if len(GeneralConfig.HookConfig.SentryConfig.Dsn) > 0 { + sentryHook := log.NewSentryHook(GeneralConfig.HookConfig.SentryConfig.Dsn, GeneralConfig.CorrelationID) + log.RegisterHook(&sentryHook) + } + + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + telemetryData := telemetry.CustomData{} + telemetryData.ErrorCode = "1" + handler := func() { + telemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds()) + telemetry.Send(&telemetryData) + } + log.DeferExitHandler(handler) + defer handler() + telemetry.Initialize(GeneralConfig.NoTelemetry, STEP_NAME) + jsonApplyPatch(stepConfig, &telemetryData) + telemetryData.ErrorCode = "0" + log.Entry().Info("SUCCESS") + }, + } + + addJsonApplyPatchFlags(createJsonApplyPatchCmd, &stepConfig) + return createJsonApplyPatchCmd +} + +func addJsonApplyPatchFlags(cmd *cobra.Command, stepConfig *jsonApplyPatchOptions) { + cmd.Flags().StringVar(&stepConfig.Input, "input", os.Getenv("PIPER_input"), "File path to the json file which schould be patched.") + cmd.Flags().StringVar(&stepConfig.Patch, "patch", os.Getenv("PIPER_patch"), "File path to the patch which should be applied to the json file.") + cmd.Flags().StringVar(&stepConfig.Output, "output", os.Getenv("PIPER_output"), "File path to destination of the patched json file.") + + cmd.MarkFlagRequired("input") + cmd.MarkFlagRequired("patch") + cmd.MarkFlagRequired("output") +} + +// retrieve step metadata +func jsonApplyPatchMetadata() config.StepData { + var theMetaData = config.StepData{ + Metadata: config.StepMetadata{ + Name: "jsonApplyPatch", + Aliases: []config.Alias{}, + }, + Spec: config.StepSpec{ + Inputs: config.StepInputs{ + Parameters: []config.StepParameters{ + { + Name: "input", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{}, + }, + { + Name: "patch", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{}, + }, + { + Name: "output", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{}, + }, + }, + }, + }, + } + return theMetaData +} diff --git a/cmd/jsonApplyPatch_generated_test.go b/cmd/jsonApplyPatch_generated_test.go new file mode 100644 index 000000000..dcbbf2f7d --- /dev/null +++ b/cmd/jsonApplyPatch_generated_test.go @@ -0,0 +1,16 @@ +package cmd + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestJsonApplyPatchCommand(t *testing.T) { + + testCmd := JsonApplyPatchCommand() + + // only high level testing performed - details are tested in step generation procudure + assert.Equal(t, "jsonApplyPatch", testCmd.Use, "command name incorrect") + +} diff --git a/cmd/jsonApplyPatch_test.go b/cmd/jsonApplyPatch_test.go new file mode 100644 index 000000000..32e2d3c4c --- /dev/null +++ b/cmd/jsonApplyPatch_test.go @@ -0,0 +1,82 @@ +package cmd + +import ( + "github.com/SAP/jenkins-library/pkg/mock" + "github.com/stretchr/testify/assert" + "testing" +) + +var schema = []byte(` +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SAP Cloud SDK pipeline_config JSON schema", + "type": "object", + "properties": { + "general": { + "type": [ + "object", + "null" + ], + "properties": { + "productiveBranch": { + "type": "string", + "default": "master" + } + } + } + } +} +`) + +var patch = []byte(` +[ + { + "op": "add", + "path": "/properties/general/properties/gitCredentialsId", + "value": { + "type": "string" + } + } +] +`) + +var patchedSchema = []byte(`{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SAP Cloud SDK pipeline_config JSON schema", + "type": "object", + "properties": { + "general": { + "type": [ + "object", + "null" + ], + "properties": { + "productiveBranch": { + "type": "string", + "default": "master" + }, + "gitCredentialsId": { + "type": "string" + } + } + } + } +}`) + +func TestSchemaPatch(t *testing.T) { + t.Run("default", func(t *testing.T) { + options := jsonApplyPatchOptions{ + Input: "schema.json", + Patch: "patch.json", + Output: "output.json", + } + filesMock := mock.FilesMock{} + filesMock.AddFile("schema.json", schema) + filesMock.AddFile("patch.json", patch) + err := runJsonApplyPatch(&options, &filesMock) + assert.NoError(t, err) + patchedSchemaResult, err := filesMock.FileRead("output.json") + assert.NoError(t, err) + assert.JSONEq(t, string(patchedSchema), string(patchedSchemaResult)) + }) +} diff --git a/cmd/piper.go b/cmd/piper.go index b5cebbbab..e04928d58 100644 --- a/cmd/piper.go +++ b/cmd/piper.go @@ -88,6 +88,7 @@ func Execute() { rootCmd.AddCommand(GctsDeployCommand()) rootCmd.AddCommand(MalwareExecuteScanCommand()) rootCmd.AddCommand(GctsCloneRepositoryCommand()) + rootCmd.AddCommand(JsonApplyPatchCommand()) addRootFlags(rootCmd) if err := rootCmd.Execute(); err != nil { diff --git a/go.mod b/go.mod index 4ba0a0668..290dc9e54 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect github.com/bmatcuk/doublestar v1.3.1 github.com/containerd/containerd v1.3.4 // indirect + github.com/evanphx/json-patch v4.5.0+incompatible github.com/getsentry/sentry-go v0.6.1 github.com/ghodss/yaml v1.0.0 github.com/go-git/go-git/v5 v5.1.0 diff --git a/go.sum b/go.sum index 70a1505fb..e58fd3a11 100644 --- a/go.sum +++ b/go.sum @@ -253,6 +253,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= +github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= diff --git a/resources/metadata/jsonApplyPatch.yaml b/resources/metadata/jsonApplyPatch.yaml new file mode 100644 index 000000000..8da599617 --- /dev/null +++ b/resources/metadata/jsonApplyPatch.yaml @@ -0,0 +1,27 @@ +metadata: + name: jsonApplyPatch + description: Patches a json with a patch file + longDescription: |- + This steps patches a json file with patch file using the json patch standard. + This step can, e.g., be used if there is a json schema which needs to be patched. +spec: + inputs: + params: + - name: input + type: string + description: File path to the json file which schould be patched. + mandatory: true + scope: + - PARAMETERS + - name: patch + type: string + description: File path to the patch which should be applied to the json file. + mandatory: true + scope: + - PARAMETERS + - name: output + type: string + description: File path to destination of the patched json file. + mandatory: true + scope: + - PARAMETERS