diff --git a/README.md b/README.md index 8b90ae4be..f0406d00c 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ secret="" [gitlab] url="" +skip_verify=false [smtp] host="" @@ -122,9 +123,11 @@ from="" user="" pass="" -[worker] +[docker] cert="" key="" + +[worker] nodes=[ "unix:///var/run/docker.sock", "unix:///var/run/docker.sock" @@ -169,6 +172,7 @@ export DRONE_BITBUCKET_SECRET="" # gitlab configuration export DRONE_GITLAB_URL="" +export DRONE_GITLAB_SKIP_VERIFY=false # email configuration export DRONE_SMTP_HOST="" @@ -216,4 +220,4 @@ You will need to include a `.drone.yml` file in the root of your repository in o configure a build. I'm still working on updated documentation, so in the meantime please refer to the `0.2` README to learn more about the `.drone.yml` format: -https://github.com/drone/drone/blob/v0.2.1/README.md#builds \ No newline at end of file +https://github.com/drone/drone/blob/v0.2.1/README.md#builds diff --git a/cli/build.go b/cli/build.go index 0c5a3e1b5..c14fb09f0 100644 --- a/cli/build.go +++ b/cli/build.go @@ -43,17 +43,17 @@ func NewBuildCommand() cli.Command { }, cli.StringFlag{ Name: "docker-host", - Value: "", + Value: getHost(), Usage: "docker daemon address", }, cli.StringFlag{ Name: "docker-cert", - Value: "", + Value: getCert(), Usage: "docker daemon tls certificate", }, cli.StringFlag{ Name: "docker-key", - Value: "", + Value: getKey(), Usage: "docker daemon tls key", }, }, @@ -115,7 +115,7 @@ func run(path, identity, dockerhost, dockercert, dockerkey string, publish, depl envs := getParamMap("DRONE_ENV_") // parse the Drone yml file - s, err := script.ParseBuildFile(script.Inject(path, envs)) + s, err := script.ParseBuildFile(path, envs) if err != nil { log.Err(err.Error()) return EXIT_STATUS, err @@ -127,10 +127,10 @@ func run(path, identity, dockerhost, dockercert, dockerkey string, publish, depl } if deploy == false { - s.Publish = nil + s.Deploy = nil } if publish == false { - s.Deploy = nil + s.Publish = nil } // get the repository root directory @@ -204,3 +204,23 @@ func run(path, identity, dockerhost, dockercert, dockerkey string, publish, depl return builder.BuildState.ExitCode, nil } + +func getHost() string { + return os.Getenv("DOCKER_HOST") +} + +func getCert() string { + if os.Getenv("DOCKER_CERT_PATH") != "" && os.Getenv("DOCKER_TLS_VERIFY") == "1" { + return filepath.Join(os.Getenv("DOCKER_CERT_PATH"), "cert.pem") + } else { + return "" + } +} + +func getKey() string { + if os.Getenv("DOCKER_CERT_PATH") != "" && os.Getenv("DOCKER_TLS_VERIFY") == "1" { + return filepath.Join(os.Getenv("DOCKER_CERT_PATH"), "key.pem") + } else { + return "" + } +} diff --git a/packaging/root/etc/drone/drone.toml b/packaging/root/etc/drone/drone.toml index a53f2e1f0..6332c7404 100644 --- a/packaging/root/etc/drone/drone.toml +++ b/packaging/root/etc/drone/drone.toml @@ -11,7 +11,7 @@ port=":80" # [session] # secret="" -# duration="" +# expires="" ##################################################################### # Database configuration, by default using SQLite3. @@ -51,6 +51,7 @@ datasource="/var/lib/drone/drone.sqlite" # [gitlab] # url="" +# skip_verify=false ##################################################################### @@ -64,10 +65,12 @@ datasource="/var/lib/drone/drone.sqlite" # user="" # pass="" -# [worker] +# [docker] # cert="" # key="" + +# [worker] # nodes=[ # "unix:///var/run/docker.sock", # "unix:///var/run/docker.sock" -# ] \ No newline at end of file +# ] diff --git a/plugin/deploy/deis/deis.go b/plugin/deploy/deis/deis.go new file mode 100644 index 000000000..acc5a7439 --- /dev/null +++ b/plugin/deploy/deis/deis.go @@ -0,0 +1,56 @@ +package deis + +import ( + "fmt" + "github.com/drone/drone/plugin/condition" + "github.com/drone/drone/shared/build/buildfile" +) + +const ( + // Gommand to the current commit hash + CmdRevParse = "COMMIT=$(git rev-parse HEAD)" + + // Command to set the git user and email based on the + // individual that made the commit. + CmdGlobalEmail = "git config --global user.email $(git --no-pager log -1 --pretty=format:'%ae')" + CmdGlobalUser = "git config --global user.name $(git --no-pager log -1 --pretty=format:'%an')" +) + +// deploy: +// deis: +// app: safe-island-6261 +// deisurl: deis.myurl.tdl:2222/ + +type Deis struct { + App string `yaml:"app,omitempty"` + Force bool `yaml:"force,omitempty"` + Deisurl string `yaml:"deisurl,omitempty"` + Condition *condition.Condition `yaml:"when,omitempty"` +} + +func (h *Deis) Write(f *buildfile.Buildfile) { + f.WriteCmdSilent(CmdRevParse) + f.WriteCmdSilent(CmdGlobalUser) + f.WriteCmdSilent(CmdGlobalEmail) + + // git@deis.yourdomain.com:2222/drone.git + + f.WriteCmd(fmt.Sprintf("git remote add deis ssh://git@%s%s.git", h.Deisurl , h.App)) + + switch h.Force { + case true: + // this is useful when the there are artifacts generated + // by the build script, such as less files converted to css, + // that need to be deployed to Deis. + f.WriteCmd(fmt.Sprintf("git add -A")) + f.WriteCmd(fmt.Sprintf("git commit -m 'adding build artifacts'")) + f.WriteCmd(fmt.Sprintf("git push deis HEAD:master --force")) + case false: + // otherwise we just do a standard git push + f.WriteCmd(fmt.Sprintf("git push deis $COMMIT:master")) + } +} + +func (h *Deis) GetCondition() *condition.Condition { + return h.Condition +} diff --git a/plugin/deploy/deis/deis_test.go b/plugin/deploy/deis/deis_test.go new file mode 100644 index 000000000..42b053f4d --- /dev/null +++ b/plugin/deploy/deis/deis_test.go @@ -0,0 +1,68 @@ +package deis + +import ( + "strings" + "testing" + + "github.com/drone/drone/shared/build/buildfile" + "github.com/franela/goblin" +) + +func Test_Deis(t *testing.T) { + + g := goblin.Goblin(t) + g.Describe("Deis Deploy", func() { + + g.It("Should set git.config", func() { + b := new(buildfile.Buildfile) + h := Deis{ + App: "drone", + Deisurl: "deis.yourdomain.com:2222", + } + + h.Write(b) + out := b.String() + g.Assert(strings.Contains(out, CmdRevParse)).Equal(true) + g.Assert(strings.Contains(out, CmdGlobalUser)).Equal(true) + g.Assert(strings.Contains(out, CmdGlobalEmail)).Equal(true) + }) + + g.It("Should add remote", func() { + b := new(buildfile.Buildfile) + h := Deis{ + App: "drone", + Deisurl: "deis.yourdomain.com:2222/", + } + + h.Write(b) + out := b.String() + g.Assert(strings.Contains(out, "\ngit remote add deis ssh://git@deis.yourdomain.com:2222/drone.git\n")).Equal(true) + }) + + g.It("Should push to remote", func() { + b := new(buildfile.Buildfile) + d := Deis{ + App: "drone", + } + + d.Write(b) + out := b.String() + g.Assert(strings.Contains(out, "\ngit push deis $COMMIT:master\n")).Equal(true) + }) + + g.It("Should force push to remote", func() { + b := new(buildfile.Buildfile) + h := Deis{ + Force: true, + App: "drone", + } + + h.Write(b) + out := b.String() + g.Assert(strings.Contains(out, "\ngit add -A\n")).Equal(true) + g.Assert(strings.Contains(out, "\ngit commit -m 'adding build artifacts'\n")).Equal(true) + g.Assert(strings.Contains(out, "\ngit push deis HEAD:master --force\n")).Equal(true) + }) + + }) +} diff --git a/plugin/deploy/deployment.go b/plugin/deploy/deployment.go index 6cb5e960c..3e059ac1e 100644 --- a/plugin/deploy/deployment.go +++ b/plugin/deploy/deployment.go @@ -7,6 +7,7 @@ import ( "github.com/drone/drone/plugin/deploy/git" "github.com/drone/drone/plugin/deploy/heroku" + "github.com/drone/drone/plugin/deploy/deis" "github.com/drone/drone/plugin/deploy/modulus" "github.com/drone/drone/plugin/deploy/nodejitsu" "github.com/drone/drone/plugin/deploy/tsuru" @@ -19,6 +20,7 @@ type Deploy struct { CloudFoundry *CloudFoundry `yaml:"cloudfoundry,omitempty"` Git *git.Git `yaml:"git,omitempty"` Heroku *heroku.Heroku `yaml:"heroku,omitempty"` + Deis *deis.Deis `yaml:"deis,omitempty"` Modulus *modulus.Modulus `yaml:"modulus,omitempty"` Nodejitsu *nodejitsu.Nodejitsu `yaml:"nodejitsu,omitempty"` SSH *SSH `yaml:"ssh,omitempty"` @@ -37,6 +39,9 @@ func (d *Deploy) Write(f *buildfile.Buildfile, r *repo.Repo) { if d.Heroku != nil && match(d.Heroku.GetCondition(), r) { d.Heroku.Write(f) } + if d.Deis != nil && match(d.Deis.GetCondition(), r) { + d.Deis.Write(f) + } if d.Modulus != nil && match(d.Modulus.GetCondition(), r) { d.Modulus.Write(f) } diff --git a/plugin/deploy/heroku/heroku.go b/plugin/deploy/heroku/heroku.go index 60cca50eb..7a551fbb0 100644 --- a/plugin/deploy/heroku/heroku.go +++ b/plugin/deploy/heroku/heroku.go @@ -14,11 +14,16 @@ const ( // individual that made the commit. CmdGlobalEmail = "git config --global user.email $(git --no-pager log -1 --pretty=format:'%ae')" CmdGlobalUser = "git config --global user.name $(git --no-pager log -1 --pretty=format:'%an')" + + // Command to write the API token to ~/.netrc + // use "_" since heroku git authentication ignores username + CmdLogin = "echo 'machine git.heroku.com login _ password %s' >> ~/.netrc" ) type Heroku struct { App string `yaml:"app,omitempty"` Force bool `yaml:"force,omitempty"` + Token string `yaml:"token,omitempty"` Condition *condition.Condition `yaml:"when,omitempty"` } @@ -27,9 +32,10 @@ func (h *Heroku) Write(f *buildfile.Buildfile) { f.WriteCmdSilent(CmdRevParse) f.WriteCmdSilent(CmdGlobalUser) f.WriteCmdSilent(CmdGlobalEmail) + f.WriteCmdSilent(fmt.Sprintf(CmdLogin, h.Token)) // add heroku as a git remote - f.WriteCmd(fmt.Sprintf("git remote add heroku git@heroku.com:%s.git", h.App)) + f.WriteCmd(fmt.Sprintf("git remote add heroku https://git.heroku.com/%s.git", h.App)) switch h.Force { case true: @@ -38,10 +44,10 @@ func (h *Heroku) Write(f *buildfile.Buildfile) { // that need to be deployed to Heroku. f.WriteCmd(fmt.Sprintf("git add -A")) f.WriteCmd(fmt.Sprintf("git commit -m 'adding build artifacts'")) - f.WriteCmd(fmt.Sprintf("git push heroku HEAD:master --force")) + f.WriteCmd(fmt.Sprintf("git push heroku HEAD:refs/heads/master --force")) case false: // otherwise we just do a standard git push - f.WriteCmd(fmt.Sprintf("git push heroku $COMMIT:master")) + f.WriteCmd(fmt.Sprintf("git push heroku $COMMIT:refs/heads/master")) } } diff --git a/plugin/deploy/heroku/heroku_test.go b/plugin/deploy/heroku/heroku_test.go index b938f603f..e5a8eeef9 100644 --- a/plugin/deploy/heroku/heroku_test.go +++ b/plugin/deploy/heroku/heroku_test.go @@ -26,6 +26,18 @@ func Test_Heroku(t *testing.T) { g.Assert(strings.Contains(out, CmdGlobalEmail)).Equal(true) }) + g.It("Should write token", func() { + b := new(buildfile.Buildfile) + h := Heroku{ + App: "drone", + Token: "mock-token", + } + + h.Write(b) + out := b.String() + g.Assert(strings.Contains(out, "\necho 'machine git.heroku.com login _ password mock-token' >> ~/.netrc\n")).Equal(true) + }) + g.It("Should add remote", func() { b := new(buildfile.Buildfile) h := Heroku{ @@ -34,7 +46,7 @@ func Test_Heroku(t *testing.T) { h.Write(b) out := b.String() - g.Assert(strings.Contains(out, "\ngit remote add heroku git@heroku.com:drone.git\n")).Equal(true) + g.Assert(strings.Contains(out, "\ngit remote add heroku https://git.heroku.com/drone.git\n")).Equal(true) }) g.It("Should push to remote", func() { @@ -45,7 +57,7 @@ func Test_Heroku(t *testing.T) { d.Write(b) out := b.String() - g.Assert(strings.Contains(out, "\ngit push heroku $COMMIT:master\n")).Equal(true) + g.Assert(strings.Contains(out, "\ngit push heroku $COMMIT:refs/heads/master\n")).Equal(true) }) g.It("Should force push to remote", func() { @@ -59,7 +71,7 @@ func Test_Heroku(t *testing.T) { out := b.String() g.Assert(strings.Contains(out, "\ngit add -A\n")).Equal(true) g.Assert(strings.Contains(out, "\ngit commit -m 'adding build artifacts'\n")).Equal(true) - g.Assert(strings.Contains(out, "\ngit push heroku HEAD:master --force\n")).Equal(true) + g.Assert(strings.Contains(out, "\ngit push heroku HEAD:refs/heads/master --force\n")).Equal(true) }) }) diff --git a/plugin/notify/irc/irc.go b/plugin/notify/irc/irc.go index 7e232677f..33fa02662 100644 --- a/plugin/notify/irc/irc.go +++ b/plugin/notify/irc/irc.go @@ -53,11 +53,23 @@ func (i *IRC) sendSuccess(req *model.Request) error { // to the connected IRC client func (i *IRC) send(channel string, message string) error { client := irc.IRC(i.Nick, i.Nick) - if client != nil { + + if client == nil { return fmt.Errorf("Error creating IRC client") } - defer client.Disconnect() - client.Connect(i.Server) - client.Notice(channel, message) + + err := client.Connect(i.Server) + + if err != nil { + return fmt.Errorf("Error connecting to IRC server: %v", err) + } + + client.AddCallback("001", func(_ *irc.Event) { + client.Notice(channel, message) + client.Quit() + }) + + go client.Loop() + return nil } diff --git a/plugin/notify/katoim/katoim.go b/plugin/notify/katoim/katoim.go new file mode 100644 index 000000000..29dd14e25 --- /dev/null +++ b/plugin/notify/katoim/katoim.go @@ -0,0 +1,139 @@ +package katoim + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + + "github.com/drone/drone/shared/model" +) + +const ( + katoimEndpoint = "https://api.kato.im/rooms/%s/simple" + katoimStartedMessage = "*Building* %s, commit [%s](%s), author %s" + katoimSuccessMessage = "*Success* %s, commit [%s](%s), author %s" + katoimFailureMessage = "*Failed* %s, commit [%s](%s), author %s" + + NotifyTrue = "true" + NotifyFalse = "false" + NotifyOn = "on" + NotifyOff = "off" + NotifyNever = "never" + NotifyAlways = "always" +) + +type KatoIM struct { + RoomID string `yaml:"room_id,omitempty"` + Started string `yaml:"on_started,omitempty"` + Success string `yaml:"on_success,omitempty"` + Failure string `yaml:"on_failure,omitempty"` +} + +func (k *KatoIM) Send(context *model.Request) error { + switch { + case context.Commit.Status == model.StatusStarted: + return k.sendStarted(context) + case context.Commit.Status == model.StatusSuccess: + return k.sendSuccess(context) + case context.Commit.Status == model.StatusFailure: + return k.sendFailure(context) + } + + return nil +} + +func (k *KatoIM) getMessage(context *model.Request, message string) string { + url := getBuildUrl(context) + return fmt.Sprintf(message, context.Repo.Name, context.Commit.ShaShort(), url, context.Commit.Author) +} + +// sendStarted disabled by default +func (k *KatoIM) sendStarted(context *model.Request) error { + switch k.Started { + case NotifyTrue, NotifyAlways, NotifyOn: + return k.send(k.getMessage(context, katoimStartedMessage), "yellow") + default: + return nil + } +} + +// sendSuccess enabled by default +func (k *KatoIM) sendSuccess(context *model.Request) error { + switch k.Success { + case NotifyFalse, NotifyNever, NotifyOff: + return nil + case NotifyTrue, NotifyAlways, NotifyOn, "": + return k.send(k.getMessage(context, katoimSuccessMessage), "green") + default: + return nil + } +} + +// sendFailure enabled by default +func (k *KatoIM) sendFailure(context *model.Request) error { + switch k.Failure { + case NotifyFalse, NotifyNever, NotifyOff: + return nil + case NotifyTrue, NotifyAlways, NotifyOn, "": + return k.send(k.getMessage(context, katoimFailureMessage), "red") + default: + return nil + } +} + +// helper function to send HTTP requests +func (k *KatoIM) send(msg, color string) error { + // data will get posted in this format + data := struct { + Text string `json:"text"` + Color string `json:"color"` + Renderer string `json:"renderer"` + From string `json:"from"` + }{msg, color, "markdown", "Drone"} + + // data json encoded + payload, err := json.Marshal(data) + if err != nil { + return err + } + + // send payload + url := fmt.Sprintf(katoimEndpoint, k.RoomID) + + // create headers + headers := make(map[string]string) + headers["Accept"] = "application/json" + + return sendJson(url, payload, headers) +} + +func getBuildUrl(context *model.Request) string { + return fmt.Sprintf("%s/%s/%s/%s/%s/%s", context.Host, context.Repo.Host, context.Repo.Owner, context.Repo.Name, context.Commit.Branch, context.Commit.Sha) +} + +// helper fuction to sent HTTP Post requests +// with JSON data as the payload. +func sendJson(url string, payload []byte, headers map[string]string) error { + client := &http.Client{} + buf := bytes.NewBuffer(payload) + + req, err := http.NewRequest("POST", url, buf) + if err != nil { + return err + } + + req.Header.Set("Content-Type", "application/json") + if headers != nil { + for k, v := range headers { + req.Header.Add(k, v) + } + } + + resp, err := client.Do(req) + if err != nil { + return err + } + resp.Body.Close() + return nil +} diff --git a/plugin/notify/notification.go b/plugin/notify/notification.go index 982be477b..c836752f7 100644 --- a/plugin/notify/notification.go +++ b/plugin/notify/notification.go @@ -9,6 +9,7 @@ import ( "github.com/drone/drone/plugin/notify/email" "github.com/drone/drone/plugin/notify/github" "github.com/drone/drone/plugin/notify/irc" + "github.com/drone/drone/plugin/notify/katoim" "github.com/drone/drone/plugin/notify/webhook" "github.com/drone/drone/shared/model" ) @@ -28,6 +29,7 @@ type Notification struct { Slack *Slack `yaml:"slack,omitempty"` Gitter *Gitter `yaml:"gitter,omitempty"` Flowdock *Flowdock `yaml:"flowdock,omitempty"` + KatoIM *katoim.KatoIM `yaml:"katoim,omitempty"` GitHub github.GitHub `yaml:"--"` } @@ -89,6 +91,14 @@ func (n *Notification) Send(context *model.Request) error { } } + // send kato-im notifications + if n.KatoIM != nil { + err := n.KatoIM.Send(context) + if err != nil { + log.Println(err) + } + } + // send email notifications // TODO (bradrydzewski) need to improve this code githubStatus := new(github.GitHub) diff --git a/plugin/notify/slack.go b/plugin/notify/slack.go index 356948ce4..395a4df63 100644 --- a/plugin/notify/slack.go +++ b/plugin/notify/slack.go @@ -8,7 +8,6 @@ import ( ) const ( - slackEndpoint = "https://%s.slack.com/services/hooks/incoming-webhook?token=%s" slackStartedMessage = "*Building* <%s|%s> (%s) by %s" slackStartedFallbackMessage = "Building %s (%s) by %s" slackSuccessMessage = "*Success* <%s|%s> (%s) by %s" @@ -18,13 +17,12 @@ const ( ) type Slack struct { - Team string `yaml:"team,omitempty"` - Channel string `yaml:"channel,omitempty"` - Username string `yaml:"username,omitempty"` - Token string `yaml:"token,omitempty"` - Started bool `yaml:"on_started,omitempty"` - Success bool `yaml:"on_success,omitempty"` - Failure bool `yaml:"on_failure,omitempty"` + WebhookUrl string `yaml:"webhook_url,omitempty"` + Channel string `yaml:"channel,omitempty"` + Username string `yaml:"username,omitempty"` + Started bool `yaml:"on_started,omitempty"` + Success bool `yaml:"on_success,omitempty"` + Failure bool `yaml:"on_failure,omitempty"` } func (s *Slack) Send(context *model.Request) error { @@ -100,10 +98,7 @@ func (s *Slack) send(msg string, fallback string, color string) error { return err } - // send payload - url := fmt.Sprintf(slackEndpoint, s.Team, s.Token) - - go sendJson(url, payload, nil) + go sendJson(s.WebhookUrl, payload, nil) return nil } diff --git a/plugin/publish/dropbox.go b/plugin/publish/dropbox.go index 30b1a5b2a..77dba0619 100644 --- a/plugin/publish/dropbox.go +++ b/plugin/publish/dropbox.go @@ -1 +1,37 @@ package publish + +import ( + "fmt" + "github.com/drone/drone/plugin/condition" + "github.com/drone/drone/shared/build/buildfile" + "strings" +) + +type Dropbox struct { + AccessToken string `yaml:"access_token,omitempty"` + + Source string `yaml:"source,omitempty"` + Target string `yaml:"target,omitempty"` + + Condition *condition.Condition `yaml:"when,omitempty"` +} + +func (d *Dropbox) Write(f *buildfile.Buildfile) { + + if len(d.AccessToken) == 0 || len(d.Source) == 0 || len(d.Target) == 0 { + return + } + if strings.HasPrefix(d.Target, "/") { + d.Target = d.Target[1:] + } + + f.WriteCmdSilent("echo 'publishing to Dropbox ...'") + + cmd := "curl --upload-file %s -H \"Authorization: Bearer %s\" \"https://api-content.dropbox.com/1/files_put/auto/%s?overwrite=true\"" + f.WriteCmd(fmt.Sprintf(cmd, d.Source, d.AccessToken, d.Target)) + +} + +func (d *Dropbox) GetCondition() *condition.Condition { + return d.Condition +} diff --git a/plugin/publish/publish.go b/plugin/publish/publish.go index 19ba9e78a..4ce6d4b1f 100644 --- a/plugin/publish/publish.go +++ b/plugin/publish/publish.go @@ -11,12 +11,13 @@ import ( // for publishing build artifacts when // a Build has succeeded type Publish struct { - S3 *S3 `yaml:"s3,omitempty"` - Swift *Swift `yaml:"swift,omitempty"` - PyPI *PyPI `yaml:"pypi,omitempty"` - NPM *npm.NPM `yaml:"npm,omitempty"` - Docker *Docker `yaml:"docker,omitempty"` - Github *Github `yaml:"github,omitempty"` + S3 *S3 `yaml:"s3,omitempty"` + Swift *Swift `yaml:"swift,omitempty"` + PyPI *PyPI `yaml:"pypi,omitempty"` + NPM *npm.NPM `yaml:"npm,omitempty"` + Docker *Docker `yaml:"docker,omitempty"` + Github *Github `yaml:"github,omitempty"` + Dropbox *Dropbox `yaml:"dropbox,omitempty"` } func (p *Publish) Write(f *buildfile.Buildfile, r *repo.Repo) { @@ -49,6 +50,11 @@ func (p *Publish) Write(f *buildfile.Buildfile, r *repo.Repo) { if p.Docker != nil && match(p.Docker.GetCondition(), r) { p.Docker.Write(f) } + + // Dropbox + if p.Dropbox != nil && match(p.Dropbox.GetCondition(), r) { + p.Dropbox.Write(f) + } } func match(c *condition.Condition, r *repo.Repo) bool { diff --git a/plugin/remote/gitlab/gitlab.go b/plugin/remote/gitlab/gitlab.go index b4e07597d..560678ece 100644 --- a/plugin/remote/gitlab/gitlab.go +++ b/plugin/remote/gitlab/gitlab.go @@ -11,11 +11,15 @@ import ( ) type Gitlab struct { - url string + url string + SkipVerify bool } -func New(url string) *Gitlab { - return &Gitlab{url: url} +func New(url string, skipVerify bool) *Gitlab { + return &Gitlab{ + url: url, + SkipVerify: skipVerify, + } } // Authorize handles authentication with thrid party remote systems, @@ -24,7 +28,7 @@ func (r *Gitlab) Authorize(res http.ResponseWriter, req *http.Request) (*model.L var username = req.FormValue("username") var password = req.FormValue("password") - var client = NewClient(r.url, "") + var client = NewClient(r.url, "", r.SkipVerify) var session, err = client.GetSession(username, password) if err != nil { return nil, err @@ -55,7 +59,7 @@ func (r *Gitlab) GetHost() string { func (r *Gitlab) GetRepos(user *model.User) ([]*model.Repo, error) { var repos []*model.Repo - var client = NewClient(r.url, user.Access) + var client = NewClient(r.url, user.Access, r.SkipVerify) var list, err = client.AllProjects() if err != nil { return nil, err @@ -110,7 +114,7 @@ func (r *Gitlab) GetRepos(user *model.User) ([]*model.Repo, error) { // GetScript fetches the build script (.drone.yml) from the remote // repository and returns in string format. func (r *Gitlab) GetScript(user *model.User, repo *model.Repo, hook *model.Hook) ([]byte, error) { - var client = NewClient(r.url, user.Access) + var client = NewClient(r.url, user.Access, r.SkipVerify) var path = ns(repo.Owner, repo.Name) return client.RepoRawFile(path, hook.Sha, ".drone.yml") } @@ -118,7 +122,7 @@ func (r *Gitlab) GetScript(user *model.User, repo *model.Repo, hook *model.Hook) // Activate activates a repository by adding a Post-commit hook and // a Public Deploy key, if applicable. func (r *Gitlab) Activate(user *model.User, repo *model.Repo, link string) error { - var client = NewClient(r.url, user.Access) + var client = NewClient(r.url, user.Access, r.SkipVerify) var path = ns(repo.Owner, repo.Name) var title, err = GetKeyTitle(link) if err != nil { diff --git a/plugin/remote/gitlab/gitlab_test.go b/plugin/remote/gitlab/gitlab_test.go index ca7e2ba0f..38c331364 100644 --- a/plugin/remote/gitlab/gitlab_test.go +++ b/plugin/remote/gitlab/gitlab_test.go @@ -14,7 +14,7 @@ func Test_Github(t *testing.T) { var server = testdata.NewServer() defer server.Close() - var gitlab = New(server.URL) + var gitlab = New(server.URL, false) var user = model.User{ Access: "e3b0c44298fc1c149afbf4c8996fb", } diff --git a/plugin/remote/gitlab/helper.go b/plugin/remote/gitlab/helper.go index ea2fb25f1..c497c3699 100644 --- a/plugin/remote/gitlab/helper.go +++ b/plugin/remote/gitlab/helper.go @@ -9,8 +9,8 @@ import ( // NewClient is a helper function that returns a new GitHub // client using the provided OAuth token. -func NewClient(uri, token string) *gogitlab.Gitlab { - return gogitlab.NewGitlab(uri, "/api/v3", token) +func NewClient(uri, token string, skipVerify bool) *gogitlab.Gitlab { + return gogitlab.NewGitlabCert(uri, "/api/v3", token, skipVerify) } // IsRead is a helper function that returns true if the diff --git a/plugin/remote/gitlab/register.go b/plugin/remote/gitlab/register.go index eaac24670..c4e7e4c48 100644 --- a/plugin/remote/gitlab/register.go +++ b/plugin/remote/gitlab/register.go @@ -6,7 +6,8 @@ import ( ) var ( - gitlabURL = config.String("gitlab-url", "") + gitlabURL = config.String("gitlab-url", "") + gitlabSkipVerify = config.Bool("gitlab-skip-verify", false) ) // Registers the Gitlab plugin using the default @@ -17,6 +18,9 @@ func Register() { return } remote.Register( - New(*gitlabURL), + New( + *gitlabURL, + *gitlabSkipVerify, + ), ) } diff --git a/plugin/remote/gogs/gogs.go b/plugin/remote/gogs/gogs.go new file mode 100644 index 000000000..9ad32a209 --- /dev/null +++ b/plugin/remote/gogs/gogs.go @@ -0,0 +1,183 @@ +package gogs + +import ( + "fmt" + "io/ioutil" + "log" + "net/http" + "net/url" + "strings" + "time" + + "github.com/drone/drone/shared/model" + "github.com/gogits/go-gogs-client" +) + +type Gogs struct { + URL string + Secret string +} + +func New(url string, secret string) *Gogs { + return &Gogs{URL: url, Secret: secret} +} + +// Authorize handles Gogs authorization +func (r *Gogs) Authorize(res http.ResponseWriter, req *http.Request) (*model.Login, error) { + var username = req.FormValue("username") + var password = req.FormValue("password") + var client = gogs.NewClient(r.URL, "") + + // try to fetch drone token if it exists + var accessToken = "" + tokens, err := client.ListAccessTokens(username, password) + if err != nil { + return nil, err + } + for _, token := range tokens { + if token.Name == "drone" { + accessToken = token.Sha1 + break + } + } + + // if drone token not found, create it + if accessToken == "" { + token, err := client.CreateAccessToken(username, password, gogs.CreateAccessTokenOption{Name: "drone"}) + if err != nil { + return nil, err + } + accessToken = token.Sha1 + } + + // update client + client = gogs.NewClient(r.URL, accessToken) + + // fetch user information + user, err := client.GetUserInfo(username) + if err != nil { + return nil, err + } + + var login = new(model.Login) + login.Name = user.FullName + login.Email = user.Email + login.Access = accessToken + login.Login = username + return login, nil +} + +// GetKind returns the internal identifier of this remote Gogs instance +func (r *Gogs) GetKind() string { + return model.RemoteGogs +} + +// GetHost returns the hostname of this remote Gogs instance +func (r *Gogs) GetHost() string { + uri, _ := url.Parse(r.URL) + return uri.Host +} + +// GetRepos fetches all repositories that the specified +// user has access to in the remote system. +func (r *Gogs) GetRepos(user *model.User) ([]*model.Repo, error) { + var repos []*model.Repo + + var remote = r.GetKind() + var hostname = r.GetHost() + var client = gogs.NewClient(r.URL, user.Access) + + gogsRepos, err := client.ListMyRepos() + + if err != nil { + return nil, err + } + + for _, repo := range gogsRepos { + var repoName = strings.Split(repo.FullName, "/") + if len(repoName) < 2 { + log.Println("invalid repo full_name", repo.FullName) + continue + } + var owner = repoName[0] + var name = repoName[1] + + var repo = model.Repo{ + UserID: user.ID, + Remote: remote, + Host: hostname, + Owner: owner, + Name: name, + Private: repo.Private, + CloneURL: repo.CloneUrl, + GitURL: repo.CloneUrl, + SSHURL: repo.SshUrl, + URL: repo.HtmlUrl, + Role: &model.Perm{ + Admin: repo.Permissions.Admin, + Write: repo.Permissions.Push, + Read: repo.Permissions.Pull, + }, + } + + repos = append(repos, &repo) + } + + return repos, err +} + +// GetScript fetches the build script (.drone.yml) from the remote +// repository and returns a byte array +func (r *Gogs) GetScript(user *model.User, repo *model.Repo, hook *model.Hook) ([]byte, error) { + var client = gogs.NewClient(r.URL, user.Access) + return client.GetFile(repo.Owner, repo.Name, hook.Sha, ".drone.yml") +} + +// Activate activates a repository +func (r *Gogs) Activate(user *model.User, repo *model.Repo, link string) error { + var client = gogs.NewClient(r.URL, user.Access) + + var config = map[string]string{ + "url": link, + "secret": r.Secret, + "content_type": "json", + } + var hook = gogs.CreateHookOption{ + Type: "gogs", + Config: config, + Active: true, + } + + _, err := client.CreateRepoHook(repo.Owner, repo.Name, hook) + return err +} + +// ParseHook parses the post-commit hook from the Request body +// and returns the required data in a standard format. +func (r *Gogs) ParseHook(req *http.Request) (*model.Hook, error) { + defer req.Body.Close() + var payloadbytes, _ = ioutil.ReadAll(req.Body) + var payload, err = gogs.ParseHook(payloadbytes) + if err != nil { + return nil, err + } + + // verify the payload has the minimum amount of required data. + if payload.Repo == nil || payload.Commits == nil || len(payload.Commits) == 0 { + return nil, fmt.Errorf("Invalid Gogs post-commit Hook. Missing Repo or Commit data.") + } + + if payload.Secret != r.Secret { + return nil, fmt.Errorf("Payload secret does not match stored secret") + } + + return &model.Hook{ + Owner: payload.Repo.Owner.UserName, + Repo: payload.Repo.Name, + Sha: payload.Commits[0].Id, + Branch: payload.Branch(), + Author: payload.Commits[0].Author.UserName, + Timestamp: time.Now().UTC().String(), + Message: payload.Commits[0].Message, + }, nil +} diff --git a/plugin/remote/gogs/register.go b/plugin/remote/gogs/register.go new file mode 100644 index 000000000..592d729f3 --- /dev/null +++ b/plugin/remote/gogs/register.go @@ -0,0 +1,23 @@ +package gogs + +import ( + "github.com/drone/config" + "github.com/drone/drone/plugin/remote" +) + +var ( + gogsUrl = config.String("gogs-url", "") + gogsSecret = config.String("gogs-secret", "") +) + +// Registers the Gogs plugin using the default +// settings from the config file or environment +// variables. +func Register() { + if len(*gogsUrl) == 0 { + return + } + remote.Register( + New(*gogsUrl, *gogsSecret), + ) +} diff --git a/server/app/robots.txt b/server/app/robots.txt index 4f9540ba3..7d329b1db 100644 --- a/server/app/robots.txt +++ b/server/app/robots.txt @@ -1 +1 @@ -User-agent: * \ No newline at end of file +User-agent: * diff --git a/server/app/scripts/app.js b/server/app/scripts/app.js index be352eaff..4f1a670b0 100644 --- a/server/app/scripts/app.js +++ b/server/app/scripts/app.js @@ -52,6 +52,10 @@ app.config(['$routeProvider', '$locationProvider', '$httpProvider', function($ro templateUrl: '/static/views/login_gitlab.html', title: 'GitLab Login', }) + .when('/gogs', { + templateUrl: '/static/views/login_gogs.html', + title: 'Gogs Setup', + }) .when('/setup', { templateUrl: '/static/views/setup.html', controller: 'SetupController', @@ -234,6 +238,6 @@ app.controller("AccountReposController", function($scope, $http, $location, user return true; }; $scope.byRemote = function(entry){ - return $scope.remote == "" || $scope.remote == entry.remote; - }; + return $scope.remote == "" || $scope.remote == entry.remote; + }; }); diff --git a/server/app/scripts/controllers/conf.js b/server/app/scripts/controllers/conf.js index 6e42b7f89..4cc8202f6 100644 --- a/server/app/scripts/controllers/conf.js +++ b/server/app/scripts/controllers/conf.js @@ -30,6 +30,9 @@ angular.module('app').controller("ConfigController", function($scope, $http, rem case 'stash.atlassian.com': $scope.stash = remote; break; + case 'gogs': + $scope.gogs = remote; + break; } } }) diff --git a/server/app/scripts/filters/filters.js b/server/app/scripts/filters/filters.js index bdb4ec0bf..32c1c0886 100644 --- a/server/app/scripts/filters/filters.js +++ b/server/app/scripts/filters/filters.js @@ -144,6 +144,7 @@ case 'enterprise.github.com' : return 'GitHub Enterprise'; case 'bitbucket.org' : return 'Bitbucket'; case 'stash.atlassian.com' : return 'Atlassian Stash'; + case 'gogs' : return 'Gogs'; } } } @@ -160,6 +161,7 @@ case 'enterprise.github.com' : return 'fa-github-square'; case 'bitbucket.org' : return 'fa-bitbucket-square'; case 'stash.atlassian.com' : return 'fa-bitbucket-square'; + case 'gogs' : return 'fa-git-square'; } } } @@ -203,4 +205,4 @@ .filter('toDuration', toDuration) .filter('unique', unique); -})(); \ No newline at end of file +})(); diff --git a/server/app/views/commit_detail.html b/server/app/views/commit_detail.html index 639b73284..c53f9dfb3 100644 --- a/server/app/views/commit_detail.html +++ b/server/app/views/commit_detail.html @@ -16,8 +16,17 @@ + +