mirror of
https://github.com/kubevela/kubevela.git
synced 2026-04-22 02:26:56 +00:00
Feat: add email support in webhook notification (#2535)
* Feat: add email support in webhook notification * Fix: change sender and receiver to from and to * fix the variable name * fix wait return
This commit is contained in:
@@ -18,13 +18,7 @@ spec:
|
||||
|
||||
parameter: {
|
||||
dingding?: {
|
||||
url: {
|
||||
address?: string
|
||||
fromSecret?: {
|
||||
name: string
|
||||
key: string
|
||||
}
|
||||
}
|
||||
url: value | secretRef
|
||||
message: {
|
||||
text?: *null | {
|
||||
content: string
|
||||
@@ -69,13 +63,7 @@ spec:
|
||||
}
|
||||
|
||||
slack?: {
|
||||
url: {
|
||||
address?: string
|
||||
fromSecret?: {
|
||||
name: string
|
||||
key: string
|
||||
}
|
||||
}
|
||||
url: value | secretRef
|
||||
message: {
|
||||
text: string
|
||||
blocks?: *null | [...block]
|
||||
@@ -87,6 +75,21 @@ spec:
|
||||
mrkdwn?: *true | bool
|
||||
}
|
||||
}
|
||||
|
||||
email?: {
|
||||
from: {
|
||||
address: string
|
||||
alias?: string
|
||||
password: value | secretRef
|
||||
host: string
|
||||
port: *587 | int
|
||||
}
|
||||
to: [...string]
|
||||
content: {
|
||||
subject: string
|
||||
body: string
|
||||
}
|
||||
}
|
||||
}
|
||||
block: {
|
||||
type: string
|
||||
@@ -133,28 +136,33 @@ spec:
|
||||
description?: text
|
||||
url?: string
|
||||
}
|
||||
secretRef: {
|
||||
name: string
|
||||
key: string
|
||||
}
|
||||
value: string
|
||||
// send webhook notification
|
||||
ding: op.#Steps & {
|
||||
if parameter.dingding != _|_ {
|
||||
if parameter.dingding.url.address != _|_ {
|
||||
if parameter.dingding.url.value != _|_ {
|
||||
ding1: op.#DingTalk & {
|
||||
message: parameter.dingding.message
|
||||
dingUrl: parameter.dingding.url.address
|
||||
dingUrl: parameter.dingding.url.value
|
||||
}
|
||||
}
|
||||
if parameter.dingding.url.fromSecret != _|_ && parameter.dingding.url.address == _|_ {
|
||||
if parameter.dingding.url.secretRef != _|_ && parameter.dingding.url.value == _|_ {
|
||||
read: op.#Read & {
|
||||
value: {
|
||||
apiVersion: "v1"
|
||||
kind: "Secret"
|
||||
metadata: {
|
||||
name: parameter.dingding.url.fromSecret.name
|
||||
name: parameter.dingding.url.secretRef.name
|
||||
namespace: context.namespace
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decoded: base64.Decode(null, read.value.data[parameter.dingding.url.fromSecret.key])
|
||||
decoded: base64.Decode(null, read.value.data[parameter.dingding.url.secretRef.key])
|
||||
stringValue: op.#ConvertString & {bt: decoded}
|
||||
ding2: op.#DingTalk & {
|
||||
message: parameter.dingding.message
|
||||
@@ -165,25 +173,25 @@ spec:
|
||||
}
|
||||
slack: op.#Steps & {
|
||||
if parameter.slack != _|_ {
|
||||
if parameter.slack.url.address != _|_ {
|
||||
if parameter.slack.url.value != _|_ {
|
||||
slack1: op.#Slack & {
|
||||
message: parameter.slack.message
|
||||
slackUrl: parameter.slack.url.address
|
||||
slackUrl: parameter.slack.url.value
|
||||
}
|
||||
}
|
||||
if parameter.slack.url.fromSecret != _|_ && parameter.slack.url.address == _|_ {
|
||||
if parameter.slack.url.secretRef != _|_ && parameter.slack.url.value == _|_ {
|
||||
read: op.#Read & {
|
||||
value: {
|
||||
kind: "Secret"
|
||||
apiVersion: "v1"
|
||||
metadata: {
|
||||
name: parameter.slack.url.fromSecret.name
|
||||
name: parameter.slack.url.secretRef.name
|
||||
namespace: context.namespace
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decoded: base64.Decode(null, read.value.data[parameter.slack.url.fromSecret.key])
|
||||
decoded: base64.Decode(null, read.value.data[parameter.slack.url.secretRef.key])
|
||||
stringValue: op.#ConvertString & {bt: decoded}
|
||||
slack2: op.#Slack & {
|
||||
message: parameter.slack.message
|
||||
@@ -192,4 +200,48 @@ spec:
|
||||
}
|
||||
}
|
||||
}
|
||||
email: op.#Steps & {
|
||||
if parameter.email != _|_ {
|
||||
if parameter.email.from.password.value != _|_ {
|
||||
email1: op.#SendEmail & {
|
||||
from: {
|
||||
address: parameter.email.from.value
|
||||
alias: parameter.email.from.alias
|
||||
password: parameter.email.from.password.value
|
||||
host: parameter.email.from.host
|
||||
port: parameter.email.from.port
|
||||
}
|
||||
to: parameter.email.to
|
||||
content: parameter.email.content
|
||||
}
|
||||
}
|
||||
|
||||
if parameter.email.from.password.secretRef != _|_ && parameter.email.from.password.value == _|_ {
|
||||
read: op.#Read & {
|
||||
value: {
|
||||
kind: "Secret"
|
||||
apiVersion: "v1"
|
||||
metadata: {
|
||||
name: parameter.email.from.password.secretRef.name
|
||||
namespace: context.namespace
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decoded: base64.Decode(null, read.value.data[parameter.email.from.password.secretRef.key])
|
||||
stringValue: op.#ConvertString & {bt: decoded}
|
||||
email2: op.#SendEmail & {
|
||||
from: {
|
||||
address: parameter.email.from.value
|
||||
alias: parameter.email.from.alias
|
||||
password: stringValue.str
|
||||
host: parameter.email.from.host
|
||||
port: parameter.email.from.port
|
||||
}
|
||||
to: parameter.email.to
|
||||
content: parameter.email.content
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,13 +18,7 @@ spec:
|
||||
|
||||
parameter: {
|
||||
dingding?: {
|
||||
url: {
|
||||
address?: string
|
||||
fromSecret?: {
|
||||
name: string
|
||||
key: string
|
||||
}
|
||||
}
|
||||
url: value | secretRef
|
||||
message: {
|
||||
text?: *null | {
|
||||
content: string
|
||||
@@ -69,13 +63,7 @@ spec:
|
||||
}
|
||||
|
||||
slack?: {
|
||||
url: {
|
||||
address?: string
|
||||
fromSecret?: {
|
||||
name: string
|
||||
key: string
|
||||
}
|
||||
}
|
||||
url: value | secretRef
|
||||
message: {
|
||||
text: string
|
||||
blocks?: *null | [...block]
|
||||
@@ -87,6 +75,21 @@ spec:
|
||||
mrkdwn?: *true | bool
|
||||
}
|
||||
}
|
||||
|
||||
email?: {
|
||||
from: {
|
||||
address: string
|
||||
alias?: string
|
||||
password: value | secretRef
|
||||
host: string
|
||||
port: *587 | int
|
||||
}
|
||||
to: [...string]
|
||||
content: {
|
||||
subject: string
|
||||
body: string
|
||||
}
|
||||
}
|
||||
}
|
||||
block: {
|
||||
type: string
|
||||
@@ -133,28 +136,33 @@ spec:
|
||||
description?: text
|
||||
url?: string
|
||||
}
|
||||
secretRef: {
|
||||
name: string
|
||||
key: string
|
||||
}
|
||||
value: string
|
||||
// send webhook notification
|
||||
ding: op.#Steps & {
|
||||
if parameter.dingding != _|_ {
|
||||
if parameter.dingding.url.address != _|_ {
|
||||
if parameter.dingding.url.value != _|_ {
|
||||
ding1: op.#DingTalk & {
|
||||
message: parameter.dingding.message
|
||||
dingUrl: parameter.dingding.url.address
|
||||
dingUrl: parameter.dingding.url.value
|
||||
}
|
||||
}
|
||||
if parameter.dingding.url.fromSecret != _|_ && parameter.dingding.url.address == _|_ {
|
||||
if parameter.dingding.url.secretRef != _|_ && parameter.dingding.url.value == _|_ {
|
||||
read: op.#Read & {
|
||||
value: {
|
||||
apiVersion: "v1"
|
||||
kind: "Secret"
|
||||
metadata: {
|
||||
name: parameter.dingding.url.fromSecret.name
|
||||
name: parameter.dingding.url.secretRef.name
|
||||
namespace: context.namespace
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decoded: base64.Decode(null, read.value.data[parameter.dingding.url.fromSecret.key])
|
||||
decoded: base64.Decode(null, read.value.data[parameter.dingding.url.secretRef.key])
|
||||
stringValue: op.#ConvertString & {bt: decoded}
|
||||
ding2: op.#DingTalk & {
|
||||
message: parameter.dingding.message
|
||||
@@ -165,25 +173,25 @@ spec:
|
||||
}
|
||||
slack: op.#Steps & {
|
||||
if parameter.slack != _|_ {
|
||||
if parameter.slack.url.address != _|_ {
|
||||
if parameter.slack.url.value != _|_ {
|
||||
slack1: op.#Slack & {
|
||||
message: parameter.slack.message
|
||||
slackUrl: parameter.slack.url.address
|
||||
slackUrl: parameter.slack.url.value
|
||||
}
|
||||
}
|
||||
if parameter.slack.url.fromSecret != _|_ && parameter.slack.url.address == _|_ {
|
||||
if parameter.slack.url.secretRef != _|_ && parameter.slack.url.value == _|_ {
|
||||
read: op.#Read & {
|
||||
value: {
|
||||
kind: "Secret"
|
||||
apiVersion: "v1"
|
||||
metadata: {
|
||||
name: parameter.slack.url.fromSecret.name
|
||||
name: parameter.slack.url.secretRef.name
|
||||
namespace: context.namespace
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decoded: base64.Decode(null, read.value.data[parameter.slack.url.fromSecret.key])
|
||||
decoded: base64.Decode(null, read.value.data[parameter.slack.url.secretRef.key])
|
||||
stringValue: op.#ConvertString & {bt: decoded}
|
||||
slack2: op.#Slack & {
|
||||
message: parameter.slack.message
|
||||
@@ -192,4 +200,48 @@ spec:
|
||||
}
|
||||
}
|
||||
}
|
||||
email: op.#Steps & {
|
||||
if parameter.email != _|_ {
|
||||
if parameter.email.from.password.value != _|_ {
|
||||
email1: op.#SendEmail & {
|
||||
from: {
|
||||
address: parameter.email.from.value
|
||||
alias: parameter.email.from.alias
|
||||
password: parameter.email.from.password.value
|
||||
host: parameter.email.from.host
|
||||
port: parameter.email.from.port
|
||||
}
|
||||
to: parameter.email.to
|
||||
content: parameter.email.content
|
||||
}
|
||||
}
|
||||
|
||||
if parameter.email.from.password.secretRef != _|_ && parameter.email.from.password.value == _|_ {
|
||||
read: op.#Read & {
|
||||
value: {
|
||||
kind: "Secret"
|
||||
apiVersion: "v1"
|
||||
metadata: {
|
||||
name: parameter.email.from.password.secretRef.name
|
||||
namespace: context.namespace
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decoded: base64.Decode(null, read.value.data[parameter.email.from.password.secretRef.key])
|
||||
stringValue: op.#ConvertString & {bt: decoded}
|
||||
email2: op.#SendEmail & {
|
||||
from: {
|
||||
address: parameter.email.from.value
|
||||
alias: parameter.email.from.alias
|
||||
password: stringValue.str
|
||||
host: parameter.email.from.host
|
||||
port: parameter.email.from.port
|
||||
}
|
||||
to: parameter.email.to
|
||||
content: parameter.email.content
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ spec:
|
||||
dingding:
|
||||
# directly specify the webhook url
|
||||
url:
|
||||
address: <dingding-url>
|
||||
value: <dingding-url>
|
||||
message:
|
||||
msgtype: text
|
||||
text:
|
||||
@@ -32,10 +32,27 @@ spec:
|
||||
slack:
|
||||
url:
|
||||
# use url in secret
|
||||
formSecret:
|
||||
secretRef:
|
||||
name: <secret-name>
|
||||
key: <secret-key>
|
||||
message:
|
||||
text: Hello KubeVela
|
||||
email:
|
||||
from:
|
||||
address: <sender-email-address>
|
||||
alias: <sender-alias>
|
||||
password:
|
||||
# secretRef:
|
||||
# name: <secret-name>
|
||||
# key: <secret-key>
|
||||
value: <sender-password>
|
||||
host: <email host like smtp.gmail.com>
|
||||
port: <email port, optional, default to 587>
|
||||
to:
|
||||
- kubevela1@gmail.com
|
||||
- kubevela2@gmail.com
|
||||
content:
|
||||
subject: test-subject
|
||||
body: test-body
|
||||
- name: first-server
|
||||
type: apply-application
|
||||
|
||||
3
go.mod
3
go.mod
@@ -7,6 +7,7 @@ require (
|
||||
github.com/AlecAivazis/survey/v2 v2.1.1
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible
|
||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8
|
||||
github.com/agiledragon/gomonkey/v2 v2.3.0
|
||||
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869
|
||||
github.com/briandowns/spinner v1.11.1
|
||||
@@ -54,6 +55,8 @@ require (
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect
|
||||
golang.org/x/tools v0.1.6 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
gotest.tools v2.2.0+incompatible
|
||||
helm.sh/helm/v3 v3.6.1
|
||||
|
||||
6
go.sum
6
go.sum
@@ -168,6 +168,8 @@ github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia
|
||||
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
||||
github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE=
|
||||
github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
||||
github.com/agiledragon/gomonkey/v2 v2.3.0 h1:olsJjnSDpvqWl4FHByeMUs/r54/az+gQitU3VeEbC98=
|
||||
github.com/agiledragon/gomonkey/v2 v2.3.0/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
|
||||
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
|
||||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
@@ -2289,6 +2291,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
|
||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -2303,6 +2307,8 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE=
|
||||
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
|
||||
gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw=
|
||||
gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=
|
||||
gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
|
||||
@@ -142,6 +142,8 @@ import (
|
||||
|
||||
#ConvertString: convert.#String
|
||||
|
||||
#SendEmail: email.#Send
|
||||
|
||||
#Load: oam.#LoadComponets
|
||||
|
||||
#Steps: {
|
||||
|
||||
19
pkg/stdlib/pkgs/email.cue
Normal file
19
pkg/stdlib/pkgs/email.cue
Normal file
@@ -0,0 +1,19 @@
|
||||
#Send: {
|
||||
#do: "send"
|
||||
#provider: "email"
|
||||
|
||||
from: {
|
||||
address: string
|
||||
alias?: string
|
||||
password: string
|
||||
host: string
|
||||
port: int
|
||||
}
|
||||
to: [...string]
|
||||
content: {
|
||||
subject: string
|
||||
body: string
|
||||
}
|
||||
stepID: context.stepSessionID
|
||||
...
|
||||
}
|
||||
@@ -20,8 +20,6 @@ import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/assert"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/cue/model/value"
|
||||
@@ -69,6 +67,7 @@ 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)
|
||||
r := require.New(t)
|
||||
r.Equal(ok, true)
|
||||
r.Equal(h != nil, true)
|
||||
}
|
||||
|
||||
136
pkg/workflow/providers/email/send.go
Normal file
136
pkg/workflow/providers/email/send.go
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
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 email
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"gopkg.in/gomail.v2"
|
||||
|
||||
"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 = "email"
|
||||
)
|
||||
|
||||
type provider struct {
|
||||
}
|
||||
|
||||
type sender struct {
|
||||
Address string `json:"address"`
|
||||
Alias string `json:"alias,omitempty"`
|
||||
Password string `json:"password"`
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
}
|
||||
|
||||
type content struct {
|
||||
Subject string `json:"subject"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
var emailRoutine sync.Map
|
||||
|
||||
// Send sends email
|
||||
func (h *provider) Send(ctx wfContext.Context, v *value.Value, act types.Action) error {
|
||||
stepID, err := v.LookupValue("stepID")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
id, err := stepID.String()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
routine, ok := emailRoutine.Load(id)
|
||||
if ok {
|
||||
switch routine {
|
||||
case "success":
|
||||
emailRoutine.Delete(id)
|
||||
return nil
|
||||
case "initializing", "sending":
|
||||
act.Wait("wait for the email")
|
||||
return nil
|
||||
default:
|
||||
emailRoutine.Delete(id)
|
||||
return fmt.Errorf("failed to send email: %v", routine)
|
||||
}
|
||||
} else {
|
||||
emailRoutine.Store(id, "initializing")
|
||||
}
|
||||
|
||||
s, err := v.LookupValue("from")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
senderValue := &sender{}
|
||||
if err := s.UnmarshalTo(senderValue); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r, err := v.LookupValue("to")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
receiverValue := &[]string{}
|
||||
if err := r.UnmarshalTo(receiverValue); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c, err := v.LookupValue("content")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
contentValue := &content{}
|
||||
if err := c.UnmarshalTo(contentValue); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m := gomail.NewMessage()
|
||||
m.SetAddressHeader("From", senderValue.Address, senderValue.Alias)
|
||||
m.SetHeader("To", *receiverValue...)
|
||||
m.SetHeader("Subject", contentValue.Subject)
|
||||
m.SetBody("text/html", contentValue.Body)
|
||||
|
||||
dial := gomail.NewDialer(senderValue.Host, senderValue.Port, senderValue.Address, senderValue.Password)
|
||||
go func() {
|
||||
if routine, ok := emailRoutine.Load(id); ok && routine == "initializing" {
|
||||
emailRoutine.Store(id, "sending")
|
||||
if err := dial.DialAndSend(m); err != nil {
|
||||
emailRoutine.Store(id, err.Error())
|
||||
return
|
||||
}
|
||||
emailRoutine.Store(id, "success")
|
||||
}
|
||||
}()
|
||||
act.Wait("wait for the email")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Install register handlers to provider discover.
|
||||
func Install(p providers.Providers) {
|
||||
prd := &provider{}
|
||||
p.Register(ProviderName, map[string]providers.Handler{
|
||||
"send": prd.Send,
|
||||
})
|
||||
}
|
||||
162
pkg/workflow/providers/email/send_test.go
Normal file
162
pkg/workflow/providers/email/send_test.go
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
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 email
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/cue/model/value"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/providers"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/providers/mock"
|
||||
|
||||
. "github.com/agiledragon/gomonkey/v2"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/gomail.v2"
|
||||
)
|
||||
|
||||
func TestSendEmail(t *testing.T) {
|
||||
var dial *gomail.Dialer
|
||||
testCases := map[string]struct {
|
||||
from string
|
||||
expectedErr error
|
||||
errMsg string
|
||||
}{
|
||||
"success": {
|
||||
from: `
|
||||
from: {
|
||||
address: "kubevela@gmail.com"
|
||||
alias: "kubevela-bot"
|
||||
password: "pwd"
|
||||
host: "smtp.test.com"
|
||||
port: 465
|
||||
}
|
||||
to: ["user1@gmail.com", "user2@gmail.com"]
|
||||
content: {
|
||||
subject: "Subject"
|
||||
body: "Test body."
|
||||
}
|
||||
stepID: "success"
|
||||
`,
|
||||
},
|
||||
"no-step-id": {
|
||||
from: ``,
|
||||
expectedErr: errors.New("var(path=stepID) not exist"),
|
||||
},
|
||||
"no-sender": {
|
||||
from: `stepID:"no-sender"`,
|
||||
expectedErr: errors.New("var(path=from) not exist"),
|
||||
},
|
||||
"no-receiver": {
|
||||
from: `
|
||||
from: {
|
||||
address: "kubevela@gmail.com"
|
||||
alias: "kubevela-bot"
|
||||
password: "pwd"
|
||||
host: "smtp.test.com"
|
||||
port: 465
|
||||
}
|
||||
stepID: "no-receiver"
|
||||
`,
|
||||
expectedErr: errors.New("var(path=to) not exist"),
|
||||
},
|
||||
"no-content": {
|
||||
from: `
|
||||
from: {
|
||||
address: "kubevela@gmail.com"
|
||||
alias: "kubevela-bot"
|
||||
password: "pwd"
|
||||
host: "smtp.test.com"
|
||||
port: 465
|
||||
}
|
||||
to: ["user1@gmail.com", "user2@gmail.com"]
|
||||
stepID: "no-content"
|
||||
`,
|
||||
expectedErr: errors.New("var(path=content) not exist"),
|
||||
},
|
||||
"send-fail": {
|
||||
from: `
|
||||
from: {
|
||||
address: "kubevela@gmail.com"
|
||||
alias: "kubevela-bot"
|
||||
password: "pwd"
|
||||
host: "smtp.test.com"
|
||||
port: 465
|
||||
}
|
||||
to: ["user1@gmail.com", "user2@gmail.com"]
|
||||
content: {
|
||||
subject: "Subject"
|
||||
body: "Test body."
|
||||
}
|
||||
stepID: "send-fail"
|
||||
`,
|
||||
errMsg: "fail to send",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := require.New(t)
|
||||
|
||||
patch := ApplyMethod(reflect.TypeOf(dial), "DialAndSend", func(_ *gomail.Dialer, _ ...*gomail.Message) error {
|
||||
return nil
|
||||
})
|
||||
defer patch.Reset()
|
||||
|
||||
act := &mock.Action{}
|
||||
|
||||
if tc.errMsg != "" {
|
||||
patch.Reset()
|
||||
patch = ApplyMethod(reflect.TypeOf(dial), "DialAndSend", func(_ *gomail.Dialer, _ ...*gomail.Message) error {
|
||||
return errors.New(tc.errMsg)
|
||||
})
|
||||
defer patch.Reset()
|
||||
}
|
||||
v, err := value.NewValue(tc.from, nil, "")
|
||||
r.NoError(err)
|
||||
prd := &provider{}
|
||||
err = prd.Send(nil, v, act)
|
||||
if tc.expectedErr != nil {
|
||||
r.Equal(tc.expectedErr.Error(), err.Error())
|
||||
return
|
||||
}
|
||||
r.NoError(err)
|
||||
r.Equal(act.Phase, "Wait")
|
||||
|
||||
// mock reconcile
|
||||
time.Sleep(time.Second)
|
||||
err = prd.Send(nil, v, act)
|
||||
if tc.errMsg != "" {
|
||||
r.Equal(fmt.Errorf("failed to send email: %s", tc.errMsg), err)
|
||||
return
|
||||
}
|
||||
r.NoError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstall(t *testing.T) {
|
||||
p := providers.NewProviders()
|
||||
Install(p)
|
||||
h, ok := p.GetHandler("email", "send")
|
||||
r := require.New(t)
|
||||
r.Equal(ok, true)
|
||||
r.Equal(h != nil, true)
|
||||
}
|
||||
41
pkg/workflow/providers/mock/mock.go
Normal file
41
pkg/workflow/providers/mock/mock.go
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
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 mock
|
||||
|
||||
// Action ...
|
||||
type Action struct {
|
||||
Phase string
|
||||
Message string
|
||||
}
|
||||
|
||||
// Suspend ...
|
||||
func (act *Action) Suspend(message string) {
|
||||
act.Phase = "Suspend"
|
||||
act.Message = message
|
||||
}
|
||||
|
||||
// Terminate ...
|
||||
func (act *Action) Terminate(message string) {
|
||||
act.Phase = "Terminate"
|
||||
act.Message = message
|
||||
}
|
||||
|
||||
// Wait ...
|
||||
func (act *Action) Wait(message string) {
|
||||
act.Phase = "Wait"
|
||||
act.Message = message
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/model/value"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/providers/mock"
|
||||
)
|
||||
|
||||
func TestParser(t *testing.T) {
|
||||
@@ -35,7 +36,7 @@ func TestParser(t *testing.T) {
|
||||
p := &provider{
|
||||
apply: simpleComponentApplyForTest,
|
||||
}
|
||||
act := &mockAction{}
|
||||
act := &mock.Action{}
|
||||
v, err := value.NewValue("", nil, "")
|
||||
assert.NilError(t, err)
|
||||
err = p.ApplyComponent(nil, v, act)
|
||||
@@ -73,12 +74,12 @@ metadata: {
|
||||
}
|
||||
`)
|
||||
|
||||
assert.Equal(t, act.phase, "Wait")
|
||||
assert.Equal(t, act.Phase, "Wait")
|
||||
testHealthy = true
|
||||
act = &mockAction{}
|
||||
act = &mock.Action{}
|
||||
_, err = value.NewValue("", nil, "")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, act.phase, "")
|
||||
assert.Equal(t, act.Phase, "")
|
||||
}
|
||||
|
||||
func TestLoadComponent(t *testing.T) {
|
||||
@@ -142,22 +143,3 @@ func simpleComponentApplyForTest(comp common.ApplicationComponent, _ *value.Valu
|
||||
traits := []*unstructured.Unstructured{trait}
|
||||
return workload, traits, testHealthy, nil
|
||||
}
|
||||
|
||||
type mockAction struct {
|
||||
phase string
|
||||
message string
|
||||
}
|
||||
|
||||
func (act *mockAction) Suspend(message string) {
|
||||
act.phase = "Suspend"
|
||||
act.message = message
|
||||
}
|
||||
|
||||
func (act *mockAction) Terminate(message string) {
|
||||
act.phase = "Terminate"
|
||||
act.message = message
|
||||
}
|
||||
func (act *mockAction) Wait(message string) {
|
||||
act.phase = "Wait"
|
||||
act.message = message
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
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/email"
|
||||
"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"
|
||||
@@ -74,6 +75,7 @@ func NewTaskDiscover(providerHandlers providers.Providers, pd *packages.PackageD
|
||||
workspace.Install(providerHandlers)
|
||||
http.Install(providerHandlers)
|
||||
convert.Install(providerHandlers)
|
||||
email.Install(providerHandlers)
|
||||
templateLoader := template.NewTemplateLoader(cli, dm)
|
||||
return &taskDiscover{
|
||||
builtins: map[string]types.TaskGenerator{
|
||||
|
||||
@@ -13,13 +13,7 @@ template: {
|
||||
|
||||
parameter: {
|
||||
dingding?: {
|
||||
url: {
|
||||
address?: string
|
||||
fromSecret?: {
|
||||
name: string
|
||||
key: string
|
||||
}
|
||||
}
|
||||
url: value | secretRef
|
||||
message: {
|
||||
text?: *null | {
|
||||
content: string
|
||||
@@ -64,13 +58,7 @@ template: {
|
||||
}
|
||||
|
||||
slack?: {
|
||||
url: {
|
||||
address?: string
|
||||
fromSecret?: {
|
||||
name: string
|
||||
key: string
|
||||
}
|
||||
}
|
||||
url: value | secretRef
|
||||
message: {
|
||||
text: string
|
||||
blocks?: *null | [...block]
|
||||
@@ -82,6 +70,21 @@ template: {
|
||||
mrkdwn?: *true | bool
|
||||
}
|
||||
}
|
||||
|
||||
email?: {
|
||||
from: {
|
||||
address: string
|
||||
alias?: string
|
||||
password: value | secretRef
|
||||
host: string
|
||||
port: *587 | int
|
||||
}
|
||||
to: [...string]
|
||||
content: {
|
||||
subject: string
|
||||
body: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
block: {
|
||||
@@ -134,28 +137,35 @@ template: {
|
||||
url?: string
|
||||
}
|
||||
|
||||
secretRef: {
|
||||
name: string
|
||||
key: string
|
||||
}
|
||||
|
||||
value: string
|
||||
|
||||
// send webhook notification
|
||||
ding: op.#Steps & {
|
||||
if parameter.dingding != _|_ {
|
||||
if parameter.dingding.url.address != _|_ {
|
||||
if parameter.dingding.url.value != _|_ {
|
||||
ding1: op.#DingTalk & {
|
||||
message: parameter.dingding.message
|
||||
dingUrl: parameter.dingding.url.address
|
||||
dingUrl: parameter.dingding.url.value
|
||||
}
|
||||
}
|
||||
if parameter.dingding.url.fromSecret != _|_ && parameter.dingding.url.address == _|_ {
|
||||
if parameter.dingding.url.secretRef != _|_ && parameter.dingding.url.value == _|_ {
|
||||
read: op.#Read & {
|
||||
value: {
|
||||
apiVersion: "v1"
|
||||
kind: "Secret"
|
||||
metadata: {
|
||||
name: parameter.dingding.url.fromSecret.name
|
||||
name: parameter.dingding.url.secretRef.name
|
||||
namespace: context.namespace
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decoded: base64.Decode(null, read.value.data[parameter.dingding.url.fromSecret.key])
|
||||
decoded: base64.Decode(null, read.value.data[parameter.dingding.url.secretRef.key])
|
||||
stringValue: op.#ConvertString & {bt: decoded}
|
||||
ding2: op.#DingTalk & {
|
||||
message: parameter.dingding.message
|
||||
@@ -167,25 +177,25 @@ template: {
|
||||
|
||||
slack: op.#Steps & {
|
||||
if parameter.slack != _|_ {
|
||||
if parameter.slack.url.address != _|_ {
|
||||
if parameter.slack.url.value != _|_ {
|
||||
slack1: op.#Slack & {
|
||||
message: parameter.slack.message
|
||||
slackUrl: parameter.slack.url.address
|
||||
slackUrl: parameter.slack.url.value
|
||||
}
|
||||
}
|
||||
if parameter.slack.url.fromSecret != _|_ && parameter.slack.url.address == _|_ {
|
||||
if parameter.slack.url.secretRef != _|_ && parameter.slack.url.value == _|_ {
|
||||
read: op.#Read & {
|
||||
value: {
|
||||
kind: "Secret"
|
||||
apiVersion: "v1"
|
||||
metadata: {
|
||||
name: parameter.slack.url.fromSecret.name
|
||||
name: parameter.slack.url.secretRef.name
|
||||
namespace: context.namespace
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decoded: base64.Decode(null, read.value.data[parameter.slack.url.fromSecret.key])
|
||||
decoded: base64.Decode(null, read.value.data[parameter.slack.url.secretRef.key])
|
||||
stringValue: op.#ConvertString & {bt: decoded}
|
||||
slack2: op.#Slack & {
|
||||
message: parameter.slack.message
|
||||
@@ -194,4 +204,49 @@ template: {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
email: op.#Steps & {
|
||||
if parameter.email != _|_ {
|
||||
if parameter.email.from.password.value != _|_ {
|
||||
email1: op.#SendEmail & {
|
||||
from: {
|
||||
address: parameter.email.from.value
|
||||
alias: parameter.email.from.alias
|
||||
password: parameter.email.from.password.value
|
||||
host: parameter.email.from.host
|
||||
port: parameter.email.from.port
|
||||
}
|
||||
to: parameter.email.to
|
||||
content: parameter.email.content
|
||||
}
|
||||
}
|
||||
|
||||
if parameter.email.from.password.secretRef != _|_ && parameter.email.from.password.value == _|_ {
|
||||
read: op.#Read & {
|
||||
value: {
|
||||
kind: "Secret"
|
||||
apiVersion: "v1"
|
||||
metadata: {
|
||||
name: parameter.email.from.password.secretRef.name
|
||||
namespace: context.namespace
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decoded: base64.Decode(null, read.value.data[parameter.email.from.password.secretRef.key])
|
||||
stringValue: op.#ConvertString & {bt: decoded}
|
||||
email2: op.#SendEmail & {
|
||||
from: {
|
||||
address: parameter.email.from.value
|
||||
alias: parameter.email.from.alias
|
||||
password: stringValue.str
|
||||
host: parameter.email.from.host
|
||||
port: parameter.email.from.port
|
||||
}
|
||||
to: parameter.email.to
|
||||
content: parameter.email.content
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user