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

Skip to content

Commit 48536b5

Browse files
committed
feat: add license resource
1 parent bf558f5 commit 48536b5

File tree

2 files changed

+264
-0
lines changed

2 files changed

+264
-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+
}
+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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+
_ = 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+
License: license,
29+
}
30+
31+
resource.Test(t, resource.TestCase{
32+
IsUnitTest: true,
33+
PreCheck: func() { testAccPreCheck(t) },
34+
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
35+
Steps: []resource.TestStep{
36+
{
37+
Config: cfg1.String(t),
38+
},
39+
},
40+
})
41+
}
42+
43+
type testAccLicenseResourceconfig struct {
44+
License string
45+
}
46+
47+
func (c testAccLicenseResourceconfig) String(t *testing.T) string {
48+
t.Helper()
49+
tpl := `
50+
provider coderd {
51+
url = "{{.URL}}"
52+
token = "{{.Token}}"
53+
}
54+
55+
resource "coderd_group" "test" {
56+
license = {{orNull .License}}
57+
}
58+
`
59+
funcMap := template.FuncMap{
60+
"orNull": PrintOrNull,
61+
}
62+
63+
buf := strings.Builder{}
64+
tmpl, err := template.New("licenseResource").Funcs(funcMap).Parse(tpl)
65+
require.NoError(t, err)
66+
67+
err = tmpl.Execute(&buf, c)
68+
require.NoError(t, err)
69+
return buf.String()
70+
}

0 commit comments

Comments
 (0)