diff --git a/docs/data-sources/projects.md b/docs/data-sources/projects.md index 30f710030..bc050a7be 100644 --- a/docs/data-sources/projects.md +++ b/docs/data-sources/projects.md @@ -3,12 +3,12 @@ page_title: "octopusdeploy_projects Data Source - terraform-provider-octopusdeploy" subcategory: "" description: |- - Provides information about existing projects. + Provides information about existing Octopus Deploy projects. --- # octopusdeploy_projects (Data Source) -Provides information about existing projects. +Provides information about existing Octopus Deploy projects. ## Example Usage @@ -32,64 +32,74 @@ data "octopusdeploy_projects" "example" { - `cloned_from_project_id` (String) A filter to search for cloned resources by a project ID. - `ids` (List of String) A filter to search by a list of IDs. - `is_clone` (Boolean) A filter to search for cloned resources. -- `name` (String) A filter to search by name. -- `partial_name` (String) A filter to search by the partial match of a name. +- `name` (String) A filter to search by name +- `partial_name` (String) A filter to search by a partial name. - `skip` (Number) A filter to specify the number of items to skip in the response. -- `space_id` (String) A Space ID to filter by. Will revert what is specified on the provider if not set. +- `space_id` (String) A Space ID to filter by. Will revert what is specified on the provider if not set - `take` (Number) A filter to specify the number of items to take (or return) in the response. ### Read-Only - `id` (String) An auto-generated identifier that includes the timestamp when this data source was last modified. -- `projects` (Block List) A list of projects that match the filter(s). (see [below for nested schema](#nestedblock--projects)) +- `projects` (Attributes List) A list of projects that match the filter(s). (see [below for nested schema](#nestedatt--projects)) - + ### Nested Schema for `projects` Read-Only: - `allow_deployments_to_no_targets` (Boolean, Deprecated) - `auto_create_release` (Boolean) -- `auto_deploy_release_overrides` (List of String) +- `auto_deploy_release_overrides` (Attributes List) (see [below for nested schema](#nestedatt--projects--auto_deploy_release_overrides)) - `cloned_from_project_id` (String) -- `connectivity_policy` (List of Object) (see [below for nested schema](#nestedatt--projects--connectivity_policy)) +- `connectivity_policy` (Attributes List) (see [below for nested schema](#nestedatt--projects--connectivity_policy)) - `default_guided_failure_mode` (String) - `default_to_skip_if_already_installed` (Boolean) - `deployment_changes_template` (String) - `deployment_process_id` (String) -- `description` (String) The description of this project. +- `description` (String) The description of this project - `discrete_channel_release` (Boolean) Treats releases of different channels to the same environment as a separate deployment dimension -- `git_anonymous_persistence_settings` (List of Object) Provides Git-related persistence settings for a version-controlled project. (see [below for nested schema](#nestedatt--projects--git_anonymous_persistence_settings)) -- `git_library_persistence_settings` (List of Object) Provides Git-related persistence settings for a version-controlled project. (see [below for nested schema](#nestedatt--projects--git_library_persistence_settings)) -- `git_username_password_persistence_settings` (List of Object) Provides Git-related persistence settings for a version-controlled project. (see [below for nested schema](#nestedatt--projects--git_username_password_persistence_settings)) -- `id` (String) The unique ID for this resource. +- `git_anonymous_persistence_settings` (Attributes List) Git-related persistence settings for a version-controlled project using anonymous authentication. (see [below for nested schema](#nestedatt--projects--git_anonymous_persistence_settings)) +- `git_library_persistence_settings` (Attributes List) Git-related persistence settings for a version-controlled project using library authentication. (see [below for nested schema](#nestedatt--projects--git_library_persistence_settings)) +- `git_username_password_persistence_settings` (Attributes List) Git-related persistence settings for a version-controlled project using username_password authentication. (see [below for nested schema](#nestedatt--projects--git_username_password_persistence_settings)) +- `id` (String) - `included_library_variable_sets` (List of String) - `is_disabled` (Boolean) -- `is_discrete_channel_release` (Boolean) Treats releases of different channels to the same environment as a separate deployment dimension +- `is_discrete_channel_release` (Boolean) - `is_version_controlled` (Boolean) -- `jira_service_management_extension_settings` (List of Object) Provides extension settings for the Jira Service Management (JSM) integration for this project. (see [below for nested schema](#nestedatt--projects--jira_service_management_extension_settings)) -- `lifecycle_id` (String) The lifecycle ID associated with this project. +- `jira_service_management_extension_settings` (Attributes List) Extension settings for the Jira Service Management (JSM) integration. (see [below for nested schema](#nestedatt--projects--jira_service_management_extension_settings)) +- `lifecycle_id` (String) The lifecycle ID associated with this project - `name` (String) The name of the project in Octopus Deploy. This name must be unique. - `project_group_id` (String) The project group ID associated with this project. -- `release_creation_strategy` (List of Object) (see [below for nested schema](#nestedatt--projects--release_creation_strategy)) -- `release_notes_template` (String) -- `servicenow_extension_settings` (List of Object) Provides extension settings for the ServiceNow integration for this project. (see [below for nested schema](#nestedatt--projects--servicenow_extension_settings)) +- `release_creation_strategy` (Attributes List) The release creation strategy for the project. (see [below for nested schema](#nestedatt--projects--release_creation_strategy)) +- `release_notes_template` (String) The template to use for release notes. +- `servicenow_extension_settings` (Attributes List) Extension settings for the ServiceNow integration. (see [below for nested schema](#nestedatt--projects--servicenow_extension_settings)) - `slug` (String) A human-readable, unique identifier, used to identify a project. - `space_id` (String) The space ID associated with this project. -- `template` (List of Object) (see [below for nested schema](#nestedatt--projects--template)) -- `tenanted_deployment_participation` (String) The tenanted deployment mode of the resource. Valid account types are `Untenanted`, `TenantedOrUntenanted`, or `Tenanted`. -- `variable_set_id` (String) -- `versioning_strategy` (Set of Object) (see [below for nested schema](#nestedatt--projects--versioning_strategy)) +- `template` (Attributes List) Template parameters for the project. (see [below for nested schema](#nestedatt--projects--template)) +- `tenanted_deployment_participation` (String) The tenanted deployment mode of the project. +- `variable_set_id` (String) The ID of the variable set associated with this project. +- `versioning_strategy` (Attributes List) The versioning strategy for the project. (see [below for nested schema](#nestedatt--projects--versioning_strategy)) + + +### Nested Schema for `projects.auto_deploy_release_overrides` + +Read-Only: + +- `environment_id` (String) The environment ID for the auto deploy release override. +- `release_id` (String) The release ID for the auto deploy release override. +- `tenant_id` (String) The tenant ID for the auto deploy release override. + ### Nested Schema for `projects.connectivity_policy` Read-Only: -- `allow_deployments_to_no_targets` (Boolean) -- `exclude_unhealthy_targets` (Boolean) -- `skip_machine_behavior` (String) -- `target_roles` (List of String) +- `allow_deployments_to_no_targets` (Boolean) Allow deployments to be created when there are no targets. +- `exclude_unhealthy_targets` (Boolean) Exclude unhealthy targets from deployments. +- `skip_machine_behavior` (String) The behavior when a machine is skipped. +- `target_roles` (List of String) The target roles for the connectivity policy. @@ -97,10 +107,10 @@ Read-Only: Read-Only: -- `base_path` (String) -- `default_branch` (String) -- `protected_branches` (Set of String) -- `url` (String) +- `base_path` (String) The base path associated with these version control settings. +- `default_branch` (String) The default branch associated with these version control settings. +- `protected_branches` (Set of String) A list of protected branch patterns. +- `url` (String) The URL associated with these version control settings. @@ -108,11 +118,11 @@ Read-Only: Read-Only: -- `base_path` (String) -- `default_branch` (String) -- `git_credential_id` (String) -- `protected_branches` (Set of String) -- `url` (String) +- `base_path` (String) The base path associated with these version control settings. +- `default_branch` (String) The default branch associated with these version control settings. +- `git_credential_id` (String) The ID of the Git credential. +- `protected_branches` (Set of String) A list of protected branch patterns. +- `url` (String) The URL associated with these version control settings. @@ -120,12 +130,12 @@ Read-Only: Read-Only: -- `base_path` (String) -- `default_branch` (String) -- `password` (String) -- `protected_branches` (Set of String) -- `url` (String) -- `username` (String) +- `base_path` (String) The base path associated with these version control settings. +- `default_branch` (String) The default branch associated with these version control settings. +- `password` (String, Sensitive) The password for the Git credential. +- `protected_branches` (Set of String) A list of protected branch patterns. +- `url` (String) The URL associated with these version control settings. +- `username` (String) The username for the Git credential. @@ -133,9 +143,9 @@ Read-Only: Read-Only: -- `connection_id` (String) -- `is_enabled` (Boolean) -- `service_desk_project_name` (String) +- `connection_id` (String) The connection identifier for JSM. +- `is_enabled` (Boolean) Whether the JSM extension is enabled. +- `service_desk_project_name` (String) The JSM service desk project name. @@ -143,17 +153,17 @@ Read-Only: Read-Only: -- `channel_id` (String) -- `release_creation_package` (List of Object) (see [below for nested schema](#nestedobjatt--projects--release_creation_strategy--release_creation_package)) -- `release_creation_package_step_id` (String) +- `channel_id` (String) The ID of the channel to use for release creation. +- `release_creation_package` (Attributes List) Details of the package used for release creation. (see [below for nested schema](#nestedatt--projects--release_creation_strategy--release_creation_package)) +- `release_creation_package_step_id` (String) The ID of the step containing the package for release creation. - + ### Nested Schema for `projects.release_creation_strategy.release_creation_package` Read-Only: -- `deployment_action` (String) -- `package_reference` (String) +- `deployment_action` (String) The deployment action for the release creation package. +- `package_reference` (String) The package reference for the release creation package. @@ -162,10 +172,10 @@ Read-Only: Read-Only: -- `connection_id` (String) -- `is_enabled` (Boolean) -- `is_state_automatically_transitioned` (Boolean) -- `standard_change_template_name` (String) +- `connection_id` (String) The connection identifier for ServiceNow. +- `is_enabled` (Boolean) Whether the ServiceNow extension is enabled. +- `is_state_automatically_transitioned` (Boolean) Whether state is automatically transitioned in ServiceNow. +- `standard_change_template_name` (String) The name of the standard change template in ServiceNow. @@ -173,12 +183,12 @@ Read-Only: Read-Only: -- `default_value` (String) -- `display_settings` (Map of String) -- `help_text` (String) -- `id` (String) -- `label` (String) -- `name` (String) +- `default_value` (String) The default value for the parameter. +- `display_settings` (Map of String) The display settings for the parameter. +- `help_text` (String) The help text for the parameter. +- `id` (String) The ID of the template parameter. +- `label` (String) The label shown beside the parameter. +- `name` (String) The name of the variable set by the parameter. @@ -186,16 +196,16 @@ Read-Only: Read-Only: -- `donor_package` (List of Object) (see [below for nested schema](#nestedobjatt--projects--versioning_strategy--donor_package)) -- `donor_package_step_id` (String) -- `template` (String) +- `donor_package` (Attributes List) (see [below for nested schema](#nestedatt--projects--versioning_strategy--donor_package)) +- `donor_package_step_id` (String) The ID of the step containing the donor package. +- `template` (String) The template to use for version numbers. - + ### Nested Schema for `projects.versioning_strategy.donor_package` Read-Only: -- `deployment_action` (String) -- `package_reference` (String) +- `deployment_action` (String) The deployment action for the donor package. +- `package_reference` (String) The package reference for the donor package. diff --git a/docs/resources/project.md b/docs/resources/project.md index 3c2d7920f..6763fc2fd 100644 --- a/docs/resources/project.md +++ b/docs/resources/project.md @@ -67,44 +67,54 @@ resource "octopusdeploy_project" "example" { ### Required - `lifecycle_id` (String) The lifecycle ID associated with this project. -- `name` (String) The name of the project in Octopus Deploy. This name must be unique. +- `name` (String) The name of this resource. - `project_group_id` (String) The project group ID associated with this project. ### Optional - `allow_deployments_to_no_targets` (Boolean, Deprecated) - `auto_create_release` (Boolean) -- `auto_deploy_release_overrides` (List of String) -- `cloned_from_project_id` (String) -- `connectivity_policy` (Block List, Max: 1) (see [below for nested schema](#nestedblock--connectivity_policy)) +- `auto_deploy_release_overrides` (Block List) (see [below for nested schema](#nestedblock--auto_deploy_release_overrides)) +- `cloned_from_project_id` (String) The ID of the project this project was cloned from. +- `connectivity_policy` (Block List) (see [below for nested schema](#nestedblock--connectivity_policy)) - `default_guided_failure_mode` (String) - `default_to_skip_if_already_installed` (Boolean) - `deployment_changes_template` (String) - `description` (String) The description of this project. - `discrete_channel_release` (Boolean) Treats releases of different channels to the same environment as a separate deployment dimension -- `git_anonymous_persistence_settings` (Block List, Max: 1) Provides Git-related persistence settings for a version-controlled project. (see [below for nested schema](#nestedblock--git_anonymous_persistence_settings)) -- `git_library_persistence_settings` (Block List, Max: 1) Provides Git-related persistence settings for a version-controlled project. (see [below for nested schema](#nestedblock--git_library_persistence_settings)) -- `git_username_password_persistence_settings` (Block List, Max: 1) Provides Git-related persistence settings for a version-controlled project. (see [below for nested schema](#nestedblock--git_username_password_persistence_settings)) +- `git_anonymous_persistence_settings` (Block List) Provides Git-related persistence settings for a version-controlled project. (see [below for nested schema](#nestedblock--git_anonymous_persistence_settings)) +- `git_library_persistence_settings` (Block List) Provides Git-related persistence settings for a version-controlled project. (see [below for nested schema](#nestedblock--git_library_persistence_settings)) +- `git_username_password_persistence_settings` (Block List) Provides Git-related persistence settings for a version-controlled project. (see [below for nested schema](#nestedblock--git_username_password_persistence_settings)) - `id` (String) The unique ID for this resource. -- `included_library_variable_sets` (List of String) +- `included_library_variable_sets` (List of String) The list of included library variable set IDs. - `is_disabled` (Boolean) - `is_discrete_channel_release` (Boolean) Treats releases of different channels to the same environment as a separate deployment dimension - `is_version_controlled` (Boolean) -- `jira_service_management_extension_settings` (Block List, Max: 1) Provides extension settings for the Jira Service Management (JSM) integration for this project. (see [below for nested schema](#nestedblock--jira_service_management_extension_settings)) -- `release_creation_strategy` (Block List, Max: 1) (see [below for nested schema](#nestedblock--release_creation_strategy)) +- `jira_service_management_extension_settings` (Block List) Provides extension settings for the Jira Service Management (JSM) integration for this project. (see [below for nested schema](#nestedblock--jira_service_management_extension_settings)) +- `release_creation_strategy` (Block List) (see [below for nested schema](#nestedblock--release_creation_strategy)) - `release_notes_template` (String) -- `servicenow_extension_settings` (Block List, Max: 1) Provides extension settings for the ServiceNow integration for this project. (see [below for nested schema](#nestedblock--servicenow_extension_settings)) +- `servicenow_extension_settings` (Block List) Provides extension settings for the ServiceNow integration for this project. (see [below for nested schema](#nestedblock--servicenow_extension_settings)) - `slug` (String) A human-readable, unique identifier, used to identify a project. - `space_id` (String) The space ID associated with this project. - `template` (Block List) (see [below for nested schema](#nestedblock--template)) - `tenanted_deployment_participation` (String) The tenanted deployment mode of the resource. Valid account types are `Untenanted`, `TenantedOrUntenanted`, or `Tenanted`. -- `versioning_strategy` (Block Set) (see [below for nested schema](#nestedblock--versioning_strategy)) +- `versioning_strategy` (Block List) (see [below for nested schema](#nestedblock--versioning_strategy)) ### Read-Only - `deployment_process_id` (String) - `variable_set_id` (String) + +### Nested Schema for `auto_deploy_release_overrides` + +Optional: + +- `environment_id` (String) +- `release_id` (String) +- `tenant_id` (String) + + ### Nested Schema for `connectivity_policy` @@ -150,7 +160,7 @@ Optional: Required: -- `password` (String, Sensitive) The password for the Git credential. +- `password` (String, Sensitive) The password for the Git credential - `url` (String) The URL associated with these version control settings. - `username` (String) The username for the Git credential. @@ -177,7 +187,7 @@ Required: Optional: - `channel_id` (String) -- `release_creation_package` (Block List, Max: 1) (see [below for nested schema](#nestedblock--release_creation_strategy--release_creation_package)) +- `release_creation_package` (Block List) (see [below for nested schema](#nestedblock--release_creation_strategy--release_creation_package)) - `release_creation_package_step_id` (String) @@ -209,15 +219,15 @@ Optional: Required: -- `name` (String) The name of the variable set by the parameter. The name can contain letters, digits, dashes and periods. Example: `ServerName`. +- `name` (String) The name of the variable set by the parameter. The name can contain letters, digits, dashes and periods. Optional: - `default_value` (String) A default value for the parameter, if applicable. This can be a hard-coded value or a variable reference. - `display_settings` (Map of String) The display settings for the parameter. - `help_text` (String) The help presented alongside the parameter input. -- `id` (String) The unique ID for this resource. -- `label` (String) The label shown beside the parameter when presented in the deployment process. Example: `Server name`. +- `id` (String) The ID of the template parameter. +- `label` (String) The label shown beside the parameter when presented in the deployment process. @@ -225,7 +235,7 @@ Optional: Optional: -- `donor_package` (Block List, Max: 1) (see [below for nested schema](#nestedblock--versioning_strategy--donor_package)) +- `donor_package` (Block List) (see [below for nested schema](#nestedblock--versioning_strategy--donor_package)) - `donor_package_step_id` (String) - `template` (String) diff --git a/docs/resources/project_group.md b/docs/resources/project_group.md index 8bd9fc7d0..c0d250e03 100644 --- a/docs/resources/project_group.md +++ b/docs/resources/project_group.md @@ -3,12 +3,12 @@ page_title: "octopusdeploy_project_group Resource - terraform-provider-octopusdeploy" subcategory: "" description: |- - This resource manages project groups in Octopus Deploy. + --- # octopusdeploy_project_group (Resource) -This resource manages project groups in Octopus Deploy. + ## Example Usage diff --git a/octopusdeploy/data_source_projects.go b/octopusdeploy/data_source_projects.go deleted file mode 100644 index 0410efae8..000000000 --- a/octopusdeploy/data_source_projects.go +++ /dev/null @@ -1,52 +0,0 @@ -package octopusdeploy - -import ( - "context" - "time" - - "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" - "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects" - "github.com/hashicorp/terraform-plugin-log/tflog" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func dataSourceProjects() *schema.Resource { - return &schema.Resource{ - Description: "Provides information about existing projects.", - ReadContext: dataSourceProjectsRead, - Schema: getProjectDataSchema(), - } -} - -func dataSourceProjectsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - tflog.Info(ctx, "reading projects") - - query := projects.ProjectsQuery{ - ClonedFromProjectID: d.Get("cloned_from_project_id").(string), - IDs: expandArray(d.Get("ids").([]interface{})), - IsClone: d.Get("is_clone").(bool), - Name: d.Get("name").(string), - PartialName: d.Get("partial_name").(string), - Skip: d.Get("skip").(int), - Take: d.Get("take").(int), - } - - spaceID := d.Get("space_id").(string) - - client := m.(*client.Client) - existingProjects, err := projects.Get(client, spaceID, query) - if err != nil { - return diag.FromErr(err) - } - - flattenedProjects := []interface{}{} - for _, project := range existingProjects.Items { - flattenedProjects = append(flattenedProjects, flattenProject(ctx, d, project)) - } - - d.Set("projects", flattenedProjects) - d.SetId("Projects " + time.Now().UTC().String()) - - return nil -} diff --git a/octopusdeploy/provider.go b/octopusdeploy/provider.go index b97e48a48..cd51d9521 100644 --- a/octopusdeploy/provider.go +++ b/octopusdeploy/provider.go @@ -26,7 +26,6 @@ func Provider() *schema.Provider { "octopusdeploy_machine_policies": dataSourceMachinePolicies(), "octopusdeploy_offline_package_drop_deployment_targets": dataSourceOfflinePackageDropDeploymentTargets(), "octopusdeploy_polling_tentacle_deployment_targets": dataSourcePollingTentacleDeploymentTargets(), - "octopusdeploy_projects": dataSourceProjects(), "octopusdeploy_script_modules": dataSourceScriptModules(), "octopusdeploy_ssh_connection_deployment_targets": dataSourceSSHConnectionDeploymentTargets(), "octopusdeploy_tag_sets": dataSourceTagSets(), @@ -59,7 +58,6 @@ func Provider() *schema.Provider { "octopusdeploy_offline_package_drop_deployment_target": resourceOfflinePackageDropDeploymentTarget(), "octopusdeploy_polling_tentacle_deployment_target": resourcePollingTentacleDeploymentTarget(), "octopusdeploy_polling_subscription_id": resourcePollingSubscriptionId(), - "octopusdeploy_project": resourceProject(), "octopusdeploy_project_deployment_target_trigger": resourceProjectDeploymentTargetTrigger(), "octopusdeploy_external_feed_create_release_trigger": resourceExternalFeedCreateReleaseTrigger(), "octopusdeploy_project_scheduled_trigger": resourceProjectScheduledTrigger(), diff --git a/octopusdeploy/resource_channel_test.go b/octopusdeploy/resource_channel_test.go index ddaa59664..9d58ac81a 100644 --- a/octopusdeploy/resource_channel_test.go +++ b/octopusdeploy/resource_channel_test.go @@ -198,6 +198,58 @@ func testAccChannelBasic(localName string, lifecycleLocalName string, lifecycleN }`, localName, description, name, projectLocalName) } +func testAccProjectBasic(lifecycleLocalName string, lifecycleName string, projectGroupLocalName string, projectGroupName string, localName string, name string, description string) string { + projectGroup := internalTest.NewProjectGroupTestOptions() + projectGroup.LocalName = projectGroupLocalName + projectGroup.Resource.Name = projectGroupName + + return fmt.Sprintf(testAccLifecycle(lifecycleLocalName, lifecycleName)+"\n"+ + internalTest.ProjectGroupConfiguration(projectGroup)+"\n"+ + `resource "octopusdeploy_project" "%s" { + description = "%s" + lifecycle_id = octopusdeploy_lifecycle.%s.id + name = "%s" + project_group_id = octopusdeploy_project_group.%s.id + + template { + default_value = "default-value" + help_text = "help-test" + label = "label" + name = "2" + + display_settings = { + "Octopus.ControlType": "SingleLineText" + } + } + + template { + default_value = "default-value" + help_text = "help-test" + label = "label" + name = "1" + + display_settings = { + "Octopus.ControlType": "SingleLineText" + } + } + + // connectivity_policy { + // allow_deployments_to_no_targets = true + // skip_machine_behavior = "None" + // } + + // version_control_settings { + // default_branch = "foo" + // url = "https://example.com/" + // username = "bar" + // } + + // versioning_strategy { + // template = "alskdjaslkdj" + // } + }`, localName, description, lifecycleLocalName, name, projectGroupLocalName) +} + func testAccChannelWithOneRule(name, description, versionRange, actionName string) string { projectGroupName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) projectName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) @@ -435,3 +487,48 @@ func TestProjectChannelResource(t *testing.T) { t.Fatal("The environment lookup did not succeed. Lookup value was \"" + lookup + "\" while the resource value was \"" + resource.ID + "\".") } } + +type ProjectTestOptions struct { + AllowDeploymentsToNoTargets bool + LifecycleLocalName string + LocalName string + Name string + ProjectGroupLocalName string +} + +func NewProjectTestOptions(projectGroupLocalName string, lifecycleLocalName string) *ProjectTestOptions { + return &ProjectTestOptions{ + LifecycleLocalName: lifecycleLocalName, + LocalName: acctest.RandStringFromCharSet(20, acctest.CharSetAlpha), + Name: acctest.RandStringFromCharSet(20, acctest.CharSetAlpha), + ProjectGroupLocalName: projectGroupLocalName, + } +} + +func testAccProjectWithOptions(opt *ProjectTestOptions) string { + + return fmt.Sprintf(`resource "octopusdeploy_project" "%s" { + allow_deployments_to_no_targets = %v + lifecycle_id = octopusdeploy_lifecycle.%s.id + name = "%s" + project_group_id = octopusdeploy_project_group.%s.id + }`, opt.LocalName, opt.AllowDeploymentsToNoTargets, opt.LifecycleLocalName, opt.Name, opt.ProjectGroupLocalName) +} + +func testAccProjectWithTemplate(localName string, name string, lifecycleLocalName string, projectGroupLocalName string) string { + return fmt.Sprintf(`resource "octopusdeploy_project" "%s" { + lifecycle_id = octopusdeploy_lifecycle.%s.id + name = "%s" + project_group_id = octopusdeploy_project_group.%s.id + + template { + name = "project variable template name" + label = "project variable template label" + + display_settings = { + "Octopus.ControlType" = "Sensitive" + } + } + }`, localName, lifecycleLocalName, name, projectGroupLocalName) + +} diff --git a/octopusdeploy/resource_deployment_process_test.go b/octopusdeploy/resource_deployment_process_test.go index ec7d06cec..d800b81da 100644 --- a/octopusdeploy/resource_deployment_process_test.go +++ b/octopusdeploy/resource_deployment_process_test.go @@ -65,6 +65,20 @@ import ( // }`, options.LocalName, options.Project.LocalName, options.StepName, options.ActionType, options.ActionName, options.PackageName, options.PackageID) // } +func testAccProjectCheckDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "octopusdeploy_project" { + continue + } + + if project, err := octoClient.Projects.GetByID(rs.Primary.ID); err == nil { + return fmt.Errorf("project (%s) still exists", project.GetID()) + } + } + + return nil +} + func TestAccOctopusDeployDeploymentProcessBasic(t *testing.T) { localName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) resourceName := "octopusdeploy_deployment_process." + localName diff --git a/octopusdeploy/resource_project.go b/octopusdeploy/resource_project.go deleted file mode 100644 index a45eb4577..000000000 --- a/octopusdeploy/resource_project.go +++ /dev/null @@ -1,156 +0,0 @@ -package octopusdeploy - -import ( - "context" - "fmt" - - "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" - "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects" - "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors" - "github.com/hashicorp/terraform-plugin-log/tflog" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func resourceProject() *schema.Resource { - return &schema.Resource{ - CreateContext: resourceProjectCreate, - DeleteContext: resourceProjectDelete, - Description: "This resource manages projects in Octopus Deploy.", - Importer: getImporter(), - ReadContext: resourceProjectRead, - Schema: getProjectSchema(), - UpdateContext: resourceProjectUpdate, - } -} - -func resourceProjectCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - project := expandProject(ctx, d) - - // DANGER: the go provider is about to nil the persistence settings, to stop the API from exploding. Take a copy - // so we can make decisions. - persistenceSettings := project.PersistenceSettings - - tflog.Info(ctx, fmt.Sprintf("creating project (%s)", project.Name)) - - var spaceID string - if v, ok := d.GetOk("space_id"); ok { - spaceID = v.(string) - } - - client := m.(*client.Client) - createdProject, err := projects.Add(client, project) - if err != nil { - return diag.FromErr(err) - } - - if persistenceSettings != nil && persistenceSettings.Type() == projects.PersistenceSettingsTypeVersionControlled { - tflog.Info(ctx, "converting project to use VCS") - - vcsProject, err := projects.ConvertToVCS(client, createdProject, "converting project to use VCS", "", persistenceSettings.(projects.GitPersistenceSettings)) - if err != nil { - projects.DeleteByID(client, spaceID, createdProject.GetID()) - return diag.FromErr(err) - } - createdProject.PersistenceSettings = vcsProject.PersistenceSettings - } - - createdProject, err = projects.GetByID(client, spaceID, createdProject.GetID()) - if err != nil { - return diag.FromErr(err) - } - - if err := setProject(ctx, d, createdProject); err != nil { - return diag.FromErr(err) - } - - d.SetId(createdProject.GetID()) - - tflog.Info(ctx, fmt.Sprintf("project created (%s)", d.Id())) - return nil -} - -func resourceProjectDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - tflog.Info(ctx, fmt.Sprintf("deleting project (%s)", d.Id())) - - var spaceID string - if v, ok := d.GetOk("space_id"); ok { - spaceID = v.(string) - } - - client := m.(*client.Client) - if err := projects.DeleteByID(client, spaceID, d.Id()); err != nil { - return diag.FromErr(err) - } - - tflog.Info(ctx, fmt.Sprintf("project deleted (%s)", d.Id())) - d.SetId("") - return nil -} - -func resourceProjectRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - tflog.Info(ctx, fmt.Sprintf("reading project (%s)", d.Id())) - - var spaceID string - if v, ok := d.GetOk("space_id"); ok { - spaceID = v.(string) - } - - client := m.(*client.Client) - project, err := projects.GetByID(client, spaceID, d.Id()) - if err != nil { - return errors.ProcessApiError(ctx, d, err, "project") - } - - if err := setProject(ctx, d, project); err != nil { - return diag.FromErr(err) - } - - tflog.Info(ctx, fmt.Sprintf("project read (%s)", d.Id())) - return nil -} - -func resourceProjectUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - tflog.Info(ctx, fmt.Sprintf("updating project (%s)", d.Id())) - - client := m.(*client.Client) - project := expandProject(ctx, d) - var updatedProject *projects.Project - var err error - - projectLinks, err := projects.GetByID(client, project.SpaceID, d.Id()) - if err != nil { - return diag.FromErr(err) - } - - if project.PersistenceSettings != nil && project.PersistenceSettings.Type() == projects.PersistenceSettingsTypeVersionControlled { - convertToVcsLink := projectLinks.Links["ConvertToVcs"] - - if len(convertToVcsLink) != 0 { - versionControlSettings := expandVersionControlSettingsForProjectConversion(ctx, d) - - tflog.Info(ctx, fmt.Sprintf("converting project to use VCS (%s)", d.Id())) - - project.Links["ConvertToVcs"] = convertToVcsLink - vcsProject, err := projects.ConvertToVCS(client, project, "converting project to use VCS", "", versionControlSettings) - if err != nil { - return diag.FromErr(err) - } - project.PersistenceSettings = vcsProject.PersistenceSettings - } - } - - project.Links = projectLinks.Links - - updatedProject, err = projects.Update(client, project) - if err != nil { - return diag.FromErr(err) - } - - if err := setProject(ctx, d, updatedProject); err != nil { - return diag.FromErr(err) - } - - tflog.Info(ctx, fmt.Sprintf("project updated (%s)", d.Id())) - return nil -} diff --git a/octopusdeploy/resource_tenant_test.go b/octopusdeploy/resource_tenant_test.go index ac2117870..22b03b365 100644 --- a/octopusdeploy/resource_tenant_test.go +++ b/octopusdeploy/resource_tenant_test.go @@ -6,7 +6,6 @@ import ( "testing" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tenants" - internalTest "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/test" "github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework/octoclient" "github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework/test" @@ -16,7 +15,6 @@ import ( ) func TestAccTenantBasic(t *testing.T) { - internalTest.SkipCI(t, "project_environment have been refactor [deprecated] - will enable this test later after Ben fix") lifecycleLocalName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) lifecycleName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) projectGroupLocalName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) diff --git a/octopusdeploy/schema_project.go b/octopusdeploy/schema_project.go deleted file mode 100644 index 695f25f32..000000000 --- a/octopusdeploy/schema_project.go +++ /dev/null @@ -1,705 +0,0 @@ -package octopusdeploy - -import ( - "context" - "fmt" - "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/actiontemplates" - "github.com/hashicorp/terraform-plugin-log/tflog" - - "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/projects" - prj "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/projects" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" -) - -func expandProject(ctx context.Context, d *schema.ResourceData) *projects.Project { - name := d.Get("name").(string) - lifecycleID := d.Get("lifecycle_id").(string) - projectGroupID := d.Get("project_group_id").(string) - - project := projects.NewProject(name, lifecycleID, projectGroupID) - project.ID = d.Id() - - if v, ok := d.GetOk("auto_create_release"); ok { - project.AutoCreateRelease = v.(bool) - } - - if v, ok := d.GetOk("auto_deploy_release_overrides"); ok { - project.AutoDeployReleaseOverrides = v.([]projects.AutoDeployReleaseOverride) - } - - if v, ok := d.GetOk("cloned_from_project_id"); ok { - project.ClonedFromProjectID = v.(string) - } - - if v, ok := d.GetOk("connectivity_policy"); ok { - project.ConnectivityPolicy = expandConnectivityPolicy(v.([]interface{})) - } - - if v, ok := d.GetOk("default_guided_failure_mode"); ok { - project.DefaultGuidedFailureMode = v.(string) - } - - if v, ok := d.GetOk("default_to_skip_if_already_installed"); ok { - project.DefaultToSkipIfAlreadyInstalled = v.(bool) - } - - if v, ok := d.GetOk("deployment_changes_template"); ok { - project.DeploymentChangesTemplate = v.(string) - } - - if v, ok := d.GetOk("deployment_process_id"); ok { - project.DeploymentProcessID = v.(string) - } - - if v, ok := d.GetOk("description"); ok { - project.Description = v.(string) - } - - tflog.Info(ctx, "expanding 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 v, ok := d.GetOk("included_library_variable_sets"); ok { - project.IncludedLibraryVariableSets = getSliceFromTerraformTypeList(v) - } - - if v, ok := d.GetOk("is_disabled"); ok { - project.IsDisabled = v.(bool) - } - - if v, ok := d.GetOk("is_discrete_channel_release"); ok { - project.IsDiscreteChannelRelease = v.(bool) - } - - if v, ok := d.GetOk("is_version_controlled"); ok { - project.IsVersionControlled = v.(bool) - } - - if v, ok := d.GetOk("jira_service_management_extension_settings"); ok { - project.ExtensionSettings = append(project.ExtensionSettings, prj.ExpandJiraServiceManagementExtensionSettings(v)) - } - - if v, ok := d.GetOk("servicenow_extension_settings"); ok { - project.ExtensionSettings = append(project.ExtensionSettings, prj.ExpandServiceNowExtensionSettings(v)) - } - - if v, ok := d.GetOk("release_creation_strategy"); ok { - project.ReleaseCreationStrategy = expandReleaseCreationStrategy(v.([]interface{})) - } - - if v, ok := d.GetOk("release_notes_template"); ok { - project.ReleaseNotesTemplate = v.(string) - } - - if v, ok := d.GetOk("slug"); ok { - project.Slug = v.(string) - } - - if v, ok := d.GetOk("space_id"); ok { - project.SpaceID = v.(string) - } - - if v, ok := d.GetOk("template"); ok { - project.Templates = expandActionTemplateParameters(v.([]interface{})) - } - - if v, ok := d.GetOk("tenanted_deployment_participation"); ok { - project.TenantedDeploymentMode = core.TenantedDeploymentMode(v.(string)) - } - - if v, ok := d.GetOk("versioning_strategy"); ok { - project.VersioningStrategy = expandVersioningStrategy(v) - } - - return project -} - -func flattenProject(ctx context.Context, d *schema.ResourceData, project *projects.Project) map[string]interface{} { - if project == nil { - return nil - } - - projectMap := map[string]interface{}{ - "auto_create_release": project.AutoCreateRelease, - "auto_deploy_release_overrides": project.AutoDeployReleaseOverrides, - "cloned_from_project_id": project.ClonedFromProjectID, - "connectivity_policy": flattenConnectivityPolicy(project.ConnectivityPolicy), - "default_guided_failure_mode": project.DefaultGuidedFailureMode, - "default_to_skip_if_already_installed": project.DefaultToSkipIfAlreadyInstalled, - "deployment_changes_template": project.DeploymentChangesTemplate, - "deployment_process_id": project.DeploymentProcessID, - "description": project.Description, - "id": project.GetID(), - "included_library_variable_sets": project.IncludedLibraryVariableSets, - "is_disabled": project.IsDisabled, - "is_discrete_channel_release": project.IsDiscreteChannelRelease, - "is_version_controlled": project.IsVersionControlled, - "lifecycle_id": project.LifecycleID, - "name": project.Name, - "project_group_id": project.ProjectGroupID, - "release_creation_strategy": flattenReleaseCreationStrategy(project.ReleaseCreationStrategy), - "release_notes_template": project.ReleaseNotesTemplate, - "slug": project.Slug, - "space_id": project.SpaceID, - "template": flattenActionTemplateParameters(project.Templates), - "tenanted_deployment_participation": project.TenantedDeploymentMode, - "variable_set_id": project.VariableSetID, - "versioning_strategy": flattenVersioningStrategy(project.VersioningStrategy), - } - - if len(project.ExtensionSettings) != 0 { - for _, extensionSettings := range project.ExtensionSettings { - switch extensionSettings.ExtensionID() { - case extensions.JiraServiceManagementExtensionID: - if jiraServiceManagementExtensionSettings, ok := extensionSettings.(*projects.JiraServiceManagementExtensionSettings); ok { - projectMap["jira_service_management_extension_settings"] = prj.FlattenJiraServiceManagementExtensionSettings(jiraServiceManagementExtensionSettings) - } - case extensions.ServiceNowExtensionID: - if serviceNowExtensionSettings, ok := extensionSettings.(*projects.ServiceNowExtensionSettings); ok { - projectMap["servicenow_extension_settings"] = prj.FlattenServiceNowExtensionSettings(serviceNowExtensionSettings) - } - } - } - } - - if project.PersistenceSettings != nil { - if project.PersistenceSettings.Type() == projects.PersistenceSettingsTypeVersionControlled { - gitCredentialType := project.PersistenceSettings.(projects.GitPersistenceSettings).Credential().Type() - switch gitCredentialType { - case credentials.GitCredentialTypeReference: - projectMap["git_library_persistence_settings"] = flattenGitPersistenceSettings(ctx, project.PersistenceSettings) - case credentials.GitCredentialTypeUsernamePassword: - projectMap["git_username_password_persistence_settings"] = flattenGitPersistenceSettings(ctx, project.PersistenceSettings) - case credentials.GitCredentialTypeAnonymous: - projectMap["git_anonymous_persistence_settings"] = flattenGitPersistenceSettings(ctx, project.PersistenceSettings) - } - } - } - - return projectMap -} - -func getProjectDataSchema() map[string]*schema.Schema { - dataSchema := getProjectSchema() - setDataSchema(&dataSchema) - - return map[string]*schema.Schema{ - "cloned_from_project_id": getQueryClonedFromProjectID(), - "id": getDataSchemaID(), - "space_id": getQuerySpaceID(), - "ids": getQueryIDs(), - "is_clone": getQueryIsClone(), - "name": getQueryName(), - "partial_name": getQueryPartialName(), - "projects": { - Computed: true, - Description: "A list of projects that match the filter(s).", - Elem: &schema.Resource{Schema: dataSchema}, - Optional: true, - Type: schema.TypeList, - }, - "skip": getQuerySkip(), - "take": getQueryTake(), - } -} - -func getProjectSchema() map[string]*schema.Schema { - return map[string]*schema.Schema{ - "allow_deployments_to_no_targets": { - Deprecated: "This value is only valid for an associated connectivity policy and should not be specified here.", - Optional: true, - Type: schema.TypeBool, - }, - "auto_create_release": { - Computed: true, - Optional: true, - Type: schema.TypeBool, - }, - "auto_deploy_release_overrides": { - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - MaxItems: 1, - Optional: true, - Type: schema.TypeList, - }, - "cloned_from_project_id": { - Computed: true, - Optional: true, - Type: schema.TypeString, - }, - "connectivity_policy": { - Computed: true, - Elem: &schema.Resource{Schema: getConnectivityPolicySchema()}, - MaxItems: 1, - Optional: true, - Type: schema.TypeList, - }, - "default_guided_failure_mode": { - Computed: true, - Optional: true, - Type: schema.TypeString, - ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ - "EnvironmentDefault", - "Off", - "On", - }, false)), - }, - "default_to_skip_if_already_installed": { - Computed: true, - Optional: true, - Type: schema.TypeBool, - }, - "deployment_changes_template": { - Computed: true, - Optional: true, - Type: schema.TypeString, - }, - "deployment_process_id": { - Computed: true, - Type: schema.TypeString, - }, - "description": { - Computed: true, - ConflictsWith: []string{"deployment_process_id"}, - Description: "The description of this project.", - Optional: true, - Type: schema.TypeString, - }, - "discrete_channel_release": { - Description: "Treats releases of different channels to the same environment as a separate deployment dimension", - Optional: true, - Type: schema.TypeBool, - }, - "git_library_persistence_settings": { - ConflictsWith: []string{"git_username_password_persistence_settings", "git_anonymous_persistence_settings"}, - Description: "Provides Git-related persistence settings for a version-controlled project.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "base_path": { - Default: ".octopus", - Description: "The base path associated with these version control settings.", - Optional: true, - Type: schema.TypeString, - }, - "default_branch": { - Default: "main", - Description: "The default branch associated with these version control settings.", - Optional: true, - Type: schema.TypeString, - }, - "git_credential_id": { - Required: true, - Type: schema.TypeString, - ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotEmpty), - }, - "protected_branches": { - Description: "A list of protected branch patterns.", - Elem: &schema.Schema{Type: schema.TypeString}, - Optional: true, - Type: schema.TypeSet, - }, - "url": { - Description: "The URL associated with these version control settings.", - Required: true, - Type: schema.TypeString, - ValidateDiagFunc: validation.ToDiagFunc(validation.IsURLWithHTTPorHTTPS), - }, - }, - }, - MaxItems: 1, - Optional: true, - Type: schema.TypeList, - }, - "git_username_password_persistence_settings": { - ConflictsWith: []string{"git_library_persistence_settings", "git_anonymous_persistence_settings"}, - Description: "Provides Git-related persistence settings for a version-controlled project.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "base_path": { - Default: ".octopus", - Description: "The base path associated with these version control settings.", - Optional: true, - Type: schema.TypeString, - }, - "default_branch": { - Default: "main", - Description: "The default branch associated with these version control settings.", - Optional: true, - Type: schema.TypeString, - }, - "password": { - Description: "The password for the Git credential.", - Required: true, - Sensitive: true, - Type: schema.TypeString, - ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotEmpty), - }, - "protected_branches": { - Description: "A list of protected branch patterns.", - Elem: &schema.Schema{Type: schema.TypeString}, - Optional: true, - Type: schema.TypeSet, - }, - "url": { - Description: "The URL associated with these version control settings.", - Required: true, - Type: schema.TypeString, - ValidateDiagFunc: validation.ToDiagFunc(validation.IsURLWithHTTPorHTTPS), - }, - "username": { - Description: "The username for the Git credential.", - Required: true, - Type: schema.TypeString, - ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotWhiteSpace), - }, - }, - }, - MaxItems: 1, - Optional: true, - Type: schema.TypeList, - }, - "git_anonymous_persistence_settings": { - ConflictsWith: []string{"git_library_persistence_settings", "git_username_password_persistence_settings"}, - Description: "Provides Git-related persistence settings for a version-controlled project.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "base_path": { - Default: ".octopus", - Description: "The base path associated with these version control settings.", - Optional: true, - Type: schema.TypeString, - }, - "default_branch": { - Default: "main", - Description: "The default branch associated with these version control settings.", - Optional: true, - Type: schema.TypeString, - }, - "protected_branches": { - Description: "A list of protected branch patterns.", - Elem: &schema.Schema{Type: schema.TypeString}, - Optional: true, - Type: schema.TypeSet, - }, - "url": { - Description: "The URL associated with these version control settings.", - Required: true, - Type: schema.TypeString, - ValidateDiagFunc: validation.ToDiagFunc(validation.IsURLWithHTTPorHTTPS), - }, - }, - }, - MaxItems: 1, - Optional: true, - Type: schema.TypeList, - }, - "id": getIDSchema(), - "included_library_variable_sets": { - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Optional: true, - Type: schema.TypeList, - }, - "is_disabled": { - Computed: true, - Optional: true, - Type: schema.TypeBool, - }, - "is_discrete_channel_release": { - Computed: true, - Description: "Treats releases of different channels to the same environment as a separate deployment dimension", - Optional: true, - Type: schema.TypeBool, - }, - "is_version_controlled": { - Computed: true, - Optional: true, - Type: schema.TypeBool, - }, - "jira_service_management_extension_settings": { - Description: "Provides extension settings for the Jira Service Management (JSM) integration for this project.", - Elem: &schema.Resource{Schema: prj.GetJiraServiceManagementExtensionSettingsSchema()}, - MaxItems: 1, - Optional: true, - Type: schema.TypeList, - }, - "lifecycle_id": { - Description: "The lifecycle ID associated with this project.", - Required: true, - Type: schema.TypeString, - ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotWhiteSpace), - }, - "name": { - Description: "The name of the project in Octopus Deploy. This name must be unique.", - Required: true, - Type: schema.TypeString, - ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotWhiteSpace), - }, - "project_group_id": { - Description: "The project group ID associated with this project.", - Required: true, - Type: schema.TypeString, - ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotWhiteSpace), - }, - "release_creation_strategy": { - Computed: true, - Elem: &schema.Resource{Schema: getReleaseCreationStrategySchema()}, - MaxItems: 1, - Optional: true, - Type: schema.TypeList, - }, - "release_notes_template": { - Computed: true, - Optional: true, - Type: schema.TypeString, - }, - "servicenow_extension_settings": { - Description: "Provides extension settings for the ServiceNow integration for this project.", - Elem: &schema.Resource{Schema: prj.GetServiceNowExtensionSettingsSchema()}, - MaxItems: 1, - Optional: true, - Type: schema.TypeList, - }, - "slug": { - Computed: true, - Description: "A human-readable, unique identifier, used to identify a project.", - Optional: true, - Type: schema.TypeString, - ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotWhiteSpace), - }, - "space_id": { - Computed: true, - Description: "The space ID associated with this project.", - Optional: true, - Type: schema.TypeString, - ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotWhiteSpace), - }, - "template": { - Elem: &schema.Resource{Schema: getActionTemplateParameterSchema()}, - Optional: true, - Type: schema.TypeList, - }, - "tenanted_deployment_participation": getTenantedDeploymentSchema(), - "variable_set_id": { - Computed: true, - Type: schema.TypeString, - }, - "versioning_strategy": { - Computed: true, - Elem: &schema.Resource{Schema: getVersionStrategySchema()}, - Optional: true, - Type: schema.TypeSet, - }, - } -} - -func setProject(ctx context.Context, d *schema.ResourceData, project *projects.Project) error { - d.Set("auto_create_release", project.AutoCreateRelease) - - if err := d.Set("auto_deploy_release_overrides", project.AutoDeployReleaseOverrides); err != nil { - return fmt.Errorf("error setting auto_deploy_release_overrides: %s", err) - } - - d.Set("cloned_from_project_id", project.ClonedFromProjectID) - - if err := d.Set("connectivity_policy", flattenConnectivityPolicy(project.ConnectivityPolicy)); err != nil { - return fmt.Errorf("error setting connectivity_policy: %s", err) - } - - d.Set("default_guided_failure_mode", project.DefaultGuidedFailureMode) - d.Set("default_to_skip_if_already_installed", project.DefaultToSkipIfAlreadyInstalled) - d.Set("deployment_changes_template", project.DeploymentChangesTemplate) - d.Set("deployment_process_id", project.DeploymentProcessID) - d.Set("description", project.Description) - - if len(project.ExtensionSettings) != 0 { - if err := prj.SetExtensionSettings(d, project.ExtensionSettings); err != nil { - return fmt.Errorf("error setting extension settings: %s", err) - } - } - - if err := d.Set("included_library_variable_sets", project.IncludedLibraryVariableSets); err != nil { - return fmt.Errorf("error setting included_library_variable_sets: %s", err) - } - - d.Set("is_disabled", project.IsDisabled) - d.Set("is_discrete_channel_release", project.IsDiscreteChannelRelease) - d.Set("is_version_controlled", project.IsVersionControlled) - d.Set("lifecycle_id", project.LifecycleID) - d.Set("name", project.Name) - - if project.PersistenceSettings != nil { - tflog.Info(ctx, "reading Persistence Settings") - if project.PersistenceSettings.Type() == projects.PersistenceSettingsTypeVersionControlled { - credential := project.PersistenceSettings.(projects.GitPersistenceSettings).Credential() - tflog.Info(ctx, fmt.Sprintf("reading Git Persistence Settings - {%v}", credential)) - gitCredentialType := credential.Type() - tflog.Info(ctx, fmt.Sprintf("reading Git Persistence Settings - {%s}", gitCredentialType)) - - // if the current settings are u/p, we need to keep the password value from state and put it back - // This is different to how this would be dealt with elsewhere, because of the way we have to reshape - // the internal objects into the schema. - if v, ok := d.GetOk("git_username_password_persistence_settings"); ok { - settings := expandGitPersistenceSettings(ctx, v, expandUsernamePasswordGitCredential) - if project.PersistenceSettings.(projects.GitPersistenceSettings).Credential().Type() == credentials.GitCredentialTypeUsernamePassword { - credential := project.PersistenceSettings.(projects.GitPersistenceSettings).Credential().(*credentials.UsernamePassword) - credential.Password.NewValue = settings.Credential().(*credentials.UsernamePassword).Password.NewValue - } - } - - // Since you can switch to different types of settings, we nil out all of the existing things - // in state and then just write back what config now says. This is why we have to store the pwd above, - // in case we're staying on u/p and then we need to keep the value. - if err := d.Set("git_library_persistence_settings", nil); err != nil { - return fmt.Errorf("error setting git_library_persistence_settings: %s", err) - } - if err := d.Set("git_username_password_persistence_settings", nil); err != nil { - return fmt.Errorf("error setting git_library_persistence_settings: %s", err) - } - if err := d.Set("git_anonymous_persistence_settings", nil); err != nil { - return fmt.Errorf("error setting git_library_persistence_settings: %s", err) - } - - switch gitCredentialType { - case credentials.GitCredentialTypeReference: - if err := d.Set("git_library_persistence_settings", setGitPersistenceSettings(ctx, project.PersistenceSettings)); err != nil { - return fmt.Errorf("error setting git_library_persistence_settings: %s", err) - } - case credentials.GitCredentialTypeUsernamePassword: - if err := d.Set("git_username_password_persistence_settings", setGitPersistenceSettings(ctx, project.PersistenceSettings)); err != nil { - return fmt.Errorf("error setting git_username_password_persistence_settings: %s", err) - } - case credentials.GitCredentialTypeAnonymous: - if err := d.Set("git_anonymous_persistence_settings", setGitPersistenceSettings(ctx, project.PersistenceSettings)); err != nil { - return fmt.Errorf("error setting git_anonymous_persistence_settings: %s", err) - } - } - } - } else { - tflog.Info(ctx, "using Database Persistence Settings") - } - - d.Set("project_group_id", project.ProjectGroupID) - - if err := d.Set("release_creation_strategy", flattenReleaseCreationStrategy(project.ReleaseCreationStrategy)); err != nil { - return fmt.Errorf("error setting release_creation_strategy: %s", err) - } - - d.Set("release_notes_template", project.ReleaseNotesTemplate) - d.Set("slug", project.Slug) - d.Set("space_id", project.SpaceID) - - if err := d.Set("template", flattenActionTemplateParameters(project.Templates)); err != nil { - return fmt.Errorf("error setting templates: %s", err) - } - - d.Set("tenanted_deployment_participation", project.TenantedDeploymentMode) - d.Set("variable_set_id", project.VariableSetID) - - if err := d.Set("versioning_strategy", flattenVersioningStrategy(project.VersioningStrategy)); err != nil { - return fmt.Errorf("error setting versioning_strategy: %s", err) - } - - d.Set("id", project.GetID()) - - return nil -} - -// The Library Variable set migration has moved these methods to the action_template_parameter.go file in the framework. These are to be removed when migrating projects and the action_template_parameter.go versions used instead. -func expandActionTemplateParameters(actionTemplateParameters []interface{}) []actiontemplates.ActionTemplateParameter { - if len(actionTemplateParameters) == 0 { - return nil - } - - expandedActionTemplateParameters := []actiontemplates.ActionTemplateParameter{} - for _, actionTemplateParameter := range actionTemplateParameters { - actionTemplateParameterMap := actionTemplateParameter.(map[string]interface{}) - expandedActionTemplateParameters = append(expandedActionTemplateParameters, expandActionTemplateParameter(actionTemplateParameterMap)) - } - return expandedActionTemplateParameters -} - -func expandActionTemplateParameter(tfTemplate map[string]interface{}) actiontemplates.ActionTemplateParameter { - actionTemplateParameter := actiontemplates.NewActionTemplateParameter() - - propertyValue := core.NewPropertyValue(tfTemplate["default_value"].(string), false) - actionTemplateParameter.DefaultValue = &propertyValue - actionTemplateParameter.DisplaySettings = flattenDisplaySettings(tfTemplate["display_settings"].(map[string]interface{})) - actionTemplateParameter.HelpText = tfTemplate["help_text"].(string) - actionTemplateParameter.ID = tfTemplate["id"].(string) - actionTemplateParameter.Label = tfTemplate["label"].(string) - actionTemplateParameter.Name = tfTemplate["name"].(string) - - return *actionTemplateParameter -} - -func flattenDisplaySettings(displaySettings map[string]interface{}) map[string]string { - flattenedDisplaySettings := make(map[string]string, len(displaySettings)) - for key, displaySetting := range displaySettings { - flattenedDisplaySettings[key] = displaySetting.(string) - } - return flattenedDisplaySettings -} - -func flattenActionTemplateParameters(actionTemplateParameters []actiontemplates.ActionTemplateParameter) []interface{} { - flattenedActionTemplateParameters := make([]interface{}, 0) - for _, actionTemplateParameter := range actionTemplateParameters { - a := make(map[string]interface{}) - a["default_value"] = actionTemplateParameter.DefaultValue.Value - a["display_settings"] = actionTemplateParameter.DisplaySettings - a["help_text"] = actionTemplateParameter.HelpText - a["id"] = actionTemplateParameter.ID - a["label"] = actionTemplateParameter.Label - a["name"] = actionTemplateParameter.Name - flattenedActionTemplateParameters = append(flattenedActionTemplateParameters, a) - } - return flattenedActionTemplateParameters -} - -func getActionTemplateParameterSchema() map[string]*schema.Schema { - return map[string]*schema.Schema{ - "default_value": { - Description: "A default value for the parameter, if applicable. This can be a hard-coded value or a variable reference.", - Optional: true, - Type: schema.TypeString, - }, - "display_settings": { - Description: "The display settings for the parameter.", - Optional: true, - Type: schema.TypeMap, - }, - "help_text": { - Description: "The help presented alongside the parameter input.", - Optional: true, - Type: schema.TypeString, - }, - "id": getIDSchema(), - "label": { - Description: "The label shown beside the parameter when presented in the deployment process. Example: `Server name`.", - Optional: true, - Type: schema.TypeString, - }, - "name": { - Description: "The name of the variable set by the parameter. The name can contain letters, digits, dashes and periods. Example: `ServerName`.", - Required: true, - Type: schema.TypeString, - ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotEmpty), - }, - } -} diff --git a/octopusdeploy_framework/datasource_project.go b/octopusdeploy_framework/datasource_project.go new file mode 100644 index 000000000..2653294b7 --- /dev/null +++ b/octopusdeploy_framework/datasource_project.go @@ -0,0 +1,95 @@ +package octopusdeploy_framework + +import ( + "context" + "fmt" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects" + "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas" + "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/types" + "time" +) + +var _ datasource.DataSource = &projectsDataSource{} + +type projectsDataSource struct { + *Config +} + +type projectsDataSourceModel struct { + ID types.String `tfsdk:"id"` + SpaceID types.String `tfsdk:"space_id"` + ClonedFromProjectID types.String `tfsdk:"cloned_from_project_id"` + IDs types.List `tfsdk:"ids"` + IsClone types.Bool `tfsdk:"is_clone"` + Name types.String `tfsdk:"name"` + PartialName types.String `tfsdk:"partial_name"` + Skip types.Int64 `tfsdk:"skip"` + Take types.Int64 `tfsdk:"take"` + Projects []projectResourceModel `tfsdk:"projects"` +} + +func NewProjectsDataSource() datasource.DataSource { + return &projectsDataSource{} +} + +func (p *projectsDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = util.GetTypeName(schemas.ProjectDataSourceName) +} + +func (p *projectsDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schemas.GetProjectDataSourceSchema() +} + +func (p *projectsDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + p.Config = DataSourceConfiguration(req, resp) +} + +func (p *projectsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data projectsDataSourceModel + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + query := projects.ProjectsQuery{ + ClonedFromProjectID: data.ClonedFromProjectID.ValueString(), + IsClone: data.IsClone.ValueBool(), + Name: data.Name.ValueString(), + PartialName: data.PartialName.ValueString(), + Skip: int(data.Skip.ValueInt64()), + Take: int(data.Take.ValueInt64()), + } + + if !data.IDs.IsNull() { + var ids []string + resp.Diagnostics.Append(data.IDs.ElementsAs(ctx, &ids, false)...) + if resp.Diagnostics.HasError() { + return + } + query.IDs = ids + } + + spaceID := data.SpaceID.ValueString() + + existingProjects, err := projects.Get(p.Client, spaceID, query) + if err != nil { + resp.Diagnostics.AddError("Unable to query projects", err.Error()) + return + } + + data.Projects = make([]projectResourceModel, 0, len(existingProjects.Items)) + for _, project := range existingProjects.Items { + flattenedProject, diags := flattenProject(ctx, project, &projectResourceModel{}) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + data.Projects = append(data.Projects, *flattenedProject) + } + + data.ID = types.StringValue(fmt.Sprintf("Projects-%s", time.Now().UTC().String())) + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/octopusdeploy_framework/framework_provider.go b/octopusdeploy_framework/framework_provider.go index 19f7809ed..816b8f774 100644 --- a/octopusdeploy_framework/framework_provider.go +++ b/octopusdeploy_framework/framework_provider.go @@ -69,6 +69,7 @@ func (p *octopusDeployFrameworkProvider) DataSources(ctx context.Context) []func NewFeedsDataSource, NewLibraryVariableSetDataSource, NewVariablesDataSource, + NewProjectsDataSource, } } @@ -90,6 +91,7 @@ func (p *octopusDeployFrameworkProvider) Resources(ctx context.Context) []func() NewTenantCommonVariableResource, NewLibraryVariableSetFeedResource, NewVariableResource, + NewProjectResource, } } diff --git a/octopusdeploy_framework/resource_project.go b/octopusdeploy_framework/resource_project.go new file mode 100644 index 000000000..6b73a6d7a --- /dev/null +++ b/octopusdeploy_framework/resource_project.go @@ -0,0 +1,220 @@ +package octopusdeploy_framework + +import ( + "context" + "fmt" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects" + "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas" + "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" +) + +var _ resource.Resource = &projectResource{} + +type projectResource struct { + *Config +} + +func NewProjectResource() resource.Resource { + return &projectResource{} +} + +func (r *projectResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = util.GetTypeName(schemas.ProjectResourceName) +} + +func (r *projectResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schemas.GetProjectResourceSchema() +} + +func (r *projectResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + r.Config = ResourceConfiguration(req, resp) +} + +func (r *projectResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan projectResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + project := expandProject(ctx, plan) + // PersistenceSettings.Password doesn't return from API so this is work around + persistenceSettings := project.PersistenceSettings + createdProject, err := projects.Add(r.Client, project) + if err != nil { + resp.Diagnostics.AddError("Error creating project", err.Error()) + return + } + + if persistenceSettings != nil && persistenceSettings.Type() == projects.PersistenceSettingsTypeVersionControlled { + _, err := projects.ConvertToVCS(r.Client, createdProject, "Converting project to use VCS", "", persistenceSettings.(projects.GitPersistenceSettings)) + if err != nil { + resp.Diagnostics.AddError("Error converting project to VCS", err.Error()) + _ = projects.DeleteByID(r.Client, plan.SpaceID.ValueString(), createdProject.GetID()) + return + } + } + + createdProject, err = projects.GetByID(r.Client, plan.SpaceID.ValueString(), createdProject.GetID()) + if persistenceSettings != nil { + createdProject.PersistenceSettings = persistenceSettings + } + + flattenedProject, diags := flattenProject(ctx, createdProject, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + deploymentDiags := r.updateStateWithDeploymentSettings(createdProject, flattenedProject, &plan) + if deploymentDiags.HasError() { + return + } + + diags = resp.State.Set(ctx, flattenedProject) + resp.Diagnostics.Append(diags...) +} + +func (r *projectResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state projectResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + stateProject := expandProject(ctx, state) + // PersistenceSettings.Password doesn't return from API so this is work around + persistenceSettings := stateProject.PersistenceSettings + + project, err := projects.GetByID(r.Client, state.SpaceID.ValueString(), state.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Error reading project", err.Error()) + return + } + if persistenceSettings != nil { + project.PersistenceSettings = persistenceSettings + } + + flattenedProject, diags := flattenProject(ctx, project, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diagFromUpdate := r.updateStateWithDeploymentSettings(project, flattenedProject, &state) + if diagFromUpdate.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, flattenedProject)...) +} + +func (r *projectResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan projectResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + existingProject, err := projects.GetByID(r.Client, plan.SpaceID.ValueString(), plan.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Error retrieving project", err.Error()) + return + } + + updatedProject := expandProject(ctx, plan) + updatedProject.ID = existingProject.ID + updatedProject.Links = existingProject.Links + // PersistenceSettings.Password doesn't return from API so this is work around + persistenceSettings := updatedProject.PersistenceSettings + + if updatedProject.PersistenceSettings != nil && updatedProject.PersistenceSettings.Type() == projects.PersistenceSettingsTypeVersionControlled { + if existingProject.PersistenceSettings == nil || existingProject.PersistenceSettings.Type() != projects.PersistenceSettingsTypeVersionControlled { + vcsProject, err := projects.ConvertToVCS(r.Client, existingProject, "Converting project to use VCS", "", updatedProject.PersistenceSettings.(projects.GitPersistenceSettings)) + if err != nil { + resp.Diagnostics.AddError("Error converting project to VCS", err.Error()) + return + } + updatedProject.PersistenceSettings = vcsProject.PersistenceSettings + } + } + + updatedProject, err = projects.Update(r.Client, updatedProject) + if err != nil { + resp.Diagnostics.AddError("Error updating project", err.Error()) + return + } + + if persistenceSettings != nil { + updatedProject.PersistenceSettings = persistenceSettings + } + + flattenedProject, diags := flattenProject(ctx, updatedProject, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diagFromUpdate := r.updateStateWithDeploymentSettings(updatedProject, flattenedProject, &plan) + if diagFromUpdate.HasError() { + return + } + + diags = resp.State.Set(ctx, flattenedProject) + resp.Diagnostics.Append(diags...) +} + +func (r *projectResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state projectResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + err := projects.DeleteByID(r.Client, state.SpaceID.ValueString(), state.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Error deleting project", err.Error()) + return + } + + resp.State.RemoveResource(ctx) +} + +func (r *projectResource) updateStateWithDeploymentSettings(project *projects.Project, newState *projectResourceModel, originalState *projectResourceModel) diag.Diagnostics { + var diags diag.Diagnostics + + var gitRef string + if project.IsVersionControlled { + if gitSettings, ok := project.PersistenceSettings.(projects.GitPersistenceSettings); ok { + gitRef = gitSettings.DefaultBranch() + } + if gitRef == "" { + gitRef = "main" + } + } + + deploymentSettings, err := r.Client.Deployments.GetDeploymentSettings(project, gitRef) + if err != nil { + return diag.FromErr(fmt.Errorf("error reading deployment settings: %w", err)) + } + + // Update the state with the deployment settings + if deploymentSettings.ConnectivityPolicy != nil && !originalState.ConnectivityPolicy.IsNull() { + newState.ConnectivityPolicy = flattenConnectivityPolicy(deploymentSettings.ConnectivityPolicy) + } + newState.DefaultGuidedFailureMode = types.StringValue(string(deploymentSettings.DefaultGuidedFailureMode)) + newState.DefaultToSkipIfAlreadyInstalled = types.BoolValue(deploymentSettings.DefaultToSkipIfAlreadyInstalled) + newState.DeploymentChangesTemplate = types.StringValue(deploymentSettings.DeploymentChangesTemplate) + newState.ReleaseNotesTemplate = types.StringValue(deploymentSettings.ReleaseNotesTemplate) + if deploymentSettings.VersioningStrategy != nil && !originalState.VersioningStrategy.IsNull() { + newState.VersioningStrategy = flattenVersioningStrategy(deploymentSettings.VersioningStrategy) + } + + return diags +} diff --git a/octopusdeploy_framework/resource_project_expand.go b/octopusdeploy_framework/resource_project_expand.go new file mode 100644 index 000000000..e0a3316da --- /dev/null +++ b/octopusdeploy_framework/resource_project_expand.go @@ -0,0 +1,339 @@ +package octopusdeploy_framework + +import ( + "context" + "fmt" + "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/packages" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "net/url" +) + +func expandProject(ctx context.Context, model projectResourceModel) *projects.Project { + project := projects.NewProject( + model.Name.ValueString(), + model.LifecycleID.ValueString(), + model.ProjectGroupID.ValueString(), + ) + + project.ID = model.ID.ValueString() + project.SpaceID = model.SpaceID.ValueString() + project.Description = model.Description.ValueString() + project.IsDisabled = model.IsDisabled.ValueBool() + project.AutoCreateRelease = model.AutoCreateRelease.ValueBool() + project.DefaultGuidedFailureMode = model.DefaultGuidedFailureMode.ValueString() + project.DefaultToSkipIfAlreadyInstalled = model.DefaultToSkipIfAlreadyInstalled.ValueBool() + project.DeploymentChangesTemplate = model.DeploymentChangesTemplate.ValueString() + project.DeploymentProcessID = model.DeploymentProcessID.ValueString() + project.IsDiscreteChannelRelease = model.IsDiscreteChannelRelease.ValueBool() + project.IsVersionControlled = model.IsVersionControlled.ValueBool() + 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 + model.IncludedLibraryVariableSets.ElementsAs(ctx, &includedSets, false) + project.IncludedLibraryVariableSets = includedSets + } + + if !model.ConnectivityPolicy.IsNull() { + project.ConnectivityPolicy = expandConnectivityPolicy(ctx, model.ConnectivityPolicy) + } + + if !model.GitLibraryPersistenceSettings.IsNull() { + var gitLibrarySettingsList []gitLibraryPersistenceSettingsModel + diags := model.GitLibraryPersistenceSettings.ElementsAs(ctx, &gitLibrarySettingsList, false) + if diags.HasError() { + fmt.Printf("Error converting Git library persistence settings: %v\n", diags) + } else { + fmt.Printf("Number of Git library persistence settings: %d\n", len(gitLibrarySettingsList)) + if len(gitLibrarySettingsList) > 0 { + project.PersistenceSettings = expandGitLibraryPersistenceSettings(ctx, gitLibrarySettingsList[0]) + project.IsVersionControlled = true + } + } + } else if !model.GitUsernamePasswordPersistenceSettings.IsNull() { + var gitUsernamePasswordSettingsList []gitUsernamePasswordPersistenceSettingsModel + diags := model.GitUsernamePasswordPersistenceSettings.ElementsAs(ctx, &gitUsernamePasswordSettingsList, false) + if diags.HasError() { + fmt.Printf("Error converting Git username/password persistence settings: %v\n", diags) + } else { + fmt.Printf("Number of Git username/password persistence settings: %d\n", len(gitUsernamePasswordSettingsList)) + if len(gitUsernamePasswordSettingsList) > 0 { + project.PersistenceSettings = expandGitUsernamePasswordPersistenceSettings(ctx, gitUsernamePasswordSettingsList[0]) + project.IsVersionControlled = true + } + } + } else if !model.GitAnonymousPersistenceSettings.IsNull() { + var gitAnonymousSettingsList []gitAnonymousPersistenceSettingsModel + diags := model.GitAnonymousPersistenceSettings.ElementsAs(ctx, &gitAnonymousSettingsList, false) + if diags.HasError() { + fmt.Printf("Error converting Git anonymous persistence settings: %v\n", diags) + } else { + fmt.Printf("Number of Git anonymous persistence settings: %d\n", len(gitAnonymousSettingsList)) + if len(gitAnonymousSettingsList) > 0 { + project.PersistenceSettings = expandGitAnonymousPersistenceSettings(ctx, gitAnonymousSettingsList[0]) + project.IsVersionControlled = true + } + } + } + + if !model.JiraServiceManagementExtensionSettings.IsNull() { + var settingsList []jiraServiceManagementExtensionSettingsModel + diags := model.JiraServiceManagementExtensionSettings.ElementsAs(ctx, &settingsList, false) + if !diags.HasError() && len(settingsList) > 0 { + settings := settingsList[0] + project.ExtensionSettings = append(project.ExtensionSettings, expandJiraServiceManagementExtensionSettings(settings)) + } + } + + if !model.ServiceNowExtensionSettings.IsNull() { + var settingsList []servicenowExtensionSettingsModel + diags := model.ServiceNowExtensionSettings.ElementsAs(ctx, &settingsList, false) + if !diags.HasError() && len(settingsList) > 0 { + settings := settingsList[0] + project.ExtensionSettings = append(project.ExtensionSettings, expandServiceNowExtensionSettings(settings)) + } + } + + if !model.VersioningStrategy.IsNull() { + project.VersioningStrategy = expandVersioningStrategy(ctx, model.VersioningStrategy) + } + + if !model.ReleaseCreationStrategy.IsNull() { + var strategy releaseCreationStrategyModel + model.ReleaseCreationStrategy.ElementsAs(ctx, &strategy, false) + project.ReleaseCreationStrategy = expandReleaseCreationStrategy(strategy) + } + + if !model.Template.IsNull() { + var templates []templateModel + diags := model.Template.ElementsAs(ctx, &templates, false) + if diags.HasError() { + fmt.Printf("Error converting templates: %v\n", diags) + } else { + fmt.Printf("Number of templates: %d\n", len(templates)) + project.Templates = expandTemplates(templates) + } + } else { + fmt.Println("Template is null") + project.Templates = []actiontemplates.ActionTemplateParameter{} + } + + if !model.AutoDeployReleaseOverrides.IsNull() { + var overrideModels []autoDeployReleaseOverrideModel + diags := model.AutoDeployReleaseOverrides.ElementsAs(ctx, &overrideModels, false) + if !diags.HasError() { + project.AutoDeployReleaseOverrides = expandAutoDeployReleaseOverrides(overrideModels) + } + } + + return project +} + +func expandGitLibraryPersistenceSettings(ctx context.Context, model gitLibraryPersistenceSettingsModel) projects.GitPersistenceSettings { + url, _ := url.Parse(model.URL.ValueString()) + var protectedBranches []string + model.ProtectedBranches.ElementsAs(ctx, &protectedBranches, false) + + return projects.NewGitPersistenceSettings( + model.BasePath.ValueString(), + &credentials.Reference{ + ID: model.GitCredentialID.ValueString(), + }, + model.DefaultBranch.ValueString(), + protectedBranches, + url, + ) +} + +func expandGitUsernamePasswordPersistenceSettings(ctx context.Context, model gitUsernamePasswordPersistenceSettingsModel) projects.GitPersistenceSettings { + url, _ := url.Parse(model.URL.ValueString()) + var protectedBranches []string + model.ProtectedBranches.ElementsAs(ctx, &protectedBranches, false) + + usernamePasswordCredential := credentials.NewUsernamePassword( + model.Username.ValueString(), + core.NewSensitiveValue(model.Password.ValueString()), + ) + + return projects.NewGitPersistenceSettings( + model.BasePath.ValueString(), + usernamePasswordCredential, + model.DefaultBranch.ValueString(), + protectedBranches, + url, + ) +} + +func expandGitAnonymousPersistenceSettings(ctx context.Context, model gitAnonymousPersistenceSettingsModel) projects.GitPersistenceSettings { + url, _ := url.Parse(model.URL.ValueString()) + var protectedBranches []string + model.ProtectedBranches.ElementsAs(ctx, &protectedBranches, false) + + return projects.NewGitPersistenceSettings( + model.BasePath.ValueString(), + &credentials.Anonymous{}, + model.DefaultBranch.ValueString(), + protectedBranches, + url, + ) +} + +func expandAutoDeployReleaseOverrides(models []autoDeployReleaseOverrideModel) []projects.AutoDeployReleaseOverride { + result := make([]projects.AutoDeployReleaseOverride, 0, len(models)) + + for _, model := range models { + override := projects.AutoDeployReleaseOverride{ + EnvironmentID: model.EnvironmentID.ValueString(), + } + + if !model.TenantID.IsNull() { + override.TenantID = model.TenantID.ValueString() + } + + result = append(result, override) + } + + return result +} + +func expandConnectivityPolicy(ctx context.Context, connectivityPolicyList types.List) *core.ConnectivityPolicy { + if connectivityPolicyList.IsNull() || connectivityPolicyList.IsUnknown() { + return nil + } + + var policyList []connectivityPolicyModel + diags := connectivityPolicyList.ElementsAs(ctx, &policyList, false) + if diags.HasError() { + return nil + } + + if len(policyList) == 0 { + return nil + } + policy := policyList[0] + + var targetRoles []string + if !policy.TargetRoles.IsNull() && !policy.TargetRoles.IsUnknown() { + policy.TargetRoles.ElementsAs(ctx, &targetRoles, false) + } + + skipMachineBehavior := core.SkipMachineBehavior(policy.SkipMachineBehavior.ValueString()) + + return &core.ConnectivityPolicy{ + AllowDeploymentsToNoTargets: policy.AllowDeploymentsToNoTargets.ValueBool(), + ExcludeUnhealthyTargets: policy.ExcludeUnhealthyTargets.ValueBool(), + SkipMachineBehavior: skipMachineBehavior, + TargetRoles: targetRoles, + } +} + +func expandJiraServiceManagementExtensionSettings(model jiraServiceManagementExtensionSettingsModel) *projects.JiraServiceManagementExtensionSettings { + return projects.NewJiraServiceManagementExtensionSettings( + model.ConnectionID.ValueString(), + model.IsEnabled.ValueBool(), + model.ServiceDeskProjectName.ValueString(), + ) +} + +func expandServiceNowExtensionSettings(model servicenowExtensionSettingsModel) *projects.ServiceNowExtensionSettings { + return projects.NewServiceNowExtensionSettings( + model.ConnectionID.ValueString(), + model.IsEnabled.ValueBool(), + model.StandardChangeTemplateName.ValueString(), + model.IsStateAutomaticallyTransitioned.ValueBool(), + ) +} + +func expandVersioningStrategy(ctx context.Context, versioningStrategyList types.List) *projects.VersioningStrategy { + if versioningStrategyList.IsNull() || versioningStrategyList.IsUnknown() { + return nil + } + + var strategyList []versioningStrategyModel + diags := versioningStrategyList.ElementsAs(ctx, &strategyList, false) + if diags.HasError() { + return nil + } + + if len(strategyList) == 0 { + return nil + } + strategy := strategyList[0] + + versioningStrategy := &projects.VersioningStrategy{ + Template: strategy.Template.ValueString(), + } + + if !strategy.DonorPackageStepID.IsNull() { + donorPackageStepID := strategy.DonorPackageStepID.ValueString() + versioningStrategy.DonorPackageStepID = &donorPackageStepID + } + + if !strategy.DonorPackage.IsNull() { + var donorPackageList []deploymentActionPackageModel + diags := strategy.DonorPackage.ElementsAs(ctx, &donorPackageList, false) + if !diags.HasError() && len(donorPackageList) > 0 { + donorPackage := donorPackageList[0] + versioningStrategy.DonorPackage = &packages.DeploymentActionPackage{ + DeploymentAction: donorPackage.DeploymentAction.ValueString(), + PackageReference: donorPackage.PackageReference.ValueString(), + } + } + } + return versioningStrategy +} + +func expandReleaseCreationStrategy(model releaseCreationStrategyModel) *projects.ReleaseCreationStrategy { + strategy := &projects.ReleaseCreationStrategy{ + ChannelID: model.ChannelID.ValueString(), + ReleaseCreationPackageStepID: model.ReleaseCreationPackageStepID.ValueString(), + } + if !model.ReleaseCreationPackage.IsNull() { + var releaseCreationPackage deploymentActionPackageModel + model.ReleaseCreationPackage.As(context.Background(), &releaseCreationPackage, basetypes.ObjectAsOptions{}) + strategy.ReleaseCreationPackage = expandDeploymentActionPackage(releaseCreationPackage) + } + return strategy +} + +func expandDeploymentActionPackage(model deploymentActionPackageModel) *packages.DeploymentActionPackage { + return &packages.DeploymentActionPackage{ + DeploymentAction: model.DeploymentAction.ValueString(), + PackageReference: model.PackageReference.ValueString(), + } +} +func expandTemplates(templates []templateModel) []actiontemplates.ActionTemplateParameter { + result := make([]actiontemplates.ActionTemplateParameter, len(templates)) + for i, template := range templates { + defaultValue := core.NewPropertyValue("", false) + if !template.DefaultValue.IsNull() { + defaultValue = core.NewPropertyValue(template.DefaultValue.ValueString(), false) + } + + displaySettings := make(map[string]string) + if !template.DisplaySettings.IsNull() && !template.DisplaySettings.IsUnknown() { + template.DisplaySettings.ElementsAs(context.Background(), &displaySettings, false) + } + + result[i] = actiontemplates.ActionTemplateParameter{ + DefaultValue: &defaultValue, + DisplaySettings: displaySettings, + HelpText: template.HelpText.ValueString(), + Label: template.Label.ValueString(), + Name: template.Name.ValueString(), + } + + if !template.ID.IsNull() { + result[i].Resource.ID = template.ID.ValueString() + } + } + return result +} diff --git a/octopusdeploy_framework/resource_project_flatten.go b/octopusdeploy_framework/resource_project_flatten.go new file mode 100644 index 000000000..39b4c3e80 --- /dev/null +++ b/octopusdeploy_framework/resource_project_flatten.go @@ -0,0 +1,427 @@ +package octopusdeploy_framework + +import ( + "context" + "fmt" + "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" + "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func flattenProject(ctx context.Context, project *projects.Project, state *projectResourceModel) (*projectResourceModel, diag.Diagnostics) { + if project == nil { + return nil, diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Error flattening project", + "The project is nil", + ), + } + } + + model := &projectResourceModel{ + ID: types.StringValue(project.GetID()), + SpaceID: types.StringValue(project.SpaceID), + Name: types.StringValue(project.Name), + Description: types.StringValue(project.Description), + LifecycleID: types.StringValue(project.LifecycleID), + ProjectGroupID: types.StringValue(project.ProjectGroupID), + IsDisabled: types.BoolValue(project.IsDisabled), + AutoCreateRelease: types.BoolValue(project.AutoCreateRelease), + DefaultGuidedFailureMode: types.StringValue(project.DefaultGuidedFailureMode), + 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: util.StringOrNull(project.ReleaseNotesTemplate), + Slug: types.StringValue(project.Slug), + ClonedFromProjectID: util.StringOrNull(project.ClonedFromProjectID), + } + + model.IncludedLibraryVariableSets = util.FlattenStringList(project.IncludedLibraryVariableSets) + model.AutoDeployReleaseOverrides = flattenAutoDeployReleaseOverrides(project.AutoDeployReleaseOverrides) + + if state.ConnectivityPolicy.IsNull() { + model.ConnectivityPolicy = types.ListNull(types.ObjectType{AttrTypes: getConnectivityPolicyAttrTypes()}) + } else { + model.ConnectivityPolicy = flattenConnectivityPolicy(project.ConnectivityPolicy) + } + + if state.ReleaseCreationStrategy.IsNull() { + model.ReleaseCreationStrategy = types.ListNull(types.ObjectType{AttrTypes: getReleaseCreationStrategyAttrTypes()}) + } else { + model.ReleaseCreationStrategy = flattenReleaseCreationStrategy(project.ReleaseCreationStrategy) + } + + if state.VersioningStrategy.IsNull() { + model.VersioningStrategy = types.ListNull(types.ObjectType{AttrTypes: getVersioningStrategyAttrTypes()}) + } else { + model.VersioningStrategy = flattenVersioningStrategy(project.VersioningStrategy) + } + + model.Template = flattenTemplates(project.Templates) + + diags := processPersistenceSettings(ctx, project, model) + + if diags.HasError() { + return model, diags + } + + // Extension Settings + model.JiraServiceManagementExtensionSettings = flattenJiraServiceManagementExtensionSettings(nil) + model.ServiceNowExtensionSettings = flattenServiceNowExtensionSettings(nil) + + for _, extensionSetting := range project.ExtensionSettings { + switch extensionSetting.ExtensionID() { + case extensions.JiraServiceManagementExtensionID: + if jsmSettings, ok := extensionSetting.(*projects.JiraServiceManagementExtensionSettings); ok { + model.JiraServiceManagementExtensionSettings = flattenJiraServiceManagementExtensionSettings(jsmSettings) + } + case extensions.ServiceNowExtensionID: + if snowSettings, ok := extensionSetting.(*projects.ServiceNowExtensionSettings); ok { + model.ServiceNowExtensionSettings = flattenServiceNowExtensionSettings(snowSettings) + } + } + } + + return model, diags +} + +func processPersistenceSettings(ctx context.Context, project *projects.Project, model *projectResourceModel) diag.Diagnostics { + var diags diag.Diagnostics + if project.PersistenceSettings != nil { + if project.PersistenceSettings.Type() == projects.PersistenceSettingsTypeVersionControlled { + gitSettings := project.PersistenceSettings.(projects.GitPersistenceSettings) + gitCredentialType := gitSettings.Credential().Type() + model.IsVersionControlled = types.BoolValue(true) + switch gitCredentialType { + case credentials.GitCredentialTypeReference: + model.GitLibraryPersistenceSettings, diags = flattenGitPersistenceSettings(ctx, gitSettings) + model.GitUsernamePasswordPersistenceSettings = types.ListNull(types.ObjectType{AttrTypes: getGitUsernamePasswordPersistenceSettingsAttrTypes()}) + model.GitAnonymousPersistenceSettings = types.ListNull(types.ObjectType{AttrTypes: getGitAnonymousPersistenceSettingsAttrTypes()}) + case credentials.GitCredentialTypeUsernamePassword: + model.GitLibraryPersistenceSettings = types.ListNull(types.ObjectType{AttrTypes: getGitLibraryPersistenceSettingsAttrTypes()}) + model.GitUsernamePasswordPersistenceSettings, diags = flattenGitPersistenceSettings(ctx, gitSettings) + model.GitAnonymousPersistenceSettings = types.ListNull(types.ObjectType{AttrTypes: getGitAnonymousPersistenceSettingsAttrTypes()}) + case credentials.GitCredentialTypeAnonymous: + model.GitLibraryPersistenceSettings = types.ListNull(types.ObjectType{AttrTypes: getGitLibraryPersistenceSettingsAttrTypes()}) + model.GitUsernamePasswordPersistenceSettings = types.ListNull(types.ObjectType{AttrTypes: getGitUsernamePasswordPersistenceSettingsAttrTypes()}) + model.GitAnonymousPersistenceSettings, diags = flattenGitPersistenceSettings(ctx, gitSettings) + } + } else { + model.GitLibraryPersistenceSettings = types.ListNull(types.ObjectType{AttrTypes: getGitLibraryPersistenceSettingsAttrTypes()}) + model.GitUsernamePasswordPersistenceSettings = types.ListNull(types.ObjectType{AttrTypes: getGitUsernamePasswordPersistenceSettingsAttrTypes()}) + model.GitAnonymousPersistenceSettings = types.ListNull(types.ObjectType{AttrTypes: getGitAnonymousPersistenceSettingsAttrTypes()}) + } + } else { + model.GitLibraryPersistenceSettings = types.ListNull(types.ObjectType{AttrTypes: getGitLibraryPersistenceSettingsAttrTypes()}) + model.GitUsernamePasswordPersistenceSettings = types.ListNull(types.ObjectType{AttrTypes: getGitUsernamePasswordPersistenceSettingsAttrTypes()}) + model.GitAnonymousPersistenceSettings = types.ListNull(types.ObjectType{AttrTypes: getGitAnonymousPersistenceSettingsAttrTypes()}) + model.IsVersionControlled = types.BoolValue(false) + } + return diags +} + +func flattenConnectivityPolicy(policy *core.ConnectivityPolicy) types.List { + if policy == nil { + return types.ListNull(types.ObjectType{AttrTypes: getConnectivityPolicyAttrTypes()}) + } + + 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 flattenVersioningStrategy(strategy *projects.VersioningStrategy) types.List { + if strategy == nil { + return types.ListNull(types.ObjectType{AttrTypes: getVersioningStrategyAttrTypes()}) + } + 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), + }) + + return types.ListValueMust(types.ObjectType{AttrTypes: getVersioningStrategyAttrTypes()}, []attr.Value{obj}) +} + +func flattenGitPersistenceSettings(ctx context.Context, persistenceSettings projects.PersistenceSettings) (types.List, diag.Diagnostics) { + if persistenceSettings == nil || persistenceSettings.Type() == projects.PersistenceSettingsTypeDatabase { + return types.ListNull(types.ObjectType{AttrTypes: getGitAnonymousPersistenceSettingsAttrTypes()}), nil + } + + gitPersistenceSettings := persistenceSettings.(projects.GitPersistenceSettings) + + baseAttrValues := map[string]attr.Value{ + "base_path": types.StringValue(gitPersistenceSettings.BasePath()), + "default_branch": types.StringValue(gitPersistenceSettings.DefaultBranch()), + "url": types.StringValue(gitPersistenceSettings.URL().String()), + } + + protectedBranches, diags := types.SetValueFrom(ctx, types.StringType, gitPersistenceSettings.ProtectedBranchNamePatterns()) + if diags.HasError() { + return types.ListNull(types.ObjectType{}), diags + } + baseAttrValues["protected_branches"] = protectedBranches + + var attrTypes map[string]attr.Type + var attrValues map[string]attr.Value + + credential := gitPersistenceSettings.Credential() + switch credential.Type() { + case credentials.GitCredentialTypeReference: + attrTypes = getGitLibraryPersistenceSettingsAttrTypes() + attrValues = baseAttrValues + attrValues["git_credential_id"] = types.StringValue(credential.(*credentials.Reference).ID) + case credentials.GitCredentialTypeUsernamePassword: + attrTypes = getGitUsernamePasswordPersistenceSettingsAttrTypes() + attrValues = baseAttrValues + attrValues["username"] = types.StringValue(credential.(*credentials.UsernamePassword).Username) + attrValues["password"] = types.StringValue(*credential.(*credentials.UsernamePassword).Password.NewValue) + case credentials.GitCredentialTypeAnonymous: + attrTypes = getGitAnonymousPersistenceSettingsAttrTypes() + attrValues = baseAttrValues + default: + return types.ListNull(types.ObjectType{}), diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Unsupported Git Credential Type", + fmt.Sprintf("Git credential type %v is not supported", credential.Type()), + ), + } + } + + objValue, diags := types.ObjectValue(attrTypes, attrValues) + if diags.HasError() { + return types.ListNull(types.ObjectType{AttrTypes: attrTypes}), diags + } + + return types.ListValueMust(types.ObjectType{AttrTypes: attrTypes}, []attr.Value{objValue}), nil +} + +func flattenJiraServiceManagementExtensionSettings(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(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": util.StringOrNull(settings.StandardChangeTemplateName), + }) + + return types.ListValueMust(types.ObjectType{AttrTypes: getServiceNowExtensionSettingsAttrTypes()}, []attr.Value{obj}) +} + +func flattenTemplates(templates []actiontemplates.ActionTemplateParameter) types.List { + if len(templates) == 0 { + return types.ListNull(types.ObjectType{AttrTypes: getTemplateAttrTypes()}) + } + + templateList := make([]attr.Value, 0, len(templates)) + for _, template := range templates { + + obj := types.ObjectValueMust(getTemplateAttrTypes(), map[string]attr.Value{ + "id": types.StringValue(template.Resource.ID), + "name": types.StringValue(template.Name), + "label": util.StringOrNull(template.Label), + "help_text": util.StringOrNull(template.HelpText), + "default_value": util.StringOrNull(template.DefaultValue.Value), + "display_settings": types.MapValueMust( + types.StringType, + convertMapStringToMapAttrValue(template.DisplaySettings), + ), + }) + + templateList = append(templateList, obj) + } + + return types.ListValueMust(types.ObjectType{AttrTypes: getTemplateAttrTypes()}, templateList) +} + +func flattenAutoDeployReleaseOverrides(overrides []projects.AutoDeployReleaseOverride) types.List { + if len(overrides) == 0 { + return types.ListValueMust(types.ObjectType{AttrTypes: getAutoDeployReleaseOverrideAttrTypes()}, []attr.Value{}) + } + + 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.ListValueMust(types.ObjectType{AttrTypes: getAutoDeployReleaseOverrideAttrTypes()}, overrideList) +} + +func getAutoDeployReleaseOverrideAttrTypes() map[string]attr.Type { + return map[string]attr.Type{ + "environment_id": types.StringType, + "release_id": types.StringType, + "tenant_id": types.StringType, + } +} + +func flattenReleaseCreationStrategy(strategy *projects.ReleaseCreationStrategy) types.List { + if strategy == nil { + return types.ListValueMust(types.ObjectType{AttrTypes: getReleaseCreationStrategyAttrTypes()}, []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": flattenDeploymentActionPackage(strategy.ReleaseCreationPackage), + }) + + return types.ListValueMust(types.ObjectType{AttrTypes: getReleaseCreationStrategyAttrTypes()}, []attr.Value{obj}) +} + +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) + } + return result +} + +func flattenDeploymentActionPackage(pkg *packages.DeploymentActionPackage) types.List { + if pkg == nil { + return types.ListNull(types.ObjectType{AttrTypes: getDonorPackageAttrTypes()}) + } + + 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, + } +} + +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}, + } +} + +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, + } +} + +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}, + } +} + +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 new file mode 100644 index 000000000..7e7386079 --- /dev/null +++ b/octopusdeploy_framework/resource_project_model.go @@ -0,0 +1,114 @@ +package octopusdeploy_framework + +import ( + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type projectResourceModel struct { + ID types.String `tfsdk:"id"` + SpaceID types.String `tfsdk:"space_id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + LifecycleID types.String `tfsdk:"lifecycle_id"` + ProjectGroupID types.String `tfsdk:"project_group_id"` + IsDisabled types.Bool `tfsdk:"is_disabled"` + AutoCreateRelease types.Bool `tfsdk:"auto_create_release"` + AllowDeploymentsToNoTargets types.Bool `tfsdk:"allow_deployments_to_no_targets"` + DefaultGuidedFailureMode types.String `tfsdk:"default_guided_failure_mode"` + DefaultToSkipIfAlreadyInstalled types.Bool `tfsdk:"default_to_skip_if_already_installed"` + DeploymentChangesTemplate types.String `tfsdk:"deployment_changes_template"` + DeploymentProcessID types.String `tfsdk:"deployment_process_id"` + DiscreteChannelRelease types.Bool `tfsdk:"discrete_channel_release"` + IsDiscreteChannelRelease types.Bool `tfsdk:"is_discrete_channel_release"` + IsVersionControlled types.Bool `tfsdk:"is_version_controlled"` + 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"` + 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 { + AllowDeploymentsToNoTargets types.Bool `tfsdk:"allow_deployments_to_no_targets"` + ExcludeUnhealthyTargets types.Bool `tfsdk:"exclude_unhealthy_targets"` + 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 jiraServiceManagementExtensionSettingsModel struct { + ConnectionID types.String `tfsdk:"connection_id"` + IsEnabled types.Bool `tfsdk:"is_enabled"` + ServiceDeskProjectName types.String `tfsdk:"service_desk_project_name"` +} + +type servicenowExtensionSettingsModel struct { + ConnectionID types.String `tfsdk:"connection_id"` + IsEnabled types.Bool `tfsdk:"is_enabled"` + IsStateAutomaticallyTransitioned types.Bool `tfsdk:"is_state_automatically_transitioned"` + StandardChangeTemplateName types.String `tfsdk:"standard_change_template_name"` +} + +type versioningStrategyModel struct { + DonorPackageStepID types.String `tfsdk:"donor_package_step_id"` + Template types.String `tfsdk:"template"` + DonorPackage types.List `tfsdk:"donor_package"` +} + +type releaseCreationStrategyModel struct { + ChannelID types.String `tfsdk:"channel_id"` + ReleaseCreationPackageStepID types.String `tfsdk:"release_creation_package_step_id"` + ReleaseCreationPackage types.Object `tfsdk:"release_creation_package"` +} + +type deploymentActionPackageModel struct { + DeploymentAction types.String `tfsdk:"deployment_action"` + PackageReference types.String `tfsdk:"package_reference"` +} + +type templateModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Label types.String `tfsdk:"label"` + HelpText types.String `tfsdk:"help_text"` + DefaultValue types.String `tfsdk:"default_value"` + DisplaySettings types.Map `tfsdk:"display_settings"` +} + +type gitLibraryPersistenceSettingsModel struct { + GitCredentialID types.String `tfsdk:"git_credential_id"` + URL types.String `tfsdk:"url"` + BasePath types.String `tfsdk:"base_path"` + DefaultBranch types.String `tfsdk:"default_branch"` + ProtectedBranches types.Set `tfsdk:"protected_branches"` +} + +type gitUsernamePasswordPersistenceSettingsModel struct { + URL types.String `tfsdk:"url"` + Username types.String `tfsdk:"username"` + Password types.String `tfsdk:"password"` + BasePath types.String `tfsdk:"base_path"` + DefaultBranch types.String `tfsdk:"default_branch"` + ProtectedBranches types.Set `tfsdk:"protected_branches"` +} + +type gitAnonymousPersistenceSettingsModel struct { + URL types.String `tfsdk:"url"` + BasePath types.String `tfsdk:"base_path"` + DefaultBranch types.String `tfsdk:"default_branch"` + ProtectedBranches types.Set `tfsdk:"protected_branches"` +} diff --git a/octopusdeploy/resource_project_test.go b/octopusdeploy_framework/resource_project_test.go similarity index 83% rename from octopusdeploy/resource_project_test.go rename to octopusdeploy_framework/resource_project_test.go index c8b1ff354..511e771e8 100644 --- a/octopusdeploy/resource_project_test.go +++ b/octopusdeploy_framework/resource_project_test.go @@ -1,20 +1,19 @@ -package octopusdeploy +package octopusdeploy_framework import ( "fmt" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/spaces" + internaltest "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/test" "github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework/octoclient" "github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework/test" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" "os" "path/filepath" "sort" "testing" - - internaltest "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/test" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) func TestAccProjectBasic(t *testing.T) { @@ -29,7 +28,7 @@ func TestAccProjectBasic(t *testing.T) { testAccProjectGroupCheckDestroy, testAccLifecycleCheckDestroy, ), - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { TestAccPreCheck(t) }, ProtoV6ProviderFactories: ProtoV6ProviderFactories(), Steps: []resource.TestStep{ { @@ -50,56 +49,33 @@ func TestAccProjectBasic(t *testing.T) { }) } -func testAccProject(localName string, name string, lifecycleLocalName string, projectGroupLocalName string) string { - return fmt.Sprintf(`resource "octopusdeploy_project" "%s" { - lifecycle_id = octopusdeploy_lifecycle.%s.id - name = "%s" - project_group_id = octopusdeploy_project_group.%s.id - }`, localName, lifecycleLocalName, name, projectGroupLocalName) -} - -type ProjectTestOptions struct { - AllowDeploymentsToNoTargets bool - LifecycleLocalName string - LocalName string - Name string - ProjectGroupLocalName string -} +func testAccProjectGroupCheckDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "octopusdeploy_project_group" { + continue + } -func NewProjectTestOptions(projectGroupLocalName string, lifecycleLocalName string) *ProjectTestOptions { - return &ProjectTestOptions{ - LifecycleLocalName: lifecycleLocalName, - LocalName: acctest.RandStringFromCharSet(20, acctest.CharSetAlpha), - Name: acctest.RandStringFromCharSet(20, acctest.CharSetAlpha), - ProjectGroupLocalName: projectGroupLocalName, + if projectGroup, err := octoClient.ProjectGroups.GetByID(rs.Primary.ID); err == nil { + return fmt.Errorf("project group (%s) still exists", projectGroup.GetID()) + } } -} -func testAccProjectWithOptions(opt *ProjectTestOptions) string { - - return fmt.Sprintf(`resource "octopusdeploy_project" "%s" { - allow_deployments_to_no_targets = %v - lifecycle_id = octopusdeploy_lifecycle.%s.id - name = "%s" - project_group_id = octopusdeploy_project_group.%s.id - }`, opt.LocalName, opt.AllowDeploymentsToNoTargets, opt.LifecycleLocalName, opt.Name, opt.ProjectGroupLocalName) + return nil } -func testAccProjectWithTemplate(localName string, name string, lifecycleLocalName string, projectGroupLocalName string) string { - return fmt.Sprintf(`resource "octopusdeploy_project" "%s" { - lifecycle_id = octopusdeploy_lifecycle.%s.id - name = "%s" - project_group_id = octopusdeploy_project_group.%s.id - - template { - name = "project variable template name" - label = "project variable template label" +func testProjectGroupExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } - display_settings = { - "Octopus.ControlType" = "Sensitive" - } + if _, err := octoClient.ProjectGroups.GetByID(rs.Primary.ID); err != nil { + return err } - }`, localName, lifecycleLocalName, name, projectGroupLocalName) + + return nil + } } func TestAccProjectWithUpdate(t *testing.T) { @@ -118,7 +94,7 @@ func TestAccProjectWithUpdate(t *testing.T) { testAccProjectCheckDestroy, testAccLifecycleCheckDestroy, ), - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { TestAccPreCheck(t) }, ProtoV6ProviderFactories: ProtoV6ProviderFactories(), Steps: []resource.TestStep{ { @@ -158,68 +134,31 @@ func testAccProjectBasic(lifecycleLocalName string, lifecycleName string, projec project_group_id = octopusdeploy_project_group.%s.id template { - default_value = "default-value" - help_text = "help-test" - label = "label" name = "2" - display_settings = { "Octopus.ControlType": "SingleLineText" } } template { - default_value = "default-value" - help_text = "help-test" - label = "label" name = "1" - display_settings = { "Octopus.ControlType": "SingleLineText" } } - // connectivity_policy { - // allow_deployments_to_no_targets = true - // skip_machine_behavior = "None" - // } + versioning_strategy { + template = "#{Octopus.Version.LastMajor}.#{Octopus.Version.LastMinor}.#{Octopus.Version.LastPatch}.#{Octopus.Version.NextRevision}" + } - // version_control_settings { - // default_branch = "foo" - // url = "https://example.com/" - // username = "bar" - // } + connectivity_policy { + allow_deployments_to_no_targets = true + skip_machine_behavior = "None" + } - // versioning_strategy { - // template = "alskdjaslkdj" - // } }`, localName, description, lifecycleLocalName, name, projectGroupLocalName) } -func testAccProjectCaC(spaceID string, lifecycleLocalName string, lifecycleName string, projectGroupLocalName string, projectGroupName string, localName string, name string, description string, basePath string, url string, password string, username string) string { - projectGroup := internaltest.NewProjectGroupTestOptions() - - return fmt.Sprintf(testAccLifecycle(lifecycleLocalName, lifecycleName)+"\n"+ - internaltest.ProjectGroupConfiguration(projectGroup)+"\n"+ - `resource "octopusdeploy_project" "%s" { - description = "%s" - lifecycle_id = octopusdeploy_lifecycle.%s.id - name = "%s" - project_group_id = octopusdeploy_project_group.%s.id - space_id = "%s" - - git_persistence_settings { - base_path = "%s" - url = "%s" - - credentials { - password = "%s" - username = "%s" - } - } - }`, localName, description, lifecycleLocalName, name, projectGroupLocalName, spaceID, basePath, url, password, username) -} - func testAccProjectCheckDestroy(s *terraform.State) error { for _, rs := range s.RootModule().Resources { if rs.Type != "octopusdeploy_project" { diff --git a/octopusdeploy_framework/resource_tenant_common_variable_test.go b/octopusdeploy_framework/resource_tenant_common_variable_test.go index 56a68c932..60ea33e05 100644 --- a/octopusdeploy_framework/resource_tenant_common_variable_test.go +++ b/octopusdeploy_framework/resource_tenant_common_variable_test.go @@ -15,7 +15,6 @@ import ( ) func TestAccTenantCommonVariableBasic(t *testing.T) { - internalTest.SkipCI(t, "project_environment have been refactor [deprecated] - will enable this test later after Ben fix") //SkipCI(t, "A managed resource \"octopusdeploy_project_group\" \"ewtxiwplhaenzmhpaqyx\" has\n not been declared in the root module.") lifecycleLocalName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) lifecycleName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) @@ -66,47 +65,51 @@ func testAccTenantCommonVariableBasic(lifecycleLocalName string, lifecycleName s sortOrder := acctest.RandIntRange(0, 10) useGuidedFailure := false projectGroup.LocalName = projectGroupLocalName + var tfConfig = fmt.Sprintf(testAccLifecycle(lifecycleLocalName, lifecycleName)+"\n"+ internalTest.ProjectGroupConfiguration(projectGroup)+"\n"+ testAccEnvironment(environmentLocalName, environmentName, description, allowDynamicInfrastructure, sortOrder, useGuidedFailure)+"\n"+` - resource "octopusdeploy_library_variable_set" "test-library-variable-set" { - name = "test" - - template { - default_value = "Default Value???" - help_text = "This is the help text" - label = "Test Label" - name = "Test Template" - - display_settings = { - "Octopus.ControlType" = "Sensitive" - } - } - } - - resource "octopusdeploy_project" "%s" { - included_library_variable_sets = [octopusdeploy_library_variable_set.test-library-variable-set.id] - lifecycle_id = octopusdeploy_lifecycle.%s.id - name = "%s" - project_group_id = octopusdeploy_project_group.%s.id - } - - resource "octopusdeploy_tenant" "%s" { - name = "%s" - } - - resource "octopusdeploy_tenant_project" "project_environment" { - tenant_id = octopusdeploy_tenant.%s.id - project_id = octopusdeploy_project.%s.id - environment_ids = [octopusdeploy_environment.%s.id] - } - - resource "octopusdeploy_tenant_common_variable" "%s" { - library_variable_set_id = octopusdeploy_library_variable_set.test-library-variable-set.id - template_id = octopusdeploy_library_variable_set.test-library-variable-set.template[0].id - tenant_id = octopusdeploy_tenant.%s.id - value = "%s" - }`, projectLocalName, lifecycleLocalName, projectName, projectGroupLocalName, tenantLocalName, tenantName, projectLocalName, environmentLocalName, localName, tenantLocalName, value) + resource "octopusdeploy_library_variable_set" "test-library-variable-set" { + name = "test" + + template { + default_value = "Default Value???" + help_text = "This is the help text" + label = "Test Label" + name = "Test Template" + + display_settings = { + "Octopus.ControlType" = "Sensitive" + } + } + } + + resource "octopusdeploy_project" "%[1]s" { + included_library_variable_sets = [octopusdeploy_library_variable_set.test-library-variable-set.id] + lifecycle_id = octopusdeploy_lifecycle.%[2]s.id + name = "%[3]s" + project_group_id = octopusdeploy_project_group.%[4]s.id + depends_on = [octopusdeploy_library_variable_set.test-library-variable-set] + } + + resource "octopusdeploy_tenant" "%[5]s" { + name = "%[6]s" + } + + resource "octopusdeploy_tenant_project" "project_environment" { + tenant_id = octopusdeploy_tenant.%[5]s.id + project_id = octopusdeploy_project.%[1]s.id + environment_ids = [octopusdeploy_environment.%[7]s.id] + depends_on = [octopusdeploy_project.%[1]s, octopusdeploy_tenant.%[5]s, octopusdeploy_environment.%[7]s] + } + + resource "octopusdeploy_tenant_common_variable" "%[8]s" { + library_variable_set_id = octopusdeploy_library_variable_set.test-library-variable-set.id + template_id = octopusdeploy_library_variable_set.test-library-variable-set.template[0].id + tenant_id = octopusdeploy_tenant.%[5]s.id + value = "%[9]s" + depends_on = [octopusdeploy_library_variable_set.test-library-variable-set, octopusdeploy_tenant_project.project_environment] + }`, projectLocalName, lifecycleLocalName, projectName, projectGroupLocalName, tenantLocalName, tenantName, environmentLocalName, localName, value) return tfConfig } diff --git a/octopusdeploy_framework/resource_tenant_project_variable_test.go b/octopusdeploy_framework/resource_tenant_project_variable_test.go index 29def6ad9..3c915e5c2 100644 --- a/octopusdeploy_framework/resource_tenant_project_variable_test.go +++ b/octopusdeploy_framework/resource_tenant_project_variable_test.go @@ -8,12 +8,9 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" - - internalTest "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/test" ) func TestAccTenantProjectVariableBasic(t *testing.T) { - internalTest.SkipCI(t, "project_environment have been refactor [deprecated] - will enable this test later after Ben fix") lifecycleLocalName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) lifecycleName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) projectGroupLocalName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) @@ -124,7 +121,11 @@ func testTenantProjectVariable(localName string, environmentLocalName string, pr tenant_id = octopusdeploy_tenant.%s.id template_id = octopusdeploy_project.%s.template[0].id value = "%s" - }`, localName, environmentLocalName, projectLocalName, tenantLocalName, templateLocalName, value) + depends_on = [ + octopusdeploy_project.%s, + octopusdeploy_tenant_project.project_environment + ] + }`, localName, environmentLocalName, projectLocalName, tenantLocalName, templateLocalName, value, projectLocalName) } func testTenantProjectVariableExists(prefix string) resource.TestCheckFunc { diff --git a/octopusdeploy_framework/schemas/project.go b/octopusdeploy_framework/schemas/project.go new file mode 100644 index 000000000..fc4fee3b7 --- /dev/null +++ b/octopusdeploy_framework/schemas/project.go @@ -0,0 +1,379 @@ +package schemas + +import ( + "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util" + datasourceSchema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + resourceSchema "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +const ProjectResourceName = "project" +const ProjectDataSourceName = "projects" + +func GetProjectResourceSchema() resourceSchema.Schema { + return resourceSchema.Schema{ + Description: "This resource manages projects in Octopus Deploy.", + Attributes: map[string]resourceSchema.Attribute{ + "id": util.GetIdResourceSchema(), + "space_id": util.GetSpaceIdResourceSchema(ProjectResourceName), + "name": util.GetNameResourceSchema(true), + "description": util.GetDescriptionResourceSchema(ProjectResourceName), + "allow_deployments_to_no_targets": util.ResourceBool().Optional().Deprecated("This value is only valid for an associated connectivity policy and should not be specified here.").Build(), + "auto_create_release": util.ResourceBool().Optional().Computed().Build(), + "cloned_from_project_id": util.ResourceString().Optional().Description("The ID of the project this project was cloned from.").Build(), + "default_guided_failure_mode": util.ResourceString().Optional().Computed().Build(), + "default_to_skip_if_already_installed": util.ResourceBool().Optional().Computed().Build(), + "deployment_changes_template": util.ResourceString().Optional().Computed().Build(), + "discrete_channel_release": util.ResourceBool().Optional().Computed().Description("Treats releases of different channels to the same environment as a separate deployment dimension").Build(), + "is_disabled": util.ResourceBool().Optional().Computed().Build(), + "is_discrete_channel_release": util.ResourceBool().Optional().Computed().Description("Treats releases of different channels to the same environment as a separate deployment dimension").Build(), + "is_version_controlled": util.ResourceBool().Optional().Computed().Build(), + "lifecycle_id": util.ResourceString().Required().Description("The lifecycle ID associated with this project.").Build(), + "project_group_id": util.ResourceString().Required().Description("The project group ID associated with this project.").Build(), + "tenanted_deployment_participation": util.ResourceString().Optional().Computed().Description("The tenanted deployment mode of the resource. Valid account types are `Untenanted`, `TenantedOrUntenanted`, or `Tenanted`.").Build(), + "included_library_variable_sets": util.ResourceList(types.StringType).Optional().Computed().Description("The list of included library variable set IDs.").Build(), + "release_notes_template": util.ResourceString().Optional().Computed().Build(), + "slug": util.ResourceString().Optional().Computed().Description("A human-readable, unique identifier, used to identify a project.").Build(), + "deployment_process_id": util.ResourceString().Computed().Build(), + "variable_set_id": util.ResourceString().Computed().Build(), + }, + Blocks: map[string]resourceSchema.Block{ + // This is correct object that return from api for project object not a list string. + "auto_deploy_release_overrides": resourceSchema.ListNestedBlock{ + NestedObject: resourceSchema.NestedBlockObject{ + Attributes: map[string]resourceSchema.Attribute{ + "environment_id": util.ResourceString().Optional().Build(), + "release_id": util.ResourceString().Optional().Build(), + "tenant_id": util.ResourceString().Optional().Build(), + }, + }, + }, + "connectivity_policy": resourceSchema.ListNestedBlock{ + NestedObject: resourceSchema.NestedBlockObject{ + Attributes: map[string]resourceSchema.Attribute{ + "allow_deployments_to_no_targets": util.ResourceBool().Optional().Computed().Build(), + "exclude_unhealthy_targets": util.ResourceBool().Optional().Computed().Build(), + "skip_machine_behavior": util.ResourceString().Optional().Build(), + "target_roles": util.ResourceList(types.StringType).Optional().Computed().Build(), + }, + }, + }, + "git_anonymous_persistence_settings": resourceSchema.ListNestedBlock{ + NestedObject: resourceSchema.NestedBlockObject{ + Attributes: map[string]resourceSchema.Attribute{ + "url": util.ResourceString().Required().Description("The URL associated with these version control settings.").Build(), + "base_path": util.ResourceString().Optional().Description("The base path associated with these version control settings.").Build(), + "default_branch": util.ResourceString().Optional().Description("The default branch associated with these version control settings.").Build(), + "protected_branches": util.ResourceSet(types.StringType).Optional().Description("A list of protected branch patterns.").Build(), + }, + }, + Description: "Provides Git-related persistence settings for a version-controlled project.", + }, + "git_library_persistence_settings": resourceSchema.ListNestedBlock{ + NestedObject: resourceSchema.NestedBlockObject{ + Attributes: map[string]resourceSchema.Attribute{ + "git_credential_id": util.ResourceString().Required().Build(), + "url": util.ResourceString().Required().Description("The URL associated with these version control settings.").Build(), + "base_path": util.ResourceString().Optional().Description("The base path associated with these version control settings.").Build(), + "default_branch": util.ResourceString().Optional().Description("The default branch associated with these version control settings.").Build(), + "protected_branches": util.ResourceSet(types.StringType).Optional().Description("A list of protected branch patterns.").Build(), + }, + }, + Description: "Provides Git-related persistence settings for a version-controlled project.", + }, + "git_username_password_persistence_settings": resourceSchema.ListNestedBlock{ + NestedObject: resourceSchema.NestedBlockObject{ + Attributes: map[string]resourceSchema.Attribute{ + "url": util.ResourceString().Required().Description("The URL associated with these version control settings.").Build(), + "username": util.ResourceString().Required().Description("The username for the Git credential.").Build(), + "password": util.ResourceString().Sensitive().Required().Description("The password for the Git credential").Build(), //util.GetPasswordResourceSchema(false), + "base_path": util.ResourceString().Optional().Description("The base path associated with these version control settings.").Build(), + "default_branch": util.ResourceString().Optional().Description("The default branch associated with these version control settings.").Build(), + "protected_branches": util.ResourceSet(types.StringType).Optional().Description("A list of protected branch patterns.").Build(), + }, + }, + Description: "Provides Git-related persistence settings for a version-controlled project.", + }, + "jira_service_management_extension_settings": resourceSchema.ListNestedBlock{ + NestedObject: resourceSchema.NestedBlockObject{ + Attributes: map[string]resourceSchema.Attribute{ + "connection_id": util.ResourceString().Required().Description("The connection identifier associated with the extension settings.").Build(), + "is_enabled": util.ResourceBool().Required().Description("Specifies whether or not this extension is enabled for this project.").Build(), + "service_desk_project_name": util.ResourceString().Required().Description("The project name associated with this extension.").Build(), + }, + }, + Description: "Provides extension settings for the Jira Service Management (JSM) integration for this project.", + }, + "servicenow_extension_settings": resourceSchema.ListNestedBlock{ + NestedObject: resourceSchema.NestedBlockObject{ + Attributes: map[string]resourceSchema.Attribute{ + "connection_id": util.ResourceString().Required().Description("The connection identifier associated with the extension settings.").Build(), + "is_enabled": util.ResourceBool().Required().Description("Specifies whether or not this extension is enabled for this project.").Build(), + "is_state_automatically_transitioned": util.ResourceBool().Required().Description("Specifies whether or not this extension will automatically transition the state of a deployment for this project.").Build(), + "standard_change_template_name": util.ResourceString().Optional().Description("The name of the standard change template associated with this extension. If provided, deployments will create a standard change based on the provided template, otherwise a normal change will be created.").Build(), + }, + }, + Description: "Provides extension settings for the ServiceNow integration for this project.", + }, + "template": resourceSchema.ListNestedBlock{ + NestedObject: resourceSchema.NestedBlockObject{ + Attributes: map[string]resourceSchema.Attribute{ + "id": util.ResourceString().Optional().Computed().Description("The ID of the template parameter.").Build(), + "name": util.ResourceString().Required().Description("The name of the variable set by the parameter. The name can contain letters, digits, dashes and periods.").Build(), + "label": util.ResourceString().Optional().Description("The label shown beside the parameter when presented in the deployment process.").Build(), + "help_text": util.ResourceString().Optional().Description("The help presented alongside the parameter input.").Build(), + "default_value": util.ResourceString().Optional().Description("A default value for the parameter, if applicable. This can be a hard-coded value or a variable reference.").Build(), + "display_settings": resourceSchema.MapAttribute{ + Description: "The display settings for the parameter.", + ElementType: types.StringType, + Optional: true, + }, + }, + }, + }, + "versioning_strategy": resourceSchema.ListNestedBlock{ + NestedObject: resourceSchema.NestedBlockObject{ + Attributes: map[string]resourceSchema.Attribute{ + "donor_package_step_id": util.ResourceString().Optional().Build(), + "template": util.ResourceString().Optional().Build(), + }, + Blocks: map[string]resourceSchema.Block{ + "donor_package": resourceSchema.ListNestedBlock{ + NestedObject: resourceSchema.NestedBlockObject{ + Attributes: map[string]resourceSchema.Attribute{ + "deployment_action": util.ResourceString().Optional().Build(), + "package_reference": util.ResourceString().Optional().Build(), + }, + }, + }, + }, + }, + }, + "release_creation_strategy": resourceSchema.ListNestedBlock{ + NestedObject: resourceSchema.NestedBlockObject{ + Attributes: map[string]resourceSchema.Attribute{ + "channel_id": util.ResourceString().Optional().Build(), + "release_creation_package_step_id": util.ResourceString().Optional().Build(), + }, + Blocks: map[string]resourceSchema.Block{ + "release_creation_package": resourceSchema.ListNestedBlock{ + NestedObject: resourceSchema.NestedBlockObject{ + Attributes: map[string]resourceSchema.Attribute{ + "deployment_action": util.ResourceString().Optional().Build(), + "package_reference": util.ResourceString().Optional().Build(), + }, + }, + }, + }, + }, + }, + }, + } +} + +func GetProjectDataSourceSchema() datasourceSchema.Schema { + return datasourceSchema.Schema{ + Description: "Provides information about existing Octopus Deploy projects.", + Attributes: map[string]datasourceSchema.Attribute{ + "id": util.ResourceString().Computed().Description("An auto-generated identifier that includes the timestamp when this data source was last modified.").Build(), + "cloned_from_project_id": util.DataSourceString().Optional().Description("A filter to search for cloned resources by a project ID.").Build(), + "ids": util.GetQueryIDsDatasourceSchema(), + "is_clone": util.DataSourceBool().Optional().Description("A filter to search for cloned resources.").Build(), + "name": util.DataSourceString().Optional().Description("A filter to search by name").Build(), + "partial_name": util.GetQueryPartialNameDatasourceSchema(), + "skip": util.GetQuerySkipDatasourceSchema(), + "space_id": util.ResourceString().Optional().Description("A Space ID to filter by. Will revert what is specified on the provider if not set").Build(), + "take": util.GetQueryTakeDatasourceSchema(), + "projects": getProjectsDataSourceAttribute(), + }, + } +} + +func getProjectsDataSourceAttribute() datasourceSchema.ListNestedAttribute { + return datasourceSchema.ListNestedAttribute{ + Description: "A list of projects that match the filter(s).", + Computed: true, + NestedObject: datasourceSchema.NestedAttributeObject{ + Attributes: map[string]datasourceSchema.Attribute{ + "allow_deployments_to_no_targets": util.DataSourceBool().Computed().Deprecated("Allow deployments to be created when there are no targets.").Build(), + "auto_create_release": util.DataSourceBool().Computed().Build(), + "auto_deploy_release_overrides": getAutoDeployReleaseOverrides(), + "cloned_from_project_id": util.DataSourceString().Computed().Build(), + "default_guided_failure_mode": util.DataSourceString().Computed().Build(), + "default_to_skip_if_already_installed": util.DataSourceBool().Computed().Build(), + "deployment_changes_template": util.DataSourceString().Computed().Build(), + "deployment_process_id": util.DataSourceString().Computed().Build(), + "description": util.DataSourceString().Computed().Description("The description of this project").Build(), + "discrete_channel_release": util.DataSourceBool().Computed().Description("Treats releases of different channels to the same environment as a separate deployment dimension").Build(), + "id": util.DataSourceString().Computed().Build(), + "included_library_variable_sets": util.DataSourceList(types.StringType).Computed().Build(), + "is_disabled": util.DataSourceBool().Computed().Build(), + "is_discrete_channel_release": util.DataSourceBool().Computed().Build(), + "is_version_controlled": util.DataSourceBool().Computed().Build(), + "lifecycle_id": util.DataSourceString().Computed().Description("The lifecycle ID associated with this project").Build(), + "name": util.DataSourceString().Computed().Description("The name of the project in Octopus Deploy. This name must be unique.").Build(), + "project_group_id": util.DataSourceString().Computed().Description("The project group ID associated with this project.").Build(), + "release_notes_template": util.DataSourceString().Computed().Description("The template to use for release notes.").Build(), + "slug": util.DataSourceString().Computed().Description("A human-readable, unique identifier, used to identify a project.").Build(), + "space_id": util.DataSourceString().Computed().Description("The space ID associated with this project.").Build(), + "tenanted_deployment_participation": util.DataSourceString().Computed().Description("The tenanted deployment mode of the project.").Build(), + "variable_set_id": util.DataSourceString().Computed().Description("The ID of the variable set associated with this project.").Build(), + "connectivity_policy": getDataSourceConnectivityPolicyAttribute(), + "git_library_persistence_settings": getDataSourceGitPersistenceSettingsAttribute("library"), + "git_username_password_persistence_settings": getDataSourceGitPersistenceSettingsAttribute("username_password"), + "git_anonymous_persistence_settings": getDataSourceGitPersistenceSettingsAttribute("anonymous"), + "jira_service_management_extension_settings": getDataSourceJSMExtensionSettingsAttribute(), + "servicenow_extension_settings": getDataSourceServiceNowExtensionSettingsAttribute(), + "versioning_strategy": getDataSourceVersioningStrategyAttribute(), + "release_creation_strategy": getDataSourceReleaseCreationStrategyAttribute(), + "template": getDataSourceTemplateAttribute(), + }, + }, + } +} + +// This is correct object that return from api for project object not a list string. +func getAutoDeployReleaseOverrides() datasourceSchema.ListNestedAttribute { + return datasourceSchema.ListNestedAttribute{ + Computed: true, + NestedObject: datasourceSchema.NestedAttributeObject{ + Attributes: map[string]datasourceSchema.Attribute{ + "environment_id": util.DataSourceString().Computed().Description("The environment ID for the auto deploy release override.").Build(), + "release_id": util.DataSourceString().Computed().Description("The release ID for the auto deploy release override.").Build(), + "tenant_id": util.DataSourceString().Computed().Description("The tenant ID for the auto deploy release override.").Build(), + }, + }, + } +} + +func getDataSourceConnectivityPolicyAttribute() datasourceSchema.ListNestedAttribute { + return datasourceSchema.ListNestedAttribute{ + Computed: true, + NestedObject: datasourceSchema.NestedAttributeObject{ + Attributes: map[string]datasourceSchema.Attribute{ + "allow_deployments_to_no_targets": util.DataSourceBool().Computed().Description("Allow deployments to be created when there are no targets.").Build(), + "exclude_unhealthy_targets": util.DataSourceBool().Computed().Description("Exclude unhealthy targets from deployments.").Build(), + "skip_machine_behavior": util.DataSourceString().Computed().Description("The behavior when a machine is skipped.").Build(), + "target_roles": util.DataSourceList(types.StringType).Computed().Description("The target roles for the connectivity policy.").Build(), + }, + }, + } +} + +func getDataSourceGitPersistenceSettingsAttribute(settingType string) datasourceSchema.ListNestedAttribute { + attributes := map[string]datasourceSchema.Attribute{ + "base_path": util.DataSourceString().Computed().Description("The base path associated with these version control settings.").Build(), + "default_branch": util.DataSourceString().Computed().Description("The default branch associated with these version control settings.").Build(), + "protected_branches": util.DataSourceSet(types.StringType).Computed().Description("A list of protected branch patterns.").Build(), + "url": util.DataSourceString().Computed().Description("The URL associated with these version control settings.").Build(), + } + + switch settingType { + case "library": + attributes["git_credential_id"] = util.DataSourceString().Computed().Description("The ID of the Git credential.").Build() + case "username_password": + attributes["username"] = util.DataSourceString().Computed().Description("The username for the Git credential.").Build() + attributes["password"] = util.DataSourceString().Computed().Sensitive().Description("The password for the Git credential.").Build() + case "anonymous": + // No additional attributes for anonymous + } + + return datasourceSchema.ListNestedAttribute{ + Description: "Git-related persistence settings for a version-controlled project using " + settingType + " authentication.", + Computed: true, + NestedObject: datasourceSchema.NestedAttributeObject{ + Attributes: attributes, + }, + } +} + +func getDataSourceJSMExtensionSettingsAttribute() datasourceSchema.ListNestedAttribute { + return datasourceSchema.ListNestedAttribute{ + Description: "Extension settings for the Jira Service Management (JSM) integration.", + Computed: true, + NestedObject: datasourceSchema.NestedAttributeObject{ + Attributes: map[string]datasourceSchema.Attribute{ + "connection_id": util.DataSourceString().Computed().Description("The connection identifier for JSM.").Build(), + "is_enabled": util.DataSourceBool().Computed().Description("Whether the JSM extension is enabled.").Build(), + "service_desk_project_name": util.DataSourceString().Computed().Description("The JSM service desk project name.").Build(), + }, + }, + } +} + +func getDataSourceServiceNowExtensionSettingsAttribute() datasourceSchema.ListNestedAttribute { + return datasourceSchema.ListNestedAttribute{ + Description: "Extension settings for the ServiceNow integration.", + Computed: true, + NestedObject: datasourceSchema.NestedAttributeObject{ + Attributes: map[string]datasourceSchema.Attribute{ + "connection_id": util.DataSourceString().Computed().Description("The connection identifier for ServiceNow.").Build(), + "is_enabled": util.DataSourceBool().Computed().Description("Whether the ServiceNow extension is enabled.").Build(), + "is_state_automatically_transitioned": util.DataSourceBool().Computed().Description("Whether state is automatically transitioned in ServiceNow.").Build(), + "standard_change_template_name": util.DataSourceString().Computed().Description("The name of the standard change template in ServiceNow.").Build(), + }, + }, + } +} + +func getDataSourceVersioningStrategyAttribute() datasourceSchema.ListNestedAttribute { + return datasourceSchema.ListNestedAttribute{ + Description: "The versioning strategy for the project.", + Computed: true, + NestedObject: datasourceSchema.NestedAttributeObject{ + Attributes: map[string]datasourceSchema.Attribute{ + "donor_package_step_id": util.DataSourceString().Computed().Description("The ID of the step containing the donor package.").Build(), + "donor_package": datasourceSchema.ListNestedAttribute{ + Computed: true, + NestedObject: datasourceSchema.NestedAttributeObject{ + Attributes: map[string]datasourceSchema.Attribute{ + "deployment_action": util.DataSourceString().Computed().Description("The deployment action for the donor package.").Build(), + "package_reference": util.DataSourceString().Computed().Description("The package reference for the donor package.").Build(), + }, + }, + }, + "template": util.DataSourceString().Computed().Description("The template to use for version numbers.").Build(), + }, + }, + } +} + +func getDataSourceReleaseCreationStrategyAttribute() datasourceSchema.ListNestedAttribute { + return datasourceSchema.ListNestedAttribute{ + Description: "The release creation strategy for the project.", + Computed: true, + NestedObject: datasourceSchema.NestedAttributeObject{ + Attributes: map[string]datasourceSchema.Attribute{ + "channel_id": util.DataSourceString().Computed().Description("The ID of the channel to use for release creation.").Build(), + "release_creation_package": datasourceSchema.ListNestedAttribute{ + Description: "Details of the package used for release creation.", + Computed: true, + NestedObject: datasourceSchema.NestedAttributeObject{ + Attributes: map[string]datasourceSchema.Attribute{ + "deployment_action": util.DataSourceString().Computed().Description("The deployment action for the release creation package.").Build(), + "package_reference": util.DataSourceString().Computed().Description("The package reference for the release creation package.").Build(), + }, + }, + }, + "release_creation_package_step_id": util.DataSourceString().Computed().Description("The ID of the step containing the package for release creation.").Build(), + }, + }, + } +} + +func getDataSourceTemplateAttribute() datasourceSchema.ListNestedAttribute { + return datasourceSchema.ListNestedAttribute{ + Description: "Template parameters for the project.", + Computed: true, + NestedObject: datasourceSchema.NestedAttributeObject{ + Attributes: map[string]datasourceSchema.Attribute{ + "id": util.DataSourceString().Computed().Description("The ID of the template parameter.").Build(), + "name": util.DataSourceString().Computed().Description("The name of the variable set by the parameter.").Build(), + "label": util.DataSourceString().Computed().Description("The label shown beside the parameter.").Build(), + "help_text": util.DataSourceString().Computed().Description("The help text for the parameter.").Build(), + "default_value": util.DataSourceString().Computed().Description("The default value for the parameter.").Build(), + "display_settings": util.DataSourceMap(types.StringType).Computed().Description("The display settings for the parameter.").Build(), + }, + }, + } +} diff --git a/octopusdeploy_framework/util/datasource_attribute_builder.go b/octopusdeploy_framework/util/datasource_attribute_builder.go new file mode 100644 index 000000000..733ab13ed --- /dev/null +++ b/octopusdeploy_framework/util/datasource_attribute_builder.go @@ -0,0 +1,182 @@ +package util + +import ( + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" +) + +type DataSourceAttributeBuilder[T any] struct { + attr T +} + +func NewDataSourceAttributeBuilder[T any]() *DataSourceAttributeBuilder[T] { + return &DataSourceAttributeBuilder[T]{} +} + +func (b *DataSourceAttributeBuilder[T]) Optional() *DataSourceAttributeBuilder[T] { + switch a := any(&b.attr).(type) { + case *schema.StringAttribute: + a.Optional = true + case *schema.BoolAttribute: + a.Optional = true + case *schema.Int64Attribute: + a.Optional = true + case *schema.Float64Attribute: + a.Optional = true + case *schema.ListAttribute: + a.Optional = true + case *schema.SetAttribute: + a.Optional = true + case *schema.MapAttribute: + a.Optional = true + } + return b +} + +func (b *DataSourceAttributeBuilder[T]) Deprecated(deprecationMessage string) *DataSourceAttributeBuilder[T] { + switch a := any(&b.attr).(type) { + case *schema.StringAttribute: + a.DeprecationMessage = deprecationMessage + case *schema.BoolAttribute: + a.DeprecationMessage = deprecationMessage + case *schema.Int64Attribute: + a.DeprecationMessage = deprecationMessage + case *schema.Float64Attribute: + a.DeprecationMessage = deprecationMessage + case *schema.NumberAttribute: + a.DeprecationMessage = deprecationMessage + case *schema.ListAttribute: + a.DeprecationMessage = deprecationMessage + case *schema.SetAttribute: + a.DeprecationMessage = deprecationMessage + case *schema.MapAttribute: + a.DeprecationMessage = deprecationMessage + case *schema.ObjectAttribute: + a.DeprecationMessage = deprecationMessage + } + return b +} + +func (b *DataSourceAttributeBuilder[T]) Required() *DataSourceAttributeBuilder[T] { + switch a := any(&b.attr).(type) { + case *schema.StringAttribute: + a.Required = true + case *schema.BoolAttribute: + a.Required = true + case *schema.Int64Attribute: + a.Required = true + case *schema.Float64Attribute: + a.Required = true + case *schema.ListAttribute: + a.Required = true + case *schema.SetAttribute: + a.Required = true + case *schema.MapAttribute: + a.Required = true + } + return b +} + +func (b *DataSourceAttributeBuilder[T]) Computed() *DataSourceAttributeBuilder[T] { + switch a := any(&b.attr).(type) { + case *schema.StringAttribute: + a.Computed = true + case *schema.BoolAttribute: + a.Computed = true + case *schema.Int64Attribute: + a.Computed = true + case *schema.Float64Attribute: + a.Computed = true + case *schema.ListAttribute: + a.Computed = true + case *schema.SetAttribute: + a.Computed = true + case *schema.MapAttribute: + a.Computed = true + } + return b +} + +func (b *DataSourceAttributeBuilder[T]) Description(desc string) *DataSourceAttributeBuilder[T] { + switch a := any(&b.attr).(type) { + case *schema.StringAttribute: + a.Description = desc + case *schema.BoolAttribute: + a.Description = desc + case *schema.Int64Attribute: + a.Description = desc + case *schema.Float64Attribute: + a.Description = desc + case *schema.ListAttribute: + a.Description = desc + case *schema.SetAttribute: + a.Description = desc + case *schema.MapAttribute: + a.Description = desc + } + return b +} + +func (b *DataSourceAttributeBuilder[T]) Sensitive() *DataSourceAttributeBuilder[T] { + switch a := any(&b.attr).(type) { + case *schema.StringAttribute: + a.Sensitive = true + case *schema.BoolAttribute: + a.Sensitive = true + case *schema.Int64Attribute: + a.Sensitive = true + case *schema.Float64Attribute: + a.Sensitive = true + case *schema.ListAttribute: + a.Sensitive = true + case *schema.SetAttribute: + a.Sensitive = true + case *schema.MapAttribute: + a.Sensitive = true + } + return b +} + +func (b *DataSourceAttributeBuilder[T]) ElementType(elementType attr.Type) *DataSourceAttributeBuilder[T] { + switch a := any(&b.attr).(type) { + case *schema.ListAttribute: + a.ElementType = elementType + case *schema.SetAttribute: + a.ElementType = elementType + case *schema.MapAttribute: + a.ElementType = elementType + } + return b +} + +func (b *DataSourceAttributeBuilder[T]) Build() T { + return b.attr +} + +func DataSourceString() *DataSourceAttributeBuilder[schema.StringAttribute] { + return NewDataSourceAttributeBuilder[schema.StringAttribute]() +} + +func DataSourceBool() *DataSourceAttributeBuilder[schema.BoolAttribute] { + return NewDataSourceAttributeBuilder[schema.BoolAttribute]() +} + +func DataSourceInt64() *DataSourceAttributeBuilder[schema.Int64Attribute] { + return NewDataSourceAttributeBuilder[schema.Int64Attribute]() +} + +func DataSourceFloat64() *DataSourceAttributeBuilder[schema.Float64Attribute] { + return NewDataSourceAttributeBuilder[schema.Float64Attribute]() +} + +func DataSourceList(elementType attr.Type) *DataSourceAttributeBuilder[schema.ListAttribute] { + return NewDataSourceAttributeBuilder[schema.ListAttribute]().ElementType(elementType) +} + +func DataSourceSet(elementType attr.Type) *DataSourceAttributeBuilder[schema.SetAttribute] { + return NewDataSourceAttributeBuilder[schema.SetAttribute]().ElementType(elementType) +} + +func DataSourceMap(elementType attr.Type) *DataSourceAttributeBuilder[schema.MapAttribute] { + return NewDataSourceAttributeBuilder[schema.MapAttribute]().ElementType(elementType) +} diff --git a/octopusdeploy_framework/util/resource_attribute_builder.go b/octopusdeploy_framework/util/resource_attribute_builder.go new file mode 100644 index 000000000..153cafbf7 --- /dev/null +++ b/octopusdeploy_framework/util/resource_attribute_builder.go @@ -0,0 +1,253 @@ +package util + +import ( + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/float64default" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/mapdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type AttributeBuilder[T any] struct { + attr T +} + +func NewAttributeBuilder[T any]() *AttributeBuilder[T] { + return &AttributeBuilder[T]{} +} + +func (b *AttributeBuilder[T]) Optional() *AttributeBuilder[T] { + switch a := any(&b.attr).(type) { + case *schema.StringAttribute: + a.Optional = true + case *schema.BoolAttribute: + a.Optional = true + case *schema.Int64Attribute: + a.Optional = true + case *schema.Float64Attribute: + a.Optional = true + case *schema.NumberAttribute: + a.Optional = true + case *schema.ListAttribute: + a.Optional = true + case *schema.SetAttribute: + a.Optional = true + case *schema.MapAttribute: + a.Optional = true + case *schema.ObjectAttribute: + a.Optional = true + } + return b +} + +func (b *AttributeBuilder[T]) Deprecated(deprecationMessage string) *AttributeBuilder[T] { + switch a := any(&b.attr).(type) { + case *schema.StringAttribute: + a.DeprecationMessage = deprecationMessage + case *schema.BoolAttribute: + a.DeprecationMessage = deprecationMessage + case *schema.Int64Attribute: + a.DeprecationMessage = deprecationMessage + case *schema.Float64Attribute: + a.DeprecationMessage = deprecationMessage + case *schema.NumberAttribute: + a.DeprecationMessage = deprecationMessage + case *schema.ListAttribute: + a.DeprecationMessage = deprecationMessage + case *schema.SetAttribute: + a.DeprecationMessage = deprecationMessage + case *schema.MapAttribute: + a.DeprecationMessage = deprecationMessage + case *schema.ObjectAttribute: + a.DeprecationMessage = deprecationMessage + } + return b +} + +func (b *AttributeBuilder[T]) Computed() *AttributeBuilder[T] { + switch a := any(&b.attr).(type) { + case *schema.StringAttribute: + a.Computed = true + case *schema.BoolAttribute: + a.Computed = true + case *schema.Int64Attribute: + a.Computed = true + case *schema.Float64Attribute: + a.Computed = true + case *schema.NumberAttribute: + a.Computed = true + case *schema.ListAttribute: + a.Computed = true + case *schema.SetAttribute: + a.Computed = true + case *schema.MapAttribute: + a.Computed = true + case *schema.ObjectAttribute: + a.Computed = true + } + return b +} + +func (b *AttributeBuilder[T]) Required() *AttributeBuilder[T] { + switch a := any(&b.attr).(type) { + case *schema.StringAttribute: + a.Required = true + case *schema.BoolAttribute: + a.Required = true + case *schema.Int64Attribute: + a.Required = true + case *schema.Float64Attribute: + a.Required = true + case *schema.NumberAttribute: + a.Required = true + case *schema.ListAttribute: + a.Required = true + case *schema.SetAttribute: + a.Required = true + case *schema.MapAttribute: + a.Required = true + case *schema.ObjectAttribute: + a.Required = true + } + return b +} + +func (b *AttributeBuilder[T]) Description(desc string) *AttributeBuilder[T] { + switch a := any(&b.attr).(type) { + case *schema.StringAttribute: + a.Description = desc + case *schema.BoolAttribute: + a.Description = desc + case *schema.Int64Attribute: + a.Description = desc + case *schema.Float64Attribute: + a.Description = desc + case *schema.NumberAttribute: + a.Description = desc + case *schema.ListAttribute: + a.Description = desc + case *schema.SetAttribute: + a.Description = desc + case *schema.MapAttribute: + a.Description = desc + case *schema.ObjectAttribute: + a.Description = desc + } + return b +} + +func (b *AttributeBuilder[T]) Default(defaultValue interface{}) *AttributeBuilder[T] { + switch a := any(&b.attr).(type) { + case *schema.StringAttribute: + if strDefault, ok := defaultValue.(string); ok { + a.Default = stringdefault.StaticString(strDefault) + } + case *schema.BoolAttribute: + if boolDefault, ok := defaultValue.(bool); ok { + a.Default = booldefault.StaticBool(boolDefault) + } + case *schema.Int64Attribute: + if intDefault, ok := defaultValue.(int64); ok { + a.Default = int64default.StaticInt64(intDefault) + } + case *schema.NumberAttribute: + case *schema.Float64Attribute: + if floatDefault, ok := defaultValue.(float64); ok { + a.Default = float64default.StaticFloat64(floatDefault) + } + case *schema.ListAttribute: + a.Default = listdefault.StaticValue(types.List{}) + case *schema.SetAttribute: + a.Default = setdefault.StaticValue(types.Set{}) + case *schema.MapAttribute: + a.Default = mapdefault.StaticValue(types.Map{}) + } + return b +} + +func (b *AttributeBuilder[T]) Sensitive() *AttributeBuilder[T] { + switch a := any(&b.attr).(type) { + case *schema.StringAttribute: + a.Sensitive = true + case *schema.BoolAttribute: + a.Sensitive = true + case *schema.Int64Attribute: + a.Sensitive = true + case *schema.Float64Attribute: + a.Sensitive = true + case *schema.NumberAttribute: + a.Sensitive = true + case *schema.ListAttribute: + a.Sensitive = true + case *schema.SetAttribute: + a.Sensitive = true + case *schema.MapAttribute: + a.Sensitive = true + case *schema.ObjectAttribute: + a.Sensitive = true + } + return b +} + +func (b *AttributeBuilder[T]) ElementType(elementType attr.Type) *AttributeBuilder[T] { + switch a := any(&b.attr).(type) { + case *schema.ListAttribute: + a.ElementType = elementType + case *schema.SetAttribute: + a.ElementType = elementType + case *schema.MapAttribute: + a.ElementType = elementType + } + return b +} + +func (b *AttributeBuilder[T]) AttributeTypes(attributeTypes map[string]attr.Type) *AttributeBuilder[T] { + if a, ok := any(&b.attr).(*schema.ObjectAttribute); ok { + a.AttributeTypes = attributeTypes + } + return b +} + +func (b *AttributeBuilder[T]) Build() T { + return b.attr +} + +func ResourceString() *AttributeBuilder[schema.StringAttribute] { + return NewAttributeBuilder[schema.StringAttribute]() +} + +func ResourceBool() *AttributeBuilder[schema.BoolAttribute] { + return NewAttributeBuilder[schema.BoolAttribute]() +} + +func ResourceInt64() *AttributeBuilder[schema.Int64Attribute] { + return NewAttributeBuilder[schema.Int64Attribute]() +} + +func ResourceFloat64() *AttributeBuilder[schema.Float64Attribute] { + return NewAttributeBuilder[schema.Float64Attribute]() +} + +func ResourceNumber() *AttributeBuilder[schema.NumberAttribute] { + return NewAttributeBuilder[schema.NumberAttribute]() +} + +func ResourceList(elementType attr.Type) *AttributeBuilder[schema.ListAttribute] { + return NewAttributeBuilder[schema.ListAttribute]().ElementType(elementType) +} + +func ResourceSet(elementType attr.Type) *AttributeBuilder[schema.SetAttribute] { + return NewAttributeBuilder[schema.SetAttribute]().ElementType(elementType) +} +func ResourceMap(elementType attr.Type) *AttributeBuilder[schema.MapAttribute] { + return NewAttributeBuilder[schema.MapAttribute]().ElementType(elementType) +} + +func ResourceObject(attributeTypes map[string]attr.Type) *AttributeBuilder[schema.ObjectAttribute] { + return NewAttributeBuilder[schema.ObjectAttribute]().AttributeTypes(attributeTypes) +} diff --git a/octopusdeploy_framework/util/schema.go b/octopusdeploy_framework/util/schema.go index a4ed08ee2..433892367 100644 --- a/octopusdeploy_framework/util/schema.go +++ b/octopusdeploy_framework/util/schema.go @@ -307,6 +307,7 @@ func GetPackageAcquisitionLocationOptionsResourceSchema() resourceSchema.Attribu }, } } + func GetFeedUriResourceSchema() resourceSchema.Attribute { return resourceSchema.StringAttribute{ Required: true, diff --git a/octopusdeploy_framework/util/util.go b/octopusdeploy_framework/util/util.go index 3aa77c459..0f167bf80 100644 --- a/octopusdeploy_framework/util/util.go +++ b/octopusdeploy_framework/util/util.go @@ -85,6 +85,13 @@ func ToValueSlice(slice []string) []attr.Value { return values } +func StringOrNull(s string) types.String { + if s == "" { + return types.StringNull() + } + return types.StringValue(s) +} + func Map[T, V any](items []T, fn func(T) V) []V { result := make([]V, len(items)) for i, t := range items { diff --git a/terraform/19b-projectspace/project.tf b/terraform/19b-projectspace/project.tf index f8104c064..92bb03da7 100644 --- a/terraform/19b-projectspace/project.tf +++ b/terraform/19b-projectspace/project.tf @@ -1,8 +1,10 @@ data "octopusdeploy_lifecycles" "lifecycle_default_lifecycle" { ids = null partial_name = "Default Lifecycle" + space_id = octopusdeploy_space.octopus_project_space_test.id skip = 0 take = 1 + depends_on = [octopusdeploy_space.octopus_project_space_test] } @@ -30,4 +32,9 @@ resource "octopusdeploy_project" "deploy_frontend_project" { exclude_unhealthy_targets = false skip_machine_behavior = "SkipUnavailableMachines" } + + depends_on = [ + octopusdeploy_space.octopus_project_space_test, + octopusdeploy_project_group.project_group_test, + ] } \ No newline at end of file diff --git a/terraform/39-projectgitusername/project.tf b/terraform/39-projectgitusername/project.tf index 44f9cb991..822ca1d1a 100644 --- a/terraform/39-projectgitusername/project.tf +++ b/terraform/39-projectgitusername/project.tf @@ -14,7 +14,7 @@ resource "octopusdeploy_project" "deploy_frontend_project" { discrete_channel_release = false is_disabled = false is_discrete_channel_release = false - is_version_controlled = false + is_version_controlled = true // use git_username_password_persistence_settings so this need to set to true lifecycle_id = data.octopusdeploy_lifecycles.lifecycle_default_lifecycle.lifecycles[0].id name = "Test" project_group_id = octopusdeploy_project_group.project_group_test.id