From dc717b7399d75c48e6423d88a5e46368f67938c3 Mon Sep 17 00:00:00 2001 From: Tim Hogarty Date: Thu, 18 Jul 2024 00:12:01 -0700 Subject: [PATCH 01/12] Add schema, data models, parse method, buildRequest, getResource, and Create method for Fabric EPT --- internal/config/config.go | 10 + .../fabric/precision_time/datasources.go | 1 + .../precision_time/datasources_schema.go | 1 + .../fabric/precision_time/datasources_test.go | 1 + .../resources/fabric/precision_time/models.go | 288 ++++++++++++++++++ .../fabric/precision_time/resource.go | 272 +++++++++++++++++ .../fabric/precision_time/resource_schema.go | 202 ++++++++++++ .../fabric/precision_time/resource_test.go | 1 + 8 files changed, 776 insertions(+) create mode 100644 internal/resources/fabric/precision_time/datasources.go create mode 100644 internal/resources/fabric/precision_time/datasources_schema.go create mode 100644 internal/resources/fabric/precision_time/datasources_test.go create mode 100644 internal/resources/fabric/precision_time/models.go create mode 100644 internal/resources/fabric/precision_time/resource.go create mode 100644 internal/resources/fabric/precision_time/resource_schema.go create mode 100644 internal/resources/fabric/precision_time/resource_test.go diff --git a/internal/config/config.go b/internal/config/config.go index 08513984f..85a5c45bf 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -122,6 +122,7 @@ func (c *Config) Load(ctx context.Context) error { // NewFabricClientForSDK returns a terraform sdkv2 plugin compatible // equinix-sdk-go/fabricv4 client to be used to access Fabric's V4 APIs +// Deprecated: migrate to NewFabricClientForFramework instead func (c *Config) NewFabricClientForSDK(d *schema.ResourceData) *fabricv4.APIClient { client := c.newFabricClient() @@ -141,6 +142,15 @@ func (c *Config) NewFabricClientForTesting() *fabricv4.APIClient { return client } +func (c *Config) NewFabricClientForFramework(ctx context.Context, meta tfsdk.Config) *fabricv4.APIClient { + client := c.newFabricClient() + + baseUserAgent := c.tfFrameworkUserAgent(client.GetConfig().UserAgent) + client.GetConfig().UserAgent = generateFwModuleUserAgentString(ctx, meta, baseUserAgent) + + return client +} + // newFabricClient returns the base fabricv4 client that is then used for either the sdkv2 or framework // implementations of the Terraform Provider with exported Methods func (c *Config) newFabricClient() *fabricv4.APIClient { diff --git a/internal/resources/fabric/precision_time/datasources.go b/internal/resources/fabric/precision_time/datasources.go new file mode 100644 index 000000000..db8c82ab3 --- /dev/null +++ b/internal/resources/fabric/precision_time/datasources.go @@ -0,0 +1 @@ +package precision_time diff --git a/internal/resources/fabric/precision_time/datasources_schema.go b/internal/resources/fabric/precision_time/datasources_schema.go new file mode 100644 index 000000000..db8c82ab3 --- /dev/null +++ b/internal/resources/fabric/precision_time/datasources_schema.go @@ -0,0 +1 @@ +package precision_time diff --git a/internal/resources/fabric/precision_time/datasources_test.go b/internal/resources/fabric/precision_time/datasources_test.go new file mode 100644 index 000000000..db8c82ab3 --- /dev/null +++ b/internal/resources/fabric/precision_time/datasources_test.go @@ -0,0 +1 @@ +package precision_time diff --git a/internal/resources/fabric/precision_time/models.go b/internal/resources/fabric/precision_time/models.go new file mode 100644 index 000000000..4227d55a3 --- /dev/null +++ b/internal/resources/fabric/precision_time/models.go @@ -0,0 +1,288 @@ +package precision_time + +import ( + "context" + "github.com/equinix/equinix-sdk-go/services/fabricv4" + fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +type ResourceModel struct { + ID types.String `tfsdk:"id"` + Type types.String `tfsdk:"type"` + Href types.String `tfsdk:"href"` + Uuid types.String `tfsdk:"uuid"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + State types.String `tfsdk:"state"` + Package fwtypes.ObjectValueOf[PackageModel] `tfsdk:"package"` + Connections fwtypes.ListNestedObjectValueOf[ConnectionModel] `tfsdk:"connections"` + Ipv4 fwtypes.ObjectValueOf[Ipv4Model] `tfsdk:"ipv4"` + Account fwtypes.ObjectValueOf[AccountModel] `tfsdk:"account"` + AdvanceConfiguration fwtypes.ObjectValueOf[AdvanceConfigurationModel] `tfsdk:"advanced_configuration"` + Project fwtypes.ObjectValueOf[ProjectModel] `tfsdk:"project"` +} + +type DataSourceModel struct { + ID types.String `tfsdk:"id"` + Type types.String `tfsdk:"type"` + Href types.String `tfsdk:"href"` + Uuid types.String `tfsdk:"uuid"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + State types.String `tfsdk:"state"` + Package fwtypes.ObjectValueOf[PackageModel] `tfsdk:"package"` + Connections fwtypes.ListNestedObjectValueOf[ConnectionModel] `tfsdk:"connections"` + Ipv4 fwtypes.ObjectValueOf[Ipv4Model] `tfsdk:"ipv4"` + Account fwtypes.ObjectValueOf[AccountModel] `tfsdk:"account"` + AdvanceConfiguration fwtypes.ObjectValueOf[AdvanceConfigurationModel] `tfsdk:"advanced_configuration"` + Project fwtypes.ObjectValueOf[ProjectModel] `tfsdk:"project"` +} + +type PackageModel struct { + Code types.String `tfsdk:"code"` + Href types.String `tfsdk:"href"` +} + +type ConnectionModel struct { + Uuid types.String `tfsdk:"uuid"` + Href types.String `tfsdk:"href"` + Type types.String `tfsdk:"type"` +} + +type Ipv4Model struct { + Primary types.String `tfsdk:"primary"` + Secondary types.String `tfsdk:"secondary"` + NetworkMask types.String `tfsdk:"network_mask"` + DefaultGateway types.String `tfsdk:"default_gateway"` +} + +type AccountModel struct { + AccountNumber types.Int64 `tfsdk:"account_number"` + IsResellerAccount types.Bool `tfsdk:"is_reseller_account"` + OrgId types.String `tfsdk:"org_id"` + GlobalOrgId types.String `tfsdk:"global_org_id"` +} + +type AdvanceConfigurationModel struct { + Ntp fwtypes.ListNestedObjectValueOf[MD5Model] `tfsdk:"ntp"` + Ptp fwtypes.ObjectValueOf[PTPModel] `tfsdk:"ptp"` +} + +type MD5Model struct { + Type types.String `tfsdk:"type"` + Id types.String `tfsdk:"id"` + Password types.String `tfsdk:"password"` +} + +type PTPModel struct { + TimeScale types.String `tfsdk:"time_scale"` + Domain types.Int64 `tfsdk:"domain"` + Priority1 types.Int64 `tfsdk:"priority_1"` + Priority2 types.Int64 `tfsdk:"priority_2"` + LogAnnounceInterval types.Int64 `tfsdk:"log_announce_interval"` + LogSyncInterval types.Int64 `tfsdk:"log_sync_interval"` + LogDelayReqInterval types.Int64 `tfsdk:"log_delay_req_interval"` + TransportMode types.String `tfsdk:"transport_mode"` + GrantTime types.Int64 `tfsdk:"grant_time"` +} + +type ProjectModel struct { + ProjectId types.String `tfsdk:"project_id"` +} + +func (m *ResourceModel) parse(ctx context.Context, ept *fabricv4.PrecisionTimeServiceCreateResponse) diag.Diagnostics { + var diags diag.Diagnostics + + diags = parsePrecisionTime(ctx, ept, + &m.ID, &m.Type, &m.Href, &m.Uuid, &m.Name, &m.Description, + &m.State, &m.Package, &m.Connections, &m.Ipv4, &m.Account, + &m.AdvanceConfiguration, &m.Project, + ) + + return diags +} + +func (m *DataSourceModel) parse(ctx context.Context, ept *fabricv4.PrecisionTimeServiceCreateResponse) diag.Diagnostics { + var diags diag.Diagnostics + + diags = parsePrecisionTime(ctx, ept, + &m.ID, &m.Type, &m.Href, &m.Uuid, &m.Name, &m.Description, + &m.State, &m.Package, &m.Connections, &m.Ipv4, &m.Account, + &m.AdvanceConfiguration, &m.Project, + ) + + return diags +} + +func parsePrecisionTime( + ctx context.Context, + ept *fabricv4.PrecisionTimeServiceCreateResponse, + id, type_, href, uuid, name, description, state *basetypes.StringValue, + package_ *fwtypes.ObjectValueOf[PackageModel], + connections *fwtypes.ListNestedObjectValueOf[ConnectionModel], + ipv4 *fwtypes.ObjectValueOf[Ipv4Model], + account *fwtypes.ObjectValueOf[AccountModel], + advancedConfiguration *fwtypes.ObjectValueOf[AdvanceConfigurationModel], + project *fwtypes.ObjectValueOf[ProjectModel], +) diag.Diagnostics { + var diags diag.Diagnostics + + *id = types.StringValue(ept.GetUuid()) + *type_ = types.StringValue(string(ept.GetType())) + *href = types.StringValue(ept.GetHref()) + *uuid = types.StringValue(ept.GetUuid()) + *name = types.StringValue(ept.GetName()) + *description = types.StringValue(ept.GetDescription()) + *state = types.StringValue(string(ept.GetState())) + + eptPackage := ept.GetPackage() + parsedEptPackage, diags := parsePackage(ctx, &eptPackage) + if diags.HasError() { + return diags + } + *package_ = parsedEptPackage + + parsedEptConnections, diags := parseConnections(ctx, ept.GetConnections()) + if diags.HasError() { + return diags + } + *connections = parsedEptConnections + + eptIpv4 := ept.GetIpv4() + parsedEptIpv4, diags := parseIpv4(ctx, &eptIpv4) + if diags.HasError() { + return diags + } + *ipv4 = parsedEptIpv4 + + eptAccount := ept.GetAccount() + parsedEptAccount, diags := parseAccount(ctx, &eptAccount) + if diags.HasError() { + return diags + } + *account = parsedEptAccount + + eptAdvanceConfiguration := ept.GetAdvanceConfiguration() + parsedEptAdvanceConfiguration, diags := parseAdvanceConfiguration(ctx, &eptAdvanceConfiguration) + if diags.HasError() { + return diags + } + *advancedConfiguration = parsedEptAdvanceConfiguration + + eptProject := ept.GetProject() + parsedEptProject, diags := parseProject(ctx, &eptProject) + if diags.HasError() { + return diags + } + *project = parsedEptProject + + return diags +} + +func parsePackage(ctx context.Context, package_ *fabricv4.PrecisionTimePackageResponse) (fwtypes.ObjectValueOf[PackageModel], diag.Diagnostics) { + var packageModel *PackageModel + + packageModel.Code = types.StringValue(string(package_.GetCode())) + packageModel.Href = types.StringValue(package_.GetHref()) + + return fwtypes.NewObjectValueOf[PackageModel](ctx, packageModel), nil + +} + +func parseConnections(ctx context.Context, connections []fabricv4.FabricConnectionUuid) (fwtypes.ListNestedObjectValueOf[ConnectionModel], diag.Diagnostics) { + connectionModels := make([]ConnectionModel, len(connections)) + + for index, connection := range connections { + connectionModels[index] = ConnectionModel{ + Uuid: types.StringValue(connection.GetUuid()), + Href: types.StringValue(connection.GetHref()), + Type: types.StringValue(connection.GetType()), + } + } + + return fwtypes.NewListNestedObjectValueOfValueSlice(ctx, connectionModels), nil +} + +func parseIpv4(ctx context.Context, ipv4 *fabricv4.Ipv4) (fwtypes.ObjectValueOf[Ipv4Model], diag.Diagnostics) { + var ipv4Model *Ipv4Model + + ipv4Model.Primary = types.StringValue(ipv4.GetPrimary()) + ipv4Model.Secondary = types.StringValue(ipv4.GetSecondary()) + ipv4Model.DefaultGateway = types.StringValue(ipv4.GetDefaultGateway()) + ipv4Model.NetworkMask = types.StringValue(ipv4.GetNetworkMask()) + + return fwtypes.NewObjectValueOf[Ipv4Model](ctx, ipv4Model), nil +} + +func parseAccount(ctx context.Context, account *fabricv4.Account) (fwtypes.ObjectValueOf[AccountModel], diag.Diagnostics) { + var accountModel *AccountModel + + accountModel.AccountNumber = types.Int64Value(int64(account.GetAccountNumber())) + accountModel.IsResellerAccount = types.BoolValue(account.GetIsResellerAccount()) + accountModel.OrgId = types.StringValue(account.GetOrgId()) + accountModel.GlobalOrgId = types.StringValue(account.GetGlobalOrgId()) + + return fwtypes.NewObjectValueOf[AccountModel](ctx, accountModel), nil +} + +func parseAdvanceConfiguration(ctx context.Context, advConfig *fabricv4.AdvanceConfiguration) (fwtypes.ObjectValueOf[AdvanceConfigurationModel], diag.Diagnostics) { + var diags diag.Diagnostics + var advConfigModel *AdvanceConfigurationModel + + md5s, diags := parseNtp(ctx, advConfig.GetNtp()) + if diags.HasError() { + return fwtypes.NewObjectValueOf[AdvanceConfigurationModel](ctx, &AdvanceConfigurationModel{}), diags + } + advConfigModel.Ntp = md5s + + ptp := advConfig.GetPtp() + parsedPtp, diags := parsePtp(ctx, &ptp) + if diags.HasError() { + return fwtypes.NewObjectValueOf[AdvanceConfigurationModel](ctx, &AdvanceConfigurationModel{}), diags + } + advConfigModel.Ptp = parsedPtp + + return fwtypes.NewObjectValueOf[AdvanceConfigurationModel](ctx, advConfigModel), nil +} + +func parseProject(ctx context.Context, project *fabricv4.Project) (fwtypes.ObjectValueOf[ProjectModel], diag.Diagnostics) { + var projectModel *ProjectModel + + projectModel.ProjectId = types.StringValue(project.GetProjectId()) + + return fwtypes.NewObjectValueOf[ProjectModel](ctx, projectModel), nil +} + +func parseNtp(ctx context.Context, ntp []fabricv4.Md5) (fwtypes.ListNestedObjectValueOf[MD5Model], diag.Diagnostics) { + ntpModel := make([]MD5Model, len(ntp)) + + for index, md5 := range ntp { + ntpModel[index] = MD5Model{ + Type: types.StringValue(string(md5.GetType())), + Id: types.StringValue(md5.GetId()), + Password: types.StringValue(md5.GetPassword()), + } + } + + return fwtypes.NewListNestedObjectValueOfValueSlice(ctx, ntpModel), nil +} + +func parsePtp(ctx context.Context, ptp *fabricv4.PtpAdvanceConfiguration) (fwtypes.ObjectValueOf[PTPModel], diag.Diagnostics) { + var ptpModel *PTPModel + + ptpModel.TimeScale = types.StringValue(string(ptp.GetTimeScale())) + ptpModel.Domain = types.Int64Value(int64(ptp.GetDomain())) + ptpModel.Priority1 = types.Int64Value(int64(ptp.GetPriority1())) + ptpModel.Priority2 = types.Int64Value(int64(ptp.GetPriority2())) + ptpModel.LogAnnounceInterval = types.Int64Value(int64(ptp.GetLogAnnounceInterval())) + ptpModel.LogSyncInterval = types.Int64Value(int64(ptp.GetLogSyncInterval())) + ptpModel.LogDelayReqInterval = types.Int64Value(int64(ptp.GetLogDelayReqInterval())) + ptpModel.TransportMode = types.StringValue(string(ptp.GetTransportMode())) + ptpModel.GrantTime = types.Int64Value(int64(ptp.GetGrantTime())) + + return fwtypes.NewObjectValueOf[PTPModel](ctx, ptpModel), nil + +} diff --git a/internal/resources/fabric/precision_time/resource.go b/internal/resources/fabric/precision_time/resource.go new file mode 100644 index 000000000..6101f2080 --- /dev/null +++ b/internal/resources/fabric/precision_time/resource.go @@ -0,0 +1,272 @@ +package precision_time + +import ( + "context" + "fmt" + "github.com/equinix/equinix-sdk-go/services/fabricv4" + equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + + "github.com/equinix/terraform-provider-equinix/internal/framework" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +func NewResource() resource.Resource { + return &Resource{ + BaseResource: framework.NewBaseResource( + framework.BaseResourceConfig{ + Name: "equinix_fabric_precision_time", + }, + ), + } +} + +type Resource struct { + framework.BaseResource +} + +func (r *Resource) Schema( + ctx context.Context, + req resource.SchemaRequest, + resp *resource.SchemaResponse, +) { + resp.Schema = resourceSchema(ctx) +} + +func (r *Resource) Create( + ctx context.Context, + req resource.CreateRequest, + resp *resource.CreateResponse, +) { + + var plan ResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Retrieve the API client from the provider metadata + client := r.Meta.NewFabricClientForFramework(ctx, req.ProviderMeta) + + createRequest, diags := buildCreateRequest(ctx, plan) + + resp.Diagnostics.Append(diags...) + if diags.HasError() { + return + } + + ept, _, err := client.PrecisionTimeApi.CreateTimeServices(ctx).PrecisionTimeServiceRequest(createRequest).Execute() + if err != nil { + resp.Diagnostics.AddError( + "Error creating Precision Time Service", + equinix_errors.FormatFabricError(err).Error(), + ) + return + } + + ept, diags = getEpt(ctx, client, &resp.State, ept.GetUuid()) + if diags != nil { + resp.Diagnostics.Append(diags...) + return + } + + // Parse API response into the Terraform state + resp.Diagnostics.Append(plan.parse(ctx, ept)...) + if resp.Diagnostics.HasError() { + return + } + + // Set state to fully populated data + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *Resource) Read( + ctx context.Context, + req resource.ReadRequest, + resp *resource.ReadResponse, +) { + +} + +func (r *Resource) Update( + ctx context.Context, + req resource.UpdateRequest, + resp *resource.UpdateResponse, +) { + +} + +func (r *Resource) Delete( + ctx context.Context, + req resource.DeleteRequest, + resp *resource.DeleteResponse, +) { + +} + +func buildCreateRequest(ctx context.Context, plan ResourceModel) (fabricv4.PrecisionTimeServiceRequest, diag.Diagnostics) { + var diags diag.Diagnostics + request := fabricv4.PrecisionTimeServiceRequest{ + Type: fabricv4.PrecisionTimeServiceRequestType(plan.Type.ValueString()), + Name: plan.Name.ValueString(), + } + + packageModel := PackageModel{} + + diags = plan.Package.As(ctx, &packageModel, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return fabricv4.PrecisionTimeServiceRequest{}, diags + } + + package_ := fabricv4.PrecisionTimePackageRequest{ + Code: fabricv4.GetTimeServicesPackageByCodePackageCodeParameter(packageModel.Code.ValueString()), + } + + if href := packageModel.Href.ValueString(); href != "" { + package_.SetHref(href) + } + request.SetPackage(package_) + + connectionsModels, diags := plan.Connections.ToSlice(ctx) + if diags.HasError() { + return fabricv4.PrecisionTimeServiceRequest{}, diags + } + + connections := make([]fabricv4.FabricConnectionUuid, len(connectionsModels)) + for index, connection := range connectionsModels { + connections[index].SetUuid(connection.Uuid.ValueString()) + + if type_ := connection.Type.ValueString(); type_ != "" { + connections[index].SetType(type_) + } + + if href := connection.Href.ValueString(); href != "" { + connections[index].SetHref(href) + } + } + request.SetConnections(connections) + + ipv4Model := Ipv4Model{} + + diags = plan.Ipv4.As(ctx, &ipv4Model, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return fabricv4.PrecisionTimeServiceRequest{}, diags + } + + ipv4 := fabricv4.Ipv4{} + ipv4.SetPrimary(ipv4Model.Primary.ValueString()) + ipv4.SetSecondary(ipv4Model.Secondary.ValueString()) + ipv4.SetNetworkMask(ipv4Model.NetworkMask.ValueString()) + ipv4.SetDefaultGateway(ipv4Model.DefaultGateway.ValueString()) + request.SetIpv4(ipv4) + + advConfigModel := AdvanceConfigurationModel{} + + diags = plan.AdvanceConfiguration.As(ctx, &advConfigModel, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return fabricv4.PrecisionTimeServiceRequest{}, diags + } + + ptpModel := PTPModel{} + diags = advConfigModel.Ptp.As(ctx, ptpModel, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return fabricv4.PrecisionTimeServiceRequest{}, diags + } + + ptp := fabricv4.PtpAdvanceConfiguration{} + if timeScale := ptpModel.TimeScale.ValueString(); timeScale != "" { + ptp.SetTimeScale(fabricv4.PtpAdvanceConfigurationTimeScale(timeScale)) + } + if domain := ptpModel.Domain.ValueInt64(); domain != 0 { + ptp.SetPriority1(int32(domain)) + } + if priority1 := ptpModel.Priority1.ValueInt64(); priority1 != 0 { + ptp.SetPriority1(int32(priority1)) + } + if priority2 := ptpModel.Priority2.ValueInt64(); priority2 != 0 { + ptp.SetPriority1(int32(priority2)) + } + if logAnnounceInterval := ptpModel.LogAnnounceInterval.ValueInt64(); logAnnounceInterval != 0 { + ptp.SetPriority1(int32(logAnnounceInterval)) + } + if logSyncInterval := ptpModel.LogSyncInterval.ValueInt64(); logSyncInterval != 0 { + ptp.SetPriority1(int32(logSyncInterval)) + } + if logDelayReqInterval := ptpModel.LogDelayReqInterval.ValueInt64(); logDelayReqInterval != 0 { + ptp.SetPriority1(int32(logDelayReqInterval)) + } + if transportMode := ptpModel.TransportMode.ValueString(); transportMode != "" { + ptp.SetPriority1(fabricv4.PtpAdvanceConfigurationTransportMode(transportMode)) + } + if grantTime := ptpModel.GrantTime.ValueInt64(); grantTime != 0 { + ptp.SetPriority1(int32(grantTime)) + } + + ntpModels, diags := advConfigModel.Ntp.ToSlice(ctx) + if diags.HasError() { + return fabricv4.PrecisionTimeServiceRequest{}, diags + } + + ntps := make([]fabricv4.Md5, len(ntpModels)) + for index, md5 := range ntpModels { + ntps[index] = fabricv4.Md5{} + if type_ := md5.Type.ValueString(); type_ != "" { + ntps[index].SetType(fabricv4.Md5Type(type_)) + } + if id := md5.Id.ValueString(); id != "" { + ntps[index].SetId(id) + } + if password := md5.Password.ValueString(); password != "" { + ntps[index].SetPassword(password) + } + } + + advConfig := fabricv4.AdvanceConfiguration{} + advConfig.SetNtp(ntps) + advConfig.SetPtp(ptp) + request.SetAdvanceConfiguration(advConfig) + + projectModel := ProjectModel{} + + diags = plan.Project.As(ctx, &projectModel, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return fabricv4.PrecisionTimeServiceRequest{}, diags + } + + project := fabricv4.Project{} + if projectId := projectModel.ProjectId.ValueString(); projectId != "" { + project.SetProjectId(projectId) + } + request.SetProject(project) + + return request, diags +} + +func getEpt(ctx context.Context, client *fabricv4.APIClient, state *tfsdk.State, id string) (*fabricv4.PrecisionTimeServiceCreateResponse, diag.Diagnostics) { + var diags diag.Diagnostics + + // Use API client to get the current state of the resource + ept, _, err := client.PrecisionTimeApi.GetTimeServicesById(ctx, id).Execute() + + if err != nil { + // If the Precision Time Service is not found, remove it from the state + if equinix_errors.IsNotFound(err) { + diags.AddWarning( + "Precision Time Service", + fmt.Sprintf("[WARN] Precision Time Service (%s) not found, removing from state", id), + ) + state.RemoveResource(ctx) + return nil, diags + } + + diags.AddError( + "Error reading Precision Time Service", + equinix_errors.FormatFabricError(err).Error(), + ) + return nil, diags + } + return ept, diags +} diff --git a/internal/resources/fabric/precision_time/resource_schema.go b/internal/resources/fabric/precision_time/resource_schema.go new file mode 100644 index 000000000..83120d9fa --- /dev/null +++ b/internal/resources/fabric/precision_time/resource_schema.go @@ -0,0 +1,202 @@ +package precision_time + +import ( + "context" + "github.com/equinix/equinix-sdk-go/services/fabricv4" + "github.com/equinix/terraform-provider-equinix/internal/framework" + fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +func resourceSchema(ctx context.Context) schema.Schema { + return schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": framework.IDAttributeDefaultDescription(), + "type": schema.StringAttribute{ + Description: "Choose type of Precision Time Service", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf( + string(fabricv4.PRECISIONTIMESERVICEREQUESTTYPE_NTP), + string(fabricv4.PRECISIONTIMESERVICEREQUESTTYPE_PTP), + ), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of Precision Time Service. Applicable values: Maximum: 24 characters; Allowed characters: alpha-numeric, hyphens ('-') and underscores ('_')", + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtMost(24), + }, + }, + "description": schema.StringAttribute{ + Description: "Optional description of time service", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "package": schema.SingleNestedAttribute{ + Description: "An object that has the Time Precision Package code and Time Precision Package href", + Required: true, + CustomType: fwtypes.NewObjectTypeOf[PackageModel](ctx), + Attributes: map[string]schema.Attribute{ + "code": schema.StringAttribute{ + Description: "Time Precision Package Code for the desired billing package", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf( + string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_NTP_STANDARD), + string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_NTP_ENTERPRISE), + string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_PTP_STANDARD), + string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_PTP_ENTERPRISE), + ), + }, + }, + "href": schema.StringAttribute{ + Description: "Time Precision Package HREF link to corresponding resource in Equinix Portal", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + }, + "connections": schema.ListAttribute{ + Description: "An array of objects with unique identifiers of connections.", + Required: true, + CustomType: fwtypes.NewListNestedObjectTypeOf[ConnectionModel](ctx), + ElementType: fwtypes.NewObjectTypeOf[ConnectionModel](ctx), + }, + "ipv4": schema.SingleNestedAttribute{ + Description: "An object that has Network IP Configurations for Timing Master Servers.", + Required: true, + CustomType: fwtypes.NewObjectTypeOf[Ipv4Model](ctx), + Attributes: map[string]schema.Attribute{ + "primary": schema.StringAttribute{ + Description: "IPv4 address for the Primary Timing Master Server.", + Required: true, + }, + "secondary": schema.StringAttribute{ + Description: "IPv4 address for the Secondary Timing Master Server.", + Required: true, + }, + "network_mask": schema.StringAttribute{ + Description: "IPv4 address that defines the range of consecutive subnets in the network.", + Required: true, + }, + "default_gateway": schema.StringAttribute{ + Description: "IPv4 address that establishes the Routing Interface where traffic is directed. It serves as the next hop in the Network.", + Required: true, + }, + }, + }, + "advance_configuration": schema.SingleNestedAttribute{ + Description: "An object that has advanced configuration options.", + Optional: true, + Computed: true, + CustomType: fwtypes.NewObjectTypeOf[AdvanceConfigurationModel](ctx), + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.UseStateForUnknown(), + }, + Attributes: map[string]schema.Attribute{ + "ntp": schema.ListAttribute{ + Description: "Advance Configuration for NTP; a list of MD5 objects", + Optional: true, + Computed: true, + CustomType: fwtypes.NewListNestedObjectTypeOf[MD5Model](ctx), + ElementType: fwtypes.NewObjectTypeOf[MD5Model](ctx), + }, + "ptp": schema.SingleNestedAttribute{ + Description: "An object that has advanced PTP configuration.", + Optional: true, + Computed: true, + CustomType: fwtypes.NewObjectTypeOf[PTPModel](ctx), + Attributes: map[string]schema.Attribute{ + "time_scale": schema.StringAttribute{ + Description: "Time scale value. ARB denotes Arbitrary, and PTP denotes Precision Time Protocol.", + Optional: true, + Computed: true, + Validators: []validator.String{ + stringvalidator.OneOf( + string(fabricv4.PTPADVANCECONFIGURATIONTIMESCALE_ARB), + string(fabricv4.PTPADVANCECONFIGURATIONTIMESCALE_PTP), + ), + }, + }, + "domain": schema.Int64Attribute{ + Description: "Represents the domain number associated with the PTP profile. This is used to differentiate multiple PTP networks within a single physical network.", + Optional: true, + Computed: true, + }, + "priority1": schema.Int64Attribute{ + Description: "Specifies the priority level 1 for the clock. The value helps in determining the best clock in the PTP network. Lower values are considered higher priority.", + Optional: true, + Computed: true, + }, + "priority2": schema.Int64Attribute{ + Description: "Specifies the priority level 2 for the clock. It acts as a tie-breaker if multiple clocks have the same priority 1 value. Lower values are considered higher priority.", + Optional: true, + Computed: true, + }, + "log_announce_interval": schema.Int64Attribute{ + Description: "Represents the log2 interval between consecutive PTP announce messages. For example, a value of 0 implies an interval of 2^0 = 1 second.", + Optional: true, + Computed: true, + }, + "log_sync_interval": schema.Int64Attribute{ + Description: "Represents the log2 interval between consecutive PTP synchronization messages. A value of 0 implies an interval of 2^0 = 1 second.", + Optional: true, + Computed: true, + }, + "log_delay_req_interval": schema.Int64Attribute{ + Description: "Represents the log2 interval between consecutive PTP delay request messages. A value of 0 implies an interval of 2^0 = 1 second.", + Optional: true, + Computed: true, + }, + "transport_mode": schema.StringAttribute{ + Description: "Mode of transport for the Time Precision Service.", + Optional: true, + Computed: true, + Validators: []validator.String{ + stringvalidator.OneOf( + string(fabricv4.PTPADVANCECONFIGURATIONTRANSPORTMODE_MULTICAST), + string(fabricv4.PTPADVANCECONFIGURATIONTRANSPORTMODE_UNICAST), + string(fabricv4.PTPADVANCECONFIGURATIONTRANSPORTMODE_HYBRID), + ), + }, + }, + "grant_time": schema.Int64Attribute{ + Description: "Unicast Grant Time in seconds. For Multicast and Hybrid transport modes, grant time defaults to 300 seconds. For Unicast mode, grant time can be between 30 to 7200.", + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + "project": schema.SingleNestedAttribute{ + Description: "An object that contains the Equinix Fabric project_id used for linking the Time Precision Service to a specific Equinix Fabric Project", + Optional: true, + Computed: true, + CustomType: fwtypes.NewObjectTypeOf[ProjectModel](ctx), + Attributes: map[string]schema.Attribute{ + "project_id": schema.StringAttribute{ + Description: "Equinix Fabric Project ID", + Required: true, + }, + }, + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.UseStateForUnknown(), + }, + }, + }, + } +} diff --git a/internal/resources/fabric/precision_time/resource_test.go b/internal/resources/fabric/precision_time/resource_test.go new file mode 100644 index 000000000..db8c82ab3 --- /dev/null +++ b/internal/resources/fabric/precision_time/resource_test.go @@ -0,0 +1 @@ +package precision_time From 4c402a3f6418ac47721c4cbba492227993709975 Mon Sep 17 00:00:00 2001 From: Tim Hogarty Date: Mon, 22 Jul 2024 12:11:54 -0700 Subject: [PATCH 02/12] Add code for precision time data source --- internal/provider/provider.go | 3 + .../fabric/precision_time/datasources.go | 80 +++++++ .../precision_time/datasources_schema.go | 175 ++++++++++++++ .../fabric/precision_time/resource.go | 213 +++++++++++++++++- 4 files changed, 465 insertions(+), 6 deletions(-) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 4e0d18653..5c7ff3c06 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/equinix/terraform-provider-equinix/internal/config" + fabricprecisiontime "github.com/equinix/terraform-provider-equinix/internal/resources/fabric/precision_time" metalconnection "github.com/equinix/terraform-provider-equinix/internal/resources/metal/connection" metalgateway "github.com/equinix/terraform-provider-equinix/internal/resources/metal/gateway" metalorganization "github.com/equinix/terraform-provider-equinix/internal/resources/metal/organization" @@ -114,6 +115,7 @@ func (p *FrameworkProvider) MetaSchema( func (p *FrameworkProvider) Resources(ctx context.Context) []func() resource.Resource { return []func() resource.Resource{ + fabricprecisiontime.NewResource, metalgateway.NewResource, metalproject.NewResource, metalprojectsshkey.NewResource, @@ -127,6 +129,7 @@ func (p *FrameworkProvider) Resources(ctx context.Context) []func() resource.Res func (p *FrameworkProvider) DataSources(ctx context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ + fabricprecisiontime.NewDataSource, metalgateway.NewDataSource, metalproject.NewDataSource, metalprojectsshkey.NewDataSource, diff --git a/internal/resources/fabric/precision_time/datasources.go b/internal/resources/fabric/precision_time/datasources.go index db8c82ab3..8c2caaf4c 100644 --- a/internal/resources/fabric/precision_time/datasources.go +++ b/internal/resources/fabric/precision_time/datasources.go @@ -1 +1,81 @@ package precision_time + +import ( + equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors" + "github.com/equinix/terraform-provider-equinix/internal/framework" + "github.com/hashicorp/terraform-plugin-framework/datasource" + + "context" + "fmt" +) + +func NewDataSource() datasource.DataSource { + return &DataSource{ + BaseDataSource: framework.NewBaseDataSource( + framework.BaseDataSourceConfig{ + Name: "equinix_fabric_precision_time", + }, + ), + } +} + +type DataSource struct { + framework.BaseDataSource +} + +func (r *DataSource) Schema( + ctx context.Context, + req datasource.SchemaRequest, + resp *datasource.SchemaResponse, +) { + resp.Schema = dataSourceSchema(ctx) +} + +func (r *DataSource) Read( + ctx context.Context, + req datasource.ReadRequest, + resp *datasource.ReadResponse, +) { + client := r.Meta.NewFabricClientForFramework(ctx, req.ProviderMeta) + + // Retrieve values from plan + var data DataSourceModel + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // Extract the ID of the resource from the state + id := data.Uuid.ValueString() + + // Use API client to get the current state of the resource + ept, _, err := client.PrecisionTimeApi.GetTimeServicesById(ctx, id). + Execute() + + if err != nil { + // If the Precision time Service is not found, remove it from the state + if equinix_errors.IsNotFound(err) { + resp.Diagnostics.AddWarning( + "Precision Time Service", + fmt.Sprintf("[WARN] Precision Time Service (%s) not found, removing from state", id), + ) + resp.State.RemoveResource(ctx) + return + } + + resp.Diagnostics.AddError( + "Error reading Precision Time Service", + "Could not read Precision Time Service with ID "+id+": "+err.Error(), + ) + return + } + + // Set state to fully populated data + resp.Diagnostics.Append(data.parse(ctx, ept)...) + if resp.Diagnostics.HasError() { + return + } + + // Update the Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/resources/fabric/precision_time/datasources_schema.go b/internal/resources/fabric/precision_time/datasources_schema.go index db8c82ab3..11754119a 100644 --- a/internal/resources/fabric/precision_time/datasources_schema.go +++ b/internal/resources/fabric/precision_time/datasources_schema.go @@ -1 +1,176 @@ package precision_time + +import ( + "context" + "github.com/equinix/equinix-sdk-go/services/fabricv4" + "github.com/equinix/terraform-provider-equinix/internal/framework" + fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +func dataSourceSchema(ctx context.Context) schema.Schema { + return schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": framework.IDAttributeDefaultDescription(), + "uuid": schema.StringAttribute{ + Description: "Uuid of Precision Time Service resource; used for lookup", + Required: true, + }, + "type": schema.StringAttribute{ + Description: "Choose type of Precision Time Service", + Computed: true, + Validators: []validator.String{ + stringvalidator.OneOf( + string(fabricv4.PRECISIONTIMESERVICEREQUESTTYPE_NTP), + string(fabricv4.PRECISIONTIMESERVICEREQUESTTYPE_PTP), + ), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of Precision Time Service. Applicable values: Maximum: 24 characters; Allowed characters: alpha-numeric, hyphens ('-') and underscores ('_')", + Computed: true, + Validators: []validator.String{ + stringvalidator.LengthAtMost(24), + }, + }, + "description": schema.StringAttribute{ + Description: "Optional description of time service", + Computed: true, + }, + "package": schema.SingleNestedAttribute{ + Description: "An object that has the Time Precision Package code and Time Precision Package href", + Computed: true, + CustomType: fwtypes.NewObjectTypeOf[PackageModel](ctx), + Attributes: map[string]schema.Attribute{ + "code": schema.StringAttribute{ + Description: "Time Precision Package Code for the desired billing package", + Computed: true, + Validators: []validator.String{ + stringvalidator.OneOf( + string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_NTP_STANDARD), + string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_NTP_ENTERPRISE), + string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_PTP_STANDARD), + string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_PTP_ENTERPRISE), + ), + }, + }, + "href": schema.StringAttribute{ + Description: "Time Precision Package HREF link to corresponding resource in Equinix Portal", + Computed: true, + }, + }, + }, + "connections": schema.ListAttribute{ + Description: "An array of objects with unique identifiers of connections.", + Computed: true, + CustomType: fwtypes.NewListNestedObjectTypeOf[ConnectionModel](ctx), + ElementType: fwtypes.NewObjectTypeOf[ConnectionModel](ctx), + }, + "ipv4": schema.SingleNestedAttribute{ + Description: "An object that has Network IP Configurations for Timing Master Servers.", + Computed: true, + CustomType: fwtypes.NewObjectTypeOf[Ipv4Model](ctx), + Attributes: map[string]schema.Attribute{ + "primary": schema.StringAttribute{ + Description: "IPv4 address for the Primary Timing Master Server.", + Computed: true, + }, + "secondary": schema.StringAttribute{ + Description: "IPv4 address for the Secondary Timing Master Server.", + Computed: true, + }, + "network_mask": schema.StringAttribute{ + Description: "IPv4 address that defines the range of consecutive subnets in the network.", + Computed: true, + }, + "default_gateway": schema.StringAttribute{ + Description: "IPv4 address that establishes the Routing Interface where traffic is directed. It serves as the next hop in the Network.", + Computed: true, + }, + }, + }, + "advance_configuration": schema.SingleNestedAttribute{ + Description: "An object that has advanced configuration options.", + Computed: true, + CustomType: fwtypes.NewObjectTypeOf[AdvanceConfigurationModel](ctx), + Attributes: map[string]schema.Attribute{ + "ntp": schema.ListAttribute{ + Description: "Advance Configuration for NTP; a list of MD5 objects", + Computed: true, + CustomType: fwtypes.NewListNestedObjectTypeOf[MD5Model](ctx), + ElementType: fwtypes.NewObjectTypeOf[MD5Model](ctx), + }, + "ptp": schema.SingleNestedAttribute{ + Description: "An object that has advanced PTP configuration.", + Computed: true, + CustomType: fwtypes.NewObjectTypeOf[PTPModel](ctx), + Attributes: map[string]schema.Attribute{ + "time_scale": schema.StringAttribute{ + Description: "Time scale value. ARB denotes Arbitrary, and PTP denotes Precision Time Protocol.", + Computed: true, + Validators: []validator.String{ + stringvalidator.OneOf( + string(fabricv4.PTPADVANCECONFIGURATIONTIMESCALE_ARB), + string(fabricv4.PTPADVANCECONFIGURATIONTIMESCALE_PTP), + ), + }, + }, + "domain": schema.Int64Attribute{ + Description: "Represents the domain number associated with the PTP profile. This is used to differentiate multiple PTP networks within a single physical network.", + Computed: true, + }, + "priority1": schema.Int64Attribute{ + Description: "Specifies the priority level 1 for the clock. The value helps in determining the best clock in the PTP network. Lower values are considered higher priority.", + Computed: true, + }, + "priority2": schema.Int64Attribute{ + Description: "Specifies the priority level 2 for the clock. It acts as a tie-breaker if multiple clocks have the same priority 1 value. Lower values are considered higher priority.", + Computed: true, + }, + "log_announce_interval": schema.Int64Attribute{ + Description: "Represents the log2 interval between consecutive PTP announce messages. For example, a value of 0 implies an interval of 2^0 = 1 second.", + Computed: true, + }, + "log_sync_interval": schema.Int64Attribute{ + Description: "Represents the log2 interval between consecutive PTP synchronization messages. A value of 0 implies an interval of 2^0 = 1 second.", + Computed: true, + }, + "log_delay_req_interval": schema.Int64Attribute{ + Description: "Represents the log2 interval between consecutive PTP delay request messages. A value of 0 implies an interval of 2^0 = 1 second.", + Computed: true, + }, + "transport_mode": schema.StringAttribute{ + Description: "Mode of transport for the Time Precision Service.", + Computed: true, + Validators: []validator.String{ + stringvalidator.OneOf( + string(fabricv4.PTPADVANCECONFIGURATIONTRANSPORTMODE_MULTICAST), + string(fabricv4.PTPADVANCECONFIGURATIONTRANSPORTMODE_UNICAST), + string(fabricv4.PTPADVANCECONFIGURATIONTRANSPORTMODE_HYBRID), + ), + }, + }, + "grant_time": schema.Int64Attribute{ + Description: "Unicast Grant Time in seconds. For Multicast and Hybrid transport modes, grant time defaults to 300 seconds. For Unicast mode, grant time can be between 30 to 7200.", + Computed: true, + }, + }, + }, + }, + }, + "project": schema.SingleNestedAttribute{ + Description: "An object that contains the Equinix Fabric project_id used for linking the Time Precision Service to a specific Equinix Fabric Project", + Computed: true, + CustomType: fwtypes.NewObjectTypeOf[ProjectModel](ctx), + Attributes: map[string]schema.Attribute{ + "project_id": schema.StringAttribute{ + Description: "Equinix Fabric Project ID", + Computed: true, + }, + }, + }, + }, + } +} diff --git a/internal/resources/fabric/precision_time/resource.go b/internal/resources/fabric/precision_time/resource.go index 6101f2080..135e48960 100644 --- a/internal/resources/fabric/precision_time/resource.go +++ b/internal/resources/fabric/precision_time/resource.go @@ -7,6 +7,11 @@ import ( equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "net/http" + "reflect" + "slices" + "time" "github.com/equinix/terraform-provider-equinix/internal/framework" "github.com/hashicorp/terraform-plugin-framework/diag" @@ -88,7 +93,34 @@ func (r *Resource) Read( req resource.ReadRequest, resp *resource.ReadResponse, ) { + var state ResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Retrieve the API client from the provider metadata + client := r.Meta.NewFabricClientForFramework(ctx, req.ProviderMeta) + + // Extract the ID of the resource from the state + id := state.ID.ValueString() + + // Use API client to get the current state of the resource + ept, diags := getEpt(ctx, client, &resp.State, id) + if diags != nil { + resp.Diagnostics.Append(diags...) + return + } + + // Set state to fully populated data + resp.Diagnostics.Append(state.parse(ctx, ept)...) + if resp.Diagnostics.HasError() { + return + } + // Update the Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) } func (r *Resource) Update( @@ -96,7 +128,120 @@ func (r *Resource) Update( req resource.UpdateRequest, resp *resource.UpdateResponse, ) { + client := r.Meta.NewFabricClientForFramework(ctx, req.ProviderMeta) + // Retrieve values from plan + var state, plan ResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + // Extract the ID of the resource from the state + id := plan.ID.ValueString() + + // Prepare update request based on the changes + updateRequest := make([]fabricv4.PrecisionTimeChangeOperation, 0) + + if !state.Name.Equal(plan.Name) { + op := fabricv4.PRECISIONTIMECHANGEOPERATIONOP_REPLACE + if plan.Name.ValueString() != "" && state.Name.ValueString() == "" { + op = fabricv4.PRECISIONTIMECHANGEOPERATIONOP_ADD + } else if plan.Name.ValueString() == "" && state.Name.ValueString() != "" { + op = fabricv4.PRECISIONTIMECHANGEOPERATIONOP_REMOVE + } + updateRequest = append(updateRequest, fabricv4.PrecisionTimeChangeOperation{ + Op: op, + Path: fabricv4.PRECISIONTIMECHANGEOPERATIONPATH_NAME, + Value: plan.Name.ValueString(), + }) + } + if !state.Ipv4.Equal(plan.Ipv4) { + ipv4 := fabricv4.Ipv4{} + diags := plan.Ipv4.As(ctx, &ipv4, basetypes.ObjectAsOptions{}) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + updateRequest = append(updateRequest, fabricv4.PrecisionTimeChangeOperation{ + Op: fabricv4.PRECISIONTIMECHANGEOPERATIONOP_REPLACE, + Path: fabricv4.PRECISIONTIMECHANGEOPERATIONPATH_NAME, + Value: ipv4, + }) + } + if !state.AdvanceConfiguration.Equal(plan.AdvanceConfiguration) { + stateAdvConfig := fabricv4.AdvanceConfiguration{} + diags := state.AdvanceConfiguration.As(ctx, &stateAdvConfig, basetypes.ObjectAsOptions{}) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + planAdvConfig := fabricv4.AdvanceConfiguration{} + diags = plan.AdvanceConfiguration.As(ctx, &planAdvConfig, basetypes.ObjectAsOptions{}) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + if !reflect.DeepEqual(stateAdvConfig.Ntp, planAdvConfig.Ntp) { + updateRequest = append(updateRequest, fabricv4.PrecisionTimeChangeOperation{ + Op: fabricv4.PRECISIONTIMECHANGEOPERATIONOP_REPLACE, + Path: fabricv4.PRECISIONTIMECHANGEOPERATIONPATH_NAME, + Value: planAdvConfig.Ntp, + }) + } + if !reflect.DeepEqual(stateAdvConfig.Ptp, planAdvConfig.Ptp) { + updateRequest = append(updateRequest, fabricv4.PrecisionTimeChangeOperation{ + Op: fabricv4.PRECISIONTIMECHANGEOPERATIONOP_REPLACE, + Path: fabricv4.PRECISIONTIMECHANGEOPERATIONPATH_NAME, + Value: planAdvConfig.Ntp, + }) + } + } + if !state.Package.Equal(plan.Package) { + packageModel := PackageModel{} + diags := plan.Package.As(ctx, &packageModel, basetypes.ObjectAsOptions{}) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + updateRequest = append(updateRequest, fabricv4.PrecisionTimeChangeOperation{ + Op: fabricv4.PRECISIONTIMECHANGEOPERATIONOP_REPLACE, + Path: fabricv4.PRECISIONTIMECHANGEOPERATIONPATH_NAME, + Value: packageModel.Code.ValueString(), + }) + } + + for _, update := range updateRequest { + if !reflect.DeepEqual(updateRequest, fabricv4.PrecisionTimeChangeOperation{}) { + _, _, err := client.PrecisionTimeApi.UpdateTimeServicesById(ctx, id). + PrecisionTimeChangeOperation([]fabricv4.PrecisionTimeChangeOperation{update}). + Execute() + + if err != nil { + resp.Diagnostics.AddError( + "Error updating Precision Time Service", + equinix_errors.FormatFabricError(err).Error(), + ) + } + } + } + + // Use API client to get the current state of the resource + conn, diags := getEpt(ctx, client, &resp.State, id) + if diags != nil { + resp.Diagnostics.Append(diags...) + return + } + + // Set state to fully populated data + resp.Diagnostics.Append(plan.parse(ctx, conn)...) + if resp.Diagnostics.HasError() { + return + } + + // Set the updated state back into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } func (r *Resource) Delete( @@ -104,7 +249,37 @@ func (r *Resource) Delete( req resource.DeleteRequest, resp *resource.DeleteResponse, ) { + // Retrieve the API client + client := r.Meta.NewFabricClientForFramework(ctx, req.ProviderMeta) + // Retrieve the current state + var state ResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Extract the ID of the resource from the state + id := state.ID.ValueString() + + // API call to delete the Precision Time Service + _, deleteResp, err := client.PrecisionTimeApi.DeleteTimeServiceById(ctx, id).Execute() + if err != nil { + if deleteResp == nil || !slices.Contains([]int{http.StatusForbidden, http.StatusNotFound}, deleteResp.StatusCode) { + resp.Diagnostics.AddError( + fmt.Sprintf("Failed to delete Precision Time Service %s", id), err.Error()) + } + } + + deleteTimeout := 10 * 60 * time.Second + deleteWaiter := getDeleteWaiter(ctx, client, id, deleteTimeout) + _, err = deleteWaiter.WaitForStateContext(ctx) + + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Failed to delete Precision Time Service %s", id), err.Error()) + } } func buildCreateRequest(ctx context.Context, plan ResourceModel) (fabricv4.PrecisionTimeServiceRequest, diag.Diagnostics) { @@ -187,22 +362,22 @@ func buildCreateRequest(ctx context.Context, plan ResourceModel) (fabricv4.Preci ptp.SetPriority1(int32(priority1)) } if priority2 := ptpModel.Priority2.ValueInt64(); priority2 != 0 { - ptp.SetPriority1(int32(priority2)) + ptp.SetPriority2(int32(priority2)) } if logAnnounceInterval := ptpModel.LogAnnounceInterval.ValueInt64(); logAnnounceInterval != 0 { - ptp.SetPriority1(int32(logAnnounceInterval)) + ptp.SetLogAnnounceInterval(int32(logAnnounceInterval)) } if logSyncInterval := ptpModel.LogSyncInterval.ValueInt64(); logSyncInterval != 0 { - ptp.SetPriority1(int32(logSyncInterval)) + ptp.SetLogSyncInterval(int32(logSyncInterval)) } if logDelayReqInterval := ptpModel.LogDelayReqInterval.ValueInt64(); logDelayReqInterval != 0 { - ptp.SetPriority1(int32(logDelayReqInterval)) + ptp.SetLogDelayReqInterval(int32(logDelayReqInterval)) } if transportMode := ptpModel.TransportMode.ValueString(); transportMode != "" { - ptp.SetPriority1(fabricv4.PtpAdvanceConfigurationTransportMode(transportMode)) + ptp.SetTransportMode(fabricv4.PtpAdvanceConfigurationTransportMode(transportMode)) } if grantTime := ptpModel.GrantTime.ValueInt64(); grantTime != 0 { - ptp.SetPriority1(int32(grantTime)) + ptp.SetGrantTime(int32(grantTime)) } ntpModels, diags := advConfigModel.Ntp.ToSlice(ctx) @@ -270,3 +445,29 @@ func getEpt(ctx context.Context, client *fabricv4.APIClient, state *tfsdk.State, } return ept, diags } + +func getDeleteWaiter(ctx context.Context, client *fabricv4.APIClient, id string, timeout time.Duration) *retry.StateChangeConf { + // deletedMarker is a terraform-provider-only value that is used by the waiter + // to indicate that the Precision Time Service appears to be deleted successfully based on + // status code + deletedMarker := "tf-marker-for-deleted-precision-time-service" + + target := []string{deletedMarker, "deleted"} + + return &retry.StateChangeConf{ + Target: target, + Refresh: func() (interface{}, string, error) { + ept, resp, err := client.PrecisionTimeApi.GetTimeServicesById(ctx, id).Execute() + if err != nil { + if resp != nil && slices.Contains([]int{http.StatusForbidden, http.StatusNotFound}, resp.StatusCode) { + return ept, deletedMarker, nil + } + return 0, "", err + } + return ept, string(ept.GetState()), nil + }, + Timeout: timeout, + Delay: 10 * time.Second, + MinTimeout: 5 * time.Second, + } +} From 4dc5717cb6103a71fb47771c2e402ff0b0bb0d66 Mon Sep 17 00:00:00 2001 From: Tim Hogarty Date: Wed, 24 Jul 2024 14:25:36 -0700 Subject: [PATCH 03/12] Working build and initial API request from EPT; oauth2 has context canceled --- internal/provider/provider.go | 2 +- .../fabric/precision_time/datasources.go | 2 +- .../precision_time/datasources_schema.go | 81 ++++-- .../resources/fabric/precision_time/models.go | 90 ++++--- .../fabric/precision_time/resource.go | 36 +-- .../fabric/precision_time/resource_schema.go | 248 +++++++++++++----- 6 files changed, 326 insertions(+), 133 deletions(-) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 5c7ff3c06..0fd737dd8 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -129,7 +129,7 @@ func (p *FrameworkProvider) Resources(ctx context.Context) []func() resource.Res func (p *FrameworkProvider) DataSources(ctx context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ - fabricprecisiontime.NewDataSource, + //fabricprecisiontime.NewDataSource, metalgateway.NewDataSource, metalproject.NewDataSource, metalprojectsshkey.NewDataSource, diff --git a/internal/resources/fabric/precision_time/datasources.go b/internal/resources/fabric/precision_time/datasources.go index 8c2caaf4c..4e7181065 100644 --- a/internal/resources/fabric/precision_time/datasources.go +++ b/internal/resources/fabric/precision_time/datasources.go @@ -45,7 +45,7 @@ func (r *DataSource) Read( return } - // Extract the ID of the resource from the state + // Extract the ID of the resource from the config id := data.Uuid.ValueString() // Use API client to get the current state of the resource diff --git a/internal/resources/fabric/precision_time/datasources_schema.go b/internal/resources/fabric/precision_time/datasources_schema.go index 11754119a..5bcf5b212 100644 --- a/internal/resources/fabric/precision_time/datasources_schema.go +++ b/internal/resources/fabric/precision_time/datasources_schema.go @@ -2,6 +2,7 @@ package precision_time import ( "context" + "fmt" "github.com/equinix/equinix-sdk-go/services/fabricv4" "github.com/equinix/terraform-provider-equinix/internal/framework" fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" @@ -16,7 +17,7 @@ func dataSourceSchema(ctx context.Context) schema.Schema { "id": framework.IDAttributeDefaultDescription(), "uuid": schema.StringAttribute{ Description: "Uuid of Precision Time Service resource; used for lookup", - Required: true, + Computed: true, }, "type": schema.StringAttribute{ Description: "Choose type of Precision Time Service", @@ -39,9 +40,14 @@ func dataSourceSchema(ctx context.Context) schema.Schema { Description: "Optional description of time service", Computed: true, }, - "package": schema.SingleNestedAttribute{ - Description: "An object that has the Time Precision Package code and Time Precision Package href", + "state": schema.StringAttribute{ + Description: fmt.Sprintf("Indicator of the state of this Precision Time Service. One of: [%v]", fabricv4.AllowedPrecisionTimeServiceCreateResponseStateEnumValues), Computed: true, + }, + }, + Blocks: map[string]schema.Block{ + "package": schema.SingleNestedBlock{ + Description: "Precision Time Service Package Details", CustomType: fwtypes.NewObjectTypeOf[PackageModel](ctx), Attributes: map[string]schema.Attribute{ "code": schema.StringAttribute{ @@ -60,17 +66,46 @@ func dataSourceSchema(ctx context.Context) schema.Schema { Description: "Time Precision Package HREF link to corresponding resource in Equinix Portal", Computed: true, }, + "type": schema.StringAttribute{ + Description: "Type of the Precision Time Service Package", + Computed: true, + }, + "bandwidth": schema.Int64Attribute{ + Description: "Bandwidth of the Precision Time Service", + Computed: true, + }, + "clients_per_second_max": schema.Int64Attribute{ + Description: "Maximum clients available per second for the Precision Time Service", + Computed: true, + }, + "redundancy_supported": schema.BoolAttribute{ + Description: "Boolean flag indicating if this Precision Time Service supports redundancy", + Computed: true, + }, + "multi_subnet_supported": schema.BoolAttribute{ + Description: "Boolean flag indicating if this Precision Time Service supports multi subnetting", + Computed: true, + }, + "accuracy_unit": schema.StringAttribute{ + Description: "Time unit of accuracy for the Precision Time Service; e.g. microseconds", + Computed: true, + }, + "accuracy_sla": schema.Int64Attribute{ + Description: "SLA for the accuracy provided by the Precision Time Service", + Computed: true, + }, + "accuracy_avg_min": schema.Int64Attribute{ + Description: "Average minimum accuracy provided by the Precision Time Service", + Computed: true, + }, + "accuracy_avg_max": schema.Int64Attribute{ + Description: "Average maximum accuracy provided by the Precision Time Service", + Computed: true, + }, }, }, - "connections": schema.ListAttribute{ - Description: "An array of objects with unique identifiers of connections.", - Computed: true, - CustomType: fwtypes.NewListNestedObjectTypeOf[ConnectionModel](ctx), - ElementType: fwtypes.NewObjectTypeOf[ConnectionModel](ctx), - }, - "ipv4": schema.SingleNestedAttribute{ + "ipv4": schema.SingleNestedBlock{ Description: "An object that has Network IP Configurations for Timing Master Servers.", - Computed: true, CustomType: fwtypes.NewObjectTypeOf[Ipv4Model](ctx), Attributes: map[string]schema.Attribute{ "primary": schema.StringAttribute{ @@ -91,9 +126,8 @@ func dataSourceSchema(ctx context.Context) schema.Schema { }, }, }, - "advance_configuration": schema.SingleNestedAttribute{ + "advance_configuration": schema.SingleNestedBlock{ Description: "An object that has advanced configuration options.", - Computed: true, CustomType: fwtypes.NewObjectTypeOf[AdvanceConfigurationModel](ctx), Attributes: map[string]schema.Attribute{ "ntp": schema.ListAttribute{ @@ -102,9 +136,10 @@ func dataSourceSchema(ctx context.Context) schema.Schema { CustomType: fwtypes.NewListNestedObjectTypeOf[MD5Model](ctx), ElementType: fwtypes.NewObjectTypeOf[MD5Model](ctx), }, - "ptp": schema.SingleNestedAttribute{ + }, + Blocks: map[string]schema.Block{ + "ptp": schema.SingleNestedBlock{ Description: "An object that has advanced PTP configuration.", - Computed: true, CustomType: fwtypes.NewObjectTypeOf[PTPModel](ctx), Attributes: map[string]schema.Attribute{ "time_scale": schema.StringAttribute{ @@ -160,9 +195,8 @@ func dataSourceSchema(ctx context.Context) schema.Schema { }, }, }, - "project": schema.SingleNestedAttribute{ + "project": schema.SingleNestedBlock{ Description: "An object that contains the Equinix Fabric project_id used for linking the Time Precision Service to a specific Equinix Fabric Project", - Computed: true, CustomType: fwtypes.NewObjectTypeOf[ProjectModel](ctx), Attributes: map[string]schema.Attribute{ "project_id": schema.StringAttribute{ @@ -171,6 +205,19 @@ func dataSourceSchema(ctx context.Context) schema.Schema { }, }, }, + "connections": schema.ListNestedBlock{ + Description: "An array of objects with unique identifiers of connections.", + CustomType: fwtypes.NewListNestedObjectTypeOf[ConnectionModel](ctx), + NestedObject: schema.NestedBlockObject{ + CustomType: fwtypes.NewObjectTypeOf[ConnectionModel](ctx), + Attributes: map[string]schema.Attribute{ + "uuid": schema.StringAttribute{ + Description: "Equinix Fabric Connection UUID; Precision Time Service will be connected with it", + Computed: true, + }, + }, + }, + }, }, } } diff --git a/internal/resources/fabric/precision_time/models.go b/internal/resources/fabric/precision_time/models.go index 4227d55a3..634134781 100644 --- a/internal/resources/fabric/precision_time/models.go +++ b/internal/resources/fabric/precision_time/models.go @@ -17,12 +17,12 @@ type ResourceModel struct { Name types.String `tfsdk:"name"` Description types.String `tfsdk:"description"` State types.String `tfsdk:"state"` - Package fwtypes.ObjectValueOf[PackageModel] `tfsdk:"package"` + Package types.Set `tfsdk:"package"` Connections fwtypes.ListNestedObjectValueOf[ConnectionModel] `tfsdk:"connections"` - Ipv4 fwtypes.ObjectValueOf[Ipv4Model] `tfsdk:"ipv4"` + Ipv4 types.Set `tfsdk:"ipv4"` Account fwtypes.ObjectValueOf[AccountModel] `tfsdk:"account"` - AdvanceConfiguration fwtypes.ObjectValueOf[AdvanceConfigurationModel] `tfsdk:"advanced_configuration"` - Project fwtypes.ObjectValueOf[ProjectModel] `tfsdk:"project"` + AdvanceConfiguration fwtypes.ObjectValueOf[AdvanceConfigurationModel] `tfsdk:"advance_configuration"` + Project types.Set `tfsdk:"project"` } type DataSourceModel struct { @@ -33,17 +33,26 @@ type DataSourceModel struct { Name types.String `tfsdk:"name"` Description types.String `tfsdk:"description"` State types.String `tfsdk:"state"` - Package fwtypes.ObjectValueOf[PackageModel] `tfsdk:"package"` + Package types.Set `tfsdk:"package"` Connections fwtypes.ListNestedObjectValueOf[ConnectionModel] `tfsdk:"connections"` - Ipv4 fwtypes.ObjectValueOf[Ipv4Model] `tfsdk:"ipv4"` + Ipv4 types.Set `tfsdk:"ipv4"` Account fwtypes.ObjectValueOf[AccountModel] `tfsdk:"account"` - AdvanceConfiguration fwtypes.ObjectValueOf[AdvanceConfigurationModel] `tfsdk:"advanced_configuration"` - Project fwtypes.ObjectValueOf[ProjectModel] `tfsdk:"project"` + AdvanceConfiguration fwtypes.ObjectValueOf[AdvanceConfigurationModel] `tfsdk:"advance_configuration"` + Project types.Set `tfsdk:"project"` } type PackageModel struct { - Code types.String `tfsdk:"code"` - Href types.String `tfsdk:"href"` + Code types.String `tfsdk:"code"` + Href types.String `tfsdk:"href"` + Type types.String `tfsdk:"type"` + Bandwidth types.Int64 `tfsdk:"bandwidth"` + ClientsPerSecondMax types.Int64 `tfsdk:"clients_per_second_max"` + RedundancySupported types.Bool `tfsdk:"redundancy_supported"` + MultiSubnetSupported types.Bool `tfsdk:"multi_subnet_supported"` + AccuracyUnit types.String `tfsdk:"accuracy_unit"` + AccuracySla types.Int64 `tfsdk:"accuracy_sla"` + AccuracyAvgMin types.Int64 `tfsdk:"accuracy_avg_min"` + AccuracyAvgMax types.Int64 `tfsdk:"accuracy_avg_max"` } type ConnectionModel struct { @@ -98,8 +107,8 @@ func (m *ResourceModel) parse(ctx context.Context, ept *fabricv4.PrecisionTimeSe diags = parsePrecisionTime(ctx, ept, &m.ID, &m.Type, &m.Href, &m.Uuid, &m.Name, &m.Description, - &m.State, &m.Package, &m.Connections, &m.Ipv4, &m.Account, - &m.AdvanceConfiguration, &m.Project, + &m.State, &m.Package, &m.Ipv4, &m.Project, + &m.Account, &m.AdvanceConfiguration, &m.Connections, ) return diags @@ -110,8 +119,8 @@ func (m *DataSourceModel) parse(ctx context.Context, ept *fabricv4.PrecisionTime diags = parsePrecisionTime(ctx, ept, &m.ID, &m.Type, &m.Href, &m.Uuid, &m.Name, &m.Description, - &m.State, &m.Package, &m.Connections, &m.Ipv4, &m.Account, - &m.AdvanceConfiguration, &m.Project, + &m.State, &m.Package, &m.Ipv4, &m.Project, + &m.Account, &m.AdvanceConfiguration, &m.Connections, ) return diags @@ -121,12 +130,10 @@ func parsePrecisionTime( ctx context.Context, ept *fabricv4.PrecisionTimeServiceCreateResponse, id, type_, href, uuid, name, description, state *basetypes.StringValue, - package_ *fwtypes.ObjectValueOf[PackageModel], - connections *fwtypes.ListNestedObjectValueOf[ConnectionModel], - ipv4 *fwtypes.ObjectValueOf[Ipv4Model], + package_, ipv4, project *basetypes.SetValue, account *fwtypes.ObjectValueOf[AccountModel], - advancedConfiguration *fwtypes.ObjectValueOf[AdvanceConfigurationModel], - project *fwtypes.ObjectValueOf[ProjectModel], + advanceConfiguration *fwtypes.ObjectValueOf[AdvanceConfigurationModel], + connections *fwtypes.ListNestedObjectValueOf[ConnectionModel], ) diag.Diagnostics { var diags diag.Diagnostics @@ -170,7 +177,7 @@ func parsePrecisionTime( if diags.HasError() { return diags } - *advancedConfiguration = parsedEptAdvanceConfiguration + *advanceConfiguration = parsedEptAdvanceConfiguration eptProject := ept.GetProject() parsedEptProject, diags := parseProject(ctx, &eptProject) @@ -182,13 +189,18 @@ func parsePrecisionTime( return diags } -func parsePackage(ctx context.Context, package_ *fabricv4.PrecisionTimePackageResponse) (fwtypes.ObjectValueOf[PackageModel], diag.Diagnostics) { - var packageModel *PackageModel +func parsePackage(ctx context.Context, package_ *fabricv4.PrecisionTimePackageResponse) (basetypes.SetValue, diag.Diagnostics) { + var packageModel PackageModel packageModel.Code = types.StringValue(string(package_.GetCode())) packageModel.Href = types.StringValue(package_.GetHref()) - return fwtypes.NewObjectValueOf[PackageModel](ctx, packageModel), nil + packageSet, diags := basetypes.NewSetValueFrom(ctx, fwtypes.NewObjectTypeOf[PackageModel](ctx), []PackageModel{packageModel}) + if diags.HasError() { + return basetypes.SetValue{}, diags + } + + return packageSet, nil } @@ -206,15 +218,20 @@ func parseConnections(ctx context.Context, connections []fabricv4.FabricConnecti return fwtypes.NewListNestedObjectValueOfValueSlice(ctx, connectionModels), nil } -func parseIpv4(ctx context.Context, ipv4 *fabricv4.Ipv4) (fwtypes.ObjectValueOf[Ipv4Model], diag.Diagnostics) { - var ipv4Model *Ipv4Model +func parseIpv4(ctx context.Context, ipv4 *fabricv4.Ipv4) (basetypes.SetValue, diag.Diagnostics) { + var ipv4Model Ipv4Model ipv4Model.Primary = types.StringValue(ipv4.GetPrimary()) ipv4Model.Secondary = types.StringValue(ipv4.GetSecondary()) ipv4Model.DefaultGateway = types.StringValue(ipv4.GetDefaultGateway()) ipv4Model.NetworkMask = types.StringValue(ipv4.GetNetworkMask()) - return fwtypes.NewObjectValueOf[Ipv4Model](ctx, ipv4Model), nil + ipv4Set, diags := basetypes.NewSetValueFrom(ctx, fwtypes.NewObjectTypeOf[Ipv4Model](ctx), []Ipv4Model{ipv4Model}) + if diags.HasError() { + return basetypes.SetValue{}, diags + } + + return ipv4Set, nil } func parseAccount(ctx context.Context, account *fabricv4.Account) (fwtypes.ObjectValueOf[AccountModel], diag.Diagnostics) { @@ -248,14 +265,6 @@ func parseAdvanceConfiguration(ctx context.Context, advConfig *fabricv4.AdvanceC return fwtypes.NewObjectValueOf[AdvanceConfigurationModel](ctx, advConfigModel), nil } -func parseProject(ctx context.Context, project *fabricv4.Project) (fwtypes.ObjectValueOf[ProjectModel], diag.Diagnostics) { - var projectModel *ProjectModel - - projectModel.ProjectId = types.StringValue(project.GetProjectId()) - - return fwtypes.NewObjectValueOf[ProjectModel](ctx, projectModel), nil -} - func parseNtp(ctx context.Context, ntp []fabricv4.Md5) (fwtypes.ListNestedObjectValueOf[MD5Model], diag.Diagnostics) { ntpModel := make([]MD5Model, len(ntp)) @@ -286,3 +295,16 @@ func parsePtp(ctx context.Context, ptp *fabricv4.PtpAdvanceConfiguration) (fwtyp return fwtypes.NewObjectValueOf[PTPModel](ctx, ptpModel), nil } + +func parseProject(ctx context.Context, project *fabricv4.Project) (basetypes.SetValue, diag.Diagnostics) { + var projectModel ProjectModel + + projectModel.ProjectId = types.StringValue(project.GetProjectId()) + + projectSet, diags := basetypes.NewSetValueFrom(ctx, fwtypes.NewObjectTypeOf[ProjectModel](ctx), []ProjectModel{projectModel}) + if diags.HasError() { + return basetypes.SetValue{}, diags + } + + return projectSet, nil +} diff --git a/internal/resources/fabric/precision_time/resource.go b/internal/resources/fabric/precision_time/resource.go index 135e48960..704bd20a5 100644 --- a/internal/resources/fabric/precision_time/resource.go +++ b/internal/resources/fabric/precision_time/resource.go @@ -63,7 +63,7 @@ func (r *Resource) Create( return } - ept, _, err := client.PrecisionTimeApi.CreateTimeServices(ctx).PrecisionTimeServiceRequest(createRequest).Execute() + ept, _, err := client.PrecisionTimeApi.CreateTimeServices(context.Background()).PrecisionTimeServiceRequest(createRequest).Execute() if err != nil { resp.Diagnostics.AddError( "Error creating Precision Time Service", @@ -158,8 +158,9 @@ func (r *Resource) Update( }) } if !state.Ipv4.Equal(plan.Ipv4) { - ipv4 := fabricv4.Ipv4{} - diags := plan.Ipv4.As(ctx, &ipv4, basetypes.ObjectAsOptions{}) + ipv4Set := make([]fabricv4.Ipv4, 1) + diags := plan.Ipv4.ElementsAs(ctx, &ipv4Set, true) + ipv4 := ipv4Set[0] if diags.HasError() { resp.Diagnostics.Append(diags...) return @@ -199,8 +200,9 @@ func (r *Resource) Update( } } if !state.Package.Equal(plan.Package) { - packageModel := PackageModel{} - diags := plan.Package.As(ctx, &packageModel, basetypes.ObjectAsOptions{}) + packageSet := make([]PackageModel, 1) + diags := plan.Package.ElementsAs(ctx, &packageSet, true) + packageModel := packageSet[0] if diags.HasError() { resp.Diagnostics.Append(diags...) return @@ -289,9 +291,9 @@ func buildCreateRequest(ctx context.Context, plan ResourceModel) (fabricv4.Preci Name: plan.Name.ValueString(), } - packageModel := PackageModel{} - - diags = plan.Package.As(ctx, &packageModel, basetypes.ObjectAsOptions{}) + packageSet := make([]PackageModel, 1) + diags = plan.Package.ElementsAs(ctx, &packageSet, true) + packageModel := packageSet[0] if diags.HasError() { return fabricv4.PrecisionTimeServiceRequest{}, diags } @@ -324,9 +326,9 @@ func buildCreateRequest(ctx context.Context, plan ResourceModel) (fabricv4.Preci } request.SetConnections(connections) - ipv4Model := Ipv4Model{} - - diags = plan.Ipv4.As(ctx, &ipv4Model, basetypes.ObjectAsOptions{}) + ipv4Set := make([]Ipv4Model, 1) + diags = plan.Ipv4.ElementsAs(ctx, &ipv4Set, true) + ipv4Model := ipv4Set[0] if diags.HasError() { return fabricv4.PrecisionTimeServiceRequest{}, diags } @@ -339,14 +341,13 @@ func buildCreateRequest(ctx context.Context, plan ResourceModel) (fabricv4.Preci request.SetIpv4(ipv4) advConfigModel := AdvanceConfigurationModel{} - diags = plan.AdvanceConfiguration.As(ctx, &advConfigModel, basetypes.ObjectAsOptions{}) if diags.HasError() { return fabricv4.PrecisionTimeServiceRequest{}, diags } ptpModel := PTPModel{} - diags = advConfigModel.Ptp.As(ctx, ptpModel, basetypes.ObjectAsOptions{}) + diags = advConfigModel.Ptp.As(ctx, &ptpModel, basetypes.ObjectAsOptions{}) if diags.HasError() { return fabricv4.PrecisionTimeServiceRequest{}, diags } @@ -404,9 +405,12 @@ func buildCreateRequest(ctx context.Context, plan ResourceModel) (fabricv4.Preci advConfig.SetPtp(ptp) request.SetAdvanceConfiguration(advConfig) + projectSet := make([]ProjectModel, 1) + diags = plan.Project.ElementsAs(ctx, &projectSet, true) projectModel := ProjectModel{} - - diags = plan.Project.As(ctx, &projectModel, basetypes.ObjectAsOptions{}) + if len(projectSet) > 0 { + projectModel = projectSet[0] + } if diags.HasError() { return fabricv4.PrecisionTimeServiceRequest{}, diags } @@ -414,8 +418,8 @@ func buildCreateRequest(ctx context.Context, plan ResourceModel) (fabricv4.Preci project := fabricv4.Project{} if projectId := projectModel.ProjectId.ValueString(); projectId != "" { project.SetProjectId(projectId) + request.SetProject(project) } - request.SetProject(project) return request, diags } diff --git a/internal/resources/fabric/precision_time/resource_schema.go b/internal/resources/fabric/precision_time/resource_schema.go index 83120d9fa..4ed59407c 100644 --- a/internal/resources/fabric/precision_time/resource_schema.go +++ b/internal/resources/fabric/precision_time/resource_schema.go @@ -2,13 +2,16 @@ package precision_time import ( "context" + "fmt" "github.com/equinix/equinix-sdk-go/services/fabricv4" "github.com/equinix/terraform-provider-equinix/internal/framework" fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" ) @@ -17,6 +20,20 @@ func resourceSchema(ctx context.Context) schema.Schema { return schema.Schema{ Attributes: map[string]schema.Attribute{ "id": framework.IDAttributeDefaultDescription(), + "uuid": schema.StringAttribute{ + Description: "Equinix generated id for the Precision Time Service", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "href": schema.StringAttribute{ + Description: "Equinix generated Portal link for the created Precision Time Service", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, "type": schema.StringAttribute{ Description: "Choose type of Precision Time Service", Required: true, @@ -42,66 +59,120 @@ func resourceSchema(ctx context.Context) schema.Schema { stringplanmodifier.UseStateForUnknown(), }, }, - "package": schema.SingleNestedAttribute{ - Description: "An object that has the Time Precision Package code and Time Precision Package href", - Required: true, - CustomType: fwtypes.NewObjectTypeOf[PackageModel](ctx), - Attributes: map[string]schema.Attribute{ - "code": schema.StringAttribute{ - Description: "Time Precision Package Code for the desired billing package", - Required: true, - Validators: []validator.String{ - stringvalidator.OneOf( - string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_NTP_STANDARD), - string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_NTP_ENTERPRISE), - string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_PTP_STANDARD), - string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_PTP_ENTERPRISE), - ), + "state": schema.StringAttribute{ + Description: fmt.Sprintf("Indicator of the state of this Precision Time Service. One of: [%v]", fabricv4.AllowedPrecisionTimeServiceCreateResponseStateEnumValues), + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + Blocks: map[string]schema.Block{ + "package": schema.SetNestedBlock{ + Description: "Precision Time Service Package Details", + Validators: []validator.Set{ + setvalidator.SizeAtMost(1), + }, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.UseStateForUnknown(), + }, + NestedObject: schema.NestedBlockObject{ + CustomType: fwtypes.NewObjectTypeOf[PackageModel](ctx), + Attributes: map[string]schema.Attribute{ + "code": schema.StringAttribute{ + Description: "Time Precision Package Code for the desired billing package", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf( + string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_NTP_STANDARD), + string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_NTP_ENTERPRISE), + string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_PTP_STANDARD), + string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_PTP_ENTERPRISE), + ), + }, }, - }, - "href": schema.StringAttribute{ - Description: "Time Precision Package HREF link to corresponding resource in Equinix Portal", - Optional: true, - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), + "href": schema.StringAttribute{ + Description: "Time Precision Package HREF link to corresponding resource in Equinix Portal", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "type": schema.StringAttribute{ + Description: "Type of the Precision Time Service Package", + Computed: true, + }, + "bandwidth": schema.Int64Attribute{ + Description: "Bandwidth of the Precision Time Service", + Computed: true, + }, + "clients_per_second_max": schema.Int64Attribute{ + Description: "Maximum clients available per second for the Precision Time Service", + Computed: true, + }, + "redundancy_supported": schema.BoolAttribute{ + Description: "Boolean flag indicating if this Precision Time Service supports redundancy", + Computed: true, + }, + "multi_subnet_supported": schema.BoolAttribute{ + Description: "Boolean flag indicating if this Precision Time Service supports multi subnetting", + Computed: true, + }, + "accuracy_unit": schema.StringAttribute{ + Description: "Time unit of accuracy for the Precision Time Service; e.g. microseconds", + Computed: true, + }, + "accuracy_sla": schema.Int64Attribute{ + Description: "SLA for the accuracy provided by the Precision Time Service", + Computed: true, + }, + "accuracy_avg_min": schema.Int64Attribute{ + Description: "Average minimum accuracy provided by the Precision Time Service", + Computed: true, + }, + "accuracy_avg_max": schema.Int64Attribute{ + Description: "Average maximum accuracy provided by the Precision Time Service", + Computed: true, }, }, }, }, - "connections": schema.ListAttribute{ - Description: "An array of objects with unique identifiers of connections.", - Required: true, - CustomType: fwtypes.NewListNestedObjectTypeOf[ConnectionModel](ctx), - ElementType: fwtypes.NewObjectTypeOf[ConnectionModel](ctx), - }, - "ipv4": schema.SingleNestedAttribute{ + "ipv4": schema.SetNestedBlock{ Description: "An object that has Network IP Configurations for Timing Master Servers.", - Required: true, - CustomType: fwtypes.NewObjectTypeOf[Ipv4Model](ctx), - Attributes: map[string]schema.Attribute{ - "primary": schema.StringAttribute{ - Description: "IPv4 address for the Primary Timing Master Server.", - Required: true, - }, - "secondary": schema.StringAttribute{ - Description: "IPv4 address for the Secondary Timing Master Server.", - Required: true, - }, - "network_mask": schema.StringAttribute{ - Description: "IPv4 address that defines the range of consecutive subnets in the network.", - Required: true, + Validators: []validator.Set{ + setvalidator.SizeAtMost(1), + }, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.UseStateForUnknown(), + }, + NestedObject: schema.NestedBlockObject{ + CustomType: fwtypes.NewObjectTypeOf[Ipv4Model](ctx), + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.UseStateForUnknown(), }, - "default_gateway": schema.StringAttribute{ - Description: "IPv4 address that establishes the Routing Interface where traffic is directed. It serves as the next hop in the Network.", - Required: true, + Attributes: map[string]schema.Attribute{ + "primary": schema.StringAttribute{ + Description: "IPv4 address for the Primary Timing Master Server.", + Required: true, + }, + "secondary": schema.StringAttribute{ + Description: "IPv4 address for the Secondary Timing Master Server.", + Required: true, + }, + "network_mask": schema.StringAttribute{ + Description: "IPv4 address that defines the range of consecutive subnets in the network.", + Required: true, + }, + "default_gateway": schema.StringAttribute{ + Description: "IPv4 address that establishes the Routing Interface where traffic is directed. It serves as the next hop in the Network.", + Required: true, + }, }, }, }, - "advance_configuration": schema.SingleNestedAttribute{ + "advance_configuration": schema.SingleNestedBlock{ Description: "An object that has advanced configuration options.", - Optional: true, - Computed: true, CustomType: fwtypes.NewObjectTypeOf[AdvanceConfigurationModel](ctx), PlanModifiers: []planmodifier.Object{ objectplanmodifier.UseStateForUnknown(), @@ -114,11 +185,14 @@ func resourceSchema(ctx context.Context) schema.Schema { CustomType: fwtypes.NewListNestedObjectTypeOf[MD5Model](ctx), ElementType: fwtypes.NewObjectTypeOf[MD5Model](ctx), }, - "ptp": schema.SingleNestedAttribute{ + }, + Blocks: map[string]schema.Block{ + "ptp": schema.SingleNestedBlock{ Description: "An object that has advanced PTP configuration.", - Optional: true, - Computed: true, CustomType: fwtypes.NewObjectTypeOf[PTPModel](ctx), + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.UseStateForUnknown(), + }, Attributes: map[string]schema.Attribute{ "time_scale": schema.StringAttribute{ Description: "Time scale value. ARB denotes Arbitrary, and PTP denotes Precision Time Protocol.", @@ -136,12 +210,12 @@ func resourceSchema(ctx context.Context) schema.Schema { Optional: true, Computed: true, }, - "priority1": schema.Int64Attribute{ + "priority_1": schema.Int64Attribute{ Description: "Specifies the priority level 1 for the clock. The value helps in determining the best clock in the PTP network. Lower values are considered higher priority.", Optional: true, Computed: true, }, - "priority2": schema.Int64Attribute{ + "priority_2": schema.Int64Attribute{ Description: "Specifies the priority level 2 for the clock. It acts as a tie-breaker if multiple clocks have the same priority 1 value. Lower values are considered higher priority.", Optional: true, Computed: true, @@ -182,19 +256,65 @@ func resourceSchema(ctx context.Context) schema.Schema { }, }, }, - "project": schema.SingleNestedAttribute{ + "project": schema.SetNestedBlock{ Description: "An object that contains the Equinix Fabric project_id used for linking the Time Precision Service to a specific Equinix Fabric Project", - Optional: true, - Computed: true, - CustomType: fwtypes.NewObjectTypeOf[ProjectModel](ctx), - Attributes: map[string]schema.Attribute{ - "project_id": schema.StringAttribute{ - Description: "Equinix Fabric Project ID", - Required: true, + Validators: []validator.Set{ + setvalidator.SizeAtMost(1), + }, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.UseStateForUnknown(), + }, + NestedObject: schema.NestedBlockObject{ + CustomType: fwtypes.NewObjectTypeOf[ProjectModel](ctx), + Attributes: map[string]schema.Attribute{ + "project_id": schema.StringAttribute{ + Description: "Equinix Fabric Project ID", + Required: true, + }, }, }, - PlanModifiers: []planmodifier.Object{ - objectplanmodifier.UseStateForUnknown(), + }, + "connections": schema.ListNestedBlock{ + Description: "An array of objects with unique identifiers of connections.", + CustomType: fwtypes.NewListNestedObjectTypeOf[ConnectionModel](ctx), + NestedObject: schema.NestedBlockObject{ + CustomType: fwtypes.NewObjectTypeOf[ConnectionModel](ctx), + Attributes: map[string]schema.Attribute{ + "uuid": schema.StringAttribute{ + Description: "Equinix Fabric Connection UUID; Precision Time Service will be connected with it", + Required: true, + }, + "href": schema.StringAttribute{ + Description: "Link to the Equinix Fabric Connection associated with the Precision Time Service", + Computed: true, + }, + "type": schema.StringAttribute{ + Description: "Type of the Equinix Fabric Connection associated with the Precision Time Service", + Computed: true, + }, + }, + }, + }, + "account": schema.SingleNestedBlock{ + Description: "Equinix User Account associated with Precision Time Service", + CustomType: fwtypes.NewObjectTypeOf[AccountModel](ctx), + Attributes: map[string]schema.Attribute{ + "account_number": schema.Int64Attribute{ + Description: "Equinix User account number", + Computed: true, + }, + "is_reseller_account": schema.BoolAttribute{ + Description: "Equinix User Boolean flag indicating if it is a reseller account", + Computed: true, + }, + "org_id": schema.StringAttribute{ + Description: "Equinix User organization id", + Computed: true, + }, + "global_org_id": schema.StringAttribute{ + Description: "Equinix User global organization id", + Computed: true, + }, }, }, }, From eb6268eac120070b24091f65667ac96b8c248026 Mon Sep 17 00:00:00 2001 From: Tim Hogarty Date: Fri, 26 Jul 2024 19:19:50 -0700 Subject: [PATCH 04/12] Completed initial testing of Time Precision Service and added doc templates for doc generation --- docs/data-sources/fabric_precision_time.md | 141 ++++++++++++ docs/resources/fabric_precision_time.md | 188 +++++++++++++++ .../fabric_precision_time/get_by_uuid.tf | 3 + .../fabric_precision_time/ntp_time_service.tf | 17 ++ .../fabric_precision_time/ptp_time_service.tf | 17 ++ internal/framework/types/attrypes.go | 5 + .../framework/types/set_nested_objectof.go | 216 ++++++++++++++++++ internal/framework/types/setof.go | 136 +++++++++++ internal/provider/provider.go | 2 +- .../precision_time/datasources_schema.go | 205 ++++++++++------- .../resources/fabric/precision_time/models.go | 74 +++--- .../fabric/precision_time/resource.go | 73 ++---- .../fabric/precision_time/resource_schema.go | 16 ++ .../fabric_precision_time.md.tmpl | 22 ++ .../resources/fabric_precision_time.md.tmpl | 25 ++ 15 files changed, 980 insertions(+), 160 deletions(-) create mode 100644 docs/data-sources/fabric_precision_time.md create mode 100644 docs/resources/fabric_precision_time.md create mode 100644 examples/data-sources/fabric_precision_time/get_by_uuid.tf create mode 100644 examples/resources/fabric_precision_time/ntp_time_service.tf create mode 100644 examples/resources/fabric_precision_time/ptp_time_service.tf create mode 100644 internal/framework/types/set_nested_objectof.go create mode 100644 internal/framework/types/setof.go create mode 100644 templates/data-sources/fabric_precision_time.md.tmpl create mode 100644 templates/resources/fabric_precision_time.md.tmpl diff --git a/docs/data-sources/fabric_precision_time.md b/docs/data-sources/fabric_precision_time.md new file mode 100644 index 000000000..7592c6da1 --- /dev/null +++ b/docs/data-sources/fabric_precision_time.md @@ -0,0 +1,141 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "equinix_fabric_precision_time Data Source - terraform-provider-equinix" +subcategory: "Fabric" +description: |- + Fabric V4 API compatible data resource that allow user to fetch Equinix Fabric Precision Time Service by Uuid +--- + +# equinix_fabric_precision_time (Data Source) + +Fabric V4 API compatible data resource that allow user to fetch Equinix Fabric Precision Time Service by Uuid + +Additional documentation: +* Getting Started: https://docs.equinix.com/en-us/Content/Edge-Services/EPT/EPT.htm +* API: https://developer.equinix.com/dev-docs/fabric/api-reference/fabric-v4-apis#precision-time + +## Example Usage + +```terraform +data "equinix_fabric_precision_time" "time_service" { + uuid = "f9361b9a-a0b3-4fa3-bf36-be265dc7617f" +} +``` + + + +## Schema + +### Required + +- `uuid` (String) Uuid of Precision Time Service resource; used for lookup + +### Read-Only + +- `account` (Block, Read-only) Equinix User Account associated with Precision Time Service (see [below for nested schema](#nestedblock--account)) +- `advance_configuration` (Block, Read-only) An object that has advanced configuration options. (see [below for nested schema](#nestedblock--advance_configuration)) +- `connections` (Block List) An array of objects with unique identifiers of connections. (see [below for nested schema](#nestedblock--connections)) +- `description` (String) Optional description of time service +- `href` (String) Equinix generated Portal link for the created Precision Time Service +- `id` (String) The unique identifier of the resource +- `ipv4` (Block Set) An object that has Network IP Configurations for Timing Master Servers. (see [below for nested schema](#nestedblock--ipv4)) +- `name` (String) Name of Precision Time Service. Applicable values: Maximum: 24 characters; Allowed characters: alpha-numeric, hyphens ('-') and underscores ('_') +- `package` (Block Set) Precision Time Service Package Details (see [below for nested schema](#nestedblock--package)) +- `project` (Block Set) An object that contains the Equinix Fabric project_id used for linking the Time Precision Service to a specific Equinix Fabric Project (see [below for nested schema](#nestedblock--project)) +- `state` (String) Indicator of the state of this Precision Time Service. One of: [[PROVISIONED PROVISIONING PROVISIONING_FAILED CONFIGURING CANCELLED DEPROVISIONING_FAILED PENDING_CONFIGURATION DEPROVISIONED CONFIGURING_FAILED DEPROVISIONING]] +- `type` (String) Choose type of Precision Time Service + + +### Nested Schema for `account` + +Read-Only: + +- `account_number` (Number) Equinix User account number +- `global_org_id` (String) Equinix User global organization id +- `is_reseller_account` (Boolean) Equinix User Boolean flag indicating if it is a reseller account +- `org_id` (String) Equinix User organization id + + + +### Nested Schema for `advance_configuration` + +Read-Only: + +- `ntp` (List of Object) Advance Configuration for NTP; a list of MD5 objects (see [below for nested schema](#nestedatt--advance_configuration--ntp)) +- `ptp` (Block, Read-only) An object that has advanced PTP configuration. (see [below for nested schema](#nestedblock--advance_configuration--ptp)) + + +### Nested Schema for `advance_configuration.ntp` + +Read-Only: + +- `id` (String) +- `password` (String) +- `type` (String) + + + +### Nested Schema for `advance_configuration.ptp` + +Read-Only: + +- `domain` (Number) Represents the domain number associated with the PTP profile. This is used to differentiate multiple PTP networks within a single physical network. +- `grant_time` (Number) Unicast Grant Time in seconds. For Multicast and Hybrid transport modes, grant time defaults to 300 seconds. For Unicast mode, grant time can be between 30 to 7200. +- `log_announce_interval` (Number) Represents the log2 interval between consecutive PTP announce messages. For example, a value of 0 implies an interval of 2^0 = 1 second. +- `log_delay_req_interval` (Number) Represents the log2 interval between consecutive PTP delay request messages. A value of 0 implies an interval of 2^0 = 1 second. +- `log_sync_interval` (Number) Represents the log2 interval between consecutive PTP synchronization messages. A value of 0 implies an interval of 2^0 = 1 second. +- `priority_1` (Number) Specifies the priority level 1 for the clock. The value helps in determining the best clock in the PTP network. Lower values are considered higher priority. +- `priority_2` (Number) Specifies the priority level 2 for the clock. It acts as a tie-breaker if multiple clocks have the same priority 1 value. Lower values are considered higher priority. +- `time_scale` (String) Time scale value. ARB denotes Arbitrary, and PTP denotes Precision Time Protocol. +- `transport_mode` (String) Mode of transport for the Time Precision Service. + + + + +### Nested Schema for `connections` + +Read-Only: + +- `href` (String) Link to the Equinix Fabric Connection associated with the Precision Time Service +- `type` (String) Type of the Equinix Fabric Connection associated with the Precision Time Service +- `uuid` (String) Equinix Fabric Connection UUID; Precision Time Service will be connected with it + + + +### Nested Schema for `ipv4` + +Read-Only: + +- `default_gateway` (String) IPv4 address that establishes the Routing Interface where traffic is directed. It serves as the next hop in the Network. +- `network_mask` (String) IPv4 address that defines the range of consecutive subnets in the network. +- `primary` (String) IPv4 address for the Primary Timing Master Server. +- `secondary` (String) IPv4 address for the Secondary Timing Master Server. + + + +### Nested Schema for `package` + +Read-Only: + +- `accuracy_avg_max` (Number) Average maximum accuracy provided by the Precision Time Service +- `accuracy_avg_min` (Number) Average minimum accuracy provided by the Precision Time Service +- `accuracy_sla` (Number) SLA for the accuracy provided by the Precision Time Service +- `accuracy_unit` (String) Time unit of accuracy for the Precision Time Service; e.g. microseconds +- `bandwidth` (Number) Bandwidth of the Precision Time Service +- `clients_per_second_max` (Number) Maximum clients available per second for the Precision Time Service +- `code` (String) Time Precision Package Code for the desired billing package +- `href` (String) Time Precision Package HREF link to corresponding resource in Equinix Portal +- `multi_subnet_supported` (Boolean) Boolean flag indicating if this Precision Time Service supports multi subnetting +- `redundancy_supported` (Boolean) Boolean flag indicating if this Precision Time Service supports redundancy +- `type` (String) Type of the Precision Time Service Package + + + +### Nested Schema for `project` + +Read-Only: + +- `project_id` (String) Equinix Fabric Project ID + + + diff --git a/docs/resources/fabric_precision_time.md b/docs/resources/fabric_precision_time.md new file mode 100644 index 000000000..fd9aa073e --- /dev/null +++ b/docs/resources/fabric_precision_time.md @@ -0,0 +1,188 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "equinix_fabric_precision_time Resource - terraform-provider-equinix" +subcategory: "Fabric" +description: |- + Fabric V4 API compatible resource allows creation and management of Equinix Fabric Precision Time +--- + +# equinix_fabric_precision_time (Resource) + +Fabric V4 API compatible resource allows creation and management of Equinix Fabric Precision Time Services + +Additional documentation: +* Getting Started: https://docs.equinix.com/en-us/Content/Edge-Services/EPT/EPT.htm +* API: https://developer.equinix.com/dev-docs/fabric/api-reference/fabric-v4-apis#precision-time + +## Example Usage + +PTP Configuration: +```terraform +resource "equinix_fabric_precision_time" "ptp" { + type = "PTP" + name = "tf_ept_PFCR" + description = "Equinix Precision Time with PTP Configuration" + package { + code = "PTP_STANDARD" + } + connections { + uuid = "30b82c65-ffb4-47d3-ab2b-3cacf46d5b8b" + } + ipv4 { + primary = "192.168.0.2" + secondary = "192.168.0.3" + network_mask = "255.255.255.224" + default_gateway = "192.168.0.1" + } +} +``` + +NTP Configuration: +```terraform +resource "equinix_fabric_precision_time" "ntp" { + type = "NTP" + name = "tf_ntp_PFCR" + description = "Equinix Precision Time with NTP Configuration" + package { + code = "NTP_STANDARD" + } + connections { + uuid = "30b82c65-ffb4-47d3-ab2b-3cacf46d5b8b" + } + ipv4 { + primary = "192.168.0.2" + secondary = "192.168.0.3" + network_mask = "255.255.255.224" + default_gateway = "192.168.0.1" + } +} +``` + + +## Schema + +### Required + +- `name` (String) Name of Precision Time Service. Applicable values: Maximum: 24 characters; Allowed characters: alpha-numeric, hyphens ('-') and underscores ('_') +- `type` (String) Choose type of Precision Time Service + +### Optional + +- `advance_configuration` (Block, Optional) An object that has advanced configuration options. (see [below for nested schema](#nestedblock--advance_configuration)) +- `connections` (Block List) An array of objects with unique identifiers of connections. (see [below for nested schema](#nestedblock--connections)) +- `description` (String) Optional description of time service +- `ipv4` (Block Set) An object that has Network IP Configurations for Timing Master Servers. (see [below for nested schema](#nestedblock--ipv4)) +- `package` (Block Set) Precision Time Service Package Details (see [below for nested schema](#nestedblock--package)) +- `project` (Block Set) An object that contains the Equinix Fabric project_id used for linking the Time Precision Service to a specific Equinix Fabric Project (see [below for nested schema](#nestedblock--project)) + +### Read-Only + +- `account` (Block, Read-only) Equinix User Account associated with Precision Time Service (see [below for nested schema](#nestedblock--account)) +- `href` (String) Equinix generated Portal link for the created Precision Time Service +- `id` (String) The unique identifier of the resource +- `state` (String) Indicator of the state of this Precision Time Service. One of: [[PROVISIONED PROVISIONING PROVISIONING_FAILED CONFIGURING CANCELLED DEPROVISIONING_FAILED PENDING_CONFIGURATION DEPROVISIONED CONFIGURING_FAILED DEPROVISIONING]] +- `uuid` (String) Equinix generated id for the Precision Time Service + + +### Nested Schema for `advance_configuration` + +Optional: + +- `ntp` (List of Object) Advance Configuration for NTP; a list of MD5 objects (see [below for nested schema](#nestedatt--advance_configuration--ntp)) +- `ptp` (Block, Optional) An object that has advanced PTP configuration. (see [below for nested schema](#nestedblock--advance_configuration--ptp)) + + +### Nested Schema for `advance_configuration.ntp` + +Optional: + +- `id` (String) +- `password` (String) +- `type` (String) + + + +### Nested Schema for `advance_configuration.ptp` + +Optional: + +- `domain` (Number) Represents the domain number associated with the PTP profile. This is used to differentiate multiple PTP networks within a single physical network. +- `grant_time` (Number) Unicast Grant Time in seconds. For Multicast and Hybrid transport modes, grant time defaults to 300 seconds. For Unicast mode, grant time can be between 30 to 7200. +- `log_announce_interval` (Number) Represents the log2 interval between consecutive PTP announce messages. For example, a value of 0 implies an interval of 2^0 = 1 second. +- `log_delay_req_interval` (Number) Represents the log2 interval between consecutive PTP delay request messages. A value of 0 implies an interval of 2^0 = 1 second. +- `log_sync_interval` (Number) Represents the log2 interval between consecutive PTP synchronization messages. A value of 0 implies an interval of 2^0 = 1 second. +- `priority_1` (Number) Specifies the priority level 1 for the clock. The value helps in determining the best clock in the PTP network. Lower values are considered higher priority. +- `priority_2` (Number) Specifies the priority level 2 for the clock. It acts as a tie-breaker if multiple clocks have the same priority 1 value. Lower values are considered higher priority. +- `time_scale` (String) Time scale value. ARB denotes Arbitrary, and PTP denotes Precision Time Protocol. +- `transport_mode` (String) Mode of transport for the Time Precision Service. + + + + +### Nested Schema for `connections` + +Required: + +- `uuid` (String) Equinix Fabric Connection UUID; Precision Time Service will be connected with it + +Read-Only: + +- `href` (String) Link to the Equinix Fabric Connection associated with the Precision Time Service +- `type` (String) Type of the Equinix Fabric Connection associated with the Precision Time Service + + + +### Nested Schema for `ipv4` + +Required: + +- `default_gateway` (String) IPv4 address that establishes the Routing Interface where traffic is directed. It serves as the next hop in the Network. +- `network_mask` (String) IPv4 address that defines the range of consecutive subnets in the network. +- `primary` (String) IPv4 address for the Primary Timing Master Server. +- `secondary` (String) IPv4 address for the Secondary Timing Master Server. + + + +### Nested Schema for `package` + +Required: + +- `code` (String) Time Precision Package Code for the desired billing package + +Optional: + +- `href` (String) Time Precision Package HREF link to corresponding resource in Equinix Portal + +Read-Only: + +- `accuracy_avg_max` (Number) Average maximum accuracy provided by the Precision Time Service +- `accuracy_avg_min` (Number) Average minimum accuracy provided by the Precision Time Service +- `accuracy_sla` (Number) SLA for the accuracy provided by the Precision Time Service +- `accuracy_unit` (String) Time unit of accuracy for the Precision Time Service; e.g. microseconds +- `bandwidth` (Number) Bandwidth of the Precision Time Service +- `clients_per_second_max` (Number) Maximum clients available per second for the Precision Time Service +- `multi_subnet_supported` (Boolean) Boolean flag indicating if this Precision Time Service supports multi subnetting +- `redundancy_supported` (Boolean) Boolean flag indicating if this Precision Time Service supports redundancy +- `type` (String) Type of the Precision Time Service Package + + + +### Nested Schema for `project` + +Required: + +- `project_id` (String) Equinix Fabric Project ID + + + +### Nested Schema for `account` + +Read-Only: + +- `account_number` (Number) Equinix User account number +- `global_org_id` (String) Equinix User global organization id +- `is_reseller_account` (Boolean) Equinix User Boolean flag indicating if it is a reseller account +- `org_id` (String) Equinix User organization id + + + diff --git a/examples/data-sources/fabric_precision_time/get_by_uuid.tf b/examples/data-sources/fabric_precision_time/get_by_uuid.tf new file mode 100644 index 000000000..dc32aa58a --- /dev/null +++ b/examples/data-sources/fabric_precision_time/get_by_uuid.tf @@ -0,0 +1,3 @@ +data "equinix_fabric_precision_time" "time_service" { + uuid = "f9361b9a-a0b3-4fa3-bf36-be265dc7617f" +} diff --git a/examples/resources/fabric_precision_time/ntp_time_service.tf b/examples/resources/fabric_precision_time/ntp_time_service.tf new file mode 100644 index 000000000..4fa6723be --- /dev/null +++ b/examples/resources/fabric_precision_time/ntp_time_service.tf @@ -0,0 +1,17 @@ +resource "equinix_fabric_precision_time" "ntp" { + type = "NTP" + name = "tf_ntp_PFCR" + description = "Equinix Precision Time with NTP Configuration" + package { + code = "NTP_STANDARD" + } + connections { + uuid = "30b82c65-ffb4-47d3-ab2b-3cacf46d5b8b" + } + ipv4 { + primary = "192.168.0.2" + secondary = "192.168.0.3" + network_mask = "255.255.255.224" + default_gateway = "192.168.0.1" + } +} diff --git a/examples/resources/fabric_precision_time/ptp_time_service.tf b/examples/resources/fabric_precision_time/ptp_time_service.tf new file mode 100644 index 000000000..0870ad644 --- /dev/null +++ b/examples/resources/fabric_precision_time/ptp_time_service.tf @@ -0,0 +1,17 @@ +resource "equinix_fabric_precision_time" "ptp" { + type = "PTP" + name = "tf_ept_PFCR" + description = "Equinix Precision Time with PTP Configuration" + package { + code = "PTP_STANDARD" + } + connections { + uuid = "30b82c65-ffb4-47d3-ab2b-3cacf46d5b8b" + } + ipv4 { + primary = "192.168.0.2" + secondary = "192.168.0.3" + network_mask = "255.255.255.224" + default_gateway = "192.168.0.1" + } +} diff --git a/internal/framework/types/attrypes.go b/internal/framework/types/attrypes.go index 6413f5c0c..3a799c714 100644 --- a/internal/framework/types/attrypes.go +++ b/internal/framework/types/attrypes.go @@ -53,3 +53,8 @@ func AttributeTypes[T any](ctx context.Context) (map[string]attr.Type, error) { func AttributeTypesMust[T any](ctx context.Context) map[string]attr.Type { return equinix_errors.Must(AttributeTypes[T](ctx)) } + +func newAttrTypeOf[T attr.Value](ctx context.Context) attr.Type { + var zero T + return zero.Type(ctx) +} diff --git a/internal/framework/types/set_nested_objectof.go b/internal/framework/types/set_nested_objectof.go new file mode 100644 index 000000000..28bd82dfb --- /dev/null +++ b/internal/framework/types/set_nested_objectof.go @@ -0,0 +1,216 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package types + +import ( + "context" + "fmt" + equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +var ( + _ basetypes.SetTypable = (*setNestedObjectTypeOf[struct{}])(nil) + _ NestedObjectCollectionType = (*setNestedObjectTypeOf[struct{}])(nil) + _ basetypes.SetValuable = (*SetNestedObjectValueOf[struct{}])(nil) + _ NestedObjectCollectionValue = (*SetNestedObjectValueOf[struct{}])(nil) +) + +// setNestedObjectTypeOf is the attribute type of a SetNestedObjectValueOf. +type setNestedObjectTypeOf[T any] struct { + basetypes.SetType +} + +func NewSetNestedObjectTypeOf[T any](ctx context.Context) setNestedObjectTypeOf[T] { + return setNestedObjectTypeOf[T]{basetypes.SetType{ElemType: NewObjectTypeOf[T](ctx)}} +} + +func (t setNestedObjectTypeOf[T]) Equal(o attr.Type) bool { + other, ok := o.(setNestedObjectTypeOf[T]) + + if !ok { + return false + } + + return t.SetType.Equal(other.SetType) +} + +func (t setNestedObjectTypeOf[T]) String() string { + var zero T + return fmt.Sprintf("SetNestedObjectTypeOf[%T]", zero) +} + +func (t setNestedObjectTypeOf[T]) ValueFromSet(ctx context.Context, in basetypes.SetValue) (basetypes.SetValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + if in.IsNull() { + return NewSetNestedObjectValueOfNull[T](ctx), diags + } + if in.IsUnknown() { + return NewSetNestedObjectValueOfUnknown[T](ctx), diags + } + + typ := NewObjectTypeOf[T](ctx) + + v, d := basetypes.NewSetValue(typ, in.Elements()) + diags.Append(d...) + if diags.HasError() { + return NewSetNestedObjectValueOfUnknown[T](ctx), diags + } + + return SetNestedObjectValueOf[T]{SetValue: v}, diags +} + +func (t setNestedObjectTypeOf[T]) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + attrValue, err := t.SetType.ValueFromTerraform(ctx, in) + + if err != nil { + return nil, err + } + + setValue, ok := attrValue.(basetypes.SetValue) + + if !ok { + return nil, fmt.Errorf("unexpected value type of %T", attrValue) + } + + setValuable, diags := t.ValueFromSet(ctx, setValue) + + if diags.HasError() { + return nil, fmt.Errorf("unexpected error converting SetValue to SetValuable: %v", diags) + } + + return setValuable, nil +} + +func (t setNestedObjectTypeOf[T]) ValueType(ctx context.Context) attr.Value { + return SetNestedObjectValueOf[T]{} +} + +func (t setNestedObjectTypeOf[T]) NewObjectPtr(ctx context.Context) (any, diag.Diagnostics) { + return objectTypeNewObjectPtr[T](ctx) +} + +func (t setNestedObjectTypeOf[T]) NewObjectSlice(ctx context.Context, len, cap int) (any, diag.Diagnostics) { + return nestedObjectTypeNewObjectSlice[T](ctx, len, cap) +} + +func (t setNestedObjectTypeOf[T]) NullValue(ctx context.Context) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics + + return NewSetNestedObjectValueOfNull[T](ctx), diags +} + +func (t setNestedObjectTypeOf[T]) ValueFromObjectPtr(ctx context.Context, ptr any) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics + + if v, ok := ptr.(*T); ok { + v, d := NewSetNestedObjectValueOfPtr(ctx, v) + diags.Append(d...) + return v, d + } + + diags.Append(diag.NewErrorDiagnostic("Invalid pointer value", fmt.Sprintf("incorrect type: want %T, got %T", (*T)(nil), ptr))) + return nil, diags +} + +func (t setNestedObjectTypeOf[T]) ValueFromObjectSlice(ctx context.Context, slice any) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics + + if v, ok := slice.([]*T); ok { + v, d := NewSetNestedObjectValueOfSlice(ctx, v) + diags.Append(d...) + return v, d + } + + diags.Append(diag.NewErrorDiagnostic("Invalid slice value", fmt.Sprintf("incorrect type: want %T, got %T", (*[]T)(nil), slice))) + return nil, diags +} + +// SetNestedObjectValueOf represents a Terraform Plugin Framework Set value whose elements are of type `ObjectTypeOf[T]`. +type SetNestedObjectValueOf[T any] struct { + basetypes.SetValue +} + +func (v SetNestedObjectValueOf[T]) Equal(o attr.Value) bool { + other, ok := o.(SetNestedObjectValueOf[T]) + + if !ok { + return false + } + + return v.SetValue.Equal(other.SetValue) +} + +func (v SetNestedObjectValueOf[T]) Type(ctx context.Context) attr.Type { + return NewSetNestedObjectTypeOf[T](ctx) +} + +func (v SetNestedObjectValueOf[T]) ToObjectPtr(ctx context.Context) (any, diag.Diagnostics) { + return v.ToPtr(ctx) +} + +func (v SetNestedObjectValueOf[T]) ToObjectSlice(ctx context.Context) (any, diag.Diagnostics) { + return v.ToSlice(ctx) +} + +// ToPtr returns a pointer to the single element of a SetNestedObject. +func (v SetNestedObjectValueOf[T]) ToPtr(ctx context.Context) (*T, diag.Diagnostics) { + return nestedObjectValueObjectPtr[T](ctx, v.SetValue) +} + +// ToSlice returns a slice of pointers to the elements of a SetNestedObject. +func (v SetNestedObjectValueOf[T]) ToSlice(ctx context.Context) ([]*T, diag.Diagnostics) { + return nestedObjectValueObjectSlice[T](ctx, v.SetValue) +} + +func NewSetNestedObjectValueOfNull[T any](ctx context.Context) SetNestedObjectValueOf[T] { + return SetNestedObjectValueOf[T]{SetValue: basetypes.NewSetNull(NewObjectTypeOf[T](ctx))} +} + +func NewSetNestedObjectValueOfUnknown[T any](ctx context.Context) SetNestedObjectValueOf[T] { + return SetNestedObjectValueOf[T]{SetValue: basetypes.NewSetUnknown(NewObjectTypeOf[T](ctx))} +} + +func NewSetNestedObjectValueOfPtr[T any](ctx context.Context, t *T) (SetNestedObjectValueOf[T], diag.Diagnostics) { + return NewSetNestedObjectValueOfSlice(ctx, []*T{t}) +} + +func NewSetNestedObjectValueOfPtrMust[T any](ctx context.Context, t *T) SetNestedObjectValueOf[T] { + return equinix_errors.MustWithDiagnostics(NewSetNestedObjectValueOfPtr(ctx, t)) +} + +func NewSetNestedObjectValueOfSlice[T any](ctx context.Context, ts []*T) (SetNestedObjectValueOf[T], diag.Diagnostics) { + return newSetNestedObjectValueOf[T](ctx, ts) +} + +func NewSetNestedObjectValueOfSliceMust[T any](ctx context.Context, ts []*T) SetNestedObjectValueOf[T] { + return equinix_errors.MustWithDiagnostics(NewSetNestedObjectValueOfSlice(ctx, ts)) +} + +func NewSetNestedObjectValueOfValueSlice[T any](ctx context.Context, ts []T) (SetNestedObjectValueOf[T], diag.Diagnostics) { + return newSetNestedObjectValueOf[T](ctx, ts) +} + +func NewSetNestedObjectValueOfValueSliceMust[T any](ctx context.Context, ts []T) SetNestedObjectValueOf[T] { + return equinix_errors.MustWithDiagnostics(NewSetNestedObjectValueOfValueSlice(ctx, ts)) +} + +func newSetNestedObjectValueOf[T any](ctx context.Context, elements any) (SetNestedObjectValueOf[T], diag.Diagnostics) { + var diags diag.Diagnostics + + typ := NewObjectTypeOf[T](ctx) + + v, d := basetypes.NewSetValueFrom(ctx, typ, elements) + diags.Append(d...) + if diags.HasError() { + return NewSetNestedObjectValueOfUnknown[T](ctx), diags + } + + return SetNestedObjectValueOf[T]{SetValue: v}, diags +} diff --git a/internal/framework/types/setof.go b/internal/framework/types/setof.go new file mode 100644 index 000000000..439cc73d0 --- /dev/null +++ b/internal/framework/types/setof.go @@ -0,0 +1,136 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package types + +import ( + "context" + "fmt" + equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +var ( + _ basetypes.SetTypable = (*setTypeOf[basetypes.StringValue])(nil) + _ basetypes.SetValuable = (*SetValueOf[basetypes.StringValue])(nil) +) + +// setTypeOf is the attribute type of a SetValueOf. +type setTypeOf[T attr.Value] struct { + basetypes.SetType +} + +var ( + SetOfStringType = setTypeOf[basetypes.StringValue]{basetypes.SetType{ElemType: basetypes.StringType{}}} +) + +func NewSetTypeOf[T attr.Value](ctx context.Context) setTypeOf[T] { + return setTypeOf[T]{basetypes.SetType{ElemType: newAttrTypeOf[T](ctx)}} +} + +func (t setTypeOf[T]) Equal(o attr.Type) bool { + other, ok := o.(setTypeOf[T]) + + if !ok { + return false + } + + return t.SetType.Equal(other.SetType) +} + +func (t setTypeOf[T]) String() string { + var zero T + return fmt.Sprintf("SetTypeOf[%T]", zero) +} + +func (t setTypeOf[T]) ValueFromSet(ctx context.Context, in basetypes.SetValue) (basetypes.SetValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + if in.IsNull() { + return NewSetValueOfNull[T](ctx), diags + } + if in.IsUnknown() { + return NewSetValueOfUnknown[T](ctx), diags + } + + v, d := basetypes.NewSetValue(newAttrTypeOf[T](ctx), in.Elements()) + diags.Append(d...) + if diags.HasError() { + return NewSetValueOfUnknown[T](ctx), diags + } + + return SetValueOf[T]{SetValue: v}, diags +} + +func (t setTypeOf[T]) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + attrValue, err := t.SetType.ValueFromTerraform(ctx, in) + + if err != nil { + return nil, err + } + + setValue, ok := attrValue.(basetypes.SetValue) + + if !ok { + return nil, fmt.Errorf("unexpected value type of %T", attrValue) + } + + setValuable, diags := t.ValueFromSet(ctx, setValue) + + if diags.HasError() { + return nil, fmt.Errorf("unexpected error converting SetValue to SetValuable: %v", diags) + } + + return setValuable, nil +} + +func (t setTypeOf[T]) ValueType(ctx context.Context) attr.Value { + return SetValueOf[T]{} +} + +// SetValueOf represents a Terraform Plugin Framework Set value whose elements are of type `T`. +type SetValueOf[T attr.Value] struct { + basetypes.SetValue +} + +func (v SetValueOf[T]) Equal(o attr.Value) bool { + other, ok := o.(SetValueOf[T]) + + if !ok { + return false + } + + return v.SetValue.Equal(other.SetValue) +} + +func (v SetValueOf[T]) Type(ctx context.Context) attr.Type { + return NewSetTypeOf[T](ctx) +} + +func NewSetValueOfNull[T attr.Value](ctx context.Context) SetValueOf[T] { + return SetValueOf[T]{SetValue: basetypes.NewSetNull(newAttrTypeOf[T](ctx))} +} + +func NewSetValueOfUnknown[T attr.Value](ctx context.Context) SetValueOf[T] { + return SetValueOf[T]{SetValue: basetypes.NewSetUnknown(newAttrTypeOf[T](ctx))} +} + +func NewSetValueOf[T attr.Value](ctx context.Context, elements []attr.Value) (SetValueOf[T], diag.Diagnostics) { + var diags diag.Diagnostics + + v, d := basetypes.NewSetValue(newAttrTypeOf[T](ctx), elements) + diags.Append(d...) + if diags.HasError() { + return NewSetValueOfUnknown[T](ctx), diags + } + + return SetValueOf[T]{SetValue: v}, diags +} + +func NewSetValueOfMust[T attr.Value](ctx context.Context, elements []attr.Value) SetValueOf[T] { + return equinix_errors.MustWithDiagnostics(NewSetValueOf[T](ctx, elements)) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 0fd737dd8..5c7ff3c06 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -129,7 +129,7 @@ func (p *FrameworkProvider) Resources(ctx context.Context) []func() resource.Res func (p *FrameworkProvider) DataSources(ctx context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ - //fabricprecisiontime.NewDataSource, + fabricprecisiontime.NewDataSource, metalgateway.NewDataSource, metalproject.NewDataSource, metalprojectsshkey.NewDataSource, diff --git a/internal/resources/fabric/precision_time/datasources_schema.go b/internal/resources/fabric/precision_time/datasources_schema.go index 5bcf5b212..4c3faf8d9 100644 --- a/internal/resources/fabric/precision_time/datasources_schema.go +++ b/internal/resources/fabric/precision_time/datasources_schema.go @@ -6,6 +6,7 @@ import ( "github.com/equinix/equinix-sdk-go/services/fabricv4" "github.com/equinix/terraform-provider-equinix/internal/framework" fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/schema/validator" @@ -17,7 +18,7 @@ func dataSourceSchema(ctx context.Context) schema.Schema { "id": framework.IDAttributeDefaultDescription(), "uuid": schema.StringAttribute{ Description: "Uuid of Precision Time Service resource; used for lookup", - Computed: true, + Required: true, }, "type": schema.StringAttribute{ Description: "Choose type of Precision Time Service", @@ -29,6 +30,10 @@ func dataSourceSchema(ctx context.Context) schema.Schema { ), }, }, + "href": schema.StringAttribute{ + Description: "Equinix generated Portal link for the created Precision Time Service", + Computed: true, + }, "name": schema.StringAttribute{ Description: "Name of Precision Time Service. Applicable values: Maximum: 24 characters; Allowed characters: alpha-numeric, hyphens ('-') and underscores ('_')", Computed: true, @@ -46,83 +51,87 @@ func dataSourceSchema(ctx context.Context) schema.Schema { }, }, Blocks: map[string]schema.Block{ - "package": schema.SingleNestedBlock{ + "package": schema.SetNestedBlock{ Description: "Precision Time Service Package Details", - CustomType: fwtypes.NewObjectTypeOf[PackageModel](ctx), - Attributes: map[string]schema.Attribute{ - "code": schema.StringAttribute{ - Description: "Time Precision Package Code for the desired billing package", - Computed: true, - Validators: []validator.String{ - stringvalidator.OneOf( - string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_NTP_STANDARD), - string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_NTP_ENTERPRISE), - string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_PTP_STANDARD), - string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_PTP_ENTERPRISE), - ), + CustomType: fwtypes.NewSetNestedObjectTypeOf[PackageModel](ctx), + Validators: []validator.Set{ + setvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + CustomType: fwtypes.NewObjectTypeOf[PackageModel](ctx), + Attributes: map[string]schema.Attribute{ + "code": schema.StringAttribute{ + Description: "Time Precision Package Code for the desired billing package", + Computed: true, + }, + "href": schema.StringAttribute{ + Description: "Time Precision Package HREF link to corresponding resource in Equinix Portal", + Computed: true, + }, + "type": schema.StringAttribute{ + Description: "Type of the Precision Time Service Package", + Computed: true, + }, + "bandwidth": schema.Int64Attribute{ + Description: "Bandwidth of the Precision Time Service", + Computed: true, + }, + "clients_per_second_max": schema.Int64Attribute{ + Description: "Maximum clients available per second for the Precision Time Service", + Computed: true, + }, + "redundancy_supported": schema.BoolAttribute{ + Description: "Boolean flag indicating if this Precision Time Service supports redundancy", + Computed: true, + }, + "multi_subnet_supported": schema.BoolAttribute{ + Description: "Boolean flag indicating if this Precision Time Service supports multi subnetting", + Computed: true, + }, + "accuracy_unit": schema.StringAttribute{ + Description: "Time unit of accuracy for the Precision Time Service; e.g. microseconds", + Computed: true, + }, + "accuracy_sla": schema.Int64Attribute{ + Description: "SLA for the accuracy provided by the Precision Time Service", + Computed: true, + }, + "accuracy_avg_min": schema.Int64Attribute{ + Description: "Average minimum accuracy provided by the Precision Time Service", + Computed: true, + }, + "accuracy_avg_max": schema.Int64Attribute{ + Description: "Average maximum accuracy provided by the Precision Time Service", + Computed: true, }, - }, - "href": schema.StringAttribute{ - Description: "Time Precision Package HREF link to corresponding resource in Equinix Portal", - Computed: true, - }, - "type": schema.StringAttribute{ - Description: "Type of the Precision Time Service Package", - Computed: true, - }, - "bandwidth": schema.Int64Attribute{ - Description: "Bandwidth of the Precision Time Service", - Computed: true, - }, - "clients_per_second_max": schema.Int64Attribute{ - Description: "Maximum clients available per second for the Precision Time Service", - Computed: true, - }, - "redundancy_supported": schema.BoolAttribute{ - Description: "Boolean flag indicating if this Precision Time Service supports redundancy", - Computed: true, - }, - "multi_subnet_supported": schema.BoolAttribute{ - Description: "Boolean flag indicating if this Precision Time Service supports multi subnetting", - Computed: true, - }, - "accuracy_unit": schema.StringAttribute{ - Description: "Time unit of accuracy for the Precision Time Service; e.g. microseconds", - Computed: true, - }, - "accuracy_sla": schema.Int64Attribute{ - Description: "SLA for the accuracy provided by the Precision Time Service", - Computed: true, - }, - "accuracy_avg_min": schema.Int64Attribute{ - Description: "Average minimum accuracy provided by the Precision Time Service", - Computed: true, - }, - "accuracy_avg_max": schema.Int64Attribute{ - Description: "Average maximum accuracy provided by the Precision Time Service", - Computed: true, }, }, }, - "ipv4": schema.SingleNestedBlock{ + "ipv4": schema.SetNestedBlock{ Description: "An object that has Network IP Configurations for Timing Master Servers.", - CustomType: fwtypes.NewObjectTypeOf[Ipv4Model](ctx), - Attributes: map[string]schema.Attribute{ - "primary": schema.StringAttribute{ - Description: "IPv4 address for the Primary Timing Master Server.", - Computed: true, - }, - "secondary": schema.StringAttribute{ - Description: "IPv4 address for the Secondary Timing Master Server.", - Computed: true, - }, - "network_mask": schema.StringAttribute{ - Description: "IPv4 address that defines the range of consecutive subnets in the network.", - Computed: true, - }, - "default_gateway": schema.StringAttribute{ - Description: "IPv4 address that establishes the Routing Interface where traffic is directed. It serves as the next hop in the Network.", - Computed: true, + CustomType: fwtypes.NewSetNestedObjectTypeOf[Ipv4Model](ctx), + Validators: []validator.Set{ + setvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + CustomType: fwtypes.NewObjectTypeOf[Ipv4Model](ctx), + Attributes: map[string]schema.Attribute{ + "primary": schema.StringAttribute{ + Description: "IPv4 address for the Primary Timing Master Server.", + Computed: true, + }, + "secondary": schema.StringAttribute{ + Description: "IPv4 address for the Secondary Timing Master Server.", + Computed: true, + }, + "network_mask": schema.StringAttribute{ + Description: "IPv4 address that defines the range of consecutive subnets in the network.", + Computed: true, + }, + "default_gateway": schema.StringAttribute{ + Description: "IPv4 address that establishes the Routing Interface where traffic is directed. It serves as the next hop in the Network.", + Computed: true, + }, }, }, }, @@ -156,11 +165,11 @@ func dataSourceSchema(ctx context.Context) schema.Schema { Description: "Represents the domain number associated with the PTP profile. This is used to differentiate multiple PTP networks within a single physical network.", Computed: true, }, - "priority1": schema.Int64Attribute{ + "priority_1": schema.Int64Attribute{ Description: "Specifies the priority level 1 for the clock. The value helps in determining the best clock in the PTP network. Lower values are considered higher priority.", Computed: true, }, - "priority2": schema.Int64Attribute{ + "priority_2": schema.Int64Attribute{ Description: "Specifies the priority level 2 for the clock. It acts as a tie-breaker if multiple clocks have the same priority 1 value. Lower values are considered higher priority.", Computed: true, }, @@ -195,13 +204,19 @@ func dataSourceSchema(ctx context.Context) schema.Schema { }, }, }, - "project": schema.SingleNestedBlock{ + "project": schema.SetNestedBlock{ Description: "An object that contains the Equinix Fabric project_id used for linking the Time Precision Service to a specific Equinix Fabric Project", - CustomType: fwtypes.NewObjectTypeOf[ProjectModel](ctx), - Attributes: map[string]schema.Attribute{ - "project_id": schema.StringAttribute{ - Description: "Equinix Fabric Project ID", - Computed: true, + CustomType: fwtypes.NewSetNestedObjectTypeOf[ProjectModel](ctx), + Validators: []validator.Set{ + setvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + CustomType: fwtypes.NewObjectTypeOf[ProjectModel](ctx), + Attributes: map[string]schema.Attribute{ + "project_id": schema.StringAttribute{ + Description: "Equinix Fabric Project ID", + Computed: true, + }, }, }, }, @@ -215,6 +230,36 @@ func dataSourceSchema(ctx context.Context) schema.Schema { Description: "Equinix Fabric Connection UUID; Precision Time Service will be connected with it", Computed: true, }, + "href": schema.StringAttribute{ + Description: "Link to the Equinix Fabric Connection associated with the Precision Time Service", + Computed: true, + }, + "type": schema.StringAttribute{ + Description: "Type of the Equinix Fabric Connection associated with the Precision Time Service", + Computed: true, + }, + }, + }, + }, + "account": schema.SingleNestedBlock{ + Description: "Equinix User Account associated with Precision Time Service", + CustomType: fwtypes.NewObjectTypeOf[AccountModel](ctx), + Attributes: map[string]schema.Attribute{ + "account_number": schema.Int64Attribute{ + Description: "Equinix User account number", + Computed: true, + }, + "is_reseller_account": schema.BoolAttribute{ + Description: "Equinix User Boolean flag indicating if it is a reseller account", + Computed: true, + }, + "org_id": schema.StringAttribute{ + Description: "Equinix User organization id", + Computed: true, + }, + "global_org_id": schema.StringAttribute{ + Description: "Equinix User global organization id", + Computed: true, }, }, }, diff --git a/internal/resources/fabric/precision_time/models.go b/internal/resources/fabric/precision_time/models.go index 634134781..62827fa11 100644 --- a/internal/resources/fabric/precision_time/models.go +++ b/internal/resources/fabric/precision_time/models.go @@ -17,12 +17,12 @@ type ResourceModel struct { Name types.String `tfsdk:"name"` Description types.String `tfsdk:"description"` State types.String `tfsdk:"state"` - Package types.Set `tfsdk:"package"` + Package fwtypes.SetNestedObjectValueOf[PackageModel] `tfsdk:"package"` Connections fwtypes.ListNestedObjectValueOf[ConnectionModel] `tfsdk:"connections"` - Ipv4 types.Set `tfsdk:"ipv4"` + Ipv4 fwtypes.SetNestedObjectValueOf[Ipv4Model] `tfsdk:"ipv4"` Account fwtypes.ObjectValueOf[AccountModel] `tfsdk:"account"` AdvanceConfiguration fwtypes.ObjectValueOf[AdvanceConfigurationModel] `tfsdk:"advance_configuration"` - Project types.Set `tfsdk:"project"` + Project fwtypes.SetNestedObjectValueOf[ProjectModel] `tfsdk:"project"` } type DataSourceModel struct { @@ -33,12 +33,12 @@ type DataSourceModel struct { Name types.String `tfsdk:"name"` Description types.String `tfsdk:"description"` State types.String `tfsdk:"state"` - Package types.Set `tfsdk:"package"` + Package fwtypes.SetNestedObjectValueOf[PackageModel] `tfsdk:"package"` Connections fwtypes.ListNestedObjectValueOf[ConnectionModel] `tfsdk:"connections"` - Ipv4 types.Set `tfsdk:"ipv4"` + Ipv4 fwtypes.SetNestedObjectValueOf[Ipv4Model] `tfsdk:"ipv4"` Account fwtypes.ObjectValueOf[AccountModel] `tfsdk:"account"` AdvanceConfiguration fwtypes.ObjectValueOf[AdvanceConfigurationModel] `tfsdk:"advance_configuration"` - Project types.Set `tfsdk:"project"` + Project fwtypes.SetNestedObjectValueOf[ProjectModel] `tfsdk:"project"` } type PackageModel struct { @@ -108,7 +108,9 @@ func (m *ResourceModel) parse(ctx context.Context, ept *fabricv4.PrecisionTimeSe diags = parsePrecisionTime(ctx, ept, &m.ID, &m.Type, &m.Href, &m.Uuid, &m.Name, &m.Description, &m.State, &m.Package, &m.Ipv4, &m.Project, - &m.Account, &m.AdvanceConfiguration, &m.Connections, + &m.Account, + &m.AdvanceConfiguration, + &m.Connections, ) return diags @@ -120,7 +122,9 @@ func (m *DataSourceModel) parse(ctx context.Context, ept *fabricv4.PrecisionTime diags = parsePrecisionTime(ctx, ept, &m.ID, &m.Type, &m.Href, &m.Uuid, &m.Name, &m.Description, &m.State, &m.Package, &m.Ipv4, &m.Project, - &m.Account, &m.AdvanceConfiguration, &m.Connections, + &m.Account, + &m.AdvanceConfiguration, + &m.Connections, ) return diags @@ -130,7 +134,9 @@ func parsePrecisionTime( ctx context.Context, ept *fabricv4.PrecisionTimeServiceCreateResponse, id, type_, href, uuid, name, description, state *basetypes.StringValue, - package_, ipv4, project *basetypes.SetValue, + package_ *fwtypes.SetNestedObjectValueOf[PackageModel], + ipv4 *fwtypes.SetNestedObjectValueOf[Ipv4Model], + project *fwtypes.SetNestedObjectValueOf[ProjectModel], account *fwtypes.ObjectValueOf[AccountModel], advanceConfiguration *fwtypes.ObjectValueOf[AdvanceConfigurationModel], connections *fwtypes.ListNestedObjectValueOf[ConnectionModel], @@ -189,15 +195,15 @@ func parsePrecisionTime( return diags } -func parsePackage(ctx context.Context, package_ *fabricv4.PrecisionTimePackageResponse) (basetypes.SetValue, diag.Diagnostics) { - var packageModel PackageModel +func parsePackage(ctx context.Context, package_ *fabricv4.PrecisionTimePackageResponse) (fwtypes.SetNestedObjectValueOf[PackageModel], diag.Diagnostics) { + packageModel := &PackageModel{} packageModel.Code = types.StringValue(string(package_.GetCode())) packageModel.Href = types.StringValue(package_.GetHref()) - packageSet, diags := basetypes.NewSetValueFrom(ctx, fwtypes.NewObjectTypeOf[PackageModel](ctx), []PackageModel{packageModel}) + packageSet, diags := fwtypes.NewSetNestedObjectValueOfPtr[PackageModel](ctx, packageModel) if diags.HasError() { - return basetypes.SetValue{}, diags + return fwtypes.NewSetNestedObjectValueOfNull[PackageModel](ctx), diags } return packageSet, nil @@ -218,47 +224,55 @@ func parseConnections(ctx context.Context, connections []fabricv4.FabricConnecti return fwtypes.NewListNestedObjectValueOfValueSlice(ctx, connectionModels), nil } -func parseIpv4(ctx context.Context, ipv4 *fabricv4.Ipv4) (basetypes.SetValue, diag.Diagnostics) { - var ipv4Model Ipv4Model +func parseIpv4(ctx context.Context, ipv4 *fabricv4.Ipv4) (fwtypes.SetNestedObjectValueOf[Ipv4Model], diag.Diagnostics) { + ipv4Model := &Ipv4Model{} ipv4Model.Primary = types.StringValue(ipv4.GetPrimary()) ipv4Model.Secondary = types.StringValue(ipv4.GetSecondary()) ipv4Model.DefaultGateway = types.StringValue(ipv4.GetDefaultGateway()) ipv4Model.NetworkMask = types.StringValue(ipv4.GetNetworkMask()) - ipv4Set, diags := basetypes.NewSetValueFrom(ctx, fwtypes.NewObjectTypeOf[Ipv4Model](ctx), []Ipv4Model{ipv4Model}) + ipv4Set, diags := fwtypes.NewSetNestedObjectValueOfPtr[Ipv4Model](ctx, ipv4Model) if diags.HasError() { - return basetypes.SetValue{}, diags + return fwtypes.NewSetNestedObjectValueOfNull[Ipv4Model](ctx), diags } return ipv4Set, nil } func parseAccount(ctx context.Context, account *fabricv4.Account) (fwtypes.ObjectValueOf[AccountModel], diag.Diagnostics) { - var accountModel *AccountModel + accountModel := &AccountModel{} - accountModel.AccountNumber = types.Int64Value(int64(account.GetAccountNumber())) - accountModel.IsResellerAccount = types.BoolValue(account.GetIsResellerAccount()) - accountModel.OrgId = types.StringValue(account.GetOrgId()) - accountModel.GlobalOrgId = types.StringValue(account.GetGlobalOrgId()) + if account.GetAccountNumber() != 0 { + accountModel.AccountNumber = types.Int64Value(int64(account.GetAccountNumber())) + } + if account.IsResellerAccount != nil { + accountModel.IsResellerAccount = types.BoolValue(account.GetIsResellerAccount()) + } + if account.OrgId != nil { + accountModel.OrgId = types.StringValue(account.GetOrgId()) + } + if account.GlobalOrgId != nil { + accountModel.GlobalOrgId = types.StringValue(account.GetGlobalOrgId()) + } return fwtypes.NewObjectValueOf[AccountModel](ctx, accountModel), nil } func parseAdvanceConfiguration(ctx context.Context, advConfig *fabricv4.AdvanceConfiguration) (fwtypes.ObjectValueOf[AdvanceConfigurationModel], diag.Diagnostics) { var diags diag.Diagnostics - var advConfigModel *AdvanceConfigurationModel + advConfigModel := &AdvanceConfigurationModel{} md5s, diags := parseNtp(ctx, advConfig.GetNtp()) if diags.HasError() { - return fwtypes.NewObjectValueOf[AdvanceConfigurationModel](ctx, &AdvanceConfigurationModel{}), diags + return fwtypes.NewObjectValueOfNull[AdvanceConfigurationModel](ctx), diags } advConfigModel.Ntp = md5s ptp := advConfig.GetPtp() parsedPtp, diags := parsePtp(ctx, &ptp) if diags.HasError() { - return fwtypes.NewObjectValueOf[AdvanceConfigurationModel](ctx, &AdvanceConfigurationModel{}), diags + return fwtypes.NewObjectValueOfNull[AdvanceConfigurationModel](ctx), diags } advConfigModel.Ptp = parsedPtp @@ -280,7 +294,7 @@ func parseNtp(ctx context.Context, ntp []fabricv4.Md5) (fwtypes.ListNestedObject } func parsePtp(ctx context.Context, ptp *fabricv4.PtpAdvanceConfiguration) (fwtypes.ObjectValueOf[PTPModel], diag.Diagnostics) { - var ptpModel *PTPModel + ptpModel := &PTPModel{} ptpModel.TimeScale = types.StringValue(string(ptp.GetTimeScale())) ptpModel.Domain = types.Int64Value(int64(ptp.GetDomain())) @@ -296,14 +310,14 @@ func parsePtp(ctx context.Context, ptp *fabricv4.PtpAdvanceConfiguration) (fwtyp } -func parseProject(ctx context.Context, project *fabricv4.Project) (basetypes.SetValue, diag.Diagnostics) { - var projectModel ProjectModel +func parseProject(ctx context.Context, project *fabricv4.Project) (fwtypes.SetNestedObjectValueOf[ProjectModel], diag.Diagnostics) { + projectModel := &ProjectModel{} projectModel.ProjectId = types.StringValue(project.GetProjectId()) - projectSet, diags := basetypes.NewSetValueFrom(ctx, fwtypes.NewObjectTypeOf[ProjectModel](ctx), []ProjectModel{projectModel}) + projectSet, diags := fwtypes.NewSetNestedObjectValueOfPtr[ProjectModel](ctx, projectModel) if diags.HasError() { - return basetypes.SetValue{}, diags + return fwtypes.NewSetNestedObjectValueOfNull[ProjectModel](ctx), diags } return projectSet, nil diff --git a/internal/resources/fabric/precision_time/resource.go b/internal/resources/fabric/precision_time/resource.go index 704bd20a5..0bd881d34 100644 --- a/internal/resources/fabric/precision_time/resource.go +++ b/internal/resources/fabric/precision_time/resource.go @@ -63,7 +63,7 @@ func (r *Resource) Create( return } - ept, _, err := client.PrecisionTimeApi.CreateTimeServices(context.Background()).PrecisionTimeServiceRequest(createRequest).Execute() + ept, _, err := client.PrecisionTimeApi.CreateTimeServices(ctx).PrecisionTimeServiceRequest(createRequest).Execute() if err != nil { resp.Diagnostics.AddError( "Error creating Precision Time Service", @@ -157,48 +157,6 @@ func (r *Resource) Update( Value: plan.Name.ValueString(), }) } - if !state.Ipv4.Equal(plan.Ipv4) { - ipv4Set := make([]fabricv4.Ipv4, 1) - diags := plan.Ipv4.ElementsAs(ctx, &ipv4Set, true) - ipv4 := ipv4Set[0] - if diags.HasError() { - resp.Diagnostics.Append(diags...) - return - } - updateRequest = append(updateRequest, fabricv4.PrecisionTimeChangeOperation{ - Op: fabricv4.PRECISIONTIMECHANGEOPERATIONOP_REPLACE, - Path: fabricv4.PRECISIONTIMECHANGEOPERATIONPATH_NAME, - Value: ipv4, - }) - } - if !state.AdvanceConfiguration.Equal(plan.AdvanceConfiguration) { - stateAdvConfig := fabricv4.AdvanceConfiguration{} - diags := state.AdvanceConfiguration.As(ctx, &stateAdvConfig, basetypes.ObjectAsOptions{}) - if diags.HasError() { - resp.Diagnostics.Append(diags...) - return - } - planAdvConfig := fabricv4.AdvanceConfiguration{} - diags = plan.AdvanceConfiguration.As(ctx, &planAdvConfig, basetypes.ObjectAsOptions{}) - if diags.HasError() { - resp.Diagnostics.Append(diags...) - return - } - if !reflect.DeepEqual(stateAdvConfig.Ntp, planAdvConfig.Ntp) { - updateRequest = append(updateRequest, fabricv4.PrecisionTimeChangeOperation{ - Op: fabricv4.PRECISIONTIMECHANGEOPERATIONOP_REPLACE, - Path: fabricv4.PRECISIONTIMECHANGEOPERATIONPATH_NAME, - Value: planAdvConfig.Ntp, - }) - } - if !reflect.DeepEqual(stateAdvConfig.Ptp, planAdvConfig.Ptp) { - updateRequest = append(updateRequest, fabricv4.PrecisionTimeChangeOperation{ - Op: fabricv4.PRECISIONTIMECHANGEOPERATIONOP_REPLACE, - Path: fabricv4.PRECISIONTIMECHANGEOPERATIONPATH_NAME, - Value: planAdvConfig.Ntp, - }) - } - } if !state.Package.Equal(plan.Package) { packageSet := make([]PackageModel, 1) diags := plan.Package.ElementsAs(ctx, &packageSet, true) @@ -209,11 +167,16 @@ func (r *Resource) Update( } updateRequest = append(updateRequest, fabricv4.PrecisionTimeChangeOperation{ Op: fabricv4.PRECISIONTIMECHANGEOPERATIONOP_REPLACE, - Path: fabricv4.PRECISIONTIMECHANGEOPERATIONPATH_NAME, + Path: fabricv4.PRECISIONTIMECHANGEOPERATIONPATH_PACKAGE, Value: packageModel.Code.ValueString(), }) } + if len(updateRequest) > 1 { + resp.Diagnostics.AddError("Error updating Precision Time Service", + "This resource only accepts one attribute change at a time; please reduce changes and try again") + return + } for _, update := range updateRequest { if !reflect.DeepEqual(updateRequest, fabricv4.PrecisionTimeChangeOperation{}) { _, _, err := client.PrecisionTimeApi.UpdateTimeServicesById(ctx, id). @@ -341,13 +304,19 @@ func buildCreateRequest(ctx context.Context, plan ResourceModel) (fabricv4.Preci request.SetIpv4(ipv4) advConfigModel := AdvanceConfigurationModel{} - diags = plan.AdvanceConfiguration.As(ctx, &advConfigModel, basetypes.ObjectAsOptions{}) + diags = plan.AdvanceConfiguration.As(ctx, &advConfigModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) if diags.HasError() { return fabricv4.PrecisionTimeServiceRequest{}, diags } ptpModel := PTPModel{} - diags = advConfigModel.Ptp.As(ctx, &ptpModel, basetypes.ObjectAsOptions{}) + diags = advConfigModel.Ptp.As(ctx, &ptpModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) if diags.HasError() { return fabricv4.PrecisionTimeServiceRequest{}, diags } @@ -401,9 +370,15 @@ func buildCreateRequest(ctx context.Context, plan ResourceModel) (fabricv4.Preci } advConfig := fabricv4.AdvanceConfiguration{} - advConfig.SetNtp(ntps) - advConfig.SetPtp(ptp) - request.SetAdvanceConfiguration(advConfig) + if len(ntps) > 0 { + advConfig.SetNtp(ntps) + } + if !reflect.DeepEqual(ptp, fabricv4.PtpAdvanceConfiguration{}) { + advConfig.SetPtp(ptp) + } + if !reflect.DeepEqual(advConfig, fabricv4.AdvanceConfiguration{}) { + request.SetAdvanceConfiguration(advConfig) + } projectSet := make([]ProjectModel, 1) diags = plan.Project.ElementsAs(ctx, &projectSet, true) diff --git a/internal/resources/fabric/precision_time/resource_schema.go b/internal/resources/fabric/precision_time/resource_schema.go index 4ed59407c..b7694b22f 100644 --- a/internal/resources/fabric/precision_time/resource_schema.go +++ b/internal/resources/fabric/precision_time/resource_schema.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier" @@ -70,6 +71,7 @@ func resourceSchema(ctx context.Context) schema.Schema { Blocks: map[string]schema.Block{ "package": schema.SetNestedBlock{ Description: "Precision Time Service Package Details", + CustomType: fwtypes.NewSetNestedObjectTypeOf[PackageModel](ctx), Validators: []validator.Set{ setvalidator.SizeAtMost(1), }, @@ -78,6 +80,9 @@ func resourceSchema(ctx context.Context) schema.Schema { }, NestedObject: schema.NestedBlockObject{ CustomType: fwtypes.NewObjectTypeOf[PackageModel](ctx), + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.UseStateForUnknown(), + }, Attributes: map[string]schema.Attribute{ "code": schema.StringAttribute{ Description: "Time Precision Package Code for the desired billing package", @@ -140,6 +145,7 @@ func resourceSchema(ctx context.Context) schema.Schema { }, "ipv4": schema.SetNestedBlock{ Description: "An object that has Network IP Configurations for Timing Master Servers.", + CustomType: fwtypes.NewSetNestedObjectTypeOf[Ipv4Model](ctx), Validators: []validator.Set{ setvalidator.SizeAtMost(1), }, @@ -184,6 +190,9 @@ func resourceSchema(ctx context.Context) schema.Schema { Computed: true, CustomType: fwtypes.NewListNestedObjectTypeOf[MD5Model](ctx), ElementType: fwtypes.NewObjectTypeOf[MD5Model](ctx), + PlanModifiers: []planmodifier.List{ + listplanmodifier.UseStateForUnknown(), + }, }, }, Blocks: map[string]schema.Block{ @@ -258,6 +267,7 @@ func resourceSchema(ctx context.Context) schema.Schema { }, "project": schema.SetNestedBlock{ Description: "An object that contains the Equinix Fabric project_id used for linking the Time Precision Service to a specific Equinix Fabric Project", + CustomType: fwtypes.NewSetNestedObjectTypeOf[ProjectModel](ctx), Validators: []validator.Set{ setvalidator.SizeAtMost(1), }, @@ -266,6 +276,9 @@ func resourceSchema(ctx context.Context) schema.Schema { }, NestedObject: schema.NestedBlockObject{ CustomType: fwtypes.NewObjectTypeOf[ProjectModel](ctx), + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.UseStateForUnknown(), + }, Attributes: map[string]schema.Attribute{ "project_id": schema.StringAttribute{ Description: "Equinix Fabric Project ID", @@ -298,6 +311,9 @@ func resourceSchema(ctx context.Context) schema.Schema { "account": schema.SingleNestedBlock{ Description: "Equinix User Account associated with Precision Time Service", CustomType: fwtypes.NewObjectTypeOf[AccountModel](ctx), + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.UseStateForUnknown(), + }, Attributes: map[string]schema.Attribute{ "account_number": schema.Int64Attribute{ Description: "Equinix User account number", diff --git a/templates/data-sources/fabric_precision_time.md.tmpl b/templates/data-sources/fabric_precision_time.md.tmpl new file mode 100644 index 000000000..bc9cf2826 --- /dev/null +++ b/templates/data-sources/fabric_precision_time.md.tmpl @@ -0,0 +1,22 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "equinix_fabric_precision_time Data Source - terraform-provider-equinix" +subcategory: "Fabric" +description: |- + Fabric V4 API compatible data resource that allow user to fetch Equinix Fabric Precision Time Service by Uuid +--- + +# equinix_fabric_precision_time (Data Source) + +Fabric V4 API compatible data resource that allow user to fetch Equinix Fabric Precision Time Service by Uuid + +Additional documentation: +* Getting Started: https://docs.equinix.com/en-us/Content/Edge-Services/EPT/EPT.htm +* API: https://developer.equinix.com/dev-docs/fabric/api-reference/fabric-v4-apis#precision-time + +## Example Usage + +{{tffile "examples/data-sources/fabric_precision_time/get_by_uuid.tf"}} + + +{{ .SchemaMarkdown }} diff --git a/templates/resources/fabric_precision_time.md.tmpl b/templates/resources/fabric_precision_time.md.tmpl new file mode 100644 index 000000000..c4eb3f0c0 --- /dev/null +++ b/templates/resources/fabric_precision_time.md.tmpl @@ -0,0 +1,25 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "equinix_fabric_precision_time Resource - terraform-provider-equinix" +subcategory: "Fabric" +description: |- + Fabric V4 API compatible resource allows creation and management of Equinix Fabric Precision Time +--- + +# equinix_fabric_precision_time (Resource) + +Fabric V4 API compatible resource allows creation and management of Equinix Fabric Precision Time Services + +Additional documentation: +* Getting Started: https://docs.equinix.com/en-us/Content/Edge-Services/EPT/EPT.htm +* API: https://developer.equinix.com/dev-docs/fabric/api-reference/fabric-v4-apis#precision-time + +## Example Usage + +PTP Configuration: +{{tffile "examples/resources/fabric_precision_time/ptp_time_service.tf"}} + +NTP Configuration: +{{tffile "examples/resources/fabric_precision_time/ntp_time_service.tf"}} + +{{ .SchemaMarkdown }} From 04edb458f4d40bc9eff7cfd73f6410c71e50a42a Mon Sep 17 00:00:00 2001 From: Tim Hogarty Date: Wed, 31 Jul 2024 15:24:32 -0700 Subject: [PATCH 05/12] Update set blocks to single nested blocks with no required attributes --- .../precision_time/datasources_schema.go | 159 ++++++------- .../resources/fabric/precision_time/models.go | 46 ++-- .../fabric/precision_time/resource.go | 45 ++-- .../fabric/precision_time/resource_schema.go | 218 +++++++----------- 4 files changed, 192 insertions(+), 276 deletions(-) diff --git a/internal/resources/fabric/precision_time/datasources_schema.go b/internal/resources/fabric/precision_time/datasources_schema.go index 4c3faf8d9..57bd940f3 100644 --- a/internal/resources/fabric/precision_time/datasources_schema.go +++ b/internal/resources/fabric/precision_time/datasources_schema.go @@ -6,7 +6,6 @@ import ( "github.com/equinix/equinix-sdk-go/services/fabricv4" "github.com/equinix/terraform-provider-equinix/internal/framework" fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" - "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/schema/validator" @@ -51,87 +50,75 @@ func dataSourceSchema(ctx context.Context) schema.Schema { }, }, Blocks: map[string]schema.Block{ - "package": schema.SetNestedBlock{ + "package": schema.SingleNestedBlock{ Description: "Precision Time Service Package Details", - CustomType: fwtypes.NewSetNestedObjectTypeOf[PackageModel](ctx), - Validators: []validator.Set{ - setvalidator.SizeAtMost(1), - }, - NestedObject: schema.NestedBlockObject{ - CustomType: fwtypes.NewObjectTypeOf[PackageModel](ctx), - Attributes: map[string]schema.Attribute{ - "code": schema.StringAttribute{ - Description: "Time Precision Package Code for the desired billing package", - Computed: true, - }, - "href": schema.StringAttribute{ - Description: "Time Precision Package HREF link to corresponding resource in Equinix Portal", - Computed: true, - }, - "type": schema.StringAttribute{ - Description: "Type of the Precision Time Service Package", - Computed: true, - }, - "bandwidth": schema.Int64Attribute{ - Description: "Bandwidth of the Precision Time Service", - Computed: true, - }, - "clients_per_second_max": schema.Int64Attribute{ - Description: "Maximum clients available per second for the Precision Time Service", - Computed: true, - }, - "redundancy_supported": schema.BoolAttribute{ - Description: "Boolean flag indicating if this Precision Time Service supports redundancy", - Computed: true, - }, - "multi_subnet_supported": schema.BoolAttribute{ - Description: "Boolean flag indicating if this Precision Time Service supports multi subnetting", - Computed: true, - }, - "accuracy_unit": schema.StringAttribute{ - Description: "Time unit of accuracy for the Precision Time Service; e.g. microseconds", - Computed: true, - }, - "accuracy_sla": schema.Int64Attribute{ - Description: "SLA for the accuracy provided by the Precision Time Service", - Computed: true, - }, - "accuracy_avg_min": schema.Int64Attribute{ - Description: "Average minimum accuracy provided by the Precision Time Service", - Computed: true, - }, - "accuracy_avg_max": schema.Int64Attribute{ - Description: "Average maximum accuracy provided by the Precision Time Service", - Computed: true, - }, + CustomType: fwtypes.NewObjectTypeOf[PackageModel](ctx), + Attributes: map[string]schema.Attribute{ + "code": schema.StringAttribute{ + Description: "Time Precision Package Code for the desired billing package", + Computed: true, + }, + "href": schema.StringAttribute{ + Description: "Time Precision Package HREF link to corresponding resource in Equinix Portal", + Computed: true, + }, + "type": schema.StringAttribute{ + Description: "Type of the Precision Time Service Package", + Computed: true, + }, + "bandwidth": schema.Int64Attribute{ + Description: "Bandwidth of the Precision Time Service", + Computed: true, + }, + "clients_per_second_max": schema.Int64Attribute{ + Description: "Maximum clients available per second for the Precision Time Service", + Computed: true, + }, + "redundancy_supported": schema.BoolAttribute{ + Description: "Boolean flag indicating if this Precision Time Service supports redundancy", + Computed: true, + }, + "multi_subnet_supported": schema.BoolAttribute{ + Description: "Boolean flag indicating if this Precision Time Service supports multi subnetting", + Computed: true, + }, + "accuracy_unit": schema.StringAttribute{ + Description: "Time unit of accuracy for the Precision Time Service; e.g. microseconds", + Computed: true, + }, + "accuracy_sla": schema.Int64Attribute{ + Description: "SLA for the accuracy provided by the Precision Time Service", + Computed: true, + }, + "accuracy_avg_min": schema.Int64Attribute{ + Description: "Average minimum accuracy provided by the Precision Time Service", + Computed: true, + }, + "accuracy_avg_max": schema.Int64Attribute{ + Description: "Average maximum accuracy provided by the Precision Time Service", + Computed: true, }, }, }, - "ipv4": schema.SetNestedBlock{ + "ipv4": schema.SingleNestedBlock{ Description: "An object that has Network IP Configurations for Timing Master Servers.", - CustomType: fwtypes.NewSetNestedObjectTypeOf[Ipv4Model](ctx), - Validators: []validator.Set{ - setvalidator.SizeAtMost(1), - }, - NestedObject: schema.NestedBlockObject{ - CustomType: fwtypes.NewObjectTypeOf[Ipv4Model](ctx), - Attributes: map[string]schema.Attribute{ - "primary": schema.StringAttribute{ - Description: "IPv4 address for the Primary Timing Master Server.", - Computed: true, - }, - "secondary": schema.StringAttribute{ - Description: "IPv4 address for the Secondary Timing Master Server.", - Computed: true, - }, - "network_mask": schema.StringAttribute{ - Description: "IPv4 address that defines the range of consecutive subnets in the network.", - Computed: true, - }, - "default_gateway": schema.StringAttribute{ - Description: "IPv4 address that establishes the Routing Interface where traffic is directed. It serves as the next hop in the Network.", - Computed: true, - }, + CustomType: fwtypes.NewObjectTypeOf[Ipv4Model](ctx), + Attributes: map[string]schema.Attribute{ + "primary": schema.StringAttribute{ + Description: "IPv4 address for the Primary Timing Master Server.", + Computed: true, + }, + "secondary": schema.StringAttribute{ + Description: "IPv4 address for the Secondary Timing Master Server.", + Computed: true, + }, + "network_mask": schema.StringAttribute{ + Description: "IPv4 address that defines the range of consecutive subnets in the network.", + Computed: true, + }, + "default_gateway": schema.StringAttribute{ + Description: "IPv4 address that establishes the Routing Interface where traffic is directed. It serves as the next hop in the Network.", + Computed: true, }, }, }, @@ -204,19 +191,13 @@ func dataSourceSchema(ctx context.Context) schema.Schema { }, }, }, - "project": schema.SetNestedBlock{ + "project": schema.SingleNestedBlock{ Description: "An object that contains the Equinix Fabric project_id used for linking the Time Precision Service to a specific Equinix Fabric Project", - CustomType: fwtypes.NewSetNestedObjectTypeOf[ProjectModel](ctx), - Validators: []validator.Set{ - setvalidator.SizeAtMost(1), - }, - NestedObject: schema.NestedBlockObject{ - CustomType: fwtypes.NewObjectTypeOf[ProjectModel](ctx), - Attributes: map[string]schema.Attribute{ - "project_id": schema.StringAttribute{ - Description: "Equinix Fabric Project ID", - Computed: true, - }, + CustomType: fwtypes.NewObjectTypeOf[ProjectModel](ctx), + Attributes: map[string]schema.Attribute{ + "project_id": schema.StringAttribute{ + Description: "Equinix Fabric Project ID", + Computed: true, }, }, }, diff --git a/internal/resources/fabric/precision_time/models.go b/internal/resources/fabric/precision_time/models.go index 62827fa11..9da38a679 100644 --- a/internal/resources/fabric/precision_time/models.go +++ b/internal/resources/fabric/precision_time/models.go @@ -17,12 +17,12 @@ type ResourceModel struct { Name types.String `tfsdk:"name"` Description types.String `tfsdk:"description"` State types.String `tfsdk:"state"` - Package fwtypes.SetNestedObjectValueOf[PackageModel] `tfsdk:"package"` + Package fwtypes.ObjectValueOf[PackageModel] `tfsdk:"package"` Connections fwtypes.ListNestedObjectValueOf[ConnectionModel] `tfsdk:"connections"` - Ipv4 fwtypes.SetNestedObjectValueOf[Ipv4Model] `tfsdk:"ipv4"` + Ipv4 fwtypes.ObjectValueOf[Ipv4Model] `tfsdk:"ipv4"` Account fwtypes.ObjectValueOf[AccountModel] `tfsdk:"account"` AdvanceConfiguration fwtypes.ObjectValueOf[AdvanceConfigurationModel] `tfsdk:"advance_configuration"` - Project fwtypes.SetNestedObjectValueOf[ProjectModel] `tfsdk:"project"` + Project fwtypes.ObjectValueOf[ProjectModel] `tfsdk:"project"` } type DataSourceModel struct { @@ -33,12 +33,12 @@ type DataSourceModel struct { Name types.String `tfsdk:"name"` Description types.String `tfsdk:"description"` State types.String `tfsdk:"state"` - Package fwtypes.SetNestedObjectValueOf[PackageModel] `tfsdk:"package"` + Package fwtypes.ObjectValueOf[PackageModel] `tfsdk:"package"` Connections fwtypes.ListNestedObjectValueOf[ConnectionModel] `tfsdk:"connections"` - Ipv4 fwtypes.SetNestedObjectValueOf[Ipv4Model] `tfsdk:"ipv4"` + Ipv4 fwtypes.ObjectValueOf[Ipv4Model] `tfsdk:"ipv4"` Account fwtypes.ObjectValueOf[AccountModel] `tfsdk:"account"` AdvanceConfiguration fwtypes.ObjectValueOf[AdvanceConfigurationModel] `tfsdk:"advance_configuration"` - Project fwtypes.SetNestedObjectValueOf[ProjectModel] `tfsdk:"project"` + Project fwtypes.ObjectValueOf[ProjectModel] `tfsdk:"project"` } type PackageModel struct { @@ -134,9 +134,9 @@ func parsePrecisionTime( ctx context.Context, ept *fabricv4.PrecisionTimeServiceCreateResponse, id, type_, href, uuid, name, description, state *basetypes.StringValue, - package_ *fwtypes.SetNestedObjectValueOf[PackageModel], - ipv4 *fwtypes.SetNestedObjectValueOf[Ipv4Model], - project *fwtypes.SetNestedObjectValueOf[ProjectModel], + package_ *fwtypes.ObjectValueOf[PackageModel], + ipv4 *fwtypes.ObjectValueOf[Ipv4Model], + project *fwtypes.ObjectValueOf[ProjectModel], account *fwtypes.ObjectValueOf[AccountModel], advanceConfiguration *fwtypes.ObjectValueOf[AdvanceConfigurationModel], connections *fwtypes.ListNestedObjectValueOf[ConnectionModel], @@ -195,19 +195,13 @@ func parsePrecisionTime( return diags } -func parsePackage(ctx context.Context, package_ *fabricv4.PrecisionTimePackageResponse) (fwtypes.SetNestedObjectValueOf[PackageModel], diag.Diagnostics) { +func parsePackage(ctx context.Context, package_ *fabricv4.PrecisionTimePackageResponse) (fwtypes.ObjectValueOf[PackageModel], diag.Diagnostics) { packageModel := &PackageModel{} packageModel.Code = types.StringValue(string(package_.GetCode())) packageModel.Href = types.StringValue(package_.GetHref()) - packageSet, diags := fwtypes.NewSetNestedObjectValueOfPtr[PackageModel](ctx, packageModel) - if diags.HasError() { - return fwtypes.NewSetNestedObjectValueOfNull[PackageModel](ctx), diags - } - - return packageSet, nil - + return fwtypes.NewObjectValueOf[PackageModel](ctx, packageModel), nil } func parseConnections(ctx context.Context, connections []fabricv4.FabricConnectionUuid) (fwtypes.ListNestedObjectValueOf[ConnectionModel], diag.Diagnostics) { @@ -224,7 +218,7 @@ func parseConnections(ctx context.Context, connections []fabricv4.FabricConnecti return fwtypes.NewListNestedObjectValueOfValueSlice(ctx, connectionModels), nil } -func parseIpv4(ctx context.Context, ipv4 *fabricv4.Ipv4) (fwtypes.SetNestedObjectValueOf[Ipv4Model], diag.Diagnostics) { +func parseIpv4(ctx context.Context, ipv4 *fabricv4.Ipv4) (fwtypes.ObjectValueOf[Ipv4Model], diag.Diagnostics) { ipv4Model := &Ipv4Model{} ipv4Model.Primary = types.StringValue(ipv4.GetPrimary()) @@ -232,12 +226,7 @@ func parseIpv4(ctx context.Context, ipv4 *fabricv4.Ipv4) (fwtypes.SetNestedObjec ipv4Model.DefaultGateway = types.StringValue(ipv4.GetDefaultGateway()) ipv4Model.NetworkMask = types.StringValue(ipv4.GetNetworkMask()) - ipv4Set, diags := fwtypes.NewSetNestedObjectValueOfPtr[Ipv4Model](ctx, ipv4Model) - if diags.HasError() { - return fwtypes.NewSetNestedObjectValueOfNull[Ipv4Model](ctx), diags - } - - return ipv4Set, nil + return fwtypes.NewObjectValueOf[Ipv4Model](ctx, ipv4Model), nil } func parseAccount(ctx context.Context, account *fabricv4.Account) (fwtypes.ObjectValueOf[AccountModel], diag.Diagnostics) { @@ -310,15 +299,10 @@ func parsePtp(ctx context.Context, ptp *fabricv4.PtpAdvanceConfiguration) (fwtyp } -func parseProject(ctx context.Context, project *fabricv4.Project) (fwtypes.SetNestedObjectValueOf[ProjectModel], diag.Diagnostics) { +func parseProject(ctx context.Context, project *fabricv4.Project) (fwtypes.ObjectValueOf[ProjectModel], diag.Diagnostics) { projectModel := &ProjectModel{} projectModel.ProjectId = types.StringValue(project.GetProjectId()) - projectSet, diags := fwtypes.NewSetNestedObjectValueOfPtr[ProjectModel](ctx, projectModel) - if diags.HasError() { - return fwtypes.NewSetNestedObjectValueOfNull[ProjectModel](ctx), diags - } - - return projectSet, nil + return fwtypes.NewObjectValueOf[ProjectModel](ctx, projectModel), nil } diff --git a/internal/resources/fabric/precision_time/resource.go b/internal/resources/fabric/precision_time/resource.go index 0bd881d34..0ac323bd9 100644 --- a/internal/resources/fabric/precision_time/resource.go +++ b/internal/resources/fabric/precision_time/resource.go @@ -158,9 +158,11 @@ func (r *Resource) Update( }) } if !state.Package.Equal(plan.Package) { - packageSet := make([]PackageModel, 1) - diags := plan.Package.ElementsAs(ctx, &packageSet, true) - packageModel := packageSet[0] + packageModel := PackageModel{} + diags := plan.Package.As(ctx, &packageModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) if diags.HasError() { resp.Diagnostics.Append(diags...) return @@ -254,9 +256,15 @@ func buildCreateRequest(ctx context.Context, plan ResourceModel) (fabricv4.Preci Name: plan.Name.ValueString(), } - packageSet := make([]PackageModel, 1) - diags = plan.Package.ElementsAs(ctx, &packageSet, true) - packageModel := packageSet[0] + if plan.Description.ValueString() != "" { + request.SetDescription(plan.Description.ValueString()) + } + + packageModel := PackageModel{} + diags = plan.Package.As(ctx, &packageModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) if diags.HasError() { return fabricv4.PrecisionTimeServiceRequest{}, diags } @@ -289,9 +297,11 @@ func buildCreateRequest(ctx context.Context, plan ResourceModel) (fabricv4.Preci } request.SetConnections(connections) - ipv4Set := make([]Ipv4Model, 1) - diags = plan.Ipv4.ElementsAs(ctx, &ipv4Set, true) - ipv4Model := ipv4Set[0] + ipv4Model := Ipv4Model{} + diags = plan.Ipv4.As(ctx, &ipv4Model, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) if diags.HasError() { return fabricv4.PrecisionTimeServiceRequest{}, diags } @@ -380,12 +390,11 @@ func buildCreateRequest(ctx context.Context, plan ResourceModel) (fabricv4.Preci request.SetAdvanceConfiguration(advConfig) } - projectSet := make([]ProjectModel, 1) - diags = plan.Project.ElementsAs(ctx, &projectSet, true) projectModel := ProjectModel{} - if len(projectSet) > 0 { - projectModel = projectSet[0] - } + diags = plan.Project.As(ctx, &projectModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) if diags.HasError() { return fabricv4.PrecisionTimeServiceRequest{}, diags } @@ -430,11 +439,11 @@ func getDeleteWaiter(ctx context.Context, client *fabricv4.APIClient, id string, // to indicate that the Precision Time Service appears to be deleted successfully based on // status code deletedMarker := "tf-marker-for-deleted-precision-time-service" - - target := []string{deletedMarker, "deleted"} - return &retry.StateChangeConf{ - Target: target, + Target: []string{ + deletedMarker, + string(fabricv4.PRECISIONTIMESERVICECREATERESPONSESTATE_DEPROVISIONED), + }, Refresh: func() (interface{}, string, error) { ept, resp, err := client.PrecisionTimeApi.GetTimeServicesById(ctx, id).Execute() if err != nil { diff --git a/internal/resources/fabric/precision_time/resource_schema.go b/internal/resources/fabric/precision_time/resource_schema.go index b7694b22f..d616799ec 100644 --- a/internal/resources/fabric/precision_time/resource_schema.go +++ b/internal/resources/fabric/precision_time/resource_schema.go @@ -6,14 +6,10 @@ import ( "github.com/equinix/equinix-sdk-go/services/fabricv4" "github.com/equinix/terraform-provider-equinix/internal/framework" fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" - "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" ) @@ -24,16 +20,10 @@ func resourceSchema(ctx context.Context) schema.Schema { "uuid": schema.StringAttribute{ Description: "Equinix generated id for the Precision Time Service", Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, }, "href": schema.StringAttribute{ Description: "Equinix generated Portal link for the created Precision Time Service", Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, }, "type": schema.StringAttribute{ Description: "Choose type of Precision Time Service", @@ -55,125 +45,91 @@ func resourceSchema(ctx context.Context) schema.Schema { "description": schema.StringAttribute{ Description: "Optional description of time service", Optional: true, - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, }, "state": schema.StringAttribute{ Description: fmt.Sprintf("Indicator of the state of this Precision Time Service. One of: [%v]", fabricv4.AllowedPrecisionTimeServiceCreateResponseStateEnumValues), Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, }, }, Blocks: map[string]schema.Block{ - "package": schema.SetNestedBlock{ + "package": schema.SingleNestedBlock{ Description: "Precision Time Service Package Details", - CustomType: fwtypes.NewSetNestedObjectTypeOf[PackageModel](ctx), - Validators: []validator.Set{ - setvalidator.SizeAtMost(1), - }, - PlanModifiers: []planmodifier.Set{ - setplanmodifier.UseStateForUnknown(), - }, - NestedObject: schema.NestedBlockObject{ - CustomType: fwtypes.NewObjectTypeOf[PackageModel](ctx), - PlanModifiers: []planmodifier.Object{ - objectplanmodifier.UseStateForUnknown(), - }, - Attributes: map[string]schema.Attribute{ - "code": schema.StringAttribute{ - Description: "Time Precision Package Code for the desired billing package", - Required: true, - Validators: []validator.String{ - stringvalidator.OneOf( - string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_NTP_STANDARD), - string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_NTP_ENTERPRISE), - string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_PTP_STANDARD), - string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_PTP_ENTERPRISE), - ), - }, - }, - "href": schema.StringAttribute{ - Description: "Time Precision Package HREF link to corresponding resource in Equinix Portal", - Optional: true, - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "type": schema.StringAttribute{ - Description: "Type of the Precision Time Service Package", - Computed: true, - }, - "bandwidth": schema.Int64Attribute{ - Description: "Bandwidth of the Precision Time Service", - Computed: true, - }, - "clients_per_second_max": schema.Int64Attribute{ - Description: "Maximum clients available per second for the Precision Time Service", - Computed: true, - }, - "redundancy_supported": schema.BoolAttribute{ - Description: "Boolean flag indicating if this Precision Time Service supports redundancy", - Computed: true, - }, - "multi_subnet_supported": schema.BoolAttribute{ - Description: "Boolean flag indicating if this Precision Time Service supports multi subnetting", - Computed: true, - }, - "accuracy_unit": schema.StringAttribute{ - Description: "Time unit of accuracy for the Precision Time Service; e.g. microseconds", - Computed: true, - }, - "accuracy_sla": schema.Int64Attribute{ - Description: "SLA for the accuracy provided by the Precision Time Service", - Computed: true, - }, - "accuracy_avg_min": schema.Int64Attribute{ - Description: "Average minimum accuracy provided by the Precision Time Service", - Computed: true, - }, - "accuracy_avg_max": schema.Int64Attribute{ - Description: "Average maximum accuracy provided by the Precision Time Service", - Computed: true, + CustomType: fwtypes.NewObjectTypeOf[PackageModel](ctx), + Attributes: map[string]schema.Attribute{ + "code": schema.StringAttribute{ + Description: "Time Precision Package Code for the desired billing package", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf( + string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_NTP_STANDARD), + string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_NTP_ENTERPRISE), + string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_PTP_STANDARD), + string(fabricv4.GETTIMESERVICESPACKAGEBYCODEPACKAGECODEPARAMETER_PTP_ENTERPRISE), + ), }, }, + "href": schema.StringAttribute{ + Description: "Time Precision Package HREF link to corresponding resource in Equinix Portal", + Optional: true, + Computed: true, + }, + "type": schema.StringAttribute{ + Description: "Type of the Precision Time Service Package", + Computed: true, + }, + "bandwidth": schema.Int64Attribute{ + Description: "Bandwidth of the Precision Time Service", + Computed: true, + }, + "clients_per_second_max": schema.Int64Attribute{ + Description: "Maximum clients available per second for the Precision Time Service", + Computed: true, + }, + "redundancy_supported": schema.BoolAttribute{ + Description: "Boolean flag indicating if this Precision Time Service supports redundancy", + Computed: true, + }, + "multi_subnet_supported": schema.BoolAttribute{ + Description: "Boolean flag indicating if this Precision Time Service supports multi subnetting", + Computed: true, + }, + "accuracy_unit": schema.StringAttribute{ + Description: "Time unit of accuracy for the Precision Time Service; e.g. microseconds", + Computed: true, + }, + "accuracy_sla": schema.Int64Attribute{ + Description: "SLA for the accuracy provided by the Precision Time Service", + Computed: true, + }, + "accuracy_avg_min": schema.Int64Attribute{ + Description: "Average minimum accuracy provided by the Precision Time Service", + Computed: true, + }, + "accuracy_avg_max": schema.Int64Attribute{ + Description: "Average maximum accuracy provided by the Precision Time Service", + Computed: true, + }, }, }, - "ipv4": schema.SetNestedBlock{ + "ipv4": schema.SingleNestedBlock{ Description: "An object that has Network IP Configurations for Timing Master Servers.", - CustomType: fwtypes.NewSetNestedObjectTypeOf[Ipv4Model](ctx), - Validators: []validator.Set{ - setvalidator.SizeAtMost(1), - }, - PlanModifiers: []planmodifier.Set{ - setplanmodifier.UseStateForUnknown(), - }, - NestedObject: schema.NestedBlockObject{ - CustomType: fwtypes.NewObjectTypeOf[Ipv4Model](ctx), - PlanModifiers: []planmodifier.Object{ - objectplanmodifier.UseStateForUnknown(), + CustomType: fwtypes.NewObjectTypeOf[Ipv4Model](ctx), + Attributes: map[string]schema.Attribute{ + "primary": schema.StringAttribute{ + Description: "IPv4 address for the Primary Timing Master Server.", + Required: true, }, - Attributes: map[string]schema.Attribute{ - "primary": schema.StringAttribute{ - Description: "IPv4 address for the Primary Timing Master Server.", - Required: true, - }, - "secondary": schema.StringAttribute{ - Description: "IPv4 address for the Secondary Timing Master Server.", - Required: true, - }, - "network_mask": schema.StringAttribute{ - Description: "IPv4 address that defines the range of consecutive subnets in the network.", - Required: true, - }, - "default_gateway": schema.StringAttribute{ - Description: "IPv4 address that establishes the Routing Interface where traffic is directed. It serves as the next hop in the Network.", - Required: true, - }, + "secondary": schema.StringAttribute{ + Description: "IPv4 address for the Secondary Timing Master Server.", + Required: true, + }, + "network_mask": schema.StringAttribute{ + Description: "IPv4 address that defines the range of consecutive subnets in the network.", + Required: true, + }, + "default_gateway": schema.StringAttribute{ + Description: "IPv4 address that establishes the Routing Interface where traffic is directed. It serves as the next hop in the Network.", + Required: true, }, }, }, @@ -190,18 +146,12 @@ func resourceSchema(ctx context.Context) schema.Schema { Computed: true, CustomType: fwtypes.NewListNestedObjectTypeOf[MD5Model](ctx), ElementType: fwtypes.NewObjectTypeOf[MD5Model](ctx), - PlanModifiers: []planmodifier.List{ - listplanmodifier.UseStateForUnknown(), - }, }, }, Blocks: map[string]schema.Block{ "ptp": schema.SingleNestedBlock{ Description: "An object that has advanced PTP configuration.", CustomType: fwtypes.NewObjectTypeOf[PTPModel](ctx), - PlanModifiers: []planmodifier.Object{ - objectplanmodifier.UseStateForUnknown(), - }, Attributes: map[string]schema.Attribute{ "time_scale": schema.StringAttribute{ Description: "Time scale value. ARB denotes Arbitrary, and PTP denotes Precision Time Protocol.", @@ -265,25 +215,17 @@ func resourceSchema(ctx context.Context) schema.Schema { }, }, }, - "project": schema.SetNestedBlock{ + "project": schema.SingleNestedBlock{ Description: "An object that contains the Equinix Fabric project_id used for linking the Time Precision Service to a specific Equinix Fabric Project", - CustomType: fwtypes.NewSetNestedObjectTypeOf[ProjectModel](ctx), - Validators: []validator.Set{ - setvalidator.SizeAtMost(1), - }, - PlanModifiers: []planmodifier.Set{ - setplanmodifier.UseStateForUnknown(), + CustomType: fwtypes.NewObjectTypeOf[ProjectModel](ctx), + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.UseStateForUnknown(), }, - NestedObject: schema.NestedBlockObject{ - CustomType: fwtypes.NewObjectTypeOf[ProjectModel](ctx), - PlanModifiers: []planmodifier.Object{ - objectplanmodifier.UseStateForUnknown(), - }, - Attributes: map[string]schema.Attribute{ - "project_id": schema.StringAttribute{ - Description: "Equinix Fabric Project ID", - Required: true, - }, + Attributes: map[string]schema.Attribute{ + "project_id": schema.StringAttribute{ + Description: "Equinix Fabric Project ID", + Optional: true, + Computed: true, }, }, }, From 43f6d85c9db361ce5d0a0df933869cece694e132 Mon Sep 17 00:00:00 2001 From: Tim Hogarty Date: Tue, 6 Aug 2024 15:40:04 -0700 Subject: [PATCH 06/12] Update schema to handle computed values for non-set config items marked as optional or computed --- .../framework/types/set_nested_objectof.go | 216 ----------------- internal/framework/types/setof.go | 136 ----------- .../fabric/precision_time/datasources.go | 2 +- .../precision_time/datasources_schema.go | 124 ++-------- .../resources/fabric/precision_time/models.go | 228 ++++++++++-------- .../fabric/precision_time/resource.go | 216 +++++++++-------- .../fabric/precision_time/resource_schema.go | 165 ++++--------- 7 files changed, 315 insertions(+), 772 deletions(-) delete mode 100644 internal/framework/types/set_nested_objectof.go delete mode 100644 internal/framework/types/setof.go diff --git a/internal/framework/types/set_nested_objectof.go b/internal/framework/types/set_nested_objectof.go deleted file mode 100644 index 28bd82dfb..000000000 --- a/internal/framework/types/set_nested_objectof.go +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package types - -import ( - "context" - "fmt" - equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors" - - "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/types/basetypes" - "github.com/hashicorp/terraform-plugin-go/tftypes" -) - -var ( - _ basetypes.SetTypable = (*setNestedObjectTypeOf[struct{}])(nil) - _ NestedObjectCollectionType = (*setNestedObjectTypeOf[struct{}])(nil) - _ basetypes.SetValuable = (*SetNestedObjectValueOf[struct{}])(nil) - _ NestedObjectCollectionValue = (*SetNestedObjectValueOf[struct{}])(nil) -) - -// setNestedObjectTypeOf is the attribute type of a SetNestedObjectValueOf. -type setNestedObjectTypeOf[T any] struct { - basetypes.SetType -} - -func NewSetNestedObjectTypeOf[T any](ctx context.Context) setNestedObjectTypeOf[T] { - return setNestedObjectTypeOf[T]{basetypes.SetType{ElemType: NewObjectTypeOf[T](ctx)}} -} - -func (t setNestedObjectTypeOf[T]) Equal(o attr.Type) bool { - other, ok := o.(setNestedObjectTypeOf[T]) - - if !ok { - return false - } - - return t.SetType.Equal(other.SetType) -} - -func (t setNestedObjectTypeOf[T]) String() string { - var zero T - return fmt.Sprintf("SetNestedObjectTypeOf[%T]", zero) -} - -func (t setNestedObjectTypeOf[T]) ValueFromSet(ctx context.Context, in basetypes.SetValue) (basetypes.SetValuable, diag.Diagnostics) { - var diags diag.Diagnostics - - if in.IsNull() { - return NewSetNestedObjectValueOfNull[T](ctx), diags - } - if in.IsUnknown() { - return NewSetNestedObjectValueOfUnknown[T](ctx), diags - } - - typ := NewObjectTypeOf[T](ctx) - - v, d := basetypes.NewSetValue(typ, in.Elements()) - diags.Append(d...) - if diags.HasError() { - return NewSetNestedObjectValueOfUnknown[T](ctx), diags - } - - return SetNestedObjectValueOf[T]{SetValue: v}, diags -} - -func (t setNestedObjectTypeOf[T]) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { - attrValue, err := t.SetType.ValueFromTerraform(ctx, in) - - if err != nil { - return nil, err - } - - setValue, ok := attrValue.(basetypes.SetValue) - - if !ok { - return nil, fmt.Errorf("unexpected value type of %T", attrValue) - } - - setValuable, diags := t.ValueFromSet(ctx, setValue) - - if diags.HasError() { - return nil, fmt.Errorf("unexpected error converting SetValue to SetValuable: %v", diags) - } - - return setValuable, nil -} - -func (t setNestedObjectTypeOf[T]) ValueType(ctx context.Context) attr.Value { - return SetNestedObjectValueOf[T]{} -} - -func (t setNestedObjectTypeOf[T]) NewObjectPtr(ctx context.Context) (any, diag.Diagnostics) { - return objectTypeNewObjectPtr[T](ctx) -} - -func (t setNestedObjectTypeOf[T]) NewObjectSlice(ctx context.Context, len, cap int) (any, diag.Diagnostics) { - return nestedObjectTypeNewObjectSlice[T](ctx, len, cap) -} - -func (t setNestedObjectTypeOf[T]) NullValue(ctx context.Context) (attr.Value, diag.Diagnostics) { - var diags diag.Diagnostics - - return NewSetNestedObjectValueOfNull[T](ctx), diags -} - -func (t setNestedObjectTypeOf[T]) ValueFromObjectPtr(ctx context.Context, ptr any) (attr.Value, diag.Diagnostics) { - var diags diag.Diagnostics - - if v, ok := ptr.(*T); ok { - v, d := NewSetNestedObjectValueOfPtr(ctx, v) - diags.Append(d...) - return v, d - } - - diags.Append(diag.NewErrorDiagnostic("Invalid pointer value", fmt.Sprintf("incorrect type: want %T, got %T", (*T)(nil), ptr))) - return nil, diags -} - -func (t setNestedObjectTypeOf[T]) ValueFromObjectSlice(ctx context.Context, slice any) (attr.Value, diag.Diagnostics) { - var diags diag.Diagnostics - - if v, ok := slice.([]*T); ok { - v, d := NewSetNestedObjectValueOfSlice(ctx, v) - diags.Append(d...) - return v, d - } - - diags.Append(diag.NewErrorDiagnostic("Invalid slice value", fmt.Sprintf("incorrect type: want %T, got %T", (*[]T)(nil), slice))) - return nil, diags -} - -// SetNestedObjectValueOf represents a Terraform Plugin Framework Set value whose elements are of type `ObjectTypeOf[T]`. -type SetNestedObjectValueOf[T any] struct { - basetypes.SetValue -} - -func (v SetNestedObjectValueOf[T]) Equal(o attr.Value) bool { - other, ok := o.(SetNestedObjectValueOf[T]) - - if !ok { - return false - } - - return v.SetValue.Equal(other.SetValue) -} - -func (v SetNestedObjectValueOf[T]) Type(ctx context.Context) attr.Type { - return NewSetNestedObjectTypeOf[T](ctx) -} - -func (v SetNestedObjectValueOf[T]) ToObjectPtr(ctx context.Context) (any, diag.Diagnostics) { - return v.ToPtr(ctx) -} - -func (v SetNestedObjectValueOf[T]) ToObjectSlice(ctx context.Context) (any, diag.Diagnostics) { - return v.ToSlice(ctx) -} - -// ToPtr returns a pointer to the single element of a SetNestedObject. -func (v SetNestedObjectValueOf[T]) ToPtr(ctx context.Context) (*T, diag.Diagnostics) { - return nestedObjectValueObjectPtr[T](ctx, v.SetValue) -} - -// ToSlice returns a slice of pointers to the elements of a SetNestedObject. -func (v SetNestedObjectValueOf[T]) ToSlice(ctx context.Context) ([]*T, diag.Diagnostics) { - return nestedObjectValueObjectSlice[T](ctx, v.SetValue) -} - -func NewSetNestedObjectValueOfNull[T any](ctx context.Context) SetNestedObjectValueOf[T] { - return SetNestedObjectValueOf[T]{SetValue: basetypes.NewSetNull(NewObjectTypeOf[T](ctx))} -} - -func NewSetNestedObjectValueOfUnknown[T any](ctx context.Context) SetNestedObjectValueOf[T] { - return SetNestedObjectValueOf[T]{SetValue: basetypes.NewSetUnknown(NewObjectTypeOf[T](ctx))} -} - -func NewSetNestedObjectValueOfPtr[T any](ctx context.Context, t *T) (SetNestedObjectValueOf[T], diag.Diagnostics) { - return NewSetNestedObjectValueOfSlice(ctx, []*T{t}) -} - -func NewSetNestedObjectValueOfPtrMust[T any](ctx context.Context, t *T) SetNestedObjectValueOf[T] { - return equinix_errors.MustWithDiagnostics(NewSetNestedObjectValueOfPtr(ctx, t)) -} - -func NewSetNestedObjectValueOfSlice[T any](ctx context.Context, ts []*T) (SetNestedObjectValueOf[T], diag.Diagnostics) { - return newSetNestedObjectValueOf[T](ctx, ts) -} - -func NewSetNestedObjectValueOfSliceMust[T any](ctx context.Context, ts []*T) SetNestedObjectValueOf[T] { - return equinix_errors.MustWithDiagnostics(NewSetNestedObjectValueOfSlice(ctx, ts)) -} - -func NewSetNestedObjectValueOfValueSlice[T any](ctx context.Context, ts []T) (SetNestedObjectValueOf[T], diag.Diagnostics) { - return newSetNestedObjectValueOf[T](ctx, ts) -} - -func NewSetNestedObjectValueOfValueSliceMust[T any](ctx context.Context, ts []T) SetNestedObjectValueOf[T] { - return equinix_errors.MustWithDiagnostics(NewSetNestedObjectValueOfValueSlice(ctx, ts)) -} - -func newSetNestedObjectValueOf[T any](ctx context.Context, elements any) (SetNestedObjectValueOf[T], diag.Diagnostics) { - var diags diag.Diagnostics - - typ := NewObjectTypeOf[T](ctx) - - v, d := basetypes.NewSetValueFrom(ctx, typ, elements) - diags.Append(d...) - if diags.HasError() { - return NewSetNestedObjectValueOfUnknown[T](ctx), diags - } - - return SetNestedObjectValueOf[T]{SetValue: v}, diags -} diff --git a/internal/framework/types/setof.go b/internal/framework/types/setof.go deleted file mode 100644 index 439cc73d0..000000000 --- a/internal/framework/types/setof.go +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package types - -import ( - "context" - "fmt" - equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors" - - "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/types/basetypes" - "github.com/hashicorp/terraform-plugin-go/tftypes" -) - -var ( - _ basetypes.SetTypable = (*setTypeOf[basetypes.StringValue])(nil) - _ basetypes.SetValuable = (*SetValueOf[basetypes.StringValue])(nil) -) - -// setTypeOf is the attribute type of a SetValueOf. -type setTypeOf[T attr.Value] struct { - basetypes.SetType -} - -var ( - SetOfStringType = setTypeOf[basetypes.StringValue]{basetypes.SetType{ElemType: basetypes.StringType{}}} -) - -func NewSetTypeOf[T attr.Value](ctx context.Context) setTypeOf[T] { - return setTypeOf[T]{basetypes.SetType{ElemType: newAttrTypeOf[T](ctx)}} -} - -func (t setTypeOf[T]) Equal(o attr.Type) bool { - other, ok := o.(setTypeOf[T]) - - if !ok { - return false - } - - return t.SetType.Equal(other.SetType) -} - -func (t setTypeOf[T]) String() string { - var zero T - return fmt.Sprintf("SetTypeOf[%T]", zero) -} - -func (t setTypeOf[T]) ValueFromSet(ctx context.Context, in basetypes.SetValue) (basetypes.SetValuable, diag.Diagnostics) { - var diags diag.Diagnostics - - if in.IsNull() { - return NewSetValueOfNull[T](ctx), diags - } - if in.IsUnknown() { - return NewSetValueOfUnknown[T](ctx), diags - } - - v, d := basetypes.NewSetValue(newAttrTypeOf[T](ctx), in.Elements()) - diags.Append(d...) - if diags.HasError() { - return NewSetValueOfUnknown[T](ctx), diags - } - - return SetValueOf[T]{SetValue: v}, diags -} - -func (t setTypeOf[T]) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { - attrValue, err := t.SetType.ValueFromTerraform(ctx, in) - - if err != nil { - return nil, err - } - - setValue, ok := attrValue.(basetypes.SetValue) - - if !ok { - return nil, fmt.Errorf("unexpected value type of %T", attrValue) - } - - setValuable, diags := t.ValueFromSet(ctx, setValue) - - if diags.HasError() { - return nil, fmt.Errorf("unexpected error converting SetValue to SetValuable: %v", diags) - } - - return setValuable, nil -} - -func (t setTypeOf[T]) ValueType(ctx context.Context) attr.Value { - return SetValueOf[T]{} -} - -// SetValueOf represents a Terraform Plugin Framework Set value whose elements are of type `T`. -type SetValueOf[T attr.Value] struct { - basetypes.SetValue -} - -func (v SetValueOf[T]) Equal(o attr.Value) bool { - other, ok := o.(SetValueOf[T]) - - if !ok { - return false - } - - return v.SetValue.Equal(other.SetValue) -} - -func (v SetValueOf[T]) Type(ctx context.Context) attr.Type { - return NewSetTypeOf[T](ctx) -} - -func NewSetValueOfNull[T attr.Value](ctx context.Context) SetValueOf[T] { - return SetValueOf[T]{SetValue: basetypes.NewSetNull(newAttrTypeOf[T](ctx))} -} - -func NewSetValueOfUnknown[T attr.Value](ctx context.Context) SetValueOf[T] { - return SetValueOf[T]{SetValue: basetypes.NewSetUnknown(newAttrTypeOf[T](ctx))} -} - -func NewSetValueOf[T attr.Value](ctx context.Context, elements []attr.Value) (SetValueOf[T], diag.Diagnostics) { - var diags diag.Diagnostics - - v, d := basetypes.NewSetValue(newAttrTypeOf[T](ctx), elements) - diags.Append(d...) - if diags.HasError() { - return NewSetValueOfUnknown[T](ctx), diags - } - - return SetValueOf[T]{SetValue: v}, diags -} - -func NewSetValueOfMust[T attr.Value](ctx context.Context, elements []attr.Value) SetValueOf[T] { - return equinix_errors.MustWithDiagnostics(NewSetValueOf[T](ctx, elements)) -} diff --git a/internal/resources/fabric/precision_time/datasources.go b/internal/resources/fabric/precision_time/datasources.go index 4e7181065..a2e5c4e76 100644 --- a/internal/resources/fabric/precision_time/datasources.go +++ b/internal/resources/fabric/precision_time/datasources.go @@ -39,7 +39,7 @@ func (r *DataSource) Read( client := r.Meta.NewFabricClientForFramework(ctx, req.ProviderMeta) // Retrieve values from plan - var data DataSourceModel + var data PrecisionTimeModel resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return diff --git a/internal/resources/fabric/precision_time/datasources_schema.go b/internal/resources/fabric/precision_time/datasources_schema.go index 57bd940f3..1da17e1a7 100644 --- a/internal/resources/fabric/precision_time/datasources_schema.go +++ b/internal/resources/fabric/precision_time/datasources_schema.go @@ -6,6 +6,7 @@ import ( "github.com/equinix/equinix-sdk-go/services/fabricv4" "github.com/equinix/terraform-provider-equinix/internal/framework" fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/schema/validator" @@ -48,6 +49,28 @@ func dataSourceSchema(ctx context.Context) schema.Schema { Description: fmt.Sprintf("Indicator of the state of this Precision Time Service. One of: [%v]", fabricv4.AllowedPrecisionTimeServiceCreateResponseStateEnumValues), Computed: true, }, + "project_id": schema.StringAttribute{ + Description: "Equinix Fabric Project ID", + Computed: true, + }, + "advance_configuration": schema.ListAttribute{ + Description: "An object that has advanced configuration options.", + CustomType: fwtypes.NewListNestedObjectTypeOf[AdvanceConfigurationModel](ctx), + ElementType: fwtypes.NewObjectTypeOf[AdvanceConfigurationModel](ctx), + Computed: true, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + "account": schema.ListAttribute{ + Description: "Equinix User Account associated with Precision Time Service", + CustomType: fwtypes.NewListNestedObjectTypeOf[AccountModel](ctx), + ElementType: fwtypes.NewObjectTypeOf[AccountModel](ctx), + Computed: true, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, }, Blocks: map[string]schema.Block{ "package": schema.SingleNestedBlock{ @@ -122,85 +145,6 @@ func dataSourceSchema(ctx context.Context) schema.Schema { }, }, }, - "advance_configuration": schema.SingleNestedBlock{ - Description: "An object that has advanced configuration options.", - CustomType: fwtypes.NewObjectTypeOf[AdvanceConfigurationModel](ctx), - Attributes: map[string]schema.Attribute{ - "ntp": schema.ListAttribute{ - Description: "Advance Configuration for NTP; a list of MD5 objects", - Computed: true, - CustomType: fwtypes.NewListNestedObjectTypeOf[MD5Model](ctx), - ElementType: fwtypes.NewObjectTypeOf[MD5Model](ctx), - }, - }, - Blocks: map[string]schema.Block{ - "ptp": schema.SingleNestedBlock{ - Description: "An object that has advanced PTP configuration.", - CustomType: fwtypes.NewObjectTypeOf[PTPModel](ctx), - Attributes: map[string]schema.Attribute{ - "time_scale": schema.StringAttribute{ - Description: "Time scale value. ARB denotes Arbitrary, and PTP denotes Precision Time Protocol.", - Computed: true, - Validators: []validator.String{ - stringvalidator.OneOf( - string(fabricv4.PTPADVANCECONFIGURATIONTIMESCALE_ARB), - string(fabricv4.PTPADVANCECONFIGURATIONTIMESCALE_PTP), - ), - }, - }, - "domain": schema.Int64Attribute{ - Description: "Represents the domain number associated with the PTP profile. This is used to differentiate multiple PTP networks within a single physical network.", - Computed: true, - }, - "priority_1": schema.Int64Attribute{ - Description: "Specifies the priority level 1 for the clock. The value helps in determining the best clock in the PTP network. Lower values are considered higher priority.", - Computed: true, - }, - "priority_2": schema.Int64Attribute{ - Description: "Specifies the priority level 2 for the clock. It acts as a tie-breaker if multiple clocks have the same priority 1 value. Lower values are considered higher priority.", - Computed: true, - }, - "log_announce_interval": schema.Int64Attribute{ - Description: "Represents the log2 interval between consecutive PTP announce messages. For example, a value of 0 implies an interval of 2^0 = 1 second.", - Computed: true, - }, - "log_sync_interval": schema.Int64Attribute{ - Description: "Represents the log2 interval between consecutive PTP synchronization messages. A value of 0 implies an interval of 2^0 = 1 second.", - Computed: true, - }, - "log_delay_req_interval": schema.Int64Attribute{ - Description: "Represents the log2 interval between consecutive PTP delay request messages. A value of 0 implies an interval of 2^0 = 1 second.", - Computed: true, - }, - "transport_mode": schema.StringAttribute{ - Description: "Mode of transport for the Time Precision Service.", - Computed: true, - Validators: []validator.String{ - stringvalidator.OneOf( - string(fabricv4.PTPADVANCECONFIGURATIONTRANSPORTMODE_MULTICAST), - string(fabricv4.PTPADVANCECONFIGURATIONTRANSPORTMODE_UNICAST), - string(fabricv4.PTPADVANCECONFIGURATIONTRANSPORTMODE_HYBRID), - ), - }, - }, - "grant_time": schema.Int64Attribute{ - Description: "Unicast Grant Time in seconds. For Multicast and Hybrid transport modes, grant time defaults to 300 seconds. For Unicast mode, grant time can be between 30 to 7200.", - Computed: true, - }, - }, - }, - }, - }, - "project": schema.SingleNestedBlock{ - Description: "An object that contains the Equinix Fabric project_id used for linking the Time Precision Service to a specific Equinix Fabric Project", - CustomType: fwtypes.NewObjectTypeOf[ProjectModel](ctx), - Attributes: map[string]schema.Attribute{ - "project_id": schema.StringAttribute{ - Description: "Equinix Fabric Project ID", - Computed: true, - }, - }, - }, "connections": schema.ListNestedBlock{ Description: "An array of objects with unique identifiers of connections.", CustomType: fwtypes.NewListNestedObjectTypeOf[ConnectionModel](ctx), @@ -222,28 +166,6 @@ func dataSourceSchema(ctx context.Context) schema.Schema { }, }, }, - "account": schema.SingleNestedBlock{ - Description: "Equinix User Account associated with Precision Time Service", - CustomType: fwtypes.NewObjectTypeOf[AccountModel](ctx), - Attributes: map[string]schema.Attribute{ - "account_number": schema.Int64Attribute{ - Description: "Equinix User account number", - Computed: true, - }, - "is_reseller_account": schema.BoolAttribute{ - Description: "Equinix User Boolean flag indicating if it is a reseller account", - Computed: true, - }, - "org_id": schema.StringAttribute{ - Description: "Equinix User organization id", - Computed: true, - }, - "global_org_id": schema.StringAttribute{ - Description: "Equinix User global organization id", - Computed: true, - }, - }, - }, }, } } diff --git a/internal/resources/fabric/precision_time/models.go b/internal/resources/fabric/precision_time/models.go index 9da38a679..28f48e93f 100644 --- a/internal/resources/fabric/precision_time/models.go +++ b/internal/resources/fabric/precision_time/models.go @@ -4,41 +4,28 @@ import ( "context" "github.com/equinix/equinix-sdk-go/services/fabricv4" fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "reflect" ) -type ResourceModel struct { - ID types.String `tfsdk:"id"` - Type types.String `tfsdk:"type"` - Href types.String `tfsdk:"href"` - Uuid types.String `tfsdk:"uuid"` - Name types.String `tfsdk:"name"` - Description types.String `tfsdk:"description"` - State types.String `tfsdk:"state"` - Package fwtypes.ObjectValueOf[PackageModel] `tfsdk:"package"` - Connections fwtypes.ListNestedObjectValueOf[ConnectionModel] `tfsdk:"connections"` - Ipv4 fwtypes.ObjectValueOf[Ipv4Model] `tfsdk:"ipv4"` - Account fwtypes.ObjectValueOf[AccountModel] `tfsdk:"account"` - AdvanceConfiguration fwtypes.ObjectValueOf[AdvanceConfigurationModel] `tfsdk:"advance_configuration"` - Project fwtypes.ObjectValueOf[ProjectModel] `tfsdk:"project"` -} - -type DataSourceModel struct { - ID types.String `tfsdk:"id"` - Type types.String `tfsdk:"type"` - Href types.String `tfsdk:"href"` - Uuid types.String `tfsdk:"uuid"` - Name types.String `tfsdk:"name"` - Description types.String `tfsdk:"description"` - State types.String `tfsdk:"state"` - Package fwtypes.ObjectValueOf[PackageModel] `tfsdk:"package"` - Connections fwtypes.ListNestedObjectValueOf[ConnectionModel] `tfsdk:"connections"` - Ipv4 fwtypes.ObjectValueOf[Ipv4Model] `tfsdk:"ipv4"` - Account fwtypes.ObjectValueOf[AccountModel] `tfsdk:"account"` - AdvanceConfiguration fwtypes.ObjectValueOf[AdvanceConfigurationModel] `tfsdk:"advance_configuration"` - Project fwtypes.ObjectValueOf[ProjectModel] `tfsdk:"project"` +type PrecisionTimeModel struct { + ID types.String `tfsdk:"id"` + Type types.String `tfsdk:"type"` + Href types.String `tfsdk:"href"` + Uuid types.String `tfsdk:"uuid"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + State types.String `tfsdk:"state"` + Package fwtypes.ObjectValueOf[PackageModel] `tfsdk:"package"` + Connections fwtypes.ListNestedObjectValueOf[ConnectionModel] `tfsdk:"connections"` + Ipv4 fwtypes.ObjectValueOf[Ipv4Model] `tfsdk:"ipv4"` + Account fwtypes.ListNestedObjectValueOf[AccountModel] `tfsdk:"account"` + AdvanceConfiguration fwtypes.ListNestedObjectValueOf[AdvanceConfigurationModel] `tfsdk:"advance_configuration"` + ProjectId types.String `tfsdk:"project_id"` + Timeouts timeouts.Value `tfsdk:"timeouts"` } type PackageModel struct { @@ -77,7 +64,7 @@ type AccountModel struct { type AdvanceConfigurationModel struct { Ntp fwtypes.ListNestedObjectValueOf[MD5Model] `tfsdk:"ntp"` - Ptp fwtypes.ObjectValueOf[PTPModel] `tfsdk:"ptp"` + Ptp fwtypes.ListNestedObjectValueOf[PTPModel] `tfsdk:"ptp"` } type MD5Model struct { @@ -98,30 +85,12 @@ type PTPModel struct { GrantTime types.Int64 `tfsdk:"grant_time"` } -type ProjectModel struct { - ProjectId types.String `tfsdk:"project_id"` -} - -func (m *ResourceModel) parse(ctx context.Context, ept *fabricv4.PrecisionTimeServiceCreateResponse) diag.Diagnostics { +func (m *PrecisionTimeModel) parse(ctx context.Context, ept *fabricv4.PrecisionTimeServiceCreateResponse) diag.Diagnostics { var diags diag.Diagnostics diags = parsePrecisionTime(ctx, ept, &m.ID, &m.Type, &m.Href, &m.Uuid, &m.Name, &m.Description, - &m.State, &m.Package, &m.Ipv4, &m.Project, - &m.Account, - &m.AdvanceConfiguration, - &m.Connections, - ) - - return diags -} - -func (m *DataSourceModel) parse(ctx context.Context, ept *fabricv4.PrecisionTimeServiceCreateResponse) diag.Diagnostics { - var diags diag.Diagnostics - - diags = parsePrecisionTime(ctx, ept, - &m.ID, &m.Type, &m.Href, &m.Uuid, &m.Name, &m.Description, - &m.State, &m.Package, &m.Ipv4, &m.Project, + &m.State, &m.ProjectId, &m.Package, &m.Ipv4, &m.Account, &m.AdvanceConfiguration, &m.Connections, @@ -133,64 +102,76 @@ func (m *DataSourceModel) parse(ctx context.Context, ept *fabricv4.PrecisionTime func parsePrecisionTime( ctx context.Context, ept *fabricv4.PrecisionTimeServiceCreateResponse, - id, type_, href, uuid, name, description, state *basetypes.StringValue, + id, type_, href, uuid, name, description, state, projectId *basetypes.StringValue, package_ *fwtypes.ObjectValueOf[PackageModel], ipv4 *fwtypes.ObjectValueOf[Ipv4Model], - project *fwtypes.ObjectValueOf[ProjectModel], - account *fwtypes.ObjectValueOf[AccountModel], - advanceConfiguration *fwtypes.ObjectValueOf[AdvanceConfigurationModel], + account *fwtypes.ListNestedObjectValueOf[AccountModel], + advanceConfiguration *fwtypes.ListNestedObjectValueOf[AdvanceConfigurationModel], connections *fwtypes.ListNestedObjectValueOf[ConnectionModel], ) diag.Diagnostics { var diags diag.Diagnostics *id = types.StringValue(ept.GetUuid()) *type_ = types.StringValue(string(ept.GetType())) - *href = types.StringValue(ept.GetHref()) *uuid = types.StringValue(ept.GetUuid()) - *name = types.StringValue(ept.GetName()) - *description = types.StringValue(ept.GetDescription()) *state = types.StringValue(string(ept.GetState())) + *href = types.StringValue(ept.GetHref()) + + if ept.GetName() != "" { + *name = types.StringValue(ept.GetName()) + } + if ept.GetDescription() != "" { + *description = types.StringValue(ept.GetDescription()) + } + + eptProject := ept.GetProject() + if eptProject.GetProjectId() != "" { + *projectId = types.StringValue(eptProject.GetProjectId()) + } eptPackage := ept.GetPackage() parsedEptPackage, diags := parsePackage(ctx, &eptPackage) if diags.HasError() { return diags } - *package_ = parsedEptPackage + if !reflect.DeepEqual(parsedEptPackage, fwtypes.NewObjectValueOfNull[PackageModel](ctx)) { + *package_ = parsedEptPackage + } parsedEptConnections, diags := parseConnections(ctx, ept.GetConnections()) if diags.HasError() { return diags } - *connections = parsedEptConnections + if !reflect.DeepEqual(parsedEptConnections, fwtypes.NewListNestedObjectValueOfValueSlice[ConnectionModel](ctx, []ConnectionModel{})) { + *connections = parsedEptConnections + } eptIpv4 := ept.GetIpv4() parsedEptIpv4, diags := parseIpv4(ctx, &eptIpv4) if diags.HasError() { return diags } - *ipv4 = parsedEptIpv4 + if !reflect.DeepEqual(parsedEptIpv4, fwtypes.NewObjectValueOfNull[Ipv4Model](ctx)) { + *ipv4 = parsedEptIpv4 + } eptAccount := ept.GetAccount() parsedEptAccount, diags := parseAccount(ctx, &eptAccount) if diags.HasError() { return diags } - *account = parsedEptAccount + if !reflect.DeepEqual(parsedEptAccount, fwtypes.NewObjectValueOfNull[AccountModel](ctx)) { + *account = parsedEptAccount + } eptAdvanceConfiguration := ept.GetAdvanceConfiguration() parsedEptAdvanceConfiguration, diags := parseAdvanceConfiguration(ctx, &eptAdvanceConfiguration) if diags.HasError() { return diags } - *advanceConfiguration = parsedEptAdvanceConfiguration - - eptProject := ept.GetProject() - parsedEptProject, diags := parseProject(ctx, &eptProject) - if diags.HasError() { - return diags + if !reflect.DeepEqual(parsedEptAdvanceConfiguration, fwtypes.NewListNestedObjectValueOfNull[AdvanceConfigurationModel](ctx)) { + *advanceConfiguration = parsedEptAdvanceConfiguration } - *project = parsedEptProject return diags } @@ -199,7 +180,22 @@ func parsePackage(ctx context.Context, package_ *fabricv4.PrecisionTimePackageRe packageModel := &PackageModel{} packageModel.Code = types.StringValue(string(package_.GetCode())) - packageModel.Href = types.StringValue(package_.GetHref()) + if package_.GetHref() != "" { + packageModel.Href = types.StringValue(package_.GetHref()) + } + if string(package_.GetType()) != "" { + packageModel.Type = types.StringValue(string(package_.GetType())) + } + if package_.GetAccuracyUnit() != "" { + packageModel.AccuracyUnit = types.StringValue(package_.GetAccuracyUnit()) + } + packageModel.AccuracySla = types.Int64Value(int64(package_.GetAccuracySla())) + packageModel.AccuracyAvgMin = types.Int64Value(int64(package_.GetAccuracyAvgMin())) + packageModel.AccuracyAvgMax = types.Int64Value(int64(package_.GetAccuracyAvgMax())) + packageModel.Bandwidth = types.Int64Value(int64(package_.GetBandwidth())) + packageModel.ClientsPerSecondMax = types.Int64Value(int64(package_.GetClientsPerSecondMax())) + packageModel.RedundancySupported = types.BoolValue(package_.GetRedundancySupported()) + packageModel.MultiSubnetSupported = types.BoolValue(package_.GetMultiSubnetSupported()) return fwtypes.NewObjectValueOf[PackageModel](ctx, packageModel), nil } @@ -208,11 +204,16 @@ func parseConnections(ctx context.Context, connections []fabricv4.FabricConnecti connectionModels := make([]ConnectionModel, len(connections)) for index, connection := range connections { - connectionModels[index] = ConnectionModel{ + connectionModel := ConnectionModel{ Uuid: types.StringValue(connection.GetUuid()), - Href: types.StringValue(connection.GetHref()), - Type: types.StringValue(connection.GetType()), } + if connection.GetHref() != "" { + connectionModel.Href = types.StringValue(connection.GetHref()) + } + if connection.GetType() != "" { + connectionModel.Type = types.StringValue(connection.GetType()) + } + connectionModels[index] = connectionModel } return fwtypes.NewListNestedObjectValueOfValueSlice(ctx, connectionModels), nil @@ -229,8 +230,8 @@ func parseIpv4(ctx context.Context, ipv4 *fabricv4.Ipv4) (fwtypes.ObjectValueOf[ return fwtypes.NewObjectValueOf[Ipv4Model](ctx, ipv4Model), nil } -func parseAccount(ctx context.Context, account *fabricv4.Account) (fwtypes.ObjectValueOf[AccountModel], diag.Diagnostics) { - accountModel := &AccountModel{} +func parseAccount(ctx context.Context, account *fabricv4.Account) (fwtypes.ListNestedObjectValueOf[AccountModel], diag.Diagnostics) { + accountModel := AccountModel{} if account.GetAccountNumber() != 0 { accountModel.AccountNumber = types.Int64Value(int64(account.GetAccountNumber())) @@ -245,27 +246,39 @@ func parseAccount(ctx context.Context, account *fabricv4.Account) (fwtypes.Objec accountModel.GlobalOrgId = types.StringValue(account.GetGlobalOrgId()) } - return fwtypes.NewObjectValueOf[AccountModel](ctx, accountModel), nil + return fwtypes.NewListNestedObjectValueOfValueSlice[AccountModel](ctx, []AccountModel{accountModel}), nil } -func parseAdvanceConfiguration(ctx context.Context, advConfig *fabricv4.AdvanceConfiguration) (fwtypes.ObjectValueOf[AdvanceConfigurationModel], diag.Diagnostics) { +func parseAdvanceConfiguration(ctx context.Context, advConfig *fabricv4.AdvanceConfiguration) (fwtypes.ListNestedObjectValueOf[AdvanceConfigurationModel], diag.Diagnostics) { var diags diag.Diagnostics - advConfigModel := &AdvanceConfigurationModel{} + advConfigModel := AdvanceConfigurationModel{} md5s, diags := parseNtp(ctx, advConfig.GetNtp()) if diags.HasError() { - return fwtypes.NewObjectValueOfNull[AdvanceConfigurationModel](ctx), diags + return fwtypes.NewListNestedObjectValueOfUnknown[AdvanceConfigurationModel](ctx), diags + } + if len(md5s.Elements()) > 0 { + advConfigModel.Ntp = md5s + } else { + advConfigModel.Ntp = fwtypes.NewListNestedObjectValueOfNull[MD5Model](ctx) } - advConfigModel.Ntp = md5s ptp := advConfig.GetPtp() parsedPtp, diags := parsePtp(ctx, &ptp) if diags.HasError() { - return fwtypes.NewObjectValueOfNull[AdvanceConfigurationModel](ctx), diags + return fwtypes.NewListNestedObjectValueOfUnknown[AdvanceConfigurationModel](ctx), diags + } + if len(parsedPtp.Elements()) > 0 { + advConfigModel.Ptp = parsedPtp + } else { + advConfigModel.Ptp = fwtypes.NewListNestedObjectValueOfNull[PTPModel](ctx) + } + + if reflect.DeepEqual(advConfigModel, AdvanceConfigurationModel{}) { + return fwtypes.NewListNestedObjectValueOfNull[AdvanceConfigurationModel](ctx), nil } - advConfigModel.Ptp = parsedPtp - return fwtypes.NewObjectValueOf[AdvanceConfigurationModel](ctx, advConfigModel), nil + return fwtypes.NewListNestedObjectValueOfValueSlice[AdvanceConfigurationModel](ctx, []AdvanceConfigurationModel{advConfigModel}), nil } func parseNtp(ctx context.Context, ntp []fabricv4.Md5) (fwtypes.ListNestedObjectValueOf[MD5Model], diag.Diagnostics) { @@ -282,27 +295,38 @@ func parseNtp(ctx context.Context, ntp []fabricv4.Md5) (fwtypes.ListNestedObject return fwtypes.NewListNestedObjectValueOfValueSlice(ctx, ntpModel), nil } -func parsePtp(ctx context.Context, ptp *fabricv4.PtpAdvanceConfiguration) (fwtypes.ObjectValueOf[PTPModel], diag.Diagnostics) { - ptpModel := &PTPModel{} - - ptpModel.TimeScale = types.StringValue(string(ptp.GetTimeScale())) - ptpModel.Domain = types.Int64Value(int64(ptp.GetDomain())) - ptpModel.Priority1 = types.Int64Value(int64(ptp.GetPriority1())) - ptpModel.Priority2 = types.Int64Value(int64(ptp.GetPriority2())) - ptpModel.LogAnnounceInterval = types.Int64Value(int64(ptp.GetLogAnnounceInterval())) - ptpModel.LogSyncInterval = types.Int64Value(int64(ptp.GetLogSyncInterval())) - ptpModel.LogDelayReqInterval = types.Int64Value(int64(ptp.GetLogDelayReqInterval())) - ptpModel.TransportMode = types.StringValue(string(ptp.GetTransportMode())) - ptpModel.GrantTime = types.Int64Value(int64(ptp.GetGrantTime())) +func parsePtp(ctx context.Context, ptp *fabricv4.PtpAdvanceConfiguration) (fwtypes.ListNestedObjectValueOf[PTPModel], diag.Diagnostics) { + ptpModel := PTPModel{} - return fwtypes.NewObjectValueOf[PTPModel](ctx, ptpModel), nil - -} + if timeScale, ok := ptp.GetTimeScaleOk(); ok && timeScale != nil { + ptpModel.TimeScale = types.StringValue(string(ptp.GetTimeScale())) + } + if domain, ok := ptp.GetDomainOk(); ok && domain != nil { + ptpModel.Domain = types.Int64Value(int64(ptp.GetDomain())) + } + if priority1, ok := ptp.GetPriority1Ok(); ok && priority1 != nil { + ptpModel.Priority1 = types.Int64Value(int64(ptp.GetPriority1())) + } + if priority2, ok := ptp.GetPriority2Ok(); ok && priority2 != nil { + ptpModel.Priority2 = types.Int64Value(int64(ptp.GetPriority2())) + } + if logAnnounceInterval, ok := ptp.GetLogAnnounceIntervalOk(); ok && logAnnounceInterval != nil { + ptpModel.LogAnnounceInterval = types.Int64Value(int64(ptp.GetLogAnnounceInterval())) + } + if logSyncInterval, ok := ptp.GetLogSyncIntervalOk(); ok && logSyncInterval != nil { + ptpModel.LogSyncInterval = types.Int64Value(int64(ptp.GetLogSyncInterval())) + } + if logDelayReqInterval, ok := ptp.GetLogDelayReqIntervalOk(); ok && logDelayReqInterval != nil { + ptpModel.LogDelayReqInterval = types.Int64Value(int64(ptp.GetLogDelayReqInterval())) + } + if transportMode, ok := ptp.GetTransportModeOk(); ok && transportMode != nil { + ptpModel.TransportMode = types.StringValue(string(ptp.GetTransportMode())) -func parseProject(ctx context.Context, project *fabricv4.Project) (fwtypes.ObjectValueOf[ProjectModel], diag.Diagnostics) { - projectModel := &ProjectModel{} + } + if grantTime, ok := ptp.GetGrantTimeOk(); ok && grantTime != nil { + ptpModel.GrantTime = types.Int64Value(int64(ptp.GetGrantTime())) + } - projectModel.ProjectId = types.StringValue(project.GetProjectId()) + return fwtypes.NewListNestedObjectValueOfValueSlice[PTPModel](ctx, []PTPModel{ptpModel}), nil - return fwtypes.NewObjectValueOf[ProjectModel](ctx, projectModel), nil } diff --git a/internal/resources/fabric/precision_time/resource.go b/internal/resources/fabric/precision_time/resource.go index 0ac323bd9..74be8a89a 100644 --- a/internal/resources/fabric/precision_time/resource.go +++ b/internal/resources/fabric/precision_time/resource.go @@ -46,7 +46,7 @@ func (r *Resource) Create( resp *resource.CreateResponse, ) { - var plan ResourceModel + var plan PrecisionTimeModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -72,14 +72,21 @@ func (r *Resource) Create( return } - ept, diags = getEpt(ctx, client, &resp.State, ept.GetUuid()) - if diags != nil { + createTimeout, diags := plan.Timeouts.Create(ctx, 10*time.Minute) + if diags.HasError() { resp.Diagnostics.Append(diags...) return } + createWaiter := getCreateUpdateWaiter(ctx, client, ept.GetUuid(), createTimeout) + eptChecked, err := createWaiter.WaitForStateContext(ctx) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Failed to create Precision Time Service %s", ept.GetUuid()), err.Error()) + return + } // Parse API response into the Terraform state - resp.Diagnostics.Append(plan.parse(ctx, ept)...) + resp.Diagnostics.Append(plan.parse(ctx, eptChecked.(*fabricv4.PrecisionTimeServiceCreateResponse))...) if resp.Diagnostics.HasError() { return } @@ -93,7 +100,7 @@ func (r *Resource) Read( req resource.ReadRequest, resp *resource.ReadResponse, ) { - var state ResourceModel + var state PrecisionTimeModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -106,10 +113,10 @@ func (r *Resource) Read( // Extract the ID of the resource from the state id := state.ID.ValueString() - // Use API client to get the current state of the resource - ept, diags := getEpt(ctx, client, &resp.State, id) - if diags != nil { - resp.Diagnostics.Append(diags...) + ept, _, err := client.PrecisionTimeApi.GetTimeServicesById(ctx, id).Execute() + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Failed retrieving Precision Time Service %s", id), err.Error()) return } @@ -131,7 +138,7 @@ func (r *Resource) Update( client := r.Meta.NewFabricClientForFramework(ctx, req.ProviderMeta) // Retrieve values from plan - var state, plan ResourceModel + var state, plan PrecisionTimeModel resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) resp.Diagnostics.Append(req.State.Get(ctx, &state)...) if resp.Diagnostics.HasError() { @@ -194,15 +201,22 @@ func (r *Resource) Update( } } - // Use API client to get the current state of the resource - conn, diags := getEpt(ctx, client, &resp.State, id) - if diags != nil { + updateTimeout, diags := plan.Timeouts.Create(ctx, 10*time.Minute) + if diags.HasError() { resp.Diagnostics.Append(diags...) return } + updateWaiter := getCreateUpdateWaiter(ctx, client, id, updateTimeout) + ept, err := updateWaiter.WaitForStateContext(ctx) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Failed to update Precision Time Service %s", id), err.Error()) + return + } + // Set state to fully populated data - resp.Diagnostics.Append(plan.parse(ctx, conn)...) + resp.Diagnostics.Append(plan.parse(ctx, ept.(*fabricv4.PrecisionTimeServiceCreateResponse))...) if resp.Diagnostics.HasError() { return } @@ -220,7 +234,7 @@ func (r *Resource) Delete( client := r.Meta.NewFabricClientForFramework(ctx, req.ProviderMeta) // Retrieve the current state - var state ResourceModel + var state PrecisionTimeModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -239,7 +253,11 @@ func (r *Resource) Delete( } } - deleteTimeout := 10 * 60 * time.Second + deleteTimeout, diags := state.Timeouts.Create(ctx, 10*time.Minute) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } deleteWaiter := getDeleteWaiter(ctx, client, id, deleteTimeout) _, err = deleteWaiter.WaitForStateContext(ctx) @@ -249,7 +267,7 @@ func (r *Resource) Delete( } } -func buildCreateRequest(ctx context.Context, plan ResourceModel) (fabricv4.PrecisionTimeServiceRequest, diag.Diagnostics) { +func buildCreateRequest(ctx context.Context, plan PrecisionTimeModel) (fabricv4.PrecisionTimeServiceRequest, diag.Diagnostics) { var diags diag.Diagnostics request := fabricv4.PrecisionTimeServiceRequest{ Type: fabricv4.PrecisionTimeServiceRequestType(plan.Type.ValueString()), @@ -313,95 +331,86 @@ func buildCreateRequest(ctx context.Context, plan ResourceModel) (fabricv4.Preci ipv4.SetDefaultGateway(ipv4Model.DefaultGateway.ValueString()) request.SetIpv4(ipv4) - advConfigModel := AdvanceConfigurationModel{} - diags = plan.AdvanceConfiguration.As(ctx, &advConfigModel, basetypes.ObjectAsOptions{ - UnhandledNullAsEmpty: true, - UnhandledUnknownAsEmpty: true, - }) + advConfigList := make([]AdvanceConfigurationModel, 0) + diags = plan.AdvanceConfiguration.ElementsAs(ctx, &advConfigList, true) if diags.HasError() { return fabricv4.PrecisionTimeServiceRequest{}, diags } - ptpModel := PTPModel{} - diags = advConfigModel.Ptp.As(ctx, &ptpModel, basetypes.ObjectAsOptions{ - UnhandledNullAsEmpty: true, - UnhandledUnknownAsEmpty: true, - }) - if diags.HasError() { - return fabricv4.PrecisionTimeServiceRequest{}, diags - } + if len(advConfigList) != 0 { + advConfigModel := advConfigList[0] + ptpList := make([]PTPModel, 0) + diags = advConfigModel.Ptp.ElementsAs(ctx, &ptpList, true) + if diags.HasError() { + return fabricv4.PrecisionTimeServiceRequest{}, diags + } - ptp := fabricv4.PtpAdvanceConfiguration{} - if timeScale := ptpModel.TimeScale.ValueString(); timeScale != "" { - ptp.SetTimeScale(fabricv4.PtpAdvanceConfigurationTimeScale(timeScale)) - } - if domain := ptpModel.Domain.ValueInt64(); domain != 0 { - ptp.SetPriority1(int32(domain)) - } - if priority1 := ptpModel.Priority1.ValueInt64(); priority1 != 0 { - ptp.SetPriority1(int32(priority1)) - } - if priority2 := ptpModel.Priority2.ValueInt64(); priority2 != 0 { - ptp.SetPriority2(int32(priority2)) - } - if logAnnounceInterval := ptpModel.LogAnnounceInterval.ValueInt64(); logAnnounceInterval != 0 { - ptp.SetLogAnnounceInterval(int32(logAnnounceInterval)) - } - if logSyncInterval := ptpModel.LogSyncInterval.ValueInt64(); logSyncInterval != 0 { - ptp.SetLogSyncInterval(int32(logSyncInterval)) - } - if logDelayReqInterval := ptpModel.LogDelayReqInterval.ValueInt64(); logDelayReqInterval != 0 { - ptp.SetLogDelayReqInterval(int32(logDelayReqInterval)) - } - if transportMode := ptpModel.TransportMode.ValueString(); transportMode != "" { - ptp.SetTransportMode(fabricv4.PtpAdvanceConfigurationTransportMode(transportMode)) - } - if grantTime := ptpModel.GrantTime.ValueInt64(); grantTime != 0 { - ptp.SetGrantTime(int32(grantTime)) - } + ptp := fabricv4.PtpAdvanceConfiguration{} + if len(ptpList) != 0 { + ptpModel := ptpList[0] + if timeScale := ptpModel.TimeScale.ValueString(); timeScale != "" { + ptp.SetTimeScale(fabricv4.PtpAdvanceConfigurationTimeScale(timeScale)) + } + if domain := ptpModel.Domain.ValueInt64(); domain != 0 { + ptp.SetPriority1(int32(domain)) + } + if priority1 := ptpModel.Priority1.ValueInt64(); priority1 != 0 { + ptp.SetPriority1(int32(priority1)) + } + if priority2 := ptpModel.Priority2.ValueInt64(); priority2 != 0 { + ptp.SetPriority2(int32(priority2)) + } + if logAnnounceInterval := ptpModel.LogAnnounceInterval.ValueInt64(); logAnnounceInterval != 0 { + ptp.SetLogAnnounceInterval(int32(logAnnounceInterval)) + } + if logSyncInterval := ptpModel.LogSyncInterval.ValueInt64(); logSyncInterval != 0 { + ptp.SetLogSyncInterval(int32(logSyncInterval)) + } + if logDelayReqInterval := ptpModel.LogDelayReqInterval.ValueInt64(); logDelayReqInterval != 0 { + ptp.SetLogDelayReqInterval(int32(logDelayReqInterval)) + } + if transportMode := ptpModel.TransportMode.ValueString(); transportMode != "" { + ptp.SetTransportMode(fabricv4.PtpAdvanceConfigurationTransportMode(transportMode)) + } + if grantTime := ptpModel.GrantTime.ValueInt64(); grantTime != 0 { + ptp.SetGrantTime(int32(grantTime)) + } + } - ntpModels, diags := advConfigModel.Ntp.ToSlice(ctx) - if diags.HasError() { - return fabricv4.PrecisionTimeServiceRequest{}, diags - } + ntpModels, diags := advConfigModel.Ntp.ToSlice(ctx) + if diags.HasError() { + return fabricv4.PrecisionTimeServiceRequest{}, diags + } - ntps := make([]fabricv4.Md5, len(ntpModels)) - for index, md5 := range ntpModels { - ntps[index] = fabricv4.Md5{} - if type_ := md5.Type.ValueString(); type_ != "" { - ntps[index].SetType(fabricv4.Md5Type(type_)) + ntps := make([]fabricv4.Md5, len(ntpModels)) + for index, md5 := range ntpModels { + ntps[index] = fabricv4.Md5{} + if type_ := md5.Type.ValueString(); type_ != "" { + ntps[index].SetType(fabricv4.Md5Type(type_)) + } + if id := md5.Id.ValueString(); id != "" { + ntps[index].SetId(id) + } + if password := md5.Password.ValueString(); password != "" { + ntps[index].SetPassword(password) + } } - if id := md5.Id.ValueString(); id != "" { - ntps[index].SetId(id) + + advConfig := fabricv4.AdvanceConfiguration{} + if len(ntps) > 0 { + advConfig.SetNtp(ntps) } - if password := md5.Password.ValueString(); password != "" { - ntps[index].SetPassword(password) + if !reflect.DeepEqual(ptp, fabricv4.PtpAdvanceConfiguration{}) { + advConfig.SetPtp(ptp) + } + if !reflect.DeepEqual(advConfig, fabricv4.AdvanceConfiguration{}) { + request.SetAdvanceConfiguration(advConfig) } } - advConfig := fabricv4.AdvanceConfiguration{} - if len(ntps) > 0 { - advConfig.SetNtp(ntps) - } - if !reflect.DeepEqual(ptp, fabricv4.PtpAdvanceConfiguration{}) { - advConfig.SetPtp(ptp) - } - if !reflect.DeepEqual(advConfig, fabricv4.AdvanceConfiguration{}) { - request.SetAdvanceConfiguration(advConfig) - } - - projectModel := ProjectModel{} - diags = plan.Project.As(ctx, &projectModel, basetypes.ObjectAsOptions{ - UnhandledNullAsEmpty: true, - UnhandledUnknownAsEmpty: true, - }) - if diags.HasError() { - return fabricv4.PrecisionTimeServiceRequest{}, diags - } - - project := fabricv4.Project{} - if projectId := projectModel.ProjectId.ValueString(); projectId != "" { - project.SetProjectId(projectId) + if plan.ProjectId.ValueString() != "" { + project := fabricv4.Project{} + project.SetProjectId(plan.ProjectId.ValueString()) request.SetProject(project) } @@ -434,6 +443,25 @@ func getEpt(ctx context.Context, client *fabricv4.APIClient, state *tfsdk.State, return ept, diags } +func getCreateUpdateWaiter(ctx context.Context, client *fabricv4.APIClient, id string, timeout time.Duration) *retry.StateChangeConf { + return &retry.StateChangeConf{ + Target: []string{ + string(fabricv4.PRECISIONTIMESERVICECREATERESPONSESTATE_PROVISIONED), + string(fabricv4.PRECISIONTIMESERVICECREATERESPONSESTATE_PENDING_CONFIGURATION), + }, + Refresh: func() (interface{}, string, error) { + ept, _, err := client.PrecisionTimeApi.GetTimeServicesById(ctx, id).Execute() + if err != nil { + return 0, "", err + } + return ept, string(ept.GetState()), nil + }, + Timeout: timeout, + Delay: 10 * time.Second, + MinTimeout: 5 * time.Second, + } +} + func getDeleteWaiter(ctx context.Context, client *fabricv4.APIClient, id string, timeout time.Duration) *retry.StateChangeConf { // deletedMarker is a terraform-provider-only value that is used by the waiter // to indicate that the Precision Time Service appears to be deleted successfully based on diff --git a/internal/resources/fabric/precision_time/resource_schema.go b/internal/resources/fabric/precision_time/resource_schema.go index d616799ec..e0f44edf4 100644 --- a/internal/resources/fabric/precision_time/resource_schema.go +++ b/internal/resources/fabric/precision_time/resource_schema.go @@ -6,10 +6,13 @@ import ( "github.com/equinix/equinix-sdk-go/services/fabricv4" "github.com/equinix/terraform-provider-equinix/internal/framework" fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" ) @@ -45,13 +48,52 @@ func resourceSchema(ctx context.Context) schema.Schema { "description": schema.StringAttribute{ Description: "Optional description of time service", Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, "state": schema.StringAttribute{ Description: fmt.Sprintf("Indicator of the state of this Precision Time Service. One of: [%v]", fabricv4.AllowedPrecisionTimeServiceCreateResponseStateEnumValues), Computed: true, }, + "project_id": schema.StringAttribute{ + Description: "Equinix Fabric Project ID", + Optional: true, + Computed: true, + }, + "advance_configuration": schema.ListAttribute{ + Description: "An object that has advanced configuration options.", + CustomType: fwtypes.NewListNestedObjectTypeOf[AdvanceConfigurationModel](ctx), + ElementType: fwtypes.NewObjectTypeOf[AdvanceConfigurationModel](ctx), + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.UseStateForUnknown(), + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, + "account": schema.ListAttribute{ + Description: "Equinix User Account associated with Precision Time Service", + CustomType: fwtypes.NewListNestedObjectTypeOf[AccountModel](ctx), + ElementType: fwtypes.NewObjectTypeOf[AccountModel](ctx), + Computed: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.UseStateForUnknown(), + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + }, }, Blocks: map[string]schema.Block{ + "timeouts": timeouts.Block(ctx, timeouts.Opts{ + Create: true, + Read: true, + Update: true, + Delete: true, + }), "package": schema.SingleNestedBlock{ Description: "Precision Time Service Package Details", CustomType: fwtypes.NewObjectTypeOf[PackageModel](ctx), @@ -133,102 +175,6 @@ func resourceSchema(ctx context.Context) schema.Schema { }, }, }, - "advance_configuration": schema.SingleNestedBlock{ - Description: "An object that has advanced configuration options.", - CustomType: fwtypes.NewObjectTypeOf[AdvanceConfigurationModel](ctx), - PlanModifiers: []planmodifier.Object{ - objectplanmodifier.UseStateForUnknown(), - }, - Attributes: map[string]schema.Attribute{ - "ntp": schema.ListAttribute{ - Description: "Advance Configuration for NTP; a list of MD5 objects", - Optional: true, - Computed: true, - CustomType: fwtypes.NewListNestedObjectTypeOf[MD5Model](ctx), - ElementType: fwtypes.NewObjectTypeOf[MD5Model](ctx), - }, - }, - Blocks: map[string]schema.Block{ - "ptp": schema.SingleNestedBlock{ - Description: "An object that has advanced PTP configuration.", - CustomType: fwtypes.NewObjectTypeOf[PTPModel](ctx), - Attributes: map[string]schema.Attribute{ - "time_scale": schema.StringAttribute{ - Description: "Time scale value. ARB denotes Arbitrary, and PTP denotes Precision Time Protocol.", - Optional: true, - Computed: true, - Validators: []validator.String{ - stringvalidator.OneOf( - string(fabricv4.PTPADVANCECONFIGURATIONTIMESCALE_ARB), - string(fabricv4.PTPADVANCECONFIGURATIONTIMESCALE_PTP), - ), - }, - }, - "domain": schema.Int64Attribute{ - Description: "Represents the domain number associated with the PTP profile. This is used to differentiate multiple PTP networks within a single physical network.", - Optional: true, - Computed: true, - }, - "priority_1": schema.Int64Attribute{ - Description: "Specifies the priority level 1 for the clock. The value helps in determining the best clock in the PTP network. Lower values are considered higher priority.", - Optional: true, - Computed: true, - }, - "priority_2": schema.Int64Attribute{ - Description: "Specifies the priority level 2 for the clock. It acts as a tie-breaker if multiple clocks have the same priority 1 value. Lower values are considered higher priority.", - Optional: true, - Computed: true, - }, - "log_announce_interval": schema.Int64Attribute{ - Description: "Represents the log2 interval between consecutive PTP announce messages. For example, a value of 0 implies an interval of 2^0 = 1 second.", - Optional: true, - Computed: true, - }, - "log_sync_interval": schema.Int64Attribute{ - Description: "Represents the log2 interval between consecutive PTP synchronization messages. A value of 0 implies an interval of 2^0 = 1 second.", - Optional: true, - Computed: true, - }, - "log_delay_req_interval": schema.Int64Attribute{ - Description: "Represents the log2 interval between consecutive PTP delay request messages. A value of 0 implies an interval of 2^0 = 1 second.", - Optional: true, - Computed: true, - }, - "transport_mode": schema.StringAttribute{ - Description: "Mode of transport for the Time Precision Service.", - Optional: true, - Computed: true, - Validators: []validator.String{ - stringvalidator.OneOf( - string(fabricv4.PTPADVANCECONFIGURATIONTRANSPORTMODE_MULTICAST), - string(fabricv4.PTPADVANCECONFIGURATIONTRANSPORTMODE_UNICAST), - string(fabricv4.PTPADVANCECONFIGURATIONTRANSPORTMODE_HYBRID), - ), - }, - }, - "grant_time": schema.Int64Attribute{ - Description: "Unicast Grant Time in seconds. For Multicast and Hybrid transport modes, grant time defaults to 300 seconds. For Unicast mode, grant time can be between 30 to 7200.", - Optional: true, - Computed: true, - }, - }, - }, - }, - }, - "project": schema.SingleNestedBlock{ - Description: "An object that contains the Equinix Fabric project_id used for linking the Time Precision Service to a specific Equinix Fabric Project", - CustomType: fwtypes.NewObjectTypeOf[ProjectModel](ctx), - PlanModifiers: []planmodifier.Object{ - objectplanmodifier.UseStateForUnknown(), - }, - Attributes: map[string]schema.Attribute{ - "project_id": schema.StringAttribute{ - Description: "Equinix Fabric Project ID", - Optional: true, - Computed: true, - }, - }, - }, "connections": schema.ListNestedBlock{ Description: "An array of objects with unique identifiers of connections.", CustomType: fwtypes.NewListNestedObjectTypeOf[ConnectionModel](ctx), @@ -250,31 +196,6 @@ func resourceSchema(ctx context.Context) schema.Schema { }, }, }, - "account": schema.SingleNestedBlock{ - Description: "Equinix User Account associated with Precision Time Service", - CustomType: fwtypes.NewObjectTypeOf[AccountModel](ctx), - PlanModifiers: []planmodifier.Object{ - objectplanmodifier.UseStateForUnknown(), - }, - Attributes: map[string]schema.Attribute{ - "account_number": schema.Int64Attribute{ - Description: "Equinix User account number", - Computed: true, - }, - "is_reseller_account": schema.BoolAttribute{ - Description: "Equinix User Boolean flag indicating if it is a reseller account", - Computed: true, - }, - "org_id": schema.StringAttribute{ - Description: "Equinix User organization id", - Computed: true, - }, - "global_org_id": schema.StringAttribute{ - Description: "Equinix User global organization id", - Computed: true, - }, - }, - }, }, } } From 0f97aa096471dbbbf4cb478bf6915375a46c2df1 Mon Sep 17 00:00:00 2001 From: Tim Hogarty Date: Tue, 6 Aug 2024 15:49:13 -0700 Subject: [PATCH 07/12] Resolve rebase modifications --- internal/config/config.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 85a5c45bf..e94835f0e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -124,7 +124,7 @@ func (c *Config) Load(ctx context.Context) error { // equinix-sdk-go/fabricv4 client to be used to access Fabric's V4 APIs // Deprecated: migrate to NewFabricClientForFramework instead func (c *Config) NewFabricClientForSDK(d *schema.ResourceData) *fabricv4.APIClient { - client := c.newFabricClient() + client := c.newFabricClient(context.WithValue(context.Background(), "fabricSDKv2", "no_oauth_reload")) baseUserAgent := c.tfSdkUserAgent(client.GetConfig().UserAgent) client.GetConfig().UserAgent = generateModuleUserAgentString(d, baseUserAgent) @@ -135,7 +135,7 @@ func (c *Config) NewFabricClientForSDK(d *schema.ResourceData) *fabricv4.APIClie // Shim for Fabric tests. // Deprecated: when the acceptance package starts to contain API clients for testing/cleanup this will move with them func (c *Config) NewFabricClientForTesting() *fabricv4.APIClient { - client := c.newFabricClient() + client := c.newFabricClient(context.WithValue(context.Background(), "fabricSDKv2", "no_oauth_reload")) client.GetConfig().UserAgent = fmt.Sprintf("tf-acceptance-tests %v", client.GetConfig().UserAgent) @@ -143,7 +143,7 @@ func (c *Config) NewFabricClientForTesting() *fabricv4.APIClient { } func (c *Config) NewFabricClientForFramework(ctx context.Context, meta tfsdk.Config) *fabricv4.APIClient { - client := c.newFabricClient() + client := c.newFabricClient(ctx) baseUserAgent := c.tfFrameworkUserAgent(client.GetConfig().UserAgent) client.GetConfig().UserAgent = generateFwModuleUserAgentString(ctx, meta, baseUserAgent) @@ -153,9 +153,18 @@ func (c *Config) NewFabricClientForFramework(ctx context.Context, meta tfsdk.Con // newFabricClient returns the base fabricv4 client that is then used for either the sdkv2 or framework // implementations of the Terraform Provider with exported Methods -func (c *Config) newFabricClient() *fabricv4.APIClient { +func (c *Config) newFabricClient(ctx context.Context) *fabricv4.APIClient { //nolint:staticcheck // We should move to subsystem loggers, but that is a much bigger change transport := logging.NewTransport("Equinix Fabric (fabricv4)", c.authClient.Transport) + if v := ctx.Value("fabricSDKv2"); v == nil { + authConfig := oauth2.Config{ + ClientID: c.ClientID, + ClientSecret: c.ClientSecret, + BaseURL: c.BaseURL, + } + authClient := authConfig.New(ctx) + transport = logging.NewTransport("Equinix Fabric (fabricv4)", authClient.Transport) + } retryClient := retryablehttp.NewClient() retryClient.HTTPClient.Transport = transport From 6f0dd0f8edd91e3737e2d27ddf099d6a95207dbb Mon Sep 17 00:00:00 2001 From: Tim Hogarty Date: Tue, 6 Aug 2024 16:50:45 -0700 Subject: [PATCH 08/12] Resolve linting errors --- internal/config/config.go | 9 ++++--- internal/framework/types/attrypes.go | 5 ---- .../resources/fabric/precision_time/models.go | 4 +-- .../fabric/precision_time/resource.go | 27 ------------------- 4 files changed, 7 insertions(+), 38 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index e94835f0e..d00037a19 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -76,6 +76,8 @@ type Config struct { TerraformVersion string } +type ctxKey string + // Load function validates configuration structure fields and configures // all required API clients. func (c *Config) Load(ctx context.Context) error { @@ -124,7 +126,7 @@ func (c *Config) Load(ctx context.Context) error { // equinix-sdk-go/fabricv4 client to be used to access Fabric's V4 APIs // Deprecated: migrate to NewFabricClientForFramework instead func (c *Config) NewFabricClientForSDK(d *schema.ResourceData) *fabricv4.APIClient { - client := c.newFabricClient(context.WithValue(context.Background(), "fabricSDKv2", "no_oauth_reload")) + client := c.newFabricClient(context.WithValue(context.Background(), ctxKey("fabricSDKv2"), "no_oauth_reload")) baseUserAgent := c.tfSdkUserAgent(client.GetConfig().UserAgent) client.GetConfig().UserAgent = generateModuleUserAgentString(d, baseUserAgent) @@ -135,7 +137,7 @@ func (c *Config) NewFabricClientForSDK(d *schema.ResourceData) *fabricv4.APIClie // Shim for Fabric tests. // Deprecated: when the acceptance package starts to contain API clients for testing/cleanup this will move with them func (c *Config) NewFabricClientForTesting() *fabricv4.APIClient { - client := c.newFabricClient(context.WithValue(context.Background(), "fabricSDKv2", "no_oauth_reload")) + client := c.newFabricClient(context.WithValue(context.Background(), ctxKey("fabricSDKv2"), "no_oauth_reload")) client.GetConfig().UserAgent = fmt.Sprintf("tf-acceptance-tests %v", client.GetConfig().UserAgent) @@ -156,13 +158,14 @@ func (c *Config) NewFabricClientForFramework(ctx context.Context, meta tfsdk.Con func (c *Config) newFabricClient(ctx context.Context) *fabricv4.APIClient { //nolint:staticcheck // We should move to subsystem loggers, but that is a much bigger change transport := logging.NewTransport("Equinix Fabric (fabricv4)", c.authClient.Transport) - if v := ctx.Value("fabricSDKv2"); v == nil { + if v := ctx.Value(ctxKey("fabricSDKv2")); v == nil { authConfig := oauth2.Config{ ClientID: c.ClientID, ClientSecret: c.ClientSecret, BaseURL: c.BaseURL, } authClient := authConfig.New(ctx) + //nolint:staticcheck // We should move to subsystem loggers, but that is a much bigger change transport = logging.NewTransport("Equinix Fabric (fabricv4)", authClient.Transport) } diff --git a/internal/framework/types/attrypes.go b/internal/framework/types/attrypes.go index 3a799c714..6413f5c0c 100644 --- a/internal/framework/types/attrypes.go +++ b/internal/framework/types/attrypes.go @@ -53,8 +53,3 @@ func AttributeTypes[T any](ctx context.Context) (map[string]attr.Type, error) { func AttributeTypesMust[T any](ctx context.Context) map[string]attr.Type { return equinix_errors.Must(AttributeTypes[T](ctx)) } - -func newAttrTypeOf[T attr.Value](ctx context.Context) attr.Type { - var zero T - return zero.Type(ctx) -} diff --git a/internal/resources/fabric/precision_time/models.go b/internal/resources/fabric/precision_time/models.go index 28f48e93f..58a9b3357 100644 --- a/internal/resources/fabric/precision_time/models.go +++ b/internal/resources/fabric/precision_time/models.go @@ -86,9 +86,7 @@ type PTPModel struct { } func (m *PrecisionTimeModel) parse(ctx context.Context, ept *fabricv4.PrecisionTimeServiceCreateResponse) diag.Diagnostics { - var diags diag.Diagnostics - - diags = parsePrecisionTime(ctx, ept, + diags := parsePrecisionTime(ctx, ept, &m.ID, &m.Type, &m.Href, &m.Uuid, &m.Name, &m.Description, &m.State, &m.ProjectId, &m.Package, &m.Ipv4, &m.Account, diff --git a/internal/resources/fabric/precision_time/resource.go b/internal/resources/fabric/precision_time/resource.go index 74be8a89a..1becd02da 100644 --- a/internal/resources/fabric/precision_time/resource.go +++ b/internal/resources/fabric/precision_time/resource.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/equinix/equinix-sdk-go/services/fabricv4" equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "net/http" @@ -417,32 +416,6 @@ func buildCreateRequest(ctx context.Context, plan PrecisionTimeModel) (fabricv4. return request, diags } -func getEpt(ctx context.Context, client *fabricv4.APIClient, state *tfsdk.State, id string) (*fabricv4.PrecisionTimeServiceCreateResponse, diag.Diagnostics) { - var diags diag.Diagnostics - - // Use API client to get the current state of the resource - ept, _, err := client.PrecisionTimeApi.GetTimeServicesById(ctx, id).Execute() - - if err != nil { - // If the Precision Time Service is not found, remove it from the state - if equinix_errors.IsNotFound(err) { - diags.AddWarning( - "Precision Time Service", - fmt.Sprintf("[WARN] Precision Time Service (%s) not found, removing from state", id), - ) - state.RemoveResource(ctx) - return nil, diags - } - - diags.AddError( - "Error reading Precision Time Service", - equinix_errors.FormatFabricError(err).Error(), - ) - return nil, diags - } - return ept, diags -} - func getCreateUpdateWaiter(ctx context.Context, client *fabricv4.APIClient, id string, timeout time.Duration) *retry.StateChangeConf { return &retry.StateChangeConf{ Target: []string{ From e659ad98c2d0261ef62dd506a30c8c9881359484 Mon Sep 17 00:00:00 2001 From: Tim Hogarty Date: Tue, 6 Aug 2024 17:04:47 -0700 Subject: [PATCH 09/12] Fix go imports for linting --- .../fabric/precision_time/datasources_schema.go | 6 ++++-- internal/resources/fabric/precision_time/models.go | 6 ++++-- internal/resources/fabric/precision_time/resource.go | 11 ++++++----- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/internal/resources/fabric/precision_time/datasources_schema.go b/internal/resources/fabric/precision_time/datasources_schema.go index 1da17e1a7..877bad125 100644 --- a/internal/resources/fabric/precision_time/datasources_schema.go +++ b/internal/resources/fabric/precision_time/datasources_schema.go @@ -3,13 +3,15 @@ package precision_time import ( "context" "fmt" + "github.com/equinix/equinix-sdk-go/services/fabricv4" - "github.com/equinix/terraform-provider-equinix/internal/framework" - fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/equinix/terraform-provider-equinix/internal/framework" + fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" ) func dataSourceSchema(ctx context.Context) schema.Schema { diff --git a/internal/resources/fabric/precision_time/models.go b/internal/resources/fabric/precision_time/models.go index 58a9b3357..330c667bb 100644 --- a/internal/resources/fabric/precision_time/models.go +++ b/internal/resources/fabric/precision_time/models.go @@ -2,13 +2,15 @@ package precision_time import ( "context" + "reflect" + "github.com/equinix/equinix-sdk-go/services/fabricv4" - fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" - "reflect" + + fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" ) type PrecisionTimeModel struct { diff --git a/internal/resources/fabric/precision_time/resource.go b/internal/resources/fabric/precision_time/resource.go index 1becd02da..406902bea 100644 --- a/internal/resources/fabric/precision_time/resource.go +++ b/internal/resources/fabric/precision_time/resource.go @@ -3,18 +3,19 @@ package precision_time import ( "context" "fmt" - "github.com/equinix/equinix-sdk-go/services/fabricv4" - equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors" - "github.com/hashicorp/terraform-plugin-framework/types/basetypes" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "net/http" "reflect" "slices" "time" - "github.com/equinix/terraform-provider-equinix/internal/framework" + "github.com/equinix/equinix-sdk-go/services/fabricv4" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + + equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors" + "github.com/equinix/terraform-provider-equinix/internal/framework" ) func NewResource() resource.Resource { From d8caa60708af25a5fb948bbc008e80b1997d37f2 Mon Sep 17 00:00:00 2001 From: Tim Hogarty Date: Tue, 6 Aug 2024 17:06:51 -0700 Subject: [PATCH 10/12] Fix go imports for linting --- internal/resources/fabric/precision_time/datasources.go | 9 +++++---- .../resources/fabric/precision_time/resource_schema.go | 6 ++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/internal/resources/fabric/precision_time/datasources.go b/internal/resources/fabric/precision_time/datasources.go index a2e5c4e76..8d55e7e58 100644 --- a/internal/resources/fabric/precision_time/datasources.go +++ b/internal/resources/fabric/precision_time/datasources.go @@ -1,12 +1,13 @@ package precision_time import ( - equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors" - "github.com/equinix/terraform-provider-equinix/internal/framework" - "github.com/hashicorp/terraform-plugin-framework/datasource" - "context" "fmt" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + + equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors" + "github.com/equinix/terraform-provider-equinix/internal/framework" ) func NewDataSource() datasource.DataSource { diff --git a/internal/resources/fabric/precision_time/resource_schema.go b/internal/resources/fabric/precision_time/resource_schema.go index e0f44edf4..9a17cf4ea 100644 --- a/internal/resources/fabric/precision_time/resource_schema.go +++ b/internal/resources/fabric/precision_time/resource_schema.go @@ -3,9 +3,8 @@ package precision_time import ( "context" "fmt" + "github.com/equinix/equinix-sdk-go/services/fabricv4" - "github.com/equinix/terraform-provider-equinix/internal/framework" - fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" @@ -14,6 +13,9 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/equinix/terraform-provider-equinix/internal/framework" + fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" ) func resourceSchema(ctx context.Context) schema.Schema { From 5f3b400b44b70a1525c0e597e60583a96f5e0b6e Mon Sep 17 00:00:00 2001 From: Tim Hogarty Date: Tue, 6 Aug 2024 17:23:38 -0700 Subject: [PATCH 11/12] Docs update after schema changes --- docs/data-sources/fabric_precision_time.md | 56 +++++++++----------- docs/resources/fabric_precision_time.md | 60 ++++++++++++---------- 2 files changed, 56 insertions(+), 60 deletions(-) diff --git a/docs/data-sources/fabric_precision_time.md b/docs/data-sources/fabric_precision_time.md index 7592c6da1..dec0f8660 100644 --- a/docs/data-sources/fabric_precision_time.md +++ b/docs/data-sources/fabric_precision_time.md @@ -32,39 +32,39 @@ data "equinix_fabric_precision_time" "time_service" { ### Read-Only -- `account` (Block, Read-only) Equinix User Account associated with Precision Time Service (see [below for nested schema](#nestedblock--account)) -- `advance_configuration` (Block, Read-only) An object that has advanced configuration options. (see [below for nested schema](#nestedblock--advance_configuration)) +- `account` (List of Object) Equinix User Account associated with Precision Time Service (see [below for nested schema](#nestedatt--account)) +- `advance_configuration` (List of Object) An object that has advanced configuration options. (see [below for nested schema](#nestedatt--advance_configuration)) - `connections` (Block List) An array of objects with unique identifiers of connections. (see [below for nested schema](#nestedblock--connections)) - `description` (String) Optional description of time service - `href` (String) Equinix generated Portal link for the created Precision Time Service - `id` (String) The unique identifier of the resource -- `ipv4` (Block Set) An object that has Network IP Configurations for Timing Master Servers. (see [below for nested schema](#nestedblock--ipv4)) +- `ipv4` (Block, Read-only) An object that has Network IP Configurations for Timing Master Servers. (see [below for nested schema](#nestedblock--ipv4)) - `name` (String) Name of Precision Time Service. Applicable values: Maximum: 24 characters; Allowed characters: alpha-numeric, hyphens ('-') and underscores ('_') -- `package` (Block Set) Precision Time Service Package Details (see [below for nested schema](#nestedblock--package)) -- `project` (Block Set) An object that contains the Equinix Fabric project_id used for linking the Time Precision Service to a specific Equinix Fabric Project (see [below for nested schema](#nestedblock--project)) +- `package` (Block, Read-only) Precision Time Service Package Details (see [below for nested schema](#nestedblock--package)) +- `project_id` (String) Equinix Fabric Project ID - `state` (String) Indicator of the state of this Precision Time Service. One of: [[PROVISIONED PROVISIONING PROVISIONING_FAILED CONFIGURING CANCELLED DEPROVISIONING_FAILED PENDING_CONFIGURATION DEPROVISIONED CONFIGURING_FAILED DEPROVISIONING]] - `type` (String) Choose type of Precision Time Service - + ### Nested Schema for `account` Read-Only: -- `account_number` (Number) Equinix User account number -- `global_org_id` (String) Equinix User global organization id -- `is_reseller_account` (Boolean) Equinix User Boolean flag indicating if it is a reseller account -- `org_id` (String) Equinix User organization id +- `account_number` (Number) +- `global_org_id` (String) +- `is_reseller_account` (Boolean) +- `org_id` (String) - + ### Nested Schema for `advance_configuration` Read-Only: -- `ntp` (List of Object) Advance Configuration for NTP; a list of MD5 objects (see [below for nested schema](#nestedatt--advance_configuration--ntp)) -- `ptp` (Block, Read-only) An object that has advanced PTP configuration. (see [below for nested schema](#nestedblock--advance_configuration--ptp)) +- `ntp` (List of Object) (see [below for nested schema](#nestedobjatt--advance_configuration--ntp)) +- `ptp` (List of Object) (see [below for nested schema](#nestedobjatt--advance_configuration--ptp)) - + ### Nested Schema for `advance_configuration.ntp` Read-Only: @@ -74,20 +74,20 @@ Read-Only: - `type` (String) - + ### Nested Schema for `advance_configuration.ptp` Read-Only: -- `domain` (Number) Represents the domain number associated with the PTP profile. This is used to differentiate multiple PTP networks within a single physical network. -- `grant_time` (Number) Unicast Grant Time in seconds. For Multicast and Hybrid transport modes, grant time defaults to 300 seconds. For Unicast mode, grant time can be between 30 to 7200. -- `log_announce_interval` (Number) Represents the log2 interval between consecutive PTP announce messages. For example, a value of 0 implies an interval of 2^0 = 1 second. -- `log_delay_req_interval` (Number) Represents the log2 interval between consecutive PTP delay request messages. A value of 0 implies an interval of 2^0 = 1 second. -- `log_sync_interval` (Number) Represents the log2 interval between consecutive PTP synchronization messages. A value of 0 implies an interval of 2^0 = 1 second. -- `priority_1` (Number) Specifies the priority level 1 for the clock. The value helps in determining the best clock in the PTP network. Lower values are considered higher priority. -- `priority_2` (Number) Specifies the priority level 2 for the clock. It acts as a tie-breaker if multiple clocks have the same priority 1 value. Lower values are considered higher priority. -- `time_scale` (String) Time scale value. ARB denotes Arbitrary, and PTP denotes Precision Time Protocol. -- `transport_mode` (String) Mode of transport for the Time Precision Service. +- `domain` (Number) +- `grant_time` (Number) +- `log_announce_interval` (Number) +- `log_delay_req_interval` (Number) +- `log_sync_interval` (Number) +- `priority_1` (Number) +- `priority_2` (Number) +- `time_scale` (String) +- `transport_mode` (String) @@ -130,12 +130,4 @@ Read-Only: - `type` (String) Type of the Precision Time Service Package - -### Nested Schema for `project` - -Read-Only: - -- `project_id` (String) Equinix Fabric Project ID - - diff --git a/docs/resources/fabric_precision_time.md b/docs/resources/fabric_precision_time.md index fd9aa073e..71e1441ee 100644 --- a/docs/resources/fabric_precision_time.md +++ b/docs/resources/fabric_precision_time.md @@ -68,30 +68,31 @@ resource "equinix_fabric_precision_time" "ntp" { ### Optional -- `advance_configuration` (Block, Optional) An object that has advanced configuration options. (see [below for nested schema](#nestedblock--advance_configuration)) +- `advance_configuration` (List of Object) An object that has advanced configuration options. (see [below for nested schema](#nestedatt--advance_configuration)) - `connections` (Block List) An array of objects with unique identifiers of connections. (see [below for nested schema](#nestedblock--connections)) - `description` (String) Optional description of time service -- `ipv4` (Block Set) An object that has Network IP Configurations for Timing Master Servers. (see [below for nested schema](#nestedblock--ipv4)) -- `package` (Block Set) Precision Time Service Package Details (see [below for nested schema](#nestedblock--package)) -- `project` (Block Set) An object that contains the Equinix Fabric project_id used for linking the Time Precision Service to a specific Equinix Fabric Project (see [below for nested schema](#nestedblock--project)) +- `ipv4` (Block, Optional) An object that has Network IP Configurations for Timing Master Servers. (see [below for nested schema](#nestedblock--ipv4)) +- `package` (Block, Optional) Precision Time Service Package Details (see [below for nested schema](#nestedblock--package)) +- `project_id` (String) Equinix Fabric Project ID +- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts)) ### Read-Only -- `account` (Block, Read-only) Equinix User Account associated with Precision Time Service (see [below for nested schema](#nestedblock--account)) +- `account` (List of Object) Equinix User Account associated with Precision Time Service (see [below for nested schema](#nestedatt--account)) - `href` (String) Equinix generated Portal link for the created Precision Time Service - `id` (String) The unique identifier of the resource - `state` (String) Indicator of the state of this Precision Time Service. One of: [[PROVISIONED PROVISIONING PROVISIONING_FAILED CONFIGURING CANCELLED DEPROVISIONING_FAILED PENDING_CONFIGURATION DEPROVISIONED CONFIGURING_FAILED DEPROVISIONING]] - `uuid` (String) Equinix generated id for the Precision Time Service - + ### Nested Schema for `advance_configuration` Optional: -- `ntp` (List of Object) Advance Configuration for NTP; a list of MD5 objects (see [below for nested schema](#nestedatt--advance_configuration--ntp)) -- `ptp` (Block, Optional) An object that has advanced PTP configuration. (see [below for nested schema](#nestedblock--advance_configuration--ptp)) +- `ntp` (List of Object) (see [below for nested schema](#nestedobjatt--advance_configuration--ntp)) +- `ptp` (List of Object) (see [below for nested schema](#nestedobjatt--advance_configuration--ptp)) - + ### Nested Schema for `advance_configuration.ntp` Optional: @@ -101,20 +102,20 @@ Optional: - `type` (String) - + ### Nested Schema for `advance_configuration.ptp` Optional: -- `domain` (Number) Represents the domain number associated with the PTP profile. This is used to differentiate multiple PTP networks within a single physical network. -- `grant_time` (Number) Unicast Grant Time in seconds. For Multicast and Hybrid transport modes, grant time defaults to 300 seconds. For Unicast mode, grant time can be between 30 to 7200. -- `log_announce_interval` (Number) Represents the log2 interval between consecutive PTP announce messages. For example, a value of 0 implies an interval of 2^0 = 1 second. -- `log_delay_req_interval` (Number) Represents the log2 interval between consecutive PTP delay request messages. A value of 0 implies an interval of 2^0 = 1 second. -- `log_sync_interval` (Number) Represents the log2 interval between consecutive PTP synchronization messages. A value of 0 implies an interval of 2^0 = 1 second. -- `priority_1` (Number) Specifies the priority level 1 for the clock. The value helps in determining the best clock in the PTP network. Lower values are considered higher priority. -- `priority_2` (Number) Specifies the priority level 2 for the clock. It acts as a tie-breaker if multiple clocks have the same priority 1 value. Lower values are considered higher priority. -- `time_scale` (String) Time scale value. ARB denotes Arbitrary, and PTP denotes Precision Time Protocol. -- `transport_mode` (String) Mode of transport for the Time Precision Service. +- `domain` (Number) +- `grant_time` (Number) +- `log_announce_interval` (Number) +- `log_delay_req_interval` (Number) +- `log_sync_interval` (Number) +- `priority_1` (Number) +- `priority_2` (Number) +- `time_scale` (String) +- `transport_mode` (String) @@ -166,23 +167,26 @@ Read-Only: - `type` (String) Type of the Precision Time Service Package - -### Nested Schema for `project` + +### Nested Schema for `timeouts` -Required: +Optional: -- `project_id` (String) Equinix Fabric Project ID +- `create` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). +- `delete` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Setting a timeout for a Delete operation is only applicable if changes are saved into state before the destroy operation occurs. +- `read` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Read operations occur during any refresh or planning operation when refresh is enabled. +- `update` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). - + ### Nested Schema for `account` Read-Only: -- `account_number` (Number) Equinix User account number -- `global_org_id` (String) Equinix User global organization id -- `is_reseller_account` (Boolean) Equinix User Boolean flag indicating if it is a reseller account -- `org_id` (String) Equinix User organization id +- `account_number` (Number) +- `global_org_id` (String) +- `is_reseller_account` (Boolean) +- `org_id` (String) From 3ccd4d277900f3f36e4a79052739de1756b635cc Mon Sep 17 00:00:00 2001 From: Tim Hogarty Date: Tue, 6 Aug 2024 18:37:28 -0700 Subject: [PATCH 12/12] Add acceptance test starter --- .../fabric/precision_time/datasources_test.go | 3 + .../fabric/precision_time/resource_test.go | 267 ++++++++++++++++++ 2 files changed, 270 insertions(+) diff --git a/internal/resources/fabric/precision_time/datasources_test.go b/internal/resources/fabric/precision_time/datasources_test.go index db8c82ab3..5559a2080 100644 --- a/internal/resources/fabric/precision_time/datasources_test.go +++ b/internal/resources/fabric/precision_time/datasources_test.go @@ -1 +1,4 @@ package precision_time + +// Tested in resource test because of the expense to create connection and precision time +// resources before performing data retrieval diff --git a/internal/resources/fabric/precision_time/resource_test.go b/internal/resources/fabric/precision_time/resource_test.go index db8c82ab3..7904076b6 100644 --- a/internal/resources/fabric/precision_time/resource_test.go +++ b/internal/resources/fabric/precision_time/resource_test.go @@ -1 +1,268 @@ package precision_time + +import ( + "fmt" + "testing" + + "github.com/equinix/terraform-provider-equinix/internal/acceptance" + "github.com/equinix/terraform-provider-equinix/internal/fabric/testing_helpers" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccFabricCreatePort2EPT_NTPConfiguration_PFCR(t *testing.T) { + ports := testing_helpers.GetFabricEnvPorts(t) + var portUuid string + if len(ports) > 0 { + portUuid = ports["pfcr"]["dot1q"][0].GetUuid() + } + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.TestAccPreCheck(t); acceptance.TestAccPreCheckProviderConfigured(t) }, + Providers: acceptance.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccFabricCreatePort2EPTNPTConfig("NTP Configured Precision Time Service", "port2eptntp_PFCR", portUuid, "SV"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("equinix_fabric_precision_time.ntp", "id"), + resource.TestCheckResourceAttrSet("equinix_fabric_precision_time.ntp", "connections.0.uuid"), + resource.TestCheckResourceAttrSet("equinix_fabric_precision_time.ntp", "project_id"), + resource.TestCheckResourceAttr( + "equinix_fabric_precision_time.ntp", "name", "tf_acc_eptntp_PFCR"), + resource.TestCheckResourceAttr( + "equinix_fabric_precision_time.ntp", "type", "NTP"), + resource.TestCheckResourceAttr( + "equinix_fabric_precision_time.ntp", "package.code", "PTP_STANDARD"), + resource.TestCheckResourceAttr( + "equinix_fabric_precision_time.ntp", "ipv4.primary", "192.168.254.241"), + resource.TestCheckResourceAttr( + "equinix_fabric_precision_time.ntp", "ipv4.secondary", "192.168.254.242"), + resource.TestCheckResourceAttr( + "equinix_fabric_precision_time.ntp", "ipv4.network_mask", "255.255.255.240"), + resource.TestCheckResourceAttr( + "equinix_fabric_precision_time.ntp", "ipv4.default_gateway", "192.168.254.254"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_precision_time.ntp", "id"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_precision_time.ntp", "connections.0.uuid"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_precision_time.ntp", "project_id"), + resource.TestCheckResourceAttr( + "data.equinix_fabric_precision_time.ntp", "name", "tf_acc_eptntp_PFCR"), + resource.TestCheckResourceAttr( + "data.equinix_fabric_precision_time.ntp", "type", "NTP"), + resource.TestCheckResourceAttr( + "data.equinix_fabric_precision_time.ntp", "package.code", "PTP_STANDARD"), + resource.TestCheckResourceAttr( + "data.equinix_fabric_precision_time.ntp", "ipv4.primary", "192.168.254.241"), + resource.TestCheckResourceAttr( + "data.equinix_fabric_precision_time.ntp", "ipv4.secondary", "192.168.254.242"), + resource.TestCheckResourceAttr( + "data.equinix_fabric_precision_time.ntp", "ipv4.network_mask", "255.255.255.240"), + resource.TestCheckResourceAttr( + "data.equinix_fabric_precision_time.ntp", "ipv4.default_gateway", "192.168.254.254"), + ), + ExpectNonEmptyPlan: false, + }, + }, + }) + +} + +func testAccFabricCreatePort2EPTNPTConfig(spName, name, portUuid, zSideMetro string) string { + return fmt.Sprintf(` + + data "equinix_fabric_service_profiles" "this" { + filter { + property = "/name" + operator = "=" + values = ["%s"] + } + } + + + resource "equinix_fabric_connection" "test" { + name = "%s" + type = "EVPL_VC" + notifications{ + type="ALL" + emails=["example@equinix.com"] + } + bandwidth = 1 + redundancy {priority= "PRIMARY"} + order { + purchase_order_number= "1-323292" + } + a_side { + access_point { + type= "COLO" + port { + uuid= "%s" + } + link_protocol { + type= "DOT1Q" + vlan_tag= "1354" + } + } + } + z_side { + access_point { + type= "SP" + profile { + type= "L2_PROFILE" + uuid= data.equinix_fabric_service_profiles.this.data.0.uuid + } + location { + metro_code= "%s" + } + } + } + } + + resource "equinix_fabric_precision_time" "ntp" { + type = "NTP" + name = "tf_acc_eptntp_PFCR" + package { + code = "NTP_STANDARD" + } + connections { + uuid = equinix_fabric_connection.test.id + } + ipv4 { + primary = "192.168.254.241" + secondary = "192.168.254.242" + network_mask = "255.255.255.240" + default_gateway = "192.168.254.254" + } + } + + data "equinix_fabric_precision_time" "ntp" { + uuid = equinix_precision_time.ntp.id + } + +`, spName, name, portUuid, zSideMetro) +} + +func TestAccFabricCreatePort2EPT_PTPConfiguration_PFCR(t *testing.T) { + ports := testing_helpers.GetFabricEnvPorts(t) + var portUuid string + if len(ports) > 0 { + portUuid = ports["pfcr"]["dot1q"][0].GetUuid() + } + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.TestAccPreCheck(t); acceptance.TestAccPreCheckProviderConfigured(t) }, + Providers: acceptance.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccFabricCreatePort2EPTPTPConfig("PTP Configured Precision Time Service", "port2eptptp_PFCR", portUuid, "SV"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("equinix_fabric_precision_time.ptp", "id"), + resource.TestCheckResourceAttrSet("equinix_fabric_precision_time.ptp", "connections.0.uuid"), + resource.TestCheckResourceAttrSet("equinix_fabric_precision_time.ptp", "project_id"), + resource.TestCheckResourceAttr( + "equinix_fabric_precision_time.ptp", "name", "tf_acc_eptptp_PFCR"), + resource.TestCheckResourceAttr( + "equinix_fabric_precision_time.ptp", "type", "PTP"), + resource.TestCheckResourceAttr( + "equinix_fabric_precision_time.ptp", "package.code", "PTP_STANDARD"), + resource.TestCheckResourceAttr( + "equinix_fabric_precision_time.ptp", "ipv4.primary", "192.168.254.241"), + resource.TestCheckResourceAttr( + "equinix_fabric_precision_time.ptp", "ipv4.secondary", "192.168.254.242"), + resource.TestCheckResourceAttr( + "equinix_fabric_precision_time.ptp", "ipv4.network_mask", "255.255.255.240"), + resource.TestCheckResourceAttr( + "equinix_fabric_precision_time.ptp", "ipv4.default_gateway", "192.168.254.254"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_precision_time.ptp", "id"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_precision_time.ptp", "connections.0.uuid"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_precision_time.ptp", "project_id"), + resource.TestCheckResourceAttr( + "data.equinix_fabric_precision_time.ptp", "name", "tf_acc_eptptp_PFCR"), + resource.TestCheckResourceAttr( + "data.equinix_fabric_precision_time.ptp", "type", "PTP"), + resource.TestCheckResourceAttr( + "data.equinix_fabric_precision_time.ptp", "package.code", "PTP_STANDARD"), + resource.TestCheckResourceAttr( + "data.equinix_fabric_precision_time.ptp", "ipv4.primary", "192.168.254.241"), + resource.TestCheckResourceAttr( + "data.equinix_fabric_precision_time.ptp", "ipv4.secondary", "192.168.254.242"), + resource.TestCheckResourceAttr( + "data.equinix_fabric_precision_time.ptp", "ipv4.network_mask", "255.255.255.240"), + resource.TestCheckResourceAttr( + "data.equinix_fabric_precision_time.ptp", "ipv4.default_gateway", "192.168.254.254"), + ), + ExpectNonEmptyPlan: false, + }, + }, + }) + +} + +func testAccFabricCreatePort2EPTPTPConfig(spName, name, portUuid, zSideMetro string) string { + return fmt.Sprintf(` + + data "equinix_fabric_service_profiles" "this" { + filter { + property = "/name" + operator = "=" + values = ["%s"] + } + } + + + resource "equinix_fabric_connection" "test" { + name = "%s" + type = "EVPL_VC" + notifications{ + type="ALL" + emails=["example@equinix.com"] + } + bandwidth = 5 + redundancy {priority= "PRIMARY"} + order { + purchase_order_number= "1-323292" + } + a_side { + access_point { + type= "COLO" + port { + uuid= "%s" + } + link_protocol { + type= "DOT1Q" + vlan_tag= "1355" + } + } + } + z_side { + access_point { + type= "SP" + profile { + type= "L2_PROFILE" + uuid= data.equinix_fabric_service_profiles.this.data.0.uuid + } + location { + metro_code= "%s" + } + } + } + } + + resource "equinix_fabric_precision_time" "ptp" { + type = "PTP" + name = "tf_acc_eptptp_PFCR" + package { + code = "PTP_STANDARD" + } + connections { + uuid = equinix_fabric_connection.test.id + } + ipv4 { + primary = "192.168.254.241" + secondary = "192.168.254.242" + network_mask = "255.255.255.240" + default_gateway = "192.168.254.254" + } + } + + data "equinix_fabric_precision_time" "ptp" { + uuid = equinix_precision_time.ptp.id + } + +`, spName, name, portUuid, zSideMetro) +}