diff --git a/octopusdeploy_framework/resource_project.go b/octopusdeploy_framework/resource_project.go index 73e8b1f37..85b1efeeb 100644 --- a/octopusdeploy_framework/resource_project.go +++ b/octopusdeploy_framework/resource_project.go @@ -64,7 +64,7 @@ func (r *projectResource) Create(ctx context.Context, req resource.CreateRequest } // Set state to fully populated data - flattenedProject, diags := flattenProject(ctx, createdProject) + flattenedProject, diags := flattenProject(ctx, createdProject, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return @@ -88,7 +88,7 @@ func (r *projectResource) Read(ctx context.Context, req resource.ReadRequest, re return } - flattenedProject, diags := flattenProject(ctx, project) + flattenedProject, diags := flattenProject(ctx, project, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return @@ -133,7 +133,7 @@ func (r *projectResource) Update(ctx context.Context, req resource.UpdateRequest return } - flattenedProject, diags := flattenProject(ctx, updatedProject) + flattenedProject, diags := flattenProject(ctx, updatedProject, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return diff --git a/octopusdeploy_framework/resource_project_expand.go b/octopusdeploy_framework/resource_project_expand.go index d7422eab7..0dd0cd923 100644 --- a/octopusdeploy_framework/resource_project_expand.go +++ b/octopusdeploy_framework/resource_project_expand.go @@ -7,7 +7,6 @@ import ( "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/credentials" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/packages" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects" - "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "net/url" @@ -34,6 +33,7 @@ func expandProject(ctx context.Context, model projectResourceModel) *projects.Pr project.TenantedDeploymentMode = core.TenantedDeploymentMode(model.TenantedDeploymentParticipation.ValueString()) project.ReleaseNotesTemplate = model.ReleaseNotesTemplate.ValueString() project.Slug = model.Slug.ValueString() + project.ClonedFromProjectID = model.ClonedFromProjectID.ValueString() if !model.IncludedLibraryVariableSets.IsNull() { var includedSets []string @@ -43,44 +43,46 @@ func expandProject(ctx context.Context, model projectResourceModel) *projects.Pr if !model.ConnectivityPolicy.IsNull() { var connectivityPolicy connectivityPolicyModel - model.ConnectivityPolicy.As(ctx, &connectivityPolicy, basetypes.ObjectAsOptions{}) + model.ConnectivityPolicy.ElementsAs(ctx, &connectivityPolicy, false) project.ConnectivityPolicy = expandConnectivityPolicy(connectivityPolicy) } - if !model.GitAnonymousPersistenceSettings.IsNull() { - var settings gitAnonymousPersistenceSettingsModel - model.GitAnonymousPersistenceSettings.As(ctx, &settings, basetypes.ObjectAsOptions{}) - project.PersistenceSettings = expandGitAnonymousPersistenceSettings(settings) - } else if !model.GitLibraryPersistenceSettings.IsNull() { - var settings gitLibraryPersistenceSettingsModel - model.GitLibraryPersistenceSettings.As(ctx, &settings, basetypes.ObjectAsOptions{}) - project.PersistenceSettings = expandGitLibraryPersistenceSettings(settings) - } else if !model.GitUsernamePasswordPersistenceSettings.IsNull() { - var settings gitUsernamePasswordPersistenceSettingsModel - model.GitUsernamePasswordPersistenceSettings.As(ctx, &settings, basetypes.ObjectAsOptions{}) - project.PersistenceSettings = expandGitUsernamePasswordPersistenceSettings(settings) - } + // TODO: git_library_persistence_settings + //if v, ok := d.GetOk("git_library_persistence_settings"); ok { + // project.PersistenceSettings = expandGitPersistenceSettings(ctx, v, expandLibraryGitCredential) + //} + //if v, ok := d.GetOk("git_username_password_persistence_settings"); ok { + // project.PersistenceSettings = expandGitPersistenceSettings(ctx, v, expandUsernamePasswordGitCredential) + //} + //if v, ok := d.GetOk("git_anonymous_persistence_settings"); ok { + // project.PersistenceSettings = expandGitPersistenceSettings(ctx, v, expandAnonymousGitCredential) + //} + // + //if project.PersistenceSettings != nil { + // tflog.Info(ctx, fmt.Sprintf("expanded persistence settings {%v}", project.PersistenceSettings)) + //} if !model.JiraServiceManagementExtensionSettings.IsNull() { var settings jiraServiceManagementExtensionSettingsModel - model.JiraServiceManagementExtensionSettings.As(ctx, &settings, basetypes.ObjectAsOptions{}) + model.JiraServiceManagementExtensionSettings.ElementsAs(ctx, &settings, false) project.ExtensionSettings = append(project.ExtensionSettings, expandJiraServiceManagementExtensionSettings(settings)) } - if !model.ServicenowExtensionSettings.IsNull() { + + if !model.ServiceNowExtensionSettings.IsNull() { var settings servicenowExtensionSettingsModel - model.ServicenowExtensionSettings.As(ctx, &settings, basetypes.ObjectAsOptions{}) + model.ServiceNowExtensionSettings.ElementsAs(ctx, &settings, false) project.ExtensionSettings = append(project.ExtensionSettings, expandServiceNowExtensionSettings(settings)) } if !model.VersioningStrategy.IsNull() { var strategy versioningStrategyModel - model.VersioningStrategy.As(ctx, &strategy, basetypes.ObjectAsOptions{}) + model.VersioningStrategy.ElementsAs(ctx, &strategy, false) project.VersioningStrategy = expandVersioningStrategy(strategy) } if !model.ReleaseCreationStrategy.IsNull() { var strategy releaseCreationStrategyModel - model.ReleaseCreationStrategy.As(ctx, &strategy, basetypes.ObjectAsOptions{}) + model.ReleaseCreationStrategy.ElementsAs(ctx, &strategy, false) project.ReleaseCreationStrategy = expandReleaseCreationStrategy(strategy) } @@ -90,9 +92,67 @@ func expandProject(ctx context.Context, model projectResourceModel) *projects.Pr project.Templates = expandTemplates(templates) } + if !model.AutoDeployReleaseOverrides.IsNull() { + var overrideModels []autoDeployReleaseOverrideModel + diags := model.AutoDeployReleaseOverrides.ElementsAs(ctx, &overrideModels, false) + if !diags.HasError() { + project.AutoDeployReleaseOverrides = expandAutoDeployReleaseOverrides(ctx, overrideModels) + } + } + return project } +func expandAutoDeployReleaseOverrides(ctx context.Context, models []autoDeployReleaseOverrideModel) []projects.AutoDeployReleaseOverride { + result := make([]projects.AutoDeployReleaseOverride, 0, len(models)) + + for _, model := range models { + override := projects.AutoDeployReleaseOverride{ + EnvironmentID: model.EnvironmentID.ValueString(), + } + + // TenantID is optional, so we only set it if it's not null + if !model.TenantID.IsNull() { + override.TenantID = model.TenantID.ValueString() + } + + result = append(result, override) + } + + return result +} + +func expandGitPersistenceSettings(model gitPersistenceSettingsModel) projects.PersistenceSettings { + gitUrl, _ := url.Parse(model.URL.ValueString()) + + basePath := model.BasePath.ValueString() + defaultBranch := model.DefaultBranch.ValueString() + + var protectedBranches []string + model.ProtectedBranches.ElementsAs(context.Background(), &protectedBranches, false) + + var gitCredential credentials.GitCredential + + if !model.GitCredentialID.IsNull() { + // Library Git Credential + gitCredential = credentials.NewReference(model.GitCredentialID.ValueString()) + } else if !model.Username.IsNull() && !model.Password.IsNull() { + // Username and Password Git Credential + gitCredential = credentials.NewUsernamePassword(model.Username.ValueString(), core.NewSensitiveValue(model.Password.ValueString())) + } else { + // Anonymous Git Credential + gitCredential = credentials.NewAnonymous() + } + + return projects.NewGitPersistenceSettings( + basePath, + gitCredential, + defaultBranch, + protectedBranches, + gitUrl, + ) +} + func expandConnectivityPolicy(model connectivityPolicyModel) *core.ConnectivityPolicy { var targetRoles []string if !model.TargetRoles.IsNull() && !model.TargetRoles.IsUnknown() { @@ -111,40 +171,6 @@ func expandConnectivityPolicy(model connectivityPolicyModel) *core.ConnectivityP } } -func expandGitAnonymousPersistenceSettings(model gitAnonymousPersistenceSettingsModel) projects.GitPersistenceSettings { - url, _ := url.Parse(model.URL.ValueString()) - return projects.NewGitPersistenceSettings( - model.BasePath.ValueString(), - credentials.NewAnonymous(), - model.DefaultBranch.ValueString(), - util.ExpandStringArray(model.ProtectedBranches), - url, - ) -} - -func expandGitLibraryPersistenceSettings(model gitLibraryPersistenceSettingsModel) projects.GitPersistenceSettings { - url, _ := url.Parse(model.URL.ValueString()) - return projects.NewGitPersistenceSettings( - model.BasePath.ValueString(), - credentials.NewReference(model.GitCredentialID.ValueString()), - model.DefaultBranch.ValueString(), - util.ExpandStringArray(model.ProtectedBranches), - url, - ) -} - -func expandGitUsernamePasswordPersistenceSettings(model gitUsernamePasswordPersistenceSettingsModel) projects.GitPersistenceSettings { - url, _ := url.Parse(model.URL.ValueString()) - passwordSensitiveValue := core.NewSensitiveValue(model.Password.ValueString()) - return projects.NewGitPersistenceSettings( - model.BasePath.ValueString(), - credentials.NewUsernamePassword(model.Username.ValueString(), passwordSensitiveValue), - model.DefaultBranch.ValueString(), - util.ExpandStringArray(model.ProtectedBranches), - url, - ) -} - func expandJiraServiceManagementExtensionSettings(model jiraServiceManagementExtensionSettingsModel) *projects.JiraServiceManagementExtensionSettings { return projects.NewJiraServiceManagementExtensionSettings( model.ConnectionID.ValueString(), diff --git a/octopusdeploy_framework/resource_project_flatten.go b/octopusdeploy_framework/resource_project_flatten.go index e09b62ba0..05653f7e6 100644 --- a/octopusdeploy_framework/resource_project_flatten.go +++ b/octopusdeploy_framework/resource_project_flatten.go @@ -4,7 +4,6 @@ import ( "context" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/actiontemplates" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core" - "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/credentials" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/extensions" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/packages" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects" @@ -14,7 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -func flattenProject(ctx context.Context, project *projects.Project) (*projectResourceModel, diag.Diagnostics) { +func flattenProject(ctx context.Context, project *projects.Project, state *projectResourceModel) (*projectResourceModel, diag.Diagnostics) { if project == nil { return nil, diag.Diagnostics{ diag.NewErrorDiagnostic( @@ -37,284 +36,207 @@ func flattenProject(ctx context.Context, project *projects.Project) (*projectRes DefaultToSkipIfAlreadyInstalled: types.BoolValue(project.DefaultToSkipIfAlreadyInstalled), DeploymentChangesTemplate: types.StringValue(project.DeploymentChangesTemplate), DeploymentProcessID: types.StringValue(project.DeploymentProcessID), + DiscreteChannelRelease: types.BoolValue(project.IsDiscreteChannelRelease), IsDiscreteChannelRelease: types.BoolValue(project.IsDiscreteChannelRelease), IsVersionControlled: types.BoolValue(project.IsVersionControlled), TenantedDeploymentParticipation: types.StringValue(string(project.TenantedDeploymentMode)), VariableSetID: types.StringValue(project.VariableSetID), ReleaseNotesTemplate: types.StringValue(project.ReleaseNotesTemplate), Slug: types.StringValue(project.Slug), + ClonedFromProjectID: util.StringOrNull(project.ClonedFromProjectID), } var diags diag.Diagnostics - model.IncludedLibraryVariableSets, diags = types.ListValueFrom(ctx, types.StringType, project.IncludedLibraryVariableSets) - if diags.HasError() { - return nil, diags + model.IncludedLibraryVariableSets = util.FlattenStringList(project.IncludedLibraryVariableSets) + model.AutoDeployReleaseOverrides = flattenAutoDeployReleaseOverrides(ctx, project.AutoDeployReleaseOverrides) + + if state.ConnectivityPolicy.IsNull() { + model.ConnectivityPolicy = types.ListNull(types.ObjectType{AttrTypes: getConnectivityPolicyAttrTypes()}) + } else { + model.ConnectivityPolicy = flattenConnectivityPolicy(ctx, project.ConnectivityPolicy) } - model.ConnectivityPolicy, diags = flattenConnectivityPolicy(ctx, project.ConnectivityPolicy) - if diags.HasError() { - return nil, diags + if state.ReleaseCreationStrategy.IsNull() { + model.ReleaseCreationStrategy = types.ListNull(types.ObjectType{AttrTypes: getReleaseCreationStrategyAttrTypes()}) + } else { + model.ReleaseCreationStrategy = flattenReleaseCreationStrategy(ctx, project.ReleaseCreationStrategy) } - if project.PersistenceSettings != nil { - switch project.PersistenceSettings.Type() { - case projects.PersistenceSettingsTypeVersionControlled: - gitSettings := project.PersistenceSettings.(projects.GitPersistenceSettings) - switch gitSettings.Credential().Type() { - case credentials.GitCredentialTypeAnonymous: - model.GitAnonymousPersistenceSettings, diags = flattenGitAnonymousPersistenceSettings(ctx, gitSettings) - case credentials.GitCredentialTypeReference: - model.GitLibraryPersistenceSettings, diags = flattenGitLibraryPersistenceSettings(ctx, gitSettings) - case credentials.GitCredentialTypeUsernamePassword: - model.GitUsernamePasswordPersistenceSettings, diags = flattenGitUsernamePasswordPersistenceSettings(ctx, gitSettings) - } - if diags.HasError() { - return nil, diags - } - } + if state.VersioningStrategy.IsNull() { + model.VersioningStrategy = types.ListNull(types.ObjectType{AttrTypes: getVersioningStrategyAttrTypes()}) + } else { + model.VersioningStrategy = flattenVersioningStrategy(project.VersioningStrategy) } + // Template + templateList, d := flattenTemplates(ctx, project.Templates) + diags.Append(d...) + model.Template = templateList + + // TODO GitPersistenceSetting + model.GitLibraryPersistenceSettings, d = flattenGitLibraryPersistenceSettings(ctx, project.PersistenceSettings) + model.GitAnonymousPersistenceSettings, d = flattenGitAnonymousPersistenceSettings(ctx, project.PersistenceSettings) + model.GitUsernamePasswordPersistenceSettings, d = flattenGitUsernamePasswordPersistenceSettings(ctx, project.PersistenceSettings) + + // Extension Settings + model.JiraServiceManagementExtensionSettings = flattenJiraServiceManagementExtensionSettings(ctx, nil) + model.ServiceNowExtensionSettings = flattenServiceNowExtensionSettings(ctx, nil) + for _, extensionSetting := range project.ExtensionSettings { switch extensionSetting.ExtensionID() { case extensions.JiraServiceManagementExtensionID: if jsmSettings, ok := extensionSetting.(*projects.JiraServiceManagementExtensionSettings); ok { - model.JiraServiceManagementExtensionSettings, diags = flattenJiraServiceManagementExtensionSettings(ctx, jsmSettings) - if diags.HasError() { - return nil, diags - } + model.JiraServiceManagementExtensionSettings = flattenJiraServiceManagementExtensionSettings(ctx, jsmSettings) } case extensions.ServiceNowExtensionID: if snowSettings, ok := extensionSetting.(*projects.ServiceNowExtensionSettings); ok { - model.ServicenowExtensionSettings, diags = flattenServiceNowExtensionSettings(ctx, snowSettings) - if diags.HasError() { - return nil, diags - } + model.ServiceNowExtensionSettings = flattenServiceNowExtensionSettings(ctx, snowSettings) } } } - if project.VersioningStrategy != nil { - model.VersioningStrategy, diags = flattenVersioningStrategy(ctx, project.VersioningStrategy) - if diags.HasError() { - return nil, diags - } - } - - if project.ReleaseCreationStrategy != nil { - model.ReleaseCreationStrategy, diags = flattenReleaseCreationStrategy(ctx, project.ReleaseCreationStrategy) - if diags.HasError() { - return nil, diags - } - } - - if len(project.Templates) > 0 { - model.Template, diags = flattenTemplates(ctx, project.Templates) - if diags.HasError() { - return nil, diags - } - } - - return model, nil + return model, diags } -func flattenConnectivityPolicy(ctx context.Context, policy *core.ConnectivityPolicy) (types.Object, diag.Diagnostics) { +func flattenConnectivityPolicy(ctx context.Context, policy *core.ConnectivityPolicy) types.List { if policy == nil { - return types.ObjectNull(map[string]attr.Type{ - "allow_deployments_to_no_targets": types.BoolType, - "exclude_unhealthy_targets": types.BoolType, - "skip_machine_behavior": types.StringType, - "target_roles": types.ListType{ElemType: types.StringType}, - }), nil + return types.ListValueMust(types.ObjectType{AttrTypes: getConnectivityPolicyAttrTypes()}, []attr.Value{}) } - return types.ObjectValueFrom(ctx, map[string]attr.Type{ - "allow_deployments_to_no_targets": types.BoolType, - "exclude_unhealthy_targets": types.BoolType, - "skip_machine_behavior": types.StringType, - "target_roles": types.ListType{ElemType: types.StringType}, - }, map[string]attr.Value{ + obj := types.ObjectValueMust(getConnectivityPolicyAttrTypes(), map[string]attr.Value{ "allow_deployments_to_no_targets": types.BoolValue(policy.AllowDeploymentsToNoTargets), "exclude_unhealthy_targets": types.BoolValue(policy.ExcludeUnhealthyTargets), "skip_machine_behavior": types.StringValue(string(policy.SkipMachineBehavior)), "target_roles": util.FlattenStringList(policy.TargetRoles), }) + + return types.ListValueMust(types.ObjectType{AttrTypes: getConnectivityPolicyAttrTypes()}, []attr.Value{obj}) } -func flattenGitAnonymousPersistenceSettings(ctx context.Context, settings projects.GitPersistenceSettings) (types.Object, diag.Diagnostics) { - protectedBranches, diags := util.TerraformSetFromStringArray(ctx, settings.ProtectedBranchNamePatterns()) - if diags.HasError() { - return types.ObjectNull(nil), diags +func flattenVersioningStrategy(strategy *projects.VersioningStrategy) types.List { + if strategy == nil { + return types.ListValueMust(types.ObjectType{AttrTypes: getVersioningStrategyAttrTypes()}, []attr.Value{}) } - return types.ObjectValueFrom(ctx, map[string]attr.Type{ - "url": types.StringType, - "base_path": types.StringType, - "default_branch": types.StringType, - "protected_branches": types.SetType{ElemType: types.StringType}, - }, map[string]attr.Value{ - "url": types.StringValue(settings.URL().String()), - "base_path": types.StringValue(settings.BasePath()), - "default_branch": types.StringValue(settings.DefaultBranch()), - "protected_branches": protectedBranches, + obj := types.ObjectValueMust(getVersioningStrategyAttrTypes(), map[string]attr.Value{ + "donor_package": flattenDeploymentActionPackage(strategy.DonorPackage), + "donor_package_step_id": types.StringPointerValue(strategy.DonorPackageStepID), + "template": types.StringValue(strategy.Template), }) -} -func flattenGitLibraryPersistenceSettings(ctx context.Context, settings projects.GitPersistenceSettings) (types.Object, diag.Diagnostics) { - credential := settings.Credential().(*credentials.Reference) - protectedBranches, diags := util.TerraformSetFromStringArray(ctx, settings.ProtectedBranchNamePatterns()) - if diags.HasError() { - return types.ObjectNull(nil), diags - } - return types.ObjectValueFrom(ctx, map[string]attr.Type{ - "url": types.StringType, - "base_path": types.StringType, - "default_branch": types.StringType, - "protected_branches": types.SetType{ElemType: types.StringType}, - "git_credential_id": types.StringType, - }, map[string]attr.Value{ - "url": types.StringValue(settings.URL().String()), - "base_path": types.StringValue(settings.BasePath()), - "default_branch": types.StringValue(settings.DefaultBranch()), - "protected_branches": protectedBranches, - "git_credential_id": types.StringValue(credential.ID), - }) + return types.ListValueMust(types.ObjectType{AttrTypes: getVersioningStrategyAttrTypes()}, []attr.Value{obj}) } -func flattenGitUsernamePasswordPersistenceSettings(ctx context.Context, settings projects.GitPersistenceSettings) (types.Object, diag.Diagnostics) { - credential := settings.Credential().(*credentials.UsernamePassword) - protectedBranches, diags := util.TerraformSetFromStringArray(ctx, settings.ProtectedBranchNamePatterns()) - if diags.HasError() { - return types.ObjectNull(nil), diags - } +func flattenGitLibraryPersistenceSettings(ctx context.Context, settings projects.PersistenceSettings) (types.List, diag.Diagnostics) { + return types.ListNull(types.ObjectType{AttrTypes: getGitLibraryPersistenceSettingsAttrTypes()}), nil +} - var passwordValue string - if credential.Password != nil && credential.Password.NewValue != nil { - passwordValue = *credential.Password.NewValue - } +func flattenGitAnonymousPersistenceSettings(ctx context.Context, settings projects.PersistenceSettings) (types.List, diag.Diagnostics) { + return types.ListNull(types.ObjectType{AttrTypes: getGitAnonymousPersistenceSettingsAttrTypes()}), nil +} - return types.ObjectValueFrom(ctx, map[string]attr.Type{ - "url": types.StringType, - "base_path": types.StringType, - "default_branch": types.StringType, - "protected_branches": types.SetType{ElemType: types.StringType}, - "username": types.StringType, - "password": types.StringType, - }, map[string]attr.Value{ - "url": types.StringValue(settings.URL().String()), - "base_path": types.StringValue(settings.BasePath()), - "default_branch": types.StringValue(settings.DefaultBranch()), - "protected_branches": protectedBranches, - "username": types.StringValue(credential.Username), - "password": types.StringValue(passwordValue), - }) +func flattenGitUsernamePasswordPersistenceSettings(ctx context.Context, settings projects.PersistenceSettings) (types.List, diag.Diagnostics) { + return types.ListNull(types.ObjectType{AttrTypes: getGitUsernamePasswordPersistenceSettingsAttrTypes()}), nil } -func flattenJiraServiceManagementExtensionSettings(ctx context.Context, settings *projects.JiraServiceManagementExtensionSettings) (types.Object, diag.Diagnostics) { - return types.ObjectValueFrom(ctx, map[string]attr.Type{ - "connection_id": types.StringType, - "is_enabled": types.BoolType, - "service_desk_project_name": types.StringType, - }, map[string]attr.Value{ +func flattenJiraServiceManagementExtensionSettings(ctx context.Context, settings *projects.JiraServiceManagementExtensionSettings) types.List { + if settings == nil { + return types.ListValueMust(types.ObjectType{AttrTypes: getJSMExtensionSettingsAttrTypes()}, []attr.Value{}) + } + + obj := types.ObjectValueMust(getJSMExtensionSettingsAttrTypes(), map[string]attr.Value{ "connection_id": types.StringValue(settings.ConnectionID()), "is_enabled": types.BoolValue(settings.IsChangeControlled()), "service_desk_project_name": types.StringValue(settings.ServiceDeskProjectName), }) + + return types.ListValueMust(types.ObjectType{AttrTypes: getJSMExtensionSettingsAttrTypes()}, []attr.Value{obj}) } -func flattenServiceNowExtensionSettings(ctx context.Context, settings *projects.ServiceNowExtensionSettings) (types.Object, diag.Diagnostics) { - return types.ObjectValueFrom(ctx, map[string]attr.Type{ - "connection_id": types.StringType, - "is_enabled": types.BoolType, - "is_state_automatically_transitioned": types.BoolType, - "standard_change_template_name": types.StringType, - }, map[string]attr.Value{ +func flattenServiceNowExtensionSettings(ctx context.Context, settings *projects.ServiceNowExtensionSettings) types.List { + if settings == nil { + return types.ListValueMust(types.ObjectType{AttrTypes: getServiceNowExtensionSettingsAttrTypes()}, []attr.Value{}) + } + + obj := types.ObjectValueMust(getServiceNowExtensionSettingsAttrTypes(), map[string]attr.Value{ "connection_id": types.StringValue(settings.ConnectionID()), "is_enabled": types.BoolValue(settings.IsChangeControlled()), "is_state_automatically_transitioned": types.BoolValue(settings.IsStateAutomaticallyTransitioned), "standard_change_template_name": types.StringValue(settings.StandardChangeTemplateName), }) + + return types.ListValueMust(types.ObjectType{AttrTypes: getServiceNowExtensionSettingsAttrTypes()}, []attr.Value{obj}) } -func flattenDeploymentActionPackage(ctx context.Context, deploymentActionPackage *packages.DeploymentActionPackage) (types.Object, diag.Diagnostics) { - if deploymentActionPackage == nil { - return types.ObjectNull(map[string]attr.Type{ - "deployment_action": types.StringType, - "package_reference": types.StringType, - }), nil +func flattenTemplates(ctx context.Context, templates []actiontemplates.ActionTemplateParameter) (types.List, diag.Diagnostics) { + if len(templates) == 0 { + return types.ListNull(types.ObjectType{AttrTypes: getTemplateAttrTypes()}), nil } - return types.ObjectValueFrom(ctx, map[string]attr.Type{ - "deployment_action": types.StringType, - "package_reference": types.StringType, - }, map[string]attr.Value{ - "deployment_action": types.StringValue(deploymentActionPackage.DeploymentAction), - "package_reference": types.StringValue(deploymentActionPackage.PackageReference), - }) + templateList := make([]attr.Value, 0, len(templates)) + for _, template := range templates { + obj, diags := types.ObjectValueFrom(ctx, getTemplateAttrTypes(), map[string]attr.Value{ + "id": types.StringValue(template.ID), + "name": types.StringValue(template.Name), + "label": types.StringValue(template.Label), + "help_text": types.StringValue(template.HelpText), + "default_value": types.StringValue(template.DefaultValue.Value), + "display_settings": types.MapValueMust( + types.StringType, + convertMapStringToMapAttrValue(template.DisplaySettings), + ), + }) + if diags.HasError() { + return types.ListNull(types.ObjectType{AttrTypes: getTemplateAttrTypes()}), diags + } + templateList = append(templateList, obj) + } + + return types.ListValueMust(types.ObjectType{AttrTypes: getTemplateAttrTypes()}, templateList), nil } -func flattenVersioningStrategy(ctx context.Context, versioningStrategy *projects.VersioningStrategy) (types.Object, diag.Diagnostics) { - if versioningStrategy == nil { - return types.ObjectNull(map[string]attr.Type{ - "donor_package": types.ObjectType{AttrTypes: map[string]attr.Type{ - "deployment_action": types.StringType, - "package_reference": types.StringType, - }}, - "donor_package_step_id": types.StringType, - "template": types.StringType, - }), nil +func flattenAutoDeployReleaseOverrides(ctx context.Context, overrides []projects.AutoDeployReleaseOverride) types.List { + if len(overrides) == 0 { + return types.ListValueMust(types.ObjectType{AttrTypes: getAutoDeployReleaseOverrideAttrTypes()}, []attr.Value{}) } - donorPackage, diags := flattenDeploymentActionPackage(ctx, versioningStrategy.DonorPackage) - if diags.HasError() { - return types.ObjectNull(nil), diags + overrideList := make([]attr.Value, 0, len(overrides)) + for _, override := range overrides { + obj := types.ObjectValueMust(getAutoDeployReleaseOverrideAttrTypes(), map[string]attr.Value{ + "environment_id": types.StringValue(override.EnvironmentID), + "release_id": types.StringValue(override.ReleaseID), + "tenant_id": types.StringValue(override.TenantID), + }) + overrideList = append(overrideList, obj) } - return types.ObjectValueFrom(ctx, map[string]attr.Type{ - "donor_package": types.ObjectType{AttrTypes: map[string]attr.Type{ - "deployment_action": types.StringType, - "package_reference": types.StringType, - }}, - "donor_package_step_id": types.StringType, - "template": types.StringType, - }, map[string]attr.Value{ - "donor_package": donorPackage, - "donor_package_step_id": types.StringPointerValue(versioningStrategy.DonorPackageStepID), - "template": types.StringValue(versioningStrategy.Template), - }) + return types.ListValueMust(types.ObjectType{AttrTypes: getAutoDeployReleaseOverrideAttrTypes()}, overrideList) } -func flattenReleaseCreationStrategy(ctx context.Context, strategy *projects.ReleaseCreationStrategy) (types.Object, diag.Diagnostics) { - if strategy == nil { - return types.ObjectNull(map[string]attr.Type{ - "channel_id": types.StringType, - "release_creation_package_step_id": types.StringType, - "release_creation_package": types.ObjectType{AttrTypes: map[string]attr.Type{ - "deployment_action": types.StringType, - "package_reference": types.StringType, - }}, - }), nil + +func getAutoDeployReleaseOverrideAttrTypes() map[string]attr.Type { + return map[string]attr.Type{ + "environment_id": types.StringType, + "release_id": types.StringType, + "tenant_id": types.StringType, } +} - releaseCreationPackage, diags := flattenDeploymentActionPackage(ctx, strategy.ReleaseCreationPackage) - if diags.HasError() { - return types.ObjectNull(nil), diags +func flattenReleaseCreationStrategy(ctx context.Context, strategy *projects.ReleaseCreationStrategy) types.List { + if strategy == nil { + return types.ListValueMust(types.ObjectType{AttrTypes: getReleaseCreationStrategyAttrTypes()}, []attr.Value{}) } - return types.ObjectValueFrom(ctx, map[string]attr.Type{ - "channel_id": types.StringType, - "release_creation_package_step_id": types.StringType, - "release_creation_package": types.ObjectType{AttrTypes: map[string]attr.Type{ - "deployment_action": types.StringType, - "package_reference": types.StringType, - }}, - }, map[string]attr.Value{ + obj := types.ObjectValueMust(getReleaseCreationStrategyAttrTypes(), map[string]attr.Value{ "channel_id": types.StringValue(strategy.ChannelID), "release_creation_package_step_id": types.StringValue(strategy.ReleaseCreationPackageStepID), - "release_creation_package": releaseCreationPackage, + "release_creation_package": flattenDeploymentActionPackage(strategy.ReleaseCreationPackage), }) + + return types.ListValueMust(types.ObjectType{AttrTypes: getReleaseCreationStrategyAttrTypes()}, []attr.Value{obj}) } -func convertDisplaySettings(m map[string]string) map[string]attr.Value { +func convertMapStringToMapAttrValue(m map[string]string) map[string]attr.Value { result := make(map[string]attr.Value, len(m)) for k, v := range m { result[k] = types.StringValue(v) @@ -322,59 +244,112 @@ func convertDisplaySettings(m map[string]string) map[string]attr.Value { return result } -func flattenTemplates(ctx context.Context, templates []actiontemplates.ActionTemplateParameter) (types.List, diag.Diagnostics) { - var diags diag.Diagnostics +func flattenDeploymentActionPackage(pkg *packages.DeploymentActionPackage) types.List { + if pkg == nil { + return types.ListNull(types.ObjectType{AttrTypes: getDonorPackageAttrTypes()}) + } - if len(templates) == 0 { - return types.ListNull(types.ObjectType{AttrTypes: map[string]attr.Type{ - "id": types.StringType, - "name": types.StringType, - "label": types.StringType, - "help_text": types.StringType, - "default_value": types.StringType, - "display_settings": types.MapType{ElemType: types.StringType}, - }}), diags + obj := types.ObjectValueMust( + getDonorPackageAttrTypes(), + map[string]attr.Value{ + "deployment_action": types.StringValue(pkg.DeploymentAction), + "package_reference": types.StringValue(pkg.PackageReference), + }, + ) + + return types.ListValueMust(types.ObjectType{AttrTypes: getDonorPackageAttrTypes()}, []attr.Value{obj}) +} + +func getVersioningStrategyAttrTypes() map[string]attr.Type { + return map[string]attr.Type{ + "donor_package": types.ListType{ElemType: types.ObjectType{AttrTypes: getDonorPackageAttrTypes()}}, + "donor_package_step_id": types.StringType, + "template": types.StringType, + } +} + +func getDonorPackageAttrTypes() map[string]attr.Type { + return map[string]attr.Type{ + "deployment_action": types.StringType, + "package_reference": types.StringType, } +} - templateList := make([]attr.Value, 0, len(templates)) - for _, template := range templates { - displaySettingsValue, mapDiags := types.MapValue(types.StringType, convertDisplaySettings(template.DisplaySettings)) - diags.Append(mapDiags...) - if diags.HasError() { - return types.ListNull(nil), diags - } +func getConnectivityPolicyAttrTypes() map[string]attr.Type { + return map[string]attr.Type{ + "allow_deployments_to_no_targets": types.BoolType, + "exclude_unhealthy_targets": types.BoolType, + "skip_machine_behavior": types.StringType, + "target_roles": types.ListType{ElemType: types.StringType}, + } +} - flattenedTemplate, objectDiags := types.ObjectValueFrom(ctx, map[string]attr.Type{ - "id": types.StringType, - "name": types.StringType, - "label": types.StringType, - "help_text": types.StringType, - "default_value": types.StringType, - "display_settings": types.MapType{ElemType: types.StringType}, - }, map[string]attr.Value{ - "id": types.StringValue(template.ID), - "name": types.StringValue(template.Name), - "label": types.StringValue(template.Label), - "help_text": types.StringValue(template.HelpText), - "default_value": types.StringValue(template.DefaultValue.Value), - "display_settings": displaySettingsValue, - }) - diags.Append(objectDiags...) - if diags.HasError() { - return types.ListNull(nil), diags - } - templateList = append(templateList, flattenedTemplate) +func getReleaseCreationStrategyAttrTypes() map[string]attr.Type { + return map[string]attr.Type{ + "channel_id": types.StringType, + "release_creation_package_step_id": types.StringType, + "release_creation_package": types.ListType{ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "deployment_action": types.StringType, + "package_reference": types.StringType, + }, + }}, + } +} + +func getGitLibraryPersistenceSettingsAttrTypes() map[string]attr.Type { + return map[string]attr.Type{ + "git_credential_id": types.StringType, + "url": types.StringType, + "base_path": types.StringType, + "default_branch": types.StringType, + "protected_branches": types.SetType{ElemType: types.StringType}, + } +} + +func getGitAnonymousPersistenceSettingsAttrTypes() map[string]attr.Type { + return map[string]attr.Type{ + "url": types.StringType, + "base_path": types.StringType, + "default_branch": types.StringType, + "protected_branches": types.SetType{ElemType: types.StringType}, + } +} + +func getGitUsernamePasswordPersistenceSettingsAttrTypes() map[string]attr.Type { + return map[string]attr.Type{ + "url": types.StringType, + "base_path": types.StringType, + "default_branch": types.StringType, + "protected_branches": types.SetType{ElemType: types.StringType}, + "username": types.StringType, + "password": types.StringType, } +} - listValue, listDiags := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: map[string]attr.Type{ +func getJSMExtensionSettingsAttrTypes() map[string]attr.Type { + return map[string]attr.Type{ + "connection_id": types.StringType, + "is_enabled": types.BoolType, + "service_desk_project_name": types.StringType, + } +} +func getTemplateAttrTypes() map[string]attr.Type { + return map[string]attr.Type{ "id": types.StringType, "name": types.StringType, "label": types.StringType, "help_text": types.StringType, "default_value": types.StringType, "display_settings": types.MapType{ElemType: types.StringType}, - }}, templateList) - diags.Append(listDiags...) + } +} - return listValue, diags +func getServiceNowExtensionSettingsAttrTypes() map[string]attr.Type { + return map[string]attr.Type{ + "connection_id": types.StringType, + "is_enabled": types.BoolType, + "is_state_automatically_transitioned": types.BoolType, + "standard_change_template_name": types.StringType, + } } diff --git a/octopusdeploy_framework/resource_project_model.go b/octopusdeploy_framework/resource_project_model.go index 8a7ee5009..af4b6063d 100644 --- a/octopusdeploy_framework/resource_project_model.go +++ b/octopusdeploy_framework/resource_project_model.go @@ -20,20 +20,22 @@ type projectResourceModel struct { DiscreteChannelRelease types.Bool `tfsdk:"discrete_channel_release"` IsDiscreteChannelRelease types.Bool `tfsdk:"is_discrete_channel_release"` IsVersionControlled types.Bool `tfsdk:"is_version_controlled"` - IncludedLibraryVariableSets types.List `tfsdk:"included_library_variable_sets"` TenantedDeploymentParticipation types.String `tfsdk:"tenanted_deployment_participation"` VariableSetID types.String `tfsdk:"variable_set_id"` ReleaseNotesTemplate types.String `tfsdk:"release_notes_template"` Slug types.String `tfsdk:"slug"` - ConnectivityPolicy types.Object `tfsdk:"connectivity_policy"` - GitAnonymousPersistenceSettings types.Object `tfsdk:"git_anonymous_persistence_settings"` - GitLibraryPersistenceSettings types.Object `tfsdk:"git_library_persistence_settings"` - GitUsernamePasswordPersistenceSettings types.Object `tfsdk:"git_username_password_persistence_settings"` - JiraServiceManagementExtensionSettings types.Object `tfsdk:"jira_service_management_extension_settings"` - ServicenowExtensionSettings types.Object `tfsdk:"servicenow_extension_settings"` - VersioningStrategy types.Object `tfsdk:"versioning_strategy"` - ReleaseCreationStrategy types.Object `tfsdk:"release_creation_strategy"` + ClonedFromProjectID types.String `tfsdk:"cloned_from_project_id"` + VersioningStrategy types.List `tfsdk:"versioning_strategy"` + ConnectivityPolicy types.List `tfsdk:"connectivity_policy"` + ReleaseCreationStrategy types.List `tfsdk:"release_creation_strategy"` Template types.List `tfsdk:"template"` + GitAnonymousPersistenceSettings types.List `tfsdk:"git_anonymous_persistence_settings"` + GitLibraryPersistenceSettings types.List `tfsdk:"git_library_persistence_settings"` + GitUsernamePasswordPersistenceSettings types.List `tfsdk:"git_username_password_persistence_settings"` + JiraServiceManagementExtensionSettings types.List `tfsdk:"jira_service_management_extension_settings"` + ServiceNowExtensionSettings types.List `tfsdk:"servicenow_extension_settings"` + IncludedLibraryVariableSets types.List `tfsdk:"included_library_variable_sets"` + AutoDeployReleaseOverrides types.List `tfsdk:"auto_deploy_release_overrides"` } type connectivityPolicyModel struct { @@ -42,27 +44,19 @@ type connectivityPolicyModel struct { SkipMachineBehavior types.String `tfsdk:"skip_machine_behavior"` TargetRoles types.List `tfsdk:"target_roles"` } +type autoDeployReleaseOverrideModel struct { + EnvironmentID types.String `tfsdk:"environment_id"` + TenantID types.String `tfsdk:"tenant_id"` +} type gitPersistenceSettingsModel struct { URL types.String `tfsdk:"url"` BasePath types.String `tfsdk:"base_path"` DefaultBranch types.String `tfsdk:"default_branch"` ProtectedBranches types.Set `tfsdk:"protected_branches"` -} - -type gitAnonymousPersistenceSettingsModel struct { - gitPersistenceSettingsModel -} - -type gitLibraryPersistenceSettingsModel struct { - gitPersistenceSettingsModel - GitCredentialID types.String `tfsdk:"git_credential_id"` -} - -type gitUsernamePasswordPersistenceSettingsModel struct { - gitPersistenceSettingsModel - Username types.String `tfsdk:"username"` - Password types.String `tfsdk:"password"` + Username types.String `tfsdk:"username"` + Password types.String `tfsdk:"password"` + GitCredentialID types.String `tfsdk:"git_credential_id"` } type jiraServiceManagementExtensionSettingsModel struct { diff --git a/octopusdeploy_framework/schemas/project.go b/octopusdeploy_framework/schemas/project.go index b22fe773b..e2f86388b 100644 --- a/octopusdeploy_framework/schemas/project.go +++ b/octopusdeploy_framework/schemas/project.go @@ -1,8 +1,9 @@ package schemas import ( - "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util" "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -12,187 +13,336 @@ func GetProjectResourceSchema() schema.Schema { return schema.Schema{ Description: "This resource manages projects in Octopus Deploy.", Attributes: map[string]schema.Attribute{ - "id": util.GetIdResourceSchema(), - "space_id": util.GetSpaceIdResourceSchema("project"), - "name": util.GetNameResourceSchema(true), - "description": util.GetDescriptionResourceSchema("project"), - "lifecycle_id": schema.StringAttribute{Description: "The lifecycle ID associated with this project.", Required: true}, - "project_group_id": schema.StringAttribute{Description: "The project group ID associated with this project.", Required: true}, - "is_disabled": schema.BoolAttribute{ - Description: "Indicates whether the project is disabled.", - Optional: true, + "id": schema.StringAttribute{ + Description: "The unique ID for this resource.", Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, - "auto_create_release": schema.BoolAttribute{ - Description: "Indicates whether to automatically create a release when a package is pushed to a trigger.", + "space_id": schema.StringAttribute{ + Description: "The space ID associated with this project.", Optional: true, Computed: true, }, - "default_guided_failure_mode": schema.StringAttribute{ - Description: "The default guided failure mode setting for the project.", + "name": schema.StringAttribute{ + Description: "The name of the project in Octopus Deploy. This name must be unique.", + Required: true, + }, + "description": schema.StringAttribute{ + Description: "The description of this project.", Optional: true, - Computed: true, + }, + "auto_create_release": schema.BoolAttribute{ + Optional: true, + Computed: true, + }, + "cloned_from_project_id": schema.StringAttribute{ + Optional: true, + }, + "default_guided_failure_mode": schema.StringAttribute{ + Optional: true, + Computed: true, }, "default_to_skip_if_already_installed": schema.BoolAttribute{ - Description: "Indicates whether deployment steps should be skipped if the relevant package is already installed.", - Optional: true, - Computed: true, + Optional: true, + Computed: true, }, "deployment_changes_template": schema.StringAttribute{ - Description: "The template to use for deployment change details.", - Optional: true, - Computed: true, - }, - "deployment_process_id": schema.StringAttribute{ - Description: "The ID of the deployment process associated with this project.", - Computed: true, + Optional: true, + Computed: true, }, "discrete_channel_release": schema.BoolAttribute{ - Description: "Treats releases of different channels to the same environment as a separate deployment dimension.", + Description: "Treats releases of different channels to the same environment as a separate deployment dimension", Optional: true, Computed: true, }, + "is_disabled": schema.BoolAttribute{ + Optional: true, + Computed: true, + }, "is_discrete_channel_release": schema.BoolAttribute{ - Description: "Treats releases of different channels to the same environment as a separate deployment dimension.", + Description: "Treats releases of different channels to the same environment as a separate deployment dimension", Optional: true, Computed: true, }, "is_version_controlled": schema.BoolAttribute{ - Description: "Indicates whether the project is version controlled.", - Optional: true, - Computed: true, + Optional: true, + Computed: true, }, - "included_library_variable_sets": schema.ListAttribute{ - Description: "The list of included library variable set IDs.", - ElementType: types.StringType, - Optional: true, - Computed: true, + "lifecycle_id": schema.StringAttribute{ + Description: "The lifecycle ID associated with this project.", + Required: true, + }, + "project_group_id": schema.StringAttribute{ + Description: "The project group ID associated with this project.", + Required: true, }, "tenanted_deployment_participation": schema.StringAttribute{ Description: "The tenanted deployment mode of the resource. Valid account types are `Untenanted`, `TenantedOrUntenanted`, or `Tenanted`.", Optional: true, Computed: true, }, - "variable_set_id": schema.StringAttribute{ - Description: "The ID of the variable set associated with this project.", + "included_library_variable_sets": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, Computed: true, }, "release_notes_template": schema.StringAttribute{ - Description: "The template to use for release notes.", + Optional: true, + Computed: true, + }, + "slug": schema.StringAttribute{ + Description: "A human-readable, unique identifier, used to identify a project.", Optional: true, Computed: true, }, - "slug": util.GetSlugResourceSchema("project"), + "deployment_process_id": schema.StringAttribute{ + Computed: true, + }, + "variable_set_id": schema.StringAttribute{ + Computed: true, + }, }, Blocks: map[string]schema.Block{ - "connectivity_policy": schema.SingleNestedBlock{ - Description: "Defines the connectivity policy for deployments.", - Attributes: map[string]schema.Attribute{ - "allow_deployments_to_no_targets": schema.BoolAttribute{ - Description: "Allow deployments to be created when there are no targets.", - Optional: true, - }, - "exclude_unhealthy_targets": schema.BoolAttribute{ - Description: "Exclude unhealthy targets from deployments.", - Optional: true, - }, - "skip_machine_behavior": schema.StringAttribute{ - Description: "The behavior when a machine is skipped.", - Optional: true, - }, - "target_roles": schema.ListAttribute{ - Description: "The target roles for the connectivity policy.", - ElementType: types.StringType, - Optional: true, + "auto_deploy_release_overrides": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "environment_id": schema.StringAttribute{Optional: true}, + "release_id": schema.StringAttribute{Optional: true}, + "tenant_id": schema.StringAttribute{Optional: true}, }, }, }, - "git_anonymous_persistence_settings": schema.SingleNestedBlock{ - Description: "Provides Git-related persistence settings for a version-controlled project.", - Attributes: map[string]schema.Attribute{ - "url": schema.StringAttribute{Optional: true}, - "base_path": schema.StringAttribute{Description: "The base path associated with these version control settings.", Optional: true}, - "default_branch": schema.StringAttribute{Description: "The default branch associated with these version control settings.", Optional: true}, - "protected_branches": schema.SetAttribute{Description: "A list of protected branch patterns.", ElementType: types.StringType, Optional: true}, + "connectivity_policy": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "allow_deployments_to_no_targets": schema.BoolAttribute{ + Optional: true, + }, + "exclude_unhealthy_targets": schema.BoolAttribute{ + Optional: true, + }, + "skip_machine_behavior": schema.StringAttribute{ + Optional: true, + }, + "target_roles": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + }, + }, }, }, - "git_library_persistence_settings": schema.SingleNestedBlock{ - Description: "Provides Git-related persistence settings for a version-controlled project.", - Attributes: map[string]schema.Attribute{ - "git_credential_id": schema.StringAttribute{Description: "The ID of the Git credential to use.", Optional: true}, - "url": schema.StringAttribute{Optional: true}, - "base_path": schema.StringAttribute{Description: "The base path associated with these version control settings.", Optional: true}, - "default_branch": schema.StringAttribute{Description: "The default branch associated with these version control settings.", Optional: true}, - "protected_branches": schema.SetAttribute{Description: "A list of protected branch patterns.", ElementType: types.StringType, Optional: true}, + "git_anonymous_persistence_settings": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "url": schema.StringAttribute{ + Description: "The URL associated with these version control settings.", + Optional: true, + }, + "base_path": schema.StringAttribute{ + Description: "The base path associated with these version control settings.", + Optional: true, + }, + "default_branch": schema.StringAttribute{ + Description: "The default branch associated with these version control settings.", + Optional: true, + }, + "protected_branches": schema.SetAttribute{ + Description: "A list of protected branch patterns.", + ElementType: types.StringType, + Optional: true, + }, + }, }, - }, - "git_username_password_persistence_settings": schema.SingleNestedBlock{ Description: "Provides Git-related persistence settings for a version-controlled project.", - Attributes: map[string]schema.Attribute{ - "url": schema.StringAttribute{Optional: true}, - "username": util.GetUsernameResourceSchema(false), - "password": util.GetPasswordResourceSchema(false), - "base_path": schema.StringAttribute{Description: "The base path associated with these version control settings.", Optional: true}, - "default_branch": schema.StringAttribute{Description: "The default branch associated with these version control settings.", Optional: true}, - "protected_branches": schema.SetAttribute{Description: "A list of protected branch patterns.", ElementType: types.StringType, Optional: true}, - }, }, - "jira_service_management_extension_settings": schema.SingleNestedBlock{ - Description: "Provides extension settings for the Jira Service Management (JSM) integration for this project.", - Attributes: map[string]schema.Attribute{ - "connection_id": schema.StringAttribute{Description: "The connection identifier associated with the extension settings.", Optional: true}, - "is_enabled": schema.BoolAttribute{Description: "Specifies whether or not this extension is enabled for this project.", Optional: true}, - "service_desk_project_name": schema.StringAttribute{Description: "The project name associated with this extension.", Optional: true}, + "git_library_persistence_settings": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "git_credential_id": schema.StringAttribute{ + Optional: true, + }, + "url": schema.StringAttribute{ + Description: "The URL associated with these version control settings.", + Optional: true, + }, + "base_path": schema.StringAttribute{ + Description: "The base path associated with these version control settings.", + Optional: true, + }, + "default_branch": schema.StringAttribute{ + Description: "The default branch associated with these version control settings.", + Optional: true, + }, + "protected_branches": schema.SetAttribute{ + Description: "A list of protected branch patterns.", + ElementType: types.StringType, + Optional: true, + }, + }, }, + Description: "Provides Git-related persistence settings for a version-controlled project.", }, - "servicenow_extension_settings": schema.SingleNestedBlock{ - Description: "Provides extension settings for the ServiceNow integration for this project.", - Attributes: map[string]schema.Attribute{ - "connection_id": schema.StringAttribute{Description: "The connection identifier associated with the extension settings.", Optional: true}, - "is_enabled": schema.BoolAttribute{Description: "Specifies whether or not this extension is enabled for this project.", Optional: true}, - "is_state_automatically_transitioned": schema.BoolAttribute{Description: "Specifies whether or not this extension will automatically transition the state of a deployment for this project.", Optional: true}, - "standard_change_template_name": schema.StringAttribute{Description: "The name of the standard change template associated with this extension.", Optional: true}, + "git_username_password_persistence_settings": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "url": schema.StringAttribute{ + Description: "The URL associated with these version control settings.", + Optional: true, + }, + "username": schema.StringAttribute{ + Description: "The username for the Git credential.", + Optional: true, + }, + "password": schema.StringAttribute{ + Description: "The password for the Git credential.", + Optional: true, + Sensitive: true, + }, + "base_path": schema.StringAttribute{ + Description: "The base path associated with these version control settings.", + Optional: true, + }, + "default_branch": schema.StringAttribute{ + Description: "The default branch associated with these version control settings.", + Optional: true, + }, + "protected_branches": schema.SetAttribute{ + Description: "A list of protected branch patterns.", + ElementType: types.StringType, + Optional: true, + }, + }, }, + Description: "Provides Git-related persistence settings for a version-controlled project.", }, - "versioning_strategy": schema.SingleNestedBlock{ - Description: "Defines the versioning strategy for the project.", - Attributes: map[string]schema.Attribute{ - "donor_package_step_id": schema.StringAttribute{Description: "The ID of the step containing the donor package.", Optional: true}, - "template": schema.StringAttribute{Description: "The template to use for version numbers.", Optional: true}, + "jira_service_management_extension_settings": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "connection_id": schema.StringAttribute{ + Description: "The connection identifier associated with the extension settings.", + Optional: true, + }, + "is_enabled": schema.BoolAttribute{ + Description: "Specifies whether or not this extension is enabled for this project.", + Optional: true, + }, + "service_desk_project_name": schema.StringAttribute{ + Description: "The project name associated with this extension.", + Optional: true, + }, + }, }, - Blocks: map[string]schema.Block{ - "donor_package": schema.SingleNestedBlock{ - Attributes: map[string]schema.Attribute{ - "deployment_action": schema.StringAttribute{Description: "The deployment action for the donor package.", Optional: true}, - "package_reference": schema.StringAttribute{Description: "The package reference for the donor package.", Optional: true}, + Description: "Provides extension settings for the Jira Service Management (JSM) integration for this project.", + }, + "servicenow_extension_settings": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "connection_id": schema.StringAttribute{ + Description: "The connection identifier associated with the extension settings.", + Optional: true, + }, + "is_enabled": schema.BoolAttribute{ + Description: "Specifies whether or not this extension is enabled for this project.", + Optional: true, + }, + "is_state_automatically_transitioned": schema.BoolAttribute{ + Description: "Specifies whether or not this extension will automatically transition the state of a deployment for this project.", + Optional: true, + }, + "standard_change_template_name": schema.StringAttribute{ + Description: "The name of the standard change template associated with this extension.", + Optional: true, }, }, }, + Description: "Provides extension settings for the ServiceNow integration for this project.", }, - "release_creation_strategy": schema.SingleNestedBlock{ - Description: "Defines the release creation strategy for the project.", - Attributes: map[string]schema.Attribute{ - "channel_id": schema.StringAttribute{Description: "The ID of the channel to use for release creation.", Optional: true}, - "release_creation_package_step_id": schema.StringAttribute{Description: "The ID of the step containing the package for release creation.", Optional: true}, + "template": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The ID of the template parameter.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "The name of the variable set by the parameter. The name can contain letters, digits, dashes and periods.", + Required: true, + }, + "label": schema.StringAttribute{ + Description: "The label shown beside the parameter when presented in the deployment process.", + Optional: true, + }, + "help_text": schema.StringAttribute{ + Description: "The help presented alongside the parameter input.", + Optional: true, + }, + "default_value": schema.StringAttribute{ + Description: "A default value for the parameter, if applicable.", + Optional: true, + }, + "display_settings": schema.MapAttribute{ + Description: "The display settings for the parameter.", + ElementType: types.StringType, + Optional: true, + }, + }, }, - Blocks: map[string]schema.Block{ - "release_creation_package": schema.SingleNestedBlock{ - Attributes: map[string]schema.Attribute{ - "deployment_action": schema.StringAttribute{Description: "The deployment action for the release creation package.", Optional: true}, - "package_reference": schema.StringAttribute{Description: "The package reference for the release creation package.", Optional: true}, + }, + "versioning_strategy": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "donor_package_step_id": schema.StringAttribute{ + Optional: true, + }, + "template": schema.StringAttribute{ + Optional: true, + }, + }, + Blocks: map[string]schema.Block{ + "donor_package": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "deployment_action": schema.StringAttribute{ + Optional: true, + }, + "package_reference": schema.StringAttribute{ + Optional: true, + }, + }, + }, }, }, }, }, - "template": schema.ListNestedBlock{ - Description: "Defines template parameters for the project.", + "release_creation_strategy": schema.ListNestedBlock{ NestedObject: schema.NestedBlockObject{ Attributes: map[string]schema.Attribute{ - "name": util.GetNameResourceSchema(false), - "label": schema.StringAttribute{Description: "The label shown beside the parameter when presented in the deployment process.", Optional: true}, - "help_text": schema.StringAttribute{Description: "The help presented alongside the parameter input.", Optional: true}, - "default_value": schema.StringAttribute{Description: "A default value for the parameter, if applicable.", Optional: true}, - "display_settings": schema.MapAttribute{Description: "The display settings for the parameter.", ElementType: types.StringType, Optional: true}, + "channel_id": schema.StringAttribute{ + Optional: true, + }, + "release_creation_package_step_id": schema.StringAttribute{ + Optional: true, + }, + }, + Blocks: map[string]schema.Block{ + "release_creation_package": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "deployment_action": schema.StringAttribute{ + Optional: true, + }, + "package_reference": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, }, }, }, diff --git a/octopusdeploy_framework/util/util.go b/octopusdeploy_framework/util/util.go index 0097588c9..7995b0395 100644 --- a/octopusdeploy_framework/util/util.go +++ b/octopusdeploy_framework/util/util.go @@ -86,30 +86,9 @@ func ToValueSlice(slice []string) []attr.Value { return values } -func TerraformSetFromStringArray(ctx context.Context, arr []string) (types.Set, diag.Diagnostics) { - if arr == nil { - return types.SetNull(types.StringType), nil +func StringOrNull(s string) types.String { + if s == "" { + return types.StringNull() } - - elements := make([]attr.Value, len(arr)) - for i, v := range arr { - elements[i] = types.StringValue(v) - } - - return types.SetValueFrom(ctx, types.StringType, elements) -} - -func ExpandStringArray(set types.Set) []string { - if set.IsNull() || set.IsUnknown() { - return nil - } - - var result []string - for _, v := range set.Elements() { - if strVal, ok := v.(types.String); ok { - result = append(result, strVal.ValueString()) - } - } - - return result + return types.StringValue(s) }