Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: add template max port sharing level attribute#110

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

Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletionsdocs/resources/template.md
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -81,6 +81,7 @@ resource "coderd_template" "ubuntu-main" {
- `display_name` (String) The display name of the template. Defaults to the template name.
- `failure_ttl_ms` (Number) (Enterprise) The max lifetime before Coder stops all resources for failed workspaces created from this template, in milliseconds.
- `icon` (String) Relative path or external URL that specifes an icon to be displayed in the dashboard.
- `max_port_share_level` (String) (Enterprise) The maximum port share level for workspaces created from this template. Defaults to `owner` on an Enterprise deployment, or `public` otherwise.
- `organization_id` (String) The ID of the organization. Defaults to the provider's default organization
- `require_active_version` (Boolean) (Enterprise) Whether workspaces must be created from the active version of this template. Defaults to false.
- `time_til_dormant_autodelete_ms` (Number) (Enterprise) The max lifetime before Coder permanently deletes dormant workspaces created from this template.
Expand Down
69 changes: 61 additions & 8 deletionsinternal/provider/template_resource.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -69,6 +69,7 @@ type TemplateResourceModel struct {
TimeTilDormantAutoDeleteMillis types.Int64 `tfsdk:"time_til_dormant_autodelete_ms"`
RequireActiveVersion types.Bool `tfsdk:"require_active_version"`
DeprecationMessage types.String `tfsdk:"deprecation_message"`
MaxPortShareLevel types.String `tfsdk:"max_port_share_level"`

// If null, we are not managing ACL via Terraform (such as for AGPL).
ACL types.Object `tfsdk:"acl"`
Expand All@@ -92,7 +93,9 @@ func (m *TemplateResourceModel) EqualTemplateMetadata(other *TemplateResourceMod
m.FailureTTLMillis.Equal(other.FailureTTLMillis) &&
m.TimeTilDormantMillis.Equal(other.TimeTilDormantMillis) &&
m.TimeTilDormantAutoDeleteMillis.Equal(other.TimeTilDormantAutoDeleteMillis) &&
m.RequireActiveVersion.Equal(other.RequireActiveVersion)
m.RequireActiveVersion.Equal(other.RequireActiveVersion) &&
m.DeprecationMessage.Equal(other.DeprecationMessage) &&
m.MaxPortShareLevel.Equal(other.MaxPortShareLevel)
}

func (m *TemplateResourceModel) CheckEntitlements(ctx context.Context, features map[codersdk.FeatureName]codersdk.Feature) (diags diag.Diagnostics) {
Expand All@@ -110,7 +113,8 @@ func (m *TemplateResourceModel) CheckEntitlements(ctx context.Context, features
len(m.AutostartPermittedDaysOfWeek.Elements()) != 7
requiresActiveVersion := m.RequireActiveVersion.ValueBool()
requiresACL := !m.ACL.IsNull()
if requiresScheduling || requiresActiveVersion || requiresACL {
requiresSharedPortsControl := m.MaxPortShareLevel.ValueString() != "" && m.MaxPortShareLevel.ValueString() != string(codersdk.WorkspaceAgentPortShareLevelPublic)
if requiresScheduling || requiresActiveVersion || requiresACL || requiresSharedPortsControl {
if requiresScheduling && !features[codersdk.FeatureAdvancedTemplateScheduling].Enabled {
diags.AddError(
"Feature not enabled",
Expand All@@ -132,6 +136,13 @@ func (m *TemplateResourceModel) CheckEntitlements(ctx context.Context, features
)
return
}
if requiresSharedPortsControl && !features[codersdk.FeatureControlSharedPorts].Enabled {
diags.AddError(
"Feature not enabled",
"Your license is not entitled to use port sharing control, so you cannot set max_port_share_level.",
)
return
}
}
return
}
Expand DownExpand Up@@ -369,6 +380,14 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques
Computed: true,
Default: booldefault.StaticBool(false),
},
"max_port_share_level": schema.StringAttribute{
MarkdownDescription: "(Enterprise) The maximum port share level for workspaces created from this template. Defaults to `owner` on an Enterprise deployment, or `public` otherwise.",
Optional: true,
Computed: true,
Validators: []validator.String{
stringvalidator.OneOfCaseInsensitive(string(codersdk.WorkspaceAgentPortShareLevelAuthenticated), string(codersdk.WorkspaceAgentPortShareLevelOwner), string(codersdk.WorkspaceAgentPortShareLevelPublic)),
},
},
"deprecation_message": schema.StringAttribute{
MarkdownDescription: "If set, the template will be marked as deprecated with the provided message and users will be blocked from creating new workspaces from it. Does nothing if set when the resource is created.",
Optional: true,
Expand DownExpand Up@@ -553,6 +572,23 @@ func (r *TemplateResource) Create(ctx context.Context, req resource.CreateReques
data.ID = UUIDValue(templateResp.ID)
data.DisplayName = types.StringValue(templateResp.DisplayName)

// TODO: Remove this update call once this provider requires a Coder
// deployment running `v2.15.0` or later.
if data.MaxPortShareLevel.IsUnknown() {
data.MaxPortShareLevel = types.StringValue(string(templateResp.MaxPortShareLevel))
} else {
mpslReq := data.toUpdateRequest(ctx, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}
mpslResp, err := client.UpdateTemplateMeta(ctx, data.ID.ValueUUID(), *mpslReq)
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to set max port share level via update: %s", err))
return
}
data.MaxPortShareLevel = types.StringValue(string(mpslResp.MaxPortShareLevel))
}

resp.Diagnostics.Append(data.Versions.setPrivateState(ctx, resp.Private)...)
if resp.Diagnostics.HasError() {
return
Expand DownExpand Up@@ -591,6 +627,7 @@ func (r *TemplateResource) Read(ctx context.Context, req resource.ReadRequest, r
resp.Diagnostics.Append(diag...)
return
}
data.MaxPortShareLevel = types.StringValue(string(template.MaxPortShareLevel))

if !data.ACL.IsNull() {
tflog.Info(ctx, "reading template ACL")
Expand DownExpand Up@@ -665,11 +702,16 @@ func (r *TemplateResource) Update(ctx context.Context, req resource.UpdateReques

client := r.data.Client

// TODO(ethanndickson): Remove this once the provider requires a Coder
// deployment running `v2.15.0` or later.
if newState.MaxPortShareLevel.IsUnknown() {
newState.MaxPortShareLevel = curState.MaxPortShareLevel
}
templateMetadataChanged := !newState.EqualTemplateMetadata(&curState)
// This is required, as the API will reject no-diff updates.
if templateMetadataChanged {
tflog.Info(ctx, "change in template metadata detected, updating.")
updateReq := newState.toUpdateRequest(ctx, resp)
updateReq := newState.toUpdateRequest(ctx,&resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}
Expand DownExpand Up@@ -758,6 +800,14 @@ func (r *TemplateResource) Update(ctx context.Context, req resource.UpdateReques
}
}
}
// TODO(ethanndickson): Remove this once the provider requires a Coder
// deployment running `v2.15.0` or later.
templateResp, err := client.Template(ctx, templateID)
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to get template: %s", err))
return
}
newState.MaxPortShareLevel = types.StringValue(string(templateResp.MaxPortShareLevel))

resp.Diagnostics.Append(newState.Versions.setPrivateState(ctx, resp.Private)...)
if resp.Diagnostics.HasError() {
Expand DownExpand Up@@ -1147,25 +1197,27 @@ func (r *TemplateResourceModel) readResponse(ctx context.Context, template *code
r.TimeTilDormantAutoDeleteMillis = types.Int64Value(template.TimeTilDormantAutoDeleteMillis)
r.RequireActiveVersion = types.BoolValue(template.RequireActiveVersion)
r.DeprecationMessage = types.StringValue(template.DeprecationMessage)
// TODO(ethanndickson): MaxPortShareLevel deliberately omitted, as it can't
// be set during a create request, and we call this during `Create`.
return nil
}

func (r *TemplateResourceModel) toUpdateRequest(ctx context.Context,resp *resource.UpdateResponse) *codersdk.UpdateTemplateMeta {
func (r *TemplateResourceModel) toUpdateRequest(ctx context.Context,diag *diag.Diagnostics) *codersdk.UpdateTemplateMeta {
var days []string
resp.Diagnostics.Append(
diag.Append(
r.AutostartPermittedDaysOfWeek.ElementsAs(ctx, &days, false)...,
)
ifresp.Diagnostics.HasError() {
ifdiag.HasError() {
return nil
}
autoStart := &codersdk.TemplateAutostartRequirement{
DaysOfWeek: days,
}
var reqs AutostopRequirement
resp.Diagnostics.Append(
diag.Append(
r.AutostopRequirement.As(ctx, &reqs, basetypes.ObjectAsOptions{})...,
)
ifresp.Diagnostics.HasError() {
ifdiag.HasError() {
return nil
}
autoStop := &codersdk.TemplateAutostopRequirement{
Expand All@@ -1189,6 +1241,7 @@ func (r *TemplateResourceModel) toUpdateRequest(ctx context.Context, resp *resou
TimeTilDormantAutoDeleteMillis: r.TimeTilDormantAutoDeleteMillis.ValueInt64(),
RequireActiveVersion: r.RequireActiveVersion.ValueBool(),
DeprecationMessage: r.DeprecationMessage.ValueStringPointer(),
MaxPortShareLevel: PtrTo(codersdk.WorkspaceAgentPortShareLevel(r.MaxPortShareLevel.ValueString())),
// If we're managing ACL, we want to delete the everyone group
DisableEveryoneGroupAccess: !r.ACL.IsNull(),
}
Expand Down
16 changes: 16 additions & 0 deletionsinternal/provider/template_resource_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -113,6 +113,7 @@ func TestAccTemplateResource(t *testing.T) {
resource.TestCheckResourceAttr("coderd_template.test", "time_til_dormant_ms", "0"),
resource.TestCheckResourceAttr("coderd_template.test", "time_til_dormant_autodelete_ms", "0"),
resource.TestCheckResourceAttr("coderd_template.test", "require_active_version", "false"),
resource.TestCheckResourceAttr("coderd_template.test", "max_port_share_level", "public"),
resource.TestMatchTypeSetElemNestedAttrs("coderd_template.test", "versions.*", map[string]*regexp.Regexp{
"name": regexp.MustCompile(".+"),
"id": regexp.MustCompile(".+"),
Expand DownExpand Up@@ -465,9 +466,11 @@ func TestAccTemplateResourceEnterprise(t *testing.T) {

cfg2 := cfg1
cfg2.ACL.GroupACL = slices.Clone(cfg2.ACL.GroupACL[1:])
cfg2.MaxPortShareLevel = PtrTo("owner")

cfg3 := cfg2
cfg3.ACL.null = true
cfg3.MaxPortShareLevel = PtrTo("public")

cfg4 := cfg3
cfg4.AllowUserAutostart = PtrTo(false)
Expand All@@ -484,6 +487,7 @@ func TestAccTemplateResourceEnterprise(t *testing.T) {
{
Config: cfg1.String(t),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("coderd_template.test", "max_port_share_level", "owner"),
resource.TestCheckResourceAttr("coderd_template.test", "acl.groups.#", "2"),
resource.TestMatchTypeSetElemNestedAttrs("coderd_template.test", "acl.groups.*", map[string]*regexp.Regexp{
"id": regexp.MustCompile(firstUser.OrganizationIDs[0].String()),
Expand All@@ -503,6 +507,7 @@ func TestAccTemplateResourceEnterprise(t *testing.T) {
{
Config: cfg2.String(t),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("coderd_template.test", "max_port_share_level", "owner"),
resource.TestMatchTypeSetElemNestedAttrs("coderd_template.test", "acl.users.*", map[string]*regexp.Regexp{
"id": regexp.MustCompile(firstUser.ID.String()),
"role": regexp.MustCompile("^admin$"),
Expand All@@ -512,6 +517,7 @@ func TestAccTemplateResourceEnterprise(t *testing.T) {
{
Config: cfg3.String(t),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("coderd_template.test", "max_port_share_level", "public"),
resource.TestCheckNoResourceAttr("coderd_template.test", "acl"),
func(s *terraform.State) error {
templates, err := client.Templates(ctx, codersdk.TemplateFilter{})
Expand DownExpand Up@@ -603,6 +609,10 @@ func TestAccTemplateResourceAGPL(t *testing.T) {
},
}

cfg7 := cfg6
cfg7.ACL.null = true
cfg7.MaxPortShareLevel = PtrTo("owner")

for _, cfg := range []testAccTemplateResourceConfig{cfg1, cfg2, cfg3, cfg4} {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Expand DownExpand Up@@ -630,6 +640,10 @@ func TestAccTemplateResourceAGPL(t *testing.T) {
Config: cfg6.String(t),
ExpectError: regexp.MustCompile("Your license is not entitled to use template access control"),
},
{
Config: cfg7.String(t),
ExpectError: regexp.MustCompile("Your license is not entitled to use port sharing control"),
},
},
})
}
Expand All@@ -655,6 +669,7 @@ type testAccTemplateResourceConfig struct {
TimeTilDormantAutodelete *int64
RequireActiveVersion *bool
DeprecationMessage *string
MaxPortShareLevel *string

Versions []testAccTemplateVersionConfig
ACL testAccTemplateACLConfig
Expand DownExpand Up@@ -761,6 +776,7 @@ resource "coderd_template" "test" {
time_til_dormant_autodelete_ms = {{orNull .TimeTilDormantAutodelete}}
require_active_version = {{orNull .RequireActiveVersion}}
deprecation_message = {{orNull .DeprecationMessage}}
max_port_share_level = {{orNull .MaxPortShareLevel}}

acl = ` + c.ACL.String(t) + `

Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp