diff --git a/client/client.go b/client/client.go index d959d7b9..c5a5e565 100644 --- a/client/client.go +++ b/client/client.go @@ -117,4 +117,19 @@ type Client interface { // AgentList returns a list of build agents. AgentList() ([]*model.Agent, error) + + // Registry returns a registry by hostname. + Registry(owner, name, hostname string) (*model.Registry, error) + + // RegistryList returns a list of all repository registries. + RegistryList(owner, name string) ([]*model.Registry, error) + + // RegistryCreate creates a registry. + RegistryCreate(owner, name string, registry *model.Registry) (*model.Registry, error) + + // RegistryUpdate updates a registry. + RegistryUpdate(owner, name string, registry *model.Registry) (*model.Registry, error) + + // RegistryDelete deletes a registry. + RegistryDelete(owner, name, hostname string) error } diff --git a/client/client_impl.go b/client/client_impl.go index 0f316797..3621014a 100644 --- a/client/client_impl.go +++ b/client/client_impl.go @@ -26,32 +26,34 @@ const ( pathLogs = "%s/api/queue/logs/%d" pathLogsAuth = "%s/api/queue/logs/%d?access_token=%s" - pathSelf = "%s/api/user" - pathFeed = "%s/api/user/feed" - pathRepos = "%s/api/user/repos" - pathRepo = "%s/api/repos/%s/%s" - pathChown = "%s/api/repos/%s/%s/chown" - pathEncrypt = "%s/api/repos/%s/%s/encrypt" - pathBuilds = "%s/api/repos/%s/%s/builds" - pathBuild = "%s/api/repos/%s/%s/builds/%v" - pathApprove = "%s/api/repos/%s/%s/builds/%d/approve" - pathDecline = "%s/api/repos/%s/%s/builds/%d/decline" - pathJob = "%s/api/repos/%s/%s/builds/%d/%d" - pathLog = "%s/api/repos/%s/%s/logs/%d/%d" - pathKey = "%s/api/repos/%s/%s/key" - pathSign = "%s/api/repos/%s/%s/sign" - pathRepoSecrets = "%s/api/repos/%s/%s/secrets" - pathRepoSecret = "%s/api/repos/%s/%s/secrets/%s" - pathTeamSecrets = "%s/api/teams/%s/secrets" - pathTeamSecret = "%s/api/teams/%s/secrets/%s" - pathGlobalSecrets = "%s/api/global/secrets" - pathGlobalSecret = "%s/api/global/secrets/%s" - pathNodes = "%s/api/nodes" - pathNode = "%s/api/nodes/%d" - pathUsers = "%s/api/users" - pathUser = "%s/api/users/%s" - pathBuildQueue = "%s/api/builds" - pathAgent = "%s/api/agents" + pathSelf = "%s/api/user" + pathFeed = "%s/api/user/feed" + pathRepos = "%s/api/user/repos" + pathRepo = "%s/api/repos/%s/%s" + pathChown = "%s/api/repos/%s/%s/chown" + pathEncrypt = "%s/api/repos/%s/%s/encrypt" + pathBuilds = "%s/api/repos/%s/%s/builds" + pathBuild = "%s/api/repos/%s/%s/builds/%v" + pathApprove = "%s/api/repos/%s/%s/builds/%d/approve" + pathDecline = "%s/api/repos/%s/%s/builds/%d/decline" + pathJob = "%s/api/repos/%s/%s/builds/%d/%d" + pathLog = "%s/api/repos/%s/%s/logs/%d/%d" + pathKey = "%s/api/repos/%s/%s/key" + pathSign = "%s/api/repos/%s/%s/sign" + pathRepoSecrets = "%s/api/repos/%s/%s/secrets" + pathRepoSecret = "%s/api/repos/%s/%s/secrets/%s" + pathRepoRegistries = "%s/api/repos/%s/%s/registry" + 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" + pathUser = "%s/api/users/%s" + pathBuildQueue = "%s/api/builds" + pathAgent = "%s/api/agents" ) type client struct { @@ -373,6 +375,44 @@ func (c *client) AgentList() ([]*model.Agent, error) { return out, err } +// Registry returns a registry by hostname. +func (c *client) Registry(owner, name, hostname string) (*model.Registry, error) { + out := new(model.Registry) + uri := fmt.Sprintf(pathRepoRegistry, c.base, owner, name, hostname) + err := c.get(uri, out) + return out, err +} + +// RegistryList returns a list of all repository registries. +func (c *client) RegistryList(owner string, name string) ([]*model.Registry, error) { + var out []*model.Registry + uri := fmt.Sprintf(pathRepoRegistries, c.base, owner, name) + err := c.get(uri, &out) + return out, err +} + +// RegistryCreate creates a registry. +func (c *client) RegistryCreate(owner, name string, in *model.Registry) (*model.Registry, error) { + out := new(model.Registry) + uri := fmt.Sprintf(pathRepoRegistries, c.base, owner, name) + err := c.post(uri, in, out) + return out, err +} + +// RegistryUpdate updates a registry. +func (c *client) RegistryUpdate(owner, name string, in *model.Registry) (*model.Registry, error) { + out := new(model.Registry) + uri := fmt.Sprintf(pathRepoRegistry, c.base, owner, name, in.Address) + err := c.patch(uri, in, out) + return out, err +} + +// RegistryDelete deletes a registry. +func (c *client) RegistryDelete(owner, name, hostname string) error { + uri := fmt.Sprintf(pathRepoRegistry, c.base, owner, name, hostname) + return c.delete(uri) +} + // // http request helper functions // diff --git a/drone/main.go b/drone/main.go index 459cf24f..cd8cdfe2 100644 --- a/drone/main.go +++ b/drone/main.go @@ -37,6 +37,7 @@ func main() { deployCmd, execCmd, infoCmd, + registryCmd, secretCmd, serverCmd, signCmd, diff --git a/drone/registry.go b/drone/registry.go new file mode 100644 index 00000000..da479796 --- /dev/null +++ b/drone/registry.go @@ -0,0 +1,15 @@ +package main + +import "github.com/urfave/cli" + +var registryCmd = cli.Command{ + Name: "registry", + Usage: "manage registries", + Subcommands: []cli.Command{ + registryCreateCmd, + registryDeleteCmd, + registryUpdateCmd, + registryInfoCmd, + registryListCmd, + }, +} diff --git a/drone/registry_add.go b/drone/registry_add.go new file mode 100644 index 00000000..470dcfeb --- /dev/null +++ b/drone/registry_add.go @@ -0,0 +1,61 @@ +package main + +import ( + "github.com/drone/drone/model" + "github.com/urfave/cli" +) + +var registryCreateCmd = cli.Command{ + Name: "add", + Usage: "adds a registry", + Action: registryCreate, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "repository", + Usage: "repository name (e.g. octocat/hello-world)", + }, + cli.StringFlag{ + Name: "hostname", + Usage: "registry hostname", + Value: "index.docker.io", + }, + cli.StringFlag{ + Name: "username", + Usage: "registry username", + }, + cli.StringFlag{ + Name: "password", + Usage: "registry password", + }, + }, +} + +func registryCreate(c *cli.Context) error { + var ( + hostname = c.String("hostname") + username = c.String("username") + password = c.String("password") + 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 + } + registry := &model.Registry{ + Address: hostname, + Username: username, + Password: password, + } + _, err = client.RegistryCreate(owner, name, registry) + if err != nil { + return err + } + return nil +} diff --git a/drone/registry_info.go b/drone/registry_info.go new file mode 100644 index 00000000..ccfd413b --- /dev/null +++ b/drone/registry_info.go @@ -0,0 +1,59 @@ +package main + +import ( + "html/template" + "os" + + "github.com/urfave/cli" +) + +var registryInfoCmd = cli.Command{ + Name: "info", + Usage: "display registry info", + Action: registryInfo, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "repository", + Usage: "repository name (e.g. octocat/hello-world)", + }, + cli.StringFlag{ + Name: "hostname", + Usage: "registry hostname", + Value: "index.docker.io", + }, + cli.StringFlag{ + Name: "format", + Usage: "repository name (e.g. octocat/hello-world)", + Value: tmplRegistryList, + Hidden: true, + }, + }, +} + +func registryInfo(c *cli.Context) error { + var ( + hostname = c.String("hostname") + 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 + } + registry, err := client.Registry(owner, name, hostname) + if err != nil { + return err + } + tmpl, err := template.New("_").Parse(format) + if err != nil { + return err + } + return tmpl.Execute(os.Stdout, registry) +} diff --git a/drone/registry_list.go b/drone/registry_list.go new file mode 100644 index 00000000..f136f861 --- /dev/null +++ b/drone/registry_list.go @@ -0,0 +1,63 @@ +package main + +import ( + "html/template" + "os" + + "github.com/urfave/cli" +) + +var registryListCmd = cli.Command{ + Name: "ls", + Usage: "list regitries", + Action: registryList, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "repository", + Usage: "repository name (e.g. octocat/hello-world)", + }, + cli.StringFlag{ + Name: "format", + Usage: "repository name (e.g. octocat/hello-world)", + Value: tmplRegistryList, + Hidden: true, + }, + }, +} + +func registryList(c *cli.Context) error { + var ( + format = c.String("format") + "\n" + 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 + } + list, err := client.RegistryList(owner, name) + if err != nil { + return err + } + tmpl, err := template.New("_").Parse(format) + if err != nil { + return err + } + for _, registry := range list { + tmpl.Execute(os.Stdout, registry) + } + return nil +} + +// template for build list information +var tmplRegistryList = "\x1b[33m{{ .Address }} \x1b[0m" + ` +Username: {{ .Username }} +Password: ******** +Email: {{ .Email }} +` diff --git a/drone/registry_rm.go b/drone/registry_rm.go new file mode 100644 index 00000000..9ad01ed0 --- /dev/null +++ b/drone/registry_rm.go @@ -0,0 +1,39 @@ +package main + +import "github.com/urfave/cli" + +var registryDeleteCmd = cli.Command{ + Name: "rm", + Usage: "remove a registry", + Action: registryDelete, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "repository", + Usage: "repository name (e.g. octocat/hello-world)", + }, + cli.StringFlag{ + Name: "hostname", + Usage: "registry hostname", + Value: "index.docker.io", + }, + }, +} + +func registryDelete(c *cli.Context) error { + var ( + hostname = c.String("hostname") + 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 + } + return client.RegistryDelete(owner, name, hostname) +} diff --git a/drone/registry_set.go b/drone/registry_set.go new file mode 100644 index 00000000..b3d7bf5b --- /dev/null +++ b/drone/registry_set.go @@ -0,0 +1,61 @@ +package main + +import ( + "github.com/drone/drone/model" + "github.com/urfave/cli" +) + +var registryUpdateCmd = cli.Command{ + Name: "update", + Usage: "update a registry", + Action: registryUpdate, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "repository", + Usage: "repository name (e.g. octocat/hello-world)", + }, + cli.StringFlag{ + Name: "hostname", + Usage: "registry hostname", + Value: "index.docker.io", + }, + cli.StringFlag{ + Name: "username", + Usage: "registry username", + }, + cli.StringFlag{ + Name: "password", + Usage: "registry password", + }, + }, +} + +func registryUpdate(c *cli.Context) error { + var ( + hostname = c.String("hostname") + username = c.String("username") + password = c.String("password") + 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 + } + registry := &model.Registry{ + Address: hostname, + Username: username, + Password: password, + } + _, err = client.RegistryUpdate(owner, name, registry) + if err != nil { + return err + } + return nil +} diff --git a/model/limit.go b/model/limit.go new file mode 100644 index 00000000..642e1f25 --- /dev/null +++ b/model/limit.go @@ -0,0 +1,23 @@ +package model + +// Limiter defines an interface for limiting repository creation. +// This could be used, for example, to limit repository creation to +// a specific organization or a specific set of users. +type Limiter interface { + LimitUser(*User) error + LimitRepo(*User, *Repo) error + LimitBuild(*User, *Repo, *Build) error +} + +// NoLimit impliments the Limiter interface without enforcing any +// actual limits. All limiting functions are no-ops. +type NoLimit struct{} + +// LimitUser is a no-op for limiting user creation. +func (NoLimit) LimitUser(*User) error { return nil } + +// LimitRepo is a no-op for limiting repo creation. +func (NoLimit) LimitRepo(*User, *Repo) error { return nil } + +// LimitBuild is a no-op for limiting build creation. +func (NoLimit) LimitBuild(*User, *Repo, *Build) error { return nil } diff --git a/model/registry.go b/model/registry.go index eec2408d..8d26499e 100644 --- a/model/registry.go +++ b/model/registry.go @@ -1,15 +1,56 @@ package model +import "errors" + +var ( + errRegistryAddressInvalid = errors.New("Invalid Registry Address") + errRegistryUsernameInvalid = errors.New("Invalid Registry Username") + errRegistryPasswordInvalid = errors.New("Invalid Registry Password") +) + +// RegistryStore persists registry information to storage. +type RegistryStore interface { + RegistryFind(*Repo, string) (*Registry, error) + RegistryList(*Repo) ([]*Registry, error) + RegistryCreate(*Registry) error + RegistryUpdate(*Registry) error + RegistryDelete(*Registry) error +} + +// Registry represents a docker registry with credentials. +// swagger:model registry type Registry struct { ID int64 `json:"id" meddler:"registry_id,pk"` RepoID int64 `json:"-" meddler:"registry_repo_id"` - Addr string `json:"addr" meddler:"registry_addr"` + Address string `json:"address" meddler:"registry_addr"` Username string `json:"username" meddler:"registry_username"` Password string `json:"password" meddler:"registry_password"` Email string `json:"email" meddler:"registry_email"` Token string `json:"token" meddler:"registry_token"` } +// Validate validates the registry information. func (r *Registry) Validate() error { - return nil + switch { + case len(r.Address) == 0: + return errRegistryAddressInvalid + case len(r.Username) == 0: + return errRegistryUsernameInvalid + case len(r.Password) == 0: + return errRegistryPasswordInvalid + default: + return nil + } +} + +// Copy makes a copy of the registry without the password. +func (r *Registry) Copy() *Registry { + return &Registry{ + ID: r.ID, + RepoID: r.RepoID, + Address: r.Address, + Username: r.Username, + Email: r.Email, + Token: r.Token, + } } diff --git a/router/router.go b/router/router.go index 366c2e3d..ff96a223 100644 --- a/router/router.go +++ b/router/router.go @@ -111,6 +111,13 @@ func Load(middleware ...gin.HandlerFunc) http.Handler { repo.POST("/secrets", session.MustPush, server.PostSecret) repo.DELETE("/secrets/:secret", session.MustPush, server.DeleteSecret) + // requires push permissions + repo.GET("/registry", session.MustPush, server.GetRegistryList) + repo.POST("/registry", session.MustPush, server.PostRegistry) + repo.GET("/registry/:address", session.MustPush, server.GetRegistry) + repo.PATCH("/registry/:address", session.MustPush, server.PatchRegistry) + repo.DELETE("/registry/:address", session.MustPush, server.DeleteRegistry) + // requires push permissions repo.PATCH("", session.MustPush, server.PatchRepo) repo.DELETE("", session.MustRepoAdmin(), server.DeleteRepo) diff --git a/server/registry.go b/server/registry.go new file mode 100644 index 00000000..5cefd09e --- /dev/null +++ b/server/registry.go @@ -0,0 +1,133 @@ +package server + +import ( + "net/http" + + "github.com/drone/drone/model" + "github.com/drone/drone/router/middleware/session" + "github.com/drone/drone/store" + + "github.com/gin-gonic/gin" +) + +// GetRegistry gets the name registry from the database and writes +// to the response in json format. +func GetRegistry(c *gin.Context) { + var ( + repo = session.Repo(c) + name = c.Param("registry") + ) + registry, err := store.FromContext(c).RegistryFind(repo, name) + if err != nil { + c.String(404, "Error getting registry %q. %s", name, err) + return + } + c.JSON(200, registry.Copy()) +} + +// PostRegistry persists the registry to the database. +func PostRegistry(c *gin.Context) { + repo := session.Repo(c) + + in := new(model.Registry) + if err := c.Bind(in); err != nil { + c.String(http.StatusBadRequest, "Error parsing request. %s", err) + return + } + registry := &model.Registry{ + RepoID: repo.ID, + Address: in.Address, + Username: in.Username, + Password: in.Password, + Token: in.Token, + Email: in.Email, + } + if err := registry.Validate(); err != nil { + c.String(400, "Error inserting registry. %s", err) + return + } + if err := store.FromContext(c).RegistryCreate(registry); err != nil { + c.String(500, "Error inserting registry %q. %s", in.Address, err) + return + } + c.JSON(200, in.Copy()) +} + +// PatchRegistry updates the registry in the database. +func PatchRegistry(c *gin.Context) { + var ( + repo = session.Repo(c) + name = c.Param("registry") + ) + + in := new(model.Registry) + err := c.Bind(in) + if err != nil { + c.String(http.StatusBadRequest, "Error parsing request. %s", err) + return + } + + registry, err := store.FromContext(c).RegistryFind(repo, name) + if err != nil { + c.String(404, "Error getting registry %q. %s", name, err) + return + } + if in.Username != "" { + registry.Username = in.Username + } + if in.Password != "" { + registry.Password = in.Password + } + if in.Token != "" { + registry.Token = in.Token + } + if in.Email != "" { + registry.Email = in.Email + } + + if err := registry.Validate(); err != nil { + c.String(400, "Error updating registry. %s", err) + return + } + if err := store.FromContext(c).RegistryUpdate(registry); err != nil { + c.String(500, "Error updating registry %q. %s", in.Address, err) + return + } + c.JSON(200, in.Copy()) +} + +// GetRegistryList gets the registry list from the database and writes +// to the response in json format. +func GetRegistryList(c *gin.Context) { + repo := session.Repo(c) + list, err := store.FromContext(c).RegistryList(repo) + if err != nil { + c.String(500, "Error getting registry list. %s", err) + return + } + // copy the registry detail to remove the sensitive + // password and token fields. + for i, registry := range list { + list[i] = registry.Copy() + } + c.JSON(200, list) +} + +// DeleteRegistry deletes the named registry from the database. +func DeleteRegistry(c *gin.Context) { + var ( + repo = session.Repo(c) + name = c.Param("registry") + ) + registry, err := store.FromContext(c).RegistryFind(repo, name) + 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) + return + } + c.String(204, "") +} diff --git a/store/datastore/agents.go b/store/datastore/agents.go deleted file mode 100644 index 90ff8859..00000000 --- a/store/datastore/agents.go +++ /dev/null @@ -1,56 +0,0 @@ -package datastore - -import ( - "github.com/drone/drone/model" - "github.com/russross/meddler" -) - -func (db *datastore) GetAgent(id int64) (*model.Agent, error) { - var agent = new(model.Agent) - var err = meddler.Load(db, agentTable, agent, id) - return agent, err -} - -func (db *datastore) GetAgentAddr(addr string) (*model.Agent, error) { - var agent = new(model.Agent) - var err = meddler.QueryRow(db, agent, rebind(agentAddrQuery), addr) - return agent, err -} - -func (db *datastore) GetAgentList() ([]*model.Agent, error) { - var agents = []*model.Agent{} - var err = meddler.QueryAll(db, &agents, rebind(agentListQuery)) - return agents, err -} - -func (db *datastore) CreateAgent(agent *model.Agent) error { - return meddler.Insert(db, agentTable, agent) -} - -func (db *datastore) UpdateAgent(agent *model.Agent) error { - return meddler.Update(db, agentTable, agent) -} - -func (db *datastore) DeleteAgent(agent *model.Agent) error { - var _, err = db.Exec(rebind(agentDeleteStmt), agent.ID) - return err -} - -const agentTable = "agents" - -const agentAddrQuery = ` -SELECT * -FROM agents -WHERE agent_addr=? -LIMIT 1 -` - -const agentListQuery = ` -SELECT * -FROM agents -ORDER BY agent_addr ASC -` - -const agentDeleteStmt = ` -DELETE FROM agents WHERE agent_id = ? -` diff --git a/store/datastore/agents_test.go b/store/datastore/agents_test.go deleted file mode 100644 index 633c5cb2..00000000 --- a/store/datastore/agents_test.go +++ /dev/null @@ -1,126 +0,0 @@ -package datastore - -import ( - "testing" - - "github.com/drone/drone/model" - "github.com/franela/goblin" -) - -func TestAgents(t *testing.T) { - db := openTest() - defer db.Close() - s := From(db) - - g := goblin.Goblin(t) - g.Describe("Agents", func() { - - // before each test be sure to purge the package - // table data from the database. - g.BeforeEach(func() { - db.Exec("DELETE FROM agents") - }) - - g.It("Should update", func() { - agent := model.Agent{ - Address: "127.0.0.1", - Platform: "linux/amd64", - } - err1 := s.CreateAgent(&agent) - agent.Platform = "windows/amd64" - err2 := s.UpdateAgent(&agent) - - getagent, err3 := s.GetAgent(agent.ID) - g.Assert(err1 == nil).IsTrue() - g.Assert(err2 == nil).IsTrue() - g.Assert(err3 == nil).IsTrue() - g.Assert(agent.ID).Equal(getagent.ID) - g.Assert(agent.Platform).Equal(getagent.Platform) - }) - - g.It("Should create", func() { - agent := model.Agent{ - Address: "127.0.0.1", - Platform: "linux/amd64", - } - err := s.CreateAgent(&agent) - g.Assert(err == nil).IsTrue() - g.Assert(agent.ID != 0).IsTrue() - }) - - g.It("Should get by ID", func() { - agent := model.Agent{ - Address: "127.0.0.1", - Platform: "linux/amd64", - } - - s.CreateAgent(&agent) - getagent, err := s.GetAgent(agent.ID) - g.Assert(err == nil).IsTrue() - g.Assert(agent.ID).Equal(getagent.ID) - g.Assert(agent.Address).Equal(getagent.Address) - g.Assert(agent.Platform).Equal(getagent.Platform) - }) - - g.It("Should get by IP address", func() { - agent := model.Agent{ - Address: "127.0.0.1", - Platform: "linux/amd64", - } - s.CreateAgent(&agent) - getagent, err := s.GetAgentAddr(agent.Address) - g.Assert(err == nil).IsTrue() - g.Assert(agent.ID).Equal(getagent.ID) - g.Assert(agent.Address).Equal(getagent.Address) - g.Assert(agent.Platform).Equal(getagent.Platform) - }) - - g.It("Should enforce unique IP address", func() { - agent1 := model.Agent{ - Address: "127.0.0.1", - Platform: "linux/amd64", - } - agent2 := model.Agent{ - Address: "127.0.0.1", - Platform: "linux/amd64", - } - err1 := s.CreateAgent(&agent1) - err2 := s.CreateAgent(&agent2) - g.Assert(err1 == nil).IsTrue() - g.Assert(err2 == nil).IsFalse() - }) - - g.It("Should list", func() { - agent1 := model.Agent{ - Address: "127.0.0.1", - Platform: "linux/amd64", - } - agent2 := model.Agent{ - Address: "localhost", - Platform: "linux/amd64", - } - s.CreateAgent(&agent1) - s.CreateAgent(&agent2) - agents, err := s.GetAgentList() - g.Assert(err == nil).IsTrue() - g.Assert(len(agents)).Equal(2) - g.Assert(agents[0].Address).Equal(agent1.Address) - g.Assert(agents[0].Platform).Equal(agent1.Platform) - }) - - // g.It("Should delete", func() { - // user := model.User{ - // Login: "joe", - // Email: "foo@bar.com", - // Token: "e42080dddf012c718e476da161d21ad5", - // } - // s.CreateUser(&user) - // _, err1 := s.GetUser(user.ID) - // err2 := s.DeleteUser(&user) - // _, err3 := s.GetUser(user.ID) - // g.Assert(err1 == nil).IsTrue() - // g.Assert(err2 == nil).IsTrue() - // g.Assert(err3 == nil).IsFalse() - // }) - }) -} diff --git a/store/datastore/jobs.go b/store/datastore/jobs.go deleted file mode 100644 index d47f9f04..00000000 --- a/store/datastore/jobs.go +++ /dev/null @@ -1,50 +0,0 @@ -package datastore - -// -// import ( -// "github.com/drone/drone/model" -// "github.com/russross/meddler" -// ) -// -// func (db *datastore) GetJob(id int64) (*model.Job, error) { -// var job = new(model.Job) -// var err = meddler.Load(db, jobTable, job, id) -// return job, err -// } -// -// func (db *datastore) GetJobNumber(build *model.Build, num int) (*model.Job, error) { -// var job = new(model.Job) -// var err = meddler.QueryRow(db, job, rebind(jobNumberQuery), build.ID, num) -// return job, err -// } -// -// func (db *datastore) GetJobList(build *model.Build) ([]*model.Job, error) { -// var jobs = []*model.Job{} -// var err = meddler.QueryAll(db, &jobs, rebind(jobListQuery), build.ID) -// return jobs, err -// } -// -// func (db *datastore) CreateJob(job *model.Job) error { -// return meddler.Insert(db, jobTable, job) -// } -// -// func (db *datastore) UpdateJob(job *model.Job) error { -// return meddler.Update(db, jobTable, job) -// } -// -// const jobTable = "jobs" -// -// const jobListQuery = ` -// SELECT * -// FROM jobs -// WHERE job_build_id = ? -// ORDER BY job_number ASC -// ` -// -// const jobNumberQuery = ` -// SELECT * -// FROM jobs -// WHERE job_build_id = ? -// AND job_number = ? -// LIMIT 1 -// ` diff --git a/store/datastore/jobs_test.go b/store/datastore/jobs_test.go deleted file mode 100644 index 46693984..00000000 --- a/store/datastore/jobs_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package datastore - -// -// import ( -// "testing" -// -// "github.com/drone/drone/model" -// "github.com/franela/goblin" -// ) -// -// func TestJobs(t *testing.T) { -// db := openTest() -// defer db.Close() -// -// s := From(db) -// g := goblin.Goblin(t) -// g.Describe("Job", func() { -// -// // before each test we purge the package table data from the database. -// g.BeforeEach(func() { -// db.Exec("DELETE FROM jobs") -// db.Exec("DELETE FROM builds") -// }) -// -// g.It("Should Set a job", func() { -// job := &model.Job{ -// BuildID: 1, -// Status: "pending", -// ExitCode: 0, -// Number: 1, -// } -// err1 := s.CreateJob(job) -// g.Assert(err1 == nil).IsTrue() -// g.Assert(job.ID != 0).IsTrue() -// -// job.Status = "started" -// err2 := s.UpdateJob(job) -// g.Assert(err2 == nil).IsTrue() -// -// getjob, err3 := s.GetJob(job.ID) -// g.Assert(err3 == nil).IsTrue() -// g.Assert(getjob.Status).Equal(job.Status) -// }) -// -// g.It("Should Get a Job by ID", func() { -// job := &model.Job{ -// BuildID: 1, -// Status: "pending", -// ExitCode: 1, -// Number: 1, -// Environment: map[string]string{"foo": "bar"}, -// } -// err1 := s.CreateJob(job) -// g.Assert(err1 == nil).IsTrue() -// g.Assert(job.ID != 0).IsTrue() -// -// getjob, err2 := s.GetJob(job.ID) -// g.Assert(err2 == nil).IsTrue() -// g.Assert(getjob.ID).Equal(job.ID) -// g.Assert(getjob.Status).Equal(job.Status) -// g.Assert(getjob.ExitCode).Equal(job.ExitCode) -// g.Assert(getjob.Environment).Equal(job.Environment) -// g.Assert(getjob.Environment["foo"]).Equal("bar") -// }) -// -// g.It("Should Get a Job by Number", func() { -// job := &model.Job{ -// BuildID: 1, -// Status: "pending", -// ExitCode: 1, -// Number: 1, -// } -// err1 := s.CreateJob(job) -// g.Assert(err1 == nil).IsTrue() -// g.Assert(job.ID != 0).IsTrue() -// -// getjob, err2 := s.GetJobNumber(&model.Build{ID: 1}, 1) -// g.Assert(err2 == nil).IsTrue() -// g.Assert(getjob.ID).Equal(job.ID) -// g.Assert(getjob.Status).Equal(job.Status) -// }) -// -// g.It("Should Get a List of Jobs by Commit", func() { -// -// build := model.Build{ -// RepoID: 1, -// Status: model.StatusSuccess, -// } -// jobs := []*model.Job{ -// { -// BuildID: 1, -// Status: "success", -// ExitCode: 0, -// Number: 1, -// }, -// { -// BuildID: 3, -// Status: "error", -// ExitCode: 1, -// Number: 2, -// }, -// { -// BuildID: 5, -// Status: "pending", -// ExitCode: 0, -// Number: 3, -// }, -// } -// -// err1 := s.CreateBuild(&build, jobs...) -// g.Assert(err1 == nil).IsTrue() -// getjobs, err2 := s.GetJobList(&build) -// g.Assert(err2 == nil).IsTrue() -// g.Assert(len(getjobs)).Equal(3) -// g.Assert(getjobs[0].Number).Equal(1) -// g.Assert(getjobs[0].Status).Equal(model.StatusSuccess) -// }) -// }) -// } diff --git a/store/datastore/registry.go b/store/datastore/registry.go new file mode 100644 index 00000000..6e16d365 --- /dev/null +++ b/store/datastore/registry.go @@ -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) RegistryFind(repo *model.Repo, addr string) (*model.Registry, error) { + stmt := sql.Lookup(db.driver, "registry-find-repo-addr") + data := new(model.Registry) + err := meddler.QueryRow(db, data, stmt, repo.ID, addr) + return data, err +} + +func (db *datastore) RegistryList(repo *model.Repo) ([]*model.Registry, error) { + stmt := sql.Lookup(db.driver, "registry-find-repo") + data := []*model.Registry{} + err := meddler.QueryAll(db, &data, stmt, repo.ID) + return data, err +} + +func (db *datastore) RegistryCreate(registry *model.Registry) error { + return meddler.Insert(db, "registry", registry) +} + +func (db *datastore) RegistryUpdate(registry *model.Registry) error { + return meddler.Update(db, "registry", registry) +} + +func (db *datastore) RegistryDelete(registry *model.Registry) error { + stmt := sql.Lookup(db.driver, "registry-delete") + _, err := db.Exec(stmt, registry.ID) + return err +} diff --git a/store/datastore/registry_test.go b/store/datastore/registry_test.go new file mode 100644 index 00000000..3df5b611 --- /dev/null +++ b/store/datastore/registry_test.go @@ -0,0 +1,142 @@ +package datastore + +import ( + "testing" + + "github.com/drone/drone/model" +) + +func TestRegistryFind(t *testing.T) { + s := newTest() + defer func() { + s.Exec("delete from registry") + s.Close() + }() + + err := s.RegistryCreate(&model.Registry{ + RepoID: 1, + Address: "index.docker.io", + Username: "foo", + Password: "bar", + Email: "foo@bar.com", + Token: "12345", + }) + if err != nil { + t.Errorf("Unexpected error: insert registry: %s", err) + return + } + + registry, err := s.RegistryFind(&model.Repo{ID: 1}, "index.docker.io") + if err != nil { + t.Error(err) + return + } + if got, want := registry.RepoID, int64(1); got != want { + t.Errorf("Want repo id %d, got %d", want, got) + } + if got, want := registry.Address, "index.docker.io"; got != want { + t.Errorf("Want registry address %s, got %s", want, got) + } + if got, want := registry.Username, "foo"; got != want { + t.Errorf("Want registry username %s, got %s", want, got) + } + if got, want := registry.Password, "bar"; got != want { + t.Errorf("Want registry password %s, got %s", want, got) + } + if got, want := registry.Email, "foo@bar.com"; got != want { + t.Errorf("Want registry email %s, got %s", want, got) + } + if got, want := registry.Token, "12345"; got != want { + t.Errorf("Want registry token %s, got %s", want, got) + } +} + +func TestRegistryList(t *testing.T) { + s := newTest() + defer func() { + s.Exec("delete from registry") + s.Close() + }() + + s.RegistryCreate(&model.Registry{ + RepoID: 1, + Address: "index.docker.io", + Username: "foo", + Password: "bar", + }) + s.RegistryCreate(&model.Registry{ + RepoID: 1, + Address: "foo.docker.io", + Username: "foo", + Password: "bar", + }) + + list, err := s.RegistryList(&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 TestRegistryUpdate(t *testing.T) { + s := newTest() + defer func() { + s.Exec("delete from registry") + s.Close() + }() + + registry := &model.Registry{ + RepoID: 1, + Address: "index.docker.io", + Username: "foo", + Password: "bar", + } + if err := s.RegistryCreate(registry); err != nil { + t.Errorf("Unexpected error: insert registry: %s", err) + return + } + registry.Password = "qux" + if err := s.RegistryUpdate(registry); err != nil { + t.Errorf("Unexpected error: update registry: %s", err) + return + } + updated, err := s.RegistryFind(&model.Repo{ID: 1}, "index.docker.io") + if err != nil { + t.Error(err) + return + } + if got, want := updated.Password, "qux"; got != want { + t.Errorf("Want registry password %s, got %s", want, got) + } +} + +func TestRegistryIndexes(t *testing.T) { + s := newTest() + defer func() { + s.Exec("delete from registry") + s.Close() + }() + + if err := s.RegistryCreate(&model.Registry{ + RepoID: 1, + Address: "index.docker.io", + Username: "foo", + Password: "bar", + }); err != nil { + t.Errorf("Unexpected error: insert registry: %s", err) + return + } + + // fail due to duplicate addr + if err := s.RegistryCreate(&model.Registry{ + RepoID: 1, + Address: "index.docker.io", + Username: "baz", + Password: "qux", + }); err == nil { + t.Errorf("Unexpected error: dupliate address") + } +} diff --git a/store/datastore/sql/postgres/files/registry.sql b/store/datastore/sql/postgres/files/registry.sql new file mode 100644 index 00000000..4cc5d4b2 --- /dev/null +++ b/store/datastore/sql/postgres/files/registry.sql @@ -0,0 +1,34 @@ +-- name: registry-find-repo + +SELECT + registry_id +,registry_repo_id +,registry_addr +,registry_username +,registry_password +,registry_email +,registry_token +FROM registry +WHERE registry_repo_id = $1 + +-- name: registry-find-repo-addr + +SELECT + registry_id +,registry_repo_id +,registry_addr +,registry_username +,registry_password +,registry_email +,registry_token +FROM registry +WHERE registry_repo_id = $1 + AND registry_addr = $2 + +-- name: registry-delete-repo + +DELETE FROM registry WHERE registry_repo_id = $1 + +-- name: registry-delete + +DELETE FROM registry WHERE registry_id = $1 diff --git a/store/datastore/sql/postgres/sql_gen.go b/store/datastore/sql/postgres/sql_gen.go index 19d26fa3..c898055f 100644 --- a/store/datastore/sql/postgres/sql_gen.go +++ b/store/datastore/sql/postgres/sql_gen.go @@ -15,6 +15,10 @@ var index = map[string]string{ "procs-find-build-pid": procsFindBuildPid, "procs-find-build-ppid": procsFindBuildPpid, "procs-delete-build": procsDeleteBuild, + "registry-find-repo": registryFindRepo, + "registry-find-repo-addr": registryFindRepoAddr, + "registry-delete-repo": registryDeleteRepo, + "registry-delete": registryDelete, } var filesFindBuild = ` @@ -149,3 +153,38 @@ WHERE proc_build_id = $1 var procsDeleteBuild = ` DELETE FROM procs WHERE proc_build_id = $1 ` + +var registryFindRepo = ` +SELECT + registry_id +,registry_repo_id +,registry_addr +,registry_username +,registry_password +,registry_email +,registry_token +FROM registry +WHERE registry_repo_id = $1 +` + +var registryFindRepoAddr = ` +SELECT + registry_id +,registry_repo_id +,registry_addr +,registry_username +,registry_password +,registry_email +,registry_token +FROM registry +WHERE registry_repo_id = $1 + AND registry_addr = $2 +` + +var registryDeleteRepo = ` +DELETE FROM registry WHERE registry_repo_id = $1 +` + +var registryDelete = ` +DELETE FROM registry WHERE registry_id = $1 +` diff --git a/store/datastore/sql/sqlite/files/registry.sql b/store/datastore/sql/sqlite/files/registry.sql new file mode 100644 index 00000000..c28ae0f3 --- /dev/null +++ b/store/datastore/sql/sqlite/files/registry.sql @@ -0,0 +1,34 @@ +-- name: registry-find-repo + +SELECT + registry_id +,registry_repo_id +,registry_addr +,registry_username +,registry_password +,registry_email +,registry_token +FROM registry +WHERE registry_repo_id = ? + +-- name: registry-find-repo-addr + +SELECT + registry_id +,registry_repo_id +,registry_addr +,registry_username +,registry_password +,registry_email +,registry_token +FROM registry +WHERE registry_repo_id = ? + AND registry_addr = ? + +-- name: registry-delete-repo + +DELETE FROM registry WHERE registry_repo_id = ? + +-- name: registry-delete + +DELETE FROM registry WHERE registry_id = ? diff --git a/store/datastore/sql/sqlite/sql_gen.go b/store/datastore/sql/sqlite/sql_gen.go index 8e257479..a1222818 100644 --- a/store/datastore/sql/sqlite/sql_gen.go +++ b/store/datastore/sql/sqlite/sql_gen.go @@ -15,6 +15,10 @@ var index = map[string]string{ "procs-find-build-pid": procsFindBuildPid, "procs-find-build-ppid": procsFindBuildPpid, "procs-delete-build": procsDeleteBuild, + "registry-find-repo": registryFindRepo, + "registry-find-repo-addr": registryFindRepoAddr, + "registry-delete-repo": registryDeleteRepo, + "registry-delete": registryDelete, } var filesFindBuild = ` @@ -149,3 +153,38 @@ WHERE proc_build_id = ? var procsDeleteBuild = ` DELETE FROM procs WHERE proc_build_id = ? ` + +var registryFindRepo = ` +SELECT + registry_id +,registry_repo_id +,registry_addr +,registry_username +,registry_password +,registry_email +,registry_token +FROM registry +WHERE registry_repo_id = ? +` + +var registryFindRepoAddr = ` +SELECT + registry_id +,registry_repo_id +,registry_addr +,registry_username +,registry_password +,registry_email +,registry_token +FROM registry +WHERE registry_repo_id = ? + AND registry_addr = ? +` + +var registryDeleteRepo = ` +DELETE FROM registry WHERE registry_repo_id = ? +` + +var registryDelete = ` +DELETE FROM registry WHERE registry_id = ? +` diff --git a/store/store.go b/store/store.go index 79abc787..03461c10 100644 --- a/store/store.go +++ b/store/store.go @@ -133,17 +133,23 @@ type Store interface { // // WriteLog writes the job logs to the datastore. // WriteLog(*model.Job, io.Reader) error - GetAgent(int64) (*model.Agent, error) + // GetAgent(int64) (*model.Agent, error) + // + // GetAgentAddr(string) (*model.Agent, error) + // + // GetAgentList() ([]*model.Agent, error) + // + // CreateAgent(*model.Agent) error + // + // UpdateAgent(*model.Agent) error + // + // DeleteAgent(*model.Agent) error - GetAgentAddr(string) (*model.Agent, error) - - GetAgentList() ([]*model.Agent, error) - - CreateAgent(*model.Agent) error - - UpdateAgent(*model.Agent) error - - DeleteAgent(*model.Agent) error + RegistryFind(*model.Repo, string) (*model.Registry, error) + RegistryList(*model.Repo) ([]*model.Registry, error) + RegistryCreate(*model.Registry) error + RegistryUpdate(*model.Registry) error + RegistryDelete(*model.Registry) error ProcLoad(int64) (*model.Proc, error) ProcFind(*model.Build, int) (*model.Proc, error) @@ -388,26 +394,26 @@ func UpdateBuild(c context.Context, build *model.Build) 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) -} +// 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) +// }