Skip to content

Commit 9a7dc67

Browse files
committed
feat: add license resource
1 parent bf558f5 commit 9a7dc67

File tree

4 files changed

+299
-0
lines changed

4 files changed

+299
-0
lines changed

docs/resources/license.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "coderd_license Resource - terraform-provider-coderd"
4+
subcategory: ""
5+
description: |-
6+
A license for a Coder deployment.
7+
It's recommended to create multiple instances of this resource when updating a license. Modifying an existing license will cause the resource to be replaced, which may result in a brief unlicensed period.
8+
Terraform does not guarantee this resource will be created before other resources or attributes that require a licensed deployment. The depends_on meta-argument is instead recommended.
9+
---
10+
11+
# coderd_license (Resource)
12+
13+
A license for a Coder deployment.
14+
15+
It's recommended to create multiple instances of this resource when updating a license. Modifying an existing license will cause the resource to be replaced, which may result in a brief unlicensed period.
16+
17+
Terraform does not guarantee this resource will be created before other resources or attributes that require a licensed deployment. The `depends_on` meta-argument is instead recommended.
18+
19+
20+
21+
<!-- schema generated by tfplugindocs -->
22+
## Schema
23+
24+
### Required
25+
26+
- `license` (String, Sensitive) A license key for Coder.
27+
28+
### Read-Only
29+
30+
- `expires_at` (Number) Unix timestamp of when the license expires.
31+
- `id` (Number) Integer ID of the license.

internal/provider/license_resource.go

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hashicorp/terraform-plugin-framework/resource"
8+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
9+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier"
10+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
11+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
12+
"github.com/hashicorp/terraform-plugin-framework/types"
13+
14+
"github.com/coder/coder/v2/codersdk"
15+
)
16+
17+
// Ensure provider defined types fully satisfy framework interfaces.
18+
var _ resource.Resource = &LicenseResource{}
19+
20+
func NewLicenseResource() resource.Resource {
21+
return &LicenseResource{}
22+
}
23+
24+
// LicenseResource defines the resource implementation.
25+
type LicenseResource struct {
26+
data *CoderdProviderData
27+
}
28+
29+
// LicenseResourceModel describes the resource data model.
30+
type LicenseResourceModel struct {
31+
ID types.Int32 `tfsdk:"id"`
32+
ExpiresAt types.Int64 `tfsdk:"expires_at"`
33+
License types.String `tfsdk:"license"`
34+
}
35+
36+
func (r *LicenseResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
37+
resp.TypeName = req.ProviderTypeName + "_license"
38+
}
39+
40+
func (r *LicenseResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
41+
resp.Schema = schema.Schema{
42+
MarkdownDescription: "A license for a Coder deployment.\n\nIt's recommended to create multiple instances of this " +
43+
"resource when updating a license. Modifying an existing license will cause the resource to be replaced, " +
44+
"which may result in a brief unlicensed period.\n\n" +
45+
"Terraform does not guarantee this resource " +
46+
"will be created before other resources or attributes that require a licensed deployment. " +
47+
"The `depends_on` meta-argument is instead recommended.",
48+
49+
Attributes: map[string]schema.Attribute{
50+
"id": schema.Int32Attribute{
51+
MarkdownDescription: "Integer ID of the license.",
52+
Computed: true,
53+
PlanModifiers: []planmodifier.Int32{
54+
int32planmodifier.UseStateForUnknown(),
55+
},
56+
},
57+
"expires_at": schema.Int64Attribute{
58+
MarkdownDescription: "Unix timestamp of when the license expires.",
59+
Computed: true,
60+
},
61+
"license": schema.StringAttribute{
62+
MarkdownDescription: "A license key for Coder.",
63+
Required: true,
64+
Sensitive: true,
65+
PlanModifiers: []planmodifier.String{
66+
stringplanmodifier.RequiresReplace(),
67+
},
68+
},
69+
},
70+
}
71+
}
72+
73+
func (r *LicenseResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
74+
// Prevent panic if the provider has not been configured.
75+
if req.ProviderData == nil {
76+
return
77+
}
78+
79+
data, ok := req.ProviderData.(*CoderdProviderData)
80+
81+
if !ok {
82+
resp.Diagnostics.AddError(
83+
"Unexpected Resource Configure Type",
84+
fmt.Sprintf("Expected *CoderdProviderData, got: %T. Please report this issue to the provider developers.", req.ProviderData),
85+
)
86+
87+
return
88+
}
89+
90+
r.data = data
91+
}
92+
93+
func (r *LicenseResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
94+
var data LicenseResourceModel
95+
96+
// Read Terraform plan data into the model
97+
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
98+
99+
if resp.Diagnostics.HasError() {
100+
return
101+
}
102+
103+
client := r.data.Client
104+
105+
license, err := client.AddLicense(ctx, codersdk.AddLicenseRequest{
106+
License: data.License.ValueString(),
107+
})
108+
if err != nil {
109+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to add license, got error: %s", err))
110+
return
111+
}
112+
data.ID = types.Int32Value(license.ID)
113+
expiresAt, err := license.ExpiresAt()
114+
if err != nil {
115+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to parse license expiration, got error: %s", err))
116+
return
117+
}
118+
data.ExpiresAt = types.Int64Value(expiresAt.Unix())
119+
120+
// Save data into Terraform state
121+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
122+
}
123+
124+
func (r *LicenseResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
125+
var data LicenseResourceModel
126+
127+
// Read Terraform prior state data into the model
128+
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
129+
130+
if resp.Diagnostics.HasError() {
131+
return
132+
}
133+
134+
licenses, err := r.data.Client.Licenses(ctx)
135+
if err != nil {
136+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to list licenses, got error: %s", err))
137+
return
138+
}
139+
140+
found := false
141+
for _, license := range licenses {
142+
if license.ID == data.ID.ValueInt32() {
143+
found = true
144+
expiresAt, err := license.ExpiresAt()
145+
if err != nil {
146+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to parse license expiration, got error: %s", err))
147+
return
148+
}
149+
data.ExpiresAt = types.Int64Value(expiresAt.Unix())
150+
}
151+
}
152+
if !found {
153+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("License with ID %d not found", data.ID.ValueInt32()))
154+
}
155+
156+
// Save updated data into Terraform state
157+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
158+
}
159+
160+
func (r *LicenseResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
161+
var data LicenseResourceModel
162+
163+
// Read Terraform plan data into the model
164+
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
165+
166+
if resp.Diagnostics.HasError() {
167+
return
168+
}
169+
170+
// Update is handled by replacement
171+
172+
// Save updated data into Terraform state
173+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
174+
}
175+
176+
func (r *LicenseResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
177+
var data LicenseResourceModel
178+
179+
// Read Terraform prior state data into the model
180+
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
181+
182+
if resp.Diagnostics.HasError() {
183+
return
184+
}
185+
186+
client := r.data.Client
187+
188+
err := client.DeleteLicense(ctx, data.ID.ValueInt32())
189+
if err != nil {
190+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete license, got error: %s", err))
191+
return
192+
}
193+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"os"
6+
"strings"
7+
"testing"
8+
"text/template"
9+
10+
"github.com/coder/terraform-provider-coderd/integration"
11+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func TestAccLicenseResource(t *testing.T) {
16+
if os.Getenv("TF_ACC") == "" {
17+
t.Skip("Acceptance tests are disabled.")
18+
}
19+
ctx := context.Background()
20+
client := integration.StartCoder(ctx, t, "license_acc", false)
21+
22+
license := os.Getenv("CODER_ENTERPRISE_LICENSE")
23+
if license == "" {
24+
t.Skip("No license found for license resource tests, skipping")
25+
}
26+
27+
cfg1 := testAccLicenseResourceconfig{
28+
URL: client.URL.String(),
29+
Token: client.SessionToken(),
30+
License: license,
31+
}
32+
33+
resource.Test(t, resource.TestCase{
34+
IsUnitTest: true,
35+
PreCheck: func() { testAccPreCheck(t) },
36+
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
37+
Steps: []resource.TestStep{
38+
{
39+
Config: cfg1.String(t),
40+
},
41+
},
42+
})
43+
}
44+
45+
type testAccLicenseResourceconfig struct {
46+
URL string
47+
Token string
48+
License string
49+
}
50+
51+
func (c testAccLicenseResourceconfig) String(t *testing.T) string {
52+
t.Helper()
53+
tpl := `
54+
provider coderd {
55+
url = "{{.URL}}"
56+
token = "{{.Token}}"
57+
}
58+
59+
resource "coderd_license" "test" {
60+
license = "{{.License}}"
61+
}
62+
`
63+
funcMap := template.FuncMap{
64+
"orNull": PrintOrNull,
65+
}
66+
67+
buf := strings.Builder{}
68+
tmpl, err := template.New("licenseResource").Funcs(funcMap).Parse(tpl)
69+
require.NoError(t, err)
70+
71+
err = tmpl.Execute(&buf, c)
72+
require.NoError(t, err)
73+
return buf.String()
74+
}

internal/provider/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ func (p *CoderdProvider) Resources(ctx context.Context) []func() resource.Resour
138138
NewGroupResource,
139139
NewTemplateResource,
140140
NewWorkspaceProxyResource,
141+
NewLicenseResource,
141142
}
142143
}
143144

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy