@@ -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,23 @@ 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+ mpslReq := data .toUpdateRequest (ctx , & resp .Diagnostics )
581+ if resp .Diagnostics .HasError () {
582+ return
583+ }
584+ mpslResp , err := client .UpdateTemplateMeta (ctx , data .ID .ValueUUID (), * mpslReq )
585+ if err != nil {
586+ resp .Diagnostics .AddError ("Client Error" , fmt .Sprintf ("Failed to set max port share level via update: %s" , err ))
587+ return
588+ }
589+ data .MaxPortShareLevel = types .StringValue (string (mpslResp .MaxPortShareLevel ))
590+ }
591+
556592 resp .Diagnostics .Append (data .Versions .setPrivateState (ctx , resp .Private )... )
557593 if resp .Diagnostics .HasError () {
558594 return
@@ -591,6 +627,7 @@ func (r *TemplateResource) Read(ctx context.Context, req resource.ReadRequest, r
591627 resp .Diagnostics .Append (diag ... )
592628 return
593629 }
630+ data .MaxPortShareLevel = types .StringValue (string (template .MaxPortShareLevel ))
594631
595632 if ! data .ACL .IsNull () {
596633 tflog .Info (ctx , "reading template ACL" )
@@ -665,11 +702,16 @@ func (r *TemplateResource) Update(ctx context.Context, req resource.UpdateReques
665702
666703 client := r .data .Client
667704
705+ // TODO(ethanndickson): Remove this once the provider requires a Coder
706+ // deployment running `v2.15.0` or later.
707+ if newState .MaxPortShareLevel .IsUnknown () {
708+ newState .MaxPortShareLevel = curState .MaxPortShareLevel
709+ }
668710 templateMetadataChanged := ! newState .EqualTemplateMetadata (& curState )
669711 // This is required, as the API will reject no-diff updates.
670712 if templateMetadataChanged {
671713 tflog .Info (ctx , "change in template metadata detected, updating." )
672- updateReq := newState .toUpdateRequest (ctx , resp )
714+ updateReq := newState .toUpdateRequest (ctx , & resp . Diagnostics )
673715 if resp .Diagnostics .HasError () {
674716 return
675717 }
@@ -758,6 +800,14 @@ func (r *TemplateResource) Update(ctx context.Context, req resource.UpdateReques
758800 }
759801 }
760802 }
803+ // TODO(ethanndickson): Remove this once the provider requires a Coder
804+ // deployment running `v2.15.0` or later.
805+ templateResp , err := client .Template (ctx , templateID )
806+ if err != nil {
807+ resp .Diagnostics .AddError ("Client Error" , fmt .Sprintf ("Failed to get template: %s" , err ))
808+ return
809+ }
810+ newState .MaxPortShareLevel = types .StringValue (string (templateResp .MaxPortShareLevel ))
761811
762812 resp .Diagnostics .Append (newState .Versions .setPrivateState (ctx , resp .Private )... )
763813 if resp .Diagnostics .HasError () {
@@ -1147,25 +1197,27 @@ func (r *TemplateResourceModel) readResponse(ctx context.Context, template *code
11471197 r .TimeTilDormantAutoDeleteMillis = types .Int64Value (template .TimeTilDormantAutoDeleteMillis )
11481198 r .RequireActiveVersion = types .BoolValue (template .RequireActiveVersion )
11491199 r .DeprecationMessage = types .StringValue (template .DeprecationMessage )
1200+ // TODO(ethanndickson): MaxPortShareLevel deliberately omitted, as it can't
1201+ // be set during a create request, and we call this during `Create`.
11501202 return nil
11511203}
11521204
1153- func (r * TemplateResourceModel ) toUpdateRequest (ctx context.Context , resp * resource. UpdateResponse ) * codersdk.UpdateTemplateMeta {
1205+ func (r * TemplateResourceModel ) toUpdateRequest (ctx context.Context , diag * diag. Diagnostics ) * codersdk.UpdateTemplateMeta {
11541206 var days []string
1155- resp . Diagnostics .Append (
1207+ diag .Append (
11561208 r .AutostartPermittedDaysOfWeek .ElementsAs (ctx , & days , false )... ,
11571209 )
1158- if resp . Diagnostics .HasError () {
1210+ if diag .HasError () {
11591211 return nil
11601212 }
11611213 autoStart := & codersdk.TemplateAutostartRequirement {
11621214 DaysOfWeek : days ,
11631215 }
11641216 var reqs AutostopRequirement
1165- resp . Diagnostics .Append (
1217+ diag .Append (
11661218 r .AutostopRequirement .As (ctx , & reqs , basetypes.ObjectAsOptions {})... ,
11671219 )
1168- if resp . Diagnostics .HasError () {
1220+ if diag .HasError () {
11691221 return nil
11701222 }
11711223 autoStop := & codersdk.TemplateAutostopRequirement {
@@ -1189,6 +1241,7 @@ func (r *TemplateResourceModel) toUpdateRequest(ctx context.Context, resp *resou
11891241 TimeTilDormantAutoDeleteMillis : r .TimeTilDormantAutoDeleteMillis .ValueInt64 (),
11901242 RequireActiveVersion : r .RequireActiveVersion .ValueBool (),
11911243 DeprecationMessage : r .DeprecationMessage .ValueStringPointer (),
1244+ MaxPortShareLevel : PtrTo (codersdk .WorkspaceAgentPortShareLevel (r .MaxPortShareLevel .ValueString ())),
11921245 // If we're managing ACL, we want to delete the everyone group
11931246 DisableEveryoneGroupAccess : ! r .ACL .IsNull (),
11941247 }
0 commit comments