From cb9c42156a4759a0f3f9a5e9f41cc3761155b9bd Mon Sep 17 00:00:00 2001
From: Huy Nguyen <162080607+HuyPhanNguyen@users.noreply.github.com>
Date: Mon, 5 Aug 2024 18:04:56 +1000
Subject: [PATCH 01/28] chore!: migrate project resource & datasource (#689)
* Initial project resource
* Fix old test
* update schema
* First test pass
* implement flatten GitPersistenceSetting
* Fix issue expand template
* reverse test
* Implement GitLibraryPersistenceSettings expand
* Just check old provider
* Add project data source
* refactor project schema
* fix datasource project build faile
* more refactor
* Fix issue helptext null
* Fix datasource name
* Handle skipMachineBehavior
* Fix bugs
* more bugs fix
* Fix data source issue
* fix GitCredentialTypeUsernamePassword test issue
* Fix git setting again
* Fix versioning strategy not return by API
* fix bugs persistenceSettings versioningStrategy connectivityPolicy not return from API
* fix incorrect test config
* Cleanup
* Get deployment settings from api server
* Fix stage issue
* refactor to use attribute builder
* Update project resource schema
* Update schema builder
* Fix project datasource schema
* Fix list test fail
* Update tf configuration test script
* Refactor
* Remove SkipCI for tenant variable test
* refactor
* Fix comment
* switch back base_path and default branch to optional
* Fix other bug for ReleaseNotesTemplate
---
docs/data-sources/projects.md | 146 ++--
docs/resources/project.md | 46 +-
docs/resources/project_group.md | 4 +-
octopusdeploy/data_source_projects.go | 52 --
octopusdeploy/provider.go | 2 -
octopusdeploy/resource_channel_test.go | 97 +++
.../resource_deployment_process_test.go | 14 +
octopusdeploy/resource_project.go | 156 ----
octopusdeploy/resource_tenant_test.go | 2 -
octopusdeploy/schema_project.go | 705 ------------------
octopusdeploy_framework/datasource_project.go | 95 +++
octopusdeploy_framework/framework_provider.go | 2 +
octopusdeploy_framework/resource_project.go | 220 ++++++
.../resource_project_expand.go | 339 +++++++++
.../resource_project_flatten.go | 427 +++++++++++
.../resource_project_model.go | 114 +++
.../resource_project_test.go | 129 +---
.../resource_tenant_common_variable_test.go | 81 +-
.../resource_tenant_project_variable_test.go | 9 +-
octopusdeploy_framework/schemas/project.go | 379 ++++++++++
.../util/datasource_attribute_builder.go | 182 +++++
.../util/resource_attribute_builder.go | 253 +++++++
octopusdeploy_framework/util/schema.go | 1 +
octopusdeploy_framework/util/util.go | 7 +
terraform/19b-projectspace/project.tf | 7 +
terraform/39-projectgitusername/project.tf | 2 +-
26 files changed, 2327 insertions(+), 1144 deletions(-)
delete mode 100644 octopusdeploy/data_source_projects.go
delete mode 100644 octopusdeploy/resource_project.go
delete mode 100644 octopusdeploy/schema_project.go
create mode 100644 octopusdeploy_framework/datasource_project.go
create mode 100644 octopusdeploy_framework/resource_project.go
create mode 100644 octopusdeploy_framework/resource_project_expand.go
create mode 100644 octopusdeploy_framework/resource_project_flatten.go
create mode 100644 octopusdeploy_framework/resource_project_model.go
rename {octopusdeploy => octopusdeploy_framework}/resource_project_test.go (83%)
create mode 100644 octopusdeploy_framework/schemas/project.go
create mode 100644 octopusdeploy_framework/util/datasource_attribute_builder.go
create mode 100644 octopusdeploy_framework/util/resource_attribute_builder.go
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
From fdcf66c8ed14ec4394d8889c1b820caa89648fe8 Mon Sep 17 00:00:00 2001
From: Isaac Calligeros <101079287+IsaacCalligeros95@users.noreply.github.com>
Date: Wed, 7 Aug 2024 14:34:53 +0930
Subject: [PATCH 02/28] Chore!: Migrate docker container registry (#702)
* Migrate docker container registry
* Tidy up
* Rename APIVersion
* Fix descriptions
---
docs/data-sources/feeds.md | 35 ++--
docs/data-sources/library_variable_sets.md | 4 +-
docs/resources/artifactory_generic_feed.md | 4 +-
.../aws_elastic_container_registry.md | 6 +-
docs/resources/docker_container_registry.md | 6 +-
docs/resources/github_repository_feed.md | 4 +-
docs/resources/helm_feed.md | 8 +-
docs/resources/library_variable_set.md | 4 +-
docs/resources/maven_feed.md | 4 +-
docs/resources/nuget_feed.md | 6 +-
octopusdeploy/provider.go | 1 -
.../resource_docker_container_registry.go | 104 ----------
.../schema_docker_container_registry.go | 104 ----------
octopusdeploy_framework/framework_provider.go | 3 +-
.../resource_artifactory_generic_feed.go | 3 +-
...resource_aws_elastic_container_registry.go | 3 +-
.../resource_docker_container_registry.go | 183 ++++++++++++++++++
...resource_docker_container_registry_test.go | 11 +-
.../resource_github_repository_feed.go | 3 +-
octopusdeploy_framework/resource_helm_feed.go | 3 +-
.../resource_maven_feed.go | 3 +-
.../resource_nuget_feed.go | 3 +-
.../schemas/docker_container_registry_feed.go | 39 ++++
.../schemas/library_variable_set.go | 4 +-
24 files changed, 286 insertions(+), 262 deletions(-)
delete mode 100644 octopusdeploy/resource_docker_container_registry.go
delete mode 100644 octopusdeploy/schema_docker_container_registry.go
create mode 100644 octopusdeploy_framework/resource_docker_container_registry.go
rename {octopusdeploy => octopusdeploy_framework}/resource_docker_container_registry_test.go (94%)
create mode 100644 octopusdeploy_framework/schemas/docker_container_registry_feed.go
diff --git a/docs/data-sources/feeds.md b/docs/data-sources/feeds.md
index 623ec5593..91fde3c96 100644
--- a/docs/data-sources/feeds.md
+++ b/docs/data-sources/feeds.md
@@ -27,37 +27,40 @@ data "octopusdeploy_feeds" "example" {
### Optional
- `feed_type` (String) A filter to search by feed type. Valid feed types are `AwsElasticContainerRegistry`, `BuiltIn`, `Docker`, `GitHub`, `Helm`, `Maven`, `NuGet`, or `OctopusProject`.
+- `feeds` (Block List) (see [below for nested schema](#nestedblock--feeds))
+- `id` (String) The unique ID for this resource.
- `ids` (List of String) A filter to search by a list of IDs.
-- `name` (String) A filter to search by name.
-- `partial_name` (String) A filter to search by the partial match of a name.
+- `name` (String) The name of this resource.
+- `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) The space ID associated with this resource.
+- `space_id` (String) The space ID associated with this feeds.
- `take` (Number) A filter to specify the number of items to take (or return) in the response.
-### Read-Only
-
-- `feeds` (Block List) A list of feeds that match the filter(s). (see [below for nested schema](#nestedblock--feeds))
-- `id` (String) The ID of this resource.
-
### Nested Schema for `feeds`
-Read-Only:
+Required:
+
+- `access_key` (String) The AWS access key to use when authenticating against Amazon Web Services.
+- `feed_uri` (String)
+- `name` (String) The name of this resource.
+
+Optional:
-- `access_key` (String)
- `api_version` (String)
- `delete_unreleased_packages_after_days` (Number)
- `download_attempts` (Number) The number of times a deployment should attempt to download a package from this feed before failing.
- `download_retry_backoff_seconds` (Number) The number of seconds to apply as a linear back off between download attempts.
-- `feed_type` (String)
-- `feed_uri` (String)
+- `feed_type` (String) A filter to search by feed type. Valid feed types are `AwsElasticContainerRegistry`, `BuiltIn`, `Docker`, `GitHub`, `Helm`, `Maven`, `NuGet`, or `OctopusProject`.
- `id` (String) The unique ID for this resource.
- `is_enhanced_mode` (Boolean)
-- `name` (String) A short, memorable, unique name for this feed. Example: ACME Builds.
- `package_acquisition_location_options` (List of String)
- `password` (String, Sensitive) The password associated with this resource.
-- `region` (String)
- `registry_path` (String)
- `secret_key` (String, Sensitive)
-- `space_id` (String) The space ID associated with this resource.
-- `username` (String, Sensitive) The username associated with this resource.
\ No newline at end of file
+- `space_id` (String) The space ID associated with this feeds.
+- `username` (String, Sensitive) The username associated with this resource.
+
+Read-Only:
+
+- `region` (String)
\ No newline at end of file
diff --git a/docs/data-sources/library_variable_sets.md b/docs/data-sources/library_variable_sets.md
index da636ab68..8b6eeb19f 100644
--- a/docs/data-sources/library_variable_sets.md
+++ b/docs/data-sources/library_variable_sets.md
@@ -3,12 +3,12 @@
page_title: "octopusdeploy_library_variable_sets Data Source - terraform-provider-octopusdeploy"
subcategory: ""
description: |-
-
+ Provides information about existing library variable sets.
---
# octopusdeploy_library_variable_sets (Data Source)
-
+Provides information about existing library variable sets.
diff --git a/docs/resources/artifactory_generic_feed.md b/docs/resources/artifactory_generic_feed.md
index 3d46c6eec..07ea676ce 100644
--- a/docs/resources/artifactory_generic_feed.md
+++ b/docs/resources/artifactory_generic_feed.md
@@ -29,7 +29,7 @@ resource "octopusdeploy_artifactory_generic_feed" "example" {
### Required
- `feed_uri` (String)
-- `name` (String) A short, memorable, unique name for this feed. Example: ACME Builds.
+- `name` (String) The name of this resource.
- `repository` (String)
### Optional
@@ -38,7 +38,7 @@ resource "octopusdeploy_artifactory_generic_feed" "example" {
- `layout_regex` (String)
- `package_acquisition_location_options` (List of String)
- `password` (String, Sensitive) The password associated with this resource.
-- `space_id` (String) The space ID associated with this resource.
+- `space_id` (String) The space ID associated with this helm feed.
- `username` (String, Sensitive) The username associated with this resource.
## Import
diff --git a/docs/resources/aws_elastic_container_registry.md b/docs/resources/aws_elastic_container_registry.md
index 5549897ea..e0b4f802f 100644
--- a/docs/resources/aws_elastic_container_registry.md
+++ b/docs/resources/aws_elastic_container_registry.md
@@ -25,15 +25,15 @@ resource "octopusdeploy_aws_elastic_container_registry" "example" {
### Required
- `access_key` (String) The AWS access key to use when authenticating against Amazon Web Services.
-- `name` (String) A short, memorable, unique name for this feed. Example: ACME Builds.
+- `name` (String) The name of this resource.
- `region` (String) The AWS region where the registry resides.
- `secret_key` (String, Sensitive) The AWS secret key to use when authenticating against Amazon Web Services.
### Optional
-- `id` (String) The unique ID for this feed.
+- `id` (String) The unique ID for this resource.
- `package_acquisition_location_options` (List of String)
-- `space_id` (String) The space ID associated with this feed.
+- `space_id` (String) The space ID associated with this aws elastic container registry.
## Import
diff --git a/docs/resources/docker_container_registry.md b/docs/resources/docker_container_registry.md
index ebc6b68cb..8a59fe071 100644
--- a/docs/resources/docker_container_registry.md
+++ b/docs/resources/docker_container_registry.md
@@ -25,8 +25,8 @@ resource "octopusdeploy_docker_container_registry" "example" {
### Required
-- `feed_uri` (String) The URL to a Docker repository.
-- `name` (String) A short, memorable, unique name for this feed. Example: ACME Builds.
+- `feed_uri` (String)
+- `name` (String) The name of this resource.
### Optional
@@ -35,7 +35,7 @@ resource "octopusdeploy_docker_container_registry" "example" {
- `package_acquisition_location_options` (List of String)
- `password` (String, Sensitive) The password associated with this resource.
- `registry_path` (String)
-- `space_id` (String) The space ID associated with this resource.
+- `space_id` (String) The space ID associated with this docker container registry feed.
- `username` (String, Sensitive) The username associated with this resource.
## Import
diff --git a/docs/resources/github_repository_feed.md b/docs/resources/github_repository_feed.md
index c743a47dd..37e9601c1 100644
--- a/docs/resources/github_repository_feed.md
+++ b/docs/resources/github_repository_feed.md
@@ -27,7 +27,7 @@ resource "octopusdeploy_github_repository_feed" "example" {
### Required
- `feed_uri` (String)
-- `name` (String) A short, memorable, unique name for this feed. Example: ACME Builds.
+- `name` (String) The name of this resource.
### Optional
@@ -36,7 +36,7 @@ resource "octopusdeploy_github_repository_feed" "example" {
- `id` (String) The unique ID for this resource.
- `package_acquisition_location_options` (List of String)
- `password` (String, Sensitive) The password associated with this resource.
-- `space_id` (String) The space ID associated with this resource.
+- `space_id` (String) The space ID associated with this github repository feed.
- `username` (String, Sensitive) The username associated with this resource.
## Import
diff --git a/docs/resources/helm_feed.md b/docs/resources/helm_feed.md
index 037bc5888..5eed44882 100644
--- a/docs/resources/helm_feed.md
+++ b/docs/resources/helm_feed.md
@@ -2,12 +2,12 @@
page_title: "octopusdeploy_helm_feed Resource - terraform-provider-octopusdeploy"
subcategory: "Feeds"
description: |-
- This resource manages a Helm feed in Octopus Deploy.
+ This resource manages a Helm Feed in Octopus Deploy.
---
# octopusdeploy_helm_feed (Resource)
-This resource manages a Helm feed in Octopus Deploy.
+This resource manages a Helm Feed in Octopus Deploy.
## Example Usage
@@ -25,14 +25,14 @@ resource "octopusdeploy_helm_feed" "example" {
### Required
- `feed_uri` (String)
-- `name` (String) A short, memorable, unique name for this feed. Example: ACME Builds.
+- `name` (String) The name of this resource.
### Optional
- `id` (String) The unique ID for this resource.
- `package_acquisition_location_options` (List of String)
- `password` (String, Sensitive) The password associated with this resource.
-- `space_id` (String) The space ID associated with this resource.
+- `space_id` (String) The space ID associated with this helm feed.
- `username` (String, Sensitive) The username associated with this resource.
## Import
diff --git a/docs/resources/library_variable_set.md b/docs/resources/library_variable_set.md
index b30fd3f1f..9bff4bf78 100644
--- a/docs/resources/library_variable_set.md
+++ b/docs/resources/library_variable_set.md
@@ -3,12 +3,12 @@
page_title: "octopusdeploy_library_variable_set Resource - terraform-provider-octopusdeploy"
subcategory: ""
description: |-
-
+ This resource manages library variable sets in Octopus Deploy.
---
# octopusdeploy_library_variable_set (Resource)
-
+This resource manages library variable sets in Octopus Deploy.
diff --git a/docs/resources/maven_feed.md b/docs/resources/maven_feed.md
index a589dc13f..1c01d16e8 100644
--- a/docs/resources/maven_feed.md
+++ b/docs/resources/maven_feed.md
@@ -27,7 +27,7 @@ resource "octopusdeploy_maven_feed" "example" {
### Required
- `feed_uri` (String)
-- `name` (String) A short, memorable, unique name for this feed. Example: ACME Builds.
+- `name` (String) The name of this resource.
### Optional
@@ -36,7 +36,7 @@ resource "octopusdeploy_maven_feed" "example" {
- `id` (String) The unique ID for this resource.
- `package_acquisition_location_options` (List of String)
- `password` (String, Sensitive) The password associated with this resource.
-- `space_id` (String) The space ID associated with this resource.
+- `space_id` (String) The space ID associated with this maven feed.
- `username` (String, Sensitive) The username associated with this resource.
## Import
diff --git a/docs/resources/nuget_feed.md b/docs/resources/nuget_feed.md
index 0444d55ba..b5758af66 100644
--- a/docs/resources/nuget_feed.md
+++ b/docs/resources/nuget_feed.md
@@ -27,8 +27,8 @@ resource "octopusdeploy_nuget_feed" "example" {
### Required
-- `feed_uri` (String) The feed URI can be a URL or a folder path.
-- `name` (String) A short, memorable, unique name for this feed. Example: ACME Builds.
+- `feed_uri` (String)
+- `name` (String) The name of this resource.
### Optional
@@ -38,7 +38,7 @@ resource "octopusdeploy_nuget_feed" "example" {
- `is_enhanced_mode` (Boolean) This will improve performance of the NuGet feed but may not be supported by some older feeds. Disable if the operation, Create Release does not return the latest version for a package.
- `package_acquisition_location_options` (List of String)
- `password` (String, Sensitive) The password associated with this resource.
-- `space_id` (String) The space ID associated with this resource.
+- `space_id` (String) The space ID associated with this nuget feed.
- `username` (String, Sensitive) The username associated with this resource.
## Import
diff --git a/octopusdeploy/provider.go b/octopusdeploy/provider.go
index cd51d9521..07f503374 100644
--- a/octopusdeploy/provider.go
+++ b/octopusdeploy/provider.go
@@ -48,7 +48,6 @@ func Provider() *schema.Provider {
"octopusdeploy_channel": resourceChannel(),
"octopusdeploy_cloud_region_deployment_target": resourceCloudRegionDeploymentTarget(),
"octopusdeploy_deployment_process": resourceDeploymentProcess(),
- "octopusdeploy_docker_container_registry": resourceDockerContainerRegistry(),
"octopusdeploy_dynamic_worker_pool": resourceDynamicWorkerPool(),
"octopusdeploy_gcp_account": resourceGoogleCloudPlatformAccount(),
"octopusdeploy_kubernetes_agent_deployment_target": resourceKubernetesAgentDeploymentTarget(),
diff --git a/octopusdeploy/resource_docker_container_registry.go b/octopusdeploy/resource_docker_container_registry.go
deleted file mode 100644
index bf5d1edd8..000000000
--- a/octopusdeploy/resource_docker_container_registry.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package octopusdeploy
-
-import (
- "context"
- "fmt"
-
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client"
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/feeds"
- "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 resourceDockerContainerRegistry() *schema.Resource {
- return &schema.Resource{
- CreateContext: resourceDockerContainerRegistryCreate,
- DeleteContext: resourceDockerContainerRegistryDelete,
- Description: "This resource manages a Docker Container Registry in Octopus Deploy.",
- Importer: getImporter(),
- ReadContext: resourceDockerContainerRegistryRead,
- Schema: getDockerContainerRegistrySchema(),
- UpdateContext: resourceDockerContainerRegistryUpdate,
- }
-}
-
-func resourceDockerContainerRegistryCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- dockerContainerRegistry, err := expandDockerContainerRegistry(d)
- if err != nil {
- return diag.FromErr(err)
- }
-
- tflog.Info(ctx, fmt.Sprintf("creating Docker container registry, %s", dockerContainerRegistry.GetName()))
-
- client := m.(*client.Client)
- createdDockerContainerRegistry, err := feeds.Add(client, dockerContainerRegistry)
- if err != nil {
- return diag.FromErr(err)
- }
-
- if err := setDockerContainerRegistry(ctx, d, createdDockerContainerRegistry.(*feeds.DockerContainerRegistry)); err != nil {
- return diag.FromErr(err)
- }
-
- d.SetId(createdDockerContainerRegistry.GetID())
-
- tflog.Info(ctx, fmt.Sprintf("Docker container registry created (%s)", d.Id()))
- return nil
-}
-
-func resourceDockerContainerRegistryDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- tflog.Info(ctx, fmt.Sprintf("deleting Docker container registry (%s)", d.Id()))
-
- client := m.(*client.Client)
- err := client.Feeds.DeleteByID(d.Id())
- if err != nil {
- return diag.FromErr(err)
- }
-
- d.SetId("")
-
- tflog.Info(ctx, "Docker container registry deleted")
- return nil
-}
-
-func resourceDockerContainerRegistryRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- tflog.Info(ctx, fmt.Sprintf("reading Docker container registry (%s)", d.Id()))
-
- client := m.(*client.Client)
- feed, err := feeds.GetByID(client, d.Get("space_id").(string), d.Id())
- if err != nil {
- return errors.ProcessApiError(ctx, d, err, "Docker container registry")
- }
-
- dockerContainerRegistry := feed.(*feeds.DockerContainerRegistry)
- if err := setDockerContainerRegistry(ctx, d, dockerContainerRegistry); err != nil {
- return diag.FromErr(err)
- }
-
- tflog.Info(ctx, fmt.Sprintf("Docker container registry read (%s)", dockerContainerRegistry.GetID()))
- return nil
-}
-
-func resourceDockerContainerRegistryUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- feed, err := expandDockerContainerRegistry(d)
- if err != nil {
- return diag.FromErr(err)
- }
-
- tflog.Info(ctx, fmt.Sprintf("updating Docker container registry (%s)", feed.GetID()))
-
- client := m.(*client.Client)
- updatedFeed, err := feeds.Update(client, feed)
- if err != nil {
- return diag.FromErr(err)
- }
-
- if err := setDockerContainerRegistry(ctx, d, updatedFeed.(*feeds.DockerContainerRegistry)); err != nil {
- return diag.FromErr(err)
- }
-
- tflog.Info(ctx, fmt.Sprintf("Docker container registry updated (%s)", d.Id()))
- return nil
-}
diff --git a/octopusdeploy/schema_docker_container_registry.go b/octopusdeploy/schema_docker_container_registry.go
deleted file mode 100644
index a28f565cd..000000000
--- a/octopusdeploy/schema_docker_container_registry.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package octopusdeploy
-
-import (
- "context"
- "fmt"
-
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/feeds"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
-)
-
-func expandDockerContainerRegistry(d *schema.ResourceData) (*feeds.DockerContainerRegistry, error) {
- name := d.Get("name").(string)
-
- feed, err := feeds.NewDockerContainerRegistry(name)
- if err != nil {
- return nil, err
- }
-
- feed.ID = d.Id()
-
- if v, ok := d.GetOk("api_version"); ok {
- feed.APIVersion = v.(string)
- }
-
- if v, ok := d.GetOk("feed_uri"); ok {
- feed.FeedURI = v.(string)
- }
-
- if v, ok := d.GetOk("registry_path"); ok {
- feed.RegistryPath = v.(string)
- }
-
- if v, ok := d.GetOk("space_id"); ok {
- feed.SpaceID = v.(string)
- }
-
- if v, ok := d.GetOk("package_acquisition_location_options"); ok {
- feed.PackageAcquisitionLocationOptions = getSliceFromTerraformTypeList(v)
- }
-
- if v, ok := d.GetOk("password"); ok {
- feed.Password = core.NewSensitiveValue(v.(string))
- }
-
- if v, ok := d.GetOk("username"); ok {
- feed.Username = v.(string)
- }
-
- return feed, nil
-}
-
-func getDockerContainerRegistrySchema() map[string]*schema.Schema {
- return map[string]*schema.Schema{
- "api_version": {
- Optional: true,
- Type: schema.TypeString,
- },
- "feed_uri": {
- Description: "The URL to a Docker repository.",
- Required: true,
- Type: schema.TypeString,
- ValidateDiagFunc: validation.ToDiagFunc(validation.IsURLWithHTTPorHTTPS),
- },
- "id": getIDSchema(),
- "name": {
- Description: "A short, memorable, unique name for this feed. Example: ACME Builds.",
- Required: true,
- Type: schema.TypeString,
- ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotEmpty),
- },
- "password": getPasswordSchema(false),
- "package_acquisition_location_options": {
- Computed: true,
- Elem: &schema.Schema{Type: schema.TypeString},
- Optional: true,
- Type: schema.TypeList,
- },
- "registry_path": {
- Optional: true,
- Type: schema.TypeString,
- },
- "space_id": getSpaceIDSchema(),
- "username": getUsernameSchema(false),
- }
-}
-
-func setDockerContainerRegistry(ctx context.Context, d *schema.ResourceData, feed *feeds.DockerContainerRegistry) error {
- d.Set("api_version", feed.APIVersion)
- d.Set("feed_uri", feed.FeedURI)
- d.Set("name", feed.Name)
- d.Set("registry_path", feed.RegistryPath)
- d.Set("space_id", feed.SpaceID)
- d.Set("username", feed.Username)
-
- if err := d.Set("package_acquisition_location_options", feed.PackageAcquisitionLocationOptions); err != nil {
- return fmt.Errorf("error setting package_acquisition_location_options: %s", err)
- }
-
- d.SetId(feed.GetID())
-
- return nil
-}
diff --git a/octopusdeploy_framework/framework_provider.go b/octopusdeploy_framework/framework_provider.go
index 816b8f774..447f66237 100644
--- a/octopusdeploy_framework/framework_provider.go
+++ b/octopusdeploy_framework/framework_provider.go
@@ -2,9 +2,9 @@ package octopusdeploy_framework
import (
"context"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
"os"
- "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
@@ -92,6 +92,7 @@ func (p *octopusDeployFrameworkProvider) Resources(ctx context.Context) []func()
NewLibraryVariableSetFeedResource,
NewVariableResource,
NewProjectResource,
+ NewDockerContainerRegistryFeedResource,
}
}
diff --git a/octopusdeploy_framework/resource_artifactory_generic_feed.go b/octopusdeploy_framework/resource_artifactory_generic_feed.go
index e70900061..a1eb7ab58 100644
--- a/octopusdeploy_framework/resource_artifactory_generic_feed.go
+++ b/octopusdeploy_framework/resource_artifactory_generic_feed.go
@@ -29,7 +29,8 @@ func (r *artifactoryGenericFeedTypeResource) Metadata(ctx context.Context, req r
func (r *artifactoryGenericFeedTypeResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
- Attributes: schemas.GetArtifactoryGenericFeedResourceSchema(),
+ Attributes: schemas.GetArtifactoryGenericFeedResourceSchema(),
+ Description: "This resource manages a Artifactory Generic feed in Octopus Deploy.",
}
}
diff --git a/octopusdeploy_framework/resource_aws_elastic_container_registry.go b/octopusdeploy_framework/resource_aws_elastic_container_registry.go
index 96f089a83..72cef3b51 100644
--- a/octopusdeploy_framework/resource_aws_elastic_container_registry.go
+++ b/octopusdeploy_framework/resource_aws_elastic_container_registry.go
@@ -28,7 +28,8 @@ func (r *awsElasticContainerRegistryFeedTypeResource) Metadata(ctx context.Conte
func (r *awsElasticContainerRegistryFeedTypeResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
- Attributes: schemas.GetAwsElasticContainerRegistryFeedResourceSchema(),
+ Attributes: schemas.GetAwsElasticContainerRegistryFeedResourceSchema(),
+ Description: "This resource manages an AWS Elastic Container Registry in Octopus Deploy.",
}
}
diff --git a/octopusdeploy_framework/resource_docker_container_registry.go b/octopusdeploy_framework/resource_docker_container_registry.go
new file mode 100644
index 000000000..fa73ef902
--- /dev/null
+++ b/octopusdeploy_framework/resource_docker_container_registry.go
@@ -0,0 +1,183 @@
+package octopusdeploy_framework
+
+import (
+ "context"
+ "fmt"
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/feeds"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+)
+
+type dockerContainerRegistryFeedTypeResource struct {
+ *Config
+}
+
+func NewDockerContainerRegistryFeedResource() resource.Resource {
+ return &dockerContainerRegistryFeedTypeResource{}
+}
+
+func (r *dockerContainerRegistryFeedTypeResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = util.GetTypeName("docker_container_registry")
+}
+
+func (r *dockerContainerRegistryFeedTypeResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Attributes: schemas.GetDockerContainerRegistryFeedResourceSchema(),
+ Description: "This resource manages a Docker Container Registry in Octopus Deploy.",
+ }
+}
+
+func (r *dockerContainerRegistryFeedTypeResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ r.Config = ResourceConfiguration(req, resp)
+}
+
+func (r *dockerContainerRegistryFeedTypeResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ var data *schemas.DockerContainerRegistryFeedTypeResourceModel
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ dockerContainerRegistryFeed, err := createDockerContainerRegistryFeedResourceFromData(data)
+ if err != nil {
+ return
+ }
+
+ tflog.Info(ctx, fmt.Sprintf("creating Docker Container Registry feed: %s", dockerContainerRegistryFeed.GetName()))
+
+ client := r.Config.Client
+ createdFeed, err := feeds.Add(client, dockerContainerRegistryFeed)
+ if err != nil {
+ resp.Diagnostics.AddError("unable to create docker container registry feed", err.Error())
+ return
+ }
+
+ updateDataFromDockerContainerRegistryFeed(data, data.SpaceID.ValueString(), createdFeed.(*feeds.DockerContainerRegistry))
+
+ tflog.Info(ctx, fmt.Sprintf("Docker Container Registry feed created (%s)", data.ID))
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *dockerContainerRegistryFeedTypeResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ var data *schemas.DockerContainerRegistryFeedTypeResourceModel
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Info(ctx, fmt.Sprintf("reading Docker Container Registry feed (%s)", data.ID))
+
+ client := r.Config.Client
+ feed, err := feeds.GetByID(client, data.SpaceID.ValueString(), data.ID.ValueString())
+ if err != nil {
+ resp.Diagnostics.AddError("unable to load docker container registry feed", err.Error())
+ return
+ }
+
+ dockerContainerRegistry := feed.(*feeds.DockerContainerRegistry)
+ updateDataFromDockerContainerRegistryFeed(data, data.SpaceID.ValueString(), dockerContainerRegistry)
+
+ tflog.Info(ctx, fmt.Sprintf("Docker Container Registry feed read (%s)", dockerContainerRegistry.GetID()))
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *dockerContainerRegistryFeedTypeResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ var data, state *schemas.DockerContainerRegistryFeedTypeResourceModel
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
+ resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Debug(ctx, fmt.Sprintf("updating docker container registry feed '%s'", data.ID.ValueString()))
+
+ feed, err := createDockerContainerRegistryFeedResourceFromData(data)
+ feed.ID = state.ID.ValueString()
+ if err != nil {
+ resp.Diagnostics.AddError("unable to load docker container registry feed", err.Error())
+ return
+ }
+
+ tflog.Info(ctx, fmt.Sprintf("updating Docker Container Registry feed (%s)", data.ID))
+
+ client := r.Config.Client
+ updatedFeed, err := feeds.Update(client, feed)
+ if err != nil {
+ resp.Diagnostics.AddError("unable to update docker container registry feed", err.Error())
+ return
+ }
+
+ updateDataFromDockerContainerRegistryFeed(data, state.SpaceID.ValueString(), updatedFeed.(*feeds.DockerContainerRegistry))
+
+ tflog.Info(ctx, fmt.Sprintf("Docker Container Registry feed updated (%s)", data.ID))
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *dockerContainerRegistryFeedTypeResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ var data schemas.DockerContainerRegistryFeedTypeResourceModel
+
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ if err := feeds.DeleteByID(r.Config.Client, data.SpaceID.ValueString(), data.ID.ValueString()); err != nil {
+ resp.Diagnostics.AddError("unable to delete docker container registry feed", err.Error())
+ return
+ }
+}
+
+func createDockerContainerRegistryFeedResourceFromData(data *schemas.DockerContainerRegistryFeedTypeResourceModel) (*feeds.DockerContainerRegistry, error) {
+ feed, err := feeds.NewDockerContainerRegistry(data.Name.ValueString())
+ if err != nil {
+ return nil, err
+ }
+
+ feed.ID = data.ID.ValueString()
+ feed.FeedURI = data.FeedUri.ValueString()
+
+ var packageAcquisitionLocationOptions []string
+ for _, element := range data.PackageAcquisitionLocationOptions.Elements() {
+ packageAcquisitionLocationOptions = append(packageAcquisitionLocationOptions, element.(types.String).ValueString())
+ }
+
+ feed.PackageAcquisitionLocationOptions = packageAcquisitionLocationOptions
+ feed.Password = core.NewSensitiveValue(data.Password.ValueString())
+ feed.SpaceID = data.SpaceID.ValueString()
+ feed.Username = data.Username.ValueString()
+ feed.APIVersion = data.APIVersion.ValueString()
+ feed.RegistryPath = data.RegistryPath.ValueString()
+
+ return feed, nil
+}
+
+func updateDataFromDockerContainerRegistryFeed(data *schemas.DockerContainerRegistryFeedTypeResourceModel, spaceId string, feed *feeds.DockerContainerRegistry) {
+ data.FeedUri = types.StringValue(feed.FeedURI)
+ data.Name = types.StringValue(feed.Name)
+ data.SpaceID = types.StringValue(spaceId)
+ if feed.APIVersion != "" {
+ data.APIVersion = types.StringValue(feed.APIVersion)
+ }
+ if feed.RegistryPath != "" {
+ data.RegistryPath = types.StringValue(feed.RegistryPath)
+ }
+ if feed.Username != "" {
+ data.Username = types.StringValue(feed.Username)
+ }
+
+ packageAcquisitionLocationOptionsList := make([]attr.Value, len(feed.PackageAcquisitionLocationOptions))
+ for i, option := range feed.PackageAcquisitionLocationOptions {
+ packageAcquisitionLocationOptionsList[i] = types.StringValue(option)
+ }
+
+ var packageAcquisitionLocationOptionsListValue, _ = types.ListValue(types.StringType, packageAcquisitionLocationOptionsList)
+ data.PackageAcquisitionLocationOptions = packageAcquisitionLocationOptionsListValue
+ data.ID = types.StringValue(feed.ID)
+}
diff --git a/octopusdeploy/resource_docker_container_registry_test.go b/octopusdeploy_framework/resource_docker_container_registry_test.go
similarity index 94%
rename from octopusdeploy/resource_docker_container_registry_test.go
rename to octopusdeploy_framework/resource_docker_container_registry_test.go
index 377ced443..9e2c893fa 100644
--- a/octopusdeploy/resource_docker_container_registry_test.go
+++ b/octopusdeploy_framework/resource_docker_container_registry_test.go
@@ -1,16 +1,15 @@
-package octopusdeploy
+package octopusdeploy_framework
import (
"fmt"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/feeds"
"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"
"path/filepath"
"testing"
-
- "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 TestAccOctopusDeployDockerContainerRegistry(t *testing.T) {
@@ -26,7 +25,7 @@ func TestAccOctopusDeployDockerContainerRegistry(t *testing.T) {
resource.Test(t, resource.TestCase{
CheckDestroy: testDockerContainerRegistryCheckDestroy,
- PreCheck: func() { testAccPreCheck(t) },
+ PreCheck: func() { TestAccPreCheck(t) },
ProtoV6ProviderFactories: ProtoV6ProviderFactories(),
Steps: []resource.TestStep{
{
diff --git a/octopusdeploy_framework/resource_github_repository_feed.go b/octopusdeploy_framework/resource_github_repository_feed.go
index 7a198daaa..30f7bdb28 100644
--- a/octopusdeploy_framework/resource_github_repository_feed.go
+++ b/octopusdeploy_framework/resource_github_repository_feed.go
@@ -28,7 +28,8 @@ func (r *githubRepositoryFeedTypeResource) Metadata(ctx context.Context, req res
func (r *githubRepositoryFeedTypeResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
- Attributes: schemas.GetGitHubRepositoryFeedResourceSchema(),
+ Attributes: schemas.GetGitHubRepositoryFeedResourceSchema(),
+ Description: "This resource manages a GitHub repository feed in Octopus Deploy.",
}
}
diff --git a/octopusdeploy_framework/resource_helm_feed.go b/octopusdeploy_framework/resource_helm_feed.go
index 4fc1c9d3c..64ec52677 100644
--- a/octopusdeploy_framework/resource_helm_feed.go
+++ b/octopusdeploy_framework/resource_helm_feed.go
@@ -29,7 +29,8 @@ func (r *helmFeedTypeResource) Metadata(ctx context.Context, req resource.Metada
func (r *helmFeedTypeResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
- Attributes: schemas.GetHelmFeedResourceSchema(),
+ Attributes: schemas.GetHelmFeedResourceSchema(),
+ Description: "This resource manages a Helm Feed in Octopus Deploy.",
}
}
diff --git a/octopusdeploy_framework/resource_maven_feed.go b/octopusdeploy_framework/resource_maven_feed.go
index c6f6ddd19..722081e81 100644
--- a/octopusdeploy_framework/resource_maven_feed.go
+++ b/octopusdeploy_framework/resource_maven_feed.go
@@ -28,7 +28,8 @@ func (r *mavenFeedTypeResource) Metadata(ctx context.Context, req resource.Metad
func (r *mavenFeedTypeResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
- Attributes: schemas.GetMavenFeedResourceSchema(),
+ Attributes: schemas.GetMavenFeedResourceSchema(),
+ Description: "This resource manages a Maven feed in Octopus Deploy.",
}
}
diff --git a/octopusdeploy_framework/resource_nuget_feed.go b/octopusdeploy_framework/resource_nuget_feed.go
index 306e16f14..f053ea450 100644
--- a/octopusdeploy_framework/resource_nuget_feed.go
+++ b/octopusdeploy_framework/resource_nuget_feed.go
@@ -28,7 +28,8 @@ func (r *nugetFeedTypeResource) Metadata(ctx context.Context, req resource.Metad
func (r *nugetFeedTypeResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
- Attributes: schemas.GetNugetFeedResourceSchema(),
+ Attributes: schemas.GetNugetFeedResourceSchema(),
+ Description: "This resource manages a Nuget feed in Octopus Deploy.",
}
}
diff --git a/octopusdeploy_framework/schemas/docker_container_registry_feed.go b/octopusdeploy_framework/schemas/docker_container_registry_feed.go
new file mode 100644
index 000000000..d7ff550c4
--- /dev/null
+++ b/octopusdeploy_framework/schemas/docker_container_registry_feed.go
@@ -0,0 +1,39 @@
+package schemas
+
+import (
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
+ resourceSchema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+const dockerContainerRegistryFeedDescription = "docker container registry feed"
+
+func GetDockerContainerRegistryFeedResourceSchema() map[string]resourceSchema.Attribute {
+ return map[string]resourceSchema.Attribute{
+ "api_version": resourceSchema.StringAttribute{
+ Optional: true,
+ },
+ "feed_uri": util.GetFeedUriResourceSchema(),
+ "id": util.GetIdResourceSchema(),
+ "name": util.GetNameResourceSchema(true),
+ "package_acquisition_location_options": util.GetPackageAcquisitionLocationOptionsResourceSchema(),
+ "password": util.GetPasswordResourceSchema(false),
+ "space_id": util.GetSpaceIdResourceSchema(dockerContainerRegistryFeedDescription),
+ "username": util.GetUsernameResourceSchema(false),
+ "registry_path": resourceSchema.StringAttribute{
+ Optional: true,
+ },
+ }
+}
+
+type DockerContainerRegistryFeedTypeResourceModel struct {
+ APIVersion types.String `tfsdk:"api_version"`
+ FeedUri types.String `tfsdk:"feed_uri"`
+ ID types.String `tfsdk:"id"`
+ Name types.String `tfsdk:"name"`
+ PackageAcquisitionLocationOptions types.List `tfsdk:"package_acquisition_location_options"`
+ Password types.String `tfsdk:"password"`
+ SpaceID types.String `tfsdk:"space_id"`
+ Username types.String `tfsdk:"username"`
+ RegistryPath types.String `tfsdk:"registry_path"`
+}
diff --git a/octopusdeploy_framework/schemas/library_variable_set.go b/octopusdeploy_framework/schemas/library_variable_set.go
index f3355768f..5fab75d02 100644
--- a/octopusdeploy_framework/schemas/library_variable_set.go
+++ b/octopusdeploy_framework/schemas/library_variable_set.go
@@ -22,7 +22,8 @@ type LibraryVariableSetResourceModel struct {
func GetLibraryVariableSetDataSourceSchema() datasourceSchema.Schema {
return datasourceSchema.Schema{
- Attributes: getLibraryVariableSetDataSchema(),
+ Attributes: getLibraryVariableSetDataSchema(),
+ Description: "Provides information about existing library variable sets.",
Blocks: map[string]datasourceSchema.Block{
"library_variable_sets": datasourceSchema.ListNestedBlock{
Description: "A list of library variable sets that match the filter(s).",
@@ -97,6 +98,7 @@ func GetLibraryVariableSetResourceSchema() resourceSchema.Schema {
Computed: true,
},
},
+ Description: "This resource manages library variable sets in Octopus Deploy.",
Blocks: map[string]resourceSchema.Block{
"template": resourceSchema.ListNestedBlock{
NestedObject: resourceSchema.NestedBlockObject{
From 7c81ce944a04db0272c2a4aab8adac7d3b56a1b1 Mon Sep 17 00:00:00 2001
From: Huy Nguyen <162080607+HuyPhanNguyen@users.noreply.github.com>
Date: Wed, 7 Aug 2024 18:37:14 +1000
Subject: [PATCH 03/28] Fix bug datasource spaces (#710)
* Fix query.Ids convert and add basic test
* Update the test
* Fix the util method instead
* just ignore if cant convert to string
* try to fix flaky test
* second try
* Add lifecycle datasource test
* Last try to avoid race condition
* Test why not 7 variables create
* switch the order
---
octopusdeploy/testing_container_test.go | 5 ++
.../datasource_lifecycle_test.go | 63 +++++++++++++++++
.../datasource_spaces_test.go | 58 ++++++++++++++++
.../resource_variable_test.go | 15 +++-
octopusdeploy_framework/schemas/schema.go | 7 +-
terraform/49-variables/variables.tf | 68 ++++++++++++++++---
6 files changed, 203 insertions(+), 13 deletions(-)
create mode 100644 octopusdeploy_framework/datasource_lifecycle_test.go
create mode 100644 octopusdeploy_framework/datasource_spaces_test.go
diff --git a/octopusdeploy/testing_container_test.go b/octopusdeploy/testing_container_test.go
index 8c1bd6397..6fd5dfba7 100644
--- a/octopusdeploy/testing_container_test.go
+++ b/octopusdeploy/testing_container_test.go
@@ -57,6 +57,11 @@ func TestMain(m *testing.M) {
log.Printf("Failed to create client: (%s)", err.Error())
panic(m)
}
+
+ octoContainer = &test.OctopusContainer{
+ Container: nil,
+ URI: url,
+ }
}
code := m.Run()
os.Exit(code)
diff --git a/octopusdeploy_framework/datasource_lifecycle_test.go b/octopusdeploy_framework/datasource_lifecycle_test.go
new file mode 100644
index 000000000..c7febe404
--- /dev/null
+++ b/octopusdeploy_framework/datasource_lifecycle_test.go
@@ -0,0 +1,63 @@
+package octopusdeploy_framework
+
+import (
+ "fmt"
+ "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+)
+
+func TestAccDataSourceLifecycles(t *testing.T) {
+ spaceName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
+ lifecycleName := "Default Lifecycle"
+ resourceName := "data.octopusdeploy_lifecycles.lifecycle_default_lifecycle"
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { TestAccPreCheck(t) },
+ ProtoV6ProviderFactories: ProtoV6ProviderFactories(),
+ Steps: []resource.TestStep{
+ {
+ Config: testAccDataSourceLifecyclesConfig(spaceName, lifecycleName),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttrSet(resourceName, "space_id"),
+ resource.TestCheckResourceAttr(resourceName, "partial_name", lifecycleName),
+ resource.TestCheckResourceAttr(resourceName, "lifecycles.#", "1"),
+ resource.TestCheckResourceAttrSet(resourceName, "lifecycles.0.id"),
+ resource.TestCheckResourceAttr(resourceName, "lifecycles.0.name", lifecycleName),
+ testAccCheckOutputExists("octopus_space_id"),
+ testAccCheckOutputExists("octopus_lifecycle_id"),
+ ),
+ },
+ },
+ })
+}
+
+func testAccDataSourceLifecyclesConfig(spaceName, lifecycleName string) string {
+ return fmt.Sprintf(`
+resource "octopusdeploy_space" "octopus_project_space_test" {
+ name = "%s"
+ is_default = false
+ is_task_queue_stopped = false
+ description = "Test space for lifecycles datasource"
+ space_managers_teams = ["teams-administrators"]
+}
+
+data "octopusdeploy_lifecycles" "lifecycle_default_lifecycle" {
+ ids = null
+ partial_name = "%s"
+ space_id = octopusdeploy_space.octopus_project_space_test.id
+ skip = 0
+ take = 1
+ depends_on = [octopusdeploy_space.octopus_project_space_test]
+}
+
+output "octopus_space_id" {
+ value = octopusdeploy_space.octopus_project_space_test.id
+}
+
+output "octopus_lifecycle_id" {
+ value = data.octopusdeploy_lifecycles.lifecycle_default_lifecycle.lifecycles[0].id
+}
+`, spaceName, lifecycleName)
+}
diff --git a/octopusdeploy_framework/datasource_spaces_test.go b/octopusdeploy_framework/datasource_spaces_test.go
new file mode 100644
index 000000000..dc48f1108
--- /dev/null
+++ b/octopusdeploy_framework/datasource_spaces_test.go
@@ -0,0 +1,58 @@
+package octopusdeploy_framework
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/terraform"
+)
+
+func TestAccDataSourceSpaces(t *testing.T) {
+ spaceID := "Spaces-1"
+ resourceName := "data.octopusdeploy_spaces.test"
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { TestAccPreCheck(t) },
+ ProtoV6ProviderFactories: ProtoV6ProviderFactories(),
+ Steps: []resource.TestStep{
+ {
+ Config: testAccDataSourceSpacesConfig(spaceID),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(resourceName, "ids.#", "1"),
+ resource.TestCheckResourceAttr(resourceName, "ids.0", spaceID),
+ resource.TestCheckResourceAttr(resourceName, "skip", "0"),
+ resource.TestCheckResourceAttr(resourceName, "take", "1"),
+ resource.TestCheckResourceAttrSet(resourceName, "spaces.0.id"),
+ testAccCheckOutputExists("octopus_space_id"),
+ resource.TestCheckOutput("octopus_space_id", spaceID),
+ ),
+ },
+ },
+ })
+}
+
+func testAccDataSourceSpacesConfig(spaceID string) string {
+ tfConfig := fmt.Sprintf(`
+ data "octopusdeploy_spaces" "test" {
+ ids = ["%s"]
+ skip = 0
+ take = 1
+ }
+
+ output "octopus_space_id" {
+ value = data.octopusdeploy_spaces.test.spaces[0].id
+ }
+ `, spaceID)
+ return tfConfig
+}
+
+func testAccCheckOutputExists(name string) resource.TestCheckFunc {
+ return func(s *terraform.State) error {
+ _, ok := s.RootModule().Outputs[name]
+ if !ok {
+ return fmt.Errorf("output %s not found", name)
+ }
+ return nil
+ }
+}
diff --git a/octopusdeploy_framework/resource_variable_test.go b/octopusdeploy_framework/resource_variable_test.go
index 055d796d4..d71cc6dd8 100644
--- a/octopusdeploy_framework/resource_variable_test.go
+++ b/octopusdeploy_framework/resource_variable_test.go
@@ -3,7 +3,9 @@ package octopusdeploy_framework
import (
"fmt"
"path/filepath"
+ "strings"
"testing"
+ "time"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/variables"
internalTest "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/test"
@@ -320,6 +322,9 @@ func TestVariableResource(t *testing.T) {
// Assert
client, err := octoclient.CreateClient(octoContainer.URI, newSpaceId, test.ApiKey)
project, err := client.Projects.GetByName("Test")
+
+ // Add a short delay before querying the API
+ time.Sleep(5 * time.Second)
variableSet, err := client.Variables.GetAll(project.ID)
if err != nil {
@@ -327,7 +332,15 @@ func TestVariableResource(t *testing.T) {
}
if len(variableSet.Variables) != 7 {
- t.Fatalf("Expected 7 variables to be created.")
+ var report strings.Builder
+ report.WriteString(fmt.Sprintf("Expected 7 variables, but found %d.\nReturned variables:\n", len(variableSet.Variables)))
+
+ for _, v := range variableSet.Variables {
+ report.WriteString(fmt.Sprintf("- Name: %s\n Type: %s\n Value: %s\n Scope: %+v\n\n",
+ v.Name, v.Type, v.Value, v.Scope))
+ }
+
+ t.Fatalf(report.String())
}
for _, variable := range variableSet.Variables {
diff --git a/octopusdeploy_framework/schemas/schema.go b/octopusdeploy_framework/schemas/schema.go
index 5c786144b..395aefc7f 100644
--- a/octopusdeploy_framework/schemas/schema.go
+++ b/octopusdeploy_framework/schemas/schema.go
@@ -191,7 +191,12 @@ func GetBooleanResourceAttribute(description string, defaultValue bool, isOption
func GetIds(ids types.List) []string {
var result = make([]string, 0, len(ids.Elements()))
for _, id := range ids.Elements() {
- result = append(result, id.String())
+ strVal, ok := id.(types.String)
+
+ if !ok || strVal.IsNull() || strVal.IsUnknown() {
+ continue
+ }
+ result = append(result, strVal.ValueString())
}
return result
}
diff --git a/terraform/49-variables/variables.tf b/terraform/49-variables/variables.tf
index 9ebdc692c..0e2f15da7 100644
--- a/terraform/49-variables/variables.tf
+++ b/terraform/49-variables/variables.tf
@@ -1,21 +1,35 @@
+
+
+resource "octopusdeploy_variable" "scoped_project_variable_action" {
+ depends_on = [
+ octopusdeploy_project.test_project,
+ ]
+ owner_id = octopusdeploy_project.test_project.id
+ type = "String"
+ name = "ActionScopedVariable"
+ value = "unscoped variable"
+ scope {
+ actions = [octopusdeploy_deployment_process.test_deployment_process.step[0].run_script_action[0].id]
+ }
+}
+
resource "octopusdeploy_variable" "unscoped_project_variable" {
+ depends_on = [
+ octopusdeploy_project.test_project,
+ octopusdeploy_variable.scoped_project_variable_action,
+ ]
owner_id = octopusdeploy_project.test_project.id
type = "String"
name = "UnscopedVariable"
value = "UnscopedVariable"
}
-resource "octopusdeploy_variable" "scoped_project_variable_action" {
- owner_id = octopusdeploy_project.test_project.id
- type = "String"
- name = "ActionScopedVariable"
- value = "unscoped variable"
- scope {
- actions = [octopusdeploy_deployment_process.test_deployment_process.step[0].run_script_action[0].id]
- }
-}
-
resource "octopusdeploy_variable" "scoped_project_variable_channel" {
+ depends_on = [
+ octopusdeploy_project.test_project,
+ octopusdeploy_variable.unscoped_project_variable,
+ octopusdeploy_variable.scoped_project_variable_action,
+ ]
owner_id = octopusdeploy_project.test_project.id
type = "String"
name = "ChannelScopedVariable"
@@ -26,6 +40,12 @@ resource "octopusdeploy_variable" "scoped_project_variable_channel" {
}
resource "octopusdeploy_variable" "scoped_project_variable_environment" {
+ depends_on = [
+ octopusdeploy_project.test_project,
+ octopusdeploy_variable.unscoped_project_variable,
+ octopusdeploy_variable.scoped_project_variable_action,
+ octopusdeploy_variable.scoped_project_variable_channel,
+ ]
owner_id = octopusdeploy_project.test_project.id
type = "String"
name = "EnvironmentScopedVariable"
@@ -36,6 +56,14 @@ resource "octopusdeploy_variable" "scoped_project_variable_environment" {
}
resource "octopusdeploy_variable" "scoped_project_variable_machine" {
+ depends_on = [
+ octopusdeploy_project.test_project,
+ octopusdeploy_variable.unscoped_project_variable,
+ octopusdeploy_variable.scoped_project_variable_action,
+ octopusdeploy_variable.scoped_project_variable_channel,
+ octopusdeploy_variable.scoped_project_variable_environment
+ ]
+
owner_id = octopusdeploy_project.test_project.id
type = "String"
name = "MachineScopedVariable"
@@ -46,6 +74,14 @@ resource "octopusdeploy_variable" "scoped_project_variable_machine" {
}
resource "octopusdeploy_variable" "scoped_project_variable_process" {
+ depends_on = [
+ octopusdeploy_project.test_project,
+ octopusdeploy_variable.unscoped_project_variable,
+ octopusdeploy_variable.scoped_project_variable_action,
+ octopusdeploy_variable.scoped_project_variable_channel,
+ octopusdeploy_variable.scoped_project_variable_environment,
+ octopusdeploy_variable.scoped_project_variable_machine,
+ ]
owner_id = octopusdeploy_project.test_project.id
type = "String"
name = "ProcessScopedVariable"
@@ -56,6 +92,16 @@ resource "octopusdeploy_variable" "scoped_project_variable_process" {
}
resource "octopusdeploy_variable" "scoped_project_variable_role" {
+ depends_on = [
+ octopusdeploy_project.test_project,
+ octopusdeploy_variable.unscoped_project_variable,
+ octopusdeploy_variable.scoped_project_variable_action,
+ octopusdeploy_variable.scoped_project_variable_channel,
+ octopusdeploy_variable.scoped_project_variable_environment,
+ octopusdeploy_variable.scoped_project_variable_machine,
+ octopusdeploy_variable.scoped_project_variable_process,
+ ]
+
owner_id = octopusdeploy_project.test_project.id
type = "String"
name = "RoleScopedVariable"
@@ -63,4 +109,4 @@ resource "octopusdeploy_variable" "scoped_project_variable_role" {
scope {
roles = ["role"]
}
-}
+}
\ No newline at end of file
From 3a8a027a764cda034b8840bbba6df254e5f0d6b4 Mon Sep 17 00:00:00 2001
From: Isaac Calligeros <101079287+IsaacCalligeros95@users.noreply.github.com>
Date: Thu, 8 Aug 2024 10:23:22 +0930
Subject: [PATCH 04/28] Isaac/mc schema fixes (#715)
* propagate an error
* The collection returned by data sources must not be optional
* Add more optionals
---------
Co-authored-by: Matthew Casperson
---
octopusdeploy/data_source_tenants.go | 5 ++++-
octopusdeploy/schema_account_resource.go | 1 +
.../schema_azure_cloud_service_deployment_target.go | 2 +-
.../schema_azure_service_fabric_cluster_deployment_target.go | 2 +-
octopusdeploy/schema_azure_web_app_deployment_target.go | 2 +-
octopusdeploy/schema_certificate.go | 2 +-
octopusdeploy/schema_channel.go | 2 +-
octopusdeploy/schema_cloud_region_deployment_target.go | 2 +-
octopusdeploy/schema_deployment_target.go | 2 +-
octopusdeploy/schema_kubernetes_agent_deployment_target.go | 2 +-
octopusdeploy/schema_kubernetes_cluster_deployment_target.go | 2 +-
octopusdeploy/schema_listening_tentacle_deployment_target.go | 2 +-
octopusdeploy/schema_machine_policy.go | 2 +-
.../schema_offline_package_drop_deployment_target.go | 2 +-
octopusdeploy/schema_polling_tentacle_deployment_target.go | 2 +-
octopusdeploy/schema_script_modules.go | 2 +-
octopusdeploy/schema_ssh_connection_deployment_target.go | 2 +-
octopusdeploy/schema_tag_set.go | 2 +-
octopusdeploy/schema_team.go | 2 +-
octopusdeploy/schema_tenant.go | 2 +-
octopusdeploy/schema_user.go | 2 +-
octopusdeploy/schema_user_role.go | 2 +-
octopusdeploy/schema_worker_pool.go | 2 +-
octopusdeploy_framework/schemas/gitCredential.go | 1 +
octopusdeploy_framework/schemas/lifecycle.go | 1 +
octopusdeploy_framework/schemas/project.go | 1 +
26 files changed, 29 insertions(+), 22 deletions(-)
diff --git a/octopusdeploy/data_source_tenants.go b/octopusdeploy/data_source_tenants.go
index fcbdceae8..4c9cfa569 100644
--- a/octopusdeploy/data_source_tenants.go
+++ b/octopusdeploy/data_source_tenants.go
@@ -44,7 +44,10 @@ func dataSourceTenantsRead(ctx context.Context, d *schema.ResourceData, meta int
flattenedTenants = append(flattenedTenants, flattenTenant(tenant))
}
- d.Set("tenants", flattenedTenants)
+ if err := d.Set("tenants", flattenedTenants); err != nil {
+ return diag.FromErr(err)
+ }
+
d.SetId("Tenants " + time.Now().UTC().String())
return nil
diff --git a/octopusdeploy/schema_account_resource.go b/octopusdeploy/schema_account_resource.go
index 5dbee9361..4a31f5017 100644
--- a/octopusdeploy/schema_account_resource.go
+++ b/octopusdeploy/schema_account_resource.go
@@ -50,6 +50,7 @@ func getAccountResourceDataSchema() map[string]*schema.Schema {
Description: "A list of accounts that match the filter(s).",
Elem: &schema.Resource{Schema: dataSchema},
Type: schema.TypeList,
+ Optional: false,
},
"id": getDataSchemaID(),
"space_id": getQuerySpaceID(),
diff --git a/octopusdeploy/schema_azure_cloud_service_deployment_target.go b/octopusdeploy/schema_azure_cloud_service_deployment_target.go
index 46488a934..45e00de20 100644
--- a/octopusdeploy/schema_azure_cloud_service_deployment_target.go
+++ b/octopusdeploy/schema_azure_cloud_service_deployment_target.go
@@ -70,7 +70,7 @@ func getAzureCloudServiceDeploymentTargetDataSchema() map[string]*schema.Schema
Computed: true,
Description: "A list of Azure cloud service deployment targets that match the filter(s).",
Elem: &schema.Resource{Schema: dataSchema},
- Optional: true,
+ Optional: false,
Type: schema.TypeList,
}
diff --git a/octopusdeploy/schema_azure_service_fabric_cluster_deployment_target.go b/octopusdeploy/schema_azure_service_fabric_cluster_deployment_target.go
index 3f8af8905..639d08553 100644
--- a/octopusdeploy/schema_azure_service_fabric_cluster_deployment_target.go
+++ b/octopusdeploy/schema_azure_service_fabric_cluster_deployment_target.go
@@ -85,7 +85,7 @@ func getAzureServiceFabricClusterDeploymentTargetDataSchema() map[string]*schema
Computed: true,
Description: "A list of Azure service fabric cluster deployment targets that match the filter(s).",
Elem: &schema.Resource{Schema: dataSchema},
- Optional: true,
+ Optional: false,
Type: schema.TypeList,
}
diff --git a/octopusdeploy/schema_azure_web_app_deployment_target.go b/octopusdeploy/schema_azure_web_app_deployment_target.go
index 8478378b6..f8b2cfe25 100644
--- a/octopusdeploy/schema_azure_web_app_deployment_target.go
+++ b/octopusdeploy/schema_azure_web_app_deployment_target.go
@@ -55,7 +55,7 @@ func getAzureWebAppDeploymentTargetDataSchema() map[string]*schema.Schema {
Computed: true,
Description: "A list of Azure web app deployment targets that match the filter(s).",
Elem: &schema.Resource{Schema: dataSchema},
- Optional: true,
+ Optional: false,
Type: schema.TypeList,
}
diff --git a/octopusdeploy/schema_certificate.go b/octopusdeploy/schema_certificate.go
index 4252f9899..2d35164c9 100644
--- a/octopusdeploy/schema_certificate.go
+++ b/octopusdeploy/schema_certificate.go
@@ -170,7 +170,7 @@ func getCertificateDataSchema() map[string]*schema.Schema {
Computed: true,
Description: "A list of certificates that match the filter(s).",
Elem: &schema.Resource{Schema: dataSchema},
- Optional: true,
+ Optional: false,
Type: schema.TypeList,
},
"first_result": getQueryFirstResult(),
diff --git a/octopusdeploy/schema_channel.go b/octopusdeploy/schema_channel.go
index 88aa0bda9..eed2eead6 100644
--- a/octopusdeploy/schema_channel.go
+++ b/octopusdeploy/schema_channel.go
@@ -73,7 +73,7 @@ func getChannelDataSchema() map[string]*schema.Schema {
Computed: true,
Description: "A channel that matches the specified filter(s).",
Elem: &schema.Resource{Schema: dataSchema},
- Optional: true,
+ Optional: false,
Type: schema.TypeList,
},
"ids": getQueryIDs(),
diff --git a/octopusdeploy/schema_cloud_region_deployment_target.go b/octopusdeploy/schema_cloud_region_deployment_target.go
index bce3dd18e..8e2375e07 100644
--- a/octopusdeploy/schema_cloud_region_deployment_target.go
+++ b/octopusdeploy/schema_cloud_region_deployment_target.go
@@ -40,7 +40,7 @@ func getCloudRegionDeploymentTargetDataSchema() map[string]*schema.Schema {
Computed: true,
Description: "A list of cloud region deployment targets that match the filter(s).",
Elem: &schema.Resource{Schema: dataSchema},
- Optional: true,
+ Optional: false,
Type: schema.TypeList,
}
diff --git a/octopusdeploy/schema_deployment_target.go b/octopusdeploy/schema_deployment_target.go
index 456265859..9649a7bec 100644
--- a/octopusdeploy/schema_deployment_target.go
+++ b/octopusdeploy/schema_deployment_target.go
@@ -90,7 +90,7 @@ func getDeploymentTargetDataSchema() map[string]*schema.Schema {
Computed: true,
Description: "A list of deployment targets that match the filter(s).",
Elem: &schema.Resource{Schema: dataSchema},
- Optional: true,
+ Optional: false,
Type: schema.TypeList,
},
"environments": getQueryEnvironments(),
diff --git a/octopusdeploy/schema_kubernetes_agent_deployment_target.go b/octopusdeploy/schema_kubernetes_agent_deployment_target.go
index 822e7dce8..2323f311b 100644
--- a/octopusdeploy/schema_kubernetes_agent_deployment_target.go
+++ b/octopusdeploy/schema_kubernetes_agent_deployment_target.go
@@ -173,7 +173,7 @@ func getKubernetesAgentDeploymentTargetDataSchema() map[string]*schema.Schema {
Computed: true,
Description: "A list of kubernetes agent deployment targets that match the filter(s).",
Elem: &schema.Resource{Schema: dataSchema},
- Optional: true,
+ Optional: false,
Type: schema.TypeList,
}
diff --git a/octopusdeploy/schema_kubernetes_cluster_deployment_target.go b/octopusdeploy/schema_kubernetes_cluster_deployment_target.go
index 6dcbd6a35..4704688ff 100644
--- a/octopusdeploy/schema_kubernetes_cluster_deployment_target.go
+++ b/octopusdeploy/schema_kubernetes_cluster_deployment_target.go
@@ -131,7 +131,7 @@ func getKubernetesClusterDeploymentTargetDataSchema() map[string]*schema.Schema
Computed: true,
Description: "A list of Kubernetes cluster deployment targets that match the filter(s).",
Elem: &schema.Resource{Schema: dataSchema},
- Optional: true,
+ Optional: false,
Type: schema.TypeList,
}
diff --git a/octopusdeploy/schema_listening_tentacle_deployment_target.go b/octopusdeploy/schema_listening_tentacle_deployment_target.go
index 32d2b3481..a35cdb8af 100644
--- a/octopusdeploy/schema_listening_tentacle_deployment_target.go
+++ b/octopusdeploy/schema_listening_tentacle_deployment_target.go
@@ -57,7 +57,7 @@ func getListeningTentacleDeploymentTargetDataSchema() map[string]*schema.Schema
Computed: true,
Description: "A list of listening tentacle deployment targets that match the filter(s).",
Elem: &schema.Resource{Schema: dataSchema},
- Optional: true,
+ Optional: false,
Type: schema.TypeList,
}
diff --git a/octopusdeploy/schema_machine_policy.go b/octopusdeploy/schema_machine_policy.go
index 537da34df..0d24b2f6c 100644
--- a/octopusdeploy/schema_machine_policy.go
+++ b/octopusdeploy/schema_machine_policy.go
@@ -112,7 +112,7 @@ func getMachinePolicyDataSchema() map[string]*schema.Schema {
Computed: true,
Description: "A list of machine policies that match the filter(s).",
Elem: &schema.Resource{Schema: dataSchema},
- Optional: true,
+ Optional: false,
Type: schema.TypeList,
},
"partial_name": getQueryPartialName(),
diff --git a/octopusdeploy/schema_offline_package_drop_deployment_target.go b/octopusdeploy/schema_offline_package_drop_deployment_target.go
index f8b88f704..371a08b01 100644
--- a/octopusdeploy/schema_offline_package_drop_deployment_target.go
+++ b/octopusdeploy/schema_offline_package_drop_deployment_target.go
@@ -51,7 +51,7 @@ func getOfflinePackageDropDeploymentTargetDataSchema() map[string]*schema.Schema
Computed: true,
Description: "A list of offline package drop deployment targets that match the filter(s).",
Elem: &schema.Resource{Schema: dataSchema},
- Optional: true,
+ Optional: false,
Type: schema.TypeList,
}
diff --git a/octopusdeploy/schema_polling_tentacle_deployment_target.go b/octopusdeploy/schema_polling_tentacle_deployment_target.go
index ab6472663..ee73b0cfb 100644
--- a/octopusdeploy/schema_polling_tentacle_deployment_target.go
+++ b/octopusdeploy/schema_polling_tentacle_deployment_target.go
@@ -51,7 +51,7 @@ func getPollingTentacleDeploymentTargetDataSchema() map[string]*schema.Schema {
Computed: true,
Description: "A list of polling tentacle deployment targets that match the filter(s).",
Elem: &schema.Resource{Schema: dataSchema},
- Optional: true,
+ Optional: false,
Type: schema.TypeList,
}
diff --git a/octopusdeploy/schema_script_modules.go b/octopusdeploy/schema_script_modules.go
index c4a24d1f8..906d2ea54 100644
--- a/octopusdeploy/schema_script_modules.go
+++ b/octopusdeploy/schema_script_modules.go
@@ -86,7 +86,7 @@ func getScriptModuleDataSchema() map[string]*schema.Schema {
Computed: true,
Description: "A list of script modules that match the filter(s).",
Elem: &schema.Resource{Schema: dataSchema},
- Optional: true,
+ Optional: false,
Type: schema.TypeList,
},
"partial_name": getQueryPartialName(),
diff --git a/octopusdeploy/schema_ssh_connection_deployment_target.go b/octopusdeploy/schema_ssh_connection_deployment_target.go
index 90934132f..df977c19d 100644
--- a/octopusdeploy/schema_ssh_connection_deployment_target.go
+++ b/octopusdeploy/schema_ssh_connection_deployment_target.go
@@ -57,7 +57,7 @@ func getSSHConnectionDeploymentTargetDataSchema() map[string]*schema.Schema {
Computed: true,
Description: "A list of SSH connection deployment targets that match the filter(s).",
Elem: &schema.Resource{Schema: dataSchema},
- Optional: true,
+ Optional: false,
Type: schema.TypeList,
}
diff --git a/octopusdeploy/schema_tag_set.go b/octopusdeploy/schema_tag_set.go
index db5c31a23..953b5c40c 100644
--- a/octopusdeploy/schema_tag_set.go
+++ b/octopusdeploy/schema_tag_set.go
@@ -54,7 +54,7 @@ func getTagSetDataSchema() map[string]*schema.Schema {
Computed: true,
Description: "A list of tag sets that match the filter(s).",
Elem: &schema.Resource{Schema: dataSchema},
- Optional: true,
+ Optional: false,
Type: schema.TypeList,
},
"take": getQueryTake(),
diff --git a/octopusdeploy/schema_team.go b/octopusdeploy/schema_team.go
index 3f782a1c9..d0656662e 100644
--- a/octopusdeploy/schema_team.go
+++ b/octopusdeploy/schema_team.go
@@ -85,7 +85,7 @@ func getTeamDataSchema() map[string]*schema.Schema {
Computed: true,
Description: "A list of teams that match the filter(s).",
Elem: &schema.Resource{Schema: dataSchema},
- Optional: true,
+ Optional: false,
Type: schema.TypeList,
},
}
diff --git a/octopusdeploy/schema_tenant.go b/octopusdeploy/schema_tenant.go
index 6045f5981..260ba93f8 100644
--- a/octopusdeploy/schema_tenant.go
+++ b/octopusdeploy/schema_tenant.go
@@ -67,7 +67,7 @@ func getTenantDataSchema() map[string]*schema.Schema {
Computed: true,
Description: "A list of tenants that match the filter(s).",
Elem: &schema.Resource{Schema: dataSchema},
- Optional: true,
+ Optional: false,
Type: schema.TypeList,
},
"take": getQueryTake(),
diff --git a/octopusdeploy/schema_user.go b/octopusdeploy/schema_user.go
index 5ef089c67..620c3db37 100644
--- a/octopusdeploy/schema_user.go
+++ b/octopusdeploy/schema_user.go
@@ -74,7 +74,7 @@ func getUserDataSchema() map[string]*schema.Schema {
Computed: true,
Description: "A list of users that match the filter(s).",
Elem: &schema.Resource{Schema: dataSchema},
- Optional: true,
+ Optional: false,
Type: schema.TypeList,
},
}
diff --git a/octopusdeploy/schema_user_role.go b/octopusdeploy/schema_user_role.go
index 9258402e5..4487a9170 100644
--- a/octopusdeploy/schema_user_role.go
+++ b/octopusdeploy/schema_user_role.go
@@ -84,7 +84,7 @@ func getUserRoleDataSchema() map[string]*schema.Schema {
Computed: true,
Description: "A list of user roles that match the filter(s).",
Elem: &schema.Resource{Schema: dataSchema},
- Optional: true,
+ Optional: false,
Type: schema.TypeList,
},
}
diff --git a/octopusdeploy/schema_worker_pool.go b/octopusdeploy/schema_worker_pool.go
index 9207124c2..036e2a04a 100644
--- a/octopusdeploy/schema_worker_pool.go
+++ b/octopusdeploy/schema_worker_pool.go
@@ -39,7 +39,7 @@ func getWorkerPoolDataSchema() map[string]*schema.Schema {
Computed: true,
Description: "A list of worker pools that match the filter(s).",
Elem: &schema.Resource{Schema: dataSchema},
- Optional: true,
+ Optional: false,
Type: schema.TypeList,
},
}
diff --git a/octopusdeploy_framework/schemas/gitCredential.go b/octopusdeploy_framework/schemas/gitCredential.go
index 2a29f1b4d..594f927b1 100644
--- a/octopusdeploy_framework/schemas/gitCredential.go
+++ b/octopusdeploy_framework/schemas/gitCredential.go
@@ -54,6 +54,7 @@ func GetGitCredentialDataSourceSchema() map[string]datasourceSchema.Attribute {
"take": util.GetQueryTakeDatasourceSchema(),
"git_credentials": datasourceSchema.ListNestedAttribute{
Computed: true,
+ Optional: false,
Description: "A list of Git Credentials that match the filter(s).",
NestedObject: datasourceSchema.NestedAttributeObject{
Attributes: GetGitCredentialAttributes(),
diff --git a/octopusdeploy_framework/schemas/lifecycle.go b/octopusdeploy_framework/schemas/lifecycle.go
index 092d48ab6..04f7f493a 100644
--- a/octopusdeploy_framework/schemas/lifecycle.go
+++ b/octopusdeploy_framework/schemas/lifecycle.go
@@ -97,6 +97,7 @@ func GetDatasourceLifecycleSchema() datasourceSchema.Schema {
"take": util.GetQueryTakeDatasourceSchema(),
"lifecycles": datasourceSchema.ListNestedAttribute{
Computed: true,
+ Optional: false,
NestedObject: datasourceSchema.NestedAttributeObject{
Attributes: map[string]datasourceSchema.Attribute{
"id": util.GetIdDatasourceSchema(),
diff --git a/octopusdeploy_framework/schemas/project.go b/octopusdeploy_framework/schemas/project.go
index fc4fee3b7..814710bda 100644
--- a/octopusdeploy_framework/schemas/project.go
+++ b/octopusdeploy_framework/schemas/project.go
@@ -193,6 +193,7 @@ func getProjectsDataSourceAttribute() datasourceSchema.ListNestedAttribute {
return datasourceSchema.ListNestedAttribute{
Description: "A list of projects that match the filter(s).",
Computed: true,
+ Optional: false,
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(),
From 7fade0508fa4f3279c9ef91caa3e97bb6c781464 Mon Sep 17 00:00:00 2001
From: Huy Nguyen <162080607+HuyPhanNguyen@users.noreply.github.com>
Date: Thu, 8 Aug 2024 14:25:25 +1000
Subject: [PATCH 05/28] Update schema and doc GitCredential, Lifecycles and
TenantProjectVariable (#717)
* Update lifecycles schema and doc
* Fix lifecyle schema
* Update git credential resource
* Refactor tenant project variable
* Make it consistent
* Update datasource GitCredentialModel
* fix test fail
* Add username_password_account resource
* Revert "Add username_password_account resource"
This reverts commit 7a20ecb5c8698d7f341c2437d5f841aa2cf179ae.
* Update doc example
* small change on lifecycle example
---
docs/data-sources/lifecycles.md | 70 ++++----
docs/resources/git_credential.md | 10 +-
docs/resources/lifecycle.md | 38 ++---
docs/resources/tenant_project_variable.md | 18 +-
.../datasource_git_credentials.go | 8 +-
.../schemas/gitCredential.go | 95 +++++------
octopusdeploy_framework/schemas/lifecycle.go | 155 +++++++++---------
.../schemas/tenant_project_variable.go | 44 +++--
.../util/resource_attribute_builder.go | 80 +++++++++
9 files changed, 307 insertions(+), 211 deletions(-)
diff --git a/docs/data-sources/lifecycles.md b/docs/data-sources/lifecycles.md
index 2d6ce478e..d7fb232f4 100644
--- a/docs/data-sources/lifecycles.md
+++ b/docs/data-sources/lifecycles.md
@@ -26,62 +26,62 @@ data "octopusdeploy_lifecycles" "example" {
### Optional
-- `ids` (List of String) A filter to search by a list of IDs.
-- `partial_name` (String) A filter to search by the partial match of a name.
+- `ids` (List of String) A list of lifecycle IDs to filter by.
+- `partial_name` (String) A partial name to filter lifecycles by.
- `skip` (Number) A filter to specify the number of items to skip in the response.
-- `space_id` (String) The space ID associated with this resource.
+- `space_id` (String) The space ID associated with this lifecycle.
- `take` (Number) A filter to specify the number of items to take (or return) in the response.
### Read-Only
-- `id` (String) The ID of this resource.
-- `lifecycles` (Block List) A list of lifecycles that match the filter(s). (see [below for nested schema](#nestedblock--lifecycles))
+- `id` (String) The ID of the lifecycle.
+- `lifecycles` (Attributes List) (see [below for nested schema](#nestedatt--lifecycles))
-
+
### Nested Schema for `lifecycles`
Read-Only:
-- `description` (String) The description of this lifecycle.
-- `id` (String) The unique ID for this resource.
-- `name` (String) The name of this resource.
-- `phase` (List of Object) (see [below for nested schema](#nestedatt--lifecycles--phase))
-- `release_retention_policy` (List of Object) (see [below for nested schema](#nestedatt--lifecycles--release_retention_policy))
-- `space_id` (String) The space ID associated with this resource.
-- `tentacle_retention_policy` (List of Object) (see [below for nested schema](#nestedatt--lifecycles--tentacle_retention_policy))
+- `description` (String) The description of the lifecycle.
+- `id` (String) The ID of the lifecycle.
+- `name` (String) The name of the lifecycle.
+- `phase` (Attributes List) (see [below for nested schema](#nestedatt--lifecycles--phase))
+- `release_retention_policy` (Attributes List) (see [below for nested schema](#nestedatt--lifecycles--release_retention_policy))
+- `space_id` (String) The space ID associated with this lifecycle.
+- `tentacle_retention_policy` (Attributes List) (see [below for nested schema](#nestedatt--lifecycles--tentacle_retention_policy))
### Nested Schema for `lifecycles.phase`
Read-Only:
-- `automatic_deployment_targets` (List of String)
-- `id` (String)
-- `is_optional_phase` (Boolean)
-- `minimum_environments_before_promotion` (Number)
-- `name` (String)
-- `optional_deployment_targets` (List of String)
-- `release_retention_policy` (List of Object) (see [below for nested schema](#nestedobjatt--lifecycles--phase--release_retention_policy))
-- `tentacle_retention_policy` (List of Object) (see [below for nested schema](#nestedobjatt--lifecycles--phase--tentacle_retention_policy))
+- `automatic_deployment_targets` (List of String) The automatic deployment targets for this phase.
+- `id` (String) The ID of the phase.
+- `is_optional_phase` (Boolean) Whether this phase is optional.
+- `minimum_environments_before_promotion` (Number) The minimum number of environments before promotion.
+- `name` (String) The name of the phase.
+- `optional_deployment_targets` (List of String) The optional deployment targets for this phase.
+- `release_retention_policy` (Attributes List) (see [below for nested schema](#nestedatt--lifecycles--phase--release_retention_policy))
+- `tentacle_retention_policy` (Attributes List) (see [below for nested schema](#nestedatt--lifecycles--phase--tentacle_retention_policy))
-
+
### Nested Schema for `lifecycles.phase.release_retention_policy`
Read-Only:
-- `quantity_to_keep` (Number)
-- `should_keep_forever` (Boolean)
-- `unit` (String)
+- `quantity_to_keep` (Number) The quantity of releases to keep.
+- `should_keep_forever` (Boolean) Whether releases should be kept forever.
+- `unit` (String) The unit of time for the retention policy.
-
+
### Nested Schema for `lifecycles.phase.tentacle_retention_policy`
Read-Only:
-- `quantity_to_keep` (Number)
-- `should_keep_forever` (Boolean)
-- `unit` (String)
+- `quantity_to_keep` (Number) The quantity of releases to keep.
+- `should_keep_forever` (Boolean) Whether releases should be kept forever.
+- `unit` (String) The unit of time for the retention policy.
@@ -90,9 +90,9 @@ Read-Only:
Read-Only:
-- `quantity_to_keep` (Number)
-- `should_keep_forever` (Boolean)
-- `unit` (String)
+- `quantity_to_keep` (Number) The quantity of releases to keep.
+- `should_keep_forever` (Boolean) Whether releases should be kept forever.
+- `unit` (String) The unit of time for the retention policy.
@@ -100,8 +100,8 @@ Read-Only:
Read-Only:
-- `quantity_to_keep` (Number)
-- `should_keep_forever` (Boolean)
-- `unit` (String)
+- `quantity_to_keep` (Number) The quantity of releases to keep.
+- `should_keep_forever` (Boolean) Whether releases should be kept forever.
+- `unit` (String) The unit of time for the retention policy.
diff --git a/docs/resources/git_credential.md b/docs/resources/git_credential.md
index e47402fa1..ad9aa4392 100644
--- a/docs/resources/git_credential.md
+++ b/docs/resources/git_credential.md
@@ -3,12 +3,12 @@
page_title: "octopusdeploy_git_credential Resource - terraform-provider-octopusdeploy"
subcategory: ""
description: |-
- This resource manages Git credentials in Octopus Deploy.
+ Manages a Git credential in Octopus Deploy.
---
# octopusdeploy_git_credential (Resource)
-This resource manages Git credentials in Octopus Deploy.
+Manages a Git credential in Octopus Deploy.
@@ -17,15 +17,15 @@ This resource manages Git credentials in Octopus Deploy.
### Required
-- `name` (String) The name of the Git credential. This name must be unique.
+- `name` (String) The name of this Git Credential.
- `password` (String, Sensitive) The password for the Git credential.
- `username` (String) The username for the Git credential.
### Optional
-- `description` (String) The description of this Git credential.
+- `description` (String) The description of this Git Credential.
- `id` (String) The unique ID for this resource.
-- `space_id` (String) The space ID associated with this resource.
+- `space_id` (String) The space ID associated with this Git Credential.
- `type` (String) The Git credential authentication type.
diff --git a/docs/resources/lifecycle.md b/docs/resources/lifecycle.md
index a367cce62..8714ab06e 100644
--- a/docs/resources/lifecycle.md
+++ b/docs/resources/lifecycle.md
@@ -33,8 +33,8 @@ resource "octopusdeploy_lifecycle" "example" {
name = "foo"
release_retention_policy {
- quantity_to_keep = 0
- should_keep_forever = true
+ quantity_to_keep = 0
+ should_keep_forever = true // true only if quantity_to_keep = 0
unit = "Days"
}
@@ -64,10 +64,10 @@ resource "octopusdeploy_lifecycle" "example" {
- `description` (String) The description of this lifecycle.
- `id` (String) The unique ID for this resource.
-- `phase` (Block List) (see [below for nested schema](#nestedblock--phase))
-- `release_retention_policy` (Block List, Max: 1) (see [below for nested schema](#nestedblock--release_retention_policy))
+- `phase` (Block List) Defines a phase in the lifecycle. (see [below for nested schema](#nestedblock--phase))
+- `release_retention_policy` (Block List) Defines the retention policy for releases or tentacles. (see [below for nested schema](#nestedblock--release_retention_policy))
- `space_id` (String) The space ID associated with this resource.
-- `tentacle_retention_policy` (Block List, Max: 1) (see [below for nested schema](#nestedblock--tentacle_retention_policy))
+- `tentacle_retention_policy` (Block List) Defines the retention policy for releases or tentacles. (see [below for nested schema](#nestedblock--tentacle_retention_policy))
### Nested Schema for `phase`
@@ -83,17 +83,17 @@ Optional:
- `is_optional_phase` (Boolean) If false a release must be deployed to this phase before it can be deployed to the next phase.
- `minimum_environments_before_promotion` (Number) The number of units required before a release can enter the next phase. If 0, all environments are required.
- `optional_deployment_targets` (List of String) Environment IDs in this phase that a release can be deployed to, but is not automatically deployed to
-- `release_retention_policy` (Block List, Max: 1) (see [below for nested schema](#nestedblock--phase--release_retention_policy))
-- `tentacle_retention_policy` (Block List, Max: 1) (see [below for nested schema](#nestedblock--phase--tentacle_retention_policy))
+- `release_retention_policy` (Block List) Defines the retention policy for releases or tentacles. (see [below for nested schema](#nestedblock--phase--release_retention_policy))
+- `tentacle_retention_policy` (Block List) Defines the retention policy for releases or tentacles. (see [below for nested schema](#nestedblock--phase--tentacle_retention_policy))
### Nested Schema for `phase.release_retention_policy`
Optional:
-- `quantity_to_keep` (Number) The number of days/releases to keep. The default value is `30`. If `0` then all are kept.
-- `should_keep_forever` (Boolean) Indicates if items should never be deleted. The default value is `false`.
-- `unit` (String) The unit of quantity to keep. Valid units are `Days` or `Items`. The default value is `Days`.
+- `quantity_to_keep` (Number) The number of days/releases to keep. The default value is 30. If 0 then all are kept.
+- `should_keep_forever` (Boolean) Indicates if items should never be deleted. The default value is false.
+- `unit` (String) The unit of quantity to keep. Valid units are Days or Items. The default value is Days.
@@ -101,9 +101,9 @@ Optional:
Optional:
-- `quantity_to_keep` (Number) The number of days/releases to keep. The default value is `30`. If `0` then all are kept.
-- `should_keep_forever` (Boolean) Indicates if items should never be deleted. The default value is `false`.
-- `unit` (String) The unit of quantity to keep. Valid units are `Days` or `Items`. The default value is `Days`.
+- `quantity_to_keep` (Number) The number of days/releases to keep. The default value is 30. If 0 then all are kept.
+- `should_keep_forever` (Boolean) Indicates if items should never be deleted. The default value is false.
+- `unit` (String) The unit of quantity to keep. Valid units are Days or Items. The default value is Days.
@@ -112,9 +112,9 @@ Optional:
Optional:
-- `quantity_to_keep` (Number) The number of days/releases to keep. The default value is `30`. If `0` then all are kept.
-- `should_keep_forever` (Boolean) Indicates if items should never be deleted. The default value is `false`.
-- `unit` (String) The unit of quantity to keep. Valid units are `Days` or `Items`. The default value is `Days`.
+- `quantity_to_keep` (Number) The number of days/releases to keep. The default value is 30. If 0 then all are kept.
+- `should_keep_forever` (Boolean) Indicates if items should never be deleted. The default value is false.
+- `unit` (String) The unit of quantity to keep. Valid units are Days or Items. The default value is Days.
@@ -122,9 +122,9 @@ Optional:
Optional:
-- `quantity_to_keep` (Number) The number of days/releases to keep. The default value is `30`. If `0` then all are kept.
-- `should_keep_forever` (Boolean) Indicates if items should never be deleted. The default value is `false`.
-- `unit` (String) The unit of quantity to keep. Valid units are `Days` or `Items`. The default value is `Days`.
+- `quantity_to_keep` (Number) The number of days/releases to keep. The default value is 30. If 0 then all are kept.
+- `should_keep_forever` (Boolean) Indicates if items should never be deleted. The default value is false.
+- `unit` (String) The unit of quantity to keep. Valid units are Days or Items. The default value is Days.
## Import
diff --git a/docs/resources/tenant_project_variable.md b/docs/resources/tenant_project_variable.md
index c27b1afae..053b6739a 100644
--- a/docs/resources/tenant_project_variable.md
+++ b/docs/resources/tenant_project_variable.md
@@ -3,12 +3,12 @@
page_title: "octopusdeploy_tenant_project_variable Resource - terraform-provider-octopusdeploy"
subcategory: ""
description: |-
- This resource manages tenant project variables in Octopus Deploy.
+ Manages a tenant project variable in Octopus Deploy.
---
# octopusdeploy_tenant_project_variable (Resource)
-This resource manages tenant project variables in Octopus Deploy.
+Manages a tenant project variable in Octopus Deploy.
@@ -17,18 +17,18 @@ This resource manages tenant project variables in Octopus Deploy.
### Required
-- `environment_id` (String)
-- `project_id` (String)
-- `template_id` (String)
-- `tenant_id` (String)
+- `environment_id` (String) The ID of the environment.
+- `project_id` (String) The ID of the project.
+- `template_id` (String) The ID of the variable template.
+- `tenant_id` (String) The ID of the tenant.
### Optional
-- `space_id` (String)
-- `value` (String, Sensitive)
+- `space_id` (String) The space ID associated with this Tenant Project Variable.
+- `value` (String, Sensitive) The value of the variable.
### Read-Only
-- `id` (String) The ID of this resource.
+- `id` (String) The unique ID for this resource.
diff --git a/octopusdeploy_framework/datasource_git_credentials.go b/octopusdeploy_framework/datasource_git_credentials.go
index 210c7d83b..5dc1592c3 100644
--- a/octopusdeploy_framework/datasource_git_credentials.go
+++ b/octopusdeploy_framework/datasource_git_credentials.go
@@ -8,7 +8,6 @@ import (
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource"
- "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"time"
)
@@ -35,6 +34,7 @@ type GitCredentialModel struct {
Description types.String `tfsdk:"description"`
Type types.String `tfsdk:"type"`
Username types.String `tfsdk:"username"`
+ Password types.String `tfsdk:"password"`
}
func NewGitCredentialsDataSource() datasource.DataSource {
@@ -46,10 +46,7 @@ func (g *gitCredentialsDataSource) Metadata(_ context.Context, req datasource.Me
}
func (g *gitCredentialsDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
- resp.Schema = schema.Schema{
- Description: "A list of Git Credentials that match the filter(s).",
- Attributes: schemas.GetGitCredentialDataSourceSchema(),
- }
+ resp.Schema = schemas.GetGitCredentialDataSourceSchema()
}
func (g *gitCredentialsDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
@@ -102,6 +99,7 @@ func GetGitCredentialAttrTypes() map[string]attr.Type {
"description": types.StringType,
"type": types.StringType,
"username": types.StringType,
+ "password": types.StringType,
}
}
diff --git a/octopusdeploy_framework/schemas/gitCredential.go b/octopusdeploy_framework/schemas/gitCredential.go
index 594f927b1..c4b0b4d6f 100644
--- a/octopusdeploy_framework/schemas/gitCredential.go
+++ b/octopusdeploy_framework/schemas/gitCredential.go
@@ -5,59 +5,55 @@ import (
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
datasourceSchema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
resourceSchema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
- "github.com/hashicorp/terraform-plugin-framework/schema/validator"
)
const (
- GitCredentialResourceDescription = "Git Credential"
- GitCredentialResourceName = "git_credential"
- GitCredentialDatasourceName = "git_credentials"
+ GitCredentialResourceName = "git_credential"
+ GitCredentialDatasourceName = "git_credentials"
)
func GetGitCredentialResourceSchema() resourceSchema.Schema {
return resourceSchema.Schema{
Description: "Manages a Git credential in Octopus Deploy.",
Attributes: map[string]resourceSchema.Attribute{
- "id": util.GetIdResourceSchema(),
- "space_id": util.GetSpaceIdResourceSchema(GitCredentialResourceDescription),
- "name": util.GetNameResourceSchema(true),
- "description": util.GetDescriptionResourceSchema(GitCredentialResourceDescription),
- "type": resourceSchema.StringAttribute{
- Optional: true,
- Description: "The Git credential authentication type.",
- },
- "username": resourceSchema.StringAttribute{
- Required: true,
- Description: "The username for the Git credential.",
- Validators: []validator.String{
- stringvalidator.LengthAtLeast(1),
- },
- },
- "password": resourceSchema.StringAttribute{
- Required: true,
- Sensitive: true,
- Description: "The password for the Git credential.",
- Validators: []validator.String{
- stringvalidator.LengthAtLeast(1),
- },
- },
+ "id": util.ResourceString().Optional().Computed().Description("The unique ID for this resource.").Build(),
+ "space_id": util.ResourceString().Optional().Computed().Description("The space ID associated with this Git Credential.").Build(),
+ "name": util.ResourceString().Required().Description("The name of this Git Credential.").Build(),
+ "description": util.ResourceString().Optional().Description("The description of this Git Credential.").Build(),
+ "type": util.ResourceString().
+ Optional().
+ Description("The Git credential authentication type.").
+ Build(),
+ "username": util.ResourceString().
+ Required().
+ Description("The username for the Git credential.").
+ Validators(stringvalidator.LengthAtLeast(1)).
+ Build(),
+ "password": util.ResourceString().
+ Required().
+ Sensitive().
+ Description("The password for the Git credential.").
+ Validators(stringvalidator.LengthAtLeast(1)).
+ Build(),
},
}
}
-func GetGitCredentialDataSourceSchema() map[string]datasourceSchema.Attribute {
- return map[string]datasourceSchema.Attribute{
- "id": util.GetIdDatasourceSchema(),
- "space_id": util.GetSpaceIdDatasourceSchema(GitCredentialResourceDescription),
- "name": util.GetQueryNameDatasourceSchema(),
- "skip": util.GetQuerySkipDatasourceSchema(),
- "take": util.GetQueryTakeDatasourceSchema(),
- "git_credentials": datasourceSchema.ListNestedAttribute{
- Computed: true,
- Optional: false,
- Description: "A list of Git Credentials that match the filter(s).",
- NestedObject: datasourceSchema.NestedAttributeObject{
- Attributes: GetGitCredentialAttributes(),
+func GetGitCredentialDataSourceSchema() datasourceSchema.Schema {
+ return datasourceSchema.Schema{
+ Description: "Use this data source to retrieve information about Git credentials in Octopus Deploy.",
+ Attributes: map[string]datasourceSchema.Attribute{
+ "id": util.DataSourceString().Computed().Description("The unique ID for this resource.").Build(),
+ "space_id": util.DataSourceString().Optional().Description("The space ID associated with this Git Credential.").Build(),
+ "name": util.DataSourceString().Optional().Description("The name of the Git Credential to filter by.").Build(),
+ "skip": util.DataSourceInt64().Optional().Description("The number of records to skip.").Build(),
+ "take": util.DataSourceInt64().Optional().Description("The number of records to take.").Build(),
+ "git_credentials": datasourceSchema.ListNestedAttribute{
+ Computed: true,
+ Description: "Provides information about existing GitCredentials.",
+ NestedObject: datasourceSchema.NestedAttributeObject{
+ Attributes: GetGitCredentialAttributes(),
+ },
},
},
}
@@ -65,17 +61,12 @@ func GetGitCredentialDataSourceSchema() map[string]datasourceSchema.Attribute {
func GetGitCredentialAttributes() map[string]datasourceSchema.Attribute {
return map[string]datasourceSchema.Attribute{
- "id": util.GetIdDatasourceSchema(),
- "space_id": util.GetSpaceIdDatasourceSchema(GitCredentialResourceDescription),
- "name": util.GetQueryNameDatasourceSchema(),
- "description": util.GetDescriptionDatasourceSchema(GitCredentialResourceDescription),
- "type": datasourceSchema.StringAttribute{
- Computed: true,
- Description: "The Git credential authentication type.",
- },
- "username": datasourceSchema.StringAttribute{
- Computed: true,
- Description: "The username for the Git credential.",
- },
+ "id": util.DataSourceString().Computed().Description("The unique ID for this resource.").Build(),
+ "space_id": util.DataSourceString().Computed().Description("The space ID associated with this Git Credential.").Build(),
+ "name": util.DataSourceString().Computed().Description("The name of this Git Credential.").Build(),
+ "description": util.DataSourceString().Computed().Description("The description of this Git Credential.").Build(),
+ "type": util.DataSourceString().Computed().Description("The Git credential authentication type.").Build(),
+ "username": util.DataSourceString().Computed().Description("The username for the Git credential.").Build(),
+ "password": util.DataSourceString().Computed().Sensitive().Description("The password for the Git credential.").Build(),
}
}
diff --git a/octopusdeploy_framework/schemas/lifecycle.go b/octopusdeploy_framework/schemas/lifecycle.go
index 04f7f493a..ba28f35b9 100644
--- a/octopusdeploy_framework/schemas/lifecycle.go
+++ b/octopusdeploy_framework/schemas/lifecycle.go
@@ -7,16 +7,18 @@ import (
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
)
func GetResourceLifecycleSchema() resourceSchema.Schema {
return resourceSchema.Schema{
+ Description: "This resource manages lifecycles in Octopus Deploy.",
Attributes: map[string]resourceSchema.Attribute{
- "id": util.GetIdResourceSchema(),
- "space_id": util.GetSpaceIdResourceSchema("lifecycle"),
- "name": util.GetNameResourceSchema(true),
- "description": util.GetDescriptionResourceSchema("lifecycle"),
+ "id": util.ResourceString().Optional().Computed().Description("The unique ID for this resource.").PlanModifiers(stringplanmodifier.UseStateForUnknown()).Build(),
+ "space_id": util.ResourceString().Optional().Computed().Description("The space ID associated with this resource.").PlanModifiers(stringplanmodifier.UseStateForUnknown()).Build(),
+ "name": util.ResourceString().Required().Description("The name of this resource.").Build(),
+ "description": util.ResourceString().Optional().Computed().Default("").Description("The description of this lifecycle.").Build(),
},
Blocks: map[string]resourceSchema.Block{
"phase": getResourcePhaseBlockSchema(),
@@ -28,30 +30,29 @@ func GetResourceLifecycleSchema() resourceSchema.Schema {
func getResourcePhaseBlockSchema() resourceSchema.ListNestedBlock {
return resourceSchema.ListNestedBlock{
+ Description: "Defines a phase in the lifecycle.",
NestedObject: resourceSchema.NestedBlockObject{
Attributes: map[string]resourceSchema.Attribute{
- "id": util.GetIdResourceSchema(),
- "name": util.GetNameResourceSchema(true),
- "automatic_deployment_targets": resourceSchema.ListAttribute{
- ElementType: types.StringType,
- Optional: true,
- Computed: true,
- },
- "optional_deployment_targets": resourceSchema.ListAttribute{
- ElementType: types.StringType,
- Optional: true,
- Computed: true,
- },
- "minimum_environments_before_promotion": resourceSchema.Int64Attribute{
- Optional: true,
- Computed: true,
- Default: int64default.StaticInt64(0),
- },
- "is_optional_phase": resourceSchema.BoolAttribute{
- Optional: true,
- Computed: true,
- Default: booldefault.StaticBool(false),
- },
+ "id": util.ResourceString().Optional().Computed().Description("The unique ID for this resource.").Build(),
+ "name": util.ResourceString().Required().Description("The name of this resource.").Build(),
+ "automatic_deployment_targets": util.ResourceList(types.StringType).
+ Optional().Computed().
+ Description("Environment IDs in this phase that a release is automatically deployed to when it is eligible for this phase").
+ Build(),
+ "optional_deployment_targets": util.ResourceList(types.StringType).
+ Optional().Computed().
+ Description("Environment IDs in this phase that a release can be deployed to, but is not automatically deployed to").
+ Build(),
+ "minimum_environments_before_promotion": util.ResourceInt64().
+ Optional().Computed().
+ Default(int64default.StaticInt64(0)).
+ Description("The number of units required before a release can enter the next phase. If 0, all environments are required.").
+ Build(),
+ "is_optional_phase": util.ResourceBool().
+ Optional().Computed().
+ Default(booldefault.StaticBool(false)).
+ Description("If false a release must be deployed to this phase before it can be deployed to the next phase.").
+ Build(),
},
Blocks: map[string]resourceSchema.Block{
"release_retention_policy": getResourceRetentionPolicyBlockSchema(),
@@ -63,83 +64,87 @@ func getResourcePhaseBlockSchema() resourceSchema.ListNestedBlock {
func getResourceRetentionPolicyBlockSchema() resourceSchema.ListNestedBlock {
return resourceSchema.ListNestedBlock{
+ Description: "Defines the retention policy for releases or tentacles.",
NestedObject: resourceSchema.NestedBlockObject{
Attributes: map[string]resourceSchema.Attribute{
- "quantity_to_keep": resourceSchema.Int64Attribute{
- Optional: true,
- Computed: true,
- Default: int64default.StaticInt64(30),
- },
- "should_keep_forever": resourceSchema.BoolAttribute{
- Optional: true,
- Computed: true,
- Default: booldefault.StaticBool(false),
- },
- "unit": resourceSchema.StringAttribute{
- Optional: true,
- Computed: true,
- Default: stringdefault.StaticString("Days"),
- },
+ "quantity_to_keep": util.ResourceInt64().
+ Optional().Computed().
+ Default(int64default.StaticInt64(30)).
+ Description("The number of days/releases to keep. The default value is 30. If 0 then all are kept.").
+ Build(),
+ "should_keep_forever": util.ResourceBool().
+ Optional().Computed().
+ Default(booldefault.StaticBool(false)).
+ Description("Indicates if items should never be deleted. The default value is false.").
+ Build(),
+ "unit": util.ResourceString().
+ Optional().Computed().
+ Default(stringdefault.StaticString("Days")).
+ Description("The unit of quantity to keep. Valid units are Days or Items. The default value is Days.").
+ Build(),
},
},
}
}
func GetDatasourceLifecycleSchema() datasourceSchema.Schema {
- description := "lifecycle"
return datasourceSchema.Schema{
+ Description: "Provides information about existing lifecycles.",
Attributes: map[string]datasourceSchema.Attribute{
- "id": util.GetIdDatasourceSchema(),
- "space_id": util.GetSpaceIdDatasourceSchema(description),
- "ids": util.GetQueryIDsDatasourceSchema(),
- "partial_name": util.GetQueryPartialNameDatasourceSchema(),
- "skip": util.GetQuerySkipDatasourceSchema(),
- "take": util.GetQueryTakeDatasourceSchema(),
- "lifecycles": datasourceSchema.ListNestedAttribute{
- Computed: true,
- Optional: false,
- NestedObject: datasourceSchema.NestedAttributeObject{
- Attributes: map[string]datasourceSchema.Attribute{
- "id": util.GetIdDatasourceSchema(),
- "space_id": util.GetSpaceIdDatasourceSchema(description),
- "name": util.GetNameDatasourceSchema(true),
- "description": util.GetDescriptionDatasourceSchema(description),
- "phase": getDatasourcePhasesSchema(),
- "release_retention_policy": getDatasourceRetentionPolicySchema(),
- "tentacle_retention_policy": getDatasourceRetentionPolicySchema(),
- },
- },
+ "id": util.DataSourceString().Computed().Description("The ID of the lifecycle.").Build(),
+ "space_id": util.DataSourceString().Optional().Description("The space ID associated with this lifecycle.").Build(),
+ "ids": util.DataSourceList(types.StringType).Optional().Description("A list of lifecycle IDs to filter by.").Build(),
+ "partial_name": util.DataSourceString().Optional().Description("A partial name to filter lifecycles by.").Build(),
+ "skip": util.DataSourceInt64().Optional().Description("A filter to specify the number of items to skip in the response.").Build(),
+ "take": util.DataSourceInt64().Optional().Description("A filter to specify the number of items to take (or return) in the response.").Build(),
+ "lifecycles": getLifecyclesAttribute(),
+ },
+ }
+}
+
+func getLifecyclesAttribute() datasourceSchema.ListNestedAttribute {
+ return datasourceSchema.ListNestedAttribute{
+ Computed: true,
+ NestedObject: datasourceSchema.NestedAttributeObject{
+ Attributes: map[string]datasourceSchema.Attribute{
+ "id": util.DataSourceString().Computed().Description("The ID of the lifecycle.").Build(),
+ "space_id": util.DataSourceString().Computed().Description("The space ID associated with this lifecycle.").Build(),
+ "name": util.DataSourceString().Computed().Description("The name of the lifecycle.").Build(),
+ "description": util.DataSourceString().Computed().Description("The description of the lifecycle.").Build(),
+ "phase": getPhasesAttribute(),
+ "release_retention_policy": getRetentionPolicyAttribute(),
+ "tentacle_retention_policy": getRetentionPolicyAttribute(),
},
},
}
}
-func getDatasourcePhasesSchema() datasourceSchema.ListNestedAttribute {
+func getPhasesAttribute() datasourceSchema.ListNestedAttribute {
return datasourceSchema.ListNestedAttribute{
Computed: true,
NestedObject: datasourceSchema.NestedAttributeObject{
Attributes: map[string]datasourceSchema.Attribute{
- "id": util.GetIdDatasourceSchema(),
- "name": util.GetNameDatasourceSchema(true),
- "automatic_deployment_targets": datasourceSchema.ListAttribute{ElementType: types.StringType, Computed: true},
- "optional_deployment_targets": datasourceSchema.ListAttribute{ElementType: types.StringType, Computed: true},
- "minimum_environments_before_promotion": datasourceSchema.Int64Attribute{Computed: true},
- "is_optional_phase": datasourceSchema.BoolAttribute{Computed: true},
- "release_retention_policy": getDatasourceRetentionPolicySchema(),
- "tentacle_retention_policy": getDatasourceRetentionPolicySchema(),
+ "id": util.DataSourceString().Computed().Description("The ID of the phase.").Build(),
+ "name": util.DataSourceString().Computed().Description("The name of the phase.").Build(),
+ "automatic_deployment_targets": util.DataSourceList(types.StringType).Computed().Description("The automatic deployment targets for this phase.").Build(),
+ "optional_deployment_targets": util.DataSourceList(types.StringType).Computed().Description("The optional deployment targets for this phase.").Build(),
+ "minimum_environments_before_promotion": util.DataSourceInt64().Computed().Description("The minimum number of environments before promotion.").Build(),
+ "is_optional_phase": util.DataSourceBool().Computed().Description("Whether this phase is optional.").Build(),
+ "release_retention_policy": getRetentionPolicyAttribute(),
+ "tentacle_retention_policy": getRetentionPolicyAttribute(),
},
},
}
}
-func getDatasourceRetentionPolicySchema() datasourceSchema.ListNestedAttribute {
+func getRetentionPolicyAttribute() datasourceSchema.ListNestedAttribute {
return datasourceSchema.ListNestedAttribute{
Computed: true,
NestedObject: datasourceSchema.NestedAttributeObject{
Attributes: map[string]datasourceSchema.Attribute{
- "quantity_to_keep": datasourceSchema.Int64Attribute{Computed: true},
- "should_keep_forever": datasourceSchema.BoolAttribute{Computed: true},
- "unit": datasourceSchema.StringAttribute{Computed: true},
+ "quantity_to_keep": util.DataSourceInt64().Computed().Description("The quantity of releases to keep.").Build(),
+ "should_keep_forever": util.DataSourceBool().Computed().Description("Whether releases should be kept forever.").Build(),
+ "unit": util.DataSourceString().Computed().Description("The unit of time for the retention policy.").Build(),
},
},
}
diff --git a/octopusdeploy_framework/schemas/tenant_project_variable.go b/octopusdeploy_framework/schemas/tenant_project_variable.go
index 99553e7c6..4e681a99e 100644
--- a/octopusdeploy_framework/schemas/tenant_project_variable.go
+++ b/octopusdeploy_framework/schemas/tenant_project_variable.go
@@ -3,6 +3,7 @@ package schemas
import (
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
)
const (
@@ -14,17 +15,38 @@ func GetTenantProjectVariableResourceSchema() schema.Schema {
return schema.Schema{
Description: "Manages a tenant project variable in Octopus Deploy.",
Attributes: map[string]schema.Attribute{
- "id": util.GetIdResourceSchema(),
- "space_id": util.GetSpaceIdResourceSchema(TenantProjectVariableResourceDescription),
- "tenant_id": util.GetRequiredStringResourceSchema("The ID of the tenant."),
- "project_id": util.GetRequiredStringResourceSchema("The ID of the project."),
- "environment_id": util.GetRequiredStringResourceSchema("The ID of the environment."),
- "template_id": util.GetRequiredStringResourceSchema("The ID of the variable template."),
- "value": schema.StringAttribute{
- Required: true,
- Description: "The value of the variable.",
- Sensitive: true,
- },
+ "id": util.ResourceString().
+ Computed().
+ Description("The unique ID for this resource.").
+ PlanModifiers(stringplanmodifier.UseStateForUnknown()).
+ Build(),
+ "space_id": util.ResourceString().
+ Optional().
+ Computed().
+ Description("The space ID associated with this Tenant Project Variable.").
+ PlanModifiers(stringplanmodifier.UseStateForUnknown()).
+ Build(),
+ "tenant_id": util.ResourceString().
+ Required().
+ Description("The ID of the tenant.").
+ Build(),
+ "project_id": util.ResourceString().
+ Required().
+ Description("The ID of the project.").
+ Build(),
+ "environment_id": util.ResourceString().
+ Required().
+ Description("The ID of the environment.").
+ Build(),
+ "template_id": util.ResourceString().
+ Required().
+ Description("The ID of the variable template.").
+ Build(),
+ "value": util.ResourceString().
+ Optional().
+ Sensitive().
+ Description("The value of the variable.").
+ Build(),
},
}
}
diff --git a/octopusdeploy_framework/util/resource_attribute_builder.go b/octopusdeploy_framework/util/resource_attribute_builder.go
index 153cafbf7..908f07929 100644
--- a/octopusdeploy_framework/util/resource_attribute_builder.go
+++ b/octopusdeploy_framework/util/resource_attribute_builder.go
@@ -8,8 +8,10 @@ import (
"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/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
)
@@ -217,6 +219,84 @@ func (b *AttributeBuilder[T]) Build() T {
return b.attr
}
+func (b *AttributeBuilder[T]) PlanModifiers(modifiers ...any) *AttributeBuilder[T] {
+ switch a := any(&b.attr).(type) {
+ case *schema.StringAttribute:
+ if stringModifiers, ok := convertToTypedSlice[planmodifier.String](modifiers); ok {
+ a.PlanModifiers = append(a.PlanModifiers, stringModifiers...)
+ }
+ case *schema.BoolAttribute:
+ if boolModifiers, ok := convertToTypedSlice[planmodifier.Bool](modifiers); ok {
+ a.PlanModifiers = append(a.PlanModifiers, boolModifiers...)
+ }
+ case *schema.Int64Attribute:
+ if int64Modifiers, ok := convertToTypedSlice[planmodifier.Int64](modifiers); ok {
+ a.PlanModifiers = append(a.PlanModifiers, int64Modifiers...)
+ }
+ case *schema.Float64Attribute:
+ if float64Modifiers, ok := convertToTypedSlice[planmodifier.Float64](modifiers); ok {
+ a.PlanModifiers = append(a.PlanModifiers, float64Modifiers...)
+ }
+ case *schema.ListAttribute:
+ if listModifiers, ok := convertToTypedSlice[planmodifier.List](modifiers); ok {
+ a.PlanModifiers = append(a.PlanModifiers, listModifiers...)
+ }
+ case *schema.SetAttribute:
+ if setModifiers, ok := convertToTypedSlice[planmodifier.Set](modifiers); ok {
+ a.PlanModifiers = append(a.PlanModifiers, setModifiers...)
+ }
+ case *schema.MapAttribute:
+ if mapModifiers, ok := convertToTypedSlice[planmodifier.Map](modifiers); ok {
+ a.PlanModifiers = append(a.PlanModifiers, mapModifiers...)
+ }
+ }
+ return b
+}
+func (b *AttributeBuilder[T]) Validators(validators ...any) *AttributeBuilder[T] {
+ switch a := any(&b.attr).(type) {
+ case *schema.StringAttribute:
+ if stringValidators, ok := convertToTypedSlice[validator.String](validators); ok {
+ a.Validators = append(a.Validators, stringValidators...)
+ }
+ case *schema.BoolAttribute:
+ if boolValidators, ok := convertToTypedSlice[validator.Bool](validators); ok {
+ a.Validators = append(a.Validators, boolValidators...)
+ }
+ case *schema.Int64Attribute:
+ if int64Validators, ok := convertToTypedSlice[validator.Int64](validators); ok {
+ a.Validators = append(a.Validators, int64Validators...)
+ }
+ case *schema.Float64Attribute:
+ if float64Validators, ok := convertToTypedSlice[validator.Float64](validators); ok {
+ a.Validators = append(a.Validators, float64Validators...)
+ }
+ case *schema.ListAttribute:
+ if listValidators, ok := convertToTypedSlice[validator.List](validators); ok {
+ a.Validators = append(a.Validators, listValidators...)
+ }
+ case *schema.SetAttribute:
+ if setValidators, ok := convertToTypedSlice[validator.Set](validators); ok {
+ a.Validators = append(a.Validators, setValidators...)
+ }
+ case *schema.MapAttribute:
+ if mapValidators, ok := convertToTypedSlice[validator.Map](validators); ok {
+ a.Validators = append(a.Validators, mapValidators...)
+ }
+ }
+ return b
+}
+
+func convertToTypedSlice[T any](slice []any) ([]T, bool) {
+ typedSlice := make([]T, 0, len(slice))
+ for _, item := range slice {
+ if typed, ok := item.(T); ok {
+ typedSlice = append(typedSlice, typed)
+ } else {
+ return nil, false
+ }
+ }
+ return typedSlice, true
+}
func ResourceString() *AttributeBuilder[schema.StringAttribute] {
return NewAttributeBuilder[schema.StringAttribute]()
}
From b1a1cd2f7f0d7638e05cd8b54a1bb76f6d8ae6cb Mon Sep 17 00:00:00 2001
From: Henrik Andersson
Date: Wed, 7 Aug 2024 22:38:06 -0700
Subject: [PATCH 06/28] chore: ensure resources deleted from Octopus are
removed from state (#720)
---
go.mod | 2 +-
go.sum | 2 ++
internal/errors/error.go | 22 +++++++++++++++++++
.../resource_artifactory_generic_feed.go | 6 ++++-
...resource_aws_elastic_container_registry.go | 6 ++++-
.../resource_docker_container_registry.go | 6 ++++-
.../resource_environment.go | 6 ++++-
.../resource_git_credential.go | 9 ++++++--
.../resource_github_repository_feed.go | 6 ++++-
octopusdeploy_framework/resource_helm_feed.go | 6 ++++-
.../resource_library_variable_set.go | 8 +++++--
octopusdeploy_framework/resource_lifecycle.go | 17 +++++++++-----
.../resource_lifecycle_test.go | 7 +++---
.../resource_maven_feed.go | 6 ++++-
.../resource_nuget_feed.go | 6 ++++-
octopusdeploy_framework/resource_project.go | 6 ++++-
.../resource_project_flatten.go | 4 +++-
.../resource_project_group.go | 6 ++++-
.../resource_project_model.go | 4 +++-
octopusdeploy_framework/resource_space.go | 8 +++++--
.../resource_tenant_common_variable.go | 3 ++-
.../resource_tenant_project.go | 3 ++-
.../resource_tenant_project_variable.go | 6 +++--
octopusdeploy_framework/resource_variable.go | 5 ++++-
.../schemas/artifactory_generic_feed.go | 3 ++-
.../schemas/aws_elastic_container_registry.go | 3 ++-
.../schemas/docker_container_registry_feed.go | 3 ++-
.../schemas/environment.go | 3 ++-
.../schemas/github_repository_feed.go | 3 ++-
octopusdeploy_framework/schemas/helm_feed.go | 3 ++-
.../schemas/library_variable_set.go | 3 ++-
octopusdeploy_framework/schemas/maven_feed.go | 3 ++-
octopusdeploy_framework/schemas/nuget_feed.go | 3 ++-
.../schemas/project_group.go | 3 ++-
octopusdeploy_framework/schemas/resource.go | 19 ++++++++++++++++
octopusdeploy_framework/schemas/space.go | 3 ++-
octopusdeploy_framework/schemas/variable.go | 6 +++--
37 files changed, 173 insertions(+), 45 deletions(-)
create mode 100644 octopusdeploy_framework/schemas/resource.go
diff --git a/go.mod b/go.mod
index 2cad3a7e8..4cc35d04a 100644
--- a/go.mod
+++ b/go.mod
@@ -8,7 +8,7 @@ require (
github.com/google/uuid v1.6.0
github.com/hashicorp/go-cty v1.4.1-0.20200723130312-85980079f637
github.com/hashicorp/terraform-plugin-docs v0.13.0
- github.com/hashicorp/terraform-plugin-framework v1.9.0
+ github.com/hashicorp/terraform-plugin-framework v1.11.0
github.com/hashicorp/terraform-plugin-framework-validators v0.12.0
github.com/hashicorp/terraform-plugin-go v0.23.0
github.com/hashicorp/terraform-plugin-log v0.9.0
diff --git a/go.sum b/go.sum
index fd5a915a5..c917536b0 100644
--- a/go.sum
+++ b/go.sum
@@ -166,6 +166,8 @@ github.com/hashicorp/terraform-plugin-docs v0.13.0 h1:6e+VIWsVGb6jYJewfzq2ok2smP
github.com/hashicorp/terraform-plugin-docs v0.13.0/go.mod h1:W0oCmHAjIlTHBbvtppWHe8fLfZ2BznQbuv8+UD8OucQ=
github.com/hashicorp/terraform-plugin-framework v1.9.0 h1:caLcDoxiRucNi2hk8+j3kJwkKfvHznubyFsJMWfZqKU=
github.com/hashicorp/terraform-plugin-framework v1.9.0/go.mod h1:qBXLDn69kM97NNVi/MQ9qgd1uWWsVftGSnygYG1tImM=
+github.com/hashicorp/terraform-plugin-framework v1.11.0 h1:M7+9zBArexHFXDx/pKTxjE6n/2UCXY6b8FIq9ZYhwfE=
+github.com/hashicorp/terraform-plugin-framework v1.11.0/go.mod h1:qBXLDn69kM97NNVi/MQ9qgd1uWWsVftGSnygYG1tImM=
github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 h1:HOjBuMbOEzl7snOdOoUfE2Jgeto6JOjLVQ39Ls2nksc=
github.com/hashicorp/terraform-plugin-framework-validators v0.12.0/go.mod h1:jfHGE/gzjxYz6XoUwi/aYiiKrJDeutQNUtGQXkaHklg=
github.com/hashicorp/terraform-plugin-go v0.23.0 h1:AALVuU1gD1kPb48aPQUjug9Ir/125t+AAurhqphJ2Co=
diff --git a/internal/errors/error.go b/internal/errors/error.go
index 21c2d4f74..733b46675 100644
--- a/internal/errors/error.go
+++ b/internal/errors/error.go
@@ -6,6 +6,8 @@ import (
"net/http"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
@@ -29,3 +31,23 @@ func ProcessApiError(ctx context.Context, d *schema.ResourceData, err error, res
return diag.FromErr(err)
}
+
+func DeleteFromStateV2(ctx context.Context, resp *resource.ReadResponse, resource schemas.IResourceModel, resourceDescription string) error {
+ log.Printf("[INFO] %s (%s) not found; deleting from state", resourceDescription, resource.GetID())
+ resp.State.RemoveResource(ctx)
+ return nil
+}
+
+func ProcessApiErrorV2(ctx context.Context, resp *resource.ReadResponse, resource schemas.IResourceModel, err error, resourceDescription string) error {
+ if err == nil {
+ return nil
+ }
+
+ if apiError, ok := err.(*core.APIError); ok {
+ if apiError.StatusCode == http.StatusNotFound {
+ return DeleteFromStateV2(ctx, resp, resource, resourceDescription)
+ }
+ }
+
+ return nil
+}
diff --git a/octopusdeploy_framework/resource_artifactory_generic_feed.go b/octopusdeploy_framework/resource_artifactory_generic_feed.go
index a1eb7ab58..b6c765d51 100644
--- a/octopusdeploy_framework/resource_artifactory_generic_feed.go
+++ b/octopusdeploy_framework/resource_artifactory_generic_feed.go
@@ -3,7 +3,9 @@ package octopusdeploy_framework
import (
"context"
"fmt"
+
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
"github.com/hashicorp/terraform-plugin-framework/attr"
@@ -77,7 +79,9 @@ func (r *artifactoryGenericFeedTypeResource) Read(ctx context.Context, req resou
client := r.Config.Client
feed, err := feeds.GetByID(client, data.SpaceID.ValueString(), data.ID.ValueString())
if err != nil {
- resp.Diagnostics.AddError("unable to load artifactoryGeneric feed", err.Error())
+ if err := errors.ProcessApiErrorV2(ctx, resp, data, err, "artifactory generic feed"); err != nil {
+ resp.Diagnostics.AddError("unable to load artifactoryGeneric feed", err.Error())
+ }
return
}
diff --git a/octopusdeploy_framework/resource_aws_elastic_container_registry.go b/octopusdeploy_framework/resource_aws_elastic_container_registry.go
index 72cef3b51..daeb36f31 100644
--- a/octopusdeploy_framework/resource_aws_elastic_container_registry.go
+++ b/octopusdeploy_framework/resource_aws_elastic_container_registry.go
@@ -3,8 +3,10 @@ package octopusdeploy_framework
import (
"context"
"fmt"
+
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/feeds"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
"github.com/hashicorp/terraform-plugin-framework/attr"
@@ -78,7 +80,9 @@ func (r *awsElasticContainerRegistryFeedTypeResource) Read(ctx context.Context,
client := r.Config.Client
feed, err := feeds.GetByID(client, data.SpaceID.ValueString(), data.ID.ValueString())
if err != nil {
- resp.Diagnostics.AddError("unable to load aws elastic container registry", err.Error())
+ if err := errors.ProcessApiErrorV2(ctx, resp, data, err, "aws elastic container registry"); err != nil {
+ resp.Diagnostics.AddError("unable to load aws elastic container registry", err.Error())
+ }
return
}
diff --git a/octopusdeploy_framework/resource_docker_container_registry.go b/octopusdeploy_framework/resource_docker_container_registry.go
index fa73ef902..0b246633e 100644
--- a/octopusdeploy_framework/resource_docker_container_registry.go
+++ b/octopusdeploy_framework/resource_docker_container_registry.go
@@ -3,8 +3,10 @@ package octopusdeploy_framework
import (
"context"
"fmt"
+
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/feeds"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
"github.com/hashicorp/terraform-plugin-framework/attr"
@@ -76,7 +78,9 @@ func (r *dockerContainerRegistryFeedTypeResource) Read(ctx context.Context, req
client := r.Config.Client
feed, err := feeds.GetByID(client, data.SpaceID.ValueString(), data.ID.ValueString())
if err != nil {
- resp.Diagnostics.AddError("unable to load docker container registry feed", err.Error())
+ if err := errors.ProcessApiErrorV2(ctx, resp, data, err, "docker container registry feed"); err != nil {
+ resp.Diagnostics.AddError("unable to load docker container registry feed", err.Error())
+ }
return
}
diff --git a/octopusdeploy_framework/resource_environment.go b/octopusdeploy_framework/resource_environment.go
index a393279ad..281639f6d 100644
--- a/octopusdeploy_framework/resource_environment.go
+++ b/octopusdeploy_framework/resource_environment.go
@@ -5,6 +5,7 @@ import (
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/environments"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/extensions"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
"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"
@@ -82,7 +83,10 @@ func (r *environmentTypeResource) Read(ctx context.Context, req resource.ReadReq
environment, err := environments.GetByID(r.Config.Client, data.SpaceID.ValueString(), data.ID.ValueString())
if err != nil {
- resp.Diagnostics.AddError("unable to load environment", err.Error())
+ if err := errors.ProcessApiErrorV2(ctx, resp, data, err, "environment"); err != nil {
+ resp.Diagnostics.AddError("unable to load environment", err.Error())
+ }
+ return
}
updateEnvironment(ctx, &data, environment)
diff --git a/octopusdeploy_framework/resource_git_credential.go b/octopusdeploy_framework/resource_git_credential.go
index 478b4f8b4..8a5baeec1 100644
--- a/octopusdeploy_framework/resource_git_credential.go
+++ b/octopusdeploy_framework/resource_git_credential.go
@@ -2,8 +2,10 @@ package octopusdeploy_framework
import (
"context"
+
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/credentials"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
"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"
@@ -18,13 +20,14 @@ type gitCredentialResource struct {
}
type gitCredentialResourceModel struct {
- ID types.String `tfsdk:"id"`
SpaceID types.String `tfsdk:"space_id"`
Name types.String `tfsdk:"name"`
Description types.String `tfsdk:"description"`
Type types.String `tfsdk:"type"`
Username types.String `tfsdk:"username"`
Password types.String `tfsdk:"password"`
+
+ schemas.ResourceModel
}
func NewGitCredentialResource() resource.Resource {
@@ -92,7 +95,9 @@ func (g *gitCredentialResource) Read(ctx context.Context, req resource.ReadReque
gitCredential, err := credentials.GetByID(g.Client, state.SpaceID.ValueString(), state.ID.ValueString())
if err != nil {
- resp.Diagnostics.AddError("Error reading Git credential", err.Error())
+ if err := errors.ProcessApiErrorV2(ctx, resp, state, err, "git credential"); err != nil {
+ resp.Diagnostics.AddError("Error reading Git credential", err.Error())
+ }
return
}
diff --git a/octopusdeploy_framework/resource_github_repository_feed.go b/octopusdeploy_framework/resource_github_repository_feed.go
index 30f7bdb28..6b6aabb63 100644
--- a/octopusdeploy_framework/resource_github_repository_feed.go
+++ b/octopusdeploy_framework/resource_github_repository_feed.go
@@ -3,8 +3,10 @@ package octopusdeploy_framework
import (
"context"
"fmt"
+
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/feeds"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
"github.com/hashicorp/terraform-plugin-framework/attr"
@@ -78,7 +80,9 @@ func (r *githubRepositoryFeedTypeResource) Read(ctx context.Context, req resourc
client := r.Config.Client
feed, err := feeds.GetByID(client, data.SpaceID.ValueString(), data.ID.ValueString())
if err != nil {
- resp.Diagnostics.AddError("unable to load github repository feed", err.Error())
+ if err := errors.ProcessApiErrorV2(ctx, resp, data, err, "github repository feed"); err != nil {
+ resp.Diagnostics.AddError("unable to load github repository feed", err.Error())
+ }
return
}
diff --git a/octopusdeploy_framework/resource_helm_feed.go b/octopusdeploy_framework/resource_helm_feed.go
index 64ec52677..dd7667eff 100644
--- a/octopusdeploy_framework/resource_helm_feed.go
+++ b/octopusdeploy_framework/resource_helm_feed.go
@@ -3,7 +3,9 @@ package octopusdeploy_framework
import (
"context"
"fmt"
+
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
"github.com/hashicorp/terraform-plugin-framework/attr"
@@ -77,7 +79,9 @@ func (r *helmFeedTypeResource) Read(ctx context.Context, req resource.ReadReques
client := r.Config.Client
feed, err := feeds.GetByID(client, data.SpaceID.ValueString(), data.ID.ValueString())
if err != nil {
- resp.Diagnostics.AddError("unable to load helm feed", err.Error())
+ if err := errors.ProcessApiErrorV2(ctx, resp, data, err, "helm feed"); err != nil {
+ resp.Diagnostics.AddError("unable to load helm feed", err.Error())
+ }
return
}
diff --git a/octopusdeploy_framework/resource_library_variable_set.go b/octopusdeploy_framework/resource_library_variable_set.go
index 733b88664..cdf616aa0 100644
--- a/octopusdeploy_framework/resource_library_variable_set.go
+++ b/octopusdeploy_framework/resource_library_variable_set.go
@@ -3,12 +3,14 @@ package octopusdeploy_framework
import (
"context"
"fmt"
+ "log"
+
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/libraryvariablesets"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
"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-log/tflog"
- "log"
)
type libraryVariableSetFeedTypeResource struct {
@@ -61,7 +63,9 @@ func (r *libraryVariableSetFeedTypeResource) Read(ctx context.Context, req resou
libraryVariableSet, err := libraryvariablesets.GetByID(r.Config.Client, data.SpaceID.ValueString(), data.ID.ValueString())
if err != nil {
- resp.Diagnostics.AddError("unable to load library variable set", err.Error())
+ if err := errors.ProcessApiErrorV2(ctx, resp, data, err, "library variable set"); err != nil {
+ resp.Diagnostics.AddError("unable to load library variable set", err.Error())
+ }
return
}
diff --git a/octopusdeploy_framework/resource_lifecycle.go b/octopusdeploy_framework/resource_lifecycle.go
index afdea2ef6..a4c863f8c 100644
--- a/octopusdeploy_framework/resource_lifecycle.go
+++ b/octopusdeploy_framework/resource_lifecycle.go
@@ -3,8 +3,11 @@ package octopusdeploy_framework
import (
"context"
"fmt"
+ "strings"
+
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/lifecycles"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
"github.com/hashicorp/terraform-plugin-framework/attr"
@@ -12,7 +15,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
- "strings"
)
type lifecycleTypeResource struct {
@@ -23,13 +25,14 @@ var _ resource.Resource = &lifecycleTypeResource{}
var _ resource.ResourceWithImportState = &lifecycleTypeResource{}
type lifecycleTypeResourceModel struct {
- ID types.String `tfsdk:"id"`
SpaceID types.String `tfsdk:"space_id"`
Name types.String `tfsdk:"name"`
Description types.String `tfsdk:"description"`
Phase types.List `tfsdk:"phase"`
ReleaseRetentionPolicy types.List `tfsdk:"release_retention_policy"`
TentacleRetentionPolicy types.List `tfsdk:"tentacle_retention_policy"`
+
+ schemas.ResourceModel
}
func NewLifecycleResource() resource.Resource {
@@ -88,7 +91,9 @@ func (r *lifecycleTypeResource) Read(ctx context.Context, req resource.ReadReque
lifecycle, err := lifecycles.GetByID(r.Config.Client, data.SpaceID.ValueString(), data.ID.ValueString())
if err != nil {
- resp.Diagnostics.AddError("unable to load lifecycle", err.Error())
+ if err := errors.ProcessApiErrorV2(ctx, resp, data, err, "lifecycle"); err != nil {
+ resp.Diagnostics.AddError("unable to load lifecycle", err.Error())
+ }
return
}
data = flattenLifecycleResource(lifecycle)
@@ -195,8 +200,7 @@ func setDefaultRetentionPolicies(data *lifecycleTypeResourceModel) {
}
func flattenLifecycleResource(lifecycle *lifecycles.Lifecycle) *lifecycleTypeResourceModel {
- return &lifecycleTypeResourceModel{
- ID: types.StringValue(lifecycle.ID),
+ flattenedLifecycle := &lifecycleTypeResourceModel{
SpaceID: types.StringValue(lifecycle.SpaceID),
Name: types.StringValue(lifecycle.Name),
Description: types.StringValue(lifecycle.Description),
@@ -204,6 +208,9 @@ func flattenLifecycleResource(lifecycle *lifecycles.Lifecycle) *lifecycleTypeRes
ReleaseRetentionPolicy: flattenRetentionPeriod(lifecycle.ReleaseRetentionPolicy),
TentacleRetentionPolicy: flattenRetentionPeriod(lifecycle.TentacleRetentionPolicy),
}
+ flattenedLifecycle.ID = types.StringValue(lifecycle.GetID())
+
+ return flattenedLifecycle
}
func flattenPhases(phases []*lifecycles.Phase) types.List {
diff --git a/octopusdeploy_framework/resource_lifecycle_test.go b/octopusdeploy_framework/resource_lifecycle_test.go
index 639fc6a93..bbcc08030 100644
--- a/octopusdeploy_framework/resource_lifecycle_test.go
+++ b/octopusdeploy_framework/resource_lifecycle_test.go
@@ -2,6 +2,9 @@ package octopusdeploy_framework
import (
"fmt"
+ "path/filepath"
+ "testing"
+
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/lifecycles"
@@ -13,8 +16,6 @@ import (
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
"github.com/stretchr/testify/require"
- "path/filepath"
- "testing"
)
func TestExpandLifecycleWithNil(t *testing.T) {
@@ -31,7 +32,6 @@ func TestExpandLifecycle(t *testing.T) {
tentacleRetention := core.NewRetentionPeriod(2, "Items", false)
data := &lifecycleTypeResourceModel{
- ID: types.StringValue(Id),
Description: types.StringValue(description),
Name: types.StringValue(name),
SpaceID: types.StringValue(spaceID),
@@ -62,6 +62,7 @@ func TestExpandLifecycle(t *testing.T) {
},
),
}
+ data.ID = types.StringValue(Id)
lifecycle := expandLifecycle(data)
diff --git a/octopusdeploy_framework/resource_maven_feed.go b/octopusdeploy_framework/resource_maven_feed.go
index 722081e81..55346da60 100644
--- a/octopusdeploy_framework/resource_maven_feed.go
+++ b/octopusdeploy_framework/resource_maven_feed.go
@@ -3,8 +3,10 @@ package octopusdeploy_framework
import (
"context"
"fmt"
+
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/feeds"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
"github.com/hashicorp/terraform-plugin-framework/attr"
@@ -76,7 +78,9 @@ func (r *mavenFeedTypeResource) Read(ctx context.Context, req resource.ReadReque
client := r.Config.Client
feed, err := feeds.GetByID(client, data.SpaceID.ValueString(), data.ID.ValueString())
if err != nil {
- resp.Diagnostics.AddError("unable to load maven feed", err.Error())
+ if err := errors.ProcessApiErrorV2(ctx, resp, data, err, "maven feed"); err != nil {
+ resp.Diagnostics.AddError("unable to load maven feed", err.Error())
+ }
return
}
diff --git a/octopusdeploy_framework/resource_nuget_feed.go b/octopusdeploy_framework/resource_nuget_feed.go
index f053ea450..bf0576248 100644
--- a/octopusdeploy_framework/resource_nuget_feed.go
+++ b/octopusdeploy_framework/resource_nuget_feed.go
@@ -3,8 +3,10 @@ package octopusdeploy_framework
import (
"context"
"fmt"
+
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/feeds"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
"github.com/hashicorp/terraform-plugin-framework/attr"
@@ -78,7 +80,9 @@ func (r *nugetFeedTypeResource) Read(ctx context.Context, req resource.ReadReque
client := r.Config.Client
feed, err := feeds.GetByID(client, data.SpaceID.ValueString(), data.ID.ValueString())
if err != nil {
- resp.Diagnostics.AddError("unable to load nuget feed", err.Error())
+ if err := errors.ProcessApiErrorV2(ctx, resp, data, err, "nuget feed"); err != nil {
+ resp.Diagnostics.AddError("unable to load nuget feed", err.Error())
+ }
return
}
diff --git a/octopusdeploy_framework/resource_project.go b/octopusdeploy_framework/resource_project.go
index 6b73a6d7a..bcefbbf69 100644
--- a/octopusdeploy_framework/resource_project.go
+++ b/octopusdeploy_framework/resource_project.go
@@ -3,7 +3,9 @@ package octopusdeploy_framework
import (
"context"
"fmt"
+
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
"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"
@@ -92,7 +94,9 @@ func (r *projectResource) Read(ctx context.Context, req resource.ReadRequest, re
project, err := projects.GetByID(r.Client, state.SpaceID.ValueString(), state.ID.ValueString())
if err != nil {
- resp.Diagnostics.AddError("Error reading project", err.Error())
+ if err := errors.ProcessApiErrorV2(ctx, resp, state, err, "lifecycle"); err != nil {
+ resp.Diagnostics.AddError("Error reading project", err.Error())
+ }
return
}
if persistenceSettings != nil {
diff --git a/octopusdeploy_framework/resource_project_flatten.go b/octopusdeploy_framework/resource_project_flatten.go
index 39b4c3e80..6e411b08d 100644
--- a/octopusdeploy_framework/resource_project_flatten.go
+++ b/octopusdeploy_framework/resource_project_flatten.go
@@ -3,6 +3,7 @@ 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"
@@ -26,7 +27,6 @@ func flattenProject(ctx context.Context, project *projects.Project, state *proje
}
model := &projectResourceModel{
- ID: types.StringValue(project.GetID()),
SpaceID: types.StringValue(project.SpaceID),
Name: types.StringValue(project.Name),
Description: types.StringValue(project.Description),
@@ -48,6 +48,8 @@ func flattenProject(ctx context.Context, project *projects.Project, state *proje
ClonedFromProjectID: util.StringOrNull(project.ClonedFromProjectID),
}
+ model.ID = types.StringValue(project.GetID())
+
model.IncludedLibraryVariableSets = util.FlattenStringList(project.IncludedLibraryVariableSets)
model.AutoDeployReleaseOverrides = flattenAutoDeployReleaseOverrides(project.AutoDeployReleaseOverrides)
diff --git a/octopusdeploy_framework/resource_project_group.go b/octopusdeploy_framework/resource_project_group.go
index e78f49cef..8d7a628de 100644
--- a/octopusdeploy_framework/resource_project_group.go
+++ b/octopusdeploy_framework/resource_project_group.go
@@ -3,7 +3,9 @@ package octopusdeploy_framework
import (
"context"
"fmt"
+
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projectgroups"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
"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"
@@ -67,7 +69,9 @@ func (r *projectGroupTypeResource) Read(ctx context.Context, req resource.ReadRe
group, err := projectgroups.GetByID(r.Config.Client, data.SpaceID.ValueString(), data.ID.ValueString())
if err != nil {
- resp.Diagnostics.AddError("unable to load project group", err.Error())
+ if err := errors.ProcessApiErrorV2(ctx, resp, data, err, "project group"); err != nil {
+ resp.Diagnostics.AddError("unable to load project group", err.Error())
+ }
return
}
diff --git a/octopusdeploy_framework/resource_project_model.go b/octopusdeploy_framework/resource_project_model.go
index 7e7386079..d58409893 100644
--- a/octopusdeploy_framework/resource_project_model.go
+++ b/octopusdeploy_framework/resource_project_model.go
@@ -1,11 +1,11 @@
package octopusdeploy_framework
import (
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
"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"`
@@ -37,6 +37,8 @@ type projectResourceModel struct {
ServiceNowExtensionSettings types.List `tfsdk:"servicenow_extension_settings"`
IncludedLibraryVariableSets types.List `tfsdk:"included_library_variable_sets"`
AutoDeployReleaseOverrides types.List `tfsdk:"auto_deploy_release_overrides"`
+
+ schemas.ResourceModel
}
type connectivityPolicyModel struct {
diff --git a/octopusdeploy_framework/resource_space.go b/octopusdeploy_framework/resource_space.go
index 01cf13cdb..42470551a 100644
--- a/octopusdeploy_framework/resource_space.go
+++ b/octopusdeploy_framework/resource_space.go
@@ -3,7 +3,10 @@ package octopusdeploy_framework
import (
"context"
"fmt"
+ "strings"
+
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/spaces"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
"github.com/hashicorp/terraform-plugin-framework/path"
@@ -11,7 +14,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
- "strings"
)
const spaceManagersTeamIDPrefix = "teams-spacemanagers-"
@@ -129,7 +131,9 @@ func (s *spaceResource) Read(ctx context.Context, req resource.ReadRequest, resp
spaceResult, err := spaces.GetByID(s.Client, data.ID.ValueString())
if err != nil {
- resp.Diagnostics.AddError("unable to query spaces", err.Error())
+ if err := errors.ProcessApiErrorV2(ctx, resp, data, err, "space"); err != nil {
+ resp.Diagnostics.AddError("unable to query spaces", err.Error())
+ }
return
}
diff --git a/octopusdeploy_framework/resource_tenant_common_variable.go b/octopusdeploy_framework/resource_tenant_common_variable.go
index 5ce0bd4a3..715f306bb 100644
--- a/octopusdeploy_framework/resource_tenant_common_variable.go
+++ b/octopusdeploy_framework/resource_tenant_common_variable.go
@@ -24,12 +24,13 @@ type tenantCommonVariableResource struct {
}
type tenantCommonVariableResourceModel struct {
- ID types.String `tfsdk:"id"`
SpaceID types.String `tfsdk:"space_id"`
TenantID types.String `tfsdk:"tenant_id"`
LibraryVariableSetID types.String `tfsdk:"library_variable_set_id"`
TemplateID types.String `tfsdk:"template_id"`
Value types.String `tfsdk:"value"`
+
+ schemas.ResourceModel
}
func NewTenantCommonVariableResource() resource.Resource {
diff --git a/octopusdeploy_framework/resource_tenant_project.go b/octopusdeploy_framework/resource_tenant_project.go
index f860af3e3..37a6879c9 100644
--- a/octopusdeploy_framework/resource_tenant_project.go
+++ b/octopusdeploy_framework/resource_tenant_project.go
@@ -21,11 +21,12 @@ import (
)
type TenantProjectModel struct {
- ID types.String `tfsdk:"id"`
SpaceID types.String `tfsdk:"space_id"`
TenantID types.String `tfsdk:"tenant_id"`
ProjectID types.String `tfsdk:"project_id"`
EnvironmentIDs types.List `tfsdk:"environment_ids"`
+
+ schemas.ResourceModel
}
type tenantProjectResource struct {
diff --git a/octopusdeploy_framework/resource_tenant_project_variable.go b/octopusdeploy_framework/resource_tenant_project_variable.go
index 7c7fb8191..7718f658e 100644
--- a/octopusdeploy_framework/resource_tenant_project_variable.go
+++ b/octopusdeploy_framework/resource_tenant_project_variable.go
@@ -3,6 +3,8 @@ package octopusdeploy_framework
import (
"context"
"fmt"
+ "strings"
+
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tenants"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/variables"
@@ -12,7 +14,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
- "strings"
)
var _ resource.Resource = &tenantProjectVariableResource{}
@@ -23,13 +24,14 @@ type tenantProjectVariableResource struct {
}
type tenantProjectVariableResourceModel struct {
- ID types.String `tfsdk:"id"`
SpaceID types.String `tfsdk:"space_id"`
TenantID types.String `tfsdk:"tenant_id"`
ProjectID types.String `tfsdk:"project_id"`
EnvironmentID types.String `tfsdk:"environment_id"`
TemplateID types.String `tfsdk:"template_id"`
Value types.String `tfsdk:"value"`
+
+ schemas.ResourceModel
}
func NewTenantProjectVariableResource() resource.Resource {
diff --git a/octopusdeploy_framework/resource_variable.go b/octopusdeploy_framework/resource_variable.go
index 2b411f50c..78d48b5a2 100644
--- a/octopusdeploy_framework/resource_variable.go
+++ b/octopusdeploy_framework/resource_variable.go
@@ -7,6 +7,7 @@ import (
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/variables"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
"github.com/hashicorp/terraform-plugin-framework/attr"
@@ -125,7 +126,9 @@ func (r *variableTypeResource) Read(ctx context.Context, req resource.ReadReques
variable, err := variables.GetByID(r.Config.Client, data.SpaceID.ValueString(), variableOwnerID.ValueString(), data.ID.ValueString())
if err != nil {
- resp.Diagnostics.AddError("unable to load variable", err.Error())
+ if err := errors.ProcessApiErrorV2(ctx, resp, data, err, schemas.VariableResourceDescription); err != nil {
+ resp.Diagnostics.AddError("unable to load variable", err.Error())
+ }
return
}
diff --git a/octopusdeploy_framework/schemas/artifactory_generic_feed.go b/octopusdeploy_framework/schemas/artifactory_generic_feed.go
index 9ee7623f8..aecde79de 100644
--- a/octopusdeploy_framework/schemas/artifactory_generic_feed.go
+++ b/octopusdeploy_framework/schemas/artifactory_generic_feed.go
@@ -33,7 +33,6 @@ func GetArtifactoryGenericFeedResourceSchema() map[string]resourceSchema.Attribu
type ArtifactoryGenericFeedTypeResourceModel struct {
FeedUri types.String `tfsdk:"feed_uri"`
- ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
PackageAcquisitionLocationOptions types.List `tfsdk:"package_acquisition_location_options"`
Password types.String `tfsdk:"password"`
@@ -41,4 +40,6 @@ type ArtifactoryGenericFeedTypeResourceModel struct {
Username types.String `tfsdk:"username"`
Repository types.String `tfsdk:"repository"`
LayoutRegex types.String `tfsdk:"layout_regex"`
+
+ ResourceModel
}
diff --git a/octopusdeploy_framework/schemas/aws_elastic_container_registry.go b/octopusdeploy_framework/schemas/aws_elastic_container_registry.go
index 92c3c847b..fbc321f9e 100644
--- a/octopusdeploy_framework/schemas/aws_elastic_container_registry.go
+++ b/octopusdeploy_framework/schemas/aws_elastic_container_registry.go
@@ -32,10 +32,11 @@ func GetAwsElasticContainerRegistryFeedResourceSchema() map[string]resourceSchem
type AwsElasticContainerRegistryFeedTypeResourceModel struct {
AccessKey types.String `tfsdk:"access_key"`
- ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
PackageAcquisitionLocationOptions types.List `tfsdk:"package_acquisition_location_options"`
Region types.String `tfsdk:"region"`
SecretKey types.String `tfsdk:"secret_key"`
SpaceID types.String `tfsdk:"space_id"`
+
+ ResourceModel
}
diff --git a/octopusdeploy_framework/schemas/docker_container_registry_feed.go b/octopusdeploy_framework/schemas/docker_container_registry_feed.go
index d7ff550c4..346eee869 100644
--- a/octopusdeploy_framework/schemas/docker_container_registry_feed.go
+++ b/octopusdeploy_framework/schemas/docker_container_registry_feed.go
@@ -29,11 +29,12 @@ func GetDockerContainerRegistryFeedResourceSchema() map[string]resourceSchema.At
type DockerContainerRegistryFeedTypeResourceModel struct {
APIVersion types.String `tfsdk:"api_version"`
FeedUri types.String `tfsdk:"feed_uri"`
- ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
PackageAcquisitionLocationOptions types.List `tfsdk:"package_acquisition_location_options"`
Password types.String `tfsdk:"password"`
SpaceID types.String `tfsdk:"space_id"`
Username types.String `tfsdk:"username"`
RegistryPath types.String `tfsdk:"registry_path"`
+
+ ResourceModel
}
diff --git a/octopusdeploy_framework/schemas/environment.go b/octopusdeploy_framework/schemas/environment.go
index af4a31e20..964532446 100644
--- a/octopusdeploy_framework/schemas/environment.go
+++ b/octopusdeploy_framework/schemas/environment.go
@@ -180,7 +180,6 @@ func MapServiceNowExtensionSettings(serviceNowExtensionSettings *environments.Se
}
type EnvironmentTypeResourceModel struct {
- ID types.String `tfsdk:"id"`
Slug types.String `tfsdk:"slug"`
Name types.String `tfsdk:"name"`
Description types.String `tfsdk:"description"`
@@ -191,4 +190,6 @@ type EnvironmentTypeResourceModel struct {
JiraExtensionSettings types.List `tfsdk:"jira_extension_settings"`
JiraServiceManagementExtensionSettings types.List `tfsdk:"jira_service_management_extension_settings"`
ServiceNowExtensionSettings types.List `tfsdk:"servicenow_extension_settings"`
+
+ ResourceModel
}
diff --git a/octopusdeploy_framework/schemas/github_repository_feed.go b/octopusdeploy_framework/schemas/github_repository_feed.go
index 12cda7b7e..71049f464 100644
--- a/octopusdeploy_framework/schemas/github_repository_feed.go
+++ b/octopusdeploy_framework/schemas/github_repository_feed.go
@@ -26,10 +26,11 @@ type GitHubRepositoryFeedTypeResourceModel struct {
DownloadAttempts types.Int64 `tfsdk:"download_attempts"`
DownloadRetryBackoffSeconds types.Int64 `tfsdk:"download_retry_backoff_seconds"`
FeedUri types.String `tfsdk:"feed_uri"`
- ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
PackageAcquisitionLocationOptions types.List `tfsdk:"package_acquisition_location_options"`
Password types.String `tfsdk:"password"`
SpaceID types.String `tfsdk:"space_id"`
Username types.String `tfsdk:"username"`
+
+ ResourceModel
}
diff --git a/octopusdeploy_framework/schemas/helm_feed.go b/octopusdeploy_framework/schemas/helm_feed.go
index 62b567b60..7e3072fee 100644
--- a/octopusdeploy_framework/schemas/helm_feed.go
+++ b/octopusdeploy_framework/schemas/helm_feed.go
@@ -22,10 +22,11 @@ func GetHelmFeedResourceSchema() map[string]resourceSchema.Attribute {
type HelmFeedTypeResourceModel struct {
FeedUri types.String `tfsdk:"feed_uri"`
- ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
PackageAcquisitionLocationOptions types.List `tfsdk:"package_acquisition_location_options"`
Password types.String `tfsdk:"password"`
SpaceID types.String `tfsdk:"space_id"`
Username types.String `tfsdk:"username"`
+
+ ResourceModel
}
diff --git a/octopusdeploy_framework/schemas/library_variable_set.go b/octopusdeploy_framework/schemas/library_variable_set.go
index 5fab75d02..3ca65b627 100644
--- a/octopusdeploy_framework/schemas/library_variable_set.go
+++ b/octopusdeploy_framework/schemas/library_variable_set.go
@@ -12,12 +12,13 @@ import (
type LibraryVariableSetResourceModel struct {
Description types.String `tfsdk:"description"`
- ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
SpaceID types.String `tfsdk:"space_id"`
Template types.List `tfsdk:"template"`
TemplateIds types.Map `tfsdk:"template_ids"`
VariableSetId types.String `tfsdk:"variable_set_id"`
+
+ ResourceModel
}
func GetLibraryVariableSetDataSourceSchema() datasourceSchema.Schema {
diff --git a/octopusdeploy_framework/schemas/maven_feed.go b/octopusdeploy_framework/schemas/maven_feed.go
index 096d7b4f4..9c01debc3 100644
--- a/octopusdeploy_framework/schemas/maven_feed.go
+++ b/octopusdeploy_framework/schemas/maven_feed.go
@@ -26,10 +26,11 @@ type MavenFeedTypeResourceModel struct {
DownloadAttempts types.Int64 `tfsdk:"download_attempts"`
DownloadRetryBackoffSeconds types.Int64 `tfsdk:"download_retry_backoff_seconds"`
FeedUri types.String `tfsdk:"feed_uri"`
- ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
PackageAcquisitionLocationOptions types.List `tfsdk:"package_acquisition_location_options"`
Password types.String `tfsdk:"password"`
SpaceID types.String `tfsdk:"space_id"`
Username types.String `tfsdk:"username"`
+
+ ResourceModel
}
diff --git a/octopusdeploy_framework/schemas/nuget_feed.go b/octopusdeploy_framework/schemas/nuget_feed.go
index a9d5567fb..a744dafb1 100644
--- a/octopusdeploy_framework/schemas/nuget_feed.go
+++ b/octopusdeploy_framework/schemas/nuget_feed.go
@@ -33,11 +33,12 @@ type NugetFeedTypeResourceModel struct {
DownloadAttempts types.Int64 `tfsdk:"download_attempts"`
DownloadRetryBackoffSeconds types.Int64 `tfsdk:"download_retry_backoff_seconds"`
FeedUri types.String `tfsdk:"feed_uri"`
- ID types.String `tfsdk:"id"`
IsEnhancedMode types.Bool `tfsdk:"is_enhanced_mode"`
Name types.String `tfsdk:"name"`
PackageAcquisitionLocationOptions types.List `tfsdk:"package_acquisition_location_options"`
Password types.String `tfsdk:"password"`
SpaceID types.String `tfsdk:"space_id"`
Username types.String `tfsdk:"username"`
+
+ ResourceModel
}
diff --git a/octopusdeploy_framework/schemas/project_group.go b/octopusdeploy_framework/schemas/project_group.go
index 1aaf65d18..bafc5ea42 100644
--- a/octopusdeploy_framework/schemas/project_group.go
+++ b/octopusdeploy_framework/schemas/project_group.go
@@ -43,9 +43,10 @@ func GetProjectGroupResourceSchema() map[string]resourceSchema.Attribute {
}
type ProjectGroupTypeResourceModel struct {
- ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
SpaceID types.String `tfsdk:"space_id"`
Description types.String `tfsdk:"description"`
RetentionPolicyID types.String `tfsdk:"retention_policy_id"`
+
+ ResourceModel
}
diff --git a/octopusdeploy_framework/schemas/resource.go b/octopusdeploy_framework/schemas/resource.go
new file mode 100644
index 000000000..b97eb5efc
--- /dev/null
+++ b/octopusdeploy_framework/schemas/resource.go
@@ -0,0 +1,19 @@
+package schemas
+
+import (
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+type IResourceModel interface {
+ GetID() string
+}
+
+type ResourceModel struct {
+ ID types.String `tfsdk:"id"`
+
+ IResourceModel `tfsdk:"-"` // Ignore resource model interface in object conversion
+}
+
+func (r ResourceModel) GetID() string {
+ return r.ID.ValueString()
+}
diff --git a/octopusdeploy_framework/schemas/space.go b/octopusdeploy_framework/schemas/space.go
index c14354752..662a95c96 100644
--- a/octopusdeploy_framework/schemas/space.go
+++ b/octopusdeploy_framework/schemas/space.go
@@ -11,7 +11,6 @@ import (
const spaceDescription = "space"
type SpaceModel struct {
- ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
Slug types.String `tfsdk:"slug"`
Description types.String `tfsdk:"description"`
@@ -19,6 +18,8 @@ type SpaceModel struct {
SpaceManagersTeams types.Set `tfsdk:"space_managers_teams"`
SpaceManagersTeamMembers types.Set `tfsdk:"space_managers_team_members"`
IsTaskQueueStopped types.Bool `tfsdk:"is_task_queue_stopped"`
+
+ ResourceModel
}
func GetSpaceResourceSchema() map[string]resourceSchema.Attribute {
diff --git a/octopusdeploy_framework/schemas/variable.go b/octopusdeploy_framework/schemas/variable.go
index b3195028b..5d9836e99 100644
--- a/octopusdeploy_framework/schemas/variable.go
+++ b/octopusdeploy_framework/schemas/variable.go
@@ -218,7 +218,6 @@ func GetVariableResourceSchema() resourceSchema.Schema {
}
type VariableTypeResourceModel struct {
- ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
Description types.String `tfsdk:"description"`
OwnerID types.String `tfsdk:"owner_id"`
@@ -234,6 +233,8 @@ type VariableTypeResourceModel struct {
Prompt types.List `tfsdk:"prompt"`
Scope types.List `tfsdk:"scope"`
SpaceID types.String `tfsdk:"space_id"`
+
+ ResourceModel
}
type VariablesDataSourceModel struct {
@@ -242,11 +243,12 @@ type VariablesDataSourceModel struct {
Scope types.List `tfsdk:"scope"`
SpaceID types.String `tfsdk:"space_id"`
Description types.String `tfsdk:"description"`
- ID types.String `tfsdk:"id"`
IsEditable types.Bool `tfsdk:"is_editable"`
IsSensitive types.Bool `tfsdk:"is_sensitive"`
Prompt types.List `tfsdk:"prompt"`
SensitiveValue types.String `tfsdk:"sensitive_value"`
Type types.String `tfsdk:"type"`
Value types.String `tfsdk:"value"`
+
+ ResourceModel
}
From 7ac25f7e821378b13a78088411332565f665a8d8 Mon Sep 17 00:00:00 2001
From: Isaac Calligeros <101079287+IsaacCalligeros95@users.noreply.github.com>
Date: Fri, 9 Aug 2024 13:29:25 +0930
Subject: [PATCH 07/28] Chore!: Migrate tenant datasource (#699)
* Migrate tenants datasource
* Update previously migrated data source schemas to be read only
---
.../azure_cloud_service_deployment_targets.md | 28 ++---
...rvice_fabric_cluster_deployment_targets.md | 30 ++---
.../azure_web_app_deployment_targets.md | 28 ++---
docs/data-sources/certificates.md | 26 ++---
docs/data-sources/channels.md | 26 ++---
.../cloud_region_deployment_targets.md | 28 ++---
docs/data-sources/deployment_targets.md | 28 ++---
docs/data-sources/environments.md | 20 ++--
docs/data-sources/feeds.md | 23 ++--
docs/data-sources/git_credentials.md | 24 ++--
.../kubernetes_agent_deployment_targets.md | 44 +++----
.../kubernetes_cluster_deployment_targets.md | 56 ++++-----
docs/data-sources/library_variable_sets.md | 14 +--
.../listening_tentacle_deployment_targets.md | 52 ++++-----
docs/data-sources/machine_policies.md | 38 +++----
...offline_package_drop_deployment_targets.md | 32 +++---
.../polling_tentacle_deployment_targets.md | 32 +++---
docs/data-sources/project_groups.md | 19 ++--
docs/data-sources/script_modules | 18 +--
docs/data-sources/script_modules.md | 18 +--
docs/data-sources/space.md | 2 +-
docs/data-sources/spaces.md | 17 +--
.../ssh_connection_deployment_targets.md | 28 ++---
docs/data-sources/tag_sets.md | 14 +--
docs/data-sources/teams.md | 20 ++--
docs/data-sources/tenants.md | 10 +-
docs/data-sources/user_roles.md | 10 +-
docs/data-sources/users.md | 18 +--
docs/data-sources/worker_pools.md | 14 +--
docs/index.md | 5 +-
docs/resources/azure_subscription_account.md | 4 +-
docs/resources/environment.md | 29 +++--
docs/resources/lifecycle.md | 4 +-
docs/resources/nuget_feed.md | 4 +-
docs/resources/space.md | 4 +-
docs/resources/tenant_common_variable.md | 19 ++--
docs/resources/tenant_project.md | 13 +--
go.mod | 2 +-
go.sum | 4 +-
octopusdeploy/data_source_tenants.go | 54 ---------
octopusdeploy/provider.go | 2 -
.../resource_deployment_process_test.go | 1 -
octopusdeploy/testing_container_test.go | 2 +-
.../datasource_environments.go | 4 +-
.../datasource_project_groups.go | 4 +-
octopusdeploy_framework/datasource_spaces.go | 2 +-
octopusdeploy_framework/datasource_tenants.go | 80 +++++++++++++
.../datasource_tenants_test.go | 11 +-
octopusdeploy_framework/framework_provider.go | 3 +-
.../schemas/environment.go | 15 +--
octopusdeploy_framework/schemas/feed.go | 33 +++---
.../schemas/library_variable_set.go | 14 +--
.../schemas/project_group.go | 2 +-
octopusdeploy_framework/schemas/schema.go | 58 ++++++++--
octopusdeploy_framework/schemas/space.go | 23 ++--
octopusdeploy_framework/schemas/tenant.go | 107 ++++++++++++++++++
.../testing_container_test.go | 2 +-
octopusdeploy_framework/util/schema.go | 40 ++++---
58 files changed, 708 insertions(+), 554 deletions(-)
delete mode 100644 octopusdeploy/data_source_tenants.go
create mode 100644 octopusdeploy_framework/datasource_tenants.go
rename octopusdeploy/data_source_tenants_test.go => octopusdeploy_framework/datasource_tenants_test.go (82%)
create mode 100644 octopusdeploy_framework/schemas/tenant.go
diff --git a/docs/data-sources/azure_cloud_service_deployment_targets.md b/docs/data-sources/azure_cloud_service_deployment_targets.md
index 5bd56afb8..c202843d6 100644
--- a/docs/data-sources/azure_cloud_service_deployment_targets.md
+++ b/docs/data-sources/azure_cloud_service_deployment_targets.md
@@ -45,10 +45,10 @@ data "octopusdeploy_azure_cloud_service_deployment_targets" "example" {
### Read-Only
-- `azure_cloud_service_deployment_targets` (Block List) A list of Azure cloud service deployment targets that match the filter(s). (see [below for nested schema](#nestedblock--azure_cloud_service_deployment_targets))
+- `azure_cloud_service_deployment_targets` (List of Object) A list of Azure cloud service deployment targets that match the filter(s). (see [below for nested schema](#nestedatt--azure_cloud_service_deployment_targets))
- `id` (String) An auto-generated identifier that includes the timestamp when this data source was last modified.
-
+
### Nested Schema for `azure_cloud_service_deployment_targets`
Read-Only:
@@ -56,33 +56,33 @@ Read-Only:
- `account_id` (String)
- `cloud_service_name` (String)
- `default_worker_pool_id` (String)
-- `endpoint` (List of Object) (see [below for nested schema](#nestedatt--azure_cloud_service_deployment_targets--endpoint))
-- `environments` (List of String) A list of environment IDs associated with this resource.
+- `endpoint` (List of Object) (see [below for nested schema](#nestedobjatt--azure_cloud_service_deployment_targets--endpoint))
+- `environments` (List of String)
- `has_latest_calamari` (Boolean)
-- `health_status` (String) Represents the health status of this deployment target. Valid health statuses are `HasWarnings`, `Healthy`, `Unavailable`, `Unhealthy`, or `Unknown`.
-- `id` (String) The unique ID for this resource.
+- `health_status` (String)
+- `id` (String)
- `is_disabled` (Boolean)
- `is_in_process` (Boolean)
- `machine_policy_id` (String)
-- `name` (String) The name of this resource.
+- `name` (String)
- `operating_system` (String)
- `roles` (List of String)
- `shell_name` (String)
- `shell_version` (String)
- `slot` (String)
-- `space_id` (String) The space ID associated with this resource.
-- `status` (String) The status of this resource. Valid statuses are `CalamariNeedsUpgrade`, `Disabled`, `NeedsUpgrade`, `Offline`, `Online`, or `Unknown`.
-- `status_summary` (String) A summary elaborating on the status of this resource.
+- `space_id` (String)
+- `status` (String)
+- `status_summary` (String)
- `storage_account_name` (String)
- `swap_if_possible` (Boolean)
-- `tenant_tags` (List of String) A list of tenant tags associated with this resource.
-- `tenanted_deployment_participation` (String) The tenanted deployment mode of the resource. Valid account types are `Untenanted`, `TenantedOrUntenanted`, or `Tenanted`.
-- `tenants` (List of String) A list of tenant IDs associated with this resource.
+- `tenant_tags` (List of String)
+- `tenanted_deployment_participation` (String)
+- `tenants` (List of String)
- `thumbprint` (String)
- `uri` (String)
- `use_current_instance_count` (Boolean)
-
+
### Nested Schema for `azure_cloud_service_deployment_targets.endpoint`
Read-Only:
diff --git a/docs/data-sources/azure_service_fabric_cluster_deployment_targets.md b/docs/data-sources/azure_service_fabric_cluster_deployment_targets.md
index f6aedf261..13a82ca92 100644
--- a/docs/data-sources/azure_service_fabric_cluster_deployment_targets.md
+++ b/docs/data-sources/azure_service_fabric_cluster_deployment_targets.md
@@ -45,47 +45,47 @@ data "octopusdeploy_azure_service_fabric_cluster_deployment_targets" "example" {
### Read-Only
-- `azure_service_fabric_cluster_deployment_targets` (Block List) A list of Azure service fabric cluster deployment targets that match the filter(s). (see [below for nested schema](#nestedblock--azure_service_fabric_cluster_deployment_targets))
+- `azure_service_fabric_cluster_deployment_targets` (List of Object) A list of Azure service fabric cluster deployment targets that match the filter(s). (see [below for nested schema](#nestedatt--azure_service_fabric_cluster_deployment_targets))
- `id` (String) An auto-generated identifier that includes the timestamp when this data source was last modified.
-
+
### Nested Schema for `azure_service_fabric_cluster_deployment_targets`
Read-Only:
- `aad_client_credential_secret` (String)
- `aad_credential_type` (String)
-- `aad_user_credential_password` (String, Sensitive)
+- `aad_user_credential_password` (String)
- `aad_user_credential_username` (String)
- `certificate_store_location` (String)
- `certificate_store_name` (String)
- `client_certificate_variable` (String)
- `connection_endpoint` (String)
-- `endpoint` (List of Object) (see [below for nested schema](#nestedatt--azure_service_fabric_cluster_deployment_targets--endpoint))
-- `environments` (List of String) A list of environment IDs associated with this resource.
+- `endpoint` (List of Object) (see [below for nested schema](#nestedobjatt--azure_service_fabric_cluster_deployment_targets--endpoint))
+- `environments` (List of String)
- `has_latest_calamari` (Boolean)
-- `health_status` (String) Represents the health status of this deployment target. Valid health statuses are `HasWarnings`, `Healthy`, `Unavailable`, `Unhealthy`, or `Unknown`.
-- `id` (String) The unique ID for this resource.
+- `health_status` (String)
+- `id` (String)
- `is_disabled` (Boolean)
- `is_in_process` (Boolean)
- `machine_policy_id` (String)
-- `name` (String) The name of this resource.
+- `name` (String)
- `operating_system` (String)
- `roles` (List of String)
- `security_mode` (String)
- `server_certificate_thumbprint` (String)
- `shell_name` (String)
- `shell_version` (String)
-- `space_id` (String) The space ID associated with this resource.
-- `status` (String) The status of this resource. Valid statuses are `CalamariNeedsUpgrade`, `Disabled`, `NeedsUpgrade`, `Offline`, `Online`, or `Unknown`.
-- `status_summary` (String) A summary elaborating on the status of this resource.
-- `tenant_tags` (List of String) A list of tenant tags associated with this resource.
-- `tenanted_deployment_participation` (String) The tenanted deployment mode of the resource. Valid account types are `Untenanted`, `TenantedOrUntenanted`, or `Tenanted`.
-- `tenants` (List of String) A list of tenant IDs associated with this resource.
+- `space_id` (String)
+- `status` (String)
+- `status_summary` (String)
+- `tenant_tags` (List of String)
+- `tenanted_deployment_participation` (String)
+- `tenants` (List of String)
- `thumbprint` (String)
- `uri` (String)
-
+
### Nested Schema for `azure_service_fabric_cluster_deployment_targets.endpoint`
Read-Only:
diff --git a/docs/data-sources/azure_web_app_deployment_targets.md b/docs/data-sources/azure_web_app_deployment_targets.md
index 26a8b8a06..a5f00dd3d 100644
--- a/docs/data-sources/azure_web_app_deployment_targets.md
+++ b/docs/data-sources/azure_web_app_deployment_targets.md
@@ -45,41 +45,41 @@ data "octopusdeploy_azure_web_app_deployment_targets" "example" {
### Read-Only
-- `azure_web_app_deployment_targets` (Block List) A list of Azure web app deployment targets that match the filter(s). (see [below for nested schema](#nestedblock--azure_web_app_deployment_targets))
+- `azure_web_app_deployment_targets` (List of Object) A list of Azure web app deployment targets that match the filter(s). (see [below for nested schema](#nestedatt--azure_web_app_deployment_targets))
- `id` (String) An auto-generated identifier that includes the timestamp when this data source was last modified.
-
+
### Nested Schema for `azure_web_app_deployment_targets`
Read-Only:
- `account_id` (String)
-- `endpoint` (List of Object) (see [below for nested schema](#nestedatt--azure_web_app_deployment_targets--endpoint))
-- `environments` (List of String) A list of environment IDs associated with this resource.
+- `endpoint` (List of Object) (see [below for nested schema](#nestedobjatt--azure_web_app_deployment_targets--endpoint))
+- `environments` (List of String)
- `has_latest_calamari` (Boolean)
-- `health_status` (String) Represents the health status of this deployment target. Valid health statuses are `HasWarnings`, `Healthy`, `Unavailable`, `Unhealthy`, or `Unknown`.
-- `id` (String) The unique ID for this resource.
+- `health_status` (String)
+- `id` (String)
- `is_disabled` (Boolean)
- `is_in_process` (Boolean)
- `machine_policy_id` (String)
-- `name` (String) The name of this resource.
+- `name` (String)
- `operating_system` (String)
- `resource_group_name` (String)
- `roles` (List of String)
- `shell_name` (String)
- `shell_version` (String)
-- `space_id` (String) The space ID associated with this resource.
-- `status` (String) The status of this resource. Valid statuses are `CalamariNeedsUpgrade`, `Disabled`, `NeedsUpgrade`, `Offline`, `Online`, or `Unknown`.
-- `status_summary` (String) A summary elaborating on the status of this resource.
-- `tenant_tags` (List of String) A list of tenant tags associated with this resource.
-- `tenanted_deployment_participation` (String) The tenanted deployment mode of the resource. Valid account types are `Untenanted`, `TenantedOrUntenanted`, or `Tenanted`.
-- `tenants` (List of String) A list of tenant IDs associated with this resource.
+- `space_id` (String)
+- `status` (String)
+- `status_summary` (String)
+- `tenant_tags` (List of String)
+- `tenanted_deployment_participation` (String)
+- `tenants` (List of String)
- `thumbprint` (String)
- `uri` (String)
- `web_app_name` (String)
- `web_app_slot_name` (String)
-
+
### Nested Schema for `azure_web_app_deployment_targets.endpoint`
Read-Only:
diff --git a/docs/data-sources/certificates.md b/docs/data-sources/certificates.md
index 9ba533b85..a62462d0b 100644
--- a/docs/data-sources/certificates.md
+++ b/docs/data-sources/certificates.md
@@ -40,29 +40,29 @@ data "octopusdeploy_certificates" "example" {
### Read-Only
-- `certificates` (Block List) A list of certificates that match the filter(s). (see [below for nested schema](#nestedblock--certificates))
+- `certificates` (List of Object) A list of certificates that match the filter(s). (see [below for nested schema](#nestedatt--certificates))
- `id` (String) An auto-generated identifier that includes the timestamp when this data source was last modified.
-
+
### Nested Schema for `certificates`
Read-Only:
- `archived` (String)
-- `certificate_data` (String, Sensitive) The encoded data of the certificate.
-- `certificate_data_format` (String) Specifies the archive file format used for storing cryptography objects in the certificate. Valid formats are `Der`, `Pem`, `Pkcs12`, or `Unknown`.
-- `environments` (List of String) A list of environment IDs associated with this resource.
-- `has_private_key` (Boolean) Indicates if the certificate has a private key.
-- `id` (String) The unique ID for this resource.
-- `is_expired` (Boolean) Indicates if the certificate has expired.
+- `certificate_data` (String)
+- `certificate_data_format` (String)
+- `environments` (List of String)
+- `has_private_key` (Boolean)
+- `id` (String)
+- `is_expired` (Boolean)
- `issuer_common_name` (String)
- `issuer_distinguished_name` (String)
- `issuer_organization` (String)
-- `name` (String) The name of this resource.
+- `name` (String)
- `not_after` (String)
- `not_before` (String)
- `notes` (String)
-- `password` (String, Sensitive) The password associated with this resource.
+- `password` (String)
- `replaced_by` (String)
- `self_signed` (Boolean)
- `serial_number` (String)
@@ -72,9 +72,9 @@ Read-Only:
- `subject_common_name` (String)
- `subject_distinguished_name` (String)
- `subject_organization` (String)
-- `tenant_tags` (List of String) A list of tenant tags associated with this resource.
-- `tenanted_deployment_participation` (String) The tenanted deployment mode of the resource. Valid account types are `Untenanted`, `TenantedOrUntenanted`, or `Tenanted`.
-- `tenants` (List of String) A list of tenant IDs associated with this resource.
+- `tenant_tags` (List of String)
+- `tenanted_deployment_participation` (String)
+- `tenants` (List of String)
- `thumbprint` (String)
- `version` (Number)
diff --git a/docs/data-sources/channels.md b/docs/data-sources/channels.md
index bf1546f4f..b19b11d00 100644
--- a/docs/data-sources/channels.md
+++ b/docs/data-sources/channels.md
@@ -34,25 +34,25 @@ data "octopusdeploy_channels" "example" {
### Read-Only
-- `channels` (Block List) A channel that matches the specified filter(s). (see [below for nested schema](#nestedblock--channels))
+- `channels` (List of Object) A channel that matches the specified filter(s). (see [below for nested schema](#nestedatt--channels))
- `id` (String) The ID of this resource.
-
+
### Nested Schema for `channels`
Read-Only:
-- `description` (String) The description of this channel.
-- `id` (String) The unique ID for this resource.
-- `is_default` (Boolean) Indicates if this is the default channel for the associated project.
-- `lifecycle_id` (String) The lifecycle ID associated with this channel.
-- `name` (String) The name of this resource.
-- `project_id` (String) The project ID associated with this channel.
-- `rule` (List of Object) A list of rules associated with this channel. (see [below for nested schema](#nestedatt--channels--rule))
-- `space_id` (String) The space ID associated with this resource.
-- `tenant_tags` (List of String) A list of tenant tags associated with this resource.
-
-
+- `description` (String)
+- `id` (String)
+- `is_default` (Boolean)
+- `lifecycle_id` (String)
+- `name` (String)
+- `project_id` (String)
+- `rule` (List of Object) (see [below for nested schema](#nestedobjatt--channels--rule))
+- `space_id` (String)
+- `tenant_tags` (List of String)
+
+
### Nested Schema for `channels.rule`
Read-Only:
diff --git a/docs/data-sources/cloud_region_deployment_targets.md b/docs/data-sources/cloud_region_deployment_targets.md
index 79adef44b..8818118d5 100644
--- a/docs/data-sources/cloud_region_deployment_targets.md
+++ b/docs/data-sources/cloud_region_deployment_targets.md
@@ -46,38 +46,38 @@ data "octopusdeploy_cloud_region_deployment_targets" "example" {
### Read-Only
-- `cloud_region_deployment_targets` (Block List) A list of cloud region deployment targets that match the filter(s). (see [below for nested schema](#nestedblock--cloud_region_deployment_targets))
+- `cloud_region_deployment_targets` (List of Object) A list of cloud region deployment targets that match the filter(s). (see [below for nested schema](#nestedatt--cloud_region_deployment_targets))
- `id` (String) An auto-generated identifier that includes the timestamp when this data source was last modified.
-
+
### Nested Schema for `cloud_region_deployment_targets`
Read-Only:
- `default_worker_pool_id` (String)
-- `endpoint` (List of Object) (see [below for nested schema](#nestedatt--cloud_region_deployment_targets--endpoint))
-- `environments` (List of String) A list of environment IDs associated with this resource.
+- `endpoint` (List of Object) (see [below for nested schema](#nestedobjatt--cloud_region_deployment_targets--endpoint))
+- `environments` (List of String)
- `has_latest_calamari` (Boolean)
-- `health_status` (String) Represents the health status of this deployment target. Valid health statuses are `HasWarnings`, `Healthy`, `Unavailable`, `Unhealthy`, or `Unknown`.
-- `id` (String) The unique ID for this resource.
+- `health_status` (String)
+- `id` (String)
- `is_disabled` (Boolean)
- `is_in_process` (Boolean)
- `machine_policy_id` (String)
-- `name` (String) The name of this resource.
+- `name` (String)
- `operating_system` (String)
- `roles` (List of String)
- `shell_name` (String)
- `shell_version` (String)
-- `space_id` (String) The space ID associated with this resource.
-- `status` (String) The status of this resource. Valid statuses are `CalamariNeedsUpgrade`, `Disabled`, `NeedsUpgrade`, `Offline`, `Online`, or `Unknown`.
-- `status_summary` (String) A summary elaborating on the status of this resource.
-- `tenant_tags` (List of String) A list of tenant tags associated with this resource.
-- `tenanted_deployment_participation` (String) The tenanted deployment mode of the resource. Valid account types are `Untenanted`, `TenantedOrUntenanted`, or `Tenanted`.
-- `tenants` (List of String) A list of tenant IDs associated with this resource.
+- `space_id` (String)
+- `status` (String)
+- `status_summary` (String)
+- `tenant_tags` (List of String)
+- `tenanted_deployment_participation` (String)
+- `tenants` (List of String)
- `thumbprint` (String)
- `uri` (String)
-
+
### Nested Schema for `cloud_region_deployment_targets.endpoint`
Read-Only:
diff --git a/docs/data-sources/deployment_targets.md b/docs/data-sources/deployment_targets.md
index 4677decd0..50a12ed38 100644
--- a/docs/data-sources/deployment_targets.md
+++ b/docs/data-sources/deployment_targets.md
@@ -36,37 +36,37 @@ Provides information about existing deployment targets.
### Read-Only
-- `deployment_targets` (Block List) A list of deployment targets that match the filter(s). (see [below for nested schema](#nestedblock--deployment_targets))
+- `deployment_targets` (List of Object) A list of deployment targets that match the filter(s). (see [below for nested schema](#nestedatt--deployment_targets))
- `id` (String) The ID of this resource.
-
+
### Nested Schema for `deployment_targets`
Read-Only:
-- `endpoint` (List of Object) (see [below for nested schema](#nestedatt--deployment_targets--endpoint))
-- `environments` (List of String) A list of environment IDs associated with this resource.
+- `endpoint` (List of Object) (see [below for nested schema](#nestedobjatt--deployment_targets--endpoint))
+- `environments` (List of String)
- `has_latest_calamari` (Boolean)
-- `health_status` (String) Represents the health status of this deployment target. Valid health statuses are `HasWarnings`, `Healthy`, `Unavailable`, `Unhealthy`, or `Unknown`.
-- `id` (String) The unique ID for this resource.
+- `health_status` (String)
+- `id` (String)
- `is_disabled` (Boolean)
- `is_in_process` (Boolean)
- `machine_policy_id` (String)
-- `name` (String) The name of this resource.
+- `name` (String)
- `operating_system` (String)
- `roles` (List of String)
- `shell_name` (String)
- `shell_version` (String)
-- `space_id` (String) The space ID associated with this resource.
-- `status` (String) The status of this resource. Valid statuses are `CalamariNeedsUpgrade`, `Disabled`, `NeedsUpgrade`, `Offline`, `Online`, or `Unknown`.
-- `status_summary` (String) A summary elaborating on the status of this resource.
-- `tenant_tags` (List of String) A list of tenant tags associated with this resource.
-- `tenanted_deployment_participation` (String) The tenanted deployment mode of the resource. Valid account types are `Untenanted`, `TenantedOrUntenanted`, or `Tenanted`.
-- `tenants` (List of String) A list of tenant IDs associated with this resource.
+- `space_id` (String)
+- `status` (String)
+- `status_summary` (String)
+- `tenant_tags` (List of String)
+- `tenanted_deployment_participation` (String)
+- `tenants` (List of String)
- `thumbprint` (String)
- `uri` (String)
-
+
### Nested Schema for `deployment_targets.endpoint`
Read-Only:
diff --git a/docs/data-sources/environments.md b/docs/data-sources/environments.md
index 69bc1ab23..d67c25219 100644
--- a/docs/data-sources/environments.md
+++ b/docs/data-sources/environments.md
@@ -28,16 +28,16 @@ data "octopusdeploy_environments" "example" {
### Optional
- `ids` (List of String) A filter to search by a list of IDs.
-- `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 search by exact 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) The space ID associated with this resource.
+- `space_id` (String) The space ID associated with this environment.
- `take` (Number) A filter to specify the number of items to take (or return) in the response.
### Read-Only
-- `environments` (Block List) A list of environments that match the filter(s). (see [below for nested schema](#nestedblock--environments))
-- `id` (String) An auto-generated identifier that includes the timestamp when this data source was last modified.
+- `environments` (Block List) Provides information about existing environments. (see [below for nested schema](#nestedblock--environments))
+- `id` (String) The unique ID for this resource.
### Nested Schema for `environments`
@@ -47,12 +47,12 @@ Read-Only:
- `allow_dynamic_infrastructure` (Boolean)
- `description` (String) The description of this environment.
- `id` (String) The unique ID for this resource.
-- `jira_extension_settings` (List of Object) Provides extension settings for the Jira integration for this environment. (see [below for nested schema](#nestedatt--environments--jira_extension_settings))
-- `jira_service_management_extension_settings` (List of Object) Provides extension settings for the Jira Service Management (JSM) integration for this environment. (see [below for nested schema](#nestedatt--environments--jira_service_management_extension_settings))
+- `jira_extension_settings` (Attributes List) Provides extension settings for the Jira integration for this environment. (see [below for nested schema](#nestedatt--environments--jira_extension_settings))
+- `jira_service_management_extension_settings` (Attributes List) Provides extension settings for the Jira Service Management (JSM) integration for this environment. (see [below for nested schema](#nestedatt--environments--jira_service_management_extension_settings))
- `name` (String) The name of this resource.
-- `servicenow_extension_settings` (List of Object) Provides extension settings for the ServiceNow integration for this environment. (see [below for nested schema](#nestedatt--environments--servicenow_extension_settings))
-- `slug` (String)
-- `sort_order` (Number) The order number to sort an environment.
+- `servicenow_extension_settings` (Attributes List) Provides extension settings for the ServiceNow integration for this environment. (see [below for nested schema](#nestedatt--environments--servicenow_extension_settings))
+- `slug` (String) The unique slug of this environment
+- `sort_order` (Number) The order number to sort an environment
- `space_id` (String) The space ID associated with this environment.
- `use_guided_failure` (Boolean)
diff --git a/docs/data-sources/feeds.md b/docs/data-sources/feeds.md
index 91fde3c96..7afdb726d 100644
--- a/docs/data-sources/feeds.md
+++ b/docs/data-sources/feeds.md
@@ -27,8 +27,6 @@ data "octopusdeploy_feeds" "example" {
### Optional
- `feed_type` (String) A filter to search by feed type. Valid feed types are `AwsElasticContainerRegistry`, `BuiltIn`, `Docker`, `GitHub`, `Helm`, `Maven`, `NuGet`, or `OctopusProject`.
-- `feeds` (Block List) (see [below for nested schema](#nestedblock--feeds))
-- `id` (String) The unique ID for this resource.
- `ids` (List of String) A filter to search by a list of IDs.
- `name` (String) The name of this resource.
- `partial_name` (String) A filter to search by a partial name.
@@ -36,31 +34,30 @@ data "octopusdeploy_feeds" "example" {
- `space_id` (String) The space ID associated with this feeds.
- `take` (Number) A filter to specify the number of items to take (or return) in the response.
+### Read-Only
+
+- `feeds` (Block List) (see [below for nested schema](#nestedblock--feeds))
+- `id` (String) The unique ID for this resource.
+
### Nested Schema for `feeds`
-Required:
+Read-Only:
- `access_key` (String) The AWS access key to use when authenticating against Amazon Web Services.
-- `feed_uri` (String)
-- `name` (String) The name of this resource.
-
-Optional:
-
- `api_version` (String)
- `delete_unreleased_packages_after_days` (Number)
- `download_attempts` (Number) The number of times a deployment should attempt to download a package from this feed before failing.
- `download_retry_backoff_seconds` (Number) The number of seconds to apply as a linear back off between download attempts.
- `feed_type` (String) A filter to search by feed type. Valid feed types are `AwsElasticContainerRegistry`, `BuiltIn`, `Docker`, `GitHub`, `Helm`, `Maven`, `NuGet`, or `OctopusProject`.
+- `feed_uri` (String)
- `id` (String) The unique ID for this resource.
- `is_enhanced_mode` (Boolean)
+- `name` (String) The name of this resource.
- `package_acquisition_location_options` (List of String)
- `password` (String, Sensitive) The password associated with this resource.
+- `region` (String)
- `registry_path` (String)
- `secret_key` (String, Sensitive)
- `space_id` (String) The space ID associated with this feeds.
-- `username` (String, Sensitive) The username associated with this resource.
-
-Read-Only:
-
-- `region` (String)
\ No newline at end of file
+- `username` (String, Sensitive) The username associated with this resource.
\ No newline at end of file
diff --git a/docs/data-sources/git_credentials.md b/docs/data-sources/git_credentials.md
index d5e0820a4..e7a4a3c4d 100644
--- a/docs/data-sources/git_credentials.md
+++ b/docs/data-sources/git_credentials.md
@@ -3,12 +3,12 @@
page_title: "octopusdeploy_git_credentials Data Source - terraform-provider-octopusdeploy"
subcategory: ""
description: |-
- Provides information about existing GitCredentials.
+ Use this data source to retrieve information about Git credentials in Octopus Deploy.
---
# octopusdeploy_git_credentials (Data Source)
-Provides information about existing GitCredentials.
+Use this data source to retrieve information about Git credentials in Octopus Deploy.
@@ -17,26 +17,26 @@ Provides information about existing GitCredentials.
### Optional
-- `name` (String) A filter to search by name.
-- `skip` (Number) A filter to specify the number of items to skip in the response.
-- `space_id` (String) The space ID associated with this resource.
-- `take` (Number) A filter to specify the number of items to take (or return) in the response.
+- `name` (String) The name of the Git Credential to filter by.
+- `skip` (Number) The number of records to skip.
+- `space_id` (String) The space ID associated with this Git Credential.
+- `take` (Number) The number of records to take.
### Read-Only
-- `git_credentials` (Block List) A list of Git Credentials that match the filter(s). (see [below for nested schema](#nestedblock--git_credentials))
-- `id` (String) The ID of this resource.
+- `git_credentials` (Attributes List) Provides information about existing GitCredentials. (see [below for nested schema](#nestedatt--git_credentials))
+- `id` (String) The unique ID for this resource.
-
+
### Nested Schema for `git_credentials`
Read-Only:
-- `description` (String) The description of this Git credential.
+- `description` (String) The description of this Git Credential.
- `id` (String) The unique ID for this resource.
-- `name` (String) The name of the Git credential. This name must be unique.
+- `name` (String) The name of this Git Credential.
- `password` (String, Sensitive) The password for the Git credential.
-- `space_id` (String) The space ID associated with this resource.
+- `space_id` (String) The space ID associated with this Git Credential.
- `type` (String) The Git credential authentication type.
- `username` (String) The username for the Git credential.
diff --git a/docs/data-sources/kubernetes_agent_deployment_targets.md b/docs/data-sources/kubernetes_agent_deployment_targets.md
index 527adad4a..dd4c61884 100644
--- a/docs/data-sources/kubernetes_agent_deployment_targets.md
+++ b/docs/data-sources/kubernetes_agent_deployment_targets.md
@@ -55,32 +55,32 @@ data "octopusdeploy_kubernetes_agent_deployment_targets" "kubernetes_agent_deplo
### Read-Only
- `id` (String) An auto-generated identifier that includes the timestamp when this data source was last modified.
-- `kubernetes_agent_deployment_targets` (Block List) A list of kubernetes agent deployment targets that match the filter(s). (see [below for nested schema](#nestedblock--kubernetes_agent_deployment_targets))
+- `kubernetes_agent_deployment_targets` (List of Object) A list of kubernetes agent deployment targets that match the filter(s). (see [below for nested schema](#nestedatt--kubernetes_agent_deployment_targets))
-
+
### Nested Schema for `kubernetes_agent_deployment_targets`
Read-Only:
-- `agent_helm_release_name` (String) Name of the Helm release that the agent belongs to.
-- `agent_kubernetes_namespace` (String) Name of the Kubernetes namespace where the agent is installed.
-- `agent_tentacle_version` (String) Current Tentacle version of the agent
-- `agent_upgrade_status` (String) Current upgrade availability status of the agent. One of 'NoUpgrades', 'UpgradeAvailable', 'UpgradeSuggested', 'UpgradeRequired'
-- `agent_version` (String) Current Helm chart version of the agent.
-- `communication_mode` (String) The communication mode used by the Kubernetes agent to communicate with Octopus Server. Currently, the only supported value is 'Polling'.
-- `default_namespace` (String) Optional default namespace that will be used when using Kubernetes deployment steps, can be overrides within step configurations.
-- `environments` (List of String) A list of environment IDs this Kubernetes agent can deploy to.
-- `id` (String) The unique ID for this resource.
-- `is_disabled` (Boolean) Whether the Kubernetes agent is disabled. If the agent is disabled, it will not be included in any deployments.
-- `machine_policy_id` (String) Optional ID of the machine policy that the Kubernetes agent will use. If not provided the default machine policy will be used.
-- `name` (String) The name of this resource.
-- `roles` (List of String) A list of target roles that are associated to this Kubernetes agent.
-- `space_id` (String) The space ID associated with this resource.
-- `tenant_tags` (List of String) A list of tenant tags associated with this resource.
-- `tenanted_deployment_participation` (String) The tenanted deployment mode of the resource. Valid account types are `Untenanted`, `TenantedOrUntenanted`, or `Tenanted`.
-- `tenants` (List of String) A list of tenant IDs associated with this resource.
-- `thumbprint` (String) The thumbprint of the Kubernetes agent's certificate used by server to verify the identity of the agent. This is the same thumbprint that was used when installing the agent.
-- `upgrade_locked` (Boolean) If enabled the Kubernetes agent will not automatically upgrade and will stay on the currently installed version, even if the associated machine policy is configured to automatically upgrade.
-- `uri` (String) The URI of the Kubernetes agent's used by the server to queue messages. This is the same subscription uri that was used when installing the agent.
+- `agent_helm_release_name` (String)
+- `agent_kubernetes_namespace` (String)
+- `agent_tentacle_version` (String)
+- `agent_upgrade_status` (String)
+- `agent_version` (String)
+- `communication_mode` (String)
+- `default_namespace` (String)
+- `environments` (List of String)
+- `id` (String)
+- `is_disabled` (Boolean)
+- `machine_policy_id` (String)
+- `name` (String)
+- `roles` (List of String)
+- `space_id` (String)
+- `tenant_tags` (List of String)
+- `tenanted_deployment_participation` (String)
+- `tenants` (List of String)
+- `thumbprint` (String)
+- `upgrade_locked` (Boolean)
+- `uri` (String)
diff --git a/docs/data-sources/kubernetes_cluster_deployment_targets.md b/docs/data-sources/kubernetes_cluster_deployment_targets.md
index 4bf178018..7b7da7741 100644
--- a/docs/data-sources/kubernetes_cluster_deployment_targets.md
+++ b/docs/data-sources/kubernetes_cluster_deployment_targets.md
@@ -36,52 +36,52 @@ Provides information about existing Kubernetes cluster deployment targets.
### Read-Only
- `id` (String) An auto-generated identifier that includes the timestamp when this data source was last modified.
-- `kubernetes_cluster_deployment_targets` (Block List) A list of Kubernetes cluster deployment targets that match the filter(s). (see [below for nested schema](#nestedblock--kubernetes_cluster_deployment_targets))
+- `kubernetes_cluster_deployment_targets` (List of Object) A list of Kubernetes cluster deployment targets that match the filter(s). (see [below for nested schema](#nestedatt--kubernetes_cluster_deployment_targets))
-
+
### Nested Schema for `kubernetes_cluster_deployment_targets`
Read-Only:
-- `authentication` (List of Object) (see [below for nested schema](#nestedatt--kubernetes_cluster_deployment_targets--authentication))
-- `aws_account_authentication` (List of Object) (see [below for nested schema](#nestedatt--kubernetes_cluster_deployment_targets--aws_account_authentication))
-- `azure_service_principal_authentication` (List of Object) (see [below for nested schema](#nestedatt--kubernetes_cluster_deployment_targets--azure_service_principal_authentication))
-- `certificate_authentication` (List of Object) (see [below for nested schema](#nestedatt--kubernetes_cluster_deployment_targets--certificate_authentication))
+- `authentication` (List of Object) (see [below for nested schema](#nestedobjatt--kubernetes_cluster_deployment_targets--authentication))
+- `aws_account_authentication` (List of Object) (see [below for nested schema](#nestedobjatt--kubernetes_cluster_deployment_targets--aws_account_authentication))
+- `azure_service_principal_authentication` (List of Object) (see [below for nested schema](#nestedobjatt--kubernetes_cluster_deployment_targets--azure_service_principal_authentication))
+- `certificate_authentication` (List of Object) (see [below for nested schema](#nestedobjatt--kubernetes_cluster_deployment_targets--certificate_authentication))
- `cluster_certificate` (String)
- `cluster_certificate_path` (String)
- `cluster_url` (String)
-- `container` (List of Object) (see [below for nested schema](#nestedatt--kubernetes_cluster_deployment_targets--container))
+- `container` (List of Object) (see [below for nested schema](#nestedobjatt--kubernetes_cluster_deployment_targets--container))
- `container_options` (String)
- `default_worker_pool_id` (String)
-- `endpoint` (List of Object) (see [below for nested schema](#nestedatt--kubernetes_cluster_deployment_targets--endpoint))
-- `environments` (List of String) A list of environment IDs associated with this resource.
-- `gcp_account_authentication` (List of Object) (see [below for nested schema](#nestedatt--kubernetes_cluster_deployment_targets--gcp_account_authentication))
+- `endpoint` (List of Object) (see [below for nested schema](#nestedobjatt--kubernetes_cluster_deployment_targets--endpoint))
+- `environments` (List of String)
+- `gcp_account_authentication` (List of Object) (see [below for nested schema](#nestedobjatt--kubernetes_cluster_deployment_targets--gcp_account_authentication))
- `has_latest_calamari` (Boolean)
-- `health_status` (String) Represents the health status of this deployment target. Valid health statuses are `HasWarnings`, `Healthy`, `Unavailable`, `Unhealthy`, or `Unknown`.
-- `id` (String) The unique ID for this resource.
+- `health_status` (String)
+- `id` (String)
- `is_disabled` (Boolean)
- `is_in_process` (Boolean)
- `machine_policy_id` (String)
-- `name` (String) The name of this resource.
+- `name` (String)
- `namespace` (String)
- `operating_system` (String)
-- `pod_authentication` (List of Object) (see [below for nested schema](#nestedatt--kubernetes_cluster_deployment_targets--pod_authentication))
+- `pod_authentication` (List of Object) (see [below for nested schema](#nestedobjatt--kubernetes_cluster_deployment_targets--pod_authentication))
- `proxy_id` (String)
- `roles` (List of String)
- `running_in_container` (Boolean)
- `shell_name` (String)
- `shell_version` (String)
- `skip_tls_verification` (Boolean)
-- `space_id` (String) The space ID associated with this resource.
-- `status` (String) The status of this resource. Valid statuses are `CalamariNeedsUpgrade`, `Disabled`, `NeedsUpgrade`, `Offline`, `Online`, or `Unknown`.
-- `status_summary` (String) A summary elaborating on the status of this resource.
-- `tenant_tags` (List of String) A list of tenant tags associated with this resource.
-- `tenanted_deployment_participation` (String) The tenanted deployment mode of the resource. Valid account types are `Untenanted`, `TenantedOrUntenanted`, or `Tenanted`.
-- `tenants` (List of String) A list of tenant IDs associated with this resource.
+- `space_id` (String)
+- `status` (String)
+- `status_summary` (String)
+- `tenant_tags` (List of String)
+- `tenanted_deployment_participation` (String)
+- `tenants` (List of String)
- `thumbprint` (String)
- `uri` (String)
-
+
### Nested Schema for `kubernetes_cluster_deployment_targets.authentication`
Read-Only:
@@ -89,7 +89,7 @@ Read-Only:
- `account_id` (String)
-
+
### Nested Schema for `kubernetes_cluster_deployment_targets.aws_account_authentication`
Read-Only:
@@ -104,7 +104,7 @@ Read-Only:
- `use_instance_role` (Boolean)
-
+
### Nested Schema for `kubernetes_cluster_deployment_targets.azure_service_principal_authentication`
Read-Only:
@@ -114,7 +114,7 @@ Read-Only:
- `cluster_resource_group` (String)
-
+
### Nested Schema for `kubernetes_cluster_deployment_targets.certificate_authentication`
Read-Only:
@@ -122,7 +122,7 @@ Read-Only:
- `client_certificate` (String)
-
+
### Nested Schema for `kubernetes_cluster_deployment_targets.container`
Read-Only:
@@ -131,7 +131,7 @@ Read-Only:
- `image` (String)
-
+
### Nested Schema for `kubernetes_cluster_deployment_targets.endpoint`
Read-Only:
@@ -235,7 +235,7 @@ Read-Only:
-
+
### Nested Schema for `kubernetes_cluster_deployment_targets.gcp_account_authentication`
Read-Only:
@@ -250,7 +250,7 @@ Read-Only:
- `zone` (String)
-
+
### Nested Schema for `kubernetes_cluster_deployment_targets.pod_authentication`
Read-Only:
diff --git a/docs/data-sources/library_variable_sets.md b/docs/data-sources/library_variable_sets.md
index 8b6eeb19f..9b10353eb 100644
--- a/docs/data-sources/library_variable_sets.md
+++ b/docs/data-sources/library_variable_sets.md
@@ -18,34 +18,34 @@ Provides information about existing library variable sets.
### Optional
- `content_type` (String) A filter to search by content type.
-- `id` (String) The unique ID for this resource.
- `ids` (List of String) A filter to search by a list of IDs.
-- `library_variable_sets` (Block List) A list of library variable sets that match the filter(s). (see [below for nested schema](#nestedblock--library_variable_sets))
- `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) The space ID associated with this library variable set.
- `take` (Number) A filter to specify the number of items to take (or return) in the response.
+### Read-Only
+
+- `id` (String) The unique ID for this resource.
+- `library_variable_sets` (Block List) A list of library variable sets that match the filter(s). (see [below for nested schema](#nestedblock--library_variable_sets))
+
### Nested Schema for `library_variable_sets`
-Optional:
+Read-Only:
- `description` (String) The description of this library variable set.
- `id` (String) The unique ID for this resource.
- `name` (String) The name of this resource.
- `space_id` (String) The space ID associated with this library variable set.
- `template` (List of Object) (see [below for nested schema](#nestedatt--library_variable_sets--template))
-
-Read-Only:
-
- `template_ids` (Map of String)
- `variable_set_id` (String)
### Nested Schema for `library_variable_sets.template`
-Optional:
+Read-Only:
- `default_value` (String)
- `display_settings` (Map of String)
diff --git a/docs/data-sources/listening_tentacle_deployment_targets.md b/docs/data-sources/listening_tentacle_deployment_targets.md
index e8f7ccb8f..c90c679f0 100644
--- a/docs/data-sources/listening_tentacle_deployment_targets.md
+++ b/docs/data-sources/listening_tentacle_deployment_targets.md
@@ -55,39 +55,39 @@ data "octopusdeploy_listening_tentacle_deployment_targets" "listening_tentacle_d
### Read-Only
- `id` (String) An auto-generated identifier that includes the timestamp when this data source was last modified.
-- `listening_tentacle_deployment_targets` (Block List) A list of listening tentacle deployment targets that match the filter(s). (see [below for nested schema](#nestedblock--listening_tentacle_deployment_targets))
+- `listening_tentacle_deployment_targets` (List of Object) A list of listening tentacle deployment targets that match the filter(s). (see [below for nested schema](#nestedatt--listening_tentacle_deployment_targets))
-
+
### Nested Schema for `listening_tentacle_deployment_targets`
Read-Only:
- `certificate_signature_algorithm` (String)
-- `environments` (List of String) A list of environment IDs associated with this listening tentacle.
+- `environments` (List of String)
- `has_latest_calamari` (Boolean)
-- `health_status` (String) Represents the health status of this deployment target. Valid health statuses are `HasWarnings`, `Healthy`, `Unavailable`, `Unhealthy`, or `Unknown`.
-- `id` (String) The unique ID for this resource.
-- `is_disabled` (Boolean) Represents the disabled status of this deployment target.
-- `is_in_process` (Boolean) Represents the in-process status of this deployment target.
-- `machine_policy_id` (String) The machine policy ID that is associated with this deployment target.
-- `name` (String) The name of this resource.
-- `operating_system` (String) The operating system that is associated with this deployment target.
-- `proxy_id` (String) The proxy ID that is associated with this deployment target.
-- `roles` (List of String) A list of role IDs that are associated with this deployment target.
-- `shell_name` (String) The shell name associated with this deployment target.
-- `shell_version` (String) The shell version associated with this deployment target.
-- `space_id` (String) The space ID associated with this resource.
-- `status` (String) The status of this resource. Valid statuses are `CalamariNeedsUpgrade`, `Disabled`, `NeedsUpgrade`, `Offline`, `Online`, or `Unknown`.
-- `status_summary` (String) A summary elaborating on the status of this resource.
-- `tenant_tags` (List of String) A list of tenant tags associated with this resource.
-- `tenanted_deployment_participation` (String) The tenanted deployment mode of the resource. Valid account types are `Untenanted`, `TenantedOrUntenanted`, or `Tenanted`.
-- `tenants` (List of String) A list of tenant IDs associated with this resource.
-- `tentacle_url` (String) The tenant URL of this deployment target.
-- `tentacle_version_details` (List of Object) (see [below for nested schema](#nestedatt--listening_tentacle_deployment_targets--tentacle_version_details))
-- `thumbprint` (String) The thumbprint of this deployment target.
-- `uri` (String) The URI of this deployment target.
-
-
+- `health_status` (String)
+- `id` (String)
+- `is_disabled` (Boolean)
+- `is_in_process` (Boolean)
+- `machine_policy_id` (String)
+- `name` (String)
+- `operating_system` (String)
+- `proxy_id` (String)
+- `roles` (List of String)
+- `shell_name` (String)
+- `shell_version` (String)
+- `space_id` (String)
+- `status` (String)
+- `status_summary` (String)
+- `tenant_tags` (List of String)
+- `tenanted_deployment_participation` (String)
+- `tenants` (List of String)
+- `tentacle_url` (String)
+- `tentacle_version_details` (List of Object) (see [below for nested schema](#nestedobjatt--listening_tentacle_deployment_targets--tentacle_version_details))
+- `thumbprint` (String)
+- `uri` (String)
+
+
### Nested Schema for `listening_tentacle_deployment_targets.tentacle_version_details`
Read-Only:
diff --git a/docs/data-sources/machine_policies.md b/docs/data-sources/machine_policies.md
index 68e70b324..614a9a02e 100644
--- a/docs/data-sources/machine_policies.md
+++ b/docs/data-sources/machine_policies.md
@@ -26,29 +26,29 @@ Provides information about existing machine policies.
### Read-Only
- `id` (String) The ID of this resource.
-- `machine_policies` (Block List) A list of machine policies that match the filter(s). (see [below for nested schema](#nestedblock--machine_policies))
+- `machine_policies` (List of Object) A list of machine policies that match the filter(s). (see [below for nested schema](#nestedatt--machine_policies))
-
+
### Nested Schema for `machine_policies`
Read-Only:
-- `connection_connect_timeout` (Number) In nanoseconds. Minimum value: 10000000000 (10 seconds).
+- `connection_connect_timeout` (Number)
- `connection_retry_count_limit` (Number)
-- `connection_retry_sleep_interval` (Number) In nanoseconds.
-- `connection_retry_time_limit` (Number) In nanoseconds.
-- `description` (String) The description of this machine policy.
-- `id` (String) The unique ID for this resource.
+- `connection_retry_sleep_interval` (Number)
+- `connection_retry_time_limit` (Number)
+- `description` (String)
+- `id` (String)
- `is_default` (Boolean)
-- `machine_cleanup_policy` (Set of Object) (see [below for nested schema](#nestedatt--machine_policies--machine_cleanup_policy))
-- `machine_connectivity_policy` (Set of Object) (see [below for nested schema](#nestedatt--machine_policies--machine_connectivity_policy))
-- `machine_health_check_policy` (Set of Object) (see [below for nested schema](#nestedatt--machine_policies--machine_health_check_policy))
-- `machine_update_policy` (Set of Object) (see [below for nested schema](#nestedatt--machine_policies--machine_update_policy))
-- `name` (String) The name of this resource.
-- `polling_request_queue_timeout` (Number) In nanoseconds.
-- `space_id` (String) The space ID associated with this resource.
-
-
+- `machine_cleanup_policy` (Set of Object) (see [below for nested schema](#nestedobjatt--machine_policies--machine_cleanup_policy))
+- `machine_connectivity_policy` (Set of Object) (see [below for nested schema](#nestedobjatt--machine_policies--machine_connectivity_policy))
+- `machine_health_check_policy` (Set of Object) (see [below for nested schema](#nestedobjatt--machine_policies--machine_health_check_policy))
+- `machine_update_policy` (Set of Object) (see [below for nested schema](#nestedobjatt--machine_policies--machine_update_policy))
+- `name` (String)
+- `polling_request_queue_timeout` (Number)
+- `space_id` (String)
+
+
### Nested Schema for `machine_policies.machine_cleanup_policy`
Read-Only:
@@ -57,7 +57,7 @@ Read-Only:
- `delete_machines_elapsed_timespan` (Number)
-
+
### Nested Schema for `machine_policies.machine_connectivity_policy`
Read-Only:
@@ -65,7 +65,7 @@ Read-Only:
- `machine_connectivity_behavior` (String)
-
+
### Nested Schema for `machine_policies.machine_health_check_policy`
Read-Only:
@@ -96,7 +96,7 @@ Read-Only:
-
+
### Nested Schema for `machine_policies.machine_update_policy`
Read-Only:
diff --git a/docs/data-sources/offline_package_drop_deployment_targets.md b/docs/data-sources/offline_package_drop_deployment_targets.md
index fc01cb4ad..193982d1e 100644
--- a/docs/data-sources/offline_package_drop_deployment_targets.md
+++ b/docs/data-sources/offline_package_drop_deployment_targets.md
@@ -36,39 +36,39 @@ Provides information about existing offline package drop deployment targets.
### Read-Only
- `id` (String) An auto-generated identifier that includes the timestamp when this data source was last modified.
-- `offline_package_drop_deployment_targets` (Block List) A list of offline package drop deployment targets that match the filter(s). (see [below for nested schema](#nestedblock--offline_package_drop_deployment_targets))
+- `offline_package_drop_deployment_targets` (List of Object) A list of offline package drop deployment targets that match the filter(s). (see [below for nested schema](#nestedatt--offline_package_drop_deployment_targets))
-
+
### Nested Schema for `offline_package_drop_deployment_targets`
Read-Only:
- `applications_directory` (String)
-- `destination` (List of Object) (see [below for nested schema](#nestedatt--offline_package_drop_deployment_targets--destination))
-- `endpoint` (List of Object) (see [below for nested schema](#nestedatt--offline_package_drop_deployment_targets--endpoint))
-- `environments` (List of String) A list of environment IDs associated with this resource.
+- `destination` (List of Object) (see [below for nested schema](#nestedobjatt--offline_package_drop_deployment_targets--destination))
+- `endpoint` (List of Object) (see [below for nested schema](#nestedobjatt--offline_package_drop_deployment_targets--endpoint))
+- `environments` (List of String)
- `has_latest_calamari` (Boolean)
-- `health_status` (String) Represents the health status of this deployment target. Valid health statuses are `HasWarnings`, `Healthy`, `Unavailable`, `Unhealthy`, or `Unknown`.
-- `id` (String) The unique ID for this resource.
+- `health_status` (String)
+- `id` (String)
- `is_disabled` (Boolean)
- `is_in_process` (Boolean)
- `machine_policy_id` (String)
-- `name` (String) The name of this resource.
+- `name` (String)
- `operating_system` (String)
- `roles` (List of String)
- `shell_name` (String)
- `shell_version` (String)
-- `space_id` (String) The space ID associated with this resource.
-- `status` (String) The status of this resource. Valid statuses are `CalamariNeedsUpgrade`, `Disabled`, `NeedsUpgrade`, `Offline`, `Online`, or `Unknown`.
-- `status_summary` (String) A summary elaborating on the status of this resource.
-- `tenant_tags` (List of String) A list of tenant tags associated with this resource.
-- `tenanted_deployment_participation` (String) The tenanted deployment mode of the resource. Valid account types are `Untenanted`, `TenantedOrUntenanted`, or `Tenanted`.
-- `tenants` (List of String) A list of tenant IDs associated with this resource.
+- `space_id` (String)
+- `status` (String)
+- `status_summary` (String)
+- `tenant_tags` (List of String)
+- `tenanted_deployment_participation` (String)
+- `tenants` (List of String)
- `thumbprint` (String)
- `uri` (String)
- `working_directory` (String)
-
+
### Nested Schema for `offline_package_drop_deployment_targets.destination`
Read-Only:
@@ -77,7 +77,7 @@ Read-Only:
- `drop_folder_path` (String)
-
+
### Nested Schema for `offline_package_drop_deployment_targets.endpoint`
Read-Only:
diff --git a/docs/data-sources/polling_tentacle_deployment_targets.md b/docs/data-sources/polling_tentacle_deployment_targets.md
index 5124fddfc..b28cdfddf 100644
--- a/docs/data-sources/polling_tentacle_deployment_targets.md
+++ b/docs/data-sources/polling_tentacle_deployment_targets.md
@@ -36,39 +36,39 @@ Provides information about existing polling tentacle deployment targets.
### Read-Only
- `id` (String) An auto-generated identifier that includes the timestamp when this data source was last modified.
-- `polling_tentacle_deployment_targets` (Block List) A list of polling tentacle deployment targets that match the filter(s). (see [below for nested schema](#nestedblock--polling_tentacle_deployment_targets))
+- `polling_tentacle_deployment_targets` (List of Object) A list of polling tentacle deployment targets that match the filter(s). (see [below for nested schema](#nestedatt--polling_tentacle_deployment_targets))
-
+
### Nested Schema for `polling_tentacle_deployment_targets`
Read-Only:
- `certificate_signature_algorithm` (String)
-- `endpoint` (List of Object) (see [below for nested schema](#nestedatt--polling_tentacle_deployment_targets--endpoint))
-- `environments` (List of String) A list of environment IDs associated with this resource.
+- `endpoint` (List of Object) (see [below for nested schema](#nestedobjatt--polling_tentacle_deployment_targets--endpoint))
+- `environments` (List of String)
- `has_latest_calamari` (Boolean)
-- `health_status` (String) Represents the health status of this deployment target. Valid health statuses are `HasWarnings`, `Healthy`, `Unavailable`, `Unhealthy`, or `Unknown`.
-- `id` (String) The unique ID for this resource.
+- `health_status` (String)
+- `id` (String)
- `is_disabled` (Boolean)
- `is_in_process` (Boolean)
- `machine_policy_id` (String)
-- `name` (String) The name of this resource.
+- `name` (String)
- `operating_system` (String)
- `roles` (List of String)
- `shell_name` (String)
- `shell_version` (String)
-- `space_id` (String) The space ID associated with this resource.
-- `status` (String) The status of this resource. Valid statuses are `CalamariNeedsUpgrade`, `Disabled`, `NeedsUpgrade`, `Offline`, `Online`, or `Unknown`.
-- `status_summary` (String) A summary elaborating on the status of this resource.
-- `tenant_tags` (List of String) A list of tenant tags associated with this resource.
-- `tenanted_deployment_participation` (String) The tenanted deployment mode of the resource. Valid account types are `Untenanted`, `TenantedOrUntenanted`, or `Tenanted`.
-- `tenants` (List of String) A list of tenant IDs associated with this resource.
+- `space_id` (String)
+- `status` (String)
+- `status_summary` (String)
+- `tenant_tags` (List of String)
+- `tenanted_deployment_participation` (String)
+- `tenants` (List of String)
- `tentacle_url` (String)
-- `tentacle_version_details` (List of Object) (see [below for nested schema](#nestedatt--polling_tentacle_deployment_targets--tentacle_version_details))
+- `tentacle_version_details` (List of Object) (see [below for nested schema](#nestedobjatt--polling_tentacle_deployment_targets--tentacle_version_details))
- `thumbprint` (String)
- `uri` (String)
-
+
### Nested Schema for `polling_tentacle_deployment_targets.endpoint`
Read-Only:
@@ -172,7 +172,7 @@ Read-Only:
-
+
### Nested Schema for `polling_tentacle_deployment_targets.tentacle_version_details`
Read-Only:
diff --git a/docs/data-sources/project_groups.md b/docs/data-sources/project_groups.md
index ed88f743a..ab1d0f4eb 100644
--- a/docs/data-sources/project_groups.md
+++ b/docs/data-sources/project_groups.md
@@ -3,12 +3,12 @@
page_title: "octopusdeploy_project_groups Data Source - terraform-provider-octopusdeploy"
subcategory: ""
description: |-
- Provides information about existing project groups.
+
---
# octopusdeploy_project_groups (Data Source)
-Provides information about existing project groups.
+
## Example Usage
@@ -27,25 +27,28 @@ data "octopusdeploy_project_groups" "example" {
### Optional
- `ids` (List of String) A filter to search by a list of IDs.
-- `partial_name` (String) A filter to search by the partial match of a name.
+- `partial_name` (String) A filter to search by a partial name.
+- `project_groups` (Block List) A list of project groups that match the filter(s). (see [below for nested schema](#nestedblock--project_groups))
- `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) The space ID associated with this project group.
- `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.
-- `project_groups` (Block List) A list of project groups that match the filter(s). (see [below for nested schema](#nestedblock--project_groups))
+- `id` (String) The unique ID for this resource.
### Nested Schema for `project_groups`
-Read-Only:
+Optional:
- `description` (String) The description of this project group.
- `id` (String) The unique ID for this resource.
-- `name` (String) The name of this resource.
- `retention_policy_id` (String) The ID of the retention policy associated with this project group.
- `space_id` (String) The space ID associated with this project group.
+Read-Only:
+
+- `name` (String) The name of this resource.
+
diff --git a/docs/data-sources/script_modules b/docs/data-sources/script_modules
index 6625186eb..6a12509e7 100644
--- a/docs/data-sources/script_modules
+++ b/docs/data-sources/script_modules
@@ -34,21 +34,21 @@ data "octopusdeploy_script_modules" "example" {
### Read-Only
- `id` (String) An auto-generated identifier that includes the timestamp when this data source was last modified.
-- `script_modules` (Block List) A list of script modules that match the filter(s). (see [below for nested schema](#nestedblock--script_modules))
+- `script_modules` (List of Object) A list of script modules that match the filter(s). (see [below for nested schema](#nestedatt--script_modules))
-
+
### Nested Schema for `script_modules`
Read-Only:
-- `description` (String) The description of this script module.
-- `id` (String) The unique ID for this resource.
-- `name` (String) The name of this resource.
-- `script` (Set of Object) The script associated with this script module. (see [below for nested schema](#nestedatt--script_modules--script))
-- `space_id` (String) The space ID associated with this resource.
-- `variable_set_id` (String) The variable set ID for this script module.
+- `description` (String)
+- `id` (String)
+- `name` (String)
+- `script` (Set of Object) (see [below for nested schema](#nestedobjatt--script_modules--script))
+- `space_id` (String)
+- `variable_set_id` (String)
-
+
### Nested Schema for `script_modules.script`
Read-Only:
diff --git a/docs/data-sources/script_modules.md b/docs/data-sources/script_modules.md
index 283f804cc..35d7e16a8 100644
--- a/docs/data-sources/script_modules.md
+++ b/docs/data-sources/script_modules.md
@@ -35,21 +35,21 @@ data "octopusdeploy_script_modules" "example" {
### Read-Only
- `id` (String) An auto-generated identifier that includes the timestamp when this data source was last modified.
-- `script_modules` (Block List) A list of script modules that match the filter(s). (see [below for nested schema](#nestedblock--script_modules))
+- `script_modules` (List of Object) A list of script modules that match the filter(s). (see [below for nested schema](#nestedatt--script_modules))
-
+
### Nested Schema for `script_modules`
Read-Only:
-- `description` (String) The description of this script module.
-- `id` (String) The unique ID for this resource.
-- `name` (String) The name of this resource.
-- `script` (Set of Object) The script associated with this script module. (see [below for nested schema](#nestedatt--script_modules--script))
-- `space_id` (String) The space ID associated with this resource.
-- `variable_set_id` (String) The variable set ID for this script module.
+- `description` (String)
+- `id` (String)
+- `name` (String)
+- `script` (Set of Object) (see [below for nested schema](#nestedobjatt--script_modules--script))
+- `space_id` (String)
+- `variable_set_id` (String)
-
+
### Nested Schema for `script_modules.script`
Read-Only:
diff --git a/docs/data-sources/space.md b/docs/data-sources/space.md
index 36bcc4c81..2ec59a7f5 100644
--- a/docs/data-sources/space.md
+++ b/docs/data-sources/space.md
@@ -25,7 +25,7 @@ Provides information about an existing space.
- `id` (String) The unique ID for this resource.
- `is_default` (Boolean) Specifies if this space is the default space in Octopus.
- `is_task_queue_stopped` (Boolean) Specifies the status of the task queue for this space.
-- `slug` (String) The unique slug of this space.
+- `slug` (String) The unique slug of this space
- `space_managers_team_members` (Set of String) A list of user IDs designated to be managers of this space.
- `space_managers_teams` (Set of String) A list of team IDs designated to be managers of this space.
diff --git a/docs/data-sources/spaces.md b/docs/data-sources/spaces.md
index 36485b0a1..ff5ae2193 100644
--- a/docs/data-sources/spaces.md
+++ b/docs/data-sources/spaces.md
@@ -3,12 +3,12 @@
page_title: "octopusdeploy_spaces Data Source - terraform-provider-octopusdeploy"
subcategory: ""
description: |-
- Provides information about existing spaces.
+
---
# octopusdeploy_spaces (Data Source)
-Provides information about existing spaces.
+
## Example Usage
@@ -27,26 +27,29 @@ data "octopusdeploy_spaces" "spaces" {
### Optional
- `ids` (List of String) A filter to search by a list of IDs.
-- `partial_name` (String) A filter to search by the partial match of a 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.
+- `spaces` (Block List) Provides information about existing spaces. (see [below for nested schema](#nestedblock--spaces))
- `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.
-- `spaces` (Block List) A list of spaces that match the filter(s). (see [below for nested schema](#nestedblock--spaces))
+- `id` (String) The unique ID for this resource.
### Nested Schema for `spaces`
+Required:
+
+- `name` (String) The name of this resource, no more than 20 characters long
+
Read-Only:
- `description` (String) The description of this space.
- `id` (String) The unique ID for this resource.
- `is_default` (Boolean) Specifies if this space is the default space in Octopus.
- `is_task_queue_stopped` (Boolean) Specifies the status of the task queue for this space.
-- `name` (String) The name of this resource, no more than 20 characters long
-- `slug` (String) The unique slug of this space.
+- `slug` (String) The unique slug of this space
- `space_managers_team_members` (Set of String) A list of user IDs designated to be managers of this space.
- `space_managers_teams` (Set of String) A list of team IDs designated to be managers of this space.
diff --git a/docs/data-sources/ssh_connection_deployment_targets.md b/docs/data-sources/ssh_connection_deployment_targets.md
index 43e054adb..4c0d32f1c 100644
--- a/docs/data-sources/ssh_connection_deployment_targets.md
+++ b/docs/data-sources/ssh_connection_deployment_targets.md
@@ -36,42 +36,42 @@ Provides information about existing SSH connection deployment targets.
### Read-Only
- `id` (String) An auto-generated identifier that includes the timestamp when this data source was last modified.
-- `ssh_connection_deployment_targets` (Block List) A list of SSH connection deployment targets that match the filter(s). (see [below for nested schema](#nestedblock--ssh_connection_deployment_targets))
+- `ssh_connection_deployment_targets` (List of Object) A list of SSH connection deployment targets that match the filter(s). (see [below for nested schema](#nestedatt--ssh_connection_deployment_targets))
-
+
### Nested Schema for `ssh_connection_deployment_targets`
Read-Only:
- `account_id` (String)
- `dot_net_core_platform` (String)
-- `endpoint` (List of Object) (see [below for nested schema](#nestedatt--ssh_connection_deployment_targets--endpoint))
-- `environments` (List of String) A list of environment IDs associated with this resource.
+- `endpoint` (List of Object) (see [below for nested schema](#nestedobjatt--ssh_connection_deployment_targets--endpoint))
+- `environments` (List of String)
- `fingerprint` (String)
- `has_latest_calamari` (Boolean)
-- `health_status` (String) Represents the health status of this deployment target. Valid health statuses are `HasWarnings`, `Healthy`, `Unavailable`, `Unhealthy`, or `Unknown`.
+- `health_status` (String)
- `host` (String)
-- `id` (String) The unique ID for this resource.
+- `id` (String)
- `is_disabled` (Boolean)
- `is_in_process` (Boolean)
- `machine_policy_id` (String)
-- `name` (String) The name of this resource.
+- `name` (String)
- `operating_system` (String)
- `port` (Number)
- `proxy_id` (String)
- `roles` (List of String)
- `shell_name` (String)
- `shell_version` (String)
-- `space_id` (String) The space ID associated with this resource.
-- `status` (String) The status of this resource. Valid statuses are `CalamariNeedsUpgrade`, `Disabled`, `NeedsUpgrade`, `Offline`, `Online`, or `Unknown`.
-- `status_summary` (String) A summary elaborating on the status of this resource.
-- `tenant_tags` (List of String) A list of tenant tags associated with this resource.
-- `tenanted_deployment_participation` (String) The tenanted deployment mode of the resource. Valid account types are `Untenanted`, `TenantedOrUntenanted`, or `Tenanted`.
-- `tenants` (List of String) A list of tenant IDs associated with this resource.
+- `space_id` (String)
+- `status` (String)
+- `status_summary` (String)
+- `tenant_tags` (List of String)
+- `tenanted_deployment_participation` (String)
+- `tenants` (List of String)
- `thumbprint` (String)
- `uri` (String)
-
+
### Nested Schema for `ssh_connection_deployment_targets.endpoint`
Read-Only:
diff --git a/docs/data-sources/tag_sets.md b/docs/data-sources/tag_sets.md
index fb8fffe78..56b41b61b 100644
--- a/docs/data-sources/tag_sets.md
+++ b/docs/data-sources/tag_sets.md
@@ -26,17 +26,17 @@ Provides information about existing tag sets.
### Read-Only
- `id` (String) The ID of this resource.
-- `tag_sets` (Block List) A list of tag sets that match the filter(s). (see [below for nested schema](#nestedblock--tag_sets))
+- `tag_sets` (List of Object) A list of tag sets that match the filter(s). (see [below for nested schema](#nestedatt--tag_sets))
-
+
### Nested Schema for `tag_sets`
Read-Only:
-- `description` (String) The description of this tag set.
-- `id` (String) The unique ID for this resource.
-- `name` (String) The name of this resource.
-- `sort_order` (Number) The sort order associated with this resource.
-- `space_id` (String) The space ID associated with this resource.
+- `description` (String)
+- `id` (String)
+- `name` (String)
+- `sort_order` (Number)
+- `space_id` (String)
diff --git a/docs/data-sources/teams.md b/docs/data-sources/teams.md
index ba635c2e0..1075d7295 100644
--- a/docs/data-sources/teams.md
+++ b/docs/data-sources/teams.md
@@ -27,9 +27,9 @@ Provides information about existing users.
### Read-Only
- `id` (String) An auto-generated identifier that includes the timestamp when this data source was last modified.
-- `teams` (Block List) A list of teams that match the filter(s). (see [below for nested schema](#nestedblock--teams))
+- `teams` (List of Object) A list of teams that match the filter(s). (see [below for nested schema](#nestedatt--teams))
-
+
### Nested Schema for `teams`
Read-Only:
@@ -38,14 +38,14 @@ Read-Only:
- `can_be_renamed` (Boolean)
- `can_change_members` (Boolean)
- `can_change_roles` (Boolean)
-- `description` (String) The user-friendly description of this team.
-- `external_security_group` (List of Object) (see [below for nested schema](#nestedatt--teams--external_security_group))
-- `id` (String) The unique ID for this resource.
-- `name` (String) The name of this team.
-- `space_id` (String) The space associated with this team.
-- `users` (Set of String) A list of user IDs designated to be members of this team.
-
-
+- `description` (String)
+- `external_security_group` (List of Object) (see [below for nested schema](#nestedobjatt--teams--external_security_group))
+- `id` (String)
+- `name` (String)
+- `space_id` (String)
+- `users` (Set of String)
+
+
### Nested Schema for `teams.external_security_group`
Read-Only:
diff --git a/docs/data-sources/tenants.md b/docs/data-sources/tenants.md
index 28d093d17..7b6486318 100644
--- a/docs/data-sources/tenants.md
+++ b/docs/data-sources/tenants.md
@@ -21,16 +21,16 @@ Provides information about existing tenants.
- `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.
+- `partial_name` (String) A filter to search by a partial name.
- `project_id` (String) A filter to search by a project ID.
- `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) The space ID associated with this tenants.
- `tags` (List of String) A filter to search by a list of tags.
- `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.
+- `id` (String) The unique ID for this resource.
- `tenants` (Block List) A list of tenants that match the filter(s). (see [below for nested schema](#nestedblock--tenants))
@@ -39,10 +39,10 @@ Provides information about existing tenants.
Read-Only:
- `cloned_from_tenant_id` (String) The ID of the tenant from which this tenant was cloned.
-- `description` (String) The description of this tenant.
+- `description` (String) The description of this tenants.
- `id` (String) The unique ID for this resource.
- `name` (String) The name of this resource.
-- `space_id` (String) The space ID associated with this resource.
+- `space_id` (String) The space ID associated with this tenant.
- `tenant_tags` (List of String) A list of tenant tags associated with this resource.
diff --git a/docs/data-sources/user_roles.md b/docs/data-sources/user_roles.md
index 19010c518..8c6bc4544 100644
--- a/docs/data-sources/user_roles.md
+++ b/docs/data-sources/user_roles.md
@@ -35,19 +35,19 @@ data "octopusdeploy_user_roles" "example" {
### Read-Only
- `id` (String) An auto-generated identifier that includes the timestamp when this data source was last modified.
-- `user_roles` (Block List) A list of user roles that match the filter(s). (see [below for nested schema](#nestedblock--user_roles))
+- `user_roles` (List of Object) A list of user roles that match the filter(s). (see [below for nested schema](#nestedatt--user_roles))
-
+
### Nested Schema for `user_roles`
Read-Only:
- `can_be_deleted` (Boolean)
-- `description` (String) The description of this user role.
+- `description` (String)
- `granted_space_permissions` (List of String)
- `granted_system_permissions` (List of String)
-- `id` (String) The unique ID for this resource.
-- `name` (String) The name of this resource.
+- `id` (String)
+- `name` (String)
- `space_permission_descriptions` (List of String)
- `supported_restrictions` (List of String)
- `system_permission_descriptions` (List of String)
diff --git a/docs/data-sources/users.md b/docs/data-sources/users.md
index dd1a19994..ca9f963dd 100644
--- a/docs/data-sources/users.md
+++ b/docs/data-sources/users.md
@@ -34,25 +34,25 @@ data "octopusdeploy_users" "example" {
### Read-Only
- `id` (String) An auto-generated identifier that includes the timestamp when this data source was last modified.
-- `users` (Block List) A list of users that match the filter(s). (see [below for nested schema](#nestedblock--users))
+- `users` (List of Object) A list of users that match the filter(s). (see [below for nested schema](#nestedatt--users))
-
+
### Nested Schema for `users`
Read-Only:
- `can_password_be_edited` (Boolean)
-- `display_name` (String) The display name of this resource.
-- `email_address` (String) The email address of this resource.
-- `id` (String) The unique ID for this resource.
-- `identity` (Set of Object) (see [below for nested schema](#nestedatt--users--identity))
+- `display_name` (String)
+- `email_address` (String)
+- `id` (String)
+- `identity` (Set of Object) (see [below for nested schema](#nestedobjatt--users--identity))
- `is_active` (Boolean)
- `is_requestor` (Boolean)
- `is_service` (Boolean)
-- `password` (String, Sensitive) The password associated with this resource.
-- `username` (String, Sensitive) The username associated with this resource.
+- `password` (String)
+- `username` (String)
-
+
### Nested Schema for `users.identity`
Read-Only:
diff --git a/docs/data-sources/worker_pools.md b/docs/data-sources/worker_pools.md
index 7c6636313..95c0cdf35 100644
--- a/docs/data-sources/worker_pools.md
+++ b/docs/data-sources/worker_pools.md
@@ -27,20 +27,20 @@ Provides information about existing worker pools.
### Read-Only
- `id` (String) The ID of this resource.
-- `worker_pools` (Block List) A list of worker pools that match the filter(s). (see [below for nested schema](#nestedblock--worker_pools))
+- `worker_pools` (List of Object) A list of worker pools that match the filter(s). (see [below for nested schema](#nestedatt--worker_pools))
-
+
### Nested Schema for `worker_pools`
Read-Only:
- `can_add_workers` (Boolean)
-- `description` (String) The description of this worker pool.
-- `id` (String) The unique ID for this resource.
+- `description` (String)
+- `id` (String)
- `is_default` (Boolean)
-- `name` (String) The name of this resource.
-- `sort_order` (Number) The order number to sort a dynamic worker pool.
-- `space_id` (String) The space ID associated with this resource.
+- `name` (String)
+- `sort_order` (Number)
+- `space_id` (String)
- `worker_pool_type` (String)
- `worker_type` (String)
diff --git a/docs/index.md b/docs/index.md
index 9787f53dc..e0fadfb5f 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -79,11 +79,8 @@ resource "octopusdeploy_environment" "Env3" {
## Schema
-### Required
+### Optional
- `address` (String) The endpoint of the Octopus REST API
- `api_key` (String) The API key to use with the Octopus REST API
-
-### Optional
-
- `space_id` (String) The space ID to target
\ No newline at end of file
diff --git a/docs/resources/azure_subscription_account.md b/docs/resources/azure_subscription_account.md
index a7025e91a..7f11a8822 100644
--- a/docs/resources/azure_subscription_account.md
+++ b/docs/resources/azure_subscription_account.md
@@ -22,9 +22,7 @@ resource "octopusdeploy_azure_subscription_account" "example" {
### Required
-- `management_endpoint` (String)
- `name` (String) The name of this resource.
-- `storage_endpoint_suffix` (String) The storage endpoint suffix associated with this Azure subscription account.
- `subscription_id` (String) The subscription ID of this resource.
### Optional
@@ -34,7 +32,9 @@ resource "octopusdeploy_azure_subscription_account" "example" {
- `certificate_thumbprint` (String, Sensitive)
- `description` (String) The description of this Azure subscription account.
- `environments` (List of String) A list of environment IDs associated with this resource.
+- `management_endpoint` (String)
- `space_id` (String) The space ID associated with this resource.
+- `storage_endpoint_suffix` (String) The storage endpoint suffix associated with this Azure subscription account.
- `tenant_tags` (List of String) A list of tenant tags associated with this resource.
- `tenanted_deployment_participation` (String) The tenanted deployment mode of the resource. Valid account types are `Untenanted`, `TenantedOrUntenanted`, or `Tenanted`.
- `tenants` (List of String) A list of tenant IDs associated with this resource.
diff --git a/docs/resources/environment.md b/docs/resources/environment.md
index e8a5c7438..c9f9c5d1e 100644
--- a/docs/resources/environment.md
+++ b/docs/resources/environment.md
@@ -3,12 +3,12 @@
page_title: "octopusdeploy_environment Resource - terraform-provider-octopusdeploy"
subcategory: ""
description: |-
- This resource manages environments in Octopus Deploy.
+
---
# octopusdeploy_environment (Resource)
-This resource manages environments in Octopus Deploy.
+
## Example Usage
@@ -45,39 +45,36 @@ resource "octopusdeploy_environment" "example" {
- `allow_dynamic_infrastructure` (Boolean)
- `description` (String) The description of this environment.
- `id` (String) The unique ID for this resource.
-- `jira_extension_settings` (Block List, Max: 1) Provides extension settings for the Jira integration for this environment. (see [below for nested schema](#nestedblock--jira_extension_settings))
-- `jira_service_management_extension_settings` (Block List, Max: 1) Provides extension settings for the Jira Service Management (JSM) integration for this environment. (see [below for nested schema](#nestedblock--jira_service_management_extension_settings))
-- `servicenow_extension_settings` (Block List, Max: 1) Provides extension settings for the ServiceNow integration for this environment. (see [below for nested schema](#nestedblock--servicenow_extension_settings))
-- `sort_order` (Number) The order number to sort an environment.
+- `jira_extension_settings` (Block List) Provides extension settings for the Jira integration for this environment. (see [below for nested schema](#nestedblock--jira_extension_settings))
+- `jira_service_management_extension_settings` (Block List) Provides extension settings for the Jira Service Management (JSM) integration for this environment. (see [below for nested schema](#nestedblock--jira_service_management_extension_settings))
+- `servicenow_extension_settings` (Block List) Provides extension settings for the ServiceNow integration for this environment. (see [below for nested schema](#nestedblock--servicenow_extension_settings))
+- `slug` (String) The unique slug of this environment
+- `sort_order` (Number) The order number to sort an environment
- `space_id` (String) The space ID associated with this environment.
- `use_guided_failure` (Boolean)
-### Read-Only
-
-- `slug` (String)
-
### Nested Schema for `jira_extension_settings`
-Required:
+Optional:
-- `environment_type` (String) The Jira environment type of this Octopus deployment environment. Valid values are `"development"`, `"production"`, `"staging"`, `"testing"`, or `"unmapped"`.
+- `environment_type` (String)
### Nested Schema for `jira_service_management_extension_settings`
-Required:
+Optional:
-- `is_enabled` (Boolean) Specifies whether or not this extension is enabled for this project.
+- `is_enabled` (Boolean)
### Nested Schema for `servicenow_extension_settings`
-Required:
+Optional:
-- `is_enabled` (Boolean) Specifies whether or not this extension is enabled for this project.
+- `is_enabled` (Boolean)
## Import
diff --git a/docs/resources/lifecycle.md b/docs/resources/lifecycle.md
index 8714ab06e..e5178b91e 100644
--- a/docs/resources/lifecycle.md
+++ b/docs/resources/lifecycle.md
@@ -33,8 +33,8 @@ resource "octopusdeploy_lifecycle" "example" {
name = "foo"
release_retention_policy {
- quantity_to_keep = 0
- should_keep_forever = true // true only if quantity_to_keep = 0
+ quantity_to_keep = 1
+ should_keep_forever = true
unit = "Days"
}
diff --git a/docs/resources/nuget_feed.md b/docs/resources/nuget_feed.md
index b5758af66..4a736c70a 100644
--- a/docs/resources/nuget_feed.md
+++ b/docs/resources/nuget_feed.md
@@ -2,12 +2,12 @@
page_title: "octopusdeploy_nuget_feed Resource - terraform-provider-octopusdeploy"
subcategory: "Feeds"
description: |-
- This resource manages a NuGet feed in Octopus Deploy.
+ This resource manages a Nuget feed in Octopus Deploy.
---
# octopusdeploy_nuget_feed (Resource)
-This resource manages a NuGet feed in Octopus Deploy.
+This resource manages a Nuget feed in Octopus Deploy.
## Example Usage
diff --git a/docs/resources/space.md b/docs/resources/space.md
index 6ab45052e..f2b0f3084 100644
--- a/docs/resources/space.md
+++ b/docs/resources/space.md
@@ -28,7 +28,7 @@ resource "octopusdeploy_space" "example" {
### Required
-- `name` (String) The name of this resource, no more than 20 characters long
+- `name` (String) The name of this resource.
### Optional
@@ -36,7 +36,7 @@ resource "octopusdeploy_space" "example" {
- `id` (String) The unique ID for this resource.
- `is_default` (Boolean) Specifies if this space is the default space in Octopus.
- `is_task_queue_stopped` (Boolean) Specifies the status of the task queue for this space.
-- `slug` (String) The unique slug of this space.
+- `slug` (String) The unique slug of this space
- `space_managers_team_members` (Set of String) A list of user IDs designated to be managers of this space.
- `space_managers_teams` (Set of String) A list of team IDs designated to be managers of this space.
diff --git a/docs/resources/tenant_common_variable.md b/docs/resources/tenant_common_variable.md
index 4c1c54054..c306e3c59 100644
--- a/docs/resources/tenant_common_variable.md
+++ b/docs/resources/tenant_common_variable.md
@@ -3,12 +3,12 @@
page_title: "octopusdeploy_tenant_common_variable Resource - terraform-provider-octopusdeploy"
subcategory: ""
description: |-
- This resource manages tenant common variables in Octopus Deploy.
+ Manages a tenant common variable in Octopus Deploy.
---
# octopusdeploy_tenant_common_variable (Resource)
-This resource manages tenant common variables in Octopus Deploy.
+Manages a tenant common variable in Octopus Deploy.
@@ -17,17 +17,14 @@ This resource manages tenant common variables in Octopus Deploy.
### Required
-- `library_variable_set_id` (String)
-- `template_id` (String)
-- `tenant_id` (String)
+- `library_variable_set_id` (String) The ID of the library variable set.
+- `template_id` (String) The ID of the variable template.
+- `tenant_id` (String) The ID of the tenant.
### Optional
-- `space_id` (String)
-- `value` (String, Sensitive)
-
-### Read-Only
-
-- `id` (String) The ID of this resource.
+- `id` (String) The unique ID for this resource.
+- `space_id` (String) The space ID associated with this Tenant Common Variable.
+- `value` (String, Sensitive) The value of the variable.
diff --git a/docs/resources/tenant_project.md b/docs/resources/tenant_project.md
index f537e8fcc..2558f847b 100644
--- a/docs/resources/tenant_project.md
+++ b/docs/resources/tenant_project.md
@@ -3,12 +3,12 @@
page_title: "octopusdeploy_tenant_project Resource - terraform-provider-octopusdeploy"
subcategory: ""
description: |-
- This resource represents the connection between tenants and projects.
+
---
# octopusdeploy_tenant_project (Resource)
-This resource represents the connection between tenants and projects.
+
@@ -22,11 +22,8 @@ This resource represents the connection between tenants and projects.
### Optional
-- `environment_ids` (List of String) The environment ID associated with this tenant.
-- `space_id` (String) The space ID associated with this resource.
-
-### Read-Only
-
-- `id` (String) The ID of this resource.
+- `environment_ids` (List of String) The environment IDs associated with this tenant.
+- `id` (String) The unique ID for this resource.
+- `space_id` (String) The space ID associated with this project tenant.
diff --git a/go.mod b/go.mod
index 4cc35d04a..f3ed22597 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.21
require (
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.49.1
- github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework v0.0.0-20240725054341-2848f54d101e
+ github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework v0.0.0-20240729041805-46db6fb717b4
github.com/google/uuid v1.6.0
github.com/hashicorp/go-cty v1.4.1-0.20200723130312-85980079f637
github.com/hashicorp/terraform-plugin-docs v0.13.0
diff --git a/go.sum b/go.sum
index c917536b0..c0bdd145e 100644
--- a/go.sum
+++ b/go.sum
@@ -22,8 +22,8 @@ github.com/OctopusDeploy/go-octodiff v1.0.0 h1:U+ORg6azniwwYo+O44giOw6TiD5USk8S4
github.com/OctopusDeploy/go-octodiff v1.0.0/go.mod h1:Mze0+EkOWTgTmi8++fyUc6r0aLZT7qD9gX+31t8MmIU=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.49.1 h1:lzC3tcnfvC07Ilqn5J51qhOnW79kGD6nTIxCBOdmAe8=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.49.1/go.mod h1:ggvOXzMnq+w0pLg6C9zdjz6YBaHfO3B3tqmmB7JQdaw=
-github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework v0.0.0-20240725054341-2848f54d101e h1:FIvWa8wNg8IBG5uVhqkKvcBhaxx4TgN7T8/5Ed4VQUE=
-github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework v0.0.0-20240725054341-2848f54d101e/go.mod h1:Oq9KbiRNDBB5jFmrwnrgLX0urIqR/1ptY18TzkqXm7M=
+github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework v0.0.0-20240729041805-46db6fb717b4 h1:QfbVf0bOIRMp/WHAWsuVDB7KHoWnRsGbvDuOf2ua7k4=
+github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework v0.0.0-20240729041805-46db6fb717b4/go.mod h1:Oq9KbiRNDBB5jFmrwnrgLX0urIqR/1ptY18TzkqXm7M=
github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg=
github.com/ProtonMail/go-crypto v1.1.0-alpha.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
diff --git a/octopusdeploy/data_source_tenants.go b/octopusdeploy/data_source_tenants.go
deleted file mode 100644
index 4c9cfa569..000000000
--- a/octopusdeploy/data_source_tenants.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package octopusdeploy
-
-import (
- "context"
- "time"
-
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client"
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tenants"
- "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
-)
-
-func dataSourceTenants() *schema.Resource {
- return &schema.Resource{
- Description: "Provides information about existing tenants.",
- ReadContext: dataSourceTenantsRead,
- Schema: getTenantDataSchema(),
- }
-}
-
-func dataSourceTenantsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
- query := tenants.TenantsQuery{
- ClonedFromTenantID: d.Get("cloned_from_tenant_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),
- ProjectID: d.Get("project_id").(string),
- Skip: d.Get("skip").(int),
- Tags: expandArray(d.Get("tags").([]interface{})),
- Take: d.Get("take").(int),
- }
-
- spaceID := d.Get("space_id").(string)
-
- client := meta.(*client.Client)
- existingTenants, err := tenants.Get(client, spaceID, query)
- if err != nil {
- return diag.FromErr(err)
- }
-
- flattenedTenants := []interface{}{}
- for _, tenant := range existingTenants.Items {
- flattenedTenants = append(flattenedTenants, flattenTenant(tenant))
- }
-
- if err := d.Set("tenants", flattenedTenants); err != nil {
- return diag.FromErr(err)
- }
-
- d.SetId("Tenants " + time.Now().UTC().String())
-
- return nil
-}
diff --git a/octopusdeploy/provider.go b/octopusdeploy/provider.go
index 07f503374..6de5657b1 100644
--- a/octopusdeploy/provider.go
+++ b/octopusdeploy/provider.go
@@ -2,7 +2,6 @@ package octopusdeploy
import (
"context"
-
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
@@ -30,7 +29,6 @@ func Provider() *schema.Provider {
"octopusdeploy_ssh_connection_deployment_targets": dataSourceSSHConnectionDeploymentTargets(),
"octopusdeploy_tag_sets": dataSourceTagSets(),
"octopusdeploy_teams": dataSourceTeams(),
- "octopusdeploy_tenants": dataSourceTenants(),
"octopusdeploy_users": dataSourceUsers(),
"octopusdeploy_user_roles": dataSourceUserRoles(),
"octopusdeploy_worker_pools": dataSourceWorkerPools(),
diff --git a/octopusdeploy/resource_deployment_process_test.go b/octopusdeploy/resource_deployment_process_test.go
index d800b81da..10bdefef0 100644
--- a/octopusdeploy/resource_deployment_process_test.go
+++ b/octopusdeploy/resource_deployment_process_test.go
@@ -436,7 +436,6 @@ func testAccDeploymentProcessCheckDestroy(s *terraform.State) error {
func TestDeploymentProcessWithGitDependency(t *testing.T) {
testFramework := test.OctopusContainerTest{}
-
newSpaceId, err := testFramework.Act(t, octoContainer, "../terraform", "51-deploymentprocesswithgitdependency", []string{})
if err != nil {
diff --git a/octopusdeploy/testing_container_test.go b/octopusdeploy/testing_container_test.go
index 6fd5dfba7..1f37ad0a3 100644
--- a/octopusdeploy/testing_container_test.go
+++ b/octopusdeploy/testing_container_test.go
@@ -27,7 +27,7 @@ func TestMain(m *testing.M) {
if *createSharedContainer {
testFramework := test.OctopusContainerTest{}
- octoContainer, octoClient, sqlServerContainer, network, err = testFramework.ArrangeContainer(m)
+ octoContainer, octoClient, sqlServerContainer, network, err = testFramework.ArrangeContainer()
if err != nil {
log.Printf("Failed to arrange containers: (%s)", err.Error())
}
diff --git a/octopusdeploy_framework/datasource_environments.go b/octopusdeploy_framework/datasource_environments.go
index b788107d5..06b52a527 100644
--- a/octopusdeploy_framework/datasource_environments.go
+++ b/octopusdeploy_framework/datasource_environments.go
@@ -45,14 +45,14 @@ func (*environmentDataSource) Schema(_ context.Context, req datasource.SchemaReq
Attributes: map[string]schema.Attribute{
//request
"ids": util.GetQueryIDsDatasourceSchema(),
- "space_id": util.GetSpaceIdDatasourceSchema(schemas.EnvironmentResourceDescription),
+ "space_id": schemas.GetSpaceIdDatasourceSchema(schemas.EnvironmentResourceDescription, false),
"name": util.GetQueryNameDatasourceSchema(),
"partial_name": util.GetQueryPartialNameDatasourceSchema(),
"skip": util.GetQuerySkipDatasourceSchema(),
"take": util.GetQueryTakeDatasourceSchema(),
//response
- "id": util.GetIdDatasourceSchema(),
+ "id": schemas.GetIdDatasourceSchema(true),
},
Blocks: map[string]schema.Block{
"environments": schema.ListNestedBlock{
diff --git a/octopusdeploy_framework/datasource_project_groups.go b/octopusdeploy_framework/datasource_project_groups.go
index 01713abbc..bca82cd2a 100644
--- a/octopusdeploy_framework/datasource_project_groups.go
+++ b/octopusdeploy_framework/datasource_project_groups.go
@@ -50,14 +50,14 @@ func (p *projectGroupsDataSource) Schema(_ context.Context, _ datasource.SchemaR
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
// request
- "space_id": util.GetSpaceIdDatasourceSchema(description),
+ "space_id": schemas.GetSpaceIdDatasourceSchema(description, false),
"ids": util.GetQueryIDsDatasourceSchema(),
"partial_name": util.GetQueryPartialNameDatasourceSchema(),
"skip": util.GetQuerySkipDatasourceSchema(),
"take": util.GetQueryTakeDatasourceSchema(),
// response
- "id": util.GetIdDatasourceSchema(),
+ "id": schemas.GetIdDatasourceSchema(true),
},
Blocks: map[string]schema.Block{
"project_groups": schema.ListNestedBlock{
diff --git a/octopusdeploy_framework/datasource_spaces.go b/octopusdeploy_framework/datasource_spaces.go
index c7ec6414c..00ba2352c 100644
--- a/octopusdeploy_framework/datasource_spaces.go
+++ b/octopusdeploy_framework/datasource_spaces.go
@@ -42,7 +42,7 @@ func (*spacesDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, r
"take": schemas.GetQueryTakeDatasourceSchema(),
// response
- "id": schemas.GetIdDatasourceSchema(),
+ "id": schemas.GetIdDatasourceSchema(true),
},
Blocks: map[string]schema.Block{
"spaces": schema.ListNestedBlock{
diff --git a/octopusdeploy_framework/datasource_tenants.go b/octopusdeploy_framework/datasource_tenants.go
new file mode 100644
index 000000000..bf8b2867a
--- /dev/null
+++ b/octopusdeploy_framework/datasource_tenants.go
@@ -0,0 +1,80 @@
+package octopusdeploy_framework
+
+import (
+ "context"
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tenants"
+ "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"
+ datasourceSchema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "time"
+)
+
+type tenantsDataSource struct {
+ *Config
+}
+
+func NewTenantsDataSource() datasource.DataSource {
+ return &tenantsDataSource{}
+}
+
+func (*tenantsDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = util.GetTypeName("tenants")
+}
+
+func (e *tenantsDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+ e.Config = DataSourceConfiguration(req, resp)
+}
+
+func (*tenantsDataSource) Schema(_ context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ resp.Schema = datasourceSchema.Schema{
+ Description: "Provides information about existing tenants.",
+ Attributes: schemas.GetTenantsDataSourceSchema(),
+ Blocks: map[string]datasourceSchema.Block{
+ "tenants": datasourceSchema.ListNestedBlock{
+ Description: "A list of tenants that match the filter(s).",
+ NestedObject: datasourceSchema.NestedBlockObject{
+ Attributes: schemas.GetTenantDataSourceSchema(),
+ },
+ },
+ },
+ }
+}
+
+func (b *tenantsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+ var err error
+ var data schemas.TenantsModel
+ resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ query := tenants.TenantsQuery{
+ ClonedFromTenantID: data.ClonedFromTenantId.ValueString(),
+ IDs: util.ExpandStringList(data.IDs),
+ IsClone: data.IsClone.ValueBool(),
+ Name: data.Name.ValueString(),
+ PartialName: data.PartialName.ValueString(),
+ ProjectID: data.ProjectId.ValueString(),
+ Skip: int(data.Skip.ValueInt64()),
+ Tags: util.ExpandStringList(data.Tags),
+ Take: int(data.Take.ValueInt64()),
+ }
+
+ existingTenants, err := tenants.Get(b.Client, data.SpaceID.ValueString(), query)
+ if err != nil {
+ resp.Diagnostics.AddError("unable to load tenants", err.Error())
+ return
+ }
+
+ flattenedTenants := []interface{}{}
+ for _, tenant := range existingTenants.Items {
+ flattenedTenants = append(flattenedTenants, schemas.FlattenTenant(tenant))
+ }
+
+ data.ID = types.StringValue("Tenants " + time.Now().UTC().String())
+ data.Tenants, _ = types.ListValueFrom(ctx, types.ObjectType{AttrTypes: schemas.TenantObjectType()}, flattenedTenants)
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
diff --git a/octopusdeploy/data_source_tenants_test.go b/octopusdeploy_framework/datasource_tenants_test.go
similarity index 82%
rename from octopusdeploy/data_source_tenants_test.go
rename to octopusdeploy_framework/datasource_tenants_test.go
index b7baa57c8..95907b087 100644
--- a/octopusdeploy/data_source_tenants_test.go
+++ b/octopusdeploy_framework/datasource_tenants_test.go
@@ -1,13 +1,12 @@
-package octopusdeploy
+package octopusdeploy_framework
import (
"fmt"
+ "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/terraform"
"strconv"
"testing"
-
- "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 TestAccDataSourceTenants(t *testing.T) {
@@ -17,7 +16,7 @@ func TestAccDataSourceTenants(t *testing.T) {
take := acctest.RandIntRange(0, 100)
resource.Test(t, resource.TestCase{
- PreCheck: func() { testAccPreCheck(t) },
+ PreCheck: func() { TestAccPreCheck(t) },
ProtoV6ProviderFactories: ProtoV6ProviderFactories(),
Steps: []resource.TestStep{
{
diff --git a/octopusdeploy_framework/framework_provider.go b/octopusdeploy_framework/framework_provider.go
index 447f66237..95bf6c0a1 100644
--- a/octopusdeploy_framework/framework_provider.go
+++ b/octopusdeploy_framework/framework_provider.go
@@ -2,9 +2,9 @@ package octopusdeploy_framework
import (
"context"
- "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
"os"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
@@ -70,6 +70,7 @@ func (p *octopusDeployFrameworkProvider) DataSources(ctx context.Context) []func
NewLibraryVariableSetDataSource,
NewVariablesDataSource,
NewProjectsDataSource,
+ NewTenantsDataSource,
}
}
diff --git a/octopusdeploy_framework/schemas/environment.go b/octopusdeploy_framework/schemas/environment.go
index 964532446..6ab91f49d 100644
--- a/octopusdeploy_framework/schemas/environment.go
+++ b/octopusdeploy_framework/schemas/environment.go
@@ -27,21 +27,20 @@ const (
func GetEnvironmentDatasourceSchema() map[string]datasourceSchema.Attribute {
return map[string]datasourceSchema.Attribute{
- "id": util.GetIdDatasourceSchema(),
- "slug": util.GetSlugDatasourceSchema(EnvironmentResourceDescription),
- "name": util.GetNameDatasourceWithMaxLengthSchema(true, 50),
+ "id": GetIdDatasourceSchema(true),
+ "slug": util.GetSlugDatasourceSchema(EnvironmentResourceDescription, true),
+ "name": GetReadonlyNameDatasourceSchema(),
"description": util.GetDescriptionDatasourceSchema(EnvironmentResourceDescription),
EnvironmentSortOrder: util.GetSortOrderDataSourceSchema(EnvironmentResourceDescription),
EnvironmentAllowDynamicInfrastructure: datasourceSchema.BoolAttribute{
- Optional: true,
+ Computed: true,
},
EnvironmentUseGuidedFailure: datasourceSchema.BoolAttribute{
- Optional: true,
+ Computed: true,
},
- "space_id": util.GetSpaceIdDatasourceSchema(EnvironmentResourceDescription),
+ "space_id": GetSpaceIdDatasourceSchema(EnvironmentResourceDescription, true),
EnvironmentJiraExtensionSettings: datasourceSchema.ListNestedAttribute{
Description: "Provides extension settings for the Jira integration for this environment.",
- Optional: true,
Computed: true,
NestedObject: datasourceSchema.NestedAttributeObject{
Attributes: map[string]datasourceSchema.Attribute{
@@ -62,7 +61,6 @@ func GetEnvironmentDatasourceSchema() map[string]datasourceSchema.Attribute {
},
EnvironmentJiraServiceManagementExtensionSettings: datasourceSchema.ListNestedAttribute{
Description: "Provides extension settings for the Jira Service Management (JSM) integration for this environment.",
- Optional: true,
Computed: true,
NestedObject: datasourceSchema.NestedAttributeObject{
Attributes: map[string]datasourceSchema.Attribute{
@@ -72,7 +70,6 @@ func GetEnvironmentDatasourceSchema() map[string]datasourceSchema.Attribute {
},
EnvironmentServiceNowExtensionSettings: datasourceSchema.ListNestedAttribute{
Description: "Provides extension settings for the ServiceNow integration for this environment.",
- Optional: true,
Computed: true,
NestedObject: datasourceSchema.NestedAttributeObject{
Attributes: map[string]datasourceSchema.Attribute{
diff --git a/octopusdeploy_framework/schemas/feed.go b/octopusdeploy_framework/schemas/feed.go
index 60a084e64..61b01ae23 100644
--- a/octopusdeploy_framework/schemas/feed.go
+++ b/octopusdeploy_framework/schemas/feed.go
@@ -78,10 +78,10 @@ func GetFeedsDataSourceSchema() map[string]datasourceSchema.Attribute {
"partial_name": util.GetQueryPartialNameDatasourceSchema(),
"skip": util.GetQuerySkipDatasourceSchema(),
"take": util.GetQueryTakeDatasourceSchema(),
- "space_id": util.GetSpaceIdDatasourceSchema("feeds"),
+ "space_id": GetSpaceIdDatasourceSchema("feeds", false),
// response
- "id": util.GetIdDatasourceSchema(),
+ "id": GetIdDatasourceSchema(true),
}
}
@@ -89,7 +89,7 @@ func GetFeedDataSourceSchema() map[string]datasourceSchema.Attribute {
return map[string]datasourceSchema.Attribute{
"feed_type": datasourceSchema.StringAttribute{
Description: "A filter to search by feed type. Valid feed types are `AwsElasticContainerRegistry`, `BuiltIn`, `Docker`, `GitHub`, `Helm`, `Maven`, `NuGet`, or `OctopusProject`.",
- Optional: true,
+ Computed: true,
Validators: []validator.String{
stringvalidator.OneOf(
"AwsElasticContainerRegistry",
@@ -103,17 +103,17 @@ func GetFeedDataSourceSchema() map[string]datasourceSchema.Attribute {
},
},
"feed_uri": datasourceSchema.StringAttribute{
- Required: true,
+ Computed: true,
},
- "id": util.GetIdDatasourceSchema(),
+ "id": GetIdDatasourceSchema(true),
"is_enhanced_mode": datasourceSchema.BoolAttribute{
- Optional: true,
+ Computed: true,
},
- "name": util.GetNameDatasourceSchema(true),
+ "name": GetReadonlyNameDatasourceSchema(),
"password": datasourceSchema.StringAttribute{
Description: "The password associated with this resource.",
Sensitive: true,
- Optional: true,
+ Computed: true,
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
},
@@ -121,45 +121,42 @@ func GetFeedDataSourceSchema() map[string]datasourceSchema.Attribute {
"package_acquisition_location_options": datasourceSchema.ListAttribute{
Computed: true,
ElementType: types.StringType,
- Optional: true,
},
"region": datasourceSchema.StringAttribute{
Computed: true,
},
"registry_path": datasourceSchema.StringAttribute{
- Optional: true,
+ Computed: true,
},
"secret_key": datasourceSchema.StringAttribute{
- Optional: true,
+ Computed: true,
Sensitive: true,
},
- "space_id": util.GetSpaceIdDatasourceSchema("feeds"),
+ "space_id": GetSpaceIdDatasourceSchema("feeds", true),
"username": datasourceSchema.StringAttribute{
Description: "The username associated with this resource.",
Sensitive: true,
- Optional: true,
+ Computed: true,
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
},
},
"delete_unreleased_packages_after_days": datasourceSchema.Int64Attribute{
- Optional: true,
+ Computed: true,
},
"access_key": datasourceSchema.StringAttribute{
- Required: true,
+ Computed: true,
Description: "The AWS access key to use when authenticating against Amazon Web Services.",
},
"api_version": datasourceSchema.StringAttribute{
- Optional: true,
+ Computed: true,
},
"download_attempts": datasourceSchema.Int64Attribute{
Description: "The number of times a deployment should attempt to download a package from this feed before failing.",
- Optional: true,
Computed: true,
},
"download_retry_backoff_seconds": datasourceSchema.Int64Attribute{
Description: "The number of seconds to apply as a linear back off between download attempts.",
- Optional: true,
Computed: true,
},
}
diff --git a/octopusdeploy_framework/schemas/library_variable_set.go b/octopusdeploy_framework/schemas/library_variable_set.go
index 3ca65b627..76ba84d14 100644
--- a/octopusdeploy_framework/schemas/library_variable_set.go
+++ b/octopusdeploy_framework/schemas/library_variable_set.go
@@ -42,8 +42,8 @@ func getLibraryVariableSetDataSchema() map[string]datasourceSchema.Attribute {
Description: "A filter to search by content type.",
Optional: true,
},
- "id": util.GetIdDatasourceSchema(),
- "space_id": util.GetSpaceIdDatasourceSchema("library variable set"),
+ "id": GetIdDatasourceSchema(true),
+ "space_id": GetSpaceIdDatasourceSchema("library variable set", false),
"ids": util.GetQueryIDsDatasourceSchema(),
"partial_name": util.GetQueryPartialNameDatasourceSchema(),
"skip": util.GetQuerySkipDatasourceSchema(),
@@ -53,16 +53,16 @@ func getLibraryVariableSetDataSchema() map[string]datasourceSchema.Attribute {
func GetLibraryVariableSetObjectDatasourceSchema() map[string]datasourceSchema.Attribute {
return map[string]datasourceSchema.Attribute{
- "description": GetDescriptionDatasourceSchema("library variable set"),
- "id": GetIdDatasourceSchema(),
- "name": GetNameDatasourceSchema(false),
- "space_id": GetSpaceIdDatasourceSchema("library variable set"),
+ "description": GetReadonlyDescriptionDatasourceSchema("library variable set"),
+ "id": GetIdDatasourceSchema(true),
+ "name": GetReadonlyNameDatasourceSchema(),
+ "space_id": GetSpaceIdDatasourceSchema("library variable set", true),
"template_ids": datasourceSchema.MapAttribute{
ElementType: types.StringType,
Computed: true,
},
"template": datasourceSchema.ListAttribute{
- Optional: true,
+ Computed: true,
ElementType: types.ObjectType{AttrTypes: TemplateObjectType()},
},
"variable_set_id": datasourceSchema.StringAttribute{
diff --git a/octopusdeploy_framework/schemas/project_group.go b/octopusdeploy_framework/schemas/project_group.go
index bafc5ea42..27b42acfd 100644
--- a/octopusdeploy_framework/schemas/project_group.go
+++ b/octopusdeploy_framework/schemas/project_group.go
@@ -15,7 +15,7 @@ func GetProjectGroupDatasourceSchema() map[string]datasourceSchema.Attribute {
return map[string]datasourceSchema.Attribute{
"id": util.GetIdResourceSchema(),
"space_id": util.GetSpaceIdResourceSchema(projectGroupDescription),
- "name": util.GetNameResourceSchema(true),
+ "name": GetReadonlyNameDatasourceSchema(),
"retention_policy_id": datasourceSchema.StringAttribute{
Computed: true,
Optional: true,
diff --git a/octopusdeploy_framework/schemas/schema.go b/octopusdeploy_framework/schemas/schema.go
index 395aefc7f..496dbc913 100644
--- a/octopusdeploy_framework/schemas/schema.go
+++ b/octopusdeploy_framework/schemas/schema.go
@@ -5,7 +5,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
-
//"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
@@ -56,20 +55,41 @@ func GetQueryTakeDatasourceSchema() datasourceSchema.Attribute {
}
}
-func GetIdDatasourceSchema() datasourceSchema.Attribute {
+func GetReadonlyNameDatasourceSchema() datasourceSchema.Attribute {
return datasourceSchema.StringAttribute{
- Description: "The unique ID for this resource.",
+ Description: "The name of this resource.",
Computed: true,
- Optional: true,
}
}
-func GetSpaceIdDatasourceSchema(resourceDescription string) datasourceSchema.Attribute {
- return datasourceSchema.StringAttribute{
+func GetIdDatasourceSchema(isReadOnly bool) datasourceSchema.Attribute {
+ s := datasourceSchema.StringAttribute{
+ Description: "The unique ID for this resource.",
+ }
+
+ if isReadOnly {
+ s.Computed = true
+ } else {
+ s.Computed = true
+ s.Optional = true
+ }
+
+ return s
+}
+
+func GetSpaceIdDatasourceSchema(resourceDescription string, isReadOnly bool) datasourceSchema.Attribute {
+ s := datasourceSchema.StringAttribute{
Description: "The space ID associated with this " + resourceDescription + ".",
- Computed: true,
- Optional: true,
}
+
+ if isReadOnly {
+ s.Computed = true
+ } else {
+ s.Computed = true
+ s.Optional = true
+ }
+
+ return s
}
func GetNameDatasourceWithMaxLengthSchema(isRequired bool, maxLength int) datasourceSchema.Attribute {
@@ -113,6 +133,13 @@ func GetDescriptionDatasourceSchema(resourceDescription string) datasourceSchema
}
}
+func GetReadonlyDescriptionDatasourceSchema(resourceDescription string) datasourceSchema.Attribute {
+ return datasourceSchema.StringAttribute{
+ Description: "The description of this " + resourceDescription + ".",
+ Computed: true,
+ }
+}
+
func GetIdResourceSchema() resourceSchema.Attribute {
return resourceSchema.StringAttribute{
Description: "The unique ID for this resource.",
@@ -155,12 +182,19 @@ func GetDescriptionResourceSchema(resourceDescription string) resourceSchema.Att
}
}
-func GetSlugDatasourceSchema(resourceDescription string) datasourceSchema.Attribute {
- return datasourceSchema.StringAttribute{
+func GetSlugDatasourceSchema(resourceDescription string, isReadOnly bool) datasourceSchema.Attribute {
+ s := datasourceSchema.StringAttribute{
Description: fmt.Sprintf("The unique slug of this %s", resourceDescription),
- Optional: true,
- Computed: true,
}
+
+ if isReadOnly {
+ s.Computed = true
+ } else {
+ s.Optional = true
+ s.Computed = true
+ }
+
+ return s
}
func GetSlugResourceSchema(resourceDescription string) resourceSchema.Attribute {
diff --git a/octopusdeploy_framework/schemas/space.go b/octopusdeploy_framework/schemas/space.go
index 662a95c96..0c1159553 100644
--- a/octopusdeploy_framework/schemas/space.go
+++ b/octopusdeploy_framework/schemas/space.go
@@ -1,10 +1,13 @@
package schemas
import (
+ "fmt"
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
datasourceSchema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
resourceSchema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
)
@@ -57,29 +60,33 @@ func GetSpaceResourceSchema() map[string]resourceSchema.Attribute {
func GetSpaceDatasourceSchema() map[string]datasourceSchema.Attribute {
return map[string]datasourceSchema.Attribute{
- "id": GetIdDatasourceSchema(),
- "description": GetDescriptionDatasourceSchema(spaceDescription),
- "name": GetNameDatasourceWithMaxLengthSchema(true, 20),
- "slug": GetSlugDatasourceSchema(spaceDescription),
+ "id": GetIdDatasourceSchema(true),
+ "description": GetReadonlyDescriptionDatasourceSchema(spaceDescription),
+ "name": datasourceSchema.StringAttribute{
+ Description: fmt.Sprintf("The name of this resource, no more than %d characters long", 20),
+ Validators: []validator.String{
+ stringvalidator.LengthBetween(1, 20),
+ },
+ Required: true,
+ },
+ "slug": GetSlugDatasourceSchema(spaceDescription, true),
"space_managers_teams": datasourceSchema.SetAttribute{
ElementType: types.StringType,
Description: "A list of team IDs designated to be managers of this space.",
- Optional: true,
Computed: true,
},
"space_managers_team_members": datasourceSchema.SetAttribute{
ElementType: types.StringType,
Description: "A list of user IDs designated to be managers of this space.",
- Optional: true,
Computed: true,
},
"is_task_queue_stopped": datasourceSchema.BoolAttribute{
Description: "Specifies the status of the task queue for this space.",
- Optional: true,
+ Computed: true,
},
"is_default": datasourceSchema.BoolAttribute{
Description: "Specifies if this space is the default space in Octopus.",
- Optional: true,
+ Computed: true,
},
}
}
diff --git a/octopusdeploy_framework/schemas/tenant.go b/octopusdeploy_framework/schemas/tenant.go
new file mode 100644
index 000000000..41d55b7d1
--- /dev/null
+++ b/octopusdeploy_framework/schemas/tenant.go
@@ -0,0 +1,107 @@
+package schemas
+
+import (
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tenants"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ datasourceSchema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+type TenantModel struct {
+ ClonedFromTenantId types.String `tfsdk:"cloned_from_tenant_id"`
+ Description types.String `tfsdk:"description"`
+ ID types.String `tfsdk:"id"`
+ Name types.String `tfsdk:"name"`
+ SpaceID types.String `tfsdk:"space_id"`
+ TenantTags types.List `tfsdk:"tenant_tags"`
+}
+
+type TenantsModel struct {
+ ClonedFromTenantId types.String `tfsdk:"cloned_from_tenant_id"`
+ ID types.String `tfsdk:"id"`
+ IDs types.List `tfsdk:"ids"`
+ IsClone types.Bool `tfsdk:"is_clone"`
+ Name types.String `tfsdk:"name"`
+ PartialName types.String `tfsdk:"partial_name"`
+ ProjectId types.String `tfsdk:"project_id"`
+ Skip types.Int64 `tfsdk:"skip"`
+ Tags types.List `tfsdk:"tags"`
+ SpaceID types.String `tfsdk:"space_id"`
+ Tenants types.List `tfsdk:"tenants"`
+ Take types.Int64 `tfsdk:"take"`
+}
+
+func TenantObjectType() map[string]attr.Type {
+ return map[string]attr.Type{
+ "cloned_from_tenant_id": types.StringType,
+ "description": types.StringType,
+ "id": types.StringType,
+ "name": types.StringType,
+ "space_id": types.StringType,
+ "tenant_tags": types.ListType{ElemType: types.StringType},
+ }
+}
+
+func FlattenTenant(tenant *tenants.Tenant) attr.Value {
+ tenantTags := make([]attr.Value, len(tenant.TenantTags))
+ for i, value := range tenant.TenantTags {
+ tenantTags[i] = types.StringValue(value)
+ }
+ var tenantTagsList, _ = types.ListValue(types.StringType, tenantTags)
+
+ return types.ObjectValueMust(TenantObjectType(), map[string]attr.Value{
+ "cloned_from_tenant_id": types.StringValue(tenant.ClonedFromTenantID),
+ "description": types.StringValue(tenant.Description),
+ "id": types.StringValue(tenant.GetID()),
+ "name": types.StringValue(tenant.Name),
+ "space_id": types.StringValue(tenant.SpaceID),
+ "tenant_tags": tenantTagsList,
+ })
+}
+
+func GetTenantsDataSourceSchema() map[string]datasourceSchema.Attribute {
+ return map[string]datasourceSchema.Attribute{
+ "cloned_from_tenant_id": datasourceSchema.StringAttribute{
+ Description: "A filter to search for a cloned tenant by its ID.",
+ Optional: true,
+ },
+ "id": GetIdDatasourceSchema(true),
+ "ids": util.GetQueryIDsDatasourceSchema(),
+ "is_clone": datasourceSchema.BoolAttribute{
+ Description: "A filter to search for cloned resources.",
+ Optional: true,
+ },
+ "name": datasourceSchema.StringAttribute{
+ Description: "A filter to search by name.",
+ Optional: true,
+ },
+ "partial_name": util.GetQueryPartialNameDatasourceSchema(),
+ "project_id": datasourceSchema.StringAttribute{
+ Description: "A filter to search by a project ID.",
+ Optional: true,
+ },
+ "skip": util.GetQuerySkipDatasourceSchema(),
+ "tags": util.GetQueryDatasourceTags(),
+ "space_id": GetSpaceIdDatasourceSchema("tenants", false),
+ "take": util.GetQueryTakeDatasourceSchema(),
+ }
+}
+
+func GetTenantDataSourceSchema() map[string]datasourceSchema.Attribute {
+ return map[string]datasourceSchema.Attribute{
+ "cloned_from_tenant_id": datasourceSchema.StringAttribute{
+ Description: "The ID of the tenant from which this tenant was cloned.",
+ Computed: true,
+ },
+ "description": util.GetDescriptionDatasourceSchema("tenants"),
+ "id": GetIdDatasourceSchema(true),
+ "name": GetReadonlyNameDatasourceSchema(),
+ "space_id": GetSpaceIdDatasourceSchema("tenant", true),
+ "tenant_tags": datasourceSchema.ListAttribute{
+ Computed: true,
+ Description: "A list of tenant tags associated with this resource.",
+ ElementType: types.StringType,
+ },
+ }
+}
diff --git a/octopusdeploy_framework/testing_container_test.go b/octopusdeploy_framework/testing_container_test.go
index a876440fd..49319d02e 100644
--- a/octopusdeploy_framework/testing_container_test.go
+++ b/octopusdeploy_framework/testing_container_test.go
@@ -28,7 +28,7 @@ func TestMain(m *testing.M) {
if *createSharedContainer {
testFramework := test.OctopusContainerTest{}
- octoContainer, octoClient, sqlServerContainer, network, err = testFramework.ArrangeContainer(m)
+ octoContainer, octoClient, sqlServerContainer, network, err = testFramework.ArrangeContainer()
if err != nil {
log.Printf("Failed to arrange containers: (%s)", err.Error())
}
diff --git a/octopusdeploy_framework/util/schema.go b/octopusdeploy_framework/util/schema.go
index 433892367..d8108b803 100644
--- a/octopusdeploy_framework/util/schema.go
+++ b/octopusdeploy_framework/util/schema.go
@@ -60,22 +60,6 @@ func GetQueryTakeDatasourceSchema() datasourceSchema.Attribute {
}
}
-func GetIdDatasourceSchema() datasourceSchema.Attribute {
- return datasourceSchema.StringAttribute{
- Description: "The unique ID for this resource.",
- Computed: true,
- Optional: true,
- }
-}
-
-func GetSpaceIdDatasourceSchema(resourceDescription string) datasourceSchema.Attribute {
- return datasourceSchema.StringAttribute{
- Description: "The space ID associated with this " + resourceDescription + ".",
- Computed: true,
- Optional: true,
- }
-}
-
func GetNameDatasourceWithMaxLengthSchema(isRequired bool, maxLength int) datasourceSchema.Attribute {
s := datasourceSchema.StringAttribute{
Description: fmt.Sprintf("The name of this resource, no more than %d characters long", maxLength),
@@ -117,6 +101,14 @@ func GetDescriptionDatasourceSchema(resourceDescription string) datasourceSchema
}
}
+func GetQueryDatasourceTags() datasourceSchema.Attribute {
+ return datasourceSchema.ListAttribute{
+ Description: "A filter to search by a list of tags.",
+ ElementType: types.StringType,
+ Optional: true,
+ }
+}
+
func GetIdResourceSchema() resourceSchema.Attribute {
return resourceSchema.StringAttribute{
Description: "The unique ID for this resource.",
@@ -165,12 +157,19 @@ func GetDescriptionResourceSchema(resourceDescription string) resourceSchema.Att
}
}
-func GetSlugDatasourceSchema(resourceDescription string) resourceSchema.Attribute {
- return resourceSchema.StringAttribute{
+func GetSlugDatasourceSchema(resourceDescription string, isReadOnly bool) datasourceSchema.Attribute {
+ s := datasourceSchema.StringAttribute{
Description: fmt.Sprintf("The unique slug of this %s", resourceDescription),
- Optional: true,
- Computed: true,
}
+
+ if isReadOnly {
+ s.Computed = true
+ } else {
+ s.Optional = true
+ s.Computed = true
+ }
+
+ return s
}
func GetSlugResourceSchema(resourceDescription string) resourceSchema.Attribute {
@@ -184,7 +183,6 @@ func GetSlugResourceSchema(resourceDescription string) resourceSchema.Attribute
func GetSortOrderDataSourceSchema(resourceDescription string) resourceSchema.Attribute {
return resourceSchema.Int64Attribute{
Description: fmt.Sprintf("The order number to sort an %s", resourceDescription),
- Optional: true,
Computed: true,
}
}
From e5533e062c96133ee8e1c084bf7a77cea5cea18e Mon Sep 17 00:00:00 2001
From: Henrik Andersson
Date: Fri, 9 Aug 2024 00:55:57 -0700
Subject: [PATCH 08/28] chore: update environment resource and datasource docs
(#708)
---
docs/resources/environment.md | 17 ++++---
.../schemas/environment.go | 51 ++++++++++++++-----
octopusdeploy_framework/util/schema.go | 3 +-
3 files changed, 49 insertions(+), 22 deletions(-)
diff --git a/docs/resources/environment.md b/docs/resources/environment.md
index c9f9c5d1e..22b847d57 100644
--- a/docs/resources/environment.md
+++ b/docs/resources/environment.md
@@ -3,12 +3,12 @@
page_title: "octopusdeploy_environment Resource - terraform-provider-octopusdeploy"
subcategory: ""
description: |-
-
+ This resource manages environments in Octopus Deploy.
---
# octopusdeploy_environment (Resource)
-
+This resource manages environments in Octopus Deploy.
## Example Usage
@@ -48,17 +48,20 @@ resource "octopusdeploy_environment" "example" {
- `jira_extension_settings` (Block List) Provides extension settings for the Jira integration for this environment. (see [below for nested schema](#nestedblock--jira_extension_settings))
- `jira_service_management_extension_settings` (Block List) Provides extension settings for the Jira Service Management (JSM) integration for this environment. (see [below for nested schema](#nestedblock--jira_service_management_extension_settings))
- `servicenow_extension_settings` (Block List) Provides extension settings for the ServiceNow integration for this environment. (see [below for nested schema](#nestedblock--servicenow_extension_settings))
-- `slug` (String) The unique slug of this environment
-- `sort_order` (Number) The order number to sort an environment
+- `sort_order` (Number) The order number to sort an environment.
- `space_id` (String) The space ID associated with this environment.
- `use_guided_failure` (Boolean)
+### Read-Only
+
+- `slug` (String) The unique slug of this environment
+
### Nested Schema for `jira_extension_settings`
Optional:
-- `environment_type` (String)
+- `environment_type` (String) The Jira environment type of this Octopus deployment environment. Valid values are `"development"`, `"production"`, `"staging"`, `"testing"`, `"unmapped"`.
@@ -66,7 +69,7 @@ Optional:
Optional:
-- `is_enabled` (Boolean)
+- `is_enabled` (Boolean) Specifies whether or not this extension is enabled for this project.
@@ -74,7 +77,7 @@ Optional:
Optional:
-- `is_enabled` (Boolean)
+- `is_enabled` (Boolean) Specifies whether or not this extension is enabled for this project.
## Import
diff --git a/octopusdeploy_framework/schemas/environment.go b/octopusdeploy_framework/schemas/environment.go
index 6ab91f49d..ed57bba65 100644
--- a/octopusdeploy_framework/schemas/environment.go
+++ b/octopusdeploy_framework/schemas/environment.go
@@ -1,6 +1,9 @@
package schemas
import (
+ "fmt"
+ "strings"
+
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/environments"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
@@ -25,6 +28,28 @@ const (
EnvironmentServiceNowExtensionSettingsIsEnabled = "is_enabled"
)
+var jiraEnvironmentTypeNames = struct {
+ Development string
+ Production string
+ Testing string
+ Staging string
+ Unmapped string
+}{
+ Development: "development",
+ Production: "production",
+ Testing: "testing",
+ Staging: "staging",
+ Unmapped: "unmapped",
+}
+
+var jiraEnvironmentTypes = []string{
+ jiraEnvironmentTypeNames.Development,
+ jiraEnvironmentTypeNames.Production,
+ jiraEnvironmentTypeNames.Staging,
+ jiraEnvironmentTypeNames.Testing,
+ jiraEnvironmentTypeNames.Unmapped,
+}
+
func GetEnvironmentDatasourceSchema() map[string]datasourceSchema.Attribute {
return map[string]datasourceSchema.Attribute{
"id": GetIdDatasourceSchema(true),
@@ -48,11 +73,7 @@ func GetEnvironmentDatasourceSchema() map[string]datasourceSchema.Attribute {
Computed: true,
Validators: []validator.String{
stringvalidator.OneOfCaseInsensitive(
- "development",
- "production",
- "testing",
- "staging",
- "unmapped",
+ jiraEnvironmentTypes...,
),
},
},
@@ -82,6 +103,7 @@ func GetEnvironmentDatasourceSchema() map[string]datasourceSchema.Attribute {
func GetEnvironmentResourceSchema() resourceSchema.Schema {
return resourceSchema.Schema{
+ Description: util.GetResourceSchemaDescription(EnvironmentResourceDescription),
Attributes: map[string]resourceSchema.Attribute{
"id": util.GetIdResourceSchema(),
"slug": util.GetSlugResourceSchema(EnvironmentResourceDescription),
@@ -106,14 +128,11 @@ func GetEnvironmentResourceSchema() resourceSchema.Schema {
NestedObject: resourceSchema.NestedBlockObject{
Attributes: map[string]resourceSchema.Attribute{
"environment_type": resourceSchema.StringAttribute{
- Optional: true,
+ Description: fmt.Sprintf("The Jira environment type of this Octopus deployment environment. Valid values are %s.", strings.Join(util.Map(jiraEnvironmentTypes, func(item string) string { return fmt.Sprintf("`\"%s\"`", item) }), ", ")),
+ Optional: true,
Validators: []validator.String{
stringvalidator.OneOfCaseInsensitive(
- "development",
- "production",
- "staging",
- "testing",
- "unmapped",
+ jiraEnvironmentTypes...,
),
},
},
@@ -124,7 +143,10 @@ func GetEnvironmentResourceSchema() resourceSchema.Schema {
Description: "Provides extension settings for the Jira Service Management (JSM) integration for this environment.",
NestedObject: resourceSchema.NestedBlockObject{
Attributes: map[string]resourceSchema.Attribute{
- "is_enabled": resourceSchema.BoolAttribute{Optional: true},
+ "is_enabled": resourceSchema.BoolAttribute{
+ Description: "Specifies whether or not this extension is enabled for this project.",
+ Optional: true,
+ },
},
},
},
@@ -132,7 +154,10 @@ func GetEnvironmentResourceSchema() resourceSchema.Schema {
Description: "Provides extension settings for the ServiceNow integration for this environment.",
NestedObject: resourceSchema.NestedBlockObject{
Attributes: map[string]resourceSchema.Attribute{
- "is_enabled": resourceSchema.BoolAttribute{Optional: true},
+ "is_enabled": resourceSchema.BoolAttribute{
+ Description: "Specifies whether or not this extension is enabled for this project.",
+ Optional: true,
+ },
},
},
},
diff --git a/octopusdeploy_framework/util/schema.go b/octopusdeploy_framework/util/schema.go
index d8108b803..26856a9da 100644
--- a/octopusdeploy_framework/util/schema.go
+++ b/octopusdeploy_framework/util/schema.go
@@ -175,7 +175,6 @@ func GetSlugDatasourceSchema(resourceDescription string, isReadOnly bool) dataso
func GetSlugResourceSchema(resourceDescription string) resourceSchema.Attribute {
return resourceSchema.StringAttribute{
Description: fmt.Sprintf("The unique slug of this %s", resourceDescription),
- Optional: true,
Computed: true,
}
}
@@ -189,7 +188,7 @@ func GetSortOrderDataSourceSchema(resourceDescription string) resourceSchema.Att
func GetSortOrderResourceSchema(resourceDescription string) resourceSchema.Attribute {
return resourceSchema.Int64Attribute{
- Description: fmt.Sprintf("The order number to sort an %s", resourceDescription),
+ Description: fmt.Sprintf("The order number to sort an %s.", resourceDescription),
Optional: true,
Computed: true,
}
From 04b191762f5773a3b62386be37391d58dc51021b Mon Sep 17 00:00:00 2001
From: Isaac Calligeros <101079287+IsaacCalligeros95@users.noreply.github.com>
Date: Mon, 12 Aug 2024 10:35:34 +0930
Subject: [PATCH 09/28] Chore!: Require updated docs (#697)
* Adds a GHA to require no docs differences between generated and checked in
---
.github/workflows/docs.yml | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
create mode 100644 .github/workflows/docs.yml
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 000000000..2e2328067
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,19 @@
+name: Validate Docs
+on:
+ push:
+ branches:
+ - '**'
+ workflow_dispatch:
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - uses: actions/setup-go@v3
+ with:
+ go-version: '1.22'
+ - run: go generate main.go
+ - name: Check for file changes
+ run: git diff --exit-code
From c595415a729f483b16006da13967325bdc5fc900 Mon Sep 17 00:00:00 2001
From: Henrik Andersson
Date: Sun, 11 Aug 2024 20:38:43 -0700
Subject: [PATCH 10/28] chore!: migrate runbook resource to tf framework (#709)
---
docs/resources/runbook.md | 10 +-
octopusdeploy/provider.go | 1 -
octopusdeploy/resource_runbook.go | 104 -------
octopusdeploy/resource_runbook_process.go | 3 +-
octopusdeploy/schema_runbook.go | 176 -----------
.../schema_runbook_retention_period.go | 50 ---
octopusdeploy_framework/framework_provider.go | 1 +
octopusdeploy_framework/resource_project.go | 2 +-
octopusdeploy_framework/resource_runbook.go | 179 +++++++++++
.../resource_runbook_migration_test.go | 121 ++++++++
.../resource_runbook_test.go | 7 +-
.../schemas/connectivity_policy.go | 142 +++++++++
octopusdeploy_framework/schemas/runbook.go | 288 ++++++++++++++++++
.../schemas/runbook_retention_period.go | 99 ++++++
octopusdeploy_framework/util/logging.go | 25 ++
15 files changed, 867 insertions(+), 341 deletions(-)
delete mode 100644 octopusdeploy/resource_runbook.go
delete mode 100644 octopusdeploy/schema_runbook.go
delete mode 100644 octopusdeploy/schema_runbook_retention_period.go
create mode 100644 octopusdeploy_framework/resource_runbook.go
create mode 100644 octopusdeploy_framework/resource_runbook_migration_test.go
rename octopusdeploy/resource_runbook_process_test.go => octopusdeploy_framework/resource_runbook_test.go (99%)
create mode 100644 octopusdeploy_framework/schemas/connectivity_policy.go
create mode 100644 octopusdeploy_framework/schemas/runbook.go
create mode 100644 octopusdeploy_framework/schemas/runbook_retention_period.go
diff --git a/docs/resources/runbook.md b/docs/resources/runbook.md
index b540fd6e7..09b12e7b3 100644
--- a/docs/resources/runbook.md
+++ b/docs/resources/runbook.md
@@ -22,16 +22,16 @@ This resource manages runbooks in Octopus Deploy.
### Optional
-- `connectivity_policy` (Block List, Max: 1) (see [below for nested schema](#nestedblock--connectivity_policy))
+- `connectivity_policy` (Block List) (see [below for nested schema](#nestedblock--connectivity_policy))
- `default_guided_failure_mode` (String) Sets the runbook guided failure mode.
- `description` (String) The description of this runbook.
- `environment_scope` (String) Determines how the runbook is scoped to environments.
- `environments` (List of String) When environment_scope is set to "Specified", this is the list of environments the runbook can be run against.
-- `force_package_download` (Boolean) Whether to force packages to be re-downloaded or not
+- `force_package_download` (Boolean) Whether to force packages to be re-downloaded or not.
- `id` (String) The unique ID for this resource.
-- `multi_tenancy_mode` (String) The tenanted deployment mode of the resource. Valid account types are `Untenanted`, `TenantedOrUntenanted`, or `Tenanted`.
-- `retention_policy` (Block List, Max: 1) Sets the runbook retention policy (see [below for nested schema](#nestedblock--retention_policy))
-- `space_id` (String) The space ID associated with this resource.
+- `multi_tenancy_mode` (String) The tenanted deployment mode of the runbook. Valid modes are `Untenanted`, `TenantedOrUntenanted`, `Tenanted`
+- `retention_policy` (Block List) Sets the runbook retention policy. (see [below for nested schema](#nestedblock--retention_policy))
+- `space_id` (String) The space ID associated with this runbook.
### Read-Only
diff --git a/octopusdeploy/provider.go b/octopusdeploy/provider.go
index 6de5657b1..794853fff 100644
--- a/octopusdeploy/provider.go
+++ b/octopusdeploy/provider.go
@@ -58,7 +58,6 @@ func Provider() *schema.Provider {
"octopusdeploy_project_deployment_target_trigger": resourceProjectDeploymentTargetTrigger(),
"octopusdeploy_external_feed_create_release_trigger": resourceExternalFeedCreateReleaseTrigger(),
"octopusdeploy_project_scheduled_trigger": resourceProjectScheduledTrigger(),
- "octopusdeploy_runbook": resourceRunbook(),
"octopusdeploy_runbook_process": resourceRunbookProcess(),
"octopusdeploy_scoped_user_role": resourceScopedUserRole(),
"octopusdeploy_script_module": resourceScriptModule(),
diff --git a/octopusdeploy/resource_runbook.go b/octopusdeploy/resource_runbook.go
deleted file mode 100644
index 7497a1a04..000000000
--- a/octopusdeploy/resource_runbook.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package octopusdeploy
-
-import (
- "context"
- "fmt"
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/runbooks"
-
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client"
- "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 resourceRunbook() *schema.Resource {
- return &schema.Resource{
- CreateContext: resourceRunbookCreate,
- DeleteContext: resourceRunbookDelete,
- Description: "This resource manages runbooks in Octopus Deploy.",
- Importer: getImporter(),
- ReadContext: resourceRunbookRead,
- Schema: getRunbookSchema(),
- UpdateContext: resourceRunbookUpdate,
- }
-}
-
-func resourceRunbookCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- runbook := expandRunbook(ctx, d)
-
- tflog.Info(ctx, fmt.Sprintf("creating runbook (%s)", runbook.Name))
-
- client := m.(*client.Client)
- createdRunbook, err := runbooks.Add(client, runbook)
- if err != nil {
- return diag.FromErr(err)
- }
-
- if err := setRunbook(ctx, d, createdRunbook); err != nil {
- return diag.FromErr(err)
- }
-
- d.SetId(createdRunbook.GetID())
-
- tflog.Info(ctx, fmt.Sprintf("runbook created (%s)", d.Id()))
- return nil
-}
-
-func resourceRunbookDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- tflog.Info(ctx, fmt.Sprintf("deleting runbook (%s)", d.Id()))
-
- client := m.(*client.Client)
- if err := runbooks.DeleteByID(client, d.Get("space_id").(string), d.Id()); err != nil {
- return diag.FromErr(err)
- }
-
- tflog.Info(ctx, fmt.Sprintf("runbook deleted (%s)", d.Id()))
- d.SetId("")
- return nil
-}
-
-func resourceRunbookRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- tflog.Info(ctx, fmt.Sprintf("reading runbook (%s)", d.Id()))
-
- client := m.(*client.Client)
- runbook, err := runbooks.GetByID(client, d.Get("space_id").(string), d.Id())
- if err != nil {
- return errors.ProcessApiError(ctx, d, err, "runbook")
- }
-
- if err := setRunbook(ctx, d, runbook); err != nil {
- return diag.FromErr(err)
- }
-
- tflog.Info(ctx, fmt.Sprintf("runbook read (%s)", d.Id()))
- return nil
-}
-
-func resourceRunbookUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- tflog.Info(ctx, fmt.Sprintf("updating runbook (%s)", d.Id()))
-
- client := m.(*client.Client)
- runbook := expandRunbook(ctx, d)
- var updatedRunbook *runbooks.Runbook
- var err error
-
- runbookLinks, err := runbooks.GetByID(client, d.Get("space_id").(string), d.Id())
- if err != nil {
- return diag.FromErr(err)
- }
-
- runbook.Links = runbookLinks.Links
-
- updatedRunbook, err = runbooks.Update(client, runbook)
- if err != nil {
- return diag.FromErr(err)
- }
-
- if err := setRunbook(ctx, d, updatedRunbook); err != nil {
- return diag.FromErr(err)
- }
-
- tflog.Info(ctx, fmt.Sprintf("runbook updated (%s)", d.Id()))
- return nil
-}
diff --git a/octopusdeploy/resource_runbook_process.go b/octopusdeploy/resource_runbook_process.go
index 5ff0fb3ac..8019bd4b5 100644
--- a/octopusdeploy/resource_runbook_process.go
+++ b/octopusdeploy/resource_runbook_process.go
@@ -105,7 +105,8 @@ func resourceRunbookProcessDelete(ctx context.Context, d *schema.ResourceData, m
}
runbookProcess := &runbookprocess.RunbookProcess{
- Version: current.Version,
+ ProjectID: current.ProjectID,
+ Version: current.Version,
}
runbookProcess.Links = current.Links
runbookProcess.ID = d.Id()
diff --git a/octopusdeploy/schema_runbook.go b/octopusdeploy/schema_runbook.go
deleted file mode 100644
index 47ffebba4..000000000
--- a/octopusdeploy/schema_runbook.go
+++ /dev/null
@@ -1,176 +0,0 @@
-package octopusdeploy
-
-import (
- "context"
- "fmt"
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/runbooks"
-
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
-)
-
-func expandRunbook(ctx context.Context, d *schema.ResourceData) *runbooks.Runbook {
- name := d.Get("name").(string)
- projectId := d.Get("project_id").(string)
-
- runbook := runbooks.NewRunbook(name, projectId)
- runbook.ID = d.Id()
-
- if v, ok := d.GetOk("description"); ok {
- runbook.Description = v.(string)
- }
-
- if v, ok := d.GetOk("runbook_process_id"); ok {
- runbook.RunbookProcessID = v.(string)
- }
-
- if v, ok := d.GetOk("published_runbook_snapshot_id"); ok {
- runbook.PublishedRunbookSnapshotID = v.(string)
- }
-
- if v, ok := d.GetOk("space_id"); ok {
- runbook.SpaceID = v.(string)
- }
-
- if v, ok := d.GetOk("multi_tenancy_mode"); ok {
- runbook.MultiTenancyMode = core.TenantedDeploymentMode(v.(string))
- }
-
- if v, ok := d.GetOk("connectivity_policy"); ok {
- runbook.ConnectivityPolicy = expandConnectivityPolicy(v.([]interface{}))
- }
-
- if v, ok := d.GetOk("environment_scope"); ok {
- runbook.EnvironmentScope = v.(string)
- }
-
- if v, ok := d.GetOk("environments"); ok {
- runbook.Environments = getSliceFromTerraformTypeList(v)
- }
-
- if v, ok := d.GetOk("default_guided_failure_mode"); ok {
- runbook.DefaultGuidedFailureMode = v.(string)
- }
-
- if v, ok := d.GetOk("retention_policy"); ok {
- runbook.RunRetentionPolicy = expandRunbookRetentionPolicy(v.([]interface{}))
- }
-
- if v, ok := d.GetOk("force_package_download"); ok {
- runbook.ForcePackageDownload = v.(bool)
- }
-
- return runbook
-}
-
-func getRunbookSchema() map[string]*schema.Schema {
- return map[string]*schema.Schema{
- "id": getIDSchema(),
- "name": {
- Description: "The name of the runbook in Octopus Deploy. This name must be unique.",
- Required: true,
- Type: schema.TypeString,
- ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotWhiteSpace),
- },
- "description": {
- Computed: true,
- Description: "The description of this runbook.",
- Optional: true,
- Type: schema.TypeString,
- },
- "project_id": {
- Description: "The project that this runbook belongs to.",
- Required: true,
- Type: schema.TypeString,
- },
- "runbook_process_id": {
- Description: "The runbook process ID.",
- Computed: true,
- Type: schema.TypeString,
- },
- "published_runbook_snapshot_id": {
- Description: "The published snapshot ID.",
- Computed: true,
- Type: schema.TypeString,
- },
- "space_id": getSpaceIDSchema(),
- "multi_tenancy_mode": getTenantedDeploymentSchema(),
- "connectivity_policy": {
- Computed: true,
- Elem: &schema.Resource{Schema: getConnectivityPolicySchema()},
- MaxItems: 1,
- Optional: true,
- Type: schema.TypeList,
- },
- "environment_scope": {
- Description: "Determines how the runbook is scoped to environments.",
- Computed: true,
- Optional: true,
- Type: schema.TypeString,
- ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{
- "All",
- "Specified",
- "FromProjectLifecycles",
- }, false)),
- },
- "environments": {
- Description: "When environment_scope is set to \"Specified\", this is the list of environments the runbook can be run against.",
- Computed: true,
- Optional: true,
- Elem: &schema.Schema{Type: schema.TypeString},
- Type: schema.TypeList,
- },
- "default_guided_failure_mode": {
- Description: "Sets the runbook guided failure mode.",
- Computed: true,
- Optional: true,
- Type: schema.TypeString,
- ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{
- "EnvironmentDefault",
- "Off",
- "On",
- }, false)),
- },
- "retention_policy": {
- Description: "Sets the runbook retention policy",
- Computed: true,
- DefaultFunc: func() (interface{}, error) {
- return flattenRunbookRetentionPeriod(&runbooks.RunbookRetentionPeriod{
- QuantityToKeep: 100,
- ShouldKeepForever: false,
- }), nil
- },
- Elem: &schema.Resource{Schema: getRunbookRetentionPeriodSchema()},
- MaxItems: 1,
- Optional: true,
- Type: schema.TypeList,
- },
- "force_package_download": {
- Description: "Whether to force packages to be re-downloaded or not",
- Computed: true,
- Optional: true,
- Type: schema.TypeBool,
- },
- }
-}
-
-func setRunbook(ctx context.Context, d *schema.ResourceData, runbook *runbooks.Runbook) error {
- d.Set("id", runbook.GetID())
- d.Set("name", runbook.Name)
- d.Set("project_id", runbook.ProjectID)
- d.Set("description", runbook.Description)
- d.Set("runbook_process_id", runbook.RunbookProcessID)
- d.Set("published_runbook_snapshot_id", runbook.PublishedRunbookSnapshotID)
- d.Set("space_id", runbook.SpaceID)
- d.Set("multi_tenancy_mode", runbook.MultiTenancyMode)
- if err := d.Set("connectivity_policy", flattenConnectivityPolicy(runbook.ConnectivityPolicy)); err != nil {
- return fmt.Errorf("error setting connectivity_policy: %s", err)
- }
- d.Set("environment_scope", runbook.EnvironmentScope)
- d.Set("environments", runbook.Environments)
- d.Set("default_guided_failure_mode", runbook.DefaultGuidedFailureMode)
- d.Set("force_package_download", runbook.ForcePackageDownload)
-
- return nil
-}
diff --git a/octopusdeploy/schema_runbook_retention_period.go b/octopusdeploy/schema_runbook_retention_period.go
deleted file mode 100644
index 516cada68..000000000
--- a/octopusdeploy/schema_runbook_retention_period.go
+++ /dev/null
@@ -1,50 +0,0 @@
-package octopusdeploy
-
-import (
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/runbooks"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
-)
-
-func expandRunbookRetentionPolicy(flattenedRetentionPeriod interface{}) *runbooks.RunbookRetentionPeriod {
- if flattenedRetentionPeriod == nil {
- return nil
- }
-
- retentionPeriodProperties := flattenedRetentionPeriod.([]interface{})
- if len(retentionPeriodProperties) == 1 {
- retentionPeriodMap := retentionPeriodProperties[0].(map[string]interface{})
- return &runbooks.RunbookRetentionPeriod{
- QuantityToKeep: int32(retentionPeriodMap["quantity_to_keep"].(int)),
- ShouldKeepForever: retentionPeriodMap["should_keep_forever"].(bool),
- }
- }
-
- return nil
-}
-
-func flattenRunbookRetentionPeriod(r *runbooks.RunbookRetentionPeriod) []interface{} {
- retentionPeriod := make(map[string]interface{})
- retentionPeriod["quantity_to_keep"] = int(r.QuantityToKeep)
- retentionPeriod["should_keep_forever"] = r.ShouldKeepForever
- return []interface{}{retentionPeriod}
-}
-
-func getRunbookRetentionPeriodSchema() map[string]*schema.Schema {
- return map[string]*schema.Schema{
- "quantity_to_keep": {
- Default: 100,
- Description: "How many runs to keep per environment.",
- Optional: true,
- Type: schema.TypeInt,
- ValidateDiagFunc: validation.ToDiagFunc(validation.IntAtLeast(0)),
- },
- "should_keep_forever": {
- Default: false,
- Description: "Indicates if items should never be deleted. The default value is `false`.",
- Optional: true,
- Type: schema.TypeBool,
- ConflictsWith: []string{"retention_policy.quantity_to_keep"},
- },
- }
-}
diff --git a/octopusdeploy_framework/framework_provider.go b/octopusdeploy_framework/framework_provider.go
index 95bf6c0a1..fc1128666 100644
--- a/octopusdeploy_framework/framework_provider.go
+++ b/octopusdeploy_framework/framework_provider.go
@@ -94,6 +94,7 @@ func (p *octopusDeployFrameworkProvider) Resources(ctx context.Context) []func()
NewVariableResource,
NewProjectResource,
NewDockerContainerRegistryFeedResource,
+ NewRunbookResource,
}
}
diff --git a/octopusdeploy_framework/resource_project.go b/octopusdeploy_framework/resource_project.go
index bcefbbf69..7a8ece169 100644
--- a/octopusdeploy_framework/resource_project.go
+++ b/octopusdeploy_framework/resource_project.go
@@ -94,7 +94,7 @@ func (r *projectResource) Read(ctx context.Context, req resource.ReadRequest, re
project, err := projects.GetByID(r.Client, state.SpaceID.ValueString(), state.ID.ValueString())
if err != nil {
- if err := errors.ProcessApiErrorV2(ctx, resp, state, err, "lifecycle"); err != nil {
+ if err := errors.ProcessApiErrorV2(ctx, resp, state, err, "project"); err != nil {
resp.Diagnostics.AddError("Error reading project", err.Error())
}
return
diff --git a/octopusdeploy_framework/resource_runbook.go b/octopusdeploy_framework/resource_runbook.go
new file mode 100644
index 000000000..066f32b13
--- /dev/null
+++ b/octopusdeploy_framework/resource_runbook.go
@@ -0,0 +1,179 @@
+package octopusdeploy_framework
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/runbooks"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+)
+
+var _ resource.ResourceWithImportState = &runbookTypeResource{}
+
+type runbookTypeResource struct {
+ *Config
+}
+
+func NewRunbookResource() resource.Resource {
+ return &runbookTypeResource{}
+}
+
+func (*runbookTypeResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = util.GetTypeName(schemas.RunbookResourceDescription)
+}
+
+func (*runbookTypeResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schemas.GetRunbookResourceSchema()
+}
+
+func (r *runbookTypeResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ r.Config = ResourceConfiguration(req, resp)
+}
+
+func (r *runbookTypeResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ var plan schemas.RunbookTypeResourceModel
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ name := plan.Name.ValueString()
+ projectId := plan.ProjectID.ValueString()
+
+ runbook := runbooks.NewRunbook(name, projectId)
+ if !plan.ID.IsNull() {
+ runbook.ID = plan.ID.ValueString()
+ }
+
+ runbook.Description = plan.Description.ValueString()
+ runbook.RunbookProcessID = plan.RunbookProcessID.ValueString()
+ runbook.PublishedRunbookSnapshotID = plan.PublishedRunbookSnapshotID.ValueString()
+ runbook.SpaceID = plan.SpaceID.ValueString()
+ if !plan.MultiTenancyMode.IsNull() {
+ runbook.MultiTenancyMode = core.TenantedDeploymentMode(plan.MultiTenancyMode.ValueString())
+ }
+ runbook.ConnectivityPolicy = schemas.MapToConnectivityPolicy(plan.ConnectivityPolicy)
+ runbook.EnvironmentScope = plan.EnvironmentScope.ValueString()
+ runbook.Environments = util.ExpandStringList(plan.Environments)
+ runbook.DefaultGuidedFailureMode = plan.DefaultGuidedFailureMode.ValueString()
+ runbook.RunRetentionPolicy = schemas.MapToRunbookRetentionPeriod(plan.RunRetentionPolicy)
+ runbook.ForcePackageDownload = plan.ForcePackageDownload.ValueBool()
+
+ util.Create(ctx, schemas.RunbookResourceDescription, plan)
+
+ createdRunbook, err := runbooks.Add(r.Config.Client, runbook)
+ if err != nil {
+ resp.Diagnostics.AddError(fmt.Sprintf("failed to create runbook (%s)", runbook.Name), err.Error())
+ return
+ }
+
+ resp.Diagnostics.Append(plan.RefreshFromApiResponse(ctx, createdRunbook)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
+
+ util.Created(ctx, schemas.RunbookResourceDescription, createdRunbook)
+}
+
+func (r *runbookTypeResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ var state schemas.RunbookTypeResourceModel
+ resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ util.Reading(ctx, schemas.RunbookResourceDescription, state)
+
+ runbook, err := runbooks.GetByID(r.Config.Client, state.SpaceID.ValueString(), state.ID.ValueString())
+ if err != nil {
+ if err := errors.ProcessApiErrorV2(ctx, resp, state, err, schemas.RunbookResourceDescription); err != nil {
+ resp.Diagnostics.AddError("failed to load runbook", err.Error())
+ }
+ return
+ }
+
+ resp.Diagnostics.Append(state.RefreshFromApiResponse(ctx, runbook)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
+
+ util.Read(ctx, schemas.RunbookResourceDescription, runbook)
+}
+
+func (r *runbookTypeResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ var plan, state schemas.RunbookTypeResourceModel
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
+ resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ util.Update(ctx, schemas.RunbookResourceDescription, plan)
+
+ runbook, err := runbooks.GetByID(r.Config.Client, state.SpaceID.ValueString(), state.ID.ValueString())
+ if err != nil {
+ resp.Diagnostics.AddError("unable to load runbook", err.Error())
+ return
+ }
+
+ updatedRunbook := runbooks.NewRunbook(plan.Name.ValueString(), plan.ProjectID.ValueString())
+ updatedRunbook.ID = runbook.GetID()
+ updatedRunbook.SpaceID = runbook.SpaceID
+ updatedRunbook.Description = plan.Description.ValueString()
+ updatedRunbook.RunbookProcessID = plan.RunbookProcessID.ValueString()
+ updatedRunbook.PublishedRunbookSnapshotID = plan.PublishedRunbookSnapshotID.ValueString()
+ if !plan.MultiTenancyMode.IsNull() {
+ updatedRunbook.MultiTenancyMode = core.TenantedDeploymentMode(plan.MultiTenancyMode.ValueString())
+ }
+ updatedRunbook.ConnectivityPolicy = schemas.MapToConnectivityPolicy(plan.ConnectivityPolicy)
+ updatedRunbook.EnvironmentScope = plan.EnvironmentScope.ValueString()
+ updatedRunbook.Environments = util.ExpandStringList(plan.Environments)
+ updatedRunbook.DefaultGuidedFailureMode = plan.DefaultGuidedFailureMode.ValueString()
+ updatedRunbook.RunRetentionPolicy = schemas.MapToRunbookRetentionPeriod(plan.RunRetentionPolicy)
+ updatedRunbook.ForcePackageDownload = plan.ForcePackageDownload.ValueBool()
+
+ updatedRunbook, err = runbooks.Update(r.Config.Client, updatedRunbook)
+ if err != nil {
+ resp.Diagnostics.AddError("failed to update runbook", err.Error())
+ }
+
+ resp.Diagnostics.Append(plan.RefreshFromApiResponse(ctx, updatedRunbook)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
+
+ util.Updated(ctx, schemas.RunbookResourceDescription, updatedRunbook)
+}
+
+func (*runbookTypeResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
+}
+
+func (r *runbookTypeResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ var state schemas.RunbookTypeResourceModel
+ resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ util.Delete(ctx, schemas.RunbookResourceDescription, state)
+
+ if err := runbooks.DeleteByID(r.Config.Client, state.SpaceID.ValueString(), state.ID.ValueString()); err != nil {
+ resp.Diagnostics.AddError("failed to delete runbook", err.Error())
+ return
+ }
+
+ util.Deleted(ctx, schemas.RunbookResourceDescription, state)
+ resp.State.RemoveResource(ctx)
+}
diff --git a/octopusdeploy_framework/resource_runbook_migration_test.go b/octopusdeploy_framework/resource_runbook_migration_test.go
new file mode 100644
index 000000000..3eb66928a
--- /dev/null
+++ b/octopusdeploy_framework/resource_runbook_migration_test.go
@@ -0,0 +1,121 @@
+package octopusdeploy_framework
+
+import (
+ "fmt"
+ "os"
+ "testing"
+
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/runbooks"
+ internaltest "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/test"
+ "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/plancheck"
+ "github.com/hashicorp/terraform-plugin-testing/terraform"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestRunbookResource_UpgradeFromSDK_ToPluginFramework(t *testing.T) {
+ internaltest.SkipCI(t, "'octopusdeploy_runbook.runbook1' - expected NoOp, got action(s): [update]")
+ os.Setenv("TF_CLI_CONFIG_FILE=", "")
+
+ name := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
+ resource.Test(t, resource.TestCase{
+ CheckDestroy: testRunbookDestroyed,
+ Steps: []resource.TestStep{
+ {
+ ExternalProviders: map[string]resource.ExternalProvider{
+ "octopusdeploy": {
+ VersionConstraint: "0.22.0",
+ Source: "OctopusDeployLabs/octopusdeploy",
+ },
+ },
+ Config: runbookConfig(name),
+ },
+ {
+ ProtoV6ProviderFactories: ProtoV6ProviderFactories(),
+ Config: runbookConfig(name),
+ ConfigPlanChecks: resource.ConfigPlanChecks{
+ PreApply: []plancheck.PlanCheck{
+ plancheck.ExpectResourceAction("octopusdeploy_runbook.runbook1", "NoOp"),
+ },
+ },
+ },
+ {
+ ProtoV6ProviderFactories: ProtoV6ProviderFactories(),
+ Config: updatedRunbookConfig(name),
+ Check: resource.ComposeTestCheckFunc(
+ testRunbookUpdated(t, name),
+ ),
+ },
+ },
+ })
+}
+
+func runbookConfig(name string) string {
+ return fmt.Sprintf(`
+ resource "octopusdeploy_project" "project1" {
+ name = "project %[1]s"
+ lifecycle_id = "Lifecycles-1"
+ project_group_id = "ProjectGroups-1"
+ }
+ resource "octopusdeploy_runbook" "runbook1" {
+ project_id = octopusdeploy_project.project1.id
+ name = "runbook %[1]s"
+ }
+ `, name)
+}
+
+func updatedRunbookConfig(name string) string {
+ return fmt.Sprintf(`
+ resource "octopusdeploy_project" "project1" {
+ name = "project %[1]s"
+ lifecycle_id = "Lifecycles-1"
+ project_group_id = "ProjectGroups-1"
+ }
+ resource "octopusdeploy_runbook" "runbook1" {
+ project_id = octopusdeploy_project.project1.id
+ name = "runbook %[1]s"
+ description = "description %[1]s"
+ connectivity_policy {
+ allow_deployments_to_no_targets = true
+ exclude_unhealthy_targets = true
+ }
+ retention_policy {
+ quantity_to_keep = 10
+ }
+ }
+ `, name)
+}
+
+func testRunbookDestroyed(s *terraform.State) error {
+ for _, rs := range s.RootModule().Resources {
+ if rs.Type != "octopusdeploy_runbook" {
+ runbook, err := runbooks.GetByID(octoClient, octoClient.GetSpaceID(), rs.Primary.ID)
+ if err == nil && runbook != nil {
+ return fmt.Errorf("runbook (%s) still exists", rs.Primary.ID)
+ }
+ }
+ }
+
+ return nil
+}
+
+func testRunbookUpdated(t *testing.T, name string) resource.TestCheckFunc {
+ return func(s *terraform.State) error {
+ projectId := s.RootModule().Resources["octopusdeploy_project.project1"].Primary.ID
+ runbookId := s.RootModule().Resources["octopusdeploy_runbook.runbook1"].Primary.ID
+ runbook, err := runbooks.GetByID(octoClient, octoClient.GetSpaceID(), runbookId)
+ if err != nil {
+ return fmt.Errorf("failed to retrieve runbook by ID: %s", err)
+ }
+
+ assert.NotEmpty(t, runbook.ID, "Runbook ID did not match expected value")
+ assert.Equal(t, runbook.ProjectID, projectId)
+ assert.Equal(t, runbook.Description, fmt.Sprintf("description %s", name))
+ assert.True(t, runbook.ConnectivityPolicy.AllowDeploymentsToNoTargets, "allow_deployments_to_no_targets should be true")
+ assert.True(t, runbook.ConnectivityPolicy.ExcludeUnhealthyTargets, "exclude_unhealthy_targets should be true")
+ assert.Equal(t, runbook.RunRetentionPolicy.QuantityToKeep, 10)
+
+ return nil
+ }
+}
diff --git a/octopusdeploy/resource_runbook_process_test.go b/octopusdeploy_framework/resource_runbook_test.go
similarity index 99%
rename from octopusdeploy/resource_runbook_process_test.go
rename to octopusdeploy_framework/resource_runbook_test.go
index 0f1de80d0..992b38238 100644
--- a/octopusdeploy/resource_runbook_process_test.go
+++ b/octopusdeploy_framework/resource_runbook_test.go
@@ -1,11 +1,12 @@
-package octopusdeploy
+package octopusdeploy_framework
import (
"fmt"
- "github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework/octoclient"
- "github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework/test"
"strings"
"testing"
+
+ "github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework/octoclient"
+ "github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework/test"
)
// TestRunbookResource verifies that a runbook can be reimported with the correct settings
diff --git a/octopusdeploy_framework/schemas/connectivity_policy.go b/octopusdeploy_framework/schemas/connectivity_policy.go
new file mode 100644
index 000000000..508718f3b
--- /dev/null
+++ b/octopusdeploy_framework/schemas/connectivity_policy.go
@@ -0,0 +1,142 @@
+package schemas
+
+import (
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ resourceSchema "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/boolplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+var runbookConnectivityPolicySchemeAttributeNames = struct {
+ AllowDeploymentsToNoTargets string
+ ExcludeUnhealthyTargets string
+ SkipMachineBehavior string
+ TargetRoles string
+}{
+ AllowDeploymentsToNoTargets: "allow_deployments_to_no_targets",
+ ExcludeUnhealthyTargets: "exclude_unhealthy_targets",
+ SkipMachineBehavior: "skip_machine_behavior",
+ TargetRoles: "target_roles",
+}
+
+var skipMachineBehaviorNames = struct {
+ SkipUnavailableMachines string
+ None string
+}{
+ SkipUnavailableMachines: "SkipUnavailableMachines",
+ None: "None",
+}
+
+var skipMachineBehaviors = []string{
+ skipMachineBehaviorNames.SkipUnavailableMachines,
+ skipMachineBehaviorNames.None,
+}
+
+func GetConnectivityPolicyObjectType() map[string]attr.Type {
+ return map[string]attr.Type{
+ runbookConnectivityPolicySchemeAttributeNames.AllowDeploymentsToNoTargets: types.BoolType,
+ runbookConnectivityPolicySchemeAttributeNames.ExcludeUnhealthyTargets: types.BoolType,
+ runbookConnectivityPolicySchemeAttributeNames.SkipMachineBehavior: types.StringType,
+ runbookConnectivityPolicySchemeAttributeNames.TargetRoles: types.ListType{ElemType: types.StringType},
+ }
+}
+
+func getConnectivityPolicySchema() map[string]resourceSchema.Attribute {
+ return map[string]resourceSchema.Attribute{
+ runbookConnectivityPolicySchemeAttributeNames.AllowDeploymentsToNoTargets: resourceSchema.BoolAttribute{
+ Computed: true,
+ Optional: true,
+ Default: booldefault.StaticBool(true),
+ PlanModifiers: []planmodifier.Bool{
+ boolplanmodifier.UseStateForUnknown(),
+ },
+ },
+ runbookConnectivityPolicySchemeAttributeNames.ExcludeUnhealthyTargets: resourceSchema.BoolAttribute{
+ Computed: true,
+ Optional: true,
+ Default: booldefault.StaticBool(false),
+ PlanModifiers: []planmodifier.Bool{
+ boolplanmodifier.UseStateForUnknown(),
+ },
+ },
+ runbookConnectivityPolicySchemeAttributeNames.SkipMachineBehavior: resourceSchema.StringAttribute{
+ Computed: true,
+ Optional: true,
+ Default: stringdefault.StaticString(skipMachineBehaviorNames.None),
+ Validators: []validator.String{
+ stringvalidator.OneOf(
+ skipMachineBehaviors...,
+ ),
+ },
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ runbookConnectivityPolicySchemeAttributeNames.TargetRoles: resourceSchema.ListAttribute{
+ Computed: true,
+ Optional: true,
+ ElementType: types.StringType,
+ PlanModifiers: []planmodifier.List{
+ listplanmodifier.UseStateForUnknown(),
+ },
+ },
+ }
+}
+
+func GetDefaultConnectivityPolicy() *core.ConnectivityPolicy {
+ return &core.ConnectivityPolicy{
+ AllowDeploymentsToNoTargets: true,
+ ExcludeUnhealthyTargets: false,
+ SkipMachineBehavior: core.SkipMachineBehaviorNone,
+ TargetRoles: []string{},
+ }
+}
+
+func MapFromConnectivityPolicy(connectivityPolicy *core.ConnectivityPolicy) attr.Value {
+ if connectivityPolicy == nil {
+ return MapFromConnectivityPolicy(GetDefaultConnectivityPolicy())
+ }
+
+ attrs := map[string]attr.Value{
+ runbookConnectivityPolicySchemeAttributeNames.AllowDeploymentsToNoTargets: types.BoolValue(connectivityPolicy.AllowDeploymentsToNoTargets),
+ runbookConnectivityPolicySchemeAttributeNames.ExcludeUnhealthyTargets: types.BoolValue(connectivityPolicy.ExcludeUnhealthyTargets),
+ runbookConnectivityPolicySchemeAttributeNames.SkipMachineBehavior: types.StringValue(string(connectivityPolicy.SkipMachineBehavior)),
+ runbookConnectivityPolicySchemeAttributeNames.TargetRoles: util.FlattenStringList(connectivityPolicy.TargetRoles),
+ }
+
+ return types.ObjectValueMust(GetConnectivityPolicyObjectType(), attrs)
+}
+
+func MapToConnectivityPolicy(flattenedConnectivityPolicy types.List) *core.ConnectivityPolicy {
+ if flattenedConnectivityPolicy.IsNull() || len(flattenedConnectivityPolicy.Elements()) == 0 {
+ return GetDefaultConnectivityPolicy()
+ }
+
+ obj := flattenedConnectivityPolicy.Elements()[0].(types.Object)
+ attrs := obj.Attributes()
+
+ var connectivityPolicy core.ConnectivityPolicy
+ if allowDeploymentsToNoTargets, ok := attrs[runbookConnectivityPolicySchemeAttributeNames.AllowDeploymentsToNoTargets].(types.Bool); ok && !allowDeploymentsToNoTargets.IsNull() {
+ connectivityPolicy.AllowDeploymentsToNoTargets = allowDeploymentsToNoTargets.ValueBool()
+ }
+ if excludeUnhealthyTargets, ok := attrs[runbookConnectivityPolicySchemeAttributeNames.ExcludeUnhealthyTargets].(types.Bool); ok && !excludeUnhealthyTargets.IsNull() {
+ connectivityPolicy.ExcludeUnhealthyTargets = excludeUnhealthyTargets.ValueBool()
+ }
+ if skipMachineBehavior, ok := attrs[runbookConnectivityPolicySchemeAttributeNames.SkipMachineBehavior].(types.String); ok && !skipMachineBehavior.IsNull() {
+ connectivityPolicy.SkipMachineBehavior = core.SkipMachineBehavior(skipMachineBehavior.ValueString())
+ }
+ if targetRoles, ok := attrs[runbookConnectivityPolicySchemeAttributeNames.TargetRoles].(types.List); ok && !targetRoles.IsNull() {
+ connectivityPolicy.TargetRoles = util.ExpandStringList(targetRoles)
+ }
+
+ return &connectivityPolicy
+}
diff --git a/octopusdeploy_framework/schemas/runbook.go b/octopusdeploy_framework/schemas/runbook.go
new file mode 100644
index 000000000..2bc493d94
--- /dev/null
+++ b/octopusdeploy_framework/schemas/runbook.go
@@ -0,0 +1,288 @@
+package schemas
+
+import (
+ "context"
+ "fmt"
+ "regexp"
+ "strings"
+
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/runbooks"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
+ "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ resourceSchema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+const RunbookResourceDescription = "runbook"
+
+var RunbookSchemaAttributeNames = struct {
+ ID string
+ Name string
+ Description string
+ ProjectID string
+ RunbookProcessID string
+ PublishedRunbookSnapshotID string
+ SpaceID string
+ MultiTenancyMode string
+ ConnectivityPolicy string
+ EnvironmentScope string
+ Environments string
+ DefaultGuidedFailureMode string
+ RetentionPolicy string
+ ForcePackageDownload string
+}{
+ ID: "id",
+ Name: "name",
+ Description: "description",
+ ProjectID: "project_id",
+ RunbookProcessID: "runbook_process_id",
+ PublishedRunbookSnapshotID: "published_runbook_snapshot_id",
+ SpaceID: "space_id",
+ MultiTenancyMode: "multi_tenancy_mode",
+ ConnectivityPolicy: "connectivity_policy",
+ EnvironmentScope: "environment_scope",
+ Environments: "environments",
+ DefaultGuidedFailureMode: "default_guided_failure_mode",
+ RetentionPolicy: "retention_policy",
+ ForcePackageDownload: "force_package_download",
+}
+
+var tenantedDeploymentModeNames = struct {
+ Untenanted string
+ TenantedOrUntenanted string
+ Tenanted string
+}{
+ Untenanted: "Untenanted",
+ TenantedOrUntenanted: "TenantedOrUntenanted",
+ Tenanted: "Tenanted",
+}
+
+var tenantedDeploymentModes = []string{
+ tenantedDeploymentModeNames.Untenanted,
+ tenantedDeploymentModeNames.TenantedOrUntenanted,
+ tenantedDeploymentModeNames.Tenanted,
+}
+
+var environmentScopeNames = struct {
+ All string
+ Specified string
+ FromProjectLifecycles string
+}{
+ All: "All",
+ Specified: "Specified",
+ FromProjectLifecycles: "FromProjectLifecycles",
+}
+
+var environmentScopeTypes = []string{
+ environmentScopeNames.All,
+ environmentScopeNames.Specified,
+ environmentScopeNames.FromProjectLifecycles,
+}
+
+var defaultGuidedFailureModeNames = struct {
+ EnvironmentDefault string
+ Off string
+ On string
+}{
+ EnvironmentDefault: "EnvironmentDefault",
+ Off: "Off",
+ On: "On",
+}
+
+var defaultGuidedFailureModes = []string{
+ defaultGuidedFailureModeNames.EnvironmentDefault,
+ defaultGuidedFailureModeNames.Off,
+ defaultGuidedFailureModeNames.On,
+}
+
+type RunbookTypeResourceModel struct {
+ Name types.String `tfsdk:"name"`
+ ProjectID types.String `tfsdk:"project_id"`
+ Description types.String `tfsdk:"description"`
+ RunbookProcessID types.String `tfsdk:"runbook_process_id"`
+ PublishedRunbookSnapshotID types.String `tfsdk:"published_runbook_snapshot_id"`
+ SpaceID types.String `tfsdk:"space_id"`
+ MultiTenancyMode types.String `tfsdk:"multi_tenancy_mode"`
+ ConnectivityPolicy types.List `tfsdk:"connectivity_policy"`
+ EnvironmentScope types.String `tfsdk:"environment_scope"`
+ Environments types.List `tfsdk:"environments"`
+ DefaultGuidedFailureMode types.String `tfsdk:"default_guided_failure_mode"`
+ RunRetentionPolicy types.List `tfsdk:"retention_policy"`
+ ForcePackageDownload types.Bool `tfsdk:"force_package_download"`
+
+ ResourceModel
+}
+
+type RunbookRetentionPeriodModel struct {
+ QuantityToKeep types.String `tfsdk:"quantity_to_keep"`
+ ShouldKeepForever types.Bool `tfsdk:"should_keep_forever"`
+}
+
+type RunbookConnectivityPolicyModel struct {
+ AllowDeploymentsToNoTargets types.Bool `tfsdk:"allow_deployments_to_no_targets"`
+ ExcludeUnhealthyTargets types.Bool `tfsdk:"exclude_unhealthy_targets"`
+ SkipMachineBehavior types.String `tfsdk:"skip_machine_behaviour"`
+ TargetRoles types.List `tfsdk:"target_roles"`
+}
+
+func GetRunbookResourceSchema() resourceSchema.Schema {
+ return resourceSchema.Schema{
+ Description: util.GetResourceSchemaDescription(RunbookResourceDescription),
+ Attributes: map[string]resourceSchema.Attribute{
+ RunbookSchemaAttributeNames.ID: util.GetIdResourceSchema(),
+ RunbookSchemaAttributeNames.Name: resourceSchema.StringAttribute{
+ Description: "The name of the runbook in Octopus Deploy. This name must be unique.",
+ Required: true,
+ Validators: []validator.String{
+ stringvalidator.RegexMatches(
+ regexp.MustCompile(`\S+`),
+ "expected value to not be an empty string or whitespace",
+ ),
+ },
+ },
+ RunbookSchemaAttributeNames.Description: util.GetDescriptionResourceSchema(RunbookResourceDescription),
+ RunbookSchemaAttributeNames.ProjectID: resourceSchema.StringAttribute{
+ Description: "The project that this runbook belongs to.",
+ Required: true,
+ },
+ RunbookSchemaAttributeNames.RunbookProcessID: resourceSchema.StringAttribute{
+ Description: "The runbook process ID.",
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ RunbookSchemaAttributeNames.PublishedRunbookSnapshotID: resourceSchema.StringAttribute{
+ Description: "The published snapshot ID.",
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ RunbookSchemaAttributeNames.SpaceID: util.GetSpaceIdResourceSchema(RunbookResourceDescription),
+ RunbookSchemaAttributeNames.MultiTenancyMode: resourceSchema.StringAttribute{
+ Description: fmt.Sprintf("The tenanted deployment mode of the runbook. Valid modes are %s", strings.Join(util.Map(tenantedDeploymentModes, func(item string) string { return fmt.Sprintf("`%s`", item) }), ", ")),
+ Computed: true,
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf(tenantedDeploymentModes...),
+ },
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ RunbookSchemaAttributeNames.EnvironmentScope: resourceSchema.StringAttribute{
+ Description: "Determines how the runbook is scoped to environments.",
+ Computed: true,
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf(environmentScopeTypes...),
+ },
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ RunbookSchemaAttributeNames.Environments: resourceSchema.ListAttribute{
+ Description: fmt.Sprintf("When %s is set to \"%s\", this is the list of environments the runbook can be run against.", RunbookSchemaAttributeNames.EnvironmentScope, environmentScopeNames.Specified),
+ Optional: true,
+ Computed: true,
+ ElementType: types.StringType,
+ PlanModifiers: []planmodifier.List{
+ listplanmodifier.UseStateForUnknown(),
+ },
+ },
+ RunbookSchemaAttributeNames.DefaultGuidedFailureMode: resourceSchema.StringAttribute{
+ Description: "Sets the runbook guided failure mode.",
+ Optional: true,
+ Computed: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf(defaultGuidedFailureModes...),
+ },
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ RunbookSchemaAttributeNames.ForcePackageDownload: resourceSchema.BoolAttribute{
+ Description: "Whether to force packages to be re-downloaded or not.",
+ Computed: true,
+ Optional: true,
+ PlanModifiers: []planmodifier.Bool{
+ boolplanmodifier.UseStateForUnknown(),
+ },
+ },
+ },
+ Blocks: map[string]resourceSchema.Block{
+ RunbookSchemaAttributeNames.ConnectivityPolicy: resourceSchema.ListNestedBlock{
+ NestedObject: resourceSchema.NestedBlockObject{
+ Attributes: getConnectivityPolicySchema(),
+ },
+ Validators: []validator.List{
+ listvalidator.SizeAtMost(1),
+ },
+ },
+ RunbookSchemaAttributeNames.RetentionPolicy: resourceSchema.ListNestedBlock{
+ Description: "Sets the runbook retention policy.",
+ NestedObject: resourceSchema.NestedBlockObject{
+ Attributes: getRunbookRetentionPeriodSchema(),
+ },
+ Validators: []validator.List{
+ listvalidator.SizeAtMost(1),
+ },
+ },
+ },
+ }
+}
+
+func (data *RunbookTypeResourceModel) RefreshFromApiResponse(ctx context.Context, runbook *runbooks.Runbook) diag.Diagnostics {
+ var diags diag.Diagnostics
+
+ if runbook == nil {
+ return diags
+ }
+
+ data.ID = types.StringValue(runbook.ID)
+ data.Name = types.StringValue(runbook.Name)
+ data.ProjectID = types.StringValue(runbook.ProjectID)
+ data.Description = types.StringValue(runbook.Description)
+ data.RunbookProcessID = types.StringValue(runbook.RunbookProcessID)
+ data.PublishedRunbookSnapshotID = types.StringValue(runbook.PublishedRunbookSnapshotID)
+ data.SpaceID = types.StringValue(runbook.SpaceID)
+ data.MultiTenancyMode = types.StringValue(string(runbook.MultiTenancyMode))
+ data.EnvironmentScope = types.StringValue(runbook.EnvironmentScope)
+ data.Environments = util.FlattenStringList(runbook.Environments)
+ data.DefaultGuidedFailureMode = types.StringValue(runbook.DefaultGuidedFailureMode)
+ data.ForcePackageDownload = types.BoolValue(runbook.ForcePackageDownload)
+ if !data.ConnectivityPolicy.IsNull() {
+ result, d := types.ListValueFrom(
+ ctx,
+ types.ObjectType{AttrTypes: GetConnectivityPolicyObjectType()},
+ []attr.Value{MapFromConnectivityPolicy(runbook.ConnectivityPolicy)},
+ )
+ diags.Append(d...)
+ data.ConnectivityPolicy = result
+ } /*else {
+ data.ConnectivityPolicy = types.ListValueMust(
+ types.ObjectType{AttrTypes: GetConnectivityPolicyObjectType()},
+ []attr.Value{MapFromConnectivityPolicy(GetDefaultConnectivityPolicy())},
+ )
+ }*/
+ if !data.RunRetentionPolicy.IsNull() {
+ result, d := types.ListValueFrom(
+ ctx,
+ types.ObjectType{AttrTypes: GetRunbookRetentionPeriodObjectType()},
+ []attr.Value{MapFromRunbookRetentionPeriod(runbook.RunRetentionPolicy)},
+ )
+ diags.Append(d...)
+ data.RunRetentionPolicy = result
+ }
+
+ return diags
+}
diff --git a/octopusdeploy_framework/schemas/runbook_retention_period.go b/octopusdeploy_framework/schemas/runbook_retention_period.go
new file mode 100644
index 000000000..d8bbdc8b6
--- /dev/null
+++ b/octopusdeploy_framework/schemas/runbook_retention_period.go
@@ -0,0 +1,99 @@
+package schemas
+
+import (
+ "fmt"
+
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/runbooks"
+ "github.com/hashicorp/terraform-plugin-framework-validators/boolvalidator"
+ "github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ resourceSchema "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/boolplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+var runbookRetentionPeriodSchemeAttributeNames = struct {
+ QuantityToKeep string
+ ShouldKeepForever string
+}{
+ QuantityToKeep: "quantity_to_keep",
+ ShouldKeepForever: "should_keep_forever",
+}
+
+func GetRunbookRetentionPeriodObjectType() map[string]attr.Type {
+ return map[string]attr.Type{
+ runbookRetentionPeriodSchemeAttributeNames.QuantityToKeep: types.Int64Type,
+ runbookRetentionPeriodSchemeAttributeNames.ShouldKeepForever: types.BoolType,
+ }
+}
+
+func getRunbookRetentionPeriodSchema() map[string]resourceSchema.Attribute {
+ return map[string]resourceSchema.Attribute{
+ runbookRetentionPeriodSchemeAttributeNames.QuantityToKeep: resourceSchema.Int64Attribute{
+ Description: "How many runs to keep per environment.",
+ Computed: true,
+ Optional: true,
+ Validators: []validator.Int64{
+ int64validator.AtLeast(0),
+ },
+ PlanModifiers: []planmodifier.Int64{
+ int64planmodifier.UseStateForUnknown(),
+ },
+ },
+ runbookRetentionPeriodSchemeAttributeNames.ShouldKeepForever: resourceSchema.BoolAttribute{
+ Description: "Indicates if items should never be deleted. The default value is `false`.",
+ Computed: true,
+ Optional: true,
+ Default: booldefault.StaticBool(false),
+ Validators: []validator.Bool{
+ boolvalidator.ConflictsWith(path.MatchRelative().AtParent().AtName(runbookRetentionPeriodSchemeAttributeNames.QuantityToKeep)),
+ },
+ PlanModifiers: []planmodifier.Bool{
+ boolplanmodifier.UseStateForUnknown(),
+ },
+ },
+ }
+}
+
+func GetDefaultRunbookRetentionPeriod() *runbooks.RunbookRetentionPeriod {
+ return &runbooks.RunbookRetentionPeriod{
+ QuantityToKeep: 100,
+ ShouldKeepForever: false,
+ }
+}
+
+func MapFromRunbookRetentionPeriod(retentionPeriod *runbooks.RunbookRetentionPeriod) attr.Value {
+ if retentionPeriod == nil {
+ return MapFromRunbookRetentionPeriod(GetDefaultRunbookRetentionPeriod())
+ }
+
+ attrs := map[string]attr.Value{
+ runbookRetentionPeriodSchemeAttributeNames.QuantityToKeep: types.Int64Value(int64(retentionPeriod.QuantityToKeep)),
+ runbookRetentionPeriodSchemeAttributeNames.ShouldKeepForever: types.BoolValue(retentionPeriod.ShouldKeepForever),
+ }
+
+ return types.ObjectValueMust(GetRunbookRetentionPeriodObjectType(), attrs)
+}
+
+func MapToRunbookRetentionPeriod(flattenedRunbookRetentionPeriod types.List) *runbooks.RunbookRetentionPeriod {
+ if flattenedRunbookRetentionPeriod.IsNull() || len(flattenedRunbookRetentionPeriod.Elements()) == 0 {
+ return GetDefaultRunbookRetentionPeriod()
+ }
+ obj := flattenedRunbookRetentionPeriod.Elements()[0].(types.Object)
+ attrs := obj.Attributes()
+
+ var runbookRetentionPeriod runbooks.RunbookRetentionPeriod
+ if quantityToKeep, ok := attrs[runbookRetentionPeriodSchemeAttributeNames.QuantityToKeep].(types.Int64); ok && !quantityToKeep.IsNull() {
+ runbookRetentionPeriod.QuantityToKeep = int32(quantityToKeep.ValueInt64())
+ }
+ if shouldKeepForever, ok := attrs[runbookRetentionPeriodSchemeAttributeNames.ShouldKeepForever].(types.Bool); ok && !shouldKeepForever.IsNull() {
+ runbookRetentionPeriod.ShouldKeepForever = shouldKeepForever.ValueBool()
+ }
+ fmt.Printf("runbook retention period: %#v", runbookRetentionPeriod)
+ return &runbookRetentionPeriod
+}
diff --git a/octopusdeploy_framework/util/logging.go b/octopusdeploy_framework/util/logging.go
index 139d7932d..66b3ca311 100644
--- a/octopusdeploy_framework/util/logging.go
+++ b/octopusdeploy_framework/util/logging.go
@@ -3,6 +3,7 @@ package util
import (
"context"
"fmt"
+
"github.com/hashicorp/terraform-plugin-log/tflog"
)
@@ -13,3 +14,27 @@ func Create(ctx context.Context, resource string, v ...any) {
func Created(ctx context.Context, resource string, v ...any) {
tflog.Info(ctx, fmt.Sprintf("created %s: %#v", resource, v))
}
+
+func Delete(ctx context.Context, resource string, v ...any) {
+ tflog.Info(ctx, fmt.Sprintf("deleting %s: %#v", resource, v))
+}
+
+func Deleted(ctx context.Context, resource string, v ...any) {
+ tflog.Info(ctx, fmt.Sprintf("deleted %s: %#v", resource, v))
+}
+
+func Reading(ctx context.Context, resource string, v ...any) {
+ tflog.Info(ctx, fmt.Sprintf("reading %s: %#v", resource, v))
+}
+
+func Read(ctx context.Context, resource string, v ...any) {
+ tflog.Info(ctx, fmt.Sprintf("read %s: %#v", resource, v))
+}
+
+func Update(ctx context.Context, resource string, v ...any) {
+ tflog.Info(ctx, fmt.Sprintf("updating %s: %#v", resource, v))
+}
+
+func Updated(ctx context.Context, resource string, v ...any) {
+ tflog.Info(ctx, fmt.Sprintf("updated %s: %#v", resource, v))
+}
From be6e0ecbc40204f589722263e49e681a6984f160 Mon Sep 17 00:00:00 2001
From: Isaac Calligeros <101079287+IsaacCalligeros95@users.noreply.github.com>
Date: Mon, 12 Aug 2024 13:27:53 +0930
Subject: [PATCH 11/28] Chore!: migrate tenant resource and datasource (#707)
* Migrate tenants resource
---
docs/resources/tenant.md | 2 +-
octopusdeploy/provider.go | 1 -
octopusdeploy/resource_tenant.go | 108 -----------
octopusdeploy/schema_tenant.go | 104 -----------
octopusdeploy/schema_tenant_test.go | 40 ----
octopusdeploy_framework/framework_provider.go | 1 +
octopusdeploy_framework/resource_tenant.go | 174 ++++++++++++++++++
.../resource_tenant_migration_test.go | 109 +++++++++++
.../resource_tenant_test.go | 11 +-
octopusdeploy_framework/schemas/tenant.go | 31 +++-
10 files changed, 320 insertions(+), 261 deletions(-)
delete mode 100644 octopusdeploy/resource_tenant.go
delete mode 100644 octopusdeploy/schema_tenant.go
delete mode 100644 octopusdeploy/schema_tenant_test.go
create mode 100644 octopusdeploy_framework/resource_tenant.go
create mode 100644 octopusdeploy_framework/resource_tenant_migration_test.go
rename {octopusdeploy => octopusdeploy_framework}/resource_tenant_test.go (95%)
diff --git a/docs/resources/tenant.md b/docs/resources/tenant.md
index ba3ce6aa6..16851becb 100644
--- a/docs/resources/tenant.md
+++ b/docs/resources/tenant.md
@@ -21,7 +21,7 @@ This resource manages tenants in Octopus Deploy.
- `cloned_from_tenant_id` (String) The ID of the tenant from which this tenant was cloned.
- `description` (String) The description of this tenant.
- `id` (String) The unique ID for this resource.
-- `space_id` (String) The space ID associated with this resource.
+- `space_id` (String) The space ID associated with this tenant.
- `tenant_tags` (List of String) A list of tenant tags associated with this resource.
~> **NOTE property `project_environment` deprecated:** The `project_environment` property has been replaced by the `octopusdeploy_tenant_project` resource to allow more advanced provisioning scenarioes.
\ No newline at end of file
diff --git a/octopusdeploy/provider.go b/octopusdeploy/provider.go
index 794853fff..ebdaf0b94 100644
--- a/octopusdeploy/provider.go
+++ b/octopusdeploy/provider.go
@@ -67,7 +67,6 @@ func Provider() *schema.Provider {
"octopusdeploy_tag": resourceTag(),
"octopusdeploy_tag_set": resourceTagSet(),
"octopusdeploy_team": resourceTeam(),
- "octopusdeploy_tenant": resourceTenant(),
"octopusdeploy_tentacle_certificate": resourceTentacleCertificate(),
"octopusdeploy_token_account": resourceTokenAccount(),
"octopusdeploy_user": resourceUser(),
diff --git a/octopusdeploy/resource_tenant.go b/octopusdeploy/resource_tenant.go
deleted file mode 100644
index 899cf6f48..000000000
--- a/octopusdeploy/resource_tenant.go
+++ /dev/null
@@ -1,108 +0,0 @@
-package octopusdeploy
-
-import (
- "context"
- "log"
-
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client"
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tenants"
- "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal"
- "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
- "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
-)
-
-func resourceTenant() *schema.Resource {
- return &schema.Resource{
- CreateContext: resourceTenantCreate,
- DeleteContext: resourceTenantDelete,
- Description: "This resource manages tenants in Octopus Deploy.",
- Importer: getImporter(),
- ReadContext: resourceTenantRead,
- Schema: getTenantSchema(),
- UpdateContext: resourceTenantUpdate,
- }
-}
-
-func resourceTenantCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- internal.Mutex.Lock()
- defer internal.Mutex.Unlock()
-
- tenant := expandTenant(d)
-
- log.Printf("[INFO] creating tenant: %#v", tenant)
-
- client := m.(*client.Client)
- createdTenant, err := tenants.Add(client, tenant)
- if err != nil {
- return diag.FromErr(err)
- }
-
- if err := setTenant(ctx, d, createdTenant); err != nil {
- return diag.FromErr(err)
- }
-
- d.SetId(createdTenant.GetID())
-
- log.Printf("[INFO] tenant created (%s)", d.Id())
- return nil
-}
-
-func resourceTenantDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- internal.Mutex.Lock()
- defer internal.Mutex.Unlock()
-
- log.Printf("[INFO] deleting tenant (%s)", d.Id())
-
- client := m.(*client.Client)
- if err := tenants.DeleteByID(client, d.Get("space_id").(string), d.Id()); err != nil {
- return diag.FromErr(err)
- }
-
- log.Printf("[INFO] tenant deleted (%s)", d.Id())
- d.SetId("")
- return nil
-}
-
-func resourceTenantRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- log.Printf("[INFO] reading tenant (%s)", d.Id())
-
- client := m.(*client.Client)
- tenant, err := tenants.GetByID(client, d.Get("space_id").(string), d.Id())
- if err != nil {
- return errors.ProcessApiError(ctx, d, err, "tenant")
- }
-
- if err := setTenant(ctx, d, tenant); err != nil {
- return diag.FromErr(err)
- }
-
- log.Printf("[INFO] tenant read (%s)", d.Id())
- return nil
-}
-
-func resourceTenantUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- internal.Mutex.Lock()
- defer internal.Mutex.Unlock()
-
- log.Printf("[INFO] updating tenant (%s)", d.Id())
-
- client := m.(*client.Client)
- tenantFromApi, err := tenants.GetByID(client, d.Get("space_id").(string), d.Id())
-
- tenant := expandTenant(d)
-
- // the project environments are not managed here, so we need to maintain the collection when updating
- tenant.ProjectEnvironments = tenantFromApi.ProjectEnvironments
- updatedTenant, err := tenants.Update(client, tenant)
- if err != nil {
- return diag.FromErr(err)
- }
-
- if err := setTenant(ctx, d, updatedTenant); err != nil {
- return diag.FromErr(err)
- }
-
- log.Printf("[INFO] tenant updated (%s)", d.Id())
- return nil
-}
diff --git a/octopusdeploy/schema_tenant.go b/octopusdeploy/schema_tenant.go
deleted file mode 100644
index 260ba93f8..000000000
--- a/octopusdeploy/schema_tenant.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package octopusdeploy
-
-import (
- "context"
- "fmt"
-
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tenants"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
-)
-
-func expandTenant(d *schema.ResourceData) *tenants.Tenant {
- name := d.Get("name").(string)
-
- tenant := tenants.NewTenant(name)
- tenant.ID = d.Id()
-
- if v, ok := d.GetOk("cloned_from_tenant_id"); ok {
- tenant.ClonedFromTenantID = v.(string)
- }
-
- if v, ok := d.GetOk("description"); ok {
- tenant.Description = v.(string)
- }
-
- if v, ok := d.GetOk("space_id"); ok {
- tenant.SpaceID = v.(string)
- }
-
- if v, ok := d.GetOk("tenant_tags"); ok {
- tenant.TenantTags = getSliceFromTerraformTypeList(v)
- }
-
- return tenant
-}
-
-func flattenTenant(tenant *tenants.Tenant) map[string]interface{} {
- if tenant == nil {
- return nil
- }
-
- return map[string]interface{}{
- "cloned_from_tenant_id": tenant.ClonedFromTenantID,
- "description": tenant.Description,
- "id": tenant.GetID(),
- "name": tenant.Name,
- "space_id": tenant.SpaceID,
- "tenant_tags": tenant.TenantTags,
- }
-}
-
-func getTenantDataSchema() map[string]*schema.Schema {
- dataSchema := getTenantSchema()
- setDataSchema(&dataSchema)
-
- return map[string]*schema.Schema{
- "cloned_from_tenant_id": getQueryClonedFromTenantID(),
- "id": getDataSchemaID(),
- "ids": getQueryIDs(),
- "is_clone": getQueryIsClone(),
- "name": getQueryName(),
- "partial_name": getQueryPartialName(),
- "project_id": getQueryProjectID(),
- "skip": getQuerySkip(),
- "tags": getQueryTags(),
- "space_id": getQuerySpaceID(),
- "tenants": {
- Computed: true,
- Description: "A list of tenants that match the filter(s).",
- Elem: &schema.Resource{Schema: dataSchema},
- Optional: false,
- Type: schema.TypeList,
- },
- "take": getQueryTake(),
- }
-}
-
-func getTenantSchema() map[string]*schema.Schema {
- return map[string]*schema.Schema{
- "cloned_from_tenant_id": {
- Description: "The ID of the tenant from which this tenant was cloned.",
- Optional: true,
- Type: schema.TypeString,
- },
- "description": getDescriptionSchema("tenant"),
- "id": getIDSchema(),
- "name": getNameSchema(true),
- "space_id": getSpaceIDSchema(),
- "tenant_tags": getTenantTagsSchema(),
- }
-}
-
-func setTenant(ctx context.Context, d *schema.ResourceData, tenant *tenants.Tenant) error {
- d.Set("cloned_from_tenant_id", tenant.ClonedFromTenantID)
- d.Set("description", tenant.Description)
- d.Set("id", tenant.GetID())
- d.Set("name", tenant.Name)
- d.Set("space_id", tenant.SpaceID)
-
- if err := d.Set("tenant_tags", tenant.TenantTags); err != nil {
- return fmt.Errorf("error setting tenant_tags: %s", err)
- }
-
- return nil
-}
diff --git a/octopusdeploy/schema_tenant_test.go b/octopusdeploy/schema_tenant_test.go
deleted file mode 100644
index 0a1d00d18..000000000
--- a/octopusdeploy/schema_tenant_test.go
+++ /dev/null
@@ -1,40 +0,0 @@
-package octopusdeploy
-
-import (
- "reflect"
- "testing"
-
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tenants"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
- "github.com/stretchr/testify/require"
-)
-
-func TestFlattenTenant(t *testing.T) {
- clonedFromTenantID := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
- description := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
- id := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
- name := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
- projectEnvironments := map[string][]string{}
- spaceID := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
- tenantTags := []string{acctest.RandStringFromCharSet(20, acctest.CharSetAlpha), acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)}
-
- expectedExpanded := tenants.NewTenant(name)
- expectedExpanded.ClonedFromTenantID = clonedFromTenantID
- expectedExpanded.Description = description
- expectedExpanded.ID = id
- expectedExpanded.ProjectEnvironments = projectEnvironments
- expectedExpanded.SpaceID = spaceID
- expectedExpanded.TenantTags = tenantTags
-
- expectedFlattened := map[string]interface{}{
- "cloned_from_tenant_id": clonedFromTenantID,
- "description": description,
- "id": id,
- "name": name,
- "space_id": spaceID,
- "tenant_tags": tenantTags,
- }
-
- actualFlattened := flattenTenant(expectedExpanded)
- require.True(t, reflect.DeepEqual(expectedFlattened, actualFlattened))
-}
diff --git a/octopusdeploy_framework/framework_provider.go b/octopusdeploy_framework/framework_provider.go
index fc1128666..68686d521 100644
--- a/octopusdeploy_framework/framework_provider.go
+++ b/octopusdeploy_framework/framework_provider.go
@@ -95,6 +95,7 @@ func (p *octopusDeployFrameworkProvider) Resources(ctx context.Context) []func()
NewProjectResource,
NewDockerContainerRegistryFeedResource,
NewRunbookResource,
+ NewTenantResource,
}
}
diff --git a/octopusdeploy_framework/resource_tenant.go b/octopusdeploy_framework/resource_tenant.go
new file mode 100644
index 000000000..3fcde55b3
--- /dev/null
+++ b/octopusdeploy_framework/resource_tenant.go
@@ -0,0 +1,174 @@
+package octopusdeploy_framework
+
+import (
+ "context"
+ "fmt"
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tenants"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+ "sort"
+)
+
+type tenantTypeResource struct {
+ *Config
+}
+
+func NewTenantResource() resource.Resource {
+ return &tenantTypeResource{}
+}
+
+func (r *tenantTypeResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = util.GetTypeName("tenant")
+}
+
+func (r *tenantTypeResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Attributes: schemas.GetTenantResourceSchema(),
+ Description: "This resource manages tenants in Octopus Deploy.",
+ }
+}
+
+func (r *tenantTypeResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ r.Config = ResourceConfiguration(req, resp)
+}
+
+func (r *tenantTypeResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ internal.Mutex.Lock()
+ defer internal.Mutex.Unlock()
+
+ var data *schemas.TenantModel
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tenant, err := mapStateToTenant(data)
+ if err != nil {
+ return
+ }
+
+ tflog.Info(ctx, fmt.Sprintf("creating Tenant: %s", tenant.Name))
+
+ createdTenant, err := tenants.Add(r.Config.Client, tenant)
+ if err != nil {
+ resp.Diagnostics.AddError("unable to create tenant", err.Error())
+ return
+ }
+
+ mapTenantToState(data, createdTenant)
+
+ tflog.Info(ctx, fmt.Sprintf("Tenant created (%s)", data.ID))
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *tenantTypeResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ var data *schemas.TenantModel
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Info(ctx, fmt.Sprintf("reading Tenant (%s)", data.ID))
+
+ client := r.Config.Client
+ tenant, err := tenants.GetByID(client, data.SpaceID.ValueString(), data.ID.ValueString())
+ if err != nil {
+ if err := errors.ProcessApiErrorV2(ctx, resp, data, err, "tenant"); err != nil {
+ resp.Diagnostics.AddError("unable to load tenant", err.Error())
+ }
+ return
+ }
+
+ mapTenantToState(data, tenant)
+
+ tflog.Info(ctx, fmt.Sprintf("Tenant read (%s)", tenant.GetID()))
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *tenantTypeResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ internal.Mutex.Lock()
+ defer internal.Mutex.Unlock()
+
+ var data, state *schemas.TenantModel
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
+ resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Debug(ctx, fmt.Sprintf("updating tenant '%s'", data.ID.ValueString()))
+
+ tenantFromApi, err := tenants.GetByID(r.Config.Client, data.SpaceID.ValueString(), data.ID.ValueString())
+
+ tenant, err := mapStateToTenant(data)
+ tenant.ID = state.ID.ValueString()
+ if err != nil {
+ resp.Diagnostics.AddError("unable to map to tenant", err.Error())
+ return
+ }
+
+ tflog.Info(ctx, fmt.Sprintf("updating Tenant (%s)", data.ID))
+
+ tenant.ProjectEnvironments = tenantFromApi.ProjectEnvironments
+ updatedTenant, err := tenants.Update(r.Config.Client, tenant)
+ if err != nil {
+ resp.Diagnostics.AddError("unable to update tenant", err.Error())
+ return
+ }
+
+ mapTenantToState(data, updatedTenant)
+
+ tflog.Info(ctx, fmt.Sprintf("Tenant updated (%s)", data.ID))
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *tenantTypeResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ internal.Mutex.Lock()
+ defer internal.Mutex.Unlock()
+
+ var data schemas.TenantModel
+
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ if err := tenants.DeleteByID(r.Config.Client, data.SpaceID.ValueString(), data.ID.ValueString()); err != nil {
+ resp.Diagnostics.AddError("unable to delete tenant", err.Error())
+ return
+ }
+}
+
+func mapStateToTenant(data *schemas.TenantModel) (*tenants.Tenant, error) {
+ tenant := tenants.NewTenant(data.Name.ValueString())
+ tenant.ID = data.ID.ValueString()
+ tenant.ClonedFromTenantID = data.ClonedFromTenantId.ValueString()
+ tenant.Description = data.Description.ValueString()
+ tenant.SpaceID = data.SpaceID.ValueString()
+ if len(data.TenantTags.Elements()) > 0 {
+ tenant.TenantTags = util.ExpandStringList(data.TenantTags)
+ } else {
+ tenant.TenantTags = []string{}
+ }
+ sort.Strings(tenant.TenantTags)
+
+ return tenant, nil
+}
+
+func mapTenantToState(data *schemas.TenantModel, tenant *tenants.Tenant) {
+ data.ID = types.StringValue(tenant.ID)
+ data.ClonedFromTenantId = types.StringValue(tenant.ClonedFromTenantID)
+ data.Description = types.StringValue(tenant.Description)
+ data.SpaceID = types.StringValue(tenant.SpaceID)
+ data.Name = types.StringValue(tenant.Name)
+ sort.Strings(tenant.TenantTags)
+ data.TenantTags = util.Ternary(tenant.TenantTags != nil && len(tenant.TenantTags) > 0, util.FlattenStringList(tenant.TenantTags), types.ListValueMust(types.StringType, make([]attr.Value, 0)))
+}
diff --git a/octopusdeploy_framework/resource_tenant_migration_test.go b/octopusdeploy_framework/resource_tenant_migration_test.go
new file mode 100644
index 000000000..0ca7404e7
--- /dev/null
+++ b/octopusdeploy_framework/resource_tenant_migration_test.go
@@ -0,0 +1,109 @@
+package octopusdeploy_framework
+
+import (
+ "fmt"
+ "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/plancheck"
+ "github.com/hashicorp/terraform-plugin-testing/terraform"
+ "github.com/stretchr/testify/assert"
+ "os"
+ "sort"
+ "testing"
+)
+
+func TestTenantResource_UpgradeFromSDK_ToPluginFramework(t *testing.T) {
+ // override the path to check for terraformrc file and test against the real 0.21.1 version
+ os.Setenv("TF_CLI_CONFIG_FILE=", "")
+
+ name := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
+ resource.Test(t, resource.TestCase{
+ CheckDestroy: testTenantProjectDestroyed,
+ Steps: []resource.TestStep{
+ {
+ ExternalProviders: map[string]resource.ExternalProvider{
+ "octopusdeploy": {
+ VersionConstraint: "0.22.0",
+ Source: "OctopusDeployLabs/octopusdeploy",
+ },
+ },
+ Config: tenantConfig(),
+ },
+ {
+ ProtoV6ProviderFactories: ProtoV6ProviderFactories(),
+ Config: tenantConfig(),
+ ConfigPlanChecks: resource.ConfigPlanChecks{
+ PreApply: []plancheck.PlanCheck{
+ plancheck.ExpectEmptyPlan(),
+ },
+ },
+ },
+ {
+ ProtoV6ProviderFactories: ProtoV6ProviderFactories(),
+ Config: updatedTenantResourceConfig(),
+ Check: resource.ComposeTestCheckFunc(
+ testTenantResourceUpdated(t, name),
+ ),
+ },
+ },
+ })
+}
+
+func tenantConfig() string {
+ return fmt.Sprintf(`
+ resource "octopusdeploy_tenant" "tenant1" {
+ name = "tenant test"
+ }`)
+}
+
+func updatedTenantResourceConfig() string {
+ return fmt.Sprintf(`
+resource "octopusdeploy_tag_set" "tagset_tag1" {
+ name = "tag1"
+ description = "Test tagset"
+ sort_order = 0
+}
+
+resource "octopusdeploy_tag" "tag_a" {
+ name = "a"
+ color = "#333333"
+ description = "tag a"
+ sort_order = 2
+ tag_set_id = octopusdeploy_tag_set.tagset_tag1.id
+}
+
+resource "octopusdeploy_tag" "tag_b" {
+ name = "b"
+ color = "#333333"
+ description = "tag b"
+ sort_order = 3
+ tag_set_id = octopusdeploy_tag_set.tagset_tag1.id
+}
+
+resource "octopusdeploy_tenant" "tenant1" {
+ name = "Updated tenant"
+ description = "Updated description"
+ tenant_tags = ["tag1/a", "tag1/b"]
+ depends_on = [octopusdeploy_tag.tag_a, octopusdeploy_tag.tag_b]
+}`)
+}
+
+func testTenantResourceUpdated(t *testing.T, name string) resource.TestCheckFunc {
+ return func(s *terraform.State) error {
+ tenantId := s.RootModule().Resources["octopusdeploy_tenant.tenant1"].Primary.ID
+ tenant, err := octoClient.Tenants.GetByID(tenantId)
+ if err != nil {
+ return fmt.Errorf("failed to retrieve tenant by ID: %s", err)
+ }
+ sort.Strings(tenant.TenantTags)
+
+ assert.NotEmpty(t, "Tenant ID did not match expected value", tenant.ID)
+ assert.Equal(t, fmt.Sprintf("Updated description"), tenant.Description)
+ assert.Equal(t, "", tenant.ClonedFromTenantID)
+ assert.Equal(t, "Updated tenant", tenant.Name)
+ assert.Equal(t, "Spaces-1", tenant.SpaceID)
+ assert.Equal(t, []string{"tag1/a", "tag1/b"}, tenant.TenantTags)
+
+ return nil
+ }
+}
diff --git a/octopusdeploy/resource_tenant_test.go b/octopusdeploy_framework/resource_tenant_test.go
similarity index 95%
rename from octopusdeploy/resource_tenant_test.go
rename to octopusdeploy_framework/resource_tenant_test.go
index 22b03b365..cd5a28b72 100644
--- a/octopusdeploy/resource_tenant_test.go
+++ b/octopusdeploy_framework/resource_tenant_test.go
@@ -1,17 +1,16 @@
-package octopusdeploy
+package octopusdeploy_framework
import (
"fmt"
+ "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/terraform"
"path/filepath"
"testing"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tenants"
"github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework/octoclient"
"github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework/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 TestAccTenantBasic(t *testing.T) {
@@ -34,7 +33,7 @@ func TestAccTenantBasic(t *testing.T) {
resource.Test(t, resource.TestCase{
CheckDestroy: testAccTenantCheckDestroy,
- PreCheck: func() { testAccPreCheck(t) },
+ PreCheck: func() { TestAccPreCheck(t) },
ProtoV6ProviderFactories: ProtoV6ProviderFactories(),
Steps: []resource.TestStep{
{
diff --git a/octopusdeploy_framework/schemas/tenant.go b/octopusdeploy_framework/schemas/tenant.go
index 41d55b7d1..497540621 100644
--- a/octopusdeploy_framework/schemas/tenant.go
+++ b/octopusdeploy_framework/schemas/tenant.go
@@ -5,16 +5,21 @@ import (
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
"github.com/hashicorp/terraform-plugin-framework/attr"
datasourceSchema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ resourceSchema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
"github.com/hashicorp/terraform-plugin-framework/types"
)
type TenantModel struct {
ClonedFromTenantId types.String `tfsdk:"cloned_from_tenant_id"`
Description types.String `tfsdk:"description"`
- ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
SpaceID types.String `tfsdk:"space_id"`
TenantTags types.List `tfsdk:"tenant_tags"`
+
+ ResourceModel
}
type TenantsModel struct {
@@ -105,3 +110,27 @@ func GetTenantDataSourceSchema() map[string]datasourceSchema.Attribute {
},
}
}
+
+func GetTenantResourceSchema() map[string]resourceSchema.Attribute {
+ return map[string]resourceSchema.Attribute{
+ "cloned_from_tenant_id": resourceSchema.StringAttribute{
+ Description: "The ID of the tenant from which this tenant was cloned.",
+ Optional: true,
+ Computed: true,
+ Default: stringdefault.StaticString(""),
+ },
+ "description": util.GetDescriptionResourceSchema("tenant"),
+ "id": util.GetIdResourceSchema(),
+ "name": util.GetNameResourceSchema(true),
+ "space_id": util.GetSpaceIdResourceSchema("tenant"),
+ "tenant_tags": resourceSchema.ListAttribute{
+ Description: "A list of tenant tags associated with this resource.",
+ ElementType: types.StringType,
+ Optional: true,
+ Computed: true,
+ PlanModifiers: []planmodifier.List{
+ listplanmodifier.UseStateForUnknown(),
+ },
+ },
+ }
+}
From d10c46797e4914498e46dff5ab571661a83cf8dd Mon Sep 17 00:00:00 2001
From: Huy Nguyen <162080607+HuyPhanNguyen@users.noreply.github.com>
Date: Mon, 12 Aug 2024 15:00:47 +1000
Subject: [PATCH 12/28] chore!: Migrate username_password_account resource
(#721)
* Add username_password_account resource
* Fix test fail
* Add import and import test
* Update octopusdeploy_framework/schemas/username_password_account.go
Co-authored-by: Henrik Andersson
* Update follow new way to handle a missing resource in the Read function
* fix the document
* fix issue list set to state as null
* tidy
---------
Co-authored-by: Henrik Andersson
---
docs/resources/username_password_account.md | 2 +-
octopusdeploy/provider.go | 1 -
...bernetes_cluster_deployment_target_test.go | 7 +
.../resource_username_password_account.go | 95 --------
.../schema_username_password_account.go | 90 --------
octopusdeploy_framework/framework_provider.go | 1 +
.../resource_username_password_account.go | 210 ++++++++++++++++++
...rname_password_account_integration_test.go | 2 +-
...resource_username_password_account_test.go | 87 ++++++--
.../schemas/username_password_account.go | 40 ++++
10 files changed, 333 insertions(+), 202 deletions(-)
delete mode 100644 octopusdeploy/resource_username_password_account.go
delete mode 100644 octopusdeploy/schema_username_password_account.go
create mode 100644 octopusdeploy_framework/resource_username_password_account.go
rename {octopusdeploy => octopusdeploy_framework}/resource_username_password_account_integration_test.go (98%)
rename {octopusdeploy => octopusdeploy_framework}/resource_username_password_account_test.go (56%)
create mode 100644 octopusdeploy_framework/schemas/username_password_account.go
diff --git a/docs/resources/username_password_account.md b/docs/resources/username_password_account.md
index cd949fa53..f00bd5ad6 100644
--- a/docs/resources/username_password_account.md
+++ b/docs/resources/username_password_account.md
@@ -23,7 +23,7 @@ resource "octopusdeploy_username_password_account" "example" {
### Required
-- `name` (String) The name of this resource.
+- `name` (String) The name of the username-password account.
- `username` (String, Sensitive) The username associated with this resource.
### Optional
diff --git a/octopusdeploy/provider.go b/octopusdeploy/provider.go
index ebdaf0b94..ed362eb12 100644
--- a/octopusdeploy/provider.go
+++ b/octopusdeploy/provider.go
@@ -71,7 +71,6 @@ func Provider() *schema.Provider {
"octopusdeploy_token_account": resourceTokenAccount(),
"octopusdeploy_user": resourceUser(),
"octopusdeploy_user_role": resourceUserRole(),
- "octopusdeploy_username_password_account": resourceUsernamePasswordAccount(),
},
Schema: map[string]*schema.Schema{
"address": {
diff --git a/octopusdeploy/resource_kubernetes_cluster_deployment_target_test.go b/octopusdeploy/resource_kubernetes_cluster_deployment_target_test.go
index 0b92199bd..e1973adc0 100644
--- a/octopusdeploy/resource_kubernetes_cluster_deployment_target_test.go
+++ b/octopusdeploy/resource_kubernetes_cluster_deployment_target_test.go
@@ -149,6 +149,13 @@ func testAccKubernetesClusterDeploymentTargetBasic(accountLocalName string, acco
}`, localName, clusterURL, environmentID, name, userRoleID, usernamePasswordAccountID)
}
+func testUsernamePasswordMinimum(localName string, name string, username string) string {
+ return fmt.Sprintf(`resource "octopusdeploy_username_password_account" "%s" {
+ name = "%s"
+ username = "%s"
+ }`, localName, name, username)
+}
+
func testAccKubernetesClusterDeploymentTargetGcp(
accountLocalName string,
accountName string,
diff --git a/octopusdeploy/resource_username_password_account.go b/octopusdeploy/resource_username_password_account.go
deleted file mode 100644
index 4d6ba8b79..000000000
--- a/octopusdeploy/resource_username_password_account.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package octopusdeploy
-
-import (
- "context"
- "log"
-
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/accounts"
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client"
- "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
- "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
-)
-
-func resourceUsernamePasswordAccount() *schema.Resource {
- return &schema.Resource{
- CreateContext: resourceUsernamePasswordAccountCreate,
- DeleteContext: resourceUsernamePasswordAccountDelete,
- Description: "This resource manages username-password accounts in Octopus Deploy.",
- Importer: getImporter(),
- ReadContext: resourceUsernamePasswordAccountRead,
- Schema: getUsernamePasswordAccountSchema(),
- UpdateContext: resourceUsernamePasswordAccountUpdate,
- }
-}
-
-func resourceUsernamePasswordAccountCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- account := expandUsernamePasswordAccount(d)
-
- log.Printf("[INFO] creating username-password account: %#v", account)
-
- client := m.(*client.Client)
- createdAccount, err := accounts.Add(client, account)
- if err != nil {
- return diag.FromErr(err)
- }
-
- if err := setUsernamePasswordAccount(ctx, d, createdAccount.(*accounts.UsernamePasswordAccount)); err != nil {
- return diag.FromErr(err)
- }
-
- d.SetId(createdAccount.GetID())
-
- log.Printf("[INFO] username-password account created (%s)", d.Id())
- return nil
-}
-
-func resourceUsernamePasswordAccountDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- log.Printf("[INFO] deleting username-password account (%s)", d.Id())
-
- client := m.(*client.Client)
- if err := accounts.DeleteByID(client, d.Get("space_id").(string), d.Id()); err != nil {
- return diag.FromErr(err)
- }
-
- d.SetId("")
-
- log.Printf("[INFO] username-password account deleted")
- return nil
-}
-
-func resourceUsernamePasswordAccountRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- log.Printf("[INFO] reading username-password account (%s)", d.Id())
-
- client := m.(*client.Client)
- accountResource, err := accounts.GetByID(client, d.Get("space_id").(string), d.Id())
- if err != nil {
- return errors.ProcessApiError(ctx, d, err, "username-password account")
- }
-
- if err := setUsernamePasswordAccount(ctx, d, accountResource.(*accounts.UsernamePasswordAccount)); err != nil {
- return diag.FromErr(err)
- }
-
- log.Printf("[INFO] username-password account read: (%s)", d.Id())
- return nil
-}
-
-func resourceUsernamePasswordAccountUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- account := expandUsernamePasswordAccount(d)
-
- log.Printf("[INFO] updating username-password account: %#v", account)
-
- client := m.(*client.Client)
- updatedAccount, err := accounts.Update(client, account)
- if err != nil {
- return diag.FromErr(err)
- }
-
- if err := setUsernamePasswordAccount(ctx, d, updatedAccount.(*accounts.UsernamePasswordAccount)); err != nil {
- return diag.FromErr(err)
- }
-
- log.Printf("[INFO] username-password account updated (%s)", d.Id())
- return nil
-}
diff --git a/octopusdeploy/schema_username_password_account.go b/octopusdeploy/schema_username_password_account.go
deleted file mode 100644
index 4e0c50b65..000000000
--- a/octopusdeploy/schema_username_password_account.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package octopusdeploy
-
-import (
- "context"
- "fmt"
-
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/accounts"
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
-)
-
-func expandUsernamePasswordAccount(d *schema.ResourceData) accounts.IUsernamePasswordAccount {
- name := d.Get("name").(string)
-
- account, _ := accounts.NewUsernamePasswordAccount(name)
- account.SetID(d.Id())
- account.SetPassword(core.NewSensitiveValue(d.Get("password").(string)))
-
- if v, ok := d.GetOk("description"); ok {
- account.SetDescription(v.(string))
- }
-
- if v, ok := d.GetOk("environments"); ok {
- account.SetEnvironmentIDs(getSliceFromTerraformTypeList(v))
- }
-
- if v, ok := d.GetOk("space_id"); ok {
- account.SetSpaceID(v.(string))
- }
-
- if v, ok := d.GetOk("tenanted_deployment_participation"); ok {
- account.SetTenantedDeploymentMode(core.TenantedDeploymentMode(v.(string)))
- }
-
- if v, ok := d.GetOk("tenants"); ok {
- account.SetTenantIDs(getSliceFromTerraformTypeList(v))
- }
-
- if v, ok := d.GetOk("tenant_tags"); ok {
- account.SetTenantTags(getSliceFromTerraformTypeList(v))
- }
-
- if v, ok := d.GetOk("username"); ok {
- account.SetUsername(v.(string))
- }
-
- return account
-}
-
-func setUsernamePasswordAccount(ctx context.Context, d *schema.ResourceData, account *accounts.UsernamePasswordAccount) error {
- d.Set("description", account.GetDescription())
-
- if err := d.Set("environments", account.GetEnvironmentIDs()); err != nil {
- return fmt.Errorf("error setting environments: %s", err)
- }
-
- d.Set("id", account.GetID())
- d.Set("name", account.GetName())
- d.Set("space_id", account.GetSpaceID())
- d.Set("tenanted_deployment_participation", account.GetTenantedDeploymentMode())
-
- if err := d.Set("tenants", account.GetTenantIDs()); err != nil {
- return fmt.Errorf("error setting tenants: %s", err)
- }
-
- if err := d.Set("tenant_tags", account.TenantTags); err != nil {
- return fmt.Errorf("error setting tenant_tags: %s", err)
- }
-
- d.Set("username", account.Username)
-
- d.SetId(account.GetID())
-
- return nil
-}
-
-func getUsernamePasswordAccountSchema() map[string]*schema.Schema {
- return map[string]*schema.Schema{
- "description": getDescriptionSchema("username/password account"),
- "environments": getEnvironmentsSchema(),
- "id": getIDSchema(),
- "name": getNameSchema(true),
- "password": getPasswordSchema(false),
- "space_id": getSpaceIDSchema(),
- "tenanted_deployment_participation": getTenantedDeploymentSchema(),
- "tenants": getTenantsSchema(),
- "tenant_tags": getTenantTagsSchema(),
- "username": getUsernameSchema(true),
- }
-}
diff --git a/octopusdeploy_framework/framework_provider.go b/octopusdeploy_framework/framework_provider.go
index 68686d521..0e1ac1453 100644
--- a/octopusdeploy_framework/framework_provider.go
+++ b/octopusdeploy_framework/framework_provider.go
@@ -94,6 +94,7 @@ func (p *octopusDeployFrameworkProvider) Resources(ctx context.Context) []func()
NewVariableResource,
NewProjectResource,
NewDockerContainerRegistryFeedResource,
+ NewUsernamePasswordAccountResource,
NewRunbookResource,
NewTenantResource,
}
diff --git a/octopusdeploy_framework/resource_username_password_account.go b/octopusdeploy_framework/resource_username_password_account.go
new file mode 100644
index 000000000..307ffe3fd
--- /dev/null
+++ b/octopusdeploy_framework/resource_username_password_account.go
@@ -0,0 +1,210 @@
+package octopusdeploy_framework
+
+import (
+ "context"
+ "fmt"
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/accounts"
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+)
+
+var _ resource.Resource = &usernamePasswordAccountResource{}
+var _ resource.ResourceWithImportState = &usernamePasswordAccountResource{}
+
+type usernamePasswordAccountResource struct {
+ *Config
+}
+
+func NewUsernamePasswordAccountResource() resource.Resource {
+ return &usernamePasswordAccountResource{}
+}
+
+func (r *usernamePasswordAccountResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = util.GetTypeName("username_password_account")
+}
+
+func (r *usernamePasswordAccountResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schemas.GetUsernamePasswordAccountResourceSchema()
+}
+
+func (r *usernamePasswordAccountResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ r.Config = ResourceConfiguration(req, resp)
+}
+func (r *usernamePasswordAccountResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ var plan schemas.UsernamePasswordAccountResourceModel
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Debug(ctx, "Creating username password account", map[string]interface{}{
+ "name": plan.Name.ValueString(),
+ })
+
+ account := expandUsernamePasswordAccount(ctx, plan)
+ createdAccount, err := accounts.Add(r.Client, account)
+ if err != nil {
+ resp.Diagnostics.AddError("Error creating username password account", err.Error())
+ return
+ }
+
+ state := flattenUsernamePasswordAccount(ctx, createdAccount.(*accounts.UsernamePasswordAccount), plan)
+ resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
+}
+
+func (r *usernamePasswordAccountResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ var state schemas.UsernamePasswordAccountResourceModel
+ resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ account, err := accounts.GetByID(r.Client, state.SpaceID.ValueString(), state.ID.ValueString())
+ if err != nil {
+ if err := errors.ProcessApiErrorV2(ctx, resp, state, err, "usernamePasswordAccountResource"); err != nil {
+ resp.Diagnostics.AddError("unable to load username password account", err.Error())
+ }
+ return
+ }
+
+ newState := flattenUsernamePasswordAccount(ctx, account.(*accounts.UsernamePasswordAccount), state)
+ resp.Diagnostics.Append(resp.State.Set(ctx, newState)...)
+}
+
+func (r *usernamePasswordAccountResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ var plan schemas.UsernamePasswordAccountResourceModel
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ account := expandUsernamePasswordAccount(ctx, plan)
+ updatedAccount, err := accounts.Update(r.Client, account)
+ if err != nil {
+ resp.Diagnostics.AddError("Error updating username password account", err.Error())
+ return
+ }
+
+ state := flattenUsernamePasswordAccount(ctx, updatedAccount.(*accounts.UsernamePasswordAccount), plan)
+ resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
+}
+
+func (r *usernamePasswordAccountResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ var state schemas.UsernamePasswordAccountResourceModel
+ resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ err := accounts.DeleteByID(r.Client, state.SpaceID.ValueString(), state.ID.ValueString())
+ if err != nil {
+ resp.Diagnostics.AddError("Error deleting username password account", err.Error())
+ return
+ }
+}
+
+func (r *usernamePasswordAccountResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ accountID := req.ID
+
+ account, err := accounts.GetByID(r.Client, r.Client.GetSpaceID(), accountID)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error reading username password account",
+ fmt.Sprintf("Unable to read username password account with ID %s: %s", accountID, err.Error()),
+ )
+ return
+ }
+
+ usernamePasswordAccount, ok := account.(*accounts.UsernamePasswordAccount)
+ if !ok {
+ resp.Diagnostics.AddError(
+ "Unexpected account type",
+ fmt.Sprintf("Expected username password account, got: %T", account),
+ )
+ return
+ }
+
+ state := schemas.UsernamePasswordAccountResourceModel{
+ SpaceID: types.StringValue(usernamePasswordAccount.GetSpaceID()),
+ Name: types.StringValue(usernamePasswordAccount.GetName()),
+ Description: types.StringValue(usernamePasswordAccount.GetDescription()),
+ Username: types.StringValue(usernamePasswordAccount.GetUsername()),
+ TenantedDeploymentParticipation: types.StringValue(string(usernamePasswordAccount.GetTenantedDeploymentMode())),
+ Environments: flattenStringList(usernamePasswordAccount.GetEnvironmentIDs(), types.ListNull(types.StringType)),
+ Tenants: flattenStringList(usernamePasswordAccount.GetTenantIDs(), types.ListNull(types.StringType)),
+ TenantTags: flattenStringList(usernamePasswordAccount.TenantTags, types.ListNull(types.StringType)),
+ Password: types.StringNull(),
+ }
+ state.ID = types.StringValue(usernamePasswordAccount.ID)
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
+}
+
+func expandUsernamePasswordAccount(ctx context.Context, model schemas.UsernamePasswordAccountResourceModel) *accounts.UsernamePasswordAccount {
+ account, _ := accounts.NewUsernamePasswordAccount(model.Name.ValueString())
+
+ account.SetID(model.ID.ValueString())
+ account.SetDescription(model.Description.ValueString())
+ account.SetSpaceID(model.SpaceID.ValueString())
+ account.SetUsername(model.Username.ValueString())
+ account.SetPassword(core.NewSensitiveValue(model.Password.ValueString()))
+ account.SetEnvironmentIDs(expandStringList(model.Environments))
+ account.SetTenantedDeploymentMode(core.TenantedDeploymentMode(model.TenantedDeploymentParticipation.ValueString()))
+ account.SetTenantIDs(expandStringList(model.Tenants))
+ account.SetTenantTags(expandStringList(model.TenantTags))
+
+ return account
+}
+
+func flattenUsernamePasswordAccount(ctx context.Context, account *accounts.UsernamePasswordAccount, model schemas.UsernamePasswordAccountResourceModel) schemas.UsernamePasswordAccountResourceModel {
+ model.ID = types.StringValue(account.GetID())
+ model.SpaceID = types.StringValue(account.GetSpaceID())
+ model.Name = types.StringValue(account.GetName())
+ model.Description = types.StringValue(account.GetDescription())
+ model.Username = types.StringValue(account.GetUsername())
+ model.TenantedDeploymentParticipation = types.StringValue(string(account.GetTenantedDeploymentMode()))
+
+ model.Environments = flattenStringList(account.GetEnvironmentIDs(), model.Environments)
+ model.Tenants = flattenStringList(account.GetTenantIDs(), model.Tenants)
+ model.TenantTags = flattenStringList(account.TenantTags, model.TenantTags)
+
+ // Note: We don't flatten the password as it's sensitive and not returned by the API
+
+ return model
+}
+
+func expandStringList(list types.List) []string {
+ if list.IsNull() || list.IsUnknown() {
+ return nil
+ }
+
+ var result []string
+ list.ElementsAs(context.Background(), &result, false)
+ if len(result) == 0 {
+ return nil
+ }
+
+ return result
+}
+
+func flattenStringList(slice []string, currentList types.List) types.List {
+ if len(slice) == 0 && currentList.IsNull() {
+ return types.ListNull(types.StringType)
+ }
+ if slice == nil {
+ return types.ListNull(types.StringType)
+ }
+
+ valueSlice := make([]attr.Value, len(slice))
+ for i, s := range slice {
+ valueSlice[i] = types.StringValue(s)
+ }
+
+ return types.ListValueMust(types.StringType, valueSlice)
+}
diff --git a/octopusdeploy/resource_username_password_account_integration_test.go b/octopusdeploy_framework/resource_username_password_account_integration_test.go
similarity index 98%
rename from octopusdeploy/resource_username_password_account_integration_test.go
rename to octopusdeploy_framework/resource_username_password_account_integration_test.go
index b5ea00af4..a730246f1 100644
--- a/octopusdeploy/resource_username_password_account_integration_test.go
+++ b/octopusdeploy_framework/resource_username_password_account_integration_test.go
@@ -1,4 +1,4 @@
-package octopusdeploy
+package octopusdeploy_framework
import (
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/accounts"
diff --git a/octopusdeploy/resource_username_password_account_test.go b/octopusdeploy_framework/resource_username_password_account_test.go
similarity index 56%
rename from octopusdeploy/resource_username_password_account_test.go
rename to octopusdeploy_framework/resource_username_password_account_test.go
index e1a29bd9f..25977a673 100644
--- a/octopusdeploy/resource_username_password_account_test.go
+++ b/octopusdeploy_framework/resource_username_password_account_test.go
@@ -1,16 +1,16 @@
-package octopusdeploy
+package octopusdeploy_framework
import (
"fmt"
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/variables"
"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"
"testing"
-
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)
func TestAccUsernamePasswordBasic(t *testing.T) {
@@ -26,8 +26,7 @@ func TestAccUsernamePasswordBasic(t *testing.T) {
config := testUsernamePasswordBasic(localName, description, name, username, password, tenantedDeploymentParticipation)
resource.Test(t, resource.TestCase{
- CheckDestroy: testAccountCheckDestroy,
- PreCheck: func() { testAccPreCheck(t) },
+ PreCheck: func() { TestAccPreCheck(t) },
ProtoV6ProviderFactories: ProtoV6ProviderFactories(),
Steps: []resource.TestStep{
{
@@ -48,6 +47,17 @@ func TestAccUsernamePasswordBasic(t *testing.T) {
})
}
+func testAccountExists(prefix string) resource.TestCheckFunc {
+ return func(s *terraform.State) error {
+ accountID := s.RootModule().Resources[prefix].Primary.ID
+ if _, err := octoClient.Accounts.GetByID(accountID); err != nil {
+ return err
+ }
+
+ return nil
+ }
+}
+
func testUsernamePasswordBasic(localName string, description string, name string, username string, password string, tenantedDeploymentParticipation core.TenantedDeploymentMode) string {
return fmt.Sprintf(`resource "octopusdeploy_username_password_account" "%s" {
description = "%s"
@@ -58,13 +68,6 @@ func testUsernamePasswordBasic(localName string, description string, name string
}`, localName, description, name, password, tenantedDeploymentParticipation, username)
}
-func testUsernamePasswordMinimum(localName string, name string, username string) string {
- return fmt.Sprintf(`resource "octopusdeploy_username_password_account" "%s" {
- name = "%s"
- username = "%s"
- }`, localName, name, username)
-}
-
// TestUsernamePasswordVariableResource verifies that a project variable referencing a username/password account
// can be created
func TestUsernamePasswordVariableResource(t *testing.T) {
@@ -111,3 +114,59 @@ func TestUsernamePasswordVariableResource(t *testing.T) {
t.Fatalf("The variable must have type of UsernamePasswordAccount.")
}
}
+
+func TestAccUsernamePasswordAccountImport(t *testing.T) {
+ localName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
+ resourceName := "octopusdeploy_username_password_account." + localName
+
+ description := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
+ name := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
+ password := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
+ tenantedDeploymentParticipation := core.TenantedDeploymentModeTenantedOrUntenanted
+ username := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { TestAccPreCheck(t) },
+ ProtoV6ProviderFactories: ProtoV6ProviderFactories(),
+ Steps: []resource.TestStep{
+ // Create and test the resource
+ {
+ Config: testAccUsernamePasswordAccountBasic(localName, description, name, username, password, tenantedDeploymentParticipation),
+ Check: resource.ComposeTestCheckFunc(
+ testAccountExists(resourceName),
+ resource.TestCheckResourceAttr(resourceName, "name", name),
+ resource.TestCheckResourceAttr(resourceName, "description", description),
+ resource.TestCheckResourceAttr(resourceName, "username", username),
+ resource.TestCheckResourceAttr(resourceName, "tenanted_deployment_participation", string(tenantedDeploymentParticipation)),
+ ),
+ },
+ {
+ ResourceName: resourceName,
+ ImportState: true,
+ ImportStateVerify: true,
+ ImportStateVerifyIgnore: []string{"password"},
+ ImportStateIdFunc: testAccUsernamePasswordAccountImportStateIdFunc(resourceName),
+ },
+ },
+ })
+}
+
+func testAccUsernamePasswordAccountImportStateIdFunc(resourceName string) resource.ImportStateIdFunc {
+ return func(s *terraform.State) (string, error) {
+ rs, ok := s.RootModule().Resources[resourceName]
+ if !ok {
+ return "", fmt.Errorf("Not found: %s", resourceName)
+ }
+
+ return rs.Primary.ID, nil
+ }
+}
+func testAccUsernamePasswordAccountBasic(localName, description, name, username, password string, tenantedDeploymentParticipation core.TenantedDeploymentMode) string {
+ return fmt.Sprintf(`resource "octopusdeploy_username_password_account" "%s" {
+ name = "%s"
+ description = "%s"
+ username = "%s"
+ password = "%s"
+ tenanted_deployment_participation = "%s"
+ }`, localName, name, description, username, password, tenantedDeploymentParticipation)
+}
diff --git a/octopusdeploy_framework/schemas/username_password_account.go b/octopusdeploy_framework/schemas/username_password_account.go
new file mode 100644
index 000000000..c5bff07fc
--- /dev/null
+++ b/octopusdeploy_framework/schemas/username_password_account.go
@@ -0,0 +1,40 @@
+package schemas
+
+import (
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+func GetUsernamePasswordAccountResourceSchema() schema.Schema {
+ return schema.Schema{
+ Description: "This resource manages username-password accounts in Octopus Deploy.",
+ Attributes: map[string]schema.Attribute{
+ "id": util.ResourceString().Optional().Computed().PlanModifiers(stringplanmodifier.UseStateForUnknown()).Description("The unique ID for this resource.").Build(),
+ "space_id": util.ResourceString().Optional().Computed().PlanModifiers(stringplanmodifier.UseStateForUnknown()).Description("The space ID associated with this resource.").Build(),
+ "name": util.ResourceString().Required().Description("The name of the username-password account.").Build(),
+ "description": util.ResourceString().Optional().Computed().PlanModifiers(stringplanmodifier.UseStateForUnknown()).Default("").Description("The description of this username/password account.").Build(),
+ "environments": util.ResourceList(types.StringType).Optional().Computed().Description("A list of environment IDs associated with this resource.").Build(),
+ "password": util.ResourceString().Optional().Sensitive().Description("The password associated with this resource.").Build(),
+ "tenanted_deployment_participation": util.ResourceString().Optional().Optional().Computed().PlanModifiers(stringplanmodifier.UseStateForUnknown()).Description("The tenanted deployment mode of the resource. Valid account types are `Untenanted`, `TenantedOrUntenanted`, or `Tenanted`.").Build(),
+ "tenants": util.ResourceList(types.StringType).Optional().Computed().Description("A list of tenant IDs associated with this resource.").Build(),
+ "tenant_tags": util.ResourceList(types.StringType).Optional().Computed().Description("A list of tenant tags associated with this resource.").Build(),
+ "username": util.ResourceString().Required().Sensitive().Description("The username associated with this resource.").Build(),
+ },
+ }
+}
+
+type UsernamePasswordAccountResourceModel struct {
+ SpaceID types.String `tfsdk:"space_id"`
+ Name types.String `tfsdk:"name"`
+ Description types.String `tfsdk:"description"`
+ Environments types.List `tfsdk:"environments"`
+ Password types.String `tfsdk:"password"`
+ TenantedDeploymentParticipation types.String `tfsdk:"tenanted_deployment_participation"`
+ Tenants types.List `tfsdk:"tenants"`
+ TenantTags types.List `tfsdk:"tenant_tags"`
+ Username types.String `tfsdk:"username"`
+
+ ResourceModel
+}
From 44acc60e960ed19d524bcd26852fd3267b4ccf5d Mon Sep 17 00:00:00 2001
From: Isaac Calligeros <101079287+IsaacCalligeros95@users.noreply.github.com>
Date: Mon, 12 Aug 2024 14:55:32 +0930
Subject: [PATCH 13/28] Chore!: Migrate script module resource (#712)
---
docs/data-sources/script_modules | 26 +--
docs/data-sources/script_modules.md | 26 +--
docs/resources/script_module.md | 8 +-
octopusdeploy/data_source_script_modules.go | 48 ----
octopusdeploy/provider.go | 2 -
octopusdeploy/resource_script_module.go | 106 ---------
octopusdeploy/schema_script_modules.go | 155 -------------
.../data_source_script_modules.go | 74 ++++++
.../data_source_script_modules_test.go | 11 +-
octopusdeploy_framework/framework_provider.go | 2 +
.../resource_script_module.go | 130 +++++++++++
.../resource_script_module_test.go | 11 +-
.../schemas/script_modules.go | 213 ++++++++++++++++++
13 files changed, 459 insertions(+), 353 deletions(-)
delete mode 100644 octopusdeploy/data_source_script_modules.go
delete mode 100644 octopusdeploy/resource_script_module.go
delete mode 100644 octopusdeploy/schema_script_modules.go
create mode 100644 octopusdeploy_framework/data_source_script_modules.go
rename {octopusdeploy => octopusdeploy_framework}/data_source_script_modules_test.go (79%)
create mode 100644 octopusdeploy_framework/resource_script_module.go
rename {octopusdeploy => octopusdeploy_framework}/resource_script_module_test.go (95%)
create mode 100644 octopusdeploy_framework/schemas/script_modules.go
diff --git a/docs/data-sources/script_modules b/docs/data-sources/script_modules
index 6a12509e7..848de86dc 100644
--- a/docs/data-sources/script_modules
+++ b/docs/data-sources/script_modules
@@ -26,32 +26,32 @@ data "octopusdeploy_script_modules" "example" {
### Optional
- `ids` (List of String) A filter to search by a list of IDs.
-- `partial_name` (String) A filter to search by the partial match of a 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) The space ID associated with this script module.
- `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.
-- `script_modules` (List of Object) A list of script modules that match the filter(s). (see [below for nested schema](#nestedatt--script_modules))
+- `id` (String) The unique ID for this resource.
+- `script_modules` (Attributes List) (see [below for nested schema](#nestedatt--script_modules))
### Nested Schema for `script_modules`
Read-Only:
-- `description` (String)
-- `id` (String)
-- `name` (String)
-- `script` (Set of Object) (see [below for nested schema](#nestedobjatt--script_modules--script))
-- `space_id` (String)
-- `variable_set_id` (String)
+- `description` (String) The description of this script module.
+- `id` (String) The unique ID for this resource.
+- `name` (String) The name of this resource.
+- `script` (Attributes List) The script associated with this script module. (see [below for nested schema](#nestedatt--script_modules--script))
+- `space_id` (String) The space ID associated with this Script Module.
+- `variable_set_id` (String) The variable set ID for this script module.
-
+
### Nested Schema for `script_modules.script`
Read-Only:
-- `body` (String)
-- `syntax` (String)
\ No newline at end of file
+- `body` (String) The body of this script module.
+- `syntax` (String) The syntax of the script. Valid types are `Bash`, `CSharp`, `FSharp`, `PowerShell`, or `Python`.
\ No newline at end of file
diff --git a/docs/data-sources/script_modules.md b/docs/data-sources/script_modules.md
index 35d7e16a8..2149ce0ca 100644
--- a/docs/data-sources/script_modules.md
+++ b/docs/data-sources/script_modules.md
@@ -27,34 +27,34 @@ data "octopusdeploy_script_modules" "example" {
### Optional
- `ids` (List of String) A filter to search by a list of IDs.
-- `partial_name` (String) A filter to search by the partial match of a 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) The space ID associated with this script module.
- `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.
-- `script_modules` (List of Object) A list of script modules that match the filter(s). (see [below for nested schema](#nestedatt--script_modules))
+- `id` (String) The unique ID for this resource.
+- `script_modules` (Attributes List) (see [below for nested schema](#nestedatt--script_modules))
### Nested Schema for `script_modules`
Read-Only:
-- `description` (String)
-- `id` (String)
-- `name` (String)
-- `script` (Set of Object) (see [below for nested schema](#nestedobjatt--script_modules--script))
-- `space_id` (String)
-- `variable_set_id` (String)
+- `description` (String) The description of this script module.
+- `id` (String) The unique ID for this resource.
+- `name` (String) The name of this resource.
+- `script` (Attributes List) The script associated with this script module. (see [below for nested schema](#nestedatt--script_modules--script))
+- `space_id` (String) The space ID associated with this Script Module.
+- `variable_set_id` (String) The variable set ID for this script module.
-
+
### Nested Schema for `script_modules.script`
Read-Only:
-- `body` (String)
-- `syntax` (String)
+- `body` (String) The body of this script module.
+- `syntax` (String) The syntax of the script. Valid types are `Bash`, `CSharp`, `FSharp`, `PowerShell`, or `Python`.
diff --git a/docs/resources/script_module.md b/docs/resources/script_module.md
index 15d81fb84..bb484cf04 100644
--- a/docs/resources/script_module.md
+++ b/docs/resources/script_module.md
@@ -3,12 +3,12 @@
page_title: "octopusdeploy_script_module Resource - terraform-provider-octopusdeploy"
subcategory: ""
description: |-
- This resource manages script modules in Octopus Deploy.
+
---
# octopusdeploy_script_module (Resource)
-This resource manages script modules in Octopus Deploy.
+
## Example Usage
@@ -30,13 +30,13 @@ resource "octopusdeploy_script_module" "example" {
### Required
- `name` (String) The name of this resource.
-- `script` (Block Set, Min: 1, Max: 1) The script associated with this script module. (see [below for nested schema](#nestedblock--script))
### Optional
- `description` (String) The description of this script module.
- `id` (String) The unique ID for this resource.
-- `space_id` (String) The space ID associated with this resource.
+- `script` (Block List) The script associated with this script module. (see [below for nested schema](#nestedblock--script))
+- `space_id` (String) The space ID associated with this Script Module.
- `variable_set_id` (String) The variable set ID for this script module.
diff --git a/octopusdeploy/data_source_script_modules.go b/octopusdeploy/data_source_script_modules.go
deleted file mode 100644
index f782e1410..000000000
--- a/octopusdeploy/data_source_script_modules.go
+++ /dev/null
@@ -1,48 +0,0 @@
-package octopusdeploy
-
-import (
- "context"
- "time"
-
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client"
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/scriptmodules"
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/variables"
- "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
-)
-
-func dataSourceScriptModules() *schema.Resource {
- return &schema.Resource{
- Description: "Provides information about existing script modules.",
- ReadContext: dataSourceScriptModulesRead,
- Schema: getScriptModuleDataSchema(),
- }
-}
-
-func dataSourceScriptModulesRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- query := variables.LibraryVariablesQuery{
- ContentType: "ScriptModule",
- IDs: expandArray(d.Get("ids").([]interface{})),
- 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)
- existingScriptModules, err := scriptmodules.Get(client, spaceID, query)
- if err != nil {
- return diag.FromErr(err)
- }
-
- flattenedScriptModules := []interface{}{}
- for _, scriptModule := range existingScriptModules.Items {
- flattenedScriptModules = append(flattenedScriptModules, flattenScriptModule(scriptModule))
- }
-
- d.Set("script_modules", flattenedScriptModules)
- d.SetId("Script Modules " + time.Now().UTC().String())
-
- return nil
-}
diff --git a/octopusdeploy/provider.go b/octopusdeploy/provider.go
index ed362eb12..1c8ff40e9 100644
--- a/octopusdeploy/provider.go
+++ b/octopusdeploy/provider.go
@@ -25,7 +25,6 @@ func Provider() *schema.Provider {
"octopusdeploy_machine_policies": dataSourceMachinePolicies(),
"octopusdeploy_offline_package_drop_deployment_targets": dataSourceOfflinePackageDropDeploymentTargets(),
"octopusdeploy_polling_tentacle_deployment_targets": dataSourcePollingTentacleDeploymentTargets(),
- "octopusdeploy_script_modules": dataSourceScriptModules(),
"octopusdeploy_ssh_connection_deployment_targets": dataSourceSSHConnectionDeploymentTargets(),
"octopusdeploy_tag_sets": dataSourceTagSets(),
"octopusdeploy_teams": dataSourceTeams(),
@@ -60,7 +59,6 @@ func Provider() *schema.Provider {
"octopusdeploy_project_scheduled_trigger": resourceProjectScheduledTrigger(),
"octopusdeploy_runbook_process": resourceRunbookProcess(),
"octopusdeploy_scoped_user_role": resourceScopedUserRole(),
- "octopusdeploy_script_module": resourceScriptModule(),
"octopusdeploy_ssh_connection_deployment_target": resourceSSHConnectionDeploymentTarget(),
"octopusdeploy_ssh_key_account": resourceSSHKeyAccount(),
"octopusdeploy_static_worker_pool": resourceStaticWorkerPool(),
diff --git a/octopusdeploy/resource_script_module.go b/octopusdeploy/resource_script_module.go
deleted file mode 100644
index faf0f5232..000000000
--- a/octopusdeploy/resource_script_module.go
+++ /dev/null
@@ -1,106 +0,0 @@
-package octopusdeploy
-
-import (
- "context"
- "log"
-
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client"
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/scriptmodules"
- "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
- "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
-)
-
-func resourceScriptModule() *schema.Resource {
- return &schema.Resource{
- CreateContext: resourceScriptModuleCreate,
- DeleteContext: resourceScriptModuleDelete,
- Description: "This resource manages script modules in Octopus Deploy.",
- Importer: getImporter(),
- ReadContext: resourceScriptModuleRead,
- Schema: getScriptModuleSchema(),
- UpdateContext: resourceScriptModuleUpdate,
- }
-}
-
-func resourceScriptModuleCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- scriptModule := expandScriptModule(d)
-
- log.Printf("[INFO] creating script module: %#v", scriptModule)
-
- client := m.(*client.Client)
- createdScriptModule, err := scriptmodules.Add(client, scriptModule)
- if err != nil {
- return diag.FromErr(err)
- }
-
- if err := setScriptModule(ctx, d, createdScriptModule); err != nil {
- return diag.FromErr(err)
- }
-
- d.SetId(createdScriptModule.GetID())
-
- log.Printf("[INFO] script module created (%s)", d.Id())
- return nil
-}
-
-func resourceScriptModuleDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- log.Printf("[INFO] deleting script module (%s)", d.Id())
-
- var spaceID string
- if v, ok := d.GetOk("space_id"); ok {
- spaceID = v.(string)
- }
-
- client := m.(*client.Client)
- err := scriptmodules.DeleteByID(client, spaceID, d.Id())
- if err != nil {
- return diag.FromErr(err)
- }
-
- log.Printf("[INFO] script module deleted (%s)", d.Id())
- d.SetId("")
- return nil
-}
-
-func resourceScriptModuleRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- log.Printf("[INFO] reading script module (%s)", d.Id())
-
- var spaceID string
- if v, ok := d.GetOk("space_id"); ok {
- spaceID = v.(string)
- }
-
- client := m.(*client.Client)
-
- scriptModule, err := scriptmodules.GetByID(client, spaceID, d.Id())
- if err != nil {
- return errors.ProcessApiError(ctx, d, err, "script module")
- }
-
- if err := setScriptModule(ctx, d, scriptModule); err != nil {
- return diag.FromErr(err)
- }
-
- log.Printf("[INFO] script module read (%s)", d.Id())
- return nil
-}
-
-func resourceScriptModuleUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- log.Printf("[INFO] updating script module (%s)", d.Id())
-
- scriptModule := expandScriptModule(d)
-
- client := m.(*client.Client)
- updatedScriptModule, err := scriptmodules.Update(client, scriptModule)
- if err != nil {
- return diag.FromErr(err)
- }
-
- if err := setScriptModule(ctx, d, updatedScriptModule); err != nil {
- return diag.FromErr(err)
- }
-
- log.Printf("[INFO] script module updated (%s)", d.Id())
- return nil
-}
diff --git a/octopusdeploy/schema_script_modules.go b/octopusdeploy/schema_script_modules.go
deleted file mode 100644
index 906d2ea54..000000000
--- a/octopusdeploy/schema_script_modules.go
+++ /dev/null
@@ -1,155 +0,0 @@
-package octopusdeploy
-
-import (
- "context"
- "fmt"
-
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/variables"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
-)
-
-func expandScriptModule(d *schema.ResourceData) *variables.ScriptModule {
- name := d.Get("name").(string)
-
- scriptModule := variables.NewScriptModule(name)
- scriptModule.ID = d.Id()
-
- if v, ok := d.GetOk("description"); ok {
- scriptModule.Description = v.(string)
- }
-
- if v, ok := d.GetOk("script"); ok {
- scripts := v.(*schema.Set).List()
- for _, script := range scripts {
- rawScript := script.(map[string]interface{})
-
- if rawScript["body"] != nil {
- scriptModule.ScriptBody = rawScript["body"].(string)
- }
-
- if rawScript["syntax"] != nil {
- scriptModule.Syntax = rawScript["syntax"].(string)
- }
- }
- }
-
- if v, ok := d.GetOk("space_id"); ok {
- scriptModule.SpaceID = v.(string)
- }
-
- if v, ok := d.GetOk("variable_set_id"); ok {
- scriptModule.VariableSetID = v.(string)
- }
-
- return scriptModule
-}
-
-func flattenScript(scriptModule *variables.ScriptModule) []interface{} {
- if scriptModule == nil {
- return nil
- }
-
- flattenedScriptModules := make([]interface{}, 1)
- flattenedScriptModules[0] = map[string]interface{}{
- "body": scriptModule.ScriptBody,
- "syntax": scriptModule.Syntax,
- }
-
- return flattenedScriptModules
-}
-
-func flattenScriptModule(scriptModule *variables.ScriptModule) map[string]interface{} {
- if scriptModule == nil {
- return nil
- }
-
- return map[string]interface{}{
- "description": scriptModule.Description,
- "id": scriptModule.GetID(),
- "name": scriptModule.Name,
- "script": flattenScript(scriptModule),
- "space_id": scriptModule.SpaceID,
- "variable_set_id": scriptModule.VariableSetID,
- }
-}
-
-func getScriptModuleDataSchema() map[string]*schema.Schema {
- dataSchema := getScriptModuleSchema()
- setDataSchema(&dataSchema)
-
- return map[string]*schema.Schema{
- "id": getDataSchemaID(),
- "space_id": getQuerySpaceID(),
- "ids": getQueryIDs(),
- "script_modules": {
- Computed: true,
- Description: "A list of script modules that match the filter(s).",
- Elem: &schema.Resource{Schema: dataSchema},
- Optional: false,
- Type: schema.TypeList,
- },
- "partial_name": getQueryPartialName(),
- "skip": getQuerySkip(),
- "take": getQueryTake(),
- }
-}
-
-func getScriptModuleSchema() map[string]*schema.Schema {
- return map[string]*schema.Schema{
- "description": getDescriptionSchema("script module"),
- "id": getIDSchema(),
- "name": getNameSchema(true),
- "script": {
- Description: "The script associated with this script module.",
- Required: true,
- Elem: &schema.Resource{
- Schema: map[string]*schema.Schema{
- "body": {
- Description: "The body of this script module.",
- Required: true,
- Type: schema.TypeString,
- },
- "syntax": {
- Description: "The syntax of the script. Valid types are `Bash`, `CSharp`, `FSharp`, `PowerShell`, or `Python`.",
- Required: true,
- Type: schema.TypeString,
- ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{
- "Bash",
- "CSharp",
- "FSharp",
- "PowerShell",
- "Python",
- }, false)),
- },
- },
- },
- MaxItems: 1,
- MinItems: 1,
- Type: schema.TypeSet,
- },
- "space_id": getSpaceIDSchema(),
- "variable_set_id": {
- Computed: true,
- Description: "The variable set ID for this script module.",
- Optional: true,
- Type: schema.TypeString,
- },
- }
-}
-
-func setScriptModule(ctx context.Context, d *schema.ResourceData, scriptModule *variables.ScriptModule) error {
- d.Set("description", scriptModule.Description)
- d.Set("name", scriptModule.Name)
-
- if err := d.Set("script", flattenScript(scriptModule)); err != nil {
- return fmt.Errorf("error setting script: %s", err)
- }
-
- d.Set("space_id", scriptModule.SpaceID)
- d.Set("variable_set_id", scriptModule.VariableSetID)
-
- d.SetId(scriptModule.GetID())
-
- return nil
-}
diff --git a/octopusdeploy_framework/data_source_script_modules.go b/octopusdeploy_framework/data_source_script_modules.go
new file mode 100644
index 000000000..8374402de
--- /dev/null
+++ b/octopusdeploy_framework/data_source_script_modules.go
@@ -0,0 +1,74 @@
+package octopusdeploy_framework
+
+import (
+ "context"
+ "fmt"
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/scriptmodules"
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/variables"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "time"
+
+ "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"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+)
+
+type scriptModulesDataSource struct {
+ *Config
+}
+
+func NewScriptModuleDataSource() datasource.DataSource {
+ return &scriptModulesDataSource{}
+}
+
+func (l *scriptModulesDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ tflog.Debug(ctx, "script modules datasource Metadata")
+ resp.TypeName = util.GetTypeName("script_modules")
+}
+
+func (l *scriptModulesDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ tflog.Debug(ctx, "script modules datasource Schema")
+ resp.Schema = schemas.GetDatasourceScriptModuleSchema()
+}
+
+func (l *scriptModulesDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+ tflog.Debug(ctx, "script modules datasource Configure")
+ l.Config = DataSourceConfiguration(req, resp)
+}
+
+func (l *scriptModulesDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+ tflog.Debug(ctx, "script modules datasource Read")
+ var data schemas.ScriptModuleDataSourceModel
+ resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ query := variables.LibraryVariablesQuery{
+ ContentType: "ScriptModule",
+ IDs: util.ExpandStringList(data.IDs),
+ PartialName: data.PartialName.ValueString(),
+ Skip: int(data.Skip.ValueInt64()),
+ Take: int(data.Take.ValueInt64()),
+ }
+
+ spaceID := data.SpaceID.ValueString()
+ existingScriptModules, err := scriptmodules.Get(l.Config.Client, spaceID, query)
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read script modules, got error: %s", err))
+ return
+ }
+
+ flattenedScriptModules := []attr.Value{}
+ for _, scriptModule := range existingScriptModules.Items {
+ flattenedScriptModules = append(flattenedScriptModules, schemas.FlattenScriptModule(scriptModule))
+ }
+
+ data.ScriptModules = types.ListValueMust(types.ObjectType{AttrTypes: schemas.ScriptModuleObjectType()},
+ flattenedScriptModules)
+ data.ID = types.StringValue("Script Modules " + time.Now().UTC().String())
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
diff --git a/octopusdeploy/data_source_script_modules_test.go b/octopusdeploy_framework/data_source_script_modules_test.go
similarity index 79%
rename from octopusdeploy/data_source_script_modules_test.go
rename to octopusdeploy_framework/data_source_script_modules_test.go
index f5fa0450e..286a8ec5b 100644
--- a/octopusdeploy/data_source_script_modules_test.go
+++ b/octopusdeploy_framework/data_source_script_modules_test.go
@@ -1,12 +1,11 @@
-package octopusdeploy
+package octopusdeploy_framework
import (
"fmt"
+ "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/terraform"
"testing"
-
- "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 TestAccDataSourceScriptModules(t *testing.T) {
@@ -15,7 +14,7 @@ func TestAccDataSourceScriptModules(t *testing.T) {
take := 10
resource.Test(t, resource.TestCase{
- PreCheck: func() { testAccPreCheck(t) },
+ PreCheck: func() { TestAccPreCheck(t) },
ProtoV6ProviderFactories: ProtoV6ProviderFactories(),
Steps: []resource.TestStep{
{
diff --git a/octopusdeploy_framework/framework_provider.go b/octopusdeploy_framework/framework_provider.go
index 0e1ac1453..894f15e82 100644
--- a/octopusdeploy_framework/framework_provider.go
+++ b/octopusdeploy_framework/framework_provider.go
@@ -71,6 +71,7 @@ func (p *octopusDeployFrameworkProvider) DataSources(ctx context.Context) []func
NewVariablesDataSource,
NewProjectsDataSource,
NewTenantsDataSource,
+ NewScriptModuleDataSource,
}
}
@@ -97,6 +98,7 @@ func (p *octopusDeployFrameworkProvider) Resources(ctx context.Context) []func()
NewUsernamePasswordAccountResource,
NewRunbookResource,
NewTenantResource,
+ NewScriptModuleResource,
}
}
diff --git a/octopusdeploy_framework/resource_script_module.go b/octopusdeploy_framework/resource_script_module.go
new file mode 100644
index 000000000..8e0a1294f
--- /dev/null
+++ b/octopusdeploy_framework/resource_script_module.go
@@ -0,0 +1,130 @@
+package octopusdeploy_framework
+
+import (
+ "context"
+ "fmt"
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/scriptmodules"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
+ "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/resource/schema"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+)
+
+type scriptModuleTypeResource struct {
+ *Config
+}
+
+func NewScriptModuleResource() resource.Resource {
+ return &scriptModuleTypeResource{}
+}
+
+func (r *scriptModuleTypeResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = util.GetTypeName("script_module")
+}
+
+func (r *scriptModuleTypeResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Attributes: schemas.GetScriptModuleResourceSchema(),
+ Blocks: schemas.GetScriptModuleSchemaBlock(),
+ }
+}
+
+func (r *scriptModuleTypeResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ r.Config = ResourceConfiguration(req, resp)
+}
+
+func (r *scriptModuleTypeResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ internal.Mutex.Lock()
+ defer internal.Mutex.Unlock()
+
+ var data *schemas.ScriptModuleResourceModel
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ scriptModule := schemas.MapFromScriptModuleToState(data)
+
+ tflog.Info(ctx, fmt.Sprintf("creating Script Module: %s", scriptModule.Name))
+
+ createdScriptModule, err := scriptmodules.Add(r.Config.Client, scriptModule)
+ if err != nil {
+ resp.Diagnostics.AddError("unable to create script module", err.Error())
+ return
+ }
+
+ schemas.MapToScriptModuleFromState(data, createdScriptModule)
+
+ tflog.Info(ctx, fmt.Sprintf("Script Module created (%s)", data.ID))
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *scriptModuleTypeResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ var data *schemas.ScriptModuleResourceModel
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Info(ctx, fmt.Sprintf("reading Script Module (%s)", data.ID))
+
+ client := r.Config.Client
+ scriptModule, err := scriptmodules.GetByID(client, data.SpaceID.ValueString(), data.ID.ValueString())
+ if err != nil {
+ if err := errors.ProcessApiErrorV2(ctx, resp, data, err, "Script Module"); err != nil {
+ resp.Diagnostics.AddError("unable to load script module", err.Error())
+ }
+ return
+ }
+
+ schemas.MapToScriptModuleFromState(data, scriptModule)
+
+ tflog.Info(ctx, fmt.Sprintf("Script Module read (%s)", scriptModule.GetID()))
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *scriptModuleTypeResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ var data, state *schemas.ScriptModuleResourceModel
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
+ resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Debug(ctx, fmt.Sprintf("updating script module '%s'", data.ID.ValueString()))
+
+ scriptModule := schemas.MapFromScriptModuleToState(data)
+ scriptModule.ID = state.ID.ValueString()
+
+ updatedScriptModule, err := scriptmodules.Update(r.Config.Client, scriptModule)
+ if err != nil {
+ resp.Diagnostics.AddError("unable to update script module", err.Error())
+ return
+ }
+
+ schemas.MapToScriptModuleFromState(data, updatedScriptModule)
+
+ tflog.Info(ctx, fmt.Sprintf("Script Module updated (%s)", data.ID))
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *scriptModuleTypeResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ internal.Mutex.Lock()
+ defer internal.Mutex.Unlock()
+
+ var data schemas.ScriptModuleResourceModel
+
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ if err := scriptmodules.DeleteByID(r.Config.Client, data.SpaceID.ValueString(), data.ID.ValueString()); err != nil {
+ resp.Diagnostics.AddError("unable to delete script module", err.Error())
+ return
+ }
+}
diff --git a/octopusdeploy/resource_script_module_test.go b/octopusdeploy_framework/resource_script_module_test.go
similarity index 95%
rename from octopusdeploy/resource_script_module_test.go
rename to octopusdeploy_framework/resource_script_module_test.go
index 3893ad972..c7828d6a3 100644
--- a/octopusdeploy/resource_script_module_test.go
+++ b/octopusdeploy_framework/resource_script_module_test.go
@@ -1,16 +1,15 @@
-package octopusdeploy
+package octopusdeploy_framework
import (
"fmt"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/variables"
"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"
"path/filepath"
"testing"
-
- "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 TestAccOctopusDeployScriptModuleBasic(t *testing.T) {
@@ -24,7 +23,7 @@ func TestAccOctopusDeployScriptModuleBasic(t *testing.T) {
resource.Test(t, resource.TestCase{
CheckDestroy: testScriptModuleCheckDestroy,
- PreCheck: func() { testAccPreCheck(t) },
+ PreCheck: func() { TestAccPreCheck(t) },
ProtoV6ProviderFactories: ProtoV6ProviderFactories(),
Steps: []resource.TestStep{
{
diff --git a/octopusdeploy_framework/schemas/script_modules.go b/octopusdeploy_framework/schemas/script_modules.go
new file mode 100644
index 000000000..f74ab893b
--- /dev/null
+++ b/octopusdeploy_framework/schemas/script_modules.go
@@ -0,0 +1,213 @@
+package schemas
+
+import (
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
+ "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ datasourceSchema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ resourceSchema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/variables"
+)
+
+type ScriptModuleResourceModel struct {
+ Description types.String `tfsdk:"description"`
+ Name types.String `tfsdk:"name"`
+ SpaceID types.String `tfsdk:"space_id"`
+ VariableSetId types.String `tfsdk:"variable_set_id"`
+ Script types.List `tfsdk:"script"`
+
+ ResourceModel
+}
+
+type ScriptModuleDataSourceModel struct {
+ ID types.String `tfsdk:"id"`
+ SpaceID types.String `tfsdk:"space_id"`
+ IDs types.List `tfsdk:"ids"`
+ PartialName types.String `tfsdk:"partial_name"`
+ Skip types.Int64 `tfsdk:"skip"`
+ Take types.Int64 `tfsdk:"take"`
+ ScriptModules types.List `tfsdk:"script_modules"`
+}
+
+func GetDatasourceScriptModuleSchema() datasourceSchema.Schema {
+ description := "script module"
+ return datasourceSchema.Schema{
+ Description: "Provides information about existing script modules.",
+ Attributes: map[string]datasourceSchema.Attribute{
+ "id": GetIdDatasourceSchema(true),
+ "space_id": GetSpaceIdDatasourceSchema(description, false),
+ "ids": util.GetQueryIDsDatasourceSchema(),
+ "partial_name": util.GetQueryPartialNameDatasourceSchema(),
+ "skip": util.GetQuerySkipDatasourceSchema(),
+ "take": util.GetQueryTakeDatasourceSchema(),
+ "script_modules": datasourceSchema.ListNestedAttribute{
+ Computed: true,
+ NestedObject: datasourceSchema.NestedAttributeObject{
+ Attributes: GetScriptModuleDatasourceSchema(),
+ },
+ },
+ },
+ }
+}
+
+func GetScriptModuleDatasourceSchema() map[string]datasourceSchema.Attribute {
+ return map[string]datasourceSchema.Attribute{
+ "description": GetReadonlyDescriptionDatasourceSchema("script module"),
+ "id": GetIdDatasourceSchema(true),
+ "name": GetReadonlyNameDatasourceSchema(),
+ "space_id": GetSpaceIdDatasourceSchema("Script Module", true),
+ "variable_set_id": datasourceSchema.StringAttribute{
+ Computed: true,
+ Description: "The variable set ID for this script module.",
+ },
+ "script": datasourceSchema.ListNestedAttribute{
+ Description: "The script associated with this script module.",
+ Computed: true,
+ NestedObject: datasourceSchema.NestedAttributeObject{
+ Attributes: map[string]datasourceSchema.Attribute{
+ "body": datasourceSchema.StringAttribute{
+ Description: "The body of this script module.",
+ Computed: true,
+ },
+ "syntax": datasourceSchema.StringAttribute{
+ Description: "The syntax of the script. Valid types are `Bash`, `CSharp`, `FSharp`, `PowerShell`, or `Python`.",
+ Computed: true,
+ Validators: []validator.String{
+ stringvalidator.OneOfCaseInsensitive(
+ "Bash",
+ "CSharp",
+ "FSharp",
+ "PowerShell",
+ "Python"),
+ },
+ },
+ },
+ },
+ Validators: []validator.List{
+ listvalidator.SizeAtMost(1),
+ listvalidator.SizeAtLeast(1),
+ },
+ },
+ }
+}
+
+func FlattenScriptModule(scriptModule *variables.ScriptModule) attr.Value {
+ attrs := map[string]attr.Value{
+ "description": types.StringValue(scriptModule.Description),
+ "id": types.StringValue(scriptModule.GetID()),
+ "name": types.StringValue(scriptModule.Name),
+ "script": types.ListValueMust(ScriptObjectType(), flattenScript(scriptModule)),
+ "space_id": types.StringValue(scriptModule.SpaceID),
+ "variable_set_id": types.StringValue(scriptModule.VariableSetID),
+ }
+
+ return types.ObjectValueMust(ScriptModuleObjectType(), attrs)
+}
+
+func ScriptObjectType() types.ObjectType {
+ return types.ObjectType{AttrTypes: map[string]attr.Type{
+ "body": types.StringType,
+ "syntax": types.StringType,
+ }}
+}
+
+func ScriptModuleObjectType() map[string]attr.Type {
+ return map[string]attr.Type{
+ "description": types.StringType,
+ "id": types.StringType,
+ "name": types.StringType,
+ "space_id": types.StringType,
+ "variable_set_id": types.StringType,
+ "script": types.ListType{ElemType: ScriptObjectType()},
+ }
+}
+
+func GetScriptModuleSchemaBlock() map[string]resourceSchema.Block {
+ return map[string]resourceSchema.Block{
+ "script": resourceSchema.ListNestedBlock{
+ Description: "The script associated with this script module.",
+ NestedObject: resourceSchema.NestedBlockObject{
+ Attributes: map[string]resourceSchema.Attribute{
+ "body": resourceSchema.StringAttribute{
+ Description: "The body of this script module.",
+ Required: true,
+ },
+ "syntax": resourceSchema.StringAttribute{
+ Description: "The syntax of the script. Valid types are `Bash`, `CSharp`, `FSharp`, `PowerShell`, or `Python`.",
+ Required: true,
+ Validators: []validator.String{
+ stringvalidator.OneOfCaseInsensitive(
+ "Bash",
+ "CSharp",
+ "FSharp",
+ "PowerShell",
+ "Python"),
+ },
+ },
+ },
+ },
+ Validators: []validator.List{
+ listvalidator.SizeAtMost(1),
+ listvalidator.SizeAtLeast(1),
+ },
+ },
+ }
+}
+
+func GetScriptModuleResourceSchema() map[string]resourceSchema.Attribute {
+ return map[string]resourceSchema.Attribute{
+ "description": GetDescriptionResourceSchema("script module"),
+ "id": GetIdResourceSchema(),
+ "name": GetNameResourceSchema(true),
+ "space_id": GetSpaceIdResourceSchema("Script Module"),
+ "variable_set_id": resourceSchema.StringAttribute{
+ Computed: true,
+ Description: "The variable set ID for this script module.",
+ Optional: true,
+ },
+ }
+}
+
+func MapFromScriptModuleToState(data *ScriptModuleResourceModel) *variables.ScriptModule {
+ name := data.Name.ValueString()
+ scriptModule := variables.NewScriptModule(name)
+ scriptModule.ID = data.ID.ValueString()
+ scriptModule.Description = data.Description.ValueString()
+ // We enforce on the schema a single required script
+ scriptDetails := data.Script.Elements()[0].(types.Object).Attributes()
+ scriptModule.Syntax = scriptDetails["syntax"].(types.String).ValueString()
+ scriptModule.ScriptBody = scriptDetails["body"].(types.String).ValueString()
+ scriptModule.SpaceID = data.SpaceID.ValueString()
+ scriptModule.VariableSetID = data.VariableSetId.ValueString()
+
+ return scriptModule
+}
+
+func flattenScript(scriptModule *variables.ScriptModule) []attr.Value {
+ return []attr.Value{
+ types.ObjectValueMust(map[string]attr.Type{
+ "body": types.StringType,
+ "syntax": types.StringType,
+ }, map[string]attr.Value{
+ "body": types.StringValue(scriptModule.ScriptBody),
+ "syntax": types.StringValue(scriptModule.Syntax),
+ }),
+ }
+}
+
+func MapToScriptModuleFromState(data *ScriptModuleResourceModel, scriptModule *variables.ScriptModule) {
+ data.Description = types.StringValue(scriptModule.Description)
+ data.Name = types.StringValue(scriptModule.Name)
+ data.SpaceID = types.StringValue(scriptModule.SpaceID)
+ data.VariableSetId = types.StringValue(scriptModule.VariableSetID)
+ data.ID = types.StringValue(scriptModule.ID)
+
+ flattenScript(scriptModule)
+
+ var script, _ = types.ListValue(ScriptObjectType(), flattenScript(scriptModule))
+ data.Script = script
+}
From 5aec963058dc8dddf93290e6a52f21477758a37d Mon Sep 17 00:00:00 2001
From: Isaac Calligeros <101079287+IsaacCalligeros95@users.noreply.github.com>
Date: Mon, 12 Aug 2024 15:16:18 +0930
Subject: [PATCH 14/28] Chore!: Migrate tentacle certificate (#716)
---
docs/resources/tentacle_certificate.md | 2 +-
{octopusdeploy => internal}/random.go | 6 +-
octopusdeploy/provider.go | 1 -
.../resource_polling_subscription_id.go | 3 +-
.../resource_tentacle_certificate.go | 97 -------------
octopusdeploy/schema_tentacle_certificate.go | 25 ----
octopusdeploy_framework/framework_provider.go | 1 +
.../resource_tentacle_certificate.go | 129 ++++++++++++++++++
.../resource_tentacle_certificate_test.go | 2 +-
.../schemas/schema_tentacle_certificate.go | 44 ++++++
10 files changed, 181 insertions(+), 129 deletions(-)
rename {octopusdeploy => internal}/random.go (88%)
delete mode 100644 octopusdeploy/resource_tentacle_certificate.go
delete mode 100644 octopusdeploy/schema_tentacle_certificate.go
create mode 100644 octopusdeploy_framework/resource_tentacle_certificate.go
rename {octopusdeploy => octopusdeploy_framework}/resource_tentacle_certificate_test.go (95%)
create mode 100644 octopusdeploy_framework/schemas/schema_tentacle_certificate.go
diff --git a/docs/resources/tentacle_certificate.md b/docs/resources/tentacle_certificate.md
index a86900df9..7ba18a05e 100644
--- a/docs/resources/tentacle_certificate.md
+++ b/docs/resources/tentacle_certificate.md
@@ -47,7 +47,7 @@ resource "octopusdeploy_kubernetes_agent_deployment_target" "agent" {
### Read-Only
- `base64` (String, Sensitive) The base64 encoded pfx certificate.
-- `id` (String) The ID of this resource.
+- `id` (String) The unique ID for this resource.
- `thumbprint` (String) The SHA1 sum of the certificate represented in hexadecimal.
diff --git a/octopusdeploy/random.go b/internal/random.go
similarity index 88%
rename from octopusdeploy/random.go
rename to internal/random.go
index 5e7e78487..42f2987c3 100644
--- a/octopusdeploy/random.go
+++ b/internal/random.go
@@ -1,4 +1,4 @@
-package octopusdeploy
+package internal
import (
"crypto/rand"
@@ -21,7 +21,7 @@ func generateRandomBytes(length int) []byte {
return randomBytes
}
-func generateRandomCryptoString(length int) string {
+func GenerateRandomCryptoString(length int) string {
result := make([]byte, length)
bufferSize := int(float64(length) * 1.3)
for i, j, randomBytes := 0, 0, []byte{}; i < length; j++ {
@@ -37,7 +37,7 @@ func generateRandomCryptoString(length int) string {
return string(result)
}
-func generateRandomSerialNumber() big.Int {
+func GenerateRandomSerialNumber() big.Int {
random := rand.Reader
randomSerialNumber, err := rand.Int(random, big.NewInt(9223372036854775807))
if err != nil {
diff --git a/octopusdeploy/provider.go b/octopusdeploy/provider.go
index 1c8ff40e9..4bc99cfc6 100644
--- a/octopusdeploy/provider.go
+++ b/octopusdeploy/provider.go
@@ -65,7 +65,6 @@ func Provider() *schema.Provider {
"octopusdeploy_tag": resourceTag(),
"octopusdeploy_tag_set": resourceTagSet(),
"octopusdeploy_team": resourceTeam(),
- "octopusdeploy_tentacle_certificate": resourceTentacleCertificate(),
"octopusdeploy_token_account": resourceTokenAccount(),
"octopusdeploy_user": resourceUser(),
"octopusdeploy_user_role": resourceUserRole(),
diff --git a/octopusdeploy/resource_polling_subscription_id.go b/octopusdeploy/resource_polling_subscription_id.go
index e97927ab9..f871377c5 100644
--- a/octopusdeploy/resource_polling_subscription_id.go
+++ b/octopusdeploy/resource_polling_subscription_id.go
@@ -2,6 +2,7 @@ package octopusdeploy
import (
"context"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
@@ -27,7 +28,7 @@ func resourcePollingSubscriptionIdDelete(ctx context.Context, d *schema.Resource
}
func resourcePollingSubscriptionIdCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- var generatedSubscriptionId = generateRandomCryptoString(20)
+ var generatedSubscriptionId = internal.GenerateRandomCryptoString(20)
d.SetId(generatedSubscriptionId)
d.Set("polling_uri", "poll://"+generatedSubscriptionId+"/")
diff --git a/octopusdeploy/resource_tentacle_certificate.go b/octopusdeploy/resource_tentacle_certificate.go
deleted file mode 100644
index 5df7f3402..000000000
--- a/octopusdeploy/resource_tentacle_certificate.go
+++ /dev/null
@@ -1,97 +0,0 @@
-package octopusdeploy
-
-import (
- "context"
- "crypto/rand"
- "crypto/rsa"
- "crypto/sha1"
- "crypto/x509"
- "crypto/x509/pkix"
- "encoding/base64"
- "encoding/hex"
- "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
- "software.sslmate.com/src/go-pkcs12"
- "strings"
- "time"
-)
-
-func resourceTentacleCertificate() *schema.Resource {
- return &schema.Resource{
- CreateContext: resourceTentacleCertificateCreate,
- DeleteContext: resourceTentacleCertificateDelete,
- Description: "Generates a X.509 self-signed certificate for use with a Octopus Deploy Tentacle.",
- ReadContext: resourceTentacleCertificateRead,
- Schema: getTentacleCertificateSchema(),
- }
-}
-
-func resourceTentacleCertificateRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- // Don't need to do anything as all the values are already in state
- return nil
-}
-
-func resourceTentacleCertificateDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- d.SetId("")
- return nil
-}
-
-func resourceTentacleCertificateCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- certificate, thumbprint, err := generateCertificate("Octopus Tentacle")
-
- if err != nil {
- return diag.FromErr(err)
- }
-
- d.Set("base64", certificate)
- d.Set("thumbprint", thumbprint)
-
- d.SetId(generateRandomCryptoString(20))
- return nil
-}
-
-func generateCertificate(fullName string) (string, string, error) {
- random := rand.Reader
-
- privateKey, err := rsa.GenerateKey(random, 2048)
- if err != nil {
- return "", "", err
- }
-
- serialNumber := generateRandomSerialNumber()
- template := x509.Certificate{
- SerialNumber: &serialNumber,
- Subject: pkix.Name{
- CommonName: fullName,
- },
- Issuer: pkix.Name{
- CommonName: fullName,
- },
- NotBefore: time.Now().AddDate(0, 0, -1),
- NotAfter: time.Now().AddDate(100, 0, 0),
- KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
- ExtKeyUsage: []x509.ExtKeyUsage{
- x509.ExtKeyUsageServerAuth,
- },
- IsCA: false,
- BasicConstraintsValid: true,
- }
-
- certBytes, err := x509.CreateCertificate(random, &template, &template, &privateKey.PublicKey, privateKey)
- if err != nil {
- return "", "", err
- }
-
- parsedCert, _ := x509.ParseCertificate(certBytes)
- pkcs12Bytes, err := pkcs12.Passwordless.Encode(privateKey, parsedCert, nil, "")
- if err != nil {
- return "", "", err
- }
-
- pkcs12Base64 := base64.StdEncoding.EncodeToString(pkcs12Bytes)
-
- thumbprint := sha1.Sum(certBytes)
- thumbprintStr := strings.ToUpper(hex.EncodeToString(thumbprint[:]))
-
- return pkcs12Base64, thumbprintStr, nil
-}
diff --git a/octopusdeploy/schema_tentacle_certificate.go b/octopusdeploy/schema_tentacle_certificate.go
deleted file mode 100644
index 4335996b0..000000000
--- a/octopusdeploy/schema_tentacle_certificate.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package octopusdeploy
-
-import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
-
-func getTentacleCertificateSchema() map[string]*schema.Schema {
- return map[string]*schema.Schema{
- "base64": {
- Computed: true,
- Sensitive: true,
- Description: "The base64 encoded pfx certificate.",
- Type: schema.TypeString,
- },
- "thumbprint": {
- Computed: true,
- Description: "The SHA1 sum of the certificate represented in hexadecimal.",
- Type: schema.TypeString,
- },
- "dependencies": {
- Optional: true,
- Type: schema.TypeMap,
- Description: "Optional map of dependencies that when modified will trigger a re-creation of this resource.",
- ForceNew: true,
- },
- }
-}
diff --git a/octopusdeploy_framework/framework_provider.go b/octopusdeploy_framework/framework_provider.go
index 894f15e82..5bdf6cc43 100644
--- a/octopusdeploy_framework/framework_provider.go
+++ b/octopusdeploy_framework/framework_provider.go
@@ -98,6 +98,7 @@ func (p *octopusDeployFrameworkProvider) Resources(ctx context.Context) []func()
NewUsernamePasswordAccountResource,
NewRunbookResource,
NewTenantResource,
+ NewTentacleCertificateResource,
NewScriptModuleResource,
}
}
diff --git a/octopusdeploy_framework/resource_tentacle_certificate.go b/octopusdeploy_framework/resource_tentacle_certificate.go
new file mode 100644
index 000000000..6f0e22a76
--- /dev/null
+++ b/octopusdeploy_framework/resource_tentacle_certificate.go
@@ -0,0 +1,129 @@
+package octopusdeploy_framework
+
+import (
+ "context"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/sha1"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/base64"
+ "encoding/hex"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal"
+ "software.sslmate.com/src/go-pkcs12"
+ "strings"
+ "time"
+
+ "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"
+)
+
+type tentacleCertificateResource struct {
+ *Config
+}
+
+func NewTentacleCertificateResource() resource.Resource {
+ return &tentacleCertificateResource{}
+}
+
+func (t *tentacleCertificateResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = util.GetTypeName("tentacle_certificate")
+}
+
+func (t *tentacleCertificateResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schemas.GetTentacleCertificateSchema()
+}
+
+func (t *tentacleCertificateResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ t.Config = ResourceConfiguration(req, resp)
+}
+
+func (t *tentacleCertificateResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ var plan schemas.TentacleCertificateResourceModel
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ certificate, thumbprint, err := generateCertificate("Octopus Tentacle")
+
+ if err != nil {
+ resp.Diagnostics.AddError("cannot generate tentacle", err.Error())
+ return
+ }
+
+ plan.Base64 = types.StringValue(certificate)
+ plan.Thumbprint = types.StringValue(thumbprint)
+ plan.ID = types.StringValue(internal.GenerateRandomCryptoString(20))
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
+
+}
+
+func (t *tentacleCertificateResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ return
+}
+
+func (t *tentacleCertificateResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ var data schemas.TentacleCertificateResourceModel
+
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ data.ID = types.StringValue("")
+ return
+}
+
+func (r *tentacleCertificateResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ return
+}
+
+func generateCertificate(fullName string) (string, string, error) {
+ random := rand.Reader
+
+ privateKey, err := rsa.GenerateKey(random, 2048)
+ if err != nil {
+ return "", "", err
+ }
+
+ serialNumber := internal.GenerateRandomSerialNumber()
+ template := x509.Certificate{
+ SerialNumber: &serialNumber,
+ Subject: pkix.Name{
+ CommonName: fullName,
+ },
+ Issuer: pkix.Name{
+ CommonName: fullName,
+ },
+ NotBefore: time.Now().AddDate(0, 0, -1),
+ NotAfter: time.Now().AddDate(100, 0, 0),
+ KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
+ ExtKeyUsage: []x509.ExtKeyUsage{
+ x509.ExtKeyUsageServerAuth,
+ },
+ IsCA: false,
+ BasicConstraintsValid: true,
+ }
+
+ certBytes, err := x509.CreateCertificate(random, &template, &template, &privateKey.PublicKey, privateKey)
+ if err != nil {
+ return "", "", err
+ }
+
+ parsedCert, _ := x509.ParseCertificate(certBytes)
+ pkcs12Bytes, err := pkcs12.Passwordless.Encode(privateKey, parsedCert, nil, "")
+ if err != nil {
+ return "", "", err
+ }
+
+ pkcs12Base64 := base64.StdEncoding.EncodeToString(pkcs12Bytes)
+
+ thumbprint := sha1.Sum(certBytes)
+ thumbprintStr := strings.ToUpper(hex.EncodeToString(thumbprint[:]))
+
+ return pkcs12Base64, thumbprintStr, nil
+}
diff --git a/octopusdeploy/resource_tentacle_certificate_test.go b/octopusdeploy_framework/resource_tentacle_certificate_test.go
similarity index 95%
rename from octopusdeploy/resource_tentacle_certificate_test.go
rename to octopusdeploy_framework/resource_tentacle_certificate_test.go
index 483d08f08..f07a20aac 100644
--- a/octopusdeploy/resource_tentacle_certificate_test.go
+++ b/octopusdeploy_framework/resource_tentacle_certificate_test.go
@@ -1,4 +1,4 @@
-package octopusdeploy
+package octopusdeploy_framework
import (
"github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework/test"
diff --git a/octopusdeploy_framework/schemas/schema_tentacle_certificate.go b/octopusdeploy_framework/schemas/schema_tentacle_certificate.go
new file mode 100644
index 000000000..74dbfd07a
--- /dev/null
+++ b/octopusdeploy_framework/schemas/schema_tentacle_certificate.go
@@ -0,0 +1,44 @@
+package schemas
+
+import (
+ resourceSchema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+func GetTentacleCertificateSchema() resourceSchema.Schema {
+ return resourceSchema.Schema{
+ Description: "Generates a X.509 self-signed certificate for use with a Octopus Deploy Tentacle.",
+ Attributes: map[string]resourceSchema.Attribute{
+ "id": resourceSchema.StringAttribute{
+ Description: "The unique ID for this resource.",
+ Computed: true,
+ },
+ "base64": resourceSchema.StringAttribute{
+ Computed: true,
+ Sensitive: true,
+ Description: "The base64 encoded pfx certificate.",
+ },
+ "thumbprint": resourceSchema.StringAttribute{
+ Computed: true,
+ Description: "The SHA1 sum of the certificate represented in hexadecimal.",
+ },
+ "dependencies": resourceSchema.MapAttribute{
+ Optional: true,
+ ElementType: types.StringType,
+ Description: "Optional map of dependencies that when modified will trigger a re-creation of this resource.",
+ PlanModifiers: []planmodifier.Map{
+ mapplanmodifier.RequiresReplace(),
+ },
+ },
+ },
+ }
+}
+
+type TentacleCertificateResourceModel struct {
+ ID types.String `tfsdk:"id"`
+ Base64 types.String `tfsdk:"base64"`
+ Thumbprint types.String `tfsdk:"thumbprint"`
+ Dependencies types.Map `tfsdk:"dependencies"`
+}
From e4c08d3ee663710280c5f35a30b9ff00df2ed976 Mon Sep 17 00:00:00 2001
From: Huy Nguyen <162080607+HuyPhanNguyen@users.noreply.github.com>
Date: Mon, 12 Aug 2024 16:36:34 +1000
Subject: [PATCH 15/28] Migrate tag set resource and datasource (#725)
* Add tagset resource and datasource
* update tag_set test
* refactor
* Fix tagset issue
* fix issue with tag_set datasource
---
octopusdeploy/data_source_tag_sets.go | 45 ----
octopusdeploy/data_source_tag_sets_test.go | 49 -----
octopusdeploy/provider.go | 2 -
octopusdeploy/resource_tag_set.go | 103 ---------
octopusdeploy/resource_tag_set_test.go | 156 -------------
octopusdeploy/schema_tag_set.go | 83 -------
octopusdeploy/schema_tag_test.go | 36 ---
.../datasource_tag_sets.go | 82 +++++++
.../datasource_tag_sets_test.go | 73 +++++++
octopusdeploy_framework/framework_provider.go | 2 +
octopusdeploy_framework/resource_tag_set.go | 144 ++++++++++++
.../resource_tag_set_test.go | 205 ++++++++++++++++++
octopusdeploy_framework/schemas/tag_set.go | 113 ++++++++++
octopusdeploy_framework/util/util.go | 1 +
14 files changed, 620 insertions(+), 474 deletions(-)
delete mode 100644 octopusdeploy/data_source_tag_sets.go
delete mode 100644 octopusdeploy/data_source_tag_sets_test.go
delete mode 100644 octopusdeploy/resource_tag_set.go
delete mode 100644 octopusdeploy/resource_tag_set_test.go
delete mode 100644 octopusdeploy/schema_tag_set.go
delete mode 100644 octopusdeploy/schema_tag_test.go
create mode 100644 octopusdeploy_framework/datasource_tag_sets.go
create mode 100644 octopusdeploy_framework/datasource_tag_sets_test.go
create mode 100644 octopusdeploy_framework/resource_tag_set.go
create mode 100644 octopusdeploy_framework/resource_tag_set_test.go
create mode 100644 octopusdeploy_framework/schemas/tag_set.go
diff --git a/octopusdeploy/data_source_tag_sets.go b/octopusdeploy/data_source_tag_sets.go
deleted file mode 100644
index 63395248b..000000000
--- a/octopusdeploy/data_source_tag_sets.go
+++ /dev/null
@@ -1,45 +0,0 @@
-package octopusdeploy
-
-import (
- "context"
- "time"
-
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client"
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tagsets"
- "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
-)
-
-func dataSourceTagSets() *schema.Resource {
- return &schema.Resource{
- Description: "Provides information about existing tag sets.",
- ReadContext: dataSourceTagSetsRead,
- Schema: getTagSetDataSchema(),
- }
-}
-
-func dataSourceTagSetsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- query := tagsets.TagSetsQuery{
- IDs: expandArray(d.Get("ids").([]interface{})),
- PartialName: d.Get("partial_name").(string),
- Skip: d.Get("skip").(int),
- Take: d.Get("take").(int),
- }
- spaceID := d.Get("space_id").(string)
-
- octopus := m.(*client.Client)
- existingTagSets, err := tagsets.Get(octopus, spaceID, query)
- if err != nil {
- return diag.FromErr(err)
- }
-
- flattenedTagSets := []interface{}{}
- for _, tagSet := range existingTagSets.Items {
- flattenedTagSets = append(flattenedTagSets, flattenTagSet(tagSet))
- }
-
- d.Set("tag_sets", flattenedTagSets)
- d.SetId("TagSets " + time.Now().UTC().String())
-
- return nil
-}
diff --git a/octopusdeploy/data_source_tag_sets_test.go b/octopusdeploy/data_source_tag_sets_test.go
deleted file mode 100644
index 2fb8c668e..000000000
--- a/octopusdeploy/data_source_tag_sets_test.go
+++ /dev/null
@@ -1,49 +0,0 @@
-package octopusdeploy
-
-import (
- "fmt"
- "testing"
-
- "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 TestAccDataSourceTagSets(t *testing.T) {
- localName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
- name := fmt.Sprintf("data.octopusdeploy_tag_sets.%s", localName)
- take := 10
-
- resource.Test(t, resource.TestCase{
- PreCheck: func() { testAccPreCheck(t) },
- ProtoV6ProviderFactories: ProtoV6ProviderFactories(),
- Steps: []resource.TestStep{
- {
- Config: testAccDataSourceTagSetsConfig(localName, take),
- Check: resource.ComposeTestCheckFunc(
- testAccCheckTagSetsDataSourceID(name),
- )},
- },
- })
-}
-
-func testAccCheckTagSetsDataSourceID(n string) resource.TestCheckFunc {
- return func(s *terraform.State) error {
- all := s.RootModule().Resources
- rs, ok := all[n]
- if !ok {
- return fmt.Errorf("cannot find TagSets data source: %s", n)
- }
-
- if rs.Primary.ID == "" {
- return fmt.Errorf("snapshot TagSets source ID not set")
- }
- return nil
- }
-}
-
-func testAccDataSourceTagSetsConfig(localName string, take int) string {
- return fmt.Sprintf(`data "octopusdeploy_tag_sets" "%s" {
- take = %v
- }`, localName, take)
-}
diff --git a/octopusdeploy/provider.go b/octopusdeploy/provider.go
index 4bc99cfc6..cdb60f086 100644
--- a/octopusdeploy/provider.go
+++ b/octopusdeploy/provider.go
@@ -26,7 +26,6 @@ func Provider() *schema.Provider {
"octopusdeploy_offline_package_drop_deployment_targets": dataSourceOfflinePackageDropDeploymentTargets(),
"octopusdeploy_polling_tentacle_deployment_targets": dataSourcePollingTentacleDeploymentTargets(),
"octopusdeploy_ssh_connection_deployment_targets": dataSourceSSHConnectionDeploymentTargets(),
- "octopusdeploy_tag_sets": dataSourceTagSets(),
"octopusdeploy_teams": dataSourceTeams(),
"octopusdeploy_users": dataSourceUsers(),
"octopusdeploy_user_roles": dataSourceUserRoles(),
@@ -63,7 +62,6 @@ func Provider() *schema.Provider {
"octopusdeploy_ssh_key_account": resourceSSHKeyAccount(),
"octopusdeploy_static_worker_pool": resourceStaticWorkerPool(),
"octopusdeploy_tag": resourceTag(),
- "octopusdeploy_tag_set": resourceTagSet(),
"octopusdeploy_team": resourceTeam(),
"octopusdeploy_token_account": resourceTokenAccount(),
"octopusdeploy_user": resourceUser(),
diff --git a/octopusdeploy/resource_tag_set.go b/octopusdeploy/resource_tag_set.go
deleted file mode 100644
index c80e19623..000000000
--- a/octopusdeploy/resource_tag_set.go
+++ /dev/null
@@ -1,103 +0,0 @@
-package octopusdeploy
-
-import (
- "context"
- "fmt"
- "log"
-
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client"
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tagsets"
- "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 resourceTagSet() *schema.Resource {
- return &schema.Resource{
- CreateContext: resourceTagSetCreate,
- DeleteContext: resourceTagSetDelete,
- Description: "This resource manages tag sets in Octopus Deploy.",
- Importer: getImporter(),
- ReadContext: resourceTagSetRead,
- Schema: getTagSetSchema(),
- UpdateContext: resourceTagSetUpdate,
- }
-}
-
-func resourceTagSetCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- tagSet := expandTagSet(d)
-
- log.Printf("[INFO] creating tag set: %#v", tagSet)
-
- octopus := m.(*client.Client)
- createdTagSet, err := tagsets.Add(octopus, tagSet)
- if err != nil {
- return diag.FromErr(err)
- }
-
- if err := setTagSet(ctx, d, createdTagSet); err != nil {
- return diag.FromErr(err)
- }
-
- d.SetId(createdTagSet.GetID())
-
- log.Printf("[INFO] tag set created (%s)", d.Id())
- return nil
-}
-
-func resourceTagSetDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- log.Printf("[INFO] deleting tag set (%s)", d.Id())
-
- octopus := m.(*client.Client)
- if err := tagsets.DeleteByID(octopus, d.Get("space_id").(string), d.Id()); err != nil {
- return diag.FromErr(err)
- }
-
- d.SetId("")
-
- log.Printf("[INFO] tag set deleted")
- return nil
-}
-
-func resourceTagSetRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- tflog.Info(ctx, fmt.Sprintf("reading tag set (%s)", d.Id()))
-
- octopus := m.(*client.Client)
- tagSet, err := tagsets.GetByID(octopus, d.Get("space_id").(string), d.Id())
- if err != nil {
- return errors.ProcessApiError(ctx, d, err, "tag set")
- }
-
- if err := setTagSet(ctx, d, tagSet); err != nil {
- return diag.FromErr(err)
- }
-
- tflog.Info(ctx, fmt.Sprintf("tag set read (%s)", d.Id()))
- return nil
-}
-
-func resourceTagSetUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- tagSet := expandTagSet(d)
-
- log.Printf("[INFO] updating tag set: %#v", tagSet)
-
- octopus := m.(*client.Client)
- existingTagSet, err := tagsets.GetByID(octopus, d.Get("space_id").(string), d.Id())
- if err != nil {
- return diag.FromErr(err)
- }
- tagSet.Tags = existingTagSet.Tags
-
- updatedTagSet, err := tagsets.Update(octopus, tagSet)
- if err != nil {
- return diag.FromErr(err)
- }
-
- if err := setTagSet(ctx, d, updatedTagSet); err != nil {
- return diag.FromErr(err)
- }
-
- log.Printf("[INFO] tag set updated (%s)", d.Id())
- return nil
-}
diff --git a/octopusdeploy/resource_tag_set_test.go b/octopusdeploy/resource_tag_set_test.go
deleted file mode 100644
index a964ea880..000000000
--- a/octopusdeploy/resource_tag_set_test.go
+++ /dev/null
@@ -1,156 +0,0 @@
-package octopusdeploy
-
-import (
- "fmt"
- "testing"
-
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tagsets"
- internalTest "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/test"
- "github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework/octoclient"
- "github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework/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 TestAccOctopusDeployTagSetBasic(t *testing.T) {
- internalTest.SkipCI(t, " Error: Unsupported block type")
- localName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
- prefix := "octopusdeploy_tag_set." + localName
-
- name := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
- tagColor := "#6e6e6e"
- tagDescription := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
- tagName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
-
- resource.Test(t, resource.TestCase{
- CheckDestroy: testTagSetDestroy,
- PreCheck: func() { testAccPreCheck(t) },
- ProtoV6ProviderFactories: ProtoV6ProviderFactories(),
- Steps: []resource.TestStep{
- {
- Check: resource.ComposeTestCheckFunc(
- testTagSetExists(prefix),
- resource.TestCheckResourceAttr(prefix, "name", name),
- resource.TestCheckResourceAttr(prefix, "tag.#", "0"),
- ),
- Config: testTagSetMinimal(localName, name),
- },
- {
- Check: resource.ComposeTestCheckFunc(
- testTagSetExists(prefix),
- resource.TestCheckResourceAttr(prefix, "name", name),
- resource.TestCheckResourceAttrSet(prefix, "id"),
- resource.TestCheckResourceAttrSet(prefix, "tag.0.id"),
- resource.TestCheckResourceAttr(prefix, "tag.0.color", tagColor),
- resource.TestCheckResourceAttr(prefix, "tag.0.description", tagDescription),
- resource.TestCheckResourceAttr(prefix, "tag.0.name", tagName),
- ),
- Config: testTagSetComplete(localName, name, tagColor, tagDescription, tagName),
- },
- },
- })
-}
-
-func testTagSetMinimal(localName string, name string) string {
- return fmt.Sprintf(`resource "octopusdeploy_tag_set" "%s" {
- name = "%s"
- }`, localName, name)
-}
-
-func testTagSetComplete(localName string, name string, tagColor string, tagDescription string, tagName string) string {
- return fmt.Sprintf(`resource "octopusdeploy_tag_set" "%s" {
- name = "%s"
- tag {
- color = "%s"
- description = "%s"
- name = "%s"
- }
- }`, localName, name, tagColor, tagDescription, tagName)
-}
-
-func testTagSetExists(prefix string) resource.TestCheckFunc {
- return func(s *terraform.State) error {
- tagSetID := s.RootModule().Resources[prefix].Primary.ID
- if _, err := octoClient.TagSets.GetByID(tagSetID); err != nil {
- return err
- }
-
- return nil
- }
-}
-
-func testTagSetDestroy(s *terraform.State) error {
- for _, rs := range s.RootModule().Resources {
- tagSetID := rs.Primary.ID
- tagSet, err := octoClient.TagSets.GetByID(tagSetID)
- if err == nil {
- if tagSet != nil {
- return fmt.Errorf("tag set (%s) still exists", rs.Primary.ID)
- }
- }
- }
-
- return nil
-}
-
-// TestTagSetResource verifies that a tag set can be reimported with the correct settings
-func TestTagSetResource(t *testing.T) {
- internalTest.SkipCI(t, " Error: Unsupported block type")
- testFramework := test.OctopusContainerTest{}
- newSpaceId, err := testFramework.Act(t, octoContainer, "../terraform", "21-tagset", []string{})
-
- if err != nil {
- t.Fatal(err.Error())
- }
-
- // Assert
- client, err := octoclient.CreateClient(octoContainer.URI, newSpaceId, test.ApiKey)
- query := tagsets.TagSetsQuery{
- PartialName: "tag1",
- Skip: 0,
- Take: 1,
- }
-
- resources, err := client.TagSets.Get(query)
- if err != nil {
- t.Fatal(err.Error())
- }
-
- if len(resources.Items) == 0 {
- t.Fatalf("Space must have a tag set called \"tag1\"")
- }
- resource := resources.Items[0]
-
- if resource.Description != "Test tagset" {
- t.Fatal("The tag set must be have a description of \"Test tagset\" (was \"" + resource.Description + "\")")
- }
-
- if resource.SortOrder != 0 {
- t.Fatal("The tag set must be have a sort order of \"0\" (was \"" + fmt.Sprint(resource.SortOrder) + "\")")
- }
-
- tagAFound := false
- for _, u := range resource.Tags {
- if u.Name == "a" {
- tagAFound = true
-
- if u.Description != "tag a" {
- t.Fatal("The tag a must be have a description of \"tag a\" (was \"" + u.Description + "\")")
- }
-
- if u.Color != "#333333" {
- t.Fatal("The tag a must be have a color of \"#333333\" (was \"" + u.Color + "\")")
- }
-
- if u.SortOrder != 2 {
- t.Fatal("The tag a must be have a sort order of \"2\" (was \"" + fmt.Sprint(u.SortOrder) + "\")")
- }
- }
- }
-
- if !tagAFound {
- t.Fatal("Tag Set must have an tag called \"a\"")
- }
-}
diff --git a/octopusdeploy/schema_tag_set.go b/octopusdeploy/schema_tag_set.go
deleted file mode 100644
index 953b5c40c..000000000
--- a/octopusdeploy/schema_tag_set.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package octopusdeploy
-
-import (
- "context"
-
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tagsets"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
-)
-
-func expandTagSet(d *schema.ResourceData) *tagsets.TagSet {
- name := d.Get("name").(string)
-
- tagSet := tagsets.NewTagSet(name)
- tagSet.ID = d.Id()
-
- if v, ok := d.GetOk("description"); ok {
- tagSet.Description = v.(string)
- }
-
- if v, ok := d.GetOk("sort_order"); ok {
- tagSet.SortOrder = int32(v.(int))
- }
-
- if v, ok := d.GetOk("space_id"); ok {
- tagSet.SpaceID = v.(string)
- }
-
- return tagSet
-}
-
-func flattenTagSet(tagSet *tagsets.TagSet) map[string]interface{} {
- if tagSet == nil {
- return nil
- }
-
- return map[string]interface{}{
- "description": tagSet.Description,
- "id": tagSet.GetID(),
- "name": tagSet.Name,
- "sort_order": tagSet.SortOrder,
- "space_id": tagSet.SpaceID,
- }
-}
-
-func getTagSetDataSchema() map[string]*schema.Schema {
- dataSchema := getTagSetSchema()
- setDataSchema(&dataSchema)
-
- return map[string]*schema.Schema{
- "ids": getQueryIDs(),
- "partial_name": getQueryPartialName(),
- "skip": getQuerySkip(),
- "tag_sets": {
- Computed: true,
- Description: "A list of tag sets that match the filter(s).",
- Elem: &schema.Resource{Schema: dataSchema},
- Optional: false,
- Type: schema.TypeList,
- },
- "take": getQueryTake(),
- "space_id": getSpaceIDSchema(),
- }
-}
-
-func getTagSetSchema() map[string]*schema.Schema {
- return map[string]*schema.Schema{
- "description": getDescriptionSchema("tag set"),
- "id": getIDSchema(),
- "name": getNameSchema(true),
- "sort_order": getSortOrderSchema(),
- "space_id": getSpaceIDSchema(),
- }
-}
-
-func setTagSet(ctx context.Context, d *schema.ResourceData, tagSet *tagsets.TagSet) error {
- d.Set("description", tagSet.Description)
- d.Set("id", tagSet.GetID())
- d.Set("name", tagSet.Name)
- d.Set("sort_order", tagSet.SortOrder)
- d.Set("space_id", tagSet.SpaceID)
-
- return nil
-}
diff --git a/octopusdeploy/schema_tag_test.go b/octopusdeploy/schema_tag_test.go
deleted file mode 100644
index 65a9704df..000000000
--- a/octopusdeploy/schema_tag_test.go
+++ /dev/null
@@ -1,36 +0,0 @@
-package octopusdeploy
-
-import (
- "testing"
-
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
- "github.com/stretchr/testify/require"
-)
-
-func TestExpandTag(t *testing.T) {
- canonicalTagName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
- color := "#FF0000"
- description := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
- id := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
- name := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
- sortOrder := acctest.RandIntRange(0, 1000)
-
- resourceDataMap := map[string]interface{}{
- "canonical_tag_name": canonicalTagName,
- "color": color,
- "description": description,
- "id": id,
- "name": name,
- "sort_order": sortOrder,
- }
-
- d := schema.TestResourceDataRaw(t, getTagSchema(), resourceDataMap)
- tag := expandTag(d)
-
- require.Equal(t, tag.CanonicalTagName, canonicalTagName)
- require.Equal(t, tag.Color, color)
- require.Equal(t, tag.Description, description)
- require.Equal(t, tag.Name, name)
- require.Equal(t, tag.SortOrder, sortOrder)
-}
diff --git a/octopusdeploy_framework/datasource_tag_sets.go b/octopusdeploy_framework/datasource_tag_sets.go
new file mode 100644
index 000000000..10566e299
--- /dev/null
+++ b/octopusdeploy_framework/datasource_tag_sets.go
@@ -0,0 +1,82 @@
+package octopusdeploy_framework
+
+import (
+ "context"
+ "fmt"
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tagsets"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/datasource"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "time"
+)
+
+var _ datasource.DataSource = &tagSetsDataSource{}
+
+type tagSetsDataSource struct {
+ *Config
+}
+
+func NewTagSetsDataSource() datasource.DataSource {
+ return &tagSetsDataSource{}
+}
+
+func (t *tagSetsDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = util.GetTypeName(schemas.TagSetDataSourceName)
+}
+
+func (t *tagSetsDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ resp.Schema = schemas.GetTagSetDataSourceSchema()
+}
+
+func (t *tagSetsDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+ t.Config = DataSourceConfiguration(req, resp)
+}
+
+func (t *tagSetsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+ var data schemas.TagSetDataSourceModel
+ resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ query := tagsets.TagSetsQuery{
+ IDs: schemas.GetIds(data.IDs),
+ PartialName: data.PartialName.ValueString(),
+ Skip: int(data.Skip.ValueInt64()),
+ Take: int(data.Take.ValueInt64()),
+ }
+ spaceID := data.SpaceID.ValueString()
+
+ existingTagSets, err := tagsets.Get(t.Client, spaceID, query)
+ if err != nil {
+ resp.Diagnostics.AddError("Unable to query tag sets", err.Error())
+ return
+ }
+
+ data.TagSets = flattenTagSets(existingTagSets.Items)
+
+ data.ID = types.StringValue(fmt.Sprintf("TagSets-%s", time.Now().UTC().String()))
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func flattenTagSets(tagSets []*tagsets.TagSet) types.List {
+ if len(tagSets) == 0 {
+ return types.ListNull(types.ObjectType{AttrTypes: schemas.GetTagSetAttrTypes()})
+ }
+
+ tfList := make([]attr.Value, len(tagSets))
+ for i, tagSet := range tagSets {
+ tfList[i] = types.ObjectValueMust(schemas.GetTagSetAttrTypes(), map[string]attr.Value{
+ "id": types.StringValue(tagSet.ID),
+ "name": types.StringValue(tagSet.Name),
+ "description": types.StringValue(tagSet.Description),
+ "sort_order": types.Int64Value(int64(tagSet.SortOrder)),
+ "space_id": types.StringValue(tagSet.SpaceID),
+ })
+ }
+
+ return types.ListValueMust(types.ObjectType{AttrTypes: schemas.GetTagSetAttrTypes()}, tfList)
+}
diff --git a/octopusdeploy_framework/datasource_tag_sets_test.go b/octopusdeploy_framework/datasource_tag_sets_test.go
new file mode 100644
index 000000000..46f34ba32
--- /dev/null
+++ b/octopusdeploy_framework/datasource_tag_sets_test.go
@@ -0,0 +1,73 @@
+package octopusdeploy_framework
+
+import (
+ "fmt"
+ "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/terraform"
+ "testing"
+)
+
+func TestAccDataSourceTagSets(t *testing.T) {
+ localName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
+ tagSetName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
+ tagSetResourceName := fmt.Sprintf("octopusdeploy_tag_set.%s", localName)
+ dataSourceName := fmt.Sprintf("data.octopusdeploy_tag_sets.%s", localName)
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { TestAccPreCheck(t) },
+ ProtoV6ProviderFactories: ProtoV6ProviderFactories(),
+ Steps: []resource.TestStep{
+ // Create a tag set
+ {
+ Config: testAccTagSetConfig(localName, tagSetName),
+ Check: resource.ComposeTestCheckFunc(
+ testTagSetExists(tagSetResourceName),
+ resource.TestCheckResourceAttr(tagSetResourceName, "name", tagSetName),
+ ),
+ },
+ // Query the created tag set using the data source
+ {
+ Config: testAccTagSetConfig(localName, tagSetName) + testAccDataSourceTagSetsConfig(localName, tagSetName),
+ Check: resource.ComposeTestCheckFunc(
+ testAccCheckTagSetsDataSourceID(dataSourceName),
+ resource.TestCheckResourceAttrPair(dataSourceName, "tag_sets.0.id", tagSetResourceName, "id"),
+ resource.TestCheckResourceAttrPair(dataSourceName, "tag_sets.0.name", tagSetResourceName, "name"),
+ ),
+ },
+ },
+ })
+}
+
+func testAccCheckTagSetsDataSourceID(n string) resource.TestCheckFunc {
+ return func(s *terraform.State) error {
+ rs, ok := s.RootModule().Resources[n]
+ if !ok {
+ return fmt.Errorf("cannot find TagSets data source: %s", n)
+ }
+
+ if rs.Primary.ID == "" {
+ return fmt.Errorf("TagSets data source ID not set")
+ }
+ return nil
+ }
+}
+
+func testAccTagSetConfig(localName, tagSetName string) string {
+ return fmt.Sprintf(`
+resource "octopusdeploy_tag_set" "%s" {
+ name = "%s"
+ description = "Test tag set"
+}
+`, localName, tagSetName)
+}
+
+func testAccDataSourceTagSetsConfig(localName, tagSetName string) string {
+ return fmt.Sprintf(`
+data "octopusdeploy_tag_sets" "%s" {
+ partial_name = "%s"
+ skip = 0
+ take = 10
+}
+`, localName, tagSetName)
+}
diff --git a/octopusdeploy_framework/framework_provider.go b/octopusdeploy_framework/framework_provider.go
index 5bdf6cc43..19bff356a 100644
--- a/octopusdeploy_framework/framework_provider.go
+++ b/octopusdeploy_framework/framework_provider.go
@@ -71,6 +71,7 @@ func (p *octopusDeployFrameworkProvider) DataSources(ctx context.Context) []func
NewVariablesDataSource,
NewProjectsDataSource,
NewTenantsDataSource,
+ NewTagSetsDataSource,
NewScriptModuleDataSource,
}
}
@@ -95,6 +96,7 @@ func (p *octopusDeployFrameworkProvider) Resources(ctx context.Context) []func()
NewVariableResource,
NewProjectResource,
NewDockerContainerRegistryFeedResource,
+ NewTagSetResource,
NewUsernamePasswordAccountResource,
NewRunbookResource,
NewTenantResource,
diff --git a/octopusdeploy_framework/resource_tag_set.go b/octopusdeploy_framework/resource_tag_set.go
new file mode 100644
index 000000000..b14ae7eaa
--- /dev/null
+++ b/octopusdeploy_framework/resource_tag_set.go
@@ -0,0 +1,144 @@
+package octopusdeploy_framework
+
+import (
+ "context"
+ "fmt"
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tagsets"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
+ "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"
+)
+
+var _ resource.Resource = &tagSetResource{}
+var _ resource.ResourceWithImportState = &tagSetResource{}
+
+type tagSetResource struct {
+ *Config
+}
+
+func NewTagSetResource() resource.Resource {
+ return &tagSetResource{}
+}
+
+func (r *tagSetResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = util.GetTypeName(schemas.TagSetResourceName)
+}
+
+func (r *tagSetResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schemas.GetTagSetResourceSchema()
+}
+
+func (r *tagSetResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ r.Config = ResourceConfiguration(req, resp)
+}
+
+func (r *tagSetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ var plan schemas.TagSetResourceModel
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tagSet := expandTagSet(plan)
+ createdTagSet, err := tagsets.Add(r.Client, tagSet)
+ if err != nil {
+ resp.Diagnostics.AddError("Error creating tag set", err.Error())
+ return
+ }
+
+ state := flattenTagSet(createdTagSet)
+ resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
+}
+
+func (r *tagSetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ var state schemas.TagSetResourceModel
+ resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tagSet, err := tagsets.GetByID(r.Client, state.SpaceID.ValueString(), state.ID.ValueString())
+ if err != nil {
+ if err := errors.ProcessApiErrorV2(ctx, resp, state, err, "tagSetResource"); err != nil {
+ resp.Diagnostics.AddError("unable to load tag set", err.Error())
+ }
+ return
+ }
+
+ newState := flattenTagSet(tagSet)
+ resp.Diagnostics.Append(resp.State.Set(ctx, newState)...)
+}
+
+func (r *tagSetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ var plan schemas.TagSetResourceModel
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tagSet := expandTagSet(plan)
+ updatedTagSet, err := tagsets.Update(r.Client, tagSet)
+ if err != nil {
+ resp.Diagnostics.AddError("Error updating tag set", err.Error())
+ return
+ }
+
+ state := flattenTagSet(updatedTagSet)
+ resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
+}
+
+func (r *tagSetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ var state schemas.TagSetResourceModel
+ resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ err := tagsets.DeleteByID(r.Client, state.SpaceID.ValueString(), state.ID.ValueString())
+ if err != nil {
+ resp.Diagnostics.AddError("Error deleting tag set", err.Error())
+ return
+ }
+}
+
+func (r *tagSetResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ tagSetID := req.ID
+
+ tagSet, err := tagsets.GetByID(r.Client, r.Client.GetSpaceID(), tagSetID)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error reading tag set",
+ fmt.Sprintf("Unable to read tag set with ID %s: %s", tagSetID, err.Error()),
+ )
+ return
+ }
+
+ state := flattenTagSet(tagSet)
+ resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
+}
+
+func expandTagSet(model schemas.TagSetResourceModel) *tagsets.TagSet {
+ tagSet := tagsets.NewTagSet(model.Name.ValueString())
+ tagSet.ID = model.ID.ValueString()
+ tagSet.Description = model.Description.ValueString()
+ tagSet.SpaceID = model.SpaceID.ValueString()
+
+ if !model.SortOrder.IsNull() {
+ tagSet.SortOrder = int32(model.SortOrder.ValueInt64())
+ }
+
+ return tagSet
+}
+
+func flattenTagSet(tagSet *tagsets.TagSet) schemas.TagSetResourceModel {
+ model := schemas.TagSetResourceModel{
+ Name: types.StringValue(tagSet.Name),
+ Description: types.StringValue(tagSet.Description),
+ SortOrder: types.Int64Value(int64(tagSet.SortOrder)),
+ SpaceID: types.StringValue(tagSet.SpaceID),
+ }
+ model.ID = types.StringValue(tagSet.ID)
+ return model
+}
diff --git a/octopusdeploy_framework/resource_tag_set_test.go b/octopusdeploy_framework/resource_tag_set_test.go
new file mode 100644
index 000000000..49b8b04ad
--- /dev/null
+++ b/octopusdeploy_framework/resource_tag_set_test.go
@@ -0,0 +1,205 @@
+package octopusdeploy_framework
+
+import (
+ "fmt"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/terraform"
+ "github.com/stretchr/testify/require"
+ "testing"
+
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tagsets"
+ "github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework/octoclient"
+ "github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework/test"
+)
+
+func TestTagSetAndTag(t *testing.T) {
+ tagSetName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
+ tagSetPrefix := "octopusdeploy_tag_set." + tagSetName
+ tagSetDescription := "TagSet Description" + tagSetName
+
+ tagName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
+ tagPrefix := "octopusdeploy_tag." + tagName
+ tagColor := "#6e6e6e"
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { TestAccPreCheck(t) },
+ ProtoV6ProviderFactories: ProtoV6ProviderFactories(),
+ Steps: []resource.TestStep{
+ {
+ Config: testTagSetConfig(tagSetName, tagSetDescription),
+ Check: resource.ComposeTestCheckFunc(
+ testTagSetExists(tagSetPrefix),
+ resource.TestCheckResourceAttr(tagSetPrefix, "name", tagSetName),
+ resource.TestCheckResourceAttr(tagSetPrefix, "description", tagSetDescription),
+ ),
+ },
+ {
+ Config: testTagSetAndTagConfig(tagSetName, tagSetDescription, tagName, tagColor),
+ Check: resource.ComposeTestCheckFunc(
+ testTagSetExists(tagSetPrefix),
+ testTagExists(tagPrefix),
+ resource.TestCheckResourceAttr(tagSetPrefix, "name", tagSetName),
+ resource.TestCheckResourceAttr(tagSetPrefix, "description", tagSetDescription),
+ resource.TestCheckResourceAttr(tagPrefix, "name", tagName),
+ resource.TestCheckResourceAttr(tagPrefix, "color", tagColor),
+ resource.TestCheckResourceAttrSet(tagPrefix, "id"),
+ resource.TestCheckResourceAttrSet(tagPrefix, "tag_set_space_id"),
+ resource.TestCheckResourceAttrSet(tagPrefix, "tag_set_id"),
+ resource.TestCheckResourceAttrPair(tagPrefix, "tag_set_id", tagSetPrefix, "id"),
+ ),
+ },
+ },
+ })
+}
+
+func testTagSetConfig(name, description string) string {
+ return fmt.Sprintf(`
+ resource "octopusdeploy_tag_set" "%s" {
+ name = "%s"
+ description = "%s"
+ }`, name, name, description)
+}
+
+func testTagSetAndTagConfig(tagSetName, tagSetDescription, tagName, tagColor string) string {
+ var tfConfig = fmt.Sprintf(`
+ resource "octopusdeploy_tag_set" "%s" {
+ name = "%s"
+ description = "%s"
+ }
+
+ resource "octopusdeploy_tag" "%s" {
+ name = "%s"
+ color = "%s"
+ description = "Test tag"
+ tag_set_id = octopusdeploy_tag_set.%s.id
+ }`, tagSetName, tagSetName, tagSetDescription, tagName, tagName, tagColor, tagSetName)
+ return tfConfig
+}
+
+func testTagSetExists(n string) resource.TestCheckFunc {
+ return func(s *terraform.State) error {
+ rs, ok := s.RootModule().Resources[n]
+ if !ok {
+ return fmt.Errorf("not found: %s", n)
+ }
+
+ if rs.Primary.ID == "" {
+ return fmt.Errorf("no ID is set")
+ }
+
+ if _, err := tagsets.GetByID(octoClient, rs.Primary.Attributes["space_id"], rs.Primary.ID); err != nil {
+ return fmt.Errorf("error retrieving tag set: %s", err)
+ }
+
+ return nil
+ }
+}
+
+func testTagExists(n string) resource.TestCheckFunc {
+ return func(s *terraform.State) error {
+ rs, ok := s.RootModule().Resources[n]
+ if !ok {
+ return fmt.Errorf("not found: %s", n)
+ }
+
+ if rs.Primary.ID == "" {
+ return fmt.Errorf("no ID is set")
+ }
+
+ tagSetID := rs.Primary.Attributes["tag_set_id"]
+ tagSet, err := tagsets.GetByID(octoClient, rs.Primary.Attributes["space_id"], tagSetID)
+ if err != nil {
+ return fmt.Errorf("error retrieving tag set: %s", err)
+ }
+
+ for _, tag := range tagSet.Tags {
+ if tag.ID == rs.Primary.ID {
+ return nil
+ }
+ }
+
+ return fmt.Errorf("tag not found in tag set")
+ }
+}
+
+func TestTagSetResource(t *testing.T) {
+ testFramework := test.OctopusContainerTest{}
+ newSpaceId, err := testFramework.Act(t, octoContainer, "../terraform", "21-tagset", []string{})
+
+ if err != nil {
+ t.Fatal(err.Error())
+ }
+
+ // Assert
+ client, err := octoclient.CreateClient(octoContainer.URI, newSpaceId, test.ApiKey)
+ query := tagsets.TagSetsQuery{
+ PartialName: "tag1",
+ Skip: 0,
+ Take: 1,
+ }
+
+ resources, err := client.TagSets.Get(query)
+ if err != nil {
+ t.Fatal(err.Error())
+ }
+
+ if len(resources.Items) == 0 {
+ t.Fatalf("Space must have a tag set called \"tag1\"")
+ }
+ resource := resources.Items[0]
+
+ if resource.Description != "Test tagset" {
+ t.Fatal("The tag set must be have a description of \"Test tagset\" (was \"" + resource.Description + "\")")
+ }
+
+ if resource.SortOrder != 0 {
+ t.Fatal("The tag set must be have a sort order of \"0\" (was \"" + fmt.Sprint(resource.SortOrder) + "\")")
+ }
+
+ tagAFound := false
+ for _, u := range resource.Tags {
+ if u.Name == "a" {
+ tagAFound = true
+
+ if u.Description != "tag a" {
+ t.Fatal("The tag a must be have a description of \"tag a\" (was \"" + u.Description + "\")")
+ }
+
+ if u.Color != "#333333" {
+ t.Fatal("The tag a must be have a color of \"#333333\" (was \"" + u.Color + "\")")
+ }
+
+ if u.SortOrder != 2 {
+ t.Fatal("The tag a must be have a sort order of \"2\" (was \"" + fmt.Sprint(u.SortOrder) + "\")")
+ }
+ }
+ }
+
+ if !tagAFound {
+ t.Fatal("Tag Set must have an tag called \"a\"")
+ }
+}
+
+func TestExpandTagSet(t *testing.T) {
+ name := "Test Tag Set"
+ description := "This is a test tag set"
+ sortOrder := int64(10)
+ spaceID := "Spaces-1"
+
+ tagSetModel := schemas.TagSetResourceModel{
+ Name: types.StringValue(name),
+ Description: types.StringValue(description),
+ SortOrder: types.Int64Value(sortOrder),
+ SpaceID: types.StringValue(spaceID),
+ }
+
+ tagSet := expandTagSet(tagSetModel)
+
+ require.Equal(t, name, tagSet.Name)
+ require.Equal(t, description, tagSet.Description)
+ require.Equal(t, int32(sortOrder), tagSet.SortOrder)
+ require.Equal(t, spaceID, tagSet.SpaceID)
+}
diff --git a/octopusdeploy_framework/schemas/tag_set.go b/octopusdeploy_framework/schemas/tag_set.go
new file mode 100644
index 000000000..a75ef2a5d
--- /dev/null
+++ b/octopusdeploy_framework/schemas/tag_set.go
@@ -0,0 +1,113 @@
+package schemas
+
+import (
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ datasourceSchema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ resourceSchema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+const TagSetDataSourceName = "tag_sets"
+const TagSetResourceName = "tag_set"
+
+func GetTagSetResourceSchema() resourceSchema.Schema {
+ return resourceSchema.Schema{
+ Description: "This resource manages tag sets in Octopus Deploy.",
+ Attributes: map[string]resourceSchema.Attribute{
+ "id": util.ResourceString().
+ Optional().
+ Computed().
+ Description("The unique ID for this resource.").
+ PlanModifiers(stringplanmodifier.UseStateForUnknown()).
+ Build(),
+ "name": util.ResourceString().
+ Required().
+ Description("The name of this resource.").
+ Build(),
+ "description": util.ResourceString().
+ Optional().
+ Computed().
+ Description("The description of this tag set.").
+ Build(),
+ "sort_order": util.ResourceInt64().
+ Optional().
+ Computed().
+ Description("The sort order associated with this resource.").
+ Build(),
+ "space_id": util.ResourceString().
+ Optional().
+ Computed().
+ Description("The space ID associated with this resource.").
+ PlanModifiers(stringplanmodifier.UseStateForUnknown()).
+ Build(),
+ },
+ }
+}
+
+func GetTagSetDataSourceSchema() datasourceSchema.Schema {
+ return datasourceSchema.Schema{
+ Description: "Provides information about existing tag sets.",
+ Attributes: map[string]datasourceSchema.Attribute{
+ "id": util.DataSourceString().
+ Computed().
+ Description("The ID of this resource.").
+ Build(),
+ "space_id": util.DataSourceString().
+ Optional().
+ Description("The space ID associated with this resource.").
+ Build(),
+ "ids": util.DataSourceList(types.StringType).
+ Optional().
+ Description("A filter to search by a list of IDs.").
+ Build(),
+ "partial_name": util.DataSourceString().
+ Optional().
+ Description("A filter to search by the partial match of a name.").
+ Build(),
+ "skip": util.DataSourceInt64().
+ Optional().
+ Description("A filter to specify the number of items to skip in the response.").
+ Build(),
+ "take": util.DataSourceInt64().
+ Optional().
+ Description("A filter to specify the number of items to take (or return) in the response.").
+ Build(),
+ "tag_sets": datasourceSchema.ListAttribute{
+ Computed: true,
+ ElementType: types.ObjectType{AttrTypes: GetTagSetAttrTypes()},
+ Description: "A list of tag sets that match the filter(s).",
+ },
+ },
+ }
+}
+
+func GetTagSetAttrTypes() map[string]attr.Type {
+ return map[string]attr.Type{
+ "id": types.StringType,
+ "space_id": types.StringType,
+ "name": types.StringType,
+ "description": types.StringType,
+ "sort_order": types.Int64Type,
+ }
+}
+
+type TagSetDataSourceModel struct {
+ ID types.String `tfsdk:"id"`
+ SpaceID types.String `tfsdk:"space_id"`
+ IDs types.List `tfsdk:"ids"`
+ PartialName types.String `tfsdk:"partial_name"`
+ Skip types.Int64 `tfsdk:"skip"`
+ Take types.Int64 `tfsdk:"take"`
+ TagSets types.List `tfsdk:"tag_sets"`
+}
+
+type TagSetResourceModel struct {
+ Name types.String `tfsdk:"name"`
+ Description types.String `tfsdk:"description"`
+ SortOrder types.Int64 `tfsdk:"sort_order"`
+ SpaceID types.String `tfsdk:"space_id"`
+
+ ResourceModel
+}
diff --git a/octopusdeploy_framework/util/util.go b/octopusdeploy_framework/util/util.go
index 0f167bf80..e0c87bdea 100644
--- a/octopusdeploy_framework/util/util.go
+++ b/octopusdeploy_framework/util/util.go
@@ -44,6 +44,7 @@ func ExpandStringList(list types.List) []string {
}
return result
}
+
func SetToStringArray(ctx context.Context, set types.Set) ([]string, diag.Diagnostics) {
teams := make([]types.String, 0, len(set.Elements()))
diags := diag.Diagnostics{}
From 3b1285c8012efd718c3560a7e83ecf9ae156116f Mon Sep 17 00:00:00 2001
From: Henrik Andersson
Date: Mon, 12 Aug 2024 17:13:58 -0700
Subject: [PATCH 16/28] chore: add ability to import an environment (#729)
---
octopusdeploy_framework/resource_environment.go | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/octopusdeploy_framework/resource_environment.go b/octopusdeploy_framework/resource_environment.go
index 281639f6d..3c78b3493 100644
--- a/octopusdeploy_framework/resource_environment.go
+++ b/octopusdeploy_framework/resource_environment.go
@@ -8,10 +8,13 @@ import (
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
+ "github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
)
+var _ resource.ResourceWithImportState = &environmentTypeResource{}
+
type environmentTypeResource struct {
*Config
}
@@ -32,6 +35,10 @@ func (r *environmentTypeResource) Configure(_ context.Context, req resource.Conf
r.Config = ResourceConfiguration(req, resp)
}
+func (*environmentTypeResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
+}
+
func (r *environmentTypeResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data schemas.EnvironmentTypeResourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
From c16f12a76e94247c854bafeca96c05e0c89eed06 Mon Sep 17 00:00:00 2001
From: Isaac Calligeros <101079287+IsaacCalligeros95@users.noreply.github.com>
Date: Tue, 13 Aug 2024 10:15:17 +0930
Subject: [PATCH 17/28] Migrate tag resource (#719)
* Chore!: Migrate tag resource
* Migrate tag resource
* fix build fail
* rename
* Add resource_tag_test.go
* Fix concurrent issue
* Fix update and import
* Fix document
* Fix wrong schema document
* refactor remove duplicate code
* tidy
* updated default value for description
---------
Co-authored-by: Huy Nguyen
Co-authored-by: Ben Pearce
---
docs/resources/tag.md | 12 +-
octopusdeploy/provider.go | 1 -
octopusdeploy/resource_tag.go | 304 --------------
octopusdeploy/schema_tag.go | 74 ----
octopusdeploy_framework/framework_provider.go | 1 +
octopusdeploy_framework/resource_tag.go | 375 ++++++++++++++++++
octopusdeploy_framework/resource_tag_test.go | 124 ++++++
octopusdeploy_framework/schemas/tag.go | 92 +++++
8 files changed, 598 insertions(+), 385 deletions(-)
delete mode 100644 octopusdeploy/resource_tag.go
delete mode 100644 octopusdeploy/schema_tag.go
create mode 100644 octopusdeploy_framework/resource_tag.go
create mode 100644 octopusdeploy_framework/resource_tag_test.go
create mode 100644 octopusdeploy_framework/schemas/tag.go
diff --git a/docs/resources/tag.md b/docs/resources/tag.md
index 9f40e39d8..314842877 100644
--- a/docs/resources/tag.md
+++ b/docs/resources/tag.md
@@ -17,19 +17,19 @@ This resource manages tags in Octopus Deploy.
### Required
-- `color` (String)
-- `name` (String) The name of this resource.
+- `color` (String) The color of the tag.
+- `name` (String) The name of the tag.
- `tag_set_id` (String) The ID of the associated tag set.
### Optional
-- `description` (String) The description of this tag.
-- `sort_order` (Number)
-- `tag_set_space_id` (String) The Space ID of the associated tag set. Required if the tag set is not in the same space as what is configured on the provider
+- `description` (String) The description of the tag.
+- `sort_order` (Number) The sort order of the tag.
+- `tag_set_space_id` (String) The Space ID of the associated tag set. Required if the tag set is not in the same space as what is configured on the provider.
### Read-Only
-- `canonical_tag_name` (String)
+- `canonical_tag_name` (String) The canonical name of the tag.
- `id` (String) The ID of this resource.
diff --git a/octopusdeploy/provider.go b/octopusdeploy/provider.go
index cdb60f086..a5f8061cb 100644
--- a/octopusdeploy/provider.go
+++ b/octopusdeploy/provider.go
@@ -61,7 +61,6 @@ func Provider() *schema.Provider {
"octopusdeploy_ssh_connection_deployment_target": resourceSSHConnectionDeploymentTarget(),
"octopusdeploy_ssh_key_account": resourceSSHKeyAccount(),
"octopusdeploy_static_worker_pool": resourceStaticWorkerPool(),
- "octopusdeploy_tag": resourceTag(),
"octopusdeploy_team": resourceTeam(),
"octopusdeploy_token_account": resourceTokenAccount(),
"octopusdeploy_user": resourceUser(),
diff --git a/octopusdeploy/resource_tag.go b/octopusdeploy/resource_tag.go
deleted file mode 100644
index dc81d3d42..000000000
--- a/octopusdeploy/resource_tag.go
+++ /dev/null
@@ -1,304 +0,0 @@
-package octopusdeploy
-
-import (
- "context"
- "log"
- "strings"
-
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client"
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tagsets"
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tenants"
- "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal"
- "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
- "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
- "golang.org/x/exp/slices"
-)
-
-func resourceTag() *schema.Resource {
- return &schema.Resource{
- CreateContext: resourceTagCreate,
- DeleteContext: resourceTagDelete,
- Description: "This resource manages tags in Octopus Deploy.",
- Importer: getImporter(),
- ReadContext: resourceTagRead,
- Schema: getTagSchema(),
- UpdateContext: resourceTagUpdate,
- }
-}
-
-func resourceTagCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- internal.Mutex.Lock()
- defer internal.Mutex.Unlock()
-
- log.Printf("[INFO] creating tag")
-
- return tagCreate(ctx, d, m)
-}
-
-func tagCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- log.Printf("[INFO] creating tag")
-
- tagSetID := d.Get("tag_set_id").(string)
- tagSetSpaceID := d.Get("tag_set_space_id").(string)
-
- octopus := m.(*client.Client)
- tagSet, err := tagsets.GetByID(octopus, tagSetSpaceID, tagSetID)
- if err != nil {
- return processUnknownTagSetError(ctx, d, err)
- }
-
- name := d.Get("name").(string)
-
- for _, tag := range tagSet.Tags {
- if tag.Name == name {
- return diag.Errorf(`the tag name '%s' is already in use by another tag in this tag set; tag names must be unique`, name)
- }
- }
-
- tag := expandTag(d)
- if tag.ID != "" {
- tag.ID = tagSet.GetID() + "/" + strings.Split(tag.ID, "/")[1]
- }
- tagSet.Tags = append(tagSet.Tags, tag)
-
- updatedTagSet, err := tagsets.Update(octopus, tagSet)
- if err != nil {
- return diag.FromErr(err)
- }
-
- return findByIdOrNameAndSetTag(ctx, d, tag, updatedTagSet)
-
-}
-
-func resourceTagDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- internal.Mutex.Lock()
- defer internal.Mutex.Unlock()
-
- tagSetID := d.Get("tag_set_id").(string)
- tagSetSpaceID := d.Get("tag_set_space_id").(string)
-
- log.Printf("[INFO] deleting tag (%s)", d.Id())
-
- octopus := m.(*client.Client)
- tagSet, err := tagsets.GetByID(octopus, tagSetSpaceID, tagSetID)
- if err != nil {
- return processUnknownTagSetError(ctx, d, err)
- }
-
- tag := expandTag(d)
-
- // verify tag is not associated with a tenant
- isUsed, err := isTagUsedByTenants(ctx, octopus, tagSetSpaceID, tag)
- if err != nil {
- d.SetId("")
- return diag.FromErr(err)
- }
-
- if isUsed {
- d.SetId("")
- return diag.Errorf("the tag may not be deleted; it is being used by one or more tenant(s)")
- }
-
- // tag is known and not associated with a tenant, therefore it may be deleted
-
- for i := 0; i < len(tagSet.Tags); i++ {
- if tagSet.Tags[i].ID == d.Id() {
- tagSet.Tags = slices.Delete(tagSet.Tags, i, i+1)
-
- if _, err := tagsets.Update(octopus, tagSet); err != nil {
- return diag.FromErr(err)
- }
-
- log.Printf("[INFO] tag deleted (%s)", d.Id())
- d.SetId("")
- return nil
- }
- }
-
- return errors.DeleteFromState(ctx, d, "tag")
-}
-
-func resourceTagRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- internal.Mutex.Lock()
- defer internal.Mutex.Unlock()
-
- // validate the tag ID
- if d.Id() == "" || !strings.Contains(d.Id(), "/") {
- return diag.Errorf(`unable to import tag; ID must be "TagSets-{ID}/Tags-{ID}"`)
- }
-
- name := d.Get("name").(string)
- tagSetID := d.Get("tag_set_id").(string)
- tagSetSpaceID := d.Get("tag_set_space_id").(string)
-
- // if name and tag set ID are empty then an import is underway
- if name == "" && tagSetID == "" {
- log.Printf("[INFO] importing tag (%s)", d.Id())
- tagSetID = strings.Split(d.Id(), "/")[0]
- } else {
- log.Printf("[INFO] reading tag (%s)", d.Id())
- }
-
- octopus := m.(*client.Client)
- tagSet, err := tagsets.GetByID(octopus, tagSetSpaceID, tagSetID)
- if err != nil {
- return processUnknownTagSetError(ctx, d, err)
- }
-
- tag := expandTag(d)
- return findByIdOrNameAndSetTag(ctx, d, tag, tagSet)
-}
-
-func resourceTagUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
- internal.Mutex.Lock()
- defer internal.Mutex.Unlock()
-
- name := d.Get("name").(string)
- tagSetID := d.Get("tag_set_id").(string)
- tagSetSpaceID := d.Get("tag_set_space_id").(string)
-
- log.Printf("[INFO] updating tag (%s)", d.Id())
-
- octopus := m.(*client.Client)
-
- // if the tag is reassigned to another tag set
- if d.HasChange("tag_set_id") {
- sourceTagSetID, destinationTagSetID := d.GetChange("tag_set_id")
- sourceTagSetSpaceID, destinationTagSetSpaceID := d.GetChange("tag_set_space_id")
-
- sourceTagSet, err := tagsets.GetByID(octopus, sourceTagSetSpaceID.(string), sourceTagSetID.(string))
- if err != nil {
- // if spaceID has change, tag has been deleted, recreate required
- if d.HasChange("tag_set_space_id") {
- return tagCreate(ctx, d, m)
- }
- return diag.FromErr(err)
- }
-
- destinationTagSet, err := tagsets.GetByID(octopus, destinationTagSetSpaceID.(string), destinationTagSetID.(string))
- if err != nil {
- return diag.FromErr(err)
- }
-
- // check to see if the name already exists in the destination tag set
- for _, tag := range destinationTagSet.Tags {
- if tag.Name == name {
- d.SetId("")
- return diag.Errorf(`the tag name '%s' is already in use by another tag in this tag set; tag names must be unique`, name)
- }
- }
-
- tag := expandTag(d)
-
- // check to see that the tag is not applied to a tenant
- isUsed, err := isTagUsedByTenants(ctx, octopus, sourceTagSetSpaceID.(string), tag)
- if err != nil {
- d.SetId("")
- return diag.FromErr(err)
- }
-
- if isUsed {
- d.SetId("")
- return diag.Errorf("the tag may not be transferred; it is being used by one or more tenant(s)")
- }
-
- // all requirements are met; it is OK to transfer the tag
-
- // remove the tag from the source tag set and update through the API
- for i := 0; i < len(sourceTagSet.Tags); i++ {
- if sourceTagSet.Tags[i].ID == d.Id() {
- sourceTagSet.Tags = slices.Delete(sourceTagSet.Tags, i, i+1)
-
- if _, err := tagsets.Update(octopus, sourceTagSet); err != nil {
- return diag.FromErr(err)
- }
- }
- }
-
- // update and add the tag to the destination tag set
- tag.ID = destinationTagSet.GetID() + "/" + strings.Split(tag.ID, "/")[1]
- destinationTagSet.Tags = append(destinationTagSet.Tags, tag)
-
- updatedTagSet, err := tagsets.Update(octopus, destinationTagSet)
- if err != nil {
- return diag.FromErr(err)
- }
-
- return findByIdOrNameAndSetTag(ctx, d, tag, updatedTagSet)
- }
-
- tagSet, err := tagsets.GetByID(octopus, tagSetSpaceID, tagSetID)
- if err != nil {
- return processUnknownTagSetError(ctx, d, err)
- }
-
- // find and update the tag that matches the one updated in configuration
- for i := 0; i < len(tagSet.Tags); i++ {
- if tagSet.Tags[i].ID == d.Id() {
- tagSet.Tags[i] = expandTag(d)
-
- updatedTagSet, err := tagsets.Update(octopus, tagSet)
- if err != nil {
- return diag.FromErr(err)
- }
-
- return findByIdOrNameAndSetTag(ctx, d, tagSet.Tags[i], updatedTagSet)
- }
- }
-
- return diag.Errorf("unable to update tag")
-}
-
-func isTagUsedByTenants(ctx context.Context, octopus *client.Client, spaceID string, tag *tagsets.Tag) (bool, error) {
- tenants, err := tenants.Get(octopus, spaceID, tenants.TenantsQuery{
- Tags: []string{tag.ID},
- })
- if err != nil {
- return false, err
- }
-
- return len(tenants.Items) > 0, nil
-}
-
-func findByIdOrNameAndSetTag(ctx context.Context, d *schema.ResourceData, tag *tagsets.Tag, tagSet *tagsets.TagSet) diag.Diagnostics {
- for _, t := range tagSet.Tags {
- if t.Name == tag.Name {
- if err := setTag(ctx, d, t, tagSet); err != nil {
- return diag.FromErr(err)
- }
-
- log.Printf("[INFO] tag (%s)", tag.ID)
- return nil
- }
- }
-
- for _, t := range tagSet.Tags {
- if t.ID == tag.ID {
- if err := setTag(ctx, d, t, tagSet); err != nil {
- return diag.FromErr(err)
- }
- log.Printf("[INFO] tag (%s)", tag.ID)
- return nil
- }
- }
-
- return errors.DeleteFromState(ctx, d, "tag")
-}
-
-func processUnknownTagSetError(ctx context.Context, d *schema.ResourceData, err error) diag.Diagnostics {
- if err == nil {
- return nil
- }
-
- if apiError, ok := err.(*core.APIError); ok {
- if apiError.StatusCode == 404 {
- log.Printf("[INFO] tag set (%s) not found; deleting tag from state", d.Id())
- d.SetId("")
- return nil
- }
- }
-
- return diag.FromErr(err)
-}
diff --git a/octopusdeploy/schema_tag.go b/octopusdeploy/schema_tag.go
deleted file mode 100644
index e9657ae61..000000000
--- a/octopusdeploy/schema_tag.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package octopusdeploy
-
-import (
- "context"
-
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tagsets"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
-)
-
-func getTagSchema() map[string]*schema.Schema {
- return map[string]*schema.Schema{
- "canonical_tag_name": {
- Computed: true,
- Type: schema.TypeString,
- },
- "color": {
- Required: true,
- Type: schema.TypeString,
- },
- "description": getDescriptionSchema("tag"),
- "name": getNameSchema(true),
- "sort_order": {
- Computed: true,
- Optional: true,
- Type: schema.TypeInt,
- },
- "tag_set_id": {
- Description: "The ID of the associated tag set.",
- Required: true,
- Type: schema.TypeString,
- },
- "tag_set_space_id": {
- Description: "The Space ID of the associated tag set. Required if the tag set is not in the same space as what is configured on the provider",
- Type: schema.TypeString,
- Optional: true,
- Computed: true,
- },
- }
-}
-
-func expandTag(d *schema.ResourceData) *tagsets.Tag {
- color := d.Get("color").(string)
- name := d.Get("name").(string)
-
- tag := tagsets.NewTag(name, color)
- tag.ID = d.Id()
-
- if v, ok := d.GetOk("canonical_tag_name"); ok {
- tag.CanonicalTagName = v.(string)
- }
-
- if v, ok := d.GetOk("description"); ok {
- tag.Description = v.(string)
- }
-
- if v, ok := d.GetOk("sort_order"); ok {
- tag.SortOrder = v.(int)
- }
-
- return tag
-}
-
-func setTag(ctx context.Context, d *schema.ResourceData, tag *tagsets.Tag, tagSet *tagsets.TagSet) error {
- d.Set("canonical_tag_name", tag.CanonicalTagName)
- d.Set("color", tag.Color)
- d.Set("description", tag.Description)
- d.Set("name", tag.Name)
- d.Set("sort_order", tag.SortOrder)
- d.Set("tag_set_id", tagSet.GetID())
- d.Set("tag_set_space_id", tagSet.SpaceID)
- d.SetId(tag.ID)
-
- return nil
-}
diff --git a/octopusdeploy_framework/framework_provider.go b/octopusdeploy_framework/framework_provider.go
index 19bff356a..a562879a1 100644
--- a/octopusdeploy_framework/framework_provider.go
+++ b/octopusdeploy_framework/framework_provider.go
@@ -95,6 +95,7 @@ func (p *octopusDeployFrameworkProvider) Resources(ctx context.Context) []func()
NewLibraryVariableSetFeedResource,
NewVariableResource,
NewProjectResource,
+ NewTagResource,
NewDockerContainerRegistryFeedResource,
NewTagSetResource,
NewUsernamePasswordAccountResource,
diff --git a/octopusdeploy_framework/resource_tag.go b/octopusdeploy_framework/resource_tag.go
new file mode 100644
index 000000000..fcc2cc2fd
--- /dev/null
+++ b/octopusdeploy_framework/resource_tag.go
@@ -0,0 +1,375 @@
+package octopusdeploy_framework
+
+import (
+ "context"
+ "fmt"
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client"
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tagsets"
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tenants"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+ "log"
+ "slices"
+ "strings"
+)
+
+type tagTypeResource struct {
+ *Config
+}
+
+func NewTagResource() resource.Resource {
+ return &tagTypeResource{}
+}
+
+func (r *tagTypeResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = util.GetTypeName(schemas.TagResourceName)
+}
+
+func (r *tagTypeResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schemas.GetTagResourceSchema()
+}
+
+func (r *tagTypeResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ r.Config = ResourceConfiguration(req, resp)
+}
+
+func (r *tagTypeResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ internal.Mutex.Lock()
+ defer internal.Mutex.Unlock()
+
+ var data *schemas.TagResourceModel
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ if data.ID.ValueString() == "" || !strings.Contains(data.ID.ValueString(), "/") {
+ resp.Diagnostics.AddError(`unable to import tag; ID must be "TagSets-{ID}/Tags-{ID}"`, "tag ID is null or does not match the required TagSets-{ID}/Tags-{ID} format")
+ return
+ }
+
+ name := data.Name.ValueString()
+ tagSetID := data.TagSetId.ValueString()
+ tagSetSpaceID := data.TagSetSpaceId.ValueString()
+
+ // if name and tag set ID are empty then an import is underway
+ if name == "" && tagSetID == "" {
+ tflog.Info(ctx, fmt.Sprintf("importing tag (%s)", data.ID.ValueString()))
+ tagSetID = strings.Split(data.ID.ValueString(), "/")[0]
+ } else {
+ tflog.Info(ctx, fmt.Sprintf("reading tag (%s)", data.ID.ValueString()))
+ }
+
+ tagSet, err := tagsets.GetByID(r.Config.Client, tagSetSpaceID, tagSetID)
+ if err != nil {
+ processUnknownTagSetError(ctx, data, err, resp.Diagnostics)
+ }
+
+ tag := schemas.MapFromStateToTag(data)
+ findByIdOrNameAndSetTag(ctx, data, tag, tagSet)
+
+ tflog.Info(ctx, fmt.Sprintf("Tag read (%s)", tag.ID))
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *tagTypeResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ internal.Mutex.Lock()
+ defer internal.Mutex.Unlock()
+
+ var data *schemas.TagResourceModel
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tagCreate(ctx, data, resp.Diagnostics, r.Client)
+
+ tflog.Info(ctx, fmt.Sprintf("tag created (%s)", data.ID))
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (t *tagTypeResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ internal.Mutex.Lock()
+ defer internal.Mutex.Unlock()
+
+ var data, state *schemas.TagResourceModel
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
+ resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ name := data.Name.ValueString()
+ tagSetID := data.TagSetId.ValueString()
+ tagSetSpaceID := data.TagSetSpaceId.ValueString()
+
+ tflog.Info(ctx, fmt.Sprintf("updating tag (%s)", data.ID))
+
+ // if the tag is reassigned to another tag set
+ if !data.TagSetId.Equal(state.TagSetId) {
+ sourceTagSetID, destinationTagSetID := state.TagSetId.ValueString(), data.TagSetId.ValueString()
+ sourceTagSetSpaceID, destinationTagSetSpaceID := state.TagSetSpaceId.ValueString(), data.TagSetSpaceId.ValueString()
+
+ sourceTagSet, err := tagsets.GetByID(t.Client, sourceTagSetSpaceID, sourceTagSetID)
+ if err != nil {
+ // if spaceID has changed, tag has been deleted, recreate required
+ if !data.TagSetSpaceId.Equal(state.TagSetSpaceId) {
+ tagCreate(ctx, data, resp.Diagnostics, t.Client)
+ if !resp.Diagnostics.HasError() {
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+ }
+ return
+ }
+ resp.Diagnostics.AddError("Failed to get source tag set", err.Error())
+ return
+ }
+
+ destinationTagSet, err := tagsets.GetByID(t.Client, destinationTagSetSpaceID, destinationTagSetID)
+ if err != nil {
+ resp.Diagnostics.AddError("Failed to get destination tag set", err.Error())
+ return
+ }
+
+ // check to see if the name already exists in the destination tag set
+ for _, tag := range destinationTagSet.Tags {
+ if tag.Name == name {
+ resp.Diagnostics.AddError("Tag name already exists", fmt.Sprintf("the tag name '%s' is already in use by another tag in this tag set; tag names must be unique", name))
+ return
+ }
+ }
+
+ tag := schemas.MapFromStateToTag(data)
+
+ // check to see that the tag is not applied to a tenant
+ isUsed, err := isTagUsedByTenants(ctx, t.Client, sourceTagSetSpaceID, tag)
+ if err != nil {
+ resp.Diagnostics.AddError("Failed to check if tag is used by tenants", err.Error())
+ return
+ }
+
+ if isUsed {
+ resp.Diagnostics.AddError("Tag in use", "the tag may not be transferred; it is being used by one or more tenant(s)")
+ return
+ }
+
+ // all requirements are met; it is OK to transfer the tag
+
+ // remove the tag from the source tag set and update through the API
+ for i := 0; i < len(sourceTagSet.Tags); i++ {
+ if sourceTagSet.Tags[i].ID == data.ID.ValueString() {
+ sourceTagSet.Tags = slices.Delete(sourceTagSet.Tags, i, i+1)
+ if _, err := tagsets.Update(t.Client, sourceTagSet); err != nil {
+ resp.Diagnostics.AddError("Failed to update source tag set", err.Error())
+ return
+ }
+ break
+ }
+ }
+
+ // update and add the tag to the destination tag set
+ tag.ID = destinationTagSet.GetID() + "/" + strings.Split(tag.ID, "/")[1]
+ destinationTagSet.Tags = append(destinationTagSet.Tags, tag)
+
+ updatedTagSet, err := tagsets.Update(t.Client, destinationTagSet)
+ if err != nil {
+ resp.Diagnostics.AddError("Failed to update destination tag set", err.Error())
+ return
+ }
+
+ if err := findByIdOrNameAndSetTag(ctx, data, tag, updatedTagSet); err != nil {
+ resp.Diagnostics.AddError("Failed to find updated tag", "")
+ return
+ }
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+ return
+ }
+
+ tagSet, err := tagsets.GetByID(t.Client, tagSetSpaceID, tagSetID)
+ if err != nil {
+ processUnknownTagSetError(ctx, data, err, resp.Diagnostics)
+ return
+ }
+
+ // find and update the tag that matches the one updated in configuration
+ var updatedTag *tagsets.Tag
+ for i := 0; i < len(tagSet.Tags); i++ {
+ if tagSet.Tags[i].ID == data.ID.ValueString() || tagSet.Tags[i].Name == data.Name.ValueString() {
+ tagSet.Tags[i] = schemas.MapFromStateToTag(data)
+
+ updatedTagSet, err := tagsets.Update(t.Client, tagSet)
+ if err != nil {
+ resp.Diagnostics.AddError("Failed to update tag set", err.Error())
+ return
+ }
+
+ // Find the updated tag in the updatedTagSet
+ for _, t := range updatedTagSet.Tags {
+ if t.ID == data.ID.ValueString() || t.Name == data.Name.ValueString() {
+ updatedTag = t
+ break
+ }
+ }
+
+ if updatedTag == nil {
+ resp.Diagnostics.AddError("Updated tag not found", "The updated tag was not found in the response from the API")
+ return
+ }
+
+ schemas.MapFromTagToState(data, updatedTag, updatedTagSet)
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+ return
+ }
+ }
+
+ resp.Diagnostics.AddError("Unable to update tag", "Tag not found in tag set")
+}
+
+func (r *tagTypeResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ internal.Mutex.Lock()
+ defer internal.Mutex.Unlock()
+
+ var data *schemas.TagResourceModel
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tagSetID := data.TagSetId.ValueString()
+ tagSetSpaceID := data.TagSetSpaceId.ValueString()
+
+ tflog.Info(ctx, fmt.Sprintf("deleting tag (%s)", data.ID))
+
+ tagSet, err := tagsets.GetByID(r.Config.Client, tagSetSpaceID, tagSetID)
+ if err != nil {
+ processUnknownTagSetError(ctx, data, err, resp.Diagnostics)
+ return
+ }
+
+ tag := schemas.MapFromStateToTag(data)
+
+ // verify tag is not associated with a tenant
+ isUsed, err := isTagUsedByTenants(ctx, r.Config.Client, tagSetSpaceID, tag)
+ if err != nil {
+ data.ID = types.StringValue("")
+ resp.Diagnostics.AddError("Failed to check if tag is used by tenants", err.Error())
+ }
+
+ if isUsed {
+ data.ID = types.StringValue("")
+ resp.Diagnostics.AddError("the tag may not be deleted; it is being used by one or more tenant(s)", "")
+ }
+
+ // tag is known and not associated with a tenant, therefore it may be deleted
+
+ for i := 0; i < len(tagSet.Tags); i++ {
+ if tagSet.Tags[i].ID == data.ID.ValueString() {
+ tagSet.Tags = slices.Delete(tagSet.Tags, i, i+1)
+
+ if _, err := tagsets.Update(r.Config.Client, tagSet); err != nil {
+ resp.Diagnostics.AddError("Failed to update tag", err.Error())
+ return
+ }
+
+ log.Printf("[INFO] tag deleted (%s)", data.ID.ValueString())
+ data.ID = types.StringValue("")
+ return
+ }
+ }
+}
+
+func tagCreate(ctx context.Context, data *schemas.TagResourceModel, diag diag.Diagnostics, client *client.Client) diag.Diagnostics {
+ tflog.Info(ctx, "creating tag")
+
+ tagSetID := data.TagSetId.ValueString()
+ tagSetSpaceID := data.TagSetSpaceId.ValueString()
+
+ tagSet, err := tagsets.GetByID(client, tagSetSpaceID, tagSetID)
+
+ if err != nil {
+ processUnknownTagSetError(ctx, data, err, diag)
+ return diag
+ }
+
+ name := data.Name.ValueString()
+
+ for _, tag := range tagSet.Tags {
+ if tag.Name == name {
+ diag.AddError(`the tag name '%s' is already in use by another tag in this tag set; tag names must be unique`, name)
+ }
+ }
+
+ tag := schemas.MapFromStateToTag(data)
+ if tag.ID != "" {
+ tag.ID = tagSet.GetID() + "/" + strings.Split(tag.ID, "/")[1]
+ }
+ tagSet.Tags = append(tagSet.Tags, tag)
+
+ updatedTagSet, err := tagsets.Update(client, tagSet)
+ if err != nil {
+ diag.AddError(`unable to update tag set`, err.Error())
+ }
+
+ return findByIdOrNameAndSetTag(ctx, data, tag, updatedTagSet)
+}
+
+func isTagUsedByTenants(ctx context.Context, octopus *client.Client, spaceID string, tag *tagsets.Tag) (bool, error) {
+ tenants, err := tenants.Get(octopus, spaceID, tenants.TenantsQuery{
+ Tags: []string{tag.ID},
+ })
+ if err != nil {
+ return false, err
+ }
+
+ return len(tenants.Items) > 0, nil
+}
+
+func findByIdOrNameAndSetTag(ctx context.Context, data *schemas.TagResourceModel, tag *tagsets.Tag, tagSet *tagsets.TagSet) diag.Diagnostics {
+ for _, t := range tagSet.Tags {
+ if t.Name == tag.Name {
+ schemas.MapFromTagToState(data, t, tagSet)
+
+ tflog.Info(ctx, fmt.Sprintf("tag (%s)", tag.ID))
+ return nil
+ }
+ }
+
+ for _, t := range tagSet.Tags {
+ if t.ID == tag.ID {
+ schemas.MapFromTagToState(data, t, tagSet)
+ tflog.Info(ctx, fmt.Sprintf("tag (%s)", tag.ID))
+ return nil
+ }
+ }
+
+ tflog.Info(ctx, fmt.Sprintf("%s (%s) not found; deleting from state", tag.ID, tag.ID))
+ data.ID = types.StringValue("")
+ return nil
+}
+
+func processUnknownTagSetError(ctx context.Context, data *schemas.TagResourceModel, err error, diag diag.Diagnostics) {
+ if err == nil {
+ return
+ }
+
+ if apiError, ok := err.(*core.APIError); ok {
+ if apiError.StatusCode == 404 {
+ tflog.Info(ctx, fmt.Sprintf("tag set (%s) not found; deleting tag from state", data.ID))
+ data.ID = types.StringValue("")
+ return
+ }
+ }
+
+ diag.AddError("Processing unknown tag set failed", err.Error())
+}
+
+func (t *tagTypeResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
+}
diff --git a/octopusdeploy_framework/resource_tag_test.go b/octopusdeploy_framework/resource_tag_test.go
new file mode 100644
index 000000000..710f8d235
--- /dev/null
+++ b/octopusdeploy_framework/resource_tag_test.go
@@ -0,0 +1,124 @@
+package octopusdeploy_framework
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tagsets"
+ "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/terraform"
+)
+
+func TestAccTag(t *testing.T) {
+ tagSetName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
+ tagName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
+ tagColor := "#6e6e6e"
+ tagResourceName := "octopusdeploy_tag." + tagName
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { TestAccPreCheck(t) },
+ ProtoV6ProviderFactories: ProtoV6ProviderFactories(),
+ CheckDestroy: testAccTagDestroy,
+ Steps: []resource.TestStep{
+ {
+ Config: testTagConfig(tagSetName, tagName, tagColor),
+ Check: resource.ComposeTestCheckFunc(
+ testTagExists(tagResourceName),
+ resource.TestCheckResourceAttr(tagResourceName, "name", tagName),
+ resource.TestCheckResourceAttr(tagResourceName, "color", tagColor),
+ resource.TestCheckResourceAttrSet(tagResourceName, "id"),
+ resource.TestCheckResourceAttrSet(tagResourceName, "tag_set_id"),
+ resource.TestCheckResourceAttrSet(tagResourceName, "tag_set_space_id"),
+ ),
+ },
+ {
+ Config: testTagConfigUpdate(tagSetName, tagName, "#ff0000"),
+ Check: resource.ComposeTestCheckFunc(
+ testTagExists(tagResourceName),
+ resource.TestCheckResourceAttr(tagResourceName, "name", tagName),
+ resource.TestCheckResourceAttr(tagResourceName, "color", "#ff0000"),
+ resource.TestCheckResourceAttrSet(tagResourceName, "id"),
+ resource.TestCheckResourceAttrSet(tagResourceName, "tag_set_id"),
+ resource.TestCheckResourceAttrSet(tagResourceName, "tag_set_space_id"),
+ ),
+ },
+ {
+ ResourceName: tagResourceName,
+ ImportState: true,
+ ImportStateVerify: true,
+ ImportStateIdFunc: testAccTagImportStateIdFunc(tagResourceName),
+ },
+ },
+ })
+}
+
+func testTagConfig(tagSetName, tagName, tagColor string) string {
+ var tfConfig = fmt.Sprintf(`
+ resource "octopusdeploy_tag_set" "%s" {
+ name = "%s"
+ description = "Test tag set"
+ }
+
+ resource "octopusdeploy_tag" "%s" {
+ name = "%s"
+ color = "%s"
+ description = "Test tag"
+ tag_set_id = octopusdeploy_tag_set.%s.id
+ }
+ `, tagSetName, tagSetName, tagName, tagName, tagColor, tagSetName)
+ return tfConfig
+}
+
+func testTagConfigUpdate(tagSetName, tagName, tagColor string) string {
+ var tfConfig = fmt.Sprintf(`
+ resource "octopusdeploy_tag_set" "%s" {
+ name = "%s"
+ description = "Test tag set"
+ }
+
+ resource "octopusdeploy_tag" "%s" {
+ name = "%s"
+ color = "%s"
+ description = "Updated test tag"
+ tag_set_id = octopusdeploy_tag_set.%s.id
+ }
+ `, tagSetName, tagSetName, tagName, tagName, tagColor, tagSetName)
+ return tfConfig
+}
+
+func testAccTagDestroy(s *terraform.State) error {
+
+ for _, rs := range s.RootModule().Resources {
+ if rs.Type != "octopusdeploy_tag" {
+ continue
+ }
+
+ tagSetID := rs.Primary.Attributes["tag_set_id"]
+ tagSet, err := tagsets.GetByID(octoClient, rs.Primary.Attributes["space_id"], tagSetID)
+ if err != nil {
+ return nil // If the tag set is gone, the tag is gone too
+ }
+
+ for _, tag := range tagSet.Tags {
+ if tag.ID == rs.Primary.ID {
+ return fmt.Errorf("Tag still exists")
+ }
+ }
+ }
+
+ return nil
+}
+
+func testAccTagImportStateIdFunc(resourceName string) resource.ImportStateIdFunc {
+ return func(s *terraform.State) (string, error) {
+ rs, ok := s.RootModule().Resources[resourceName]
+ if !ok {
+ return "", fmt.Errorf("Not found: %s", resourceName)
+ }
+
+ tagID := rs.Primary.ID
+
+ return tagID, nil
+ }
+}
diff --git a/octopusdeploy_framework/schemas/tag.go b/octopusdeploy_framework/schemas/tag.go
new file mode 100644
index 000000000..55e2fb42d
--- /dev/null
+++ b/octopusdeploy_framework/schemas/tag.go
@@ -0,0 +1,92 @@
+package schemas
+
+import (
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tagsets"
+ "github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
+ resourceSchema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+const TagResourceName = "tag"
+
+func GetTagResourceSchema() resourceSchema.Schema {
+ return resourceSchema.Schema{
+ Description: "This resource manages tags in Octopus Deploy.",
+ Attributes: map[string]resourceSchema.Attribute{
+ "id": util.ResourceString().
+ Computed().
+ Description("The ID of this resource.").
+ Build(),
+ "canonical_tag_name": util.ResourceString().
+ Computed().
+ Description("The canonical name of the tag.").
+ Build(),
+ "color": util.ResourceString().
+ Required().
+ Description("The color of the tag.").
+ Build(),
+ "description": util.ResourceString().
+ Optional().
+ Description("The description of the tag.").
+ Default("").
+ Computed().
+ Build(),
+ "name": util.ResourceString().
+ Required().
+ Description("The name of the tag.").
+ Build(),
+ "sort_order": util.ResourceInt64().
+ Optional().
+ Computed().
+ Description("The sort order of the tag.").
+ Build(),
+ "tag_set_id": util.ResourceString().
+ Required().
+ Description("The ID of the associated tag set.").
+ Build(),
+ "tag_set_space_id": util.ResourceString().
+ Optional().
+ Computed().
+ Description("The Space ID of the associated tag set. Required if the tag set is not in the same space as what is configured on the provider.").
+ Build(),
+ },
+ }
+}
+
+type TagResourceModel struct {
+ CanonicalTagName types.String `tfsdk:"canonical_tag_name"`
+ Color types.String `tfsdk:"color"`
+ Description types.String `tfsdk:"description"`
+ Name types.String `tfsdk:"name"`
+ SortOrder types.Int64 `tfsdk:"sort_order"`
+ TagSetId types.String `tfsdk:"tag_set_id"`
+ TagSetSpaceId types.String `tfsdk:"tag_set_space_id"`
+ ResourceModel
+}
+
+func MapFromStateToTag(data *TagResourceModel) *tagsets.Tag {
+ color := data.Color.ValueString()
+ name := data.Name.ValueString()
+
+ tag := tagsets.NewTag(name, color)
+ tag.ID = data.ID.ValueString()
+ tag.CanonicalTagName = data.CanonicalTagName.ValueString()
+ tag.Description = data.Description.ValueString()
+ tag.SortOrder = int(data.SortOrder.ValueInt64())
+
+ return tag
+}
+
+func MapFromTagToState(data *TagResourceModel, tag *tagsets.Tag, tagSet *tagsets.TagSet) {
+
+ data.CanonicalTagName = types.StringValue(tag.CanonicalTagName)
+ data.Color = types.StringValue(tag.Color)
+ data.Description = types.StringValue(tag.Description)
+ data.Name = types.StringValue(tag.Name)
+ data.SortOrder = types.Int64Value(int64(tag.SortOrder))
+
+ data.TagSetId = types.StringValue(tagSet.ID)
+ data.TagSetSpaceId = types.StringValue(tagSet.SpaceID)
+
+ data.ID = types.StringValue(tag.ID)
+}
From 131a5650be784c10020f8178a2ce77ef07be4454 Mon Sep 17 00:00:00 2001
From: Isaac Calligeros <101079287+IsaacCalligeros95@users.noreply.github.com>
Date: Tue, 13 Aug 2024 10:28:02 +0930
Subject: [PATCH 18/28] Add import state to migrated resources (#731)
---
.../resource_artifactory_generic_feed.go | 7 +++++++
.../resource_aws_elastic_container_registry.go | 7 +++++++
.../resource_docker_container_registry.go | 7 +++++++
octopusdeploy_framework/resource_github_repository_feed.go | 7 +++++++
octopusdeploy_framework/resource_helm_feed.go | 7 +++++++
octopusdeploy_framework/resource_library_variable_set.go | 7 +++++++
octopusdeploy_framework/resource_maven_feed.go | 7 +++++++
octopusdeploy_framework/resource_nuget_feed.go | 7 +++++++
octopusdeploy_framework/resource_script_module.go | 7 +++++++
octopusdeploy_framework/resource_tenant.go | 7 +++++++
10 files changed, 70 insertions(+)
diff --git a/octopusdeploy_framework/resource_artifactory_generic_feed.go b/octopusdeploy_framework/resource_artifactory_generic_feed.go
index b6c765d51..857043fd9 100644
--- a/octopusdeploy_framework/resource_artifactory_generic_feed.go
+++ b/octopusdeploy_framework/resource_artifactory_generic_feed.go
@@ -3,6 +3,7 @@ package octopusdeploy_framework
import (
"context"
"fmt"
+ "github.com/hashicorp/terraform-plugin-framework/path"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
@@ -25,6 +26,8 @@ func NewArtifactoryGenericFeedResource() resource.Resource {
return &artifactoryGenericFeedTypeResource{}
}
+var _ resource.ResourceWithImportState = &artifactoryGenericFeedTypeResource{}
+
func (r *artifactoryGenericFeedTypeResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = util.GetTypeName("artifactory_generic_feed")
}
@@ -185,3 +188,7 @@ func updateDataFromArtifactoryGenericFeed(data *schemas.ArtifactoryGenericFeedTy
data.PackageAcquisitionLocationOptions = packageAcquisitionLocationOptionsListValue
data.ID = types.StringValue(feed.GetID())
}
+
+func (*artifactoryGenericFeedTypeResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
+}
diff --git a/octopusdeploy_framework/resource_aws_elastic_container_registry.go b/octopusdeploy_framework/resource_aws_elastic_container_registry.go
index daeb36f31..b54adf9d7 100644
--- a/octopusdeploy_framework/resource_aws_elastic_container_registry.go
+++ b/octopusdeploy_framework/resource_aws_elastic_container_registry.go
@@ -3,6 +3,7 @@ package octopusdeploy_framework
import (
"context"
"fmt"
+ "github.com/hashicorp/terraform-plugin-framework/path"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/feeds"
@@ -20,6 +21,8 @@ type awsElasticContainerRegistryFeedTypeResource struct {
*Config
}
+var _ resource.ResourceWithImportState = &awsElasticContainerRegistryFeedTypeResource{}
+
func NewAwsElasticContainerRegistryFeedResource() resource.Resource {
return &awsElasticContainerRegistryFeedTypeResource{}
}
@@ -175,3 +178,7 @@ func updateDataFromAwsElasticContainerRegistryFeed(data *schemas.AwsElasticConta
data.PackageAcquisitionLocationOptions = packageAcquisitionLocationOptionsListValue
data.ID = types.StringValue(feed.GetID())
}
+
+func (*awsElasticContainerRegistryFeedTypeResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
+}
diff --git a/octopusdeploy_framework/resource_docker_container_registry.go b/octopusdeploy_framework/resource_docker_container_registry.go
index 0b246633e..97131399b 100644
--- a/octopusdeploy_framework/resource_docker_container_registry.go
+++ b/octopusdeploy_framework/resource_docker_container_registry.go
@@ -3,6 +3,7 @@ package octopusdeploy_framework
import (
"context"
"fmt"
+ "github.com/hashicorp/terraform-plugin-framework/path"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/feeds"
@@ -24,6 +25,8 @@ func NewDockerContainerRegistryFeedResource() resource.Resource {
return &dockerContainerRegistryFeedTypeResource{}
}
+var _ resource.ResourceWithImportState = &dockerContainerRegistryFeedTypeResource{}
+
func (r *dockerContainerRegistryFeedTypeResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = util.GetTypeName("docker_container_registry")
}
@@ -185,3 +188,7 @@ func updateDataFromDockerContainerRegistryFeed(data *schemas.DockerContainerRegi
data.PackageAcquisitionLocationOptions = packageAcquisitionLocationOptionsListValue
data.ID = types.StringValue(feed.ID)
}
+
+func (*dockerContainerRegistryFeedTypeResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
+}
diff --git a/octopusdeploy_framework/resource_github_repository_feed.go b/octopusdeploy_framework/resource_github_repository_feed.go
index 6b6aabb63..ae4f95977 100644
--- a/octopusdeploy_framework/resource_github_repository_feed.go
+++ b/octopusdeploy_framework/resource_github_repository_feed.go
@@ -3,6 +3,7 @@ package octopusdeploy_framework
import (
"context"
"fmt"
+ "github.com/hashicorp/terraform-plugin-framework/path"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/feeds"
@@ -24,6 +25,8 @@ func NewGitHubRepositoryFeedResource() resource.Resource {
return &githubRepositoryFeedTypeResource{}
}
+var _ resource.ResourceWithImportState = &githubRepositoryFeedTypeResource{}
+
func (r *githubRepositoryFeedTypeResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = util.GetTypeName("github_repository_feed")
}
@@ -183,3 +186,7 @@ func updateDataFromGitHubRepositoryFeed(data *schemas.GitHubRepositoryFeedTypeRe
data.PackageAcquisitionLocationOptions = packageAcquisitionLocationOptionsListValue
data.ID = types.StringValue(feed.GetID())
}
+
+func (*githubRepositoryFeedTypeResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
+}
diff --git a/octopusdeploy_framework/resource_helm_feed.go b/octopusdeploy_framework/resource_helm_feed.go
index dd7667eff..c8204a63e 100644
--- a/octopusdeploy_framework/resource_helm_feed.go
+++ b/octopusdeploy_framework/resource_helm_feed.go
@@ -3,6 +3,7 @@ package octopusdeploy_framework
import (
"context"
"fmt"
+ "github.com/hashicorp/terraform-plugin-framework/path"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
@@ -25,6 +26,8 @@ func NewHelmFeedResource() resource.Resource {
return &helmFeedTypeResource{}
}
+var _ resource.ResourceWithImportState = &helmFeedTypeResource{}
+
func (r *helmFeedTypeResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = util.GetTypeName("helm_feed")
}
@@ -179,3 +182,7 @@ func updateDataFromHelmFeed(data *schemas.HelmFeedTypeResourceModel, spaceId str
data.PackageAcquisitionLocationOptions = packageAcquisitionLocationOptionsListValue
data.ID = types.StringValue(feed.GetID())
}
+
+func (*helmFeedTypeResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
+}
diff --git a/octopusdeploy_framework/resource_library_variable_set.go b/octopusdeploy_framework/resource_library_variable_set.go
index cdf616aa0..795482862 100644
--- a/octopusdeploy_framework/resource_library_variable_set.go
+++ b/octopusdeploy_framework/resource_library_variable_set.go
@@ -3,6 +3,7 @@ package octopusdeploy_framework
import (
"context"
"fmt"
+ "github.com/hashicorp/terraform-plugin-framework/path"
"log"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/libraryvariablesets"
@@ -21,6 +22,8 @@ func NewLibraryVariableSetFeedResource() resource.Resource {
return &libraryVariableSetFeedTypeResource{}
}
+var _ resource.ResourceWithImportState = &libraryVariableSetFeedTypeResource{}
+
func (r *libraryVariableSetFeedTypeResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = util.GetTypeName("library_variable_set")
}
@@ -109,3 +112,7 @@ func (r *libraryVariableSetFeedTypeResource) Delete(ctx context.Context, req res
return
}
}
+
+func (*libraryVariableSetFeedTypeResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
+}
diff --git a/octopusdeploy_framework/resource_maven_feed.go b/octopusdeploy_framework/resource_maven_feed.go
index 55346da60..e260debcd 100644
--- a/octopusdeploy_framework/resource_maven_feed.go
+++ b/octopusdeploy_framework/resource_maven_feed.go
@@ -3,6 +3,7 @@ package octopusdeploy_framework
import (
"context"
"fmt"
+ "github.com/hashicorp/terraform-plugin-framework/path"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/feeds"
@@ -24,6 +25,8 @@ func NewMavenFeedResource() resource.Resource {
return &mavenFeedTypeResource{}
}
+var _ resource.ResourceWithImportState = &mavenFeedTypeResource{}
+
func (r *mavenFeedTypeResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = util.GetTypeName("maven_feed")
}
@@ -181,3 +184,7 @@ func updateDataFromMavenFeed(data *schemas.MavenFeedTypeResourceModel, spaceId s
data.PackageAcquisitionLocationOptions = packageAcquisitionLocationOptionsListValue
data.ID = types.StringValue(feed.ID)
}
+
+func (*mavenFeedTypeResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
+}
diff --git a/octopusdeploy_framework/resource_nuget_feed.go b/octopusdeploy_framework/resource_nuget_feed.go
index bf0576248..6a737e8b5 100644
--- a/octopusdeploy_framework/resource_nuget_feed.go
+++ b/octopusdeploy_framework/resource_nuget_feed.go
@@ -3,6 +3,7 @@ package octopusdeploy_framework
import (
"context"
"fmt"
+ "github.com/hashicorp/terraform-plugin-framework/path"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/feeds"
@@ -24,6 +25,8 @@ func NewNugetFeedResource() resource.Resource {
return &nugetFeedTypeResource{}
}
+var _ resource.ResourceWithImportState = &nugetFeedTypeResource{}
+
func (r *nugetFeedTypeResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = util.GetTypeName("nuget_feed")
}
@@ -184,3 +187,7 @@ func updateDataFromNugetFeed(data *schemas.NugetFeedTypeResourceModel, spaceId s
data.PackageAcquisitionLocationOptions = packageAcquisitionLocationOptionsListValue
data.ID = types.StringValue(feed.GetID())
}
+
+func (*nugetFeedTypeResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
+}
diff --git a/octopusdeploy_framework/resource_script_module.go b/octopusdeploy_framework/resource_script_module.go
index 8e0a1294f..bb2f5fcee 100644
--- a/octopusdeploy_framework/resource_script_module.go
+++ b/octopusdeploy_framework/resource_script_module.go
@@ -8,6 +8,7 @@ import (
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
+ "github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-log/tflog"
@@ -21,6 +22,8 @@ func NewScriptModuleResource() resource.Resource {
return &scriptModuleTypeResource{}
}
+var _ resource.ResourceWithImportState = &scriptModuleTypeResource{}
+
func (r *scriptModuleTypeResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = util.GetTypeName("script_module")
}
@@ -128,3 +131,7 @@ func (r *scriptModuleTypeResource) Delete(ctx context.Context, req resource.Dele
return
}
}
+
+func (*scriptModuleTypeResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
+}
diff --git a/octopusdeploy_framework/resource_tenant.go b/octopusdeploy_framework/resource_tenant.go
index 3fcde55b3..cc0b04b67 100644
--- a/octopusdeploy_framework/resource_tenant.go
+++ b/octopusdeploy_framework/resource_tenant.go
@@ -9,6 +9,7 @@ import (
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
"github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
@@ -24,6 +25,8 @@ func NewTenantResource() resource.Resource {
return &tenantTypeResource{}
}
+var _ resource.ResourceWithImportState = &tenantTypeResource{}
+
func (r *tenantTypeResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = util.GetTypeName("tenant")
}
@@ -172,3 +175,7 @@ func mapTenantToState(data *schemas.TenantModel, tenant *tenants.Tenant) {
sort.Strings(tenant.TenantTags)
data.TenantTags = util.Ternary(tenant.TenantTags != nil && len(tenant.TenantTags) > 0, util.FlattenStringList(tenant.TenantTags), types.ListValueMust(types.StringType, make([]attr.Value, 0)))
}
+
+func (*tenantTypeResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
+}
From 3fecf49b153e008c2f96e1e16ad04140425afe91 Mon Sep 17 00:00:00 2001
From: Henrik Andersson
Date: Mon, 12 Aug 2024 20:57:48 -0700
Subject: [PATCH 19/28] chore: return error if we can't find a space matching
the name provided (#728)
---
octopusdeploy_framework/datasource_space.go | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/octopusdeploy_framework/datasource_space.go b/octopusdeploy_framework/datasource_space.go
index 42077c6cd..c4ab13758 100644
--- a/octopusdeploy_framework/datasource_space.go
+++ b/octopusdeploy_framework/datasource_space.go
@@ -2,13 +2,15 @@ package octopusdeploy_framework
import (
"context"
+ "fmt"
+ "strings"
+
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/spaces"
"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/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
- "strings"
)
type spaceDataSource struct {
@@ -57,6 +59,10 @@ func (b *spaceDataSource) Read(ctx context.Context, req datasource.ReadRequest,
matchedSpace = spaceResult
}
}
+ if matchedSpace == nil {
+ resp.Diagnostics.AddError(fmt.Sprintf("unable to find space with name %s", data.Name.ValueString()), "")
+ return
+ }
mapSpaceToState(ctx, &data, matchedSpace)
From 1ba92e16c06d92bda364c53da644c7571ea3b975 Mon Sep 17 00:00:00 2001
From: Isaac Calligeros <101079287+IsaacCalligeros95@users.noreply.github.com>
Date: Tue, 13 Aug 2024 15:19:54 +0930
Subject: [PATCH 20/28] Add aws oidc account type (#733)
* Add aws oidc account type
* Update Schema queries
* Update aws oidc account type
* Update docs
---
docs/data-sources/accounts.md | 2 +-
examples/resources/octopusdeploy_account/resource.tf | 2 +-
octopusdeploy/schema_queries.go | 4 ++--
octopusdeploy/schema_utilities.go | 3 ++-
4 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/docs/data-sources/accounts.md b/docs/data-sources/accounts.md
index 90d281505..a108cfe1d 100644
--- a/docs/data-sources/accounts.md
+++ b/docs/data-sources/accounts.md
@@ -26,7 +26,7 @@ data "octopusdeploy_accounts" "example" {
### Optional
-- `account_type` (String) A filter to search by a list of account types. Valid account types are `AmazonWebServicesAccount`, `AmazonWebServicesRoleAccount`, `AzureServicePrincipal`, `AzureSubscription`, `None`, `SshKeyPair`, `Token`, or `UsernamePassword`.
+- `account_type` (String) A filter to search by a list of account types. Valid account types are `AmazonWebServicesAccount`, `AmazonWebServicesRoleAccount`, `AmazonWebServicesOidcAccount`, `AzureServicePrincipal`, `AzureSubscription`, `None`, `SshKeyPair`, `Token`, or `UsernamePassword`.
- `ids` (List of String) A filter to search by a list of IDs.
- `partial_name` (String) A filter to search by the partial match of a name.
- `skip` (Number) A filter to specify the number of items to skip in the response.
diff --git a/examples/resources/octopusdeploy_account/resource.tf b/examples/resources/octopusdeploy_account/resource.tf
index c18a1aba5..b44de2669 100644
--- a/examples/resources/octopusdeploy_account/resource.tf
+++ b/examples/resources/octopusdeploy_account/resource.tf
@@ -7,7 +7,7 @@ resource "octopusdeploy_account" "amazon_web_services_account" {
}
resource "octopusdeploy_account" "amazon_web_services_openid_connect_account" {
- account_type = "AwsOIDCAccount"
+ account_type = "AmazonWebServicesOidcAccount"
name = "AWS OIDC Account (OK to Delete)"
role_arn = "arn:aws:iam::sourceAccountId:roleroleName"
session_duration = "3600"
diff --git a/octopusdeploy/schema_queries.go b/octopusdeploy/schema_queries.go
index d7457abec..1322c9311 100644
--- a/octopusdeploy/schema_queries.go
+++ b/octopusdeploy/schema_queries.go
@@ -7,13 +7,13 @@ import (
func getQueryAccountType() *schema.Schema {
return &schema.Schema{
- Description: "A filter to search by a list of account types. Valid account types are `AmazonWebServicesAccount`, `AmazonWebServicesRoleAccount`, `AzureServicePrincipal`, `AzureSubscription`, `None`, `SshKeyPair`, `Token`, or `UsernamePassword`.",
+ Description: "A filter to search by a list of account types. Valid account types are `AmazonWebServicesAccount`, `AmazonWebServicesRoleAccount`, `AmazonWebServicesOidcAccount`, `AzureServicePrincipal`, `AzureSubscription`, `None`, `SshKeyPair`, `Token`, or `UsernamePassword`.",
Optional: true,
Type: schema.TypeString,
ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{
"AmazonWebServicesAccount",
"AmazonWebServicesRoleAccount",
- "AwsOIDCAccount",
+ "AmazonWebServicesOidcAccount",
"AzureServicePrincipal",
"AzureOIDC",
"AzureSubscription",
diff --git a/octopusdeploy/schema_utilities.go b/octopusdeploy/schema_utilities.go
index 50294a90c..1cb481661 100644
--- a/octopusdeploy/schema_utilities.go
+++ b/octopusdeploy/schema_utilities.go
@@ -9,7 +9,7 @@ import (
func getAccountTypeSchema(isRequired bool) *schema.Schema {
schema := &schema.Schema{
- Description: "Specifies the type of the account. Valid account types are `AmazonWebServicesAccount`, `AmazonWebServicesRoleAccount`, `AzureServicePrincipal`, `AzureOIDC`, `AzureSubscription`, `None`, `SshKeyPair`, `Token`, or `UsernamePassword`.",
+ Description: "Specifies the type of the account. Valid account types are `AmazonWebServicesAccount`, `AmazonWebServicesRoleAccount`, `AzureServicePrincipal`, `AzureOIDC`, `AzureSubscription`, `AmazonWebServicesOidcAccount`, `None`, `SshKeyPair`, `Token`, or `UsernamePassword`.",
ForceNew: true,
Type: schema.TypeString,
ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{
@@ -22,6 +22,7 @@ func getAccountTypeSchema(isRequired bool) *schema.Schema {
"SshKeyPair",
"Token",
"UsernamePassword",
+ "AmazonWebServicesOidcAccount",
}, false)),
}
From 4d50ff07c61f3fa447c77eaba7a889f025a417be Mon Sep 17 00:00:00 2001
From: Henrik Andersson
Date: Mon, 12 Aug 2024 22:53:18 -0700
Subject: [PATCH 21/28] chore: when specifying name in datasource we should
only return exact match (#726)
---
.../datasource_environments.go | 89 +++++--------------
.../datasource_environments_test.go | 78 ++++++++++++++--
.../schemas/environment.go | 69 ++++++++++++++
3 files changed, 160 insertions(+), 76 deletions(-)
diff --git a/octopusdeploy_framework/datasource_environments.go b/octopusdeploy_framework/datasource_environments.go
index 06b52a527..13f884e74 100644
--- a/octopusdeploy_framework/datasource_environments.go
+++ b/octopusdeploy_framework/datasource_environments.go
@@ -3,13 +3,12 @@ package octopusdeploy_framework
import (
"context"
"fmt"
+ "strings"
"time"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/environments"
- "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/extensions"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
- "github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
@@ -85,83 +84,35 @@ func (e *environmentDataSource) Read(ctx context.Context, req datasource.ReadReq
Take: util.GetNumber(data.Take),
}
- existingEnvironments, err := environments.Get(e.Client, data.SpaceID.ValueString(), query)
+ spaceID := util.Ternary(data.SpaceID.IsNull(), e.Client.GetSpaceID(), data.SpaceID.ValueString())
+
+ existingEnvironments, err := environments.Get(e.Client, spaceID, query)
if err != nil {
resp.Diagnostics.AddError("unable to load environments", err.Error())
return
}
- tflog.Debug(ctx, fmt.Sprintf("environments returned from API: %#v", existingEnvironments))
+
var mappedEnvironments []schemas.EnvironmentTypeResourceModel
- for _, environment := range existingEnvironments.Items {
- var env schemas.EnvironmentTypeResourceModel
- env.ID = types.StringValue(environment.ID)
- env.SpaceID = types.StringValue(environment.SpaceID)
- env.Slug = types.StringValue(environment.Slug)
- env.Name = types.StringValue(environment.Name)
- env.Description = types.StringValue(environment.Description)
- env.AllowDynamicInfrastructure = types.BoolValue(environment.AllowDynamicInfrastructure)
- env.SortOrder = types.Int64Value(int64(environment.SortOrder))
- env.UseGuidedFailure = types.BoolValue(environment.UseGuidedFailure)
- env.JiraExtensionSettings, _ = types.ListValueFrom(ctx, types.ObjectType{AttrTypes: schemas.JiraExtensionSettingsObjectType()}, []any{})
- env.JiraServiceManagementExtensionSettings, _ = types.ListValueFrom(ctx, types.ObjectType{AttrTypes: schemas.JiraServiceManagementExtensionSettingsObjectType()}, []any{})
- env.ServiceNowExtensionSettings, _ = types.ListValueFrom(ctx, types.ObjectType{AttrTypes: schemas.ServiceNowExtensionSettingsObjectType()}, []any{})
-
- for _, extensionSetting := range environment.ExtensionSettings {
- switch extensionSetting.ExtensionID() {
- case extensions.JiraExtensionID:
- if jiraExtension, ok := extensionSetting.(*environments.JiraExtensionSettings); ok {
- env.JiraExtensionSettings, _ = types.ListValueFrom(
- ctx,
- types.ObjectType{AttrTypes: schemas.JiraExtensionSettingsObjectType()},
- []any{schemas.MapJiraExtensionSettings(jiraExtension)},
- )
- }
- case extensions.JiraServiceManagementExtensionID:
- if jiraServiceManagementExtensionSettings, ok := extensionSetting.(*environments.JiraServiceManagementExtensionSettings); ok {
- env.JiraServiceManagementExtensionSettings, _ = types.ListValueFrom(
- ctx,
- types.ObjectType{AttrTypes: schemas.JiraServiceManagementExtensionSettingsObjectType()},
- []any{schemas.MapJiraServiceManagementExtensionSettings(jiraServiceManagementExtensionSettings)},
- )
- }
- case extensions.ServiceNowExtensionID:
- if serviceNowExtensionSettings, ok := extensionSetting.(*environments.ServiceNowExtensionSettings); ok {
- env.ServiceNowExtensionSettings, _ = types.ListValueFrom(
- ctx,
- types.ObjectType{AttrTypes: schemas.ServiceNowExtensionSettingsObjectType()},
- []any{schemas.MapServiceNowExtensionSettings(serviceNowExtensionSettings)},
- )
- }
+ if data.Name.IsNull() {
+ tflog.Debug(ctx, fmt.Sprintf("environments returned from API: %#v", existingEnvironments))
+ for _, environment := range existingEnvironments.Items {
+ mappedEnvironments = append(mappedEnvironments, schemas.MapFromEnvironment(ctx, environment))
+ }
+ } else { // if name has been specified, match by exact name rather than partial name as the API does
+ var matchedEnvironment *environments.Environment
+ tflog.Debug(ctx, fmt.Sprintf("matching environment by name: %s", data.Name))
+ for _, env := range existingEnvironments.Items {
+ if strings.EqualFold(env.Name, data.Name.ValueString()) {
+ matchedEnvironment = env
}
}
-
- mappedEnvironments = append(mappedEnvironments, env)
+ if matchedEnvironment != nil {
+ mappedEnvironments = append(mappedEnvironments, schemas.MapFromEnvironment(ctx, matchedEnvironment))
+ }
}
- data.Environments, _ = types.ListValueFrom(ctx, types.ObjectType{AttrTypes: environmentObjectType()}, mappedEnvironments)
+ data.Environments, _ = types.ListValueFrom(ctx, types.ObjectType{AttrTypes: schemas.EnvironmentObjectType()}, mappedEnvironments)
data.ID = types.StringValue("Environments " + time.Now().UTC().String())
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
-
-func environmentObjectType() map[string]attr.Type {
- return map[string]attr.Type{
- "id": types.StringType,
- "name": types.StringType,
- "slug": types.StringType,
- "description": types.StringType,
- schemas.EnvironmentAllowDynamicInfrastructure: types.BoolType,
- schemas.EnvironmentSortOrder: types.Int64Type,
- schemas.EnvironmentUseGuidedFailure: types.BoolType,
- "space_id": types.StringType,
- schemas.EnvironmentJiraExtensionSettings: types.ListType{
- ElemType: types.ObjectType{AttrTypes: schemas.JiraExtensionSettingsObjectType()},
- },
- schemas.EnvironmentJiraServiceManagementExtensionSettings: types.ListType{
- ElemType: types.ObjectType{AttrTypes: schemas.JiraServiceManagementExtensionSettingsObjectType()},
- },
- schemas.EnvironmentServiceNowExtensionSettings: types.ListType{
- ElemType: types.ObjectType{AttrTypes: schemas.ServiceNowExtensionSettingsObjectType()},
- },
- }
-}
diff --git a/octopusdeploy_framework/datasource_environments_test.go b/octopusdeploy_framework/datasource_environments_test.go
index 3ca2c90ac..7195d8aa6 100644
--- a/octopusdeploy_framework/datasource_environments_test.go
+++ b/octopusdeploy_framework/datasource_environments_test.go
@@ -11,8 +11,14 @@ import (
)
func TestAccDataSourceEnvironments(t *testing.T) {
- localName := acctest.RandStringFromCharSet(50, acctest.CharSetAlpha)
+ localName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
prefix := fmt.Sprintf("data.octopusdeploy_environments.%s", localName)
+
+ spaceName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
+
+ environmentLocalName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
+ environmentName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
+
take := 10
resource.Test(t, resource.TestCase{
@@ -23,13 +29,34 @@ func TestAccDataSourceEnvironments(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
testAccCheckEnvironmentsDataSourceID(prefix),
),
- Config: testAccDataSourceEnvironmentsConfig(localName, take),
+ Config: testAccDataSourceEnvironmentsEmpty(localName),
},
{
Check: resource.ComposeTestCheckFunc(
testAccCheckEnvironmentsDataSourceID(prefix),
+ resource.TestCheckResourceAttr(prefix, "environments.#", "3"),
+ ),
+ Config: fmt.Sprintf(`%s
+
+ %s`,
+ createTestAccDataSourceEnvironmentsConfig(spaceName, environmentLocalName, environmentName),
+ testAccDataSourceEnvironmentsConfig(localName, take, spaceName, environmentLocalName),
+ ),
+ },
+ {
+ Check: resource.ComposeTestCheckFunc(
+ testAccCheckEnvironmentsDataSourceID(prefix),
+ resource.TestCheckResourceAttr(prefix, "name", environmentName),
+ resource.TestCheckResourceAttr(prefix, "environments.#", "1"),
+ resource.TestCheckResourceAttrSet(prefix, "environments.0.id"),
+ resource.TestCheckResourceAttr(prefix, "environments.0.name", environmentName),
+ ),
+ Config: fmt.Sprintf(`%s
+
+ %s`,
+ createTestAccDataSourceEnvironmentsConfig(spaceName, environmentLocalName, environmentName),
+ testAccDataSourceEnvironmentByNameConfig(localName, environmentName, spaceName, environmentLocalName),
),
- Config: testAccDataSourceEnvironmentsEmpty(localName),
},
},
})
@@ -50,12 +77,49 @@ func testAccCheckEnvironmentsDataSourceID(n string) resource.TestCheckFunc {
}
}
-func testAccDataSourceEnvironmentsConfig(localName string, take int) string {
- return fmt.Sprintf(`data "octopusdeploy_environments" "%s" {
- take = %v
- }`, localName, take)
+func createTestAccDataSourceEnvironmentsConfig(spaceName string, localName string, name string) string {
+ return fmt.Sprintf(`
+ resource "octopusdeploy_space" "%[1]s" {
+ name = "%[1]s"
+ is_default = false
+ is_task_queue_stopped = false
+ description = "Test space for environments datasource"
+ space_managers_teams = ["teams-administrators"]
+ }
+
+ resource "octopusdeploy_environment" "%[2]s" {
+ name = "%[3]s"
+ space_id = octopusdeploy_space.%[1]s.id
+ }
+
+ resource "octopusdeploy_environment" "%[2]s-1" {
+ name = "%[3]s-1"
+ space_id = octopusdeploy_space.%[1]s.id
+ }
+
+ resource "octopusdeploy_environment" "%[2]s-2" {
+ name = "%[3]s-2"
+ space_id = octopusdeploy_space.%[1]s.id
+ }
+ `, spaceName, localName, name)
}
func testAccDataSourceEnvironmentsEmpty(localName string) string {
return fmt.Sprintf(`data "octopusdeploy_environments" "%s" {}`, localName)
}
+
+func testAccDataSourceEnvironmentsConfig(localName string, take int, spaceName string, environmentLocalName string) string {
+ return fmt.Sprintf(`data "octopusdeploy_environments" "%s" {
+ take = %v
+ space_id = octopusdeploy_space.%s.id
+ depends_on = [octopusdeploy_environment.%s, octopusdeploy_environment.%[4]s-1, octopusdeploy_environment.%[4]s-2]
+ }`, localName, take, spaceName, environmentLocalName)
+}
+
+func testAccDataSourceEnvironmentByNameConfig(localName string, name string, spaceName string, environmentLocalName string) string {
+ return fmt.Sprintf(`data "octopusdeploy_environments" "%s" {
+ name = "%s"
+ space_id = octopusdeploy_space.%s.id
+ depends_on = [octopusdeploy_environment.%s, octopusdeploy_environment.%[4]s-1, octopusdeploy_environment.%[4]s-2]
+ }`, localName, name, spaceName, environmentLocalName)
+}
diff --git a/octopusdeploy_framework/schemas/environment.go b/octopusdeploy_framework/schemas/environment.go
index ed57bba65..3090f7cc1 100644
--- a/octopusdeploy_framework/schemas/environment.go
+++ b/octopusdeploy_framework/schemas/environment.go
@@ -1,10 +1,12 @@
package schemas
import (
+ "context"
"fmt"
"strings"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/environments"
+ "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/extensions"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
@@ -50,6 +52,28 @@ var jiraEnvironmentTypes = []string{
jiraEnvironmentTypeNames.Unmapped,
}
+func EnvironmentObjectType() map[string]attr.Type {
+ return map[string]attr.Type{
+ "id": types.StringType,
+ "name": types.StringType,
+ "slug": types.StringType,
+ "description": types.StringType,
+ EnvironmentAllowDynamicInfrastructure: types.BoolType,
+ EnvironmentSortOrder: types.Int64Type,
+ EnvironmentUseGuidedFailure: types.BoolType,
+ "space_id": types.StringType,
+ EnvironmentJiraExtensionSettings: types.ListType{
+ ElemType: types.ObjectType{AttrTypes: JiraExtensionSettingsObjectType()},
+ },
+ EnvironmentJiraServiceManagementExtensionSettings: types.ListType{
+ ElemType: types.ObjectType{AttrTypes: JiraServiceManagementExtensionSettingsObjectType()},
+ },
+ EnvironmentServiceNowExtensionSettings: types.ListType{
+ ElemType: types.ObjectType{AttrTypes: ServiceNowExtensionSettingsObjectType()},
+ },
+ }
+}
+
func GetEnvironmentDatasourceSchema() map[string]datasourceSchema.Attribute {
return map[string]datasourceSchema.Attribute{
"id": GetIdDatasourceSchema(true),
@@ -201,6 +225,51 @@ func MapServiceNowExtensionSettings(serviceNowExtensionSettings *environments.Se
})
}
+func MapFromEnvironment(ctx context.Context, environment *environments.Environment) EnvironmentTypeResourceModel {
+ var env EnvironmentTypeResourceModel
+ env.ID = types.StringValue(environment.ID)
+ env.SpaceID = types.StringValue(environment.SpaceID)
+ env.Slug = types.StringValue(environment.Slug)
+ env.Name = types.StringValue(environment.Name)
+ env.Description = types.StringValue(environment.Description)
+ env.AllowDynamicInfrastructure = types.BoolValue(environment.AllowDynamicInfrastructure)
+ env.SortOrder = types.Int64Value(int64(environment.SortOrder))
+ env.UseGuidedFailure = types.BoolValue(environment.UseGuidedFailure)
+ env.JiraExtensionSettings, _ = types.ListValueFrom(ctx, types.ObjectType{AttrTypes: JiraExtensionSettingsObjectType()}, []any{})
+ env.JiraServiceManagementExtensionSettings, _ = types.ListValueFrom(ctx, types.ObjectType{AttrTypes: JiraServiceManagementExtensionSettingsObjectType()}, []any{})
+ env.ServiceNowExtensionSettings, _ = types.ListValueFrom(ctx, types.ObjectType{AttrTypes: ServiceNowExtensionSettingsObjectType()}, []any{})
+
+ for _, extensionSetting := range environment.ExtensionSettings {
+ switch extensionSetting.ExtensionID() {
+ case extensions.JiraExtensionID:
+ if jiraExtension, ok := extensionSetting.(*environments.JiraExtensionSettings); ok {
+ env.JiraExtensionSettings, _ = types.ListValueFrom(
+ ctx,
+ types.ObjectType{AttrTypes: JiraExtensionSettingsObjectType()},
+ []any{MapJiraExtensionSettings(jiraExtension)},
+ )
+ }
+ case extensions.JiraServiceManagementExtensionID:
+ if jiraServiceManagementExtensionSettings, ok := extensionSetting.(*environments.JiraServiceManagementExtensionSettings); ok {
+ env.JiraServiceManagementExtensionSettings, _ = types.ListValueFrom(
+ ctx,
+ types.ObjectType{AttrTypes: JiraServiceManagementExtensionSettingsObjectType()},
+ []any{MapJiraServiceManagementExtensionSettings(jiraServiceManagementExtensionSettings)},
+ )
+ }
+ case extensions.ServiceNowExtensionID:
+ if serviceNowExtensionSettings, ok := extensionSetting.(*environments.ServiceNowExtensionSettings); ok {
+ env.ServiceNowExtensionSettings, _ = types.ListValueFrom(
+ ctx,
+ types.ObjectType{AttrTypes: ServiceNowExtensionSettingsObjectType()},
+ []any{MapServiceNowExtensionSettings(serviceNowExtensionSettings)},
+ )
+ }
+ }
+ }
+ return env
+}
+
type EnvironmentTypeResourceModel struct {
Slug types.String `tfsdk:"slug"`
Name types.String `tfsdk:"name"`
From f11f6f90c1edd9d5da5c4149e1299bf42e6db79b Mon Sep 17 00:00:00 2001
From: Huy Nguyen <162080607+HuyPhanNguyen@users.noreply.github.com>
Date: Tue, 13 Aug 2024 16:30:06 +1000
Subject: [PATCH 22/28] chore: Fix some unwanted change in project (#732)
* Fix some unwanted change in project
* remove default
---
octopusdeploy_framework/schemas/project.go | 50 ++++++++++++++--------
1 file changed, 31 insertions(+), 19 deletions(-)
diff --git a/octopusdeploy_framework/schemas/project.go b/octopusdeploy_framework/schemas/project.go
index 814710bda..3b02bd3c8 100644
--- a/octopusdeploy_framework/schemas/project.go
+++ b/octopusdeploy_framework/schemas/project.go
@@ -4,6 +4,11 @@ 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/resource/schema/boolplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
)
@@ -19,23 +24,23 @@ func GetProjectResourceSchema() resourceSchema.Schema {
"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(),
+ "auto_create_release": util.ResourceBool().Optional().Computed().PlanModifiers(boolplanmodifier.UseStateForUnknown()).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(),
+ "default_guided_failure_mode": util.ResourceString().Optional().Computed().PlanModifiers(stringplanmodifier.UseStateForUnknown()).Build(),
+ "default_to_skip_if_already_installed": util.ResourceBool().Optional().Computed().PlanModifiers(boolplanmodifier.UseStateForUnknown()).Build(),
+ "deployment_changes_template": util.ResourceString().Optional().Computed().PlanModifiers(stringplanmodifier.UseStateForUnknown()).Build(),
+ "discrete_channel_release": util.ResourceBool().Optional().Computed().PlanModifiers(boolplanmodifier.UseStateForUnknown()).Description("Treats releases of different channels to the same environment as a separate deployment dimension").Build(),
+ "is_disabled": util.ResourceBool().Optional().Computed().PlanModifiers(boolplanmodifier.UseStateForUnknown()).Build(),
+ "is_discrete_channel_release": util.ResourceBool().Optional().Computed().PlanModifiers(boolplanmodifier.UseStateForUnknown()).Description("Treats releases of different channels to the same environment as a separate deployment dimension").Build(),
+ "is_version_controlled": util.ResourceBool().Optional().Computed().PlanModifiers(boolplanmodifier.UseStateForUnknown()).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(),
+ "tenanted_deployment_participation": util.ResourceString().Optional().Computed().PlanModifiers(stringplanmodifier.UseStateForUnknown()).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().PlanModifiers(listplanmodifier.UseStateForUnknown()).Description("The list of included library variable set IDs.").Build(),
+ "release_notes_template": util.ResourceString().Optional().Computed().PlanModifiers(stringplanmodifier.UseStateForUnknown()).Build(),
+ "slug": util.ResourceString().Optional().Computed().PlanModifiers(stringplanmodifier.UseStateForUnknown()).Description("A human-readable, unique identifier, used to identify a project.").Build(),
+ "deployment_process_id": util.ResourceString().Computed().PlanModifiers(stringplanmodifier.UseStateForUnknown()).Build(),
+ "variable_set_id": util.ResourceString().Computed().PlanModifiers(stringplanmodifier.UseStateForUnknown()).Build(),
},
Blocks: map[string]resourceSchema.Block{
// This is correct object that return from api for project object not a list string.
@@ -50,13 +55,19 @@ func GetProjectResourceSchema() resourceSchema.Schema {
},
"connectivity_policy": resourceSchema.ListNestedBlock{
NestedObject: resourceSchema.NestedBlockObject{
+ PlanModifiers: []planmodifier.Object{
+ objectplanmodifier.UseStateForUnknown(),
+ },
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(),
+ "allow_deployments_to_no_targets": util.ResourceBool().Optional().Computed().Default(false).PlanModifiers(boolplanmodifier.UseStateForUnknown()).Build(),
+ "exclude_unhealthy_targets": util.ResourceBool().Optional().Computed().Default(false).PlanModifiers(boolplanmodifier.UseStateForUnknown()).Build(),
+ "skip_machine_behavior": util.ResourceString().Optional().Computed().Default("None").PlanModifiers(stringplanmodifier.UseStateForUnknown()).Build(),
+ "target_roles": util.ResourceList(types.StringType).Optional().Computed().PlanModifiers(listplanmodifier.UseStateForUnknown()).Build(),
},
},
+ PlanModifiers: []planmodifier.List{
+ listplanmodifier.UseStateForUnknown(),
+ },
},
"git_anonymous_persistence_settings": resourceSchema.ListNestedBlock{
NestedObject: resourceSchema.NestedBlockObject{
@@ -133,9 +144,10 @@ func GetProjectResourceSchema() resourceSchema.Schema {
},
"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(),
+ "template": util.ResourceString().Optional().Computed().Build(),
},
Blocks: map[string]resourceSchema.Block{
"donor_package": resourceSchema.ListNestedBlock{
From c32f73ff5a4ec7d933860f25bc4b15f2dc4bf5b2 Mon Sep 17 00:00:00 2001
From: Huy Nguyen <162080607+HuyPhanNguyen@users.noreply.github.com>
Date: Tue, 13 Aug 2024 17:19:16 +1000
Subject: [PATCH 23/28] fix git credential null description issue (#735)
---
octopusdeploy_framework/schemas/gitCredential.go | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/octopusdeploy_framework/schemas/gitCredential.go b/octopusdeploy_framework/schemas/gitCredential.go
index c4b0b4d6f..b11c3fd5a 100644
--- a/octopusdeploy_framework/schemas/gitCredential.go
+++ b/octopusdeploy_framework/schemas/gitCredential.go
@@ -5,6 +5,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
datasourceSchema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
resourceSchema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
)
const (
@@ -16,21 +17,23 @@ func GetGitCredentialResourceSchema() resourceSchema.Schema {
return resourceSchema.Schema{
Description: "Manages a Git credential in Octopus Deploy.",
Attributes: map[string]resourceSchema.Attribute{
- "id": util.ResourceString().Optional().Computed().Description("The unique ID for this resource.").Build(),
- "space_id": util.ResourceString().Optional().Computed().Description("The space ID associated with this Git Credential.").Build(),
+ "id": util.ResourceString().Optional().Computed().PlanModifiers(stringplanmodifier.UseStateForUnknown()).Description("The unique ID for this resource.").Build(),
+ "space_id": util.ResourceString().Optional().Computed().PlanModifiers(stringplanmodifier.UseStateForUnknown()).Description("The space ID associated with this Git Credential.").Build(),
"name": util.ResourceString().Required().Description("The name of this Git Credential.").Build(),
- "description": util.ResourceString().Optional().Description("The description of this Git Credential.").Build(),
+ "description": util.ResourceString().Optional().Computed().Default("").Description("The description of this Git Credential.").Build(),
"type": util.ResourceString().
Optional().
Description("The Git credential authentication type.").
Build(),
"username": util.ResourceString().
Required().
+ PlanModifiers(stringplanmodifier.UseStateForUnknown()).
Description("The username for the Git credential.").
Validators(stringvalidator.LengthAtLeast(1)).
Build(),
"password": util.ResourceString().
Required().
+ PlanModifiers(stringplanmodifier.UseStateForUnknown()).
Sensitive().
Description("The password for the Git credential.").
Validators(stringvalidator.LengthAtLeast(1)).
From 8ef9e5f892f388a820aa4fe7907344a2c819e59c Mon Sep 17 00:00:00 2001
From: Ben Pearce
Date: Tue, 13 Aug 2024 17:21:21 +1000
Subject: [PATCH 24/28] chore: fix template ids for project (#734)
---
.../resource_project_expand.go | 31 ++++++++++---------
.../resource_project_test.go | 29 +++++++----------
.../resource_tenant_test.go | 2 +-
octopusdeploy_framework/schemas/project.go | 2 +-
4 files changed, 29 insertions(+), 35 deletions(-)
diff --git a/octopusdeploy_framework/resource_project_expand.go b/octopusdeploy_framework/resource_project_expand.go
index e0a3316da..08c9a71c3 100644
--- a/octopusdeploy_framework/resource_project_expand.go
+++ b/octopusdeploy_framework/resource_project_expand.go
@@ -10,6 +10,7 @@ import (
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
"net/url"
)
@@ -50,9 +51,9 @@ func expandProject(ctx context.Context, model projectResourceModel) *projects.Pr
var gitLibrarySettingsList []gitLibraryPersistenceSettingsModel
diags := model.GitLibraryPersistenceSettings.ElementsAs(ctx, &gitLibrarySettingsList, false)
if diags.HasError() {
- fmt.Printf("Error converting Git library persistence settings: %v\n", diags)
+ tflog.Error(ctx, fmt.Sprintf("Error converting Git library persistence settings: %v\n", diags))
} else {
- fmt.Printf("Number of Git library persistence settings: %d\n", len(gitLibrarySettingsList))
+ tflog.Debug(ctx, fmt.Sprintf("Number of Git library persistence settings: %d\n", len(gitLibrarySettingsList)))
if len(gitLibrarySettingsList) > 0 {
project.PersistenceSettings = expandGitLibraryPersistenceSettings(ctx, gitLibrarySettingsList[0])
project.IsVersionControlled = true
@@ -62,9 +63,9 @@ func expandProject(ctx context.Context, model projectResourceModel) *projects.Pr
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)
+ tflog.Error(ctx, fmt.Sprintf("Error converting Git username/password persistence settings: %v\n", diags))
} else {
- fmt.Printf("Number of Git username/password persistence settings: %d\n", len(gitUsernamePasswordSettingsList))
+ tflog.Debug(ctx, fmt.Sprintf("Number of Git username/password persistence settings: %d\n", len(gitUsernamePasswordSettingsList)))
if len(gitUsernamePasswordSettingsList) > 0 {
project.PersistenceSettings = expandGitUsernamePasswordPersistenceSettings(ctx, gitUsernamePasswordSettingsList[0])
project.IsVersionControlled = true
@@ -74,9 +75,9 @@ func expandProject(ctx context.Context, model projectResourceModel) *projects.Pr
var gitAnonymousSettingsList []gitAnonymousPersistenceSettingsModel
diags := model.GitAnonymousPersistenceSettings.ElementsAs(ctx, &gitAnonymousSettingsList, false)
if diags.HasError() {
- fmt.Printf("Error converting Git anonymous persistence settings: %v\n", diags)
+ tflog.Error(ctx, fmt.Sprintf("Error converting Git anonymous persistence settings: %v\n", diags))
} else {
- fmt.Printf("Number of Git anonymous persistence settings: %d\n", len(gitAnonymousSettingsList))
+ tflog.Debug(ctx, fmt.Sprintf("Number of Git anonymous persistence settings: %d\n", len(gitAnonymousSettingsList)))
if len(gitAnonymousSettingsList) > 0 {
project.PersistenceSettings = expandGitAnonymousPersistenceSettings(ctx, gitAnonymousSettingsList[0])
project.IsVersionControlled = true
@@ -116,13 +117,13 @@ func expandProject(ctx context.Context, model projectResourceModel) *projects.Pr
var templates []templateModel
diags := model.Template.ElementsAs(ctx, &templates, false)
if diags.HasError() {
- fmt.Printf("Error converting templates: %v\n", diags)
+ tflog.Error(ctx, fmt.Sprintf("Error converting templates: %v\n", diags))
} else {
- fmt.Printf("Number of templates: %d\n", len(templates))
+ tflog.Info(ctx, fmt.Sprintf("Number of templates: %d\n", len(templates)))
project.Templates = expandTemplates(templates)
}
} else {
- fmt.Println("Template is null")
+ tflog.Debug(ctx, "Template is null")
project.Templates = []actiontemplates.ActionTemplateParameter{}
}
@@ -138,7 +139,7 @@ func expandProject(ctx context.Context, model projectResourceModel) *projects.Pr
}
func expandGitLibraryPersistenceSettings(ctx context.Context, model gitLibraryPersistenceSettingsModel) projects.GitPersistenceSettings {
- url, _ := url.Parse(model.URL.ValueString())
+ gitUrl, _ := url.Parse(model.URL.ValueString())
var protectedBranches []string
model.ProtectedBranches.ElementsAs(ctx, &protectedBranches, false)
@@ -149,12 +150,12 @@ func expandGitLibraryPersistenceSettings(ctx context.Context, model gitLibraryPe
},
model.DefaultBranch.ValueString(),
protectedBranches,
- url,
+ gitUrl,
)
}
func expandGitUsernamePasswordPersistenceSettings(ctx context.Context, model gitUsernamePasswordPersistenceSettingsModel) projects.GitPersistenceSettings {
- url, _ := url.Parse(model.URL.ValueString())
+ gitUrl, _ := url.Parse(model.URL.ValueString())
var protectedBranches []string
model.ProtectedBranches.ElementsAs(ctx, &protectedBranches, false)
@@ -168,12 +169,12 @@ func expandGitUsernamePasswordPersistenceSettings(ctx context.Context, model git
usernamePasswordCredential,
model.DefaultBranch.ValueString(),
protectedBranches,
- url,
+ gitUrl,
)
}
func expandGitAnonymousPersistenceSettings(ctx context.Context, model gitAnonymousPersistenceSettingsModel) projects.GitPersistenceSettings {
- url, _ := url.Parse(model.URL.ValueString())
+ gitUrl, _ := url.Parse(model.URL.ValueString())
var protectedBranches []string
model.ProtectedBranches.ElementsAs(ctx, &protectedBranches, false)
@@ -182,7 +183,7 @@ func expandGitAnonymousPersistenceSettings(ctx context.Context, model gitAnonymo
&credentials.Anonymous{},
model.DefaultBranch.ValueString(),
protectedBranches,
- url,
+ gitUrl,
)
}
diff --git a/octopusdeploy_framework/resource_project_test.go b/octopusdeploy_framework/resource_project_test.go
index 511e771e8..a9971f906 100644
--- a/octopusdeploy_framework/resource_project_test.go
+++ b/octopusdeploy_framework/resource_project_test.go
@@ -103,7 +103,7 @@ func TestAccProjectWithUpdate(t *testing.T) {
resource.TestCheckResourceAttr(prefix, "description", description),
resource.TestCheckResourceAttr(prefix, "name", name),
),
- Config: testAccProjectBasic(lifecycleLocalName, lifecycleName, projectGroupLocalName, projectGroupName, localName, name, description),
+ Config: testAccProjectBasic(lifecycleLocalName, lifecycleName, projectGroupLocalName, projectGroupName, localName, name, description, 2),
},
{
Check: resource.ComposeTestCheckFunc(
@@ -114,17 +114,22 @@ func TestAccProjectWithUpdate(t *testing.T) {
resource.TestCheckNoResourceAttr(prefix, "deployment_step.0.windows_service.1.step_name"),
resource.TestCheckNoResourceAttr(prefix, "deployment_step.0.iis_website.0.step_name"),
),
- Config: testAccProjectBasic(lifecycleLocalName, lifecycleName, projectGroupLocalName, projectGroupName, localName, name, description),
+ Config: testAccProjectBasic(lifecycleLocalName, lifecycleName, projectGroupLocalName, projectGroupName, localName, name, description, 3),
},
},
})
}
-func testAccProjectBasic(lifecycleLocalName string, lifecycleName string, projectGroupLocalName string, projectGroupName string, localName string, name string, description string) string {
+func testAccProjectBasic(lifecycleLocalName string, lifecycleName string, projectGroupLocalName string, projectGroupName string, localName string, name string, description string, templateCount int) string {
projectGroup := internaltest.NewProjectGroupTestOptions()
projectGroup.LocalName = projectGroupLocalName
projectGroup.Resource.Name = projectGroupName
+ var templates string
+ for i := 0; i < templateCount; i++ {
+ templates += fmt.Sprintf("\ntemplate {\n\t\t\t\tname = \"%d\"\n\t\t\t\tdisplay_settings = {\n\t\t\t\t\t\"Octopus.ControlType\": \"SingleLineText\"\n\t\t\t\t}\n\t\t\t}\n", i)
+ }
+
return fmt.Sprintf(testAccLifecycle(lifecycleLocalName, lifecycleName)+"\n"+
internaltest.ProjectGroupConfiguration(projectGroup)+"\n"+
`resource "octopusdeploy_project" "%s" {
@@ -133,20 +138,8 @@ func testAccProjectBasic(lifecycleLocalName string, lifecycleName string, projec
name = "%s"
project_group_id = octopusdeploy_project_group.%s.id
- template {
- name = "2"
- display_settings = {
- "Octopus.ControlType": "SingleLineText"
- }
- }
-
- template {
- name = "1"
- display_settings = {
- "Octopus.ControlType": "SingleLineText"
- }
- }
-
+ %s
+
versioning_strategy {
template = "#{Octopus.Version.LastMajor}.#{Octopus.Version.LastMinor}.#{Octopus.Version.LastPatch}.#{Octopus.Version.NextRevision}"
}
@@ -156,7 +149,7 @@ func testAccProjectBasic(lifecycleLocalName string, lifecycleName string, projec
skip_machine_behavior = "None"
}
- }`, localName, description, lifecycleLocalName, name, projectGroupLocalName)
+ }`, localName, description, lifecycleLocalName, name, projectGroupLocalName, templates)
}
func testAccProjectCheckDestroy(s *terraform.State) error {
diff --git a/octopusdeploy_framework/resource_tenant_test.go b/octopusdeploy_framework/resource_tenant_test.go
index cd5a28b72..3d99614c5 100644
--- a/octopusdeploy_framework/resource_tenant_test.go
+++ b/octopusdeploy_framework/resource_tenant_test.go
@@ -62,7 +62,7 @@ func testAccTenantBasic(lifecycleLocalName string, lifecycleName string, project
sortOrder := acctest.RandIntRange(0, 10)
useGuidedFailure := false
- return fmt.Sprintf(testAccProjectBasic(lifecycleLocalName, lifecycleName, projectGroupLocalName, projectGroupName, projectLocalName, projectName, projectDescription)+"\n"+
+ return fmt.Sprintf(testAccProjectBasic(lifecycleLocalName, lifecycleName, projectGroupLocalName, projectGroupName, projectLocalName, projectName, projectDescription, 2)+"\n"+
testAccEnvironment(environmentLocalName, environmentName, environmentDescription, allowDynamicInfrastructure, sortOrder, useGuidedFailure)+"\n"+`
resource "octopusdeploy_tenant" "%s" {
description = "%s"
diff --git a/octopusdeploy_framework/schemas/project.go b/octopusdeploy_framework/schemas/project.go
index 3b02bd3c8..6ab43ea08 100644
--- a/octopusdeploy_framework/schemas/project.go
+++ b/octopusdeploy_framework/schemas/project.go
@@ -129,7 +129,7 @@ func GetProjectResourceSchema() resourceSchema.Schema {
"template": resourceSchema.ListNestedBlock{
NestedObject: resourceSchema.NestedBlockObject{
Attributes: map[string]resourceSchema.Attribute{
- "id": util.ResourceString().Optional().Computed().Description("The ID of the template parameter.").Build(),
+ "id": util.ResourceString().Optional().Computed().PlanModifiers(stringplanmodifier.UseStateForUnknown()).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(),
From 2261c2a7b519bb1a52d4f99b9b53df6b8e91be43 Mon Sep 17 00:00:00 2001
From: Isaac Calligeros <101079287+IsaacCalligeros95@users.noreply.github.com>
Date: Tue, 13 Aug 2024 17:20:16 +0930
Subject: [PATCH 25/28] fix: Fix datasource feeds search not respecting name
(#736)
---
go.mod | 2 +-
go.sum | 6 ++----
octopusdeploy_framework/datasource_feeds.go | 1 +
3 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/go.mod b/go.mod
index f3ed22597..cd8a3a059 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module github.com/OctopusDeploy/terraform-provider-octopusdeploy
go 1.21
require (
- github.com/OctopusDeploy/go-octopusdeploy/v2 v2.49.1
+ github.com/OctopusDeploy/go-octopusdeploy/v2 v2.49.2
github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework v0.0.0-20240729041805-46db6fb717b4
github.com/google/uuid v1.6.0
github.com/hashicorp/go-cty v1.4.1-0.20200723130312-85980079f637
diff --git a/go.sum b/go.sum
index c0bdd145e..cfe020223 100644
--- a/go.sum
+++ b/go.sum
@@ -20,8 +20,8 @@ github.com/Microsoft/hcsshim v0.12.4 h1:Ev7YUMHAHoWNm+aDSPzc5W9s6E2jyL1szpVDJeZ/
github.com/Microsoft/hcsshim v0.12.4/go.mod h1:Iyl1WVpZzr+UkzjekHZbV8o5Z9ZkxNGx6CtY2Qg/JVQ=
github.com/OctopusDeploy/go-octodiff v1.0.0 h1:U+ORg6azniwwYo+O44giOw6TiD5USk8S4VDhOQ0Ven0=
github.com/OctopusDeploy/go-octodiff v1.0.0/go.mod h1:Mze0+EkOWTgTmi8++fyUc6r0aLZT7qD9gX+31t8MmIU=
-github.com/OctopusDeploy/go-octopusdeploy/v2 v2.49.1 h1:lzC3tcnfvC07Ilqn5J51qhOnW79kGD6nTIxCBOdmAe8=
-github.com/OctopusDeploy/go-octopusdeploy/v2 v2.49.1/go.mod h1:ggvOXzMnq+w0pLg6C9zdjz6YBaHfO3B3tqmmB7JQdaw=
+github.com/OctopusDeploy/go-octopusdeploy/v2 v2.49.2 h1:ENd1MyQbYIDiW1ZyXRUcZr4OQ0d8j47I5a6DOT9Ez4o=
+github.com/OctopusDeploy/go-octopusdeploy/v2 v2.49.2/go.mod h1:ggvOXzMnq+w0pLg6C9zdjz6YBaHfO3B3tqmmB7JQdaw=
github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework v0.0.0-20240729041805-46db6fb717b4 h1:QfbVf0bOIRMp/WHAWsuVDB7KHoWnRsGbvDuOf2ua7k4=
github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework v0.0.0-20240729041805-46db6fb717b4/go.mod h1:Oq9KbiRNDBB5jFmrwnrgLX0urIqR/1ptY18TzkqXm7M=
github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg=
@@ -164,8 +164,6 @@ github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7
github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A=
github.com/hashicorp/terraform-plugin-docs v0.13.0 h1:6e+VIWsVGb6jYJewfzq2ok2smPzZrt1Wlm9koLeKazY=
github.com/hashicorp/terraform-plugin-docs v0.13.0/go.mod h1:W0oCmHAjIlTHBbvtppWHe8fLfZ2BznQbuv8+UD8OucQ=
-github.com/hashicorp/terraform-plugin-framework v1.9.0 h1:caLcDoxiRucNi2hk8+j3kJwkKfvHznubyFsJMWfZqKU=
-github.com/hashicorp/terraform-plugin-framework v1.9.0/go.mod h1:qBXLDn69kM97NNVi/MQ9qgd1uWWsVftGSnygYG1tImM=
github.com/hashicorp/terraform-plugin-framework v1.11.0 h1:M7+9zBArexHFXDx/pKTxjE6n/2UCXY6b8FIq9ZYhwfE=
github.com/hashicorp/terraform-plugin-framework v1.11.0/go.mod h1:qBXLDn69kM97NNVi/MQ9qgd1uWWsVftGSnygYG1tImM=
github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 h1:HOjBuMbOEzl7snOdOoUfE2Jgeto6JOjLVQ39Ls2nksc=
diff --git a/octopusdeploy_framework/datasource_feeds.go b/octopusdeploy_framework/datasource_feeds.go
index d8c19a863..fad80df86 100644
--- a/octopusdeploy_framework/datasource_feeds.go
+++ b/octopusdeploy_framework/datasource_feeds.go
@@ -53,6 +53,7 @@ func (e *feedsDataSource) Read(ctx context.Context, req datasource.ReadRequest,
}
query := feeds.FeedsQuery{
+ Name: data.Name.ValueString(),
IDs: util.GetIds(data.IDs),
PartialName: data.PartialName.ValueString(),
Skip: util.GetNumber(data.Skip),
From 6a770fa102e38b9d9453573ca31b19f618435ca4 Mon Sep 17 00:00:00 2001
From: Henrik Andersson
Date: Wed, 14 Aug 2024 18:56:47 -0700
Subject: [PATCH 26/28] chore: remove plan modifiers from retention period
(#738)
---
.../schemas/runbook_retention_period.go | 9 ---------
1 file changed, 9 deletions(-)
diff --git a/octopusdeploy_framework/schemas/runbook_retention_period.go b/octopusdeploy_framework/schemas/runbook_retention_period.go
index d8bbdc8b6..efe7833d4 100644
--- a/octopusdeploy_framework/schemas/runbook_retention_period.go
+++ b/octopusdeploy_framework/schemas/runbook_retention_period.go
@@ -10,9 +10,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/path"
resourceSchema "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/boolplanmodifier"
- "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
- "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
)
@@ -41,9 +38,6 @@ func getRunbookRetentionPeriodSchema() map[string]resourceSchema.Attribute {
Validators: []validator.Int64{
int64validator.AtLeast(0),
},
- PlanModifiers: []planmodifier.Int64{
- int64planmodifier.UseStateForUnknown(),
- },
},
runbookRetentionPeriodSchemeAttributeNames.ShouldKeepForever: resourceSchema.BoolAttribute{
Description: "Indicates if items should never be deleted. The default value is `false`.",
@@ -53,9 +47,6 @@ func getRunbookRetentionPeriodSchema() map[string]resourceSchema.Attribute {
Validators: []validator.Bool{
boolvalidator.ConflictsWith(path.MatchRelative().AtParent().AtName(runbookRetentionPeriodSchemeAttributeNames.QuantityToKeep)),
},
- PlanModifiers: []planmodifier.Bool{
- boolplanmodifier.UseStateForUnknown(),
- },
},
}
}
From 0678c067bd0154f31bf937cbacb5f9075375daaa Mon Sep 17 00:00:00 2001
From: Henrik Andersson
Date: Wed, 14 Aug 2024 21:20:19 -0700
Subject: [PATCH 27/28] chore: should pass in space_id from config as it'll
grab the space id from the client if nil (#737)
---
octopusdeploy_framework/datasource_environments.go | 4 +---
octopusdeploy_framework/resource_variable.go | 2 +-
2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/octopusdeploy_framework/datasource_environments.go b/octopusdeploy_framework/datasource_environments.go
index 13f884e74..da219658c 100644
--- a/octopusdeploy_framework/datasource_environments.go
+++ b/octopusdeploy_framework/datasource_environments.go
@@ -84,9 +84,7 @@ func (e *environmentDataSource) Read(ctx context.Context, req datasource.ReadReq
Take: util.GetNumber(data.Take),
}
- spaceID := util.Ternary(data.SpaceID.IsNull(), e.Client.GetSpaceID(), data.SpaceID.ValueString())
-
- existingEnvironments, err := environments.Get(e.Client, spaceID, query)
+ existingEnvironments, err := environments.Get(e.Client, data.SpaceID.ValueString(), query)
if err != nil {
resp.Diagnostics.AddError("unable to load environments", err.Error())
return
diff --git a/octopusdeploy_framework/resource_variable.go b/octopusdeploy_framework/resource_variable.go
index 78d48b5a2..af4e5b775 100644
--- a/octopusdeploy_framework/resource_variable.go
+++ b/octopusdeploy_framework/resource_variable.go
@@ -211,7 +211,7 @@ func (r *variableTypeResource) Delete(ctx context.Context, req resource.DeleteRe
return
}
- if _, err := variables.DeleteSingle(r.Config.Client, r.Config.SpaceID, variableOwnerID.ValueString(), data.ID.ValueString()); err != nil {
+ if _, err := variables.DeleteSingle(r.Config.Client, data.SpaceID.ValueString(), variableOwnerID.ValueString(), data.ID.ValueString()); err != nil {
resp.Diagnostics.AddError("unable to delete variable", err.Error())
return
}
From d7fc15c6c8f1a69681c5da470899e0883c20df97 Mon Sep 17 00:00:00 2001
From: Isaac Calligeros <101079287+IsaacCalligeros95@users.noreply.github.com>
Date: Thu, 15 Aug 2024 14:21:45 +0930
Subject: [PATCH 28/28] Chore!: fix lvs datasource unexpected changes and
resource Known After Apply differences. (#739)
* Fix an issue with help text and templates when empty
* Fix library variable set resource and data source schema + changes
* Remove template ids plan modifier
---
docs/resources/library_variable_set.md | 2 +-
.../schemas/library_variable_set.go | 10 +++++++---
octopusdeploy_framework/schemas/schema.go | 8 ++++++++
3 files changed, 16 insertions(+), 4 deletions(-)
diff --git a/docs/resources/library_variable_set.md b/docs/resources/library_variable_set.md
index 9bff4bf78..1eac0caca 100644
--- a/docs/resources/library_variable_set.md
+++ b/docs/resources/library_variable_set.md
@@ -25,10 +25,10 @@ This resource manages library variable sets in Octopus Deploy.
- `id` (String) The unique ID for this resource.
- `space_id` (String) The space ID associated with this library variable set.
- `template` (Block List) (see [below for nested schema](#nestedblock--template))
-- `template_ids` (Map of String)
### Read-Only
+- `template_ids` (Map of String)
- `variable_set_id` (String)
diff --git a/octopusdeploy_framework/schemas/library_variable_set.go b/octopusdeploy_framework/schemas/library_variable_set.go
index 76ba84d14..055db386c 100644
--- a/octopusdeploy_framework/schemas/library_variable_set.go
+++ b/octopusdeploy_framework/schemas/library_variable_set.go
@@ -7,6 +7,8 @@ import (
"github.com/hashicorp/terraform-plugin-framework/attr"
datasourceSchema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
resourceSchema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
types "github.com/hashicorp/terraform-plugin-framework/types"
)
@@ -93,10 +95,12 @@ func GetLibraryVariableSetResourceSchema() resourceSchema.Schema {
"template_ids": resourceSchema.MapAttribute{
ElementType: types.StringType,
Computed: true,
- Optional: true,
},
"variable_set_id": resourceSchema.StringAttribute{
Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
},
},
Description: "This resource manages library variable sets in Octopus Deploy.",
@@ -135,7 +139,7 @@ func MapToLibraryVariableSet(data *LibraryVariableSetResourceModel) *variables.L
func FlattenTemplates(actionTemplateParameters []actiontemplates.ActionTemplateParameter) types.List {
if len(actionTemplateParameters) == 0 {
- return types.ListNull(types.ObjectType{AttrTypes: TemplateObjectType()})
+ return types.ListValueMust(types.ObjectType{AttrTypes: TemplateObjectType()}, []attr.Value{})
}
actionTemplateList := make([]attr.Value, 0, len(actionTemplateParameters))
@@ -143,7 +147,7 @@ func FlattenTemplates(actionTemplateParameters []actiontemplates.ActionTemplateP
attrs := map[string]attr.Value{
"default_value": util.Ternary(actionTemplateParams.DefaultValue.Value != "", types.StringValue(actionTemplateParams.DefaultValue.Value), types.StringNull()),
"display_settings": flattenDisplaySettingsMap(actionTemplateParams.DisplaySettings),
- "help_text": util.Ternary(actionTemplateParams.HelpText != "", types.StringValue(actionTemplateParams.HelpText), types.StringNull()),
+ "help_text": util.Ternary(actionTemplateParams.HelpText != "", types.StringValue(actionTemplateParams.HelpText), types.StringValue("")),
"id": types.StringValue(actionTemplateParams.GetID()),
"label": util.Ternary(actionTemplateParams.Label != "", types.StringValue(actionTemplateParams.Label), types.StringNull()),
"name": types.StringValue(actionTemplateParams.Name),
diff --git a/octopusdeploy_framework/schemas/schema.go b/octopusdeploy_framework/schemas/schema.go
index 496dbc913..e67c11dc3 100644
--- a/octopusdeploy_framework/schemas/schema.go
+++ b/octopusdeploy_framework/schemas/schema.go
@@ -2,6 +2,8 @@ package schemas
import (
"fmt"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
@@ -145,6 +147,9 @@ func GetIdResourceSchema() resourceSchema.Attribute {
Description: "The unique ID for this resource.",
Computed: true,
Optional: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
}
}
@@ -153,6 +158,9 @@ func GetSpaceIdResourceSchema(resourceDescription string) resourceSchema.Attribu
Description: "The space ID associated with this " + resourceDescription + ".",
Computed: true,
Optional: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
}
}