From a021daaf95461ab49317fcd5c6cf861edbda1d4f Mon Sep 17 00:00:00 2001 From: Peter Broadhurst Date: Wed, 29 May 2024 14:33:47 -0400 Subject: [PATCH 1/6] Provide utility resource to upsert data model Signed-off-by: Peter Broadhurst --- kaleido/platform/ams_dmlistener.go | 7 +- kaleido/platform/ams_dmupsert.go | 114 ++++++++++++++++++++++++++ kaleido/platform/ams_dmupsert_test.go | 109 ++++++++++++++++++++++++ kaleido/platform/common.go | 1 + kaleido/platform/mockserver_test.go | 5 ++ 5 files changed, 232 insertions(+), 4 deletions(-) create mode 100644 kaleido/platform/ams_dmupsert.go create mode 100644 kaleido/platform/ams_dmupsert_test.go diff --git a/kaleido/platform/ams_dmlistener.go b/kaleido/platform/ams_dmlistener.go index f2e60dd..1a656bf 100644 --- a/kaleido/platform/ams_dmlistener.go +++ b/kaleido/platform/ams_dmlistener.go @@ -18,7 +18,6 @@ import ( "fmt" "net/http" - "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -85,7 +84,7 @@ func (r *ams_dmlistenerResource) Schema(_ context.Context, _ resource.SchemaRequ } } -func (data *AMSDMListenerResourceModel) toAPI(api *AMSDMListenerAPIModel, diagnostics *diag.Diagnostics) bool { +func (data *AMSDMListenerResourceModel) toAPI(api *AMSDMListenerAPIModel) bool { api.Name = data.Name.ValueString() api.TaskID = data.TaskID.ValueString() if !data.TopicFilter.IsNull() { @@ -112,7 +111,7 @@ func (r *ams_dmlistenerResource) Create(ctx context.Context, req resource.Create resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) var api AMSDMListenerAPIModel - ok := data.toAPI(&api, &resp.Diagnostics) + ok := data.toAPI(&api) if ok { ok, _ = r.apiRequest(ctx, http.MethodPut, r.apiPath(&data, data.Name.ValueString()), &api, &api, &resp.Diagnostics) } @@ -132,7 +131,7 @@ func (r *ams_dmlistenerResource) Update(ctx context.Context, req resource.Update resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("id"), &data.ID)...) var api AMSDMListenerAPIModel - ok := data.toAPI(&api, &resp.Diagnostics) + ok := data.toAPI(&api) if ok { ok, _ = r.apiRequest(ctx, http.MethodPut, r.apiPath(&data, data.ID.ValueString()), &api, &api, &resp.Diagnostics) } diff --git a/kaleido/platform/ams_dmupsert.go b/kaleido/platform/ams_dmupsert.go new file mode 100644 index 0000000..a8f192c --- /dev/null +++ b/kaleido/platform/ams_dmupsert.go @@ -0,0 +1,114 @@ +// Copyright © Kaleido, Inc. 2024 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package platform + +import ( + "context" + "fmt" + "net/http" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "gopkg.in/yaml.v3" +) + +type AMSDMUpsertResourceModel struct { + Environment types.String `tfsdk:"environment"` + Service types.String `tfsdk:"service"` + BulkUpsertYAML types.String `tfsdk:"bulk_upsert_yaml"` +} + +func AMSDMUpsertResourceFactory() resource.Resource { + return &ams_dmupsertResource{} +} + +type ams_dmupsertResource struct { + commonResource +} + +func (r *ams_dmupsertResource) Metadata(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "kaleido_platform_ams_dmupsert" +} + +func (r *ams_dmupsertResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "environment": &schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + }, + "service": &schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + }, + "bulk_upsert_yaml": &schema.StringAttribute{ + Required: true, + Description: "This is a bulk upsert input payload in YAML/JSON", + }, + }, + } +} + +func (data *AMSDMUpsertResourceModel) toAPI(diagnostics *diag.Diagnostics) map[string]interface{} { + var parsedYAML map[string]interface{} + err := yaml.Unmarshal([]byte(data.BulkUpsertYAML.ValueString()), &parsedYAML) + if err != nil { + diagnostics.AddError("invalid task YAML", err.Error()) + return nil + } + return parsedYAML +} + +func (r *ams_dmupsertResource) apiPath(data *AMSDMUpsertResourceModel) string { + return fmt.Sprintf("/endpoint/%s/%s/rest/api/v1/bulk/datamodel", data.Environment.ValueString(), data.Service.ValueString()) +} + +func (r *ams_dmupsertResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + + var data AMSDMUpsertResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + parsedYAML := data.toAPI(&resp.Diagnostics) + if parsedYAML != nil { + _, _ = r.apiRequest(ctx, http.MethodPut, r.apiPath(&data), parsedYAML, nil, &resp.Diagnostics) + } + + resp.Diagnostics.Append(resp.State.Set(ctx, data)...) + +} + +func (r *ams_dmupsertResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + + var data AMSDMUpsertResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + parsedYAML := data.toAPI(&resp.Diagnostics) + if parsedYAML != nil { + _, _ = r.apiRequest(ctx, http.MethodPut, r.apiPath(&data), parsedYAML, nil, &resp.Diagnostics) + } + + resp.Diagnostics.Append(resp.State.Set(ctx, data)...) +} + +func (r *ams_dmupsertResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // no-op +} + +func (r *ams_dmupsertResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // no-op +} diff --git a/kaleido/platform/ams_dmupsert_test.go b/kaleido/platform/ams_dmupsert_test.go new file mode 100644 index 0000000..3704841 --- /dev/null +++ b/kaleido/platform/ams_dmupsert_test.go @@ -0,0 +1,109 @@ +// Copyright © Kaleido, Inc. 2024 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package platform + +import ( + "net/http" + "testing" + + "github.com/gorilla/mux" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + + _ "embed" +) + +var ams_dmupsertStep1 = ` +resource "kaleido_platform_ams_dmupsert" "ams_dmupsert1" { + environment = "env1" + service = "service1" + bulk_upsert_yaml = yamlencode( + { + "addresses": [ + { + "updateType": "create_or_replace", + "address": "0x93976AE88d24130979FE554bFdfF32008839b04B", + "displayName": "bob" + } + ] + } + ) +} +` + +var ams_dmupsertStep2 = ` +resource "kaleido_platform_ams_dmupsert" "ams_dmupsert1" { + environment = "env1" + service = "service1" + bulk_upsert_yaml = yamlencode( + { + "addresses": [ + { + "updateType": "create_or_replace", + "address": "0x93976AE88d24130979FE554bFdfF32008839b04B", + "displayName": "sally" + } + ] + } + ) +} +` + +func TestAMSDMUpsert1(t *testing.T) { + + mp, providerConfig := testSetup(t) + defer func() { + mp.checkClearCalls([]string{ + "PUT /endpoint/{env}/{service}/rest/api/v1/bulk/datamodel", + "PUT /endpoint/{env}/{service}/rest/api/v1/bulk/datamodel", + }) + mp.server.Close() + }() + + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV6ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: providerConfig + ams_dmupsertStep1, + }, + { + Config: providerConfig + ams_dmupsertStep2, + Check: resource.ComposeAggregateTestCheckFunc( + func(s *terraform.State) error { + // Compare the final result on the mock-server side + obj := mp.amsDMUpserts["env1/service1"] + testYAMLEqual(t, obj, `{ + "addresses": [ + { + "updateType": "create_or_replace", + "address": "0x93976AE88d24130979FE554bFdfF32008839b04B", + "displayName": "sally" + } + ] + }`) + return nil + }, + ), + }, + }, + }) +} + +func (mp *mockPlatform) putAMSDMUpsert(res http.ResponseWriter, req *http.Request) { + var newObj map[string]interface{} + mp.getBody(req, &newObj) + mp.amsDMUpserts[mux.Vars(req)["env"]+"/"+mux.Vars(req)["service"]] = newObj + mp.respond(res, &newObj, 200) +} diff --git a/kaleido/platform/common.go b/kaleido/platform/common.go index ab163a8..2000fdb 100644 --- a/kaleido/platform/common.go +++ b/kaleido/platform/common.go @@ -242,6 +242,7 @@ func Resources() []func() resource.Resource { AMSTaskResourceFactory, AMSFFListenerResourceFactory, AMSDMListenerResourceFactory, + AMSDMUpsertResourceFactory, FireFlyRegistrationResourceFactory, } } diff --git a/kaleido/platform/mockserver_test.go b/kaleido/platform/mockserver_test.go index 31fb197..cd92ba3 100644 --- a/kaleido/platform/mockserver_test.go +++ b/kaleido/platform/mockserver_test.go @@ -46,6 +46,7 @@ type mockPlatform struct { cmsActions map[string]CMSActionAPIBaseAccessor amsTasks map[string]*AMSTaskAPIModel amsTaskVersions map[string]map[string]interface{} + amsDMUpserts map[string]map[string]interface{} amsFFListeners map[string]*AMSFFListenerAPIModel amsDMListeners map[string]*AMSDMListenerAPIModel groups map[string]*GroupAPIModel @@ -67,6 +68,7 @@ func startMockPlatformServer(t *testing.T) *mockPlatform { cmsActions: make(map[string]CMSActionAPIBaseAccessor), amsTasks: make(map[string]*AMSTaskAPIModel), amsTaskVersions: make(map[string]map[string]interface{}), + amsDMUpserts: make(map[string]map[string]interface{}), amsFFListeners: make(map[string]*AMSFFListenerAPIModel), amsDMListeners: make(map[string]*AMSDMListenerAPIModel), groups: make(map[string]*GroupAPIModel), @@ -128,6 +130,9 @@ func startMockPlatformServer(t *testing.T) *mockPlatform { mp.register("/endpoint/{env}/{service}/rest/api/v1/tasks/{task}", http.MethodPatch, mp.patchAMSTask) mp.register("/endpoint/{env}/{service}/rest/api/v1/tasks/{task}", http.MethodDelete, mp.deleteAMSTask) + // See ams_dmupsert.go + mp.register("/endpoint/{env}/{service}/rest/api/v1/bulk/datamodel", http.MethodPut, mp.putAMSDMUpsert) + // See ams_fflistener.go mp.register("/endpoint/{env}/{service}/rest/api/v1/listeners/firefly/{listener}", http.MethodGet, mp.getAMSFFListener) mp.register("/endpoint/{env}/{service}/rest/api/v1/listeners/firefly/{listener}", http.MethodPut, mp.putAMSFFListener) From 60ef52a6d0e9c676cc03ab1649386625b389b3d6 Mon Sep 17 00:00:00 2001 From: Peter Broadhurst Date: Wed, 29 May 2024 16:05:12 -0400 Subject: [PATCH 2/6] Add AMS Policy Signed-off-by: Peter Broadhurst --- kaleido/platform/ams_policy.go | 213 ++++++++++++++++++++++++++++ kaleido/platform/ams_policy_test.go | 170 ++++++++++++++++++++++ kaleido/platform/common.go | 1 + kaleido/platform/mockserver_test.go | 87 +++++++----- 4 files changed, 433 insertions(+), 38 deletions(-) create mode 100644 kaleido/platform/ams_policy.go create mode 100644 kaleido/platform/ams_policy_test.go diff --git a/kaleido/platform/ams_policy.go b/kaleido/platform/ams_policy.go new file mode 100644 index 0000000..1bbc561 --- /dev/null +++ b/kaleido/platform/ams_policy.go @@ -0,0 +1,213 @@ +// Copyright © Kaleido, Inc. 2024 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package platform + +import ( + "context" + "fmt" + "net/http" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type AMSPolicyResourceModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + Environment types.String `tfsdk:"environment"` + Service types.String `tfsdk:"service"` + Document types.String `tfsdk:"document"` // this is propagated to a policy version + ExampleInput types.String `tfsdk:"example_input"` // this is propagated to a policy version + Hash types.String `tfsdk:"hash"` + AppliedVersion types.String `tfsdk:"applied_version"` +} + +type AMSPolicyAPIModel struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Created string `json:"created,omitempty"` + Updated string `json:"updated,omitempty"` + CurrentVersion string `json:"currentVersion,omitempty"` +} + +type AMSPolicyVersionAPIModel struct { + ID string `json:"id,omitempty"` + Description string `json:"description,omitempty"` + Document string `json:"document,omitempty"` + ExampleInput string `json:"exampleInput,omitempty"` + Hash string `json:"hash,omitempty"` + Created string `json:"created,omitempty"` + Updated string `json:"updated,omitempty"` +} + +func AMSPolicyResourceFactory() resource.Resource { + return &ams_policyResource{} +} + +type ams_policyResource struct { + commonResource +} + +func (r *ams_policyResource) Metadata(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "kaleido_platform_ams_policy" +} + +func (r *ams_policyResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": &schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, + }, + "environment": &schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + }, + "name": &schema.StringAttribute{ + Required: true, + }, + "description": &schema.StringAttribute{ + Optional: true, + }, + "service": &schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + }, + "document": &schema.StringAttribute{ + Required: true, + Description: "This is the definition of the policy - which will be put into a new version each time the policy is updated", + }, + "example_input": &schema.StringAttribute{ + Optional: true, + }, + "hash": &schema.StringAttribute{ + Computed: true, + }, + "applied_version": &schema.StringAttribute{ + Computed: true, + }, + }, + } +} + +func (api *AMSPolicyAPIModel) toData(data *AMSPolicyResourceModel) { + data.ID = types.StringValue(api.ID) + data.AppliedVersion = types.StringValue(api.CurrentVersion) +} + +func (api *AMSPolicyVersionAPIModel) toData(data *AMSPolicyResourceModel) { + data.Hash = types.StringValue(api.Hash) +} + +func (data *AMSPolicyResourceModel) toAPI(api *AMSPolicyAPIModel, apiV *AMSPolicyVersionAPIModel) { + api.Name = data.Name.ValueString() + api.Description = data.Description.ValueString() + + apiV.Description = data.Description.ValueString() + apiV.Document = data.Document.ValueString() + if data.ExampleInput.ValueString() != "" { + apiV.ExampleInput = data.ExampleInput.ValueString() + } +} + +func (r *ams_policyResource) apiPath(data *AMSPolicyResourceModel, idOrName string) string { + return fmt.Sprintf("/endpoint/%s/%s/rest/api/v1/policies/%s", data.Environment.ValueString(), data.Service.ValueString(), idOrName) +} + +func (r *ams_policyResource) apiPolicyVersionPath(data *AMSPolicyResourceModel, idOrName string) string { + return fmt.Sprintf("%s/versions", r.apiPath(data, idOrName)) +} + +func (r *ams_policyResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + + var data AMSPolicyResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + var api AMSPolicyAPIModel + var apiV AMSPolicyVersionAPIModel + data.toAPI(&api, &apiV) + // Policy PUT + ok, _ := r.apiRequest(ctx, http.MethodPut, r.apiPath(&data, api.Name), &api, &api, &resp.Diagnostics) + if ok { + // Policy version POST + ok, _ = r.apiRequest(ctx, http.MethodPost, r.apiPolicyVersionPath(&data, api.Name), &apiV, &apiV, &resp.Diagnostics) + } + if !ok { + return + } + + api.toData(&data) + apiV.toData(&data) + resp.Diagnostics.Append(resp.State.Set(ctx, data)...) + +} + +func (r *ams_policyResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + + var data AMSPolicyResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("id"), &data.ID)...) + + var api AMSPolicyAPIModel + var apiV AMSPolicyVersionAPIModel + data.toAPI(&api, &apiV) + policyID := data.ID.ValueString() + // Policy PATCH + ok, _ := r.apiRequest(ctx, http.MethodPatch, r.apiPath(&data, policyID), &api, &api, &resp.Diagnostics) + if ok { + // Policy version POST + ok, _ = r.apiRequest(ctx, http.MethodPost, r.apiPolicyVersionPath(&data, api.Name), &apiV, &apiV, &resp.Diagnostics) + } + if !ok { + return + } + + api.toData(&data) + apiV.toData(&data) + resp.Diagnostics.Append(resp.State.Set(ctx, data)...) +} + +func (r *ams_policyResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data AMSPolicyResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + var api AMSPolicyAPIModel + api.ID = data.ID.ValueString() + ok, status := r.apiRequest(ctx, http.MethodGet, r.apiPath(&data, data.ID.ValueString()), nil, &api, &resp.Diagnostics, Allow404()) + if !ok { + return + } + if status == 404 { + resp.State.RemoveResource(ctx) + return + } + + api.toData(&data) + resp.Diagnostics.Append(resp.State.Set(ctx, data)...) +} + +func (r *ams_policyResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data AMSPolicyResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + _, _ = r.apiRequest(ctx, http.MethodDelete, r.apiPath(&data, data.ID.ValueString()), nil, nil, &resp.Diagnostics, Allow404()) + + r.waitForRemoval(ctx, r.apiPath(&data, data.ID.ValueString()), &resp.Diagnostics) +} diff --git a/kaleido/platform/ams_policy_test.go b/kaleido/platform/ams_policy_test.go new file mode 100644 index 0000000..23d5e59 --- /dev/null +++ b/kaleido/platform/ams_policy_test.go @@ -0,0 +1,170 @@ +// Copyright © Kaleido, Inc. 2024 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package platform + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "net/http" + "testing" + "time" + + "github.com/aidarkhanov/nanoid" + "github.com/gorilla/mux" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/stretchr/testify/assert" + + _ "embed" +) + +var ams_policyStep1 = ` +resource "kaleido_platform_ams_policy" "ams_policy1" { + environment = "env1" + service = "service1" + name = "ams_policy1" + document = "document 1" +} +` + +var ams_policyStep2 = ` +resource "kaleido_platform_ams_policy" "ams_policy1" { + environment = "env1" + service = "service1" + name = "ams_policy1" + description = "shiny policy that does stuff and more stuff" + document = "document 2" + example_input = "input 2" +} +` + +func TestAMSPolicy1(t *testing.T) { + + mp, providerConfig := testSetup(t) + defer func() { + mp.checkClearCalls([]string{ + "PUT /endpoint/{env}/{service}/rest/api/v1/policies/{policy}", // by name initially + "POST /endpoint/{env}/{service}/rest/api/v1/policies/{policy}/versions", + "GET /endpoint/{env}/{service}/rest/api/v1/policies/{policy}", + "GET /endpoint/{env}/{service}/rest/api/v1/policies/{policy}", + "PATCH /endpoint/{env}/{service}/rest/api/v1/policies/{policy}", // then by ID + "POST /endpoint/{env}/{service}/rest/api/v1/policies/{policy}/versions", + "GET /endpoint/{env}/{service}/rest/api/v1/policies/{policy}", + "DELETE /endpoint/{env}/{service}/rest/api/v1/policies/{policy}", + "GET /endpoint/{env}/{service}/rest/api/v1/policies/{policy}", + }) + mp.server.Close() + }() + + ams_policy1Resource := "kaleido_platform_ams_policy.ams_policy1" + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV6ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: providerConfig + ams_policyStep1, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(ams_policy1Resource, "id"), + resource.TestCheckResourceAttrSet(ams_policy1Resource, "hash"), + resource.TestCheckResourceAttrSet(ams_policy1Resource, "applied_version"), + ), + }, + { + Config: providerConfig + ams_policyStep2, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(ams_policy1Resource, "id"), + resource.TestCheckResourceAttrSet(ams_policy1Resource, "hash"), + resource.TestCheckResourceAttrSet(ams_policy1Resource, "applied_version"), + func(s *terraform.State) error { + // Compare the final result on the mock-server side + id := s.RootModule().Resources[ams_policy1Resource].Primary.Attributes["id"] + obj := mp.amsPolicies[fmt.Sprintf("env1/service1/%s", id)] + testJSONEqual(t, obj, fmt.Sprintf(`{ + "id": "%[1]s", + "name": "ams_policy1", + "description": "shiny policy that does stuff and more stuff", + "created": "%[2]s", + "updated": "%[3]s", + "currentVersion": "%[4]s" + }`, + // generated fields that vary per test run + id, + obj.Created, + obj.Updated, + obj.CurrentVersion, + )) + return nil + }, + ), + }, + }, + }) +} + +func (mp *mockPlatform) getAMSPolicy(res http.ResponseWriter, req *http.Request) { + obj := mp.amsPolicies[mux.Vars(req)["env"]+"/"+mux.Vars(req)["service"]+"/"+mux.Vars(req)["policy"]] + if obj == nil { + mp.respond(res, nil, 404) + } else { + mp.respond(res, obj, 200) + } +} + +func (mp *mockPlatform) putAMSPolicy(res http.ResponseWriter, req *http.Request) { + now := time.Now().UTC() + obj := mp.amsPolicies[mux.Vars(req)["env"]+"/"+mux.Vars(req)["service"]+"/"+mux.Vars(req)["policy"]] // expected behavior of provider is PUT only on exists + var newObj AMSPolicyAPIModel + mp.getBody(req, &newObj) + assert.Nil(mp.t, obj) + assert.Equal(mp.t, newObj.Name, mux.Vars(req)["policy"]) + newObj.ID = nanoid.New() + newObj.Created = now.Format(time.RFC3339Nano) + newObj.Updated = now.Format(time.RFC3339Nano) + newObj.CurrentVersion = nanoid.New() + mp.amsPolicies[mux.Vars(req)["env"]+"/"+mux.Vars(req)["service"]+"/"+newObj.ID] = &newObj + mp.respond(res, &newObj, 200) +} + +func (mp *mockPlatform) patchAMSPolicy(res http.ResponseWriter, req *http.Request) { + now := time.Now().UTC() + obj := mp.amsPolicies[mux.Vars(req)["env"]+"/"+mux.Vars(req)["service"]+"/"+mux.Vars(req)["policy"]] // expected behavior of provider is PUT only on exists + var newObj AMSPolicyAPIModel + mp.getBody(req, &newObj) + assert.NotNil(mp.t, obj) + assert.Equal(mp.t, obj.ID, mux.Vars(req)["policy"]) + newObj.ID = mux.Vars(req)["policy"] + newObj.Created = obj.Created + newObj.Updated = now.Format(time.RFC3339Nano) + newObj.CurrentVersion = nanoid.New() + mp.amsPolicies[mux.Vars(req)["env"]+"/"+mux.Vars(req)["service"]+"/"+newObj.ID] = &newObj + mp.respond(res, &newObj, 200) +} + +func (mp *mockPlatform) postAMSPolicyVersion(res http.ResponseWriter, req *http.Request) { + var newObj AMSPolicyVersionAPIModel + mp.getBody(req, &newObj) + mp.amsPolicyVersions[mux.Vars(req)["env"]+"/"+mux.Vars(req)["service"]+"/"+mux.Vars(req)["policy"]] = &newObj + hash := sha256.New() + hash.Write([]byte(newObj.Document)) + newObj.Hash = hex.EncodeToString(hash.Sum(nil)) + mp.respond(res, &newObj, 200) +} + +func (mp *mockPlatform) deleteAMSPolicy(res http.ResponseWriter, req *http.Request) { + obj := mp.amsPolicies[mux.Vars(req)["env"]+"/"+mux.Vars(req)["service"]+"/"+mux.Vars(req)["policy"]] + assert.NotNil(mp.t, obj) + delete(mp.amsPolicies, mux.Vars(req)["env"]+"/"+mux.Vars(req)["service"]+"/"+mux.Vars(req)["policy"]) + mp.respond(res, nil, 204) +} diff --git a/kaleido/platform/common.go b/kaleido/platform/common.go index 2000fdb..9b55466 100644 --- a/kaleido/platform/common.go +++ b/kaleido/platform/common.go @@ -240,6 +240,7 @@ func Resources() []func() resource.Resource { CMSActionDeployResourceFactory, CMSActionCreateAPIResourceFactory, AMSTaskResourceFactory, + AMSPolicyResourceFactory, AMSFFListenerResourceFactory, AMSDMListenerResourceFactory, AMSDMUpsertResourceFactory, diff --git a/kaleido/platform/mockserver_test.go b/kaleido/platform/mockserver_test.go index cd92ba3..229d10e 100644 --- a/kaleido/platform/mockserver_test.go +++ b/kaleido/platform/mockserver_test.go @@ -32,48 +32,52 @@ import ( ) type mockPlatform struct { - t *testing.T - lock sync.Mutex - router *mux.Router - server *httptest.Server - environments map[string]*EnvironmentAPIModel - runtimes map[string]*RuntimeAPIModel - services map[string]*ServiceAPIModel - networks map[string]*NetworkAPIModel - kmsWallets map[string]*KMSWalletAPIModel - kmsKeys map[string]*KMSKeyAPIModel - cmsBuilds map[string]*CMSBuildAPIModel - cmsActions map[string]CMSActionAPIBaseAccessor - amsTasks map[string]*AMSTaskAPIModel - amsTaskVersions map[string]map[string]interface{} - amsDMUpserts map[string]map[string]interface{} - amsFFListeners map[string]*AMSFFListenerAPIModel - amsDMListeners map[string]*AMSDMListenerAPIModel - groups map[string]*GroupAPIModel - ffsNode *FireFlyStatusNodeAPIModel - ffsOrg *FireFlyStatusOrgAPIModel - calls []string + t *testing.T + lock sync.Mutex + router *mux.Router + server *httptest.Server + environments map[string]*EnvironmentAPIModel + runtimes map[string]*RuntimeAPIModel + services map[string]*ServiceAPIModel + networks map[string]*NetworkAPIModel + kmsWallets map[string]*KMSWalletAPIModel + kmsKeys map[string]*KMSKeyAPIModel + cmsBuilds map[string]*CMSBuildAPIModel + cmsActions map[string]CMSActionAPIBaseAccessor + amsTasks map[string]*AMSTaskAPIModel + amsTaskVersions map[string]map[string]interface{} + amsPolicies map[string]*AMSPolicyAPIModel + amsPolicyVersions map[string]*AMSPolicyVersionAPIModel + amsDMUpserts map[string]map[string]interface{} + amsFFListeners map[string]*AMSFFListenerAPIModel + amsDMListeners map[string]*AMSDMListenerAPIModel + groups map[string]*GroupAPIModel + ffsNode *FireFlyStatusNodeAPIModel + ffsOrg *FireFlyStatusOrgAPIModel + calls []string } func startMockPlatformServer(t *testing.T) *mockPlatform { mp := &mockPlatform{ - t: t, - environments: make(map[string]*EnvironmentAPIModel), - runtimes: make(map[string]*RuntimeAPIModel), - services: make(map[string]*ServiceAPIModel), - networks: make(map[string]*NetworkAPIModel), - kmsWallets: make(map[string]*KMSWalletAPIModel), - kmsKeys: make(map[string]*KMSKeyAPIModel), - cmsBuilds: make(map[string]*CMSBuildAPIModel), - cmsActions: make(map[string]CMSActionAPIBaseAccessor), - amsTasks: make(map[string]*AMSTaskAPIModel), - amsTaskVersions: make(map[string]map[string]interface{}), - amsDMUpserts: make(map[string]map[string]interface{}), - amsFFListeners: make(map[string]*AMSFFListenerAPIModel), - amsDMListeners: make(map[string]*AMSDMListenerAPIModel), - groups: make(map[string]*GroupAPIModel), - router: mux.NewRouter(), - calls: []string{}, + t: t, + environments: make(map[string]*EnvironmentAPIModel), + runtimes: make(map[string]*RuntimeAPIModel), + services: make(map[string]*ServiceAPIModel), + networks: make(map[string]*NetworkAPIModel), + kmsWallets: make(map[string]*KMSWalletAPIModel), + kmsKeys: make(map[string]*KMSKeyAPIModel), + cmsBuilds: make(map[string]*CMSBuildAPIModel), + cmsActions: make(map[string]CMSActionAPIBaseAccessor), + amsTasks: make(map[string]*AMSTaskAPIModel), + amsTaskVersions: make(map[string]map[string]interface{}), + amsPolicies: make(map[string]*AMSPolicyAPIModel), + amsPolicyVersions: make(map[string]*AMSPolicyVersionAPIModel), + amsDMUpserts: make(map[string]map[string]interface{}), + amsFFListeners: make(map[string]*AMSFFListenerAPIModel), + amsDMListeners: make(map[string]*AMSDMListenerAPIModel), + groups: make(map[string]*GroupAPIModel), + router: mux.NewRouter(), + calls: []string{}, } // See environment_test.go mp.register("/api/v1/environments", http.MethodPost, mp.postEnvironment) @@ -130,6 +134,13 @@ func startMockPlatformServer(t *testing.T) *mockPlatform { mp.register("/endpoint/{env}/{service}/rest/api/v1/tasks/{task}", http.MethodPatch, mp.patchAMSTask) mp.register("/endpoint/{env}/{service}/rest/api/v1/tasks/{task}", http.MethodDelete, mp.deleteAMSTask) + // See ams_policy.go + mp.register("/endpoint/{env}/{service}/rest/api/v1/policies/{policy}", http.MethodGet, mp.getAMSPolicy) + mp.register("/endpoint/{env}/{service}/rest/api/v1/policies/{policy}", http.MethodPut, mp.putAMSPolicy) + mp.register("/endpoint/{env}/{service}/rest/api/v1/policies/{policy}/versions", http.MethodPost, mp.postAMSPolicyVersion) + mp.register("/endpoint/{env}/{service}/rest/api/v1/policies/{policy}", http.MethodPatch, mp.patchAMSPolicy) + mp.register("/endpoint/{env}/{service}/rest/api/v1/policies/{policy}", http.MethodDelete, mp.deleteAMSPolicy) + // See ams_dmupsert.go mp.register("/endpoint/{env}/{service}/rest/api/v1/bulk/datamodel", http.MethodPut, mp.putAMSDMUpsert) From 234e2188d0954ac99f237539657b8fc4d626202a Mon Sep 17 00:00:00 2001 From: John Hosie Date: Sat, 1 Jun 2024 15:14:25 +0100 Subject: [PATCH 3/6] attach variable set to task --- kaleido/platform/ams_task.go | 6 ++++++ kaleido/platform/ams_task_test.go | 14 +++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/kaleido/platform/ams_task.go b/kaleido/platform/ams_task.go index b99139c..fcc6f6c 100644 --- a/kaleido/platform/ams_task.go +++ b/kaleido/platform/ams_task.go @@ -36,6 +36,7 @@ type AMSTaskResourceModel struct { Service types.String `tfsdk:"service"` TaskYAML types.String `tfsdk:"task_yaml"` // this is propagated to a task version AppliedVersion types.String `tfsdk:"applied_version"` + VariableSet types.String `tfsdk:"variable_set"` } type AMSTaskAPIModel struct { @@ -45,6 +46,7 @@ type AMSTaskAPIModel struct { Created string `json:"created,omitempty"` Updated string `json:"updated,omitempty"` CurrentVersion string `json:"currentVersion,omitempty"` + VariableSet string `json:"variableSet,omitempty"` } func AMSTaskResourceFactory() resource.Resource { @@ -87,6 +89,9 @@ func (r *ams_taskResource) Schema(_ context.Context, _ resource.SchemaRequest, r "applied_version": &schema.StringAttribute{ Computed: true, }, + "variable_set": &schema.StringAttribute{ + Optional: true, + }, }, } } @@ -119,6 +124,7 @@ func (data *AMSTaskResourceModel) toAPI(api *AMSTaskAPIModel, diagnostics *diag. } api.Name = data.Name.ValueString() api.Description = data.Description.ValueString() + api.VariableSet = data.VariableSet.ValueString() return true } diff --git a/kaleido/platform/ams_task_test.go b/kaleido/platform/ams_task_test.go index 41f7761..5657138 100644 --- a/kaleido/platform/ams_task_test.go +++ b/kaleido/platform/ams_task_test.go @@ -14,6 +14,7 @@ package platform import ( + _ "embed" "fmt" "net/http" "testing" @@ -24,8 +25,6 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/stretchr/testify/assert" - - _ "embed" ) var ams_taskStep1 = ` @@ -38,7 +37,8 @@ resource "kaleido_platform_ams_task" "ams_task1" { name = "step1" things = "stuff" }] - }) + }) + variable_set = "my-variables" } ` @@ -58,7 +58,9 @@ resource "kaleido_platform_ams_task" "ams_task1" { name = "step2" stuff = "other stuff" }] - }) + }) + variable_set = "my-variables" + } ` @@ -90,6 +92,7 @@ func TestAMSTask1(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrSet(ams_task1Resource, "id"), resource.TestCheckResourceAttrSet(ams_task1Resource, "applied_version"), + resource.TestCheckResourceAttrSet(ams_task1Resource, "variable_set"), ), }, { @@ -107,7 +110,8 @@ func TestAMSTask1(t *testing.T) { "description": "shiny task that does stuff and more stuff", "created": "%[2]s", "updated": "%[3]s", - "currentVersion": "%[4]s" + "currentVersion": "%[4]s", + "variableSet": "my-variables" }`, // generated fields that vary per test run id, From 5f95d3537ce546be6dee93c80688bda8248b0d8b Mon Sep 17 00:00:00 2001 From: John Hosie Date: Tue, 18 Jun 2024 13:41:18 -0400 Subject: [PATCH 4/6] add variable set resource Signed-off-by: John Hosie --- kaleido/platform/ams_variableset.go | 185 +++++++++++++++++++++++ kaleido/platform/ams_variableset_test.go | 173 +++++++++++++++++++++ kaleido/platform/common.go | 4 +- kaleido/platform/mockserver_test.go | 7 + 4 files changed, 368 insertions(+), 1 deletion(-) create mode 100644 kaleido/platform/ams_variableset.go create mode 100644 kaleido/platform/ams_variableset_test.go diff --git a/kaleido/platform/ams_variableset.go b/kaleido/platform/ams_variableset.go new file mode 100644 index 0000000..2ab360b --- /dev/null +++ b/kaleido/platform/ams_variableset.go @@ -0,0 +1,185 @@ +// Copyright © Kaleido, Inc. 2024 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package platform + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type AMSVariableSetResourceModel struct { + ID types.String `tfsdk:"id"` + Environment types.String `tfsdk:"environment"` + Service types.String `tfsdk:"service"` + Name types.String `tfsdk:"name"` + Classification types.String `tfsdk:"classification"` + Description types.String `tfsdk:"description"` + VariablesJSON types.String `tfsdk:"variables_json"` +} + +type AMSVariableSetAPIModel struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Created *time.Time `json:"created,omitempty"` + Updated *time.Time `json:"updated,omitempty"` + Classification string `json:"classification,omitempty"` + Description string `json:"description,omitempty"` + Variables interface{} `json:"variables,omitempty"` +} + +func AMSVariableSetResourceFactory() resource.Resource { + return &ams_variablesetResource{} +} + +type ams_variablesetResource struct { + commonResource +} + +func (r *ams_variablesetResource) Metadata(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "kaleido_platform_ams_variableset" +} + +func (r *ams_variablesetResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": &schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, + }, + "environment": &schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + }, + "service": &schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + }, + "name": &schema.StringAttribute{ + Required: true, + }, + "classification": &schema.StringAttribute{ + Computed: true, + Optional: true, + }, + "description": &schema.StringAttribute{ + Optional: true, + }, + "variables_json": &schema.StringAttribute{ + Required: true, + }, + }, + } +} + +func (data *AMSVariableSetResourceModel) toAPI(api *AMSVariableSetAPIModel, diagnostics *diag.Diagnostics) bool { + api.Name = data.Name.ValueString() + api.Description = data.Description.ValueString() + api.Classification = data.Classification.ValueString() + err := json.Unmarshal([]byte(data.VariablesJSON.ValueString()), &api.Variables) + if err != nil { + diagnostics.AddError("failed to serialize variables JSON", err.Error()) + return false + } + return true +} + +func (api *AMSVariableSetAPIModel) toData(data *AMSVariableSetResourceModel) { + data.ID = types.StringValue(api.ID) + data.Name = types.StringValue(api.Name) + if api.Description != "" { + data.Description = types.StringValue(api.Description) + } + data.Classification = types.StringValue(api.Classification) +} + +func (r *ams_variablesetResource) apiPath(data *AMSVariableSetResourceModel, idOrName string) string { + return fmt.Sprintf("/endpoint/%s/%s/rest/api/v1/variable-sets/%s", data.Environment.ValueString(), data.Service.ValueString(), idOrName) +} + +func (r *ams_variablesetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + + var data AMSVariableSetResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + var api AMSVariableSetAPIModel + ok := data.toAPI(&api, &resp.Diagnostics) + if ok { + ok, _ = r.apiRequest(ctx, http.MethodPut, r.apiPath(&data, data.Name.ValueString()), &api, &api, &resp.Diagnostics) + } + if !ok { + return + } + + api.toData(&data) + resp.Diagnostics.Append(resp.State.Set(ctx, data)...) + +} + +func (r *ams_variablesetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + + var data AMSVariableSetResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("id"), &data.ID)...) + + var api AMSVariableSetAPIModel + ok := data.toAPI(&api, &resp.Diagnostics) + if ok { + ok, _ = r.apiRequest(ctx, http.MethodPut, r.apiPath(&data, data.ID.ValueString()), &api, &api, &resp.Diagnostics) + } + if !ok { + return + } + + api.toData(&data) + resp.Diagnostics.Append(resp.State.Set(ctx, data)...) +} + +func (r *ams_variablesetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data AMSVariableSetResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + var api AMSVariableSetAPIModel + api.ID = data.ID.ValueString() + ok, status := r.apiRequest(ctx, http.MethodGet, r.apiPath(&data, data.ID.ValueString()), nil, &api, &resp.Diagnostics, Allow404()) + if !ok { + return + } + if status == 404 { + resp.State.RemoveResource(ctx) + return + } + + api.toData(&data) + resp.Diagnostics.Append(resp.State.Set(ctx, data)...) +} + +func (r *ams_variablesetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data AMSVariableSetResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + _, _ = r.apiRequest(ctx, http.MethodDelete, r.apiPath(&data, data.ID.ValueString()), nil, nil, &resp.Diagnostics, Allow404()) + + r.waitForRemoval(ctx, r.apiPath(&data, data.ID.ValueString()), &resp.Diagnostics) +} diff --git a/kaleido/platform/ams_variableset_test.go b/kaleido/platform/ams_variableset_test.go new file mode 100644 index 0000000..cb47a8b --- /dev/null +++ b/kaleido/platform/ams_variableset_test.go @@ -0,0 +1,173 @@ +// Copyright © Kaleido, Inc. 2024 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package platform + +import ( + _ "embed" + "fmt" + "net/http" + "testing" + "time" + + "github.com/aidarkhanov/nanoid" + "github.com/gorilla/mux" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/stretchr/testify/assert" +) + +var ams_variablesetStep1 = ` + resource "kaleido_platform_ams_variableset" "ams_variableset1" { + environment = "env1" + service = "service1" + name = "variable_set_1" + classification = "secret" + variables_json = "{\"foo\":\"bar\"}" +} +` + +var ams_variablesetUpdateDescription = ` + resource "kaleido_platform_ams_variableset" "ams_variableset1" { + environment = "env1" + service = "service1" + name = "variable_set_1" + description = "this is my updated description" + classification = "secret" + variables_json = "{\"foo\":\"bar\"}" +} +` + +var ams_variablesetUpdateVariable = ` + resource "kaleido_platform_ams_variableset" "ams_variableset1" { + environment = "env1" + service = "service1" + name = "variable_set_1" + description = "this is my updated description" + classification = "secret" + variables_json = "{\"baz\":\"quz\"}" +} +` + +func TestAMSVariableSet1(t *testing.T) { + + mp, providerConfig := testSetup(t) + defer func() { + mp.checkClearCalls([]string{ + "PUT /endpoint/{env}/{service}/rest/api/v1/variable-sets/{variable-set}", // by name initially + "GET /endpoint/{env}/{service}/rest/api/v1/variable-sets/{variable-set}", + "GET /endpoint/{env}/{service}/rest/api/v1/variable-sets/{variable-set}", + "PUT /endpoint/{env}/{service}/rest/api/v1/variable-sets/{variable-set}", // by name initially + "GET /endpoint/{env}/{service}/rest/api/v1/variable-sets/{variable-set}", + "GET /endpoint/{env}/{service}/rest/api/v1/variable-sets/{variable-set}", + "PUT /endpoint/{env}/{service}/rest/api/v1/variable-sets/{variable-set}", // by name initially + "GET /endpoint/{env}/{service}/rest/api/v1/variable-sets/{variable-set}", + "DELETE /endpoint/{env}/{service}/rest/api/v1/variable-sets/{variable-set}", + "GET /endpoint/{env}/{service}/rest/api/v1/variable-sets/{variable-set}", + }) + mp.server.Close() + }() + + ams_variableset1Resource := "kaleido_platform_ams_variableset.ams_variableset1" + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV6ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: providerConfig + ams_variablesetStep1, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(ams_variableset1Resource, "id"), + resource.TestCheckResourceAttr(ams_variableset1Resource, "classification", "secret"), + resource.TestCheckNoResourceAttr(ams_variableset1Resource, "description"), + ), + }, + { + Config: providerConfig + ams_variablesetUpdateDescription, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(ams_variableset1Resource, "id"), + resource.TestCheckResourceAttr(ams_variableset1Resource, "classification", "secret"), + resource.TestCheckResourceAttr(ams_variableset1Resource, "description", "this is my updated description"), + resource.TestCheckResourceAttr(ams_variableset1Resource, "variables_json", "{\"foo\":\"bar\"}"), + ), + }, + { + Config: providerConfig + ams_variablesetUpdateVariable, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(ams_variableset1Resource, "id"), + resource.TestCheckResourceAttr(ams_variableset1Resource, "classification", "secret"), + resource.TestCheckResourceAttr(ams_variableset1Resource, "description", "this is my updated description"), + resource.TestCheckResourceAttr(ams_variableset1Resource, "variables_json", "{\"baz\":\"quz\"}"), + func(s *terraform.State) error { + // Compare the final result on the mock-server side + id := s.RootModule().Resources[ams_variableset1Resource].Primary.Attributes["id"] + obj := mp.amsVariableSets[fmt.Sprintf("env1/service1/%s", id)] + testJSONEqual(t, obj, fmt.Sprintf(` + { + "id": "%[1]s", + "created": "%[2]s", + "updated": "%[3]s", + "name": "variable_set_1", + "classification":"secret", + "description": "this is my updated description", + "variables":{ + "baz":"quz" + } + } + `, + // generated fields that vary per test run + id, + obj.Created.UTC().Format(time.RFC3339Nano), + obj.Updated.UTC().Format(time.RFC3339Nano), + )) + return nil + }, + ), + }, + }, + }) +} + +func (mp *mockPlatform) getAMSVariableSet(res http.ResponseWriter, req *http.Request) { + obj := mp.amsVariableSets[mux.Vars(req)["env"]+"/"+mux.Vars(req)["service"]+"/"+mux.Vars(req)["variable-set"]] + if obj == nil { + mp.respond(res, nil, 404) + } else { + mp.respond(res, obj, 200) + } +} + +func (mp *mockPlatform) putAMSVariableSet(res http.ResponseWriter, req *http.Request) { + now := time.Now().UTC() + obj := mp.amsVariableSets[mux.Vars(req)["env"]+"/"+mux.Vars(req)["service"]+"/"+mux.Vars(req)["variable-set"]] + var newObj AMSVariableSetAPIModel + mp.getBody(req, &newObj) + if obj == nil { + assert.Equal(mp.t, newObj.Name, mux.Vars(req)["variable-set"]) + newObj.ID = nanoid.New() + newObj.Created = &now + } else { + assert.Equal(mp.t, obj.ID, mux.Vars(req)["variable-set"]) + newObj.ID = mux.Vars(req)["variable-set"] + newObj.Created = obj.Created + } + newObj.Updated = &now + mp.amsVariableSets[mux.Vars(req)["env"]+"/"+mux.Vars(req)["service"]+"/"+newObj.ID] = &newObj + mp.respond(res, &newObj, 200) +} + +func (mp *mockPlatform) deleteAMSVariableSet(res http.ResponseWriter, req *http.Request) { + obj := mp.amsVariableSets[mux.Vars(req)["env"]+"/"+mux.Vars(req)["service"]+"/"+mux.Vars(req)["variable-set"]] + assert.NotNil(mp.t, obj) + delete(mp.amsVariableSets, mux.Vars(req)["env"]+"/"+mux.Vars(req)["service"]+"/"+mux.Vars(req)["variable-set"]) + mp.respond(res, nil, 204) +} diff --git a/kaleido/platform/common.go b/kaleido/platform/common.go index 9b55466..c574907 100644 --- a/kaleido/platform/common.go +++ b/kaleido/platform/common.go @@ -26,8 +26,9 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-log/tflog" - "github.com/kaleido-io/terraform-provider-kaleido/kaleido/kaleidobase" "gopkg.in/yaml.v3" + + "github.com/kaleido-io/terraform-provider-kaleido/kaleido/kaleidobase" ) type APIRequestOption struct { @@ -244,6 +245,7 @@ func Resources() []func() resource.Resource { AMSFFListenerResourceFactory, AMSDMListenerResourceFactory, AMSDMUpsertResourceFactory, + AMSVariableSetResourceFactory, FireFlyRegistrationResourceFactory, } } diff --git a/kaleido/platform/mockserver_test.go b/kaleido/platform/mockserver_test.go index 229d10e..1dcd126 100644 --- a/kaleido/platform/mockserver_test.go +++ b/kaleido/platform/mockserver_test.go @@ -51,6 +51,7 @@ type mockPlatform struct { amsDMUpserts map[string]map[string]interface{} amsFFListeners map[string]*AMSFFListenerAPIModel amsDMListeners map[string]*AMSDMListenerAPIModel + amsVariableSets map[string]*AMSVariableSetAPIModel groups map[string]*GroupAPIModel ffsNode *FireFlyStatusNodeAPIModel ffsOrg *FireFlyStatusOrgAPIModel @@ -75,6 +76,7 @@ func startMockPlatformServer(t *testing.T) *mockPlatform { amsDMUpserts: make(map[string]map[string]interface{}), amsFFListeners: make(map[string]*AMSFFListenerAPIModel), amsDMListeners: make(map[string]*AMSDMListenerAPIModel), + amsVariableSets: make(map[string]*AMSVariableSetAPIModel), groups: make(map[string]*GroupAPIModel), router: mux.NewRouter(), calls: []string{}, @@ -154,6 +156,11 @@ func startMockPlatformServer(t *testing.T) *mockPlatform { mp.register("/endpoint/{env}/{service}/rest/api/v1/listeners/datamodel/{listener}", http.MethodPut, mp.putAMSDMListener) mp.register("/endpoint/{env}/{service}/rest/api/v1/listeners/datamodel/{listener}", http.MethodDelete, mp.deleteAMSDMListener) + // See ams_dmlistener.go + mp.register("/endpoint/{env}/{service}/rest/api/v1/variable-sets/{variable-set}", http.MethodGet, mp.getAMSVariableSet) + mp.register("/endpoint/{env}/{service}/rest/api/v1/variable-sets/{variable-set}", http.MethodPut, mp.putAMSVariableSet) + mp.register("/endpoint/{env}/{service}/rest/api/v1/variable-sets/{variable-set}", http.MethodDelete, mp.deleteAMSVariableSet) + // See firefly_registration.go mp.register("/endpoint/{env}/{service}/rest/api/v1/network/nodes/self", http.MethodPost, mp.postFireFlyRegistrationNode) mp.register("/endpoint/{env}/{service}/rest/api/v1/network/organizations/self", http.MethodPost, mp.postFireFlyRegistrationOrg) From 65d6a2341379953908dd39f742e945b26c461542 Mon Sep 17 00:00:00 2001 From: John Hosie Date: Wed, 19 Jun 2024 09:00:30 -0400 Subject: [PATCH 5/6] PR comments --- kaleido/platform/ams_variableset.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kaleido/platform/ams_variableset.go b/kaleido/platform/ams_variableset.go index 2ab360b..d31ab9c 100644 --- a/kaleido/platform/ams_variableset.go +++ b/kaleido/platform/ams_variableset.go @@ -87,7 +87,8 @@ func (r *ams_variablesetResource) Schema(_ context.Context, _ resource.SchemaReq Optional: true, }, "variables_json": &schema.StringAttribute{ - Required: true, + Sensitive: true, + Required: true, }, }, } From 6059f786f061f1f8cbc34ab29db615ff33002bc1 Mon Sep 17 00:00:00 2001 From: John Hosie Date: Wed, 19 Jun 2024 09:00:50 -0400 Subject: [PATCH 6/6] indentation --- kaleido/platform/ams_policy_test.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/kaleido/platform/ams_policy_test.go b/kaleido/platform/ams_policy_test.go index 23d5e59..6a514fb 100644 --- a/kaleido/platform/ams_policy_test.go +++ b/kaleido/platform/ams_policy_test.go @@ -15,6 +15,7 @@ package platform import ( "crypto/sha256" + _ "embed" "encoding/hex" "fmt" "net/http" @@ -26,26 +27,24 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/stretchr/testify/assert" - - _ "embed" ) var ams_policyStep1 = ` resource "kaleido_platform_ams_policy" "ams_policy1" { - environment = "env1" + environment = "env1" service = "service1" name = "ams_policy1" - document = "document 1" + document = "document 1" } ` var ams_policyStep2 = ` resource "kaleido_platform_ams_policy" "ams_policy1" { - environment = "env1" + environment = "env1" service = "service1" name = "ams_policy1" description = "shiny policy that does stuff and more stuff" - document = "document 2" + document = "document 2" example_input = "input 2" } `