Skip to content

Commit

Permalink
Add Base to application resources.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
hmlanigan committed Oct 18, 2023
1 parent edec2ae commit 31859ef
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 64 deletions.
3 changes: 2 additions & 1 deletion docs/resources/application.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 \<track>/\<risk>/\<branch>.
- `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.


<a id="nestedblock--expose"></a>
Expand Down
71 changes: 46 additions & 25 deletions internal/juju/applications.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ type CreateApplicationInput struct {
ModelName string
CharmName string
CharmChannel string
CharmBase string
CharmSeries string
CharmRevision int
Units int
Expand All @@ -114,6 +115,7 @@ type CreateApplicationInput struct {
type CreateApplicationResponse struct {
AppName string
Revision int
Base string
Series string
}

Expand All @@ -126,6 +128,7 @@ type ReadApplicationResponse struct {
Name string
Channel string
Revision int
Base string
Series string
Units int
Trust bool
Expand Down Expand Up @@ -213,26 +216,37 @@ 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()
// Unfortunately, DeducePlatform expects a series to
// be provided, not a base. However, the DeduceOrigin
// returns an origin which does contain the base.
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
Expand All @@ -249,11 +263,11 @@ 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 {
if userSuppliedSeries != "" && seriesToUse != userSuppliedSeries {
return nil, jujuerrors.Errorf("juju controller bug (LP 2039179), deploy will have operating system different from request. ")
}

Expand Down Expand Up @@ -303,25 +317,21 @@ 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
Expand All @@ -330,7 +340,8 @@ func (c applicationsClient) CreateApplication(input *CreateApplicationInput) (*C
return &CreateApplicationResponse{
AppName: appName,
Revision: *origin.Revision,
Series: seriesToUse,
Series: resultOrigin.Series,
Base: fmt.Sprintf("%s@%s", resultOrigin.Base.Name, resultOrigin.Base.Channel.Track),
}, err
}

Expand Down Expand Up @@ -385,6 +396,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"))
Expand Down Expand Up @@ -742,11 +755,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,
Expand Down
10 changes: 5 additions & 5 deletions internal/provider/data_source_machine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
),
Expand All @@ -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),
),
Expand All @@ -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
Expand All @@ -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)
}
10 changes: 5 additions & 5 deletions internal/provider/data_source_offer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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),
Expand All @@ -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
Expand All @@ -68,7 +68,7 @@ resource "juju_application" "this" {
charm {
name = "postgresql"
series = "jammy"
%s
}
}
Expand All @@ -82,5 +82,5 @@ resource "juju_offer" "this" {
data "juju_offer" "this" {
url = juju_offer.this.url
}
`, modelName, offerName)
`, modelName, os, offerName)
}
32 changes: 28 additions & 4 deletions internal/provider/resource_application.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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{},
},
},
},
},
Expand Down Expand Up @@ -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"`
}

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 31859ef

Please sign in to comment.