Merge pull request #2000 from bradrydzewski/master

pluggable secrets, registry, approval backends
This commit is contained in:
Brad Rydzewski 2017-04-12 00:16:27 +02:00 committed by GitHub
commit b15e9bab6f
76 changed files with 1680 additions and 1611 deletions

View file

@ -26,9 +26,6 @@ type Client interface {
// UserDel deletes a user account. // UserDel deletes a user account.
UserDel(string) error UserDel(string) error
// // UserFeed returns the user's activity feed.
// UserFeed() ([]*Activity, error)
// Repo returns a repository by name. // Repo returns a repository by name.
Repo(string, string) (*model.Repo, error) Repo(string, string) (*model.Repo, error)
@ -48,36 +45,6 @@ type Client interface {
// RepoDel deletes a repository. // RepoDel deletes a repository.
RepoDel(string, string) error RepoDel(string, string) error
// Sign returns a cryptographic signature for the input string.
Sign(string, string, []byte) ([]byte, error)
// SecretList returns a list of all repository secrets.
SecretList(string, string) ([]*model.Secret, error)
// SecretPost create or updates a repository secret.
SecretPost(string, string, *model.Secret) error
// 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
// GlobalSecretList returns a list of global secrets.
GlobalSecretList() ([]*model.Secret, error)
// GlobalSecretPost create or updates a global secret.
GlobalSecretPost(secret *model.Secret) error
// GlobalSecretDel deletes a named global secret.
GlobalSecretDel(secret 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)
@ -129,4 +96,19 @@ type Client interface {
// RegistryDelete deletes a registry. // RegistryDelete deletes a registry.
RegistryDelete(owner, name, hostname string) error RegistryDelete(owner, name, hostname string) error
// Secret returns a secret by name.
Secret(owner, name, secret string) (*model.Secret, error)
// SecretList returns a list of all repository secrets.
SecretList(owner, name string) ([]*model.Secret, error)
// SecretCreate creates a registry.
SecretCreate(owner, name string, secret *model.Secret) (*model.Secret, error)
// SecretUpdate updates a registry.
SecretUpdate(owner, name string, secret *model.Secret) (*model.Secret, error)
// SecretDelete deletes a secret.
SecretDelete(owner, name, secret string) error
} }

View file

@ -31,25 +31,16 @@ const (
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"
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"
pathApprove = "%s/api/repos/%s/%s/builds/%d/approve" pathApprove = "%s/api/repos/%s/%s/builds/%d/approve"
pathDecline = "%s/api/repos/%s/%s/builds/%d/decline" pathDecline = "%s/api/repos/%s/%s/builds/%d/decline"
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"
pathSign = "%s/api/repos/%s/%s/sign"
pathRepoSecrets = "%s/api/repos/%s/%s/secrets" pathRepoSecrets = "%s/api/repos/%s/%s/secrets"
pathRepoSecret = "%s/api/repos/%s/%s/secrets/%s" pathRepoSecret = "%s/api/repos/%s/%s/secrets/%s"
pathRepoRegistries = "%s/api/repos/%s/%s/registry" pathRepoRegistries = "%s/api/repos/%s/%s/registry"
pathRepoRegistry = "%s/api/repos/%s/%s/registry/%s" pathRepoRegistry = "%s/api/repos/%s/%s/registry/%s"
pathTeamSecrets = "%s/api/teams/%s/secrets"
pathTeamSecret = "%s/api/teams/%s/secrets/%s"
pathGlobalSecrets = "%s/api/global/secrets"
pathGlobalSecret = "%s/api/global/secrets/%s"
pathNodes = "%s/api/nodes"
pathNode = "%s/api/nodes/%d"
pathUsers = "%s/api/users" pathUsers = "%s/api/users"
pathUser = "%s/api/users/%s" pathUser = "%s/api/users/%s"
pathBuildQueue = "%s/api/builds" pathBuildQueue = "%s/api/builds"
@ -295,77 +286,6 @@ func (c *client) Deploy(owner, name string, num int, env string, params map[stri
return out, err return out, err
} }
// 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(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(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(pathRepoSecret, c.base, owner, name, secret)
return c.delete(uri)
}
// TeamSecretList returns a list of organizational 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 organizational 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 orgainization secret.
func (c *client) TeamSecretDel(team, secret string) error {
uri := fmt.Sprintf(pathTeamSecret, c.base, team, secret)
return c.delete(uri)
}
// GlobalSecretList returns a list of global secrets.
func (c *client) GlobalSecretList() ([]*model.Secret, error) {
var out []*model.Secret
uri := fmt.Sprintf(pathGlobalSecrets, c.base)
err := c.get(uri, &out)
return out, err
}
// GlobalSecretPost create or updates a global secret.
func (c *client) GlobalSecretPost(secret *model.Secret) error {
uri := fmt.Sprintf(pathGlobalSecrets, c.base)
return c.post(uri, secret, nil)
}
// GlobalSecretDel deletes a named global secret.
func (c *client) GlobalSecretDel(secret string) error {
uri := fmt.Sprintf(pathGlobalSecret, c.base, secret)
return c.delete(uri)
}
// Sign returns a cryptographic signature for the input string.
func (c *client) Sign(owner, name string, in []byte) ([]byte, error) {
uri := fmt.Sprintf(pathSign, c.base, owner, name)
rc, err := stream(c.client, uri, "POST", in, nil)
if err != nil {
return nil, err
}
defer rc.Close()
return ioutil.ReadAll(rc)
}
// Registry returns a registry by hostname. // Registry returns a registry by hostname.
func (c *client) Registry(owner, name, hostname string) (*model.Registry, error) { func (c *client) Registry(owner, name, hostname string) (*model.Registry, error) {
out := new(model.Registry) out := new(model.Registry)
@ -404,6 +324,44 @@ func (c *client) RegistryDelete(owner, name, hostname string) error {
return c.delete(uri) return c.delete(uri)
} }
// Secret returns a secret by name.
func (c *client) Secret(owner, name, secret string) (*model.Secret, error) {
out := new(model.Secret)
uri := fmt.Sprintf(pathRepoSecret, c.base, owner, name, secret)
err := c.get(uri, out)
return out, err
}
// SecretList returns a list of all repository secrets.
func (c *client) SecretList(owner string, name string) ([]*model.Secret, error) {
var out []*model.Secret
uri := fmt.Sprintf(pathRepoSecrets, c.base, owner, name)
err := c.get(uri, &out)
return out, err
}
// SecretCreate creates a secret.
func (c *client) SecretCreate(owner, name string, in *model.Secret) (*model.Secret, error) {
out := new(model.Secret)
uri := fmt.Sprintf(pathRepoSecrets, c.base, owner, name)
err := c.post(uri, in, out)
return out, err
}
// SecretUpdate updates a secret.
func (c *client) SecretUpdate(owner, name string, in *model.Secret) (*model.Secret, error) {
out := new(model.Secret)
uri := fmt.Sprintf(pathRepoSecret, c.base, owner, name, in.Name)
err := c.patch(uri, in, out)
return out, err
}
// SecretDelete deletes a secret.
func (c *client) SecretDelete(owner, name, secret string) error {
uri := fmt.Sprintf(pathRepoSecret, c.base, owner, name, secret)
return c.delete(uri)
}
// //
// http request helper functions // http request helper functions
// //

View file

@ -1,11 +0,0 @@
package main
import "github.com/urfave/cli"
var globalCmd = cli.Command{
Name: "global",
Usage: "manage global state",
Subcommands: []cli.Command{
globalSecretCmd,
},
}

View file

@ -1,13 +0,0 @@
package main
import "github.com/urfave/cli"
var globalSecretCmd = cli.Command{
Name: "secret",
Usage: "manage secrets",
Subcommands: []cli.Command{
globalSecretAddCmd,
globalSecretRemoveCmd,
globalSecretListCmd,
},
}

View file

@ -1,33 +0,0 @@
package main
import "github.com/urfave/cli"
var globalSecretAddCmd = cli.Command{
Name: "add",
Usage: "adds a secret",
ArgsUsage: "[key] [value]",
Action: globalSecretAdd,
Flags: secretAddFlags(),
}
func globalSecretAdd(c *cli.Context) error {
if len(c.Args()) != 2 {
cli.ShowSubcommandHelp(c)
return nil
}
name := c.Args().First()
value := c.Args().Get(1)
secret, err := secretParseCmd(name, value, c)
if err != nil {
return err
}
client, err := newClient(c)
if err != nil {
return err
}
return client.GlobalSecretPost(secret)
}

View file

@ -1 +0,0 @@
package main

View file

@ -1,25 +0,0 @@
package main
import "github.com/urfave/cli"
var globalSecretListCmd = cli.Command{
Name: "ls",
Usage: "list all secrets",
Action: globalSecretList,
Flags: secretListFlags(),
}
func globalSecretList(c *cli.Context) error {
client, err := newClient(c)
if err != nil {
return err
}
secrets, err := client.GlobalSecretList()
if err != nil || len(secrets) == 0 {
return err
}
return secretDisplayList(secrets, c)
}

View file

@ -1,25 +0,0 @@
package main
import "github.com/urfave/cli"
var globalSecretRemoveCmd = cli.Command{
Name: "rm",
Usage: "remove a secret",
Action: globalSecretRemove,
}
func globalSecretRemove(c *cli.Context) error {
if len(c.Args()) != 1 {
cli.ShowSubcommandHelp(c)
return nil
}
secret := c.Args().First()
client, err := newClient(c)
if err != nil {
return err
}
return client.GlobalSecretDel(secret)
}

View file

@ -40,11 +40,8 @@ func main() {
registryCmd, registryCmd,
secretCmd, secretCmd,
serverCmd, serverCmd,
signCmd,
repoCmd, repoCmd,
userCmd, userCmd,
orgCmd,
globalCmd,
} }
if err := app.Run(os.Args); err != nil { if err := app.Run(os.Args); err != nil {

View file

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

View file

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

View file

@ -1,34 +0,0 @@
package main
import "github.com/urfave/cli"
var orgSecretAddCmd = cli.Command{
Name: "add",
Usage: "adds a secret",
ArgsUsage: "[org] [key] [value]",
Action: orgSecretAdd,
Flags: secretAddFlags(),
}
func orgSecretAdd(c *cli.Context) error {
if len(c.Args()) != 3 {
cli.ShowSubcommandHelp(c)
return nil
}
team := c.Args().First()
name := c.Args().Get(1)
value := c.Args().Get(2)
secret, err := secretParseCmd(name, value, c)
if err != nil {
return err
}
client, err := newClient(c)
if err != nil {
return err
}
return client.TeamSecretPost(team, secret)
}

View file

@ -1 +0,0 @@
package main

View file

@ -1,32 +0,0 @@
package main
import "github.com/urfave/cli"
var orgSecretListCmd = cli.Command{
Name: "ls",
Usage: "list all secrets",
Action: orgSecretList,
Flags: secretListFlags(),
}
func orgSecretList(c *cli.Context) error {
if len(c.Args()) != 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
}
return secretDisplayList(secrets, c)
}

View file

@ -1,34 +0,0 @@
package main
import (
"log"
"github.com/urfave/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()) != 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)
}

View file

@ -23,7 +23,7 @@ var registryInfoCmd = cli.Command{
}, },
cli.StringFlag{ cli.StringFlag{
Name: "format", Name: "format",
Usage: "repository name (e.g. octocat/hello-world)", Usage: "format output",
Value: tmplRegistryList, Value: tmplRegistryList,
Hidden: true, Hidden: true,
}, },

View file

@ -18,7 +18,7 @@ var registryListCmd = cli.Command{
}, },
cli.StringFlag{ cli.StringFlag{
Name: "format", Name: "format",
Usage: "repository name (e.g. octocat/hello-world)", Usage: "format output",
Value: tmplRegistryList, Value: tmplRegistryList,
Hidden: true, Hidden: true,
}, },
@ -58,6 +58,5 @@ func registryList(c *cli.Context) error {
// template for build list information // template for build list information
var tmplRegistryList = "\x1b[33m{{ .Address }} \x1b[0m" + ` var tmplRegistryList = "\x1b[33m{{ .Address }} \x1b[0m" + `
Username: {{ .Username }} Username: {{ .Username }}
Password: ********
Email: {{ .Email }} Email: {{ .Email }}
` `

View file

@ -1,136 +1,15 @@
package main package main
import ( import "github.com/urfave/cli"
"io/ioutil"
"os"
"strings"
"text/template"
"github.com/drone/drone/model"
"github.com/urfave/cli"
)
var secretCmd = cli.Command{ var secretCmd = cli.Command{
Name: "secret", Name: "secret",
Usage: "manage secrets", Usage: "manage secrets",
Subcommands: []cli.Command{ Subcommands: []cli.Command{
secretAddCmd, secretCreateCmd,
secretRemoveCmd, secretDeleteCmd,
secretUpdateCmd,
secretInfoCmd,
secretListCmd, secretListCmd,
}, },
} }
func secretAddFlags() []cli.Flag {
return []cli.Flag{
cli.StringSliceFlag{
Name: "event",
Usage: "inject the secret for these event types",
},
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",
},
cli.BoolFlag{
Name: "skip-verify",
Usage: "skip verification for the secret",
},
cli.BoolFlag{
Name: "conceal",
Usage: "conceal secret in build logs",
},
}
}
func secretListFlags() []cli.Flag {
return []cli.Flag{
cli.StringFlag{
Name: "format",
Usage: "format output",
Value: tmplSecretList,
},
cli.StringFlag{
Name: "image",
Usage: "filter by image",
},
cli.StringFlag{
Name: "event",
Usage: "filter by event",
},
}
}
func secretParseCmd(name string, value string, c *cli.Context) (*model.Secret, error) {
secret := &model.Secret{}
secret.Name = name
secret.Value = value
secret.Images = c.StringSlice("image")
secret.Events = c.StringSlice("event")
secret.SkipVerify = c.Bool("skip-verify")
secret.Conceal = c.Bool("conceal")
if len(secret.Events) == 0 {
secret.Events = []string{
model.EventPush,
model.EventTag,
model.EventDeploy,
}
}
// 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 nil, ferr
}
secret.Value = string(out)
}
return secret, nil
}
func secretDisplayList(secrets []*model.Secret, c *cli.Context) error {
tmpl, err := template.New("_").Funcs(secretFuncMap).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 tmplSecretList = "\x1b[33m{{ .Name }} \x1b[0m" + `
Events: {{ list .Events }}
{{- if .Images }}
Images: {{ list .Images }}
{{- else }}
Images: <any>
{{- end }}
`
var secretFuncMap = template.FuncMap{
"list": func(s []string) string {
return strings.Join(s, ", ")
},
}

View file

@ -1,37 +1,66 @@
package main package main
import "github.com/urfave/cli" import (
"github.com/drone/drone/model"
"github.com/urfave/cli"
)
var secretAddCmd = cli.Command{ var secretCreateCmd = cli.Command{
Name: "add", Name: "add",
Usage: "adds a secret", Usage: "adds a secret",
ArgsUsage: "[repo] [key] [value]", Action: secretCreate,
Action: secretAdd, Flags: []cli.Flag{
Flags: secretAddFlags(), cli.StringFlag{
Name: "repository",
Usage: "repository name (e.g. octocat/hello-world)",
},
cli.StringFlag{
Name: "name",
Usage: "secret name",
},
cli.StringFlag{
Name: "value",
Usage: "secret value",
},
cli.StringSliceFlag{
Name: "event",
Usage: "secret limited to these events",
},
cli.StringSliceFlag{
Name: "image",
Usage: "secret limited to these images",
},
},
} }
func secretAdd(c *cli.Context) error { func secretCreate(c *cli.Context) error {
repo := c.Args().First() reponame := c.String("repository")
owner, name, err := parseRepo(repo) if reponame == "" {
reponame = c.Args().First()
}
owner, name, err := parseRepo(reponame)
if err != nil { if err != nil {
return err return err
} }
tail := c.Args().Tail()
if len(tail) != 2 {
cli.ShowSubcommandHelp(c)
return nil
}
secret, err := secretParseCmd(tail[0], tail[1], c)
if err != nil {
return err
}
client, err := newClient(c) client, err := newClient(c)
if err != nil { if err != nil {
return err return err
} }
secret := &model.Secret{
return client.SecretPost(owner, name, secret) Name: c.String("name"),
Value: c.String("value"),
Images: c.StringSlice("image"),
Events: c.StringSlice("events"),
}
if len(secret.Events) == 0 {
secret.Events = defaultSecretEvents
}
_, err = client.SecretUpdate(owner, name, secret)
return err
}
var defaultSecretEvents = []string{
model.EventPush,
model.EventTag,
model.EventDeploy,
} }

View file

@ -1 +1,58 @@
package main package main
import (
"html/template"
"os"
"github.com/urfave/cli"
)
var secretInfoCmd = cli.Command{
Name: "info",
Usage: "display secret info",
Action: secretInfo,
Flags: []cli.Flag{
cli.StringFlag{
Name: "repository",
Usage: "repository name (e.g. octocat/hello-world)",
},
cli.StringFlag{
Name: "name",
Usage: "secret name",
},
cli.StringFlag{
Name: "format",
Usage: "format output",
Value: tmplSecretList,
Hidden: true,
},
},
}
func secretInfo(c *cli.Context) error {
var (
secretName = c.String("name")
repoName = c.String("repository")
format = c.String("format") + "\n"
)
if repoName == "" {
repoName = c.Args().First()
}
owner, name, err := parseRepo(repoName)
if err != nil {
return err
}
client, err := newClient(c)
if err != nil {
return err
}
secret, err := client.Secret(owner, name, secretName)
if err != nil {
return err
}
tmpl, err := template.New("_").Funcs(secretFuncMap).Parse(format)
if err != nil {
return err
}
return tmpl.Execute(os.Stdout, secret)
}

View file

@ -1,32 +1,73 @@
package main package main
import "github.com/urfave/cli" import (
"html/template"
"os"
"strings"
"github.com/urfave/cli"
)
var secretListCmd = cli.Command{ var secretListCmd = cli.Command{
Name: "ls", Name: "ls",
Usage: "list all secrets", Usage: "list secrets",
Action: secretList, Action: secretList,
Flags: secretListFlags(), Flags: []cli.Flag{
cli.StringFlag{
Name: "repository",
Usage: "repository name (e.g. octocat/hello-world)",
},
cli.StringFlag{
Name: "format",
Usage: "format output",
Value: tmplSecretList,
Hidden: true,
},
},
} }
func secretList(c *cli.Context) error { func secretList(c *cli.Context) error {
owner, name, err := parseRepo(c.Args().First()) var (
format = c.String("format") + "\n"
reponame = c.String("repository")
)
if reponame == "" {
reponame = c.Args().First()
}
owner, name, err := parseRepo(reponame)
if err != nil { if err != nil {
return err return err
} }
client, err := newClient(c) client, err := newClient(c)
if err != nil { if err != nil {
return err return err
} }
list, err := client.SecretList(owner, name)
secrets, err := client.SecretList(owner, name) if err != nil {
if err != nil || len(secrets) == 0 {
return err return err
} }
tmpl, err := template.New("_").Funcs(secretFuncMap).Parse(format)
return secretDisplayList(secrets, c) if err != nil {
return err
}
for _, registry := range list {
tmpl.Execute(os.Stdout, registry)
}
return nil
}
// template for secret list items
var tmplSecretList = "\x1b[33m{{ .Name }} \x1b[0m" + `
Events: {{ list .Events }}
{{- if .Images }}
Images: {{ list .Images }}
{{- else }}
Images: <any>
{{- end }}
`
var secretFuncMap = template.FuncMap{
"list": func(s []string) string {
return strings.Join(s, ", ")
},
} }

View file

@ -2,24 +2,37 @@ package main
import "github.com/urfave/cli" import "github.com/urfave/cli"
var secretRemoveCmd = cli.Command{ var secretDeleteCmd = cli.Command{
Name: "rm", Name: "rm",
Usage: "remove a secret", Usage: "remove a secret",
Action: secretRemove, Action: secretDelete,
Flags: []cli.Flag{
cli.StringFlag{
Name: "repository",
Usage: "repository name (e.g. octocat/hello-world)",
},
cli.StringFlag{
Name: "name",
Usage: "secret name",
},
},
} }
func secretRemove(c *cli.Context) error { func secretDelete(c *cli.Context) error {
repo := c.Args().First() var (
owner, name, err := parseRepo(repo) secret = c.String("name")
reponame = c.String("repository")
)
if reponame == "" {
reponame = c.Args().First()
}
owner, name, err := parseRepo(reponame)
if err != nil { if err != nil {
return err return err
} }
secret := c.Args().Get(1)
client, err := newClient(c) client, err := newClient(c)
if err != nil { if err != nil {
return err return err
} }
return client.SecretDel(owner, name, secret) return client.SecretDelete(owner, name, secret)
} }

57
drone/secret_set.go Normal file
View file

@ -0,0 +1,57 @@
package main
import (
"github.com/drone/drone/model"
"github.com/urfave/cli"
)
var secretUpdateCmd = cli.Command{
Name: "update",
Usage: "update a secret",
Action: secretUpdate,
Flags: []cli.Flag{
cli.StringFlag{
Name: "repository",
Usage: "repository name (e.g. octocat/hello-world)",
},
cli.StringFlag{
Name: "name",
Usage: "secret name",
},
cli.StringFlag{
Name: "value",
Usage: "secret value",
},
cli.StringSliceFlag{
Name: "event",
Usage: "secret limited to these events",
},
cli.StringSliceFlag{
Name: "image",
Usage: "secret limited to these images",
},
},
}
func secretUpdate(c *cli.Context) error {
reponame := c.String("repository")
if reponame == "" {
reponame = c.Args().First()
}
owner, name, err := parseRepo(reponame)
if err != nil {
return err
}
client, err := newClient(c)
if err != nil {
return err
}
secret := &model.Secret{
Name: c.String("name"),
Value: c.String("value"),
Images: c.StringSlice("image"),
Events: c.StringSlice("events"),
}
_, err = client.SecretUpdate(owner, name, secret)
return err
}

View file

@ -22,10 +22,10 @@ var serverCmd = cli.Command{
Name: "debug", Name: "debug",
Usage: "start the server in debug mode", Usage: "start the server in debug mode",
}, },
cli.BoolFlag{ cli.StringFlag{
EnvVar: "DRONE_BROKER_DEBUG", EnvVar: "DRONE_SERVER_HOST",
Name: "broker-debug", Name: "server-host",
Usage: "start the broker in debug mode", Usage: "server host",
}, },
cli.StringFlag{ cli.StringFlag{
EnvVar: "DRONE_SERVER_ADDR", EnvVar: "DRONE_SERVER_ADDR",
@ -58,12 +58,6 @@ var serverCmd = cli.Command{
Name: "open", Name: "open",
Usage: "open user registration", Usage: "open user registration",
}, },
cli.StringFlag{
EnvVar: "DRONE_YAML",
Name: "yaml",
Usage: "build configuraton file name",
Value: ".drone.yml",
},
cli.DurationFlag{ cli.DurationFlag{
EnvVar: "DRONE_CACHE_TTL", EnvVar: "DRONE_CACHE_TTL",
Name: "cache-ttl", Name: "cache-ttl",
@ -75,6 +69,21 @@ var serverCmd = cli.Command{
Name: "agent-secret", Name: "agent-secret",
Usage: "agent secret passcode", Usage: "agent secret passcode",
}, },
cli.StringFlag{
EnvVar: "DRONE_SECRET_ENDPOINT",
Name: "secret-service",
Usage: "secret plugin endpoint",
},
cli.StringFlag{
EnvVar: "DRONE_REGISTRY_ENDPOINT",
Name: "registry-service",
Usage: "registry plugin endpoint",
},
cli.StringFlag{
EnvVar: "DRONE_GATEKEEPER_ENDPOINT",
Name: "gating-service",
Usage: "gated build endpoint",
},
cli.StringFlag{ cli.StringFlag{
EnvVar: "DRONE_DATABASE_DRIVER,DATABASE_DRIVER", EnvVar: "DRONE_DATABASE_DRIVER,DATABASE_DRIVER",
Name: "driver", Name: "driver",

View file

@ -1,50 +0,0 @@
package main
import (
"io/ioutil"
"github.com/urfave/cli"
)
var signCmd = cli.Command{
Name: "sign",
Usage: "creates a secure yaml file",
Action: sign,
Flags: []cli.Flag{
cli.StringFlag{
Name: "in",
Usage: "input file",
Value: ".drone.yml",
},
cli.StringFlag{
Name: "out",
Usage: "output file signature",
Value: ".drone.yml.sig",
},
},
}
func sign(c *cli.Context) error {
repo := c.Args().First()
owner, name, err := parseRepo(repo)
if err != nil {
return err
}
in, err := readInput(c.String("in"))
if err != nil {
return err
}
client, err := newClient(c)
if err != nil {
return err
}
sig, err := client.Sign(owner, name, in)
if err != nil {
return err
}
return ioutil.WriteFile(c.String("out"), sig, 0664)
}

View file

@ -1,17 +0,0 @@
package model
type (
// ApprovalStore persists approved committers to storage.
ApprovalStore interface {
Approve(*Repo) error
}
// Approval represents an approved committer.
Approval struct {
Username string `json:"username" meddler:"approval_username"`
Approved bool `json:"approved" meddler:"approval_approved"`
Comments string `json:"comments" meddler:"approval_comments"`
CreatedBy string `json:"created_by" meddler:"approval_created_by"`
CreatedAt int64 `json:"created_at" meddler:"approval_created_at"`
}
)

View file

@ -1,23 +0,0 @@
package model
// Gated indicates a build is gated and requires approval to proceed. It also
// includes the reason the build was gated.
type Gated struct {
Gated bool `json:"gated"`
Reason string `json:"reason"`
}
// GateService defines a service for gating builds.
type GateService interface {
Gated(*User, *Repo, *Build) (*Gated, error)
}
type gateService struct{}
func (s *gateService) Gated(owner *User, repo *Repo, build *Build) (*Gated, error) {
g := new(Gated)
if repo.IsPrivate && build.Event == EventPull && build.Sender != owner.Login {
g.Gated = true
}
return g, nil
}

View file

@ -8,6 +8,15 @@ var (
errRegistryPasswordInvalid = errors.New("Invalid Registry Password") errRegistryPasswordInvalid = errors.New("Invalid Registry Password")
) )
// RegistryService defines a service for managing registries.
type RegistryService interface {
RegistryFind(*Repo, string) (*Registry, error)
RegistryList(*Repo) ([]*Registry, error)
RegistryCreate(*Repo, *Registry) error
RegistryUpdate(*Repo, *Registry) error
RegistryDelete(*Repo, string) error
}
// RegistryStore persists registry information to storage. // RegistryStore persists registry information to storage.
type RegistryStore interface { type RegistryStore interface {
RegistryFind(*Repo, string) (*Registry, error) RegistryFind(*Repo, string) (*Registry, error)

View file

@ -25,6 +25,7 @@ type Repo struct {
IsPrivate bool `json:"private,omitempty" meddler:"repo_private"` IsPrivate bool `json:"private,omitempty" meddler:"repo_private"`
IsTrusted bool `json:"trusted" meddler:"repo_trusted"` IsTrusted bool `json:"trusted" meddler:"repo_trusted"`
IsStarred bool `json:"starred,omitempty" meddler:"-"` IsStarred bool `json:"starred,omitempty" meddler:"-"`
IsGated bool `json:"gated" meddler:"repo_gated"`
AllowPull bool `json:"allow_pr" meddler:"repo_allow_pr"` AllowPull bool `json:"allow_pr" meddler:"repo_allow_pr"`
AllowPush bool `json:"allow_push" meddler:"repo_allow_push"` AllowPush bool `json:"allow_push" meddler:"repo_allow_push"`
AllowDeploy bool `json:"allow_deploys" meddler:"repo_allow_deploys"` AllowDeploy bool `json:"allow_deploys" meddler:"repo_allow_deploys"`

View file

@ -1,58 +0,0 @@
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"`
// whether the secret requires verification
SkipVerify bool `json:"skip_verify" meddler:"secret_skip_verify"`
// whether the secret should be concealed in the build log
Conceal bool `json:"conceal" meddler:"secret_conceal"`
}
// 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,
SkipVerify: s.SkipVerify,
Conceal: s.Conceal,
}
}
// 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,
SkipVerify: s.SkipVerify,
Conceal: s.Conceal,
}
}
// Validate validates the required fields and formats.
func (s *RepoSecret) Validate() error {
return nil
}

View file

@ -1,50 +1,48 @@
package model package model
import ( import (
"errors"
"path/filepath" "path/filepath"
) )
var (
errSecretNameInvalid = errors.New("Invalid Secret Name")
errSecretValueInvalid = errors.New("Invalid Secret Value")
)
// SecretService defines a service for managing secrets.
type SecretService interface {
SecretFind(*Repo, string) (*Secret, error)
SecretList(*Repo) ([]*Secret, error)
SecretCreate(*Repo, *Secret) error
SecretUpdate(*Repo, *Secret) error
SecretDelete(*Repo, string) error
}
// SecretStore persists secret information to storage.
type SecretStore interface {
SecretFind(*Repo, string) (*Secret, error)
SecretList(*Repo) ([]*Secret, error)
SecretCreate(*Secret) error
SecretUpdate(*Secret) error
SecretDelete(*Secret) error
}
// Secret represents a secret variable, such as a password or token.
// swagger:model registry
type Secret struct { type Secret struct {
// the name of the secret which will be used as the environment variable ID int64 `json:"id" meddler:"secret_id,pk"`
// name at runtime. RepoID int64 `json:"-" meddler:"secret_repo_id"`
Name string `json:"name"` Name string `json:"name" meddler:"secret_name"`
Value string `json:"value,omitempty" meddler:"secret_value"`
// the value of the secret which will be provided to the runtime environment Images []string `json:"image" meddler:"secret_images,json"`
// as a named environment variable. Events []string `json:"event" meddler:"secret_events,json"`
Value string `json:"value"` SkipVerify bool `json:"-" meddler:"secret_skip_verify"`
Conceal bool `json:"-" meddler:"secret_conceal"`
// the secret is restricted to this list of images.
Images []string `json:"image,omitempty"`
// the secret is restricted to this list of events.
Events []string `json:"event,omitempty"`
// whether the secret requires verification
SkipVerify bool `json:"skip_verify"`
// whether the secret should be concealed in the build log
Conceal bool `json:"conceal"`
} }
// Match returns true if an image and event match the restricted list. // Match returns true if an image and event match the restricted list.
func (s *Secret) Match(image, event string) bool { func (s *Secret) Match(event string) bool {
return s.MatchImage(image) && s.MatchEvent(event)
}
// MatchImage returns true if an image matches the restricted list.
func (s *Secret) MatchImage(image string) bool {
for _, pattern := range s.Images {
if match, _ := filepath.Match(pattern, image); match {
return true
} else if pattern == "*" {
return true
}
}
return false
}
// MatchEvent returns true if an event matches the restricted list.
func (s *Secret) MatchEvent(event string) bool {
for _, pattern := range s.Events { for _, pattern := range s.Events {
if match, _ := filepath.Match(pattern, event); match { if match, _ := filepath.Match(pattern, event); match {
return true return true
@ -55,5 +53,23 @@ func (s *Secret) MatchEvent(event string) bool {
// Validate validates the required fields and formats. // Validate validates the required fields and formats.
func (s *Secret) Validate() error { func (s *Secret) Validate() error {
return nil switch {
case len(s.Name) == 0:
return errSecretNameInvalid
case len(s.Value) == 0:
return errSecretValueInvalid
default:
return nil
}
}
// Copy makes a copy of the secret without the value.
func (s *Secret) Copy() *Secret {
return &Secret{
ID: s.ID,
RepoID: s.RepoID,
Name: s.Name,
Images: s.Images,
Events: s.Events,
}
} }

View file

@ -11,52 +11,15 @@ func TestSecret(t *testing.T) {
g := goblin.Goblin(t) g := goblin.Goblin(t)
g.Describe("Secret", func() { g.Describe("Secret", func() {
g.It("should match image", func() {
secret := Secret{}
secret.Images = []string{"golang"}
g.Assert(secret.MatchImage("golang")).IsTrue()
})
g.It("should match event", func() { g.It("should match event", func() {
secret := Secret{} secret := Secret{}
secret.Events = []string{"pull_request"} secret.Events = []string{"pull_request"}
g.Assert(secret.MatchEvent("pull_request")).IsTrue() g.Assert(secret.Match("pull_request")).IsTrue()
})
g.It("should match image patterns", func() {
secret := Secret{}
secret.Images = []string{"golang:*"}
g.Assert(secret.MatchImage("golang:1.4.2")).IsTrue()
})
g.It("should match any image", func() {
secret := Secret{}
secret.Images = []string{"*"}
g.Assert(secret.MatchImage("custom/golang")).IsTrue()
})
g.It("should match any event", func() {
secret := Secret{}
secret.Events = []string{"*"}
g.Assert(secret.MatchEvent("pull_request")).IsTrue()
})
g.It("should not match image", func() {
secret := Secret{}
secret.Images = []string{"golang"}
g.Assert(secret.MatchImage("node")).IsFalse()
})
g.It("should not match image substring", func() {
secret := Secret{}
secret.Images = []string{"golang"}
// image is only authorized for golang, not golang:1.4.2
g.Assert(secret.MatchImage("golang:1.4.2")).IsFalse()
})
g.It("should not match empty image", func() {
secret := Secret{}
secret.Images = []string{}
g.Assert(secret.MatchImage("node")).IsFalse()
}) })
g.It("should not match event", func() { g.It("should not match event", func() {
secret := Secret{} secret := Secret{}
secret.Events = []string{"pull_request"} secret.Events = []string{"pull_request"}
g.Assert(secret.MatchEvent("push")).IsFalse() g.Assert(secret.Match("push")).IsFalse()
}) })
g.It("should pass validation") g.It("should pass validation")
g.Describe("should fail validation", func() { g.Describe("should fail validation", func() {

28
model/sender.go Normal file
View file

@ -0,0 +1,28 @@
package model
type SenderService interface {
SenderAllowed(*User, *Repo, *Build) (bool, error)
SenderCreate(*Repo, *Sender) error
SenderUpdate(*Repo, *Sender) error
SenderDelete(*Repo, string) error
SenderList(*Repo) ([]*Sender, error)
}
type SenderStore interface {
SenderFind(*Repo, string) (*Sender, error)
SenderList(*Repo) ([]*Sender, error)
SenderCreate(*Sender) error
SenderUpdate(*Sender) error
SenderDelete(*Sender) error
}
type Sender struct {
ID int64 `json:"-" meddler:"sender_id,pk"`
RepoID int64 `json:"-" meddler:"sender_repo_id"`
Login string `json:"login" meddler:"sender_login"`
Allow bool `json:"allow" meddler:"sender_allow"`
Block bool `json:"block" meddler:"sender_block"`
Branch []string `json:"branch" meddler:"-"`
Deploy []string `json:"deploy" meddler:"-"`
Event []string `json:"event" meddler:"-"`
}

View file

@ -1,9 +0,0 @@
package model
type System struct {
Version string `json:"version"`
Link string `json:"link_url"`
Plugins []string `json:"plugins"`
Globals []string `json:"globals"`
Escalates []string `json:"privileged_plugins"`
}

View file

@ -1,58 +0,0 @@
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"`
// whether the secret requires verification
SkipVerify bool `json:"skip_verify" meddler:"team_secret_skip_verify"`
// whether the secret should be concealed in the build log
Conceal bool `json:"conceal" meddler:"team_secret_conceal"`
}
// 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,
SkipVerify: s.SkipVerify,
Conceal: s.Conceal,
}
}
// 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,
SkipVerify: s.SkipVerify,
Conceal: s.Conceal,
}
}
// Validate validates the required fields and formats.
func (s *TeamSecret) Validate() error {
return nil
}

61
plugins/internal/http.go Normal file
View file

@ -0,0 +1,61 @@
package internal
import (
"bytes"
"encoding/json"
"errors"
"io"
"io/ioutil"
"net/http"
"net/url"
)
// Send makes an http request to the given endpoint, writing the input
// to the request body and unmarshaling the output from the response body.
func Send(method, path string, in, out interface{}) error {
uri, err := url.Parse(path)
if err != nil {
return err
}
// if we are posting or putting data, we need to
// write it to the body of the request.
var buf io.ReadWriter
if in != nil {
buf = new(bytes.Buffer)
jsonerr := json.NewEncoder(buf).Encode(in)
if jsonerr != nil {
return jsonerr
}
}
// creates a new http request to bitbucket.
req, err := http.NewRequest(method, uri.String(), buf)
if err != nil {
return err
}
if in != nil {
req.Header.Set("Content-Type", "application/json")
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
// if an error is encountered, parse and return the
// error response.
if resp.StatusCode > http.StatusPartialContent {
out, _ := ioutil.ReadAll(resp.Body)
return errors.New(string(out))
}
// if a json response is expected, parse and return
// the json response.
if out != nil {
return json.NewDecoder(resp.Body).Decode(out)
}
return nil
}

View file

@ -0,0 +1,38 @@
package registry
import (
"github.com/drone/drone/model"
)
type builtin struct {
store model.RegistryStore
}
// New returns a new local registry service.
func New(store model.RegistryStore) model.RegistryService {
return &builtin{store}
}
func (b *builtin) RegistryFind(repo *model.Repo, name string) (*model.Registry, error) {
return b.store.RegistryFind(repo, name)
}
func (b *builtin) RegistryList(repo *model.Repo) ([]*model.Registry, error) {
return b.store.RegistryList(repo)
}
func (b *builtin) RegistryCreate(repo *model.Repo, in *model.Registry) error {
return b.store.RegistryCreate(in)
}
func (b *builtin) RegistryUpdate(repo *model.Repo, in *model.Registry) error {
return b.store.RegistryUpdate(in)
}
func (b *builtin) RegistryDelete(repo *model.Repo, addr string) error {
registry, err := b.RegistryFind(repo, addr)
if err != nil {
return err
}
return b.store.RegistryDelete(registry)
}

View file

@ -0,0 +1 @@
package registry

View file

@ -0,0 +1,46 @@
package registry
import (
"fmt"
"github.com/drone/drone/model"
"github.com/drone/drone/plugins/internal"
)
type plugin struct {
endpoint string
}
// NewRemote returns a new remote registry service.
func NewRemote(endpoint string) model.RegistryService {
return &plugin{endpoint}
}
func (p *plugin) RegistryFind(repo *model.Repo, name string) (*model.Registry, error) {
path := fmt.Sprintf("%s/registry/%s/%s/%s", p.endpoint, repo.Owner, repo.Name, name)
out := new(model.Registry)
err := internal.Send("GET", path, nil, out)
return out, err
}
func (p *plugin) RegistryList(repo *model.Repo) ([]*model.Registry, error) {
path := fmt.Sprintf("%s/registry/%s/%s", p.endpoint, repo.Owner, repo.Name)
out := []*model.Registry{}
err := internal.Send("GET", path, nil, out)
return out, err
}
func (p *plugin) RegistryCreate(repo *model.Repo, in *model.Registry) error {
path := fmt.Sprintf("%s/registry/%s/%s", p.endpoint, repo.Owner, repo.Name)
return internal.Send("PATCH", path, in, nil)
}
func (p *plugin) RegistryUpdate(repo *model.Repo, in *model.Registry) error {
path := fmt.Sprintf("%s/registry/%s/%s/%s", p.endpoint, repo.Owner, repo.Name, in.Address)
return internal.Send("PATCH", path, in, nil)
}
func (p *plugin) RegistryDelete(repo *model.Repo, name string) error {
path := fmt.Sprintf("%s/registry/%s/%s/%s", p.endpoint, repo.Owner, repo.Name, name)
return internal.Send("DELETE", path, nil, nil)
}

View file

@ -0,0 +1 @@
package registry

View file

@ -0,0 +1,38 @@
package secrets
import (
"github.com/drone/drone/model"
)
type builtin struct {
store model.SecretStore
}
// New returns a new local secret service.
func New(store model.SecretStore) model.SecretService {
return &builtin{store}
}
func (b *builtin) SecretFind(repo *model.Repo, name string) (*model.Secret, error) {
return b.store.SecretFind(repo, name)
}
func (b *builtin) SecretList(repo *model.Repo) ([]*model.Secret, error) {
return b.store.SecretList(repo)
}
func (b *builtin) SecretCreate(repo *model.Repo, in *model.Secret) error {
return b.store.SecretCreate(in)
}
func (b *builtin) SecretUpdate(repo *model.Repo, in *model.Secret) error {
return b.store.SecretUpdate(in)
}
func (b *builtin) SecretDelete(repo *model.Repo, name string) error {
secret, err := b.store.SecretFind(repo, name)
if err != nil {
return err
}
return b.store.SecretDelete(secret)
}

View file

@ -0,0 +1 @@
package secrets

46
plugins/secrets/plugin.go Normal file
View file

@ -0,0 +1,46 @@
package secrets
import (
"fmt"
"github.com/drone/drone/model"
"github.com/drone/drone/plugins/internal"
)
type plugin struct {
endpoint string
}
// NewRemote returns a new remote secret service.
func NewRemote(endpoint string) model.SecretService {
return &plugin{endpoint}
}
func (p *plugin) SecretFind(repo *model.Repo, name string) (*model.Secret, error) {
path := fmt.Sprintf("%s/secrets/%s/%s/%s", p.endpoint, repo.Owner, repo.Name, name)
out := new(model.Secret)
err := internal.Send("GET", path, nil, out)
return out, err
}
func (p *plugin) SecretList(repo *model.Repo) ([]*model.Secret, error) {
path := fmt.Sprintf("%s/secrets/%s/%s", p.endpoint, repo.Owner, repo.Name)
out := []*model.Secret{}
err := internal.Send("GET", path, nil, out)
return out, err
}
func (p *plugin) SecretCreate(repo *model.Repo, in *model.Secret) error {
path := fmt.Sprintf("%s/secrets/%s/%s", p.endpoint, repo.Owner, repo.Name)
return internal.Send("POST", path, in, nil)
}
func (p *plugin) SecretUpdate(repo *model.Repo, in *model.Secret) error {
path := fmt.Sprintf("%s/secrets/%s/%s/%s", p.endpoint, repo.Owner, repo.Name, in.Name)
return internal.Send("PATCH", path, in, nil)
}
func (p *plugin) SecretDelete(repo *model.Repo, name string) error {
path := fmt.Sprintf("%s/secrets/%s/%s/%s", p.endpoint, repo.Owner, repo.Name, name)
return internal.Send("DELETE", path, nil, nil)
}

View file

@ -0,0 +1 @@
package secrets

44
plugins/sender/builtin.go Normal file
View file

@ -0,0 +1,44 @@
package sender
import (
"github.com/drone/drone/model"
)
type builtin struct {
store model.SenderStore
}
// New returns a new local gating service.
func New(store model.SenderStore) model.SenderService {
return &builtin{store}
}
func (b *builtin) SenderAllowed(user *model.User, repo *model.Repo, build *model.Build) (bool, error) {
if repo.IsPrivate == false && build.Event == model.EventPull && build.Sender != user.Login {
sender, err := b.store.SenderFind(repo, build.Sender)
if err != nil || sender.Block {
return false, nil
}
}
return true, nil
}
func (b *builtin) SenderCreate(repo *model.Repo, sender *model.Sender) error {
return b.store.SenderCreate(sender)
}
func (b *builtin) SenderUpdate(repo *model.Repo, sender *model.Sender) error {
return b.store.SenderUpdate(sender)
}
func (b *builtin) SenderDelete(repo *model.Repo, login string) error {
sender, err := b.store.SenderFind(repo, login)
if err != nil {
return err
}
return b.store.SenderDelete(sender)
}
func (b *builtin) SenderList(repo *model.Repo) ([]*model.Sender, error) {
return b.store.SenderList(repo)
}

View file

@ -0,0 +1 @@
package sender

49
plugins/sender/plugin.go Normal file
View file

@ -0,0 +1,49 @@
package sender
import (
"fmt"
"github.com/drone/drone/model"
"github.com/drone/drone/plugins/internal"
)
type plugin struct {
endpoint string
}
// NewRemote returns a new remote gating service.
func NewRemote(endpoint string) model.SenderService {
return &plugin{endpoint}
}
func (p *plugin) SenderAllowed(user *model.User, repo *model.Repo, build *model.Build) (bool, error) {
path := fmt.Sprintf("%s/sender/%s/%s/%s", p.endpoint, repo.Owner, repo.Name, build.Sender)
out := new(model.Sender)
err := internal.Send("POST", path, build, out)
if err != nil {
return false, err
}
return out.Allow, nil
}
func (p *plugin) SenderCreate(repo *model.Repo, sender *model.Sender) error {
path := fmt.Sprintf("%s/sender/%s/%s", p.endpoint, repo.Owner, repo.Name)
return internal.Send("POST", path, sender, nil)
}
func (p *plugin) SenderUpdate(repo *model.Repo, sender *model.Sender) error {
path := fmt.Sprintf("%s/sender/%s/%s", p.endpoint, repo.Owner, repo.Name)
return internal.Send("PUT", path, sender, nil)
}
func (p *plugin) SenderDelete(repo *model.Repo, login string) error {
path := fmt.Sprintf("%s/sender/%s/%s/%s", p.endpoint, repo.Owner, repo.Name, login)
return internal.Send("DELETE", path, nil, nil)
}
func (p *plugin) SenderList(repo *model.Repo) ([]*model.Sender, error) {
path := fmt.Sprintf("%s/sender/%s/%s", p.endpoint, repo.Owner, repo.Name)
out := []*model.Sender{}
err := internal.Send("GET", path, nil, out)
return out, err
}

View file

@ -0,0 +1 @@
package sender

View file

@ -23,13 +23,13 @@ func setupConfig(c *cli.Context) *model.Config {
return &model.Config{ return &model.Config{
Open: c.Bool("open"), Open: c.Bool("open"),
Secret: c.String("agent-secret"), Secret: c.String("agent-secret"),
Admins: sliceToMap(c.StringSlice("admin")), Admins: sliceToMap2(c.StringSlice("admin")),
Orgs: sliceToMap(c.StringSlice("orgs")), Orgs: sliceToMap2(c.StringSlice("orgs")),
} }
} }
// helper function to convert a string slice to a map. // helper function to convert a string slice to a map.
func sliceToMap(s []string) map[string]bool { func sliceToMap2(s []string) map[string]bool {
v := map[string]bool{} v := map[string]bool{}
for _, ss := range s { for _, ss := range s {
v[ss] = true v[ss] = true

View file

@ -1,6 +1,15 @@
package middleware package middleware
import ( import (
"context"
"github.com/cncd/logging"
"github.com/cncd/pubsub"
"github.com/cncd/queue"
"github.com/drone/drone/plugins/registry"
"github.com/drone/drone/plugins/secrets"
"github.com/drone/drone/plugins/sender"
"github.com/drone/drone/server"
"github.com/drone/drone/store" "github.com/drone/drone/store"
"github.com/drone/drone/store/datastore" "github.com/drone/drone/store/datastore"
"github.com/urfave/cli" "github.com/urfave/cli"
@ -12,6 +21,40 @@ import (
// the context of every http.Request. // the context of every http.Request.
func Store(cli *cli.Context) gin.HandlerFunc { func Store(cli *cli.Context) gin.HandlerFunc {
v := setupStore(cli) v := setupStore(cli)
// HACK during refactor period. Please ignore my mess.
// storage
server.Config.Storage.Files = v
// services
server.Config.Services.Queue = queue.New()
server.Config.Services.Logs = logging.New()
server.Config.Services.Pubsub = pubsub.New()
server.Config.Services.Pubsub.Create(context.Background(), "topic/events")
server.Config.Services.Registries = registry.New(v)
server.Config.Services.Secrets = secrets.New(v)
server.Config.Services.Senders = sender.New(v)
if endpoint := cli.String("registry-service"); endpoint != "" {
server.Config.Services.Registries = registry.NewRemote(endpoint)
}
if endpoint := cli.String("secret-service"); endpoint != "" {
server.Config.Services.Secrets = secrets.NewRemote(endpoint)
}
if endpoint := cli.String("gating-service"); endpoint != "" {
server.Config.Services.Senders = sender.NewRemote(endpoint)
}
// server configuration
server.Config.Server.Cert = cli.String("server-cert")
server.Config.Server.Key = cli.String("server-key")
server.Config.Server.Pass = cli.String("agent-secret")
server.Config.Server.Host = cli.String("server-host")
server.Config.Server.Port = cli.String("server-addr")
// server.Config.Server.Open = cli.Bool("open")
// server.Config.Server.Orgs = sliceToMap(cli.StringSlice("orgs"))
// server.Config.Server.Admins = sliceToMap(cli.StringSlice("admin"))
return func(c *gin.Context) { return func(c *gin.Context) {
store.ToContext(c, v) store.ToContext(c, v)
c.Next() c.Next()
@ -25,3 +68,12 @@ func setupStore(c *cli.Context) store.Store {
c.String("datasource"), c.String("datasource"),
) )
} }
// helper function to convert a string slice to a map.
func sliceToMap(s []string) map[string]struct{} {
v := map[string]struct{}{}
for _, ss := range s {
v[ss] = struct{}{}
}
return v
}

View file

@ -70,27 +70,6 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
users.DELETE("/:login", server.DeleteUser) users.DELETE("/:login", server.DeleteUser)
} }
teams := e.Group("/api/teams")
{
teams.Use(session.MustTeamAdmin())
team := teams.Group("/:team")
{
team.GET("/secrets", server.GetTeamSecrets)
team.POST("/secrets", server.PostTeamSecret)
team.DELETE("/secrets/:secret", server.DeleteTeamSecret)
}
}
global := e.Group("/api/global")
{
global.Use(session.MustAdmin())
global.GET("/secrets", server.GetGlobalSecrets)
global.POST("/secrets", server.PostGlobalSecret)
global.DELETE("/secrets/:secret", server.DeleteGlobalSecret)
}
repos := e.Group("/api/repos/:owner/:name") repos := e.Group("/api/repos/:owner/:name")
{ {
repos.POST("", server.PostRepo) repos.POST("", server.PostRepo)
@ -107,8 +86,11 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
repo.GET("/logs/:number/:ppid/:proc", server.GetBuildLogs) repo.GET("/logs/:number/:ppid/:proc", server.GetBuildLogs)
repo.POST("/sign", session.MustPush, server.Sign) repo.POST("/sign", session.MustPush, server.Sign)
repo.GET("/secrets", session.MustPush, server.GetSecrets) // requires push permissions
repo.GET("/secrets", session.MustPush, server.GetSecretList)
repo.POST("/secrets", session.MustPush, server.PostSecret) repo.POST("/secrets", session.MustPush, server.PostSecret)
repo.GET("/secrets/:secret", session.MustPush, server.GetSecret)
repo.PATCH("/secrets/:secret", session.MustPush, server.PatchSecret)
repo.DELETE("/secrets/:secret", session.MustPush, server.DeleteSecret) repo.DELETE("/secrets/:secret", session.MustPush, server.DeleteSecret)
// requires push permissions // requires push permissions

View file

@ -138,7 +138,7 @@ func DeleteBuild(c *gin.Context) {
// TODO cancel child procs // TODO cancel child procs
store.FromContext(c).ProcUpdate(proc) store.FromContext(c).ProcUpdate(proc)
config.queue.Error(context.Background(), fmt.Sprint(proc.ID), queue.ErrCancel) Config.Services.Queue.Error(context.Background(), fmt.Sprint(proc.ID), queue.ErrCancel)
c.String(204, "") c.String(204, "")
} }
@ -197,11 +197,11 @@ func PostApproval(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.GetMergedSecretList(c, repo) secs, err := Config.Services.Secrets.SecretList(repo)
if err != nil { if err != nil {
logrus.Debugf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err) logrus.Debugf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err)
} }
regs, err := store.FromContext(c).RegistryList(repo) regs, err := Config.Services.Registries.RegistryList(repo)
if err != nil { if err != nil {
logrus.Debugf("Error getting registry credentials for %s#%d. %s", repo.FullName, build.Number, err) logrus.Debugf("Error getting registry credentials for %s#%d. %s", repo.FullName, build.Number, err)
} }
@ -277,7 +277,7 @@ func PostApproval(c *gin.Context) {
Build: buildCopy, Build: buildCopy,
}) })
// TODO remove global reference // TODO remove global reference
config.pubsub.Publish(c, "topic/events", message) Config.Services.Pubsub.Publish(c, "topic/events", message)
// //
// end publish topic // end publish topic
@ -298,8 +298,8 @@ func PostApproval(c *gin.Context) {
Timeout: b.Repo.Timeout, Timeout: b.Repo.Timeout,
}) })
config.logger.Open(context.Background(), task.ID) Config.Services.Logs.Open(context.Background(), task.ID)
config.queue.Push(context.Background(), task) Config.Services.Queue.Push(context.Background(), task)
} }
} }
@ -476,11 +476,11 @@ 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.GetMergedSecretList(c, repo) secs, err := Config.Services.Secrets.SecretList(repo)
if err != nil { if err != nil {
logrus.Debugf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err) logrus.Debugf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err)
} }
regs, err := store.FromContext(c).RegistryList(repo) regs, err := Config.Services.Registries.RegistryList(repo)
if err != nil { if err != nil {
logrus.Debugf("Error getting registry credentials for %s#%d. %s", repo.FullName, build.Number, err) logrus.Debugf("Error getting registry credentials for %s#%d. %s", repo.FullName, build.Number, err)
} }
@ -561,7 +561,7 @@ func PostBuild(c *gin.Context) {
Build: buildCopy, Build: buildCopy,
}) })
// TODO remove global reference // TODO remove global reference
config.pubsub.Publish(c, "topic/events", message) Config.Services.Pubsub.Publish(c, "topic/events", message)
// //
// end publish topic // end publish topic
// //
@ -581,7 +581,7 @@ func PostBuild(c *gin.Context) {
Timeout: b.Repo.Timeout, Timeout: b.Repo.Timeout,
}) })
config.logger.Open(context.Background(), task.ID) Config.Services.Logs.Open(context.Background(), task.ID)
config.queue.Push(context.Background(), task) Config.Services.Queue.Push(context.Background(), task)
} }
} }

View file

@ -40,7 +40,7 @@ var skipRe = regexp.MustCompile(`\[(?i:ci *skip|skip *ci)\]`)
func GetQueueInfo(c *gin.Context) { func GetQueueInfo(c *gin.Context) {
c.IndentedJSON(200, c.IndentedJSON(200,
config.queue.Info(c), Config.Services.Queue.Info(c),
) )
} }
@ -153,80 +153,30 @@ func PostHook(c *gin.Context) {
} }
} }
secs, err := store.GetMergedSecretList(c, repo) secs, err := Config.Services.Secrets.SecretList(repo)
if err != nil { if err != nil {
logrus.Debugf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err) logrus.Debugf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err)
} }
regs, err := store.FromContext(c).RegistryList(repo) regs, err := Config.Services.Registries.RegistryList(repo)
if err != nil { if err != nil {
logrus.Debugf("Error getting registry credentials for %s#%d. %s", repo.FullName, build.Number, err) logrus.Debugf("Error getting registry credentials for %s#%d. %s", repo.FullName, build.Number, err)
} }
// var mustApprove bool
// if build.Event == model.EventPull {
// for _, sec := range secs {
// if sec.SkipVerify {
// continue
// }
// if sec.MatchEvent(model.EventPull) {
// mustApprove = true
// break
// }
// }
// if !mustApprove {
// logrus.Debugf("no secrets exposed to pull_request: status: accepted")
// }
// }
// if build.Event == model.EventPull && mustApprove {
// old, ferr := remote_.FileRef(user, repo, build.Branch, repo.Config)
// if ferr != nil {
// build.Status = model.StatusBlocked
// logrus.Debugf("cannot fetch base yaml: status: blocked")
// } else if bytes.Equal(old, raw) {
// build.Status = model.StatusPending
// logrus.Debugf("base yaml matches head yaml: status: accepted")
// } else {
// // this block is executed if the target yaml file
// // does not match the base yaml.
//
// // TODO unfortunately we have no good way to get the
// // sender repository permissions unless the user is
// // a registered drone user.
// sender, uerr := store.GetUserLogin(c, build.Sender)
// if uerr != nil {
// build.Status = model.StatusBlocked
// logrus.Debugf("sender does not have a drone account: status: blocked")
// } else {
// if refresher, ok := remote_.(remote.Refresher); ok {
// ok, _ := refresher.Refresh(sender)
// if ok {
// store.UpdateUser(c, sender)
// }
// }
// // if the sender does not have push access to the
// // repository the pull request should be blocked.
// perm, perr := remote_.Perm(sender, repo.Owner, repo.Name)
// if perr == nil && perm.Push == true {
// build.Status = model.StatusPending
// logrus.Debugf("sender %s has push access: status: accepted", sender.Login)
// } else {
// build.Status = model.StatusBlocked
// logrus.Debugf("sender %s does not have push access: status: blocked", sender.Login)
// }
// }
// }
// } else {
// build.Status = model.StatusPending
// }
// update some build fields // update some build fields
build.RepoID = repo.ID build.RepoID = repo.ID
build.Verified = true build.Verified = true
build.Status = model.StatusPending build.Status = model.StatusPending
if err := store.CreateBuild(c, build, build.Procs...); err != nil { if repo.IsGated {
allowed, _ := Config.Services.Senders.SenderAllowed(user, repo, build)
if !allowed {
build.Status = model.StatusBlocked
}
}
err = store.CreateBuild(c, build, build.Procs...)
if err != nil {
logrus.Errorf("failure to save commit for %s. %s", repo.FullName, err) logrus.Errorf("failure to save commit for %s. %s", repo.FullName, err)
c.AbortWithError(500, err) c.AbortWithError(500, err)
return return
@ -234,9 +184,9 @@ func PostHook(c *gin.Context) {
c.JSON(200, build) c.JSON(200, build)
// if build.Status == model.StatusBlocked { if build.Status == model.StatusBlocked {
// return return
// } }
// 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
@ -321,7 +271,7 @@ func PostHook(c *gin.Context) {
Build: buildCopy, Build: buildCopy,
}) })
// TODO remove global reference // TODO remove global reference
config.pubsub.Publish(c, "topic/events", message) Config.Services.Pubsub.Publish(c, "topic/events", message)
// //
// end publish topic // end publish topic
// //
@ -341,8 +291,8 @@ func PostHook(c *gin.Context) {
Timeout: b.Repo.Timeout, Timeout: b.Repo.Timeout,
}) })
config.logger.Open(context.Background(), task.ID) Config.Services.Logs.Open(context.Background(), task.ID)
config.queue.Push(context.Background(), task) Config.Services.Queue.Push(context.Background(), task)
} }
} }
@ -460,7 +410,7 @@ func (b *builder) Build() ([]*buildItem, error) {
var secrets []compiler.Secret var secrets []compiler.Secret
for _, sec := range b.Secs { for _, sec := range b.Secs {
if !sec.MatchEvent(b.Curr.Event) { if !sec.Match(b.Curr.Event) {
continue continue
} }
secrets = append(secrets, compiler.Secret{ secrets = append(secrets, compiler.Secret{

View file

@ -5,7 +5,6 @@ import (
"github.com/drone/drone/model" "github.com/drone/drone/model"
"github.com/drone/drone/router/middleware/session" "github.com/drone/drone/router/middleware/session"
"github.com/drone/drone/store"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -17,7 +16,7 @@ func GetRegistry(c *gin.Context) {
repo = session.Repo(c) repo = session.Repo(c)
name = c.Param("registry") name = c.Param("registry")
) )
registry, err := store.FromContext(c).RegistryFind(repo, name) registry, err := Config.Services.Registries.RegistryFind(repo, name)
if err != nil { if err != nil {
c.String(404, "Error getting registry %q. %s", name, err) c.String(404, "Error getting registry %q. %s", name, err)
return return
@ -46,7 +45,7 @@ func PostRegistry(c *gin.Context) {
c.String(400, "Error inserting registry. %s", err) c.String(400, "Error inserting registry. %s", err)
return return
} }
if err := store.FromContext(c).RegistryCreate(registry); err != nil { if err := Config.Services.Registries.RegistryCreate(repo, registry); err != nil {
c.String(500, "Error inserting registry %q. %s", in.Address, err) c.String(500, "Error inserting registry %q. %s", in.Address, err)
return return
} }
@ -67,7 +66,7 @@ func PatchRegistry(c *gin.Context) {
return return
} }
registry, err := store.FromContext(c).RegistryFind(repo, name) registry, err := Config.Services.Registries.RegistryFind(repo, name)
if err != nil { if err != nil {
c.String(404, "Error getting registry %q. %s", name, err) c.String(404, "Error getting registry %q. %s", name, err)
return return
@ -89,7 +88,7 @@ func PatchRegistry(c *gin.Context) {
c.String(400, "Error updating registry. %s", err) c.String(400, "Error updating registry. %s", err)
return return
} }
if err := store.FromContext(c).RegistryUpdate(registry); err != nil { if err := Config.Services.Registries.RegistryUpdate(repo, registry); err != nil {
c.String(500, "Error updating registry %q. %s", in.Address, err) c.String(500, "Error updating registry %q. %s", in.Address, err)
return return
} }
@ -100,7 +99,7 @@ func PatchRegistry(c *gin.Context) {
// to the response in json format. // to the response in json format.
func GetRegistryList(c *gin.Context) { func GetRegistryList(c *gin.Context) {
repo := session.Repo(c) repo := session.Repo(c)
list, err := store.FromContext(c).RegistryList(repo) list, err := Config.Services.Registries.RegistryList(repo)
if err != nil { if err != nil {
c.String(500, "Error getting registry list. %s", err) c.String(500, "Error getting registry list. %s", err)
return return
@ -119,13 +118,7 @@ func DeleteRegistry(c *gin.Context) {
repo = session.Repo(c) repo = session.Repo(c)
name = c.Param("registry") name = c.Param("registry")
) )
registry, err := store.FromContext(c).RegistryFind(repo, name) if err := Config.Services.Registries.RegistryDelete(repo, name); err != nil {
if err != nil {
c.String(404, "Error getting registry %q. %s", name, err)
return
}
err = store.FromContext(c).RegistryDelete(registry)
if err != nil {
c.String(500, "Error deleting registry %q. %s", name, err) c.String(500, "Error deleting registry %q. %s", name, err)
return return
} }

View file

@ -98,6 +98,7 @@ func PatchRepo(c *gin.Context) {
in := &struct { in := &struct {
IsTrusted *bool `json:"trusted,omitempty"` IsTrusted *bool `json:"trusted,omitempty"`
IsGated *bool `json:"gated,omitempty"`
Timeout *int64 `json:"timeout,omitempty"` Timeout *int64 `json:"timeout,omitempty"`
AllowPull *bool `json:"allow_pr,omitempty"` AllowPull *bool `json:"allow_pr,omitempty"`
AllowPush *bool `json:"allow_push,omitempty"` AllowPush *bool `json:"allow_push,omitempty"`
@ -109,6 +110,11 @@ func PatchRepo(c *gin.Context) {
return return
} }
if (in.IsTrusted != nil || in.Timeout != nil) && !user.Admin {
c.String(403, "Insufficient privileges")
return
}
if in.AllowPush != nil { if in.AllowPush != nil {
repo.AllowPush = *in.AllowPush repo.AllowPush = *in.AllowPush
} }
@ -121,10 +127,13 @@ func PatchRepo(c *gin.Context) {
if in.AllowTag != nil { if in.AllowTag != nil {
repo.AllowTag = *in.AllowTag repo.AllowTag = *in.AllowTag
} }
if in.IsTrusted != nil && user.Admin { if in.IsGated != nil {
repo.IsGated = *in.IsGated
}
if in.IsTrusted != nil {
repo.IsTrusted = *in.IsTrusted repo.IsTrusted = *in.IsTrusted
} }
if in.Timeout != nil && user.Admin { if in.Timeout != nil {
repo.Timeout = *in.Timeout repo.Timeout = *in.Timeout
} }

View file

@ -5,7 +5,6 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"log" "log"
"os"
"strconv" "strconv"
"github.com/cncd/logging" "github.com/cncd/logging"
@ -22,23 +21,57 @@ import (
// This file is a complete disaster because I'm trying to wedge in some // This file is a complete disaster because I'm trying to wedge in some
// experimental code. Please pardon our appearance during renovations. // experimental code. Please pardon our appearance during renovations.
var config = struct { // Config is an evil global configuration that will be used as we transition /
pubsub pubsub.Publisher // refactor the codebase to move away from storing these values in the Context.
queue queue.Queue var Config = struct {
logger logging.Log Services struct {
secret string Pubsub pubsub.Publisher
host string Queue queue.Queue
}{ Logs logging.Log
pubsub.New(), Senders model.SenderService
queue.New(), Secrets model.SecretService
logging.New(), Registries model.RegistryService
os.Getenv("DRONE_SECRET"), }
os.Getenv("DRONE_HOST"), Storage struct {
} // Users model.UserStore
// Repos model.RepoStore
// Builds model.BuildStore
// Logs model.LogStore
Files model.FileStore
Procs model.ProcStore
// Registries model.RegistryStore
// Secrets model.SecretStore
}
Server struct {
Key string
Cert string
Host string
Port string
Pass string
// Open bool
// Orgs map[string]struct{}
// Admins map[string]struct{}
}
Pipeline struct {
Volumes []string
Networks []string
Privileged []string
}
}{}
func init() { // var config = struct {
config.pubsub.Create(context.Background(), "topic/events") // pubsub pubsub.Publisher
} // queue queue.Queue
// logger logging.Log
// secret string
// host string
// }{
// pubsub.New(),
// queue.New(),
// logging.New(),
// os.Getenv("DRONE_SECRET"),
// os.Getenv("DRONE_HOST"),
// }
// func SetupRPC() gin.HandlerFunc { // func SetupRPC() gin.HandlerFunc {
// return func(c *gin.Context) { // return func(c *gin.Context) {
@ -48,18 +81,18 @@ func init() {
func RPCHandler(c *gin.Context) { func RPCHandler(c *gin.Context) {
if secret := c.Request.Header.Get("Authorization"); secret != "Bearer "+config.secret { if secret := c.Request.Header.Get("Authorization"); secret != "Bearer "+Config.Server.Pass {
log.Printf("Unable to connect agent. Invalid authorization token %q does not match %q", secret, config.secret) log.Printf("Unable to connect agent. Invalid authorization token %q does not match %q", secret, Config.Server.Pass)
c.String(401, "Unable to connect agent. Invalid authorization token") c.String(401, "Unable to connect agent. Invalid authorization token")
return return
} }
peer := RPC{ peer := RPC{
remote: remote.FromContext(c), remote: remote.FromContext(c),
store: store.FromContext(c), store: store.FromContext(c),
queue: config.queue, queue: Config.Services.Queue,
pubsub: config.pubsub, pubsub: Config.Services.Pubsub,
logger: config.logger, logger: Config.Services.Logs,
host: config.host, host: Config.Server.Host,
} }
rpc.NewServer(&peer).ServeHTTP(c.Writer, c.Request) rpc.NewServer(&peer).ServeHTTP(c.Writer, c.Request)
} }
@ -201,7 +234,7 @@ func (s *RPC) Upload(c context.Context, id string, file *rpc.File) error {
) )
} }
return s.store.FileCreate(&model.File{ return Config.Storage.Files.FileCreate(&model.File{
BuildID: proc.BuildID, BuildID: proc.BuildID,
ProcID: proc.ID, ProcID: proc.ID,
Mime: file.Mime, Mime: file.Mime,

View file

@ -5,173 +5,118 @@ import (
"github.com/drone/drone/model" "github.com/drone/drone/model"
"github.com/drone/drone/router/middleware/session" "github.com/drone/drone/router/middleware/session"
"github.com/drone/drone/store"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
func GetGlobalSecrets(c *gin.Context) { // GetSecret gets the named secret from the database and writes
secrets, err := store.GetGlobalSecretList(c) // to the response in json format.
func GetSecret(c *gin.Context) {
var (
repo = session.Repo(c)
name = c.Param("secret")
)
secret, err := Config.Services.Secrets.SecretFind(repo, name)
if err != nil { if err != nil {
c.AbortWithStatus(http.StatusInternalServerError) c.String(404, "Error getting secret %q. %s", name, err)
return return
} }
c.JSON(200, secret.Copy())
var list []*model.TeamSecret
for _, s := range secrets {
list = append(list, s.Clone())
}
c.JSON(http.StatusOK, list)
}
func PostGlobalSecret(c *gin.Context) {
in := &model.TeamSecret{}
err := c.Bind(in)
if err != nil {
c.String(http.StatusBadRequest, "Invalid JSON input. %s", err.Error())
return
}
in.ID = 0
err = store.SetGlobalSecret(c, in)
if err != nil {
c.String(http.StatusInternalServerError, "Unable to persist global secret. %s", err.Error())
return
}
c.String(http.StatusOK, "")
}
func DeleteGlobalSecret(c *gin.Context) {
name := c.Param("secret")
secret, err := store.GetGlobalSecret(c, name)
if err != nil {
c.String(http.StatusNotFound, "Cannot find secret %s.", name)
return
}
err = store.DeleteGlobalSecret(c, secret)
if err != nil {
c.String(http.StatusInternalServerError, "Unable to delete global secret. %s", err.Error())
return
}
c.String(http.StatusOK, "")
}
func GetSecrets(c *gin.Context) {
repo := session.Repo(c)
secrets, err := store.GetSecretList(c, repo)
if err != nil {
c.AbortWithStatus(http.StatusInternalServerError)
return
}
var list []*model.RepoSecret
for _, s := range secrets {
list = append(list, s.Clone())
}
c.JSON(http.StatusOK, list)
} }
// PostSecret persists the secret to the database.
func PostSecret(c *gin.Context) { func PostSecret(c *gin.Context) {
repo := session.Repo(c) repo := session.Repo(c)
in := &model.RepoSecret{} in := new(model.Secret)
err := c.Bind(in) if err := c.Bind(in); err != nil {
if err != nil { c.String(http.StatusBadRequest, "Error parsing secret. %s", err)
c.String(http.StatusBadRequest, "Invalid JSON input. %s", err.Error())
return return
} }
in.ID = 0 secret := &model.Secret{
in.RepoID = repo.ID RepoID: repo.ID,
Name: in.Name,
err = store.SetSecret(c, in) Value: in.Value,
if err != nil { Events: in.Events,
c.String(http.StatusInternalServerError, "Unable to persist secret. %s", err.Error()) Images: in.Images,
}
if err := secret.Validate(); err != nil {
c.String(400, "Error inserting secret. %s", err)
return return
} }
if err := Config.Services.Secrets.SecretCreate(repo, secret); err != nil {
c.String(http.StatusOK, "") c.String(500, "Error inserting secret %q. %s", in.Name, err)
return
}
c.JSON(200, secret.Copy())
} }
func DeleteSecret(c *gin.Context) { // PatchSecret updates the secret in the database.
func PatchSecret(c *gin.Context) {
var (
repo = session.Repo(c)
name = c.Param("secret")
)
in := new(model.Secret)
err := c.Bind(in)
if err != nil {
c.String(http.StatusBadRequest, "Error parsing secret. %s", err)
return
}
secret, err := Config.Services.Secrets.SecretFind(repo, name)
if err != nil {
c.String(404, "Error getting secret %q. %s", name, err)
return
}
if in.Value != "" {
secret.Value = in.Value
}
if len(in.Events) != 0 {
secret.Events = in.Events
}
if len(in.Images) != 0 {
secret.Images = in.Images
}
if err := secret.Validate(); err != nil {
c.String(400, "Error updating secret. %s", err)
return
}
if err := Config.Services.Secrets.SecretUpdate(repo, secret); err != nil {
c.String(500, "Error updating secret %q. %s", in.Name, err)
return
}
c.JSON(200, secret.Copy())
}
// GetSecretList gets the secret list from the database and writes
// to the response in json format.
func GetSecretList(c *gin.Context) {
repo := session.Repo(c) repo := session.Repo(c)
name := c.Param("secret") list, err := Config.Services.Secrets.SecretList(repo)
secret, err := store.GetSecret(c, repo, name)
if err != nil { if err != nil {
c.String(http.StatusNotFound, "Cannot find secret %s.", name) c.String(500, "Error getting secret list. %s", err)
return return
} }
err = store.DeleteSecret(c, secret) // copy the secret detail to remove the sensitive
if err != nil { // password and token fields.
c.String(http.StatusInternalServerError, "Unable to delete secret. %s", err.Error()) for i, secret := range list {
return list[i] = secret.Copy()
} }
c.JSON(200, list)
c.String(http.StatusOK, "")
} }
func GetTeamSecrets(c *gin.Context) { // DeleteSecret deletes the named secret from the database.
team := c.Param("team") func DeleteSecret(c *gin.Context) {
secrets, err := store.GetTeamSecretList(c, team) var (
repo = session.Repo(c)
if err != nil { name = c.Param("secret")
c.AbortWithStatus(http.StatusInternalServerError) )
if err := Config.Services.Secrets.SecretDelete(repo, name); err != nil {
c.String(500, "Error deleting secret %q. %s", name, err)
return return
} }
c.String(204, "")
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

@ -102,7 +102,7 @@ func LogStream(c *gin.Context) {
go func() { go func() {
// TODO remove global variable // TODO remove global variable
config.logger.Tail(ctx, fmt.Sprint(proc.ID), func(entries ...*logging.Entry) { Config.Services.Logs.Tail(ctx, fmt.Sprint(proc.ID), func(entries ...*logging.Entry) {
for _, entry := range entries { for _, entry := range entries {
select { select {
case <-ctx.Done(): case <-ctx.Done():
@ -167,7 +167,7 @@ func EventStream(c *gin.Context) {
go func() { go func() {
// TODO remove this from global config // TODO remove this from global config
config.pubsub.Subscribe(c, "topic/events", func(m pubsub.Message) { Config.Services.Pubsub.Subscribe(c, "topic/events", func(m pubsub.Message) {
name := m.Labels["repo"] name := m.Labels["repo"]
priv := m.Labels["private"] priv := m.Labels["private"]
if repo[name] || priv == "false" { if repo[name] || priv == "false" {

View file

@ -0,0 +1,22 @@
-- +migrate Up
ALTER TABLE repos ADD COLUMN repo_gated BOOLEAN;
UPDATE repos SET repo_gated = false;
CREATE TABLE senders (
sender_id INTEGER PRIMARY KEY AUTO_INCREMENT
,sender_repo_id INTEGER
,sender_login VARCHAR(250)
,sender_allow BOOLEAN
,sender_block BOOLEAN
,UNIQUE(sender_repo_id,sender_login)
);
CREATE INDEX sender_repo_ix ON senders (sender_repo_id);
-- +migrate Down
ALTER TABLE repos DROP COLUMN repo_gated;
DROP INDEX sender_repo_ix;
DROP TABLE senders;

View file

@ -0,0 +1,22 @@
-- +migrate Up
ALTER TABLE repos ADD COLUMN repo_gated BOOLEAN;
UPDATE repos SET repo_gated = false;
CREATE TABLE senders (
sender_id SERIAL PRIMARY KEY
,sender_repo_id INTEGER
,sender_login VARCHAR(250)
,sender_allow BOOLEAN
,sender_block BOOLEAN
,UNIQUE(sender_repo_id,sender_login)
);
CREATE INDEX sender_repo_ix ON senders (sender_repo_id);
-- +migrate Down
ALTER TABLE repos DROP COLUMN repo_gated;
DROP INDEX sender_repo_ix;
DROP TABLE senders;

View file

@ -0,0 +1,22 @@
-- +migrate Up
ALTER TABLE repos ADD COLUMN repo_gated BOOLEAN;
UPDATE repos SET repo_gated = 0;
CREATE TABLE senders (
sender_id INTEGER PRIMARY KEY AUTOINCREMENT
,sender_repo_id INTEGER
,sender_login BOOLEAN
,sender_allow BOOLEAN
,sender_block BOOLEAN
,UNIQUE(sender_repo_id,sender_login)
);
CREATE INDEX sender_repo_ix ON senders (sender_repo_id);
-- +migrate Down
ALTER TABLE repos DROP COLUMN repo_gated;
DROP INDEX sender_repo_ix;
DROP TABLE senders;

View file

@ -1,53 +0,0 @@
package datastore
import (
"github.com/drone/drone/model"
"github.com/russross/meddler"
)
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.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.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
}
return meddler.Save(db, secretTable, sec)
}
func (db *datastore) DeleteSecret(sec *model.RepoSecret) error {
_, err := db.Exec(rebind(secretDeleteStmt), sec.ID)
return err
}
const secretTable = "secrets"
const secretListQuery = `
SELECT *
FROM secrets
WHERE secret_repo_id = ?
`
const secretNameQuery = `
SELECT *
FROM secrets
WHERE secret_repo_id = ?
AND secret_name = ?
LIMIT 1;
`
const secretDeleteStmt = `
DELETE FROM secrets
WHERE secret_id = ?
`

View file

@ -1,98 +0,0 @@
package datastore
import (
"testing"
"github.com/drone/drone/model"
"github.com/franela/goblin"
)
func TestRepoSecrets(t *testing.T) {
db := openTest()
defer db.Close()
s := From(db)
g := goblin.Goblin(t)
g.Describe("RepoSecrets", func() {
// before each test be sure to purge the package
// table data from the database.
g.BeforeEach(func() {
db.Exec(rebind("DELETE FROM secrets"))
})
g.It("Should set and get a secret", func() {
secret := &model.RepoSecret{
RepoID: 1,
Name: "foo",
Value: "bar",
Images: []string{"docker", "gcr"},
Events: []string{"push", "tag"},
SkipVerify: true,
Conceal: true,
}
err := s.SetSecret(secret)
g.Assert(err == nil).IsTrue()
g.Assert(secret.ID != 0).IsTrue()
got, err := s.GetSecret(&model.Repo{ID: 1}, 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.Assert(got.SkipVerify).Equal(secret.SkipVerify)
g.Assert(got.Conceal).Equal(secret.Conceal)
})
g.It("Should update a secret", func() {
secret := &model.RepoSecret{
RepoID: 1,
Name: "foo",
Value: "bar",
}
s.SetSecret(secret)
secret.Value = "baz"
s.SetSecret(secret)
got, err := s.GetSecret(&model.Repo{ID: 1}, 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.SetSecret(&model.RepoSecret{
RepoID: 1,
Name: "foo",
Value: "bar",
})
s.SetSecret(&model.RepoSecret{
RepoID: 1,
Name: "bar",
Value: "baz",
})
secrets, err := s.GetSecretList(&model.Repo{ID: 1})
g.Assert(err == nil).IsTrue()
g.Assert(len(secrets)).Equal(2)
})
g.It("Should delete a secret", func() {
secret := &model.RepoSecret{
RepoID: 1,
Name: "foo",
Value: "bar",
}
s.SetSecret(secret)
_, err := s.GetSecret(&model.Repo{ID: 1}, secret.Name)
g.Assert(err == nil).IsTrue()
err = s.DeleteSecret(secret)
g.Assert(err == nil).IsTrue()
_, err = s.GetSecret(&model.Repo{ID: 1}, secret.Name)
g.Assert(err != nil).IsTrue("expect a no rows in result set error")
})
})
}

35
store/datastore/secret.go Normal file
View file

@ -0,0 +1,35 @@
package datastore
import (
"github.com/drone/drone/model"
"github.com/drone/drone/store/datastore/sql"
"github.com/russross/meddler"
)
func (db *datastore) SecretFind(repo *model.Repo, name string) (*model.Secret, error) {
stmt := sql.Lookup(db.driver, "secret-find-repo-name")
data := new(model.Secret)
err := meddler.QueryRow(db, data, stmt, repo.ID, name)
return data, err
}
func (db *datastore) SecretList(repo *model.Repo) ([]*model.Secret, error) {
stmt := sql.Lookup(db.driver, "secret-find-repo")
data := []*model.Secret{}
err := meddler.QueryAll(db, &data, stmt, repo.ID)
return data, err
}
func (db *datastore) SecretCreate(secret *model.Secret) error {
return meddler.Insert(db, "secrets", secret)
}
func (db *datastore) SecretUpdate(secret *model.Secret) error {
return meddler.Update(db, "secrets", secret)
}
func (db *datastore) SecretDelete(secret *model.Secret) error {
stmt := sql.Lookup(db.driver, "secret-delete")
_, err := db.Exec(stmt, secret.ID)
return err
}

View file

@ -0,0 +1,139 @@
package datastore
import (
"testing"
"github.com/drone/drone/model"
)
func TestSecretFind(t *testing.T) {
s := newTest()
defer func() {
s.Exec("delete from secrets")
s.Close()
}()
err := s.SecretCreate(&model.Secret{
RepoID: 1,
Name: "password",
Value: "correct-horse-battery-staple",
Images: []string{"golang", "node"},
Events: []string{"push", "tag"},
})
if err != nil {
t.Errorf("Unexpected error: insert secret: %s", err)
return
}
secret, err := s.SecretFind(&model.Repo{ID: 1}, "password")
if err != nil {
t.Error(err)
return
}
if got, want := secret.RepoID, int64(1); got != want {
t.Errorf("Want repo id %d, got %d", want, got)
}
if got, want := secret.Name, "password"; got != want {
t.Errorf("Want secret name %s, got %s", want, got)
}
if got, want := secret.Value, "correct-horse-battery-staple"; got != want {
t.Errorf("Want secret value %s, got %s", want, got)
}
if got, want := secret.Events[0], "push"; got != want {
t.Errorf("Want secret event %s, got %s", want, got)
}
if got, want := secret.Events[1], "tag"; got != want {
t.Errorf("Want secret event %s, got %s", want, got)
}
if got, want := secret.Images[0], "golang"; got != want {
t.Errorf("Want secret image %s, got %s", want, got)
}
if got, want := secret.Images[1], "node"; got != want {
t.Errorf("Want secret image %s, got %s", want, got)
}
}
func TestSecretList(t *testing.T) {
s := newTest()
defer func() {
s.Exec("delete from secrets")
s.Close()
}()
s.SecretCreate(&model.Secret{
RepoID: 1,
Name: "foo",
Value: "bar",
})
s.SecretCreate(&model.Secret{
RepoID: 1,
Name: "baz",
Value: "qux",
})
list, err := s.SecretList(&model.Repo{ID: 1})
if err != nil {
t.Error(err)
return
}
if got, want := len(list), 2; got != want {
t.Errorf("Want %d registries, got %d", want, got)
}
}
func TestSecretUpdate(t *testing.T) {
s := newTest()
defer func() {
s.Exec("delete from secrets")
s.Close()
}()
secret := &model.Secret{
RepoID: 1,
Name: "foo",
Value: "baz",
}
if err := s.SecretCreate(secret); err != nil {
t.Errorf("Unexpected error: insert secret: %s", err)
return
}
secret.Value = "qux"
if err := s.SecretUpdate(secret); err != nil {
t.Errorf("Unexpected error: update secret: %s", err)
return
}
updated, err := s.SecretFind(&model.Repo{ID: 1}, "foo")
if err != nil {
t.Error(err)
return
}
if got, want := updated.Value, "qux"; got != want {
t.Errorf("Want secret value %s, got %s", want, got)
}
}
func TestSecretIndexes(t *testing.T) {
s := newTest()
defer func() {
s.Exec("delete from secrets")
s.Close()
}()
if err := s.SecretCreate(&model.Secret{
RepoID: 1,
Name: "foo",
Value: "bar",
}); err != nil {
t.Errorf("Unexpected error: insert secret: %s", err)
return
}
// fail due to duplicate name
if err := s.SecretCreate(&model.Secret{
RepoID: 1,
Name: "foo",
Value: "baz",
}); err == nil {
t.Errorf("Unexpected error: dupliate name")
}
}

35
store/datastore/sender.go Normal file
View file

@ -0,0 +1,35 @@
package datastore
import (
"github.com/drone/drone/model"
"github.com/drone/drone/store/datastore/sql"
"github.com/russross/meddler"
)
func (db *datastore) SenderFind(repo *model.Repo, login string) (*model.Sender, error) {
stmt := sql.Lookup(db.driver, "sender-find-repo-login")
data := new(model.Sender)
err := meddler.QueryRow(db, data, stmt, repo.ID, login)
return data, err
}
func (db *datastore) SenderList(repo *model.Repo) ([]*model.Sender, error) {
stmt := sql.Lookup(db.driver, "sender-find-repo")
data := []*model.Sender{}
err := meddler.QueryAll(db, &data, stmt, repo.ID)
return data, err
}
func (db *datastore) SenderCreate(sender *model.Sender) error {
return meddler.Insert(db, "senders", sender)
}
func (db *datastore) SenderUpdate(sender *model.Sender) error {
return meddler.Update(db, "senders", sender)
}
func (db *datastore) SenderDelete(sender *model.Sender) error {
stmt := sql.Lookup(db.driver, "sender-delete")
_, err := db.Exec(stmt, sender.ID)
return err
}

View file

@ -0,0 +1,131 @@
package datastore
import (
"testing"
"github.com/drone/drone/model"
)
func TestSenderFind(t *testing.T) {
s := newTest()
defer func() {
s.Exec("delete from senders")
s.Close()
}()
err := s.SenderCreate(&model.Sender{
RepoID: 1,
Login: "octocat",
Allow: true,
Block: false,
})
if err != nil {
t.Errorf("Unexpected error: insert secret: %s", err)
return
}
sender, err := s.SenderFind(&model.Repo{ID: 1}, "octocat")
if err != nil {
t.Error(err)
return
}
if got, want := sender.RepoID, int64(1); got != want {
t.Errorf("Want repo id %d, got %d", want, got)
}
if got, want := sender.Login, "octocat"; got != want {
t.Errorf("Want sender login %s, got %s", want, got)
}
if got, want := sender.Allow, true; got != want {
t.Errorf("Want sender allow %v, got %v", want, got)
}
}
func TestSenderList(t *testing.T) {
s := newTest()
defer func() {
s.Exec("delete from senders")
s.Close()
}()
s.SenderCreate(&model.Sender{
RepoID: 1,
Login: "octocat",
Allow: true,
Block: false,
})
s.SenderCreate(&model.Sender{
RepoID: 1,
Login: "defunkt",
Allow: true,
Block: false,
})
list, err := s.SenderList(&model.Repo{ID: 1})
if err != nil {
t.Error(err)
return
}
if got, want := len(list), 2; got != want {
t.Errorf("Want %d senders, got %d", want, got)
}
}
func TestSenderUpdate(t *testing.T) {
s := newTest()
defer func() {
s.Exec("delete from senders")
s.Close()
}()
sender := &model.Sender{
RepoID: 1,
Login: "octocat",
Allow: true,
Block: false,
}
if err := s.SenderCreate(sender); err != nil {
t.Errorf("Unexpected error: insert sender: %s", err)
return
}
sender.Allow = false
if err := s.SenderUpdate(sender); err != nil {
t.Errorf("Unexpected error: update sender: %s", err)
return
}
updated, err := s.SenderFind(&model.Repo{ID: 1}, "octocat")
if err != nil {
t.Error(err)
return
}
if got, want := updated.Allow, false; got != want {
t.Errorf("Want allow value %v, got %v", want, got)
}
}
func TestSenderIndexes(t *testing.T) {
s := newTest()
defer func() {
s.Exec("delete from senders")
s.Close()
}()
if err := s.SenderCreate(&model.Sender{
RepoID: 1,
Login: "octocat",
Allow: true,
Block: false,
}); err != nil {
t.Errorf("Unexpected error: insert sender: %s", err)
return
}
// fail due to duplicate name
if err := s.SenderCreate(&model.Sender{
RepoID: 1,
Login: "octocat",
Allow: true,
Block: false,
}); err == nil {
t.Errorf("Unexpected error: dupliate login")
}
}

View file

@ -0,0 +1,32 @@
-- name: secret-find-repo
SELECT
secret_id
,secret_repo_id
,secret_name
,secret_value
,secret_images
,secret_events
,secret_conceal
,secret_skip_verify
FROM secrets
WHERE secret_repo_id = $1
-- name: secret-find-repo-name
SELECT
secret_id
,secret_repo_id
,secret_name
,secret_value
,secret_images
,secret_events
,secret_conceal
,secret_skip_verify
FROM secrets
WHERE secret_repo_id = $1
AND secret_name = $2
-- name: secret-delete
DELETE FROM secrets WHERE secret_id = $1

View file

@ -0,0 +1,30 @@
-- name: sender-find-repo
SELECT
sender_id
,sender_repo_id
,sender_login
,sender_allow
,sender_block
FROM senders
WHERE sender_repo_id = $1
-- name: sender-find-repo-login
SELECT
sender_id
,sender_repo_id
,sender_login
,sender_allow
,sender_block
FROM senders
WHERE sender_repo_id = $1
AND sender_login = $2
-- name: sender-delete-repo
DELETE FROM senders WHERE sender_repo_id = $1
-- name: sender-delete
DELETE FROM senders WHERE sender_id = $1

View file

@ -19,6 +19,13 @@ var index = map[string]string{
"registry-find-repo-addr": registryFindRepoAddr, "registry-find-repo-addr": registryFindRepoAddr,
"registry-delete-repo": registryDeleteRepo, "registry-delete-repo": registryDeleteRepo,
"registry-delete": registryDelete, "registry-delete": registryDelete,
"secret-find-repo": secretFindRepo,
"secret-find-repo-name": secretFindRepoName,
"secret-delete": secretDelete,
"sender-find-repo": senderFindRepo,
"sender-find-repo-login": senderFindRepoLogin,
"sender-delete-repo": senderDeleteRepo,
"sender-delete": senderDelete,
} }
var filesFindBuild = ` var filesFindBuild = `
@ -188,3 +195,67 @@ DELETE FROM registry WHERE registry_repo_id = $1
var registryDelete = ` var registryDelete = `
DELETE FROM registry WHERE registry_id = $1 DELETE FROM registry WHERE registry_id = $1
` `
var secretFindRepo = `
SELECT
secret_id
,secret_repo_id
,secret_name
,secret_value
,secret_images
,secret_events
,secret_conceal
,secret_skip_verify
FROM secrets
WHERE secret_repo_id = $1
`
var secretFindRepoName = `
SELECT
secret_id
,secret_repo_id
,secret_name
,secret_value
,secret_images
,secret_events
,secret_conceal
,secret_skip_verify
FROM secrets
WHERE secret_repo_id = $1
AND secret_name = $2
`
var secretDelete = `
DELETE FROM secrets WHERE secret_id = $1
`
var senderFindRepo = `
SELECT
sender_id
,sender_repo_id
,sender_login
,sender_allow
,sender_block
FROM senders
WHERE sender_repo_id = $1
`
var senderFindRepoLogin = `
SELECT
sender_id
,sender_repo_id
,sender_login
,sender_allow
,sender_block
FROM senders
WHERE sender_repo_id = $1
AND sender_login = $2
`
var senderDeleteRepo = `
DELETE FROM senders WHERE sender_repo_id = $1
`
var senderDelete = `
DELETE FROM senders WHERE sender_id = $1
`

View file

@ -0,0 +1,32 @@
-- name: secret-find-repo
SELECT
secret_id
,secret_repo_id
,secret_name
,secret_value
,secret_images
,secret_events
,secret_conceal
,secret_skip_verify
FROM secrets
WHERE secret_repo_id = ?
-- name: secret-find-repo-name
SELECT
secret_id
,secret_repo_id
,secret_name
,secret_value
,secret_images
,secret_events
,secret_conceal
,secret_skip_verify
FROM secrets
WHERE secret_repo_id = ?
AND secret_name = ?
-- name: secret-delete
DELETE FROM secrets WHERE secret_id = ?

View file

@ -0,0 +1,30 @@
-- name: sender-find-repo
SELECT
sender_id
,sender_repo_id
,sender_login
,sender_allow
,sender_block
FROM senders
WHERE sender_repo_id = ?
-- name: sender-find-repo-login
SELECT
sender_id
,sender_repo_id
,sender_login
,sender_allow
,sender_block
FROM senders
WHERE sender_repo_id = ?
AND sender_login = ?
-- name: sender-delete-repo
DELETE FROM senders WHERE sender_repo_id = ?
-- name: sender-delete
DELETE FROM senders WHERE sender_id = ?

View file

@ -19,6 +19,13 @@ var index = map[string]string{
"registry-find-repo-addr": registryFindRepoAddr, "registry-find-repo-addr": registryFindRepoAddr,
"registry-delete-repo": registryDeleteRepo, "registry-delete-repo": registryDeleteRepo,
"registry-delete": registryDelete, "registry-delete": registryDelete,
"secret-find-repo": secretFindRepo,
"secret-find-repo-name": secretFindRepoName,
"secret-delete": secretDelete,
"sender-find-repo": senderFindRepo,
"sender-find-repo-login": senderFindRepoLogin,
"sender-delete-repo": senderDeleteRepo,
"sender-delete": senderDelete,
} }
var filesFindBuild = ` var filesFindBuild = `
@ -188,3 +195,67 @@ DELETE FROM registry WHERE registry_repo_id = ?
var registryDelete = ` var registryDelete = `
DELETE FROM registry WHERE registry_id = ? DELETE FROM registry WHERE registry_id = ?
` `
var secretFindRepo = `
SELECT
secret_id
,secret_repo_id
,secret_name
,secret_value
,secret_images
,secret_events
,secret_conceal
,secret_skip_verify
FROM secrets
WHERE secret_repo_id = ?
`
var secretFindRepoName = `
SELECT
secret_id
,secret_repo_id
,secret_name
,secret_value
,secret_images
,secret_events
,secret_conceal
,secret_skip_verify
FROM secrets
WHERE secret_repo_id = ?
AND secret_name = ?
`
var secretDelete = `
DELETE FROM secrets WHERE secret_id = ?
`
var senderFindRepo = `
SELECT
sender_id
,sender_repo_id
,sender_login
,sender_allow
,sender_block
FROM senders
WHERE sender_repo_id = ?
`
var senderFindRepoLogin = `
SELECT
sender_id
,sender_repo_id
,sender_login
,sender_allow
,sender_block
FROM senders
WHERE sender_repo_id = ?
AND sender_login = ?
`
var senderDeleteRepo = `
DELETE FROM senders WHERE sender_repo_id = ?
`
var senderDelete = `
DELETE FROM senders WHERE sender_id = ?
`

View file

@ -1,53 +0,0 @@
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

@ -1,98 +0,0 @@
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"},
SkipVerify: true,
Conceal: true,
}
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.Assert(got.SkipVerify).Equal(secret.SkipVerify)
g.Assert(got.Conceal).Equal(secret.Conceal)
})
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

@ -58,30 +58,6 @@ type Store interface {
// DeleteRepo deletes a user repository. // DeleteRepo deletes a user repository.
DeleteRepo(*model.Repo) error DeleteRepo(*model.Repo) error
// GetSecretList gets a list of repository secrets
GetSecretList(*model.Repo) ([]*model.RepoSecret, error)
// GetSecret gets the named repository secret.
GetSecret(*model.Repo, string) (*model.RepoSecret, error)
// SetSecret sets the named repository secret.
SetSecret(*model.RepoSecret) error
// DeleteSecret deletes the named repository secret.
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)
@ -112,38 +88,21 @@ type Store interface {
// UpdateBuild updates a build. // UpdateBuild updates a build.
UpdateBuild(*model.Build) error UpdateBuild(*model.Build) error
// // GetJob gets a job by unique ID.
// GetJob(int64) (*model.Job, error)
// //
// // GetJobNumber gets a job by number. // new functions
// GetJobNumber(*model.Build, int) (*model.Job, error)
// //
// // GetJobList gets a list of all users in the system.
// GetJobList(*model.Build) ([]*model.Job, error)
//
// // CreateJob creates a job.
// CreateJob(*model.Job) error
//
// // UpdateJob updates a job.
// UpdateJob(*model.Job) error
//
// // ReadLog reads the Job logs from the datastore.
// ReadLog(*model.Job) (io.ReadCloser, error)
//
// // WriteLog writes the job logs to the datastore.
// WriteLog(*model.Job, io.Reader) error
// GetAgent(int64) (*model.Agent, error) SenderFind(*model.Repo, string) (*model.Sender, error)
// SenderList(*model.Repo) ([]*model.Sender, error)
// GetAgentAddr(string) (*model.Agent, error) SenderCreate(*model.Sender) error
// SenderUpdate(*model.Sender) error
// GetAgentList() ([]*model.Agent, error) SenderDelete(*model.Sender) error
//
// CreateAgent(*model.Agent) error SecretFind(*model.Repo, string) (*model.Secret, error)
// SecretList(*model.Repo) ([]*model.Secret, error)
// UpdateAgent(*model.Agent) error SecretCreate(*model.Secret) error
// SecretUpdate(*model.Secret) error
// DeleteAgent(*model.Agent) error SecretDelete(*model.Secret) error
RegistryFind(*model.Repo, string) (*model.Registry, error) RegistryFind(*model.Repo, string) (*model.Registry, error)
RegistryList(*model.Repo) ([]*model.Registry, error) RegistryList(*model.Repo) ([]*model.Registry, error)
@ -168,8 +127,6 @@ type Store interface {
FileCreate(*model.File, io.Reader) error FileCreate(*model.File, io.Reader) error
} }
const globalTeamName = "__global__"
// GetUser gets a user by unique ID. // GetUser gets a user by unique ID.
func GetUser(c context.Context, id int64) (*model.User, error) { func GetUser(c context.Context, id int64) (*model.User, error) {
return FromContext(c).GetUser(id) return FromContext(c).GetUser(id)
@ -238,94 +195,6 @@ 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.RepoSecret, error) {
return FromContext(c).GetSecretList(r)
}
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.RepoSecret) error {
return FromContext(c).SetSecret(s)
}
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 GetGlobalSecretList(c context.Context) ([]*model.TeamSecret, error) {
return GetTeamSecretList(c, globalTeamName)
}
func GetGlobalSecret(c context.Context, name string) (*model.TeamSecret, error) {
return GetTeamSecret(c, globalTeamName, name)
}
func SetGlobalSecret(c context.Context, s *model.TeamSecret) error {
s.Key = globalTeamName
return SetTeamSecret(c, s)
}
func DeleteGlobalSecret(c context.Context, s *model.TeamSecret) error {
s.Key = globalTeamName
return DeleteTeamSecret(c, s)
}
func GetMergedSecretList(c context.Context, r *model.Repo) ([]*model.Secret, error) {
var (
secrets []*model.Secret
)
globalSecs, err := GetGlobalSecretList(c)
if err != nil {
return nil, err
}
for _, secret := range globalSecs {
secrets = append(secrets, secret.Secret())
}
teamSecs, err := GetTeamSecretList(c, r.Owner)
if err != nil {
return nil, err
}
for _, secret := range teamSecs {
secrets = append(secrets, secret.Secret())
}
repoSecs, err := GetSecretList(c, r)
if err != nil {
return nil, err
}
for _, secret := range repoSecs {
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)
} }
@ -365,55 +234,3 @@ func CreateBuild(c context.Context, build *model.Build, procs ...*model.Proc) er
func UpdateBuild(c context.Context, build *model.Build) error { func UpdateBuild(c context.Context, build *model.Build) error {
return FromContext(c).UpdateBuild(build) return FromContext(c).UpdateBuild(build)
} }
// func GetJob(c context.Context, id int64) (*model.Job, error) {
// return FromContext(c).GetJob(id)
// }
//
// func GetJobNumber(c context.Context, build *model.Build, num int) (*model.Job, error) {
// return FromContext(c).GetJobNumber(build, num)
// }
//
// func GetJobList(c context.Context, build *model.Build) ([]*model.Job, error) {
// return FromContext(c).GetJobList(build)
// }
//
// func CreateJob(c context.Context, job *model.Job) error {
// return FromContext(c).CreateJob(job)
// }
//
// func UpdateJob(c context.Context, job *model.Job) error {
// return FromContext(c).UpdateJob(job)
// }
//
// func ReadLog(c context.Context, job *model.Job) (io.ReadCloser, error) {
// return FromContext(c).ReadLog(job)
// }
//
// func WriteLog(c context.Context, job *model.Job, r io.Reader) error {
// return FromContext(c).WriteLog(job, r)
// }
// func GetAgent(c context.Context, id int64) (*model.Agent, error) {
// return FromContext(c).GetAgent(id)
// }
//
// func GetAgentAddr(c context.Context, addr string) (*model.Agent, error) {
// return FromContext(c).GetAgentAddr(addr)
// }
//
// func GetAgentList(c context.Context) ([]*model.Agent, error) {
// return FromContext(c).GetAgentList()
// }
//
// func CreateAgent(c context.Context, agent *model.Agent) error {
// return FromContext(c).CreateAgent(agent)
// }
//
// func UpdateAgent(c context.Context, agent *model.Agent) error {
// return FromContext(c).UpdateAgent(agent)
// }
//
// func DeleteAgent(c context.Context, agent *model.Agent) error {
// return FromContext(c).DeleteAgent(agent)
// }