diff --git a/go.mod b/go.mod index 1098f57d8..741df18e2 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/jedib0t/go-pretty/v6 v6.4.4 github.com/joshdk/go-junit v0.0.0-20210226021600-6145f504ca0d github.com/mitchellh/go-homedir v1.1.0 - github.com/newrelic/newrelic-client-go/v2 v2.21.0 + github.com/newrelic/newrelic-client-go/v2 v2.22.1 github.com/pkg/errors v0.9.1 github.com/shirou/gopsutil/v3 v3.23.9 github.com/sirupsen/logrus v1.9.0 diff --git a/go.sum b/go.sum index 281fbcd36..811f3dc89 100644 --- a/go.sum +++ b/go.sum @@ -133,6 +133,10 @@ github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/z github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= github.com/newrelic/newrelic-client-go/v2 v2.21.0 h1:SZ6FEwbLG7nzCJaT402dWPSDvqVegBoCEX7XsAEd3y8= github.com/newrelic/newrelic-client-go/v2 v2.21.0/go.mod h1:VPWTvEfKvnTZLunAC7fiW33y4e0srznNfN5HJH2cOp8= +github.com/newrelic/newrelic-client-go/v2 v2.22.0 h1:8+CS3FWCG0uHz9ApVlwaufoSdYKD474/rfM1De1FdSQ= +github.com/newrelic/newrelic-client-go/v2 v2.22.0/go.mod h1:VPWTvEfKvnTZLunAC7fiW33y4e0srznNfN5HJH2cOp8= +github.com/newrelic/newrelic-client-go/v2 v2.22.1 h1:WtdoXJeFGHXTReEJLZzpIj5doYso2J/qV//8zfSG5vA= +github.com/newrelic/newrelic-client-go/v2 v2.22.1/go.mod h1:VPWTvEfKvnTZLunAC7fiW33y4e0srznNfN5HJH2cOp8= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= diff --git a/internal/entities/command_deployment.go b/internal/entities/command_deployment.go index e14684b46..0bf940303 100644 --- a/internal/entities/command_deployment.go +++ b/internal/entities/command_deployment.go @@ -1,7 +1,9 @@ package entities import ( + "encoding/json" "errors" + "fmt" "log" "strings" "time" @@ -19,16 +21,17 @@ import ( ) var ( - changelog string - commit string - customAttribute []string - deepLink string - deploymentType string - description string - groupID string - timestamp int64 - user string - version string + changelog string + commit string + customAttribute []string + customAttributes string + deepLink string + deploymentType string + description string + groupID string + timestamp int64 + user string + version string ) var cmdEntityDeployment = &cobra.Command{ @@ -41,6 +44,8 @@ The deployment command manages deployments for a New Relic entity. Use --help fo Example: "newrelic entity deployment create --guid --version <0.0.1>", } +var cmdEntityDeploymentCreateExample = fmt.Sprintf(`newrelic entity deployment create --guid --version <0.0.1> --changelog 'what changed' --commit '12345e' --deepLink --deploymentType 'BASIC' --description 'about' --timestamp %v --user 'jenkins-bot'`, time.Now().Unix()) + var cmdEntityDeploymentCreate = &cobra.Command{ Use: "create", Short: "Create a New Relic entity deployment marker", @@ -48,7 +53,7 @@ var cmdEntityDeploymentCreate = &cobra.Command{ The deployment command marks a change for a New Relic entity `, - Example: "newrelic entity deployment create --guid --version <0.0.1> --changelog 'what changed' --commit '12345e' --customAttribute test1:123,test2:456 --deepLink --deploymentType 'BASIC' --description 'about' --timestamp <1668446197100> --user 'jenkins-bot'", + Example: cmdEntityDeploymentCreateExample, PreRun: client.RequireClient, Run: func(cmd *cobra.Command, args []string) { params := changetracking.ChangeTrackingDeploymentInput{} @@ -63,15 +68,20 @@ The deployment command marks a change for a New Relic entity log.Fatal("--version cannot be empty") } - customAttributes, err := parseCustomAttributes(&customAttribute) - - if err != nil { - log.Fatal(err) + var ( + attrs map[string]interface{} + err error + ) + if len(customAttribute) > 0 || customAttributes != "" { + attrs, err = getCustomAttributes(customAttribute, customAttributes) + if err != nil { + log.Fatal(err) + } + params.CustomAttributes = attrs } params.Changelog = changelog params.Commit = commit - params.CustomAttributes = customAttributes params.DeepLink = deepLink params.DeploymentType = changetracking.ChangeTrackingDeploymentType(deploymentType) params.Description = description @@ -80,7 +90,11 @@ The deployment command marks a change for a New Relic entity params.User = user params.Version = version - result, err := client.NRClient.ChangeTracking.ChangeTrackingCreateDeploymentWithContext(utils.SignalCtx, params) + result, err := client.NRClient.ChangeTracking.ChangeTrackingCreateDeploymentWithContext( + utils.SignalCtx, + changetracking.ChangeTrackingDataHandlingRules{ValidationFlags: []changetracking.ChangeTrackingValidationFlag{changetracking.ChangeTrackingValidationFlagTypes.FAIL_ON_FIELD_LENGTH}}, + params, + ) utils.LogIfFatal(err) utils.LogIfFatal(output.Print(result)) @@ -100,6 +114,9 @@ func init() { cmdEntityDeploymentCreate.Flags().StringVar(&changelog, "changelog", "", "a URL for the changelog or list of changes if not linkable") cmdEntityDeploymentCreate.Flags().StringVar(&commit, "commit", "", "the commit identifier, for example, a Git commit SHA") cmdEntityDeploymentCreate.Flags().StringSliceVar(&customAttribute, "customAttribute", []string{}, "(EARLY ACCESS) a comma separated list of key:value custom attributes to apply to the deployment") + _ = cmdEntityDeploymentCreate.Flags().MarkDeprecated("customAttribute", "please use 'customAttributes'") + cmdEntityDeploymentCreate.Flags().StringVar(&customAttributes, "customAttributes", "", "(EARLY ACCESS) key-value pairs of custom attributes in JSON format to apply to the deployment") + _ = cmdEntityDeploymentCreate.Flags().MarkHidden("customAttributes") cmdEntityDeploymentCreate.Flags().StringVar(&deepLink, "deepLink", "", "a link back to the system generating the deployment") cmdEntityDeploymentCreate.Flags().StringVar(&deploymentType, "deploymentType", "", "type of deployment, one of BASIC, BLUE_GREEN, CANARY, OTHER, ROLLING or SHADOW") cmdEntityDeploymentCreate.Flags().StringVar(&description, "description", "", "a description of the deployment") @@ -108,13 +125,13 @@ func init() { cmdEntityDeploymentCreate.Flags().StringVarP(&user, "user", "u", "", "username of the deployer or bot") } -func parseCustomAttributes(a *[]string) (*map[string]string, error) { - customAttributeMap := make(map[string]string) - +func parseCustomAttributes(a *[]string) (*map[string]interface{}, error) { if len(*a) < 1 { return nil, nil } + customAttributeMap := make(map[string]interface{}) + for _, v := range *a { pair := strings.Split(v, ":") @@ -127,3 +144,23 @@ func parseCustomAttributes(a *[]string) (*map[string]string, error) { return &customAttributeMap, nil } + +func getCustomAttributes(a []string, b string) (map[string]interface{}, error) { + var result *map[string]interface{} + if len(a) > 0 && b == "" { + attrsMap, err := parseCustomAttributes(&a) + if err != nil { + return nil, errors.New("unable to parse custom attributes") + } + result = attrsMap + } + if b != "" { + var attrsJSON *map[string]interface{} + err := json.Unmarshal([]byte(b), &attrsJSON) + if err != nil { + return nil, errors.New("unable to unmarshal custom attributes") + } + result = attrsJSON + } + return *result, nil +} diff --git a/internal/entities/command_deployment_test.go b/internal/entities/command_deployment_test.go index 0b00f1a09..bee18f8ad 100644 --- a/internal/entities/command_deployment_test.go +++ b/internal/entities/command_deployment_test.go @@ -1,6 +1,7 @@ package entities import ( + "encoding/json" "errors" "testing" @@ -28,7 +29,7 @@ func TestParseAttributesSingleKeyValue(t *testing.T) { "key:value", } - var want = map[string]string{ + var want = map[string]interface{}{ "key": "value", } var errWant error @@ -39,13 +40,83 @@ func TestParseAttributesSingleKeyValue(t *testing.T) { assert.Equal(t, want, *got) } +func TestParseAttributesSingleIntegerKeyValue(t *testing.T) { + a := []string{ + "a:1", + } + + var want = map[string]interface{}{ + "a": "1", + } + var errWant error + + got, errGot := parseCustomAttributes(&a) + + assert.Equal(t, errWant, errGot) + assert.Equal(t, want, *got) +} + +func TestParseAttributesSingleFloatingKeyValue(t *testing.T) { + a := []string{ + "a:1.5", + } + + var want = map[string]interface{}{ + "a": "1.5", + } + var errWant error + + got, errGot := parseCustomAttributes(&a) + + assert.Equal(t, errWant, errGot) + assert.Equal(t, want, *got) +} + +func TestParseAttributesSingleBooleanKeyValue(t *testing.T) { + a := []string{ + "a:true", + } + + var want = map[string]interface{}{ + "a": "true", + } + var errWant error + + got, errGot := parseCustomAttributes(&a) + + assert.Equal(t, errWant, errGot) + assert.Equal(t, want, *got) +} + +func TestParseAttributesMultipleTypesKeyValues(t *testing.T) { + a := []string{ + "a:true", + "b:1", + "c:1.5", + `d:"value"`, + } + + var want = map[string]interface{}{ + "a": "true", + "b": "1", + "c": "1.5", + "d": `"value"`, + } + var errWant error + + got, errGot := parseCustomAttributes(&a) + + assert.Equal(t, errWant, errGot) + assert.Equal(t, want, *got) +} + func TestParseAttributesTwoKeyValues(t *testing.T) { a := []string{ "key:value", "key2:value2", } - var want = map[string]string{ + var want = map[string]interface{}{ "key": "value", "key2": "value2", } @@ -97,6 +168,40 @@ func TestParseAttributesEmptyStringSlice(t *testing.T) { assert.Equal(t, want, got) } -func nilPointerMapStringString() *map[string]string { +func nilPointerMapStringString() *map[string]interface{} { return nil } + +func TestReturningMapOverStringIfBothCustomAttributesFlagsAreInadvertentlyUsedSimultaneously(t *testing.T) { + a := []string{"a:true"} + b := "" + var want = map[string]interface{}{"a": "true"} + + got, _ := getCustomAttributes(a, b) + assert.Equal(t, want, got) +} + +func TestReturningJsonOverStringIfBothCustomAttributesFlagsAreInadvertentlyUsedSimultaneously(t *testing.T) { + a := []string{"a:1", "b:2"} + b := `{"a":"true"}` + + want := make(map[string]interface{}) + _ = json.Unmarshal([]byte(b), &want) + + got, _ := getCustomAttributes(a, b) + + assert.Equal(t, want, got) +} + +func TestIncorrectlyFormattedCustomAttributesStringPairFailsWithUnableToParse(t *testing.T) { + a := []string{"a::1"} + _, err := getCustomAttributes(a, "") + assert.Equal(t, "unable to parse custom attributes", err.Error()) +} + +func TestIncorrectlyFormattedCustomAttributesJSONStringFailsWithUnableToUnmarshal(t *testing.T) { + a := []string{} + b := `{"a":"1}` + _, err := getCustomAttributes(a, b) + assert.Equal(t, "unable to unmarshal custom attributes", err.Error()) +}