Merge branch 'master' into bbserver-cleanup

updated to also include build config updates

# Conflicts:
#	remote/bitbucketserver/bitbucketserver.go
This commit is contained in:
Joachim Hill-Grannec 2016-08-11 13:35:47 -07:00
commit 529a9eba49
30 changed files with 791 additions and 84 deletions

View file

@ -61,6 +61,15 @@ type Client interface {
// SecretDel deletes a named repository secret. // SecretDel deletes a named repository secret.
SecretDel(string, string, string) error 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 returns a repository build by number.
Build(string, string, int) (*model.Build, error) Build(string, string, int) (*model.Build, error)

View file

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

View file

@ -42,6 +42,7 @@ func main() {
signCmd, signCmd,
repoCmd, repoCmd,
userCmd, userCmd,
orgCmd,
} }
app.Run(os.Args) 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 package model
import "path/filepath" import (
"path/filepath"
)
type Secret struct { 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 // the name of the secret which will be used as the environment variable
// name at runtime. // 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 // the value of the secret which will be provided to the runtime environment
// as a named environment variable. // 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. // 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. // 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. // 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 { func (s *Secret) Validate() error {
return nil 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

@ -145,7 +145,7 @@ func (c *Config) Perm(u *model.User, owner, repo string) (*model.Perm, error) {
func (c *Config) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) { func (c *Config) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
client := internal.NewClientWithToken(c.URL, c.Consumer, u.Token) client := internal.NewClientWithToken(c.URL, c.Consumer, u.Token)
return client.FindFileForRepo(r.Owner, r.Name, f) return client.FindFileForRepo(r.Owner, r.Name, f, b.Ref)
} }
// Status is not supported by the bitbucketserver driver. // Status is not supported by the bitbucketserver driver.
@ -184,7 +184,7 @@ func (c *Config) Deactivate(u *model.User, r *model.Repo, link string) error {
} }
func (c *Config) Hook(r *http.Request) (*model.Repo, *model.Build, error) { func (c *Config) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
return parseHook(r) return parseHook(r, c.URL)
} }
func CreateConsumer(URL string, ConsumerKey string, PrivateKey *rsa.PrivateKey) *oauth.Consumer { func CreateConsumer(URL string, ConsumerKey string, PrivateKey *rsa.PrivateKey) *oauth.Consumer {

View file

@ -10,6 +10,7 @@ import (
"github.com/mrjones/oauth" "github.com/mrjones/oauth"
"net/url" "net/url"
"strings" "strings"
"time"
) )
// convertRepo is a helper function used to convert a Bitbucket server repository // convertRepo is a helper function used to convert a Bitbucket server repository
@ -60,7 +61,7 @@ func convertRepoLite(from *internal.Repo) *model.RepoLite {
// convertPushHook is a helper function used to convert a Bitbucket push // convertPushHook is a helper function used to convert a Bitbucket push
// hook to the Drone build struct holding commit information. // hook to the Drone build struct holding commit information.
func convertPushHook(hook *internal.PostHook) *model.Build { func convertPushHook(hook *internal.PostHook, baseURL string) *model.Build {
//get the ref parts to see if it's a tags or heads //get the ref parts to see if it's a tags or heads
refParts := strings.Split(hook.RefChanges[0].RefID, "/") refParts := strings.Split(hook.RefChanges[0].RefID, "/")
name := refParts[2] name := refParts[2]
@ -69,12 +70,14 @@ func convertPushHook(hook *internal.PostHook) *model.Build {
build := &model.Build{ build := &model.Build{
Commit: hook.RefChanges[0].ToHash, // TODO check for index value Commit: hook.RefChanges[0].ToHash, // TODO check for index value
//Link: TODO find link //Link: TODO find link
Branch: name, Branch: name,
Message: hook.Changesets.Values[0].ToCommit.Message, //TODO check for index Values Message: hook.Changesets.Values[0].ToCommit.Message, //TODO check for index Values
Avatar: avatarLink(hook.Changesets.Values[0].ToCommit.Author.EmailAddress), Avatar: avatarLink(hook.Changesets.Values[0].ToCommit.Author.EmailAddress),
Author: hook.Changesets.Values[0].ToCommit.Author.EmailAddress, // TODO check for index Values Author: fmt.Sprintf("%s <%s>", hook.Changesets.Values[0].ToCommit.Author.Name, hook.Changesets.Values[0].ToCommit.Author.EmailAddress),
//Timestamp: TODO find time parsing Email: hook.Changesets.Values[0].ToCommit.Author.EmailAddress,
Ref: hook.RefChanges[0].RefID, // TODO check for index Values Timestamp: time.Now().UTC().Unix(),
Ref: hook.RefChanges[0].RefID, // TODO check for index Values
Link: fmt.Sprintf("%s/projects/%s/repos/%s/commits/%s", baseURL, hook.Repository.Project.Key, hook.Repository.Slug, hook.RefChanges[0].ToHash),
} }
switch commitType { switch commitType {
case "tags": case "tags":

View file

@ -17,7 +17,7 @@ const (
pathRepo = "%s/rest/api/1.0/projects/%s/repos/%s" pathRepo = "%s/rest/api/1.0/projects/%s/repos/%s"
pathRepos = "%s/rest/api/1.0/repos?limit=%s" pathRepos = "%s/rest/api/1.0/repos?limit=%s"
pathHook = "%s/rest/api/1.0/projects/%s/repos/%s/settings/hooks/%s" pathHook = "%s/rest/api/1.0/projects/%s/repos/%s/settings/hooks/%s"
pathSource = "%s/projects/%s/repos/%s/browse/%s?raw" pathSource = "%s/projects/%s/repos/%s/browse/%s?at=%s&raw"
hookName = "com.atlassian.stash.plugin.stash-web-post-receive-hooks-plugin:postReceiveHook" hookName = "com.atlassian.stash.plugin.stash-web-post-receive-hooks-plugin:postReceiveHook"
pathHookEnabled = "%s/rest/api/1.0/projects/%s/repos/%s/settings/hooks/%s/enabled" pathHookEnabled = "%s/rest/api/1.0/projects/%s/repos/%s/settings/hooks/%s/enabled"
) )
@ -128,8 +128,8 @@ func (c *Client) FindRepoPerms(owner string, repo string) (*model.Perm, error) {
return perms, nil return perms, nil
} }
func (c *Client) FindFileForRepo(owner string, repo string, fileName string) ([]byte, error) { func (c *Client) FindFileForRepo(owner string, repo string, fileName string, ref string) ([]byte, error) {
response, err := c.client.Get(fmt.Sprintf(pathSource, c.base, owner, repo, fileName)) response, err := c.client.Get(fmt.Sprintf(pathSource, c.base, owner, repo, fileName, ref))
if err != nil { if err != nil {
log.Error(err) log.Error(err)
} }

View file

@ -10,12 +10,12 @@ import (
// parseHook parses a Bitbucket hook from an http.Request request and returns // parseHook parses a Bitbucket hook from an http.Request request and returns
// Repo and Build detail. TODO: find a way to support PR hooks // Repo and Build detail. TODO: find a way to support PR hooks
func parseHook(r *http.Request) (*model.Repo, *model.Build, error) { func parseHook(r *http.Request, baseURL string) (*model.Repo, *model.Build, error) {
hook := new(internal.PostHook) hook := new(internal.PostHook)
if err := json.NewDecoder(r.Body).Decode(hook); err != nil { if err := json.NewDecoder(r.Body).Decode(hook); err != nil {
return nil, nil, err return nil, nil, err
} }
build := convertPushHook(hook) build := convertPushHook(hook, baseURL)
repo := &model.Repo{ repo := &model.Repo{
Name: hook.Repository.Slug, Name: hook.Repository.Slug,
Owner: hook.Repository.Project.Key, Owner: hook.Repository.Project.Key,

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) 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 := e.Group("/api/repos/:owner/:name")
{ {
repos.POST("", server.PostRepo) repos.POST("", server.PostRepo)

View file

@ -291,7 +291,7 @@ func PostBuild(c *gin.Context) {
// get the previous build so that we can send // get the previous build so that we can send
// on status change notifications // on status change notifications
last, _ := store.GetBuildLastBefore(c, repo, build.Branch, build.ID) last, _ := store.GetBuildLastBefore(c, repo, build.Branch, build.ID)
secs, err := store.GetSecretList(c, repo) secs, err := store.GetMergedSecretList(c, repo)
if err != nil { if err != nil {
log.Errorf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err) 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 // get the previous build so that we can send
// on status change notifications // on status change notifications
last, _ := store.GetBuildLastBefore(c, repo, build.Branch, build.ID) last, _ := store.GetBuildLastBefore(c, repo, build.Branch, build.ID)
secs, err := store.GetSecretList(c, repo) secs, err := store.GetMergedSecretList(c, repo)
if err != nil { if err != nil {
log.Errorf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err) 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 return
} }
var list []*model.Secret var list []*model.RepoSecret
for _, s := range secrets { for _, s := range secrets {
list = append(list, s.Clone()) list = append(list, s.Clone())
@ -31,7 +31,7 @@ func GetSecrets(c *gin.Context) {
func PostSecret(c *gin.Context) { func PostSecret(c *gin.Context) {
repo := session.Repo(c) repo := session.Repo(c)
in := &model.Secret{} in := &model.RepoSecret{}
err := c.Bind(in) err := c.Bind(in)
if err != nil { if err != nil {
c.String(http.StatusBadRequest, "Invalid JSON input. %s", err.Error()) 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" "github.com/russross/meddler"
) )
func (db *datastore) GetSecretList(repo *model.Repo) ([]*model.Secret, error) { func (db *datastore) GetSecretList(repo *model.Repo) ([]*model.RepoSecret, error) {
var secrets = []*model.Secret{} var secrets = []*model.RepoSecret{}
var err = meddler.QueryAll(db, &secrets, rebind(secretListQuery), repo.ID) var err = meddler.QueryAll(db, &secrets, rebind(secretListQuery), repo.ID)
return secrets, err return secrets, err
} }
func (db *datastore) GetSecret(repo *model.Repo, name string) (*model.Secret, error) { func (db *datastore) GetSecret(repo *model.Repo, name string) (*model.RepoSecret, error) {
var secret = new(model.Secret) var secret = new(model.RepoSecret)
var err = meddler.QueryRow(db, secret, rebind(secretNameQuery), repo.ID, name) var err = meddler.QueryRow(db, secret, rebind(secretNameQuery), repo.ID, name)
return secret, err return secret, err
} }
func (db *datastore) SetSecret(sec *model.Secret) error { func (db *datastore) SetSecret(sec *model.RepoSecret) error {
var got = new(model.Secret) var got = new(model.RepoSecret)
var err = meddler.QueryRow(db, got, rebind(secretNameQuery), sec.RepoID, sec.Name) var err = meddler.QueryRow(db, got, rebind(secretNameQuery), sec.RepoID, sec.Name)
if err == nil && got.ID != 0 { if err == nil && got.ID != 0 {
sec.ID = got.ID // update existing id 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) 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) _, err := db.Exec(rebind(secretDeleteStmt), sec.ID)
return err return err
} }

View file

@ -7,13 +7,13 @@ import (
"github.com/franela/goblin" "github.com/franela/goblin"
) )
func TestSecrets(t *testing.T) { func TestRepoSecrets(t *testing.T) {
db := openTest() db := openTest()
defer db.Close() defer db.Close()
s := From(db) s := From(db)
g := goblin.Goblin(t) g := goblin.Goblin(t)
g.Describe("Secrets", func() { g.Describe("RepoSecrets", func() {
// before each test be sure to purge the package // before each test be sure to purge the package
// table data from the database. // table data from the database.
@ -22,7 +22,7 @@ func TestSecrets(t *testing.T) {
}) })
g.It("Should set and get a secret", func() { g.It("Should set and get a secret", func() {
secret := &model.Secret{ secret := &model.RepoSecret{
RepoID: 1, RepoID: 1,
Name: "foo", Name: "foo",
Value: "bar", Value: "bar",
@ -42,7 +42,7 @@ func TestSecrets(t *testing.T) {
}) })
g.It("Should update a secret", func() { g.It("Should update a secret", func() {
secret := &model.Secret{ secret := &model.RepoSecret{
RepoID: 1, RepoID: 1,
Name: "foo", Name: "foo",
Value: "bar", Value: "bar",
@ -58,12 +58,12 @@ func TestSecrets(t *testing.T) {
}) })
g.It("Should list secrets", func() { g.It("Should list secrets", func() {
s.SetSecret(&model.Secret{ s.SetSecret(&model.RepoSecret{
RepoID: 1, RepoID: 1,
Name: "foo", Name: "foo",
Value: "bar", Value: "bar",
}) })
s.SetSecret(&model.Secret{ s.SetSecret(&model.RepoSecret{
RepoID: 1, RepoID: 1,
Name: "bar", Name: "bar",
Value: "baz", Value: "baz",
@ -74,7 +74,7 @@ func TestSecrets(t *testing.T) {
}) })
g.It("Should delete a secret", func() { g.It("Should delete a secret", func() {
secret := &model.Secret{ secret := &model.RepoSecret{
RepoID: 1, RepoID: 1,
Name: "foo", Name: "foo",
Value: "bar", 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 DeleteRepo(*model.Repo) error
// GetSecretList gets a list of repository secrets // 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 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 sets the named repository secret.
SetSecret(*model.Secret) error SetSecret(*model.RepoSecret) error
// DeleteSecret deletes the named repository secret. // 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 gets a build by unique ID.
GetBuild(int64) (*model.Build, error) GetBuild(int64) (*model.Build, error)
@ -202,22 +214,66 @@ func DeleteRepo(c context.Context, repo *model.Repo) error {
return FromContext(c).DeleteRepo(repo) 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) 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) 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) 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) 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) { func GetBuild(c context.Context, id int64) (*model.Build, error) {
return FromContext(c).GetBuild(id) return FromContext(c).GetBuild(id)
} }