Merge pull request #1737 from tboerger/feature/org-secrets

Organization/Team secrets
This commit is contained in:
Brad Rydzewski 2016-08-09 20:01:37 -07:00 committed by GitHub
commit abaa02efa2
27 changed files with 779 additions and 75 deletions

View file

@ -61,6 +61,15 @@ type Client interface {
// SecretDel deletes a named repository secret.
SecretDel(string, string, string) error
// TeamSecretList returns a list of all team secrets.
TeamSecretList(string) ([]*model.Secret, error)
// TeamSecretPost create or updates a team secret.
TeamSecretPost(string, *model.Secret) error
// TeamSecretDel deletes a named team secret.
TeamSecretDel(string, string) error
// Build returns a repository build by number.
Build(string, string, int) (*model.Build, error)

View file

@ -28,26 +28,28 @@ const (
pathLogs = "%s/api/queue/logs/%d"
pathLogsAuth = "%s/api/queue/logs/%d?access_token=%s"
pathSelf = "%s/api/user"
pathFeed = "%s/api/user/feed"
pathRepos = "%s/api/user/repos"
pathRepo = "%s/api/repos/%s/%s"
pathChown = "%s/api/repos/%s/%s/chown"
pathEncrypt = "%s/api/repos/%s/%s/encrypt"
pathBuilds = "%s/api/repos/%s/%s/builds"
pathBuild = "%s/api/repos/%s/%s/builds/%v"
pathJob = "%s/api/repos/%s/%s/builds/%d/%d"
pathLog = "%s/api/repos/%s/%s/logs/%d/%d"
pathKey = "%s/api/repos/%s/%s/key"
pathSign = "%s/api/repos/%s/%s/sign"
pathSecrets = "%s/api/repos/%s/%s/secrets"
pathSecret = "%s/api/repos/%s/%s/secrets/%s"
pathNodes = "%s/api/nodes"
pathNode = "%s/api/nodes/%d"
pathUsers = "%s/api/users"
pathUser = "%s/api/users/%s"
pathBuildQueue = "%s/api/builds"
pathAgent = "%s/api/agents"
pathSelf = "%s/api/user"
pathFeed = "%s/api/user/feed"
pathRepos = "%s/api/user/repos"
pathRepo = "%s/api/repos/%s/%s"
pathChown = "%s/api/repos/%s/%s/chown"
pathEncrypt = "%s/api/repos/%s/%s/encrypt"
pathBuilds = "%s/api/repos/%s/%s/builds"
pathBuild = "%s/api/repos/%s/%s/builds/%v"
pathJob = "%s/api/repos/%s/%s/builds/%d/%d"
pathLog = "%s/api/repos/%s/%s/logs/%d/%d"
pathKey = "%s/api/repos/%s/%s/key"
pathSign = "%s/api/repos/%s/%s/sign"
pathRepoSecrets = "%s/api/repos/%s/%s/secrets"
pathRepoSecret = "%s/api/repos/%s/%s/secrets/%s"
pathTeamSecrets = "%s/api/teams/%s/secrets"
pathTeamSecret = "%s/api/teams/%s/secrets/%s"
pathNodes = "%s/api/nodes"
pathNode = "%s/api/nodes/%d"
pathUsers = "%s/api/users"
pathUser = "%s/api/users/%s"
pathBuildQueue = "%s/api/builds"
pathAgent = "%s/api/agents"
)
type client struct {
@ -78,7 +80,7 @@ func NewClientTokenTLS(uri, token string, c *tls.Config) Client {
if trans, ok := auther.Transport.(*oauth2.Transport); ok {
trans.Base = &http.Transport{
TLSClientConfig: c,
Proxy: http.ProxyFromEnvironment,
Proxy: http.ProxyFromEnvironment,
}
}
}
@ -265,20 +267,40 @@ func (c *client) Deploy(owner, name string, num int, env string, params map[stri
// SecretList returns a list of a repository secrets.
func (c *client) SecretList(owner, name string) ([]*model.Secret, error) {
var out []*model.Secret
uri := fmt.Sprintf(pathSecrets, c.base, owner, name)
uri := fmt.Sprintf(pathRepoSecrets, c.base, owner, name)
err := c.get(uri, &out)
return out, err
}
// SecretPost create or updates a repository secret.
func (c *client) SecretPost(owner, name string, secret *model.Secret) error {
uri := fmt.Sprintf(pathSecrets, c.base, owner, name)
uri := fmt.Sprintf(pathRepoSecrets, c.base, owner, name)
return c.post(uri, secret, nil)
}
// SecretDel deletes a named repository secret.
func (c *client) SecretDel(owner, name, secret string) error {
uri := fmt.Sprintf(pathSecret, c.base, owner, name, secret)
uri := fmt.Sprintf(pathRepoSecret, c.base, owner, name, secret)
return c.delete(uri)
}
// TeamSecretList returns a list of a repository secrets.
func (c *client) TeamSecretList(team string) ([]*model.Secret, error) {
var out []*model.Secret
uri := fmt.Sprintf(pathTeamSecrets, c.base, team)
err := c.get(uri, &out)
return out, err
}
// TeamSecretPost create or updates a repository secret.
func (c *client) TeamSecretPost(team string, secret *model.Secret) error {
uri := fmt.Sprintf(pathTeamSecrets, c.base, team)
return c.post(uri, secret, nil)
}
// TeamSecretDel deletes a named repository secret.
func (c *client) TeamSecretDel(team, secret string) error {
uri := fmt.Sprintf(pathTeamSecret, c.base, team, secret)
return c.delete(uri)
}

View file

@ -42,6 +42,7 @@ func main() {
signCmd,
repoCmd,
userCmd,
orgCmd,
}
app.Run(os.Args)

11
drone/org.go Normal file
View file

@ -0,0 +1,11 @@
package main
import "github.com/codegangsta/cli"
var orgCmd = cli.Command{
Name: "org",
Usage: "manage organizations",
Subcommands: []cli.Command{
orgSecretCmd,
},
}

13
drone/org_secret.go Normal file
View file

@ -0,0 +1,13 @@
package main
import "github.com/codegangsta/cli"
var orgSecretCmd = cli.Command{
Name: "secret",
Usage: "manage secrets",
Subcommands: []cli.Command{
orgSecretAddCmd,
orgSecretRemoveCmd,
orgSecretListCmd,
},
}

83
drone/org_secret_add.go Normal file
View file

@ -0,0 +1,83 @@
package main
import (
"fmt"
"io/ioutil"
"log"
"strings"
"github.com/codegangsta/cli"
"github.com/drone/drone/model"
)
var orgSecretAddCmd = cli.Command{
Name: "add",
Usage: "adds a secret",
ArgsUsage: "[org] [key] [value]",
Action: func(c *cli.Context) {
if err := orgSecretAdd(c); err != nil {
log.Fatalln(err)
}
},
Flags: []cli.Flag{
cli.StringSliceFlag{
Name: "event",
Usage: "inject the secret for these event types",
Value: &cli.StringSlice{
model.EventPush,
model.EventTag,
model.EventDeploy,
},
},
cli.StringSliceFlag{
Name: "image",
Usage: "inject the secret for these image types",
Value: &cli.StringSlice{},
},
cli.StringFlag{
Name: "input",
Usage: "input secret value from a file",
},
},
}
func orgSecretAdd(c *cli.Context) error {
if len(c.Args().Tail()) != 3 {
cli.ShowSubcommandHelp(c)
return nil
}
team := c.Args().First()
name := c.Args().Get(1)
value := c.Args().Get(2)
secret := &model.Secret{}
secret.Name = name
secret.Value = value
secret.Images = c.StringSlice("image")
secret.Events = c.StringSlice("event")
if len(secret.Images) == 0 {
return fmt.Errorf("Please specify the --image parameter")
}
// TODO(bradrydzewski) below we use an @ sybmol to denote that the secret
// value should be loaded from a file (inspired by curl). I'd prefer to use
// a --input flag to explicitly specify a filepath instead.
if strings.HasPrefix(secret.Value, "@") {
path := secret.Value[1:]
out, ferr := ioutil.ReadFile(path)
if ferr != nil {
return ferr
}
secret.Value = string(out)
}
client, err := newClient(c)
if err != nil {
return err
}
return client.TeamSecretPost(team, secret)
}

1
drone/org_secret_info.go Normal file
View file

@ -0,0 +1 @@
package main

87
drone/org_secret_list.go Normal file
View file

@ -0,0 +1,87 @@
package main
import (
"log"
"os"
"strings"
"text/template"
"github.com/codegangsta/cli"
)
var orgSecretListCmd = cli.Command{
Name: "ls",
Usage: "list all secrets",
Action: func(c *cli.Context) {
if err := orgSecretList(c); err != nil {
log.Fatalln(err)
}
},
Flags: []cli.Flag{
cli.StringFlag{
Name: "format",
Usage: "format output",
Value: tmplOrgSecretList,
},
cli.StringFlag{
Name: "image",
Usage: "filter by image",
},
cli.StringFlag{
Name: "event",
Usage: "filter by event",
},
},
}
func orgSecretList(c *cli.Context) error {
if len(c.Args().Tail()) != 1 {
cli.ShowSubcommandHelp(c)
return nil
}
team := c.Args().First()
client, err := newClient(c)
if err != nil {
return err
}
secrets, err := client.TeamSecretList(team)
if err != nil || len(secrets) == 0 {
return err
}
tmpl, err := template.New("_").Funcs(orgSecretFuncMap).Parse(c.String("format") + "\n")
if err != nil {
return err
}
for _, secret := range secrets {
if c.String("image") != "" && !stringInSlice(c.String("image"), secret.Images) {
continue
}
if c.String("event") != "" && !stringInSlice(c.String("event"), secret.Events) {
continue
}
tmpl.Execute(os.Stdout, secret)
}
return nil
}
// template for secret list items
var tmplOrgSecretList = "\x1b[33m{{ .Name }} \x1b[0m" + `
Images: {{ list .Images }}
Events: {{ list .Events }}
`
var orgSecretFuncMap = template.FuncMap{
"list": func(s []string) string {
return strings.Join(s, ", ")
},
}

34
drone/org_secret_rm.go Normal file
View file

@ -0,0 +1,34 @@
package main
import (
"log"
"github.com/codegangsta/cli"
)
var orgSecretRemoveCmd = cli.Command{
Name: "rm",
Usage: "remove a secret",
Action: func(c *cli.Context) {
if err := orgSecretRemove(c); err != nil {
log.Fatalln(err)
}
},
}
func orgSecretRemove(c *cli.Context) error {
if len(c.Args().Tail()) != 2 {
cli.ShowSubcommandHelp(c)
return nil
}
team := c.Args().First()
secret := c.Args().Get(1)
client, err := newClient(c)
if err != nil {
return err
}
return client.TeamSecretDel(team, secret)
}

48
model/repo_secret.go Normal file
View file

@ -0,0 +1,48 @@
package model
type RepoSecret struct {
// the id for this secret.
ID int64 `json:"id" meddler:"secret_id,pk"`
// the foreign key for this secret.
RepoID int64 `json:"-" meddler:"secret_repo_id"`
// the name of the secret which will be used as the environment variable
// name at runtime.
Name string `json:"name" meddler:"secret_name"`
// the value of the secret which will be provided to the runtime environment
// as a named environment variable.
Value string `json:"value" meddler:"secret_value"`
// the secret is restricted to this list of images.
Images []string `json:"image,omitempty" meddler:"secret_images,json"`
// the secret is restricted to this list of events.
Events []string `json:"event,omitempty" meddler:"secret_events,json"`
}
// Secret transforms a repo secret into a simple secret.
func (s *RepoSecret) Secret() *Secret {
return &Secret{
Name: s.Name,
Value: s.Value,
Images: s.Images,
Events: s.Events,
}
}
// Clone provides a repo secrets clone without the value.
func (s *RepoSecret) Clone() *RepoSecret {
return &RepoSecret{
ID: s.ID,
Name: s.Name,
Images: s.Images,
Events: s.Events,
}
}
// Validate validates the required fields and formats.
func (s *RepoSecret) Validate() error {
return nil
}

View file

@ -1,27 +1,23 @@
package model
import "path/filepath"
import (
"path/filepath"
)
type Secret struct {
// the id for this secret.
ID int64 `json:"id" meddler:"secret_id,pk"`
// the foreign key for this secret.
RepoID int64 `json:"-" meddler:"secret_repo_id"`
// the name of the secret which will be used as the environment variable
// name at runtime.
Name string `json:"name" meddler:"secret_name"`
Name string `json:"name"`
// the value of the secret which will be provided to the runtime environment
// as a named environment variable.
Value string `json:"value" meddler:"secret_value"`
Value string `json:"value"`
// the secret is restricted to this list of images.
Images []string `json:"image,omitempty" meddler:"secret_images,json"`
Images []string `json:"image,omitempty"`
// the secret is restricted to this list of events.
Events []string `json:"event,omitempty" meddler:"secret_events,json"`
Events []string `json:"event,omitempty"`
}
// Match returns true if an image and event match the restricted list.
@ -55,12 +51,3 @@ func (s *Secret) MatchEvent(event string) bool {
func (s *Secret) Validate() error {
return nil
}
func (s *Secret) Clone() *Secret {
return &Secret{
ID: s.ID,
Name: s.Name,
Images: s.Images,
Events: s.Events,
}
}

48
model/team_secret.go Normal file
View file

@ -0,0 +1,48 @@
package model
type TeamSecret struct {
// the id for this secret.
ID int64 `json:"id" meddler:"team_secret_id,pk"`
// the foreign key for this secret.
Key string `json:"-" meddler:"team_secret_key"`
// the name of the secret which will be used as the environment variable
// name at runtime.
Name string `json:"name" meddler:"team_secret_name"`
// the value of the secret which will be provided to the runtime environment
// as a named environment variable.
Value string `json:"value" meddler:"team_secret_value"`
// the secret is restricted to this list of images.
Images []string `json:"image,omitempty" meddler:"team_secret_images,json"`
// the secret is restricted to this list of events.
Events []string `json:"event,omitempty" meddler:"team_secret_events,json"`
}
// Secret transforms a repo secret into a simple secret.
func (s *TeamSecret) Secret() *Secret {
return &Secret{
Name: s.Name,
Value: s.Value,
Images: s.Images,
Events: s.Events,
}
}
// Clone provides a repo secrets clone without the value.
func (s *TeamSecret) Clone() *TeamSecret {
return &TeamSecret{
ID: s.ID,
Name: s.Name,
Images: s.Images,
Events: s.Events,
}
}
// Validate validates the required fields and formats.
func (s *TeamSecret) Validate() error {
return nil
}

View file

@ -122,7 +122,7 @@ func (c *client) Login(res http.ResponseWriter, req *http.Request) (*model.User,
defer response1.Body.Close()
contents, err := ioutil.ReadAll(response1.Body)
if err !=nil {
if err != nil {
return nil, err
}
@ -152,7 +152,7 @@ func (*client) Teams(u *model.User) ([]*model.Team, error) {
func (c *client) Repo(u *model.User, owner, name string) (*model.Repo, error) {
client := NewClientWithToken(&c.Consumer, u.Token)
repo , err := c.FindRepo(client,owner,name)
repo, err := c.FindRepo(client, owner, name)
if err != nil {
return nil, err
}
@ -202,7 +202,7 @@ func (c *client) Perm(u *model.User, owner, repo string) (*model.Perm, error) {
}
// Must have admin to be able to list hooks. If have access the enable perms
_, err = client.Get(fmt.Sprintf("%s/rest/api/1.0/projects/%s/repos/%s/settings/hooks/%s", c.URL, owner, repo,"com.atlassian.stash.plugin.stash-web-post-receive-hooks-plugin:postReceiveHook"))
_, err = client.Get(fmt.Sprintf("%s/rest/api/1.0/projects/%s/repos/%s/settings/hooks/%s", c.URL, owner, repo, "com.atlassian.stash.plugin.stash-web-post-receive-hooks-plugin:postReceiveHook"))
if err == nil {
perms.Push = true
perms.Admin = true
@ -339,7 +339,7 @@ func (bs *client) DeleteHook(client *http.Client, project, slug, hook_key, link
return nil
}
func (c *client) FindRepo(client *http.Client, owner string, name string) (*model.Repo, error){
func (c *client) FindRepo(client *http.Client, owner string, name string) (*model.Repo, error) {
urlString := fmt.Sprintf("%s/rest/api/1.0/projects/%s/repos/%s", c.URL, owner, name)
@ -351,7 +351,7 @@ func (c *client) FindRepo(client *http.Client, owner string, name string) (*mode
contents, err := ioutil.ReadAll(response.Body)
bsRepo := BSRepo{}
err = json.Unmarshal(contents, &bsRepo)
if err !=nil {
if err != nil {
return nil, err
}
repo := &model.Repo{

View file

@ -0,0 +1,21 @@
package session
import (
"github.com/gin-gonic/gin"
)
func MustTeamAdmin() gin.HandlerFunc {
return func(c *gin.Context) {
user := User(c)
switch {
case user == nil:
c.String(401, "User not authorized")
c.Abort()
case user.Admin == false:
c.String(413, "User not authorized")
c.Abort()
default:
c.Next()
}
}
}

View file

@ -62,6 +62,18 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
users.DELETE("/:login", server.DeleteUser)
}
teams := e.Group("/api/teams")
{
user.Use(session.MustTeamAdmin())
team := teams.Group("/:team")
{
team.GET("/secrets", server.GetTeamSecrets)
team.POST("/secrets", server.PostTeamSecret)
team.DELETE("/secrets/:secret", server.DeleteTeamSecret)
}
}
repos := e.Group("/api/repos/:owner/:name")
{
repos.POST("", server.PostRepo)

View file

@ -291,7 +291,7 @@ func PostBuild(c *gin.Context) {
// get the previous build so that we can send
// on status change notifications
last, _ := store.GetBuildLastBefore(c, repo, build.Branch, build.ID)
secs, err := store.GetSecretList(c, repo)
secs, err := store.GetMergedSecretList(c, repo)
if err != nil {
log.Errorf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err)
}

View file

@ -206,7 +206,7 @@ func PostHook(c *gin.Context) {
// get the previous build so that we can send
// on status change notifications
last, _ := store.GetBuildLastBefore(c, repo, build.Branch, build.ID)
secs, err := store.GetSecretList(c, repo)
secs, err := store.GetMergedSecretList(c, repo)
if err != nil {
log.Errorf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err)
}

View file

@ -19,7 +19,7 @@ func GetSecrets(c *gin.Context) {
return
}
var list []*model.Secret
var list []*model.RepoSecret
for _, s := range secrets {
list = append(list, s.Clone())
@ -31,7 +31,7 @@ func GetSecrets(c *gin.Context) {
func PostSecret(c *gin.Context) {
repo := session.Repo(c)
in := &model.Secret{}
in := &model.RepoSecret{}
err := c.Bind(in)
if err != nil {
c.String(http.StatusBadRequest, "Invalid JSON input. %s", err.Error())

67
server/team_secret.go Normal file
View file

@ -0,0 +1,67 @@
package server
import (
"net/http"
"github.com/drone/drone/model"
"github.com/drone/drone/store"
"github.com/gin-gonic/gin"
)
func GetTeamSecrets(c *gin.Context) {
team := c.Param("team")
secrets, err := store.GetTeamSecretList(c, team)
if err != nil {
c.AbortWithStatus(http.StatusInternalServerError)
return
}
var list []*model.TeamSecret
for _, s := range secrets {
list = append(list, s.Clone())
}
c.JSON(http.StatusOK, list)
}
func PostTeamSecret(c *gin.Context) {
team := c.Param("team")
in := &model.TeamSecret{}
err := c.Bind(in)
if err != nil {
c.String(http.StatusBadRequest, "Invalid JSON input. %s", err.Error())
return
}
in.ID = 0
in.Key = team
err = store.SetTeamSecret(c, in)
if err != nil {
c.String(http.StatusInternalServerError, "Unable to persist team secret. %s", err.Error())
return
}
c.String(http.StatusOK, "")
}
func DeleteTeamSecret(c *gin.Context) {
team := c.Param("team")
name := c.Param("secret")
secret, err := store.GetTeamSecret(c, team, name)
if err != nil {
c.String(http.StatusNotFound, "Cannot find secret %s.", name)
return
}
err = store.DeleteTeamSecret(c, secret)
if err != nil {
c.String(http.StatusInternalServerError, "Unable to delete team secret. %s", err.Error())
return
}
c.String(http.StatusOK, "")
}

View file

@ -0,0 +1,19 @@
-- +migrate Up
CREATE TABLE team_secrets (
team_secret_id INTEGER PRIMARY KEY AUTO_INCREMENT
,team_secret_key VARCHAR(255)
,team_secret_name VARCHAR(255)
,team_secret_value MEDIUMBLOB
,team_secret_images VARCHAR(2000)
,team_secret_events VARCHAR(2000)
,UNIQUE(team_secret_name, team_secret_key)
);
CREATE INDEX ix_team_secrets_key ON team_secrets (team_secret_key);
-- +migrate Down
DROP INDEX ix_team_secrets_key;
DROP TABLE team_secrets;

View file

@ -0,0 +1,19 @@
-- +migrate Up
CREATE TABLE team_secrets (
team_secret_id SERIAL PRIMARY KEY
,team_secret_key VARCHAR(255)
,team_secret_name VARCHAR(255)
,team_secret_value BYTEA
,team_secret_images VARCHAR(2000)
,team_secret_events VARCHAR(2000)
,UNIQUE(team_secret_name, team_secret_key)
);
CREATE INDEX ix_team_secrets_key ON team_secrets (team_secret_key);
-- +migrate Down
DROP INDEX ix_team_secrets_key;
DROP TABLE team_secrets;

View file

@ -0,0 +1,19 @@
-- +migrate Up
CREATE TABLE team_secrets (
team_secret_id INTEGER PRIMARY KEY AUTOINCREMENT
,team_secret_key TEXT
,team_secret_name TEXT
,team_secret_value TEXT
,team_secret_images TEXT
,team_secret_events TEXT
,UNIQUE(team_secret_name, team_secret_key)
);
CREATE INDEX ix_team_secrets_key ON team_secrets (team_secret_key);
-- +migrate Down
DROP INDEX ix_team_secrets_key;
DROP TABLE team_secrets;

View file

@ -5,20 +5,20 @@ import (
"github.com/russross/meddler"
)
func (db *datastore) GetSecretList(repo *model.Repo) ([]*model.Secret, error) {
var secrets = []*model.Secret{}
func (db *datastore) GetSecretList(repo *model.Repo) ([]*model.RepoSecret, error) {
var secrets = []*model.RepoSecret{}
var err = meddler.QueryAll(db, &secrets, rebind(secretListQuery), repo.ID)
return secrets, err
}
func (db *datastore) GetSecret(repo *model.Repo, name string) (*model.Secret, error) {
var secret = new(model.Secret)
func (db *datastore) GetSecret(repo *model.Repo, name string) (*model.RepoSecret, error) {
var secret = new(model.RepoSecret)
var err = meddler.QueryRow(db, secret, rebind(secretNameQuery), repo.ID, name)
return secret, err
}
func (db *datastore) SetSecret(sec *model.Secret) error {
var got = new(model.Secret)
func (db *datastore) SetSecret(sec *model.RepoSecret) error {
var got = new(model.RepoSecret)
var err = meddler.QueryRow(db, got, rebind(secretNameQuery), sec.RepoID, sec.Name)
if err == nil && got.ID != 0 {
sec.ID = got.ID // update existing id
@ -26,7 +26,7 @@ func (db *datastore) SetSecret(sec *model.Secret) error {
return meddler.Save(db, secretTable, sec)
}
func (db *datastore) DeleteSecret(sec *model.Secret) error {
func (db *datastore) DeleteSecret(sec *model.RepoSecret) error {
_, err := db.Exec(rebind(secretDeleteStmt), sec.ID)
return err
}

View file

@ -7,13 +7,13 @@ import (
"github.com/franela/goblin"
)
func TestSecrets(t *testing.T) {
func TestRepoSecrets(t *testing.T) {
db := openTest()
defer db.Close()
s := From(db)
g := goblin.Goblin(t)
g.Describe("Secrets", func() {
g.Describe("RepoSecrets", func() {
// before each test be sure to purge the package
// table data from the database.
@ -22,7 +22,7 @@ func TestSecrets(t *testing.T) {
})
g.It("Should set and get a secret", func() {
secret := &model.Secret{
secret := &model.RepoSecret{
RepoID: 1,
Name: "foo",
Value: "bar",
@ -42,7 +42,7 @@ func TestSecrets(t *testing.T) {
})
g.It("Should update a secret", func() {
secret := &model.Secret{
secret := &model.RepoSecret{
RepoID: 1,
Name: "foo",
Value: "bar",
@ -58,12 +58,12 @@ func TestSecrets(t *testing.T) {
})
g.It("Should list secrets", func() {
s.SetSecret(&model.Secret{
s.SetSecret(&model.RepoSecret{
RepoID: 1,
Name: "foo",
Value: "bar",
})
s.SetSecret(&model.Secret{
s.SetSecret(&model.RepoSecret{
RepoID: 1,
Name: "bar",
Value: "baz",
@ -74,7 +74,7 @@ func TestSecrets(t *testing.T) {
})
g.It("Should delete a secret", func() {
secret := &model.Secret{
secret := &model.RepoSecret{
RepoID: 1,
Name: "foo",
Value: "bar",

View file

@ -0,0 +1,53 @@
package datastore
import (
"github.com/drone/drone/model"
"github.com/russross/meddler"
)
func (db *datastore) GetTeamSecretList(team string) ([]*model.TeamSecret, error) {
var secrets = []*model.TeamSecret{}
var err = meddler.QueryAll(db, &secrets, rebind(teamSecretListQuery), team)
return secrets, err
}
func (db *datastore) GetTeamSecret(team, name string) (*model.TeamSecret, error) {
var secret = new(model.TeamSecret)
var err = meddler.QueryRow(db, secret, rebind(teamSecretNameQuery), team, name)
return secret, err
}
func (db *datastore) SetTeamSecret(sec *model.TeamSecret) error {
var got = new(model.TeamSecret)
var err = meddler.QueryRow(db, got, rebind(teamSecretNameQuery), sec.Key, sec.Name)
if err == nil && got.ID != 0 {
sec.ID = got.ID // update existing id
}
return meddler.Save(db, teamSecretTable, sec)
}
func (db *datastore) DeleteTeamSecret(sec *model.TeamSecret) error {
_, err := db.Exec(rebind(teamSecretDeleteStmt), sec.ID)
return err
}
const teamSecretTable = "team_secrets"
const teamSecretListQuery = `
SELECT *
FROM team_secrets
WHERE team_secret_key = ?
`
const teamSecretNameQuery = `
SELECT *
FROM team_secrets
WHERE team_secret_key = ?
AND team_secret_name = ?
LIMIT 1;
`
const teamSecretDeleteStmt = `
DELETE FROM team_secrets
WHERE team_secret_id = ?
`

View file

@ -0,0 +1,94 @@
package datastore
import (
"testing"
"github.com/drone/drone/model"
"github.com/franela/goblin"
)
func TestTeamSecrets(t *testing.T) {
db := openTest()
defer db.Close()
s := From(db)
g := goblin.Goblin(t)
g.Describe("TeamSecrets", func() {
// before each test be sure to purge the package
// table data from the database.
g.BeforeEach(func() {
db.Exec(rebind("DELETE FROM team_secrets"))
})
g.It("Should set and get a secret", func() {
secret := &model.TeamSecret{
Key: "octocat",
Name: "foo",
Value: "bar",
Images: []string{"docker", "gcr"},
Events: []string{"push", "tag"},
}
err := s.SetTeamSecret(secret)
g.Assert(err == nil).IsTrue()
g.Assert(secret.ID != 0).IsTrue()
got, err := s.GetTeamSecret("octocat", secret.Name)
g.Assert(err == nil).IsTrue()
g.Assert(got.Name).Equal(secret.Name)
g.Assert(got.Value).Equal(secret.Value)
g.Assert(got.Images).Equal(secret.Images)
g.Assert(got.Events).Equal(secret.Events)
})
g.It("Should update a secret", func() {
secret := &model.TeamSecret{
Key: "octocat",
Name: "foo",
Value: "bar",
}
s.SetTeamSecret(secret)
secret.Value = "baz"
s.SetTeamSecret(secret)
got, err := s.GetTeamSecret("octocat", secret.Name)
g.Assert(err == nil).IsTrue()
g.Assert(got.Name).Equal(secret.Name)
g.Assert(got.Value).Equal(secret.Value)
})
g.It("Should list secrets", func() {
s.SetTeamSecret(&model.TeamSecret{
Key: "octocat",
Name: "foo",
Value: "bar",
})
s.SetTeamSecret(&model.TeamSecret{
Key: "octocat",
Name: "bar",
Value: "baz",
})
secrets, err := s.GetTeamSecretList("octocat")
g.Assert(err == nil).IsTrue()
g.Assert(len(secrets)).Equal(2)
})
g.It("Should delete a secret", func() {
secret := &model.TeamSecret{
Key: "octocat",
Name: "foo",
Value: "bar",
}
s.SetTeamSecret(secret)
_, err := s.GetTeamSecret("octocat", secret.Name)
g.Assert(err == nil).IsTrue()
err = s.DeleteTeamSecret(secret)
g.Assert(err == nil).IsTrue()
_, err = s.GetTeamSecret("octocat", secret.Name)
g.Assert(err != nil).IsTrue("expect a no rows in result set error")
})
})
}

View file

@ -59,16 +59,28 @@ type Store interface {
DeleteRepo(*model.Repo) error
// GetSecretList gets a list of repository secrets
GetSecretList(*model.Repo) ([]*model.Secret, error)
GetSecretList(*model.Repo) ([]*model.RepoSecret, error)
// GetSecret gets the named repository secret.
GetSecret(*model.Repo, string) (*model.Secret, error)
GetSecret(*model.Repo, string) (*model.RepoSecret, error)
// SetSecret sets the named repository secret.
SetSecret(*model.Secret) error
SetSecret(*model.RepoSecret) error
// DeleteSecret deletes the named repository secret.
DeleteSecret(*model.Secret) error
DeleteSecret(*model.RepoSecret) error
// GetTeamSecretList gets a list of team secrets
GetTeamSecretList(string) ([]*model.TeamSecret, error)
// GetTeamSecret gets the named team secret.
GetTeamSecret(string, string) (*model.TeamSecret, error)
// SetTeamSecret sets the named team secret.
SetTeamSecret(*model.TeamSecret) error
// DeleteTeamSecret deletes the named team secret.
DeleteTeamSecret(*model.TeamSecret) error
// GetBuild gets a build by unique ID.
GetBuild(int64) (*model.Build, error)
@ -202,22 +214,66 @@ func DeleteRepo(c context.Context, repo *model.Repo) error {
return FromContext(c).DeleteRepo(repo)
}
func GetSecretList(c context.Context, r *model.Repo) ([]*model.Secret, error) {
func GetSecretList(c context.Context, r *model.Repo) ([]*model.RepoSecret, error) {
return FromContext(c).GetSecretList(r)
}
func GetSecret(c context.Context, r *model.Repo, name string) (*model.Secret, error) {
func GetSecret(c context.Context, r *model.Repo, name string) (*model.RepoSecret, error) {
return FromContext(c).GetSecret(r, name)
}
func SetSecret(c context.Context, s *model.Secret) error {
func SetSecret(c context.Context, s *model.RepoSecret) error {
return FromContext(c).SetSecret(s)
}
func DeleteSecret(c context.Context, s *model.Secret) error {
func DeleteSecret(c context.Context, s *model.RepoSecret) error {
return FromContext(c).DeleteSecret(s)
}
func GetTeamSecretList(c context.Context, team string) ([]*model.TeamSecret, error) {
return FromContext(c).GetTeamSecretList(team)
}
func GetTeamSecret(c context.Context, team, name string) (*model.TeamSecret, error) {
return FromContext(c).GetTeamSecret(team, name)
}
func SetTeamSecret(c context.Context, s *model.TeamSecret) error {
return FromContext(c).SetTeamSecret(s)
}
func DeleteTeamSecret(c context.Context, s *model.TeamSecret) error {
return FromContext(c).DeleteTeamSecret(s)
}
func GetMergedSecretList(c context.Context, r *model.Repo) ([]*model.Secret, error) {
var (
secrets []*model.Secret
)
repoSecs, err := FromContext(c).GetSecretList(r)
if err != nil {
return nil, err
}
for _, secret := range repoSecs {
secrets = append(secrets, secret.Secret())
}
teamSecs, err := FromContext(c).GetTeamSecretList(r.Owner)
if err != nil {
return nil, err
}
for _, secret := range teamSecs {
secrets = append(secrets, secret.Secret())
}
return secrets, nil
}
func GetBuild(c context.Context, id int64) (*model.Build, error) {
return FromContext(c).GetBuild(id)
}