• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Scalr / terraform-provider-scalr / 13494954504

24 Feb 2025 09:37AM UTC coverage: 69.72% (-1.2%) from 70.967%
13494954504

Pull #397

github

denkl
SCALRCORE-30924 Manage environment access to Infracost via Terraform provider
Pull Request #397: SCALRCORE-30924 Manage environment access to Infracost via Terraform …

85 of 305 new or added lines in 3 files covered. (27.87%)

2 existing lines in 1 file now uncovered.

7460 of 10700 relevant lines covered (69.72%)

173.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

22.86
/internal/provider/integration_infracost_resource.go
1
package provider
2

3
import (
4
        "context"
5
        "errors"
6
        "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
7
        "github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier"
8
        "github.com/scalr/go-scalr"
9

10
        "github.com/hashicorp/terraform-plugin-framework/path"
11
        "github.com/hashicorp/terraform-plugin-framework/resource"
12
        "github.com/hashicorp/terraform-plugin-framework/resource/schema"
13
        "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
14
        "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
15
        "github.com/hashicorp/terraform-plugin-framework/schema/validator"
16
        "github.com/hashicorp/terraform-plugin-framework/types"
17
        "github.com/scalr/terraform-provider-scalr/internal/framework"
18
        "github.com/scalr/terraform-provider-scalr/internal/framework/validation"
19
)
20

21
// Compile-time interface checks
22
var (
23
        _ resource.Resource                     = &integrationInfracostResource{}
24
        _ resource.ResourceWithConfigure        = &integrationInfracostResource{}
25
        _ resource.ResourceWithConfigValidators = &integrationInfracostResource{}
26
        _ resource.ResourceWithImportState      = &integrationInfracostResource{}
27
)
28

29
func newIntegrationInfracostResource() resource.Resource {
1,474✔
30
        return &integrationInfracostResource{}
1,474✔
31
}
1,474✔
32

33
// integrationInfracostResource defines the resource implementation.
34
type integrationInfracostResource struct {
35
        framework.ResourceWithScalrClient
36
}
37

38
// integrationInfracostResourceModel describes the resource data model.
39
type integrationInfracostResourceModel struct {
40
        Id           types.String `tfsdk:"id"`
41
        Name         types.String `tfsdk:"name"`
42
        ApiKey       types.String `tfsdk:"api_key"`
43
        Environments types.Set    `tfsdk:"environments"`
44
}
45

46
func (r *integrationInfracostResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
124✔
47
        resp.TypeName = req.ProviderTypeName + "_integration_infracost"
124✔
48
}
124✔
49

50
func (r *integrationInfracostResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
1,350✔
51
        resp.Schema = schema.Schema{
1,350✔
52
                MarkdownDescription: "Manages the state of Infracost integration in Scalr.",
1,350✔
53

1,350✔
54
                Attributes: map[string]schema.Attribute{
1,350✔
55
                        "id": schema.StringAttribute{
1,350✔
56
                                MarkdownDescription: "The ID of this resource.",
1,350✔
57
                                Computed:            true,
1,350✔
58
                                PlanModifiers: []planmodifier.String{
1,350✔
59
                                        stringplanmodifier.UseStateForUnknown(),
1,350✔
60
                                },
1,350✔
61
                        },
1,350✔
62
                        "name": schema.StringAttribute{
1,350✔
63
                                MarkdownDescription: "Name of the Infracost integration.",
1,350✔
64
                                Required:            true,
1,350✔
65
                                Validators: []validator.String{
1,350✔
66
                                        validation.StringIsNotWhiteSpace(),
1,350✔
67
                                },
1,350✔
68
                        },
1,350✔
69
                        "api_key": schema.StringAttribute{
1,350✔
70
                                MarkdownDescription: "API key for the Infracost integration.",
1,350✔
71
                                Required:            true,
1,350✔
72
                                Sensitive:           true,
1,350✔
73
                                Validators: []validator.String{
1,350✔
74
                                        validation.StringIsNotWhiteSpace(),
1,350✔
75
                                },
1,350✔
76
                        },
1,350✔
77
                        "environments": schema.SetAttribute{
1,350✔
78
                                MarkdownDescription: "List of environments this integration is linked to. Use `[\"*\"]` to allow in all environments.",
1,350✔
79
                                ElementType:         types.StringType,
1,350✔
80
                                Optional:            true,
1,350✔
81
                                Computed:            true,
1,350✔
82
                                Validators: []validator.Set{
1,350✔
83
                                        setvalidator.ValueStringsAre(validation.StringIsNotWhiteSpace()),
1,350✔
84
                                },
1,350✔
85
                                PlanModifiers: []planmodifier.Set{
1,350✔
86
                                        setplanmodifier.UseStateForUnknown(),
1,350✔
87
                                },
1,350✔
88
                        },
1,350✔
89
                },
1,350✔
90
        }
1,350✔
91
}
1,350✔
92

NEW
93
func (r *integrationInfracostResource) ConfigValidators(_ context.Context) []resource.ConfigValidator {
×
NEW
94
        return []resource.ConfigValidator{}
×
NEW
95
}
×
96

NEW
97
func (r *integrationInfracostResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
×
NEW
98
        var plan integrationInfracostResourceModel
×
NEW
99

×
NEW
100
        // Read plan data
×
NEW
101
        resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
×
NEW
102
        if resp.Diagnostics.HasError() {
×
NEW
103
                return
×
NEW
104
        }
×
105

NEW
106
        opts := scalr.InfracostIntegrationCreateOptions{
×
NEW
107
                Name:     plan.Name.ValueStringPointer(),
×
NEW
108
                ApiKey:   plan.ApiKey.ValueStringPointer(),
×
NEW
109
                IsShared: ptr(false),
×
NEW
110
        }
×
NEW
111

×
NEW
112
        if !plan.Environments.IsUnknown() && !plan.Environments.IsNull() {
×
NEW
113
                var environments []string
×
NEW
114
                resp.Diagnostics.Append(plan.Environments.ElementsAs(ctx, &environments, false)...)
×
NEW
115

×
NEW
116
                if (len(environments) == 1) && (environments[0] == "*") {
×
NEW
117
                        opts.IsShared = ptr(true)
×
NEW
118
                } else if len(environments) > 0 {
×
NEW
119
                        envs := make([]*scalr.Environment, len(environments))
×
NEW
120
                        for i, env := range environments {
×
NEW
121
                                envs[i] = &scalr.Environment{ID: env}
×
NEW
122
                        }
×
NEW
123
                        opts.Environments = envs
×
124
                }
125
        }
126

NEW
127
        if resp.Diagnostics.HasError() {
×
NEW
128
                return
×
NEW
129
        }
×
130

NEW
131
        integrationInfracost, err := r.Client.InfracostIntegrations.Create(ctx, opts)
×
NEW
132
        if err != nil {
×
NEW
133
                resp.Diagnostics.AddError("Error creating Infracost integration", err.Error())
×
NEW
134
                return
×
NEW
135
        }
×
136

NEW
137
        plan.Id = types.StringValue(integrationInfracost.ID)
×
NEW
138
        plan.Name = types.StringValue(integrationInfracost.Name)
×
NEW
139

×
NEW
140
        envs := make([]string, len(integrationInfracost.Environments))
×
NEW
141
        for i, env := range integrationInfracost.Environments {
×
NEW
142
                envs[i] = env.ID
×
NEW
143
        }
×
NEW
144
        if integrationInfracost.IsShared {
×
NEW
145
                envs = []string{"*"}
×
NEW
146
        }
×
NEW
147
        envsValues, d := types.SetValueFrom(ctx, types.StringType, envs)
×
NEW
148
        resp.Diagnostics.Append(d...)
×
NEW
149
        plan.Environments = envsValues
×
NEW
150

×
NEW
151
        // Set state to fully populated data
×
NEW
152
        resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
×
NEW
153
        if resp.Diagnostics.HasError() {
×
NEW
154
                return
×
NEW
155
        }
×
156
}
157

NEW
158
func (r *integrationInfracostResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
×
NEW
159
        // Get current state
×
NEW
160
        var state integrationInfracostResourceModel
×
NEW
161
        resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
×
NEW
162
        if resp.Diagnostics.HasError() {
×
NEW
163
                return
×
NEW
164
        }
×
165

166
        // Get refreshed resource state from API
NEW
167
        integrationInfracost, err := r.Client.InfracostIntegrations.Read(ctx, state.Id.ValueString())
×
NEW
168
        if err != nil {
×
NEW
169
                if errors.Is(err, scalr.ErrResourceNotFound) {
×
NEW
170
                        resp.State.RemoveResource(ctx)
×
NEW
171
                        return
×
NEW
172
                }
×
NEW
173
                resp.Diagnostics.AddError("Error retrieving Infracost integration", err.Error())
×
NEW
174
                return
×
175
        }
176

177
        // Overwrite attributes with refreshed values
NEW
178
        state.Name = types.StringValue(integrationInfracost.Name)
×
NEW
179

×
NEW
180
        if integrationInfracost.IsShared {
×
NEW
181
                envs := []string{"*"}
×
NEW
182
                envsValues, d := types.SetValueFrom(ctx, types.StringType, envs)
×
NEW
183
                resp.Diagnostics.Append(d...)
×
NEW
184
                state.Environments = envsValues
×
NEW
185
        } else {
×
NEW
186
                envs := make([]string, len(integrationInfracost.Environments))
×
NEW
187
                for i, env := range integrationInfracost.Environments {
×
NEW
188
                        envs[i] = env.ID
×
NEW
189
                }
×
NEW
190
                envsValues, d := types.SetValueFrom(ctx, types.StringType, envs)
×
NEW
191
                resp.Diagnostics.Append(d...)
×
NEW
192
                state.Environments = envsValues
×
193
        }
194

195
        // Set refreshed state
NEW
196
        resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
×
NEW
197
        if resp.Diagnostics.HasError() {
×
NEW
198
                return
×
NEW
199
        }
×
200
}
201

NEW
202
func (r *integrationInfracostResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
×
NEW
203
        var plan, state integrationInfracostResourceModel
×
NEW
204
        resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
×
NEW
205
        resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
×
NEW
206
        if resp.Diagnostics.HasError() {
×
NEW
207
                return
×
NEW
208
        }
×
209

NEW
210
        opts := scalr.InfracostIntegrationUpdateOptions{}
×
NEW
211

×
NEW
212
        if !plan.Name.Equal(state.Name) {
×
NEW
213
                opts.Name = plan.Name.ValueStringPointer()
×
NEW
214
        }
×
215

NEW
216
        if !plan.ApiKey.Equal(state.ApiKey) {
×
NEW
217
                opts.ApiKey = plan.ApiKey.ValueStringPointer()
×
NEW
218
        }
×
219

NEW
220
        if !plan.Environments.Equal(state.Environments) {
×
NEW
221
                var environments []string
×
NEW
222
                resp.Diagnostics.Append(plan.Environments.ElementsAs(ctx, &environments, false)...)
×
NEW
223

×
NEW
224
                if (len(environments) == 1) && (environments[0] == "*") {
×
NEW
225
                        opts.IsShared = ptr(true)
×
NEW
226
                        opts.Environments = make([]*scalr.Environment, 0)
×
NEW
227
                } else if len(environments) > 0 {
×
NEW
228
                        envs := make([]*scalr.Environment, len(environments))
×
NEW
229
                        for i, env := range environments {
×
NEW
230
                                envs[i] = &scalr.Environment{ID: env}
×
NEW
231
                        }
×
NEW
232
                        opts.Environments = envs
×
NEW
233
                        opts.IsShared = ptr(false)
×
NEW
234
                } else {
×
NEW
235
                        opts.IsShared = ptr(false)
×
NEW
236
                        opts.Environments = make([]*scalr.Environment, 0)
×
NEW
237
                }
×
238
        }
239

240
        // Update existing resource
NEW
241
        integrationInfracost, err := r.Client.InfracostIntegrations.Update(ctx, plan.Id.ValueString(), opts)
×
NEW
242
        if err != nil {
×
NEW
243
                resp.Diagnostics.AddError("Error updating Infracost integration", err.Error())
×
NEW
244
                return
×
NEW
245
        }
×
246

247
        // Overwrite attributes with refreshed values
NEW
248
        plan.Name = types.StringValue(integrationInfracost.Name)
×
NEW
249

×
NEW
250
        if integrationInfracost.IsShared {
×
NEW
251
                envs := []string{"*"}
×
NEW
252
                envsValues, d := types.SetValueFrom(ctx, types.StringType, envs)
×
NEW
253
                resp.Diagnostics.Append(d...)
×
NEW
254
                state.Environments = envsValues
×
NEW
255
        } else {
×
NEW
256
                envs := make([]string, len(integrationInfracost.Environments))
×
NEW
257
                for i, env := range integrationInfracost.Environments {
×
NEW
258
                        envs[i] = env.ID
×
NEW
259
                }
×
NEW
260
                envsValues, d := types.SetValueFrom(ctx, types.StringType, envs)
×
NEW
261
                resp.Diagnostics.Append(d...)
×
NEW
262
                state.Environments = envsValues
×
263
        }
264

265
        // Set refreshed state
NEW
266
        resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
×
NEW
267
        if resp.Diagnostics.HasError() {
×
NEW
268
                return
×
NEW
269
        }
×
270
}
271

NEW
272
func (r *integrationInfracostResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
×
NEW
273
        // Get current state
×
NEW
274
        var state integrationInfracostResourceModel
×
NEW
275
        resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
×
NEW
276
        if resp.Diagnostics.HasError() {
×
NEW
277
                return
×
NEW
278
        }
×
279

NEW
280
        err := r.Client.InfracostIntegrations.Delete(ctx, state.Id.ValueString())
×
NEW
281
        if err != nil && !errors.Is(err, scalr.ErrResourceNotFound) {
×
NEW
282
                resp.Diagnostics.AddError("Error deleting Infracost integration", err.Error())
×
NEW
283
                return
×
NEW
284
        }
×
285
}
286

NEW
287
func (r *integrationInfracostResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
×
NEW
288
        resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
×
NEW
289
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc