From 349db98878222d4d78c63746be00011582dc38c2 Mon Sep 17 00:00:00 2001 From: fullykubed Date: Mon, 14 Oct 2024 08:08:43 -0400 Subject: [PATCH] feat: adds sanitization functions --- docs/data-sources/aws_tags.md | 4 +- docs/data-sources/kube_labels.md | 4 +- docs/functions/sanitize_aws_tags.md | 26 +++++++ docs/functions/sanitize_kube_labels.md | 26 +++++++ docs/functions/test.md | 26 ------- docs/index.md | 9 +-- provider/aws_tags_data_source.go | 45 ++++++----- provider/kube_labels_data_source.go | 60 +++++--------- provider/provider.go | 13 ++-- provider/sanitize_aws_tags_function.go | 71 +++++++++++++++++ provider/sanitize_kube_labels_function.go | 95 +++++++++++++++++++++++ provider/test_function.go | 60 -------------- 12 files changed, 275 insertions(+), 164 deletions(-) create mode 100644 docs/functions/sanitize_aws_tags.md create mode 100644 docs/functions/sanitize_kube_labels.md delete mode 100644 docs/functions/test.md create mode 100644 provider/sanitize_aws_tags_function.go create mode 100644 provider/sanitize_kube_labels_function.go delete mode 100644 provider/test_function.go diff --git a/docs/data-sources/aws_tags.md b/docs/data-sources/aws_tags.md index 01b6ce6..c2442c1 100644 --- a/docs/data-sources/aws_tags.md +++ b/docs/data-sources/aws_tags.md @@ -3,12 +3,12 @@ page_title: "pf_aws_tags Data Source - pf" subcategory: "" description: |- - Example data source + Provides the standard set of Panfactum resource tags for AWS resources --- # pf_aws_tags (Data Source) -Example data source +Provides the standard set of Panfactum resource tags for AWS resources diff --git a/docs/data-sources/kube_labels.md b/docs/data-sources/kube_labels.md index 016e724..3c4a8bf 100644 --- a/docs/data-sources/kube_labels.md +++ b/docs/data-sources/kube_labels.md @@ -3,12 +3,12 @@ page_title: "pf_kube_labels Data Source - pf" subcategory: "" description: |- - Example data source + Provides the standard set of Panfactum resource labels for Kubernetes resources --- # pf_kube_labels (Data Source) -Example data source +Provides the standard set of Panfactum resource labels for Kubernetes resources diff --git a/docs/functions/sanitize_aws_tags.md b/docs/functions/sanitize_aws_tags.md new file mode 100644 index 0000000..aa70ae8 --- /dev/null +++ b/docs/functions/sanitize_aws_tags.md @@ -0,0 +1,26 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "sanitize_aws_tags function - pf" +subcategory: "" +description: |- + Returns the AWS tags that have been sanitized of invalid characters +--- + +# function: sanitize_aws_tags + + + + + +## Signature + + +```text +sanitize_aws_tags(tags map of string) map of string +``` + +## Arguments + + +1. `tags` (Map of String) The AWS tags to sanitize + diff --git a/docs/functions/sanitize_kube_labels.md b/docs/functions/sanitize_kube_labels.md new file mode 100644 index 0000000..a2b7f04 --- /dev/null +++ b/docs/functions/sanitize_kube_labels.md @@ -0,0 +1,26 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "sanitize_kube_labels function - pf" +subcategory: "" +description: |- + Returns the Kubernetes labels that have been sanitized of invalid characters +--- + +# function: sanitize_kube_labels + + + + + +## Signature + + +```text +sanitize_kube_labels(labels map of string) map of string +``` + +## Arguments + + +1. `labels` (Map of String) The Kubernetes labels to sanitize + diff --git a/docs/functions/test.md b/docs/functions/test.md deleted file mode 100644 index 688814a..0000000 --- a/docs/functions/test.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "test function - pf" -subcategory: "" -description: |- - Checks whether a string is lowercased ---- - -# function: test - - - - - -## Signature - - -```text -test(string string) bool -``` - -## Arguments - - -1. `string` (String) The string to check - diff --git a/docs/index.md b/docs/index.md index 593220d..599cb3b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -15,15 +15,12 @@ description: |- ## Schema -### Required +### Optional - `environment` (String) The name of the environment that you are currently deploying infrastructure to +- `extra_tags` (Map of String) Extra tags to apply to all resources +- `is_local` (Boolean) Whether the provider is being used a part of a local development deployment - `region` (String) The name of the region that you are currently deploying infrastructure to - `root_module` (String) The name of the root / top-level module that you are currently deploying infrastructure with - `stack_commit` (String) The commit hash of the Panfactum Stack that you are currently using - `stack_version` (String) The version of the Panfactum Stack that you are currently using - -### Optional - -- `extra_tags` (Map of String) Extra tags to apply to all resources -- `is_local` (Boolean) Whether the provider is being used a part of a local development deployment diff --git a/provider/aws_tags_data_source.go b/provider/aws_tags_data_source.go index d5ad3d1..b62c922 100644 --- a/provider/aws_tags_data_source.go +++ b/provider/aws_tags_data_source.go @@ -1,5 +1,5 @@ // Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 +// SPDX-License-Identifier: Apache-2.0 package provider @@ -10,7 +10,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/types" - "regexp" ) /************************************************************** @@ -39,7 +38,8 @@ func (d *awsTagsDataSource) Metadata(ctx context.Context, req datasource.Metadat func (d *awsTagsDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { resp.Schema = schema.Schema{ - MarkdownDescription: "Example data source", + Description: "Provides the standard set of Panfactum resource tags for AWS resources", + MarkdownDescription: "Provides the standard set of Panfactum resource tags for AWS resources", Attributes: map[string]schema.Attribute{ "tags": schema.MapAttribute{ @@ -91,26 +91,29 @@ func (d *awsTagsDataSource) Read(ctx context.Context, req datasource.ReadRequest return } - labels := map[string]attr.Value{ - "panfactum.com/environment": sanitizeAWSTagValue(d.ProviderData.Environment), - "panfactum.com/stack-version": sanitizeAWSTagValue(d.ProviderData.StackVersion), - "panfactum.com/stack-commit": sanitizeAWSTagValue(d.ProviderData.StackCommit), - "panfactum.com/local": types.StringValue(d.ProviderData.IsLocal.String()), - "panfactum.com/root-module": sanitizeAWSTagValue(d.ProviderData.RootModule), - "panfactum.com/module": sanitizeAWSTagValue(data.Module), + tags := map[string]attr.Value{ + "panfactum.com/local": types.StringValue(d.ProviderData.IsLocal.String()), } + // Set the default tags from the provider + setAWSTag(tags, "panfactum.com/environment", d.ProviderData.Environment) + setAWSTag(tags, "panfactum.com/stack-version", d.ProviderData.StackVersion) + setAWSTag(tags, "panfactum.com/stack-commit", d.ProviderData.StackCommit) + setAWSTag(tags, "panfactum.com/root-module", d.ProviderData.RootModule) + setAWSTag(tags, "panfactum.com/module", data.Module) + // Allow the region to be overridden - trueRegion := d.ProviderData.Region + var region = d.ProviderData.Region if !data.RegionOverride.IsNull() && !data.RegionOverride.IsUnknown() { - trueRegion = data.RegionOverride + region = data.RegionOverride } - labels["panfactum.com/region"] = sanitizeAWSTagValue(trueRegion) + setAWSTag(tags, "panfactum.com/region", region) + // Iterate over the extra tags and set them one-by-one for key, value := range (d.ProviderData.ExtraTags).Elements() { strValue, ok := value.(types.String) if ok { - labels[sanitizeAWSTagKey(key)] = sanitizeAWSTagValue(strValue) + setAWSTag(tags, key, strValue) } else { resp.Diagnostics.AddError( "Invalid type found", @@ -120,7 +123,7 @@ func (d *awsTagsDataSource) Read(ctx context.Context, req datasource.ReadRequest } } - data.Tags, _ = types.MapValue(types.StringType, labels) + data.Tags, _ = types.MapValue(types.StringType, tags) // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) @@ -130,12 +133,12 @@ func (d *awsTagsDataSource) Read(ctx context.Context, req datasource.ReadRequest Utility Functions **************************************************************/ -func sanitizeAWSTagKey(input string) string { - re := regexp.MustCompile(`[^a-zA-Z0-9.:/_@+=-]`) - return re.ReplaceAllString(input, ".") +func setAWSTag(tags map[string]attr.Value, key string, value types.String) { + if !value.IsNull() && !value.IsUnknown() { + tags[sanitizeAWSTagKey(key)] = sanitizeAWSTagValueWrapped(value) + } } -func sanitizeAWSTagValue(input types.String) types.String { - re := regexp.MustCompile(`[^a-zA-Z0-9.:/_@+=-]`) - return types.StringValue(re.ReplaceAllString(input.ValueString(), ".")) +func sanitizeAWSTagValueWrapped(input types.String) types.String { + return types.StringValue(sanitizeAWSTagValue(input.ValueString())) } diff --git a/provider/kube_labels_data_source.go b/provider/kube_labels_data_source.go index e8df2b3..8c91e94 100644 --- a/provider/kube_labels_data_source.go +++ b/provider/kube_labels_data_source.go @@ -1,5 +1,5 @@ // Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 +// SPDX-License-Identifier: Apache-2.0 package provider @@ -10,9 +10,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/types" - "regexp" - "strings" - "unicode" ) /************************************************************** @@ -40,7 +37,8 @@ func (d *kubeLabelsDataSource) Metadata(ctx context.Context, req datasource.Meta func (d *kubeLabelsDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { resp.Schema = schema.Schema{ - MarkdownDescription: "Example data source", + Description: "Provides the standard set of Panfactum resource labels for Kubernetes resources", + MarkdownDescription: "Provides the standard set of Panfactum resource labels for Kubernetes resources", Attributes: map[string]schema.Attribute{ "labels": schema.MapAttribute{ @@ -88,19 +86,21 @@ func (d *kubeLabelsDataSource) Read(ctx context.Context, req datasource.ReadRequ } labels := map[string]attr.Value{ - "panfactum.com/environment": sanitizeKubeLabelValue(d.ProviderData.Environment), - "panfactum.com/region": sanitizeKubeLabelValue(d.ProviderData.Region), - "panfactum.com/stack-version": sanitizeKubeLabelValue(d.ProviderData.StackVersion), - "panfactum.com/stack-commit": sanitizeKubeLabelValue(d.ProviderData.StackCommit), - "panfactum.com/local": types.StringValue(d.ProviderData.IsLocal.String()), - "panfactum.com/root-module": sanitizeKubeLabelValue(d.ProviderData.RootModule), - "panfactum.com/module": sanitizeKubeLabelValue(data.Module), + "panfactum.com/local": types.StringValue(d.ProviderData.IsLocal.String()), } + // Set the default labels from the provider + setKubeLabel(labels, "panfactum.com/environment", d.ProviderData.Environment) + setKubeLabel(labels, "panfactum.com/region", d.ProviderData.Region) + setKubeLabel(labels, "panfactum.com/stack-version", d.ProviderData.StackVersion) + setKubeLabel(labels, "panfactum.com/stack-commit", d.ProviderData.StackCommit) + setKubeLabel(labels, "panfactum.com/root-module", d.ProviderData.RootModule) + setKubeLabel(labels, "panfactum.com/module", data.Module) + for key, value := range (d.ProviderData.ExtraTags).Elements() { strValue, ok := value.(types.String) if ok { - labels[sanitizeKubeLabelKey(key)] = sanitizeKubeLabelValue(strValue) + labels[sanitizeKubeLabelKey(key)] = sanitizeKubeLabelValueWrapped(strValue) } else { resp.Diagnostics.AddError( "Invalid type found", @@ -120,34 +120,12 @@ func (d *kubeLabelsDataSource) Read(ctx context.Context, req datasource.ReadRequ Utility Functions **************************************************************/ -// sanitizeKubeLabelValue performs the required sanitization steps: -// 1. Replaces any non-alphanumeric, '.', '_', or '-' characters with '.' -// 2. Ensures the string starts and ends with an alphanumeric character -func sanitizeKubeLabelValue(input types.String) types.String { - // Replace any non-alphanumeric, '.', '_', or '-' characters with '.' - re := regexp.MustCompile(`[^a-zA-Z0-9._-]`) - sanitized := re.ReplaceAllString(input.ValueString(), ".") - - // Trim any leading or trailing non-alphanumeric characters - sanitized = strings.TrimFunc(sanitized, func(r rune) bool { - return !unicode.IsLetter(r) && !unicode.IsNumber(r) - }) - - return types.StringValue(sanitized) +func setKubeLabel(tags map[string]attr.Value, key string, value types.String) { + if !value.IsNull() && !value.IsUnknown() { + tags[sanitizeKubeLabelKey(key)] = sanitizeKubeLabelValueWrapped(value) + } } -// sanitizeKubeLabelKey performs the required sanitization steps: -// 1. Replaces any non-alphanumeric, '.', '_', '-', or '/' characters with '.' -// 2. Ensures the string starts and ends with an alphanumeric character -func sanitizeKubeLabelKey(input string) string { - // Replace any non-alphanumeric, '.', '_', '-', or '/' characters with '.' - re := regexp.MustCompile(`[^a-zA-Z0-9._/-]`) - sanitized := re.ReplaceAllString(input, ".") - - // Trim any leading or trailing non-alphanumeric characters - sanitized = strings.TrimFunc(sanitized, func(r rune) bool { - return !unicode.IsLetter(r) && !unicode.IsNumber(r) - }) - - return sanitized +func sanitizeKubeLabelValueWrapped(input types.String) types.String { + return types.StringValue(sanitizeKubeLabelValue(input.ValueString())) } diff --git a/provider/provider.go b/provider/provider.go index b1878d4..039bc2f 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -38,27 +38,27 @@ func (p *PanfactumProvider) Schema(ctx context.Context, req provider.SchemaReque resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ "environment": schema.StringAttribute{ - Required: true, + Optional: true, Description: "The name of the environment that you are currently deploying infrastructure to", MarkdownDescription: "The name of the environment that you are currently deploying infrastructure to", }, "region": schema.StringAttribute{ - Required: true, + Optional: true, Description: "The name of the region that you are currently deploying infrastructure to", MarkdownDescription: "The name of the region that you are currently deploying infrastructure to", }, "root_module": schema.StringAttribute{ - Required: true, + Optional: true, Description: "The name of the root / top-level module that you are currently deploying infrastructure with", MarkdownDescription: "The name of the root / top-level module that you are currently deploying infrastructure with", }, "stack_version": schema.StringAttribute{ - Required: true, + Optional: true, Description: "The version of the Panfactum Stack that you are currently using", MarkdownDescription: "The version of the Panfactum Stack that you are currently using", }, "stack_commit": schema.StringAttribute{ - Required: true, + Optional: true, Description: "The commit hash of the Panfactum Stack that you are currently using", MarkdownDescription: "The commit hash of the Panfactum Stack that you are currently using", }, @@ -96,6 +96,7 @@ func (p *PanfactumProvider) DataSources(ctx context.Context) []func() datasource func (p *PanfactumProvider) Functions(ctx context.Context) []func() function.Function { return []func() function.Function{ - NewLowercasedFunction, + NewSanitizeAWSTagsFunction, + NewSanitizeKubeLabelsFunction, } } diff --git a/provider/sanitize_aws_tags_function.go b/provider/sanitize_aws_tags_function.go new file mode 100644 index 0000000..2d8320a --- /dev/null +++ b/provider/sanitize_aws_tags_function.go @@ -0,0 +1,71 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package provider + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/function" + "github.com/hashicorp/terraform-plugin-framework/types" + "regexp" +) + +var ( + _ function.Function = SanitizeAWSTagsFunction{} +) + +func NewSanitizeAWSTagsFunction() function.Function { + return SanitizeAWSTagsFunction{} +} + +type SanitizeAWSTagsFunction struct{} + +func (f SanitizeAWSTagsFunction) Metadata(_ context.Context, req function.MetadataRequest, resp *function.MetadataResponse) { + resp.Name = "sanitize_aws_tags" +} + +func (f SanitizeAWSTagsFunction) Definition(_ context.Context, _ function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + Summary: "Returns the AWS tags that have been sanitized of invalid characters", + Parameters: []function.Parameter{ + function.MapParameter{ + AllowNullValue: false, + AllowUnknownValues: false, + Description: "The AWS tags to sanitize", + Name: "tags", + ElementType: types.StringType, + }, + }, + Return: function.MapReturn{ + ElementType: types.StringType, + }, + } +} + +func (f SanitizeAWSTagsFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var tags map[string]string + + resp.Error = function.ConcatFuncErrors(req.Arguments.Get(ctx, &tags)) + if resp.Error != nil { + return + + } + + sanitizedTags := map[string]string{} + + for k, v := range tags { + sanitizedTags[sanitizeAWSTagKey(k)] = sanitizeAWSTagValue(v) + } + + resp.Error = function.ConcatFuncErrors(resp.Result.Set(ctx, sanitizedTags)) +} + +func sanitizeAWSTagKey(input string) string { + re := regexp.MustCompile(`[^a-zA-Z0-9.:/_@+=-]`) + return re.ReplaceAllString(input, ".") +} + +func sanitizeAWSTagValue(input string) string { + re := regexp.MustCompile(`[^a-zA-Z0-9.:/_@+=-]`) + return re.ReplaceAllString(input, ".") +} diff --git a/provider/sanitize_kube_labels_function.go b/provider/sanitize_kube_labels_function.go new file mode 100644 index 0000000..62df591 --- /dev/null +++ b/provider/sanitize_kube_labels_function.go @@ -0,0 +1,95 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package provider + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/function" + "github.com/hashicorp/terraform-plugin-framework/types" + "regexp" + "strings" + "unicode" +) + +var ( + _ function.Function = SanitizeKubeLabelsFunction{} +) + +func NewSanitizeKubeLabelsFunction() function.Function { + return SanitizeKubeLabelsFunction{} +} + +type SanitizeKubeLabelsFunction struct{} + +func (f SanitizeKubeLabelsFunction) Metadata(_ context.Context, req function.MetadataRequest, resp *function.MetadataResponse) { + resp.Name = "sanitize_kube_labels" +} + +func (f SanitizeKubeLabelsFunction) Definition(_ context.Context, _ function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + Summary: "Returns the Kubernetes labels that have been sanitized of invalid characters", + Parameters: []function.Parameter{ + function.MapParameter{ + AllowNullValue: false, + AllowUnknownValues: false, + Description: "The Kubernetes labels to sanitize", + Name: "labels", + ElementType: types.StringType, + }, + }, + Return: function.MapReturn{ + ElementType: types.StringType, + }, + } +} + +func (f SanitizeKubeLabelsFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var labels map[string]string + + resp.Error = function.ConcatFuncErrors(req.Arguments.Get(ctx, &labels)) + if resp.Error != nil { + return + + } + + sanitizedLabels := map[string]string{} + + for k, v := range labels { + sanitizedLabels[sanitizeKubeLabelKey(k)] = sanitizeKubeLabelValue(v) + } + + resp.Error = function.ConcatFuncErrors(resp.Result.Set(ctx, sanitizedLabels)) +} + +// sanitizeKubeLabelValue performs the required sanitization steps: +// 1. Replaces any non-alphanumeric, '.', '_', or '-' characters with '.' +// 2. Ensures the string starts and ends with an alphanumeric character +func sanitizeKubeLabelValue(input string) string { + // Replace any non-alphanumeric, '.', '_', or '-' characters with '.' + re := regexp.MustCompile(`[^a-zA-Z0-9._-]`) + sanitized := re.ReplaceAllString(input, ".") + + // Trim any leading or trailing non-alphanumeric characters + sanitized = strings.TrimFunc(sanitized, func(r rune) bool { + return !unicode.IsLetter(r) && !unicode.IsNumber(r) + }) + + return sanitized +} + +// sanitizeKubeLabelKey performs the required sanitization steps: +// 1. Replaces any non-alphanumeric, '.', '_', '-', or '/' characters with '.' +// 2. Ensures the string starts and ends with an alphanumeric character +func sanitizeKubeLabelKey(input string) string { + // Replace any non-alphanumeric, '.', '_', '-', or '/' characters with '.' + re := regexp.MustCompile(`[^a-zA-Z0-9._/-]`) + sanitized := re.ReplaceAllString(input, ".") + + // Trim any leading or trailing non-alphanumeric characters + sanitized = strings.TrimFunc(sanitized, func(r rune) bool { + return !unicode.IsLetter(r) && !unicode.IsNumber(r) + }) + + return sanitized +} diff --git a/provider/test_function.go b/provider/test_function.go deleted file mode 100644 index 4c51c11..0000000 --- a/provider/test_function.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package provider - -import ( - "context" - "unicode" - - "github.com/hashicorp/terraform-plugin-framework/function" -) - -var ( - _ function.Function = LowercasedFunction{} -) - -func NewLowercasedFunction() function.Function { - return LowercasedFunction{} -} - -type LowercasedFunction struct{} - -func (r LowercasedFunction) Metadata(_ context.Context, req function.MetadataRequest, resp *function.MetadataResponse) { - resp.Name = "test" -} - -func (r LowercasedFunction) Definition(_ context.Context, _ function.DefinitionRequest, resp *function.DefinitionResponse) { - resp.Definition = function.Definition{ - Summary: "Checks whether a string is lowercased", - Parameters: []function.Parameter{ - function.StringParameter{ - AllowNullValue: false, - AllowUnknownValues: false, - Description: "The string to check", - Name: "string", - }, - }, - Return: function.BoolReturn{}, - } -} - -func (r LowercasedFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { - var s string - - resp.Error = function.ConcatFuncErrors(req.Arguments.Get(ctx, &s)) - if resp.Error != nil { - return - } - - resp.Error = function.ConcatFuncErrors(resp.Result.Set(ctx, isLower(s))) -} - -func isLower(s string) bool { - for _, r := range s { - if !unicode.IsLower(r) && unicode.IsLetter(r) { - return false - } - } - return true -}