From ae857bca057e7ac65b93f02cd14f7408b95cd4af Mon Sep 17 00:00:00 2001 From: David Bloss Date: Mon, 15 Jul 2024 10:22:38 -0700 Subject: [PATCH] add StateUpgrader for opslevel_check_service_ownership (#401) * WIP: add StateUpgrader for opslevel_check_service_ownership * WIP: fixing StateImporter * fix StateUpgrader for opslevel_check_service_ownership resource * add StateUpgraders for remaining check resources * add changie log * add Version to check schemas * fix field type of schema in check repo file StateUpgrader * add StateUpgrader to opslevel_check_repository_grep resource --- .../unreleased/Bugfix-20240715-112534.yaml | 3 + ...ource_opslevel_check_alert_source_usage.go | 55 ++++++++++++++ opslevel/resource_opslevel_check_base.go | 63 ++++++++++++++++ ...resource_opslevel_check_repository_file.go | 66 ++++++++++++++++ ...resource_opslevel_check_repository_grep.go | 60 +++++++++++++++ ...source_opslevel_check_repository_search.go | 55 ++++++++++++++ ...source_opslevel_check_service_ownership.go | 68 +++++++++++++++++ ...esource_opslevel_check_service_property.go | 55 ++++++++++++++ .../resource_opslevel_check_tag_defined.go | 55 ++++++++++++++ .../resource_opslevel_check_tool_usage.go | 75 +++++++++++++++++++ 10 files changed, 555 insertions(+) create mode 100644 .changes/unreleased/Bugfix-20240715-112534.yaml diff --git a/.changes/unreleased/Bugfix-20240715-112534.yaml b/.changes/unreleased/Bugfix-20240715-112534.yaml new file mode 100644 index 00000000..d439df2f --- /dev/null +++ b/.changes/unreleased/Bugfix-20240715-112534.yaml @@ -0,0 +1,3 @@ +kind: Bugfix +body: fix Terraform state upgrades for previous provider versions on checks resources +time: 2024-07-15T11:25:34.595155-05:00 diff --git a/opslevel/resource_opslevel_check_alert_source_usage.go b/opslevel/resource_opslevel_check_alert_source_usage.go index dd6f5983..c2b789df 100644 --- a/opslevel/resource_opslevel_check_alert_source_usage.go +++ b/opslevel/resource_opslevel_check_alert_source_usage.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -92,6 +93,7 @@ func (r *CheckAlertSourceUsageResource) Metadata(ctx context.Context, req resour func (r *CheckAlertSourceUsageResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ + Version: 1, // This description is used by the documentation generator and the language server. MarkdownDescription: "Check Alert Source Usage Resource", @@ -109,6 +111,59 @@ func (r *CheckAlertSourceUsageResource) Schema(ctx context.Context, req resource } } +func (r *CheckAlertSourceUsageResource) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader { + return map[int64]resource.StateUpgrader{ + // State upgrade implementation from 0 (prior state version) to 1 (Schema.Version) + 0: { + PriorSchema: &schema.Schema{ + Description: "Check Alert Source Usage Resource", + Attributes: getCheckBaseSchemaV0(map[string]schema.Attribute{ + "alert_type": schema.StringAttribute{ + Description: "The type of the alert source.", + Required: true, + }, + "id": schema.StringAttribute{ + Description: "The ID of this resource.", + Computed: true, + }, + }), + Blocks: map[string]schema.Block{ + "alert_name_predicate": schema.ListNestedBlock{ + NestedObject: predicateSchemaV0, + }, + }, + }, + StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) { + var diags diag.Diagnostics + upgradedStateModel := CheckAlertSourceUsageResourceModel{} + alertNamePredicateList := types.ListNull(types.ObjectType{AttrTypes: predicateType}) + + // base check attributes + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("category"), &upgradedStateModel.Category)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("enable_on"), &upgradedStateModel.EnableOn)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("enabled"), &upgradedStateModel.Enabled)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("filter"), &upgradedStateModel.Filter)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("id"), &upgradedStateModel.Id)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("level"), &upgradedStateModel.Level)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("name"), &upgradedStateModel.Name)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("notes"), &upgradedStateModel.Notes)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("owner"), &upgradedStateModel.Owner)...) + + // alert source specific attributes + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("alert_type"), &upgradedStateModel.AlertType)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("alert_name_predicate"), &alertNamePredicateList)...) + if len(alertNamePredicateList.Elements()) == 1 { + alertNamePredicate := alertNamePredicateList.Elements()[0] + upgradedStateModel.AlertNamePredicate, diags = types.ObjectValueFrom(ctx, predicateType, alertNamePredicate) + resp.Diagnostics.Append(diags...) + } + + resp.Diagnostics.Append(resp.State.Set(ctx, upgradedStateModel)...) + }, + }, + } +} + func (r *CheckAlertSourceUsageResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { var configModel CheckAlertSourceUsageResourceModel resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...) diff --git a/opslevel/resource_opslevel_check_base.go b/opslevel/resource_opslevel_check_base.go index 231e01a9..c23475db 100644 --- a/opslevel/resource_opslevel_check_base.go +++ b/opslevel/resource_opslevel_check_base.go @@ -136,3 +136,66 @@ func PredicateSchema() schema.SingleNestedAttribute { }, } } + +// pre-v1.x base schema fields for checks used by StateUpgraders +func getCheckBaseSchemaV0(extras map[string]schema.Attribute) map[string]schema.Attribute { + output := map[string]schema.Attribute{ + "last_updated": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + "name": schema.StringAttribute{ + Description: "The display name of the check.", + Required: true, + }, + "enabled": schema.BoolAttribute{ + Description: `Whether the check is enabled or not. Do not use this field in tandem with 'enable_on'.`, + Required: true, + }, + "enable_on": schema.StringAttribute{ + Description: `The date when the check will be automatically enabled. +If you use this field you should add both 'enabled' and 'enable_on' to the lifecycle ignore_changes settings. +See example in opslevel_check_manual for proper configuration. +`, + Optional: true, + }, + "category": schema.StringAttribute{ + Description: "The id of the category the check belongs to.", + Required: true, + }, + "level": schema.StringAttribute{ + Description: "The id of the level the check belongs to.", + Required: true, + }, + "owner": schema.StringAttribute{ + Description: "The id of the team that owns the check.", + Optional: true, + }, + "filter": schema.StringAttribute{ + Description: "The id of the filter of the check.", + Optional: true, + }, + "notes": schema.StringAttribute{ + Description: "Additional information about the check.", + Optional: true, + }, + } + for k, v := range extras { + output[k] = v + } + return output +} + +var predicateSchemaV0 = schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Description: "A condition that should be satisfied.", + Required: true, + Validators: []validator.String{stringvalidator.OneOf(opslevel.AllPredicateTypeEnum...)}, + }, + "value": schema.StringAttribute{ + Description: "The condition value used by the predicate.", + Optional: true, + }, + }, +} diff --git a/opslevel/resource_opslevel_check_repository_file.go b/opslevel/resource_opslevel_check_repository_file.go index 87023aab..24d65218 100644 --- a/opslevel/resource_opslevel_check_repository_file.go +++ b/opslevel/resource_opslevel_check_repository_file.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -94,6 +95,7 @@ func (r *CheckRepositoryFileResource) Metadata(ctx context.Context, req resource func (r *CheckRepositoryFileResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ + Version: 1, // This description is used by the documentation generator and the language server. MarkdownDescription: "Check Repository File Resource", @@ -116,6 +118,70 @@ func (r *CheckRepositoryFileResource) Schema(ctx context.Context, req resource.S } } +func (r *CheckRepositoryFileResource) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader { + return map[int64]resource.StateUpgrader{ + // State upgrade implementation from 0 (prior state version) to 1 (Schema.Version) + 0: { + PriorSchema: &schema.Schema{ + Description: "Check Repository File Resource", + Attributes: getCheckBaseSchemaV0(map[string]schema.Attribute{ + "directory_search": schema.BoolAttribute{ + Description: "Whether the check looks for the existence of a directory instead of a file.", + Required: true, + }, + "filepaths": schema.ListAttribute{ + Description: "Restrict the search to certain file paths.", + ElementType: types.StringType, + Required: true, + }, + "id": schema.StringAttribute{ + Description: "The ID of this resource.", + Computed: true, + }, + "use_absolute_root": schema.BoolAttribute{ + Description: "Whether the checks looks at the absolute root of a repo or the relative root (the directory specified when attached a repo to a service).", + Required: true, + }, + }), + Blocks: map[string]schema.Block{ + "file_contents_predicate": schema.ListNestedBlock{ + NestedObject: predicateSchemaV0, + }, + }, + }, + StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) { + var diags diag.Diagnostics + upgradedStateModel := CheckRepositoryFileResourceModel{} + fileContentsPredicateList := types.ListNull(types.ObjectType{AttrTypes: predicateType}) + + // base check attributes + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("category"), &upgradedStateModel.Category)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("enable_on"), &upgradedStateModel.EnableOn)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("enabled"), &upgradedStateModel.Enabled)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("filter"), &upgradedStateModel.Filter)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("id"), &upgradedStateModel.Id)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("level"), &upgradedStateModel.Level)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("name"), &upgradedStateModel.Name)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("notes"), &upgradedStateModel.Notes)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("owner"), &upgradedStateModel.Owner)...) + + // repository file specific attributes + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("directory_search"), &upgradedStateModel.DirectorySearch)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("filepaths"), &upgradedStateModel.Filepaths)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("use_absolute_root"), &upgradedStateModel.UseAbsoluteRoot)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("file_contents_predicate"), &fileContentsPredicateList)...) + if len(fileContentsPredicateList.Elements()) == 1 { + fileContentsPredicate := fileContentsPredicateList.Elements()[0] + upgradedStateModel.FileContentsPredicate, diags = types.ObjectValueFrom(ctx, predicateType, fileContentsPredicate) + resp.Diagnostics.Append(diags...) + } + + resp.Diagnostics.Append(resp.State.Set(ctx, upgradedStateModel)...) + }, + }, + } +} + func (r *CheckRepositoryFileResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { var configModel CheckRepositoryFileResourceModel resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...) diff --git a/opslevel/resource_opslevel_check_repository_grep.go b/opslevel/resource_opslevel_check_repository_grep.go index add4a318..7a4c07ef 100644 --- a/opslevel/resource_opslevel_check_repository_grep.go +++ b/opslevel/resource_opslevel_check_repository_grep.go @@ -6,6 +6,7 @@ import ( "slices" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -111,6 +112,65 @@ func (r *CheckRepositoryGrepResource) Schema(ctx context.Context, req resource.S } } +func (r *CheckRepositoryGrepResource) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader { + return map[int64]resource.StateUpgrader{ + // State upgrade implementation from 0 (prior state version) to 1 (Schema.Version) + 0: { + PriorSchema: &schema.Schema{ + Description: "Check Repository Grep Resource", + Attributes: getCheckBaseSchemaV0(map[string]schema.Attribute{ + "directory_search": schema.BoolAttribute{ + Description: "Whether the check looks for the existence of a directory instead of a file.", + Required: true, + }, + "filepaths": schema.ListAttribute{ + Description: "Restrict the search to certain file paths.", + ElementType: types.StringType, + Required: true, + }, + "id": schema.StringAttribute{ + Description: "The ID of this resource.", + Computed: true, + }, + }), + Blocks: map[string]schema.Block{ + "file_contents_predicate": schema.ListNestedBlock{ + NestedObject: predicateSchemaV0, + }, + }, + }, + StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) { + var diags diag.Diagnostics + upgradedStateModel := CheckRepositoryGrepResourceModel{} + fileContentsPredicateList := types.ListNull(types.ObjectType{AttrTypes: predicateType}) + + // base check attributes + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("category"), &upgradedStateModel.Category)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("enable_on"), &upgradedStateModel.EnableOn)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("enabled"), &upgradedStateModel.Enabled)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("filter"), &upgradedStateModel.Filter)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("id"), &upgradedStateModel.Id)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("level"), &upgradedStateModel.Level)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("name"), &upgradedStateModel.Name)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("notes"), &upgradedStateModel.Notes)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("owner"), &upgradedStateModel.Owner)...) + + // repository grep specific attributes + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("directory_search"), &upgradedStateModel.DirectorySearch)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("filepaths"), &upgradedStateModel.Filepaths)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("file_contents_predicate"), &fileContentsPredicateList)...) + if len(fileContentsPredicateList.Elements()) == 1 { + fileContentsPredicate := fileContentsPredicateList.Elements()[0] + upgradedStateModel.FileContentsPredicate, diags = types.ObjectValueFrom(ctx, predicateType, fileContentsPredicate) + resp.Diagnostics.Append(diags...) + } + + resp.Diagnostics.Append(resp.State.Set(ctx, upgradedStateModel)...) + }, + }, + } +} + func (r *CheckRepositoryGrepResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { var configModel CheckRepositoryGrepResourceModel resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...) diff --git a/opslevel/resource_opslevel_check_repository_search.go b/opslevel/resource_opslevel_check_repository_search.go index 6c615677..f15f2451 100644 --- a/opslevel/resource_opslevel_check_repository_search.go +++ b/opslevel/resource_opslevel_check_repository_search.go @@ -101,6 +101,7 @@ func (r *CheckRepositorySearchResource) Schema(ctx context.Context, req resource predicateSchema.Required = true resp.Schema = schema.Schema{ + Version: 1, // This description is used by the documentation generator and the language server. MarkdownDescription: "Check Repository Search Resource", @@ -118,6 +119,60 @@ func (r *CheckRepositorySearchResource) Schema(ctx context.Context, req resource } } +func (r *CheckRepositorySearchResource) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader { + return map[int64]resource.StateUpgrader{ + // State upgrade implementation from 0 (prior state version) to 1 (Schema.Version) + 0: { + PriorSchema: &schema.Schema{ + Description: "Check Repository File Resource", + Attributes: getCheckBaseSchemaV0(map[string]schema.Attribute{ + "file_extensions": schema.ListAttribute{ + Description: "Restrict the search to certain file paths.", + ElementType: types.StringType, + Optional: true, + }, + "id": schema.StringAttribute{ + Description: "The ID of this resource.", + Computed: true, + }, + }), + Blocks: map[string]schema.Block{ + "file_contents_predicate": schema.ListNestedBlock{ + NestedObject: predicateSchemaV0, + }, + }, + }, + StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) { + var diags diag.Diagnostics + upgradedStateModel := CheckRepositoryFileResourceModel{} + fileContentsPredicateList := types.ListNull(types.ObjectType{AttrTypes: predicateType}) + + // base check attributes + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("category"), &upgradedStateModel.Category)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("enable_on"), &upgradedStateModel.EnableOn)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("enabled"), &upgradedStateModel.Enabled)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("filter"), &upgradedStateModel.Filter)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("id"), &upgradedStateModel.Id)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("level"), &upgradedStateModel.Level)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("name"), &upgradedStateModel.Name)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("notes"), &upgradedStateModel.Notes)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("owner"), &upgradedStateModel.Owner)...) + + // repository file specific attributes + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("file_extensions"), &upgradedStateModel.Filepaths)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("file_contents_predicate"), &fileContentsPredicateList)...) + if len(fileContentsPredicateList.Elements()) == 1 { + fileContentsPredicate := fileContentsPredicateList.Elements()[0] + upgradedStateModel.FileContentsPredicate, diags = types.ObjectValueFrom(ctx, predicateType, fileContentsPredicate) + resp.Diagnostics.Append(diags...) + } + + resp.Diagnostics.Append(resp.State.Set(ctx, upgradedStateModel)...) + }, + }, + } +} + func (r *CheckRepositorySearchResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { var configModel CheckRepositorySearchResourceModel resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...) diff --git a/opslevel/resource_opslevel_check_service_ownership.go b/opslevel/resource_opslevel_check_service_ownership.go index cf8c5004..15a5dcc2 100644 --- a/opslevel/resource_opslevel_check_service_ownership.go +++ b/opslevel/resource_opslevel_check_service_ownership.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -22,6 +23,7 @@ import ( var ( _ resource.ResourceWithConfigure = &CheckServiceOwnershipResource{} _ resource.ResourceWithImportState = &CheckServiceOwnershipResource{} + _ resource.ResourceWithUpgradeState = &CheckServiceOwnershipResource{} _ resource.ResourceWithValidateConfig = &CheckServiceOwnershipResource{} ) @@ -107,6 +109,7 @@ func (r *CheckServiceOwnershipResource) Metadata(ctx context.Context, req resour func (r *CheckServiceOwnershipResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { enumAllContactTypes := append(opslevel.AllContactType, "any") resp.Schema = schema.Schema{ + Version: 1, // This description is used by the documentation generator and the language server. MarkdownDescription: "Check Service Ownership Resource", @@ -136,6 +139,71 @@ func (r *CheckServiceOwnershipResource) Schema(ctx context.Context, req resource } } +func (r *CheckServiceOwnershipResource) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader { + enumAllContactTypes := append(opslevel.AllContactType, "any") + return map[int64]resource.StateUpgrader{ + // State upgrade implementation from 0 (prior state version) to 1 (Schema.Version) + 0: { + PriorSchema: &schema.Schema{ + Description: "Check Service Ownership Resource", + Attributes: getCheckBaseSchemaV0(map[string]schema.Attribute{ + "contact_method": schema.StringAttribute{ + Description: "The type of contact method that is required.", + Optional: true, + Validators: []validator.String{stringvalidator.OneOfCaseInsensitive(enumAllContactTypes...)}, + }, + "id": schema.StringAttribute{ + Description: "The ID of this resource.", + Computed: true, + }, + "require_contact_method": schema.BoolAttribute{ + Description: "True if a service's owner must have a contact method, False otherwise.", + Optional: true, + }, + "tag_key": schema.StringAttribute{ + Description: "The tag key where the tag predicate should be applied.", + Optional: true, + }, + }), + Blocks: map[string]schema.Block{ + "tag_predicate": schema.ListNestedBlock{ + NestedObject: predicateSchemaV0, + }, + }, + }, + StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) { + var diags diag.Diagnostics + upgradedStateModel := CheckServiceOwnershipResourceModel{} + tagPredicateList := types.ListNull(types.ObjectType{AttrTypes: predicateType}) + + // base check attributes + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("category"), &upgradedStateModel.Category)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("enable_on"), &upgradedStateModel.EnableOn)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("enabled"), &upgradedStateModel.Enabled)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("filter"), &upgradedStateModel.Filter)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("id"), &upgradedStateModel.Id)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("level"), &upgradedStateModel.Level)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("name"), &upgradedStateModel.Name)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("notes"), &upgradedStateModel.Notes)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("owner"), &upgradedStateModel.Owner)...) + + // service ownership specific attributes + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("contact_method"), &upgradedStateModel.ContactMethod)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("require_contact_method"), &upgradedStateModel.RequireContactMethod)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("tag_key"), &upgradedStateModel.TagKey)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("tag_predicate"), &tagPredicateList)...) + if len(tagPredicateList.Elements()) == 1 { + tagPredicate := tagPredicateList.Elements()[0] + upgradedStateModel.TagPredicate, diags = types.ObjectValueFrom(ctx, predicateType, tagPredicate) + resp.Diagnostics.Append(diags...) + } + + resp.Diagnostics.Append(resp.State.Set(ctx, upgradedStateModel)...) + }, + }, + } +} + func (r *CheckServiceOwnershipResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { var configModel CheckServiceOwnershipResourceModel resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...) diff --git a/opslevel/resource_opslevel_check_service_property.go b/opslevel/resource_opslevel_check_service_property.go index 11a05695..4a396052 100644 --- a/opslevel/resource_opslevel_check_service_property.go +++ b/opslevel/resource_opslevel_check_service_property.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -93,6 +94,7 @@ func (r *CheckServicePropertyResource) Metadata(ctx context.Context, req resourc func (r *CheckServicePropertyResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ + Version: 1, // This description is used by the documentation generator and the language server. MarkdownDescription: "Check Service Property Resource", @@ -112,6 +114,59 @@ func (r *CheckServicePropertyResource) Schema(ctx context.Context, req resource. } } +func (r *CheckServicePropertyResource) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader { + return map[int64]resource.StateUpgrader{ + // State upgrade implementation from 0 (prior state version) to 1 (Schema.Version) + 0: { + PriorSchema: &schema.Schema{ + Description: "Check Service Ownership Resource", + Attributes: getCheckBaseSchemaV0(map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The ID of this resource.", + Computed: true, + }, + "property": schema.StringAttribute{ + Description: "The property of the service that the check will verify.", + Required: true, + }, + }), + Blocks: map[string]schema.Block{ + "predicate": schema.ListNestedBlock{ + NestedObject: predicateSchemaV0, + }, + }, + }, + StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) { + var diags diag.Diagnostics + upgradedStateModel := CheckServicePropertyResourceModel{} + predicateList := types.ListNull(types.ObjectType{AttrTypes: predicateType}) + + // base check attributes + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("category"), &upgradedStateModel.Category)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("enable_on"), &upgradedStateModel.EnableOn)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("enabled"), &upgradedStateModel.Enabled)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("filter"), &upgradedStateModel.Filter)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("id"), &upgradedStateModel.Id)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("level"), &upgradedStateModel.Level)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("name"), &upgradedStateModel.Name)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("notes"), &upgradedStateModel.Notes)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("owner"), &upgradedStateModel.Owner)...) + + // service property specific attributes + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("property"), &upgradedStateModel.Property)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("predicate"), &predicateList)...) + if len(predicateList.Elements()) == 1 { + predicate := predicateList.Elements()[0] + upgradedStateModel.Predicate, diags = types.ObjectValueFrom(ctx, predicateType, predicate) + resp.Diagnostics.Append(diags...) + } + + resp.Diagnostics.Append(resp.State.Set(ctx, upgradedStateModel)...) + }, + }, + } +} + func (r *CheckServicePropertyResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { var configModel CheckServicePropertyResourceModel resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...) diff --git a/opslevel/resource_opslevel_check_tag_defined.go b/opslevel/resource_opslevel_check_tag_defined.go index 2bdb3e8b..61a76dab 100644 --- a/opslevel/resource_opslevel_check_tag_defined.go +++ b/opslevel/resource_opslevel_check_tag_defined.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -90,6 +91,7 @@ func (r *CheckTagDefinedResource) Metadata(ctx context.Context, req resource.Met func (r *CheckTagDefinedResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ + Version: 1, // This description is used by the documentation generator and the language server. MarkdownDescription: "Check Tag Defined Resource", @@ -103,6 +105,59 @@ func (r *CheckTagDefinedResource) Schema(ctx context.Context, req resource.Schem } } +func (r *CheckTagDefinedResource) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader { + return map[int64]resource.StateUpgrader{ + // State upgrade implementation from 0 (prior state version) to 1 (Schema.Version) + 0: { + PriorSchema: &schema.Schema{ + Description: "Check Tag Defined Resource", + Attributes: getCheckBaseSchemaV0(map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The ID of this resource.", + Computed: true, + }, + "tag_key": schema.StringAttribute{ + Description: "The tag key where the tag predicate should be applied.", + Required: true, + }, + }), + Blocks: map[string]schema.Block{ + "tag_predicate": schema.ListNestedBlock{ + NestedObject: predicateSchemaV0, + }, + }, + }, + StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) { + var diags diag.Diagnostics + upgradedStateModel := CheckTagDefinedResourceModel{} + tagPredicateList := types.ListNull(types.ObjectType{AttrTypes: predicateType}) + + // base check attributes + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("category"), &upgradedStateModel.Category)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("enable_on"), &upgradedStateModel.EnableOn)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("enabled"), &upgradedStateModel.Enabled)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("filter"), &upgradedStateModel.Filter)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("id"), &upgradedStateModel.Id)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("level"), &upgradedStateModel.Level)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("name"), &upgradedStateModel.Name)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("notes"), &upgradedStateModel.Notes)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("owner"), &upgradedStateModel.Owner)...) + + // check tag defined specific attributes + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("tag_key"), &upgradedStateModel.TagKey)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("tag_predicate"), &tagPredicateList)...) + if len(tagPredicateList.Elements()) == 1 { + tagPredicate := tagPredicateList.Elements()[0] + upgradedStateModel.TagPredicate, diags = types.ObjectValueFrom(ctx, predicateType, tagPredicate) + resp.Diagnostics.Append(diags...) + } + + resp.Diagnostics.Append(resp.State.Set(ctx, upgradedStateModel)...) + }, + }, + } +} + func (r *CheckTagDefinedResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { var configModel CheckTagDefinedResourceModel resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...) diff --git a/opslevel/resource_opslevel_check_tool_usage.go b/opslevel/resource_opslevel_check_tool_usage.go index f11972d0..c2492b8e 100644 --- a/opslevel/resource_opslevel_check_tool_usage.go +++ b/opslevel/resource_opslevel_check_tool_usage.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -116,6 +117,7 @@ func (r *CheckToolUsageResource) Metadata(ctx context.Context, req resource.Meta func (r *CheckToolUsageResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ + Version: 1, // This description is used by the documentation generator and the language server. MarkdownDescription: "Check Tool Usage Resource", @@ -135,6 +137,79 @@ func (r *CheckToolUsageResource) Schema(ctx context.Context, req resource.Schema } } +func (r *CheckToolUsageResource) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader { + return map[int64]resource.StateUpgrader{ + // State upgrade implementation from 0 (prior state version) to 1 (Schema.Version) + 0: { + PriorSchema: &schema.Schema{ + Description: "Check Tool Usage Resource", + Attributes: getCheckBaseSchemaV0(map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The ID of this resource.", + Computed: true, + }, + "tool_category": schema.StringAttribute{ + Description: "The category that the tool belongs to.", + Required: true, + }, + }), + Blocks: map[string]schema.Block{ + "environment_predicate": schema.ListNestedBlock{ + NestedObject: predicateSchemaV0, + }, + "tool_name_predicate": schema.ListNestedBlock{ + NestedObject: predicateSchemaV0, + }, + "tool_url_predicate": schema.ListNestedBlock{ + NestedObject: predicateSchemaV0, + }, + }, + }, + StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) { + var diags diag.Diagnostics + upgradedStateModel := CheckToolUsageResourceModel{} + environmentPredicateList := types.ListNull(types.ObjectType{AttrTypes: predicateType}) + toolNamePredicateList := types.ListNull(types.ObjectType{AttrTypes: predicateType}) + toolUrlPredicateList := types.ListNull(types.ObjectType{AttrTypes: predicateType}) + + // base check attributes + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("category"), &upgradedStateModel.Category)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("enable_on"), &upgradedStateModel.EnableOn)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("enabled"), &upgradedStateModel.Enabled)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("filter"), &upgradedStateModel.Filter)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("id"), &upgradedStateModel.Id)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("level"), &upgradedStateModel.Level)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("name"), &upgradedStateModel.Name)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("notes"), &upgradedStateModel.Notes)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("owner"), &upgradedStateModel.Owner)...) + + // tool usage specific attributes + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("tool_category"), &upgradedStateModel.ToolCategory)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("environment_predicate"), &environmentPredicateList)...) + if len(environmentPredicateList.Elements()) == 1 { + environmentPredicate := environmentPredicateList.Elements()[0] + upgradedStateModel.EnvironmentPredicate, diags = types.ObjectValueFrom(ctx, predicateType, environmentPredicate) + resp.Diagnostics.Append(diags...) + } + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("tool_name_predicate"), &toolNamePredicateList)...) + if len(toolNamePredicateList.Elements()) == 1 { + toolNamePredicate := toolNamePredicateList.Elements()[0] + upgradedStateModel.ToolNamePredicate, diags = types.ObjectValueFrom(ctx, predicateType, toolNamePredicate) + resp.Diagnostics.Append(diags...) + } + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("tool_url_predicate"), &toolUrlPredicateList)...) + if len(toolUrlPredicateList.Elements()) == 1 { + toolUrlPredicate := toolUrlPredicateList.Elements()[0] + upgradedStateModel.ToolUrlPredicate, diags = types.ObjectValueFrom(ctx, predicateType, toolUrlPredicate) + resp.Diagnostics.Append(diags...) + } + + resp.Diagnostics.Append(resp.State.Set(ctx, upgradedStateModel)...) + }, + }, + } +} + func (r *CheckToolUsageResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { var configModel CheckToolUsageResourceModel resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...)