@@ -69,6 +69,7 @@ type TemplateResourceModel struct {
6969 TimeTilDormantAutoDeleteMillis types.Int64 `tfsdk:"time_til_dormant_autodelete_ms"`
7070 RequireActiveVersion types.Bool `tfsdk:"require_active_version"`
7171 DeprecationMessage types.String `tfsdk:"deprecation_message"`
72+ MaxPortShareLevel types.String `tfsdk:"max_port_share_level"`
7273
7374 // If null, we are not managing ACL via Terraform (such as for AGPL).
7475 ACL types.Object `tfsdk:"acl"`
@@ -92,7 +93,9 @@ func (m *TemplateResourceModel) EqualTemplateMetadata(other *TemplateResourceMod
9293 m .FailureTTLMillis .Equal (other .FailureTTLMillis ) &&
9394 m .TimeTilDormantMillis .Equal (other .TimeTilDormantMillis ) &&
9495 m .TimeTilDormantAutoDeleteMillis .Equal (other .TimeTilDormantAutoDeleteMillis ) &&
95- m .RequireActiveVersion .Equal (other .RequireActiveVersion )
96+ m .RequireActiveVersion .Equal (other .RequireActiveVersion ) &&
97+ m .DeprecationMessage .Equal (other .DeprecationMessage ) &&
98+ m .MaxPortShareLevel .Equal (other .MaxPortShareLevel )
9699}
97100
98101func (m * TemplateResourceModel ) CheckEntitlements (ctx context.Context , features map [codersdk.FeatureName ]codersdk.Feature ) (diags diag.Diagnostics ) {
@@ -110,7 +113,8 @@ func (m *TemplateResourceModel) CheckEntitlements(ctx context.Context, features
110113 len (m .AutostartPermittedDaysOfWeek .Elements ()) != 7
111114 requiresActiveVersion := m .RequireActiveVersion .ValueBool ()
112115 requiresACL := ! m .ACL .IsNull ()
113- if requiresScheduling || requiresActiveVersion || requiresACL {
116+ requiresSharedPortsControl := m .MaxPortShareLevel .ValueString () != "" && m .MaxPortShareLevel .ValueString () != string (codersdk .WorkspaceAgentPortShareLevelPublic )
117+ if requiresScheduling || requiresActiveVersion || requiresACL || requiresSharedPortsControl {
114118 if requiresScheduling && ! features [codersdk .FeatureAdvancedTemplateScheduling ].Enabled {
115119 diags .AddError (
116120 "Feature not enabled" ,
@@ -132,6 +136,13 @@ func (m *TemplateResourceModel) CheckEntitlements(ctx context.Context, features
132136 )
133137 return
134138 }
139+ if requiresSharedPortsControl && ! features [codersdk .FeatureControlSharedPorts ].Enabled {
140+ diags .AddError (
141+ "Feature not enabled" ,
142+ "Your license is not entitled to use port sharing control, so you cannot set max_port_share_level." ,
143+ )
144+ return
145+ }
135146 }
136147 return
137148}
@@ -369,6 +380,14 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques
369380 Computed : true ,
370381 Default : booldefault .StaticBool (false ),
371382 },
383+ "max_port_share_level" : schema.StringAttribute {
384+ MarkdownDescription : "(Enterprise) The maximum port share level for workspaces created from this template. Defaults to `owner` on an Enterprise deployment, or `public` otherwise." ,
385+ Optional : true ,
386+ Computed : true ,
387+ Validators : []validator.String {
388+ stringvalidator .OneOfCaseInsensitive (string (codersdk .WorkspaceAgentPortShareLevelAuthenticated ), string (codersdk .WorkspaceAgentPortShareLevelOwner ), string (codersdk .WorkspaceAgentPortShareLevelPublic )),
389+ },
390+ },
372391 "deprecation_message" : schema.StringAttribute {
373392 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." ,
374393 Optional : true ,
@@ -553,6 +572,24 @@ func (r *TemplateResource) Create(ctx context.Context, req resource.CreateReques
553572 data .ID = UUIDValue (templateResp .ID )
554573 data .DisplayName = types .StringValue (templateResp .DisplayName )
555574
575+ // TODO: Remove this update call once this provider requires a Coder
576+ // deployment running `v2.15.0` or later.
577+ if data .MaxPortShareLevel .IsUnknown () {
578+ data .MaxPortShareLevel = types .StringValue (string (templateResp .MaxPortShareLevel ))
579+ } else {
580+
581+ mpslReq := data .toUpdateRequest (ctx , & resp .Diagnostics )
582+ if resp .Diagnostics .HasError () {
583+ return
584+ }
585+ mpslResp , err := client .UpdateTemplateMeta (ctx , data .ID .ValueUUID (), * mpslReq )
586+ if err != nil {
587+ resp .Diagnostics .AddError ("Client Error" , fmt .Sprintf ("Failed to set max port share level via update: %s" , err ))
588+ return
589+ }
590+ data .MaxPortShareLevel = types .StringValue (string (mpslResp .MaxPortShareLevel ))
591+ }
592+
556593 resp .Diagnostics .Append (data .Versions .setPrivateState (ctx , resp .Private )... )
557594 if resp .Diagnostics .HasError () {
558595 return
@@ -591,6 +628,7 @@ func (r *TemplateResource) Read(ctx context.Context, req resource.ReadRequest, r
591628 resp .Diagnostics .Append (diag ... )
592629 return
593630 }
631+ data .MaxPortShareLevel = types .StringValue (string (template .MaxPortShareLevel ))
594632
595633 if ! data .ACL .IsNull () {
596634 tflog .Info (ctx , "reading template ACL" )
@@ -665,11 +703,16 @@ func (r *TemplateResource) Update(ctx context.Context, req resource.UpdateReques
665703
666704 client := r .data .Client
667705
706+ // TODO(ethanndickson): Remove this once the provider requires a Coder
707+ // deployment running `v2.15.0` or later.
708+ if newState .MaxPortShareLevel .IsUnknown () {
709+ newState .MaxPortShareLevel = curState .MaxPortShareLevel
710+ }
668711 templateMetadataChanged := ! newState .EqualTemplateMetadata (& curState )
669712 // This is required, as the API will reject no-diff updates.
670713 if templateMetadataChanged {
671714 tflog .Info (ctx , "change in template metadata detected, updating." )
672- updateReq := newState .toUpdateRequest (ctx , resp )
715+ updateReq := newState .toUpdateRequest (ctx , & resp . Diagnostics )
673716 if resp .Diagnostics .HasError () {
674717 return
675718 }
@@ -758,6 +801,14 @@ func (r *TemplateResource) Update(ctx context.Context, req resource.UpdateReques
758801 }
759802 }
760803 }
804+ // TODO(ethanndickson): Remove this once the provider requires a Coder
805+ // deployment running `v2.15.0` or later.
806+ templateResp , err := client .Template (ctx , templateID )
807+ if err != nil {
808+ resp .Diagnostics .AddError ("Client Error" , fmt .Sprintf ("Failed to get template: %s" , err ))
809+ return
810+ }
811+ newState .MaxPortShareLevel = types .StringValue (string (templateResp .MaxPortShareLevel ))
761812
762813 resp .Diagnostics .Append (newState .Versions .setPrivateState (ctx , resp .Private )... )
763814 if resp .Diagnostics .HasError () {
@@ -1147,25 +1198,27 @@ func (r *TemplateResourceModel) readResponse(ctx context.Context, template *code
11471198 r .TimeTilDormantAutoDeleteMillis = types .Int64Value (template .TimeTilDormantAutoDeleteMillis )
11481199 r .RequireActiveVersion = types .BoolValue (template .RequireActiveVersion )
11491200 r .DeprecationMessage = types .StringValue (template .DeprecationMessage )
1201+ // TODO(ethanndickson): MaxPortShareLevel deliberately omitted, as it can't
1202+ // be set during a create request, and we call this during `Create`.
11501203 return nil
11511204}
11521205
1153- func (r * TemplateResourceModel ) toUpdateRequest (ctx context.Context , resp * resource. UpdateResponse ) * codersdk.UpdateTemplateMeta {
1206+ func (r * TemplateResourceModel ) toUpdateRequest (ctx context.Context , diag * diag. Diagnostics ) * codersdk.UpdateTemplateMeta {
11541207 var days []string
1155- resp . Diagnostics .Append (
1208+ diag .Append (
11561209 r .AutostartPermittedDaysOfWeek .ElementsAs (ctx , & days , false )... ,
11571210 )
1158- if resp . Diagnostics .HasError () {
1211+ if diag .HasError () {
11591212 return nil
11601213 }
11611214 autoStart := & codersdk.TemplateAutostartRequirement {
11621215 DaysOfWeek : days ,
11631216 }
11641217 var reqs AutostopRequirement
1165- resp . Diagnostics .Append (
1218+ diag .Append (
11661219 r .AutostopRequirement .As (ctx , & reqs , basetypes.ObjectAsOptions {})... ,
11671220 )
1168- if resp . Diagnostics .HasError () {
1221+ if diag .HasError () {
11691222 return nil
11701223 }
11711224 autoStop := & codersdk.TemplateAutostopRequirement {
@@ -1189,6 +1242,7 @@ func (r *TemplateResourceModel) toUpdateRequest(ctx context.Context, resp *resou
11891242 TimeTilDormantAutoDeleteMillis : r .TimeTilDormantAutoDeleteMillis .ValueInt64 (),
11901243 RequireActiveVersion : r .RequireActiveVersion .ValueBool (),
11911244 DeprecationMessage : r .DeprecationMessage .ValueStringPointer (),
1245+ MaxPortShareLevel : PtrTo (codersdk .WorkspaceAgentPortShareLevel (r .MaxPortShareLevel .ValueString ())),
11921246 // If we're managing ACL, we want to delete the everyone group
11931247 DisableEveryoneGroupAccess : ! r .ACL .IsNull (),
11941248 }
0 commit comments