diff --git a/docs/data-sources/kubernetes_agent_workers.md b/docs/data-sources/kubernetes_agent_workers.md new file mode 100644 index 000000000..332ac9598 --- /dev/null +++ b/docs/data-sources/kubernetes_agent_workers.md @@ -0,0 +1,58 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octopusdeploy_kubernetes_agent_workers Data Source - terraform-provider-octopusdeploy" +subcategory: "" +description: |- + Provides information about existing kubernetes agent workers. +--- + +# octopusdeploy_kubernetes_agent_workers (Data Source) + +Provides information about existing kubernetes agent workers. + + + + +## Schema + +### Optional + +- `health_statuses` (List of String) A filter to search by a list of health statuses of resources. Valid health statuses are `HasWarnings`, `Healthy`, `Unavailable`, `Unhealthy`, or `Unknown`. +- `ids` (List of String) A filter to search by a list of IDs. +- `is_disabled` (Boolean) A filter to search by the disabled status of a resource. +- `name` (String) A filter to search by name. +- `partial_name` (String) A filter to search by the partial match of a name. +- `roles` (List of String) A filter to search by a list of role IDs. +- `shell_names` (List of String) A list of shell names to match in the query and/or search +- `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. +- `thumbprint` (String) The thumbprint of the deployment target to match in the query and/or search + +### Read-Only + +- `id` (String) An auto-generated identifier that includes the timestamp when this data source was last modified. +- `kubernetes_agent_workers` (List of Object) A list of kubernetes agent workers that match the filter(s). (see [below for nested schema](#nestedatt--kubernetes_agent_workers)) + + +### Nested Schema for `kubernetes_agent_workers` + +Read-Only: + +- `agent_helm_release_name` (String) +- `agent_kubernetes_namespace` (String) +- `agent_tentacle_version` (String) +- `agent_upgrade_status` (String) +- `agent_version` (String) +- `communication_mode` (String) +- `id` (String) +- `is_disabled` (Boolean) +- `machine_policy_id` (String) +- `name` (String) +- `space_id` (String) +- `thumbprint` (String) +- `upgrade_locked` (Boolean) +- `uri` (String) +- `worker_pool_ids` (List of String) + + diff --git a/docs/resources/kubernetes_agent_worker.md b/docs/resources/kubernetes_agent_worker.md new file mode 100644 index 000000000..436f44c08 --- /dev/null +++ b/docs/resources/kubernetes_agent_worker.md @@ -0,0 +1,67 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octopusdeploy_kubernetes_agent_worker Resource - terraform-provider-octopusdeploy" +subcategory: "" +description: |- + This resource manages Kubernetes agent workers in Octopus Deploy. +--- + +# octopusdeploy_kubernetes_agent_worker (Resource) + +This resource manages Kubernetes agent workers in Octopus Deploy. + +## Example Usage + +```terraform +resource "octopusdeploy_kubernetes_agent_worker" "minimal" { + name = "agent-minimal" + worker_pools = ["worker-pools-1"] + thumbprint = "96203ED84246201C26A2F4360D7CBC36AC1D232D" + uri = "poll://kcxzcv2fpsxkn6tk9u6d/" +} + +resource "octopusdeploy_kubernetes_agent_worker" "optionals" { + name = "agent-optionals" + worker_pools = ["worker-pools-1", "worker-pools-3"] + thumbprint = "96203ED84246201C26A2F4360D7CBC36AC1D232D" + uri = "poll://kcxzcv2fpsxkn6tk9u6d/" + machine_policy_id = "machinepolicies-1" + upgrade_locked = true + is_disabled = true +} +``` + + +## Schema + +### Required + +- `name` (String) The name of 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. +- `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. +- `worker_pool_ids` (List of String) A list of worker pool Ids specifying the pools in which this worker belongs + +### Optional + +- `communication_mode` (String) The communication mode used by the Kubernetes agent to communicate with Octopus Server. Currently, the only supported value is 'Polling'. +- `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. +- `space_id` (String) The space ID associated with this resource. +- `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. + +### 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. + +## Import + +Import is supported using the following syntax: + +```shell +terraform import [options] octopusdeploy_kubernetes_agent_worker. +``` diff --git a/examples/resources/octopusdeploy_kubernetes_agent_worker/import.sh b/examples/resources/octopusdeploy_kubernetes_agent_worker/import.sh new file mode 100644 index 000000000..f3165103d --- /dev/null +++ b/examples/resources/octopusdeploy_kubernetes_agent_worker/import.sh @@ -0,0 +1 @@ +terraform import [options] octopusdeploy_kubernetes_agent_worker. \ No newline at end of file diff --git a/examples/resources/octopusdeploy_kubernetes_agent_worker/resource.tf b/examples/resources/octopusdeploy_kubernetes_agent_worker/resource.tf new file mode 100644 index 000000000..92fd1669b --- /dev/null +++ b/examples/resources/octopusdeploy_kubernetes_agent_worker/resource.tf @@ -0,0 +1,16 @@ +resource "octopusdeploy_kubernetes_agent_worker" "minimal" { + name = "agent-minimal" + worker_pools = ["worker-pools-1"] + thumbprint = "96203ED84246201C26A2F4360D7CBC36AC1D232D" + uri = "poll://kcxzcv2fpsxkn6tk9u6d/" +} + +resource "octopusdeploy_kubernetes_agent_worker" "optionals" { + name = "agent-optionals" + worker_pools = ["worker-pools-1", "worker-pools-3"] + thumbprint = "96203ED84246201C26A2F4360D7CBC36AC1D232D" + uri = "poll://kcxzcv2fpsxkn6tk9u6d/" + machine_policy_id = "machinepolicies-1" + upgrade_locked = true + is_disabled = true +} \ No newline at end of file diff --git a/go.mod b/go.mod index cd8a3a059..1d574617d 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.2 + github.com/OctopusDeploy/go-octopusdeploy/v2 v2.50.0 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 cfe020223..066bca243 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.2 h1:ENd1MyQbYIDiW1ZyXRUcZr4OQ0d8j47I5a6DOT9Ez4o= -github.com/OctopusDeploy/go-octopusdeploy/v2 v2.49.2/go.mod h1:ggvOXzMnq+w0pLg6C9zdjz6YBaHfO3B3tqmmB7JQdaw= +github.com/OctopusDeploy/go-octopusdeploy/v2 v2.50.0 h1:rQiLEbqt/D3lPQw3pq9sXAW1C0WhVLrfN/h0cqUzaFY= +github.com/OctopusDeploy/go-octopusdeploy/v2 v2.50.0/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= diff --git a/octopusdeploy/data_source_kubernetes_agent_workers.go b/octopusdeploy/data_source_kubernetes_agent_workers.go new file mode 100644 index 000000000..c270d7c3e --- /dev/null +++ b/octopusdeploy/data_source_kubernetes_agent_workers.go @@ -0,0 +1,51 @@ +package octopusdeploy + +import ( + "context" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/machines" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "time" +) + +func dataSourceKubernetesAgentWorkers() *schema.Resource { + return &schema.Resource{ + Description: "Provides information about existing kubernetes agent workers.", + ReadContext: dataSourceKubernetesAgentWorkersRead, + Schema: getKubernetesAgentWorkerDataSchema(), + } +} + +func dataSourceKubernetesAgentWorkersRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + query := machines.WorkersQuery{ + CommunicationStyles: []string{"KubernetesTentacle"}, + HealthStatuses: expandArray(d.Get("health_statuses").([]interface{})), + IDs: expandArray(d.Get("ids").([]interface{})), + IsDisabled: d.Get("is_disabled").(bool), + Name: d.Get("name").(string), + PartialName: d.Get("partial_name").(string), + ShellNames: expandArray(d.Get("shell_names").([]interface{})), + Skip: d.Get("skip").(int), + Take: d.Get("take").(int), + Thumbprint: d.Get("thumbprint").(string), + } + + client := m.(*client.Client) + existingWorkers, err := client.Workers.Get(query) + if err != nil { + return diag.FromErr(err) + } + + flattenedKubernetesAgents := []interface{}{} + for _, worker := range existingWorkers.Items { + flattenedKubernetesAgents = append(flattenedKubernetesAgents, flattenKubernetesAgentWorker(worker)) + } + + err = d.Set("kubernetes_agent_workers", flattenedKubernetesAgents) + if err != nil { + return diag.FromErr(err) + } + d.SetId("KubernetesAgentWorkers " + time.Now().UTC().String()) + return nil +} diff --git a/octopusdeploy/provider.go b/octopusdeploy/provider.go index a5f8061cb..5f1a0822f 100644 --- a/octopusdeploy/provider.go +++ b/octopusdeploy/provider.go @@ -19,6 +19,7 @@ func Provider() *schema.Provider { "octopusdeploy_channels": dataSourceChannels(), "octopusdeploy_deployment_targets": dataSourceDeploymentTargets(), "octopusdeploy_kubernetes_agent_deployment_targets": dataSourceKubernetesAgentDeploymentTargets(), + "octopusdeploy_kubernetes_agent_workers": dataSourceKubernetesAgentWorkers(), "octopusdeploy_kubernetes_cluster_deployment_targets": dataSourceKubernetesClusterDeploymentTargets(), "octopusdeploy_listening_tentacle_deployment_targets": dataSourceListeningTentacleDeploymentTargets(), "octopusdeploy_machine": dataSourceMachine(), @@ -47,6 +48,7 @@ func Provider() *schema.Provider { "octopusdeploy_dynamic_worker_pool": resourceDynamicWorkerPool(), "octopusdeploy_gcp_account": resourceGoogleCloudPlatformAccount(), "octopusdeploy_kubernetes_agent_deployment_target": resourceKubernetesAgentDeploymentTarget(), + "octopusdeploy_kubernetes_agent_worker": resourceKubernetesAgentWorker(), "octopusdeploy_kubernetes_cluster_deployment_target": resourceKubernetesClusterDeploymentTarget(), "octopusdeploy_listening_tentacle_deployment_target": resourceListeningTentacleDeploymentTarget(), "octopusdeploy_machine_policy": resourceMachinePolicy(), diff --git a/octopusdeploy/resource_kubernetes_agent_worker.go b/octopusdeploy/resource_kubernetes_agent_worker.go new file mode 100644 index 000000000..e761f3e13 --- /dev/null +++ b/octopusdeploy/resource_kubernetes_agent_worker.go @@ -0,0 +1,79 @@ +package octopusdeploy + +import ( + "context" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/workers" + "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 resourceKubernetesAgentWorker() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceKubernetesAgentWorkerCreate, + DeleteContext: resourceKubernetesAgentWorkerDelete, + Description: "This resource manages Kubernetes agent workers in Octopus Deploy.", + Importer: getImporter(), + ReadContext: resourceKubernetesAgentWorkerRead, + Schema: getKubernetesAgentWorkerSchema(), + UpdateContext: resourceKubernetesAgentWorkerUpdate, + } +} + +func resourceKubernetesAgentWorkerCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + worker := expandKubernetesAgentWorker(d) + client := m.(*client.Client) + createdWorker, err := workers.Add(client, worker) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(createdWorker.GetID()) + return nil +} + +func resourceKubernetesAgentWorkerRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + Worker, err := workers.GetByID(client, d.Get("space_id").(string), d.Id()) + if err != nil { + return errors.ProcessApiError(ctx, d, err, "kubernetes tentacle worker") + } + + flattenedKubernetesAgentWorker := flattenKubernetesAgentWorker(Worker) + for key, value := range flattenedKubernetesAgentWorker { + if key != "id" { + err := d.Set(key, value) + if err != nil { + return diag.FromErr(err) + } + } + } + + return nil +} + +func resourceKubernetesAgentWorkerDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*client.Client) + if err := workers.DeleteByID(client, d.Get("space_id").(string), d.Id()); err != nil { + return diag.FromErr(err) + } + d.SetId("") + return nil +} + +func resourceKubernetesAgentWorkerUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + worker := expandKubernetesAgentWorker(d) + client := m.(*client.Client) + + worker.ID = d.Id() + + updatedWorker, err := workers.Update(client, worker) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(updatedWorker.GetID()) + + return nil +} diff --git a/octopusdeploy/resource_kubernetes_agent_worker_integration_test.go b/octopusdeploy/resource_kubernetes_agent_worker_integration_test.go new file mode 100644 index 000000000..d6c109563 --- /dev/null +++ b/octopusdeploy/resource_kubernetes_agent_worker_integration_test.go @@ -0,0 +1,107 @@ +package octopusdeploy + +import ( + "context" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/machines" + "github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework/octoclient" + "github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework/test" + "log" + "os" + "path/filepath" + stdslices "slices" + "testing" + "time" +) + +func TestKubernetesAgentWorkerResource(t *testing.T) { + testFramework := test.OctopusContainerTest{ + CustomEnvironment: map[string]string{ + "OCTOPUS__FeatureToggles__KubernetesAgentAsWorkerFeatureToggle": "true", + }, + } + + // Use separate Octopus container as this test requires a custom environment variable to be set + testSpecificOctoContainer, _, testSpecificSqlServerContainer, testSpecificNetwork, err := testFramework.ArrangeContainer() + if err != nil { + log.Printf("Failed to arrange containers: (%s)", err.Error()) + } + os.Setenv("TF_ACC", "1") + + inputVars := []string{"-var=octopus_server_58-kubernetesagentworker=" + testSpecificOctoContainer.URI, "-var=octopus_apikey_58-kubernetesagentworker=" + test.ApiKey} + + newSpaceId, err := testFramework.Act(t, testSpecificOctoContainer, "../terraform", "58-kubernetesagentworker", inputVars) + if err != nil { + t.Fatal(err.Error()) + } + + err = testFramework.TerraformInitAndApply(t, testSpecificOctoContainer, filepath.Join("../terraform", "58a-kubernetesagentworkerds"), newSpaceId, inputVars) + if err != nil { + t.Fatal(err.Error()) + } + + // Assert + client, err := octoclient.CreateClient(testSpecificOctoContainer.URI, newSpaceId, test.ApiKey) + query := machines.WorkersQuery{ + CommunicationStyles: []string{"KubernetesTentacle"}, + Skip: 0, + Take: 3, + } + + resources, err := client.Workers.Get(query) + if err != nil { + t.Fatal(err.Error()) + } + + if len(resources.Items) != 2 { + t.Fatalf("Space must have two workers (both KubernetesTentacles), instead found %v", resources.Items) + } + + minimalAgentName := "minimum-agent" + minimalAgentIndex := stdslices.IndexFunc(resources.Items, func(t *machines.Worker) bool { return t.Name == minimalAgentName }) + minimalAgentWorker := resources.Items[minimalAgentIndex] + minimalAgentEndpoint := minimalAgentWorker.Endpoint.(*machines.KubernetesTentacleEndpoint) + if minimalAgentWorker.IsDisabled { + t.Fatalf("Expected \"%s\" to be enabled", minimalAgentName) + } + + if minimalAgentEndpoint.UpgradeLocked { + t.Fatalf("Expected \"%s\" to not be upgrade locked", minimalAgentName) + } + + if len(minimalAgentWorker.WorkerPoolIDs) != 1 { + t.Fatalf("Expected \"%s\" to have one worker pool id", minimalAgentName) + } + + fullAgentName := "agent-with-optionals" + fullAgentIndex := stdslices.IndexFunc(resources.Items, func(t *machines.Worker) bool { return t.Name == fullAgentName }) + fullAgentWorker := resources.Items[fullAgentIndex] + + if !fullAgentWorker.IsDisabled { + t.Fatalf("Expected \"%s\" to be disabled", fullAgentName) + } + + fullAgentEndpoint := fullAgentWorker.Endpoint.(*machines.KubernetesTentacleEndpoint) + if !fullAgentEndpoint.UpgradeLocked { + t.Fatalf("Expected \"%s\" to be upgrade locked", fullAgentName) + } + + if len(fullAgentWorker.WorkerPoolIDs) != 2 { + t.Fatalf("Expected \"%s\" to have two worker pool ids", fullAgentName) + } + + _, err = testFramework.GetOutputVariable(t, filepath.Join("../terraform", "58a-kubernetesagentworkerds"), "data_lookup_kubernetes_worker_1_id") + _, err = testFramework.GetOutputVariable(t, filepath.Join("../terraform", "58a-kubernetesagentworkerds"), "data_lookup_kubernetes_worker_2_id") + if err != nil { + t.Fatal("Failed to query for created k8s workers") + } + + ctx := context.Background() + + // Waiting for the container logs to clear. + time.Sleep(5000 * time.Millisecond) + err = testFramework.CleanUp(ctx, testSpecificOctoContainer, testSpecificSqlServerContainer, testSpecificNetwork) + + if err != nil { + log.Printf("Failed to clean up containers: (%s)", err.Error()) + } +} diff --git a/octopusdeploy/schema_kubernetes_agent_worker.go b/octopusdeploy/schema_kubernetes_agent_worker.go new file mode 100644 index 000000000..8c6f1e753 --- /dev/null +++ b/octopusdeploy/schema_kubernetes_agent_worker.go @@ -0,0 +1,157 @@ +package octopusdeploy + +import ( + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/machines" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "net/url" +) + +func expandKubernetesAgentWorker(kubernetesAgent *schema.ResourceData) *machines.Worker { + uri, _ := url.Parse(kubernetesAgent.Get("uri").(string)) + thumbprint := kubernetesAgent.Get("thumbprint").(string) + + communicationsMode := kubernetesAgent.Get("communication_mode").(string) + upgradeLocked := kubernetesAgent.Get("upgrade_locked").(bool) + var endpoint machines.IEndpoint = machines.NewKubernetesTentacleEndpoint(uri, thumbprint, upgradeLocked, communicationsMode, "") + + name := kubernetesAgent.Get("name").(string) + Worker := machines.NewWorker(name, endpoint) + + Worker.IsDisabled = kubernetesAgent.Get("is_disabled").(bool) + Worker.Thumbprint = thumbprint + Worker.WorkerPoolIDs = getSliceFromTerraformTypeList(kubernetesAgent.Get("worker_pool_ids")) + + Worker.SpaceID = kubernetesAgent.Get("space_id").(string) + + return Worker +} + +func flattenKubernetesAgentWorker(Worker *machines.Worker) map[string]interface{} { + if Worker == nil { + return nil + } + + if Worker.Endpoint.GetCommunicationStyle() != "KubernetesTentacle" { + return nil + } + + endpoint := Worker.Endpoint.(*machines.KubernetesTentacleEndpoint) + + flattenedWorker := map[string]interface{}{} + flattenedWorker["id"] = Worker.GetID() + flattenedWorker["space_id"] = Worker.SpaceID + flattenedWorker["name"] = Worker.Name + flattenedWorker["machine_policy_id"] = Worker.MachinePolicyID + flattenedWorker["is_disabled"] = Worker.IsDisabled + + flattenedWorker["thumbprint"] = endpoint.TentacleEndpointConfiguration.Thumbprint + flattenedWorker["uri"] = endpoint.TentacleEndpointConfiguration.URI.String() + flattenedWorker["communication_mode"] = endpoint.TentacleEndpointConfiguration.CommunicationMode + flattenedWorker["worker_pool_ids"] = Worker.WorkerPoolIDs + + if endpoint.KubernetesAgentDetails != nil { + flattenedWorker["agent_version"] = endpoint.KubernetesAgentDetails.AgentVersion + flattenedWorker["agent_tentacle_version"] = endpoint.KubernetesAgentDetails.TentacleVersion + flattenedWorker["agent_upgrade_status"] = endpoint.KubernetesAgentDetails.UpgradeStatus + flattenedWorker["agent_helm_release_name"] = endpoint.KubernetesAgentDetails.HelmReleaseName + flattenedWorker["agent_kubernetes_namespace"] = endpoint.KubernetesAgentDetails.KubernetesNamespace + } + + return flattenedWorker +} + +func getKubernetesAgentWorkerSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "id": getIDSchema(), + "space_id": getSpaceIDSchema(), + "name": getNameSchema(true), + "communication_mode": { + Optional: true, + Description: "The communication mode used by the Kubernetes agent to communicate with Octopus Server. Currently, the only supported value is 'Polling'.", + Type: schema.TypeString, + Default: "Polling", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"Polling"}, false)), + }, + "machine_policy_id": { + Description: "Optional ID of the machine policy that the Kubernetes agent will use. If not provided the default machine policy will be used.", + Computed: true, + Optional: true, + Type: schema.TypeString, + }, + "thumbprint": { + Description: "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.", + Required: true, + Type: schema.TypeString, + }, + "uri": { + Description: "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.", + Required: true, + Type: schema.TypeString, + }, + "upgrade_locked": { + Description: "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.", + Optional: true, + Type: schema.TypeBool, + }, + "is_disabled": { + Description: "Whether the Kubernetes agent is disabled. If the agent is disabled, it will not be included in any deployments.", + Optional: true, + Default: false, + Type: schema.TypeBool, + }, + "worker_pool_ids": { + Description: "A list of worker pool Ids specifying the pools in which this worker belongs", + Elem: &schema.Schema{Type: schema.TypeString}, + MinItems: 1, + Required: true, + Type: schema.TypeList, + }, + + // Read-only Values + "agent_version": { + Description: "Current Helm chart version of the agent.", + Computed: true, + Type: schema.TypeString, + }, + "agent_tentacle_version": { + Description: "Current Tentacle version of the agent", + Computed: true, + Type: schema.TypeString, + }, + "agent_upgrade_status": { + Description: "Current upgrade availability status of the agent. One of 'NoUpgrades', 'UpgradeAvailable', 'UpgradeSuggested', 'UpgradeRequired'", + Computed: true, + Type: schema.TypeString, + }, + "agent_helm_release_name": { + Description: "Name of the Helm release that the agent belongs to.", + Computed: true, + Type: schema.TypeString, + }, + "agent_kubernetes_namespace": { + Description: "Name of the Kubernetes namespace where the agent is installed.", + Computed: true, + Type: schema.TypeString, + }, + } +} + +func getKubernetesAgentWorkerDataSchema() map[string]*schema.Schema { + dataSchema := getKubernetesAgentWorkerSchema() + setDataSchema(&dataSchema) + + WorkerDataSchema := getWorkerDataSchema() + WorkerDataSchema["kubernetes_agent_workers"] = &schema.Schema{ + Computed: true, + Description: "A list of kubernetes agent workers that match the filter(s).", + Elem: &schema.Resource{Schema: dataSchema}, + Optional: false, + Type: schema.TypeList, + } + + delete(WorkerDataSchema, "communication_styles") + delete(WorkerDataSchema, "workers") + WorkerDataSchema["id"] = getDataSchemaID() + return WorkerDataSchema +} diff --git a/octopusdeploy/schema_worker.go b/octopusdeploy/schema_worker.go new file mode 100644 index 000000000..56822960a --- /dev/null +++ b/octopusdeploy/schema_worker.go @@ -0,0 +1,201 @@ +package octopusdeploy + +import ( + "context" + "fmt" + + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/machines" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func expandWorker(d *schema.ResourceData) *machines.Worker { + endpoint := expandEndpoint(d.Get("endpoint")) + name := d.Get("name").(string) + + worker := machines.NewWorker(name, endpoint) + worker.ID = d.Id() + + if v, ok := d.GetOk("machine_policy_id"); ok { + worker.MachinePolicyID = v.(string) + } + + if v, ok := d.GetOk("is_disabled"); ok { + worker.IsDisabled = v.(bool) + } + + if v, ok := d.GetOk("thumbprint"); ok { + worker.Thumbprint = v.(string) + } + + if v, ok := d.GetOk("uri"); ok { + worker.URI = v.(string) + } + + if v, ok := d.GetOk("space_id"); ok { + worker.SpaceID = v.(string) + } + + if v, ok := d.GetOk("thumbprint"); ok { + worker.Thumbprint = v.(string) + } + + worker.WorkerPoolIDs = getSliceFromTerraformTypeList(d.Get("worker_pool_ids")) + + return worker +} + +func flattenWorker(worker *machines.Worker) map[string]interface{} { + if worker == nil { + return nil + } + + endpointResource, _ := machines.ToEndpointResource(worker.Endpoint) + + return map[string]interface{}{ + "endpoint": flattenEndpointResource(endpointResource), + "has_latest_calamari": worker.HasLatestCalamari, + "health_status": worker.HealthStatus, + "id": worker.GetID(), + "is_disabled": worker.IsDisabled, + "is_in_process": worker.IsInProcess, + "machine_policy_id": worker.MachinePolicyID, + "name": worker.Name, + "operating_system": worker.OperatingSystem, + "shell_name": worker.ShellName, + "shell_version": worker.ShellVersion, + "space_id": worker.SpaceID, + "status": worker.Status, + "status_summary": worker.StatusSummary, + "thumbprint": worker.Thumbprint, + "uri": worker.URI, + "worker_pool_ids": worker.WorkerPoolIDs, + } +} + +func getWorkerDataSchema() map[string]*schema.Schema { + dataSchema := getWorkerSchema() + setDataSchema(&dataSchema) + + return map[string]*schema.Schema{ + "communication_styles": getQueryCommunicationStyles(), + "workers": { + Computed: true, + Description: "A list of workers that match the filter(s).", + Elem: &schema.Resource{Schema: dataSchema}, + Optional: true, + Type: schema.TypeList, + }, + "health_statuses": getQueryHealthStatuses(), + "ids": getQueryIDs(), + "is_disabled": getQueryIsDisabled(), + "name": getQueryName(), + "partial_name": getQueryPartialName(), + "roles": getQueryRoles(), + "shell_names": getQueryShellNames(), + "skip": getQuerySkip(), + "take": getQueryTake(), + "thumbprint": getQueryThumbprint(), + "space_id": getSpaceIDSchema(), + } +} + +func getWorkerSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "endpoint": { + Computed: true, + Elem: &schema.Resource{Schema: getEndpointSchema()}, + MinItems: 1, + Optional: true, + Type: schema.TypeList, + }, + "has_latest_calamari": { + Computed: true, + Type: schema.TypeBool, + }, + "health_status": getHealthStatusSchema(), + "id": getIDSchema(), + "is_disabled": { + Computed: true, + Optional: true, + Type: schema.TypeBool, + }, + "is_in_process": { + Computed: true, + Type: schema.TypeBool, + }, + "machine_policy_id": { + Computed: true, + Optional: true, + Type: schema.TypeString, + }, + "name": getNameSchema(true), + "operating_system": { + Computed: true, + Optional: true, + Type: schema.TypeString, + }, + "shell_name": { + Computed: true, + Optional: true, + Type: schema.TypeString, + }, + "shell_version": { + Computed: true, + Optional: true, + Type: schema.TypeString, + }, + "space_id": getSpaceIDSchema(), + "status": getStatusSchema(), + "status_summary": getStatusSummarySchema(), + "thumbprint": { + Computed: true, + Optional: true, + Type: schema.TypeString, + }, + "uri": { + Computed: true, + Optional: true, + Type: schema.TypeString, + }, + "worker_pool_ids": { + Elem: &schema.Schema{Type: schema.TypeString}, + MinItems: 1, + Required: true, + Type: schema.TypeList, + }, + } +} + +func setWorker(ctx context.Context, d *schema.ResourceData, worker *machines.Worker) error { + d.Set("has_latest_calamari", worker.HasLatestCalamari) + d.Set("health_status", worker.HealthStatus) + d.Set("is_disabled", worker.IsDisabled) + d.Set("is_in_process", worker.IsInProcess) + d.Set("machine_policy_id", worker.MachinePolicyID) + d.Set("name", worker.Name) + d.Set("operating_system", worker.OperatingSystem) + d.Set("shell_name", worker.ShellName) + d.Set("shell_version", worker.ShellVersion) + d.Set("space_id", worker.SpaceID) + d.Set("status", worker.Status) + d.Set("status_summary", worker.StatusSummary) + d.Set("thumbprint", worker.Thumbprint) + d.Set("uri", worker.URI) + d.Set("space_id", worker.SpaceID) + d.Set("worker_pool_ids", worker.WorkerPoolIDs) + + endpointResource, err := machines.ToEndpointResource(worker.Endpoint) + if err != nil { + return fmt.Errorf("error setting endpoint: %s", err) + } + + if d.Get("endpoint") != nil { + if err := d.Set("endpoint", flattenEndpointResource(endpointResource)); err != nil { + return fmt.Errorf("error setting endpoint: %s", err) + } + } + + d.SetId(worker.GetID()) + + return nil +} diff --git a/terraform/58-kubernetesagentworker/config.tf b/terraform/58-kubernetesagentworker/config.tf new file mode 100644 index 000000000..2113da144 --- /dev/null +++ b/terraform/58-kubernetesagentworker/config.tf @@ -0,0 +1,5 @@ +terraform { + required_providers { + octopusdeploy = { source = "OctopusDeployLabs/octopusdeploy", version = "0.18.1" } + } +} diff --git a/terraform/58-kubernetesagentworker/kubernetes_agent_workers.tf b/terraform/58-kubernetesagentworker/kubernetes_agent_workers.tf new file mode 100644 index 000000000..dfec82c37 --- /dev/null +++ b/terraform/58-kubernetesagentworker/kubernetes_agent_workers.tf @@ -0,0 +1,17 @@ +resource "octopusdeploy_kubernetes_agent_worker" "agent_with_minimum" { + name = "minimum-agent" + worker_pool_ids = [octopusdeploy_static_worker_pool.workerpool_docker.id] + uri = "poll://kcxzcv2fpsxkn6tk9u6d/" + thumbprint = "96203ED84246201C26A2F4360D7CBC36AC1D232D" +} + +resource "octopusdeploy_kubernetes_agent_worker" "agent_with_optional" { + name = "agent-with-optionals" + machine_policy_id = octopusdeploy_machine_policy.machinepolicy_testing.id + worker_pool_ids = [octopusdeploy_static_worker_pool.workerpool_docker.id, octopusdeploy_static_worker_pool.workerpool_example.id] + communication_mode = "Polling" + uri = "poll://kcxzcv2fpsxkn6tk9u6d/" + thumbprint = "96203ED84246201C26A2F4360D7CBC36AC1D232D" + is_disabled = true + upgrade_locked = true +} \ No newline at end of file diff --git a/terraform/58-kubernetesagentworker/machinepolicy.tf b/terraform/58-kubernetesagentworker/machinepolicy.tf new file mode 100644 index 000000000..b3ef159d8 --- /dev/null +++ b/terraform/58-kubernetesagentworker/machinepolicy.tf @@ -0,0 +1,39 @@ +resource "octopusdeploy_machine_policy" "machinepolicy_testing" { + name = "Testing" + description = "test machine policy" + connection_connect_timeout = 60000000000 + connection_retry_count_limit = 5 + connection_retry_sleep_interval = 100000000 + connection_retry_time_limit = 300000000000 + + machine_cleanup_policy { + delete_machines_behavior = "DeleteUnavailableMachines" + delete_machines_elapsed_timespan = 1200000000000 + } + + machine_connectivity_policy { + machine_connectivity_behavior = "ExpectedToBeOnline" + } + + machine_health_check_policy { + + bash_health_check_policy { + run_type = "Inline" + script_body = "" + } + + powershell_health_check_policy { + run_type = "Inline" + script_body = "$freeDiskSpaceThreshold = 5GB\r\n\r\nTry {\r\n\tGet-WmiObject win32_LogicalDisk -ErrorAction Stop | ? { ($_.DriveType -eq 3) -and ($_.FreeSpace -ne $null)} | % { CheckDriveCapacity @{Name =$_.DeviceId; FreeSpace=$_.FreeSpace} }\r\n} Catch [System.Runtime.InteropServices.COMException] {\r\n\tGet-WmiObject win32_Volume | ? { ($_.DriveType -eq 3) -and ($_.FreeSpace -ne $null) -and ($_.DriveLetter -ne $null)} | % { CheckDriveCapacity @{Name =$_.DriveLetter; FreeSpace=$_.FreeSpace} }\r\n\tGet-WmiObject Win32_MappedLogicalDisk | ? { ($_.FreeSpace -ne $null) -and ($_.DeviceId -ne $null)} | % { CheckDriveCapacity @{Name =$_.DeviceId; FreeSpace=$_.FreeSpace} }\t\r\n}" + } + + health_check_cron_timezone = "UTC" + health_check_interval = 600000000000 + health_check_type = "RunScript" + } + + machine_update_policy { + calamari_update_behavior = "UpdateOnDeployment" + tentacle_update_behavior = "NeverUpdate" + } +} \ No newline at end of file diff --git a/terraform/58-kubernetesagentworker/provider.tf b/terraform/58-kubernetesagentworker/provider.tf new file mode 100644 index 000000000..d563e69b0 --- /dev/null +++ b/terraform/58-kubernetesagentworker/provider.tf @@ -0,0 +1,5 @@ +provider "octopusdeploy" { + address = "${var.octopus_server_58-kubernetesagentworker}" + api_key = "${var.octopus_apikey_58-kubernetesagentworker}" + space_id = "${var.octopus_space_id}" +} diff --git a/terraform/58-kubernetesagentworker/provider_vars.tf b/terraform/58-kubernetesagentworker/provider_vars.tf new file mode 100644 index 000000000..96be8b1f5 --- /dev/null +++ b/terraform/58-kubernetesagentworker/provider_vars.tf @@ -0,0 +1,30 @@ +variable "octopus_server" { + type = string + nullable = false + sensitive = false + description = "Not used for this test but need to be declared" +} +variable "octopus_apikey" { + type = string + nullable = false + sensitive = true + description = "Not used for this test but need to be declared" +} +variable "octopus_server_58-kubernetesagentworker" { + type = string + nullable = false + sensitive = false + description = "The URL of the Octopus server e.g. https://myinstance.octopus.app." +} +variable "octopus_apikey_58-kubernetesagentworker" { + type = string + nullable = false + sensitive = true + description = "The API key used to access the Octopus server. See https://octopus.com/docs/octopus-rest-api/how-to-create-an-api-key for details on creating an API key." +} +variable "octopus_space_id" { + type = string + nullable = false + sensitive = false + description = "The space ID to populate" +} diff --git a/terraform/58-kubernetesagentworker/space.tf b/terraform/58-kubernetesagentworker/space.tf new file mode 100644 index 000000000..ee59bdc80 --- /dev/null +++ b/terraform/58-kubernetesagentworker/space.tf @@ -0,0 +1,3 @@ +output "octopus_space_id" { + value = var.octopus_space_id +} diff --git a/terraform/58-kubernetesagentworker/workerpool.tf b/terraform/58-kubernetesagentworker/workerpool.tf new file mode 100644 index 000000000..a8ff2d101 --- /dev/null +++ b/terraform/58-kubernetesagentworker/workerpool.tf @@ -0,0 +1,13 @@ +resource "octopusdeploy_static_worker_pool" "workerpool_docker" { + name = "Docker" + description = "A test worker pool" + is_default = false + sort_order = 3 +} + +resource "octopusdeploy_static_worker_pool" "workerpool_example" { + name = "Example" + description = "My other example worker pool" + is_default = false + sort_order = 4 +} diff --git a/terraform/58a-kubernetesagentworkerds/config.tf b/terraform/58a-kubernetesagentworkerds/config.tf new file mode 100644 index 000000000..2113da144 --- /dev/null +++ b/terraform/58a-kubernetesagentworkerds/config.tf @@ -0,0 +1,5 @@ +terraform { + required_providers { + octopusdeploy = { source = "OctopusDeployLabs/octopusdeploy", version = "0.18.1" } + } +} diff --git a/terraform/58a-kubernetesagentworkerds/kubernetes_agent_workers.tf b/terraform/58a-kubernetesagentworkerds/kubernetes_agent_workers.tf new file mode 100644 index 000000000..f3bad68eb --- /dev/null +++ b/terraform/58a-kubernetesagentworkerds/kubernetes_agent_workers.tf @@ -0,0 +1,10 @@ +data "octopusdeploy_kubernetes_agent_workers" "all_workers" { +} + +output "data_lookup_kubernetes_worker_1_id" { + value = data.octopusdeploy_kubernetes_agent_workers.all_workers.kubernetes_agent_workers[0].id +} + +output "data_lookup_kubernetes_worker_2_id" { + value = data.octopusdeploy_kubernetes_agent_workers.all_workers.kubernetes_agent_workers[1].id +} \ No newline at end of file diff --git a/terraform/58a-kubernetesagentworkerds/provider.tf b/terraform/58a-kubernetesagentworkerds/provider.tf new file mode 100644 index 000000000..d563e69b0 --- /dev/null +++ b/terraform/58a-kubernetesagentworkerds/provider.tf @@ -0,0 +1,5 @@ +provider "octopusdeploy" { + address = "${var.octopus_server_58-kubernetesagentworker}" + api_key = "${var.octopus_apikey_58-kubernetesagentworker}" + space_id = "${var.octopus_space_id}" +} diff --git a/terraform/58a-kubernetesagentworkerds/provider_vars.tf b/terraform/58a-kubernetesagentworkerds/provider_vars.tf new file mode 100644 index 000000000..96be8b1f5 --- /dev/null +++ b/terraform/58a-kubernetesagentworkerds/provider_vars.tf @@ -0,0 +1,30 @@ +variable "octopus_server" { + type = string + nullable = false + sensitive = false + description = "Not used for this test but need to be declared" +} +variable "octopus_apikey" { + type = string + nullable = false + sensitive = true + description = "Not used for this test but need to be declared" +} +variable "octopus_server_58-kubernetesagentworker" { + type = string + nullable = false + sensitive = false + description = "The URL of the Octopus server e.g. https://myinstance.octopus.app." +} +variable "octopus_apikey_58-kubernetesagentworker" { + type = string + nullable = false + sensitive = true + description = "The API key used to access the Octopus server. See https://octopus.com/docs/octopus-rest-api/how-to-create-an-api-key for details on creating an API key." +} +variable "octopus_space_id" { + type = string + nullable = false + sensitive = false + description = "The space ID to populate" +} diff --git a/terraform/58a-kubernetesagentworkerds/space.tf b/terraform/58a-kubernetesagentworkerds/space.tf new file mode 100644 index 000000000..ee59bdc80 --- /dev/null +++ b/terraform/58a-kubernetesagentworkerds/space.tf @@ -0,0 +1,3 @@ +output "octopus_space_id" { + value = var.octopus_space_id +}