Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 33d3d36

Browse files
committed
feat: add license resource
1 parent bf558f5 commit 33d3d36

File tree

3 files changed

+269
-0
lines changed

3 files changed

+269
-0
lines changed

internal/provider/license_resource.go

+194
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"time"
7+
8+
"github.com/coder/coder/v2/codersdk"
9+
"github.com/hashicorp/terraform-plugin-framework/resource"
10+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
11+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier"
12+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
13+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
14+
"github.com/hashicorp/terraform-plugin-framework/types"
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\nTerraform does not guarantee this resource" +
43+
"will be created before other resources or attributes that require a licensed deployment. " +
44+
"It is instead recommended to use the `depends_on` meta-argument.",
45+
46+
Attributes: map[string]schema.Attribute{
47+
"id": schema.Int32Attribute{
48+
MarkdownDescription: "Integer ID of the license.",
49+
Computed: true,
50+
PlanModifiers: []planmodifier.Int32{
51+
int32planmodifier.UseStateForUnknown(),
52+
},
53+
},
54+
"expires_at": schema.Int64Attribute{
55+
MarkdownDescription: "Unix timestamp of when the license expires.",
56+
Computed: true,
57+
},
58+
"license": schema.StringAttribute{
59+
MarkdownDescription: "A license key for Coder.",
60+
Required: true,
61+
Sensitive: true,
62+
PlanModifiers: []planmodifier.String{
63+
stringplanmodifier.RequiresReplace(),
64+
},
65+
},
66+
},
67+
}
68+
}
69+
70+
func (r *LicenseResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
71+
// Prevent panic if the provider has not been configured.
72+
if req.ProviderData == nil {
73+
return
74+
}
75+
76+
data, ok := req.ProviderData.(*CoderdProviderData)
77+
78+
if !ok {
79+
resp.Diagnostics.AddError(
80+
"Unexpected Resource Configure Type",
81+
fmt.Sprintf("Expected *CoderdProviderData, got: %T. Please report this issue to the provider developers.", req.ProviderData),
82+
)
83+
84+
return
85+
}
86+
87+
r.data = data
88+
}
89+
90+
func (r *LicenseResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
91+
var data LicenseResourceModel
92+
93+
// Read Terraform plan data into the model
94+
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
95+
96+
if resp.Diagnostics.HasError() {
97+
return
98+
}
99+
100+
client := r.data.Client
101+
102+
license, err := client.AddLicense(ctx, codersdk.AddLicenseRequest{
103+
License: data.License.ValueString(),
104+
})
105+
if err != nil {
106+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to add license, got error: %s", err))
107+
return
108+
}
109+
data.ID = types.Int32Value(license.ID)
110+
expiresAt, err := license.ExpiresAt()
111+
if err != nil {
112+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to parse license expiration, got error: %s", err))
113+
return
114+
}
115+
data.ExpiresAt = types.Int64Value(expiresAt.Unix())
116+
117+
// Save data into Terraform state
118+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
119+
}
120+
121+
func (r *LicenseResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
122+
var data LicenseResourceModel
123+
124+
// Read Terraform prior state data into the model
125+
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
126+
127+
if resp.Diagnostics.HasError() {
128+
return
129+
}
130+
131+
licenses, err := r.data.Client.Licenses(ctx)
132+
if err != nil {
133+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to list licenses, got error: %s", err))
134+
return
135+
}
136+
137+
found := false
138+
for _, license := range licenses {
139+
if license.ID == data.ID.ValueInt32() {
140+
found = true
141+
expiresAt, err := license.ExpiresAt()
142+
if err != nil {
143+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to parse license expiration, got error: %s", err))
144+
return
145+
}
146+
if expiresAt.Before(time.Now()) {
147+
resp.Diagnostics.AddError("Client Error", "License has expired.")
148+
return
149+
}
150+
data.ExpiresAt = types.Int64Value(expiresAt.Unix())
151+
}
152+
}
153+
if !found {
154+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("License with ID %d not found", data.ID.ValueInt32()))
155+
}
156+
157+
// Save updated data into Terraform state
158+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
159+
}
160+
161+
func (r *LicenseResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
162+
var data LicenseResourceModel
163+
164+
// Read Terraform plan data into the model
165+
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
166+
167+
if resp.Diagnostics.HasError() {
168+
return
169+
}
170+
171+
// Update is handled by replacement
172+
173+
// Save updated data into Terraform state
174+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
175+
}
176+
177+
func (r *LicenseResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
178+
var data LicenseResourceModel
179+
180+
// Read Terraform prior state data into the model
181+
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
182+
183+
if resp.Diagnostics.HasError() {
184+
return
185+
}
186+
187+
client := r.data.Client
188+
189+
err := client.DeleteLicense(ctx, data.ID.ValueInt32())
190+
if err != nil {
191+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete license, got error: %s", err))
192+
return
193+
}
194+
}
+74
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

+1
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)