Skip to content

support for new data source for IMIs #58

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions examples/datasource/imis/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
terraform {
required_providers {
intelcloud = {
source = "intel/intelcloud"
version = "0.0.19"
}
}
}


provider "intelcloud" {
region = "us-staging-1"
endpoints = {
api = "https://us-staging-1-sdk-api.eglb.intel.com"
auth = "https://client-token.staging.api.idcservice.net"
}
}

// Commenting this temporarily as it is won't work without merging the PR for imis datasource
#data "intelcloud_imis" "filtered_imis" {
# clusteruuid = "<your-cluster-uuid>"
# filters = [
# {
# name = "instance-type"
# values = ["vm-spr-sml"]
# },
# ]
#}
#
#output "print_images" {
# value = data.intelcloud_imis.filtered_imis
#}
16 changes: 16 additions & 0 deletions internal/models/imis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package models

import "github.com/hashicorp/terraform-plugin-framework/types"

// model for imis

Check failure on line 5 in internal/models/imis.go

View workflow job for this annotation

GitHub Actions / build-and-test

comment on exported type ImisModel should be of the form "ImisModel ..." (with optional leading article)
type ImisModel struct {
InstanceTypeName string `tfsdk:"instancetypename"`
WorkerImi []WorkerImi `tfsdk:"workerimi"`
}

// model for worker imi

Check failure on line 11 in internal/models/imis.go

View workflow job for this annotation

GitHub Actions / build-and-test

comment on exported type WorkerImi should be of the form "WorkerImi ..." (with optional leading article)
type WorkerImi struct {
ImiName types.String `tfsdk:"iminame"`
Info types.String `tfsdk:"info"`
IsDefaultImi types.Bool `tfsdk:"isdefaultimi"`
}
3 changes: 3 additions & 0 deletions internal/provider/filesystem_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,9 @@ func (r *filesystemResource) Update(ctx context.Context, req resource.UpdateRequ
}
currState.Spec.Size = plan.Spec.Size

// set timeout again for consistency
currState.Timeouts = plan.Timeouts

// Set refreshed state
diags = resp.State.Set(ctx, currState)
resp.Diagnostics.Append(diags...)
Expand Down
5 changes: 5 additions & 0 deletions internal/provider/iks_node_group_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ func (r *iksNodeGroupResource) Schema(_ context.Context, _ resource.SchemaReques
},
"imiid": schema.StringAttribute{
Computed: true,
Optional: true,
},
"state": schema.StringAttribute{
Computed: true,
Expand Down Expand Up @@ -196,6 +197,10 @@ func (r *iksNodeGroupResource) Create(ctx context.Context, req resource.CreateRe
NetworkInterfaceVnetName: vnetResp.Metadata.Name,
})

if !plan.IMIId.IsNull() && !plan.IMIId.IsUnknown() {
inArg.WorkerImiId = plan.IMIId.ValueString()
}

nodeGroupResp, _, err := r.client.CreateIKSNodeGroup(ctx, &inArg, plan.ClusterUUID.ValueString(), false)
if err != nil {
resp.Diagnostics.AddError(
Expand Down
243 changes: 243 additions & 0 deletions internal/provider/imis_datasource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
package provider

import (
"context"
"fmt"
"sort"
"strings"

"terraform-provider-intelcloud/internal/models"
"terraform-provider-intelcloud/pkg/itacservices"

"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
)

func NewImisDataSource() datasource.DataSource {
return &imisDataSource{}
}

type imisDataSource struct {
client *itacservices.IDCServicesClient
}

// Ensure the implementation satisfies the expected interfaces.
var (
_ datasource.DataSource = &imisDataSource{}
_ datasource.DataSourceWithConfigure = &imisDataSource{}
)

// storagesDataSourceModel maps the data source schema data.
type imisDataSourceModel struct {
Latest types.Bool `tfsdk:"latest"`
ClusterUUID types.String `tfsdk:"clusteruuid"`
Filters []KVFilter `tfsdk:"filters"`
Result *models.ImisModel `tfsdk:"result"`
Imis []models.ImisModel `tfsdk:"items"`
}

// Configure adds the provider configured client to the data source.
func (d *imisDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
if req.ProviderData == nil {
fmt.Println("[DEBUG] ProviderData is nil")
return
}
client, ok := req.ProviderData.(*itacservices.IDCServicesClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected *itacservices.IDCServicesClient, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)

return
}

d.client = client
}

func (d *imisDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_imis"
}

func (d *imisDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"clusteruuid": schema.StringAttribute{
Required: true,
Description: "The UUID of the cluster.",
},
"latest": schema.BoolAttribute{
Optional: true,
Description: "If true, only the latest IMI will be returned.",
Computed: true,
},
"filters": schema.ListNestedAttribute{
Required: true,
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{ // maps to KVFilter.Key
Required: true,
},
"values": schema.ListAttribute{ // maps to KVFilter.Values
ElementType: types.StringType,
Required: true,
},
},
},
},
"result": schema.SingleNestedAttribute{
Computed: true,
Attributes: map[string]schema.Attribute{
"instancetypename": schema.StringAttribute{
Computed: true,
},
"workerimi": schema.ListNestedAttribute{
Computed: true,
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"iminame": schema.StringAttribute{
Computed: true,
},
"info": schema.StringAttribute{
Computed: true,
},
"isdefaultimi": schema.BoolAttribute{
Computed: true,
},
},
},
},
},
},
"items": schema.ListNestedAttribute{
Computed: true,
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"instancetypename": schema.StringAttribute{
Computed: true,
},
"workerimi": schema.ListNestedAttribute{
Computed: true,
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"iminame": schema.StringAttribute{
Computed: true,
},
"info": schema.StringAttribute{
Computed: true,
},
"isdefaultimi": schema.BoolAttribute{
Computed: true,
},
},
},
},
},
},
},
},
}
}

func (d *imisDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var state imisDataSourceModel

diags := req.Config.Get(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

if state.ClusterUUID.IsUnknown() || state.ClusterUUID.IsNull() {
resp.Diagnostics.AddError("Missing clusteruuid", "The 'clusteruuid' field is required.")
return
}

if d.client == nil {
resp.Diagnostics.AddError("client is nil", "The client is not configured. Please check your provider configuration.")
return
}

instanceImis, err := d.client.GetImis(ctx, state.ClusterUUID.ValueString())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Unable to Read ITAC Imis: %s", state.ClusterUUID.ValueString()),
err.Error(),
)
return
}

allImis := []models.ImisModel{}

for _, imi := range instanceImis.InstanceTypes {
tfImi := models.ImisModel{
InstanceTypeName: imi.Name,
WorkerImi: []models.WorkerImi{},
}
for _, workerImis := range imi.WorkerImi {
tfImi.WorkerImi = append(tfImi.WorkerImi, models.WorkerImi{
ImiName: types.StringValue(workerImis.ImiName),
Info: types.StringValue(workerImis.Info),
IsDefaultImi: types.BoolValue(workerImis.IsDefaultImi),
})
}
allImis = append(allImis, tfImi)
}
filteredImages := filterImis(allImis, state.Filters, state.Latest.ValueBool())

state.Imis = append(state.Imis, filteredImages...)
if len(filteredImages) > 0 {
state.Imis = filteredImages
state.Result = &filteredImages[0]
} else {
state.Imis = []models.ImisModel{}
state.Result = nil
}

// Set state
diags = resp.State.Set(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}

func filterImis(allImis []models.ImisModel, filters []KVFilter, latest bool) []models.ImisModel {
filteredImages := allImis

for _, filter := range filters {
switch filter.Key {
case "instance-type":
filteredImages = filterByInstanceType(filteredImages, filter.Values, latest)
default:
return allImis
}
}
return filteredImages
}

func filterByInstanceType(allImis []models.ImisModel, values []string, latest bool) []models.ImisModel {
filteredImages := []models.ImisModel{}
for _, v := range values {
for _, imi := range allImis {
if strings.Contains(imi.InstanceTypeName, v) {
filteredImages = append(filteredImages, imi)
}
}
}
if latest && len(filteredImages) > 0 {
getLatestImi(filteredImages)
}
return filteredImages
}

// getLatestImi sorts the WorkerImi slice in each ImisModel based on ImiName and returns latest IMI
func getLatestImi(imisList []models.ImisModel) {
for i := range imisList {
sort.Slice(imisList[i].WorkerImi, func(a, b int) bool {
return imisList[i].WorkerImi[a].ImiName.ValueString() < imisList[i].WorkerImi[b].ImiName.ValueString()
})
}
imisList[0].WorkerImi = imisList[0].WorkerImi[:1]
}
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ func (p *idcProvider) DataSources(_ context.Context) []func() datasource.DataSou
NewMachineImagesDataSource,
// NewKubernetesDataSource,
NewKubeconfigDataSource,
NewImisDataSource,
}
}

Expand Down
3 changes: 2 additions & 1 deletion pkg/itacservices/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func NewClient(ctx context.Context, host, tokenSvc, cloudaccount, clientid, clie

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

authStr := fmt.Sprintf("%s:%s", *clientid, *clientsecret)
Expand Down Expand Up @@ -100,5 +100,6 @@ func NewClient(ctx context.Context, host, tokenSvc, cloudaccount, clientid, clie
Region: region,
Apitoken: &tokenResp.AccessToken,
ExpireAt: time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second),
APIClient: common.NewAPIClient(),
}, nil
}
33 changes: 33 additions & 0 deletions pkg/itacservices/common/httpclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,36 @@ func printRequest(req *http.Request) {
req.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
}
}

type apiClientImpl struct{}

// NewAPIClient returns a concrete implementation of the APIClient interface.
func NewAPIClient() APIClient {
return &apiClientImpl{}
}

func (c *apiClientImpl) MakeGetAPICall(ctx context.Context, url, token string, headers map[string]string) (int, []byte, error) {
return MakeGetAPICall(ctx, url, token, nil)
}

func (c *apiClientImpl) MakePOSTAPICall(ctx context.Context, url, token string, payload []byte) (int, []byte, error) {
return MakePOSTAPICall(ctx, url, token, payload)
}

func (c *apiClientImpl) MakePutAPICall(ctx context.Context, url, token string, payload []byte) (int, []byte, error) {
return MakePutAPICall(ctx, url, token, payload)
}

func (c *apiClientImpl) MakeDeleteAPICall(ctx context.Context, url, token string, headers map[string]string) (int, []byte, error) {
return MakeDeleteAPICall(ctx, url, token, nil)
}

func (c *apiClientImpl) GenerateFilesystemLoginCredentials(ctx context.Context, resourceId string) (*string, error) {
// Placeholder: implement your actual logic here
return nil, fmt.Errorf("GenerateFilesystemLoginCredentials not implemented")
}

func (c *apiClientImpl) ParseString(tmpl string, data any) (string, error) {
// Placeholder: implement your actual logic here
return ParseString(tmpl, data)
}
Loading
Loading