mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-23 10:21:00 +00:00
Merge pull request #1737 from tboerger/feature/org-secrets
Organization/Team secrets
This commit is contained in:
commit
abaa02efa2
27 changed files with 779 additions and 75 deletions
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ func main() {
|
|||
signCmd,
|
||||
repoCmd,
|
||||
userCmd,
|
||||
orgCmd,
|
||||
}
|
||||
|
||||
app.Run(os.Args)
|
||||
|
|
11
drone/org.go
Normal file
11
drone/org.go
Normal 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
13
drone/org_secret.go
Normal 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
83
drone/org_secret_add.go
Normal 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
1
drone/org_secret_info.go
Normal file
|
@ -0,0 +1 @@
|
|||
package main
|
87
drone/org_secret_list.go
Normal file
87
drone/org_secret_list.go
Normal 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
34
drone/org_secret_rm.go
Normal 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
48
model/repo_secret.go
Normal 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
|
||||
}
|
|
@ -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
48
model/team_secret.go
Normal 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
|
||||
}
|
|
@ -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{
|
||||
|
|
21
router/middleware/session/team.go
Normal file
21
router/middleware/session/team.go
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
67
server/team_secret.go
Normal 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, "")
|
||||
}
|
19
store/datastore/ddl/mysql/7.sql
Normal file
19
store/datastore/ddl/mysql/7.sql
Normal 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;
|
19
store/datastore/ddl/postgres/7.sql
Normal file
19
store/datastore/ddl/postgres/7.sql
Normal 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;
|
19
store/datastore/ddl/sqlite3/7.sql
Normal file
19
store/datastore/ddl/sqlite3/7.sql
Normal 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;
|
|
@ -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
|
||||
}
|
|
@ -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",
|
53
store/datastore/team_secret.go
Normal file
53
store/datastore/team_secret.go
Normal 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 = ?
|
||||
`
|
94
store/datastore/team_secret_test.go
Normal file
94
store/datastore/team_secret_test.go
Normal 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")
|
||||
})
|
||||
})
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue