mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-06-02 11:19:40 +00:00
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>
This commit is contained in:
parent
4f015edc05
commit
b76606308c
|
@ -19,6 +19,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -77,6 +78,13 @@ func (s *Secret) Match(event WebhookEvent) bool {
|
||||||
return false
|
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.
|
// Validate validates the required fields and formats.
|
||||||
func (s *Secret) Validate() error {
|
func (s *Secret) Validate() error {
|
||||||
for _, event := range s.Events {
|
for _, event := range s.Events {
|
||||||
|
@ -84,6 +92,18 @@ func (s *Secret) Validate() error {
|
||||||
return fmt.Errorf("%s: '%s'", errSecretEventInvalid, event)
|
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 {
|
switch {
|
||||||
case len(s.Name) == 0:
|
case len(s.Name) == 0:
|
||||||
|
|
|
@ -24,13 +24,11 @@ func TestSecret(t *testing.T) {
|
||||||
g := goblin.Goblin(t)
|
g := goblin.Goblin(t)
|
||||||
g.Describe("Secret", func() {
|
g.Describe("Secret", func() {
|
||||||
g.It("should match event", func() {
|
g.It("should match event", func() {
|
||||||
secret := Secret{}
|
secret := Secret{Events: []WebhookEvent{"pull_request"}}
|
||||||
secret.Events = []WebhookEvent{"pull_request"}
|
|
||||||
g.Assert(secret.Match("pull_request")).IsTrue()
|
g.Assert(secret.Match("pull_request")).IsTrue()
|
||||||
})
|
})
|
||||||
g.It("should not match event", func() {
|
g.It("should not match event", func() {
|
||||||
secret := Secret{}
|
secret := Secret{Events: []WebhookEvent{"pull_request"}}
|
||||||
secret.Events = []WebhookEvent{"pull_request"}
|
|
||||||
g.Assert(secret.Match("push")).IsFalse()
|
g.Assert(secret.Match("push")).IsFalse()
|
||||||
})
|
})
|
||||||
g.It("should match when no event filters defined", func() {
|
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.Assert(secret.Match("pull_request")).IsTrue()
|
||||||
})
|
})
|
||||||
g.It("should pass validation", func() {
|
g.It("should pass validation", func() {
|
||||||
secret := Secret{}
|
secret := Secret{
|
||||||
secret.Name = "secretname"
|
Name: "secretname",
|
||||||
secret.Value = "secretvalue"
|
Value: "secretvalue",
|
||||||
|
Events: []WebhookEvent{EventPush},
|
||||||
|
Images: []string{"docker.io/library/mysql:latest", "alpine"},
|
||||||
|
}
|
||||||
err := secret.Validate()
|
err := secret.Validate()
|
||||||
g.Assert(err).Equal(nil)
|
g.Assert(err).IsNil()
|
||||||
})
|
})
|
||||||
g.Describe("should fail validation", func() {
|
g.Describe("should fail validation", func() {
|
||||||
g.It("when no name", func() {
|
g.It("when no name", func() {
|
||||||
secret := Secret{}
|
secret := Secret{
|
||||||
secret.Value = "secretvalue"
|
Value: "secretvalue",
|
||||||
|
Events: []WebhookEvent{EventPush},
|
||||||
|
Images: []string{"docker.io/library/mysql:latest", "alpine"},
|
||||||
|
}
|
||||||
err := secret.Validate()
|
err := secret.Validate()
|
||||||
g.Assert(err).IsNotNil()
|
g.Assert(err).IsNotNil()
|
||||||
})
|
})
|
||||||
g.It("when no value", func() {
|
g.It("when no value", func() {
|
||||||
secret := Secret{}
|
secret := Secret{
|
||||||
secret.Name = "secretname"
|
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()
|
err := secret.Validate()
|
||||||
g.Assert(err).IsNotNil()
|
g.Assert(err).IsNotNil()
|
||||||
})
|
})
|
||||||
|
|
|
@ -50,6 +50,13 @@
|
||||||
<TextField v-model="selectedSecret.value" placeholder="Value" :lines="5" required />
|
<TextField v-model="selectedSecret.value" placeholder="Value" :lines="5" required />
|
||||||
</InputField>
|
</InputField>
|
||||||
|
|
||||||
|
<InputField label="Available for following images">
|
||||||
|
<TextField
|
||||||
|
v-model="images"
|
||||||
|
placeholder="Comma separated list of images where this secret is available, leave empty to allow all images"
|
||||||
|
/>
|
||||||
|
</InputField>
|
||||||
|
|
||||||
<InputField label="Available at following events">
|
<InputField label="Available at following events">
|
||||||
<CheckboxesField v-model="selectedSecret.event" :options="secretEventsOptions" />
|
<CheckboxesField v-model="selectedSecret.event" :options="secretEventsOptions" />
|
||||||
</InputField>
|
</InputField>
|
||||||
|
@ -118,6 +125,7 @@ export default defineComponent({
|
||||||
const secrets = ref<Secret[]>();
|
const secrets = ref<Secret[]>();
|
||||||
const showAddSecret = ref(false);
|
const showAddSecret = ref(false);
|
||||||
const selectedSecret = ref<Partial<Secret>>({ ...emptySecret });
|
const selectedSecret = ref<Partial<Secret>>({ ...emptySecret });
|
||||||
|
const images = ref('');
|
||||||
|
|
||||||
async function loadSecrets() {
|
async function loadSecrets() {
|
||||||
if (!repo?.value) {
|
if (!repo?.value) {
|
||||||
|
@ -132,6 +140,8 @@ export default defineComponent({
|
||||||
throw new Error("Unexpected: Can't load repo");
|
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);
|
await apiClient.createSecret(repo.value.owner, repo.value.name, selectedSecret.value);
|
||||||
notifications.notify({ title: 'Secret created', type: 'success' });
|
notifications.notify({ title: 'Secret created', type: 'success' });
|
||||||
showAddSecret.value = false;
|
showAddSecret.value = false;
|
||||||
|
@ -157,6 +167,7 @@ export default defineComponent({
|
||||||
secretEventsOptions,
|
secretEventsOptions,
|
||||||
selectedSecret,
|
selectedSecret,
|
||||||
secrets,
|
secrets,
|
||||||
|
images,
|
||||||
showAddSecret,
|
showAddSecret,
|
||||||
isSaving,
|
isSaving,
|
||||||
isDeleting,
|
isDeleting,
|
||||||
|
|
Loading…
Reference in a new issue