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