From dcb677f97645f197a0e1a248e8b421dd6be8d08d Mon Sep 17 00:00:00 2001 From: Tianxin Dong Date: Tue, 19 Oct 2021 10:42:40 +0800 Subject: [PATCH] Feat: support secret in webhook notification (#2509) --- .../defwithtemplate/webhook-notification.yaml | 75 ++++++++++++++++--- .../defwithtemplate/webhook-notification.yaml | 75 ++++++++++++++++--- pkg/stdlib/op.cue | 2 + pkg/stdlib/pkgs/convert.cue | 8 ++ pkg/workflow/providers/convert/string.go | 53 +++++++++++++ pkg/workflow/providers/convert/string_test.go | 74 ++++++++++++++++++ pkg/workflow/tasks/discover.go | 2 + .../internal/webhook-notification.cue | 75 ++++++++++++++++--- 8 files changed, 334 insertions(+), 30 deletions(-) create mode 100644 pkg/stdlib/pkgs/convert.cue create mode 100644 pkg/workflow/providers/convert/string.go create mode 100644 pkg/workflow/providers/convert/string_test.go diff --git a/charts/vela-core/templates/defwithtemplate/webhook-notification.yaml b/charts/vela-core/templates/defwithtemplate/webhook-notification.yaml index 0761f8990..bf6496d74 100644 --- a/charts/vela-core/templates/defwithtemplate/webhook-notification.yaml +++ b/charts/vela-core/templates/defwithtemplate/webhook-notification.yaml @@ -13,11 +13,18 @@ spec: template: | import ( "vela/op" + "encoding/base64" ) parameter: { dingding?: { - url: string + url: { + address?: string + fromSecret?: { + name: string + key: string + } + } message: { text?: *null | { content: string @@ -62,7 +69,13 @@ spec: } slack?: { - url: string + url: { + address?: string + fromSecret?: { + name: string + key: string + } + } message: { text: string blocks?: *null | [...block] @@ -121,19 +134,61 @@ spec: url?: string } // send webhook notification - ding: { + ding: op.#Steps & { if parameter.dingding != _|_ { - op.#DingTalk & { - message: parameter.dingding.message - dingUrl: parameter.dingding.url + if parameter.dingding.url.address != _|_ { + ding1: op.#DingTalk & { + message: parameter.dingding.message + dingUrl: parameter.dingding.url.address + } + } + if parameter.dingding.url.fromSecret != _|_ && parameter.dingding.url.address == _|_ { + read: op.#Read & { + value: { + apiVersion: "v1" + kind: "Secret" + metadata: { + name: parameter.dingding.url.fromSecret.name + namespace: context.namespace + } + } + } + + decoded: base64.Decode(null, read.value.data[parameter.dingding.url.fromSecret.key]) + stringValue: op.#ConvertString & {bt: decoded} + ding2: op.#DingTalk & { + message: parameter.dingding.message + dingUrl: stringValue.str + } } } } - slack: { + slack: op.#Steps & { if parameter.slack != _|_ { - op.#Slack & { - message: parameter.slack.message - slackUrl: parameter.slack.url + if parameter.slack.url.address != _|_ { + slack1: op.#Slack & { + message: parameter.slack.message + slackUrl: parameter.slack.url.address + } + } + if parameter.slack.url.fromSecret != _|_ && parameter.slack.url.address == _|_ { + read: op.#Read & { + value: { + kind: "Secret" + apiVersion: "v1" + metadata: { + name: parameter.slack.url.fromSecret.name + namespace: context.namespace + } + } + } + + decoded: base64.Decode(null, read.value.data[parameter.slack.url.fromSecret.key]) + stringValue: op.#ConvertString & {bt: decoded} + slack2: op.#Slack & { + message: parameter.slack.message + slackUrl: stringValue.str + } } } } diff --git a/charts/vela-minimal/templates/defwithtemplate/webhook-notification.yaml b/charts/vela-minimal/templates/defwithtemplate/webhook-notification.yaml index 0761f8990..bf6496d74 100644 --- a/charts/vela-minimal/templates/defwithtemplate/webhook-notification.yaml +++ b/charts/vela-minimal/templates/defwithtemplate/webhook-notification.yaml @@ -13,11 +13,18 @@ spec: template: | import ( "vela/op" + "encoding/base64" ) parameter: { dingding?: { - url: string + url: { + address?: string + fromSecret?: { + name: string + key: string + } + } message: { text?: *null | { content: string @@ -62,7 +69,13 @@ spec: } slack?: { - url: string + url: { + address?: string + fromSecret?: { + name: string + key: string + } + } message: { text: string blocks?: *null | [...block] @@ -121,19 +134,61 @@ spec: url?: string } // send webhook notification - ding: { + ding: op.#Steps & { if parameter.dingding != _|_ { - op.#DingTalk & { - message: parameter.dingding.message - dingUrl: parameter.dingding.url + if parameter.dingding.url.address != _|_ { + ding1: op.#DingTalk & { + message: parameter.dingding.message + dingUrl: parameter.dingding.url.address + } + } + if parameter.dingding.url.fromSecret != _|_ && parameter.dingding.url.address == _|_ { + read: op.#Read & { + value: { + apiVersion: "v1" + kind: "Secret" + metadata: { + name: parameter.dingding.url.fromSecret.name + namespace: context.namespace + } + } + } + + decoded: base64.Decode(null, read.value.data[parameter.dingding.url.fromSecret.key]) + stringValue: op.#ConvertString & {bt: decoded} + ding2: op.#DingTalk & { + message: parameter.dingding.message + dingUrl: stringValue.str + } } } } - slack: { + slack: op.#Steps & { if parameter.slack != _|_ { - op.#Slack & { - message: parameter.slack.message - slackUrl: parameter.slack.url + if parameter.slack.url.address != _|_ { + slack1: op.#Slack & { + message: parameter.slack.message + slackUrl: parameter.slack.url.address + } + } + if parameter.slack.url.fromSecret != _|_ && parameter.slack.url.address == _|_ { + read: op.#Read & { + value: { + kind: "Secret" + apiVersion: "v1" + metadata: { + name: parameter.slack.url.fromSecret.name + namespace: context.namespace + } + } + } + + decoded: base64.Decode(null, read.value.data[parameter.slack.url.fromSecret.key]) + stringValue: op.#ConvertString & {bt: decoded} + slack2: op.#Slack & { + message: parameter.slack.message + slackUrl: stringValue.str + } } } } diff --git a/pkg/stdlib/op.cue b/pkg/stdlib/op.cue index a241859df..75974f6ee 100644 --- a/pkg/stdlib/op.cue +++ b/pkg/stdlib/op.cue @@ -134,6 +134,8 @@ import ( #HTTPDelete: http.#Do & {method: "DELETE"} +#ConvertString: convert.#String + #Load: oam.#LoadComponets #Read: kube.#Read diff --git a/pkg/stdlib/pkgs/convert.cue b/pkg/stdlib/pkgs/convert.cue new file mode 100644 index 000000000..7b8f4a5bb --- /dev/null +++ b/pkg/stdlib/pkgs/convert.cue @@ -0,0 +1,8 @@ +#String: { + #do: "string" + #provider: "convert" + + bt: bytes + str?: string + ... +} diff --git a/pkg/workflow/providers/convert/string.go b/pkg/workflow/providers/convert/string.go new file mode 100644 index 000000000..e4715112e --- /dev/null +++ b/pkg/workflow/providers/convert/string.go @@ -0,0 +1,53 @@ +/* +Copyright 2021 The KubeVela Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package convert + +import ( + "github.com/oam-dev/kubevela/pkg/cue/model/value" + wfContext "github.com/oam-dev/kubevela/pkg/workflow/context" + "github.com/oam-dev/kubevela/pkg/workflow/providers" + "github.com/oam-dev/kubevela/pkg/workflow/types" +) + +const ( + // ProviderName is provider name for install. + ProviderName = "convert" +) + +type provider struct { +} + +// String convert byte to string +func (h *provider) String(ctx wfContext.Context, v *value.Value, act types.Action) error { + b, err := v.LookupValue("bt") + if err != nil { + return err + } + s, err := b.CueValue().Bytes() + if err != nil { + return err + } + return v.FillObject(string(s), "str") +} + +// Install register handlers to provider discover. +func Install(p providers.Providers) { + prd := &provider{} + p.Register(ProviderName, map[string]providers.Handler{ + "string": prd.String, + }) +} diff --git a/pkg/workflow/providers/convert/string_test.go b/pkg/workflow/providers/convert/string_test.go new file mode 100644 index 000000000..a62af723a --- /dev/null +++ b/pkg/workflow/providers/convert/string_test.go @@ -0,0 +1,74 @@ +/* +Copyright 2021 The KubeVela Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package convert + +import ( + "errors" + "testing" + + "gotest.tools/assert" + + "github.com/stretchr/testify/require" + + "github.com/oam-dev/kubevela/pkg/cue/model/value" + "github.com/oam-dev/kubevela/pkg/workflow/providers" +) + +func TestConvertString(t *testing.T) { + testCases := map[string]struct { + from string + expected string + expectedErr error + }{ + "success": { + from: `bt: 'test'`, + expected: "test", + }, + "fail": { + from: `bt: 123`, + expectedErr: errors.New("bt: cannot use value 123 (type int) as string|bytes"), + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + r := require.New(t) + v, err := value.NewValue(tc.from, nil, "") + r.NoError(err) + prd := &provider{} + err = prd.String(nil, v, nil) + if tc.expectedErr != nil { + r.Equal(tc.expectedErr.Error(), err.Error()) + return + } + r.NoError(err) + expected, err := v.LookupValue("str") + r.NoError(err) + ret, err := expected.CueValue().String() + r.NoError(err) + r.Equal(ret, tc.expected) + }) + } +} + +func TestInstall(t *testing.T) { + p := providers.NewProviders() + Install(p) + h, ok := p.GetHandler("convert", "string") + assert.Equal(t, ok, true) + assert.Equal(t, h != nil, true) +} diff --git a/pkg/workflow/tasks/discover.go b/pkg/workflow/tasks/discover.go index 975c8b56a..4d6d36759 100644 --- a/pkg/workflow/tasks/discover.go +++ b/pkg/workflow/tasks/discover.go @@ -28,6 +28,7 @@ import ( "github.com/oam-dev/kubevela/pkg/oam/discoverymapper" wfContext "github.com/oam-dev/kubevela/pkg/workflow/context" "github.com/oam-dev/kubevela/pkg/workflow/providers" + "github.com/oam-dev/kubevela/pkg/workflow/providers/convert" "github.com/oam-dev/kubevela/pkg/workflow/providers/http" "github.com/oam-dev/kubevela/pkg/workflow/providers/workspace" "github.com/oam-dev/kubevela/pkg/workflow/tasks/custom" @@ -72,6 +73,7 @@ func NewTaskDiscover(providerHandlers providers.Providers, pd *packages.PackageD // install builtin provider workspace.Install(providerHandlers) http.Install(providerHandlers) + convert.Install(providerHandlers) templateLoader := template.NewTemplateLoader(cli, dm) return &taskDiscover{ builtins: map[string]types.TaskGenerator{ diff --git a/vela-templates/definitions/internal/webhook-notification.cue b/vela-templates/definitions/internal/webhook-notification.cue index a6ad62308..da90790e5 100644 --- a/vela-templates/definitions/internal/webhook-notification.cue +++ b/vela-templates/definitions/internal/webhook-notification.cue @@ -1,5 +1,6 @@ import ( "vela/op" + "encoding/base64" ) "webhook-notification": { @@ -12,7 +13,13 @@ template: { parameter: { dingding?: { - url: string + url: { + address?: string + fromSecret?: { + name: string + key: string + } + } message: { text?: *null | { content: string @@ -57,7 +64,13 @@ template: { } slack?: { - url: string + url: { + address?: string + fromSecret?: { + name: string + key: string + } + } message: { text: string blocks?: *null | [...block] @@ -122,20 +135,62 @@ template: { } // send webhook notification - ding: { + ding: op.#Steps & { if parameter.dingding != _|_ { - op.#DingTalk & { - message: parameter.dingding.message - dingUrl: parameter.dingding.url + if parameter.dingding.url.address != _|_ { + ding1: op.#DingTalk & { + message: parameter.dingding.message + dingUrl: parameter.dingding.url.address + } + } + if parameter.dingding.url.fromSecret != _|_ && parameter.dingding.url.address == _|_ { + read: op.#Read & { + value: { + apiVersion: "v1" + kind: "Secret" + metadata: { + name: parameter.dingding.url.fromSecret.name + namespace: context.namespace + } + } + } + + decoded: base64.Decode(null, read.value.data[parameter.dingding.url.fromSecret.key]) + stringValue: op.#ConvertString & {bt: decoded} + ding2: op.#DingTalk & { + message: parameter.dingding.message + dingUrl: stringValue.str + } } } } - slack: { + slack: op.#Steps & { if parameter.slack != _|_ { - op.#Slack & { - message: parameter.slack.message - slackUrl: parameter.slack.url + if parameter.slack.url.address != _|_ { + slack1: op.#Slack & { + message: parameter.slack.message + slackUrl: parameter.slack.url.address + } + } + if parameter.slack.url.fromSecret != _|_ && parameter.slack.url.address == _|_ { + read: op.#Read & { + value: { + kind: "Secret" + apiVersion: "v1" + metadata: { + name: parameter.slack.url.fromSecret.name + namespace: context.namespace + } + } + } + + decoded: base64.Decode(null, read.value.data[parameter.slack.url.fromSecret.key]) + stringValue: op.#ConvertString & {bt: decoded} + slack2: op.#Slack & { + message: parameter.slack.message + slackUrl: stringValue.str + } } } }