support for per-organization secrets
This commit is contained in:
parent
727177da13
commit
96132e3d0a
34 changed files with 1819 additions and 6 deletions
|
@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- support for Cron job name in Yaml when block, by [@bradrydzewski](https://github.com/bradrydzewski). [#2628](https://github.com/drone/drone/issues/2628).
|
- support for Cron job name in Yaml when block, by [@bradrydzewski](https://github.com/bradrydzewski). [#2628](https://github.com/drone/drone/issues/2628).
|
||||||
- sqlite username column changed to case-insensitive, by [@bradrydzewski](https://github.com/bradrydzewski).
|
- sqlite username column changed to case-insensitive, by [@bradrydzewski](https://github.com/bradrydzewski).
|
||||||
- endpoint to purge repository from database, by [@bradrydzewski](https://github.com/bradrydzewski).
|
- endpoint to purge repository from database, by [@bradrydzewski](https://github.com/bradrydzewski).
|
||||||
|
- support for per-organization secrets, by [@bradrydzewski](https://github.com/bradrydzewski).
|
||||||
- update drone-yaml from version 1.0.6 to 1.0.8.
|
- update drone-yaml from version 1.0.6 to 1.0.8.
|
||||||
- update drone-runtime from version 1.0.4 to 1.0.6.
|
- update drone-runtime from version 1.0.4 to 1.0.6.
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"github.com/drone/drone/store/perm"
|
"github.com/drone/drone/store/perm"
|
||||||
"github.com/drone/drone/store/repos"
|
"github.com/drone/drone/store/repos"
|
||||||
"github.com/drone/drone/store/secret"
|
"github.com/drone/drone/store/secret"
|
||||||
|
"github.com/drone/drone/store/secret/global"
|
||||||
"github.com/drone/drone/store/shared/db"
|
"github.com/drone/drone/store/shared/db"
|
||||||
"github.com/drone/drone/store/shared/encrypt"
|
"github.com/drone/drone/store/shared/encrypt"
|
||||||
"github.com/drone/drone/store/stage"
|
"github.com/drone/drone/store/stage"
|
||||||
|
@ -47,6 +48,7 @@ var storeSet = wire.NewSet(
|
||||||
cron.New,
|
cron.New,
|
||||||
perm.New,
|
perm.New,
|
||||||
secret.New,
|
secret.New,
|
||||||
|
global.New,
|
||||||
step.New,
|
step.New,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"github.com/drone/drone/store/cron"
|
"github.com/drone/drone/store/cron"
|
||||||
"github.com/drone/drone/store/perm"
|
"github.com/drone/drone/store/perm"
|
||||||
"github.com/drone/drone/store/secret"
|
"github.com/drone/drone/store/secret"
|
||||||
|
"github.com/drone/drone/store/secret/global"
|
||||||
"github.com/drone/drone/store/step"
|
"github.com/drone/drone/store/step"
|
||||||
"github.com/drone/drone/trigger"
|
"github.com/drone/drone/trigger"
|
||||||
cron2 "github.com/drone/drone/trigger/cron"
|
cron2 "github.com/drone/drone/trigger/cron"
|
||||||
|
@ -70,8 +71,9 @@ func InitializeApplication(config2 config.Config) (application, error) {
|
||||||
return application{}, err
|
return application{}, err
|
||||||
}
|
}
|
||||||
secretStore := secret.New(db, encrypter)
|
secretStore := secret.New(db, encrypter)
|
||||||
|
globalSecretStore := global.New(db, encrypter)
|
||||||
stepStore := step.New(db)
|
stepStore := step.New(db)
|
||||||
buildManager := manager.New(buildStore, configService, corePubsub, logStore, logStream, netrcService, repositoryStore, scheduler, secretStore, statusService, stageStore, stepStore, system, userStore, webhookSender)
|
buildManager := manager.New(buildStore, configService, corePubsub, logStore, logStream, netrcService, repositoryStore, scheduler, secretStore, globalSecretStore, statusService, stageStore, stepStore, system, userStore, webhookSender)
|
||||||
secretService := provideSecretPlugin(config2)
|
secretService := provideSecretPlugin(config2)
|
||||||
registryService := provideRegistryPlugin(config2)
|
registryService := provideRegistryPlugin(config2)
|
||||||
runner := provideRunner(buildManager, secretService, registryService, config2)
|
runner := provideRunner(buildManager, secretService, registryService, config2)
|
||||||
|
@ -82,7 +84,7 @@ func InitializeApplication(config2 config.Config) (application, error) {
|
||||||
session := provideSession(userStore, config2)
|
session := provideSession(userStore, config2)
|
||||||
batcher := batch.New(db)
|
batcher := batch.New(db)
|
||||||
syncer := provideSyncer(repositoryService, repositoryStore, userStore, batcher, config2)
|
syncer := provideSyncer(repositoryService, repositoryStore, userStore, batcher, config2)
|
||||||
server := api.New(buildStore, commitService, cronStore, corePubsub, hookService, logStore, coreLicense, licenseService, permStore, repositoryStore, repositoryService, scheduler, secretStore, stageStore, stepStore, statusService, session, logStream, syncer, system, triggerer, userStore, webhookSender)
|
server := api.New(buildStore, commitService, cronStore, corePubsub, globalSecretStore, hookService, logStore, coreLicense, licenseService, permStore, repositoryStore, repositoryService, scheduler, secretStore, stageStore, stepStore, statusService, session, logStream, syncer, system, triggerer, userStore, webhookSender)
|
||||||
organizationService := orgs.New(client, renewer)
|
organizationService := orgs.New(client, renewer)
|
||||||
userService := user.New(client)
|
userService := user.New(client)
|
||||||
admissionService := provideAdmissionPlugin(client, organizationService, userService, config2)
|
admissionService := provideAdmissionPlugin(client, organizationService, userService, config2)
|
||||||
|
|
|
@ -33,7 +33,9 @@ type (
|
||||||
Secret struct {
|
Secret struct {
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
RepoID int64 `json:"repo_id,omitempty"`
|
RepoID int64 `json:"repo_id,omitempty"`
|
||||||
|
Namespace string `json:"repo_namespace,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
Data string `json:"data,omitempty"`
|
Data string `json:"data,omitempty"`
|
||||||
PullRequest bool `json:"pull_request,omitempty"`
|
PullRequest bool `json:"pull_request,omitempty"`
|
||||||
PullRequestPush bool `json:"pull_request_push,omitempty"`
|
PullRequestPush bool `json:"pull_request_push,omitempty"`
|
||||||
|
@ -69,6 +71,32 @@ type (
|
||||||
Delete(context.Context, *Secret) error
|
Delete(context.Context, *Secret) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GlobalSecretStore manages global secrets accessible to
|
||||||
|
// all repositories in the system.
|
||||||
|
GlobalSecretStore interface {
|
||||||
|
// List returns a secret list from the datastore.
|
||||||
|
List(ctx context.Context, namespace string) ([]*Secret, error)
|
||||||
|
|
||||||
|
// ListAll returns a secret list from the datastore
|
||||||
|
// for all namespaces.
|
||||||
|
ListAll(ctx context.Context) ([]*Secret, error)
|
||||||
|
|
||||||
|
// Find returns a secret from the datastore.
|
||||||
|
Find(ctx context.Context, id int64) (*Secret, error)
|
||||||
|
|
||||||
|
// FindName returns a secret from the datastore.
|
||||||
|
FindName(ctx context.Context, namespace, name string) (*Secret, error)
|
||||||
|
|
||||||
|
// Create persists a new secret to the datastore.
|
||||||
|
Create(ctx context.Context, secret *Secret) error
|
||||||
|
|
||||||
|
// Update persists an updated secret to the datastore.
|
||||||
|
Update(ctx context.Context, secret *Secret) error
|
||||||
|
|
||||||
|
// Delete deletes a secret from the datastore.
|
||||||
|
Delete(ctx context.Context, secret *Secret) error
|
||||||
|
}
|
||||||
|
|
||||||
// SecretService provides secrets from an external service.
|
// SecretService provides secrets from an external service.
|
||||||
SecretService interface {
|
SecretService interface {
|
||||||
// Find returns a named secret from the global remote service.
|
// Find returns a named secret from the global remote service.
|
||||||
|
@ -95,7 +123,9 @@ func (s *Secret) Copy() *Secret {
|
||||||
return &Secret{
|
return &Secret{
|
||||||
ID: s.ID,
|
ID: s.ID,
|
||||||
RepoID: s.RepoID,
|
RepoID: s.RepoID,
|
||||||
|
Namespace: s.Namespace,
|
||||||
Name: s.Name,
|
Name: s.Name,
|
||||||
|
Type: s.Type,
|
||||||
PullRequest: s.PullRequest,
|
PullRequest: s.PullRequest,
|
||||||
PullRequestPush: s.PullRequestPush,
|
PullRequestPush: s.PullRequestPush,
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,8 @@ func TestSecretSafeCopy(t *testing.T) {
|
||||||
ID: 1,
|
ID: 1,
|
||||||
RepoID: 2,
|
RepoID: 2,
|
||||||
Name: "docker_password",
|
Name: "docker_password",
|
||||||
|
Namespace: "octocat",
|
||||||
|
Type: "",
|
||||||
Data: "correct-horse-battery-staple",
|
Data: "correct-horse-battery-staple",
|
||||||
PullRequest: true,
|
PullRequest: true,
|
||||||
PullRequestPush: true,
|
PullRequestPush: true,
|
||||||
|
@ -61,6 +63,9 @@ func TestSecretSafeCopy(t *testing.T) {
|
||||||
if got, want := after.Name, before.Name; got != want {
|
if got, want := after.Name, before.Name; got != want {
|
||||||
t.Errorf("Want secret Name %s, got %s", want, got)
|
t.Errorf("Want secret Name %s, got %s", want, got)
|
||||||
}
|
}
|
||||||
|
if got, want := after.Namespace, before.Namespace; got != want {
|
||||||
|
t.Errorf("Want secret Namespace %s, got %s", want, got)
|
||||||
|
}
|
||||||
if got, want := after.PullRequest, before.PullRequest; got != want {
|
if got, want := after.PullRequest, before.PullRequest; got != want {
|
||||||
t.Errorf("Want secret PullRequest %v, got %v", want, got)
|
t.Errorf("Want secret PullRequest %v, got %v", want, got)
|
||||||
}
|
}
|
||||||
|
|
1
go.sum
1
go.sum
|
@ -65,6 +65,7 @@ github.com/drone/go-login v1.0.3 h1:YmZMUoWWd3QrgmobC1DcExFjW7w2ZEBO1R1VeeobIRU=
|
||||||
github.com/drone/go-login v1.0.3/go.mod h1:FLxy9vRzLbyBxoCJYxGbG9R0WGn6OyuvBmAtYNt43uw=
|
github.com/drone/go-login v1.0.3/go.mod h1:FLxy9vRzLbyBxoCJYxGbG9R0WGn6OyuvBmAtYNt43uw=
|
||||||
github.com/drone/go-login v1.0.4-0.20190308175602-213d1719faed h1:Y0qiKFf6gsgTRTQS1roMh7kKVyrx+HSQmFsIgcZsHsM=
|
github.com/drone/go-login v1.0.4-0.20190308175602-213d1719faed h1:Y0qiKFf6gsgTRTQS1roMh7kKVyrx+HSQmFsIgcZsHsM=
|
||||||
github.com/drone/go-login v1.0.4-0.20190308175602-213d1719faed/go.mod h1:FLxy9vRzLbyBxoCJYxGbG9R0WGn6OyuvBmAtYNt43uw=
|
github.com/drone/go-login v1.0.4-0.20190308175602-213d1719faed/go.mod h1:FLxy9vRzLbyBxoCJYxGbG9R0WGn6OyuvBmAtYNt43uw=
|
||||||
|
github.com/drone/go-login v1.0.4-0.20190311170324-2a4df4f242a2 h1:RGpgNkowJc5LAVn/ZONx70qmnaTA0z/3hHPzTBdAEO8=
|
||||||
github.com/drone/go-login v1.0.4-0.20190311170324-2a4df4f242a2/go.mod h1:FLxy9vRzLbyBxoCJYxGbG9R0WGn6OyuvBmAtYNt43uw=
|
github.com/drone/go-login v1.0.4-0.20190311170324-2a4df4f242a2/go.mod h1:FLxy9vRzLbyBxoCJYxGbG9R0WGn6OyuvBmAtYNt43uw=
|
||||||
github.com/drone/go-scm v1.2.0 h1:ezb8xCvMHX99cSOf3WPI2bmYS6tDVTTap9BiPsPmmXg=
|
github.com/drone/go-scm v1.2.0 h1:ezb8xCvMHX99cSOf3WPI2bmYS6tDVTTap9BiPsPmmXg=
|
||||||
github.com/drone/go-scm v1.2.0/go.mod h1:YT4FxQ3U/ltdCrBJR9B0tRpJ1bYA/PM3NyaLE/rYIvw=
|
github.com/drone/go-scm v1.2.0/go.mod h1:YT4FxQ3U/ltdCrBJR9B0tRpJ1bYA/PM3NyaLE/rYIvw=
|
||||||
|
|
|
@ -34,6 +34,7 @@ import (
|
||||||
"github.com/drone/drone/handler/api/repos/encrypt"
|
"github.com/drone/drone/handler/api/repos/encrypt"
|
||||||
"github.com/drone/drone/handler/api/repos/secrets"
|
"github.com/drone/drone/handler/api/repos/secrets"
|
||||||
"github.com/drone/drone/handler/api/repos/sign"
|
"github.com/drone/drone/handler/api/repos/sign"
|
||||||
|
globalsecrets "github.com/drone/drone/handler/api/secrets"
|
||||||
"github.com/drone/drone/handler/api/system"
|
"github.com/drone/drone/handler/api/system"
|
||||||
"github.com/drone/drone/handler/api/user"
|
"github.com/drone/drone/handler/api/user"
|
||||||
"github.com/drone/drone/handler/api/users"
|
"github.com/drone/drone/handler/api/users"
|
||||||
|
@ -58,6 +59,7 @@ func New(
|
||||||
commits core.CommitService,
|
commits core.CommitService,
|
||||||
cron core.CronStore,
|
cron core.CronStore,
|
||||||
events core.Pubsub,
|
events core.Pubsub,
|
||||||
|
globals core.GlobalSecretStore,
|
||||||
hooks core.HookService,
|
hooks core.HookService,
|
||||||
logs core.LogStore,
|
logs core.LogStore,
|
||||||
license *core.License,
|
license *core.License,
|
||||||
|
@ -83,6 +85,7 @@ func New(
|
||||||
Cron: cron,
|
Cron: cron,
|
||||||
Commits: commits,
|
Commits: commits,
|
||||||
Events: events,
|
Events: events,
|
||||||
|
Globals: globals,
|
||||||
Hooks: hooks,
|
Hooks: hooks,
|
||||||
Logs: logs,
|
Logs: logs,
|
||||||
License: license,
|
License: license,
|
||||||
|
@ -111,6 +114,7 @@ type Server struct {
|
||||||
Cron core.CronStore
|
Cron core.CronStore
|
||||||
Commits core.CommitService
|
Commits core.CommitService
|
||||||
Events core.Pubsub
|
Events core.Pubsub
|
||||||
|
Globals core.GlobalSecretStore
|
||||||
Hooks core.HookService
|
Hooks core.HookService
|
||||||
Logs core.LogStore
|
Logs core.LogStore
|
||||||
License *core.License
|
License *core.License
|
||||||
|
@ -298,6 +302,16 @@ func (s Server) Handler() http.Handler {
|
||||||
r.Get("/incomplete", globalbuilds.HandleIncomplete(s.Repos))
|
r.Get("/incomplete", globalbuilds.HandleIncomplete(s.Repos))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
r.Route("/secrets", func(r chi.Router) {
|
||||||
|
r.Use(acl.AuthorizeAdmin)
|
||||||
|
r.Get("/", globalsecrets.HandleAll(s.Globals))
|
||||||
|
r.Get("/{namespace}", globalsecrets.HandleList(s.Globals))
|
||||||
|
r.Post("/{namespace}", globalsecrets.HandleCreate(s.Globals))
|
||||||
|
r.Get("/{namespace}/{name}", globalsecrets.HandleFind(s.Globals))
|
||||||
|
r.Patch("/{namespace}/{name}", globalsecrets.HandleUpdate(s.Globals))
|
||||||
|
r.Delete("/{namespace}/{name}", globalsecrets.HandleDelete(s.Globals))
|
||||||
|
})
|
||||||
|
|
||||||
r.Route("/system", func(r chi.Router) {
|
r.Route("/system", func(r chi.Router) {
|
||||||
r.Use(acl.AuthorizeAdmin)
|
r.Use(acl.AuthorizeAdmin)
|
||||||
// r.Get("/license", system.HandleLicense())
|
// r.Get("/license", system.HandleLicense())
|
||||||
|
|
33
handler/api/secrets/all.go
Normal file
33
handler/api/secrets/all.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Drone Non-Commercial License
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !oss
|
||||||
|
|
||||||
|
package secrets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/drone/drone/core"
|
||||||
|
"github.com/drone/drone/handler/api/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandleAll returns an http.HandlerFunc that writes a json-encoded
|
||||||
|
// list of secrets to the response body.
|
||||||
|
func HandleAll(secrets core.GlobalSecretStore) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
list, err := secrets.ListAll(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
render.NotFound(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// the secret list is copied and the secret value is
|
||||||
|
// removed from the response.
|
||||||
|
secrets := []*core.Secret{}
|
||||||
|
for _, secret := range list {
|
||||||
|
secrets = append(secrets, secret.Copy())
|
||||||
|
}
|
||||||
|
render.JSON(w, secrets, 200)
|
||||||
|
}
|
||||||
|
}
|
65
handler/api/secrets/all_test.go
Normal file
65
handler/api/secrets/all_test.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Drone Non-Commercial License
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !oss
|
||||||
|
|
||||||
|
package secrets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/drone/drone/core"
|
||||||
|
"github.com/drone/drone/handler/api/errors"
|
||||||
|
"github.com/drone/drone/mock"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHandleAll(t *testing.T) {
|
||||||
|
controller := gomock.NewController(t)
|
||||||
|
defer controller.Finish()
|
||||||
|
|
||||||
|
secrets := mock.NewMockGlobalSecretStore(controller)
|
||||||
|
secrets.EXPECT().ListAll(gomock.Any()).Return(dummySecretList, nil)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r := httptest.NewRequest("GET", "/", nil)
|
||||||
|
|
||||||
|
HandleAll(secrets).ServeHTTP(w, r)
|
||||||
|
if got, want := w.Code, http.StatusOK; want != got {
|
||||||
|
t.Errorf("Want response code %d, got %d", want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, want := []*core.Secret{}, dummySecretListScrubbed
|
||||||
|
json.NewDecoder(w.Body).Decode(&got)
|
||||||
|
if diff := cmp.Diff(got, want); len(diff) != 0 {
|
||||||
|
t.Errorf(diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleAll_SecretListErr(t *testing.T) {
|
||||||
|
controller := gomock.NewController(t)
|
||||||
|
defer controller.Finish()
|
||||||
|
|
||||||
|
secrets := mock.NewMockGlobalSecretStore(controller)
|
||||||
|
secrets.EXPECT().ListAll(gomock.Any()).Return(nil, errors.ErrNotFound)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r := httptest.NewRequest("GET", "/", nil)
|
||||||
|
|
||||||
|
HandleAll(secrets).ServeHTTP(w, r)
|
||||||
|
if got, want := w.Code, http.StatusNotFound; want != got {
|
||||||
|
t.Errorf("Want response code %d, got %d", want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, want := new(errors.Error), errors.ErrNotFound
|
||||||
|
json.NewDecoder(w.Body).Decode(got)
|
||||||
|
if diff := cmp.Diff(got, want); len(diff) != 0 {
|
||||||
|
t.Errorf(diff)
|
||||||
|
}
|
||||||
|
}
|
60
handler/api/secrets/create.go
Normal file
60
handler/api/secrets/create.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Drone Non-Commercial License
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !oss
|
||||||
|
|
||||||
|
package secrets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/drone/drone/core"
|
||||||
|
"github.com/drone/drone/handler/api/render"
|
||||||
|
"github.com/go-chi/chi"
|
||||||
|
)
|
||||||
|
|
||||||
|
type secretInput struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Data string `json:"data"`
|
||||||
|
PullRequest bool `json:"pull_request"`
|
||||||
|
PullRequestPush bool `json:"pull_request_push"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleCreate returns an http.HandlerFunc that processes http
|
||||||
|
// requests to create a new secret.
|
||||||
|
func HandleCreate(secrets core.GlobalSecretStore) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
in := new(secretInput)
|
||||||
|
err := json.NewDecoder(r.Body).Decode(in)
|
||||||
|
if err != nil {
|
||||||
|
render.BadRequest(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &core.Secret{
|
||||||
|
Namespace: chi.URLParam(r, "namespace"),
|
||||||
|
Name: in.Name,
|
||||||
|
Data: in.Data,
|
||||||
|
PullRequest: in.PullRequest,
|
||||||
|
PullRequestPush: in.PullRequestPush,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.Validate()
|
||||||
|
if err != nil {
|
||||||
|
render.BadRequest(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = secrets.Create(r.Context(), s)
|
||||||
|
if err != nil {
|
||||||
|
render.InternalError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s = s.Copy()
|
||||||
|
render.JSON(w, s, 200)
|
||||||
|
}
|
||||||
|
}
|
139
handler/api/secrets/create_test.go
Normal file
139
handler/api/secrets/create_test.go
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Drone Non-Commercial License
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !oss
|
||||||
|
|
||||||
|
package secrets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/drone/drone/core"
|
||||||
|
"github.com/drone/drone/handler/api/errors"
|
||||||
|
"github.com/drone/drone/mock"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHandleCreate(t *testing.T) {
|
||||||
|
controller := gomock.NewController(t)
|
||||||
|
defer controller.Finish()
|
||||||
|
|
||||||
|
secrets := mock.NewMockGlobalSecretStore(controller)
|
||||||
|
secrets.EXPECT().Create(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
|
||||||
|
c := new(chi.Context)
|
||||||
|
c.URLParams.Add("namespace", "octocat")
|
||||||
|
|
||||||
|
in := new(bytes.Buffer)
|
||||||
|
json.NewEncoder(in).Encode(dummySecret)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r := httptest.NewRequest("GET", "/", in)
|
||||||
|
r = r.WithContext(
|
||||||
|
context.WithValue(context.Background(), chi.RouteCtxKey, c),
|
||||||
|
)
|
||||||
|
|
||||||
|
HandleCreate(secrets).ServeHTTP(w, r)
|
||||||
|
if got, want := w.Code, http.StatusOK; want != got {
|
||||||
|
t.Errorf("Want response code %d, got %d", want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, want := &core.Secret{}, dummySecretScrubbed
|
||||||
|
json.NewDecoder(w.Body).Decode(got)
|
||||||
|
if diff := cmp.Diff(got, want); len(diff) != 0 {
|
||||||
|
t.Errorf(diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleCreate_ValidationError(t *testing.T) {
|
||||||
|
controller := gomock.NewController(t)
|
||||||
|
defer controller.Finish()
|
||||||
|
|
||||||
|
c := new(chi.Context)
|
||||||
|
c.URLParams.Add("namespace", "octocat")
|
||||||
|
|
||||||
|
in := new(bytes.Buffer)
|
||||||
|
json.NewEncoder(in).Encode(&core.Secret{Name: "", Data: "pa55word"})
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r := httptest.NewRequest("GET", "/", in)
|
||||||
|
r = r.WithContext(
|
||||||
|
context.WithValue(context.Background(), chi.RouteCtxKey, c),
|
||||||
|
)
|
||||||
|
|
||||||
|
HandleCreate(nil).ServeHTTP(w, r)
|
||||||
|
if got, want := w.Code, http.StatusBadRequest; want != got {
|
||||||
|
t.Errorf("Want response code %d, got %d", want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, want := &errors.Error{}, &errors.Error{Message: "Invalid Secret Name"}
|
||||||
|
json.NewDecoder(w.Body).Decode(got)
|
||||||
|
if diff := cmp.Diff(got, want); len(diff) != 0 {
|
||||||
|
t.Errorf(diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleCreate_BadRequest(t *testing.T) {
|
||||||
|
controller := gomock.NewController(t)
|
||||||
|
defer controller.Finish()
|
||||||
|
|
||||||
|
c := new(chi.Context)
|
||||||
|
c.URLParams.Add("namespace", "octocat")
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r := httptest.NewRequest("GET", "/", nil)
|
||||||
|
r = r.WithContext(
|
||||||
|
context.WithValue(context.Background(), chi.RouteCtxKey, c),
|
||||||
|
)
|
||||||
|
|
||||||
|
HandleCreate(nil).ServeHTTP(w, r)
|
||||||
|
if got, want := w.Code, http.StatusBadRequest; want != got {
|
||||||
|
t.Errorf("Want response code %d, got %d", want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, want := &errors.Error{}, &errors.Error{Message: "EOF"}
|
||||||
|
json.NewDecoder(w.Body).Decode(got)
|
||||||
|
if diff := cmp.Diff(got, want); len(diff) != 0 {
|
||||||
|
t.Errorf(diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleCreate_CreateError(t *testing.T) {
|
||||||
|
controller := gomock.NewController(t)
|
||||||
|
defer controller.Finish()
|
||||||
|
|
||||||
|
secrets := mock.NewMockGlobalSecretStore(controller)
|
||||||
|
secrets.EXPECT().Create(gomock.Any(), gomock.Any()).Return(errors.ErrNotFound)
|
||||||
|
|
||||||
|
c := new(chi.Context)
|
||||||
|
c.URLParams.Add("namespace", "octocat")
|
||||||
|
|
||||||
|
in := new(bytes.Buffer)
|
||||||
|
json.NewEncoder(in).Encode(dummySecret)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r := httptest.NewRequest("GET", "/", in)
|
||||||
|
r = r.WithContext(
|
||||||
|
context.WithValue(context.Background(), chi.RouteCtxKey, c),
|
||||||
|
)
|
||||||
|
|
||||||
|
HandleCreate(secrets).ServeHTTP(w, r)
|
||||||
|
if got, want := w.Code, http.StatusInternalServerError; want != got {
|
||||||
|
t.Errorf("Want response code %d, got %d", want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, want := new(errors.Error), errors.ErrNotFound
|
||||||
|
json.NewDecoder(w.Body).Decode(got)
|
||||||
|
if diff := cmp.Diff(got, want); len(diff) != 0 {
|
||||||
|
t.Errorf(diff)
|
||||||
|
}
|
||||||
|
}
|
38
handler/api/secrets/delete.go
Normal file
38
handler/api/secrets/delete.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Drone Non-Commercial License
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !oss
|
||||||
|
|
||||||
|
package secrets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/drone/drone/core"
|
||||||
|
"github.com/drone/drone/handler/api/render"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandleDelete returns an http.HandlerFunc that processes http
|
||||||
|
// requests to delete the secret.
|
||||||
|
func HandleDelete(secrets core.GlobalSecretStore) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var (
|
||||||
|
namespace = chi.URLParam(r, "namespace")
|
||||||
|
name = chi.URLParam(r, "name")
|
||||||
|
)
|
||||||
|
s, err := secrets.FindName(r.Context(), namespace, name)
|
||||||
|
if err != nil {
|
||||||
|
render.NotFound(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = secrets.Delete(r.Context(), s)
|
||||||
|
if err != nil {
|
||||||
|
render.InternalError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
}
|
105
handler/api/secrets/delete_test.go
Normal file
105
handler/api/secrets/delete_test.go
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Drone Non-Commercial License
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !oss
|
||||||
|
|
||||||
|
package secrets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/drone/drone/handler/api/errors"
|
||||||
|
"github.com/drone/drone/mock"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHandleDelete(t *testing.T) {
|
||||||
|
controller := gomock.NewController(t)
|
||||||
|
defer controller.Finish()
|
||||||
|
|
||||||
|
secrets := mock.NewMockGlobalSecretStore(controller)
|
||||||
|
secrets.EXPECT().FindName(gomock.Any(), dummySecret.Namespace, dummySecret.Name).Return(dummySecret, nil)
|
||||||
|
secrets.EXPECT().Delete(gomock.Any(), dummySecret).Return(nil)
|
||||||
|
|
||||||
|
c := new(chi.Context)
|
||||||
|
c.URLParams.Add("namespace", "octocat")
|
||||||
|
c.URLParams.Add("name", "github_password")
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r := httptest.NewRequest("GET", "/", nil)
|
||||||
|
r = r.WithContext(
|
||||||
|
context.WithValue(context.Background(), chi.RouteCtxKey, c),
|
||||||
|
)
|
||||||
|
|
||||||
|
HandleDelete(secrets).ServeHTTP(w, r)
|
||||||
|
if got, want := w.Code, http.StatusNoContent; want != got {
|
||||||
|
t.Errorf("Want response code %d, got %d", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleDelete_SecretNotFound(t *testing.T) {
|
||||||
|
controller := gomock.NewController(t)
|
||||||
|
defer controller.Finish()
|
||||||
|
|
||||||
|
secrets := mock.NewMockGlobalSecretStore(controller)
|
||||||
|
secrets.EXPECT().FindName(gomock.Any(), dummySecret.Namespace, dummySecret.Name).Return(nil, errors.ErrNotFound)
|
||||||
|
|
||||||
|
c := new(chi.Context)
|
||||||
|
c.URLParams.Add("namespace", "octocat")
|
||||||
|
c.URLParams.Add("name", "github_password")
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r := httptest.NewRequest("GET", "/", nil)
|
||||||
|
r = r.WithContext(
|
||||||
|
context.WithValue(context.Background(), chi.RouteCtxKey, c),
|
||||||
|
)
|
||||||
|
|
||||||
|
HandleDelete(secrets).ServeHTTP(w, r)
|
||||||
|
if got, want := w.Code, http.StatusNotFound; want != got {
|
||||||
|
t.Errorf("Want response code %d, got %d", want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, want := new(errors.Error), errors.ErrNotFound
|
||||||
|
json.NewDecoder(w.Body).Decode(got)
|
||||||
|
if diff := cmp.Diff(got, want); len(diff) != 0 {
|
||||||
|
t.Errorf(diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleDelete_DeleteError(t *testing.T) {
|
||||||
|
controller := gomock.NewController(t)
|
||||||
|
defer controller.Finish()
|
||||||
|
|
||||||
|
secrets := mock.NewMockGlobalSecretStore(controller)
|
||||||
|
secrets.EXPECT().FindName(gomock.Any(), dummySecret.Namespace, dummySecret.Name).Return(dummySecret, nil)
|
||||||
|
secrets.EXPECT().Delete(gomock.Any(), dummySecret).Return(errors.ErrNotFound)
|
||||||
|
|
||||||
|
c := new(chi.Context)
|
||||||
|
c.URLParams.Add("namespace", "octocat")
|
||||||
|
c.URLParams.Add("name", "github_password")
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r := httptest.NewRequest("GET", "/", nil)
|
||||||
|
r = r.WithContext(
|
||||||
|
context.WithValue(context.Background(), chi.RouteCtxKey, c),
|
||||||
|
)
|
||||||
|
|
||||||
|
HandleDelete(secrets).ServeHTTP(w, r)
|
||||||
|
if got, want := w.Code, http.StatusInternalServerError; want != got {
|
||||||
|
t.Errorf("Want response code %d, got %d", want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, want := new(errors.Error), errors.ErrNotFound
|
||||||
|
json.NewDecoder(w.Body).Decode(got)
|
||||||
|
if diff := cmp.Diff(got, want); len(diff) != 0 {
|
||||||
|
t.Errorf(diff)
|
||||||
|
}
|
||||||
|
}
|
34
handler/api/secrets/find.go
Normal file
34
handler/api/secrets/find.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Drone Non-Commercial License
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !oss
|
||||||
|
|
||||||
|
package secrets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/drone/drone/core"
|
||||||
|
"github.com/drone/drone/handler/api/render"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandleFind returns an http.HandlerFunc that writes json-encoded
|
||||||
|
// secret details to the the response body.
|
||||||
|
func HandleFind(secrets core.GlobalSecretStore) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var (
|
||||||
|
namespace = chi.URLParam(r, "namespace")
|
||||||
|
name = chi.URLParam(r, "name")
|
||||||
|
)
|
||||||
|
secret, err := secrets.FindName(r.Context(), namespace, name)
|
||||||
|
if err != nil {
|
||||||
|
render.NotFound(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
safe := secret.Copy()
|
||||||
|
render.JSON(w, safe, 200)
|
||||||
|
}
|
||||||
|
}
|
81
handler/api/secrets/find_test.go
Normal file
81
handler/api/secrets/find_test.go
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Drone Non-Commercial License
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !oss
|
||||||
|
|
||||||
|
package secrets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/drone/drone/core"
|
||||||
|
"github.com/drone/drone/handler/api/errors"
|
||||||
|
"github.com/drone/drone/mock"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHandleFind(t *testing.T) {
|
||||||
|
controller := gomock.NewController(t)
|
||||||
|
defer controller.Finish()
|
||||||
|
|
||||||
|
secrets := mock.NewMockGlobalSecretStore(controller)
|
||||||
|
secrets.EXPECT().FindName(gomock.Any(), dummySecret.Namespace, dummySecret.Name).Return(dummySecret, nil)
|
||||||
|
|
||||||
|
c := new(chi.Context)
|
||||||
|
c.URLParams.Add("namespace", "octocat")
|
||||||
|
c.URLParams.Add("name", "github_password")
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r := httptest.NewRequest("GET", "/", nil)
|
||||||
|
r = r.WithContext(
|
||||||
|
context.WithValue(context.Background(), chi.RouteCtxKey, c),
|
||||||
|
)
|
||||||
|
|
||||||
|
HandleFind(secrets).ServeHTTP(w, r)
|
||||||
|
if got, want := w.Code, http.StatusOK; want != got {
|
||||||
|
t.Errorf("Want response code %d, got %d", want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, want := &core.Secret{}, dummySecretScrubbed
|
||||||
|
json.NewDecoder(w.Body).Decode(got)
|
||||||
|
if diff := cmp.Diff(got, want); len(diff) != 0 {
|
||||||
|
t.Errorf(diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleFind_SecretNotFound(t *testing.T) {
|
||||||
|
controller := gomock.NewController(t)
|
||||||
|
defer controller.Finish()
|
||||||
|
|
||||||
|
secrets := mock.NewMockGlobalSecretStore(controller)
|
||||||
|
secrets.EXPECT().FindName(gomock.Any(), dummySecret.Namespace, dummySecret.Name).Return(nil, errors.ErrNotFound)
|
||||||
|
|
||||||
|
c := new(chi.Context)
|
||||||
|
c.URLParams.Add("namespace", "octocat")
|
||||||
|
c.URLParams.Add("name", "github_password")
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r := httptest.NewRequest("GET", "/", nil)
|
||||||
|
r = r.WithContext(
|
||||||
|
context.WithValue(context.Background(), chi.RouteCtxKey, c),
|
||||||
|
)
|
||||||
|
|
||||||
|
HandleFind(secrets).ServeHTTP(w, r)
|
||||||
|
if got, want := w.Code, http.StatusNotFound; want != got {
|
||||||
|
t.Errorf("Want response code %d, got %d", want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, want := new(errors.Error), errors.ErrNotFound
|
||||||
|
json.NewDecoder(w.Body).Decode(got)
|
||||||
|
if diff := cmp.Diff(got, want); len(diff) != 0 {
|
||||||
|
t.Errorf(diff)
|
||||||
|
}
|
||||||
|
}
|
36
handler/api/secrets/list.go
Normal file
36
handler/api/secrets/list.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Drone Non-Commercial License
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !oss
|
||||||
|
|
||||||
|
package secrets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/drone/drone/core"
|
||||||
|
"github.com/drone/drone/handler/api/render"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandleList returns an http.HandlerFunc that writes a json-encoded
|
||||||
|
// list of secrets to the response body.
|
||||||
|
func HandleList(secrets core.GlobalSecretStore) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
namespace := chi.URLParam(r, "namespace")
|
||||||
|
list, err := secrets.List(r.Context(), namespace)
|
||||||
|
if err != nil {
|
||||||
|
render.NotFound(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// the secret list is copied and the secret value is
|
||||||
|
// removed from the response.
|
||||||
|
secrets := []*core.Secret{}
|
||||||
|
for _, secret := range list {
|
||||||
|
secrets = append(secrets, secret.Copy())
|
||||||
|
}
|
||||||
|
render.JSON(w, secrets, 200)
|
||||||
|
}
|
||||||
|
}
|
105
handler/api/secrets/list_test.go
Normal file
105
handler/api/secrets/list_test.go
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Drone Non-Commercial License
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !oss
|
||||||
|
|
||||||
|
package secrets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/drone/drone/core"
|
||||||
|
"github.com/drone/drone/handler/api/errors"
|
||||||
|
"github.com/drone/drone/mock"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
dummySecret = &core.Secret{
|
||||||
|
Namespace: "octocat",
|
||||||
|
Name: "github_password",
|
||||||
|
Data: "pa55word",
|
||||||
|
}
|
||||||
|
|
||||||
|
dummySecretScrubbed = &core.Secret{
|
||||||
|
Namespace: "octocat",
|
||||||
|
Name: "github_password",
|
||||||
|
Data: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
dummySecretList = []*core.Secret{
|
||||||
|
dummySecret,
|
||||||
|
}
|
||||||
|
|
||||||
|
dummySecretListScrubbed = []*core.Secret{
|
||||||
|
dummySecretScrubbed,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
//
|
||||||
|
// HandleList
|
||||||
|
//
|
||||||
|
|
||||||
|
func TestHandleList(t *testing.T) {
|
||||||
|
controller := gomock.NewController(t)
|
||||||
|
defer controller.Finish()
|
||||||
|
|
||||||
|
secrets := mock.NewMockGlobalSecretStore(controller)
|
||||||
|
secrets.EXPECT().List(gomock.Any(), dummySecret.Namespace).Return(dummySecretList, nil)
|
||||||
|
|
||||||
|
c := new(chi.Context)
|
||||||
|
c.URLParams.Add("namespace", "octocat")
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r := httptest.NewRequest("GET", "/", nil)
|
||||||
|
r = r.WithContext(
|
||||||
|
context.WithValue(context.Background(), chi.RouteCtxKey, c),
|
||||||
|
)
|
||||||
|
|
||||||
|
HandleList(secrets).ServeHTTP(w, r)
|
||||||
|
if got, want := w.Code, http.StatusOK; want != got {
|
||||||
|
t.Errorf("Want response code %d, got %d", want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, want := []*core.Secret{}, dummySecretListScrubbed
|
||||||
|
json.NewDecoder(w.Body).Decode(&got)
|
||||||
|
if diff := cmp.Diff(got, want); len(diff) != 0 {
|
||||||
|
t.Errorf(diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleList_SecretListErr(t *testing.T) {
|
||||||
|
controller := gomock.NewController(t)
|
||||||
|
defer controller.Finish()
|
||||||
|
|
||||||
|
secrets := mock.NewMockGlobalSecretStore(controller)
|
||||||
|
secrets.EXPECT().List(gomock.Any(), dummySecret.Namespace).Return(nil, errors.ErrNotFound)
|
||||||
|
|
||||||
|
c := new(chi.Context)
|
||||||
|
c.URLParams.Add("namespace", "octocat")
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r := httptest.NewRequest("GET", "/", nil)
|
||||||
|
r = r.WithContext(
|
||||||
|
context.WithValue(context.Background(), chi.RouteCtxKey, c),
|
||||||
|
)
|
||||||
|
|
||||||
|
HandleList(secrets).ServeHTTP(w, r)
|
||||||
|
if got, want := w.Code, http.StatusNotFound; want != got {
|
||||||
|
t.Errorf("Want response code %d, got %d", want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, want := new(errors.Error), errors.ErrNotFound
|
||||||
|
json.NewDecoder(w.Body).Decode(got)
|
||||||
|
if diff := cmp.Diff(got, want); len(diff) != 0 {
|
||||||
|
t.Errorf(diff)
|
||||||
|
}
|
||||||
|
}
|
52
handler/api/secrets/none.go
Normal file
52
handler/api/secrets/none.go
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright 2019 Drone IO, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// +build oss
|
||||||
|
|
||||||
|
package secrets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/drone/drone/core"
|
||||||
|
"github.com/drone/drone/handler/api/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
var notImplemented = func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
render.NotImplemented(w, render.ErrNotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleCreate(core.GlobalSecretStore) http.HandlerFunc {
|
||||||
|
return notImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleUpdate(core.GlobalSecretStore) http.HandlerFunc {
|
||||||
|
return notImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleDelete(core.GlobalSecretStore) http.HandlerFunc {
|
||||||
|
return notImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleFind(core.GlobalSecretStore) http.HandlerFunc {
|
||||||
|
return notImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleList(core.GlobalSecretStore) http.HandlerFunc {
|
||||||
|
return notImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleAll(core.GlobalSecretStore) http.HandlerFunc {
|
||||||
|
return notImplemented
|
||||||
|
}
|
72
handler/api/secrets/update.go
Normal file
72
handler/api/secrets/update.go
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Drone Non-Commercial License
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !oss
|
||||||
|
|
||||||
|
package secrets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/drone/drone/core"
|
||||||
|
"github.com/drone/drone/handler/api/render"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi"
|
||||||
|
)
|
||||||
|
|
||||||
|
type secretUpdate struct {
|
||||||
|
Data *string `json:"data"`
|
||||||
|
PullRequest *bool `json:"pull_request"`
|
||||||
|
PullRequestPush *bool `json:"pull_request_push"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleUpdate returns an http.HandlerFunc that processes http
|
||||||
|
// requests to update a secret.
|
||||||
|
func HandleUpdate(secrets core.GlobalSecretStore) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var (
|
||||||
|
namespace = chi.URLParam(r, "namespace")
|
||||||
|
name = chi.URLParam(r, "name")
|
||||||
|
)
|
||||||
|
|
||||||
|
in := new(secretUpdate)
|
||||||
|
err := json.NewDecoder(r.Body).Decode(in)
|
||||||
|
if err != nil {
|
||||||
|
render.BadRequest(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := secrets.FindName(r.Context(), namespace, name)
|
||||||
|
if err != nil {
|
||||||
|
render.NotFound(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if in.Data != nil {
|
||||||
|
s.Data = *in.Data
|
||||||
|
}
|
||||||
|
if in.PullRequest != nil {
|
||||||
|
s.PullRequest = *in.PullRequest
|
||||||
|
}
|
||||||
|
if in.PullRequestPush != nil {
|
||||||
|
s.PullRequestPush = *in.PullRequestPush
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.Validate()
|
||||||
|
if err != nil {
|
||||||
|
render.BadRequest(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = secrets.Update(r.Context(), s)
|
||||||
|
if err != nil {
|
||||||
|
render.InternalError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s = s.Copy()
|
||||||
|
render.JSON(w, s, 200)
|
||||||
|
}
|
||||||
|
}
|
180
handler/api/secrets/update_test.go
Normal file
180
handler/api/secrets/update_test.go
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Drone Non-Commercial License
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !oss
|
||||||
|
|
||||||
|
package secrets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/drone/drone/core"
|
||||||
|
"github.com/drone/drone/handler/api/errors"
|
||||||
|
"github.com/drone/drone/mock"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHandleUpdate(t *testing.T) {
|
||||||
|
controller := gomock.NewController(t)
|
||||||
|
defer controller.Finish()
|
||||||
|
|
||||||
|
secrets := mock.NewMockGlobalSecretStore(controller)
|
||||||
|
secrets.EXPECT().FindName(gomock.Any(), dummySecret.Namespace, dummySecret.Name).Return(dummySecret, nil)
|
||||||
|
secrets.EXPECT().Update(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
|
||||||
|
c := new(chi.Context)
|
||||||
|
c.URLParams.Add("namespace", "octocat")
|
||||||
|
c.URLParams.Add("name", "github_password")
|
||||||
|
|
||||||
|
in := new(bytes.Buffer)
|
||||||
|
json.NewEncoder(in).Encode(dummySecret)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r := httptest.NewRequest("GET", "/", in)
|
||||||
|
r = r.WithContext(
|
||||||
|
context.WithValue(context.Background(), chi.RouteCtxKey, c),
|
||||||
|
)
|
||||||
|
|
||||||
|
HandleUpdate(secrets).ServeHTTP(w, r)
|
||||||
|
if got, want := w.Code, http.StatusOK; want != got {
|
||||||
|
t.Errorf("Want response code %d, got %d", want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, want := new(core.Secret), dummySecretScrubbed
|
||||||
|
json.NewDecoder(w.Body).Decode(got)
|
||||||
|
if diff := cmp.Diff(got, want); len(diff) != 0 {
|
||||||
|
t.Errorf(diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleUpdate_ValidationError(t *testing.T) {
|
||||||
|
controller := gomock.NewController(t)
|
||||||
|
defer controller.Finish()
|
||||||
|
|
||||||
|
secrets := mock.NewMockGlobalSecretStore(controller)
|
||||||
|
secrets.EXPECT().FindName(gomock.Any(), dummySecret.Namespace, dummySecret.Name).Return(&core.Secret{Name: "github_password"}, nil)
|
||||||
|
|
||||||
|
c := new(chi.Context)
|
||||||
|
c.URLParams.Add("namespace", "octocat")
|
||||||
|
c.URLParams.Add("name", "github_password")
|
||||||
|
|
||||||
|
in := new(bytes.Buffer)
|
||||||
|
json.NewEncoder(in).Encode(&core.Secret{Data: ""})
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r := httptest.NewRequest("GET", "/", in)
|
||||||
|
r = r.WithContext(
|
||||||
|
context.WithValue(context.Background(), chi.RouteCtxKey, c),
|
||||||
|
)
|
||||||
|
|
||||||
|
HandleUpdate(secrets).ServeHTTP(w, r)
|
||||||
|
if got, want := w.Code, http.StatusBadRequest; want != got {
|
||||||
|
t.Errorf("Want response code %d, got %d", want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, want := new(errors.Error), &errors.Error{Message: "Invalid Secret Value"}
|
||||||
|
json.NewDecoder(w.Body).Decode(got)
|
||||||
|
if diff := cmp.Diff(got, want); len(diff) != 0 {
|
||||||
|
t.Errorf(diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleUpdate_BadRequest(t *testing.T) {
|
||||||
|
controller := gomock.NewController(t)
|
||||||
|
defer controller.Finish()
|
||||||
|
|
||||||
|
c := new(chi.Context)
|
||||||
|
c.URLParams.Add("namespace", "octocat")
|
||||||
|
c.URLParams.Add("name", "github_password")
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r := httptest.NewRequest("GET", "/", nil)
|
||||||
|
r = r.WithContext(
|
||||||
|
context.WithValue(context.Background(), chi.RouteCtxKey, c),
|
||||||
|
)
|
||||||
|
|
||||||
|
HandleUpdate(nil).ServeHTTP(w, r)
|
||||||
|
if got, want := w.Code, http.StatusBadRequest; want != got {
|
||||||
|
t.Errorf("Want response code %d, got %d", want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, want := new(errors.Error), &errors.Error{Message: "EOF"}
|
||||||
|
json.NewDecoder(w.Body).Decode(got)
|
||||||
|
if diff := cmp.Diff(got, want); len(diff) != 0 {
|
||||||
|
t.Errorf(diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleUpdate_SecretNotFound(t *testing.T) {
|
||||||
|
controller := gomock.NewController(t)
|
||||||
|
defer controller.Finish()
|
||||||
|
|
||||||
|
secrets := mock.NewMockGlobalSecretStore(controller)
|
||||||
|
secrets.EXPECT().FindName(gomock.Any(), dummySecret.Namespace, dummySecret.Name).Return(nil, errors.ErrNotFound)
|
||||||
|
|
||||||
|
c := new(chi.Context)
|
||||||
|
c.URLParams.Add("namespace", "octocat")
|
||||||
|
c.URLParams.Add("name", "github_password")
|
||||||
|
|
||||||
|
in := new(bytes.Buffer)
|
||||||
|
json.NewEncoder(in).Encode(&core.Secret{})
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r := httptest.NewRequest("GET", "/", in)
|
||||||
|
r = r.WithContext(
|
||||||
|
context.WithValue(context.Background(), chi.RouteCtxKey, c),
|
||||||
|
)
|
||||||
|
|
||||||
|
HandleUpdate(secrets).ServeHTTP(w, r)
|
||||||
|
if got, want := w.Code, http.StatusNotFound; want != got {
|
||||||
|
t.Errorf("Want response code %d, got %d", want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, want := new(errors.Error), errors.ErrNotFound
|
||||||
|
json.NewDecoder(w.Body).Decode(got)
|
||||||
|
if diff := cmp.Diff(got, want); len(diff) != 0 {
|
||||||
|
t.Errorf(diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleUpdate_UpdateError(t *testing.T) {
|
||||||
|
controller := gomock.NewController(t)
|
||||||
|
defer controller.Finish()
|
||||||
|
|
||||||
|
secrets := mock.NewMockGlobalSecretStore(controller)
|
||||||
|
secrets.EXPECT().FindName(gomock.Any(), dummySecret.Namespace, dummySecret.Name).Return(&core.Secret{Name: "github_password"}, nil)
|
||||||
|
secrets.EXPECT().Update(gomock.Any(), gomock.Any()).Return(errors.ErrNotFound)
|
||||||
|
|
||||||
|
c := new(chi.Context)
|
||||||
|
c.URLParams.Add("namespace", "octocat")
|
||||||
|
c.URLParams.Add("name", "github_password")
|
||||||
|
|
||||||
|
in := new(bytes.Buffer)
|
||||||
|
json.NewEncoder(in).Encode(&core.Secret{Data: "password"})
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r := httptest.NewRequest("GET", "/", in)
|
||||||
|
r = r.WithContext(
|
||||||
|
context.WithValue(context.Background(), chi.RouteCtxKey, c),
|
||||||
|
)
|
||||||
|
|
||||||
|
HandleUpdate(secrets).ServeHTTP(w, r)
|
||||||
|
if got, want := w.Code, http.StatusInternalServerError; want != got {
|
||||||
|
t.Errorf("Want response code %d, got %d", want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, want := new(errors.Error), errors.ErrNotFound
|
||||||
|
json.NewDecoder(w.Body).Decode(got)
|
||||||
|
if diff := cmp.Diff(got, want); len(diff) != 0 {
|
||||||
|
t.Errorf(diff)
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,4 +6,4 @@
|
||||||
|
|
||||||
package mock
|
package mock
|
||||||
|
|
||||||
//go:generate mockgen -package=mock -destination=mock_gen.go github.com/drone/drone/core NetrcService,Renewer,HookParser,UserService,RepositoryService,CommitService,StatusService,HookService,FileService,Batcher,BuildStore,CronStore,LogStore,PermStore,SecretStore,StageStore,StepStore,RepositoryStore,UserStore,Scheduler,Session,OrganizationService,SecretService,RegistryService,ConfigService,Triggerer,Syncer,LogStream,WebhookSender,LicenseService
|
//go:generate mockgen -package=mock -destination=mock_gen.go github.com/drone/drone/core NetrcService,Renewer,HookParser,UserService,RepositoryService,CommitService,StatusService,HookService,FileService,Batcher,BuildStore,CronStore,LogStore,PermStore,SecretStore,GlobalSecretStore,StageStore,StepStore,RepositoryStore,UserStore,Scheduler,Session,OrganizationService,SecretService,RegistryService,ConfigService,Triggerer,Syncer,LogStream,WebhookSender,LicenseService
|
||||||
|
|
113
mock/mock_gen.go
113
mock/mock_gen.go
|
@ -1,5 +1,5 @@
|
||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: github.com/drone/drone/core (interfaces: NetrcService,Renewer,HookParser,UserService,RepositoryService,CommitService,StatusService,HookService,FileService,Batcher,BuildStore,CronStore,LogStore,PermStore,SecretStore,StageStore,StepStore,RepositoryStore,UserStore,Scheduler,Session,OrganizationService,SecretService,RegistryService,ConfigService,Triggerer,Syncer,LogStream,WebhookSender,LicenseService)
|
// Source: github.com/drone/drone/core (interfaces: NetrcService,Renewer,HookParser,UserService,RepositoryService,CommitService,StatusService,HookService,FileService,Batcher,BuildStore,CronStore,LogStore,PermStore,SecretStore,GlobalSecretStore,StageStore,StepStore,RepositoryStore,UserStore,Scheduler,Session,OrganizationService,SecretService,RegistryService,ConfigService,Triggerer,Syncer,LogStream,WebhookSender,LicenseService)
|
||||||
|
|
||||||
// Package mock is a generated GoMock package.
|
// Package mock is a generated GoMock package.
|
||||||
package mock
|
package mock
|
||||||
|
@ -963,6 +963,117 @@ func (mr *MockSecretStoreMockRecorder) Update(arg0, arg1 interface{}) *gomock.Ca
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockSecretStore)(nil).Update), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockSecretStore)(nil).Update), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MockGlobalSecretStore is a mock of GlobalSecretStore interface
|
||||||
|
type MockGlobalSecretStore struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockGlobalSecretStoreMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockGlobalSecretStoreMockRecorder is the mock recorder for MockGlobalSecretStore
|
||||||
|
type MockGlobalSecretStoreMockRecorder struct {
|
||||||
|
mock *MockGlobalSecretStore
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockGlobalSecretStore creates a new mock instance
|
||||||
|
func NewMockGlobalSecretStore(ctrl *gomock.Controller) *MockGlobalSecretStore {
|
||||||
|
mock := &MockGlobalSecretStore{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockGlobalSecretStoreMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use
|
||||||
|
func (m *MockGlobalSecretStore) EXPECT() *MockGlobalSecretStoreMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create mocks base method
|
||||||
|
func (m *MockGlobalSecretStore) Create(arg0 context.Context, arg1 *core.Secret) error {
|
||||||
|
ret := m.ctrl.Call(m, "Create", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create indicates an expected call of Create
|
||||||
|
func (mr *MockGlobalSecretStoreMockRecorder) Create(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockGlobalSecretStore)(nil).Create), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete mocks base method
|
||||||
|
func (m *MockGlobalSecretStore) Delete(arg0 context.Context, arg1 *core.Secret) error {
|
||||||
|
ret := m.ctrl.Call(m, "Delete", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete indicates an expected call of Delete
|
||||||
|
func (mr *MockGlobalSecretStoreMockRecorder) Delete(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockGlobalSecretStore)(nil).Delete), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find mocks base method
|
||||||
|
func (m *MockGlobalSecretStore) Find(arg0 context.Context, arg1 int64) (*core.Secret, error) {
|
||||||
|
ret := m.ctrl.Call(m, "Find", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(*core.Secret)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find indicates an expected call of Find
|
||||||
|
func (mr *MockGlobalSecretStoreMockRecorder) Find(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Find", reflect.TypeOf((*MockGlobalSecretStore)(nil).Find), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindName mocks base method
|
||||||
|
func (m *MockGlobalSecretStore) FindName(arg0 context.Context, arg1, arg2 string) (*core.Secret, error) {
|
||||||
|
ret := m.ctrl.Call(m, "FindName", arg0, arg1, arg2)
|
||||||
|
ret0, _ := ret[0].(*core.Secret)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindName indicates an expected call of FindName
|
||||||
|
func (mr *MockGlobalSecretStoreMockRecorder) FindName(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindName", reflect.TypeOf((*MockGlobalSecretStore)(nil).FindName), arg0, arg1, arg2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List mocks base method
|
||||||
|
func (m *MockGlobalSecretStore) List(arg0 context.Context, arg1 string) ([]*core.Secret, error) {
|
||||||
|
ret := m.ctrl.Call(m, "List", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].([]*core.Secret)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// List indicates an expected call of List
|
||||||
|
func (mr *MockGlobalSecretStoreMockRecorder) List(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockGlobalSecretStore)(nil).List), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAll mocks base method
|
||||||
|
func (m *MockGlobalSecretStore) ListAll(arg0 context.Context) ([]*core.Secret, error) {
|
||||||
|
ret := m.ctrl.Call(m, "ListAll", arg0)
|
||||||
|
ret0, _ := ret[0].([]*core.Secret)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAll indicates an expected call of ListAll
|
||||||
|
func (mr *MockGlobalSecretStoreMockRecorder) ListAll(arg0 interface{}) *gomock.Call {
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAll", reflect.TypeOf((*MockGlobalSecretStore)(nil).ListAll), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update mocks base method
|
||||||
|
func (m *MockGlobalSecretStore) Update(arg0 context.Context, arg1 *core.Secret) error {
|
||||||
|
ret := m.ctrl.Call(m, "Update", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update indicates an expected call of Update
|
||||||
|
func (mr *MockGlobalSecretStoreMockRecorder) Update(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockGlobalSecretStore)(nil).Update), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
// MockStageStore is a mock of StageStore interface
|
// MockStageStore is a mock of StageStore interface
|
||||||
type MockStageStore struct {
|
type MockStageStore struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
|
|
|
@ -108,6 +108,7 @@ func New(
|
||||||
repos core.RepositoryStore,
|
repos core.RepositoryStore,
|
||||||
scheduler core.Scheduler,
|
scheduler core.Scheduler,
|
||||||
secrets core.SecretStore,
|
secrets core.SecretStore,
|
||||||
|
globals core.GlobalSecretStore,
|
||||||
status core.StatusService,
|
status core.StatusService,
|
||||||
stages core.StageStore,
|
stages core.StageStore,
|
||||||
steps core.StepStore,
|
steps core.StepStore,
|
||||||
|
@ -119,6 +120,7 @@ func New(
|
||||||
Builds: builds,
|
Builds: builds,
|
||||||
Config: config,
|
Config: config,
|
||||||
Events: events,
|
Events: events,
|
||||||
|
Globals: globals,
|
||||||
Logs: logs,
|
Logs: logs,
|
||||||
Logz: logz,
|
Logz: logz,
|
||||||
Netrcs: netrcs,
|
Netrcs: netrcs,
|
||||||
|
@ -140,6 +142,7 @@ type Manager struct {
|
||||||
Builds core.BuildStore
|
Builds core.BuildStore
|
||||||
Config core.ConfigService
|
Config core.ConfigService
|
||||||
Events core.Pubsub
|
Events core.Pubsub
|
||||||
|
Globals core.GlobalSecretStore
|
||||||
Logs core.LogStore
|
Logs core.LogStore
|
||||||
Logz core.LogStream
|
Logz core.LogStream
|
||||||
Netrcs core.NetrcService
|
Netrcs core.NetrcService
|
||||||
|
@ -287,6 +290,12 @@ func (m *Manager) Details(ctx context.Context, id int64) (*Context, error) {
|
||||||
logger.Warnln("manager: cannot list secrets")
|
logger.Warnln("manager: cannot list secrets")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
tmpGlobalSecrets, err := m.Globals.List(noContext, repo.Namespace)
|
||||||
|
if err != nil {
|
||||||
|
logger = logger.WithError(err)
|
||||||
|
logger.Warnln("manager: cannot list global secrets")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
// TODO(bradrydzewski) can we delegate filtering
|
// TODO(bradrydzewski) can we delegate filtering
|
||||||
// secrets to the agent? If not, we should add
|
// secrets to the agent? If not, we should add
|
||||||
// unit tests.
|
// unit tests.
|
||||||
|
@ -297,6 +306,13 @@ func (m *Manager) Details(ctx context.Context, id int64) (*Context, error) {
|
||||||
}
|
}
|
||||||
secrets = append(secrets, secret)
|
secrets = append(secrets, secret)
|
||||||
}
|
}
|
||||||
|
for _, secret := range tmpGlobalSecrets {
|
||||||
|
if secret.PullRequest == false &&
|
||||||
|
build.Event == core.EventPullRequest {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
secrets = append(secrets, secret)
|
||||||
|
}
|
||||||
return &Context{
|
return &Context{
|
||||||
Repo: repo,
|
Repo: repo,
|
||||||
Build: build,
|
Build: build,
|
||||||
|
|
|
@ -17,11 +17,11 @@
|
||||||
package license
|
package license
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/drone/drone/core"
|
"github.com/drone/drone/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultLicense is an empty license with no restrictions.
|
// DefaultLicense is an empty license with no restrictions.
|
||||||
var DefaultLicense = &core.License{Kind: core.LicenseFoss}
|
var DefaultLicense = &core.License{Kind: core.LicenseFoss}
|
||||||
|
|
||||||
func Trial(string) *core.License { return nil }
|
func Trial(string) *core.License { return DefaultLicense }
|
||||||
func Load(string) (*core.License, error) { return DefaultLicense, nil }
|
func Load(string) (*core.License, error) { return DefaultLicense, nil }
|
||||||
|
|
74
store/secret/global/scan.go
Normal file
74
store/secret/global/scan.go
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Drone Non-Commercial License
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !oss
|
||||||
|
|
||||||
|
package global
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/drone/drone/core"
|
||||||
|
"github.com/drone/drone/store/shared/db"
|
||||||
|
"github.com/drone/drone/store/shared/encrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// helper function converts the User structure to a set
|
||||||
|
// of named query parameters.
|
||||||
|
func toParams(encrypt encrypt.Encrypter, secret *core.Secret) (map[string]interface{}, error) {
|
||||||
|
ciphertext, err := encrypt.Encrypt(secret.Data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return map[string]interface{}{
|
||||||
|
"secret_id": secret.ID,
|
||||||
|
"secret_namespace": secret.Namespace,
|
||||||
|
"secret_name": secret.Name,
|
||||||
|
"secret_type": secret.Type,
|
||||||
|
"secret_data": ciphertext,
|
||||||
|
"secret_pull_request": secret.PullRequest,
|
||||||
|
"secret_pull_request_push": secret.PullRequestPush,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function scans the sql.Row and copies the column
|
||||||
|
// values to the destination object.
|
||||||
|
func scanRow(encrypt encrypt.Encrypter, scanner db.Scanner, dst *core.Secret) error {
|
||||||
|
var ciphertext []byte
|
||||||
|
err := scanner.Scan(
|
||||||
|
&dst.ID,
|
||||||
|
&dst.Namespace,
|
||||||
|
&dst.Name,
|
||||||
|
&dst.Type,
|
||||||
|
&ciphertext,
|
||||||
|
&dst.PullRequest,
|
||||||
|
&dst.PullRequestPush,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
plaintext, err := encrypt.Decrypt(ciphertext)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dst.Data = plaintext
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function scans the sql.Row and copies the column
|
||||||
|
// values to the destination object.
|
||||||
|
func scanRows(encrypt encrypt.Encrypter, rows *sql.Rows) ([]*core.Secret, error) {
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
secrets := []*core.Secret{}
|
||||||
|
for rows.Next() {
|
||||||
|
sec := new(core.Secret)
|
||||||
|
err := scanRow(encrypt, rows, sec)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
secrets = append(secrets, sec)
|
||||||
|
}
|
||||||
|
return secrets, nil
|
||||||
|
}
|
233
store/secret/global/secret.go
Normal file
233
store/secret/global/secret.go
Normal file
|
@ -0,0 +1,233 @@
|
||||||
|
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Drone Non-Commercial License
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !oss
|
||||||
|
|
||||||
|
package global
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/drone/drone/core"
|
||||||
|
"github.com/drone/drone/store/shared/db"
|
||||||
|
"github.com/drone/drone/store/shared/encrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New returns a new global Secret database store.
|
||||||
|
func New(db *db.DB, enc encrypt.Encrypter) core.GlobalSecretStore {
|
||||||
|
return &secretStore{
|
||||||
|
db: db,
|
||||||
|
enc: enc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type secretStore struct {
|
||||||
|
db *db.DB
|
||||||
|
enc encrypt.Encrypter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *secretStore) List(ctx context.Context, namespace string) ([]*core.Secret, error) {
|
||||||
|
var out []*core.Secret
|
||||||
|
err := s.db.View(func(queryer db.Queryer, binder db.Binder) error {
|
||||||
|
params := map[string]interface{}{"secret_namespace": namespace}
|
||||||
|
stmt, args, err := binder.BindNamed(queryNamespace, params)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rows, err := queryer.Query(stmt, args...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out, err = scanRows(s.enc, rows)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *secretStore) ListAll(ctx context.Context) ([]*core.Secret, error) {
|
||||||
|
var out []*core.Secret
|
||||||
|
err := s.db.View(func(queryer db.Queryer, binder db.Binder) error {
|
||||||
|
rows, err := queryer.Query(queryAll)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out, err = scanRows(s.enc, rows)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *secretStore) Find(ctx context.Context, id int64) (*core.Secret, error) {
|
||||||
|
out := &core.Secret{ID: id}
|
||||||
|
err := s.db.View(func(queryer db.Queryer, binder db.Binder) error {
|
||||||
|
params, err := toParams(s.enc, out)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
query, args, err := binder.BindNamed(queryKey, params)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
row := queryer.QueryRow(query, args...)
|
||||||
|
return scanRow(s.enc, row, out)
|
||||||
|
})
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *secretStore) FindName(ctx context.Context, namespace, name string) (*core.Secret, error) {
|
||||||
|
out := &core.Secret{Name: name, Namespace: namespace}
|
||||||
|
err := s.db.View(func(queryer db.Queryer, binder db.Binder) error {
|
||||||
|
params, err := toParams(s.enc, out)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
query, args, err := binder.BindNamed(queryName, params)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
row := queryer.QueryRow(query, args...)
|
||||||
|
return scanRow(s.enc, row, out)
|
||||||
|
})
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *secretStore) Create(ctx context.Context, secret *core.Secret) error {
|
||||||
|
if s.db.Driver() == db.Postgres {
|
||||||
|
return s.createPostgres(ctx, secret)
|
||||||
|
}
|
||||||
|
return s.create(ctx, secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *secretStore) create(ctx context.Context, secret *core.Secret) error {
|
||||||
|
return s.db.Lock(func(execer db.Execer, binder db.Binder) error {
|
||||||
|
params, err := toParams(s.enc, secret)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stmt, args, err := binder.BindNamed(stmtInsert, params)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
res, err := execer.Exec(stmt, args...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
secret.ID, err = res.LastInsertId()
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *secretStore) createPostgres(ctx context.Context, secret *core.Secret) error {
|
||||||
|
return s.db.Lock(func(execer db.Execer, binder db.Binder) error {
|
||||||
|
params, err := toParams(s.enc, secret)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stmt, args, err := binder.BindNamed(stmtInsertPg, params)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return execer.QueryRow(stmt, args...).Scan(&secret.ID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *secretStore) Update(ctx context.Context, secret *core.Secret) error {
|
||||||
|
return s.db.Lock(func(execer db.Execer, binder db.Binder) error {
|
||||||
|
params, err := toParams(s.enc, secret)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stmt, args, err := binder.BindNamed(stmtUpdate, params)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = execer.Exec(stmt, args...)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *secretStore) Delete(ctx context.Context, secret *core.Secret) error {
|
||||||
|
return s.db.Lock(func(execer db.Execer, binder db.Binder) error {
|
||||||
|
params, err := toParams(s.enc, secret)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stmt, args, err := binder.BindNamed(stmtDelete, params)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = execer.Exec(stmt, args...)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryBase = `
|
||||||
|
SELECT
|
||||||
|
secret_id
|
||||||
|
,secret_namespace
|
||||||
|
,secret_name
|
||||||
|
,secret_type
|
||||||
|
,secret_data
|
||||||
|
,secret_pull_request
|
||||||
|
,secret_pull_request_push
|
||||||
|
`
|
||||||
|
|
||||||
|
const queryKey = queryBase + `
|
||||||
|
FROM orgsecrets
|
||||||
|
WHERE secret_id = :secret_id
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
const queryAll = queryBase + `
|
||||||
|
FROM orgsecrets
|
||||||
|
ORDER BY secret_name
|
||||||
|
`
|
||||||
|
|
||||||
|
const queryName = queryBase + `
|
||||||
|
FROM orgsecrets
|
||||||
|
WHERE secret_name = :secret_name
|
||||||
|
AND secret_namespace = :secret_namespace
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
const queryNamespace = queryBase + `
|
||||||
|
FROM orgsecrets
|
||||||
|
WHERE secret_namespace = :secret_namespace
|
||||||
|
ORDER BY secret_name
|
||||||
|
`
|
||||||
|
|
||||||
|
const stmtUpdate = `
|
||||||
|
UPDATE orgsecrets SET
|
||||||
|
secret_data = :secret_data
|
||||||
|
,secret_pull_request = :secret_pull_request
|
||||||
|
,secret_pull_request_push = :secret_pull_request_push
|
||||||
|
WHERE secret_id = :secret_id
|
||||||
|
`
|
||||||
|
|
||||||
|
const stmtDelete = `
|
||||||
|
DELETE FROM orgsecrets
|
||||||
|
WHERE secret_id = :secret_id
|
||||||
|
`
|
||||||
|
|
||||||
|
const stmtInsert = `
|
||||||
|
INSERT INTO orgsecrets (
|
||||||
|
secret_namespace
|
||||||
|
,secret_name
|
||||||
|
,secret_type
|
||||||
|
,secret_data
|
||||||
|
,secret_pull_request
|
||||||
|
,secret_pull_request_push
|
||||||
|
) VALUES (
|
||||||
|
:secret_namespace
|
||||||
|
,:secret_name
|
||||||
|
,:secret_type
|
||||||
|
,:secret_data
|
||||||
|
,:secret_pull_request
|
||||||
|
,:secret_pull_request_push
|
||||||
|
)
|
||||||
|
`
|
||||||
|
|
||||||
|
const stmtInsertPg = stmtInsert + `
|
||||||
|
RETURNING secret_id
|
||||||
|
`
|
60
store/secret/global/secret_oss.go
Normal file
60
store/secret/global/secret_oss.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
// Copyright 2019 Drone IO, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// +build oss
|
||||||
|
|
||||||
|
package global
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/drone/drone/core"
|
||||||
|
"github.com/drone/drone/store/shared/db"
|
||||||
|
"github.com/drone/drone/store/shared/encrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New returns a new Secret database store.
|
||||||
|
func New(db *db.DB, enc encrypt.Encrypter) core.GlobalSecretStore {
|
||||||
|
return new(noop)
|
||||||
|
}
|
||||||
|
|
||||||
|
type noop struct{}
|
||||||
|
|
||||||
|
func (noop) List(context.Context, string) ([]*core.Secret, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (noop) ListAll(context.Context) ([]*core.Secret, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (noop) Find(context.Context, int64) (*core.Secret, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (noop) FindName(context.Context, string, string) (*core.Secret, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (noop) Create(context.Context, *core.Secret) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (noop) Update(context.Context, *core.Secret) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (noop) Delete(context.Context, *core.Secret) error {
|
||||||
|
return nil
|
||||||
|
}
|
165
store/secret/global/secret_test.go
Normal file
165
store/secret/global/secret_test.go
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Drone Non-Commercial License
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !oss
|
||||||
|
|
||||||
|
package global
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/drone/drone/core"
|
||||||
|
"github.com/drone/drone/store/shared/db/dbtest"
|
||||||
|
"github.com/drone/drone/store/shared/encrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var noContext = context.TODO()
|
||||||
|
|
||||||
|
func TestSecret(t *testing.T) {
|
||||||
|
conn, err := dbtest.Connect()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
dbtest.Reset(conn)
|
||||||
|
dbtest.Disconnect(conn)
|
||||||
|
}()
|
||||||
|
|
||||||
|
store := New(conn, nil).(*secretStore)
|
||||||
|
store.enc, _ = encrypt.New("fb4b4d6267c8a5ce8231f8b186dbca92")
|
||||||
|
t.Run("Create", testSecretCreate(store))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSecretCreate(store *secretStore) func(t *testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
item := &core.Secret{
|
||||||
|
Namespace: "octocat",
|
||||||
|
Name: "password",
|
||||||
|
Data: "correct-horse-battery-staple",
|
||||||
|
}
|
||||||
|
err := store.Create(noContext, item)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if item.ID == 0 {
|
||||||
|
t.Errorf("Want secret ID assigned, got %d", item.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Find", testSecretFind(store, item))
|
||||||
|
t.Run("FindName", testSecretFindName(store))
|
||||||
|
t.Run("List", testSecretList(store))
|
||||||
|
t.Run("ListAll", testSecretListAll(store))
|
||||||
|
t.Run("Update", testSecretUpdate(store))
|
||||||
|
t.Run("Delete", testSecretDelete(store))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSecretFind(store *secretStore, secret *core.Secret) func(t *testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
item, err := store.Find(noContext, secret.ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else {
|
||||||
|
t.Run("Fields", testSecret(item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSecretFindName(store *secretStore) func(t *testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
item, err := store.FindName(noContext, "octocat", "password")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else {
|
||||||
|
t.Run("Fields", testSecret(item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSecretList(store *secretStore) func(t *testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
list, err := store.List(noContext, "octocat")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if got, want := len(list), 1; got != want {
|
||||||
|
t.Errorf("Want count %d, got %d", want, got)
|
||||||
|
} else {
|
||||||
|
t.Run("Fields", testSecret(list[0]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSecretListAll(store *secretStore) func(t *testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
list, err := store.ListAll(noContext)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if got, want := len(list), 1; got != want {
|
||||||
|
t.Errorf("Want count %d, got %d", want, got)
|
||||||
|
} else {
|
||||||
|
t.Run("Fields", testSecret(list[0]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSecretUpdate(store *secretStore) func(t *testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
before, err := store.FindName(noContext, "octocat", "password")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = store.Update(noContext, before)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
after, err := store.Find(noContext, before.ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if after == nil {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSecretDelete(store *secretStore) func(t *testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
secret, err := store.FindName(noContext, "octocat", "password")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = store.Delete(noContext, secret)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = store.Find(noContext, secret.ID)
|
||||||
|
if got, want := sql.ErrNoRows, err; got != want {
|
||||||
|
t.Errorf("Want sql.ErrNoRows, got %v", got)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSecret(item *core.Secret) func(t *testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
if got, want := item.Name, "password"; got != want {
|
||||||
|
t.Errorf("Want secret name %q, got %q", want, got)
|
||||||
|
}
|
||||||
|
if got, want := item.Data, "correct-horse-battery-staple"; got != want {
|
||||||
|
t.Errorf("Want secret data %q, got %q", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -120,6 +120,10 @@ var migrations = []struct {
|
||||||
name: "alter-table-builds-add-column-cron",
|
name: "alter-table-builds-add-column-cron",
|
||||||
stmt: alterTableBuildsAddColumnCron,
|
stmt: alterTableBuildsAddColumnCron,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "create-table-org-secrets",
|
||||||
|
stmt: createTableOrgSecrets,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate performs the database migration. If the migration fails
|
// Migrate performs the database migration. If the migration fails
|
||||||
|
@ -556,3 +560,20 @@ CREATE TABLE IF NOT EXISTS nodes (
|
||||||
var alterTableBuildsAddColumnCron = `
|
var alterTableBuildsAddColumnCron = `
|
||||||
ALTER TABLE builds ADD COLUMN build_cron VARCHAR(50) NOT NULL DEFAULT '';
|
ALTER TABLE builds ADD COLUMN build_cron VARCHAR(50) NOT NULL DEFAULT '';
|
||||||
`
|
`
|
||||||
|
|
||||||
|
//
|
||||||
|
// 012_create_table_global_secrets.sql
|
||||||
|
//
|
||||||
|
|
||||||
|
var createTableOrgSecrets = `
|
||||||
|
CREATE TABLE IF NOT EXISTS orgsecrets (
|
||||||
|
secret_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||||
|
,secret_namespace VARCHAR(50)
|
||||||
|
,secret_name VARCHAR(200)
|
||||||
|
,secret_type VARCHAR(50)
|
||||||
|
,secret_data BLOB
|
||||||
|
,secret_pull_request BOOLEAN
|
||||||
|
,secret_pull_request_push BOOLEAN
|
||||||
|
,UNIQUE(secret_namespace, secret_name)
|
||||||
|
);
|
||||||
|
`
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
-- name: create-table-org-secrets
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS orgsecrets (
|
||||||
|
secret_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||||
|
,secret_namespace VARCHAR(50)
|
||||||
|
,secret_name VARCHAR(200)
|
||||||
|
,secret_type VARCHAR(50)
|
||||||
|
,secret_data BLOB
|
||||||
|
,secret_pull_request BOOLEAN
|
||||||
|
,secret_pull_request_push BOOLEAN
|
||||||
|
,UNIQUE(secret_namespace, secret_name)
|
||||||
|
);
|
|
@ -116,6 +116,10 @@ var migrations = []struct {
|
||||||
name: "alter-table-builds-add-column-cron",
|
name: "alter-table-builds-add-column-cron",
|
||||||
stmt: alterTableBuildsAddColumnCron,
|
stmt: alterTableBuildsAddColumnCron,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "create-table-org-secrets",
|
||||||
|
stmt: createTableOrgSecrets,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate performs the database migration. If the migration fails
|
// Migrate performs the database migration. If the migration fails
|
||||||
|
@ -534,3 +538,20 @@ CREATE TABLE IF NOT EXISTS nodes (
|
||||||
var alterTableBuildsAddColumnCron = `
|
var alterTableBuildsAddColumnCron = `
|
||||||
ALTER TABLE builds ADD COLUMN build_cron VARCHAR(50) NOT NULL DEFAULT '';
|
ALTER TABLE builds ADD COLUMN build_cron VARCHAR(50) NOT NULL DEFAULT '';
|
||||||
`
|
`
|
||||||
|
|
||||||
|
//
|
||||||
|
// 012_create_table_org_secrets.sql
|
||||||
|
//
|
||||||
|
|
||||||
|
var createTableOrgSecrets = `
|
||||||
|
CREATE TABLE IF NOT EXISTS orgsecrets (
|
||||||
|
secret_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||||
|
,secret_namespace VARCHAR(50)
|
||||||
|
,secret_name VARCHAR(200)
|
||||||
|
,secret_type VARCHAR(50)
|
||||||
|
,secret_data BYTEA
|
||||||
|
,secret_pull_request BOOLEAN
|
||||||
|
,secret_pull_request_push BOOLEAN
|
||||||
|
,UNIQUE(secret_namespace, secret_name)
|
||||||
|
);
|
||||||
|
`
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
-- name: create-table-org-secrets
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS orgsecrets (
|
||||||
|
secret_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||||
|
,secret_namespace VARCHAR(50)
|
||||||
|
,secret_name VARCHAR(200)
|
||||||
|
,secret_type VARCHAR(50)
|
||||||
|
,secret_data BYTEA
|
||||||
|
,secret_pull_request BOOLEAN
|
||||||
|
,secret_pull_request_push BOOLEAN
|
||||||
|
,UNIQUE(secret_namespace, secret_name)
|
||||||
|
);
|
|
@ -116,6 +116,10 @@ var migrations = []struct {
|
||||||
name: "alter-table-builds-add-column-cron",
|
name: "alter-table-builds-add-column-cron",
|
||||||
stmt: alterTableBuildsAddColumnCron,
|
stmt: alterTableBuildsAddColumnCron,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "create-table-org-secrets",
|
||||||
|
stmt: createTableOrgSecrets,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate performs the database migration. If the migration fails
|
// Migrate performs the database migration. If the migration fails
|
||||||
|
@ -536,3 +540,20 @@ CREATE TABLE IF NOT EXISTS nodes (
|
||||||
var alterTableBuildsAddColumnCron = `
|
var alterTableBuildsAddColumnCron = `
|
||||||
ALTER TABLE builds ADD COLUMN build_cron TEXT NOT NULL DEFAULT '';
|
ALTER TABLE builds ADD COLUMN build_cron TEXT NOT NULL DEFAULT '';
|
||||||
`
|
`
|
||||||
|
|
||||||
|
//
|
||||||
|
// 012_create_table_org_secrets.sql
|
||||||
|
//
|
||||||
|
|
||||||
|
var createTableOrgSecrets = `
|
||||||
|
CREATE TABLE IF NOT EXISTS orgsecrets (
|
||||||
|
secret_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||||
|
,secret_namespace TEXT COLLATE NOCASE
|
||||||
|
,secret_name TEXT COLLATE NOCASE
|
||||||
|
,secret_type TEXT
|
||||||
|
,secret_data BLOB
|
||||||
|
,secret_pull_request BOOLEAN
|
||||||
|
,secret_pull_request_push BOOLEAN
|
||||||
|
,UNIQUE(secret_namespace, secret_name)
|
||||||
|
);
|
||||||
|
`
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
-- name: create-table-org-secrets
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS orgsecrets (
|
||||||
|
secret_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||||
|
,secret_namespace TEXT COLLATE NOCASE
|
||||||
|
,secret_name TEXT COLLATE NOCASE
|
||||||
|
,secret_type TEXT
|
||||||
|
,secret_data BLOB
|
||||||
|
,secret_pull_request BOOLEAN
|
||||||
|
,secret_pull_request_push BOOLEAN
|
||||||
|
,UNIQUE(secret_namespace, secret_name)
|
||||||
|
);
|
Loading…
Reference in a new issue