Merge branch 'master' into feature/helm_v3_overall_upgrade

feature/helm_v3_overall_upgrade
Karolis Rusenas 2020-10-06 16:39:47 +01:00
commit 80c241991a
15 changed files with 312 additions and 9 deletions

View File

@ -13,7 +13,7 @@ echo "Packaging charts from source code"
mkdir -p temp
for d in chart/*
do
# shellcheck disable=SC2039
# shellcheck disable=SC3010
if [[ -d $d ]]
then
# Will generate a helm package per chart in a folder

View File

@ -81,6 +81,8 @@ The following table lists has the main configurable parameters (polling, trigger
| ------------------------------------------- | -------------------------------------- | --------------------------------------------------------- |
| `polling.enabled` | Docker registries polling | `true` |
| `helmProvider.enabled` | Enable/disable Helm provider | `true` |
| `helmProvider.helmDriver` | Set driver for Helm3 | `` |
| `helmProvider.helmDriverSqlConnectionString`| Set SQL connection string for Helm3 | `` |
| `gcr.enabled` | Enable/disable GCR Registry | `false` |
| `gcr.projectId` | GCP Project ID GCR belongs to | |
| `gcr.pubsub.enabled` | Enable/disable GCP Pub/Sub trigger | `false` |
@ -97,6 +99,8 @@ The following table lists has the main configurable parameters (polling, trigger
| `slack.token` | Slack token | |
| `slack.channel` | Slack channel | |
| `slack.approvalsChannel` | Slack channel for approvals | |
| `teams.enabled` | Enable/disable MS Teams Notification | `false` |
| `teams.webhookUrl` | MS Teams Connector's webhook url | |
| `service.enabled` | Enable/disable Keel service | `false` |
| `service.type` | Keel service type | `LoadBalancer` |
| `service.externalIP` | Keel static IP | |

View File

@ -0,0 +1,3 @@
schemaVersion: v1
summary: Security mitigation information for this application is tracked by the security-mitigation.yaml file that's part of this helm chart.
mitigations: []

View File

@ -55,10 +55,9 @@ spec:
- name: HELM_PROVIDER
value: "true"
- name: HELM_DRIVER
value: "{{ .Values.helmProvider.driver }}"
value: "{{ .Values.helmProvider.helmDriver }}"
- name: HELM_DRIVER_SQL_CONNECTION_STRING
value: "{{ .Values.helmProvider.driverSqlConnectionString }}"
{{- end }}
value: "{{ .Values.helmProvider.helmDriverSqlConnectionString }}"
{{- end }}
{{- if .Values.gcr.enabled }}
# Enable GCR with pub/sub support

View File

@ -24,6 +24,9 @@ data:
HIPCHAT_TOKEN: {{ .Values.hipchat.token | b64enc}}
HIPCHAT_APPROVALS_PASSWORT: {{ .Values.hipchat.password | b64enc }}
{{- end }}
{{- if .Values.teams.enabled }}
TEAMS_WEBHOOK_URL: {{ .Values.teams.webhookUrl | b64enc }}
{{- end }}
{{- if and .Values.mail.enabled .Values.mail.smtp.pass }}
MAIL_SMTP_PASS: {{ .Values.mail.smtp.pass | b64enc }}
{{- end }}

View File

@ -16,10 +16,10 @@ helmProvider:
# Additional Helm configuration, more info here:
# https://helm.sh/docs/helm/helm/
#
# driver sets HELM_DRIVER
driver: ""
# driverSqlConnectionString sets HELM_DRIVER_SQL_CONNECTION_STRING
driverSqlConnectionString: ""
# helmDriver sets HELM_DRIVER
helmDriver: ""
# helmDriverSqlConnectionString sets HELM_DRIVER_SQL_CONNECTION_STRING
helmDriverSqlConnectionString: ""
# Google Container Registry
# GCP Project ID
@ -67,10 +67,16 @@ hipchat:
userName: ""
password: ""
# Mattermost notifications
mattermost:
enabled: false
endpoint: ""
# MS Teams notifications
teams:
enabled: false
webhookUrl: ""
# Mail notifications
mail:
enabled: false

View File

@ -42,6 +42,7 @@ import (
_ "github.com/keel-hq/keel/extension/notification/mail"
_ "github.com/keel-hq/keel/extension/notification/mattermost"
_ "github.com/keel-hq/keel/extension/notification/slack"
_ "github.com/keel-hq/keel/extension/notification/teams"
_ "github.com/keel-hq/keel/extension/notification/webhook"
// credentials helpers

View File

@ -31,6 +31,9 @@ const (
EnvMattermostEndpoint = "MATTERMOST_ENDPOINT"
EnvMattermostName = "MATTERMOST_USERNAME"
// MS Teams webhook url, see https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-using#setting-up-a-custom-incoming-webhook
EnvTeamsWebhookUrl = "TEAMS_WEBHOOK_URL"
// Mail notification settings
EnvMailTo = "MAIL_TO"
EnvMailFrom = "MAIL_FROM"

View File

@ -12,4 +12,5 @@ The manifests in this are generated from the Helm chart automatically.
The `values.yaml` files used to configure `keel` can be found in
[`values`](./values/).
<!-- Deprecated -->
They are automatically generated by running `./deployment/scripts/gen-deploy.sh`.

View File

@ -182,6 +182,9 @@ spec:
# Enable mattermost endpoint
- name: MATTERMOST_ENDPOINT
value: ""
# Enable MS Teams webhook endpoint
- name: TEAMS_WEBHOOK_URL
value: "{{ .teams_webhook_url }}"
- name: SLACK_TOKEN
value: "{{ .slack_token }}"
- name: SLACK_CHANNELS

View File

@ -0,0 +1,142 @@
package teams
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"time"
"github.com/keel-hq/keel/constants"
"github.com/keel-hq/keel/extension/notification"
"github.com/keel-hq/keel/types"
"github.com/keel-hq/keel/version"
log "github.com/sirupsen/logrus"
)
const timeout = 5 * time.Second
type sender struct {
endpoint string
client *http.Client
}
// Config represents the configuration of a Teams Webhook Sender.
type Config struct {
Endpoint string
}
func init() {
notification.RegisterSender("teams", &sender{})
}
func (s *sender) Configure(config *notification.Config) (bool, error) {
// Get configuration
var httpConfig Config
if os.Getenv(constants.EnvTeamsWebhookUrl) != "" {
httpConfig.Endpoint = os.Getenv(constants.EnvTeamsWebhookUrl)
} else {
return false, nil
}
// Validate endpoint URL.
if httpConfig.Endpoint == "" {
return false, nil
}
if _, err := url.ParseRequestURI(httpConfig.Endpoint); err != nil {
return false, fmt.Errorf("could not parse endpoint URL: %s\n", err)
}
s.endpoint = httpConfig.Endpoint
// Setup HTTP client.
s.client = &http.Client{
Transport: http.DefaultTransport,
Timeout: timeout,
}
log.WithFields(log.Fields{
"name": "teams",
"webhook": s.endpoint,
}).Info("extension.notification.teams: sender configured")
return true, nil
}
type SimpleTeamsMessageCard struct {
AtContext string `json:"@context"`
AtType string `json:"@type"`
Sections []TeamsMessageSection `json:"sections"`
Summary string `json:"summary"`
ThemeColor string `json:"themeColor"`
}
type TeamsMessageSection struct {
ActivityImage string `json:"activityImage"`
ActivitySubtitle string `json:"activitySubtitle"`
ActivityText string `json:"activityText"`
ActivityTitle string `json:"activityTitle"`
Facts []TeamsFact `json:"facts"`
Markdown bool `json:"markdown"`
}
type TeamsFact struct {
Name string `json:"name"`
Value string `json:"value"`
}
// Microsoft Teams expects the hexidecimal formatted color to not have a "#" at the front
// Source: https://stackoverflow.com/a/48798875/2199949
func TrimFirstChar(s string) string {
for i := range s {
if i > 0 {
// The value i is the index in s of the second
// character. Slice to remove the first character.
return s[i:]
}
}
// There are 0 or 1 characters in the string.
return ""
}
func (s *sender) Send(event types.EventNotification) error {
// Marshal notification.
jsonNotification, err := json.Marshal(SimpleTeamsMessageCard{
AtType: "MessageCard",
AtContext: "http://schema.org/extensions",
ThemeColor: TrimFirstChar(event.Level.Color()),
Summary: event.Type.String(),
Sections: []TeamsMessageSection{
{
ActivityImage: constants.KeelLogoURL,
ActivityText: fmt.Sprintf("*%s*: %s", event.Name, event.Message),
ActivityTitle: fmt.Sprintf("**%s**",event.Type.String()),
Facts: []TeamsFact{
{
Name: "Version",
Value: fmt.Sprintf("[https://keel.sh](https://keel.sh) %s", version.GetKeelVersion().Version),
},
},
Markdown: true,
},
},
})
if err != nil {
return fmt.Errorf("could not marshal: %s", err)
}
// Send notification via HTTP POST.
resp, err := s.client.Post(s.endpoint, "application/json", bytes.NewBuffer(jsonNotification))
if err != nil || resp == nil || (resp.StatusCode != 200 && resp.StatusCode != 201) {
if resp != nil {
return fmt.Errorf("got status %d, expected 200/201", resp.StatusCode)
}
return err
}
defer resp.Body.Close()
return nil
}

View File

@ -0,0 +1,79 @@
package teams
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
"fmt"
"github.com/keel-hq/keel/constants"
"github.com/keel-hq/keel/types"
"github.com/keel-hq/keel/version"
)
func TestTrimLeftChar(t *testing.T) {
fmt.Printf("%q\n", "Hello, 世界")
fmt.Printf("%q\n", TrimFirstChar(""))
fmt.Printf("%q\n", TrimFirstChar("H"))
fmt.Printf("%q\n", TrimFirstChar("世"))
fmt.Printf("%q\n", TrimFirstChar("Hello"))
fmt.Printf("%q\n", TrimFirstChar("世界"))
}
func TestTeamsRequest(t *testing.T) {
handler := func(resp http.ResponseWriter, req *http.Request) {
body, err := ioutil.ReadAll(req.Body)
if err != nil {
t.Errorf("failed to parse body: %s", err)
}
bodyStr := string(body)
if !strings.Contains(bodyStr, "MessageCard") {
t.Errorf("missing MessageCard indicator")
}
if !strings.Contains(bodyStr, "themeColor") {
t.Errorf("missing themeColor")
}
if !strings.Contains(bodyStr, constants.KeelLogoURL) {
t.Errorf("missing logo url")
}
if !strings.Contains(bodyStr, "**" + types.NotificationPreDeploymentUpdate.String() + "**") {
t.Errorf("missing deployment type")
}
if !strings.Contains(bodyStr, version.GetKeelVersion().Version) {
t.Errorf("missing version")
}
if !strings.Contains(bodyStr, "update deployment") {
t.Errorf("missing name")
}
if !strings.Contains(bodyStr, "message here") {
t.Errorf("missing message")
}
t.Log(bodyStr)
}
// create test server with handler
ts := httptest.NewServer(http.HandlerFunc(handler))
defer ts.Close()
s := &sender{
endpoint: ts.URL,
client: &http.Client{},
}
s.Send(types.EventNotification{
Name: "update deployment",
Message: "message here",
Type: types.NotificationPreDeploymentUpdate,
})
}

1
go.sum
View File

@ -775,6 +775,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=

View File

@ -81,7 +81,7 @@ func (s *TriggerServer) harborHandler(resp http.ResponseWriter, req *http.Reques
"event": hn,
}).Debug("harborHandler: received event, looking for a pushImage tag")
if hn.Type == "pushImage" {
if hn.Type == "pushImage" || hn.Type == "PUSH_ARTIFACT" {
// go trough all the ressource items
for _, e := range hn.EventData.Resources {
imageRepo, err := image.Parse(e.ResourceURL)

View File

@ -31,6 +31,30 @@ var fakeHarborWebhook = ` {
}
`
var fakeHarborWebhook2 = `
{
"type": "PUSH_ARTIFACT",
"occur_at": 1582640688,
"operator": "user",
"event_data": {
"resources": [
{
"digest": "sha256:b4758aaed11c155a476b9857e1178f157759c99cb04c907a04993f5481eff848",
"tag": "latest",
"resource_url": "quay.io/mynamespace/repository:1.2.3"
}
],
"repository": {
"date_created": 1582634337,
"name": "repository",
"namespace": "mynamespace",
"repo_full_name": "mynamespace/repository",
"repo_type": "private"
}
}
}
`
func TestHarborWebhookHandler(t *testing.T) {
fp := &fakeProvider{}
@ -65,6 +89,40 @@ func TestHarborWebhookHandler(t *testing.T) {
}
}
func TestHarborWebhookHandler2(t *testing.T) {
fp := &fakeProvider{}
srv, teardown := NewTestingServer(fp)
defer teardown()
req, err := http.NewRequest("POST", "/v1/webhooks/harbor", bytes.NewBuffer([]byte(fakeHarborWebhook2)))
if err != nil {
t.Fatalf("failed to create req: %s", err)
}
//The response recorder used to record HTTP responses
rec := httptest.NewRecorder()
srv.router.ServeHTTP(rec, req)
if rec.Code != 200 {
t.Errorf("unexpected status code: %d", rec.Code)
t.Log(rec.Body.String())
}
if len(fp.submitted) != 1 {
t.Fatalf("unexpected number of events submitted: %d", len(fp.submitted))
}
if fp.submitted[0].Repository.Name != "quay.io/mynamespace/repository" {
t.Errorf("expected quay.io/mynamespace/repository but got %s", fp.submitted[0].Repository.Name)
}
if fp.submitted[0].Repository.Tag != "1.2.3" {
t.Errorf("expected 1.2.3 but got %s", fp.submitted[0].Repository.Tag)
}
}
var fakeHarborWebhookMalformed = ` {
"type": "pushImage",
"occur_at": 1582640688,