From edec2aef4f00b2501c8afa13e6c08d353f849eb7 Mon Sep 17 00:00:00 2001 From: Heather Lanigan Date: Tue, 17 Oct 2023 17:24:18 -0400 Subject: [PATCH 1/2] Update base description. It shouldn't reference the old series nomenclature. --- docs/resources/machine.md | 2 +- internal/provider/resource_machine.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/resources/machine.md b/docs/resources/machine.md index 5abfd5b6..d78c9102 100644 --- a/docs/resources/machine.md +++ b/docs/resources/machine.md @@ -30,7 +30,7 @@ resource "juju_machine" "this_machine" { ### Optional -- `base` (String) The operating system series to install on the new machine(s). +- `base` (String) The operating system to install on the new machine(s). E.g. ubuntu@22.04. - `constraints` (String) Machine constraints that overwrite those available from 'juju get-model-constraints' and provider's defaults. - `disks` (String) Storage constraints for disks to attach to the machine(s). - `name` (String) A name for the machine resource in Terraform. diff --git a/internal/provider/resource_machine.go b/internal/provider/resource_machine.go index 7de167f9..d2ce1eed 100644 --- a/internal/provider/resource_machine.go +++ b/internal/provider/resource_machine.go @@ -138,7 +138,7 @@ func (r *machineResource) Schema(_ context.Context, req resource.SchemaRequest, }, }, BaseKey: schema.StringAttribute{ - Description: "The operating system series to install on the new machine(s).", + Description: "The operating system to install on the new machine(s). E.g. ubuntu@22.04.", Optional: true, Computed: true, PlanModifiers: []planmodifier.String{ From 667cf444b3b95ef95dc45d8813d1b124926e9153 Mon Sep 17 00:00:00 2001 From: Heather Lanigan Date: Tue, 17 Oct 2023 17:26:10 -0400 Subject: [PATCH 2/2] Add Base to application resources. Will replace series which is now deprecated. Due to use of the 2.9.45 code base, most operating system pieces will be handled as a series still. But this gives users a transition to be able to use bases. Remove unused elemented of CreateApplicationResponse. ReadApplication is necessary for all required data anyway. --- docs/resources/application.md | 3 +- internal/juju/applications.go | 85 ++++++++++++------- internal/provider/data_source_machine_test.go | 10 +-- internal/provider/data_source_offer_test.go | 10 +-- internal/provider/resource_application.go | 34 ++++++-- .../provider/resource_integration_test.go | 34 ++++---- internal/provider/resource_offer_test.go | 14 +-- 7 files changed, 119 insertions(+), 71 deletions(-) diff --git a/docs/resources/application.md b/docs/resources/application.md index c2eeaeeb..3ed9e50f 100644 --- a/docs/resources/application.md +++ b/docs/resources/application.md @@ -83,9 +83,10 @@ Required: Optional: +- `base` (String) The operating system on which to deploy. E.g. ubuntu@22.04. - `channel` (String) The channel to use when deploying a charm. Specified as \/\/\. - `revision` (Number) The revision of the charm to deploy. -- `series` (String) The series on which to deploy. +- `series` (String, Deprecated) The series on which to deploy. diff --git a/internal/juju/applications.go b/internal/juju/applications.go index adf28257..2d0370d8 100644 --- a/internal/juju/applications.go +++ b/internal/juju/applications.go @@ -101,6 +101,7 @@ type CreateApplicationInput struct { ModelName string CharmName string CharmChannel string + CharmBase string CharmSeries string CharmRevision int Units int @@ -112,9 +113,7 @@ type CreateApplicationInput struct { } type CreateApplicationResponse struct { - AppName string - Revision int - Series string + AppName string } type ReadApplicationInput struct { @@ -126,6 +125,7 @@ type ReadApplicationResponse struct { Name string Channel string Revision int + Base string Series string Units int Trust bool @@ -213,26 +213,40 @@ func (c applicationsClient) CreateApplication(input *CreateApplicationInput) (*C return nil, fmt.Errorf("specifying a revision requires a channel for future upgrades") } - // The architecture constraint is required to find the proper - // platform to deploy. First look at constraints provided by - // the user, fall back to any model constraints. - var platformCons constraints.Value - if input.Constraints.HasArch() { - platformCons = input.Constraints - } else { - platformCons, err = modelconfigAPIClient.GetModelConstraints() + // Look at input.CharmBase and input.CharmSeries for an operating + // system to deploy with. Only one is allowed and Charm Base is + // preferred. Keep the data as a Series for now as, the + // DeducePlatform method expects a series to be provided, not a + // base. Luckily, the DeduceOrigin method returns an origin which + // does contain the base and a series. + var userSuppliedSeries string + if input.CharmBase != "" { + b, err := series.ParseBaseFromString(input.CharmBase) + if err != nil { + return nil, err + } + userSuppliedSeries, err = series.GetSeriesFromBase(b) if err != nil { return nil, err } + } else if input.CharmSeries != "" { + userSuppliedSeries = input.CharmSeries + } + platformCons, err := modelconfigAPIClient.GetModelConstraints() + if err != nil { + return nil, err } - platform, err := utils.DeducePlatform(constraints.Value{}, input.CharmSeries, platformCons) + platform, err := utils.DeducePlatform(input.Constraints, userSuppliedSeries, platformCons) if err != nil { return nil, err } + urlForOrigin := charmURL if input.CharmRevision != UnspecifiedRevision { urlForOrigin = urlForOrigin.WithRevision(input.CharmRevision) } + urlForOrigin = urlForOrigin.WithSeries(userSuppliedSeries) + origin, err := utils.DeduceOrigin(urlForOrigin, channel, platform) if err != nil { return nil, err @@ -249,12 +263,17 @@ func (c applicationsClient) CreateApplication(input *CreateApplicationInput) (*C return nil, jujuerrors.NotSupportedf("deploying bundles") } - seriesToUse, err := c.seriesToUse(modelconfigAPIClient, input.CharmSeries, resolvedOrigin.Series, set.NewStrings(supportedSeries...)) + seriesToUse, err := c.seriesToUse(modelconfigAPIClient, userSuppliedSeries, resolvedOrigin.Series, set.NewStrings(supportedSeries...)) if err != nil { return nil, err } - if input.CharmSeries != "" && seriesToUse != input.CharmSeries { - return nil, jujuerrors.Errorf("juju controller bug (LP 2039179), deploy will have operating system different from request. ") + if userSuppliedSeries != "" && seriesToUse != userSuppliedSeries { + // Ignore errors, the series have already been vetted above. + userBase, _ := series.GetBaseFromSeries(userSuppliedSeries) + suggestedBase, _ := series.GetBaseFromSeries(seriesToUse) + return nil, jujuerrors.Errorf( + "juju bug (LP 2039179), requested base %q does not match base %q found for charm.", + userBase, suggestedBase) } // Add the charm to the model @@ -303,34 +322,28 @@ func (c applicationsClient) CreateApplication(input *CreateApplicationInput) (*C CharmID: charmID, ApplicationName: appName, NumUnits: input.Units, - Series: resultOrigin.Series, - CharmOrigin: resultOrigin, - Config: appConfig, - Cons: input.Constraints, - Resources: resources, - Placement: placements, + // Still supply series, to be compatible with juju 2.9 controllers. + // 3.x controllers will only use the CharmOrigin and its base. + Series: resultOrigin.Series, + CharmOrigin: resultOrigin, + Config: appConfig, + Cons: input.Constraints, + Resources: resources, + Placement: placements, } c.Tracef("Calling Deploy", map[string]interface{}{"args": args}) err = applicationAPIClient.Deploy(args) if err != nil { // unfortunate error during deployment - // TODO: 01-Aug-2023 - // Why are we returning data on a failure to deploy? - return &CreateApplicationResponse{ - AppName: appName, - Revision: *origin.Revision, - Series: seriesToUse, - }, err + return nil, err } // If we have managed to deploy something, now we have // to check if we have to expose something err = c.processExpose(applicationAPIClient, appName, input.Expose) return &CreateApplicationResponse{ - AppName: appName, - Revision: *origin.Revision, - Series: seriesToUse, + AppName: appName, }, err } @@ -385,6 +398,8 @@ func (c applicationsClient) supportedWorkloadSeries(imageStream string) (set.Str // Note, we are re-implementing the logic of series_selector in juju code as it's // a private object. func (c applicationsClient) seriesToUse(modelconfigAPIClient *apimodelconfig.Client, inputSeries, suggestedSeries string, charmSeries set.Strings) (string, error) { + c.Tracef("seriesToUse", map[string]interface{}{"inputSeries": inputSeries, "suggestedSeries": suggestedSeries, "charmSeries": charmSeries.Values()}) + attrs, err := modelconfigAPIClient.ModelGet() if err != nil { return "", jujuerrors.Wrap(err, errors.New("cannot fetch model settings")) @@ -742,11 +757,19 @@ func (c applicationsClient) ReadApplication(input *ReadApplicationInput) (*ReadA exposed["spaces"] = spaces exposed["cidrs"] = cidrs } + // ParseChannel to send back a base without the risk. + // Having the risk will cause issues with the provider + // saving a different value than the user did. + baseChannel, err := series.ParseChannel(appInfo.Base.Channel) + if err != nil { + return nil, jujuerrors.Annotate(err, "failed parse channel for base") + } response := &ReadApplicationResponse{ Name: charmURL.Name, Channel: appInfo.Channel, Revision: charmURL.Revision, + Base: fmt.Sprintf("%s@%s", appInfo.Base.Name, baseChannel.Track), Series: appInfo.Series, Units: unitCount, Trust: trustValue, diff --git a/internal/provider/data_source_machine_test.go b/internal/provider/data_source_machine_test.go index 1920c183..86cad78d 100644 --- a/internal/provider/data_source_machine_test.go +++ b/internal/provider/data_source_machine_test.go @@ -22,7 +22,7 @@ func TestAcc_DataSourceMachine_Edge(t *testing.T) { ProtoV6ProviderFactories: frameworkProviderFactories, Steps: []resource.TestStep{ { - Config: testAccDataSourceMachine(modelName), + Config: testAccDataSourceMachine(modelName, "base = \"ubuntu@22.04\""), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("data.juju_machine.machine", "model", modelName), ), @@ -47,7 +47,7 @@ func TestAcc_DataSourceMachine_Stable(t *testing.T) { }, Steps: []resource.TestStep{ { - Config: testAccDataSourceMachine(modelName), + Config: testAccDataSourceMachine(modelName, "series = \"jammy\""), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("data.juju_machine.machine", "model", modelName), ), @@ -56,7 +56,7 @@ func TestAcc_DataSourceMachine_Stable(t *testing.T) { }) } -func testAccDataSourceMachine(modelName string) string { +func testAccDataSourceMachine(modelName, os string) string { return fmt.Sprintf(` resource "juju_model" "model" { name = %q @@ -65,11 +65,11 @@ resource "juju_model" "model" { resource "juju_machine" "machine" { model = juju_model.model.name name = "machine" - series = "jammy" + %s } data "juju_machine" "machine" { model = juju_model.model.name machine_id = juju_machine.machine.machine_id -}`, modelName) +}`, modelName, os) } diff --git a/internal/provider/data_source_offer_test.go b/internal/provider/data_source_offer_test.go index 1fdff1af..5bac9598 100644 --- a/internal/provider/data_source_offer_test.go +++ b/internal/provider/data_source_offer_test.go @@ -21,7 +21,7 @@ func TestAcc_DataSourceOffer_Edge(t *testing.T) { ProtoV6ProviderFactories: frameworkProviderFactories, Steps: []resource.TestStep{ { - Config: testAccDataSourceOffer(modelName, offerName), + Config: testAccDataSourceOffer(modelName, "base = \"ubuntu@22.04\"", offerName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("data.juju_offer.this", "model", modelName), resource.TestCheckResourceAttr("data.juju_offer.this", "name", offerName), @@ -46,7 +46,7 @@ func TestAcc_DataSourceOffer_Stable(t *testing.T) { }, Steps: []resource.TestStep{ { - Config: testAccDataSourceOffer(modelName, offerName), + Config: testAccDataSourceOffer(modelName, "series = \"jammy\"", offerName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("data.juju_offer.this", "model", modelName), resource.TestCheckResourceAttr("data.juju_offer.this", "name", offerName), @@ -56,7 +56,7 @@ func TestAcc_DataSourceOffer_Stable(t *testing.T) { }) } -func testAccDataSourceOffer(modelName string, offerName string) string { +func testAccDataSourceOffer(modelName, os, offerName string) string { return fmt.Sprintf(` resource "juju_model" "this" { name = %q @@ -68,7 +68,7 @@ resource "juju_application" "this" { charm { name = "postgresql" - series = "jammy" + %s } } @@ -82,5 +82,5 @@ resource "juju_offer" "this" { data "juju_offer" "this" { url = juju_offer.this.url } -`, modelName, offerName) +`, modelName, os, offerName) } diff --git a/internal/provider/resource_application.go b/internal/provider/resource_application.go index 8214a1fb..7515aff7 100644 --- a/internal/provider/resource_application.go +++ b/internal/provider/resource_application.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" @@ -198,13 +199,33 @@ func (r *applicationResource) Schema(_ context.Context, _ resource.SchemaRequest int64planmodifier.UseStateForUnknown(), }, }, - "series": schema.StringAttribute{ + SeriesKey: schema.StringAttribute{ Description: "The series on which to deploy.", Optional: true, Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, + Validators: []validator.String{ + stringvalidator.ConflictsWith(path.Expressions{ + path.MatchRelative().AtParent().AtName(BaseKey), + }...), + }, + DeprecationMessage: "Configure base instead. This attribute will be removed in the next major version of the provider.", + }, + BaseKey: schema.StringAttribute{ + Description: "The operating system on which to deploy. E.g. ubuntu@22.04.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + Validators: []validator.String{ + stringvalidator.ConflictsWith(path.Expressions{ + path.MatchRelative().AtParent().AtName(SeriesKey), + }...), + stringIsBaseValidator{}, + }, }, }, }, @@ -245,6 +266,7 @@ type nestedCharm struct { Name types.String `tfsdk:"name"` Channel types.String `tfsdk:"channel"` Revision types.Int64 `tfsdk:"revision"` + Base types.String `tfsdk:"base"` Series types.String `tfsdk:"series"` } @@ -323,7 +345,6 @@ func (r *applicationResource) Create(ctx context.Context, req resource.CreateReq if !planCharm.Revision.IsUnknown() { revision = int(planCharm.Revision.ValueInt64()) } - series := planCharm.Series.ValueString() // TODO: investigate using map[string]string here and let // terraform do the conversion, will help in CreateApplication. @@ -366,7 +387,8 @@ func (r *applicationResource) Create(ctx context.Context, req resource.CreateReq CharmName: charmName, CharmChannel: channel, CharmRevision: revision, - CharmSeries: series, + CharmBase: planCharm.Base.ValueString(), + CharmSeries: planCharm.Series.ValueString(), Units: int(plan.UnitCount.ValueInt64()), Config: configField, Constraints: parsedConstraints, @@ -396,6 +418,7 @@ func (r *applicationResource) Create(ctx context.Context, req resource.CreateReq plan.Placement = types.StringValue(readResp.Placement) plan.ApplicationName = types.StringValue(createResp.AppName) planCharm.Revision = types.Int64Value(int64(readResp.Revision)) + planCharm.Base = types.StringValue(readResp.Base) planCharm.Series = types.StringValue(readResp.Series) planCharm.Channel = types.StringValue(readResp.Channel) charmType := req.Config.Schema.GetBlocks()[CharmKey].(schema.ListNestedBlock).NestedObject.Type() @@ -478,6 +501,7 @@ func (r *applicationResource) Read(ctx context.Context, req resource.ReadRequest 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.State.Schema.GetBlocks()[CharmKey].(schema.ListNestedBlock).NestedObject.Type() @@ -602,7 +626,7 @@ func (r *applicationResource) Update(ctx context.Context, req resource.UpdateReq if !planCharm.Channel.Equal(stateCharm.Channel) { updateApplicationInput.Channel = planCharm.Channel.ValueString() } - if !planCharm.Series.Equal(stateCharm.Series) { + if !planCharm.Series.Equal(stateCharm.Series) || !planCharm.Base.Equal(stateCharm.Base) { // This violates terraform's declarative model. We could implement // `juju set-application-base`, usually used after `upgrade-machine`, // which would change the operating system used for future units of @@ -743,7 +767,7 @@ func (r *applicationResource) Delete(ctx context.Context, req resource.DeleteReq return } var state applicationResourceModel - // Read Terraform prior state state into the model + // Read Terraform prior state into the model resp.Diagnostics.Append(req.State.Get(ctx, &state)...) if resp.Diagnostics.HasError() { return diff --git a/internal/provider/resource_integration_test.go b/internal/provider/resource_integration_test.go index 41e76018..7247611a 100644 --- a/internal/provider/resource_integration_test.go +++ b/internal/provider/resource_integration_test.go @@ -25,7 +25,7 @@ func TestAcc_ResourceIntegration_Edge(t *testing.T) { CheckDestroy: testAccCheckIntegrationDestroy, Steps: []resource.TestStep{ { - Config: testAccResourceIntegration(modelName), + Config: testAccResourceIntegration(modelName, "base = \"ubuntu@22.04\"", "base = \"ubuntu@22.04\""), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("juju_integration.this", "model", modelName), resource.TestCheckResourceAttr("juju_integration.this", "id", fmt.Sprintf("%v:%v:%v", modelName, "one:source", "two:sink")), @@ -39,7 +39,7 @@ func TestAcc_ResourceIntegration_Edge(t *testing.T) { ResourceName: "juju_integration.this", }, { - Config: testAccResourceIntegration(modelName), + Config: testAccResourceIntegration(modelName, "base = \"ubuntu@22.04\"", "base = \"ubuntu@22.04\""), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("juju_integration.this", "model", modelName), resource.TestCheckResourceAttr("juju_integration.this", "id", fmt.Sprintf("%v:%v:%v", modelName, "one:source", "two:sink")), @@ -64,7 +64,7 @@ func TestAcc_ResourceIntegrationWithViaCIDRs_Edge(t *testing.T) { CheckDestroy: testAccCheckIntegrationDestroy, Steps: []resource.TestStep{ { - Config: testAccResourceIntegrationWithVia(srcModelName, dstModelName, via), + Config: testAccResourceIntegrationWithVia(srcModelName, "base = \"ubuntu@22.04\"", dstModelName, "base = \"ubuntu@22.04\"", via), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("juju_integration.a", "model", srcModelName), resource.TestCheckResourceAttr("juju_integration.a", "id", fmt.Sprintf("%v:%v:%v", srcModelName, "a:source", "b:sink")), @@ -94,7 +94,7 @@ func TestAcc_ResourceIntegration_Stable(t *testing.T) { CheckDestroy: testAccCheckIntegrationDestroy, Steps: []resource.TestStep{ { - Config: testAccResourceIntegration(modelName), + Config: testAccResourceIntegration(modelName, "series = \"jammy\"", "series = \"jammy\""), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("juju_integration.this", "model", modelName), resource.TestCheckResourceAttr("juju_integration.this", "id", fmt.Sprintf("%v:%v:%v", modelName, "one:source", "two:sink")), @@ -108,7 +108,7 @@ func TestAcc_ResourceIntegration_Stable(t *testing.T) { ResourceName: "juju_integration.this", }, { - Config: testAccResourceIntegration(modelName), + Config: testAccResourceIntegration(modelName, "series = \"jammy\"", "series = \"jammy\""), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("juju_integration.this", "model", modelName), resource.TestCheckResourceAttr("juju_integration.this", "id", fmt.Sprintf("%v:%v:%v", modelName, "one:source", "two:sink")), @@ -123,7 +123,7 @@ func testAccCheckIntegrationDestroy(s *terraform.State) error { return nil } -func testAccResourceIntegration(modelName string) string { +func testAccResourceIntegration(modelName, osOne, osTwo string) string { return fmt.Sprintf(` resource "juju_model" "this" { name = %q @@ -135,7 +135,7 @@ resource "juju_application" "one" { charm { name = "juju-qa-dummy-sink" - series = "jammy" + %s } } @@ -145,7 +145,7 @@ resource "juju_application" "two" { charm { name = "juju-qa-dummy-source" - series = "jammy" + %s } } @@ -162,7 +162,7 @@ resource "juju_integration" "this" { endpoint = "sink" } } -`, modelName) +`, modelName, osOne, osTwo) } func TestAcc_ResourceIntegrationWithViaCIDRs_Stable(t *testing.T) { @@ -184,7 +184,7 @@ func TestAcc_ResourceIntegrationWithViaCIDRs_Stable(t *testing.T) { CheckDestroy: testAccCheckIntegrationDestroy, Steps: []resource.TestStep{ { - Config: testAccResourceIntegrationWithVia(srcModelName, dstModelName, via), + Config: testAccResourceIntegrationWithVia(srcModelName, "series = \"jammy\"", dstModelName, "series = \"jammy\"", via), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("juju_integration.a", "model", srcModelName), resource.TestCheckResourceAttr("juju_integration.a", "id", fmt.Sprintf("%v:%v:%v", srcModelName, "a:source", "b:sink")), @@ -200,7 +200,7 @@ func TestAcc_ResourceIntegrationWithViaCIDRs_Stable(t *testing.T) { // testAccResourceIntegrationWithVia generates a plan where a // postgresql:source relates to a pgbouncer:backend-source using // and offer of pgbouncer. -func testAccResourceIntegrationWithVia(srcModelName string, dstModelName string, viaCIDRs string) string { +func testAccResourceIntegrationWithVia(srcModelName, aOS, dstModelName, bOS, viaCIDRs string) string { return fmt.Sprintf(` resource "juju_model" "a" { name = %q @@ -212,7 +212,7 @@ resource "juju_application" "a" { charm { name = "juju-qa-dummy-sink" - series = "jammy" + %s } } @@ -226,7 +226,7 @@ resource "juju_application" "b" { charm { name = "juju-qa-dummy-source" - series = "jammy" + %s } } @@ -249,7 +249,7 @@ resource "juju_integration" "a" { offer_url = juju_offer.b.url } } -`, srcModelName, dstModelName, viaCIDRs) +`, srcModelName, aOS, dstModelName, bOS, viaCIDRs) } func TestAcc_ResourceIntegrationWithMultipleConsumers_Edge(t *testing.T) { @@ -319,7 +319,7 @@ resource "juju_application" "a" { charm { name = "postgresql" - series = "jammy" + base = "ubuntu@22.04" } } @@ -339,7 +339,7 @@ resource "juju_application" "b1" { charm { name = "pgbouncer" - series = "focal" + base = "ubuntu@20.04" } } @@ -363,7 +363,7 @@ resource "juju_application" "b2" { charm { name = "pgbouncer" - series = "focal" + base = "ubuntu@20.04" } } diff --git a/internal/provider/resource_offer_test.go b/internal/provider/resource_offer_test.go index de48f7bd..2b0e9b5c 100644 --- a/internal/provider/resource_offer_test.go +++ b/internal/provider/resource_offer_test.go @@ -24,7 +24,7 @@ func TestAcc_ResourceOffer_Edge(t *testing.T) { ProtoV6ProviderFactories: frameworkProviderFactories, Steps: []resource.TestStep{ { - Config: testAccResourceOffer(modelName), + Config: testAccResourceOffer(modelName, "base = \"ubuntu@22.04\""), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("juju_offer.this", "model", modelName), resource.TestCheckResourceAttr("juju_offer.this", "url", fmt.Sprintf("%v/%v.%v", "admin", modelName, "this")), @@ -66,7 +66,7 @@ resource "juju_application" "appone" { charm { name = "postgresql" - series = "jammy" + base = "ubuntu@22.04" } } @@ -86,7 +86,7 @@ resource "juju_application" "apptwo" { charm { name = "hello-juju" - series = "focal" + base = "ubuntu@20.04" } } @@ -120,7 +120,7 @@ func TestAcc_ResourceOffer_Stable(t *testing.T) { }, Steps: []resource.TestStep{ { - Config: testAccResourceOffer(modelName), + Config: testAccResourceOffer(modelName, "series = \"focal\""), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("juju_offer.this", "model", modelName), resource.TestCheckResourceAttr("juju_offer.this", "url", fmt.Sprintf("%v/%v.%v", "admin", modelName, "this")), @@ -136,7 +136,7 @@ func TestAcc_ResourceOffer_Stable(t *testing.T) { }) } -func testAccResourceOffer(modelName string) string { +func testAccResourceOffer(modelName, os string) string { return fmt.Sprintf(` resource "juju_model" "this" { name = %q @@ -148,7 +148,7 @@ resource "juju_application" "this" { charm { name = "postgresql" - series = "jammy" + %s } } @@ -157,5 +157,5 @@ resource "juju_offer" "this" { application_name = juju_application.this.name endpoint = "db" } -`, modelName) +`, modelName, os) }