- Notifications
You must be signed in to change notification settings - Fork3
chore: allow pushing only inactivecoderd_template
versions#167
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -286,7 +286,7 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques | ||
}, | ||
}, | ||
"icon": schema.StringAttribute{ | ||
MarkdownDescription: "Relative path or external URL thatspecifies an icon to be displayed in the dashboard.", | ||
Optional: true, | ||
Computed: true, | ||
Default: stringdefault.StaticString(""), | ||
@@ -404,7 +404,7 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques | ||
Required: true, | ||
Validators: []validator.List{ | ||
listvalidator.SizeAtLeast(1), | ||
NewVersionsValidator(), | ||
}, | ||
NestedObject: schema.NestedAttributeObject{ | ||
Attributes: map[string]schema.Attribute{ | ||
@@ -867,24 +867,24 @@ func (r *TemplateResource) ConfigValidators(context.Context) []resource.ConfigVa | ||
return []resource.ConfigValidator{} | ||
} | ||
typeversionsValidator struct{} | ||
funcNewVersionsValidator() validator.List { | ||
return &versionsValidator{} | ||
} | ||
// Description implements validator.List. | ||
func (a *versionsValidator) Description(ctx context.Context) string { | ||
return a.MarkdownDescription(ctx) | ||
} | ||
// MarkdownDescription implements validator.List. | ||
func (a *versionsValidator) MarkdownDescription(context.Context) string { | ||
return "Validate that template versionnames are unique and that at most one version is active." | ||
} | ||
// ValidateList implements validator.List. | ||
func (a *versionsValidator) ValidateList(ctx context.Context, req validator.ListRequest, resp *validator.ListResponse) { | ||
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { | ||
return | ||
} | ||
@@ -908,13 +908,13 @@ func (a *activeVersionValidator) ValidateList(ctx context.Context, req validator | ||
uniqueNames[version.Name.ValueString()] = struct{}{} | ||
} | ||
//Ensure at most oneversion isactive | ||
active := false | ||
for _, version := range data { | ||
// `active`defaults to false, so if it's null or unknown, this is Terraform | ||
// requesting an early validation. | ||
if version.Active.IsNull() || version.Active.IsUnknown() { | ||
continue | ||
} | ||
if version.Active.ValueBool() { | ||
if active { | ||
@@ -924,12 +924,9 @@ func (a *activeVersionValidator) ValidateList(ctx context.Context, req validator | ||
active = true | ||
} | ||
} | ||
} | ||
var _ validator.List = &versionsValidator{} | ||
type versionsPlanModifier struct{} | ||
@@ -956,6 +953,12 @@ func (d *versionsPlanModifier) PlanModifyList(ctx context.Context, req planmodif | ||
return | ||
} | ||
hasActiveVersion, diag := hasOneActiveVersion(configVersions) | ||
if diag.HasError() { | ||
resp.Diagnostics.Append(diag...) | ||
return | ||
} | ||
for i := range planVersions { | ||
hash, err := computeDirectoryHash(planVersions[i].Directory.ValueString()) | ||
if err != nil { | ||
@@ -974,6 +977,13 @@ func (d *versionsPlanModifier) PlanModifyList(ctx context.Context, req planmodif | ||
// If this is the first read, init the private state value | ||
if lvBytes == nil { | ||
lv = make(LastVersionsByHash) | ||
// If there's no prior private state, this might be resource creation, | ||
// in which case one version must be active. | ||
if !hasActiveVersion { | ||
resp.Diagnostics.AddError("Client Error", "At least one template version must be active when creating a"+ | ||
" `coderd_template` resource.\n(Subsequent resource updates can be made without an active template in the list).") | ||
return | ||
} | ||
} else { | ||
err := json.Unmarshal(lvBytes, &lv) | ||
if err != nil { | ||
@@ -982,9 +992,34 @@ func (d *versionsPlanModifier) PlanModifyList(ctx context.Context, req planmodif | ||
} | ||
} | ||
diag = planVersions.reconcileVersionIDs(lv, configVersions, hasActiveVersion) | ||
if diag.HasError() { | ||
resp.Diagnostics.Append(diag...) | ||
return | ||
} | ||
resp.PlanValue, diag = types.ListValueFrom(ctx, req.PlanValue.ElementType(ctx), planVersions) | ||
if diag.HasError() { | ||
resp.Diagnostics.Append(diag...) | ||
} | ||
} | ||
func hasOneActiveVersion(data Versions) (hasActiveVersion bool, diags diag.Diagnostics) { | ||
active := false | ||
for _, version := range data { | ||
if version.Active.IsNull() || version.Active.IsUnknown() { | ||
// If null or unknown, the value will be defaulted to false | ||
continue | ||
} | ||
if version.Active.ValueBool() { | ||
if active { | ||
diags.AddError("Client Error", "Only one template version can be active at a time.") | ||
return | ||
} | ||
active = true | ||
} | ||
} | ||
return active, diags | ||
} | ||
func NewVersionsPlanModifier() planmodifier.List { | ||
@@ -1309,6 +1344,7 @@ type PreviousTemplateVersion struct { | ||
ID uuid.UUID `json:"id"` | ||
Name string `json:"name"` | ||
TFVars map[string]string `json:"tf_vars"` | ||
Active bool `json:"active"` | ||
} | ||
type privateState interface { | ||
@@ -1331,13 +1367,15 @@ func (v Versions) setPrivateState(ctx context.Context, ps privateState) (diags d | ||
ID: version.ID.ValueUUID(), | ||
Name: version.Name.ValueString(), | ||
TFVars: tfVars, | ||
Active: version.Active.ValueBool(), | ||
}) | ||
} else { | ||
lv[version.DirectoryHash.ValueString()] = []PreviousTemplateVersion{ | ||
{ | ||
ID: version.ID.ValueUUID(), | ||
Name: version.Name.ValueString(), | ||
TFVars: tfVars, | ||
Active: version.Active.ValueBool(), | ||
}, | ||
} | ||
} | ||
@@ -1350,7 +1388,7 @@ func (v Versions) setPrivateState(ctx context.Context, ps privateState) (diags d | ||
return ps.SetKey(ctx, LastVersionsKey, lvBytes) | ||
} | ||
func (planVersions Versions) reconcileVersionIDs(lv LastVersionsByHash, configVersions Versions, hasOneActiveVersion bool) (diag diag.Diagnostics) { | ||
// We remove versions that we've matched from `lv`, so make a copy for | ||
// resolving tfvar changes at the end. | ||
fullLv := make(LastVersionsByHash) | ||
@@ -1420,6 +1458,39 @@ func (planVersions Versions) reconcileVersionIDs(lv LastVersionsByHash, configVe | ||
} | ||
} | ||
} | ||
// If a version was deactivated, and no active version was set, we need to | ||
// return an error to avoid a post-apply plan being non-empty. | ||
if !hasOneActiveVersion { | ||
for i := range planVersions { | ||
if !planVersions[i].ID.IsUnknown() { | ||
prevs, ok := fullLv[planVersions[i].DirectoryHash.ValueString()] | ||
if !ok { | ||
continue | ||
} | ||
if versionDeactivated(prevs, &planVersions[i]) { | ||
diag.AddError("Client Error", "Plan could not determine which version should be active.\n"+ | ||
"Either specify an active version or modify the contents of the previously active version before marking it as inactive.") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Instead of returning an error and failing the plan here, we could just push a new, identical, template version and not promote it to active. Given there's no other way to push an unmodified template version, and the whole point of this resource is to avoid pushing spurious diffs, I think it's better to just fail the plan. | ||
return diag | ||
} | ||
} | ||
} | ||
} | ||
return diag | ||
} | ||
func versionDeactivated(prevs []PreviousTemplateVersion, planned *TemplateVersion) bool { | ||
for _, prev := range prevs { | ||
if prev.ID == planned.ID.ValueUUID() { | ||
if prev.Active && | ||
!planned.Active.IsNull() && | ||
!planned.Active.IsUnknown() && | ||
!planned.Active.ValueBool() { | ||
return true | ||
} | ||
} | ||
} | ||
return false | ||
} | ||
func tfVariablesChanged(prevs []PreviousTemplateVersion, planned *TemplateVersion) bool { | ||
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.