From d3bb5ff21ac2e8209af7063c9f365a9469dbe7d1 Mon Sep 17 00:00:00 2001 From: Dhia Ayachi Date: Fri, 22 Sep 2023 16:51:18 -0400 Subject: [PATCH] Add CLI support for json (#18991) * add cli support for json format * add tests for json parsing * make owner and id pointers. * add copyright header * remove print --------- Co-authored-by: Poonam Jadhav --- command/resource/helper.go | 80 ++++++++++++++++++++++++++++++++- command/resource/helper_test.go | 33 ++++++++++++++ 2 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 command/resource/helper_test.go diff --git a/command/resource/helper.go b/command/resource/helper.go index aa8d45e02b..0b79ea8ec1 100644 --- a/command/resource/helper.go +++ b/command/resource/helper.go @@ -4,9 +4,12 @@ package resource import ( + "encoding/json" "errors" "flag" "fmt" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/types/known/anypb" "net/http" "strings" @@ -17,14 +20,87 @@ import ( "github.com/hashicorp/consul/proto-public/pbresource" ) +type OuterResource struct { + ID *ID `json:"id"` + Owner *ID `json:"owner"` + Generation string `json:"generation"` + Version string `json:"version"` + Metadata map[string]any `json:"metadata"` + Data map[string]any `json:"data"` +} + +type Tenancy struct { + Namespace string `json:"namespace"` + Partition string `json:"partition"` + PeerName string `json:"peerName"` +} +type Type struct { + Group string `json:"group"` + GroupVersion string `json:"groupVersion"` + Kind string `json:"kind"` +} +type ID struct { + Name string `json:"name"` + Tenancy Tenancy `json:"tenancy"` + Type Type `json:"type"` + UID string `json:"uid"` +} + +func parseJson(js string) (*pbresource.Resource, error) { + + parsedResource := new(pbresource.Resource) + + var outerResource OuterResource + + if err := json.Unmarshal([]byte(js), &outerResource); err != nil { + return nil, err + } + + if outerResource.ID == nil { + return nil, fmt.Errorf("\"id\" field need to be provided") + } + + typ := pbresource.Type{ + Kind: outerResource.ID.Type.Kind, + Group: outerResource.ID.Type.Group, + GroupVersion: outerResource.ID.Type.GroupVersion, + } + + reg, ok := consul.NewTypeRegistry().Resolve(&typ) + if !ok { + return nil, fmt.Errorf("invalid type %v", parsedResource) + } + data := reg.Proto.ProtoReflect().New().Interface() + anyProtoMsg, err := anypb.New(data) + if err != nil { + return nil, err + } + + outerResource.Data["@type"] = anyProtoMsg.TypeUrl + + marshal, err := json.Marshal(outerResource) + if err != nil { + return nil, err + } + + if err := protojson.Unmarshal(marshal, parsedResource); err != nil { + return nil, err + } + return parsedResource, nil +} + func ParseResourceFromFile(filePath string) (*pbresource.Resource, error) { data, err := helpers.LoadDataSourceNoRaw(filePath, nil) if err != nil { return nil, fmt.Errorf("Failed to load data: %v", err) } - parsedResource, err := resourcehcl.Unmarshal([]byte(data), consul.NewTypeRegistry()) + var parsedResource *pbresource.Resource + parsedResource, err = resourcehcl.Unmarshal([]byte(data), consul.NewTypeRegistry()) if err != nil { - return nil, fmt.Errorf("Failed to decode resource from input file: %v", err) + parsedResource, err = parseJson(data) + if err != nil { + return nil, fmt.Errorf("Failed to decode resource from input file: %v", err) + } } return parsedResource, nil diff --git a/command/resource/helper_test.go b/command/resource/helper_test.go new file mode 100644 index 0000000000..003c2b7aa6 --- /dev/null +++ b/command/resource/helper_test.go @@ -0,0 +1,33 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package resource + +import ( + "github.com/stretchr/testify/require" + "testing" +) + +func Test_parseJson(t *testing.T) { + tests := []struct { + name string + js string + wantErr bool + }{ + {"valid resource", "{\n \"data\": {\n \"genre\": \"GENRE_METAL\",\n \"name\": \"Korn\"\n },\n \"generation\": \"01HAYWBPV1KMT2KWECJ6CEWDQ0\",\n \"id\": {\n \"name\": \"korn\",\n \"tenancy\": {\n \"namespace\": \"default\",\n \"partition\": \"default\",\n \"peerName\": \"local\"\n },\n \"type\": {\n \"group\": \"demo\",\n \"groupVersion\": \"v2\",\n \"kind\": \"Artist\"\n },\n \"uid\": \"01HAYWBPV1KMT2KWECJ4NW88S1\"\n },\n \"metadata\": {\n \"foo\": \"bar\"\n },\n \"version\": \"18\"\n}", false}, + {"invalid resource", "{\n \"data\": {\n \"genre\": \"GENRE_METAL\",\n \"name\": \"Korn\"\n },\n \"id\": {\n \"name\": \"korn\",\n \"tenancy\": {\n \"namespace\": \"default\",\n \"partition\": \"default\",\n \"peerName\": \"local\"\n },\n \"type\": \"\"\n },\n \"metadata\": {\n \"foo\": \"bar\"\n }\n}\n", true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := parseJson(tt.js) + if tt.wantErr { + require.Error(t, err) + require.Nil(t, got) + } else { + require.NoError(t, err) + require.NotNil(t, got) + } + + }) + } +}