From 34895ca8ccbf7742e4c64adb32bee4602dfec347 Mon Sep 17 00:00:00 2001 From: Guru Prasad Date: Fri, 7 Jan 2022 01:08:12 +0530 Subject: [PATCH 1/2] Added datasource and resource for tmc_namespace --- tanzuclient/namespaces.go | 107 +++++++++++++++++++ tmc/data_source_tmc_namespace.go | 98 ++++++++++++++++++ tmc/provider.go | 2 + tmc/resource_tmc_namespace.go | 172 +++++++++++++++++++++++++++++++ 4 files changed, 379 insertions(+) create mode 100644 tanzuclient/namespaces.go create mode 100644 tmc/data_source_tmc_namespace.go create mode 100644 tmc/resource_tmc_namespace.go diff --git a/tanzuclient/namespaces.go b/tanzuclient/namespaces.go new file mode 100644 index 0000000..caa3005 --- /dev/null +++ b/tanzuclient/namespaces.go @@ -0,0 +1,107 @@ +package tanzuclient + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" +) + +type NamespaceSpec struct { + WorkspaceName string `json:"workspaceName"` +} + +type Namespace struct { + FullName *FullName `json:"fullName"` + Meta *MetaData `json:"meta"` + Spec *NamespaceSpec `json:"spec"` +} + +type NamespaceJsonObject struct { + Namespace Namespace `json:"namespace"` +} + +type NamespaceOpts struct { + Description string + Labels map[string]interface{} + ManagementCluster string + ProvisionerName string + ClusterName string + WorkspaceName string +} + +func (c *Client) CreateNamespace(name string, opts NamespaceOpts) (*Namespace, error) { + requestURL := fmt.Sprintf("%s/v1alpha1/clusters/%s/namespaces?fullName.managementClusterName=%s&fullName.provisionerName=%s", c.baseURL, opts.ClusterName, opts.ManagementCluster, opts.ProvisionerName) + + newNamespace := &Namespace{ + FullName: &FullName{ + Name: name, + ProvisionerName: opts.ProvisionerName, + ManagementClusterName: opts.ManagementCluster, + ClusterName: opts.ClusterName, + }, + Meta: &MetaData{ + Description: opts.Description, + Labels: opts.Labels, + }, + Spec: &NamespaceSpec{ + WorkspaceName: opts.WorkspaceName, + }, + } + + newNamespaceObject := &NamespaceJsonObject{ + Namespace: *newNamespace, + } + + json_data, err := json.Marshal(newNamespaceObject) // returns []byte + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", requestURL, bytes.NewBuffer(json_data)) + if err != nil { + return nil, err + } + + res := NamespaceJsonObject{} + + if err := c.sendRequest(req, &res); err != nil { + return nil, err + } + + return &res.Namespace, nil +} + +func (c *Client) GetNamespace(name string, clusterName string, managementClusterName string, provisionerName string) (*Namespace, error) { + requestURL := fmt.Sprintf("%s/v1alpha1/clusters/%s/namespaces/%s?fullName.managementClusterName=%s&fullName.provisionerName=%s", c.baseURL, clusterName, name, managementClusterName, provisionerName) + + req, err := http.NewRequest("GET", requestURL, nil) + if err != nil { + return nil, err + } + + res := NamespaceJsonObject{} + + if err := c.sendRequest(req, &res); err != nil { + return nil, err + } + + return &res.Namespace, nil +} + +func (c *Client) DeleteNamespace(name string, clusterName string, managementClusterName string, provisionerName string) error { + requestURL := fmt.Sprintf("%s/v1alpha1/clusters/%s/namespaces/%s?fullName.managementClusterName=%s&fullName.provisionerName=%s", c.baseURL, clusterName, name, managementClusterName, provisionerName) + + req, err := http.NewRequest("DELETE", requestURL, nil) + if err != nil { + return err + } + + res := NamespaceJsonObject{} + + if err := c.sendRequest(req, &res); err != nil { + return err + } + + return nil +} diff --git a/tmc/data_source_tmc_namespace.go b/tmc/data_source_tmc_namespace.go new file mode 100644 index 0000000..4b09b80 --- /dev/null +++ b/tmc/data_source_tmc_namespace.go @@ -0,0 +1,98 @@ +package tmc + +import ( + "context" + "fmt" + + "github.com/codaglobal/terraform-provider-tmc/tanzuclient" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceTmcNamespace() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceTmcNamespaceRead, + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + Description: "Unique ID of the Cluster Group", + }, + "name": { + Type: schema.TypeString, + Required: true, + Description: "Unique Name of the Namespace in your Org", + ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { + v := val.(string) + if !IsValidTanzuName(v) { + errs = append(errs, fmt.Errorf("name should contain only lowercase letters, numbers or hyphens and should begin with either an alphabet or number")) + } + return + }, + }, + "cluster_name": { + Type: schema.TypeString, + Required: true, + Description: "Name of the cluster in which the namespace is to be created", + }, + "description": { + Type: schema.TypeString, + Computed: true, + Description: "Description of the Namespace", + }, + "management_cluster": { + Type: schema.TypeString, + Required: true, + Description: "Name of the management cluster used", + }, + "provisioner_name": { + Type: schema.TypeString, + Required: true, + Description: "Name of the provisioner", + }, + "labels": labelsSchemaComputed(), + "workspace_name": { + Type: schema.TypeString, + Computed: true, + Description: "Name of the workspace for the namespace", + }, + }, + } +} + +func dataSourceTmcNamespaceRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + var diags diag.Diagnostics + + client := m.(*tanzuclient.Client) + + NamespaceName := d.Get("name").(string) + clusterName := d.Get("cluster_name").(string) + managementClusterName := d.Get("management_cluster").(string) + provisionerName := d.Get("provisioner_name").(string) + + Namespace, err := client.GetNamespace(NamespaceName, clusterName, managementClusterName, provisionerName) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to read namespace", + Detail: fmt.Sprintf("Error reading resource %s: %s", d.Get("name"), err), + }) + return diags + } + + d.Set("description", Namespace.Meta.Description) + if err := d.Set("labels", Namespace.Meta.Labels); err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to read namespace", + Detail: fmt.Sprintf("Error setting labels for resource %s: %s", d.Get("name"), err), + }) + return diags + } + + d.Set("workspace_name", Namespace.Spec.WorkspaceName) + + d.SetId(Namespace.Meta.UID) + + return nil +} diff --git a/tmc/provider.go b/tmc/provider.go index 7d0604a..8ea124a 100644 --- a/tmc/provider.go +++ b/tmc/provider.go @@ -43,6 +43,7 @@ func Provider() *schema.Provider { "tmc_aws_storage_credential": dataSourceTmcAwsStorageCredential(), "tmc_observability_credential": dataSourceTmcObservabilityCredential(), "tmc_cluster_backup": dataSourceTmcClusterBackup(), + "tmc_namespace": dataSourceTmcNamespace(), }, // List of Resources supported by the provider @@ -56,6 +57,7 @@ func Provider() *schema.Provider { "tmc_aws_storage_credential": resourceTmcAwsStorageCredential(), "tmc_observability_credential": resourceTmcObservabilityCredential(), "tmc_cluster_backup": resourceTmcClusterBackup(), + "tmc_namespace": resourceTmcNamespace(), }, } diff --git a/tmc/resource_tmc_namespace.go b/tmc/resource_tmc_namespace.go new file mode 100644 index 0000000..8955a68 --- /dev/null +++ b/tmc/resource_tmc_namespace.go @@ -0,0 +1,172 @@ +package tmc + +import ( + "context" + "fmt" + + "github.com/codaglobal/terraform-provider-tmc/tanzuclient" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceTmcNamespace() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceTmcNamespaceCreate, + ReadContext: resourceTmcNamespaceRead, + DeleteContext: resourceTmcNamespaceDelete, + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + Description: "Unique ID of the Namespace", + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Unique Name of the Namespace in your Org", + ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { + v := val.(string) + if !IsValidTanzuName(v) { + errs = append(errs, fmt.Errorf("name should contain only lowercase letters, numbers or hyphens and should begin with either an alphabet or number")) + } + return + }, + }, + "cluster_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Name of the cluster in which the namespace is to be created", + }, + "description": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "Description of the Namespace", + }, + "management_cluster": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Name of the management cluster used", + }, + "provisioner_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Name of the provisioner", + }, + "labels": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + // Ignore changes to the creator label added automatically added by TMC and + // also ignore changes when the labels field itself is deleted when updating + return k == "labels.tmc.cloud.vmware.com/creator" || k == "labels.%" || k == "labels.tmc.cloud.vmware.com/managed" || k == "labels.tmc.cloud.vmware.com/workspace" + }, + }, + "workspace_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Name of the workspace for the namespace", + }, + }, + } +} + +func resourceTmcNamespaceCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + + var diags diag.Diagnostics + + client := m.(*tanzuclient.Client) + + NamespaceName := d.Get("name").(string) + + opts := &tanzuclient.NamespaceOpts{ + Description: d.Get("description").(string), + Labels: d.Get("labels").(map[string]interface{}), + ManagementCluster: d.Get("management_cluster").(string), + ProvisionerName: d.Get("provisioner_name").(string), + ClusterName: d.Get("cluster_name").(string), + WorkspaceName: d.Get("workspace_name").(string), + } + + Namespace, err := client.CreateNamespace(NamespaceName, *opts) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to create namespace", + Detail: fmt.Sprintf("Error creating resource %s: %s", d.Get("name"), err), + }) + return diags + } + + d.SetId(Namespace.Meta.UID) + + return resourceTmcNamespaceRead(ctx, d, m) +} + +func resourceTmcNamespaceRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + + var diags diag.Diagnostics + + client := m.(*tanzuclient.Client) + + NamespaceName := d.Get("name").(string) + clusterName := d.Get("cluster_name").(string) + managementClusterName := d.Get("management_cluster").(string) + provisionerName := d.Get("provisioner_name").(string) + + Namespace, err := client.GetNamespace(NamespaceName, clusterName, managementClusterName, provisionerName) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to read namespace", + Detail: fmt.Sprintf("Error reading resource %s: %s", d.Get("name"), err), + }) + return diags + } + + d.Set("description", Namespace.Meta.Description) + if err := d.Set("labels", Namespace.Meta.Labels); err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to read namespace", + Detail: fmt.Sprintf("Error setting labels for resource %s: %s", d.Get("name"), err), + }) + return diags + } + + d.Set("workspace_name", Namespace.Spec.WorkspaceName) + + return diags +} + +func resourceTmcNamespaceDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + + var diags diag.Diagnostics + + client := m.(*tanzuclient.Client) + + NamespaceName := d.Get("name").(string) + clusterName := d.Get("cluster_name").(string) + managementClusterName := d.Get("management_cluster").(string) + provisionerName := d.Get("provisioner_name").(string) + + err := client.DeleteNamespace(NamespaceName, clusterName, managementClusterName, provisionerName) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to delete namespace", + Detail: fmt.Sprintf("Error deleting resource %s: %s", d.Get("name"), err), + }) + return diags + } + + d.SetId("") + + return diags +} From dc02b8cadd557de46120846d6fcf9c2626a80327 Mon Sep 17 00:00:00 2001 From: Guru Prasad Date: Fri, 7 Jan 2022 13:16:49 +0530 Subject: [PATCH 2/2] Updated docs --- docs/data-sources/aws_nodepool.md | 10 ++--- docs/data-sources/namespace.md | 36 +++++++++++++++++ docs/resources/aws_nodepool.md | 4 +- docs/resources/namespace.md | 43 +++++++++++++++++++++ tanzuclient/{namespaces.go => namespace.go} | 0 5 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 docs/data-sources/namespace.md create mode 100644 docs/resources/namespace.md rename tanzuclient/{namespaces.go => namespace.go} (100%) diff --git a/docs/data-sources/aws_nodepool.md b/docs/data-sources/aws_nodepool.md index f532f5f..254c32c 100644 --- a/docs/data-sources/aws_nodepool.md +++ b/docs/data-sources/aws_nodepool.md @@ -1,12 +1,12 @@ --- -page_title: "TMC: tmc_nodepool" +page_title: "TMC: tmc_aws_nodepool" layout: "tmc" subcategory: "TKG Cluster" description: |- Get information on a specific nodepool of a AWS cluster in Tanzu Mission Control (TMC) --- -# Data Source: tmc_cluster +# Data Source: tmc_aws_nodepool The TMC Nodepool data resource can be used to get the information of a nodepool for a AWS cluster in Tanzu Mission Control (TMC). @@ -31,9 +31,9 @@ The following arguments are supported: ## Attributes Reference * `id` - The UID of the Tanzu Cluster. -* `description` - (Optional) The description of the nodepool. -* `node_labels` - (Optional) A map of node labels to assign to the resource. -* `cloud_labels` - (Optional) A map of cloud labels to assign to the resource. +* `description` - The description of the nodepool. +* `node_labels` - A map of node labels to assign to the resource. +* `cloud_labels` - A map of cloud labels to assign to the resource. * `availability_zone` - The AWS availability zone for the cluster's worker nodes. * `instance_type` - Instance type of the EC2 nodes to be used as part of the nodepool. * `version` - Version of Kubernetes to be used in the cluster. diff --git a/docs/data-sources/namespace.md b/docs/data-sources/namespace.md new file mode 100644 index 0000000..fa28962 --- /dev/null +++ b/docs/data-sources/namespace.md @@ -0,0 +1,36 @@ +--- +page_title: "TMC: tmc_namespace" +layout: "tmc" +subcategory: "Tanzu Namespace" +description: |- + Get information on a specific namespace of a cluster in Tanzu Mission Control (TMC) +--- + +# Data Source: tmc_namespace + +The TMC Namespace data resource can be used to get the information of a namespace for a cluster in Tanzu Mission Control (TMC). + +```terraform +data "tmc_namespace" "example" { + cluster_name = "example-cluster" + management_cluster = "example-hosted" + provisioner_name = "example-provisioner" + name = "example-ns" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the namespace. Changing the name forces recreation of this resource. +* `cluster_name` - (Required) The name of the Tanzu Cluster for which the namespace is to be created. +* `management_cluster` - (Required) Name of the management cluster used to provision the cluster. +* `provisioner_name` - (Required) Name of the provisioner to be used. + +## Attributes Reference + +* `id` - The UID of the Tanzu Cluster. +* `description` - The description of the nodepool. +* `workspace_name` - Name of the workspace for the created namespace. +* `labels` - A map of labels to assign to the resource. diff --git a/docs/resources/aws_nodepool.md b/docs/resources/aws_nodepool.md index 512059f..61c9ac5 100644 --- a/docs/resources/aws_nodepool.md +++ b/docs/resources/aws_nodepool.md @@ -1,12 +1,12 @@ --- -page_title: "TMC: tmc_nodepool" +page_title: "TMC: tmc_aws_nodepool" layout: "tmc" subcategory: "TKG Cluster" description: |- Creates and manages a nodepool for a AWS cluster in the TMC platform --- -# Resource: tmc_cluster +# Resource: tmc_aws_nodepool The TMC Cluster resource allows requesting the creation of a nodepool for a AWS cluster in Tanzu Mission Control (TMC). diff --git a/docs/resources/namespace.md b/docs/resources/namespace.md new file mode 100644 index 0000000..86b51ce --- /dev/null +++ b/docs/resources/namespace.md @@ -0,0 +1,43 @@ +--- +page_title: "TMC: tmc_namespace" +layout: "tmc" +subcategory: "Tanzu Namespace" +description: |- + Creates and manages a namespace for a cluster in the TMC platform +--- + +# Resource: tmc_namespace + +The TMC Namespace resource allows requesting the creation of a namespace for a cluster in Tanzu Mission Control (TMC). + +```terraform +resource "tmc_namespace" "example" { + name = "example-ns" + description = "terraform created mgmt cluster" + cluster_name = "example-cluster" + management_cluster = "example-hosted" + provisioner_name = "example-provisioner" + workspace_name = "default" + labels = { + "CreatedBy" = "terraform" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the Namespace. Changing the name forces recreation of this resource. +* `description` - (Optional) The description of the namespace. +* `cluster_name` - (Required) The name of the Tanzu Cluster for which the namespace is to be created. +* `management_cluster` - (Required) Name of the management cluster used to provision the cluster. +* `provisioner_name` - (Required) Name of the provisioner to be used. +* `workspace_name` - (Required) Name of the workspace for the created namespace. +* `labels` - (Optional) A map of labels to assign to the resource. + +## Attributes Reference + +In addition to all arguments above, the following attribute is exported: + +* `id` - The UID of the Tanzu Namespace. \ No newline at end of file diff --git a/tanzuclient/namespaces.go b/tanzuclient/namespace.go similarity index 100% rename from tanzuclient/namespaces.go rename to tanzuclient/namespace.go