From ed03acd5a23b20f948d1795a77b507fab5c4aab7 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Sat, 6 Sep 2025 00:12:01 +0800 Subject: [PATCH] feat: custom k8s secret annotations and labels --- .../deployers/sp_kubernetes_secret.go | 47 +++++++++++ .../providers/k8s-secret/k8s_secret.go | 23 ++++++ ...deConfigFieldsProviderKubernetesSecret.tsx | 79 +++++++++++++++++++ .../i18n/locales/en/nls.workflow.nodes.json | 10 +++ .../i18n/locales/zh/nls.workflow.nodes.json | 10 +++ 5 files changed, 169 insertions(+) diff --git a/internal/certdeploy/deployers/sp_kubernetes_secret.go b/internal/certdeploy/deployers/sp_kubernetes_secret.go index 85d6fc051..8fe565744 100644 --- a/internal/certdeploy/deployers/sp_kubernetes_secret.go +++ b/internal/certdeploy/deployers/sp_kubernetes_secret.go @@ -2,6 +2,7 @@ package deployers import ( "fmt" + "strings" "github.com/certimate-go/certimate/internal/domain" "github.com/certimate-go/certimate/pkg/core" @@ -16,6 +17,50 @@ func init() { return nil, fmt.Errorf("failed to populate provider access config: %w", err) } + parseKeyValueMap := func(s string) (map[string]string, error) { + result := make(map[string]string) + + lines := strings.Split(s, "\n") + for i, line := range lines { + if strings.TrimSpace(line) == "" { + continue + } + + pos := strings.Index(line, ":") + if pos == -1 { + return nil, fmt.Errorf("invalid line format at line %d", i+1) + } + + key := strings.TrimSpace(line[:pos]) + value := strings.TrimSpace(line[pos+1:]) + if key == "" { + return nil, fmt.Errorf("invalid key at line %d", i+1) + } + + result[key] = value + } + + return result, nil + } + + secretAnnotations := make(map[string]string) + if secretAnnotationsString := xmaps.GetString(options.ProviderExtendedConfig, "secretAnnotations"); secretAnnotationsString != "" { + temp, err := parseKeyValueMap(secretAnnotationsString) + if err != nil { + return nil, fmt.Errorf("failed to parse kubernetes secret annotations: %w", err) + } + secretAnnotations = temp + } + + secretLabels := make(map[string]string) + if secretLabelsString := xmaps.GetString(options.ProviderExtendedConfig, "secretLabels"); secretLabelsString != "" { + temp, err := parseKeyValueMap(secretLabelsString) + if err != nil { + return nil, fmt.Errorf("failed to parse kubernetes secret labels: %w", err) + } + secretLabels = temp + } + provider, err := k8ssecret.NewSSLDeployerProvider(&k8ssecret.SSLDeployerProviderConfig{ KubeConfig: credentials.KubeConfig, Namespace: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "namespace", "default"), @@ -23,6 +68,8 @@ func init() { SecretType: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "secretType", "kubernetes.io/tls"), SecretDataKeyForCrt: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "secretDataKeyForCrt", "tls.crt"), SecretDataKeyForKey: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "secretDataKeyForKey", "tls.key"), + SecretAnnotations: secretAnnotations, + SecretLabels: secretLabels, }) return provider, err }); err != nil { diff --git a/pkg/core/ssl-deployer/providers/k8s-secret/k8s_secret.go b/pkg/core/ssl-deployer/providers/k8s-secret/k8s_secret.go index e9a166836..b90db949f 100644 --- a/pkg/core/ssl-deployer/providers/k8s-secret/k8s_secret.go +++ b/pkg/core/ssl-deployer/providers/k8s-secret/k8s_secret.go @@ -30,6 +30,10 @@ type SSLDeployerProviderConfig struct { SecretDataKeyForCrt string `json:"secretDataKeyForCrt,omitempty"` // Kubernetes Secret 中用于存放私钥的 Key。 SecretDataKeyForKey string `json:"secretDataKeyForKey,omitempty"` + // Kubernetes Secret 注解。 + SecretAnnotations map[string]string `json:"secretAnnotations,omitempty"` + // Kubernetes Secret 标签。 + SecretLabels map[string]string `json:"secretLabels,omitempty"` } type SSLDeployerProvider struct { @@ -94,6 +98,17 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke "certimate/issuer-sn": certX509.Issuer.SerialNumber, "certimate/issuer-org": strings.Join(certX509.Issuer.Organization, ","), } + secretLabels := map[string]string{} + if d.config.SecretAnnotations != nil { + for k, v := range d.config.SecretAnnotations { + secretAnnotations[k] = v + } + } + if d.config.SecretLabels != nil { + for k, v := range d.config.SecretLabels { + secretLabels[k] = v + } + } // 获取 Secret 实例,如果不存在则创建 secretPayload, err = client.CoreV1().Secrets(d.config.Namespace).Get(context.TODO(), d.config.SecretName, k8smeta.GetOptions{}) @@ -106,6 +121,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke ObjectMeta: k8smeta.ObjectMeta{ Name: d.config.SecretName, Annotations: secretAnnotations, + Labels: secretLabels, }, Type: k8score.SecretType(d.config.SecretType), } @@ -131,6 +147,13 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke secretPayload.ObjectMeta.Annotations[k] = v } } + if secretPayload.ObjectMeta.Labels == nil { + secretPayload.ObjectMeta.Labels = secretLabels + } else { + for k, v := range secretLabels { + secretPayload.ObjectMeta.Labels[k] = v + } + } if secretPayload.Data == nil { secretPayload.Data = make(map[string][]byte) } diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderKubernetesSecret.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderKubernetesSecret.tsx index 6730b4d81..c6859b1a6 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderKubernetesSecret.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderKubernetesSecret.tsx @@ -3,6 +3,8 @@ import { Form, Input } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; +import CodeInput from "@/components/CodeInput"; + import { useFormNestedFieldsContext } from "./_context"; const BizDeployNodeConfigFieldsProviderKubernetesSecret = () => { @@ -13,8 +15,23 @@ const BizDeployNodeConfigFieldsProviderKubernetesSecret = () => { [parentNamePath]: getSchema({ i18n }), }); const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); const initialValues = getInitialValues(); + const handleSecretAnnotationsBlur = () => { + let value = formInst.getFieldValue([parentNamePath, "secretAnnotations"]); + value = value.trim(); + value = value.replace(/(? { + let value = formInst.getFieldValue([parentNamePath, "secretLabels"]); + value = value.trim(); + value = value.replace(/(? { > + + } + > + + + + } + > + + ); }; @@ -88,6 +139,34 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) secretType: z.string().nonempty(t("workflow_node.deploy.form.k8s_secret_type.placeholder")), secretDataKeyForCrt: z.string().nonempty(t("workflow_node.deploy.form.k8s_secret_data_key_for_crt.placeholder")), secretDataKeyForKey: z.string().nonempty(t("workflow_node.deploy.form.k8s_secret_data_key_for_key.placeholder")), + secretAnnotations: z + .string() + .nullish() + .refine((v) => { + if (!v) return true; + + const lines = v.split(/\r?\n/); + for (const line of lines) { + if (line.split(":").length < 2) { + return false; + } + } + return true; + }, t("workflow_node.deploy.form.k8s_secret_annotations.errmsg.invalid")), + secretLabels: z + .string() + .nullish() + .refine((v) => { + if (!v) return true; + + const lines = v.split(/\r?\n/); + for (const line of lines) { + if (line.split(":").length < 2) { + return false; + } + } + return true; + }, t("workflow_node.deploy.form.k8s_secret_labels.errmsg.invalid")), }); }; diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 958569f65..07c523daa 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -594,6 +594,16 @@ "workflow_node.deploy.form.k8s_secret_data_key_for_key.label": "Kubernetes Secret data key for private key", "workflow_node.deploy.form.k8s_secret_data_key_for_key.placeholder": "Please enter Kubernetes Secret data key for private key", "workflow_node.deploy.form.k8s_secret_data_key_for_key.tooltip": "For more information, see https://kubernetes.io/docs/concepts/configuration/secret/", + "workflow_node.deploy.form.k8s_secret_annotations.label": "Kubernetes Secret annotations (Optional)", + "workflow_node.deploy.form.k8s_secret_annotations.placeholder": "Please enter Kubernetes Secret annotations", + "workflow_node.deploy.form.k8s_secret_annotations.help": "Notes: One key value pair per line, separated by colon.", + "workflow_node.deploy.form.k8s_secret_annotations.errmsg.invalid": "Please enter a valid annotations", + "workflow_node.deploy.form.k8s_secret_annotations.tooltip": "Example:
environment: production
app: nginx
", + "workflow_node.deploy.form.k8s_secret_labels.label": "Kubernetes Secret labels (Optional)", + "workflow_node.deploy.form.k8s_secret_labels.placeholder": "Please enter Kubernetes Secret labels", + "workflow_node.deploy.form.k8s_secret_labels.help": "Notes: One key value pair per line, separated by colon.", + "workflow_node.deploy.form.k8s_secret_labels.errmsg.invalid": "Please enter a valid labels", + "workflow_node.deploy.form.k8s_secret_labels.tooltip": "Example:
environment: production
app: nginx
", "workflow_node.deploy.form.kong_resource_type.label": "Resource type", "workflow_node.deploy.form.kong_resource_type.placeholder": "Please select resource type", "workflow_node.deploy.form.kong_resource_type.option.certificate.label": "SSL certificate", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index feb992b7f..19bdbcafb 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -592,6 +592,16 @@ "workflow_node.deploy.form.k8s_secret_data_key_for_key.label": "Kubernetes Secret 数据键(用于存放私钥的字段)", "workflow_node.deploy.form.k8s_secret_data_key_for_key.placeholder": "请输入 Kubernetes Secret 中用于存放私钥的数据键", "workflow_node.deploy.form.k8s_secret_data_key_for_key.tooltip": "这是什么?请参阅 https://kubernetes.io/zh-cn/docs/concepts/configuration/secret/", + "workflow_node.deploy.form.k8s_secret_annotations.label": "Kubernetes Secret 注解(可选)", + "workflow_node.deploy.form.k8s_secret_annotations.placeholder": "请输入 Kubernetes Secret 注解", + "workflow_node.deploy.form.k8s_secret_annotations.help": "提示:每行一个键值对,以分号分隔。", + "workflow_node.deploy.form.k8s_secret_annotations.errmsg.invalid": "请输入有效的注解键值对", + "workflow_node.deploy.form.k8s_secret_annotations.tooltip": "示例:
environment: production
app: nginx
", + "workflow_node.deploy.form.k8s_secret_labels.label": "Kubernetes Secret 标签(可选)", + "workflow_node.deploy.form.k8s_secret_labels.placeholder": "请输入 Kubernetes Secret 标签", + "workflow_node.deploy.form.k8s_secret_labels.help": "提示:每行一个键值对,以分号分隔。", + "workflow_node.deploy.form.k8s_secret_labels.errmsg.invalid": "请输入有效的标签键值对", + "workflow_node.deploy.form.k8s_secret_labels.tooltip": "示例:
environment: production
app: nginx
", "workflow_node.deploy.form.kong_resource_type.label": "证书部署方式", "workflow_node.deploy.form.kong_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.kong_resource_type.option.certificate.label": "替换指定证书",