diff --git a/internal/provider/data_source_application.go b/internal/provider/data_source_application.go index 9c89f4e9..1b072f8e 100644 --- a/internal/provider/data_source_application.go +++ b/internal/provider/data_source_application.go @@ -31,22 +31,9 @@ type applicationDataSource struct { type applicationDataSourceModel struct { ApplicationName types.String `tfsdk:"name"` - // Charm types.List `tfsdk:"charm"` - // Config types.Map `tfsdk:"config"` - // Constraints types.String `tfsdk:"constraints"` - // Expose types.List `tfsdk:"expose"` - ModelName types.String `tfsdk:"model"` - // Placement types.String `tfsdk:"placement"` - // EndpointBindings types.Set `tfsdk:"endpoint_bindings"` - // Resources types.Map `tfsdk:"resources"` - // StorageDirectives types.Map `tfsdk:"storage_directives"` - // Storage types.Set `tfsdk:"storage"` - // TODO - remove Principal when we version the schema - // and remove deprecated elements. Once we create upgrade - // functionality it can be removed from the structure. - // Principal types.Bool `tfsdk:"principal"` - Trust types.Bool `tfsdk:"trust"` - // UnitCount types.Int64 `tfsdk:"units"` + ModelName types.String `tfsdk:"model"` + Trust types.Bool `tfsdk:"trust"` + UnitCount types.Int64 `tfsdk:"units"` // ID required by the testing framework ID types.String `tfsdk:"id"` } @@ -63,15 +50,19 @@ func (d *applicationDataSource) Schema(_ context.Context, _ datasource.SchemaReq " is not supported.", Attributes: map[string]schema.Attribute{ "name": schema.StringAttribute{ - Description: "A custom name for the application deployment. If empty, uses the charm's name.", + Description: "Name of the application deployment.", Required: true, }, "model": schema.StringAttribute{ - Description: "The name of the model where the application is to be deployed.", + Description: "The name of the model where the application is deployed.", Required: true, }, "trust": schema.BoolAttribute{ - Description: "Set the trust for the application.", + Description: "Trust of the application.", + Computed: true, + }, + "units": schema.Int64Attribute{ + Description: "The number of application units deployed for the charm.", Computed: true, }, "id": schema.StringAttribute{ @@ -147,207 +138,13 @@ func (d *applicationDataSource) Read(ctx context.Context, req datasource.ReadReq // Use the response to fill in data - // data.Placement = types.StringValue(response.Placement) - // data.Principal = types.BoolNull() - // data.UnitCount = types.Int64Value(int64(response.Units)) data.Trust = types.BoolValue(response.Trust) - - // // data requiring transformation - // dataCharm := nestedCharm{ - // Name: types.StringValue(response.Name), - // Channel: types.StringValue(response.Channel), - // Revision: types.Int64Value(int64(response.Revision)), - // Base: types.StringValue(response.Base), - // Series: types.StringValue(response.Series), - // } - // charmType := req.data.Schema.GetBlocks()[CharmKey].(schema.ListNestedBlock).NestedObject.Type() - // data.Charm, dErr = types.ListValueFrom(ctx, charmType, []nestedCharm{dataCharm}) - // if dErr.HasError() { - // resp.Diagnostics.Append(dErr...) - // return - // } - - // // Constraints do not apply to subordinate applications. If the application - // // is subordinate, the constraints will be set to the empty string. - // data.Constraints = types.StringValue(response.Constraints.String()) - - // exposeType := req.data.Schema.GetBlocks()[ExposeKey].(schema.ListNestedBlock).NestedObject.Type() - // if response.Expose != nil { - // exp := parseNestedExpose(response.Expose) - // data.Expose, dErr = types.ListValueFrom(ctx, exposeType, []nestedExpose{exp}) - // if dErr.HasError() { - // resp.Diagnostics.Append(dErr...) - // return - // } - // } else { - // data.Expose = types.ListNull(exposeType) - // } - - // // we only set changes if there is any difference between - // // the previous and the current config values - // configType := req.data.Schema.GetAttributes()[ConfigKey].(schema.MapAttribute).ElementType - // data.Config, dErr = r.configureConfigData(ctx, configType, data.Config, response.Config) - // if dErr.HasError() { - // resp.Diagnostics.Append(dErr...) - // return - // } - - // endpointBindingsType := req.data.Schema.GetAttributes()[EndpointBindingsKey].(schema.SetNestedAttribute).NestedObject.Type() - // if len(response.EndpointBindings) > 0 { - // data.EndpointBindings, dErr = r.toEndpointBindingsSet(ctx, endpointBindingsType, response.EndpointBindings) - // if dErr.HasError() { - // resp.Diagnostics.Append(dErr...) - // return - // } - // } - - // // convert the storage map to a list of nestedStorage - // nestedStorageSlice := make([]nestedStorage, 0, len(response.Storage)) - // for name, storage := range response.Storage { - // humanizedSize := transformSizeToHumanizedFormat(storage.Size) - // nestedStorageSlice = append(nestedStorageSlice, nestedStorage{ - // Label: types.StringValue(name), - // Size: types.StringValue(humanizedSize), - // Pool: types.StringValue(storage.Pool), - // Count: types.Int64Value(int64(storage.Count)), - // }) - // } - // storageType := req.data.Schema.GetAttributes()[StorageKey].(schema.SetNestedAttribute).NestedObject.Type() - // if len(nestedStorageSlice) > 0 { - // data.Storage, dErr = types.SetValueFrom(ctx, storageType, nestedStorageSlice) - // if dErr.HasError() { - // resp.Diagnostics.Append(dErr...) - // return - // } - // } else { - // data.Storage = types.SetNull(storageType) - // } - - // resourceType := req.data.Schema.GetAttributes()[ResourceKey].(schema.MapAttribute).ElementType - // data.Resources, dErr = d.configureDataSourceData(ctx, resourceType, data.Resources, response.Resources) - // if dErdasError() { - // resp.Diagnostics.Append(dErr...) - // return - // } + data.UnitCount = types.Int64Value(int64(response.Units)) d.trace("Found", applicationDataSourceModelForLogging(ctx, &data)) resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } -// func (d *applicationDataSource) configureConfigData(ctx context.Context, configType attr.Type, config types.Map, respCfg map[string]juju.ConfigEntry) (types.Map, diag.Diagnostics) { -// // We focus on those config entries that are not the default value. -// // If the value was the same we ignore it. If no changes were made, -// // jump to the next step. -// var previousConfig map[string]string -// diagErr := config.ElementsAs(ctx, &previousConfig, false) -// if diagErr.HasError() { -// d.trace("configureConfigData exit A") -// return types.Map{}, diagErr -// } -// if previousConfig == nil { -// previousConfig = make(map[string]string) -// } -// // known previously -// // update the values from the previous config -// changes := false -// for k, v := range respCfg { -// // Add if the value has changed from the previous state -// if previousValue, found := previousConfig[k]; found { -// if !juju.EqualConfigEntries(v, previousValue) { -// // remember that this Terraform schema type only accepts strings -// previousConfig[k] = v.String() -// changes = true -// } -// } else if !v.IsDefault { -// // Add if the value is not default -// previousConfig[k] = v.String() -// changes = true -// } -// } -// if changes { -// return types.MapValueFrom(ctx, configType, previousConfig) -// } -// return config, nil -// } - -// // Read is called when the provider must read data source values in -// // order to update state. Config values should be read from the -// // ReadRequest and new state values set on the ReadResponse. -// func (d *applicationDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { -// // Prevent panic if the provider has not been configured. -// if d.client == nil { -// addDSClientNotConfiguredError(&resp.Diagnostics, "model") -// return -// } - -// var data applicationDataSourceModel - -// // Read Terraform configuration data into the model. -// resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) -// if resp.Diagnostics.HasError() { -// return -// } - -// // Get current juju model data source values. -// model, err := d.client.Models.GetModelByName(data.Name.ValueString()) -// if err != nil { -// resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read model, got error: %s", err)) -// return -// } -// d.trace(fmt.Sprintf("read juju model %q data source", data.Name)) - -// // Save data into Terraform state -// data.Name = types.StringValue(model.Name) -// data.UUID = types.StringValue(model.UUID) -// data.ID = types.StringValue(model.UUID) -// resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) -// } - -// // Convert the endpoint bindings from the juju api to terraform nestedEndpointBinding set -// func (d *applicationDataSource) toEndpointBindingsSet(ctx context.Context, endpointBindingsType attr.Type, endpointBindings map[string]string) (types.Set, diag.Diagnostics) { -// endpointBindingsSlice := make([]nestedEndpointBinding, 0, len(endpointBindings)) -// for endpoint, space := range endpointBindings { -// var endpointString types.String -// if endpoint == "" { -// endpointString = types.StringNull() -// } else { -// endpointString = types.StringValue(endpoint) -// } -// endpointBindingsSlice = append(endpointBindingsSlice, nestedEndpointBinding{Endpoint: endpointString, Space: types.StringValue(space)}) -// } - -// return types.SetValueFrom(ctx, endpointBindingsType, endpointBindingsSlice) -// } - -// func (d *applicationDataSource) configureDataSourceData(ctx context.Context, resourceType attr.Type, resources types.Map, respResources map[string]string) (types.Map, diag.Diagnostics) { -// var previousResources map[string]string -// diagErr := resources.ElementsAs(ctx, &previousResources, false) -// if diagErr.HasError() { -// d.trace("configureDataSourceData exit A") -// return types.Map{}, diagErr -// } -// if previousResources == nil { -// previousResources = make(map[string]string) -// } -// // known previously -// // update the values from the previous config -// changes := false -// for k, v := range respResources { -// // Add if the value has changed from the previous state -// if previousValue, found := previousResources[k]; found { -// if v != previousValue { -// // remember that this Terraform schema type only accepts strings -// previousResources[k] = v -// changes = true -// } -// } -// } -// if changes { -// return types.MapValueFrom(ctx, resourceType, previousResources) -// } -// return resources, nil -// } - func (d *applicationDataSource) trace(msg string, additionalFields ...map[string]interface{}) { if d.subCtx == nil { return @@ -362,14 +159,9 @@ func (d *applicationDataSource) trace(msg string, additionalFields ...map[string func applicationDataSourceModelForLogging(_ context.Context, app *applicationDataSourceModel) map[string]interface{} { value := map[string]interface{}{ "application-name": app.ApplicationName.ValueString(), - // "charm": app.Charm.String(), - // "constraints": app.Constraints.ValueString(), - "model": app.ModelName.ValueString(), - // "placement": app.Placement.ValueString(), - // "expose": app.Expose.String(), - "trust": app.Trust.ValueBoolPointer(), - // "units": app.UnitCount.ValueInt64(), - // "storage": app.Storage.String(), + "model": app.ModelName.ValueString(), + "trust": app.Trust.ValueBoolPointer(), + "units": app.UnitCount.ValueInt64(), } return value } diff --git a/internal/provider/data_source_application_test.go b/internal/provider/data_source_application_test.go new file mode 100644 index 00000000..a00f8cdb --- /dev/null +++ b/internal/provider/data_source_application_test.go @@ -0,0 +1,94 @@ +// Copyright 2023 Canonical Ltd. +// Licensed under the Apache License, Version 2.0, see LICENCE file for details. + +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAcc_DataSourceApplication_Edge(t *testing.T) { + if testingCloud != LXDCloudTesting { + t.Skip(t.Name() + " only runs with LXD") + } + modelName := acctest.RandomWithPrefix("tf-datasource-application-test-model") + applicationName := acctest.RandomWithPrefix("tf-datasource-application-test-name") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: frameworkProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceApplication(modelName, applicationName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.juju_application.this", "model", modelName), + resource.TestCheckResourceAttr("data.juju_application.this", "name", applicationName), + ), + }, + }, + }) +} + +func TestAcc_DataSourceApplication_UpgradeProvider(t *testing.T) { + if testingCloud != LXDCloudTesting { + t.Skip(t.Name() + " only runs with LXD") + } + modelName := acctest.RandomWithPrefix("tf-datasource-application-test-model") + applicationName := acctest.RandomWithPrefix("tf-datasource-application-test-name") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "juju": { + VersionConstraint: TestProviderStableVersion, + Source: "juju/juju", + }, + }, + Config: testAccDataSourceApplication(modelName, applicationName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.juju_application.this", "model", modelName), + resource.TestCheckResourceAttr("data.juju_application.this", "name", applicationName), + ), + }, + { + ProtoV6ProviderFactories: frameworkProviderFactories, + Config: testAccDataSourceApplication(modelName, applicationName), + PlanOnly: true, + }, + }, + }) +} + +func testAccDataSourceApplication(modelName, applicationName string) string { + return fmt.Sprintf(` +resource "juju_model" "model" { + name = %q +} + +resource "juju_application" "this" { + name = %q + model = juju_model.model.name + + units = 2 + + charm { + name = "ubuntu" + channel = "edge" + revision = 24 + series = "trusty" + } +} + +data "juju_application" "this" { + model = juju_model.model.name + name = juju_application.this.name + +}`, modelName, applicationName) +}