diff --git a/.github/workflows/go.yaml b/.github/workflows/go.yaml index 378a263..572c879 100644 --- a/.github/workflows/go.yaml +++ b/.github/workflows/go.yaml @@ -14,6 +14,11 @@ jobs: - uses: actions/checkout@v4 with: path: 'terraform-provider-kaleido' + + - uses: hashicorp/setup-terraform@v3 + with: + terraform_version: "1.8.5" + terraform_wrapper: false - name: Set up Go uses: actions/setup-go@v5 diff --git a/kaleido/platform/runtime.go b/kaleido/platform/runtime.go index b00bf17..10a3b1b 100644 --- a/kaleido/platform/runtime.go +++ b/kaleido/platform/runtime.go @@ -38,6 +38,10 @@ type RuntimeResourceModel struct { Size types.String `tfsdk:"size"` EnvironmentMemberID types.String `tfsdk:"environment_member_id"` Stopped types.Bool `tfsdk:"stopped"` + Zone types.String `tfsdk:"zone"` + SubZone types.String `tfsdk:"sub_zone"` + StorageSize types.Int64 `tfsdk:"storage_size"` + StorageType types.String `tfsdk:"storage_type"` } type RuntimeAPIModel struct { @@ -52,7 +56,11 @@ type RuntimeAPIModel struct { EnvironmentMemberID string `json:"environmentMemberId,omitempty"` Status string `json:"status,omitempty"` Deleted bool `json:"deleted,omitempty"` - Stopped bool `json:"stopped,omitempty"` + Stopped bool `json:"stopped"` + Zone string `json:"zone,omitempty"` + SubZone string `json:"subZone,omitempty"` + StorageSize int64 `json:"storageSize,omitempty"` + StorageType string `json:"storageType,omitempty"` } func RuntimeResourceFactory() resource.Resource { @@ -103,6 +111,21 @@ func (r *runtimeResource) Schema(_ context.Context, _ resource.SchemaRequest, re Optional: true, Computed: true, }, + "zone": &schema.StringAttribute{ + Optional: true, + Computed: true, + }, + "sub_zone": &schema.StringAttribute{ + Optional: true, + }, + "storage_size": &schema.Int64Attribute{ + Optional: true, + // may be computed for certain storage required runtime types, but we will not track it if the user did not provide it + }, + "storage_type": &schema.StringAttribute{ + Optional: true, + // may be computed for certain runtime types, but we will not track it if the user did not provide it + }, }, } } @@ -125,6 +148,18 @@ func (data *RuntimeResourceModel) toAPI(api *RuntimeAPIModel) { if !data.Stopped.IsNull() { api.Stopped = data.Stopped.ValueBool() } + if !data.Zone.IsNull() { + api.Zone = data.Zone.ValueString() + } + if !data.SubZone.IsNull() { + api.SubZone = data.SubZone.ValueString() + } + if !data.StorageSize.IsNull() { + api.StorageSize = data.StorageSize.ValueInt64() + } + if !data.StorageType.IsNull() { + api.StorageType = data.StorageType.ValueString() + } } func (api *RuntimeAPIModel) toData(data *RuntimeResourceModel) { @@ -133,6 +168,19 @@ func (api *RuntimeAPIModel) toData(data *RuntimeResourceModel) { data.LogLevel = types.StringValue(api.LogLevel) data.Size = types.StringValue(api.Size) data.Stopped = types.BoolValue(api.Stopped) + data.Zone = types.StringValue(api.Zone) + if api.SubZone != "" { // the API should only return a subzone if a subzone was specified + data.SubZone = types.StringValue(api.SubZone) + } + // For storage - it is optional for the user and conditional for the runtime based on its type. + // We can't mark it computed as a result, so we only track API storage state if the user provided desired + // storage state. + if api.StorageSize > 0 && !data.StorageSize.IsNull() { + data.StorageSize = types.Int64Value(api.StorageSize) + } + if api.StorageType != "" && !data.StorageType.IsNull() { + data.StorageType = types.StringValue(api.StorageType) + } } func (r *runtimeResource) apiPath(data *RuntimeResourceModel) string { diff --git a/kaleido/platform/runtime_test.go b/kaleido/platform/runtime_test.go index e19da04..ace3678 100644 --- a/kaleido/platform/runtime_test.go +++ b/kaleido/platform/runtime_test.go @@ -36,10 +36,31 @@ resource "kaleido_platform_runtime" "runtime1" { config_json = jsonencode({ "setting1": "value1" }) + zone = "use2" + storage_size = 10 + storage_type = "default" } ` var runtimeStep2 = ` +resource "kaleido_platform_runtime" "runtime1" { + environment = "env1" + type = "besu" + name = "runtime1" + config_json = jsonencode({ + "setting1": "value1", + "setting2": "value2", + }) + log_level = "trace" + size = "large" + stopped = false + zone = "use2" + storage_size = 20 + storage_type = "default" +} +` + +var runtimeStep3 = ` resource "kaleido_platform_runtime" "runtime1" { environment = "env1" type = "besu" @@ -51,6 +72,9 @@ resource "kaleido_platform_runtime" "runtime1" { log_level = "trace" size = "large" stopped = true + zone = "use2" + storage_size = 20 + storage_type = "default" } ` @@ -65,6 +89,10 @@ func TestRuntime1(t *testing.T) { "GET /api/v1/environments/{env}/runtimes/{runtime}", "PUT /api/v1/environments/{env}/runtimes/{runtime}", "GET /api/v1/environments/{env}/runtimes/{runtime}", + "GET /api/v1/environments/{env}/runtimes/{runtime}", + "GET /api/v1/environments/{env}/runtimes/{runtime}", + "PUT /api/v1/environments/{env}/runtimes/{runtime}", + "GET /api/v1/environments/{env}/runtimes/{runtime}", "DELETE /api/v1/environments/{env}/runtimes/{runtime}", "GET /api/v1/environments/{env}/runtimes/{runtime}", }) @@ -86,10 +114,63 @@ func TestRuntime1(t *testing.T) { resource.TestCheckResourceAttr(runtime1Resource, "log_level", `info`), resource.TestCheckResourceAttr(runtime1Resource, "size", `small`), resource.TestCheckResourceAttr(runtime1Resource, "stopped", `false`), + resource.TestCheckResourceAttr(runtime1Resource, "zone", "use2"), + resource.TestCheckResourceAttr(runtime1Resource, "storage_size", "10"), + resource.TestCheckResourceAttr(runtime1Resource, "storage_type", "default"), ), }, { Config: providerConfig + runtimeStep2, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(runtime1Resource, "id"), + resource.TestCheckResourceAttr(runtime1Resource, "name", `runtime1`), + resource.TestCheckResourceAttr(runtime1Resource, "type", `besu`), + resource.TestCheckResourceAttr(runtime1Resource, "config_json", `{"setting1":"value1","setting2":"value2"}`), + resource.TestCheckResourceAttr(runtime1Resource, "log_level", `trace`), + resource.TestCheckResourceAttr(runtime1Resource, "size", `large`), + resource.TestCheckResourceAttr(runtime1Resource, "stopped", `false`), + resource.TestCheckResourceAttr(runtime1Resource, "zone", "use2"), + resource.TestCheckResourceAttr(runtime1Resource, "storage_size", "20"), + resource.TestCheckResourceAttr(runtime1Resource, "storage_type", "default"), + func(s *terraform.State) error { + // Compare the final result on the mock-server side + id := s.RootModule().Resources[runtime1Resource].Primary.Attributes["id"] + rt := mp.runtimes[fmt.Sprintf("env1/%s", id)] + // Note the pending status is allowed to remain in runtimes, as they require at least one + // service to be created to get out of pending. + testJSONEqual(t, rt, fmt.Sprintf(` + { + "id": "%[1]s", + "created": "%[2]s", + "updated": "%[3]s", + "type": "besu", + "name": "runtime1", + "config": { + "setting1": "value1", + "setting2": "value2" + }, + "loglevel": "trace", + "size": "large", + "environmentMemberId": "%[4]s", + "status": "pending", + "stopped": false, + "zone": "use2", + "storageSize": 20, + "storageType": "default" + } + `, + // generated fields that vary per test run + id, + rt.Created.UTC().Format(time.RFC3339Nano), + rt.Updated.UTC().Format(time.RFC3339Nano), + rt.EnvironmentMemberID, + )) + return nil + }, + ), + }, + { + Config: providerConfig + runtimeStep3, Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrSet(runtime1Resource, "id"), resource.TestCheckResourceAttr(runtime1Resource, "name", `runtime1`), @@ -98,6 +179,9 @@ func TestRuntime1(t *testing.T) { resource.TestCheckResourceAttr(runtime1Resource, "log_level", `trace`), resource.TestCheckResourceAttr(runtime1Resource, "size", `large`), resource.TestCheckResourceAttr(runtime1Resource, "stopped", `true`), + resource.TestCheckResourceAttr(runtime1Resource, "zone", "use2"), + resource.TestCheckResourceAttr(runtime1Resource, "storage_size", "20"), + resource.TestCheckResourceAttr(runtime1Resource, "storage_type", "default"), func(s *terraform.State) error { // Compare the final result on the mock-server side id := s.RootModule().Resources[runtime1Resource].Primary.Attributes["id"] @@ -119,7 +203,10 @@ func TestRuntime1(t *testing.T) { "size": "large", "environmentMemberId": "%[4]s", "status": "pending", - "stopped": true + "stopped": true, + "zone": "use2", + "storageSize": 20, + "storageType": "default" } `, // generated fields that vary per test run @@ -161,6 +248,16 @@ func (mp *mockPlatform) postRuntime(res http.ResponseWriter, req *http.Request) if rt.Size == "" { rt.Size = "small" } + if rt.Zone == "" { + rt.Zone = "default" + } + // if they provide a SubZone its just returned back + if rt.StorageType == "" { + rt.StorageType = "default" + } + if rt.StorageSize <= 0 { + rt.StorageSize = 50 + } rt.Status = "pending" mp.runtimes[mux.Vars(req)["env"]+"/"+rt.ID] = &rt mp.respond(res, &rt, 201) @@ -176,6 +273,9 @@ func (mp *mockPlatform) putRuntime(res http.ResponseWriter, req *http.Request) { now := time.Now().UTC() newRT.Created = rt.Created newRT.Updated = &now + if rt.StorageSize > 0 && newRT.StorageSize < rt.StorageSize { + mp.respond(res, nil, 400) + } newRT.Status = "pending" mp.runtimes[mux.Vars(req)["env"]+"/"+mux.Vars(req)["runtime"]] = &newRT mp.respond(res, &newRT, 200)