-
Notifications
You must be signed in to change notification settings - Fork 1k
Adding initial configuration for the new resource #2505
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
9570a03
Adding initial configuration for the new resource
056f9e8
Adding the Update and delte functions, for the new resource. Created …
4d1ced6
Added field_manager attribute to the schema
2c6b9c6
Adding field_manager to the schema, and removing the unncessary valid…
jaylonmcshan19-x 996c0a7
Merge branch 'main' into secretV1Data
BBBmau d4eead4
Current state of resource
jaylonmcshan19-x f55ea0d
Adding changelog entry for the new resource
jaylonmcshan19-x 15baf34
Adding documentation for the secret_v1_data resource
jaylonmcshan19-x 8d44021
Adding license headers for new resource / test file
jaylonmcshan19-x cda8cba
Updated test handling field manager conflict error
jaylonmcshan19-x 0594e2f
Merge branch 'main' into secretV1Data
BBBmau 3269a9e
cleanup
BBBmau File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| ```release-note:enhancement | ||
| Adding the `kubernetes_secret_v1_data` resource to the kubernetes provider. This resource will allow users to manage kubernetes secrets | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| --- | ||
| subcategory: "core/v1" | ||
| page_title: "Kubernetes: kubernetes_secret_v1_data" | ||
| description: |- | ||
| This resource allows Terraform to manage the data for a Secret that already exists. | ||
| --- | ||
|
|
||
| # kubernetes_secret_v1_data | ||
|
|
||
| This resource allows Terraform to manage data within a pre-existing Secret. This resource uses [field management](https://kubernetes.io/docs/reference/using-api/server-side-apply/#field-management) and [server-side apply](https://kubernetes.io/docs/reference/using-api/server-side-apply/) to manage only the data that is defined in the Terraform configuration. Existing data not specified in the configuration will be ignored. If data specified in the config is already managed by another client, it will cause a conflict which can be overridden by setting `force` to true. | ||
|
|
||
| <!-- schema generated by tfplugindocs --> | ||
| ## Schema | ||
|
|
||
| ### Required | ||
|
|
||
| - `data` (Map of String) The data we want to add to the Secret. | ||
| - `metadata` (Block List, Min: 1, Max: 1) (see [below for nested schema](#nestedblock--metadata)) | ||
|
|
||
| ### Optional | ||
|
|
||
| - `field_manager` (String) Set the name of the field manager for the specified labels. | ||
| - `force` (Boolean) Force overwriting data that is managed outside of Terraform. | ||
|
|
||
| ### Read-Only | ||
|
|
||
| - `id` (String) The ID of this resource. | ||
|
|
||
| <a id="nestedblock--metadata"></a> | ||
| ### Nested Schema for `metadata` | ||
|
|
||
| Required: | ||
|
|
||
| - `name` (String) The name of the Secret. | ||
|
|
||
| Optional: | ||
|
|
||
| - `namespace` (String) The namespace of the Secret. | ||
|
|
||
| ## Example Usage | ||
|
|
||
| ```terraform | ||
| resource "kubernetes_secret_v1_data" "example" { | ||
| metadata { | ||
| name = "my-secret" | ||
| } | ||
| data = { | ||
| "username" = "admin" | ||
| "password" = "s3cr3t" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Import | ||
|
|
||
| This resource does not support the `import` command. As this resource operates on Kubernetes resources that already exist, creating the resource is equivalent to importing it. | ||
|
|
||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,249 @@ | ||
| // Copyright (c) HashiCorp, Inc. | ||
| // SPDX-License-Identifier: MPL-2.0 | ||
|
|
||
| package kubernetes | ||
|
|
||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
|
|
||
| "github.com/hashicorp/terraform-plugin-sdk/v2/diag" | ||
| "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
|
|
||
| "k8s.io/apimachinery/pkg/api/errors" | ||
| v1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||
| "k8s.io/apimachinery/pkg/types" | ||
| "k8s.io/utils/ptr" | ||
| ) | ||
|
|
||
| func resourceKubernetesSecretV1Data() *schema.Resource { | ||
| return &schema.Resource{ | ||
| CreateContext: resourceKubernetesSecretV1DataCreate, | ||
| ReadContext: resourceKubernetesSecretV1DataRead, | ||
| UpdateContext: resourceKubernetesSecretV1DataUpdate, | ||
| DeleteContext: resourceKubernetesSecretV1DataDelete, | ||
|
|
||
| Schema: map[string]*schema.Schema{ | ||
| "metadata": { | ||
| Type: schema.TypeList, | ||
| Description: "Metadata for the kubernetes Secret.", | ||
| Required: true, | ||
| MaxItems: 1, | ||
| Elem: &schema.Resource{ | ||
| Schema: map[string]*schema.Schema{ | ||
| "name": { | ||
| Type: schema.TypeString, | ||
| Description: "The name of the Secret.", | ||
| Required: true, | ||
| ForceNew: true, | ||
| }, | ||
| "namespace": { | ||
| Type: schema.TypeString, | ||
| Description: "The namespace of the Secret.", | ||
| Optional: true, | ||
| ForceNew: true, | ||
| Default: "default", | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| "data": { | ||
| Type: schema.TypeMap, | ||
| Description: "Data to be stored in the Kubernetes Secret.", | ||
| Required: true, | ||
| Elem: &schema.Schema{ | ||
| Type: schema.TypeString, | ||
| }, | ||
| }, | ||
| "force": { | ||
| Type: schema.TypeBool, | ||
| Description: "Flag to force updates to the Kubernetes Secret.", | ||
| Optional: true, | ||
| }, | ||
| "field_manager": { | ||
| Type: schema.TypeString, | ||
| Description: "Set the name of the field manager for the specified labels", | ||
| Optional: true, | ||
| Default: defaultFieldManagerName, | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| func resourceKubernetesSecretV1DataCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { | ||
| metadata := expandMetadata(d.Get("metadata").([]interface{})) | ||
| // Sets the resource id based on the metadata | ||
| d.SetId(buildId(metadata)) | ||
|
|
||
| //Calling the update function ensuring resource config is correct | ||
| diag := resourceKubernetesSecretV1DataUpdate(ctx, d, m) | ||
| if diag.HasError() { | ||
| d.SetId("") | ||
| } | ||
| return diag | ||
| } | ||
|
|
||
| // Retrieves the current state of the k8s secret, and update the current sate | ||
| func resourceKubernetesSecretV1DataRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { | ||
| conn, err := m.(KubeClientsets).MainClientset() | ||
| if err != nil { | ||
| return diag.FromErr(err) | ||
| } | ||
|
|
||
| namespace, name, err := idParts(d.Id()) | ||
| if err != nil { | ||
| return diag.FromErr(err) | ||
| } | ||
|
|
||
| // getting the secret data | ||
| res, err := conn.CoreV1().Secrets(namespace).Get(ctx, name, v1.GetOptions{}) | ||
| if err != nil { | ||
| if errors.IsNotFound(err) { | ||
| return diag.Diagnostics{{ | ||
| Severity: diag.Warning, | ||
| Summary: "Secret deleted", | ||
| Detail: fmt.Sprintf("The underlying secret %q has been deleted. You should recreate the underlying secret, or remove it from your configuration.", name), | ||
| }} | ||
| } | ||
| return diag.FromErr(err) | ||
| } | ||
|
|
||
| configuredData := d.Get("data").(map[string]interface{}) | ||
|
|
||
| // stripping out the data not managed by Terraform | ||
| fieldManagerName := d.Get("field_manager").(string) | ||
|
|
||
| managedSecretData, err := getManagedSecretData(res.GetManagedFields(), fieldManagerName) | ||
| if err != nil { | ||
| return diag.FromErr(err) | ||
| } | ||
| data := res.Data | ||
| for k := range data { | ||
| _, managed := managedSecretData["f:"+k] | ||
| _, configured := configuredData[k] | ||
| if !managed && !configured { | ||
| delete(data, k) | ||
| } | ||
|
|
||
| } | ||
| decodedData := make(map[string]string, len(data)) | ||
| for k, v := range data { | ||
| decodedData[k] = string(v) | ||
| } | ||
|
|
||
| d.Set("data", decodedData) | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // getManagedSecretData reads the field manager metadata to discover which fields we're managing | ||
| func getManagedSecretData(managedFields []v1.ManagedFieldsEntry, manager string) (map[string]interface{}, error) { | ||
| var data map[string]interface{} | ||
| for _, m := range managedFields { | ||
| // Only consider entries managed by the specified manager | ||
| if m.Manager != manager { | ||
| continue | ||
| } | ||
| var mm map[string]interface{} | ||
| err := json.Unmarshal(m.FieldsV1.Raw, &mm) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| // Check if the "data" field exists and extract it | ||
| if l, ok := mm["f:data"].(map[string]interface{}); ok { | ||
| data = l | ||
| } | ||
| } | ||
| return data, nil | ||
| } | ||
|
|
||
| func resourceKubernetesSecretV1DataUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { | ||
| conn, err := m.(KubeClientsets).MainClientset() | ||
| if err != nil { | ||
| return diag.FromErr(err) | ||
| } | ||
|
|
||
| metadata := expandMetadata(d.Get("metadata").([]interface{})) | ||
| name := metadata.GetName() | ||
| namespace := metadata.GetNamespace() | ||
|
|
||
| _, err = conn.CoreV1().Secrets(namespace).Get(ctx, name, v1.GetOptions{}) | ||
| if err != nil { | ||
JaylonmcShan03 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if d.Id() == "" { | ||
| // If we are deleting then there is nothing to do if the resource is gone | ||
| return nil | ||
| } | ||
| if statusErr, ok := err.(*errors.StatusError); ok && errors.IsNotFound(statusErr) { | ||
| return diag.Errorf("The Secret %q does not exist", name) | ||
| } | ||
| return diag.Errorf("Have got the following error while validating the existence of the Secret %q: %v", name, err) | ||
| } | ||
|
|
||
| // Craft the patch to update the data | ||
| dataInterface := d.Get("data") | ||
| data, ok := dataInterface.(map[string]interface{}) | ||
| if !ok { | ||
| return diag.Errorf("Error casting data to map[string]interface{}") | ||
| } | ||
| if d.Id() == "" { | ||
| // If we're deleting then we just patch with an empty data map | ||
| data = map[string]interface{}{} | ||
| } | ||
|
|
||
| encodedData := make(map[string][]byte, len(data)) | ||
| for k, v := range data { | ||
| encodedData[k] = []byte(v.(string)) | ||
| } | ||
|
|
||
| patchobj := map[string]interface{}{ | ||
| "apiVersion": "v1", | ||
| "kind": "Secret", | ||
| "metadata": map[string]interface{}{ | ||
| "name": name, | ||
| "namespace": namespace, | ||
| }, | ||
| "data": encodedData, | ||
| } | ||
| patch := unstructured.Unstructured{} | ||
| patch.Object = patchobj | ||
| patchbytes, err := patch.MarshalJSON() | ||
| if err != nil { | ||
| return diag.FromErr(err) | ||
| } | ||
|
|
||
| // Apply the patch | ||
| _, err = conn.CoreV1().Secrets(namespace).Patch(ctx, | ||
| name, | ||
| types.ApplyPatchType, | ||
| patchbytes, | ||
| v1.PatchOptions{ | ||
| FieldManager: d.Get("field_manager").(string), | ||
JaylonmcShan03 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Force: ptr.To(d.Get("force").(bool)), | ||
| }, | ||
| ) | ||
| if err != nil { | ||
| if errors.IsConflict(err) { | ||
| return diag.Diagnostics{{ | ||
| Severity: diag.Error, | ||
| Summary: "Field manager conflict", | ||
| Detail: fmt.Sprintf("Another client is managing a field Terraform tried to update. Set 'force' to true to override: %v", err), | ||
| }} | ||
| } | ||
| return diag.FromErr(err) | ||
| } | ||
|
|
||
| if d.Id() == "" { | ||
| return nil | ||
| } | ||
|
|
||
| return resourceKubernetesSecretV1DataRead(ctx, d, m) | ||
| } | ||
|
|
||
| func resourceKubernetesSecretV1DataDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { | ||
| // sets resource id to an empty. Simulating the deletion. | ||
| d.SetId("") | ||
| // Now we are calling the update function, to update the resource state | ||
| return resourceKubernetesSecretV1DataUpdate(ctx, d, m) | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.