From b76606308ccc4b7858232ba03fc24487cd1cc394 Mon Sep 17 00:00:00 2001
From: ktprograms <71804605+ktprograms@users.noreply.github.com>
Date: Thu, 30 Dec 2021 14:26:36 +0800
Subject: [PATCH] Add field for image list in Secrets Repo Settings (Web UI)
(#638)
- Add field for image list in Secrets Repo Settings (Web UI)
Simple comma separated input field, split into images array
- validate secret images in backend
- trim spaces and filter empty list items
Signed-off-by: 6543 <6543@obermui.de>
Co-authored-by: 6543 <6543@obermui.de>
---
server/model/secret.go | 20 ++++++++
server/model/secret_test.go | 50 ++++++++++++++-----
.../components/repo/settings/SecretsTab.vue | 11 ++++
3 files changed, 69 insertions(+), 12 deletions(-)
diff --git a/server/model/secret.go b/server/model/secret.go
index dd19f3be6..c48344488 100644
--- a/server/model/secret.go
+++ b/server/model/secret.go
@@ -19,6 +19,7 @@ import (
"errors"
"fmt"
"path/filepath"
+ "regexp"
)
var (
@@ -77,6 +78,13 @@ func (s *Secret) Match(event WebhookEvent) bool {
return false
}
+var validDockerImageString = regexp.MustCompile(
+ `^([\w\d\-_\.\/]*` + // optional url prefix
+ `[\w\d\-_]+` + // image name
+ `)+` +
+ `(:[\w\d\-_]+)?$`, // optional image tag
+)
+
// Validate validates the required fields and formats.
func (s *Secret) Validate() error {
for _, event := range s.Events {
@@ -84,6 +92,18 @@ func (s *Secret) Validate() error {
return fmt.Errorf("%s: '%s'", errSecretEventInvalid, event)
}
}
+ if len(s.Events) == 0 {
+ return fmt.Errorf("%s: no event specified", errSecretEventInvalid)
+ }
+
+ for _, image := range s.Images {
+ if len(image) == 0 {
+ return fmt.Errorf("empty image in images")
+ }
+ if !validDockerImageString.MatchString(image) {
+ return fmt.Errorf("image '%s' do not match regexp '%s'", image, validDockerImageString.String())
+ }
+ }
switch {
case len(s.Name) == 0:
diff --git a/server/model/secret_test.go b/server/model/secret_test.go
index 58dc8fea4..9b72a36ff 100644
--- a/server/model/secret_test.go
+++ b/server/model/secret_test.go
@@ -24,13 +24,11 @@ func TestSecret(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Secret", func() {
g.It("should match event", func() {
- secret := Secret{}
- secret.Events = []WebhookEvent{"pull_request"}
+ secret := Secret{Events: []WebhookEvent{"pull_request"}}
g.Assert(secret.Match("pull_request")).IsTrue()
})
g.It("should not match event", func() {
- secret := Secret{}
- secret.Events = []WebhookEvent{"pull_request"}
+ secret := Secret{Events: []WebhookEvent{"pull_request"}}
g.Assert(secret.Match("push")).IsFalse()
})
g.It("should match when no event filters defined", func() {
@@ -38,22 +36,50 @@ func TestSecret(t *testing.T) {
g.Assert(secret.Match("pull_request")).IsTrue()
})
g.It("should pass validation", func() {
- secret := Secret{}
- secret.Name = "secretname"
- secret.Value = "secretvalue"
+ secret := Secret{
+ Name: "secretname",
+ Value: "secretvalue",
+ Events: []WebhookEvent{EventPush},
+ Images: []string{"docker.io/library/mysql:latest", "alpine"},
+ }
err := secret.Validate()
- g.Assert(err).Equal(nil)
+ g.Assert(err).IsNil()
})
g.Describe("should fail validation", func() {
g.It("when no name", func() {
- secret := Secret{}
- secret.Value = "secretvalue"
+ secret := Secret{
+ Value: "secretvalue",
+ Events: []WebhookEvent{EventPush},
+ Images: []string{"docker.io/library/mysql:latest", "alpine"},
+ }
err := secret.Validate()
g.Assert(err).IsNotNil()
})
g.It("when no value", func() {
- secret := Secret{}
- secret.Name = "secretname"
+ secret := Secret{
+ Name: "secretname",
+ Events: []WebhookEvent{EventPush},
+ Images: []string{"docker.io/library/mysql:latest", "alpine"},
+ }
+ err := secret.Validate()
+ g.Assert(err).IsNotNil()
+ })
+ g.It("when no events", func() {
+ secret := Secret{
+ Name: "secretname",
+ Value: "secretvalue",
+ Images: []string{"docker.io/library/mysql-alpine:latest", "alpine"},
+ }
+ err := secret.Validate()
+ g.Assert(err).IsNotNil()
+ })
+ g.It("wrong image no value", func() {
+ secret := Secret{
+ Name: "secretname",
+ Value: "secretvalue",
+ Events: []WebhookEvent{EventPush},
+ Images: []string{"wrong image:no"},
+ }
err := secret.Validate()
g.Assert(err).IsNotNil()
})
diff --git a/web/src/components/repo/settings/SecretsTab.vue b/web/src/components/repo/settings/SecretsTab.vue
index cd4c1a895..c901209b4 100644
--- a/web/src/components/repo/settings/SecretsTab.vue
+++ b/web/src/components/repo/settings/SecretsTab.vue
@@ -50,6 +50,13 @@
+
+
+
+
@@ -118,6 +125,7 @@ export default defineComponent({
const secrets = ref();
const showAddSecret = ref(false);
const selectedSecret = ref>({ ...emptySecret });
+ const images = ref('');
async function loadSecrets() {
if (!repo?.value) {
@@ -132,6 +140,8 @@ export default defineComponent({
throw new Error("Unexpected: Can't load repo");
}
+ const imageList = images.value.split(',').map((s) => s.trim());
+ selectedSecret.value.image = imageList.filter((s) => s !== '');
await apiClient.createSecret(repo.value.owner, repo.value.name, selectedSecret.value);
notifications.notify({ title: 'Secret created', type: 'success' });
showAddSecret.value = false;
@@ -157,6 +167,7 @@ export default defineComponent({
secretEventsOptions,
selectedSecret,
secrets,
+ images,
showAddSecret,
isSaving,
isDeleting,