Skip to content

Commit 79f183f

Browse files
support for new data source for IMIs
Signed-off-by: Rohit Kulkarni <[email protected]>
1 parent 06da4d4 commit 79f183f

File tree

11 files changed

+487
-3
lines changed

11 files changed

+487
-3
lines changed

examples/datasource/imis/main.tf

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
terraform {
2+
required_providers {
3+
intelcloud = {
4+
source = "intel/intelcloud"
5+
version = "0.0.19"
6+
}
7+
}
8+
}
9+
10+
11+
provider "intelcloud" {
12+
region = "us-staging-1"
13+
endpoints = {
14+
api = "https://us-staging-1-sdk-api.eglb.intel.com"
15+
auth = "https://client-token.staging.api.idcservice.net"
16+
}
17+
}
18+
19+
// Commenting this temporarily as it is won't work without merging the PR for imis datasource
20+
#data "intelcloud_imis" "filtered_imis" {
21+
# clusteruuid = "<your-cluster-uuid>"
22+
# filters = [
23+
# {
24+
# name = "instance-type"
25+
# values = ["vm-spr-sml"]
26+
# },
27+
# ]
28+
#}
29+
#
30+
#output "print_images" {
31+
# value = data.intelcloud_imis.filtered_imis
32+
#}

internal/models/imis.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package models
2+
3+
import "github.com/hashicorp/terraform-plugin-framework/types"
4+
5+
// model for imis
6+
type ImisModel struct {
7+
InstanceTypeName string `tfsdk:"instancetypename"`
8+
WorkerImi []WorkerImi `tfsdk:"workerimi"`
9+
}
10+
11+
// model for worker imi
12+
type WorkerImi struct {
13+
ImiName types.String `tfsdk:"iminame"`
14+
Info types.String `tfsdk:"info"`
15+
IsDefaultImi types.Bool `tfsdk:"isdefaultimi"`
16+
}

internal/provider/filesystem_resource.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,9 @@ func (r *filesystemResource) Update(ctx context.Context, req resource.UpdateRequ
382382
}
383383
currState.Spec.Size = plan.Spec.Size
384384

385+
// set timeout again for consistency
386+
currState.Timeouts = plan.Timeouts
387+
385388
// Set refreshed state
386389
diags = resp.State.Set(ctx, currState)
387390
resp.Diagnostics.Append(diags...)

internal/provider/iks_node_group_resource.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ func (r *iksNodeGroupResource) Schema(_ context.Context, _ resource.SchemaReques
9595
},
9696
"imiid": schema.StringAttribute{
9797
Computed: true,
98+
Optional: true,
9899
},
99100
"state": schema.StringAttribute{
100101
Computed: true,
@@ -196,6 +197,10 @@ func (r *iksNodeGroupResource) Create(ctx context.Context, req resource.CreateRe
196197
NetworkInterfaceVnetName: vnetResp.Metadata.Name,
197198
})
198199

200+
if !plan.IMIId.IsNull() && !plan.IMIId.IsUnknown() {
201+
inArg.WorkerImiId = plan.IMIId.ValueString()
202+
}
203+
199204
nodeGroupResp, _, err := r.client.CreateIKSNodeGroup(ctx, &inArg, plan.ClusterUUID.ValueString(), false)
200205
if err != nil {
201206
resp.Diagnostics.AddError(

internal/provider/imis_datasource.go

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"sort"
7+
"strings"
8+
9+
"terraform-provider-intelcloud/internal/models"
10+
"terraform-provider-intelcloud/pkg/itacservices"
11+
12+
"github.com/hashicorp/terraform-plugin-framework/datasource"
13+
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
14+
"github.com/hashicorp/terraform-plugin-framework/types"
15+
)
16+
17+
func NewImisDataSource() datasource.DataSource {
18+
return &imisDataSource{}
19+
}
20+
21+
type imisDataSource struct {
22+
client *itacservices.IDCServicesClient
23+
}
24+
25+
// Ensure the implementation satisfies the expected interfaces.
26+
var (
27+
_ datasource.DataSource = &imisDataSource{}
28+
_ datasource.DataSourceWithConfigure = &imisDataSource{}
29+
)
30+
31+
// storagesDataSourceModel maps the data source schema data.
32+
type imisDataSourceModel struct {
33+
Latest types.Bool `tfsdk:"latest"`
34+
ClusterUUID types.String `tfsdk:"clusteruuid"`
35+
Filters []KVFilter `tfsdk:"filters"`
36+
Result *models.ImisModel `tfsdk:"result"`
37+
Imis []models.ImisModel `tfsdk:"items"`
38+
}
39+
40+
// Configure adds the provider configured client to the data source.
41+
func (d *imisDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
42+
if req.ProviderData == nil {
43+
fmt.Println("[DEBUG] ProviderData is nil")
44+
return
45+
}
46+
client, ok := req.ProviderData.(*itacservices.IDCServicesClient)
47+
if !ok {
48+
resp.Diagnostics.AddError(
49+
"Unexpected Data Source Configure Type",
50+
fmt.Sprintf("Expected *itacservices.IDCServicesClient, got: %T. Please report this issue to the provider developers.", req.ProviderData),
51+
)
52+
53+
return
54+
}
55+
56+
d.client = client
57+
}
58+
59+
func (d *imisDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
60+
resp.TypeName = req.ProviderTypeName + "_imis"
61+
}
62+
63+
func (d *imisDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
64+
resp.Schema = schema.Schema{
65+
Attributes: map[string]schema.Attribute{
66+
"clusteruuid": schema.StringAttribute{
67+
Required: true,
68+
Description: "The UUID of the cluster.",
69+
},
70+
"latest": schema.BoolAttribute{
71+
Optional: true,
72+
Description: "If true, only the latest IMI will be returned.",
73+
Computed: true,
74+
},
75+
"filters": schema.ListNestedAttribute{
76+
Required: true,
77+
NestedObject: schema.NestedAttributeObject{
78+
Attributes: map[string]schema.Attribute{
79+
"name": schema.StringAttribute{ // maps to KVFilter.Key
80+
Required: true,
81+
},
82+
"values": schema.ListAttribute{ // maps to KVFilter.Values
83+
ElementType: types.StringType,
84+
Required: true,
85+
},
86+
},
87+
},
88+
},
89+
"result": schema.SingleNestedAttribute{
90+
Computed: true,
91+
Attributes: map[string]schema.Attribute{
92+
"instancetypename": schema.StringAttribute{
93+
Computed: true,
94+
},
95+
"workerimi": schema.ListNestedAttribute{
96+
Computed: true,
97+
NestedObject: schema.NestedAttributeObject{
98+
Attributes: map[string]schema.Attribute{
99+
"iminame": schema.StringAttribute{
100+
Computed: true,
101+
},
102+
"info": schema.StringAttribute{
103+
Computed: true,
104+
},
105+
"isdefaultimi": schema.BoolAttribute{
106+
Computed: true,
107+
},
108+
},
109+
},
110+
},
111+
},
112+
},
113+
"items": schema.ListNestedAttribute{
114+
Computed: true,
115+
NestedObject: schema.NestedAttributeObject{
116+
Attributes: map[string]schema.Attribute{
117+
"instancetypename": schema.StringAttribute{
118+
Computed: true,
119+
},
120+
"workerimi": schema.ListNestedAttribute{
121+
Computed: true,
122+
NestedObject: schema.NestedAttributeObject{
123+
Attributes: map[string]schema.Attribute{
124+
"iminame": schema.StringAttribute{
125+
Computed: true,
126+
},
127+
"info": schema.StringAttribute{
128+
Computed: true,
129+
},
130+
"isdefaultimi": schema.BoolAttribute{
131+
Computed: true,
132+
},
133+
},
134+
},
135+
},
136+
},
137+
},
138+
},
139+
},
140+
}
141+
}
142+
143+
func (d *imisDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
144+
var state imisDataSourceModel
145+
146+
diags := req.Config.Get(ctx, &state)
147+
resp.Diagnostics.Append(diags...)
148+
if resp.Diagnostics.HasError() {
149+
return
150+
}
151+
152+
if state.ClusterUUID.IsUnknown() || state.ClusterUUID.IsNull() {
153+
resp.Diagnostics.AddError("Missing clusteruuid", "The 'clusteruuid' field is required.")
154+
return
155+
}
156+
157+
if d.client == nil {
158+
resp.Diagnostics.AddError("client is nil", "The client is not configured. Please check your provider configuration.")
159+
return
160+
}
161+
162+
instanceImis, err := d.client.GetImis(ctx, state.ClusterUUID.ValueString())
163+
if err != nil {
164+
resp.Diagnostics.AddError(
165+
fmt.Sprintf("Unable to Read ITAC Imis: %s", state.ClusterUUID.ValueString()),
166+
err.Error(),
167+
)
168+
return
169+
}
170+
171+
allImis := []models.ImisModel{}
172+
173+
for _, imi := range instanceImis.InstanceTypes {
174+
tfImi := models.ImisModel{
175+
InstanceTypeName: imi.Name,
176+
WorkerImi: []models.WorkerImi{},
177+
}
178+
for _, workerImis := range imi.WorkerImi {
179+
tfImi.WorkerImi = append(tfImi.WorkerImi, models.WorkerImi{
180+
ImiName: types.StringValue(workerImis.ImiName),
181+
Info: types.StringValue(workerImis.Info),
182+
IsDefaultImi: types.BoolValue(workerImis.IsDefaultImi),
183+
})
184+
}
185+
allImis = append(allImis, tfImi)
186+
}
187+
filteredImages := filterImis(allImis, state.Filters, state.Latest.ValueBool())
188+
189+
state.Imis = append(state.Imis, filteredImages...)
190+
if len(filteredImages) > 0 {
191+
state.Imis = filteredImages
192+
state.Result = &filteredImages[0]
193+
} else {
194+
state.Imis = []models.ImisModel{}
195+
state.Result = nil
196+
}
197+
198+
// Set state
199+
diags = resp.State.Set(ctx, &state)
200+
resp.Diagnostics.Append(diags...)
201+
if resp.Diagnostics.HasError() {
202+
return
203+
}
204+
}
205+
206+
func filterImis(allImis []models.ImisModel, filters []KVFilter, latest bool) []models.ImisModel {
207+
filteredImages := allImis
208+
209+
for _, filter := range filters {
210+
switch filter.Key {
211+
case "instance-type":
212+
filteredImages = filterByInstanceType(filteredImages, filter.Values, latest)
213+
default:
214+
return allImis
215+
}
216+
}
217+
return filteredImages
218+
}
219+
220+
func filterByInstanceType(allImis []models.ImisModel, values []string, latest bool) []models.ImisModel {
221+
filteredImages := []models.ImisModel{}
222+
for _, v := range values {
223+
for _, imi := range allImis {
224+
if strings.Contains(imi.InstanceTypeName, v) {
225+
filteredImages = append(filteredImages, imi)
226+
}
227+
}
228+
}
229+
if latest && len(filteredImages) > 0 {
230+
getLatestImi(filteredImages)
231+
}
232+
return filteredImages
233+
}
234+
235+
// getLatestImi sorts the WorkerImi slice in each ImisModel based on ImiName and returns latest IMI
236+
func getLatestImi(imisList []models.ImisModel) {
237+
for i := range imisList {
238+
sort.Slice(imisList[i].WorkerImi, func(a, b int) bool {
239+
return imisList[i].WorkerImi[a].ImiName.ValueString() < imisList[i].WorkerImi[b].ImiName.ValueString()
240+
})
241+
}
242+
imisList[0].WorkerImi = imisList[0].WorkerImi[:1]
243+
}

internal/provider/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ func (p *idcProvider) DataSources(_ context.Context) []func() datasource.DataSou
226226
NewMachineImagesDataSource,
227227
// NewKubernetesDataSource,
228228
NewKubeconfigDataSource,
229+
NewImisDataSource,
229230
}
230231
}
231232

pkg/itacservices/client.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func NewClient(ctx context.Context, host, tokenSvc, cloudaccount, clientid, clie
5959

6060
req, err := http.NewRequest("POST", parsedURL, bytes.NewBufferString(data.Encode()))
6161
if err != nil {
62-
return nil, fmt.Errorf("error creating ITAC Token request")
62+
return nil, fmt.Errorf("error creating ITAC Token request: %v", err)
6363
}
6464

6565
authStr := fmt.Sprintf("%s:%s", *clientid, *clientsecret)
@@ -100,5 +100,6 @@ func NewClient(ctx context.Context, host, tokenSvc, cloudaccount, clientid, clie
100100
Region: region,
101101
Apitoken: &tokenResp.AccessToken,
102102
ExpireAt: time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second),
103+
APIClient: common.NewAPIClient(),
103104
}, nil
104105
}

pkg/itacservices/common/httpclient.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,36 @@ func printRequest(req *http.Request) {
165165
req.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
166166
}
167167
}
168+
169+
type apiClientImpl struct{}
170+
171+
// NewAPIClient returns a concrete implementation of the APIClient interface.
172+
func NewAPIClient() APIClient {
173+
return &apiClientImpl{}
174+
}
175+
176+
func (c *apiClientImpl) MakeGetAPICall(ctx context.Context, url, token string, headers map[string]string) (int, []byte, error) {
177+
return MakeGetAPICall(ctx, url, token, nil)
178+
}
179+
180+
func (c *apiClientImpl) MakePOSTAPICall(ctx context.Context, url, token string, payload []byte) (int, []byte, error) {
181+
return MakePOSTAPICall(ctx, url, token, payload)
182+
}
183+
184+
func (c *apiClientImpl) MakePutAPICall(ctx context.Context, url, token string, payload []byte) (int, []byte, error) {
185+
return MakePutAPICall(ctx, url, token, payload)
186+
}
187+
188+
func (c *apiClientImpl) MakeDeleteAPICall(ctx context.Context, url, token string, headers map[string]string) (int, []byte, error) {
189+
return MakeDeleteAPICall(ctx, url, token, nil)
190+
}
191+
192+
func (c *apiClientImpl) GenerateFilesystemLoginCredentials(ctx context.Context, resourceId string) (*string, error) {
193+
// Placeholder: implement your actual logic here
194+
return nil, fmt.Errorf("GenerateFilesystemLoginCredentials not implemented")
195+
}
196+
197+
func (c *apiClientImpl) ParseString(tmpl string, data any) (string, error) {
198+
// Placeholder: implement your actual logic here
199+
return ParseString(tmpl, data)
200+
}

0 commit comments

Comments
 (0)