diff --git a/castai/data_source_omni_cluster.go b/castai/data_source_omni_cluster.go
new file mode 100644
index 000000000..dca3440b9
--- /dev/null
+++ b/castai/data_source_omni_cluster.go
@@ -0,0 +1,159 @@
+package castai
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+
+ "github.com/hashicorp/terraform-plugin-framework/datasource"
+ "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+var (
+ _ datasource.DataSource = (*omniClusterDataSource)(nil)
+ _ datasource.DataSourceWithConfigure = (*omniClusterDataSource)(nil)
+)
+
+type omniClusterDataSource struct {
+ client *ProviderConfig
+}
+
+type omniClusterDataSourceModel struct {
+ ID types.String `tfsdk:"id"`
+ OrganizationID types.String `tfsdk:"organization_id"`
+ ClusterID types.String `tfsdk:"cluster_id"`
+ Name types.String `tfsdk:"name"`
+ State types.String `tfsdk:"state"`
+ ProviderType types.String `tfsdk:"provider_type"`
+ ServiceAccountID types.String `tfsdk:"service_account_id"`
+ CastaiOidcConfig *castaiOidcConfigModel `tfsdk:"castai_oidc_config"`
+}
+
+type castaiOidcConfigModel struct {
+ GcpServiceAccountEmail types.String `tfsdk:"gcp_service_account_email"`
+ GcpServiceAccountUniqueID types.String `tfsdk:"gcp_service_account_unique_id"`
+}
+
+func newOmniClusterDataSource() datasource.DataSource {
+ return &omniClusterDataSource{}
+}
+
+func (d *omniClusterDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_omni_cluster"
+}
+
+func (d *omniClusterDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Description: "Retrieve information about a CAST AI Omni cluster",
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Computed: true,
+ Description: "Cluster ID (same as cluster_id)",
+ },
+ "organization_id": schema.StringAttribute{
+ Required: true,
+ Description: "CAST AI organization ID",
+ },
+ "cluster_id": schema.StringAttribute{
+ Required: true,
+ Description: "CAST AI cluster ID",
+ },
+ "name": schema.StringAttribute{
+ Computed: true,
+ Description: "Name of the cluster",
+ },
+ "state": schema.StringAttribute{
+ Computed: true,
+ Description: "State of the cluster on API level",
+ },
+ "provider_type": schema.StringAttribute{
+ Computed: true,
+ Description: "Provider type of the cluster (e.g. GKE, EKS)",
+ },
+ "service_account_id": schema.StringAttribute{
+ Computed: true,
+ Description: "CAST AI service account ID associated with OMNI operations",
+ },
+ "castai_oidc_config": schema.SingleNestedAttribute{
+ Computed: true,
+ Description: "CAST AI OIDC configuration for service account impersonation",
+ Attributes: map[string]schema.Attribute{
+ "gcp_service_account_email": schema.StringAttribute{
+ Computed: true,
+ Description: "CAST AI GCP service account email for impersonation",
+ },
+ "gcp_service_account_unique_id": schema.StringAttribute{
+ Computed: true,
+ Description: "CAST AI GCP service account unique ID for impersonation",
+ },
+ },
+ },
+ },
+ }
+}
+
+func (d *omniClusterDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
+ }
+
+ client, ok := req.ProviderData.(*ProviderConfig)
+ if !ok {
+ resp.Diagnostics.AddError(
+ "Unexpected Data Source Configure Type",
+ fmt.Sprintf("Expected *ProviderConfig, got: %T", req.ProviderData),
+ )
+ return
+ }
+
+ d.client = client
+}
+
+func (d *omniClusterDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+ var data omniClusterDataSourceModel
+ resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ client := d.client.omniAPI
+ organizationID := data.OrganizationID.ValueString()
+ clusterID := data.ClusterID.ValueString()
+
+ apiResp, err := client.ClustersAPIGetClusterWithResponse(ctx, organizationID, clusterID, nil)
+ if err != nil {
+ resp.Diagnostics.AddError("Failed to read omni cluster", err.Error())
+ return
+ }
+
+ if apiResp.StatusCode() != http.StatusOK {
+ resp.Diagnostics.AddError(
+ "Failed to read omni cluster",
+ fmt.Sprintf("unexpected status code: %d, body: %s", apiResp.StatusCode(), string(apiResp.Body)),
+ )
+ return
+ }
+
+ cluster := apiResp.JSON200
+
+ data.ID = types.StringValue(clusterID)
+ data.Name = types.StringPointerValue(cluster.Name)
+ data.ServiceAccountID = types.StringPointerValue(cluster.ServiceAccountId)
+
+ if cluster.State != nil {
+ data.State = types.StringValue(string(*cluster.State))
+ }
+ if cluster.ProviderType != nil {
+ data.ProviderType = types.StringValue(string(*cluster.ProviderType))
+ }
+
+ if cluster.CastaiOidcConfig != nil {
+ data.CastaiOidcConfig = &castaiOidcConfigModel{
+ GcpServiceAccountEmail: types.StringPointerValue(cluster.CastaiOidcConfig.GcpServiceAccountEmail),
+ GcpServiceAccountUniqueID: types.StringPointerValue(cluster.CastaiOidcConfig.GcpServiceAccountUniqueId),
+ }
+ }
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
diff --git a/castai/data_source_omni_cluster_test.go b/castai/data_source_omni_cluster_test.go
new file mode 100644
index 000000000..5c1adb58d
--- /dev/null
+++ b/castai/data_source_omni_cluster_test.go
@@ -0,0 +1,50 @@
+package castai
+
+import (
+ "fmt"
+ "regexp"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+)
+
+func TestAccCloudAgnostic_DataSourceOmniCluster(t *testing.T) {
+ clusterName := "omni-tf-acc-gcp"
+ dataSourceName := "data.castai_omni_cluster.test"
+ resourceName := "castai_omni_cluster.test"
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccOmniClusterDataSourceConfig(clusterName),
+ Check: resource.ComposeTestCheckFunc(
+ // Verify data source ID matches the resource ID
+ resource.TestCheckResourceAttrPair(dataSourceName, "id", resourceName, "id"),
+ // Verify organization_id is correctly passed through
+ resource.TestCheckResourceAttr(dataSourceName, "organization_id", testAccGetOrganizationID()),
+ // Verify cluster metadata fields are populated
+ resource.TestCheckResourceAttrSet(dataSourceName, "name"),
+ resource.TestCheckResourceAttrSet(dataSourceName, "state"),
+ // Verify OIDC config fields have expected formats
+ resource.TestMatchResourceAttr(dataSourceName, "castai_oidc_config.gcp_service_account_email",
+ regexp.MustCompile(`^.+@.+\.iam\.gserviceaccount\.com$`)),
+ resource.TestMatchResourceAttr(dataSourceName, "castai_oidc_config.gcp_service_account_unique_id",
+ regexp.MustCompile(`^\d+$`)),
+ ),
+ },
+ },
+ })
+}
+
+func testAccOmniClusterDataSourceConfig(clusterName string) string {
+ organizationID := testAccGetOrganizationID()
+
+ return ConfigCompose(testOmniClusterConfig(clusterName), fmt.Sprintf(`
+data "castai_omni_cluster" "test" {
+ organization_id = %[1]q
+ cluster_id = castai_omni_cluster.test.id
+}
+`, organizationID))
+}
diff --git a/castai/provider_framework.go b/castai/provider_framework.go
index a962aed3b..a3900b9a9 100644
--- a/castai/provider_framework.go
+++ b/castai/provider_framework.go
@@ -132,5 +132,7 @@ func (p *frameworkProvider) Resources(_ context.Context) []func() resource.Resou
}
func (p *frameworkProvider) DataSources(_ context.Context) []func() datasource.DataSource {
- return []func() datasource.DataSource{}
+ return []func() datasource.DataSource{
+ newOmniClusterDataSource,
+ }
}
diff --git a/castai/resource_edge_location.go b/castai/resource_edge_location.go
index a2e2a901a..da2c18d28 100644
--- a/castai/resource_edge_location.go
+++ b/castai/resource_edge_location.go
@@ -57,9 +57,11 @@ type zoneModel struct {
type awsModel struct {
AccountID types.String `tfsdk:"account_id"`
InstanceProfile types.String `tfsdk:"instance_profile"`
+ RoleArn types.String `tfsdk:"role_arn"`
AccessKeyIDWO types.String `tfsdk:"access_key_id_wo"`
SecretAccessKeyWO types.String `tfsdk:"secret_access_key_wo"`
VpcID types.String `tfsdk:"vpc_id"`
+ VpcCidr types.String `tfsdk:"vpc_cidr"`
VpcPeered types.Bool `tfsdk:"vpc_peered"`
SecurityGroupID types.String `tfsdk:"security_group_id"`
SubnetIDs types.Map `tfsdk:"subnet_ids"`
@@ -70,9 +72,11 @@ type awsModel struct {
type gcpModel struct {
ProjectID types.String `tfsdk:"project_id"`
InstanceServiceAccount types.String `tfsdk:"instance_service_account"`
+ TargetServiceAccountEmail types.String `tfsdk:"target_service_account_email"`
ClientServiceAccountJSONBase64WO types.String `tfsdk:"client_service_account_json_base64_wo"`
NetworkName types.String `tfsdk:"network_name"`
SubnetName types.String `tfsdk:"subnet_name"`
+ SubnetCidr types.String `tfsdk:"subnet_cidr"`
NetworkTags types.Set `tfsdk:"network_tags"`
}
@@ -82,11 +86,19 @@ type ociModel struct {
UserIDWO types.String `tfsdk:"user_id_wo"`
FingerprintWO types.String `tfsdk:"fingerprint_wo"`
PrivateKeyBase64WO types.String `tfsdk:"private_key_base64_wo"`
+ ClientID types.String `tfsdk:"client_id"`
+ ClientSecretWO types.String `tfsdk:"client_secret_wo"`
+ IdentityDomainUri types.String `tfsdk:"identity_domain_uri"`
VcnID types.String `tfsdk:"vcn_id"`
+ VcnCidr types.String `tfsdk:"vcn_cidr"`
SubnetID types.String `tfsdk:"subnet_id"`
+ SecurityGroupID types.String `tfsdk:"security_group_id"`
}
func (m awsModel) credentials() types.String {
+ if m.AccessKeyIDWO.IsNull() && m.SecretAccessKeyWO.IsNull() {
+ return types.StringNull()
+ }
return types.StringValue(m.SecretAccessKeyWO.String() + m.AccessKeyIDWO.String())
}
@@ -95,7 +107,9 @@ func (m awsModel) Equal(other *awsModel) bool {
return false
}
return m.AccountID.Equal(other.AccountID) &&
+ m.RoleArn.Equal(other.RoleArn) &&
m.VpcID.Equal(other.VpcID) &&
+ m.VpcCidr.Equal(other.VpcCidr) &&
m.VpcPeered.Equal(other.VpcPeered) &&
m.SecurityGroupID.Equal(other.SecurityGroupID) &&
m.SubnetIDs.Equal(other.SubnetIDs) &&
@@ -103,6 +117,9 @@ func (m awsModel) Equal(other *awsModel) bool {
}
func (m gcpModel) credentials() types.String {
+ if m.ClientServiceAccountJSONBase64WO.IsNull() {
+ return types.StringNull()
+ }
return m.ClientServiceAccountJSONBase64WO
}
@@ -111,14 +128,24 @@ func (m gcpModel) Equal(other *gcpModel) bool {
return false
}
return m.ProjectID.Equal(other.ProjectID) &&
+ m.TargetServiceAccountEmail.Equal(other.TargetServiceAccountEmail) &&
m.NetworkName.Equal(other.NetworkName) &&
m.SubnetName.Equal(other.SubnetName) &&
+ m.SubnetCidr.Equal(other.SubnetCidr) &&
m.NetworkTags.Equal(other.NetworkTags) &&
m.InstanceServiceAccount.Equal(other.InstanceServiceAccount)
}
func (m ociModel) credentials() types.String {
- return types.StringValue(m.UserIDWO.String() + m.PrivateKeyBase64WO.String() + m.FingerprintWO.String())
+ // WIF flow: track client secret
+ if !m.ClientSecretWO.IsNull() {
+ return m.ClientSecretWO
+ }
+ // Legacy API key flow: track user+key+fingerprint
+ if !m.UserIDWO.IsNull() || !m.FingerprintWO.IsNull() || !m.PrivateKeyBase64WO.IsNull() {
+ return types.StringValue(m.UserIDWO.String() + m.PrivateKeyBase64WO.String() + m.FingerprintWO.String())
+ }
+ return types.StringNull()
}
func (m ociModel) Equal(other *ociModel) bool {
@@ -127,8 +154,12 @@ func (m ociModel) Equal(other *ociModel) bool {
}
return m.TenancyID.Equal(other.TenancyID) &&
m.CompartmentID.Equal(other.CompartmentID) &&
+ m.ClientID.Equal(other.ClientID) &&
+ m.IdentityDomainUri.Equal(other.IdentityDomainUri) &&
+ m.VcnID.Equal(other.VcnID) &&
+ m.VcnCidr.Equal(other.VcnCidr) &&
m.SubnetID.Equal(other.SubnetID) &&
- m.VcnID.Equal(other.VcnID)
+ m.SecurityGroupID.Equal(other.SecurityGroupID)
}
type ModelWithCredentials interface {
@@ -231,14 +262,18 @@ func (r *edgeLocationResource) Schema(_ context.Context, _ resource.SchemaReques
Optional: true,
Description: "AWS IAM instance profile ARN to be attached to edge instances. It can be used to grant permissions to access other AWS resources such as ECR.",
},
+ "role_arn": schema.StringAttribute{
+ Optional: true,
+ Description: "AWS IAM role ARN used for Google OIDC federation impersonation",
+ },
"access_key_id_wo": schema.StringAttribute{
- Required: true,
+ Optional: true,
WriteOnly: true,
Sensitive: true,
Description: "AWS access key ID",
},
"secret_access_key_wo": schema.StringAttribute{
- Required: true,
+ Optional: true,
Sensitive: true,
WriteOnly: true,
Description: "AWS secret access key",
@@ -247,6 +282,10 @@ func (r *edgeLocationResource) Schema(_ context.Context, _ resource.SchemaReques
Required: true,
Description: "VPC ID to be used in the selected region",
},
+ "vpc_cidr": schema.StringAttribute{
+ Optional: true,
+ Description: "VPC IPv4 CIDR block",
+ },
"vpc_peered": schema.BoolAttribute{
Optional: true,
Description: "Whether existing VPC is peered with main cluster's VPC. Field is ignored if vpc_id is not provided or main cluster is not EKS",
@@ -279,8 +318,12 @@ func (r *edgeLocationResource) Schema(_ context.Context, _ resource.SchemaReques
Optional: true,
Description: "GCP service account email to be attached to edge instances. It can be used to grant permissions to access other GCP resources.",
},
+ "target_service_account_email": schema.StringAttribute{
+ Optional: true,
+ Description: "Target service account email to be used for impersonation",
+ },
"client_service_account_json_base64_wo": schema.StringAttribute{
- Required: true,
+ Optional: true,
Sensitive: true,
WriteOnly: true,
Description: "Base64 encoded service account JSON for provisioning edge resources",
@@ -296,6 +339,10 @@ func (r *edgeLocationResource) Schema(_ context.Context, _ resource.SchemaReques
Required: true,
Description: "The name of the subnetwork to be used in the selected region",
},
+ "subnet_cidr": schema.StringAttribute{
+ Optional: true,
+ Description: "VPC Subnet IPv4 CIDR block",
+ },
"network_tags": schema.SetAttribute{
Required: true,
ElementType: types.StringType,
@@ -315,20 +362,34 @@ func (r *edgeLocationResource) Schema(_ context.Context, _ resource.SchemaReques
Required: true,
Description: "OCI compartment ID of edge location",
},
+ "client_id": schema.StringAttribute{
+ Optional: true,
+ Description: "ID of the OCI confidential application used for WIF token exchange",
+ },
+ "client_secret_wo": schema.StringAttribute{
+ Optional: true,
+ Sensitive: true,
+ WriteOnly: true,
+ Description: "Secret of the OCI confidential application used for WIF token exchange",
+ },
+ "identity_domain_uri": schema.StringAttribute{
+ Optional: true,
+ Description: "OCI Identity Domain URI (e.g., idcs-xxxx.identity.oraclecloud.com)",
+ },
"user_id_wo": schema.StringAttribute{
- Required: true,
+ Optional: true,
Description: "User ID used to authenticate OCI",
WriteOnly: true,
},
"fingerprint_wo": schema.StringAttribute{
- Required: true,
+ Optional: true,
Sensitive: true,
WriteOnly: true,
Description: "API key fingerprint",
},
"private_key_base64_wo": schema.StringAttribute{
WriteOnly: true,
- Required: true,
+ Optional: true,
Sensitive: true,
Description: "Base64 encoded API private key",
Validators: []validator.String{
@@ -339,10 +400,18 @@ func (r *edgeLocationResource) Schema(_ context.Context, _ resource.SchemaReques
Required: true,
Description: "OCI virtual cloud network ID",
},
+ "vcn_cidr": schema.StringAttribute{
+ Optional: true,
+ Description: "OCI VCN IPv4 CIDR block",
+ },
"subnet_id": schema.StringAttribute{
Required: true,
Description: "OCI subnet ID of edge location",
},
+ "security_group_id": schema.StringAttribute{
+ Optional: true,
+ Description: "OCI network security group ID",
+ },
},
},
},
@@ -426,8 +495,10 @@ func (r *edgeLocationResource) Create(ctx context.Context, req resource.CreateRe
plan.ID = types.StringValue(*apiResp.JSON200.Id)
plan.CredentialsRevision = types.Int64Value(1)
- // Store credential hash in private state
- resp.Diagnostics.Append(r.woCredentialsStore(resp.Private).Set(ctx, mc.credentials())...)
+ // Store credential hash in private state (skip when credentials are missing e.g. OIDC flow with AWS and GCP)
+ if mc != nil && !mc.credentials().IsNull() {
+ resp.Diagnostics.Append(r.woCredentialsStore(resp.Private).Set(ctx, mc.credentials())...)
+ }
resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
}
@@ -561,8 +632,8 @@ func (r *edgeLocationResource) Update(ctx context.Context, req resource.UpdateRe
return
}
- // Update stored credentials hash if credentials changed
- if credentialsChanged {
+ // Update stored credentials hash if credentials changed (skip when using impersonation flow)
+ if credentialsChanged && mc != nil && !mc.credentials().IsNull() {
resp.Diagnostics.Append(r.woCredentialsStore(resp.Private).Set(ctx, mc.credentials())...)
}
@@ -613,27 +684,29 @@ func (r *edgeLocationResource) ModifyPlan(ctx context.Context, req resource.Modi
return
}
- var (
- credentialsEqual bool
- diags diag.Diagnostics
- )
+ var mc ModelWithCredentials
switch {
case config.AWS != nil:
- credentialsEqual, diags = r.woCredentialsStore(req.Private).Equal(ctx, config.AWS.credentials())
+ mc = config.AWS
case config.GCP != nil:
- credentialsEqual, diags = r.woCredentialsStore(req.Private).Equal(ctx, config.GCP.credentials())
+ mc = config.GCP
case config.OCI != nil:
- credentialsEqual, diags = r.woCredentialsStore(req.Private).Equal(ctx, config.OCI.credentials())
- }
- resp.Diagnostics.Append(diags...)
- if resp.Diagnostics.HasError() {
- return
+ mc = config.OCI
}
- // If credentials changed, update the planned credentials_revision
- if !credentialsEqual {
- plan.CredentialsRevision = types.Int64Value(state.CredentialsRevision.ValueInt64() + 1)
- resp.Diagnostics.Append(resp.Plan.Set(ctx, plan)...)
+ // Skip credentials comparison when no credentials are provided (e.g. impersonation flow)
+ if mc != nil && !mc.credentials().IsNull() {
+ credentialsEqual, diags := r.woCredentialsStore(req.Private).Equal(ctx, mc.credentials())
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // If credentials changed, update the planned credentials_revision
+ if !credentialsEqual {
+ plan.CredentialsRevision = types.Int64Value(state.CredentialsRevision.ValueInt64() + 1)
+ resp.Diagnostics.Append(resp.Plan.Set(ctx, plan)...)
+ }
}
}
@@ -704,17 +777,21 @@ func (r *edgeLocationResource) toAWS(ctx context.Context, plan, config *awsModel
out := &omni.AWSParam{
AccountId: toPtr(plan.AccountID.ValueString()),
InstanceProfile: instanceProfile,
- Credentials: &omni.AWSParamCredentials{
- AccessKeyId: config.AccessKeyIDWO.ValueStringPointer(),
- SecretAccessKey: config.SecretAccessKeyWO.ValueStringPointer(),
- },
+ RoleArn: plan.RoleArn.ValueStringPointer(),
Networking: &omni.AWSParamAWSNetworking{
VpcId: plan.VpcID.ValueString(),
VpcPeered: plan.VpcPeered.ValueBoolPointer(),
+ VpcCidr: plan.VpcCidr.ValueStringPointer(),
SecurityGroupId: plan.SecurityGroupID.ValueString(),
SubnetIds: subnetMap,
},
}
+ if !config.AccessKeyIDWO.IsNull() || !config.SecretAccessKeyWO.IsNull() {
+ out.Credentials = &omni.AWSParamCredentials{
+ AccessKeyId: config.AccessKeyIDWO.ValueStringPointer(),
+ SecretAccessKey: config.SecretAccessKeyWO.ValueStringPointer(),
+ }
+ }
if !plan.NameTag.IsNull() {
out.Networking.NameTag = lo.ToPtr(plan.NameTag.ValueString())
}
@@ -731,7 +808,9 @@ func (r *edgeLocationResource) toAWSModel(ctx context.Context, config *omni.AWSP
aws := &awsModel{
AccountID: types.StringValue(lo.FromPtr(config.AccountId)),
InstanceProfile: types.StringNull(),
+ RoleArn: types.StringNull(),
VpcID: types.StringNull(),
+ VpcCidr: types.StringNull(),
SecurityGroupID: types.StringNull(),
VpcPeered: types.BoolNull(),
SubnetIDs: types.MapNull(types.StringType),
@@ -744,10 +823,17 @@ func (r *edgeLocationResource) toAWSModel(ctx context.Context, config *omni.AWSP
aws.InstanceProfile = types.StringValue(*config.InstanceProfile)
}
+ if config.RoleArn != nil && *config.RoleArn != "" {
+ aws.RoleArn = types.StringValue(*config.RoleArn)
+ }
+
if config.Networking != nil {
aws.VpcID = types.StringValue(config.Networking.VpcId)
aws.VpcPeered = types.BoolPointerValue(config.Networking.VpcPeered)
aws.SecurityGroupID = types.StringValue(config.Networking.SecurityGroupId)
+ if config.Networking.VpcCidr != nil && *config.Networking.VpcCidr != "" {
+ aws.VpcCidr = types.StringValue(*config.Networking.VpcCidr)
+ }
if config.Networking.NameTag != nil && *config.Networking.NameTag != "" {
aws.NameTag = types.StringValue(*config.Networking.NameTag)
}
@@ -776,17 +862,21 @@ func (r *edgeLocationResource) toGCP(ctx context.Context, plan, config *gcpModel
}
out := &omni.GCPParam{
- ProjectId: plan.ProjectID.ValueString(),
- InstanceServiceAccount: plan.InstanceServiceAccount.ValueStringPointer(),
- Credentials: &omni.GCPParamCredentials{
- ClientServiceAccountJsonBase64: config.ClientServiceAccountJSONBase64WO.ValueStringPointer(),
- },
+ ProjectId: plan.ProjectID.ValueString(),
+ InstanceServiceAccount: plan.InstanceServiceAccount.ValueStringPointer(),
+ TargetServiceAccountEmail: plan.TargetServiceAccountEmail.ValueStringPointer(),
Networking: &omni.GCPParamGCPNetworking{
NetworkName: plan.NetworkName.ValueString(),
SubnetName: plan.SubnetName.ValueString(),
+ SubnetCidr: plan.SubnetCidr.ValueStringPointer(),
Tags: tags,
},
}
+ if !config.ClientServiceAccountJSONBase64WO.IsNull() {
+ out.Credentials = &omni.GCPParamCredentials{
+ ClientServiceAccountJsonBase64: config.ClientServiceAccountJSONBase64WO.ValueStringPointer(),
+ }
+ }
return out, diags
}
@@ -800,9 +890,11 @@ func (r *edgeLocationResource) toGCPModel(ctx context.Context, config *omni.GCPP
gcp := &gcpModel{
ProjectID: types.StringValue(config.ProjectId),
InstanceServiceAccount: types.StringNull(),
+ TargetServiceAccountEmail: types.StringNull(),
ClientServiceAccountJSONBase64WO: types.StringNull(),
NetworkName: types.StringNull(),
SubnetName: types.StringNull(),
+ SubnetCidr: types.StringNull(),
NetworkTags: types.SetNull(types.StringType),
}
@@ -810,9 +902,16 @@ func (r *edgeLocationResource) toGCPModel(ctx context.Context, config *omni.GCPP
gcp.InstanceServiceAccount = types.StringValue(*config.InstanceServiceAccount)
}
+ if config.TargetServiceAccountEmail != nil && *config.TargetServiceAccountEmail != "" {
+ gcp.TargetServiceAccountEmail = types.StringValue(*config.TargetServiceAccountEmail)
+ }
+
if config.Networking != nil {
gcp.NetworkName = types.StringValue(config.Networking.NetworkName)
gcp.SubnetName = types.StringValue(config.Networking.SubnetName)
+ if config.Networking.SubnetCidr != nil && *config.Networking.SubnetCidr != "" {
+ gcp.SubnetCidr = types.StringValue(*config.Networking.SubnetCidr)
+ }
if config.Networking.Tags != nil {
tagsSet, d := types.SetValueFrom(ctx, types.StringType, config.Networking.Tags)
diags.Append(d...)
@@ -831,15 +930,30 @@ func (r *edgeLocationResource) toOCI(plan, config *ociModel) *omni.OCIParam {
out := &omni.OCIParam{
TenancyId: toPtr(plan.TenancyID.ValueString()),
CompartmentId: toPtr(plan.CompartmentID.ValueString()),
- Credentials: &omni.OCIParamCredentials{
+ Networking: &omni.OCIParamNetworking{
+ VcnId: plan.VcnID.ValueString(),
+ SubnetId: plan.SubnetID.ValueString(),
+ VcnCidr: plan.VcnCidr.ValueStringPointer(),
+ SecurityGroupId: plan.SecurityGroupID.ValueStringPointer(),
+ },
+ }
+
+ // WIF flow: use confidential application client
+ if !plan.ClientID.IsNull() {
+ out.Client = &omni.OCIParamClient{
+ Id: plan.ClientID.ValueString(),
+ Secret: config.ClientSecretWO.ValueStringPointer(),
+ IdentityDomainUri: plan.IdentityDomainUri.ValueString(),
+ }
+ }
+
+ // Legacy API key flow
+ if !config.UserIDWO.IsNull() || !config.FingerprintWO.IsNull() || !config.PrivateKeyBase64WO.IsNull() {
+ out.Credentials = &omni.OCIParamCredentials{
UserId: config.UserIDWO.ValueString(),
Fingerprint: config.FingerprintWO.ValueString(),
PrivateKeyBase64: config.PrivateKeyBase64WO.ValueString(),
- },
- Networking: &omni.OCIParamNetworking{
- VcnId: plan.VcnID.ValueString(),
- SubnetId: plan.SubnetID.ValueString(),
- },
+ }
}
return out
@@ -853,15 +967,32 @@ func (r *edgeLocationResource) toOCIModel(config *omni.OCIParam) *ociModel {
oci := &ociModel{
TenancyID: types.StringValue(lo.FromPtr(config.TenancyId)),
CompartmentID: types.StringValue(lo.FromPtr(config.CompartmentId)),
+ ClientID: types.StringNull(),
+ IdentityDomainUri: types.StringNull(),
+ ClientSecretWO: types.StringNull(),
VcnID: types.StringNull(),
+ VcnCidr: types.StringNull(),
SubnetID: types.StringNull(),
+ SecurityGroupID: types.StringNull(),
UserIDWO: types.StringNull(),
FingerprintWO: types.StringNull(),
PrivateKeyBase64WO: types.StringNull(),
}
+
+ if config.Client != nil {
+ oci.ClientID = types.StringValue(config.Client.Id)
+ oci.IdentityDomainUri = types.StringValue(config.Client.IdentityDomainUri)
+ }
+
if config.Networking != nil {
- oci.SubnetID = types.StringValue(config.Networking.SubnetId)
oci.VcnID = types.StringValue(config.Networking.VcnId)
+ oci.SubnetID = types.StringValue(config.Networking.SubnetId)
+ if config.Networking.VcnCidr != nil && *config.Networking.VcnCidr != "" {
+ oci.VcnCidr = types.StringValue(*config.Networking.VcnCidr)
+ }
+ if config.Networking.SecurityGroupId != nil && *config.Networking.SecurityGroupId != "" {
+ oci.SecurityGroupID = types.StringValue(*config.Networking.SecurityGroupId)
+ }
}
return oci
diff --git a/castai/resource_edge_location_test.go b/castai/resource_edge_location_test.go
index 50c277532..6e2dc8e93 100644
--- a/castai/resource_edge_location_test.go
+++ b/castai/resource_edge_location_test.go
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/http"
+ "strings"
"testing"
"time"
@@ -191,20 +192,7 @@ func testAccEdgeLocationAWSCredentialsUpdated(rName, clusterName string) string
func testAccEdgeLocationAWSConfigWithParams(rName, clusterName, description string, zones []string, awsCredentials string) string {
organizationID := testAccGetOrganizationID()
- zonesConfig := "zones = ["
- subnetConfig := ""
- for i, zone := range zones {
- if i > 0 {
- zonesConfig += ", "
- }
- zonesConfig += fmt.Sprintf(`{
- id = %q
- name = %q
- }`, zone, zone)
- subnetConfig += fmt.Sprintf(`
- %q = "subnet-%08d"`, zone, 12345678+i)
- }
- zonesConfig += "]"
+ zonesConfig, subnetConfig := formatAWSZonesAndSubnets(zones)
return ConfigCompose(testOmniClusterConfig(clusterName), fmt.Sprintf(`
resource "castai_edge_location" "test" {
@@ -276,25 +264,8 @@ func testAccEdgeLocationGCPCredentialsUpdated(rName, clusterName string) string
func testAccEdgeLocationGCPConfigWithParams(rName, clusterName, description string, zones []string, networkTags []string, gcpCredentials string) string {
organizationID := testAccGetOrganizationID()
- zonesConfig := "zones = ["
- for i, zone := range zones {
- if i > 0 {
- zonesConfig += ", "
- }
- zonesConfig += fmt.Sprintf(`{
- id = %q
- name = %q
- }`, zone, zone)
- }
- zonesConfig += "]"
-
- networkTagsConfig := ""
- for i, tag := range networkTags {
- if i > 0 {
- networkTagsConfig += ", "
- }
- networkTagsConfig += fmt.Sprintf("%q", tag)
- }
+ zonesConfig := formatGCPZones(zones)
+ networkTagsConfig := formatGCPNetworkTags(networkTags)
return ConfigCompose(testOmniClusterConfig(clusterName), fmt.Sprintf(`
resource "castai_edge_location" "test" {
@@ -365,6 +336,337 @@ resource "castai_edge_location" "test" {
`, rName, description, organizationID, ociCredentials))
}
+// New credential-less flow tests
+// When the legacy credential fields are removed, the tests above can be deleted
+
+func TestAccCloudAgnostic_ResourceEdgeLocationAWSImpersonation(t *testing.T) {
+ rName := fmt.Sprintf("%v-edge-loc-%v", ResourcePrefix, acctest.RandString(8))
+ resourceName := "castai_edge_location.test"
+ clusterName := "omni-tf-acc-aws"
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ CheckDestroy: testAccCheckEdgeLocationDestroy,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccEdgeLocationAWSImpersonationConfig(rName, clusterName),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(resourceName, "name", rName),
+ resource.TestCheckResourceAttr(resourceName, "description", "Test edge location impersonation"),
+ resource.TestCheckResourceAttr(resourceName, "region", "us-east-1"),
+ resource.TestCheckResourceAttr(resourceName, "zones.#", "2"),
+ resource.TestCheckResourceAttr(resourceName, "zones.0.id", "us-east-1a"),
+ resource.TestCheckResourceAttr(resourceName, "zones.0.name", "us-east-1a"),
+ resource.TestCheckResourceAttr(resourceName, "zones.1.id", "us-east-1b"),
+ resource.TestCheckResourceAttr(resourceName, "zones.1.name", "us-east-1b"),
+ resource.TestCheckResourceAttrSet(resourceName, "aws.account_id"),
+ resource.TestCheckResourceAttr(resourceName, "aws.role_arn", "arn:aws:iam::123456789012:role/castai-omni-edge"),
+ resource.TestCheckResourceAttr(resourceName, "aws.vpc_cidr", "10.0.0.0/16"),
+ resource.TestCheckResourceAttrSet(resourceName, "aws.vpc_peered"),
+ resource.TestCheckResourceAttrSet(resourceName, "aws.vpc_id"),
+ resource.TestCheckResourceAttrSet(resourceName, "aws.security_group_id"),
+ resource.TestCheckResourceAttrSet(resourceName, "id"),
+ resource.TestCheckResourceAttr(resourceName, "credentials_revision", "1"),
+ ),
+ },
+ {
+ ResourceName: resourceName,
+ ImportStateIdFunc: func(s *terraform.State) (string, error) {
+ organizationID := testAccGetOrganizationID()
+ clusterID := s.RootModule().Resources["castai_omni_cluster.test"].Primary.ID
+ edgeLocationID := s.RootModule().Resources[resourceName].Primary.ID
+ return fmt.Sprintf("%v/%v/%v", organizationID, clusterID, edgeLocationID), nil
+ },
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ {
+ Config: testAccEdgeLocationAWSImpersonationUpdated(rName, clusterName),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(resourceName, "description", "Updated edge location impersonation"),
+ resource.TestCheckResourceAttr(resourceName, "zones.#", "3"),
+ resource.TestCheckResourceAttr(resourceName, "zones.0.id", "us-east-1a"),
+ resource.TestCheckResourceAttr(resourceName, "zones.0.name", "us-east-1a"),
+ resource.TestCheckResourceAttr(resourceName, "zones.1.id", "us-east-1b"),
+ resource.TestCheckResourceAttr(resourceName, "zones.1.name", "us-east-1b"),
+ resource.TestCheckResourceAttr(resourceName, "zones.2.id", "us-east-1c"),
+ resource.TestCheckResourceAttr(resourceName, "zones.2.name", "us-east-1c"),
+ resource.TestCheckResourceAttr(resourceName, "aws.role_arn", "arn:aws:iam::123456789012:role/castai-omni-edge-updated"),
+ ),
+ },
+ },
+ })
+}
+
+func TestAccCloudAgnostic_ResourceEdgeLocationGCPImpersonation(t *testing.T) {
+ rName := fmt.Sprintf("%v-edge-loc-%v", ResourcePrefix, acctest.RandString(8))
+ resourceName := "castai_edge_location.test"
+ clusterName := "omni-tf-acc-gcp"
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ CheckDestroy: testAccCheckEdgeLocationDestroy,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccEdgeLocationGCPImpersonationConfig(rName, clusterName),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(resourceName, "name", rName),
+ resource.TestCheckResourceAttr(resourceName, "description", "Test GCP edge location impersonation"),
+ resource.TestCheckResourceAttr(resourceName, "region", "us-central1"),
+ resource.TestCheckResourceAttr(resourceName, "zones.#", "2"),
+ resource.TestCheckResourceAttr(resourceName, "zones.0.id", "us-central1-a"),
+ resource.TestCheckResourceAttr(resourceName, "zones.0.name", "us-central1-a"),
+ resource.TestCheckResourceAttr(resourceName, "zones.1.id", "us-central1-b"),
+ resource.TestCheckResourceAttr(resourceName, "zones.1.name", "us-central1-b"),
+ resource.TestCheckResourceAttrSet(resourceName, "gcp.project_id"),
+ resource.TestCheckResourceAttr(resourceName, "gcp.target_service_account_email", "castai-omni@test-project-123456.iam.gserviceaccount.com"),
+ resource.TestCheckResourceAttr(resourceName, "gcp.subnet_cidr", "10.0.0.0/20"),
+ resource.TestCheckResourceAttrSet(resourceName, "id"),
+ resource.TestCheckResourceAttr(resourceName, "credentials_revision", "1"),
+ ),
+ },
+ {
+ Config: testAccEdgeLocationGCPImpersonationUpdated(rName, clusterName),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(resourceName, "description", "Updated GCP edge location impersonation"),
+ resource.TestCheckResourceAttr(resourceName, "gcp.target_service_account_email", "castai-omni-updated@test-project-123456.iam.gserviceaccount.com"),
+ resource.TestCheckResourceAttr(resourceName, "zones.#", "2"),
+ resource.TestCheckResourceAttr(resourceName, "zones.0.id", "us-central1-a"),
+ resource.TestCheckResourceAttr(resourceName, "zones.0.name", "us-central1-a"),
+ resource.TestCheckResourceAttr(resourceName, "zones.1.id", "us-central1-b"),
+ resource.TestCheckResourceAttr(resourceName, "zones.1.name", "us-central1-b"),
+ ),
+ },
+ },
+ })
+}
+
+func TestAccCloudAgnostic_ResourceEdgeLocationOCIWIF(t *testing.T) {
+ rName := fmt.Sprintf("%v-edge-loc-%v", ResourcePrefix, acctest.RandString(8))
+ resourceName := "castai_edge_location.test"
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ CheckDestroy: testAccCheckEdgeLocationDestroy,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccEdgeLocationOCIWIFConfig(rName),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(resourceName, "name", rName),
+ resource.TestCheckResourceAttr(resourceName, "description", "Test OCI edge location WIF"),
+ resource.TestCheckResourceAttr(resourceName, "region", "us-phoenix-1"),
+ resource.TestCheckResourceAttr(resourceName, "zones.#", "1"),
+ resource.TestCheckResourceAttr(resourceName, "zones.0.id", "1"),
+ resource.TestCheckResourceAttr(resourceName, "zones.0.name", "PHX-AD-1"),
+ resource.TestCheckResourceAttrSet(resourceName, "oci.tenancy_id"),
+ resource.TestCheckResourceAttrSet(resourceName, "oci.compartment_id"),
+ resource.TestCheckResourceAttr(resourceName, "oci.client_id", "test-app-client-id"),
+ resource.TestCheckResourceAttr(resourceName, "oci.identity_domain_uri", "idcs-example.identity.oraclecloud.com"),
+ resource.TestCheckResourceAttr(resourceName, "oci.vcn_cidr", "10.0.0.0/16"),
+ resource.TestCheckResourceAttrSet(resourceName, "oci.security_group_id"),
+ resource.TestCheckResourceAttrSet(resourceName, "id"),
+ resource.TestCheckResourceAttr(resourceName, "credentials_revision", "1"),
+ ),
+ },
+ {
+ Config: testAccEdgeLocationOCIWIFUpdated(rName),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(resourceName, "description", "Updated OCI edge location WIF"),
+ ),
+ },
+ // Rotate the client secret: credentials_revision must increment
+ {
+ Config: testAccEdgeLocationOCIWIFCredentialsUpdated(rName),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(resourceName, "credentials_revision", "2"),
+ ),
+ },
+ },
+ })
+}
+
+func formatAWSZonesAndSubnets(zones []string) (zonesConfig string, subnetConfig string) {
+ var zonesBuilder strings.Builder
+ var subnetBuilder strings.Builder
+
+ zonesBuilder.WriteString("zones = [")
+ for i, zone := range zones {
+ if i > 0 {
+ zonesBuilder.WriteString(", ")
+ }
+ fmt.Fprintf(&zonesBuilder, `{
+ id = %q
+ name = %q
+ }`, zone, zone)
+ fmt.Fprintf(&subnetBuilder, `
+ %q = "subnet-%08d"`, zone, 12345678+i)
+ }
+ zonesBuilder.WriteString("]")
+
+ zonesConfig = zonesBuilder.String()
+ subnetConfig = subnetBuilder.String()
+
+ return
+}
+
+func testAccEdgeLocationAWSImpersonationConfig(rName, clusterName string) string {
+ return testAccEdgeLocationAWSImpersonationConfigWithParams(rName, clusterName, "Test edge location impersonation",
+ []string{"us-east-1a", "us-east-1b"}, "arn:aws:iam::123456789012:role/castai-omni-edge")
+}
+
+func testAccEdgeLocationAWSImpersonationUpdated(rName, clusterName string) string {
+ return testAccEdgeLocationAWSImpersonationConfigWithParams(rName, clusterName, "Updated edge location impersonation",
+ []string{"us-east-1a", "us-east-1b", "us-east-1c"}, "arn:aws:iam::123456789012:role/castai-omni-edge-updated")
+}
+
+func testAccEdgeLocationAWSImpersonationConfigWithParams(rName, clusterName, description string, zones []string, roleArn string) string {
+ organizationID := testAccGetOrganizationID()
+
+ zonesConfig, subnetConfig := formatAWSZonesAndSubnets(zones)
+
+ return ConfigCompose(testOmniClusterConfig(clusterName), fmt.Sprintf(`
+resource "castai_edge_location" "test" {
+ organization_id = %[5]q
+ cluster_id = castai_omni_cluster.test.id
+ name = %[1]q
+ description = %[2]q
+ region = "us-east-1"
+%[3]s
+
+ aws = {
+ account_id = "123456789012"
+ role_arn = "%[6]s"
+ vpc_id = "vpc-12345678"
+ vpc_peered = true
+ instance_service_account = "arn:aws:iam::123456789012:role/castai-omni-edge"
+ vpc_cidr = "10.0.0.0/16"
+ security_group_id = "sg-12345678"
+ subnet_ids = {%[4]s
+ }
+ }
+}
+`, rName, description, zonesConfig, subnetConfig, organizationID, roleArn))
+}
+
+func testAccEdgeLocationGCPImpersonationConfig(rName, clusterName string) string {
+ return testAccEdgeLocationGCPImpersonationConfigWithParams(rName, clusterName,
+ "Test GCP edge location impersonation",
+ "castai-omni@test-project-123456.iam.gserviceaccount.com",
+ []string{"us-central1-a", "us-central1-b"},
+ []string{"edge-location", "castai"})
+}
+
+func testAccEdgeLocationGCPImpersonationUpdated(rName, clusterName string) string {
+ return testAccEdgeLocationGCPImpersonationConfigWithParams(rName, clusterName,
+ "Updated GCP edge location impersonation",
+ "castai-omni-updated@test-project-123456.iam.gserviceaccount.com",
+ []string{"us-central1-a", "us-central1-b"},
+ []string{"edge-location", "castai"})
+}
+
+func formatGCPZones(zones []string) string {
+ var builder strings.Builder
+
+ builder.WriteString("zones = [")
+ for i, zone := range zones {
+ if i > 0 {
+ builder.WriteString(", ")
+ }
+ fmt.Fprintf(&builder, `{
+ id = %q
+ name = %q
+ }`, zone, zone)
+ }
+ builder.WriteString("]")
+ return builder.String()
+}
+
+func formatGCPNetworkTags(networkTags []string) string {
+ var builder strings.Builder
+
+ for i, tag := range networkTags {
+ if i > 0 {
+ builder.WriteString(", ")
+ }
+ fmt.Fprintf(&builder, "%q", tag)
+ }
+ return builder.String()
+}
+
+func testAccEdgeLocationGCPImpersonationConfigWithParams(rName, clusterName, description, targetSA string, zones []string, networkTags []string) string {
+ organizationID := testAccGetOrganizationID()
+
+ zonesConfig := formatGCPZones(zones)
+ networkTagsConfig := formatGCPNetworkTags(networkTags)
+
+ return ConfigCompose(testOmniClusterConfig(clusterName), fmt.Sprintf(`
+resource "castai_edge_location" "test" {
+ organization_id = %[5]q
+ cluster_id = castai_omni_cluster.test.id
+ name = %[1]q
+ description = %[2]q
+ region = "us-central1"
+%[3]s
+
+ gcp = {
+ project_id = "test-project-123456"
+ instance_service_account = "custom-sa@test-project-123456.iam.gserviceaccount.com"
+ target_service_account_email = "%[6]s"
+ network_name = "test-network"
+ subnet_name = "test-subnet"
+ subnet_cidr = "10.0.0.0/20"
+ network_tags = [%[4]s]
+ }
+}
+`, rName, description, zonesConfig, networkTagsConfig, organizationID, targetSA))
+}
+
+func testAccEdgeLocationOCIWIFConfig(rName string) string {
+ return testAccEdgeLocationOCIWIFConfigWithParams(rName, "Test OCI edge location WIF", "test-app-client-secret")
+}
+
+func testAccEdgeLocationOCIWIFUpdated(rName string) string {
+ return testAccEdgeLocationOCIWIFConfigWithParams(rName, "Updated OCI edge location WIF", "test-app-client-secret")
+}
+
+func testAccEdgeLocationOCIWIFCredentialsUpdated(rName string) string {
+ return testAccEdgeLocationOCIWIFConfigWithParams(rName, "Updated OCI edge location WIF", "test-app-client-secret-changed")
+}
+
+func testAccEdgeLocationOCIWIFConfigWithParams(rName, description, clientSecret string) string {
+ organizationID := testAccGetOrganizationID()
+ clusterName := "test-oci-cluster"
+
+ return ConfigCompose(testOmniClusterConfig(clusterName), fmt.Sprintf(`
+resource "castai_edge_location" "test" {
+ organization_id = %[3]q
+ cluster_id = castai_omni_cluster.test.id
+ name = %[1]q
+ description = %[2]q
+ region = "us-phoenix-1"
+ zones = [{
+ id = "1"
+ name = "PHX-AD-1"
+ }]
+
+ oci = {
+ tenancy_id = "ocid1.tenancy.oc1..example"
+ compartment_id = "ocid1.compartment.oc1..example"
+ client_id = "test-app-client-id"
+ client_secret_wo = %[4]q
+ identity_domain_uri = "idcs-example.identity.oraclecloud.com"
+ vcn_id = "ocid1.vcn.oc1.phx.example"
+ vcn_cidr = "10.0.0.0/16"
+ subnet_id = "ocid1.subnet.oc1.phx.example"
+ security_group_id = "ocid1.networksecuritygroup.oc1.phx.example"
+ }
+}
+`, rName, description, organizationID, clientSecret))
+}
+
func testAccCheckEdgeLocationDestroy(s *terraform.State) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
diff --git a/docs/data-sources/omni_cluster.md b/docs/data-sources/omni_cluster.md
new file mode 100644
index 000000000..3905737e2
--- /dev/null
+++ b/docs/data-sources/omni_cluster.md
@@ -0,0 +1,40 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "castai_omni_cluster Data Source - terraform-provider-castai"
+subcategory: ""
+description: |-
+ Retrieve information about a CAST AI Omni cluster
+---
+
+# castai_omni_cluster (Data Source)
+
+Retrieve information about a CAST AI Omni cluster
+
+
+
+
+## Schema
+
+### Required
+
+- `cluster_id` (String) CAST AI cluster ID
+- `organization_id` (String) CAST AI organization ID
+
+### Read-Only
+
+- `castai_oidc_config` (Attributes) CAST AI OIDC configuration for service account impersonation (see [below for nested schema](#nestedatt--castai_oidc_config))
+- `id` (String) Cluster ID (same as cluster_id)
+- `name` (String) Name of the cluster
+- `provider_type` (String) Provider type of the cluster (e.g. GKE, EKS)
+- `service_account_id` (String) CAST AI service account ID associated with OMNI operations
+- `state` (String) State of the cluster on API level
+
+
+### Nested Schema for `castai_oidc_config`
+
+Read-Only:
+
+- `gcp_service_account_email` (String) CAST AI GCP service account email for impersonation
+- `gcp_service_account_unique_id` (String) CAST AI GCP service account unique ID for impersonation
+
+
diff --git a/docs/resources/edge_location.md b/docs/resources/edge_location.md
index 4c2467c72..94f0b7f7b 100644
--- a/docs/resources/edge_location.md
+++ b/docs/resources/edge_location.md
@@ -75,17 +75,19 @@ resource "castai_edge_location" "aws_example" {
Required:
-- `access_key_id_wo` (String, Sensitive) AWS access key ID
- `account_id` (String) AWS account ID
-- `secret_access_key_wo` (String, Sensitive) AWS secret access key
- `security_group_id` (String) Security group ID to be used in the selected region
- `subnet_ids` (Map of String) Map of zone names to subnet IDs to be used in the selected region
- `vpc_id` (String) VPC ID to be used in the selected region
Optional:
+- `access_key_id_wo` (String, Sensitive) AWS access key ID
- `instance_profile` (String) AWS IAM instance profile ARN to be attached to edge instances. It can be used to grant permissions to access other AWS resources such as ECR.
- `name_tag` (String, Deprecated) The value of a 'Name' tag applied to VPC resources
+- `role_arn` (String) AWS IAM role ARN used for Google OIDC federation impersonation
+- `secret_access_key_wo` (String, Sensitive) AWS secret access key
+- `vpc_cidr` (String) VPC IPv4 CIDR block
- `vpc_peered` (Boolean) Whether existing VPC is peered with main cluster's VPC. Field is ignored if vpc_id is not provided or main cluster is not EKS
@@ -94,7 +96,6 @@ Optional:
Required:
-- `client_service_account_json_base64_wo` (String, Sensitive) Base64 encoded service account JSON for provisioning edge resources
- `network_name` (String) The name of the network to be used in the selected region
- `network_tags` (Set of String) Tags applied on the provisioned cloud resources and the firewall rule
- `project_id` (String) GCP project ID where edges run
@@ -102,7 +103,10 @@ Required:
Optional:
+- `client_service_account_json_base64_wo` (String, Sensitive) Base64 encoded service account JSON for provisioning edge resources
- `instance_service_account` (String) GCP service account email to be attached to edge instances. It can be used to grant permissions to access other GCP resources.
+- `subnet_cidr` (String) VPC Subnet IPv4 CIDR block
+- `target_service_account_email` (String) Target service account email to be used for impersonation
@@ -111,13 +115,21 @@ Optional:
Required:
- `compartment_id` (String) OCI compartment ID of edge location
-- `fingerprint_wo` (String, Sensitive) API key fingerprint
-- `private_key_base64_wo` (String, Sensitive) Base64 encoded API private key
- `subnet_id` (String) OCI subnet ID of edge location
- `tenancy_id` (String) OCI tenancy ID of the account
-- `user_id_wo` (String) User ID used to authenticate OCI
- `vcn_id` (String) OCI virtual cloud network ID
+Optional:
+
+- `client_id` (String) ID of the OCI confidential application used for WIF token exchange
+- `client_secret_wo` (String, Sensitive) Secret of the OCI confidential application used for WIF token exchange
+- `fingerprint_wo` (String, Sensitive) API key fingerprint
+- `identity_domain_uri` (String) OCI Identity Domain URI (e.g., idcs-xxxx.identity.oraclecloud.com)
+- `private_key_base64_wo` (String, Sensitive) Base64 encoded API private key
+- `security_group_id` (String) OCI network security group ID
+- `user_id_wo` (String) User ID used to authenticate OCI
+- `vcn_cidr` (String) OCI VCN IPv4 CIDR block
+
### Nested Schema for `zones`