Skip to content

Commit

Permalink
feat(entities): Support custom attributes JSON (2) (#1531)
Browse files Browse the repository at this point in the history
  • Loading branch information
amolero-nr authored Nov 8, 2023
1 parent e1bf884 commit dbae5e0
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 24 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
77 changes: 57 additions & 20 deletions internal/entities/command_deployment.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package entities

import (
"encoding/json"
"errors"
"fmt"
"log"
"strings"
"time"
Expand All @@ -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{
Expand All @@ -41,14 +44,16 @@ The deployment command manages deployments for a New Relic entity. Use --help fo
Example: "newrelic entity deployment create --guid <GUID> --version <0.0.1>",
}

var cmdEntityDeploymentCreateExample = fmt.Sprintf(`newrelic entity deployment create --guid <GUID> --version <0.0.1> --changelog 'what changed' --commit '12345e' --deepLink <link back to deployer> --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",
Long: `Create a New Relic entity deployment marker
The deployment command marks a change for a New Relic entity
`,
Example: "newrelic entity deployment create --guid <GUID> --version <0.0.1> --changelog 'what changed' --commit '12345e' --customAttribute test1:123,test2:456 --deepLink <link back to deployer> --deploymentType 'BASIC' --description 'about' --timestamp <1668446197100> --user 'jenkins-bot'",
Example: cmdEntityDeploymentCreateExample,
PreRun: client.RequireClient,
Run: func(cmd *cobra.Command, args []string) {
params := changetracking.ChangeTrackingDeploymentInput{}
Expand All @@ -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
Expand All @@ -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))
Expand All @@ -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")
Expand All @@ -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, ":")

Expand All @@ -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
}
111 changes: 108 additions & 3 deletions internal/entities/command_deployment_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package entities

import (
"encoding/json"
"errors"
"testing"

Expand Down Expand Up @@ -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
Expand All @@ -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",
}
Expand Down Expand Up @@ -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())
}

0 comments on commit dbae5e0

Please sign in to comment.