Merge pull request #2114 from bradrydzewski/rfc/sync

implement fast repository sync
This commit is contained in:
Brad Rydzewski 2017-07-16 14:34:08 -04:00 committed by GitHub
commit 0f693cb66d
149 changed files with 3092 additions and 22517 deletions

40
cache/cache.go vendored
View file

@ -1,40 +0,0 @@
package cache
//go:generate mockery -name Cache -output mock -case=underscore
import (
"time"
"github.com/koding/cache"
"golang.org/x/net/context"
)
type Cache interface {
Get(string) (interface{}, error)
Set(string, interface{}) error
Delete(string) error
}
func Get(c context.Context, key string) (interface{}, error) {
return FromContext(c).Get(key)
}
func Set(c context.Context, key string, value interface{}) error {
return FromContext(c).Set(key, value)
}
func Delete(c context.Context, key string) error {
return FromContext(c).Delete(key)
}
// Default creates an in-memory cache with the default
// 30 minute expiration period.
func Default() Cache {
return NewTTL(time.Minute * 30)
}
// NewTTL returns an in-memory cache with the specified
// ttl expiration period.
func NewTTL(t time.Duration) Cache {
return cache.NewMemoryWithTTL(t)
}

34
cache/cache_test.go vendored
View file

@ -1,34 +0,0 @@
package cache
import (
"testing"
"github.com/franela/goblin"
"github.com/gin-gonic/gin"
)
func TestCache(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Cache", func() {
var c *gin.Context
g.BeforeEach(func() {
c = new(gin.Context)
ToContext(c, Default())
})
g.It("Should set and get an item", func() {
Set(c, "foo", "bar")
v, e := Get(c, "foo")
g.Assert(v).Equal("bar")
g.Assert(e == nil).IsTrue()
})
g.It("Should return nil when item not found", func() {
v, e := Get(c, "foo")
g.Assert(v == nil).IsTrue()
g.Assert(e == nil).IsFalse()
})
})
}

23
cache/context.go vendored
View file

@ -1,23 +0,0 @@
package cache
import (
"golang.org/x/net/context"
)
const key = "cache"
// Setter defines a context that enables setting values.
type Setter interface {
Set(string, interface{})
}
// FromContext returns the Cache associated with this context.
func FromContext(c context.Context) Cache {
return c.Value(key).(Cache)
}
// ToContext adds the Cache to this context if it supports
// the Setter interface.
func ToContext(c Setter, cache Cache) {
c.Set(key, cache)
}

99
cache/helper.go vendored
View file

@ -1,99 +0,0 @@
package cache
import (
"fmt"
"github.com/drone/drone/model"
"github.com/drone/drone/remote"
"golang.org/x/net/context"
)
// GetPerms returns the user permissions repositories from the cache
// associated with the current repository.
func GetPerms(c context.Context, user *model.User, owner, name string) (*model.Perm, error) {
key := fmt.Sprintf("perms:%s:%s/%s",
user.Login,
owner,
name,
)
// if we fetch from the cache we can return immediately
val, err := Get(c, key)
if err == nil {
return val.(*model.Perm), nil
}
// else we try to grab from the remote system and
// populate our cache.
perm, err := remote.Perm(c, user, owner, name)
if err != nil {
return nil, err
}
Set(c, key, perm)
return perm, nil
}
// GetTeamPerms returns the user permissions from the cache
// associated with the current organization.
func GetTeamPerms(c context.Context, user *model.User, org string) (*model.Perm, error) {
key := fmt.Sprintf("perms:%s:%s",
user.Login,
org,
)
// if we fetch from the cache we can return immediately
val, err := Get(c, key)
if err == nil {
return val.(*model.Perm), nil
}
// else we try to grab from the remote system and
// populate our cache.
perm, err := remote.TeamPerm(c, user, org)
if err != nil {
return nil, err
}
Set(c, key, perm)
return perm, nil
}
// GetRepos returns the list of user repositories from the cache
// associated with the current context.
func GetRepos(c context.Context, user *model.User) ([]*model.RepoLite, error) {
key := fmt.Sprintf("repos:%s",
user.Login,
)
// if we fetch from the cache we can return immediately
val, err := Get(c, key)
if err == nil {
return val.([]*model.RepoLite), nil
}
// else we try to grab from the remote system and
// populate our cache.
repos, err := remote.Repos(c, user)
if err != nil {
return nil, err
}
Set(c, key, repos)
return repos, nil
}
// GetRepoMap returns the list of user repositories from the cache
// associated with the current context in a map structure.
func GetRepoMap(c context.Context, user *model.User) (map[string]bool, error) {
repos, err := GetRepos(c, user)
if err != nil {
return nil, err
}
repom := map[string]bool{}
for _, repo := range repos {
repom[repo.FullName] = true
}
return repom, nil
}
// DeleteRepos evicts the cached user repositories from the cache associated
// with the current context.
func DeleteRepos(c context.Context, user *model.User) error {
key := fmt.Sprintf("repos:%s",
user.Login,
)
return Delete(c, key)
}

115
cache/helper_test.go vendored
View file

@ -1,115 +0,0 @@
package cache
import (
"errors"
"fmt"
"testing"
"github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/drone/drone/remote/mock"
"github.com/franela/goblin"
"github.com/gin-gonic/gin"
)
func TestHelper(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Cache helpers", func() {
var c *gin.Context
var r *mock.Remote
g.BeforeEach(func() {
c = new(gin.Context)
ToContext(c, Default())
r = new(mock.Remote)
remote.ToContext(c, r)
})
g.It("Should get permissions from remote", func() {
r.On("Perm", fakeUser, fakeRepo.Owner, fakeRepo.Name).Return(fakePerm, nil).Once()
p, err := GetPerms(c, fakeUser, fakeRepo.Owner, fakeRepo.Name)
g.Assert(p).Equal(fakePerm)
g.Assert(err).Equal(nil)
})
g.It("Should get permissions from cache", func() {
key := fmt.Sprintf("perms:%s:%s/%s",
fakeUser.Login,
fakeRepo.Owner,
fakeRepo.Name,
)
Set(c, key, fakePerm)
r.On("Perm", fakeUser, fakeRepo.Owner, fakeRepo.Name).Return(nil, fakeErr).Once()
p, err := GetPerms(c, fakeUser, fakeRepo.Owner, fakeRepo.Name)
g.Assert(p).Equal(fakePerm)
g.Assert(err).Equal(nil)
})
g.It("Should get permissions error", func() {
r.On("Perm", fakeUser, fakeRepo.Owner, fakeRepo.Name).Return(nil, fakeErr).Once()
p, err := GetPerms(c, fakeUser, fakeRepo.Owner, fakeRepo.Name)
g.Assert(p == nil).IsTrue()
g.Assert(err).Equal(fakeErr)
})
g.It("Should set and get repos", func() {
r.On("Repos", fakeUser).Return(fakeRepos, nil).Once()
p, err := GetRepos(c, fakeUser)
g.Assert(p).Equal(fakeRepos)
g.Assert(err).Equal(nil)
})
g.It("Should get repos", func() {
key := fmt.Sprintf("repos:%s",
fakeUser.Login,
)
Set(c, key, fakeRepos)
r.On("Repos", fakeUser).Return(nil, fakeErr).Once()
p, err := GetRepos(c, fakeUser)
g.Assert(p).Equal(fakeRepos)
g.Assert(err).Equal(nil)
})
g.It("Should get repos error", func() {
r.On("Repos", fakeUser).Return(nil, fakeErr).Once()
p, err := GetRepos(c, fakeUser)
g.Assert(p == nil).IsTrue()
g.Assert(err).Equal(fakeErr)
})
g.It("Should evict repos", func() {
key := fmt.Sprintf("repos:%s",
fakeUser.Login,
)
Set(c, key, fakeRepos)
repos, err := Get(c, key)
g.Assert(repos != nil).IsTrue()
g.Assert(err == nil).IsTrue()
DeleteRepos(c, fakeUser)
repos, err = Get(c, key)
g.Assert(repos == nil).IsTrue()
})
})
}
var (
fakeErr = errors.New("Not Found")
fakeUser = &model.User{Login: "octocat"}
fakePerm = &model.Perm{true, true, true}
fakeRepo = &model.RepoLite{Owner: "octocat", Name: "Hello-World"}
fakeRepos = []*model.RepoLite{
{Owner: "octocat", Name: "Hello-World"},
{Owner: "octocat", Name: "hello-world"},
{Owner: "octocat", Name: "Spoon-Knife"},
}
)

View file

@ -80,12 +80,6 @@ var flags = []cli.Flag{
Name: "open", Name: "open",
Usage: "open user registration", Usage: "open user registration",
}, },
cli.DurationFlag{
EnvVar: "DRONE_CACHE_TTL",
Name: "cache-ttl",
Usage: "cache duration",
Value: time.Minute * 15,
},
cli.StringSliceFlag{ cli.StringSliceFlag{
EnvVar: "DRONE_ESCALATE", EnvVar: "DRONE_ESCALATE",
Name: "escalate", Name: "escalate",
@ -415,7 +409,6 @@ func server(c *cli.Context) error {
ginrus.Ginrus(logrus.StandardLogger(), time.RFC3339, true), ginrus.Ginrus(logrus.StandardLogger(), time.RFC3339, true),
middleware.Version, middleware.Version,
middleware.Config(c), middleware.Config(c),
middleware.Cache(c),
middleware.Store(c, store_), middleware.Store(c, store_),
middleware.Remote(remote_), middleware.Remote(remote_),
) )

View file

@ -1,7 +1,21 @@
package model package model
type Perm struct { // PermStore persists repository permissions information to storage.
Pull bool `json:"pull"` type PermStore interface {
Push bool `json:"push"` PermFind(user *User, repo *Repo) (*Perm, error)
Admin bool `json:"admin"` PermUpsert(perm *Perm) error
PermBatch(perms []*Perm) error
PermDelete(perm *Perm) error
PermFlush(user *User, before int64) error
}
// Perm defines a repository permission for an individual user.
type Perm struct {
UserID int64 `json:"-" meddler:"perm_user_id"`
RepoID int64 `json:"-" meddler:"perm_repo_id"`
Repo string `json:"-" meddler:"-"`
Pull bool `json:"pull" meddler:"perm_pull"`
Push bool `json:"push" meddler:"perm_push"`
Admin bool `json:"admin" meddler:"perm_admin"`
Synced int64 `json:"synced" meddler:"perm_synced"`
} }

View file

@ -27,6 +27,7 @@ type Repo struct {
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"` IsGated bool `json:"gated" meddler:"repo_gated"`
IsActive bool `json:"active,omitempty" meddler:"repo_active"`
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"`
@ -34,6 +35,7 @@ type Repo struct {
Counter int `json:"last_build" meddler:"repo_counter"` Counter int `json:"last_build" meddler:"repo_counter"`
Config string `json:"config_file" meddler:"repo_config_path"` Config string `json:"config_file" meddler:"repo_config_path"`
Hash string `json:"-" meddler:"repo_hash"` Hash string `json:"-" meddler:"repo_hash"`
Perm *Perm `json:"-" meddler:"-"`
} }
// RepoPatch represents a repository patch object. // RepoPatch represents a repository patch object.

View file

@ -34,6 +34,9 @@ type User struct {
// Activate indicates the user is active in the system. // Activate indicates the user is active in the system.
Active bool `json:"active" meddler:"user_active"` Active bool `json:"active" meddler:"user_active"`
// Synced is the timestamp when the user was synced with the remote system.
Synced int64 `json:"synced" meddler:"user_synced"`
// Admin indicates the user is a system administrator. // Admin indicates the user is a system administrator.
// //
// NOTE: This is sourced from the DRONE_ADMINS environment variable and is no // NOTE: This is sourced from the DRONE_ADMINS environment variable and is no

View file

@ -114,11 +114,6 @@ func (c *config) Teams(u *model.User) ([]*model.Team, error) {
return convertTeamList(resp.Values), nil return convertTeamList(resp.Values), nil
} }
// TeamPerm is not supported by the Bitbucket driver.
func (c *config) TeamPerm(u *model.User, org string) (*model.Perm, error) {
return nil, nil
}
// Repo returns the named Bitbucket repository. // Repo returns the named Bitbucket repository.
func (c *config) Repo(u *model.User, owner, name string) (*model.Repo, error) { func (c *config) Repo(u *model.User, owner, name string) (*model.Repo, error) {
repo, err := c.newClient(u).FindRepo(owner, name) repo, err := c.newClient(u).FindRepo(owner, name)
@ -130,10 +125,10 @@ func (c *config) Repo(u *model.User, owner, name string) (*model.Repo, error) {
// Repos returns a list of all repositories for Bitbucket account, including // Repos returns a list of all repositories for Bitbucket account, including
// organization repositories. // organization repositories.
func (c *config) Repos(u *model.User) ([]*model.RepoLite, error) { func (c *config) Repos(u *model.User) ([]*model.Repo, error) {
client := c.newClient(u) client := c.newClient(u)
var all []*model.RepoLite var all []*model.Repo
accounts := []string{u.Login} accounts := []string{u.Login}
resp, err := client.ListTeams(&internal.ListTeamOpts{ resp, err := client.ListTeams(&internal.ListTeamOpts{
@ -153,7 +148,7 @@ func (c *config) Repos(u *model.User) ([]*model.RepoLite, error) {
return all, err return all, err
} }
for _, repo := range repos { for _, repo := range repos {
all = append(all, convertRepoLite(repo)) all = append(all, convertRepo(repo))
} }
} }
return all, nil return all, nil

View file

@ -110,17 +110,6 @@ func cloneLink(repo *internal.Repo) string {
return clone return clone
} }
// convertRepoLite is a helper function used to convert a Bitbucket repository
// structure to the simplified Drone repository structure.
func convertRepoLite(from *internal.Repo) *model.RepoLite {
return &model.RepoLite{
Owner: strings.Split(from.FullName, "/")[0],
Name: strings.Split(from.FullName, "/")[1],
FullName: from.FullName,
Avatar: from.Owner.Links.Avatar.Href,
}
}
// convertUser is a helper function used to convert a Bitbucket user account // convertUser is a helper function used to convert a Bitbucket user account
// structure to the Drone User structure. // structure to the Drone User structure.
func convertUser(from *internal.Account, token *oauth2.Token) *model.User { func convertUser(from *internal.Account, token *oauth2.Token) *model.User {

View file

@ -49,18 +49,6 @@ func Test_helper(t *testing.T) {
g.Assert(convertDesc(model.StatusError)).Equal(descError) g.Assert(convertDesc(model.StatusError)).Equal(descError)
}) })
g.It("should convert repository lite", func() {
from := &internal.Repo{}
from.FullName = "octocat/hello-world"
from.Owner.Links.Avatar.Href = "http://..."
to := convertRepoLite(from)
g.Assert(to.Avatar).Equal(from.Owner.Links.Avatar.Href)
g.Assert(to.FullName).Equal(from.FullName)
g.Assert(to.Owner).Equal("octocat")
g.Assert(to.Name).Equal("hello-world")
})
g.It("should convert repository", func() { g.It("should convert repository", func() {
from := &internal.Repo{ from := &internal.Repo{
FullName: "octocat/hello-world", FullName: "octocat/hello-world",

View file

@ -140,14 +140,14 @@ func (c *Config) Repo(u *model.User, owner, name string) (*model.Repo, error) {
return convertRepo(repo), nil return convertRepo(repo), nil
} }
func (c *Config) Repos(u *model.User) ([]*model.RepoLite, error) { func (c *Config) Repos(u *model.User) ([]*model.Repo, error) {
repos, err := internal.NewClientWithToken(c.URL, c.Consumer, u.Token).FindRepos() repos, err := internal.NewClientWithToken(c.URL, c.Consumer, u.Token).FindRepos()
if err != nil { if err != nil {
return nil, err return nil, err
} }
var all []*model.RepoLite var all []*model.Repo
for _, repo := range repos { for _, repo := range repos {
all = append(all, convertRepoLite(repo)) all = append(all, convertRepo(repo))
} }
return all, nil return all, nil
@ -233,7 +233,7 @@ func CreateConsumer(URL string, ConsumerKey string, PrivateKey *rsa.PrivateKey)
consumer.HttpClient = &http.Client{ consumer.HttpClient = &http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,
}, },
} }
return consumer return consumer

View file

@ -86,19 +86,6 @@ func convertRepo(from *internal.Repo) *model.Repo {
} }
// convertRepoLite is a helper function used to convert a Bitbucket repository
// structure to the simplified Drone repository structure.
func convertRepoLite(from *internal.Repo) *model.RepoLite {
return &model.RepoLite{
Owner: from.Project.Key,
Name: from.Slug,
FullName: from.Project.Key + "/" + from.Slug,
//TODO: find the avatar for the repo
//Avatar: might need another ws call?
}
}
// convertPushHook is a helper function used to convert a Bitbucket push // convertPushHook is a helper function used to convert a Bitbucket push
// hook to the Drone build struct holding commit information. // hook to the Drone build struct holding commit information.
func convertPushHook(hook *internal.PostHook, baseURL string) *model.Build { func convertPushHook(hook *internal.PostHook, baseURL string) *model.Build {

View file

@ -1,11 +1,12 @@
package bitbucketserver package bitbucketserver
import ( import (
"testing"
"github.com/drone/drone/model" "github.com/drone/drone/model"
"github.com/drone/drone/remote/bitbucketserver/internal" "github.com/drone/drone/remote/bitbucketserver/internal"
"github.com/franela/goblin" "github.com/franela/goblin"
"github.com/mrjones/oauth" "github.com/mrjones/oauth"
"testing"
) )
func Test_helper(t *testing.T) { func Test_helper(t *testing.T) {
@ -13,17 +14,6 @@ func Test_helper(t *testing.T) {
g := goblin.Goblin(t) g := goblin.Goblin(t)
g.Describe("Bitbucket Server converter", func() { g.Describe("Bitbucket Server converter", func() {
g.It("should convert repository lite", func() {
from := &internal.Repo{}
from.Project.Key = "octocat"
from.Slug = "hello-world"
to := convertRepoLite(from)
g.Assert(to.FullName).Equal("octocat/hello-world")
g.Assert(to.Owner).Equal("octocat")
g.Assert(to.Name).Equal("hello-world")
})
g.It("should convert repository", func() { g.It("should convert repository", func() {
from := &internal.Repo{ from := &internal.Repo{
Slug: "hello-world", Slug: "hello-world",

View file

@ -69,18 +69,13 @@ func (c *client) Teams(u *model.User) ([]*model.Team, error) {
return empty, nil return empty, nil
} }
// TeamPerm is not supported by the Gerrit driver.
func (c *client) TeamPerm(u *model.User, org string) (*model.Perm, error) {
return nil, nil
}
// Repo is not supported by the Gerrit driver. // Repo is not supported by the Gerrit driver.
func (c *client) Repo(u *model.User, owner, name string) (*model.Repo, error) { func (c *client) Repo(u *model.User, owner, name string) (*model.Repo, error) {
return nil, nil return nil, nil
} }
// Repos is not supported by the Gerrit driver. // Repos is not supported by the Gerrit driver.
func (c *client) Repos(u *model.User) ([]*model.RepoLite, error) { func (c *client) Repos(u *model.User) ([]*model.Repo, error) {
return nil, nil return nil, nil
} }

View file

@ -200,8 +200,8 @@ func (c *client) Repo(u *model.User, owner, name string) (*model.Repo, error) {
// Repos returns a list of all repositories for the Gitea account, including // Repos returns a list of all repositories for the Gitea account, including
// organization repositories. // organization repositories.
func (c *client) Repos(u *model.User) ([]*model.RepoLite, error) { func (c *client) Repos(u *model.User) ([]*model.Repo, error) {
repos := []*model.RepoLite{} repos := []*model.Repo{}
client := c.newClientToken(u.Token) client := c.newClientToken(u.Token)
all, err := client.ListMyRepos() all, err := client.ListMyRepos()
@ -210,7 +210,7 @@ func (c *client) Repos(u *model.User) ([]*model.RepoLite, error) {
} }
for _, repo := range all { for _, repo := range all {
repos = append(repos, toRepoLite(repo)) repos = append(repos, toRepo(repo))
} }
return repos, err return repos, err
} }

View file

@ -12,21 +12,6 @@ import (
"github.com/drone/drone/model" "github.com/drone/drone/model"
) )
// helper function that converts a Gitea repository to a Drone repository.
func toRepoLite(from *gitea.Repository) *model.RepoLite {
name := strings.Split(from.FullName, "/")[1]
avatar := expandAvatar(
from.HTMLURL,
from.Owner.AvatarURL,
)
return &model.RepoLite{
Name: name,
Owner: from.Owner.UserName,
FullName: from.FullName,
Avatar: avatar,
}
}
// helper function that converts a Gitea repository to a Drone repository. // helper function that converts a Gitea repository to a Drone repository.
func toRepo(from *gitea.Repository) *model.Repo { func toRepo(from *gitea.Repository) *model.Repo {
name := strings.Split(from.FullName, "/")[1] name := strings.Split(from.FullName, "/")[1]

View file

@ -177,21 +177,6 @@ func Test_parse(t *testing.T) {
g.Assert(repo.IsPrivate).Equal(from.Private) g.Assert(repo.IsPrivate).Equal(from.Private)
}) })
g.It("Should return a RepoLite struct from a Gitea Repo", func() {
from := gitea.Repository{
FullName: "gophers/hello-world",
Owner: &gitea.User{
UserName: "gordon",
AvatarURL: "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87",
},
}
repo := toRepoLite(&from)
g.Assert(repo.FullName).Equal(from.FullName)
g.Assert(repo.Owner).Equal(from.Owner.UserName)
g.Assert(repo.Name).Equal("hello-world")
g.Assert(repo.Avatar).Equal(from.Owner.AvatarURL)
})
g.It("Should correct a malformed avatar url", func() { g.It("Should correct a malformed avatar url", func() {
var urls = []struct { var urls = []struct {

View file

@ -80,6 +80,7 @@ func convertRepo(from *github.Repository, private bool) *model.Repo {
Avatar: *from.Owner.AvatarURL, Avatar: *from.Owner.AvatarURL,
Kind: model.RepoGit, Kind: model.RepoGit,
Branch: defaultBranch, Branch: defaultBranch,
Perm: convertPerm(from),
} }
if from.DefaultBranch != nil { if from.DefaultBranch != nil {
repo.Branch = *from.DefaultBranch repo.Branch = *from.DefaultBranch
@ -114,24 +115,24 @@ func convertTeamPerm(from *github.Membership) *model.Perm {
// convertRepoList is a helper function used to convert a GitHub repository // convertRepoList is a helper function used to convert a GitHub repository
// list to the common Drone repository structure. // list to the common Drone repository structure.
func convertRepoList(from []github.Repository) []*model.RepoLite { func convertRepoList(from []github.Repository, private bool) []*model.Repo {
var repos []*model.RepoLite var repos []*model.Repo
for _, repo := range from { for _, repo := range from {
repos = append(repos, convertRepoLite(repo)) repos = append(repos, convertRepo(&repo, private))
} }
return repos return repos
} }
// convertRepoLite is a helper function used to convert a GitHub repository // // convertRepoLite is a helper function used to convert a GitHub repository
// structure to the common Drone repository structure. // // structure to the common Drone repository structure.
func convertRepoLite(from github.Repository) *model.RepoLite { // func convertRepoLite(from github.Repository) *model.RepoLite {
return &model.RepoLite{ // return &model.RepoLite{
Owner: *from.Owner.Login, // Owner: *from.Owner.Login,
Name: *from.Name, // Name: *from.Name,
FullName: *from.FullName, // FullName: *from.FullName,
Avatar: *from.Owner.AvatarURL, // Avatar: *from.Owner.AvatarURL,
} // }
} // }
// convertTeamList is a helper function used to convert a GitHub team list to // convertTeamList is a helper function used to convert a GitHub team list to
// the common Drone repository structure. // the common Drone repository structure.

View file

@ -50,36 +50,27 @@ func Test_helper(t *testing.T) {
g.Assert(convertDesc(model.StatusError)).Equal(descError) g.Assert(convertDesc(model.StatusError)).Equal(descError)
}) })
g.It("should convert repository lite", func() {
from := github.Repository{
FullName: github.String("octocat/hello-world"),
Name: github.String("hello-world"),
Owner: &github.User{
AvatarURL: github.String("http://..."),
Login: github.String("octocat"),
},
}
to := convertRepoLite(from)
g.Assert(to.Avatar).Equal("http://...")
g.Assert(to.FullName).Equal("octocat/hello-world")
g.Assert(to.Owner).Equal("octocat")
g.Assert(to.Name).Equal("hello-world")
})
g.It("should convert repository list", func() { g.It("should convert repository list", func() {
from := []github.Repository{ from := []github.Repository{
{ {
Private: github.Bool(false),
FullName: github.String("octocat/hello-world"), FullName: github.String("octocat/hello-world"),
Name: github.String("hello-world"), Name: github.String("hello-world"),
Owner: &github.User{ Owner: &github.User{
AvatarURL: github.String("http://..."), AvatarURL: github.String("http://..."),
Login: github.String("octocat"), Login: github.String("octocat"),
}, },
HTMLURL: github.String("https://github.com/octocat/hello-world"),
CloneURL: github.String("https://github.com/octocat/hello-world.git"),
Permissions: &map[string]bool{
"push": true,
"pull": true,
"admin": true,
},
}, },
} }
to := convertRepoList(from) to := convertRepoList(from, false)
g.Assert(to[0].Avatar).Equal("http://...") g.Assert(to[0].Avatar).Equal("http://...")
g.Assert(to[0].FullName).Equal("octocat/hello-world") g.Assert(to[0].FullName).Equal("octocat/hello-world")
g.Assert(to[0].Owner).Equal("octocat") g.Assert(to[0].Owner).Equal("octocat")
@ -98,6 +89,11 @@ func Test_helper(t *testing.T) {
AvatarURL: github.String("http://..."), AvatarURL: github.String("http://..."),
Login: github.String("octocat"), Login: github.String("octocat"),
}, },
Permissions: &map[string]bool{
"push": true,
"pull": true,
"admin": true,
},
} }
to := convertRepo(&from, false) to := convertRepo(&from, false)

View file

@ -168,16 +168,6 @@ func (c *client) Teams(u *model.User) ([]*model.Team, error) {
return teams, nil return teams, nil
} }
// TeamPerm returns the user permissions for the named GitHub organization.
func (c *client) TeamPerm(u *model.User, org string) (*model.Perm, error) {
client := c.newClientToken(u.Token)
membership, _, err := client.Organizations.GetOrgMembership(u.Login, org)
if err != nil {
return nil, err
}
return convertTeamPerm(membership), nil
}
// Repo returns the named GitHub repository. // Repo returns the named GitHub repository.
func (c *client) Repo(u *model.User, owner, name string) (*model.Repo, error) { func (c *client) Repo(u *model.User, owner, name string) (*model.Repo, error) {
client := c.newClientToken(u.Token) client := c.newClientToken(u.Token)
@ -190,20 +180,20 @@ func (c *client) Repo(u *model.User, owner, name string) (*model.Repo, error) {
// Repos returns a list of all repositories for GitHub account, including // Repos returns a list of all repositories for GitHub account, including
// organization repositories. // organization repositories.
func (c *client) Repos(u *model.User) ([]*model.RepoLite, error) { func (c *client) Repos(u *model.User) ([]*model.Repo, error) {
client := c.newClientToken(u.Token) client := c.newClientToken(u.Token)
opts := new(github.RepositoryListOptions) opts := new(github.RepositoryListOptions)
opts.PerPage = 100 opts.PerPage = 100
opts.Page = 1 opts.Page = 1
var repos []*model.RepoLite var repos []*model.Repo
for opts.Page > 0 { for opts.Page > 0 {
list, resp, err := client.Repositories.List("", opts) list, resp, err := client.Repositories.List("", opts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
repos = append(repos, convertRepoList(list)...) repos = append(repos, convertRepoList(list, c.PrivateMode)...)
opts.Page = resp.NextPage opts.Page = resp.NextPage
} }
return repos, nil return repos, nil

View file

@ -110,23 +110,6 @@ func Test_github(t *testing.T) {
}) })
}) })
g.Describe("Requesting organization permissions", func() {
g.It("Should return the permission details of an admin", func() {
perm, err := c.TeamPerm(fakeUser, "octocat")
g.Assert(err == nil).IsTrue()
g.Assert(perm.Admin).IsTrue()
})
g.It("Should return the permission details of a member", func() {
perm, err := c.TeamPerm(fakeUser, "github")
g.Assert(err == nil).IsTrue()
g.Assert(perm.Admin).IsFalse()
})
g.It("Should handle a not found error", func() {
_, err := c.TeamPerm(fakeUser, "org_not_found")
g.Assert(err != nil).IsTrue()
})
})
g.It("Should return a user repository list") g.It("Should return a user repository list")
g.It("Should return a user team list") g.It("Should return a user team list")

View file

@ -203,11 +203,6 @@ func (g *Gitlab) Teams(u *model.User) ([]*model.Team, error) {
return teams, nil return teams, nil
} }
// TeamPerm is not supported by the Gitlab driver.
func (g *Gitlab) TeamPerm(u *model.User, org string) (*model.Perm, error) {
return nil, nil
}
// Repo fetches the named repository from the remote system. // Repo fetches the named repository from the remote system.
func (g *Gitlab) Repo(u *model.User, owner, name string) (*model.Repo, error) { func (g *Gitlab) Repo(u *model.User, owner, name string) (*model.Repo, error) {
client := NewClient(g.URL, u.Token, g.SkipVerify) client := NewClient(g.URL, u.Token, g.SkipVerify)
@ -248,32 +243,40 @@ func (g *Gitlab) Repo(u *model.User, owner, name string) (*model.Repo, error) {
} }
// Repos fetches a list of repos from the remote system. // Repos fetches a list of repos from the remote system.
func (g *Gitlab) Repos(u *model.User) ([]*model.RepoLite, error) { func (g *Gitlab) Repos(u *model.User) ([]*model.Repo, error) {
client := NewClient(g.URL, u.Token, g.SkipVerify) client := NewClient(g.URL, u.Token, g.SkipVerify)
var repos = []*model.RepoLite{} var repos = []*model.Repo{}
all, err := client.AllProjects(g.HideArchives) all, err := client.AllProjects(g.HideArchives)
if err != nil { if err != nil {
return repos, err return repos, err
} }
for _, repo := range all { for _, repo_ := range all {
var parts = strings.Split(repo.PathWithNamespace, "/") var parts = strings.Split(repo_.PathWithNamespace, "/")
var owner = parts[0] var owner = parts[0]
var name = parts[1] var name = parts[1]
var avatar = repo.AvatarUrl
if len(avatar) != 0 && !strings.HasPrefix(avatar, "http") { repo := &model.Repo{}
avatar = fmt.Sprintf("%s/%s", g.URL, avatar) repo.Owner = owner
repo.Name = name
repo.FullName = repo_.PathWithNamespace
repo.Link = repo_.Url
repo.Clone = repo_.HttpRepoUrl
repo.Branch = "master"
if repo_.DefaultBranch != "" {
repo.Branch = repo_.DefaultBranch
} }
repos = append(repos, &model.RepoLite{ if g.PrivateMode {
Owner: owner, repo.IsPrivate = true
Name: name, } else {
FullName: repo.PathWithNamespace, repo.IsPrivate = !repo_.Public
Avatar: avatar, }
})
repos = append(repos, repo)
} }
return repos, err return repos, err
@ -295,7 +298,7 @@ func (g *Gitlab) Perm(u *model.User, owner, name string) (*model.Perm, error) {
// repo owner is granted full access // repo owner is granted full access
if repo.Owner != nil && repo.Owner.Username == u.Login { if repo.Owner != nil && repo.Owner.Username == u.Login {
return &model.Perm{true, true, true}, nil return &model.Perm{Push: true, Pull: true, Admin: true}, nil
} }
// check permission for current user // check permission for current user

View file

@ -127,11 +127,6 @@ func (c *client) Teams(u *model.User) ([]*model.Team, error) {
return teams, nil return teams, nil
} }
// TeamPerm is not supported by the Gogs driver.
func (c *client) TeamPerm(u *model.User, org string) (*model.Perm, error) {
return nil, nil
}
// Repo returns the named Gogs repository. // Repo returns the named Gogs repository.
func (c *client) Repo(u *model.User, owner, name string) (*model.Repo, error) { func (c *client) Repo(u *model.User, owner, name string) (*model.Repo, error) {
client := c.newClientToken(u.Token) client := c.newClientToken(u.Token)
@ -147,8 +142,8 @@ func (c *client) Repo(u *model.User, owner, name string) (*model.Repo, error) {
// Repos returns a list of all repositories for the Gogs account, including // Repos returns a list of all repositories for the Gogs account, including
// organization repositories. // organization repositories.
func (c *client) Repos(u *model.User) ([]*model.RepoLite, error) { func (c *client) Repos(u *model.User) ([]*model.Repo, error) {
repos := []*model.RepoLite{} repos := []*model.Repo{}
client := c.newClientToken(u.Token) client := c.newClientToken(u.Token)
all, err := client.ListMyRepos() all, err := client.ListMyRepos()
@ -157,7 +152,7 @@ func (c *client) Repos(u *model.User) ([]*model.RepoLite, error) {
} }
for _, repo := range all { for _, repo := range all {
repos = append(repos, toRepoLite(repo)) repos = append(repos, toRepo(repo))
} }
return repos, err return repos, err
} }

View file

@ -12,21 +12,6 @@ import (
"github.com/gogits/go-gogs-client" "github.com/gogits/go-gogs-client"
) )
// helper function that converts a Gogs repository to a Drone repository.
func toRepoLite(from *gogs.Repository) *model.RepoLite {
name := strings.Split(from.FullName, "/")[1]
avatar := expandAvatar(
from.HtmlUrl,
from.Owner.AvatarUrl,
)
return &model.RepoLite{
Name: name,
Owner: from.Owner.UserName,
FullName: from.FullName,
Avatar: avatar,
}
}
// helper function that converts a Gogs repository to a Drone repository. // helper function that converts a Gogs repository to a Drone repository.
func toRepo(from *gogs.Repository) *model.Repo { func toRepo(from *gogs.Repository) *model.Repo {
name := strings.Split(from.FullName, "/")[1] name := strings.Split(from.FullName, "/")[1]

View file

@ -176,21 +176,6 @@ func Test_parse(t *testing.T) {
g.Assert(repo.IsPrivate).Equal(from.Private) g.Assert(repo.IsPrivate).Equal(from.Private)
}) })
g.It("Should return a RepoLite struct from a Gogs Repo", func() {
from := gogs.Repository{
FullName: "gophers/hello-world",
Owner: gogs.User{
UserName: "gordon",
AvatarUrl: "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87",
},
}
repo := toRepoLite(&from)
g.Assert(repo.FullName).Equal(from.FullName)
g.Assert(repo.Owner).Equal(from.Owner.UserName)
g.Assert(repo.Name).Equal("hello-world")
g.Assert(repo.Avatar).Equal(from.Owner.AvatarUrl)
})
g.It("Should correct a malformed avatar url", func() { g.It("Should correct a malformed avatar url", func() {
var urls = []struct { var urls = []struct {

View file

@ -23,15 +23,11 @@ type Remote interface {
// Teams fetches a list of team memberships from the remote system. // Teams fetches a list of team memberships from the remote system.
Teams(u *model.User) ([]*model.Team, error) Teams(u *model.User) ([]*model.Team, error)
// TeamPerm fetches the named organization permissions from
// the remote system for the specified user.
TeamPerm(u *model.User, org string) (*model.Perm, error)
// Repo fetches the named repository from the remote system. // Repo fetches the named repository from the remote system.
Repo(u *model.User, owner, repo string) (*model.Repo, error) Repo(u *model.User, owner, repo string) (*model.Repo, error)
// Repos fetches a list of repos from the remote system. // Repos fetches a list of repos from the remote system.
Repos(u *model.User) ([]*model.RepoLite, error) Repos(u *model.User) ([]*model.Repo, error)
// Perm fetches the named repository permissions from // Perm fetches the named repository permissions from
// the remote system for the specified user. // the remote system for the specified user.
@ -89,19 +85,13 @@ func Teams(c context.Context, u *model.User) ([]*model.Team, error) {
return FromContext(c).Teams(u) return FromContext(c).Teams(u)
} }
// TeamPerm fetches the named organization permissions from
// the remote system for the specified user.
func TeamPerm(c context.Context, u *model.User, org string) (*model.Perm, error) {
return FromContext(c).TeamPerm(u, org)
}
// Repo fetches the named repository from the remote system. // Repo fetches the named repository from the remote system.
func Repo(c context.Context, u *model.User, owner, repo string) (*model.Repo, error) { func Repo(c context.Context, u *model.User, owner, repo string) (*model.Repo, error) {
return FromContext(c).Repo(u, owner, repo) return FromContext(c).Repo(u, owner, repo)
} }
// Repos fetches a list of repos from the remote system. // Repos fetches a list of repos from the remote system.
func Repos(c context.Context, u *model.User) ([]*model.RepoLite, error) { func Repos(c context.Context, u *model.User) ([]*model.Repo, error) {
return FromContext(c).Repos(u) return FromContext(c).Repos(u)
} }

View file

@ -1,24 +0,0 @@
package middleware
import (
"github.com/drone/drone/cache"
"github.com/gin-gonic/gin"
"github.com/urfave/cli"
)
// Cache is a middleware function that initializes the Cache and attaches to
// the context of every http.Request.
func Cache(cli *cli.Context) gin.HandlerFunc {
v := setupCache(cli)
return func(c *gin.Context) {
cache.ToContext(c, v)
}
}
// helper function to create the cache from the CLI context.
func setupCache(c *cli.Context) cache.Cache {
return cache.NewTTL(
c.Duration("cache-ttl"),
)
}

View file

@ -2,9 +2,10 @@ package session
import ( import (
"net/http" "net/http"
"time"
"github.com/drone/drone/cache"
"github.com/drone/drone/model" "github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/drone/drone/store" "github.com/drone/drone/store"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
@ -92,10 +93,20 @@ func SetPerm() gin.HandlerFunc {
case user != nil: case user != nil:
var err error var err error
perm, err = cache.GetPerms(c, user, repo.Owner, repo.Name) perm, err = store.FromContext(c).PermFind(user, repo)
if err != nil { if err != nil {
log.Errorf("Error fetching permission for %s %s", log.Errorf("Error fetching permission for %s %s. %s",
user.Login, repo.FullName) user.Login, repo.FullName, err)
}
if time.Unix(perm.Synced, 0).Add(time.Hour).Before(time.Now()) {
perm, err = remote.FromContext(c).Perm(user, repo.Owner, repo.Name)
if err == nil {
log.Debugf("Synced user permission for %s %s", user.Login, repo.FullName)
perm.Repo = repo.FullName
perm.UserID = user.ID
perm.Synced = time.Now().Unix()
store.FromContext(c).PermUpsert(perm)
}
} }
} }

View file

@ -1,73 +0,0 @@
package session
import (
"github.com/drone/drone/cache"
"github.com/drone/drone/model"
log "github.com/Sirupsen/logrus"
"github.com/gin-gonic/gin"
)
func TeamPerm(c *gin.Context) *model.Perm {
user := User(c)
team := c.Param("team")
perm := &model.Perm{}
switch {
// if the user is not authenticated
case user == nil:
perm.Admin = false
perm.Pull = false
perm.Push = false
// if the user is a DRONE_ADMIN
case user.Admin:
perm.Admin = true
perm.Pull = true
perm.Push = true
// otherwise if the user is authenticated we should
// check the remote system to get the users permissiosn.
default:
log.Debugf("Fetching team permission for %s %s",
user.Login, team)
var err error
perm, err = cache.GetTeamPerms(c, user, team)
if err != nil {
// debug
log.Errorf("Error fetching team permission for %s %s",
user.Login, team)
perm.Admin = false
perm.Pull = false
perm.Push = false
}
}
if user != nil {
log.Debugf("%s granted %+v team permission to %s",
user.Login, perm, team)
} else {
log.Debugf("Guest granted %+v to %s", perm, team)
perm.Admin = false
perm.Pull = false
perm.Push = false
}
return perm
}
func MustTeamAdmin() gin.HandlerFunc {
return func(c *gin.Context) {
perm := TeamPerm(c)
if perm.Admin {
c.Next()
} else {
c.String(401, "User not authorized")
c.Abort()
}
}
}

View file

@ -1,95 +0,0 @@
package session
import (
"testing"
"github.com/drone/drone/cache"
"github.com/drone/drone/model"
"github.com/franela/goblin"
"github.com/gin-gonic/gin"
)
func TestTeamPerm(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("TeamPerm", func() {
var c *gin.Context
g.BeforeEach(func() {
c = new(gin.Context)
cache.ToContext(c, cache.Default())
})
g.It("Should set admin to false (user not logged in)", func() {
p := TeamPerm(c)
g.Assert(p.Admin).IsFalse("admin should be false")
})
g.It("Should set admin to true (user is DRONE_ADMIN)", func() {
// Set DRONE_ADMIN user
c.Set("user", fakeUserAdmin)
p := TeamPerm(c)
g.Assert(p.Admin).IsTrue("admin should be false")
})
g.It("Should set admin to false (user logged in, not owner of org)", func() {
// Set fake org
params := gin.Params{
gin.Param{
Key: "team",
Value: "test_org",
},
}
c.Params = params
// Set cache to show user does not Owner/Admin
cache.Set(c, "perms:octocat:test_org", fakeTeamPerm)
// Set User
c.Set("user", fakeUser)
p := TeamPerm(c)
g.Assert(p.Admin).IsFalse("admin should be false")
})
g.It("Should set admin to true (user logged in, owner of org)", func() {
// Set fake org
params := gin.Params{
gin.Param{
Key: "team",
Value: "test_org",
},
}
c.Params = params
// Set cache to show user is Owner/Admin
cache.Set(c, "perms:octocat:test_org", fakeTeamPermAdmin)
// Set User
c.Set("user", fakeUser)
p := TeamPerm(c)
g.Assert(p.Admin).IsTrue("admin should be true")
})
})
}
var (
fakeUserAdmin = &model.User{
Login: "octocatAdmin",
Token: "cfcd2084",
Admin: true,
}
fakeUser = &model.User{
Login: "octocat",
Token: "cfcd2084",
Admin: false,
}
fakeTeamPermAdmin = &model.Perm{
Admin: true,
}
fakeTeamPerm = &model.Perm{
Admin: false,
}
)

View file

@ -47,7 +47,6 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
user.GET("", server.GetSelf) user.GET("", server.GetSelf)
user.GET("/feed", server.GetFeed) user.GET("/feed", server.GetFeed)
user.GET("/repos", server.GetRepos) user.GET("/repos", server.GetRepos)
user.GET("/repos/remote", server.GetRemoteRepos)
user.POST("/token", server.PostToken) user.POST("/token", server.PostToken)
user.DELETE("/token", server.DeleteToken) user.DELETE("/token", server.DeleteToken)
} }
@ -62,46 +61,42 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
users.DELETE("/:login", server.DeleteUser) users.DELETE("/:login", server.DeleteUser)
} }
repos := e.Group("/api/repos/:owner/:name") repo := e.Group("/api/repos/:owner/:name")
{ {
repos.POST("", server.PostRepo) repo.Use(session.SetRepo())
repo.Use(session.SetPerm())
repo.Use(session.MustPull)
repo := repos.Group("") repo.POST("", session.MustRepoAdmin(), server.PostRepo)
{ repo.GET("", server.GetRepo)
repo.Use(session.SetRepo()) repo.GET("/builds", server.GetBuilds)
repo.Use(session.SetPerm()) repo.GET("/builds/:number", server.GetBuild)
repo.Use(session.MustPull) repo.GET("/logs/:number/:ppid/:proc", server.GetBuildLogs)
repo.GET("", server.GetRepo) // requires push permissions
repo.GET("/builds", server.GetBuilds) repo.GET("/secrets", session.MustPush, server.GetSecretList)
repo.GET("/builds/:number", server.GetBuild) repo.POST("/secrets", session.MustPush, server.PostSecret)
repo.GET("/logs/:number/:ppid/:proc", server.GetBuildLogs) repo.GET("/secrets/:secret", session.MustPush, server.GetSecret)
repo.PATCH("/secrets/:secret", session.MustPush, server.PatchSecret)
repo.DELETE("/secrets/:secret", session.MustPush, server.DeleteSecret)
// requires push permissions // requires push permissions
repo.GET("/secrets", session.MustPush, server.GetSecretList) repo.GET("/registry", session.MustPush, server.GetRegistryList)
repo.POST("/secrets", session.MustPush, server.PostSecret) repo.POST("/registry", session.MustPush, server.PostRegistry)
repo.GET("/secrets/:secret", session.MustPush, server.GetSecret) repo.GET("/registry/:registry", session.MustPush, server.GetRegistry)
repo.PATCH("/secrets/:secret", session.MustPush, server.PatchSecret) repo.PATCH("/registry/:registry", session.MustPush, server.PatchRegistry)
repo.DELETE("/secrets/:secret", session.MustPush, server.DeleteSecret) repo.DELETE("/registry/:registry", session.MustPush, server.DeleteRegistry)
// requires push permissions // requires admin permissions
repo.GET("/registry", session.MustPush, server.GetRegistryList) repo.PATCH("", session.MustRepoAdmin(), server.PatchRepo)
repo.POST("/registry", session.MustPush, server.PostRegistry) repo.DELETE("", session.MustRepoAdmin(), server.DeleteRepo)
repo.GET("/registry/:registry", session.MustPush, server.GetRegistry) repo.POST("/chown", session.MustRepoAdmin(), server.ChownRepo)
repo.PATCH("/registry/:registry", session.MustPush, server.PatchRegistry) repo.POST("/repair", session.MustRepoAdmin(), server.RepairRepo)
repo.DELETE("/registry/:registry", session.MustPush, server.DeleteRegistry)
// requires push permissions repo.POST("/builds/:number", session.MustPush, server.PostBuild)
repo.PATCH("", session.MustPush, server.PatchRepo) repo.POST("/builds/:number/approve", session.MustPush, server.PostApproval)
repo.DELETE("", session.MustRepoAdmin(), server.DeleteRepo) repo.POST("/builds/:number/decline", session.MustPush, server.PostDecline)
repo.POST("/chown", session.MustRepoAdmin(), server.ChownRepo) repo.DELETE("/builds/:number/:job", session.MustPush, server.DeleteBuild)
repo.POST("/repair", session.MustRepoAdmin(), server.RepairRepo)
repo.POST("/builds/:number", session.MustPush, server.PostBuild)
repo.POST("/builds/:number/approve", session.MustPush, server.PostApproval)
repo.POST("/builds/:number/decline", session.MustPush, server.PostDecline)
repo.DELETE("/builds/:number/:job", session.MustPush, server.DeleteBuild)
}
} }
badges := e.Group("/api/badges/:owner/:name") badges := e.Group("/api/badges/:owner/:name")

View file

@ -84,6 +84,11 @@ func PostHook(c *gin.Context) {
c.AbortWithError(404, err) c.AbortWithError(404, err)
return return
} }
if !repo.IsActive {
logrus.Errorf("ignoring hook. %s/%s is inactive.", tmprepo.Owner, tmprepo.Name)
c.AbortWithError(204, err)
return
}
// get the token and verify the hook is authorized // get the token and verify the hook is authorized
parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) { parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {

View file

@ -4,11 +4,11 @@ import (
"encoding/base32" "encoding/base32"
"fmt" "fmt"
"net/http" "net/http"
"strconv"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gorilla/securecookie" "github.com/gorilla/securecookie"
"github.com/drone/drone/cache"
"github.com/drone/drone/model" "github.com/drone/drone/model"
"github.com/drone/drone/remote" "github.com/drone/drone/remote"
"github.com/drone/drone/router/middleware/session" "github.com/drone/drone/router/middleware/session"
@ -20,54 +20,40 @@ import (
func PostRepo(c *gin.Context) { func PostRepo(c *gin.Context) {
remote := remote.FromContext(c) remote := remote.FromContext(c)
user := session.User(c) user := session.User(c)
owner := c.Param("owner") repo := session.Repo(c)
name := c.Param("name")
if user == nil { if repo.IsActive {
c.AbortWithStatus(403) c.String(409, "Repository is already active.")
return return
} }
r, err := remote.Repo(user, owner, name) repo.IsActive = true
if err != nil { repo.UserID = user.ID
c.String(404, err.Error()) if !repo.AllowPush && !repo.AllowPull && !repo.AllowDeploy && !repo.AllowTag {
return repo.AllowPush = true
repo.AllowPull = true
} }
m, err := cache.GetPerms(c, user, owner, name) if repo.Visibility == "" {
if err != nil { repo.Visibility = model.VisibilityPublic
c.String(404, err.Error()) if repo.IsPrivate {
return repo.Visibility = model.VisibilityPrivate
}
} }
if !m.Admin { if repo.Config == "" {
c.String(403, "Administrative access is required.") repo.Config = ".drone.yml"
return }
if repo.Timeout == 0 {
repo.Timeout = 60 // 1 hour default build time
}
if repo.Hash == "" {
repo.Hash = base32.StdEncoding.EncodeToString(
securecookie.GenerateRandomKey(32),
)
} }
// error if the repository already exists // creates the jwt token used to verify the repository
_, err = store.GetRepoOwnerName(c, owner, name) t := token.New(token.HookToken, repo.FullName)
if err == nil { sig, err := t.Sign(repo.Hash)
c.String(409, "Repository already exists.")
return
}
// set the repository owner to the
// currently authenticated user.
r.UserID = user.ID
r.AllowPush = true
r.AllowPull = true
r.Visibility = model.VisibilityPublic
r.Config = ".drone.yml"
r.Timeout = 60 // 1 hour default build time
r.Hash = base32.StdEncoding.EncodeToString(
securecookie.GenerateRandomKey(32),
)
if r.IsPrivate {
r.Visibility = model.VisibilityPrivate
}
// crates the jwt token used to verify the repository
t := token.New(token.HookToken, r.FullName)
sig, err := t.Sign(r.Hash)
if err != nil { if err != nil {
c.String(500, err.Error()) c.String(500, err.Error())
return return
@ -79,22 +65,19 @@ func PostRepo(c *gin.Context) {
sig, sig,
) )
// activate the repository before we make any err = remote.Activate(user, repo, link)
// local changes to the database.
err = remote.Activate(user, r, link)
if err != nil { if err != nil {
c.String(500, err.Error()) c.String(500, err.Error())
return return
} }
// persist the repository err = store.UpdateRepo(c, repo)
err = store.CreateRepo(c, r)
if err != nil { if err != nil {
c.String(500, err.Error()) c.String(500, err.Error())
return return
} }
c.JSON(200, r) c.JSON(200, repo)
} }
func PatchRepo(c *gin.Context) { func PatchRepo(c *gin.Context) {
@ -173,16 +156,28 @@ func GetRepo(c *gin.Context) {
} }
func DeleteRepo(c *gin.Context) { func DeleteRepo(c *gin.Context) {
remove, _ := strconv.ParseBool(c.Query("remove"))
remote := remote.FromContext(c) remote := remote.FromContext(c)
repo := session.Repo(c) repo := session.Repo(c)
user := session.User(c) user := session.User(c)
err := store.DeleteRepo(c, repo) repo.IsActive = false
repo.UserID = 0
err := store.UpdateRepo(c, repo)
if err != nil { if err != nil {
c.AbortWithError(http.StatusInternalServerError, err) c.AbortWithError(http.StatusInternalServerError, err)
return return
} }
if remove {
err := store.DeleteRepo(c, repo)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
}
remote.Deactivate(user, repo, httputil.GetURL(c.Request)) remote.Deactivate(user, repo, httputil.GetURL(c.Request))
c.Writer.WriteHeader(http.StatusOK) c.Writer.WriteHeader(http.StatusOK)
} }

View file

@ -9,7 +9,6 @@ import (
"github.com/cncd/logging" "github.com/cncd/logging"
"github.com/cncd/pubsub" "github.com/cncd/pubsub"
"github.com/drone/drone/cache"
"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/drone/drone/store"
@ -149,7 +148,10 @@ func EventStream(c *gin.Context) {
user := session.User(c) user := session.User(c)
repo := map[string]bool{} repo := map[string]bool{}
if user != nil { if user != nil {
repo, _ = cache.GetRepoMap(c, user) repos, _ := store.FromContext(c).RepoList(user)
for _, r := range repos {
repo[r.FullName] = true
}
} }
ticker := time.NewTicker(pingPeriod) ticker := time.NewTicker(pingPeriod)

66
server/sync.go Normal file
View file

@ -0,0 +1,66 @@
package server
import (
"time"
"github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/drone/drone/store"
)
// Syncer synces the user repository and permissions.
type Syncer interface {
Sync(user *model.User) error
}
type syncer struct {
remote remote.Remote
store store.Store
perms model.PermStore
}
func (s *syncer) Sync(user *model.User) error {
unix := time.Now().Unix() - 1 // force immediate expiration
repos, err := s.remote.Repos(user)
if err != nil {
return err
}
var perms []*model.Perm
for _, repo := range repos {
perm := model.Perm{
UserID: user.ID,
Repo: repo.FullName,
Pull: true,
Synced: unix,
}
if repo.Perm != nil {
perm.Push = repo.Perm.Push
perm.Admin = repo.Perm.Admin
}
perms = append(perms, &perm)
}
err = s.store.RepoBatch(repos)
if err != nil {
return err
}
err = s.store.PermBatch(perms)
if err != nil {
return err
}
// this is here as a precaution. I want to make sure that if an api
// call to the version control system fails and (for some reason) returns
// an empty list, we don't wipe out the user repository permissions.
//
// the side-effect of this code is that a user with 1 repository whose
// access is removed will still display in the feed, but they will not
// be able to access the actual repository data.
if len(repos) == 0 {
return nil
}
return s.perms.PermFlush(user, unix)
}

View file

@ -5,6 +5,9 @@
<meta name="author" content="bradrydzewski"> <meta name="author" content="bradrydzewski">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes"> <meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<link rel="shortcut icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="shortcut icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<title></title> <title></title>
<script> <script>
window.ENV = {}; window.ENV = {};

View file

@ -94,6 +94,9 @@ var indexpolymer = `<!DOCTYPE html>
<meta name="author" content="bradrydzewski"> <meta name="author" content="bradrydzewski">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes"> <meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<link rel="shortcut icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="shortcut icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<title></title> <title></title>
<script> <script>
window.ENV = {}; window.ENV = {};

View file

@ -23,12 +23,13 @@ type website struct {
// NewWebsite returns a new website loader. // NewWebsite returns a new website loader.
func NewWebsite() Website { func NewWebsite() Website {
// TODO change to DRONE_WEB_PATH and add DRONE_WEB_PROXY
path := os.Getenv("DRONE_WWW") path := os.Getenv("DRONE_WWW")
if path != "" { if path != "" {
return NewLocalWebsite(path) return NewLocalWebsite(path)
} }
return &website{ return &website{
fs: http.FileServer(dist.AssetFS()), fs: http.FileServer(dist.New()),
} }
} }
@ -38,14 +39,9 @@ func (w *website) Page(rw http.ResponseWriter, r *http.Request, u *model.User) {
path := r.URL.Path path := r.URL.Path
switch path { switch path {
case "/login/form":
params := map[string]interface{}{}
template.T.ExecuteTemplate(rw, "login.html", params)
case "/login": case "/login":
if err := r.FormValue("error"); err != "" { if err := r.FormValue("error"); err != "" {
params := map[string]interface{}{"error": err} // TODO login error
template.T.ExecuteTemplate(rw, "error.html", params)
} else { } else {
http.Redirect(rw, r, "/authorize", 303) http.Redirect(rw, r, "/authorize", 303)
} }
@ -62,7 +58,8 @@ func (w *website) Page(rw http.ResponseWriter, r *http.Request, u *model.User) {
"user": u, "user": u,
"csrf": csrf, "csrf": csrf,
} }
template.T.ExecuteTemplate(rw, "index.html", params)
template.T.ExecuteTemplate(rw, "index_polymer.html", params)
} }
} }
@ -73,6 +70,9 @@ func (w *website) File(rw http.ResponseWriter, r *http.Request) {
func (w *website) Routes() []string { func (w *website) Routes() []string {
return []string{ return []string{
"/static/*filepath", "/favicon-32x32.png",
"/favicon-16x16.png",
"/src/*filepath",
"/bower_components/*filepath",
} }
} }

View file

@ -4,13 +4,14 @@ import (
"encoding/base32" "encoding/base32"
"net/http" "net/http"
"strconv" "strconv"
"time"
log "github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gorilla/securecookie" "github.com/gorilla/securecookie"
"github.com/drone/drone/cache"
"github.com/drone/drone/model" "github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/drone/drone/router/middleware/session" "github.com/drone/drone/router/middleware/session"
"github.com/drone/drone/shared/token" "github.com/drone/drone/shared/token"
"github.com/drone/drone/store" "github.com/drone/drone/store"
@ -21,17 +22,38 @@ func GetSelf(c *gin.Context) {
} }
func GetFeed(c *gin.Context) { func GetFeed(c *gin.Context) {
user := session.User(c)
latest, _ := strconv.ParseBool(c.Query("latest")) latest, _ := strconv.ParseBool(c.Query("latest"))
repos, err := cache.GetRepos(c, session.User(c)) if time.Unix(user.Synced, 0).Add(time.Hour * 72).Before(time.Now()) {
if err != nil { logrus.Debugf("sync begin: %s", user.Login)
c.String(500, "Error fetching repository list. %s", err) sync := syncer{
remote: remote.FromContext(c),
store: store.FromContext(c),
perms: store.FromContext(c),
}
if err := sync.Sync(user); err != nil {
logrus.Debugf("sync error: %s: %s", user.Login, err)
} else {
logrus.Debugf("sync complete: %s", user.Login)
user.Synced = time.Now().Unix()
store.FromContext(c).UpdateUser(user)
}
}
if latest {
feed, err := store.FromContext(c).RepoListLatest(user)
if err != nil {
c.String(500, "Error fetching feed. %s", err)
} else {
c.JSON(200, feed)
}
return return
} }
feed, err := store.GetUserFeed(c, repos, latest) feed, err := store.FromContext(c).UserFeed(user)
if err != nil { if err != nil {
c.String(500, "Error fetching feed. %s", err) c.String(500, "Error fetching user feed. %s", err)
return return
} }
c.JSON(200, feed) c.JSON(200, feed)
@ -44,58 +66,40 @@ func GetRepos(c *gin.Context) {
flush, _ = strconv.ParseBool(c.Query("flush")) flush, _ = strconv.ParseBool(c.Query("flush"))
) )
if flush { if flush || time.Unix(user.Synced, 0).Add(time.Hour*72).Before(time.Now()) {
log.Debugf("Evicting repository cache for user %s.", user.Login) logrus.Debugf("sync begin: %s", user.Login)
cache.DeleteRepos(c, user) sync := syncer{
remote: remote.FromContext(c),
store: store.FromContext(c),
perms: store.FromContext(c),
}
if err := sync.Sync(user); err != nil {
logrus.Debugf("sync error: %s: %s", user.Login, err)
} else {
logrus.Debugf("sync complete: %s", user.Login)
user.Synced = time.Now().Unix()
store.FromContext(c).UpdateUser(user)
}
} }
remote, err := cache.GetRepos(c, user) repos, err := store.FromContext(c).RepoList(user)
if err != nil { if err != nil {
c.String(500, "Error fetching repository list. %s", err) c.String(500, "Error fetching repository list. %s", err)
return return
} }
repos, err := store.GetRepoListOf(c, remote) if all {
if err != nil {
c.String(500, "Error fetching repository list. %s", err)
return
}
if !all {
c.JSON(http.StatusOK, repos) c.JSON(http.StatusOK, repos)
return return
} }
// below we combine the two lists to include both active and inactive active := []*model.Repo{}
// repositories. This is displayed on the settings screen to enable
// toggling on / off repository settings.
repom := map[string]bool{}
for _, repo := range repos { for _, repo := range repos {
repom[repo.FullName] = true if repo.IsActive {
} active = append(active, repo)
for _, repo := range remote {
if repom[repo.FullName] {
continue
} }
repos = append(repos, &model.Repo{
Avatar: repo.Avatar,
FullName: repo.FullName,
Owner: repo.Owner,
Name: repo.Name,
})
} }
c.JSON(http.StatusOK, repos) c.JSON(http.StatusOK, active)
}
func GetRemoteRepos(c *gin.Context) {
repos, err := cache.GetRepos(c, session.User(c))
if err != nil {
c.String(500, "Error fetching repository list. %s", err)
return
}
c.JSON(http.StatusOK, repos)
} }
func PostToken(c *gin.Context) { func PostToken(c *gin.Context) {

View file

@ -32,7 +32,7 @@ func TestBuilds(t *testing.T) {
// table data from the database. // table data from the database.
g.BeforeEach(func() { g.BeforeEach(func() {
s.Exec("DELETE FROM builds") s.Exec("DELETE FROM builds")
s.Exec("DELETE FROM jobs") s.Exec("DELETE FROM procs")
}) })
g.It("Should Post a Build", func() { g.It("Should Post a Build", func() {

View file

@ -108,6 +108,34 @@ var migrations = []struct {
name: "update-table-set-repo-seq-default", name: "update-table-set-repo-seq-default",
stmt: updateTableSetRepoSeqDefault, stmt: updateTableSetRepoSeqDefault,
}, },
{
name: "alter-table-add-repo-active",
stmt: alterTableAddRepoActive,
},
{
name: "update-table-set-repo-active",
stmt: updateTableSetRepoActive,
},
{
name: "alter-table-add-user-synced",
stmt: alterTableAddUserSynced,
},
{
name: "update-table-set-user-synced",
stmt: updateTableSetUserSynced,
},
{
name: "create-table-perms",
stmt: createTablePerms,
},
{
name: "create-index-perms-repo",
stmt: createIndexPermsRepo,
},
{
name: "create-index-perms-user",
stmt: createIndexPermsUser,
},
} }
// Migrate performs the database migration. If the migration fails // Migrate performs the database migration. If the migration fails
@ -499,3 +527,51 @@ var updateTableSetRepoSeqDefault = `
UPDATE repos SET repo_counter = 0 UPDATE repos SET repo_counter = 0
WHERE repo_counter IS NULL WHERE repo_counter IS NULL
` `
//
// 015_add_column_repo_active.sql
//
var alterTableAddRepoActive = `
ALTER TABLE repos ADD COLUMN repo_active BOOLEAN
`
var updateTableSetRepoActive = `
UPDATE repos SET repo_active = true
`
//
// 016_add_column_user_synced.sql
//
var alterTableAddUserSynced = `
ALTER TABLE users ADD COLUMN user_synced INTEGER;
`
var updateTableSetUserSynced = `
UPDATE users SET user_synced = 0
`
//
// 017_create_table_perms.sql
//
var createTablePerms = `
CREATE TABLE IF NOT EXISTS perms (
perm_user_id INTEGER NOT NULL
,perm_repo_id INTEGER NOT NULL
,perm_pull BOOLEAN
,perm_push BOOLEAN
,perm_admin BOOLEAN
,perm_synced INTEGER
,UNIQUE(perm_user_id, perm_repo_id)
);
`
var createIndexPermsRepo = `
CREATE INDEX ix_perms_repo ON perms (perm_repo_id);
`
var createIndexPermsUser = `
CREATE INDEX ix_perms_user ON perms (perm_user_id);
`

View file

@ -0,0 +1,7 @@
-- name: alter-table-add-repo-active
ALTER TABLE repos ADD COLUMN repo_active BOOLEAN
-- name: update-table-set-repo-active
UPDATE repos SET repo_active = true

View file

@ -0,0 +1,7 @@
-- name: alter-table-add-user-synced
ALTER TABLE users ADD COLUMN user_synced INTEGER;
-- name: update-table-set-user-synced
UPDATE users SET user_synced = 0

View file

@ -0,0 +1,19 @@
-- name: create-table-perms
CREATE TABLE IF NOT EXISTS perms (
perm_user_id INTEGER NOT NULL
,perm_repo_id INTEGER NOT NULL
,perm_pull BOOLEAN
,perm_push BOOLEAN
,perm_admin BOOLEAN
,perm_synced INTEGER
,UNIQUE(perm_user_id, perm_repo_id)
);
-- name: create-index-perms-repo
CREATE INDEX ix_perms_repo ON perms (perm_repo_id);
-- name: create-index-perms-user
CREATE INDEX ix_perms_user ON perms (perm_user_id);

View file

@ -108,6 +108,34 @@ var migrations = []struct {
name: "update-table-set-repo-seq-default", name: "update-table-set-repo-seq-default",
stmt: updateTableSetRepoSeqDefault, stmt: updateTableSetRepoSeqDefault,
}, },
{
name: "alter-table-add-repo-active",
stmt: alterTableAddRepoActive,
},
{
name: "update-table-set-repo-active",
stmt: updateTableSetRepoActive,
},
{
name: "alter-table-add-user-synced",
stmt: alterTableAddUserSynced,
},
{
name: "update-table-set-user-synced",
stmt: updateTableSetUserSynced,
},
{
name: "create-table-perms",
stmt: createTablePerms,
},
{
name: "create-index-perms-repo",
stmt: createIndexPermsRepo,
},
{
name: "create-index-perms-user",
stmt: createIndexPermsUser,
},
} }
// Migrate performs the database migration. If the migration fails // Migrate performs the database migration. If the migration fails
@ -499,3 +527,51 @@ var updateTableSetRepoSeqDefault = `
UPDATE repos SET repo_counter = 0 UPDATE repos SET repo_counter = 0
WHERE repo_counter IS NULL WHERE repo_counter IS NULL
` `
//
// 015_add_column_repo_active.sql
//
var alterTableAddRepoActive = `
ALTER TABLE repos ADD COLUMN repo_active BOOLEAN
`
var updateTableSetRepoActive = `
UPDATE repos SET repo_active = true
`
//
// 016_add_column_user_synced.sql
//
var alterTableAddUserSynced = `
ALTER TABLE users ADD COLUMN user_synced INTEGER;
`
var updateTableSetUserSynced = `
UPDATE users SET user_synced = 0
`
//
// 017_create_table_perms.sql
//
var createTablePerms = `
CREATE TABLE IF NOT EXISTS perms (
perm_user_id INTEGER NOT NULL
,perm_repo_id INTEGER NOT NULL
,perm_pull BOOLEAN
,perm_push BOOLEAN
,perm_admin BOOLEAN
,perm_synced INTEGER
,UNIQUE(perm_user_id, perm_repo_id)
);
`
var createIndexPermsRepo = `
CREATE INDEX IF NOT EXISTS ix_perms_repo ON perms (perm_repo_id);
`
var createIndexPermsUser = `
CREATE INDEX IF NOT EXISTS ix_perms_user ON perms (perm_user_id);
`

View file

@ -0,0 +1,7 @@
-- name: alter-table-add-repo-active
ALTER TABLE repos ADD COLUMN repo_active BOOLEAN
-- name: update-table-set-repo-active
UPDATE repos SET repo_active = true

View file

@ -0,0 +1,7 @@
-- name: alter-table-add-user-synced
ALTER TABLE users ADD COLUMN user_synced INTEGER;
-- name: update-table-set-user-synced
UPDATE users SET user_synced = 0

View file

@ -0,0 +1,19 @@
-- name: create-table-perms
CREATE TABLE IF NOT EXISTS perms (
perm_user_id INTEGER NOT NULL
,perm_repo_id INTEGER NOT NULL
,perm_pull BOOLEAN
,perm_push BOOLEAN
,perm_admin BOOLEAN
,perm_synced INTEGER
,UNIQUE(perm_user_id, perm_repo_id)
);
-- name: create-index-perms-repo
CREATE INDEX IF NOT EXISTS ix_perms_repo ON perms (perm_repo_id);
-- name: create-index-perms-user
CREATE INDEX IF NOT EXISTS ix_perms_user ON perms (perm_user_id);

View file

@ -112,6 +112,34 @@ var migrations = []struct {
name: "update-table-set-repo-seq-default", name: "update-table-set-repo-seq-default",
stmt: updateTableSetRepoSeqDefault, stmt: updateTableSetRepoSeqDefault,
}, },
{
name: "alter-table-add-repo-active",
stmt: alterTableAddRepoActive,
},
{
name: "update-table-set-repo-active",
stmt: updateTableSetRepoActive,
},
{
name: "alter-table-add-user-synced",
stmt: alterTableAddUserSynced,
},
{
name: "update-table-set-user-synced",
stmt: updateTableSetUserSynced,
},
{
name: "create-table-perms",
stmt: createTablePerms,
},
{
name: "create-index-perms-repo",
stmt: createIndexPermsRepo,
},
{
name: "create-index-perms-user",
stmt: createIndexPermsUser,
},
} }
// Migrate performs the database migration. If the migration fails // Migrate performs the database migration. If the migration fails
@ -500,3 +528,51 @@ var updateTableSetRepoSeqDefault = `
UPDATE repos SET repo_counter = 0 UPDATE repos SET repo_counter = 0
WHERE repo_counter IS NULL WHERE repo_counter IS NULL
` `
//
// 015_add_column_repo_active.sql
//
var alterTableAddRepoActive = `
ALTER TABLE repos ADD COLUMN repo_active BOOLEAN
`
var updateTableSetRepoActive = `
UPDATE repos SET repo_active = 1
`
//
// 016_add_column_user_synced.sql
//
var alterTableAddUserSynced = `
ALTER TABLE users ADD COLUMN user_synced INTEGER;
`
var updateTableSetUserSynced = `
UPDATE users SET user_synced = 0
`
//
// 017_create_table_perms.sql
//
var createTablePerms = `
CREATE TABLE IF NOT EXISTS perms (
perm_user_id INTEGER NOT NULL
,perm_repo_id INTEGER NOT NULL
,perm_pull BOOLEAN
,perm_push BOOLEAN
,perm_admin BOOLEAN
,perm_synced INTEGER
,UNIQUE(perm_user_id, perm_repo_id)
);
`
var createIndexPermsRepo = `
CREATE INDEX IF NOT EXISTS ix_perms_repo ON perms (perm_repo_id);
`
var createIndexPermsUser = `
CREATE INDEX IF NOT EXISTS ix_perms_user ON perms (perm_user_id);
`

View file

@ -0,0 +1,7 @@
-- name: alter-table-add-repo-active
ALTER TABLE repos ADD COLUMN repo_active BOOLEAN
-- name: update-table-set-repo-active
UPDATE repos SET repo_active = 1

View file

@ -0,0 +1,7 @@
-- name: alter-table-add-user-synced
ALTER TABLE users ADD COLUMN user_synced INTEGER;
-- name: update-table-set-user-synced
UPDATE users SET user_synced = 0

View file

@ -0,0 +1,19 @@
-- name: create-table-perms
CREATE TABLE IF NOT EXISTS perms (
perm_user_id INTEGER NOT NULL
,perm_repo_id INTEGER NOT NULL
,perm_pull BOOLEAN
,perm_push BOOLEAN
,perm_admin BOOLEAN
,perm_synced INTEGER
,UNIQUE(perm_user_id, perm_repo_id)
);
-- name: create-index-perms-repo
CREATE INDEX IF NOT EXISTS ix_perms_repo ON perms (perm_repo_id);
-- name: create-index-perms-user
CREATE INDEX IF NOT EXISTS ix_perms_user ON perms (perm_user_id);

View file

@ -1,242 +0,0 @@
-- name: create-table-users
CREATE TABLE users (
user_id INTEGER PRIMARY KEY AUTOINCREMENT
,user_login TEXT
,user_token TEXT
,user_secret TEXT
,user_expiry INTEGER
,user_email TEXT
,user_avatar TEXT
,user_active BOOLEAN
,user_admin BOOLEAN
,user_hash TEXT
,UNIQUE(user_login)
);
--------------------------------------------------------------------------------
-- name: create-table-repos
CREATE TABLE repos (
repo_id INTEGER PRIMARY KEY AUTOINCREMENT
,repo_user_id INTEGER
,repo_owner TEXT
,repo_name TEXT
,repo_full_name TEXT
,repo_avatar TEXT
,repo_link TEXT
,repo_clone TEXT
,repo_branch TEXT
,repo_timeout INTEGER
,repo_private BOOLEAN
,repo_trusted BOOLEAN
,repo_allow_pr BOOLEAN
,repo_allow_push BOOLEAN
,repo_allow_deploys BOOLEAN
,repo_allow_tags BOOLEAN
,repo_hash TEXT
,repo_scm TEXT
,repo_config_path TEXT
,repo_gated BOOLEAN
,UNIQUE(repo_full_name)
);
--------------------------------------------------------------------------------
-- name: create-table-secrets
CREATE TABLE secrets (
secret_id INTEGER PRIMARY KEY AUTOINCREMENT
,secret_repo_id INTEGER
,secret_name TEXT
,secret_value TEXT
,secret_images TEXT
,secret_events TEXT
,secret_skip_verify BOOLEAN
,secret_conceal BOOLEAN
,UNIQUE(secret_name, secret_repo_id)
);
-- name: create-index-secrets-repo
CREATE INDEX ix_secrets_repo ON secrets (secret_repo_id);
--------------------------------------------------------------------------------
-- name: create-table-registry
CREATE TABLE registry (
registry_id INTEGER PRIMARY KEY AUTOINCREMENT
,registry_repo_id INTEGER
,registry_addr TEXT
,registry_username TEXT
,registry_password TEXT
,registry_email TEXT
,registry_token TEXT
,UNIQUE(registry_addr, registry_repo_id)
);
-- name: create-index-registry-repo
CREATE INDEX ix_registry_repo ON registry (registry_repo_id);
--------------------------------------------------------------------------------
-- name: create-table-builds
CREATE TABLE builds (
build_id INTEGER PRIMARY KEY AUTOINCREMENT
,build_repo_id INTEGER
,build_number INTEGER
,build_event TEXT
,build_status TEXT
,build_enqueued INTEGER
,build_created INTEGER
,build_started INTEGER
,build_finished INTEGER
,build_commit TEXT
,build_branch TEXT
,build_ref TEXT
,build_refspec TEXT
,build_remote TEXT
,build_title TEXT
,build_message TEXT
,build_timestamp INTEGER
,build_author TEXT
,build_avatar TEXT
,build_email TEXT
,build_link TEXT
,build_deploy TEXT
,build_signed BOOLEAN
,build_verified BOOLEAN
,build_parent INTEGER
,build_error TEXT
,build_reviewer TEXT
,build_reviewed INTEGER
,build_sender TEXT
,build_config_id INTEGER
,UNIQUE(build_number, build_repo_id)
);
-- name: create-index-builds-repo
CREATE INDEX ix_build_repo ON builds (build_repo_id);
-- name: create-index-builds-author
CREATE INDEX ix_build_author ON builds (build_author);
-- name: create-index-builds-status
CREATE INDEX ix_build_status_running ON builds (build_status)
WHERE build_status IN ('pending', 'running');
--------------------------------------------------------------------------------
-- name: create-table-procs
CREATE TABLE procs (
proc_id INTEGER PRIMARY KEY AUTOINCREMENT
,proc_build_id INTEGER
,proc_pid INTEGER
,proc_ppid INTEGER
,proc_pgid INTEGER
,proc_name TEXT
,proc_state TEXT
,proc_error TEXT
,proc_exit_code INTEGER
,proc_started INTEGER
,proc_stopped INTEGER
,proc_machine TEXT
,proc_platform TEXT
,proc_environ TEXT
,UNIQUE(proc_build_id, proc_pid)
);
-- name: create-index-procs-build
CREATE INDEX proc_build_ix ON procs (proc_build_id);
--------------------------------------------------------------------------------
-- name: create-table-logs
CREATE TABLE IF NOT EXISTS logs (
log_id INTEGER PRIMARY KEY AUTOINCREMENT
,log_job_id INTEGER
,log_data BLOB
,UNIQUE(log_job_id)
);
--------------------------------------------------------------------------------
-- name: create-table-files
CREATE TABLE IF NOT EXISTS files (
file_id INTEGER PRIMARY KEY AUTOINCREMENT
,file_build_id INTEGER
,file_proc_id INTEGER
,file_name TEXT
,file_mime TEXT
,file_size INTEGER
,file_time INTEGER
,file_data BLOB
,UNIQUE(file_proc_id,file_name)
,FOREIGN KEY(file_proc_id) REFERENCES procs (proc_id) ON DELETE CASCADE
);
-- name: create-index-files-builds
CREATE INDEX file_build_ix ON files (file_build_id);
-- name: create-index-files-procs
CREATE INDEX file_proc_ix ON files (file_proc_id);
--------------------------------------------------------------------------------
-- name: create-table-senders
CREATE TABLE IF NOT EXISTS 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)
);
-- name: create-index-sender-repos
CREATE INDEX sender_repo_ix ON senders (sender_repo_id);
--------------------------------------------------------------------------------
-- name: create-table-config
CREATE TABLE IF NOT EXISTS config (
config_id INTEGER PRIMARY KEY AUTOINCREMENT
,config_repo_id INTEGER
,config_hash TEXT
,config_data BLOB
,UNIQUE(config_hash, config_repo_id)
);
--------------------------------------------------------------------------------
-- name: create-table-tasks
CREATE TABLE IF NOT EXISTS tasks (
task_id TEXT PRIMARY KEY
,task_data BLOB
,task_labels BLOB
);
--------------------------------------------------------------------------------
-- name: create-table-agents
CREATE TABLE IF NOT EXISTS agents (
agent_id INTEGER PRIMARY KEY AUTOINCREMENT
,agent_addr TEXT
,agent_platform TEXT
,agent_capacity INTEGER
,agent_created INTEGER
,agent_updated INTEGER
,UNIQUE(agent_addr)
);

50
store/datastore/perms.go Normal file
View file

@ -0,0 +1,50 @@
package datastore
import (
"github.com/drone/drone/model"
"github.com/drone/drone/store/datastore/sql"
"github.com/russross/meddler"
)
func (db *datastore) PermFind(user *model.User, repo *model.Repo) (*model.Perm, error) {
stmt := sql.Lookup(db.driver, "perms-find-user-repo")
data := new(model.Perm)
err := meddler.QueryRow(db, data, stmt, user.ID, repo.ID)
return data, err
}
func (db *datastore) PermUpsert(perm *model.Perm) error {
stmt := sql.Lookup(db.driver, "perms-insert-replace-lookup")
_, err := db.Exec(stmt,
perm.UserID,
perm.Repo,
perm.Pull,
perm.Push,
perm.Admin,
perm.Synced,
)
return err
}
func (db *datastore) PermBatch(perms []*model.Perm) (err error) {
for _, perm := range perms {
err = db.PermUpsert(perm)
if err != nil {
return err
}
}
return nil
}
func (db *datastore) PermDelete(perm *model.Perm) error {
stmt := sql.Lookup(db.driver, "perms-delete-user-repo")
_, err := db.Exec(stmt, perm.UserID, perm.RepoID)
return err
}
func (db *datastore) PermFlush(user *model.User, before int64) error {
stmt := sql.Lookup(db.driver, "perms-delete-user-date")
_, err := db.Exec(stmt, user.ID, before)
return err
}

View file

@ -0,0 +1,187 @@
package datastore
import (
"testing"
"github.com/drone/drone/model"
)
func TestPermFind(t *testing.T) {
s := newTest()
defer func() {
s.Exec("delete from perms")
s.Exec("delete from repos")
s.Close()
}()
user := &model.User{ID: 1}
repo := &model.Repo{
UserID: 1,
FullName: "bradrydzewski/drone",
Owner: "bradrydzewski",
Name: "drone",
}
s.CreateRepo(repo)
err := s.PermUpsert(
&model.Perm{
UserID: user.ID,
RepoID: repo.ID,
Repo: repo.FullName,
Pull: true,
Push: false,
Admin: false,
},
)
if err != nil {
t.Error(err)
return
}
perm, err := s.PermFind(user, repo)
if err != nil {
t.Error(err)
return
}
if got, want := perm.Pull, true; got != want {
t.Errorf("Wanted pull %v, got %v", want, got)
}
if got, want := perm.Push, false; got != want {
t.Errorf("Wanted push %v, got %v", want, got)
}
if got, want := perm.Admin, false; got != want {
t.Errorf("Wanted admin %v, got %v", want, got)
}
}
func TestPermUpsert(t *testing.T) {
s := newTest()
defer func() {
s.Exec("delete from perms")
s.Exec("delete from repos")
s.Close()
}()
user := &model.User{ID: 1}
repo := &model.Repo{
UserID: 1,
FullName: "bradrydzewski/drone",
Owner: "bradrydzewski",
Name: "drone",
}
s.CreateRepo(repo)
err := s.PermUpsert(
&model.Perm{
UserID: user.ID,
RepoID: repo.ID,
Repo: repo.FullName,
Pull: true,
Push: false,
Admin: false,
},
)
if err != nil {
t.Error(err)
return
}
perm, err := s.PermFind(user, repo)
if err != nil {
t.Error(err)
return
}
if got, want := perm.Pull, true; got != want {
t.Errorf("Wanted pull %v, got %v", want, got)
}
if got, want := perm.Push, false; got != want {
t.Errorf("Wanted push %v, got %v", want, got)
}
if got, want := perm.Admin, false; got != want {
t.Errorf("Wanted admin %v, got %v", want, got)
}
//
// this will attempt to replace the existing permissions
// using the insert or replace logic.
//
err = s.PermUpsert(
&model.Perm{
UserID: user.ID,
RepoID: repo.ID,
Repo: repo.FullName,
Pull: true,
Push: true,
Admin: true,
},
)
if err != nil {
t.Error(err)
return
}
perm, err = s.PermFind(user, repo)
if err != nil {
t.Error(err)
return
}
if got, want := perm.Pull, true; got != want {
t.Errorf("Wanted pull %v, got %v", want, got)
}
if got, want := perm.Push, true; got != want {
t.Errorf("Wanted push %v, got %v", want, got)
}
if got, want := perm.Admin, true; got != want {
t.Errorf("Wanted admin %v, got %v", want, got)
}
}
func TestPermDelete(t *testing.T) {
s := newTest()
defer func() {
s.Exec("delete from perms")
s.Exec("delete from repos")
s.Close()
}()
user := &model.User{ID: 1}
repo := &model.Repo{
UserID: 1,
FullName: "bradrydzewski/drone",
Owner: "bradrydzewski",
Name: "drone",
}
s.CreateRepo(repo)
err := s.PermUpsert(
&model.Perm{
UserID: user.ID,
RepoID: repo.ID,
Repo: repo.FullName,
Pull: true,
Push: false,
Admin: false,
},
)
if err != nil {
t.Errorf("Unexpected error: insert perm: %s", err)
return
}
perm, err := s.PermFind(user, repo)
if err != nil {
t.Errorf("Unexpected error: select perm: %s", err)
return
}
err = s.PermDelete(perm)
if err != nil {
t.Errorf("Unexpected error: delete perm: %s", err)
return
}
_, err = s.PermFind(user, repo)
if err == nil {
t.Errorf("Expect error: sql.ErrNoRows")
return
}
}

View file

@ -1,8 +1,6 @@
package datastore package datastore
import ( import (
"fmt"
"github.com/drone/drone/model" "github.com/drone/drone/model"
"github.com/drone/drone/store/datastore/sql" "github.com/drone/drone/store/datastore/sql"
"github.com/russross/meddler" "github.com/russross/meddler"
@ -20,25 +18,6 @@ func (db *datastore) GetRepoName(name string) (*model.Repo, error) {
return repo, err return repo, err
} }
func (db *datastore) GetRepoListOf(listof []*model.RepoLite) ([]*model.Repo, error) {
var (
repos []*model.Repo
args []interface{}
stmt string
err error
)
switch meddler.Default {
case meddler.PostgreSQL:
stmt, args = toListPostgres(listof)
default:
stmt, args = toList(listof)
}
if len(args) > 0 {
err = meddler.QueryAll(db, &repos, fmt.Sprintf(repoListOfQuery, stmt), args...)
}
return repos, err
}
func (db *datastore) GetRepoCount() (count int, err error) { func (db *datastore) GetRepoCount() (count int, err error) {
err = db.QueryRow( err = db.QueryRow(
sql.Lookup(db.driver, "count-repos"), sql.Lookup(db.driver, "count-repos"),
@ -55,10 +34,59 @@ func (db *datastore) UpdateRepo(repo *model.Repo) error {
} }
func (db *datastore) DeleteRepo(repo *model.Repo) error { func (db *datastore) DeleteRepo(repo *model.Repo) error {
var _, err = db.Exec(rebind(repoDeleteStmt), repo.ID) stmt := sql.Lookup(db.driver, "repo-delete")
_, err := db.Exec(stmt, repo.ID)
return err return err
} }
func (db *datastore) RepoList(user *model.User) ([]*model.Repo, error) {
stmt := sql.Lookup(db.driver, "repo-find-user")
data := []*model.Repo{}
err := meddler.QueryAll(db, &data, stmt, user.ID)
return data, err
}
func (db *datastore) RepoListLatest(user *model.User) ([]*model.Feed, error) {
stmt := sql.Lookup(db.driver, "feed-latest-build")
data := []*model.Feed{}
err := meddler.QueryAll(db, &data, stmt, user.ID)
return data, err
}
func (db *datastore) RepoBatch(repos []*model.Repo) error {
stmt := sql.Lookup(db.driver, "repo-insert-ignore")
for _, repo := range repos {
_, err := db.Exec(stmt,
repo.UserID,
repo.Owner,
repo.Name,
repo.FullName,
repo.Avatar,
repo.Link,
repo.Clone,
repo.Branch,
repo.Timeout,
repo.IsPrivate,
repo.IsTrusted,
repo.IsActive,
repo.AllowPull,
repo.AllowPush,
repo.AllowDeploy,
repo.AllowTag,
repo.Hash,
repo.Kind,
repo.Config,
repo.IsGated,
repo.Visibility,
repo.Counter,
)
if err != nil {
return err
}
}
return nil
}
const repoTable = "repos" const repoTable = "repos"
const repoNameQuery = ` const repoNameQuery = `
@ -68,17 +96,6 @@ WHERE repo_full_name = ?
LIMIT 1; LIMIT 1;
` `
const repoListOfQuery = `
SELECT *
FROM repos
WHERE repo_full_name IN (%s)
ORDER BY repo_name
`
const repoCountQuery = `
SELECT COUNT(*) FROM repos
`
const repoDeleteStmt = ` const repoDeleteStmt = `
DELETE FROM repos DELETE FROM repos
WHERE repo_id = ? WHERE repo_id = ?

View file

@ -84,76 +84,6 @@ func TestRepos(t *testing.T) {
g.Assert(repo.Name).Equal(getrepo.Name) g.Assert(repo.Name).Equal(getrepo.Name)
}) })
g.It("Should Get a Repo List", func() {
repo1 := &model.Repo{
UserID: 1,
Owner: "bradrydzewski",
Name: "drone",
FullName: "bradrydzewski/drone",
}
repo2 := &model.Repo{
UserID: 2,
Owner: "drone",
Name: "drone",
FullName: "drone/drone",
}
repo3 := &model.Repo{
UserID: 2,
Owner: "octocat",
Name: "hello-world",
FullName: "octocat/hello-world",
}
s.CreateRepo(repo1)
s.CreateRepo(repo2)
s.CreateRepo(repo3)
repos, err := s.GetRepoListOf([]*model.RepoLite{
{FullName: "bradrydzewski/drone"},
{FullName: "drone/drone"},
})
g.Assert(err == nil).IsTrue()
g.Assert(len(repos)).Equal(2)
g.Assert(repos[0].ID).Equal(repo1.ID)
g.Assert(repos[1].ID).Equal(repo2.ID)
})
g.It("Should Get a Repo List", func() {
repo1 := &model.Repo{
UserID: 1,
Owner: "bradrydzewski",
Name: "drone",
FullName: "bradrydzewski/drone",
}
repo2 := &model.Repo{
UserID: 2,
Owner: "drone",
Name: "drone",
FullName: "drone/drone",
}
s.CreateRepo(repo1)
s.CreateRepo(repo2)
count, err := s.GetRepoCount()
g.Assert(err == nil).IsTrue()
g.Assert(count).Equal(2)
})
g.It("Should Delete a Repo", func() {
repo := model.Repo{
UserID: 1,
FullName: "bradrydzewski/drone",
Owner: "bradrydzewski",
Name: "drone",
}
s.CreateRepo(&repo)
_, err1 := s.GetRepo(repo.ID)
err2 := s.DeleteRepo(&repo)
_, err3 := s.GetRepo(repo.ID)
g.Assert(err1 == nil).IsTrue()
g.Assert(err2 == nil).IsTrue()
g.Assert(err3 == nil).IsFalse()
})
g.It("Should Enforce Unique Repo Name", func() { g.It("Should Enforce Unique Repo Name", func() {
repo1 := model.Repo{ repo1 := model.Repo{
UserID: 1, UserID: 1,
@ -174,3 +104,263 @@ func TestRepos(t *testing.T) {
}) })
}) })
} }
func TestRepoList(t *testing.T) {
s := newTest()
s.Exec("delete from repos")
s.Exec("delete from users")
s.Exec("delete from perms")
defer func() {
s.Exec("delete from repos")
s.Exec("delete from users")
s.Exec("delete from perms")
s.Close()
}()
user := &model.User{
Login: "joe",
Email: "foo@bar.com",
Token: "e42080dddf012c718e476da161d21ad5",
}
s.CreateUser(user)
repo1 := &model.Repo{
Owner: "bradrydzewski",
Name: "drone",
FullName: "bradrydzewski/drone",
}
repo2 := &model.Repo{
Owner: "drone",
Name: "drone",
FullName: "drone/drone",
}
repo3 := &model.Repo{
Owner: "octocat",
Name: "hello-world",
FullName: "octocat/hello-world",
}
s.CreateRepo(repo1)
s.CreateRepo(repo2)
s.CreateRepo(repo3)
s.PermBatch([]*model.Perm{
{UserID: user.ID, Repo: repo1.FullName},
{UserID: user.ID, Repo: repo2.FullName},
})
repos, err := s.RepoList(user)
if err != nil {
t.Error(err)
return
}
if got, want := len(repos), 2; got != want {
t.Errorf("Want %d repositories, got %d", want, got)
}
if got, want := repos[0].ID, repo1.ID; got != want {
t.Errorf("Want repository id %d, got %d", want, got)
}
if got, want := repos[1].ID, repo2.ID; got != want {
t.Errorf("Want repository id %d, got %d", want, got)
}
}
func TestRepoListLatest(t *testing.T) {
s := newTest()
defer func() {
s.Exec("delete from repos")
s.Exec("delete from users")
s.Exec("delete from perms")
s.Close()
}()
user := &model.User{
Login: "joe",
Email: "foo@bar.com",
Token: "e42080dddf012c718e476da161d21ad5",
}
s.CreateUser(user)
repo1 := &model.Repo{
Owner: "bradrydzewski",
Name: "drone",
FullName: "bradrydzewski/drone",
IsActive: true,
}
repo2 := &model.Repo{
Owner: "drone",
Name: "drone",
FullName: "drone/drone",
IsActive: true,
}
repo3 := &model.Repo{
Owner: "octocat",
Name: "hello-world",
FullName: "octocat/hello-world",
IsActive: true,
}
s.CreateRepo(repo1)
s.CreateRepo(repo2)
s.CreateRepo(repo3)
s.PermBatch([]*model.Perm{
{UserID: user.ID, Repo: repo1.FullName},
{UserID: user.ID, Repo: repo2.FullName},
})
build1 := &model.Build{
RepoID: repo1.ID,
Status: model.StatusFailure,
}
build2 := &model.Build{
RepoID: repo1.ID,
Status: model.StatusRunning,
}
build3 := &model.Build{
RepoID: repo2.ID,
Status: model.StatusKilled,
}
build4 := &model.Build{
RepoID: repo3.ID,
Status: model.StatusError,
}
s.CreateBuild(build1)
s.CreateBuild(build2)
s.CreateBuild(build3)
s.CreateBuild(build4)
builds, err := s.RepoListLatest(user)
if err != nil {
t.Errorf("Unexpected error: repository list with latest build: %s", err)
return
}
if got, want := len(builds), 2; got != want {
t.Errorf("Want %d repositories, got %d", want, got)
}
if got, want := builds[0].Status, model.StatusRunning; want != got {
t.Errorf("Want repository status %s, got %s", want, got)
}
if got, want := builds[0].FullName, repo1.FullName; want != got {
t.Errorf("Want repository name %s, got %s", want, got)
}
if got, want := builds[1].Status, model.StatusKilled; want != got {
t.Errorf("Want repository status %s, got %s", want, got)
}
if got, want := builds[1].FullName, repo2.FullName; want != got {
t.Errorf("Want repository name %s, got %s", want, got)
}
}
func TestRepoCount(t *testing.T) {
s := newTest()
defer func() {
s.Exec("delete from repos")
s.Exec("delete from users")
s.Exec("delete from perms")
s.Close()
}()
repo1 := &model.Repo{
Owner: "bradrydzewski",
Name: "drone",
FullName: "bradrydzewski/drone",
}
repo2 := &model.Repo{
Owner: "drone",
Name: "drone",
FullName: "drone/drone",
}
s.CreateRepo(repo1)
s.CreateRepo(repo2)
s.Exec("ANALYZE")
count, _ := s.GetRepoCount()
if got, want := count, 2; got != want {
t.Errorf("Want %d repositories, got %d", want, got)
}
}
func TestRepoBatch(t *testing.T) {
s := newTest()
defer func() {
s.Exec("delete from repos")
s.Exec("delete from users")
s.Exec("delete from perms")
s.Close()
}()
repo := &model.Repo{
UserID: 1,
FullName: "foo/bar",
Owner: "foo",
Name: "bar",
}
err := s.CreateRepo(repo)
if err != nil {
t.Error(err)
return
}
err = s.RepoBatch(
[]*model.Repo{
{
UserID: 1,
FullName: "foo/bar",
Owner: "foo",
Name: "bar",
},
{
UserID: 1,
FullName: "bar/baz",
Owner: "bar",
Name: "baz",
},
{
UserID: 1,
FullName: "baz/qux",
Owner: "baz",
Name: "qux",
},
},
)
if err != nil {
t.Error(err)
return
}
s.Exec("ANALYZE")
count, _ := s.GetRepoCount()
if got, want := count, 3; got != want {
t.Errorf("Want %d repositories, got %d", want, got)
}
}
func TestRepoCrud(t *testing.T) {
s := newTest()
defer func() {
s.Exec("delete from repos")
s.Exec("delete from users")
s.Exec("delete from perms")
s.Close()
}()
repo := model.Repo{
UserID: 1,
FullName: "bradrydzewski/drone",
Owner: "bradrydzewski",
Name: "drone",
}
s.CreateRepo(&repo)
_, err1 := s.GetRepo(repo.ID)
err2 := s.DeleteRepo(&repo)
_, err3 := s.GetRepo(repo.ID)
if err1 != nil {
t.Errorf("Unexpected error: select repository: %s", err1)
}
if err2 != nil {
t.Errorf("Unexpected error: delete repository: %s", err2)
}
if err3 == nil {
t.Errorf("Expected error: sql.ErrNoRows")
}
}

View file

@ -77,7 +77,7 @@ func TestSecretList(t *testing.T) {
return return
} }
if got, want := len(list), 2; got != want { if got, want := len(list), 2; got != want {
t.Errorf("Want %d registries, got %d", want, got) t.Errorf("Want %d secrets, got %d", want, got)
} }
} }
@ -112,6 +112,34 @@ func TestSecretUpdate(t *testing.T) {
} }
} }
func TestSecretDelete(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
}
if err := s.SecretDelete(secret); err != nil {
t.Errorf("Unexpected error: delete secret: %s", err)
return
}
_, err := s.SecretFind(&model.Repo{ID: 1}, "foo")
if err == nil {
t.Errorf("Expect error: sql.ErrNoRows")
return
}
}
func TestSecretIndexes(t *testing.T) { func TestSecretIndexes(t *testing.T) {
s := newTest() s := newTest()
defer func() { defer func() {

View file

@ -1,6 +1,7 @@
package sql package sql
import ( import (
"github.com/drone/drone/store/datastore/sql/mysql"
"github.com/drone/drone/store/datastore/sql/postgres" "github.com/drone/drone/store/datastore/sql/postgres"
"github.com/drone/drone/store/datastore/sql/sqlite" "github.com/drone/drone/store/datastore/sql/sqlite"
) )
@ -18,6 +19,8 @@ func Lookup(driver string, name string) string {
switch driver { switch driver {
case DriverPostgres: case DriverPostgres:
return postgres.Lookup(name) return postgres.Lookup(name)
case DriverMysql:
return mysql.Lookup(name)
default: default:
return sqlite.Lookup(name) return sqlite.Lookup(name)
} }

View file

@ -0,0 +1,28 @@
-- name: config-find-id
SELECT
config_id
,config_repo_id
,config_hash
,config_data
FROM config
WHERE config_id = ?
-- name: config-find-repo-hash
SELECT
config_id
,config_repo_id
,config_hash
,config_data
FROM config
WHERE config_repo_id = ?
AND config_hash = ?
-- name: config-find-approved
SELECT build_id FROM builds
WHERE build_repo_id = ?
AND build_config_id = ?
AND build_status NOT IN ('blocked', 'pending')
LIMIT 1

View file

@ -0,0 +1,14 @@
-- name: count-users
SELECT count(1)
FROM users
-- name: count-repos
SELECT count(1)
FROM repos
-- name: count-builds
SELECT count(1)
FROM builds

View file

@ -0,0 +1,61 @@
-- name: feed-latest-build
SELECT
repo_owner
,repo_name
,repo_full_name
,build_number
,build_event
,build_status
,build_created
,build_started
,build_finished
,build_commit
,build_branch
,build_ref
,build_refspec
,build_remote
,build_title
,build_message
,build_author
,build_email
,build_avatar
FROM repos LEFT OUTER JOIN builds ON build_id = (
SELECT build_id FROM builds
WHERE builds.build_repo_id = repos.repo_id
ORDER BY build_id DESC
LIMIT 1
)
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
WHERE perms.perm_user_id = ?
AND repos.repo_active = true
ORDER BY repo_full_name ASC;
-- name: feed
SELECT
repo_owner
,repo_name
,repo_full_name
,build_number
,build_event
,build_status
,build_created
,build_started
,build_finished
,build_commit
,build_branch
,build_ref
,build_refspec
,build_remote
,build_title
,build_message
,build_author
,build_email
,build_avatar
FROM repos
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
INNER JOIN builds ON builds.build_repo_id = repos.repo_id
WHERE perms.perm_user_id = ?
ORDER BY build_id DESC
LIMIT 50

View file

@ -0,0 +1,45 @@
-- name: files-find-build
SELECT
file_id
,file_build_id
,file_proc_id
,file_name
,file_mime
,file_size
,file_time
FROM files
WHERE file_build_id = ?
-- name: files-find-proc-name
SELECT
file_id
,file_build_id
,file_proc_id
,file_name
,file_mime
,file_size
,file_time
FROM files
WHERE file_proc_id = ?
AND file_name = ?
-- name: files-find-proc-name-data
SELECT
file_id
,file_build_id
,file_proc_id
,file_name
,file_mime
,file_size
,file_time
,file_data
FROM files
WHERE file_proc_id = ?
AND file_name = ?
-- name: files-delete-build
DELETE FROM files WHERE file_build_id = ?

View file

@ -0,0 +1,58 @@
-- name: perms-find-user
SELECT
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_date
FROM perms
WHERE perm_user_id = ?
-- name: perms-find-user-repo
SELECT
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_synced
FROM perms
WHERE perm_user_id = ?
AND perm_repo_id = ?
-- name: perms-insert-replace
REPLACE INTO perms (
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_synced
) VALUES (?,?,?,?,?,?)
-- name: perms-insert-replace-lookup
REPLACE INTO perms (
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_synced
) VALUES (?,(SELECT repo_id FROM repos WHERE repo_full_name = ?),?,?,?,?)
-- name: perms-delete-user-repo
DELETE FROM perms
WHERE perm_user_id = ?
AND perm_repo_id = ?
-- name: perms-delete-user-date
DELETE FROM perms
WHERE perm_user_id = ?
AND perm_synced < ?

View file

@ -0,0 +1,87 @@
-- name: procs-find-id
SELECT
proc_id
,proc_build_id
,proc_pid
,proc_ppid
,proc_pgid
,proc_name
,proc_state
,proc_error
,proc_exit_code
,proc_started
,proc_stopped
,proc_machine
,proc_platform
,proc_environ
FROM procs
WHERE proc_id = ?
-- name: procs-find-build
SELECT
proc_id
,proc_build_id
,proc_pid
,proc_ppid
,proc_pgid
,proc_name
,proc_state
,proc_error
,proc_exit_code
,proc_started
,proc_stopped
,proc_machine
,proc_platform
,proc_environ
FROM procs
WHERE proc_build_id = ?
ORDER BY proc_id ASC
-- name: procs-find-build-pid
SELECT
proc_id
,proc_build_id
,proc_pid
,proc_ppid
,proc_pgid
,proc_name
,proc_state
,proc_error
,proc_exit_code
,proc_started
,proc_stopped
,proc_machine
,proc_platform
,proc_environ
FROM procs
WHERE proc_build_id = ?
AND proc_pid = ?
-- name: procs-find-build-ppid
SELECT
proc_id
,proc_build_id
,proc_pid
,proc_ppid
,proc_pgid
,proc_name
,proc_state
,proc_error
,proc_exit_code
,proc_started
,proc_stopped
,proc_machine
,proc_platform
,proc_environ
FROM procs
WHERE proc_build_id = ?
AND proc_ppid = ?
AND proc_name = ?
-- name: procs-delete-build
DELETE FROM procs WHERE proc_build_id = ?

View file

@ -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 = ?

View file

@ -0,0 +1,67 @@
-- name: repo-update-counter
UPDATE repos SET repo_counter = ?
WHERE repo_counter = ?
AND repo_id = ?
-- name: repo-find-user
SELECT
repo_id
,repo_user_id
,repo_owner
,repo_name
,repo_full_name
,repo_avatar
,repo_link
,repo_clone
,repo_branch
,repo_timeout
,repo_private
,repo_trusted
,repo_active
,repo_allow_pr
,repo_allow_push
,repo_allow_deploys
,repo_allow_tags
,repo_hash
,repo_scm
,repo_config_path
,repo_gated
,repo_visibility
,repo_counter
FROM repos
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
WHERE perms.perm_user_id = ?
ORDER BY repo_full_name ASC
-- name: repo-insert-ignore
INSERT IGNORE INTO repos (
repo_user_id
,repo_owner
,repo_name
,repo_full_name
,repo_avatar
,repo_link
,repo_clone
,repo_branch
,repo_timeout
,repo_private
,repo_trusted
,repo_active
,repo_allow_pr
,repo_allow_push
,repo_allow_deploys
,repo_allow_tags
,repo_hash
,repo_scm
,repo_config_path
,repo_gated
,repo_visibility
,repo_counter
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
-- name: repo-delete
DELETE FROM repos WHERE repo_id = ?

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

@ -0,0 +1,11 @@
-- name: task-list
SELECT
task_id
,task_data
,task_labels
FROM tasks
-- name: task-delete
DELETE FROM tasks WHERE task_id = ?

View file

@ -0,0 +1,3 @@
package mysql
//go:generate togo sql --package=mysql

View file

@ -0,0 +1,527 @@
package mysql
// Lookup returns the named statement.
func Lookup(name string) string {
return index[name]
}
var index = map[string]string{
"config-find-id": configFindId,
"config-find-repo-hash": configFindRepoHash,
"config-find-approved": configFindApproved,
"count-users": countUsers,
"count-repos": countRepos,
"count-builds": countBuilds,
"feed-latest-build": feedLatestBuild,
"feed": feed,
"files-find-build": filesFindBuild,
"files-find-proc-name": filesFindProcName,
"files-find-proc-name-data": filesFindProcNameData,
"files-delete-build": filesDeleteBuild,
"perms-find-user": permsFindUser,
"perms-find-user-repo": permsFindUserRepo,
"perms-insert-replace": permsInsertReplace,
"perms-insert-replace-lookup": permsInsertReplaceLookup,
"perms-delete-user-repo": permsDeleteUserRepo,
"perms-delete-user-date": permsDeleteUserDate,
"procs-find-id": procsFindId,
"procs-find-build": procsFindBuild,
"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,
"repo-update-counter": repoUpdateCounter,
"repo-find-user": repoFindUser,
"repo-insert-ignore": repoInsertIgnore,
"repo-delete": repoDelete,
"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,
"task-list": taskList,
"task-delete": taskDelete,
}
var configFindId = `
SELECT
config_id
,config_repo_id
,config_hash
,config_data
FROM config
WHERE config_id = ?
`
var configFindRepoHash = `
SELECT
config_id
,config_repo_id
,config_hash
,config_data
FROM config
WHERE config_repo_id = ?
AND config_hash = ?
`
var configFindApproved = `
SELECT build_id FROM builds
WHERE build_repo_id = ?
AND build_config_id = ?
AND build_status NOT IN ('blocked', 'pending')
LIMIT 1
`
var countUsers = `
SELECT count(1)
FROM users
`
var countRepos = `
SELECT count(1)
FROM repos
`
var countBuilds = `
SELECT count(1)
FROM builds
`
var feedLatestBuild = `
SELECT
repo_owner
,repo_name
,repo_full_name
,build_number
,build_event
,build_status
,build_created
,build_started
,build_finished
,build_commit
,build_branch
,build_ref
,build_refspec
,build_remote
,build_title
,build_message
,build_author
,build_email
,build_avatar
FROM repos LEFT OUTER JOIN builds ON build_id = (
SELECT build_id FROM builds
WHERE builds.build_repo_id = repos.repo_id
ORDER BY build_id DESC
LIMIT 1
)
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
WHERE perms.perm_user_id = ?
AND repos.repo_active = true
ORDER BY repo_full_name ASC;
`
var feed = `
SELECT
repo_owner
,repo_name
,repo_full_name
,build_number
,build_event
,build_status
,build_created
,build_started
,build_finished
,build_commit
,build_branch
,build_ref
,build_refspec
,build_remote
,build_title
,build_message
,build_author
,build_email
,build_avatar
FROM repos
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
INNER JOIN builds ON builds.build_repo_id = repos.repo_id
WHERE perms.perm_user_id = ?
ORDER BY build_id DESC
LIMIT 50
`
var filesFindBuild = `
SELECT
file_id
,file_build_id
,file_proc_id
,file_name
,file_mime
,file_size
,file_time
FROM files
WHERE file_build_id = ?
`
var filesFindProcName = `
SELECT
file_id
,file_build_id
,file_proc_id
,file_name
,file_mime
,file_size
,file_time
FROM files
WHERE file_proc_id = ?
AND file_name = ?
`
var filesFindProcNameData = `
SELECT
file_id
,file_build_id
,file_proc_id
,file_name
,file_mime
,file_size
,file_time
,file_data
FROM files
WHERE file_proc_id = ?
AND file_name = ?
`
var filesDeleteBuild = `
DELETE FROM files WHERE file_build_id = ?
`
var permsFindUser = `
SELECT
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_date
FROM perms
WHERE perm_user_id = ?
`
var permsFindUserRepo = `
SELECT
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_synced
FROM perms
WHERE perm_user_id = ?
AND perm_repo_id = ?
`
var permsInsertReplace = `
REPLACE INTO perms (
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_synced
) VALUES (?,?,?,?,?,?)
`
var permsInsertReplaceLookup = `
REPLACE INTO perms (
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_synced
) VALUES (?,(SELECT repo_id FROM repos WHERE repo_full_name = ?),?,?,?,?)
`
var permsDeleteUserRepo = `
DELETE FROM perms
WHERE perm_user_id = ?
AND perm_repo_id = ?
`
var permsDeleteUserDate = `
DELETE FROM perms
WHERE perm_user_id = ?
AND perm_synced < ?
`
var procsFindId = `
SELECT
proc_id
,proc_build_id
,proc_pid
,proc_ppid
,proc_pgid
,proc_name
,proc_state
,proc_error
,proc_exit_code
,proc_started
,proc_stopped
,proc_machine
,proc_platform
,proc_environ
FROM procs
WHERE proc_id = ?
`
var procsFindBuild = `
SELECT
proc_id
,proc_build_id
,proc_pid
,proc_ppid
,proc_pgid
,proc_name
,proc_state
,proc_error
,proc_exit_code
,proc_started
,proc_stopped
,proc_machine
,proc_platform
,proc_environ
FROM procs
WHERE proc_build_id = ?
ORDER BY proc_id ASC
`
var procsFindBuildPid = `
SELECT
proc_id
,proc_build_id
,proc_pid
,proc_ppid
,proc_pgid
,proc_name
,proc_state
,proc_error
,proc_exit_code
,proc_started
,proc_stopped
,proc_machine
,proc_platform
,proc_environ
FROM procs
WHERE proc_build_id = ?
AND proc_pid = ?
`
var procsFindBuildPpid = `
SELECT
proc_id
,proc_build_id
,proc_pid
,proc_ppid
,proc_pgid
,proc_name
,proc_state
,proc_error
,proc_exit_code
,proc_started
,proc_stopped
,proc_machine
,proc_platform
,proc_environ
FROM procs
WHERE proc_build_id = ?
AND proc_ppid = ?
AND proc_name = ?
`
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 = ?
`
var repoUpdateCounter = `
UPDATE repos SET repo_counter = ?
WHERE repo_counter = ?
AND repo_id = ?
`
var repoFindUser = `
SELECT
repo_id
,repo_user_id
,repo_owner
,repo_name
,repo_full_name
,repo_avatar
,repo_link
,repo_clone
,repo_branch
,repo_timeout
,repo_private
,repo_trusted
,repo_active
,repo_allow_pr
,repo_allow_push
,repo_allow_deploys
,repo_allow_tags
,repo_hash
,repo_scm
,repo_config_path
,repo_gated
,repo_visibility
,repo_counter
FROM repos
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
WHERE perms.perm_user_id = ?
ORDER BY repo_full_name ASC
`
var repoInsertIgnore = `
INSERT IGNORE INTO repos (
repo_user_id
,repo_owner
,repo_name
,repo_full_name
,repo_avatar
,repo_link
,repo_clone
,repo_branch
,repo_timeout
,repo_private
,repo_trusted
,repo_active
,repo_allow_pr
,repo_allow_push
,repo_allow_deploys
,repo_allow_tags
,repo_hash
,repo_scm
,repo_config_path
,repo_gated
,repo_visibility
,repo_counter
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
`
var repoDelete = `
DELETE FROM repos WHERE repo_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 = ?
`
var taskList = `
SELECT
task_id
,task_data
,task_labels
FROM tasks
`
var taskDelete = `
DELETE FROM tasks WHERE task_id = ?
`

View file

@ -0,0 +1,61 @@
-- name: feed-latest-build
SELECT
repo_owner
,repo_name
,repo_full_name
,build_number
,build_event
,build_status
,build_created
,build_started
,build_finished
,build_commit
,build_branch
,build_ref
,build_refspec
,build_remote
,build_title
,build_message
,build_author
,build_email
,build_avatar
FROM repos LEFT OUTER JOIN builds ON build_id = (
SELECT build_id FROM builds
WHERE builds.build_repo_id = repos.repo_id
ORDER BY build_id DESC
LIMIT 1
)
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
WHERE perms.perm_user_id = $1
AND repos.repo_active = true
ORDER BY repo_full_name ASC;
-- name: feed
SELECT
repo_owner
,repo_name
,repo_full_name
,build_number
,build_event
,build_status
,build_created
,build_started
,build_finished
,build_commit
,build_branch
,build_ref
,build_refspec
,build_remote
,build_title
,build_message
,build_author
,build_email
,build_avatar
FROM repos
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
INNER JOIN builds ON builds.build_repo_id = repos.repo_id
WHERE perms.perm_user_id = $1
ORDER BY build_id DESC
LIMIT 50

View file

@ -0,0 +1,63 @@
-- name: perms-find-user
SELECT
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_date
FROM perms
WHERE perm_user_id = $1
-- name: perms-find-user-repo
SELECT
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_synced
FROM perms
WHERE perm_user_id = $1
AND perm_repo_id = $2
-- name: perms-insert-replace
REPLACE INTO perms (
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_synced
) VALUES ($1,$2,$3,$4,$5,$6)
-- name: perms-insert-replace-lookup
INSERT INTO perms (
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_synced
) VALUES ($1,(SELECT repo_id FROM repos WHERE repo_full_name = $2),$3,$4,$5,$6)
ON CONFLICT (perm_user_id, perm_repo_id) DO UPDATE SET
perm_pull = EXCLUDED.perm_pull
,perm_push = EXCLUDED.perm_push
,perm_admin = EXCLUDED.perm_admin
,perm_synced = EXCLUDED.perm_synced
-- name: perms-delete-user-repo
DELETE FROM perms
WHERE perm_user_id = $1
AND perm_repo_id = $2
-- name: perms-delete-user-date
DELETE FROM perms
WHERE perm_user_id = $1
AND perm_synced < $2

View file

@ -3,3 +3,67 @@
UPDATE repos SET repo_counter = $1 UPDATE repos SET repo_counter = $1
WHERE repo_counter = $2 WHERE repo_counter = $2
AND repo_id = $3 AND repo_id = $3
-- name: repo-find-user
SELECT
repo_id
,repo_user_id
,repo_owner
,repo_name
,repo_full_name
,repo_avatar
,repo_link
,repo_clone
,repo_branch
,repo_timeout
,repo_private
,repo_trusted
,repo_active
,repo_allow_pr
,repo_allow_push
,repo_allow_deploys
,repo_allow_tags
,repo_hash
,repo_scm
,repo_config_path
,repo_gated
,repo_visibility
,repo_counter
FROM repos
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
WHERE perms.perm_user_id = $1
ORDER BY repo_full_name ASC
-- name: repo-insert-ignore
INSERT INTO repos (
repo_user_id
,repo_owner
,repo_name
,repo_full_name
,repo_avatar
,repo_link
,repo_clone
,repo_branch
,repo_timeout
,repo_private
,repo_trusted
,repo_active
,repo_allow_pr
,repo_allow_push
,repo_allow_deploys
,repo_allow_tags
,repo_hash
,repo_scm
,repo_config_path
,repo_gated
,repo_visibility
,repo_counter
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22)
ON CONFLICT (repo_full_name) DO NOTHING
-- name: repo-delete
DELETE FROM repos
WHERE repo_id = $1

View file

@ -6,35 +6,46 @@ func Lookup(name string) string {
} }
var index = map[string]string{ var index = map[string]string{
"config-find-id": configFindId, "config-find-id": configFindId,
"config-find-repo-hash": configFindRepoHash, "config-find-repo-hash": configFindRepoHash,
"config-find-approved": configFindApproved, "config-find-approved": configFindApproved,
"count-users": countUsers, "count-users": countUsers,
"count-repos": countRepos, "count-repos": countRepos,
"count-builds": countBuilds, "count-builds": countBuilds,
"files-find-build": filesFindBuild, "feed-latest-build": feedLatestBuild,
"files-find-proc-name": filesFindProcName, "feed": feed,
"files-find-proc-name-data": filesFindProcNameData, "files-find-build": filesFindBuild,
"files-delete-build": filesDeleteBuild, "files-find-proc-name": filesFindProcName,
"procs-find-id": procsFindId, "files-find-proc-name-data": filesFindProcNameData,
"procs-find-build": procsFindBuild, "files-delete-build": filesDeleteBuild,
"procs-find-build-pid": procsFindBuildPid, "perms-find-user": permsFindUser,
"procs-find-build-ppid": procsFindBuildPpid, "perms-find-user-repo": permsFindUserRepo,
"procs-delete-build": procsDeleteBuild, "perms-insert-replace": permsInsertReplace,
"registry-find-repo": registryFindRepo, "perms-insert-replace-lookup": permsInsertReplaceLookup,
"registry-find-repo-addr": registryFindRepoAddr, "perms-delete-user-repo": permsDeleteUserRepo,
"registry-delete-repo": registryDeleteRepo, "perms-delete-user-date": permsDeleteUserDate,
"registry-delete": registryDelete, "procs-find-id": procsFindId,
"repo-update-counter": repoUpdateCounter, "procs-find-build": procsFindBuild,
"secret-find-repo": secretFindRepo, "procs-find-build-pid": procsFindBuildPid,
"secret-find-repo-name": secretFindRepoName, "procs-find-build-ppid": procsFindBuildPpid,
"secret-delete": secretDelete, "procs-delete-build": procsDeleteBuild,
"sender-find-repo": senderFindRepo, "registry-find-repo": registryFindRepo,
"sender-find-repo-login": senderFindRepoLogin, "registry-find-repo-addr": registryFindRepoAddr,
"sender-delete-repo": senderDeleteRepo, "registry-delete-repo": registryDeleteRepo,
"sender-delete": senderDelete, "registry-delete": registryDelete,
"task-list": taskList, "repo-update-counter": repoUpdateCounter,
"task-delete": taskDelete, "repo-find-user": repoFindUser,
"repo-insert-ignore": repoInsertIgnore,
"repo-delete": repoDelete,
"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,
"task-list": taskList,
"task-delete": taskDelete,
} }
var configFindId = ` var configFindId = `
@ -81,6 +92,68 @@ SELECT reltuples
FROM pg_class WHERE relname = 'builds'; FROM pg_class WHERE relname = 'builds';
` `
var feedLatestBuild = `
SELECT
repo_owner
,repo_name
,repo_full_name
,build_number
,build_event
,build_status
,build_created
,build_started
,build_finished
,build_commit
,build_branch
,build_ref
,build_refspec
,build_remote
,build_title
,build_message
,build_author
,build_email
,build_avatar
FROM repos LEFT OUTER JOIN builds ON build_id = (
SELECT build_id FROM builds
WHERE builds.build_repo_id = repos.repo_id
ORDER BY build_id DESC
LIMIT 1
)
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
WHERE perms.perm_user_id = $1
AND repos.repo_active = true
ORDER BY repo_full_name ASC;
`
var feed = `
SELECT
repo_owner
,repo_name
,repo_full_name
,build_number
,build_event
,build_status
,build_created
,build_started
,build_finished
,build_commit
,build_branch
,build_ref
,build_refspec
,build_remote
,build_title
,build_message
,build_author
,build_email
,build_avatar
FROM repos
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
INNER JOIN builds ON builds.build_repo_id = repos.repo_id
WHERE perms.perm_user_id = $1
ORDER BY build_id DESC
LIMIT 50
`
var filesFindBuild = ` var filesFindBuild = `
SELECT SELECT
file_id file_id
@ -127,6 +200,70 @@ var filesDeleteBuild = `
DELETE FROM files WHERE file_build_id = $1 DELETE FROM files WHERE file_build_id = $1
` `
var permsFindUser = `
SELECT
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_date
FROM perms
WHERE perm_user_id = $1
`
var permsFindUserRepo = `
SELECT
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_synced
FROM perms
WHERE perm_user_id = $1
AND perm_repo_id = $2
`
var permsInsertReplace = `
REPLACE INTO perms (
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_synced
) VALUES ($1,$2,$3,$4,$5,$6)
`
var permsInsertReplaceLookup = `
INSERT INTO perms (
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_synced
) VALUES ($1,(SELECT repo_id FROM repos WHERE repo_full_name = $2),$3,$4,$5,$6)
ON CONFLICT (perm_user_id, perm_repo_id) DO UPDATE SET
perm_pull = EXCLUDED.perm_pull
,perm_push = EXCLUDED.perm_push
,perm_admin = EXCLUDED.perm_admin
,perm_synced = EXCLUDED.perm_synced
`
var permsDeleteUserRepo = `
DELETE FROM perms
WHERE perm_user_id = $1
AND perm_repo_id = $2
`
var permsDeleteUserDate = `
DELETE FROM perms
WHERE perm_user_id = $1
AND perm_synced < $2
`
var procsFindId = ` var procsFindId = `
SELECT SELECT
proc_id proc_id
@ -256,6 +393,70 @@ WHERE repo_counter = $2
AND repo_id = $3 AND repo_id = $3
` `
var repoFindUser = `
SELECT
repo_id
,repo_user_id
,repo_owner
,repo_name
,repo_full_name
,repo_avatar
,repo_link
,repo_clone
,repo_branch
,repo_timeout
,repo_private
,repo_trusted
,repo_active
,repo_allow_pr
,repo_allow_push
,repo_allow_deploys
,repo_allow_tags
,repo_hash
,repo_scm
,repo_config_path
,repo_gated
,repo_visibility
,repo_counter
FROM repos
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
WHERE perms.perm_user_id = $1
ORDER BY repo_full_name ASC
`
var repoInsertIgnore = `
INSERT INTO repos (
repo_user_id
,repo_owner
,repo_name
,repo_full_name
,repo_avatar
,repo_link
,repo_clone
,repo_branch
,repo_timeout
,repo_private
,repo_trusted
,repo_active
,repo_allow_pr
,repo_allow_push
,repo_allow_deploys
,repo_allow_tags
,repo_hash
,repo_scm
,repo_config_path
,repo_gated
,repo_visibility
,repo_counter
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22)
ON CONFLICT (repo_full_name) DO NOTHING
`
var repoDelete = `
DELETE FROM repos
WHERE repo_id = $1
`
var secretFindRepo = ` var secretFindRepo = `
SELECT SELECT
secret_id secret_id

View file

@ -0,0 +1,61 @@
-- name: feed-latest-build
SELECT
repo_owner
,repo_name
,repo_full_name
,build_number
,build_event
,build_status
,build_created
,build_started
,build_finished
,build_commit
,build_branch
,build_ref
,build_refspec
,build_remote
,build_title
,build_message
,build_author
,build_email
,build_avatar
FROM repos LEFT OUTER JOIN builds ON build_id = (
SELECT build_id FROM builds
WHERE builds.build_repo_id = repos.repo_id
ORDER BY build_id DESC
LIMIT 1
)
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
WHERE perms.perm_user_id = ?
AND repos.repo_active = 1
ORDER BY repo_full_name ASC;
-- name: feed
SELECT
repo_owner
,repo_name
,repo_full_name
,build_number
,build_event
,build_status
,build_created
,build_started
,build_finished
,build_commit
,build_branch
,build_ref
,build_refspec
,build_remote
,build_title
,build_message
,build_author
,build_email
,build_avatar
FROM repos
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
INNER JOIN builds ON builds.build_repo_id = repos.repo_id
WHERE perms.perm_user_id = ?
ORDER BY build_id DESC
LIMIT 50

View file

@ -0,0 +1,58 @@
-- name: perms-find-user
SELECT
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_date
FROM perms
WHERE perm_user_id = ?
-- name: perms-find-user-repo
SELECT
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_synced
FROM perms
WHERE perm_user_id = ?
AND perm_repo_id = ?
-- name: perms-insert-replace
REPLACE INTO perms (
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_synced
) VALUES (?,?,?,?,?,?)
-- name: perms-insert-replace-lookup
REPLACE INTO perms (
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_synced
) VALUES (?,(SELECT repo_id FROM repos WHERE repo_full_name = ?),?,?,?,?)
-- name: perms-delete-user-repo
DELETE FROM perms
WHERE perm_user_id = ?
AND perm_repo_id = ?
-- name: perms-delete-user-date
DELETE FROM perms
WHERE perm_user_id = ?
AND perm_synced < ?

View file

@ -3,3 +3,65 @@
UPDATE repos SET repo_counter = ? UPDATE repos SET repo_counter = ?
WHERE repo_counter = ? WHERE repo_counter = ?
AND repo_id = ? AND repo_id = ?
-- name: repo-find-user
SELECT
repo_id
,repo_user_id
,repo_owner
,repo_name
,repo_full_name
,repo_avatar
,repo_link
,repo_clone
,repo_branch
,repo_timeout
,repo_private
,repo_trusted
,repo_active
,repo_allow_pr
,repo_allow_push
,repo_allow_deploys
,repo_allow_tags
,repo_hash
,repo_scm
,repo_config_path
,repo_gated
,repo_visibility
,repo_counter
FROM repos
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
WHERE perms.perm_user_id = ?
ORDER BY repo_full_name ASC
-- name: repo-insert-ignore
INSERT OR IGNORE INTO repos (
repo_user_id
,repo_owner
,repo_name
,repo_full_name
,repo_avatar
,repo_link
,repo_clone
,repo_branch
,repo_timeout
,repo_private
,repo_trusted
,repo_active
,repo_allow_pr
,repo_allow_push
,repo_allow_deploys
,repo_allow_tags
,repo_hash
,repo_scm
,repo_config_path
,repo_gated
,repo_visibility
,repo_counter
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
-- name: repo-delete
DELETE FROM repos WHERE repo_id = ?

View file

@ -6,35 +6,46 @@ func Lookup(name string) string {
} }
var index = map[string]string{ var index = map[string]string{
"config-find-id": configFindId, "config-find-id": configFindId,
"config-find-repo-hash": configFindRepoHash, "config-find-repo-hash": configFindRepoHash,
"config-find-approved": configFindApproved, "config-find-approved": configFindApproved,
"count-users": countUsers, "count-users": countUsers,
"count-repos": countRepos, "count-repos": countRepos,
"count-builds": countBuilds, "count-builds": countBuilds,
"files-find-build": filesFindBuild, "feed-latest-build": feedLatestBuild,
"files-find-proc-name": filesFindProcName, "feed": feed,
"files-find-proc-name-data": filesFindProcNameData, "files-find-build": filesFindBuild,
"files-delete-build": filesDeleteBuild, "files-find-proc-name": filesFindProcName,
"procs-find-id": procsFindId, "files-find-proc-name-data": filesFindProcNameData,
"procs-find-build": procsFindBuild, "files-delete-build": filesDeleteBuild,
"procs-find-build-pid": procsFindBuildPid, "perms-find-user": permsFindUser,
"procs-find-build-ppid": procsFindBuildPpid, "perms-find-user-repo": permsFindUserRepo,
"procs-delete-build": procsDeleteBuild, "perms-insert-replace": permsInsertReplace,
"registry-find-repo": registryFindRepo, "perms-insert-replace-lookup": permsInsertReplaceLookup,
"registry-find-repo-addr": registryFindRepoAddr, "perms-delete-user-repo": permsDeleteUserRepo,
"registry-delete-repo": registryDeleteRepo, "perms-delete-user-date": permsDeleteUserDate,
"registry-delete": registryDelete, "procs-find-id": procsFindId,
"repo-update-counter": repoUpdateCounter, "procs-find-build": procsFindBuild,
"secret-find-repo": secretFindRepo, "procs-find-build-pid": procsFindBuildPid,
"secret-find-repo-name": secretFindRepoName, "procs-find-build-ppid": procsFindBuildPpid,
"secret-delete": secretDelete, "procs-delete-build": procsDeleteBuild,
"sender-find-repo": senderFindRepo, "registry-find-repo": registryFindRepo,
"sender-find-repo-login": senderFindRepoLogin, "registry-find-repo-addr": registryFindRepoAddr,
"sender-delete-repo": senderDeleteRepo, "registry-delete-repo": registryDeleteRepo,
"sender-delete": senderDelete, "registry-delete": registryDelete,
"task-list": taskList, "repo-update-counter": repoUpdateCounter,
"task-delete": taskDelete, "repo-find-user": repoFindUser,
"repo-insert-ignore": repoInsertIgnore,
"repo-delete": repoDelete,
"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,
"task-list": taskList,
"task-delete": taskDelete,
} }
var configFindId = ` var configFindId = `
@ -81,6 +92,68 @@ SELECT count(1)
FROM builds FROM builds
` `
var feedLatestBuild = `
SELECT
repo_owner
,repo_name
,repo_full_name
,build_number
,build_event
,build_status
,build_created
,build_started
,build_finished
,build_commit
,build_branch
,build_ref
,build_refspec
,build_remote
,build_title
,build_message
,build_author
,build_email
,build_avatar
FROM repos LEFT OUTER JOIN builds ON build_id = (
SELECT build_id FROM builds
WHERE builds.build_repo_id = repos.repo_id
ORDER BY build_id DESC
LIMIT 1
)
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
WHERE perms.perm_user_id = ?
AND repos.repo_active = 1
ORDER BY repo_full_name ASC;
`
var feed = `
SELECT
repo_owner
,repo_name
,repo_full_name
,build_number
,build_event
,build_status
,build_created
,build_started
,build_finished
,build_commit
,build_branch
,build_ref
,build_refspec
,build_remote
,build_title
,build_message
,build_author
,build_email
,build_avatar
FROM repos
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
INNER JOIN builds ON builds.build_repo_id = repos.repo_id
WHERE perms.perm_user_id = ?
ORDER BY build_id DESC
LIMIT 50
`
var filesFindBuild = ` var filesFindBuild = `
SELECT SELECT
file_id file_id
@ -127,6 +200,65 @@ var filesDeleteBuild = `
DELETE FROM files WHERE file_build_id = ? DELETE FROM files WHERE file_build_id = ?
` `
var permsFindUser = `
SELECT
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_date
FROM perms
WHERE perm_user_id = ?
`
var permsFindUserRepo = `
SELECT
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_synced
FROM perms
WHERE perm_user_id = ?
AND perm_repo_id = ?
`
var permsInsertReplace = `
REPLACE INTO perms (
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_synced
) VALUES (?,?,?,?,?,?)
`
var permsInsertReplaceLookup = `
REPLACE INTO perms (
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_synced
) VALUES (?,(SELECT repo_id FROM repos WHERE repo_full_name = ?),?,?,?,?)
`
var permsDeleteUserRepo = `
DELETE FROM perms
WHERE perm_user_id = ?
AND perm_repo_id = ?
`
var permsDeleteUserDate = `
DELETE FROM perms
WHERE perm_user_id = ?
AND perm_synced < ?
`
var procsFindId = ` var procsFindId = `
SELECT SELECT
proc_id proc_id
@ -256,6 +388,68 @@ WHERE repo_counter = ?
AND repo_id = ? AND repo_id = ?
` `
var repoFindUser = `
SELECT
repo_id
,repo_user_id
,repo_owner
,repo_name
,repo_full_name
,repo_avatar
,repo_link
,repo_clone
,repo_branch
,repo_timeout
,repo_private
,repo_trusted
,repo_active
,repo_allow_pr
,repo_allow_push
,repo_allow_deploys
,repo_allow_tags
,repo_hash
,repo_scm
,repo_config_path
,repo_gated
,repo_visibility
,repo_counter
FROM repos
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
WHERE perms.perm_user_id = ?
ORDER BY repo_full_name ASC
`
var repoInsertIgnore = `
INSERT OR IGNORE INTO repos (
repo_user_id
,repo_owner
,repo_name
,repo_full_name
,repo_avatar
,repo_link
,repo_clone
,repo_branch
,repo_timeout
,repo_private
,repo_trusted
,repo_active
,repo_allow_pr
,repo_allow_push
,repo_allow_deploys
,repo_allow_tags
,repo_hash
,repo_scm
,repo_config_path
,repo_gated
,repo_visibility
,repo_counter
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
`
var repoDelete = `
DELETE FROM repos WHERE repo_id = ?
`
var secretFindRepo = ` var secretFindRepo = `
SELECT SELECT
secret_id secret_id

View file

@ -1,8 +1,6 @@
package datastore package datastore
import ( import (
"fmt"
"github.com/drone/drone/model" "github.com/drone/drone/model"
"github.com/drone/drone/store/datastore/sql" "github.com/drone/drone/store/datastore/sql"
"github.com/russross/meddler" "github.com/russross/meddler"
@ -26,46 +24,6 @@ func (db *datastore) GetUserList() ([]*model.User, error) {
return users, err return users, err
} }
func (db *datastore) GetUserFeed(listof []*model.RepoLite) ([]*model.Feed, error) {
var (
args []interface{}
stmt string
err error
feed = []*model.Feed{}
)
switch meddler.Default {
case meddler.PostgreSQL:
stmt, args = toListPostgres(listof)
default:
stmt, args = toList(listof)
}
if len(args) > 0 {
err = meddler.QueryAll(db, &feed, fmt.Sprintf(userFeedQuery, stmt), args...)
}
return feed, err
}
func (db *datastore) GetUserFeedLatest(listof []*model.RepoLite) ([]*model.Feed, error) {
var (
args []interface{}
stmt string
err error
feed = []*model.Feed{}
)
switch meddler.Default {
case meddler.PostgreSQL:
stmt, args = toListPostgres(listof)
default:
stmt, args = toList(listof)
}
if len(args) > 0 {
err = meddler.QueryAll(db, &feed, fmt.Sprintf(userFeedLatest, stmt), args...)
}
return feed, err
}
func (db *datastore) GetUserCount() (count int, err error) { func (db *datastore) GetUserCount() (count int, err error) {
err = db.QueryRow( err = db.QueryRow(
sql.Lookup(db.driver, "count-users"), sql.Lookup(db.driver, "count-users"),
@ -86,6 +44,13 @@ func (db *datastore) DeleteUser(user *model.User) error {
return err return err
} }
func (db *datastore) UserFeed(user *model.User) ([]*model.Feed, error) {
stmt := sql.Lookup(db.driver, "feed")
data := []*model.Feed{}
err := meddler.QueryAll(db, &data, stmt, user.ID)
return data, err
}
const userTable = "users" const userTable = "users"
const userLoginQuery = ` const userLoginQuery = `
@ -101,75 +66,7 @@ FROM users
ORDER BY user_login ASC ORDER BY user_login ASC
` `
const userCountQuery = `
SELECT count(1)
FROM users
`
const userDeleteStmt = ` const userDeleteStmt = `
DELETE FROM users DELETE FROM users
WHERE user_id=? WHERE user_id=?
` `
const userFeedQuery = `
SELECT
repo_owner
,repo_name
,repo_full_name
,build_number
,build_event
,build_status
,build_created
,build_started
,build_finished
,build_commit
,build_branch
,build_ref
,build_refspec
,build_remote
,build_title
,build_message
,build_author
,build_email
,build_avatar
FROM
builds b
,repos r
WHERE b.build_repo_id = r.repo_id
AND r.repo_full_name IN (%s)
ORDER BY b.build_id DESC
LIMIT 50
`
// thanks to this article for helping me find a sane sql query
// https://www.periscopedata.com/blog/4-ways-to-join-only-the-first-row-in-sql.html
const userFeedLatest = `
SELECT
repo_owner
,repo_name
,repo_full_name
,build_number
,build_event
,build_status
,build_created
,build_started
,build_finished
,build_commit
,build_branch
,build_ref
,build_refspec
,build_remote
,build_title
,build_message
,build_author
,build_email
,build_avatar
FROM repos LEFT OUTER JOIN builds ON build_id = (
SELECT build_id FROM builds
WHERE builds.build_repo_id = repos.repo_id
ORDER BY build_id DESC
LIMIT 1
)
WHERE repo_full_name IN (%s)
`

View file

@ -20,7 +20,7 @@ func TestUsers(t *testing.T) {
s.Exec("DELETE FROM users") s.Exec("DELETE FROM users")
s.Exec("DELETE FROM repos") s.Exec("DELETE FROM repos")
s.Exec("DELETE FROM builds") s.Exec("DELETE FROM builds")
s.Exec("DELETE FROM jobs") s.Exec("DELETE FROM procs")
}) })
g.It("Should Update a User", func() { g.It("Should Update a User", func() {
@ -166,28 +166,40 @@ func TestUsers(t *testing.T) {
}) })
g.It("Should get the Build feed for a User", func() { g.It("Should get the Build feed for a User", func() {
user := &model.User{
Login: "joe",
Email: "foo@bar.com",
Token: "e42080dddf012c718e476da161d21ad5",
}
s.CreateUser(user)
repo1 := &model.Repo{ repo1 := &model.Repo{
UserID: 1,
Owner: "bradrydzewski", Owner: "bradrydzewski",
Name: "drone", Name: "drone",
FullName: "bradrydzewski/drone", FullName: "bradrydzewski/drone",
IsActive: true,
} }
repo2 := &model.Repo{ repo2 := &model.Repo{
UserID: 2,
Owner: "drone", Owner: "drone",
Name: "drone", Name: "drone",
FullName: "drone/drone", FullName: "drone/drone",
IsActive: true,
} }
repo3 := &model.Repo{ repo3 := &model.Repo{
UserID: 2,
Owner: "octocat", Owner: "octocat",
Name: "hello-world", Name: "hello-world",
FullName: "octocat/hello-world", FullName: "octocat/hello-world",
IsActive: true,
} }
s.CreateRepo(repo1) s.CreateRepo(repo1)
s.CreateRepo(repo2) s.CreateRepo(repo2)
s.CreateRepo(repo3) s.CreateRepo(repo3)
s.PermBatch([]*model.Perm{
{UserID: user.ID, Repo: repo1.FullName},
{UserID: user.ID, Repo: repo2.FullName},
})
build1 := &model.Build{ build1 := &model.Build{
RepoID: repo1.ID, RepoID: repo1.ID,
Status: model.StatusFailure, Status: model.StatusFailure,
@ -209,10 +221,7 @@ func TestUsers(t *testing.T) {
s.CreateBuild(build3) s.CreateBuild(build3)
s.CreateBuild(build4) s.CreateBuild(build4)
builds, err := s.GetUserFeed([]*model.RepoLite{ builds, err := s.UserFeed(user)
{FullName: "bradrydzewski/drone"},
{FullName: "drone/drone"},
})
g.Assert(err == nil).IsTrue() g.Assert(err == nil).IsTrue()
g.Assert(len(builds)).Equal(3) g.Assert(len(builds)).Equal(3)
g.Assert(builds[0].FullName).Equal(repo2.FullName) g.Assert(builds[0].FullName).Equal(repo2.FullName)

View file

@ -2,9 +2,7 @@ package datastore
import ( import (
"strconv" "strconv"
"strings"
"github.com/drone/drone/model"
"github.com/russross/meddler" "github.com/russross/meddler"
) )
@ -35,41 +33,3 @@ func rebind(query string) string {
} }
return string(rqb) return string(rqb)
} }
// helper function that converts a simple repsitory list
// to a sql IN statment.
func toList(listof []*model.RepoLite) (string, []interface{}) {
var size = len(listof)
switch {
case meddler.Default == meddler.SQLite && size > 999:
size = 999
listof = listof[:999]
case size > 15000:
size = 15000
listof = listof[:15000]
}
var qs = make([]string, size, size)
var in = make([]interface{}, size, size)
for i, repo := range listof {
qs[i] = "?"
in[i] = repo.FullName
}
return strings.Join(qs, ","), in
}
// helper function that converts a simple repository list
// to a sql IN statement compatible with postgres.
func toListPostgres(listof []*model.RepoLite) (string, []interface{}) {
var size = len(listof)
if size > 15000 {
size = 15000
listof = listof[:15000]
}
var qs = make([]string, size, size)
var in = make([]interface{}, size, size)
for i, repo := range listof {
qs[i] = "$" + strconv.Itoa(i+1)
in[i] = repo.FullName
}
return strings.Join(qs, ","), in
}

View file

@ -18,13 +18,6 @@ type Store interface {
// GetUserList gets a list of all users in the system. // GetUserList gets a list of all users in the system.
GetUserList() ([]*model.User, error) GetUserList() ([]*model.User, error)
// GetUserFeed gets a user activity feed.
GetUserFeed([]*model.RepoLite) ([]*model.Feed, error)
// GetUserFeedLatest gets a user activity feed for all repositories including
// only the latest build for each repository.
GetUserFeedLatest(listof []*model.RepoLite) ([]*model.Feed, error)
// GetUserCount gets a count of all users in the system. // GetUserCount gets a count of all users in the system.
GetUserCount() (int, error) GetUserCount() (int, error)
@ -43,9 +36,6 @@ type Store interface {
// GetRepoName gets a repo by its full name. // GetRepoName gets a repo by its full name.
GetRepoName(string) (*model.Repo, error) GetRepoName(string) (*model.Repo, error)
// GetRepoListOf gets the list of enumerated repos in the system.
GetRepoListOf([]*model.RepoLite) ([]*model.Repo, error)
// GetRepoCount gets a count of all repositories in the system. // GetRepoCount gets a count of all repositories in the system.
GetRepoCount() (int, error) GetRepoCount() (int, error)
@ -92,6 +82,18 @@ type Store interface {
// new functions // new functions
// //
UserFeed(*model.User) ([]*model.Feed, error)
RepoList(*model.User) ([]*model.Repo, error)
RepoListLatest(*model.User) ([]*model.Feed, error)
RepoBatch([]*model.Repo) error
PermFind(user *model.User, repo *model.Repo) (*model.Perm, error)
PermUpsert(perm *model.Perm) error
PermBatch(perms []*model.Perm) error
PermDelete(perm *model.Perm) error
PermFlush(user *model.User, before int64) error
ConfigLoad(int64) (*model.Config, error) ConfigLoad(int64) (*model.Config, error)
ConfigFind(*model.Repo, string) (*model.Config, error) ConfigFind(*model.Repo, string) (*model.Config, error)
ConfigFindApproved(*model.Config) (bool, error) ConfigFindApproved(*model.Config) (bool, error)
@ -151,14 +153,6 @@ func GetUserList(c context.Context) ([]*model.User, error) {
return FromContext(c).GetUserList() return FromContext(c).GetUserList()
} }
// GetUserFeed gets a user activity feed.
func GetUserFeed(c context.Context, listof []*model.RepoLite, latest bool) ([]*model.Feed, error) {
if latest {
return FromContext(c).GetUserFeedLatest(listof)
}
return FromContext(c).GetUserFeed(listof)
}
// GetUserCount gets a count of all users in the system. // GetUserCount gets a count of all users in the system.
func GetUserCount(c context.Context) (int, error) { func GetUserCount(c context.Context) (int, error) {
return FromContext(c).GetUserCount() return FromContext(c).GetUserCount()
@ -188,10 +182,6 @@ func GetRepoOwnerName(c context.Context, owner, name string) (*model.Repo, error
return FromContext(c).GetRepoName(owner + "/" + name) return FromContext(c).GetRepoName(owner + "/" + name)
} }
func GetRepoListOf(c context.Context, listof []*model.RepoLite) ([]*model.Repo, error) {
return FromContext(c).GetRepoListOf(listof)
}
func CreateRepo(c context.Context, repo *model.Repo) error { func CreateRepo(c context.Context, repo *model.Repo) error {
return FromContext(c).CreateRepo(repo) return FromContext(c).CreateRepo(repo)
} }

View file

@ -1,27 +0,0 @@
Copyright (c) 2013, Ian Schenck
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
Neither the name of Ian Schenck nor the names of its contributors
may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -1,37 +0,0 @@
envflag
=======
Golang flags, but bolted onto the environment rather than the command-line.
Read the [godocs](http://godoc.org/github.com/ianschenck/envflag).
Motivation
==========
Some like the distinction that command-line flags control behavior
while environment variables configure. Also
[12-factor](http://12factor.net/) recommends the use of environment
variables for configuration. The interface of the golang flag package
is well designed and easy to use, and allows for other lists
(os.Environ() vs os.Args) to be parsed as flags. It makes sense then
to use the same interface, the same types, and the same parsing
(caveat: there is some ugly string hacking to make environment
variables look like flags) to the same ends.
Differences
===========
Calling `flag.Parse()` will not parse environment flags. Calling
`envflag.Parse()` will not parse command-line flags. There is no good
reason to combine these two when the net savings is a single line in a
`func main()`. Furthermore, doing so would require users to accept a
precedence order of my choosing.
The presence of an environment variable named `h` or `help` will
probably cause problems (print Usage and os.Exit(0)). Work around this
by defining those flags somewhere (and ignoring them).
Before calling `Flagset.Parse` on `EnvironmentFlags`, the environment
variables being passed to `Parse` are trimmed down using
`Lookup`. This behavior is different from `flag.Parse` in that extra
environment variables are ignored (and won't crash `envflag.Parse`).

View file

@ -1,192 +0,0 @@
// Copyright 2013 Ian Schenck. Use of this source code is governed by
// a license that can be found in the LICENSE file.
/*
Package envflag adds environment variable flags to the flag package.
Usage:
Define flags using envflag.String(), Bool(), Int(), etc. This package
works nearly the same as the stdlib flag package. Parsing the
Environment flags is done by calling envflag.Parse()
It will *not* attempt to parse any normally-defined command-line
flags. Command-line flags are explicitly left alone and separate.
*/
package envflag
import (
"flag"
"fmt"
"os"
"strings"
"time"
)
// VisitAll visits the environment flags in lexicographical order,
// calling fn for each. It visits all flags, even those not set.
func VisitAll(fn func(*flag.Flag)) {
EnvironmentFlags.VisitAll(fn)
}
// Visit visits the environment flags in lexicographical order,
// calling fn for each. It visits only those flags that have been
// set.
func Visit(fn func(*flag.Flag)) {
EnvironmentFlags.Visit(fn)
}
// Lookup returns the Flag structure of the named environment flag,
// returning nil if none exists.
func Lookup(name string) *flag.Flag {
return EnvironmentFlags.Lookup(name)
}
// Set sets the value of the named environment flag.
func Set(name, value string) error {
return EnvironmentFlags.Set(name, value)
}
// BoolVar defines a bool flag with specified name, default value, and
// usage string. The argument p points to a bool variable in which to
// store the value of the flag.
func BoolVar(p *bool, name string, value bool, usage string) {
EnvironmentFlags.BoolVar(p, name, value, usage)
}
// Bool defines a bool flag with specified name, default value, and
// usage string. The return value is the address of a bool variable
// that stores the value of the flag.
func Bool(name string, value bool, usage string) *bool {
return EnvironmentFlags.Bool(name, value, usage)
}
// IntVar defines an int flag with specified name, default value, and
// usage string. The argument p points to an int variable in which to
// store the value of the flag.
func IntVar(p *int, name string, value int, usage string) {
EnvironmentFlags.IntVar(p, name, value, usage)
}
// Int defines an int flag with specified name, default value, and
// usage string. The return value is the address of an int variable
// that stores the value of the flag.
func Int(name string, value int, usage string) *int {
return EnvironmentFlags.Int(name, value, usage)
}
// Int64Var defines an int64 flag with specified name, default value,
// and usage string. The argument p points to an int64 variable in
// which to store the value of the flag.
func Int64Var(p *int64, name string, value int64, usage string) {
EnvironmentFlags.Int64Var(p, name, value, usage)
}
// Int64 defines an int64 flag with specified name, default value, and
// usage string. The return value is the address of an int64 variable
// that stores the value of the flag.
func Int64(name string, value int64, usage string) *int64 {
return EnvironmentFlags.Int64(name, value, usage)
}
// UintVar defines a uint flag with specified name, default value, and
// usage string. The argument p points to a uint variable in which to
// store the value of the flag.
func UintVar(p *uint, name string, value uint, usage string) {
EnvironmentFlags.UintVar(p, name, value, usage)
}
// Uint defines a uint flag with specified name, default value, and
// usage string. The return value is the address of a uint variable
// that stores the value of the flag.
func Uint(name string, value uint, usage string) *uint {
return EnvironmentFlags.Uint(name, value, usage)
}
// Uint64Var defines a uint64 flag with specified name, default value,
// and usage string. The argument p points to a uint64 variable in
// which to store the value of the flag.
func Uint64Var(p *uint64, name string, value uint64, usage string) {
EnvironmentFlags.Uint64Var(p, name, value, usage)
}
// Uint64 defines a uint64 flag with specified name, default value,
// and usage string. The return value is the address of a uint64
// variable that stores the value of the flag.
func Uint64(name string, value uint64, usage string) *uint64 {
return EnvironmentFlags.Uint64(name, value, usage)
}
// StringVar defines a string flag with specified name, default value,
// and usage string. The argument p points to a string variable in
// which to store the value of the flag.
func StringVar(p *string, name string, value string, usage string) {
EnvironmentFlags.StringVar(p, name, value, usage)
}
// String defines a string flag with specified name, default value,
// and usage string. The return value is the address of a string
// variable that stores the value of the flag.
func String(name string, value string, usage string) *string {
return EnvironmentFlags.String(name, value, usage)
}
// Float64Var defines a float64 flag with specified name, default
// value, and usage string. The argument p points to a float64
// variable in which to store the value of the flag.
func Float64Var(p *float64, name string, value float64, usage string) {
EnvironmentFlags.Float64Var(p, name, value, usage)
}
// Float64 defines a float64 flag with specified name, default value,
// and usage string. The return value is the address of a float64
// variable that stores the value of the flag.
func Float64(name string, value float64, usage string) *float64 {
return EnvironmentFlags.Float64(name, value, usage)
}
// DurationVar defines a time.Duration flag with specified name,
// default value, and usage string. The argument p points to a
// time.Duration variable in which to store the value of the flag.
func DurationVar(p *time.Duration, name string, value time.Duration, usage string) {
EnvironmentFlags.DurationVar(p, name, value, usage)
}
// Duration defines a time.Duration flag with specified name, default
// value, and usage string. The return value is the address of a
// time.Duration variable that stores the value of the flag.
func Duration(name string, value time.Duration, usage string) *time.Duration {
return EnvironmentFlags.Duration(name, value, usage)
}
// PrintDefaults prints to standard error the default values of all
// defined environment flags.
func PrintDefaults() {
EnvironmentFlags.PrintDefaults()
}
// Parse parses the environment flags from os.Environ. Must be called
// after all flags are defined and before flags are accessed by the
// program.
func Parse() {
env := os.Environ()
// Clean up and "fake" some flag k/v pairs.
args := make([]string, 0, len(env))
for _, value := range env {
if Lookup(value[:strings.Index(value, "=")]) == nil {
continue
}
args = append(args, fmt.Sprintf("-%s", value))
}
EnvironmentFlags.Parse(args)
}
// Parsed returns true if the environment flags have been parsed.
func Parsed() bool {
return EnvironmentFlags.Parsed()
}
// EnvironmentFlags is the default set of environment flags, parsed
// from os.Environ(). The top-level functions such as BoolVar, Arg,
// and on are wrappers for the methods of EnvironmentFlags.
var EnvironmentFlags = flag.NewFlagSet("environment", flag.ExitOnError)

View file

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -1,42 +0,0 @@
# syscerts
Gather local system certificates in Go via a public `SystemRootsPool` method.
#### What does this do?
Provide a way to gather local system certificates
on different OS platforms.
#### How does it do it?
It uses the `crypto/x509` package and provides a single public method called
`SystemRootsPool()` to return a `*x509.CertPool` object.
#### How do you use it?
```Go
// gather CA certs
certpool := syscerts.SystemRootsPool()
// place them in an HTTP client for trusted SSL/TLS connections
tlsConfig := &tls.Config{RootCAs: certpool}
transport := &http.Transport{TLSClientConfig: tlsConfig}
client := &http.Client{Transport: transport}
// make a request
resp, err := client.Do(req)
```
#### Why even do it?
The `crypto/x509` package already has a `systemRootsPool` method.
The `crypto/x509.systemRootsPool` method is almost the same as
`github.com/jackspirou/syscerts.SystemRootsPool`.
The difference? The `crypto/x509.systemRootsPool` method is private so you
cannot access it. :(
There are plans for the `crypto/x509.systemRootsPool` method to become public
in Go 1.7. When this happens you might no longer need `github.com/jackspirou/syscerts.SystemRootsPool`.
The only reason you may still use this package after the Go 1.7 release might
be for the Mac OSX System Keychain certs which are not included in the
`crypto/x509` package. Relevant lines below:
* https://github.com/jackspirou/syscerts/blob/master/root_darwin.go#L24-L32
Find more about this Go issue here: https://github.com/golang/go/issues/13335

View file

@ -1,22 +0,0 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package syscerts
import (
"crypto/x509"
"sync"
)
var (
once sync.Once
systemRoots *x509.CertPool
)
// SystemRootsPool attempts to find and return a pool of all all installed
// system certificates.
func SystemRootsPool() *x509.CertPool {
once.Do(initSystemRoots)
return systemRoots
}

View file

@ -1,14 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build dragonfly freebsd netbsd openbsd
package syscerts
// Possible certificate files; stop after finding one.
var certFiles = []string{
"/usr/local/share/certs/ca-root-nss.crt", // FreeBSD/DragonFly
"/etc/ssl/cert.pem", // OpenBSD
"/etc/openssl/certs/ca-certificates.crt", // NetBSD
}

View file

@ -1,85 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build cgo,darwin,!arm,!arm64,!ios
package syscerts
/*
#cgo CFLAGS: -mmacosx-version-min=10.6 -D__MAC_OS_X_VERSION_MAX_ALLOWED=1060
#cgo LDFLAGS: -framework CoreFoundation -framework Security
#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
// FetchPEMRootsC fetches the system's list of trusted X.509 root certificates.
//
// On success it returns 0 and fills pemRoots with a CFDataRef that contains the extracted root
// certificates of the system. On failure, the function returns -1.
//
// Note: The CFDataRef returned in pemRoots must be released (using CFRelease) after
// we've consumed its content.
int FetchPEMRootsC(CFDataRef *pemRoots) {
if (pemRoots == NULL) {
return -1;
}
CFArrayRef certs = NULL;
OSStatus err = SecTrustCopyAnchorCertificates(&certs);
if (err != noErr) {
return -1;
}
CFMutableDataRef combinedData = CFDataCreateMutable(kCFAllocatorDefault, 0);
int i, ncerts = CFArrayGetCount(certs);
for (i = 0; i < ncerts; i++) {
CFDataRef data = NULL;
SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(certs, i);
if (cert == NULL) {
continue;
}
// Note: SecKeychainItemExport is deprecated as of 10.7 in favor of SecItemExport.
// Once we support weak imports via cgo we should prefer that, and fall back to this
// for older systems.
err = SecKeychainItemExport(cert, kSecFormatX509Cert, kSecItemPemArmour, NULL, &data);
if (err != noErr) {
continue;
}
if (data != NULL) {
CFDataAppendBytes(combinedData, CFDataGetBytePtr(data), CFDataGetLength(data));
CFRelease(data);
}
}
CFRelease(certs);
*pemRoots = combinedData;
return 0;
}
*/
/*
import "C"
import (
"crypto/x509"
"unsafe"
)
func initSystemRoots() {
roots := x509.NewCertPool()
var data C.CFDataRef = nil
err := C.FetchPEMRootsC(&data)
if err == -1 {
return
}
defer C.CFRelease(C.CFTypeRef(data))
buf := C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(data)), C.int(C.CFDataGetLength(data)))
roots.AppendCertsFromPEM(buf)
systemRoots = roots
}
*/

View file

@ -1,39 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:generate go run root_darwin_arm_gen.go -output root_darwin_armx.go
package syscerts
import (
"crypto/x509"
"os"
"os/exec"
)
func execSecurityRoots() (*x509.CertPool, error) {
roots := x509.NewCertPool()
cmd := exec.Command("/usr/bin/security", "find-certificate", "-a", "-p", "/System/Library/Keychains/SystemRootCertificates.keychain")
data, err := cmd.Output()
if err != nil {
return nil, err
}
roots.AppendCertsFromPEM(data)
// if available add the Mac OSX System Keychain
if _, err := os.Stat("/Library/Keychains/System.keychain"); err == nil {
cmd = exec.Command("/usr/bin/security", "find-certificate", "-a", "-p", "/Library/Keychains/System.keychain")
data, err = cmd.Output()
if err != nil {
return nil, err
}
roots.AppendCertsFromPEM(data)
}
return roots, nil
}
func initSystemRoots() {
systemRoots, _ = execSecurityRoots()
}

File diff suppressed because it is too large Load diff

View file

@ -1,13 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package syscerts
// Possible certificate files; stop after finding one.
var certFiles = []string{
"/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc.
"/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL
"/etc/ssl/ca-bundle.pem", // OpenSUSE
"/etc/pki/tls/cacert.pem", // OpenELEC
}

View file

@ -1,8 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package syscerts
// Possible certificate files; stop after finding one.
var certFiles = []string{}

View file

@ -1,32 +0,0 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build plan9
package syscerts
import (
"crypto/x509"
"io/ioutil"
)
// Possible certificate files; stop after finding one.
var certFiles = []string{
"/sys/lib/tls/ca.pem",
}
func initSystemRoots() {
roots := x509.NewCertPool()
for _, file := range certFiles {
data, err := ioutil.ReadFile(file)
if err == nil {
roots.AppendCertsFromPEM(data)
systemRoots = roots
return
}
}
// All of the files failed to load. systemRoots will be nil which will
// trigger a specific error at verification time.
}

View file

@ -1,12 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package syscerts
// Possible certificate files; stop after finding one.
var certFiles = []string{
"/etc/certs/ca-certificates.crt", // Solaris 11.2+
"/etc/ssl/certs/ca-certificates.crt", // Joyent SmartOS
"/etc/ssl/cacert.pem", // OmniOS
}

Some files were not shown because too many files have changed in this diff Show more