diff --git a/Gopkg.lock b/Gopkg.lock
index a6186d3c9b..c8ab147f31 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -85,6 +85,12 @@
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
version = "v1.0"
+[[projects]]
+ branch = "master"
+ name = "github.com/lkysow/go-gitlab"
+ packages = ["."]
+ revision = "5aba7769280ffa5408f98e1b95290989cc1428c5"
+
[[projects]]
name = "github.com/magiconair/properties"
packages = ["."]
@@ -232,6 +238,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
- inputs-digest = "ff26f498317e49b0c36b98f2ec0b91a1b7c9ccfb4b04777a3f1e8f7e143828b7"
+ inputs-digest = "ae2f51ff70354344e93c471a531b44a57822ea9dcc57dddbb8b476d0e1f1d3a8"
solver-name = "gps-cdcl"
solver-version = 1
diff --git a/Gopkg.toml b/Gopkg.toml
index dd5bdb274d..4d84b6a5be 100644
--- a/Gopkg.toml
+++ b/Gopkg.toml
@@ -88,3 +88,7 @@
[[constraint]]
branch = "v2"
name = "gopkg.in/yaml.v2"
+
+[[constraint]]
+ branch = "master"
+ name = "github.com/lkysow/go-gitlab"
diff --git a/cmd/root.go b/cmd/root.go
index b136308110..6daab7e27a 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -1,5 +1,5 @@
// Package cmd holds all our cli commands.
-// These are different from the commands that get run via GitHub comments
+// These are different from the commands that get run via pull request comments.
package cmd
import (
diff --git a/cmd/server.go b/cmd/server.go
index 8326ac5456..694688ebe4 100644
--- a/cmd/server.go
+++ b/cmd/server.go
@@ -25,6 +25,10 @@ const (
GHTokenFlag = "gh-token"
GHUserFlag = "gh-user"
GHWebHookSecret = "gh-webhook-secret"
+ GitlabHostnameFlag = "gitlab-hostname"
+ GitlabTokenFlag = "gitlab-token"
+ GitlabUserFlag = "gitlab-user"
+ GitlabWebHookSecret = "gitlab-webhook-secret"
LogLevelFlag = "log-level"
PortFlag = "port"
RequireApprovalFlag = "require-approval"
@@ -49,19 +53,42 @@ var stringFlags = []stringFlag{
description: "Hostname of your Github Enterprise installation. If using github.com, no need to set.",
value: "github.com",
},
+ {
+ name: GHUserFlag,
+ description: "GitHub username of API user.",
+ },
{
name: GHTokenFlag,
- description: "[REQUIRED] GitHub token of API user. Can also be specified via the ATLANTIS_GH_TOKEN environment variable.",
+ description: "GitHub token of API user. Can also be specified via the ATLANTIS_GH_TOKEN environment variable.",
env: "ATLANTIS_GH_TOKEN",
},
{
- name: GHUserFlag,
- description: "[REQUIRED] GitHub username of API user.",
+ name: GHWebHookSecret,
+ description: "Optional secret used to validate GitHub webhooks (see https://developer.github.com/webhooks/securing/)." +
+ " If not specified, Atlantis won't be able to validate that the incoming webhook call came from GitHub. " +
+ "Can also be specified via the ATLANTIS_GH_WEBHOOK_SECRET environment variable.",
+ env: "ATLANTIS_GH_WEBHOOK_SECRET",
+ },
+ {
+ name: GitlabHostnameFlag,
+ description: "Hostname of your GitLab Enterprise installation. If using gitlab.com, no need to set.",
+ value: "gitlab.com",
},
{
- name: GHWebHookSecret,
- description: "Optional secret used for GitHub webhooks (see https://developer.github.com/webhooks/securing/). If not specified, Atlantis won't validate the incoming webhook call.",
- env: "ATLANTIS_GH_WEBHOOK_SECRET",
+ name: GitlabUserFlag,
+ description: "GitLab username of API user.",
+ },
+ {
+ name: GitlabTokenFlag,
+ description: "GitLab token of API user. Can also be specified via the ATLANTIS_GITLAB_TOKEN environment variable.",
+ env: "ATLANTIS_GITLAB_TOKEN",
+ },
+ {
+ name: GitlabWebHookSecret,
+ description: "Optional secret used to validate GitLab webhooks." +
+ " If not specified, Atlantis won't be able to validate that the incoming webhook call came from GitLab. " +
+ "Can also be specified via the ATLANTIS_GITLAB_WEBHOOK_SECRET environment variable.",
+ env: "ATLANTIS_GITLAB_WEBHOOK_SECRET",
},
{
name: LogLevelFlag,
@@ -206,7 +233,7 @@ func (s *ServerCmd) run() error {
if err := setDataDir(&config); err != nil {
return err
}
- sanitizeGithubUser(&config)
+ trimAtSymbolFromUsers(&config)
// Config looks good. Start the server.
server, err := s.ServerCreator.NewServer(config)
@@ -221,11 +248,23 @@ func validate(config server.Config) error {
if logLevel != "debug" && logLevel != "info" && logLevel != "warn" && logLevel != "error" {
return errors.New("invalid log level: not one of debug, info, warn, error")
}
- if config.GithubUser == "" {
- return fmt.Errorf("--%s must be set", GHUserFlag)
+ vcsErr := fmt.Errorf("--%s/--%s or --%s/--%s must be set", GHUserFlag, GHTokenFlag, GitlabUserFlag, GitlabTokenFlag)
+
+ // The following combinations are valid.
+ // 1. github user and token
+ // 2. gitlab user and token
+ // 3. all 4 set
+ // We validate using contradiction (I think).
+ if config.GithubUser != "" && config.GithubToken == "" || config.GithubToken != "" && config.GithubUser == "" {
+ return vcsErr
+ }
+ if config.GitlabUser != "" && config.GitlabToken == "" || config.GitlabToken != "" && config.GitlabUser == "" {
+ return vcsErr
}
- if config.GithubToken == "" {
- return fmt.Errorf("--%s must be set", GHTokenFlag)
+ // At this point, we know that there can't be a single user/token without
+ // its pair, but we haven't checked if any user/token is set at all.
+ if config.GithubUser == "" && config.GitlabUser == "" {
+ return vcsErr
}
return nil
}
@@ -256,9 +295,10 @@ func setDataDir(config *server.Config) error {
return nil
}
-// sanitizeGithubUser trims @ from the front of the github username if it exists.
-func sanitizeGithubUser(config *server.Config) {
+// trimAtSymbolFromUsers trims @ from the front of the github and gitlab usernames
+func trimAtSymbolFromUsers(config *server.Config) {
config.GithubUser = strings.TrimPrefix(config.GithubUser, "@")
+ config.GitlabUser = strings.TrimPrefix(config.GitlabUser, "@")
}
// withErrPrint prints out any errors to a terminal in red.
diff --git a/cmd/server_test.go b/cmd/server_test.go
index 393536635b..56357d2f50 100644
--- a/cmd/server_test.go
+++ b/cmd/server_test.go
@@ -90,50 +90,121 @@ func TestExecute_InvalidConfig(t *testing.T) {
Assert(t, strings.Contains(err.Error(), "unmarshal errors"), "should be an unmarshal error")
}
-func TestExecute_Validation(t *testing.T) {
+func TestExecute_ValidateLogLevel(t *testing.T) {
+ t.Log("Should validate log level.")
+ c := setup(map[string]interface{}{
+ cmd.LogLevelFlag: "invalid",
+ cmd.GHUserFlag: "user",
+ cmd.GHTokenFlag: "token",
+ })
+ err := c.Execute()
+ Assert(t, err != nil, "should be an error")
+ Equals(t, "invalid log level: not one of debug, info, warn, error", err.Error())
+}
+
+func TestExecute_ValidateVCSConfig(t *testing.T) {
+ expErr := "--gh-user/--gh-token or --gitlab-user/--gitlab-token must be set"
cases := []struct {
description string
flags map[string]interface{}
- expErr string
+ expectError bool
}{
{
- "Should validate log level.",
+ "no config set",
+ nil,
+ true,
+ },
+ {
+ "just github token set",
map[string]interface{}{
- cmd.LogLevelFlag: "invalid",
- cmd.GHUserFlag: "user",
- cmd.GHTokenFlag: "token",
+ cmd.GHTokenFlag: "token",
},
- "invalid log level: not one of debug, info, warn, error",
+ true,
},
{
- "Should ensure github user is set.",
+ "just gitlab token set",
map[string]interface{}{
- cmd.GHTokenFlag: "token",
+ cmd.GitlabTokenFlag: "token",
},
- "--gh-user must be set",
+ true,
},
{
- "Should ensure github token is set.",
+ "just github user set",
map[string]interface{}{
cmd.GHUserFlag: "user",
},
- "--gh-token must be set",
+ true,
+ },
+ {
+ "just gitlab user set",
+ map[string]interface{}{
+ cmd.GitlabUserFlag: "user",
+ },
+ true,
+ },
+ {
+ "github user and gitlab token set",
+ map[string]interface{}{
+ cmd.GHUserFlag: "user",
+ cmd.GitlabTokenFlag: "token",
+ },
+ true,
+ },
+ {
+ "gitlab user and github token set",
+ map[string]interface{}{
+ cmd.GitlabUserFlag: "user",
+ cmd.GHTokenFlag: "token",
+ },
+ true,
+ },
+ {
+ "github user and github token set and should be successful",
+ map[string]interface{}{
+ cmd.GHUserFlag: "user",
+ cmd.GHTokenFlag: "token",
+ },
+ false,
+ },
+ {
+ "gitlab user and gitlab token set and should be successful",
+ map[string]interface{}{
+ cmd.GitlabUserFlag: "user",
+ cmd.GitlabTokenFlag: "token",
+ },
+ false,
+ },
+ {
+ "github and gitlab user and github and gitlab token set and should be successful",
+ map[string]interface{}{
+ cmd.GHUserFlag: "user",
+ cmd.GHTokenFlag: "token",
+ cmd.GitlabUserFlag: "user",
+ cmd.GitlabTokenFlag: "token",
+ },
+ false,
},
}
for _, testCase := range cases {
- t.Log(testCase.description)
+ t.Log("Should validate vcs config when " + testCase.description)
c := setup(testCase.flags)
err := c.Execute()
- Assert(t, err != nil, "should be an error")
- Equals(t, testCase.expErr, err.Error())
+ if testCase.expectError {
+ Assert(t, err != nil, "should be an error")
+ Equals(t, expErr, err.Error())
+ } else {
+ Ok(t, err)
+ }
}
}
func TestExecute_Defaults(t *testing.T) {
t.Log("Should set the defaults for all unspecified flags.")
c := setup(map[string]interface{}{
- cmd.GHUserFlag: "user",
- cmd.GHTokenFlag: "token",
+ cmd.GHUserFlag: "user",
+ cmd.GHTokenFlag: "token",
+ cmd.GitlabUserFlag: "gitlab-user",
+ cmd.GitlabTokenFlag: "gitlab-token",
})
err := c.Execute()
Ok(t, err)
@@ -141,6 +212,9 @@ func TestExecute_Defaults(t *testing.T) {
Equals(t, "user", passedConfig.GithubUser)
Equals(t, "token", passedConfig.GithubToken)
Equals(t, "", passedConfig.GithubWebHookSecret)
+ Equals(t, "gitlab-user", passedConfig.GitlabUser)
+ Equals(t, "gitlab-token", passedConfig.GitlabToken)
+ Equals(t, "", passedConfig.GitlabWebHookSecret)
// Get our hostname since that's what gets defaulted to
hostname, err := os.Hostname()
Ok(t, err)
@@ -151,6 +225,7 @@ func TestExecute_Defaults(t *testing.T) {
Ok(t, err)
Equals(t, dataDir, passedConfig.DataDir)
Equals(t, "github.com", passedConfig.GithubHostname)
+ Equals(t, "gitlab.com", passedConfig.GitlabHostname)
Equals(t, "info", passedConfig.LogLevel)
Equals(t, false, passedConfig.RequireApproval)
Equals(t, 4141, passedConfig.Port)
@@ -183,6 +258,18 @@ func TestExecute_GithubUser(t *testing.T) {
Equals(t, "user", passedConfig.GithubUser)
}
+func TestExecute_GitlabUser(t *testing.T) {
+ t.Log("Should remove the @ from the gitlab username if it's passed.")
+ c := setup(map[string]interface{}{
+ cmd.GitlabUserFlag: "@user",
+ cmd.GitlabTokenFlag: "token",
+ })
+ err := c.Execute()
+ Ok(t, err)
+
+ Equals(t, "user", passedConfig.GitlabUser)
+}
+
func TestExecute_Flags(t *testing.T) {
t.Log("Should use all flags that are set.")
c := setup(map[string]interface{}{
@@ -192,6 +279,10 @@ func TestExecute_Flags(t *testing.T) {
cmd.GHUserFlag: "user",
cmd.GHTokenFlag: "token",
cmd.GHWebHookSecret: "secret",
+ cmd.GitlabHostnameFlag: "gitlab-hostname",
+ cmd.GitlabUserFlag: "gitlab-user",
+ cmd.GitlabTokenFlag: "gitlab-token",
+ cmd.GitlabWebHookSecret: "gitlab-secret",
cmd.LogLevelFlag: "debug",
cmd.PortFlag: 8181,
cmd.RequireApprovalFlag: true,
@@ -205,6 +296,10 @@ func TestExecute_Flags(t *testing.T) {
Equals(t, "user", passedConfig.GithubUser)
Equals(t, "token", passedConfig.GithubToken)
Equals(t, "secret", passedConfig.GithubWebHookSecret)
+ Equals(t, "gitlab-hostname", passedConfig.GitlabHostname)
+ Equals(t, "gitlab-user", passedConfig.GitlabUser)
+ Equals(t, "gitlab-token", passedConfig.GitlabToken)
+ Equals(t, "gitlab-secret", passedConfig.GitlabWebHookSecret)
Equals(t, "debug", passedConfig.LogLevel)
Equals(t, 8181, passedConfig.Port)
Equals(t, true, passedConfig.RequireApproval)
@@ -219,6 +314,10 @@ gh-hostname: "ghhostname"
gh-user: "user"
gh-token: "token"
gh-webhook-secret: "secret"
+gitlab-hostname: "gitlab-hostname"
+gitlab-user: "gitlab-user"
+gitlab-token: "gitlab-token"
+gitlab-webhook-secret: "gitlab-secret"
log-level: "debug"
port: 8181
require-approval: true`)
@@ -235,6 +334,10 @@ require-approval: true`)
Equals(t, "user", passedConfig.GithubUser)
Equals(t, "token", passedConfig.GithubToken)
Equals(t, "secret", passedConfig.GithubWebHookSecret)
+ Equals(t, "gitlab-hostname", passedConfig.GitlabHostname)
+ Equals(t, "gitlab-user", passedConfig.GitlabUser)
+ Equals(t, "gitlab-token", passedConfig.GitlabToken)
+ Equals(t, "gitlab-secret", passedConfig.GitlabWebHookSecret)
Equals(t, "debug", passedConfig.LogLevel)
Equals(t, 8181, passedConfig.Port)
Equals(t, true, passedConfig.RequireApproval)
@@ -279,6 +382,24 @@ func TestExecute_FlagEnvVarOverride(t *testing.T) {
Equals(t, "override", passedConfig.GithubToken)
}
+func TestExecute_EnvVars(t *testing.T) {
+ t.Log("Setting flags by env var should work.")
+ os.Setenv("ATLANTIS_GH_TOKEN", "gh-token") // nolint: errcheck
+ os.Setenv("ATLANTIS_GH_WEBHOOK_SECRET", "gh-webhook") // nolint: errcheck
+ os.Setenv("ATLANTIS_GITLAB_TOKEN", "gitlab-token") // nolint: errcheck
+ os.Setenv("ATLANTIS_GITLAB_WEBHOOK_SECRET", "gitlab-webhook") // nolint: errcheck
+ c := setup(map[string]interface{}{
+ cmd.GHUserFlag: "user",
+ cmd.GitlabUserFlag: "user",
+ })
+ err := c.Execute()
+ Ok(t, err)
+ Equals(t, "gh-token", passedConfig.GithubToken)
+ Equals(t, "gh-webhook", passedConfig.GithubWebHookSecret)
+ Equals(t, "gitlab-token", passedConfig.GitlabToken)
+ Equals(t, "gitlab-webhook", passedConfig.GitlabWebHookSecret)
+}
+
func setup(flags map[string]interface{}) *cobra.Command {
viper := viper.New()
for k, v := range flags {
diff --git a/server/events/apply_executor.go b/server/events/apply_executor.go
index 309eab26b9..867b2df615 100644
--- a/server/events/apply_executor.go
+++ b/server/events/apply_executor.go
@@ -8,14 +8,14 @@ import (
"path/filepath"
- "github.com/hootsuite/atlantis/server/events/github"
"github.com/hootsuite/atlantis/server/events/models"
"github.com/hootsuite/atlantis/server/events/run"
"github.com/hootsuite/atlantis/server/events/terraform"
+ "github.com/hootsuite/atlantis/server/events/vcs"
)
type ApplyExecutor struct {
- Github github.Client
+ VCSClient vcs.ClientProxy
Terraform *terraform.Client
RequireApproval bool
Run *run.Run
@@ -25,7 +25,7 @@ type ApplyExecutor struct {
func (a *ApplyExecutor) Execute(ctx *CommandContext) CommandResponse {
if a.RequireApproval {
- approved, err := a.Github.PullIsApproved(ctx.BaseRepo, ctx.Pull)
+ approved, err := a.VCSClient.PullIsApproved(ctx.BaseRepo, ctx.Pull, ctx.VCSHost)
if err != nil {
return CommandResponse{Error: errors.Wrap(err, "checking if pull request was approved")}
}
diff --git a/server/events/command_context.go b/server/events/command_context.go
index 8d14c94e68..39dc0c4b2e 100644
--- a/server/events/command_context.go
+++ b/server/events/command_context.go
@@ -2,6 +2,7 @@ package events
import (
"github.com/hootsuite/atlantis/server/events/models"
+ "github.com/hootsuite/atlantis/server/events/vcs"
"github.com/hootsuite/atlantis/server/logging"
)
@@ -12,4 +13,5 @@ type CommandContext struct {
User models.User
Command *Command
Log *logging.SimpleLogger
+ VCSHost vcs.Host
}
diff --git a/server/events/command_handler.go b/server/events/command_handler.go
index b600053c50..6ff8f93adc 100644
--- a/server/events/command_handler.go
+++ b/server/events/command_handler.go
@@ -3,65 +3,123 @@ package events
import (
"fmt"
- "github.com/hootsuite/atlantis/server/events/github"
+ "github.com/google/go-github/github"
+ "github.com/hootsuite/atlantis/server/events/models"
+ "github.com/hootsuite/atlantis/server/events/vcs"
"github.com/hootsuite/atlantis/server/logging"
"github.com/hootsuite/atlantis/server/recovery"
+ "github.com/lkysow/go-gitlab"
+ "github.com/pkg/errors"
)
//go:generate pegomock generate --use-experimental-model-gen --package mocks -o mocks/mock_command_runner.go CommandRunner
type CommandRunner interface {
- ExecuteCommand(ctx *CommandContext)
+ ExecuteCommand(baseRepo models.Repo, headRepo models.Repo, user models.User, pullNum int, cmd *Command, vcsHost vcs.Host)
+}
+
+//go:generate pegomock generate --use-experimental-model-gen --package mocks -o mocks/mock_github_pull_getter.go GithubPullGetter
+
+type GithubPullGetter interface {
+ GetPullRequest(repo models.Repo, pullNum int) (*github.PullRequest, error)
+}
+
+//go:generate pegomock generate --use-experimental-model-gen --package mocks -o mocks/mock_gitlab_merge_request_getter.go GitlabMergeRequestGetter
+
+type GitlabMergeRequestGetter interface {
+ GetMergeRequest(repoFullName string, pullNum int) (*gitlab.MergeRequest, error)
}
// CommandHandler is the first step when processing a comment command.
type CommandHandler struct {
- PlanExecutor Executor
- ApplyExecutor Executor
- HelpExecutor Executor
- LockURLGenerator LockURLGenerator
- GHClient github.Client
- GHStatus GHStatusUpdater
- EventParser EventParsing
- EnvLocker EnvLocker
- GHCommentRenderer *GithubCommentRenderer
- Logger *logging.SimpleLogger
+ PlanExecutor Executor
+ ApplyExecutor Executor
+ HelpExecutor Executor
+ LockURLGenerator LockURLGenerator
+ VCSClient vcs.ClientProxy
+ GithubPullGetter GithubPullGetter
+ GitlabMergeRequestGetter GitlabMergeRequestGetter
+ CommitStatusUpdater CommitStatusUpdater
+ EventParser EventParsing
+ EnvLocker EnvLocker
+ MarkdownRenderer *MarkdownRenderer
+ Logger logging.SimpleLogging
}
-func (c *CommandHandler) ExecuteCommand(ctx *CommandContext) {
- // Set up logger specific to this command.
- // It's safe to reuse the underlying logger.
- src := fmt.Sprintf("%s/pull/%d", ctx.BaseRepo.FullName, ctx.Pull.Num)
- ctx.Log = logging.NewSimpleLogger(src, c.Logger.Logger, true, c.Logger.Level)
- defer c.logPanics(ctx)
+// ExecuteCommand executes the command
+func (c *CommandHandler) ExecuteCommand(baseRepo models.Repo, headRepo models.Repo, user models.User, pullNum int, cmd *Command, vcsHost vcs.Host) {
+ var err error
+ var pull models.PullRequest
+ if vcsHost == vcs.Github {
+ pull, headRepo, err = c.getGithubData(baseRepo, pullNum)
+ } else if vcsHost == vcs.Gitlab {
+ pull, err = c.getGitlabData(baseRepo.FullName, pullNum)
+ }
- // Get additional data from the pull request.
- ghPull, _, err := c.GHClient.GetPullRequest(ctx.BaseRepo, ctx.Pull.Num)
+ log := c.buildLogger(baseRepo.FullName, pullNum)
if err != nil {
- ctx.Log.Err("making pull request API call to GitHub: %s", err)
+ log.Err(err.Error())
return
}
- if ghPull.GetState() != "open" {
- ctx.Log.Info("command was run on closed pull request")
- c.GHClient.CreateComment(ctx.BaseRepo, ctx.Pull, "Atlantis commands can't be run on closed pull requests") // nolint: errcheck
- return
+ ctx := &CommandContext{
+ User: user,
+ Log: log,
+ Pull: pull,
+ HeadRepo: headRepo,
+ Command: cmd,
+ VCSHost: vcsHost,
+ BaseRepo: baseRepo,
+ }
+ c.run(ctx)
+}
+
+func (c *CommandHandler) getGithubData(baseRepo models.Repo, pullNum int) (models.PullRequest, models.Repo, error) {
+ if c.GithubPullGetter == nil {
+ return models.PullRequest{}, models.Repo{}, errors.New("Atlantis not configured to support GitHub")
}
- pull, headRepo, err := c.EventParser.ExtractPullData(ghPull)
+ ghPull, err := c.GithubPullGetter.GetPullRequest(baseRepo, pullNum)
if err != nil {
- ctx.Log.Err("extracting required fields from comment data: %s", err)
- return
+ return models.PullRequest{}, models.Repo{}, errors.Wrap(err, "making pull request API call to GitHub")
}
- ctx.Pull = pull
- ctx.HeadRepo = headRepo
- c.run(ctx)
+ pull, repo, err := c.EventParser.ParseGithubPull(ghPull)
+ if err != nil {
+ return pull, repo, errors.Wrap(err, "extracting required fields from comment data")
+ }
+ return pull, repo, nil
+}
+
+func (c *CommandHandler) getGitlabData(repoFullName string, pullNum int) (models.PullRequest, error) {
+ if c.GitlabMergeRequestGetter == nil {
+ return models.PullRequest{}, errors.New("Atlantis not configured to support GitLab")
+ }
+ mr, err := c.GitlabMergeRequestGetter.GetMergeRequest(repoFullName, pullNum)
+ if err != nil {
+ return models.PullRequest{}, errors.Wrap(err, "making merge request API call to GitLab")
+ }
+ pull := c.EventParser.ParseGitlabMergeRequest(mr)
+ return pull, nil
+}
+
+func (c *CommandHandler) buildLogger(repoFullName string, pullNum int) *logging.SimpleLogger {
+ src := fmt.Sprintf("%s#%d", repoFullName, pullNum)
+ return logging.NewSimpleLogger(src, c.Logger.Underlying(), true, c.Logger.GetLevel())
}
func (c *CommandHandler) SetLockURL(f func(id string) (url string)) {
c.LockURLGenerator.SetLockURL(f)
}
-
func (c *CommandHandler) run(ctx *CommandContext) {
- c.GHStatus.Update(ctx.BaseRepo, ctx.Pull, Pending, ctx.Command) // nolint: errcheck
+ log := c.buildLogger(ctx.BaseRepo.FullName, ctx.Pull.Num)
+ ctx.Log = log
+ defer c.logPanics(ctx)
+
+ if ctx.Pull.State != models.Open {
+ ctx.Log.Info("command was run on closed pull request")
+ c.VCSClient.CreateComment(ctx.BaseRepo, ctx.Pull, "Atlantis commands can't be run on closed pull requests", ctx.VCSHost) // nolint: errcheck
+ return
+ }
+
+ c.CommitStatusUpdater.Update(ctx.BaseRepo, ctx.Pull, vcs.Pending, ctx.Command, ctx.VCSHost) // nolint: errcheck
if !c.EnvLocker.TryLock(ctx.BaseRepo.FullName, ctx.Command.Environment, ctx.Pull.Num) {
errMsg := fmt.Sprintf(
"The %s environment is currently locked by another"+
@@ -97,17 +155,17 @@ func (c *CommandHandler) updatePull(ctx *CommandContext, res CommandResponse) {
}
// Update the pull request's status icon and comment back.
- c.GHStatus.UpdateProjectResult(ctx, res) // nolint: errcheck
- comment := c.GHCommentRenderer.Render(res, ctx.Command.Name, ctx.Log.History.String(), ctx.Command.Verbose)
- c.GHClient.CreateComment(ctx.BaseRepo, ctx.Pull, comment) // nolint: errcheck
+ c.CommitStatusUpdater.UpdateProjectResult(ctx, res) // nolint: errcheck
+ comment := c.MarkdownRenderer.Render(res, ctx.Command.Name, ctx.Log.History.String(), ctx.Command.Verbose)
+ c.VCSClient.CreateComment(ctx.BaseRepo, ctx.Pull, comment, ctx.VCSHost) // nolint: errcheck
}
// logPanics logs and creates a comment on the pull request for panics
func (c *CommandHandler) logPanics(ctx *CommandContext) {
if err := recover(); err != nil {
stack := recovery.Stack(3)
- c.GHClient.CreateComment(ctx.BaseRepo, ctx.Pull, // nolint: errcheck
- fmt.Sprintf("**Error: goroutine panic. This is a bug.**\n```\n%s\n%s```", err, stack))
+ c.VCSClient.CreateComment(ctx.BaseRepo, ctx.Pull, // nolint: errcheck
+ fmt.Sprintf("**Error: goroutine panic. This is a bug.**\n```\n%s\n%s```", err, stack), ctx.VCSHost)
ctx.Log.Err("PANIC: %s\n%s", err, stack)
}
}
diff --git a/server/events/command_handler_test.go b/server/events/command_handler_test.go
index 12ca73026b..ccf6fd23df 100644
--- a/server/events/command_handler_test.go
+++ b/server/events/command_handler_test.go
@@ -1,145 +1,164 @@
package events_test
import (
+ "bytes"
"errors"
- "testing"
-
+ "log"
"reflect"
-
"strings"
+ "testing"
"github.com/google/go-github/github"
"github.com/hootsuite/atlantis/server/events"
- gh "github.com/hootsuite/atlantis/server/events/github/fixtures"
- ghmocks "github.com/hootsuite/atlantis/server/events/github/mocks"
"github.com/hootsuite/atlantis/server/events/mocks"
+ "github.com/hootsuite/atlantis/server/events/models"
"github.com/hootsuite/atlantis/server/events/models/fixtures"
- "github.com/hootsuite/atlantis/server/logging"
+ "github.com/hootsuite/atlantis/server/events/vcs"
+ vcsmocks "github.com/hootsuite/atlantis/server/events/vcs/mocks"
+ logmocks "github.com/hootsuite/atlantis/server/logging/mocks"
. "github.com/hootsuite/atlantis/testing"
- "github.com/mohae/deepcopy"
. "github.com/petergtz/pegomock"
)
var applier *mocks.MockExecutor
var helper *mocks.MockExecutor
-var planner *mocks.MockPlanner
+var planner *mocks.MockExecutor
var eventParsing *mocks.MockEventParsing
-var ghClient *ghmocks.MockClient
-var ghStatus *mocks.MockGHStatusUpdater
+var vcsClient *vcsmocks.MockClientProxy
+var ghStatus *mocks.MockCommitStatusUpdater
+var githubGetter *mocks.MockGithubPullGetter
+var gitlabGetter *mocks.MockGitlabMergeRequestGetter
var envLocker *mocks.MockEnvLocker
var ch events.CommandHandler
+var logBytes *bytes.Buffer
func setup(t *testing.T) {
RegisterMockTestingT(t)
applier = mocks.NewMockExecutor()
helper = mocks.NewMockExecutor()
- planner = mocks.NewMockPlanner()
+ planner = mocks.NewMockExecutor()
eventParsing = mocks.NewMockEventParsing()
- ghClient = ghmocks.NewMockClient()
- ghStatus = mocks.NewMockGHStatusUpdater()
+ ghStatus = mocks.NewMockCommitStatusUpdater()
envLocker = mocks.NewMockEnvLocker()
+ vcsClient = vcsmocks.NewMockClientProxy()
+ githubGetter = mocks.NewMockGithubPullGetter()
+ gitlabGetter = mocks.NewMockGitlabMergeRequestGetter()
+ logger := logmocks.NewMockSimpleLogging()
+ logBytes = new(bytes.Buffer)
+ When(logger.Underlying()).ThenReturn(log.New(logBytes, "", 0))
ch = events.CommandHandler{
- PlanExecutor: planner,
- ApplyExecutor: applier,
- HelpExecutor: helper,
- GHClient: ghClient,
- GHStatus: ghStatus,
- EventParser: eventParsing,
- EnvLocker: envLocker,
- GHCommentRenderer: &events.GithubCommentRenderer{},
- Logger: logging.NewNoopLogger(),
+ PlanExecutor: planner,
+ ApplyExecutor: applier,
+ HelpExecutor: helper,
+ VCSClient: vcsClient,
+ CommitStatusUpdater: ghStatus,
+ EventParser: eventParsing,
+ EnvLocker: envLocker,
+ MarkdownRenderer: &events.MarkdownRenderer{},
+ GithubPullGetter: githubGetter,
+ GitlabMergeRequestGetter: gitlabGetter,
+ Logger: logger,
}
}
func TestExecuteCommand_LogPanics(t *testing.T) {
t.Log("if there is a panic it is commented back on the pull request")
setup(t)
- When(ghClient.GetPullRequest(fixtures.Repo, fixtures.Pull.Num)).ThenPanic("panic")
- ch.ExecuteCommand(&events.CommandContext{
- BaseRepo: fixtures.Repo,
- Pull: fixtures.Pull,
- })
- _, _, comment := ghClient.VerifyWasCalledOnce().CreateComment(AnyRepo(), AnyPullRequest(), AnyString()).GetCapturedArguments()
+ When(ghStatus.Update(fixtures.Repo, fixtures.Pull, vcs.Pending, nil, vcs.Github)).ThenPanic("panic")
+ ch.ExecuteCommand(fixtures.Repo, fixtures.Repo, fixtures.User, 1, nil, vcs.Github)
+ _, _, comment, _ := vcsClient.VerifyWasCalledOnce().CreateComment(AnyRepo(), AnyPullRequest(), AnyString(), AnyVCSHost()).GetCapturedArguments()
Assert(t, strings.Contains(comment, "Error: goroutine panic"), "comment should be about a goroutine panic")
}
-func TestExecuteCommand_PullErr(t *testing.T) {
- t.Log("if getting the pull request fails nothing should continue")
+func TestExecuteCommand_NoGithubPullGetter(t *testing.T) {
+ t.Log("if CommandHandler was constructed with a nil GithubPullGetter an error should be logged")
+ setup(t)
+ ch.GithubPullGetter = nil
+ ch.ExecuteCommand(fixtures.Repo, fixtures.Repo, fixtures.User, 1, nil, vcs.Github)
+ Equals(t, "[ERROR] hootsuite/atlantis#1: Atlantis not configured to support GitHub\n", logBytes.String())
+}
+
+func TestExecuteCommand_NoGitlabMergeGetter(t *testing.T) {
+ t.Log("if CommandHandler was constructed with a nil GitlabMergeRequestGetter an error should be logged")
+ setup(t)
+ ch.GitlabMergeRequestGetter = nil
+ ch.ExecuteCommand(fixtures.Repo, fixtures.Repo, fixtures.User, 1, nil, vcs.Gitlab)
+ Equals(t, "[ERROR] hootsuite/atlantis#1: Atlantis not configured to support GitLab\n", logBytes.String())
+}
+
+func TestExecuteCommand_GithubPullErr(t *testing.T) {
+ t.Log("if getting the github pull request fails an error should be logged")
+ setup(t)
+ When(githubGetter.GetPullRequest(fixtures.Repo, fixtures.Pull.Num)).ThenReturn(nil, errors.New("err"))
+ ch.ExecuteCommand(fixtures.Repo, fixtures.Repo, fixtures.User, fixtures.Pull.Num, nil, vcs.Github)
+ Equals(t, "[ERROR] hootsuite/atlantis#1: Making pull request API call to GitHub: err\n", logBytes.String())
+}
+
+func TestExecuteCommand_GitlabMergeRequestErr(t *testing.T) {
+ t.Log("if getting the gitlab merge request fails an error should be logged")
setup(t)
- When(ghClient.GetPullRequest(fixtures.Repo, fixtures.Pull.Num)).ThenReturn(nil, nil, errors.New("err"))
- ch.ExecuteCommand(&events.CommandContext{
- BaseRepo: fixtures.Repo,
- Pull: fixtures.Pull,
- })
+ When(gitlabGetter.GetMergeRequest(fixtures.Repo.FullName, fixtures.Pull.Num)).ThenReturn(nil, errors.New("err"))
+ ch.ExecuteCommand(fixtures.Repo, fixtures.Repo, fixtures.User, fixtures.Pull.Num, nil, vcs.Gitlab)
+ Equals(t, "[ERROR] hootsuite/atlantis#1: Making merge request API call to GitLab: err\n", logBytes.String())
}
-func TestExecuteCommand_ExtractErr(t *testing.T) {
- t.Log("if extracting data from the pull request fails nothing should continue")
+func TestExecuteCommand_GithubPullParseErr(t *testing.T) {
+ t.Log("if parsing the returned github pull request fails an error should be logged")
setup(t)
- pull := deepcopy.Copy(gh.Pull).(github.PullRequest)
- pull.State = github.String("open")
- When(ghClient.GetPullRequest(fixtures.Repo, fixtures.Pull.Num)).ThenReturn(&pull, nil, nil)
- When(eventParsing.ExtractPullData(&pull)).ThenReturn(fixtures.Pull, fixtures.Repo, errors.New("err"))
-
- ch.ExecuteCommand(&events.CommandContext{
- BaseRepo: fixtures.Repo,
- Pull: fixtures.Pull,
- })
+ var pull github.PullRequest
+ When(githubGetter.GetPullRequest(fixtures.Repo, fixtures.Pull.Num)).ThenReturn(&pull, nil)
+ When(eventParsing.ParseGithubPull(&pull)).ThenReturn(fixtures.Pull, fixtures.Repo, errors.New("err"))
+
+ ch.ExecuteCommand(fixtures.Repo, fixtures.Repo, fixtures.User, fixtures.Pull.Num, nil, vcs.Github)
+ Equals(t, "[ERROR] hootsuite/atlantis#1: Extracting required fields from comment data: err\n", logBytes.String())
}
func TestExecuteCommand_ClosedPull(t *testing.T) {
t.Log("if a command is run on a closed pull request atlantis should" +
" comment saying that this is not allowed")
setup(t)
- pull := deepcopy.Copy(gh.Pull).(github.PullRequest)
- pull.State = github.String("closed")
- When(ghClient.GetPullRequest(fixtures.Repo, fixtures.Pull.Num)).ThenReturn(&pull, nil, nil)
-
- ch.ExecuteCommand(&events.CommandContext{
- BaseRepo: fixtures.Repo,
- User: fixtures.User,
- Pull: fixtures.Pull,
- Command: &events.Command{
- Name: events.Plan,
- },
- })
- ghClient.VerifyWasCalledOnce().CreateComment(fixtures.Repo, fixtures.Pull, "Atlantis commands can't be run on closed pull requests")
+ pull := &github.PullRequest{
+ State: github.String("closed"),
+ }
+ modelPull := models.PullRequest{State: models.Closed}
+ When(githubGetter.GetPullRequest(fixtures.Repo, fixtures.Pull.Num)).ThenReturn(pull, nil)
+ When(eventParsing.ParseGithubPull(pull)).ThenReturn(modelPull, fixtures.Repo, nil)
+
+ ch.ExecuteCommand(fixtures.Repo, fixtures.Repo, fixtures.User, fixtures.Pull.Num, nil, vcs.Github)
+ vcsClient.VerifyWasCalledOnce().CreateComment(fixtures.Repo, modelPull, "Atlantis commands can't be run on closed pull requests", vcs.Github)
}
func TestExecuteCommand_EnvLocked(t *testing.T) {
t.Log("if the environment is locked, should comment back on the pull")
setup(t)
- pull := deepcopy.Copy(gh.Pull).(github.PullRequest)
- pull.State = github.String("open")
+ pull := &github.PullRequest{
+ State: github.String("closed"),
+ }
cmd := events.Command{
Name: events.Plan,
Environment: "env",
}
- baseCtx := events.CommandContext{
- BaseRepo: fixtures.Repo,
- User: fixtures.User,
- Pull: fixtures.Pull,
- Command: &cmd,
- }
- When(ghClient.GetPullRequest(fixtures.Repo, fixtures.Pull.Num)).ThenReturn(&pull, nil, nil)
- When(eventParsing.ExtractPullData(&pull)).ThenReturn(fixtures.Pull, fixtures.Repo, nil)
+ When(githubGetter.GetPullRequest(fixtures.Repo, fixtures.Pull.Num)).ThenReturn(pull, nil)
+ When(eventParsing.ParseGithubPull(pull)).ThenReturn(fixtures.Pull, fixtures.Repo, nil)
When(envLocker.TryLock(fixtures.Repo.FullName, cmd.Environment, fixtures.Pull.Num)).ThenReturn(false)
- ch.ExecuteCommand(&baseCtx)
+ ch.ExecuteCommand(fixtures.Repo, fixtures.Repo, fixtures.User, fixtures.Pull.Num, &cmd, vcs.Github)
msg := "The env environment is currently locked by another" +
" command that is running for this pull request." +
" Wait until the previous command is complete and try again."
- ghStatus.VerifyWasCalledOnce().Update(fixtures.Repo, fixtures.Pull, events.Pending, &cmd)
- ghStatus.VerifyWasCalledOnce().UpdateProjectResult(&baseCtx, events.CommandResponse{Failure: msg})
- ghClient.VerifyWasCalledOnce().CreateComment(fixtures.Repo, fixtures.Pull,
- "**Plan Failed**: "+msg+"\n\n")
+ ghStatus.VerifyWasCalledOnce().Update(fixtures.Repo, fixtures.Pull, vcs.Pending, &cmd, vcs.Github)
+ _, response := ghStatus.VerifyWasCalledOnce().UpdateProjectResult(AnyCommandContext(), AnyCommandResponse()).GetCapturedArguments()
+ Equals(t, msg, response.Failure)
+ vcsClient.VerifyWasCalledOnce().CreateComment(fixtures.Repo, fixtures.Pull,
+ "**Plan Failed**: "+msg+"\n\n", vcs.Github)
}
func TestExecuteCommand_FullRun(t *testing.T) {
t.Log("when running a plan, apply or help should comment")
- pull := deepcopy.Copy(gh.Pull).(github.PullRequest)
- pull.State = github.String("open")
+ pull := &github.PullRequest{
+ State: github.String("closed"),
+ }
cmdResponse := events.CommandResponse{}
for _, c := range []events.CommandName{events.Help, events.Plan, events.Apply} {
setup(t)
@@ -147,14 +166,8 @@ func TestExecuteCommand_FullRun(t *testing.T) {
Name: c,
Environment: "env",
}
- baseCtx := events.CommandContext{
- BaseRepo: fixtures.Repo,
- User: fixtures.User,
- Pull: fixtures.Pull,
- Command: &cmd,
- }
- When(ghClient.GetPullRequest(fixtures.Repo, fixtures.Pull.Num)).ThenReturn(&pull, nil, nil)
- When(eventParsing.ExtractPullData(&pull)).ThenReturn(fixtures.Pull, fixtures.Repo, nil)
+ When(githubGetter.GetPullRequest(fixtures.Repo, fixtures.Pull.Num)).ThenReturn(pull, nil)
+ When(eventParsing.ParseGithubPull(pull)).ThenReturn(fixtures.Pull, fixtures.Repo, nil)
When(envLocker.TryLock(fixtures.Repo.FullName, cmd.Environment, fixtures.Pull.Num)).ThenReturn(true)
switch c {
case events.Help:
@@ -165,11 +178,12 @@ func TestExecuteCommand_FullRun(t *testing.T) {
When(applier.Execute(AnyCommandContext())).ThenReturn(cmdResponse)
}
- ch.ExecuteCommand(&baseCtx)
+ ch.ExecuteCommand(fixtures.Repo, fixtures.Repo, fixtures.User, fixtures.Pull.Num, &cmd, vcs.Github)
- ghStatus.VerifyWasCalledOnce().Update(fixtures.Repo, fixtures.Pull, events.Pending, &cmd)
- ghStatus.VerifyWasCalledOnce().UpdateProjectResult(&baseCtx, cmdResponse)
- ghClient.VerifyWasCalledOnce().CreateComment(AnyRepo(), AnyPullRequest(), AnyString())
+ ghStatus.VerifyWasCalledOnce().Update(fixtures.Repo, fixtures.Pull, vcs.Pending, &cmd, vcs.Github)
+ _, response := ghStatus.VerifyWasCalledOnce().UpdateProjectResult(AnyCommandContext(), AnyCommandResponse()).GetCapturedArguments()
+ Equals(t, cmdResponse, response)
+ vcsClient.VerifyWasCalledOnce().CreateComment(AnyRepo(), AnyPullRequest(), AnyString(), AnyVCSHost())
envLocker.VerifyWasCalledOnce().Unlock(fixtures.Repo.FullName, cmd.Environment, fixtures.Pull.Num)
}
}
@@ -178,3 +192,13 @@ func AnyCommandContext() *events.CommandContext {
RegisterMatcher(NewAnyMatcher(reflect.TypeOf(&events.CommandContext{})))
return &events.CommandContext{}
}
+
+func AnyVCSHost() vcs.Host {
+ RegisterMatcher(NewAnyMatcher(reflect.TypeOf(vcs.Github)))
+ return vcs.Github
+}
+
+func AnyCommandResponse() events.CommandResponse {
+ RegisterMatcher(NewAnyMatcher(reflect.TypeOf(events.CommandResponse{})))
+ return events.CommandResponse{}
+}
diff --git a/server/events/commit_status_updater.go b/server/events/commit_status_updater.go
new file mode 100644
index 0000000000..590a8f1b37
--- /dev/null
+++ b/server/events/commit_status_updater.go
@@ -0,0 +1,48 @@
+package events
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/hootsuite/atlantis/server/events/models"
+ "github.com/hootsuite/atlantis/server/events/vcs"
+)
+
+//go:generate pegomock generate --use-experimental-model-gen --package mocks -o mocks/mock_commit_status_updater.go CommitStatusUpdater
+
+type CommitStatusUpdater interface {
+ Update(repo models.Repo, pull models.PullRequest, status vcs.CommitStatus, cmd *Command, host vcs.Host) error
+ UpdateProjectResult(ctx *CommandContext, res CommandResponse) error
+}
+
+type DefaultCommitStatusUpdater struct {
+ Client vcs.ClientProxy
+}
+
+func (d *DefaultCommitStatusUpdater) Update(repo models.Repo, pull models.PullRequest, status vcs.CommitStatus, cmd *Command, host vcs.Host) error {
+ description := fmt.Sprintf("%s %s", strings.Title(cmd.Name.String()), strings.Title(status.String()))
+ return d.Client.UpdateStatus(repo, pull, status, description, host)
+}
+
+func (d *DefaultCommitStatusUpdater) UpdateProjectResult(ctx *CommandContext, res CommandResponse) error {
+ var status vcs.CommitStatus
+ if res.Error != nil || res.Failure != "" {
+ status = vcs.Failed
+ } else {
+ var statuses []vcs.CommitStatus
+ for _, p := range res.ProjectResults {
+ statuses = append(statuses, p.Status())
+ }
+ status = d.worstStatus(statuses)
+ }
+ return d.Update(ctx.BaseRepo, ctx.Pull, status, ctx.Command, ctx.VCSHost)
+}
+
+func (d *DefaultCommitStatusUpdater) worstStatus(ss []vcs.CommitStatus) vcs.CommitStatus {
+ for _, s := range ss {
+ if s == vcs.Failed {
+ return vcs.Failed
+ }
+ }
+ return vcs.Success
+}
diff --git a/server/events/github_status_test.go b/server/events/commit_status_updater_test.go
similarity index 67%
rename from server/events/github_status_test.go
rename to server/events/commit_status_updater_test.go
index 548368db8e..50ce5b6295 100644
--- a/server/events/github_status_test.go
+++ b/server/events/commit_status_updater_test.go
@@ -1,31 +1,30 @@
package events_test
import (
- "testing"
-
"errors"
"strings"
+ "testing"
"github.com/hootsuite/atlantis/server/events"
- "github.com/hootsuite/atlantis/server/events/github/mocks"
"github.com/hootsuite/atlantis/server/events/models"
+ "github.com/hootsuite/atlantis/server/events/vcs"
+ "github.com/hootsuite/atlantis/server/events/vcs/mocks"
. "github.com/hootsuite/atlantis/testing"
. "github.com/petergtz/pegomock"
)
var repoModel = models.Repo{}
var pullModel = models.PullRequest{}
-var status = events.Success
+var status = vcs.Success
var cmd = events.Command{
Name: events.Plan,
}
func TestStatus_String(t *testing.T) {
- cases := map[events.Status]string{
- events.Pending: "pending",
- events.Success: "success",
- events.Failure: "failure",
- events.Error: "error",
+ cases := map[vcs.CommitStatus]string{
+ vcs.Pending: "pending",
+ vcs.Success: "success",
+ vcs.Failed: "failed",
}
for k, v := range cases {
Equals(t, v, k.String())
@@ -34,11 +33,11 @@ func TestStatus_String(t *testing.T) {
func TestUpdate(t *testing.T) {
RegisterMockTestingT(t)
- client := mocks.NewMockClient()
- s := events.GithubStatus{Client: client}
- err := s.Update(repoModel, pullModel, status, &cmd)
+ client := mocks.NewMockClientProxy()
+ s := events.DefaultCommitStatusUpdater{Client: client}
+ err := s.Update(repoModel, pullModel, status, &cmd, vcs.Github)
Ok(t, err)
- client.VerifyWasCalledOnce().UpdateStatus(repoModel, pullModel, "success", "Plan Success", "Atlantis")
+ client.VerifyWasCalledOnce().UpdateStatus(repoModel, pullModel, status, "Plan Success", vcs.Github)
}
func TestUpdateProjectResult_Error(t *testing.T) {
@@ -47,12 +46,13 @@ func TestUpdateProjectResult_Error(t *testing.T) {
BaseRepo: repoModel,
Pull: pullModel,
Command: &events.Command{Name: events.Plan},
+ VCSHost: vcs.Github,
}
- client := mocks.NewMockClient()
- s := events.GithubStatus{Client: client}
+ client := mocks.NewMockClientProxy()
+ s := events.DefaultCommitStatusUpdater{Client: client}
err := s.UpdateProjectResult(ctx, events.CommandResponse{Error: errors.New("err")})
Ok(t, err)
- client.VerifyWasCalledOnce().UpdateStatus(repoModel, pullModel, events.Error.String(), "Plan Error", "Atlantis")
+ client.VerifyWasCalledOnce().UpdateStatus(repoModel, pullModel, vcs.Failed, "Plan Failed", vcs.Github)
}
func TestUpdateProjectResult_Failure(t *testing.T) {
@@ -61,12 +61,13 @@ func TestUpdateProjectResult_Failure(t *testing.T) {
BaseRepo: repoModel,
Pull: pullModel,
Command: &events.Command{Name: events.Plan},
+ VCSHost: vcs.Github,
}
- client := mocks.NewMockClient()
- s := events.GithubStatus{Client: client}
+ client := mocks.NewMockClientProxy()
+ s := events.DefaultCommitStatusUpdater{Client: client}
err := s.UpdateProjectResult(ctx, events.CommandResponse{Failure: "failure"})
Ok(t, err)
- client.VerifyWasCalledOnce().UpdateStatus(repoModel, pullModel, events.Failure.String(), "Plan Failure", "Atlantis")
+ client.VerifyWasCalledOnce().UpdateStatus(repoModel, pullModel, vcs.Failed, "Plan Failed", vcs.Github)
}
func TestUpdateProjectResult(t *testing.T) {
@@ -77,31 +78,40 @@ func TestUpdateProjectResult(t *testing.T) {
BaseRepo: repoModel,
Pull: pullModel,
Command: &events.Command{Name: events.Plan},
+ VCSHost: vcs.Github,
}
cases := []struct {
Statuses []string
- Expected string
+ Expected vcs.CommitStatus
}{
{
[]string{"success", "failure", "error"},
- "error",
+ vcs.Failed,
},
{
[]string{"failure", "error", "success"},
- "error",
+ vcs.Failed,
},
{
[]string{"success", "failure"},
- "failure",
+ vcs.Failed,
},
{
[]string{"success", "error"},
- "error",
+ vcs.Failed,
},
{
[]string{"failure", "error"},
- "error",
+ vcs.Failed,
+ },
+ {
+ []string{"success"},
+ vcs.Success,
+ },
+ {
+ []string{"success", "success"},
+ vcs.Success,
},
}
@@ -121,10 +131,10 @@ func TestUpdateProjectResult(t *testing.T) {
}
resp := events.CommandResponse{ProjectResults: results}
- client := mocks.NewMockClient()
- s := events.GithubStatus{Client: client}
+ client := mocks.NewMockClientProxy()
+ s := events.DefaultCommitStatusUpdater{Client: client}
err := s.UpdateProjectResult(ctx, resp)
Ok(t, err)
- client.VerifyWasCalledOnce().UpdateStatus(repoModel, pullModel, c.Expected, "Plan "+strings.Title(c.Expected), "Atlantis")
+ client.VerifyWasCalledOnce().UpdateStatus(repoModel, pullModel, c.Expected, "Plan "+strings.Title(c.Expected.String()), vcs.Github)
}
}
diff --git a/server/events/event_parser.go b/server/events/event_parser.go
index e7e72887a0..2d99449715 100644
--- a/server/events/event_parser.go
+++ b/server/events/event_parser.go
@@ -7,8 +7,12 @@ import (
"github.com/google/go-github/github"
"github.com/hootsuite/atlantis/server/events/models"
+ "github.com/hootsuite/atlantis/server/events/vcs"
+ "github.com/lkysow/go-gitlab"
)
+const gitlabPullOpened = "opened"
+
//go:generate pegomock generate --use-experimental-model-gen --package mocks -o mocks/mock_event_parsing.go EventParsing
type Command struct {
@@ -19,20 +23,25 @@ type Command struct {
}
type EventParsing interface {
- DetermineCommand(comment *github.IssueCommentEvent) (*Command, error)
- ExtractCommentData(comment *github.IssueCommentEvent) (baseRepo models.Repo, user models.User, pull models.PullRequest, err error)
- ExtractPullData(pull *github.PullRequest) (models.PullRequest, models.Repo, error)
- ExtractRepoData(ghRepo *github.Repository) (models.Repo, error)
+ DetermineCommand(comment string, vcsHost vcs.Host) (*Command, error)
+ ParseGithubIssueCommentEvent(comment *github.IssueCommentEvent) (baseRepo models.Repo, user models.User, pullNum int, err error)
+ ParseGithubPull(pull *github.PullRequest) (models.PullRequest, models.Repo, error)
+ ParseGithubRepo(ghRepo *github.Repository) (models.Repo, error)
+ ParseGitlabMergeEvent(event gitlab.MergeEvent) (models.PullRequest, models.Repo)
+ ParseGitlabMergeCommentEvent(event gitlab.MergeCommentEvent) (baseRepo models.Repo, headRepo models.Repo, user models.User)
+ ParseGitlabMergeRequest(mr *gitlab.MergeRequest) models.PullRequest
}
type EventParser struct {
GithubUser string
GithubToken string
+ GitlabUser string
+ GitlabToken string
}
// DetermineCommand parses the comment as an atlantis command. If it succeeds,
// it returns the command. Otherwise it returns error.
-func (e *EventParser) DetermineCommand(comment *github.IssueCommentEvent) (*Command, error) {
+func (e *EventParser) DetermineCommand(comment string, vcsHost vcs.Host) (*Command, error) {
// valid commands contain:
// the initial "executable" name, 'run' or 'atlantis' or '@GithubUser' where GithubUser is the api user atlantis is running as
// then a command, either 'plan', 'apply', or 'help'
@@ -44,12 +53,8 @@ func (e *EventParser) DetermineCommand(comment *github.IssueCommentEvent) (*Comm
// @GithubUser plan staging
// atlantis plan staging --verbose
// atlantis plan staging --verbose -key=value -key2 value2
- commentBody := comment.Comment.GetBody()
- if commentBody == "" {
- return nil, errors.New("comment.body is null")
- }
err := errors.New("not an Atlantis command")
- args := strings.Fields(commentBody)
+ args := strings.Fields(comment)
if len(args) < 2 {
return nil, err
}
@@ -58,7 +63,11 @@ func (e *EventParser) DetermineCommand(comment *github.IssueCommentEvent) (*Comm
verbose := false
var flags []string
- if !e.stringInSlice(args[0], []string{"run", "atlantis", "@" + e.GithubUser}) {
+ vcsUser := e.GithubUser
+ if vcsHost == vcs.Gitlab {
+ vcsUser = e.GitlabUser
+ }
+ if !e.stringInSlice(args[0], []string{"run", "atlantis", "@" + vcsUser}) {
return nil, err
}
if !e.stringInSlice(args[1], []string{"plan", "apply", "help"}) {
@@ -99,41 +108,28 @@ func (e *EventParser) DetermineCommand(comment *github.IssueCommentEvent) (*Comm
return c, nil
}
-func (e *EventParser) ExtractCommentData(comment *github.IssueCommentEvent) (baseRepo models.Repo, user models.User, pull models.PullRequest, err error) {
- baseRepo, err = e.ExtractRepoData(comment.Repo)
+func (e *EventParser) ParseGithubIssueCommentEvent(comment *github.IssueCommentEvent) (baseRepo models.Repo, user models.User, pullNum int, err error) {
+ baseRepo, err = e.ParseGithubRepo(comment.Repo)
if err != nil {
return
}
- pullNum := comment.Issue.GetNumber()
- if pullNum == 0 {
- err = errors.New("issue.number is null")
- return
- }
- pullCreator := comment.Issue.User.GetLogin()
- if pullCreator == "" {
- err = errors.New("issue.user.login is null")
- return
- }
- htmlURL := comment.Issue.GetHTMLURL()
- if htmlURL == "" {
- err = errors.New("issue.html_url is null")
- return
- }
- commentorUsername := comment.Comment.User.GetLogin()
- if commentorUsername == "" {
+ if comment.Comment == nil || comment.Comment.User.GetLogin() == "" {
err = errors.New("comment.user.login is null")
return
}
+ commentorUsername := comment.Comment.User.GetLogin()
user = models.User{
Username: commentorUsername,
}
- pull = models.PullRequest{
- Num: pullNum,
+ pullNum = comment.Issue.GetNumber()
+ if pullNum == 0 {
+ err = errors.New("issue.number is null")
+ return
}
return
}
-func (e *EventParser) ExtractPullData(pull *github.PullRequest) (models.PullRequest, models.Repo, error) {
+func (e *EventParser) ParseGithubPull(pull *github.PullRequest) (models.PullRequest, models.Repo, error) {
var pullModel models.PullRequest
var headRepoModel models.Repo
@@ -141,10 +137,6 @@ func (e *EventParser) ExtractPullData(pull *github.PullRequest) (models.PullRequ
if commit == "" {
return pullModel, headRepoModel, errors.New("head.sha is null")
}
- base := pull.Base.GetSHA()
- if base == "" {
- return pullModel, headRepoModel, errors.New("base.sha is null")
- }
url := pull.GetHTMLURL()
if url == "" {
return pullModel, headRepoModel, errors.New("html_url is null")
@@ -162,22 +154,27 @@ func (e *EventParser) ExtractPullData(pull *github.PullRequest) (models.PullRequ
return pullModel, headRepoModel, errors.New("number is null")
}
- headRepoModel, err := e.ExtractRepoData(pull.Head.Repo)
+ headRepoModel, err := e.ParseGithubRepo(pull.Head.Repo)
if err != nil {
return pullModel, headRepoModel, err
}
+ pullState := models.Closed
+ if pull.GetState() == "open" {
+ pullState = models.Open
+ }
+
return models.PullRequest{
- BaseCommit: base,
Author: authorUsername,
Branch: branch,
HeadCommit: commit,
URL: url,
Num: num,
+ State: pullState,
}, headRepoModel, nil
}
-func (e *EventParser) ExtractRepoData(ghRepo *github.Repository) (models.Repo, error) {
+func (e *EventParser) ParseGithubRepo(ghRepo *github.Repository) (models.Repo, error) {
var repo models.Repo
repoFullName := ghRepo.GetFullName()
if repoFullName == "" {
@@ -196,7 +193,7 @@ func (e *EventParser) ExtractRepoData(ghRepo *github.Repository) (models.Repo, e
return repo, errors.New("repository.clone_url is null")
}
- // construct HTTPS repo clone url string with username and password
+ // Construct HTTPS repo clone url string with username and password.
repoCloneURL := strings.Replace(repoSanitizedCloneURL, "https://", fmt.Sprintf("https://%s:%s@", e.GithubUser, e.GithubToken), -1)
return models.Repo{
@@ -208,6 +205,100 @@ func (e *EventParser) ExtractRepoData(ghRepo *github.Repository) (models.Repo, e
}, nil
}
+func (e *EventParser) ParseGitlabMergeEvent(event gitlab.MergeEvent) (models.PullRequest, models.Repo) {
+ modelState := models.Closed
+ if event.ObjectAttributes.State == gitlabPullOpened {
+ modelState = models.Open
+ }
+ // GitLab also has a "merged" state, but we map that to Closed so we don't
+ // need to check for it.
+
+ pull := models.PullRequest{
+ URL: event.ObjectAttributes.URL,
+ Author: event.User.Username,
+ Num: event.ObjectAttributes.IID,
+ HeadCommit: event.ObjectAttributes.LastCommit.ID,
+ Branch: event.ObjectAttributes.SourceBranch,
+ State: modelState,
+ }
+
+ cloneURL := e.addGitlabAuth(event.Project.GitHTTPURL)
+ // Get owner and name from PathWithNamespace because the fields
+ // event.Project.Name and event.Project.Owner can have capitals.
+ owner, name := e.getOwnerAndName(event.Project.PathWithNamespace)
+ repo := models.Repo{
+ FullName: event.Project.PathWithNamespace,
+ Name: name,
+ SanitizedCloneURL: event.Project.GitHTTPURL,
+ Owner: owner,
+ CloneURL: cloneURL,
+ }
+ return pull, repo
+}
+
+// addGitlabAuth adds gitlab username/password to the cloneURL.
+// We support http and https URLs because GitLab's docs have http:// URLs whereas
+// their API responses have https://.
+// Ex. https://gitlab.com/owner/repo.git => https://uname:pass@gitlab.com/owner/repo.git
+func (e *EventParser) addGitlabAuth(cloneURL string) string {
+ httpsReplaced := strings.Replace(cloneURL, "https://", fmt.Sprintf("https://%s:%s@", e.GitlabUser, e.GitlabToken), -1)
+ return strings.Replace(httpsReplaced, "http://", fmt.Sprintf("http://%s:%s@", e.GitlabUser, e.GitlabToken), -1)
+}
+
+// getOwnerAndName takes pathWithNamespace that should look like "owner/repo"
+// and returns "owner", "repo"
+func (e *EventParser) getOwnerAndName(pathWithNamespace string) (string, string) {
+ pathSplit := strings.Split(pathWithNamespace, "/")
+ if len(pathSplit) > 1 {
+ return pathSplit[0], pathSplit[1]
+ }
+ return "", ""
+}
+
+// ParseGitlabMergeCommentEvent creates Atlantis models out of a GitLab event.
+func (e *EventParser) ParseGitlabMergeCommentEvent(event gitlab.MergeCommentEvent) (baseRepo models.Repo, headRepo models.Repo, user models.User) {
+ // Get owner and name from PathWithNamespace because the fields
+ // event.Project.Name and event.Project.Owner can have capitals.
+ owner, name := e.getOwnerAndName(event.Project.PathWithNamespace)
+ baseRepo = models.Repo{
+ FullName: event.Project.PathWithNamespace,
+ Name: name,
+ SanitizedCloneURL: event.Project.GitHTTPURL,
+ Owner: owner,
+ CloneURL: e.addGitlabAuth(event.Project.GitHTTPURL),
+ }
+ user = models.User{
+ Username: event.User.Username,
+ }
+ owner, name = e.getOwnerAndName(event.MergeRequest.Source.PathWithNamespace)
+ headRepo = models.Repo{
+ FullName: event.MergeRequest.Source.PathWithNamespace,
+ Name: name,
+ SanitizedCloneURL: event.MergeRequest.Source.GitHTTPURL,
+ Owner: owner,
+ CloneURL: e.addGitlabAuth(event.MergeRequest.Source.GitHTTPURL),
+ }
+ return
+}
+
+func (e *EventParser) ParseGitlabMergeRequest(mr *gitlab.MergeRequest) models.PullRequest {
+ pullState := models.Closed
+ if mr.State == gitlabPullOpened {
+ pullState = models.Open
+ }
+ // GitLab also has a "merged" state, but we map that to Closed so we don't
+ // need to check for it.
+
+ return models.PullRequest{
+ URL: mr.WebURL,
+ Author: mr.Author.Username,
+ Num: mr.IID,
+ HeadCommit: mr.SHA,
+ Branch: mr.SourceBranch,
+ State: pullState,
+ }
+}
+
func (e *EventParser) stringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
diff --git a/server/events/event_parser_test.go b/server/events/event_parser_test.go
index 996c7e91b9..29c8efd8ff 100644
--- a/server/events/event_parser_test.go
+++ b/server/events/event_parser_test.go
@@ -6,22 +6,23 @@ import (
"errors"
"strings"
+ "encoding/json"
+
"github.com/google/go-github/github"
"github.com/hootsuite/atlantis/server/events"
- . "github.com/hootsuite/atlantis/server/events/github/fixtures"
"github.com/hootsuite/atlantis/server/events/models"
+ "github.com/hootsuite/atlantis/server/events/vcs"
+ . "github.com/hootsuite/atlantis/server/events/vcs/fixtures"
. "github.com/hootsuite/atlantis/testing"
+ "github.com/lkysow/go-gitlab"
"github.com/mohae/deepcopy"
)
var parser = events.EventParser{
- GithubUser: "user",
- GithubToken: "token",
-}
-
-func TestDetermineCommandNoBody(t *testing.T) {
- _, err := parser.DetermineCommand(&github.IssueCommentEvent{})
- Equals(t, errors.New("comment.body is null"), err)
+ GithubUser: "github-user",
+ GithubToken: "github-token",
+ GitlabUser: "gitlab-user",
+ GitlabToken: "gitlab-token",
}
func TestDetermineCommandInvalid(t *testing.T) {
@@ -30,17 +31,17 @@ func TestDetermineCommandInvalid(t *testing.T) {
// just the executable, no command
"run",
"atlantis",
- "@user",
+ "@github-user",
// invalid command
"run slkjd",
"atlantis slkjd",
- "@user slkjd",
+ "@github-user slkjd",
"atlantis plans",
// misc
"related comment mentioning atlantis",
}
for _, c := range comments {
- _, e := parser.DetermineCommand(buildComment(c))
+ _, e := parser.DetermineCommand(c, vcs.Github)
Assert(t, e != nil, "expected error for comment: "+c)
}
}
@@ -50,18 +51,18 @@ func TestDetermineCommandHelp(t *testing.T) {
comments := []string{
"run help",
"atlantis help",
- "@user help",
+ "@github-user help",
"atlantis help --verbose",
}
for _, c := range comments {
- command, e := parser.DetermineCommand(buildComment(c))
+ command, e := parser.DetermineCommand(c, vcs.Github)
Ok(t, e)
Equals(t, events.Help, command.Name)
}
}
func TestDetermineCommandPermutations(t *testing.T) {
- execNames := []string{"run", "atlantis", "@user"}
+ execNames := []string{"run", "atlantis", "@github-user", "@gitlab-user"}
commandNames := []events.CommandName{events.Plan, events.Apply}
envs := []string{"", "default", "env", "env-dash", "env_underscore", "camelEnv"}
flagCases := [][]string{
@@ -86,7 +87,15 @@ func TestDetermineCommandPermutations(t *testing.T) {
for _, lineEnding := range []string{"", "\r\n"} {
comment := strings.Join(append([]string{exec, name.String(), env}, flags...), " ") + lineEnding
t.Log("testing comment: " + comment)
- c, err := parser.DetermineCommand(buildComment(comment))
+
+ // In order to test gitlab without fully refactoring this test
+ // we're just detecting if we're using the gitlab user as the
+ // exec name.
+ vcsHost := vcs.Github
+ if exec == "@gitlab-user" {
+ vcsHost = vcs.Gitlab
+ }
+ c, err := parser.DetermineCommand(comment, vcsHost)
Ok(t, err)
Equals(t, name, c.Name)
if env == "" {
@@ -114,42 +123,42 @@ func TestDetermineCommandPermutations(t *testing.T) {
}
}
-func TestExtractRepoData(t *testing.T) {
+func TestParseGithubRepo(t *testing.T) {
testRepo := Repo
testRepo.FullName = nil
- _, err := parser.ExtractRepoData(&testRepo)
+ _, err := parser.ParseGithubRepo(&testRepo)
Equals(t, errors.New("repository.full_name is null"), err)
testRepo = Repo
testRepo.Owner = nil
- _, err = parser.ExtractRepoData(&testRepo)
+ _, err = parser.ParseGithubRepo(&testRepo)
Equals(t, errors.New("repository.owner.login is null"), err)
testRepo = Repo
testRepo.Name = nil
- _, err = parser.ExtractRepoData(&testRepo)
+ _, err = parser.ParseGithubRepo(&testRepo)
Equals(t, errors.New("repository.name is null"), err)
testRepo = Repo
testRepo.CloneURL = nil
- _, err = parser.ExtractRepoData(&testRepo)
+ _, err = parser.ParseGithubRepo(&testRepo)
Equals(t, errors.New("repository.clone_url is null"), err)
t.Log("should replace https clone with user/pass")
{
- r, err := parser.ExtractRepoData(&Repo)
+ r, err := parser.ParseGithubRepo(&Repo)
Ok(t, err)
Equals(t, models.Repo{
Owner: "owner",
FullName: "owner/repo",
- CloneURL: "https://user:token@github.com/lkysow/atlantis-example.git",
+ CloneURL: "https://github-user:github-token@github.com/lkysow/atlantis-example.git",
SanitizedCloneURL: Repo.GetCloneURL(),
Name: "repo",
}, r)
}
}
-func TestExtractCommentData(t *testing.T) {
+func TestParseGithubIssueCommentEvent(t *testing.T) {
comment := github.IssueCommentEvent{
Repo: &Repo,
Issue: &github.Issue{
@@ -163,109 +172,169 @@ func TestExtractCommentData(t *testing.T) {
}
testComment := deepcopy.Copy(comment).(github.IssueCommentEvent)
testComment.Repo = nil
- _, _, _, err := parser.ExtractCommentData(&testComment)
+ _, _, _, err := parser.ParseGithubIssueCommentEvent(&testComment)
Equals(t, errors.New("repository.full_name is null"), err)
testComment = deepcopy.Copy(comment).(github.IssueCommentEvent)
- testComment.Issue = nil
- _, _, _, err = parser.ExtractCommentData(&testComment)
- Equals(t, errors.New("issue.number is null"), err)
-
- testComment = deepcopy.Copy(comment).(github.IssueCommentEvent)
- testComment.Issue.User = nil
- _, _, _, err = parser.ExtractCommentData(&testComment)
- Equals(t, errors.New("issue.user.login is null"), err)
+ testComment.Comment = nil
+ _, _, _, err = parser.ParseGithubIssueCommentEvent(&testComment)
+ Equals(t, errors.New("comment.user.login is null"), err)
testComment = deepcopy.Copy(comment).(github.IssueCommentEvent)
- testComment.Issue.HTMLURL = nil
- _, _, _, err = parser.ExtractCommentData(&testComment)
- Equals(t, errors.New("issue.html_url is null"), err)
+ testComment.Comment.User = nil
+ _, _, _, err = parser.ParseGithubIssueCommentEvent(&testComment)
+ Equals(t, errors.New("comment.user.login is null"), err)
testComment = deepcopy.Copy(comment).(github.IssueCommentEvent)
testComment.Comment.User.Login = nil
- _, _, _, err = parser.ExtractCommentData(&testComment)
+ _, _, _, err = parser.ParseGithubIssueCommentEvent(&testComment)
Equals(t, errors.New("comment.user.login is null"), err)
+ testComment = deepcopy.Copy(comment).(github.IssueCommentEvent)
+ testComment.Issue = nil
+ _, _, _, err = parser.ParseGithubIssueCommentEvent(&testComment)
+ Equals(t, errors.New("issue.number is null"), err)
+
// this should be successful
- repo, user, pull, err := parser.ExtractCommentData(&comment)
+ repo, user, pullNum, err := parser.ParseGithubIssueCommentEvent(&comment)
Ok(t, err)
Equals(t, models.Repo{
Owner: *comment.Repo.Owner.Login,
FullName: *comment.Repo.FullName,
- CloneURL: "https://user:token@github.com/lkysow/atlantis-example.git",
+ CloneURL: "https://github-user:github-token@github.com/lkysow/atlantis-example.git",
SanitizedCloneURL: *comment.Repo.CloneURL,
Name: "repo",
}, repo)
Equals(t, models.User{
Username: *comment.Comment.User.Login,
}, user)
- Equals(t, models.PullRequest{
- Num: *comment.Issue.Number,
- }, pull)
+ Equals(t, *comment.Issue.Number, pullNum)
}
-func TestExtractPullData(t *testing.T) {
+func TestParseGithubPull(t *testing.T) {
testPull := deepcopy.Copy(Pull).(github.PullRequest)
testPull.Head.SHA = nil
- _, _, err := parser.ExtractPullData(&testPull)
+ _, _, err := parser.ParseGithubPull(&testPull)
Equals(t, errors.New("head.sha is null"), err)
- testPull = deepcopy.Copy(Pull).(github.PullRequest)
- testPull.Base.SHA = nil
- _, _, err = parser.ExtractPullData(&testPull)
- Equals(t, errors.New("base.sha is null"), err)
-
testPull = deepcopy.Copy(Pull).(github.PullRequest)
testPull.HTMLURL = nil
- _, _, err = parser.ExtractPullData(&testPull)
+ _, _, err = parser.ParseGithubPull(&testPull)
Equals(t, errors.New("html_url is null"), err)
testPull = deepcopy.Copy(Pull).(github.PullRequest)
testPull.Head.Ref = nil
- _, _, err = parser.ExtractPullData(&testPull)
+ _, _, err = parser.ParseGithubPull(&testPull)
Equals(t, errors.New("head.ref is null"), err)
testPull = deepcopy.Copy(Pull).(github.PullRequest)
testPull.User.Login = nil
- _, _, err = parser.ExtractPullData(&testPull)
+ _, _, err = parser.ParseGithubPull(&testPull)
Equals(t, errors.New("user.login is null"), err)
testPull = deepcopy.Copy(Pull).(github.PullRequest)
testPull.Number = nil
- _, _, err = parser.ExtractPullData(&testPull)
+ _, _, err = parser.ParseGithubPull(&testPull)
Equals(t, errors.New("number is null"), err)
testPull = deepcopy.Copy(Pull).(github.PullRequest)
testPull.Head.Repo = nil
- _, _, err = parser.ExtractPullData(&testPull)
+ _, _, err = parser.ParseGithubPull(&testPull)
Equals(t, errors.New("repository.full_name is null"), err)
- PullRes, repoRes, err := parser.ExtractPullData(&Pull)
+ pullRes, repoRes, err := parser.ParseGithubPull(&Pull)
Ok(t, err)
Equals(t, models.PullRequest{
- BaseCommit: Pull.Base.GetSHA(),
URL: Pull.GetHTMLURL(),
Author: Pull.User.GetLogin(),
Branch: Pull.Head.GetRef(),
HeadCommit: Pull.Head.GetSHA(),
Num: Pull.GetNumber(),
- }, PullRes)
+ State: models.Open,
+ }, pullRes)
Equals(t, models.Repo{
Owner: "owner",
FullName: "owner/repo",
- CloneURL: "https://user:token@github.com/lkysow/atlantis-example.git",
+ CloneURL: "https://github-user:github-token@github.com/lkysow/atlantis-example.git",
SanitizedCloneURL: Repo.GetCloneURL(),
Name: "repo",
}, repoRes)
}
-func buildComment(c string) *github.IssueCommentEvent {
- return &github.IssueCommentEvent{
- Comment: &github.IssueComment{
- Body: github.String(c),
- },
- }
+func TestParseGitlabMergeEvent(t *testing.T) {
+ t.Log("should properly parse a gitlab merge event")
+ var event *gitlab.MergeEvent
+ err := json.Unmarshal([]byte(mergeEventJSON), &event)
+ Ok(t, err)
+ pull, repo := parser.ParseGitlabMergeEvent(*event)
+ Equals(t, models.PullRequest{
+ URL: "http://example.com/diaspora/merge_requests/1",
+ Author: "root",
+ Num: 1,
+ HeadCommit: "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ Branch: "ms-viewport",
+ State: models.Open,
+ }, pull)
+
+ Equals(t, models.Repo{
+ FullName: "gitlabhq/gitlab-test",
+ Name: "gitlab-test",
+ SanitizedCloneURL: "https://example.com/gitlabhq/gitlab-test.git",
+ Owner: "gitlabhq",
+ CloneURL: "https://gitlab-user:gitlab-token@example.com/gitlabhq/gitlab-test.git",
+ }, repo)
+
+ t.Log("If the state is closed, should set field correctly.")
+ event.ObjectAttributes.State = "closed"
+ pull, _ = parser.ParseGitlabMergeEvent(*event)
+ Equals(t, models.Closed, pull.State)
+}
+
+func TestParseGitlabMergeRequest(t *testing.T) {
+ t.Log("should properly parse a gitlab merge request")
+ var event *gitlab.MergeRequest
+ err := json.Unmarshal([]byte(mergeRequestJSON), &event)
+ Ok(t, err)
+ pull := parser.ParseGitlabMergeRequest(event)
+ Equals(t, models.PullRequest{
+ URL: "https://gitlab.com/lkysow/atlantis-example/merge_requests/8",
+ Author: "lkysow",
+ Num: 8,
+ HeadCommit: "0b4ac85ea3063ad5f2974d10cd68dd1f937aaac2",
+ Branch: "abc",
+ State: models.Open,
+ }, pull)
+
+ t.Log("If the state is closed, should set field correctly.")
+ event.State = "closed"
+ pull = parser.ParseGitlabMergeRequest(event)
+ Equals(t, models.Closed, pull.State)
+}
+
+func TestParseGitlabMergeCommentEvent(t *testing.T) {
+ t.Log("should properly parse a gitlab merge comment event")
+ var event *gitlab.MergeCommentEvent
+ err := json.Unmarshal([]byte(mergeCommentEventJSON), &event)
+ Ok(t, err)
+ baseRepo, headRepo, user := parser.ParseGitlabMergeCommentEvent(*event)
+ Equals(t, models.Repo{
+ FullName: "gitlabhq/gitlab-test",
+ Name: "gitlab-test",
+ SanitizedCloneURL: "https://example.com/gitlabhq/gitlab-test.git",
+ Owner: "gitlabhq",
+ CloneURL: "https://gitlab-user:gitlab-token@example.com/gitlabhq/gitlab-test.git",
+ }, baseRepo)
+ Equals(t, models.Repo{
+ FullName: "gitlab-org/gitlab-test",
+ Name: "gitlab-test",
+ SanitizedCloneURL: "https://example.com/gitlab-org/gitlab-test.git",
+ Owner: "gitlab-org",
+ CloneURL: "https://gitlab-user:gitlab-token@example.com/gitlab-org/gitlab-test.git",
+ }, headRepo)
+ Equals(t, models.User{
+ Username: "root",
+ }, user)
}
func containsVerbose(list []string) bool {
@@ -276,3 +345,310 @@ func containsVerbose(list []string) bool {
}
return false
}
+
+var mergeEventJSON = `{
+ "object_kind": "merge_request",
+ "user": {
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ },
+ "project": {
+ "id": 1,
+ "name":"Gitlab Test",
+ "description":"Aut reprehenderit ut est.",
+ "web_url":"http://example.com/gitlabhq/gitlab-test",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
+ "git_http_url":"https://example.com/gitlabhq/gitlab-test.git",
+ "namespace":"GitlabHQ",
+ "visibility_level":20,
+ "path_with_namespace":"gitlabhq/gitlab-test",
+ "default_branch":"master",
+ "homepage":"http://example.com/gitlabhq/gitlab-test",
+ "url":"https://example.com/gitlabhq/gitlab-test.git",
+ "ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
+ "http_url":"https://example.com/gitlabhq/gitlab-test.git"
+ },
+ "repository": {
+ "name": "Gitlab Test",
+ "url": "https://example.com/gitlabhq/gitlab-test.git",
+ "description": "Aut reprehenderit ut est.",
+ "homepage": "http://example.com/gitlabhq/gitlab-test"
+ },
+ "object_attributes": {
+ "id": 99,
+ "target_branch": "master",
+ "source_branch": "ms-viewport",
+ "source_project_id": 14,
+ "author_id": 51,
+ "assignee_id": 6,
+ "title": "MS-Viewport",
+ "created_at": "2013-12-03T17:23:34Z",
+ "updated_at": "2013-12-03T17:23:34Z",
+ "st_commits": null,
+ "st_diffs": null,
+ "milestone_id": null,
+ "state": "opened",
+ "merge_status": "unchecked",
+ "target_project_id": 14,
+ "iid": 1,
+ "description": "",
+ "source": {
+ "name":"Awesome Project",
+ "description":"Aut reprehenderit ut est.",
+ "web_url":"http://example.com/awesome_space/awesome_project",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:awesome_space/awesome_project.git",
+ "git_http_url":"http://example.com/awesome_space/awesome_project.git",
+ "namespace":"Awesome Space",
+ "visibility_level":20,
+ "path_with_namespace":"awesome_space/awesome_project",
+ "default_branch":"master",
+ "homepage":"http://example.com/awesome_space/awesome_project",
+ "url":"http://example.com/awesome_space/awesome_project.git",
+ "ssh_url":"git@example.com:awesome_space/awesome_project.git",
+ "http_url":"http://example.com/awesome_space/awesome_project.git"
+ },
+ "target": {
+ "name":"Awesome Project",
+ "description":"Aut reprehenderit ut est.",
+ "web_url":"http://example.com/awesome_space/awesome_project",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:awesome_space/awesome_project.git",
+ "git_http_url":"http://example.com/awesome_space/awesome_project.git",
+ "namespace":"Awesome Space",
+ "visibility_level":20,
+ "path_with_namespace":"awesome_space/awesome_project",
+ "default_branch":"master",
+ "homepage":"http://example.com/awesome_space/awesome_project",
+ "url":"http://example.com/awesome_space/awesome_project.git",
+ "ssh_url":"git@example.com:awesome_space/awesome_project.git",
+ "http_url":"http://example.com/awesome_space/awesome_project.git"
+ },
+ "last_commit": {
+ "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "message": "fixed readme",
+ "timestamp": "2012-01-03T23:36:29+02:00",
+ "url": "http://example.com/awesome_space/awesome_project/commits/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "author": {
+ "name": "GitLab dev user",
+ "email": "gitlabdev@dv6700.(none)"
+ }
+ },
+ "work_in_progress": false,
+ "url": "http://example.com/diaspora/merge_requests/1",
+ "action": "open",
+ "assignee": {
+ "name": "User1",
+ "username": "user1",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ }
+ },
+ "labels": [{
+ "id": 206,
+ "title": "API",
+ "color": "#ffffff",
+ "project_id": 14,
+ "created_at": "2013-12-03T17:15:43Z",
+ "updated_at": "2013-12-03T17:15:43Z",
+ "template": false,
+ "description": "API related issues",
+ "type": "ProjectLabel",
+ "group_id": 41
+ }],
+ "changes": {
+ "updated_by_id": [null, 1],
+ "updated_at": ["2017-09-15 16:50:55 UTC", "2017-09-15 16:52:00 UTC"],
+ "labels": {
+ "previous": [{
+ "id": 206,
+ "title": "API",
+ "color": "#ffffff",
+ "project_id": 14,
+ "created_at": "2013-12-03T17:15:43Z",
+ "updated_at": "2013-12-03T17:15:43Z",
+ "template": false,
+ "description": "API related issues",
+ "type": "ProjectLabel",
+ "group_id": 41
+ }],
+ "current": [{
+ "id": 205,
+ "title": "Platform",
+ "color": "#123123",
+ "project_id": 14,
+ "created_at": "2013-12-03T17:15:43Z",
+ "updated_at": "2013-12-03T17:15:43Z",
+ "template": false,
+ "description": "Platform related issues",
+ "type": "ProjectLabel",
+ "group_id": 41
+ }]
+ }
+ }
+}`
+
+var mergeCommentEventJSON = `{
+ "object_kind": "note",
+ "user": {
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ },
+ "project_id": 5,
+ "project":{
+ "id": 5,
+ "name":"Gitlab Test",
+ "description":"Aut reprehenderit ut est.",
+ "web_url":"http://example.com/gitlabhq/gitlab-test",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
+ "git_http_url":"https://example.com/gitlabhq/gitlab-test.git",
+ "namespace":"Gitlab Org",
+ "visibility_level":10,
+ "path_with_namespace":"gitlabhq/gitlab-test",
+ "default_branch":"master",
+ "homepage":"http://example.com/gitlabhq/gitlab-test",
+ "url":"https://example.com/gitlabhq/gitlab-test.git",
+ "ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
+ "http_url":"https://example.com/gitlabhq/gitlab-test.git"
+ },
+ "repository":{
+ "name": "Gitlab Test",
+ "url": "http://localhost/gitlab-org/gitlab-test.git",
+ "description": "Aut reprehenderit ut est.",
+ "homepage": "http://example.com/gitlab-org/gitlab-test"
+ },
+ "object_attributes": {
+ "id": 1244,
+ "note": "This MR needs work.",
+ "noteable_type": "MergeRequest",
+ "author_id": 1,
+ "created_at": "2015-05-17",
+ "updated_at": "2015-05-17",
+ "project_id": 5,
+ "attachment": null,
+ "line_code": null,
+ "commit_id": "",
+ "noteable_id": 7,
+ "system": false,
+ "st_diff": null,
+ "url": "http://example.com/gitlab-org/gitlab-test/merge_requests/1#note_1244"
+ },
+ "merge_request": {
+ "id": 7,
+ "target_branch": "markdown",
+ "source_branch": "master",
+ "source_project_id": 5,
+ "author_id": 8,
+ "assignee_id": 28,
+ "title": "Tempora et eos debitis quae laborum et.",
+ "created_at": "2015-03-01 20:12:53 UTC",
+ "updated_at": "2015-03-21 18:27:27 UTC",
+ "milestone_id": 11,
+ "state": "opened",
+ "merge_status": "cannot_be_merged",
+ "target_project_id": 5,
+ "iid": 1,
+ "description": "Et voluptas corrupti assumenda temporibus. Architecto cum animi eveniet amet asperiores. Vitae numquam voluptate est natus sit et ad id.",
+ "position": 0,
+ "source":{
+ "name":"Gitlab Test",
+ "description":"Aut reprehenderit ut est.",
+ "web_url":"http://example.com/gitlab-org/gitlab-test",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:gitlab-org/gitlab-test.git",
+ "git_http_url":"https://example.com/gitlab-org/gitlab-test.git",
+ "namespace":"Gitlab Org",
+ "visibility_level":10,
+ "path_with_namespace":"gitlab-org/gitlab-test",
+ "default_branch":"master",
+ "homepage":"http://example.com/gitlab-org/gitlab-test",
+ "url":"https://example.com/gitlab-org/gitlab-test.git",
+ "ssh_url":"git@example.com:gitlab-org/gitlab-test.git",
+ "http_url":"https://example.com/gitlab-org/gitlab-test.git",
+ "git_http_url":"https://example.com/gitlab-org/gitlab-test.git"
+ },
+ "target": {
+ "name":"Gitlab Test",
+ "description":"Aut reprehenderit ut est.",
+ "web_url":"http://example.com/gitlabhq/gitlab-test",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
+ "git_http_url":"https://example.com/gitlabhq/gitlab-test.git",
+ "namespace":"Gitlab Org",
+ "visibility_level":10,
+ "path_with_namespace":"gitlabhq/gitlab-test",
+ "default_branch":"master",
+ "homepage":"http://example.com/gitlabhq/gitlab-test",
+ "url":"https://example.com/gitlabhq/gitlab-test.git",
+ "ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
+ "http_url":"https://example.com/gitlabhq/gitlab-test.git"
+ },
+ "last_commit": {
+ "id": "562e173be03b8ff2efb05345d12df18815438a4b",
+ "message": "Merge branch 'another-branch' into 'master'\n\nCheck in this test\n",
+ "timestamp": "2002-10-02T10:00:00-05:00",
+ "url": "http://example.com/gitlab-org/gitlab-test/commit/562e173be03b8ff2efb05345d12df18815438a4b",
+ "author": {
+ "name": "John Smith",
+ "email": "john@example.com"
+ }
+ },
+ "work_in_progress": false,
+ "assignee": {
+ "name": "User1",
+ "username": "user1",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ }
+ }
+}`
+
+var mergeRequestJSON = `{
+ "id":6056811,
+ "iid":8,
+ "project_id":4580910,
+ "title":"Update main.tf",
+ "description":"",
+ "state":"opened",
+ "created_at":"2017-11-13T19:33:42.704Z",
+ "updated_at":"2017-11-13T23:35:26.200Z",
+ "target_branch":"master",
+ "source_branch":"abc",
+ "upvotes":0,
+ "downvotes":0,
+ "author":{
+ "id":1755902,
+ "name":"Luke Kysow",
+ "username":"lkysow",
+ "state":"active",
+ "avatar_url":"https://secure.gravatar.com/avatar/25fd57e71590fe28736624ff24d41c5f?s=80\u0026d=identicon",
+ "web_url":"https://gitlab.com/lkysow"
+ },
+ "assignee":null,
+ "source_project_id":4580910,
+ "target_project_id":4580910,
+ "labels":[
+
+ ],
+ "work_in_progress":false,
+ "milestone":null,
+ "merge_when_pipeline_succeeds":false,
+ "merge_status":"can_be_merged",
+ "sha":"0b4ac85ea3063ad5f2974d10cd68dd1f937aaac2",
+ "merge_commit_sha":null,
+ "user_notes_count":10,
+ "approvals_before_merge":null,
+ "discussion_locked":null,
+ "should_remove_source_branch":null,
+ "force_remove_source_branch":false,
+ "squash":false,
+ "web_url":"https://gitlab.com/lkysow/atlantis-example/merge_requests/8",
+ "time_stats":{
+ "time_estimate":0,
+ "total_time_spent":0,
+ "human_time_estimate":null,
+ "human_total_time_spent":null
+ }
+}`
diff --git a/server/events/github_status.go b/server/events/github_status.go
deleted file mode 100644
index 4159b4c20e..0000000000
--- a/server/events/github_status.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package events
-
-import (
- "fmt"
-
- "strings"
-
- "github.com/hootsuite/atlantis/server/events/github"
- "github.com/hootsuite/atlantis/server/events/models"
-)
-
-type Status int
-
-const (
- statusContext = "Atlantis"
-)
-
-const (
- Pending Status = iota
- Success
- Failure
- Error
-)
-
-//go:generate pegomock generate --use-experimental-model-gen --package mocks -o mocks/mock_github_status.go GHStatusUpdater
-
-type GHStatusUpdater interface {
- Update(repo models.Repo, pull models.PullRequest, status Status, cmd *Command) error
- UpdateProjectResult(ctx *CommandContext, res CommandResponse) error
-}
-
-type GithubStatus struct {
- Client github.Client
-}
-
-func (s Status) String() string {
- switch s {
- case Pending:
- return "pending"
- case Success:
- return "success"
- case Failure:
- return "failure"
- case Error:
- return "error"
- }
- return "error"
-}
-
-func (g *GithubStatus) Update(repo models.Repo, pull models.PullRequest, status Status, cmd *Command) error {
- description := fmt.Sprintf("%s %s", strings.Title(cmd.Name.String()), strings.Title(status.String()))
- return g.Client.UpdateStatus(repo, pull, status.String(), description, statusContext)
-}
-
-func (g *GithubStatus) UpdateProjectResult(ctx *CommandContext, res CommandResponse) error {
- var status Status
- if res.Error != nil {
- status = Error
- } else if res.Failure != "" {
- status = Failure
- } else {
- var statuses []Status
- for _, p := range res.ProjectResults {
- statuses = append(statuses, p.Status())
- }
- status = g.worstStatus(statuses)
- }
- return g.Update(ctx.BaseRepo, ctx.Pull, status, ctx.Command)
-}
-
-func (g *GithubStatus) worstStatus(ss []Status) Status {
- worst := Success
- for _, s := range ss {
- if s > worst {
- worst = s
- }
- }
- return worst
-}
diff --git a/server/events/locking/mocks/mock_backend.go b/server/events/locking/mocks/mock_backend.go
index b54ef69ce9..4006813bcc 100644
--- a/server/events/locking/mocks/mock_backend.go
+++ b/server/events/locking/mocks/mock_backend.go
@@ -4,9 +4,10 @@
package mocks
import (
+ "reflect"
+
models "github.com/hootsuite/atlantis/server/events/models"
pegomock "github.com/petergtz/pegomock"
- "reflect"
)
type MockBackend struct {
diff --git a/server/events/locking/mocks/mock_locker.go b/server/events/locking/mocks/mock_locker.go
index 7e64806675..02dfc26cfa 100644
--- a/server/events/locking/mocks/mock_locker.go
+++ b/server/events/locking/mocks/mock_locker.go
@@ -4,10 +4,11 @@
package mocks
import (
+ "reflect"
+
locking "github.com/hootsuite/atlantis/server/events/locking"
models "github.com/hootsuite/atlantis/server/events/models"
pegomock "github.com/petergtz/pegomock"
- "reflect"
)
type MockLocker struct {
diff --git a/server/events/github_comment_renderer.go b/server/events/markdown_renderer.go
similarity index 90%
rename from server/events/github_comment_renderer.go
rename to server/events/markdown_renderer.go
index 0f13f256b0..2e854cdab4 100644
--- a/server/events/github_comment_renderer.go
+++ b/server/events/markdown_renderer.go
@@ -63,8 +63,8 @@ var failureTmpl = template.Must(template.New("").Parse(failureTmplText))
var failureWithLogTmpl = template.Must(template.New("").Parse(failureTmplText + logTmpl))
var logTmpl = "{{if .Verbose}}\nLog
\n \n\n```\n{{.Log}}```\n
{{end}}\n"
-// GithubCommentRenderer renders responses as GitHub comments
-type GithubCommentRenderer struct{}
+// MarkdownRenderer renders responses as markdown
+type MarkdownRenderer struct{}
type CommonData struct {
Command string
@@ -89,7 +89,7 @@ type ResultData struct {
// Render formats the data into a string that can be commented back to GitHub.
// nolint: interfacer
-func (g *GithubCommentRenderer) Render(res CommandResponse, cmdName CommandName, log string, verbose bool) string {
+func (g *MarkdownRenderer) Render(res CommandResponse, cmdName CommandName, log string, verbose bool) string {
if cmdName == Help {
return g.renderTemplate(helpTmpl, nil)
}
@@ -104,7 +104,7 @@ func (g *GithubCommentRenderer) Render(res CommandResponse, cmdName CommandName,
return g.renderProjectResults(res.ProjectResults, common)
}
-func (g *GithubCommentRenderer) renderProjectResults(pathResults []ProjectResult, common CommonData) string {
+func (g *MarkdownRenderer) renderProjectResults(pathResults []ProjectResult, common CommonData) string {
results := make(map[string]string)
for _, result := range pathResults {
if result.Error != nil {
@@ -141,7 +141,7 @@ func (g *GithubCommentRenderer) renderProjectResults(pathResults []ProjectResult
return g.renderTemplate(tmpl, ResultData{results, common})
}
-func (g *GithubCommentRenderer) renderTemplate(tmpl *template.Template, data interface{}) string {
+func (g *MarkdownRenderer) renderTemplate(tmpl *template.Template, data interface{}) string {
buf := &bytes.Buffer{}
if err := tmpl.Execute(buf, data); err != nil {
return fmt.Sprintf("Failed to render template, this is a bug: %v", err)
diff --git a/server/events/github_comment_renderer_test.go b/server/events/markdown_renderer_test.go
similarity index 97%
rename from server/events/github_comment_renderer_test.go
rename to server/events/markdown_renderer_test.go
index cd74469e59..c4a8e674e3 100644
--- a/server/events/github_comment_renderer_test.go
+++ b/server/events/markdown_renderer_test.go
@@ -30,7 +30,7 @@ func TestRenderErr(t *testing.T) {
},
}
- r := events.GithubCommentRenderer{}
+ r := events.MarkdownRenderer{}
for _, c := range cases {
res := events.CommandResponse{
Error: c.Error,
@@ -68,7 +68,7 @@ func TestRenderFailure(t *testing.T) {
},
}
- r := events.GithubCommentRenderer{}
+ r := events.MarkdownRenderer{}
for _, c := range cases {
res := events.CommandResponse{
Failure: c.Failure,
@@ -87,7 +87,7 @@ func TestRenderFailure(t *testing.T) {
func TestRenderErrAndFailure(t *testing.T) {
t.Log("if there is an error and a failure, the error should be printed")
- r := events.GithubCommentRenderer{}
+ r := events.MarkdownRenderer{}
res := events.CommandResponse{
Error: errors.New("error"),
Failure: "failure",
@@ -225,7 +225,7 @@ func TestRenderProjectResults(t *testing.T) {
},
}
- r := events.GithubCommentRenderer{}
+ r := events.MarkdownRenderer{}
for _, c := range cases {
res := events.CommandResponse{
ProjectResults: c.ProjectResults,
diff --git a/server/events/mocks/mock_command_runner.go b/server/events/mocks/mock_command_runner.go
index 8df1d35c43..92223daadb 100644
--- a/server/events/mocks/mock_command_runner.go
+++ b/server/events/mocks/mock_command_runner.go
@@ -4,9 +4,12 @@
package mocks
import (
+ "reflect"
+
events "github.com/hootsuite/atlantis/server/events"
+ models "github.com/hootsuite/atlantis/server/events/models"
+ vcs "github.com/hootsuite/atlantis/server/events/vcs"
pegomock "github.com/petergtz/pegomock"
- "reflect"
)
type MockCommandRunner struct {
@@ -17,8 +20,8 @@ func NewMockCommandRunner() *MockCommandRunner {
return &MockCommandRunner{fail: pegomock.GlobalFailHandler}
}
-func (mock *MockCommandRunner) ExecuteCommand(ctx *events.CommandContext) {
- params := []pegomock.Param{ctx}
+func (mock *MockCommandRunner) ExecuteCommand(baseRepo models.Repo, headRepo models.Repo, user models.User, pullNum int, cmd *events.Command, vcsHost vcs.Host) {
+ params := []pegomock.Param{baseRepo, headRepo, user, pullNum, cmd, vcsHost}
pegomock.GetGenericMockFrom(mock).Invoke("ExecuteCommand", params, []reflect.Type{})
}
@@ -40,8 +43,8 @@ type VerifierCommandRunner struct {
inOrderContext *pegomock.InOrderContext
}
-func (verifier *VerifierCommandRunner) ExecuteCommand(ctx *events.CommandContext) *CommandRunner_ExecuteCommand_OngoingVerification {
- params := []pegomock.Param{ctx}
+func (verifier *VerifierCommandRunner) ExecuteCommand(baseRepo models.Repo, headRepo models.Repo, user models.User, pullNum int, cmd *events.Command, vcsHost vcs.Host) *CommandRunner_ExecuteCommand_OngoingVerification {
+ params := []pegomock.Param{baseRepo, headRepo, user, pullNum, cmd, vcsHost}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ExecuteCommand", params)
return &CommandRunner_ExecuteCommand_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
@@ -51,17 +54,37 @@ type CommandRunner_ExecuteCommand_OngoingVerification struct {
methodInvocations []pegomock.MethodInvocation
}
-func (c *CommandRunner_ExecuteCommand_OngoingVerification) GetCapturedArguments() *events.CommandContext {
- ctx := c.GetAllCapturedArguments()
- return ctx[len(ctx)-1]
+func (c *CommandRunner_ExecuteCommand_OngoingVerification) GetCapturedArguments() (models.Repo, models.Repo, models.User, int, *events.Command, vcs.Host) {
+ baseRepo, headRepo, user, pullNum, cmd, vcsHost := c.GetAllCapturedArguments()
+ return baseRepo[len(baseRepo)-1], headRepo[len(headRepo)-1], user[len(user)-1], pullNum[len(pullNum)-1], cmd[len(cmd)-1], vcsHost[len(vcsHost)-1]
}
-func (c *CommandRunner_ExecuteCommand_OngoingVerification) GetAllCapturedArguments() (_param0 []*events.CommandContext) {
+func (c *CommandRunner_ExecuteCommand_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.Repo, _param2 []models.User, _param3 []int, _param4 []*events.Command, _param5 []vcs.Host) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
- _param0 = make([]*events.CommandContext, len(params[0]))
+ _param0 = make([]models.Repo, len(params[0]))
for u, param := range params[0] {
- _param0[u] = param.(*events.CommandContext)
+ _param0[u] = param.(models.Repo)
+ }
+ _param1 = make([]models.Repo, len(params[1]))
+ for u, param := range params[1] {
+ _param1[u] = param.(models.Repo)
+ }
+ _param2 = make([]models.User, len(params[2]))
+ for u, param := range params[2] {
+ _param2[u] = param.(models.User)
+ }
+ _param3 = make([]int, len(params[3]))
+ for u, param := range params[3] {
+ _param3[u] = param.(int)
+ }
+ _param4 = make([]*events.Command, len(params[4]))
+ for u, param := range params[4] {
+ _param4[u] = param.(*events.Command)
+ }
+ _param5 = make([]vcs.Host, len(params[5]))
+ for u, param := range params[5] {
+ _param5[u] = param.(vcs.Host)
}
}
return
diff --git a/server/events/mocks/mock_commit_status_updater.go b/server/events/mocks/mock_commit_status_updater.go
new file mode 100644
index 0000000000..91b4205f3c
--- /dev/null
+++ b/server/events/mocks/mock_commit_status_updater.go
@@ -0,0 +1,136 @@
+// Automatically generated by pegomock. DO NOT EDIT!
+// Source: github.com/hootsuite/atlantis/server/events (interfaces: CommitStatusUpdater)
+
+package mocks
+
+import (
+ events "github.com/hootsuite/atlantis/server/events"
+ models "github.com/hootsuite/atlantis/server/events/models"
+ vcs "github.com/hootsuite/atlantis/server/events/vcs"
+ pegomock "github.com/petergtz/pegomock"
+ "reflect"
+)
+
+type MockCommitStatusUpdater struct {
+ fail func(message string, callerSkip ...int)
+}
+
+func NewMockCommitStatusUpdater() *MockCommitStatusUpdater {
+ return &MockCommitStatusUpdater{fail: pegomock.GlobalFailHandler}
+}
+
+func (mock *MockCommitStatusUpdater) Update(repo models.Repo, pull models.PullRequest, status vcs.CommitStatus, cmd *events.Command, host vcs.Host) error {
+ params := []pegomock.Param{repo, pull, status, cmd, host}
+ result := pegomock.GetGenericMockFrom(mock).Invoke("Update", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
+ var ret0 error
+ if len(result) != 0 {
+ if result[0] != nil {
+ ret0 = result[0].(error)
+ }
+ }
+ return ret0
+}
+
+func (mock *MockCommitStatusUpdater) UpdateProjectResult(ctx *events.CommandContext, res events.CommandResponse) error {
+ params := []pegomock.Param{ctx, res}
+ result := pegomock.GetGenericMockFrom(mock).Invoke("UpdateProjectResult", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
+ var ret0 error
+ if len(result) != 0 {
+ if result[0] != nil {
+ ret0 = result[0].(error)
+ }
+ }
+ return ret0
+}
+
+func (mock *MockCommitStatusUpdater) VerifyWasCalledOnce() *VerifierCommitStatusUpdater {
+ return &VerifierCommitStatusUpdater{mock, pegomock.Times(1), nil}
+}
+
+func (mock *MockCommitStatusUpdater) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierCommitStatusUpdater {
+ return &VerifierCommitStatusUpdater{mock, invocationCountMatcher, nil}
+}
+
+func (mock *MockCommitStatusUpdater) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierCommitStatusUpdater {
+ return &VerifierCommitStatusUpdater{mock, invocationCountMatcher, inOrderContext}
+}
+
+type VerifierCommitStatusUpdater struct {
+ mock *MockCommitStatusUpdater
+ invocationCountMatcher pegomock.Matcher
+ inOrderContext *pegomock.InOrderContext
+}
+
+func (verifier *VerifierCommitStatusUpdater) Update(repo models.Repo, pull models.PullRequest, status vcs.CommitStatus, cmd *events.Command, host vcs.Host) *CommitStatusUpdater_Update_OngoingVerification {
+ params := []pegomock.Param{repo, pull, status, cmd, host}
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Update", params)
+ return &CommitStatusUpdater_Update_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+}
+
+type CommitStatusUpdater_Update_OngoingVerification struct {
+ mock *MockCommitStatusUpdater
+ methodInvocations []pegomock.MethodInvocation
+}
+
+func (c *CommitStatusUpdater_Update_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest, vcs.CommitStatus, *events.Command, vcs.Host) {
+ repo, pull, status, cmd, host := c.GetAllCapturedArguments()
+ return repo[len(repo)-1], pull[len(pull)-1], status[len(status)-1], cmd[len(cmd)-1], host[len(host)-1]
+}
+
+func (c *CommitStatusUpdater_Update_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest, _param2 []vcs.CommitStatus, _param3 []*events.Command, _param4 []vcs.Host) {
+ params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
+ if len(params) > 0 {
+ _param0 = make([]models.Repo, len(params[0]))
+ for u, param := range params[0] {
+ _param0[u] = param.(models.Repo)
+ }
+ _param1 = make([]models.PullRequest, len(params[1]))
+ for u, param := range params[1] {
+ _param1[u] = param.(models.PullRequest)
+ }
+ _param2 = make([]vcs.CommitStatus, len(params[2]))
+ for u, param := range params[2] {
+ _param2[u] = param.(vcs.CommitStatus)
+ }
+ _param3 = make([]*events.Command, len(params[3]))
+ for u, param := range params[3] {
+ _param3[u] = param.(*events.Command)
+ }
+ _param4 = make([]vcs.Host, len(params[4]))
+ for u, param := range params[4] {
+ _param4[u] = param.(vcs.Host)
+ }
+ }
+ return
+}
+
+func (verifier *VerifierCommitStatusUpdater) UpdateProjectResult(ctx *events.CommandContext, res events.CommandResponse) *CommitStatusUpdater_UpdateProjectResult_OngoingVerification {
+ params := []pegomock.Param{ctx, res}
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "UpdateProjectResult", params)
+ return &CommitStatusUpdater_UpdateProjectResult_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+}
+
+type CommitStatusUpdater_UpdateProjectResult_OngoingVerification struct {
+ mock *MockCommitStatusUpdater
+ methodInvocations []pegomock.MethodInvocation
+}
+
+func (c *CommitStatusUpdater_UpdateProjectResult_OngoingVerification) GetCapturedArguments() (*events.CommandContext, events.CommandResponse) {
+ ctx, res := c.GetAllCapturedArguments()
+ return ctx[len(ctx)-1], res[len(res)-1]
+}
+
+func (c *CommitStatusUpdater_UpdateProjectResult_OngoingVerification) GetAllCapturedArguments() (_param0 []*events.CommandContext, _param1 []events.CommandResponse) {
+ params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
+ if len(params) > 0 {
+ _param0 = make([]*events.CommandContext, len(params[0]))
+ for u, param := range params[0] {
+ _param0[u] = param.(*events.CommandContext)
+ }
+ _param1 = make([]events.CommandResponse, len(params[1]))
+ for u, param := range params[1] {
+ _param1[u] = param.(events.CommandResponse)
+ }
+ }
+ return
+}
diff --git a/server/events/mocks/mock_env_locker.go b/server/events/mocks/mock_env_locker.go
index 5687f10a8c..e3b7929aca 100644
--- a/server/events/mocks/mock_env_locker.go
+++ b/server/events/mocks/mock_env_locker.go
@@ -4,8 +4,9 @@
package mocks
import (
- pegomock "github.com/petergtz/pegomock"
"reflect"
+
+ pegomock "github.com/petergtz/pegomock"
)
type MockEnvLocker struct {
diff --git a/server/events/mocks/mock_event_parsing.go b/server/events/mocks/mock_event_parsing.go
index 37de199e2c..e8ddc264fe 100644
--- a/server/events/mocks/mock_event_parsing.go
+++ b/server/events/mocks/mock_event_parsing.go
@@ -4,11 +4,14 @@
package mocks
import (
+ "reflect"
+
github "github.com/google/go-github/github"
events "github.com/hootsuite/atlantis/server/events"
models "github.com/hootsuite/atlantis/server/events/models"
+ vcs "github.com/hootsuite/atlantis/server/events/vcs"
+ go_gitlab "github.com/lkysow/go-gitlab"
pegomock "github.com/petergtz/pegomock"
- "reflect"
)
type MockEventParsing struct {
@@ -19,8 +22,8 @@ func NewMockEventParsing() *MockEventParsing {
return &MockEventParsing{fail: pegomock.GlobalFailHandler}
}
-func (mock *MockEventParsing) DetermineCommand(comment *github.IssueCommentEvent) (*events.Command, error) {
- params := []pegomock.Param{comment}
+func (mock *MockEventParsing) DetermineCommand(comment string, vcsHost vcs.Host) (*events.Command, error) {
+ params := []pegomock.Param{comment, vcsHost}
result := pegomock.GetGenericMockFrom(mock).Invoke("DetermineCommand", params, []reflect.Type{reflect.TypeOf((**events.Command)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
var ret0 *events.Command
var ret1 error
@@ -35,12 +38,12 @@ func (mock *MockEventParsing) DetermineCommand(comment *github.IssueCommentEvent
return ret0, ret1
}
-func (mock *MockEventParsing) ExtractCommentData(comment *github.IssueCommentEvent) (models.Repo, models.User, models.PullRequest, error) {
+func (mock *MockEventParsing) ParseGithubIssueCommentEvent(comment *github.IssueCommentEvent) (models.Repo, models.User, int, error) {
params := []pegomock.Param{comment}
- result := pegomock.GetGenericMockFrom(mock).Invoke("ExtractCommentData", params, []reflect.Type{reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*models.User)(nil)).Elem(), reflect.TypeOf((*models.PullRequest)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
+ result := pegomock.GetGenericMockFrom(mock).Invoke("ParseGithubIssueCommentEvent", params, []reflect.Type{reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*models.User)(nil)).Elem(), reflect.TypeOf((*int)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
var ret0 models.Repo
var ret1 models.User
- var ret2 models.PullRequest
+ var ret2 int
var ret3 error
if len(result) != 0 {
if result[0] != nil {
@@ -50,7 +53,7 @@ func (mock *MockEventParsing) ExtractCommentData(comment *github.IssueCommentEve
ret1 = result[1].(models.User)
}
if result[2] != nil {
- ret2 = result[2].(models.PullRequest)
+ ret2 = result[2].(int)
}
if result[3] != nil {
ret3 = result[3].(error)
@@ -59,9 +62,9 @@ func (mock *MockEventParsing) ExtractCommentData(comment *github.IssueCommentEve
return ret0, ret1, ret2, ret3
}
-func (mock *MockEventParsing) ExtractPullData(pull *github.PullRequest) (models.PullRequest, models.Repo, error) {
+func (mock *MockEventParsing) ParseGithubPull(pull *github.PullRequest) (models.PullRequest, models.Repo, error) {
params := []pegomock.Param{pull}
- result := pegomock.GetGenericMockFrom(mock).Invoke("ExtractPullData", params, []reflect.Type{reflect.TypeOf((*models.PullRequest)(nil)).Elem(), reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
+ result := pegomock.GetGenericMockFrom(mock).Invoke("ParseGithubPull", params, []reflect.Type{reflect.TypeOf((*models.PullRequest)(nil)).Elem(), reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
var ret0 models.PullRequest
var ret1 models.Repo
var ret2 error
@@ -79,9 +82,9 @@ func (mock *MockEventParsing) ExtractPullData(pull *github.PullRequest) (models.
return ret0, ret1, ret2
}
-func (mock *MockEventParsing) ExtractRepoData(ghRepo *github.Repository) (models.Repo, error) {
+func (mock *MockEventParsing) ParseGithubRepo(ghRepo *github.Repository) (models.Repo, error) {
params := []pegomock.Param{ghRepo}
- result := pegomock.GetGenericMockFrom(mock).Invoke("ExtractRepoData", params, []reflect.Type{reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
+ result := pegomock.GetGenericMockFrom(mock).Invoke("ParseGithubRepo", params, []reflect.Type{reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
var ret0 models.Repo
var ret1 error
if len(result) != 0 {
@@ -95,6 +98,54 @@ func (mock *MockEventParsing) ExtractRepoData(ghRepo *github.Repository) (models
return ret0, ret1
}
+func (mock *MockEventParsing) ParseGitlabMergeEvent(event go_gitlab.MergeEvent) (models.PullRequest, models.Repo) {
+ params := []pegomock.Param{event}
+ result := pegomock.GetGenericMockFrom(mock).Invoke("ParseGitlabMergeEvent", params, []reflect.Type{reflect.TypeOf((*models.PullRequest)(nil)).Elem(), reflect.TypeOf((*models.Repo)(nil)).Elem()})
+ var ret0 models.PullRequest
+ var ret1 models.Repo
+ if len(result) != 0 {
+ if result[0] != nil {
+ ret0 = result[0].(models.PullRequest)
+ }
+ if result[1] != nil {
+ ret1 = result[1].(models.Repo)
+ }
+ }
+ return ret0, ret1
+}
+
+func (mock *MockEventParsing) ParseGitlabMergeCommentEvent(event go_gitlab.MergeCommentEvent) (models.Repo, models.Repo, models.User) {
+ params := []pegomock.Param{event}
+ result := pegomock.GetGenericMockFrom(mock).Invoke("ParseGitlabMergeCommentEvent", params, []reflect.Type{reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*models.User)(nil)).Elem()})
+ var ret0 models.Repo
+ var ret1 models.Repo
+ var ret2 models.User
+ if len(result) != 0 {
+ if result[0] != nil {
+ ret0 = result[0].(models.Repo)
+ }
+ if result[1] != nil {
+ ret1 = result[1].(models.Repo)
+ }
+ if result[2] != nil {
+ ret2 = result[2].(models.User)
+ }
+ }
+ return ret0, ret1, ret2
+}
+
+func (mock *MockEventParsing) ParseGitlabMergeRequest(mr *go_gitlab.MergeRequest) models.PullRequest {
+ params := []pegomock.Param{mr}
+ result := pegomock.GetGenericMockFrom(mock).Invoke("ParseGitlabMergeRequest", params, []reflect.Type{reflect.TypeOf((*models.PullRequest)(nil)).Elem()})
+ var ret0 models.PullRequest
+ if len(result) != 0 {
+ if result[0] != nil {
+ ret0 = result[0].(models.PullRequest)
+ }
+ }
+ return ret0
+}
+
func (mock *MockEventParsing) VerifyWasCalledOnce() *VerifierEventParsing {
return &VerifierEventParsing{mock, pegomock.Times(1), nil}
}
@@ -113,8 +164,8 @@ type VerifierEventParsing struct {
inOrderContext *pegomock.InOrderContext
}
-func (verifier *VerifierEventParsing) DetermineCommand(comment *github.IssueCommentEvent) *EventParsing_DetermineCommand_OngoingVerification {
- params := []pegomock.Param{comment}
+func (verifier *VerifierEventParsing) DetermineCommand(comment string, vcsHost vcs.Host) *EventParsing_DetermineCommand_OngoingVerification {
+ params := []pegomock.Param{comment, vcsHost}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "DetermineCommand", params)
return &EventParsing_DetermineCommand_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
@@ -124,39 +175,43 @@ type EventParsing_DetermineCommand_OngoingVerification struct {
methodInvocations []pegomock.MethodInvocation
}
-func (c *EventParsing_DetermineCommand_OngoingVerification) GetCapturedArguments() *github.IssueCommentEvent {
- comment := c.GetAllCapturedArguments()
- return comment[len(comment)-1]
+func (c *EventParsing_DetermineCommand_OngoingVerification) GetCapturedArguments() (string, vcs.Host) {
+ comment, vcsHost := c.GetAllCapturedArguments()
+ return comment[len(comment)-1], vcsHost[len(vcsHost)-1]
}
-func (c *EventParsing_DetermineCommand_OngoingVerification) GetAllCapturedArguments() (_param0 []*github.IssueCommentEvent) {
+func (c *EventParsing_DetermineCommand_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 []vcs.Host) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
- _param0 = make([]*github.IssueCommentEvent, len(params[0]))
+ _param0 = make([]string, len(params[0]))
for u, param := range params[0] {
- _param0[u] = param.(*github.IssueCommentEvent)
+ _param0[u] = param.(string)
+ }
+ _param1 = make([]vcs.Host, len(params[1]))
+ for u, param := range params[1] {
+ _param1[u] = param.(vcs.Host)
}
}
return
}
-func (verifier *VerifierEventParsing) ExtractCommentData(comment *github.IssueCommentEvent) *EventParsing_ExtractCommentData_OngoingVerification {
+func (verifier *VerifierEventParsing) ParseGithubIssueCommentEvent(comment *github.IssueCommentEvent) *EventParsing_ParseGithubIssueCommentEvent_OngoingVerification {
params := []pegomock.Param{comment}
- methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ExtractCommentData", params)
- return &EventParsing_ExtractCommentData_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ParseGithubIssueCommentEvent", params)
+ return &EventParsing_ParseGithubIssueCommentEvent_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
-type EventParsing_ExtractCommentData_OngoingVerification struct {
+type EventParsing_ParseGithubIssueCommentEvent_OngoingVerification struct {
mock *MockEventParsing
methodInvocations []pegomock.MethodInvocation
}
-func (c *EventParsing_ExtractCommentData_OngoingVerification) GetCapturedArguments() *github.IssueCommentEvent {
+func (c *EventParsing_ParseGithubIssueCommentEvent_OngoingVerification) GetCapturedArguments() *github.IssueCommentEvent {
comment := c.GetAllCapturedArguments()
return comment[len(comment)-1]
}
-func (c *EventParsing_ExtractCommentData_OngoingVerification) GetAllCapturedArguments() (_param0 []*github.IssueCommentEvent) {
+func (c *EventParsing_ParseGithubIssueCommentEvent_OngoingVerification) GetAllCapturedArguments() (_param0 []*github.IssueCommentEvent) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([]*github.IssueCommentEvent, len(params[0]))
@@ -167,23 +222,23 @@ func (c *EventParsing_ExtractCommentData_OngoingVerification) GetAllCapturedArgu
return
}
-func (verifier *VerifierEventParsing) ExtractPullData(pull *github.PullRequest) *EventParsing_ExtractPullData_OngoingVerification {
+func (verifier *VerifierEventParsing) ParseGithubPull(pull *github.PullRequest) *EventParsing_ParseGithubPull_OngoingVerification {
params := []pegomock.Param{pull}
- methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ExtractPullData", params)
- return &EventParsing_ExtractPullData_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ParseGithubPull", params)
+ return &EventParsing_ParseGithubPull_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
-type EventParsing_ExtractPullData_OngoingVerification struct {
+type EventParsing_ParseGithubPull_OngoingVerification struct {
mock *MockEventParsing
methodInvocations []pegomock.MethodInvocation
}
-func (c *EventParsing_ExtractPullData_OngoingVerification) GetCapturedArguments() *github.PullRequest {
+func (c *EventParsing_ParseGithubPull_OngoingVerification) GetCapturedArguments() *github.PullRequest {
pull := c.GetAllCapturedArguments()
return pull[len(pull)-1]
}
-func (c *EventParsing_ExtractPullData_OngoingVerification) GetAllCapturedArguments() (_param0 []*github.PullRequest) {
+func (c *EventParsing_ParseGithubPull_OngoingVerification) GetAllCapturedArguments() (_param0 []*github.PullRequest) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([]*github.PullRequest, len(params[0]))
@@ -194,23 +249,23 @@ func (c *EventParsing_ExtractPullData_OngoingVerification) GetAllCapturedArgumen
return
}
-func (verifier *VerifierEventParsing) ExtractRepoData(ghRepo *github.Repository) *EventParsing_ExtractRepoData_OngoingVerification {
+func (verifier *VerifierEventParsing) ParseGithubRepo(ghRepo *github.Repository) *EventParsing_ParseGithubRepo_OngoingVerification {
params := []pegomock.Param{ghRepo}
- methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ExtractRepoData", params)
- return &EventParsing_ExtractRepoData_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ParseGithubRepo", params)
+ return &EventParsing_ParseGithubRepo_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
-type EventParsing_ExtractRepoData_OngoingVerification struct {
+type EventParsing_ParseGithubRepo_OngoingVerification struct {
mock *MockEventParsing
methodInvocations []pegomock.MethodInvocation
}
-func (c *EventParsing_ExtractRepoData_OngoingVerification) GetCapturedArguments() *github.Repository {
+func (c *EventParsing_ParseGithubRepo_OngoingVerification) GetCapturedArguments() *github.Repository {
ghRepo := c.GetAllCapturedArguments()
return ghRepo[len(ghRepo)-1]
}
-func (c *EventParsing_ExtractRepoData_OngoingVerification) GetAllCapturedArguments() (_param0 []*github.Repository) {
+func (c *EventParsing_ParseGithubRepo_OngoingVerification) GetAllCapturedArguments() (_param0 []*github.Repository) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([]*github.Repository, len(params[0]))
@@ -220,3 +275,84 @@ func (c *EventParsing_ExtractRepoData_OngoingVerification) GetAllCapturedArgumen
}
return
}
+
+func (verifier *VerifierEventParsing) ParseGitlabMergeEvent(event go_gitlab.MergeEvent) *EventParsing_ParseGitlabMergeEvent_OngoingVerification {
+ params := []pegomock.Param{event}
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ParseGitlabMergeEvent", params)
+ return &EventParsing_ParseGitlabMergeEvent_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+}
+
+type EventParsing_ParseGitlabMergeEvent_OngoingVerification struct {
+ mock *MockEventParsing
+ methodInvocations []pegomock.MethodInvocation
+}
+
+func (c *EventParsing_ParseGitlabMergeEvent_OngoingVerification) GetCapturedArguments() go_gitlab.MergeEvent {
+ event := c.GetAllCapturedArguments()
+ return event[len(event)-1]
+}
+
+func (c *EventParsing_ParseGitlabMergeEvent_OngoingVerification) GetAllCapturedArguments() (_param0 []go_gitlab.MergeEvent) {
+ params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
+ if len(params) > 0 {
+ _param0 = make([]go_gitlab.MergeEvent, len(params[0]))
+ for u, param := range params[0] {
+ _param0[u] = param.(go_gitlab.MergeEvent)
+ }
+ }
+ return
+}
+
+func (verifier *VerifierEventParsing) ParseGitlabMergeCommentEvent(event go_gitlab.MergeCommentEvent) *EventParsing_ParseGitlabMergeCommentEvent_OngoingVerification {
+ params := []pegomock.Param{event}
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ParseGitlabMergeCommentEvent", params)
+ return &EventParsing_ParseGitlabMergeCommentEvent_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+}
+
+type EventParsing_ParseGitlabMergeCommentEvent_OngoingVerification struct {
+ mock *MockEventParsing
+ methodInvocations []pegomock.MethodInvocation
+}
+
+func (c *EventParsing_ParseGitlabMergeCommentEvent_OngoingVerification) GetCapturedArguments() go_gitlab.MergeCommentEvent {
+ event := c.GetAllCapturedArguments()
+ return event[len(event)-1]
+}
+
+func (c *EventParsing_ParseGitlabMergeCommentEvent_OngoingVerification) GetAllCapturedArguments() (_param0 []go_gitlab.MergeCommentEvent) {
+ params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
+ if len(params) > 0 {
+ _param0 = make([]go_gitlab.MergeCommentEvent, len(params[0]))
+ for u, param := range params[0] {
+ _param0[u] = param.(go_gitlab.MergeCommentEvent)
+ }
+ }
+ return
+}
+
+func (verifier *VerifierEventParsing) ParseGitlabMergeRequest(mr *go_gitlab.MergeRequest) *EventParsing_ParseGitlabMergeRequest_OngoingVerification {
+ params := []pegomock.Param{mr}
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ParseGitlabMergeRequest", params)
+ return &EventParsing_ParseGitlabMergeRequest_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+}
+
+type EventParsing_ParseGitlabMergeRequest_OngoingVerification struct {
+ mock *MockEventParsing
+ methodInvocations []pegomock.MethodInvocation
+}
+
+func (c *EventParsing_ParseGitlabMergeRequest_OngoingVerification) GetCapturedArguments() *go_gitlab.MergeRequest {
+ mr := c.GetAllCapturedArguments()
+ return mr[len(mr)-1]
+}
+
+func (c *EventParsing_ParseGitlabMergeRequest_OngoingVerification) GetAllCapturedArguments() (_param0 []*go_gitlab.MergeRequest) {
+ params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
+ if len(params) > 0 {
+ _param0 = make([]*go_gitlab.MergeRequest, len(params[0]))
+ for u, param := range params[0] {
+ _param0[u] = param.(*go_gitlab.MergeRequest)
+ }
+ }
+ return
+}
diff --git a/server/events/mocks/mock_executor.go b/server/events/mocks/mock_executor.go
index 89722bcb5f..140a813373 100644
--- a/server/events/mocks/mock_executor.go
+++ b/server/events/mocks/mock_executor.go
@@ -4,9 +4,10 @@
package mocks
import (
+ "reflect"
+
events "github.com/hootsuite/atlantis/server/events"
pegomock "github.com/petergtz/pegomock"
- "reflect"
)
type MockExecutor struct {
diff --git a/server/events/mocks/mock_github_pull_getter.go b/server/events/mocks/mock_github_pull_getter.go
new file mode 100644
index 0000000000..43f70ff8e2
--- /dev/null
+++ b/server/events/mocks/mock_github_pull_getter.go
@@ -0,0 +1,85 @@
+// Automatically generated by pegomock. DO NOT EDIT!
+// Source: github.com/hootsuite/atlantis/server/events (interfaces: GithubPullGetter)
+
+package mocks
+
+import (
+ "reflect"
+
+ github "github.com/google/go-github/github"
+ models "github.com/hootsuite/atlantis/server/events/models"
+ pegomock "github.com/petergtz/pegomock"
+)
+
+type MockGithubPullGetter struct {
+ fail func(message string, callerSkip ...int)
+}
+
+func NewMockGithubPullGetter() *MockGithubPullGetter {
+ return &MockGithubPullGetter{fail: pegomock.GlobalFailHandler}
+}
+
+func (mock *MockGithubPullGetter) GetPullRequest(repo models.Repo, pullNum int) (*github.PullRequest, error) {
+ params := []pegomock.Param{repo, pullNum}
+ result := pegomock.GetGenericMockFrom(mock).Invoke("GetPullRequest", params, []reflect.Type{reflect.TypeOf((**github.PullRequest)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
+ var ret0 *github.PullRequest
+ var ret1 error
+ if len(result) != 0 {
+ if result[0] != nil {
+ ret0 = result[0].(*github.PullRequest)
+ }
+ if result[1] != nil {
+ ret1 = result[1].(error)
+ }
+ }
+ return ret0, ret1
+}
+
+func (mock *MockGithubPullGetter) VerifyWasCalledOnce() *VerifierGithubPullGetter {
+ return &VerifierGithubPullGetter{mock, pegomock.Times(1), nil}
+}
+
+func (mock *MockGithubPullGetter) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierGithubPullGetter {
+ return &VerifierGithubPullGetter{mock, invocationCountMatcher, nil}
+}
+
+func (mock *MockGithubPullGetter) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierGithubPullGetter {
+ return &VerifierGithubPullGetter{mock, invocationCountMatcher, inOrderContext}
+}
+
+type VerifierGithubPullGetter struct {
+ mock *MockGithubPullGetter
+ invocationCountMatcher pegomock.Matcher
+ inOrderContext *pegomock.InOrderContext
+}
+
+func (verifier *VerifierGithubPullGetter) GetPullRequest(repo models.Repo, pullNum int) *GithubPullGetter_GetPullRequest_OngoingVerification {
+ params := []pegomock.Param{repo, pullNum}
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "GetPullRequest", params)
+ return &GithubPullGetter_GetPullRequest_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+}
+
+type GithubPullGetter_GetPullRequest_OngoingVerification struct {
+ mock *MockGithubPullGetter
+ methodInvocations []pegomock.MethodInvocation
+}
+
+func (c *GithubPullGetter_GetPullRequest_OngoingVerification) GetCapturedArguments() (models.Repo, int) {
+ repo, pullNum := c.GetAllCapturedArguments()
+ return repo[len(repo)-1], pullNum[len(pullNum)-1]
+}
+
+func (c *GithubPullGetter_GetPullRequest_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []int) {
+ params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
+ if len(params) > 0 {
+ _param0 = make([]models.Repo, len(params[0]))
+ for u, param := range params[0] {
+ _param0[u] = param.(models.Repo)
+ }
+ _param1 = make([]int, len(params[1]))
+ for u, param := range params[1] {
+ _param1[u] = param.(int)
+ }
+ }
+ return
+}
diff --git a/server/events/mocks/mock_github_status.go b/server/events/mocks/mock_github_status.go
deleted file mode 100644
index 78d7cf0184..0000000000
--- a/server/events/mocks/mock_github_status.go
+++ /dev/null
@@ -1,131 +0,0 @@
-// Automatically generated by pegomock. DO NOT EDIT!
-// Source: github.com/hootsuite/atlantis/server/events (interfaces: GHStatusUpdater)
-
-package mocks
-
-import (
- events "github.com/hootsuite/atlantis/server/events"
- models "github.com/hootsuite/atlantis/server/events/models"
- pegomock "github.com/petergtz/pegomock"
- "reflect"
-)
-
-type MockGHStatusUpdater struct {
- fail func(message string, callerSkip ...int)
-}
-
-func NewMockGHStatusUpdater() *MockGHStatusUpdater {
- return &MockGHStatusUpdater{fail: pegomock.GlobalFailHandler}
-}
-
-func (mock *MockGHStatusUpdater) Update(repo models.Repo, pull models.PullRequest, status events.Status, cmd *events.Command) error {
- params := []pegomock.Param{repo, pull, status, cmd}
- result := pegomock.GetGenericMockFrom(mock).Invoke("Update", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
- var ret0 error
- if len(result) != 0 {
- if result[0] != nil {
- ret0 = result[0].(error)
- }
- }
- return ret0
-}
-
-func (mock *MockGHStatusUpdater) UpdateProjectResult(ctx *events.CommandContext, res events.CommandResponse) error {
- params := []pegomock.Param{ctx, res}
- result := pegomock.GetGenericMockFrom(mock).Invoke("UpdateProjectResult", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
- var ret0 error
- if len(result) != 0 {
- if result[0] != nil {
- ret0 = result[0].(error)
- }
- }
- return ret0
-}
-
-func (mock *MockGHStatusUpdater) VerifyWasCalledOnce() *VerifierGHStatusUpdater {
- return &VerifierGHStatusUpdater{mock, pegomock.Times(1), nil}
-}
-
-func (mock *MockGHStatusUpdater) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierGHStatusUpdater {
- return &VerifierGHStatusUpdater{mock, invocationCountMatcher, nil}
-}
-
-func (mock *MockGHStatusUpdater) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierGHStatusUpdater {
- return &VerifierGHStatusUpdater{mock, invocationCountMatcher, inOrderContext}
-}
-
-type VerifierGHStatusUpdater struct {
- mock *MockGHStatusUpdater
- invocationCountMatcher pegomock.Matcher
- inOrderContext *pegomock.InOrderContext
-}
-
-func (verifier *VerifierGHStatusUpdater) Update(repo models.Repo, pull models.PullRequest, status events.Status, cmd *events.Command) *GHStatusUpdater_Update_OngoingVerification {
- params := []pegomock.Param{repo, pull, status, cmd}
- methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Update", params)
- return &GHStatusUpdater_Update_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
-}
-
-type GHStatusUpdater_Update_OngoingVerification struct {
- mock *MockGHStatusUpdater
- methodInvocations []pegomock.MethodInvocation
-}
-
-func (c *GHStatusUpdater_Update_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest, events.Status, *events.Command) {
- repo, pull, status, cmd := c.GetAllCapturedArguments()
- return repo[len(repo)-1], pull[len(pull)-1], status[len(status)-1], cmd[len(cmd)-1]
-}
-
-func (c *GHStatusUpdater_Update_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest, _param2 []events.Status, _param3 []*events.Command) {
- params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
- if len(params) > 0 {
- _param0 = make([]models.Repo, len(params[0]))
- for u, param := range params[0] {
- _param0[u] = param.(models.Repo)
- }
- _param1 = make([]models.PullRequest, len(params[1]))
- for u, param := range params[1] {
- _param1[u] = param.(models.PullRequest)
- }
- _param2 = make([]events.Status, len(params[2]))
- for u, param := range params[2] {
- _param2[u] = param.(events.Status)
- }
- _param3 = make([]*events.Command, len(params[3]))
- for u, param := range params[3] {
- _param3[u] = param.(*events.Command)
- }
- }
- return
-}
-
-func (verifier *VerifierGHStatusUpdater) UpdateProjectResult(ctx *events.CommandContext, res events.CommandResponse) *GHStatusUpdater_UpdateProjectResult_OngoingVerification {
- params := []pegomock.Param{ctx, res}
- methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "UpdateProjectResult", params)
- return &GHStatusUpdater_UpdateProjectResult_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
-}
-
-type GHStatusUpdater_UpdateProjectResult_OngoingVerification struct {
- mock *MockGHStatusUpdater
- methodInvocations []pegomock.MethodInvocation
-}
-
-func (c *GHStatusUpdater_UpdateProjectResult_OngoingVerification) GetCapturedArguments() (*events.CommandContext, events.CommandResponse) {
- ctx, res := c.GetAllCapturedArguments()
- return ctx[len(ctx)-1], res[len(res)-1]
-}
-
-func (c *GHStatusUpdater_UpdateProjectResult_OngoingVerification) GetAllCapturedArguments() (_param0 []*events.CommandContext, _param1 []events.CommandResponse) {
- params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
- if len(params) > 0 {
- _param0 = make([]*events.CommandContext, len(params[0]))
- for u, param := range params[0] {
- _param0[u] = param.(*events.CommandContext)
- }
- _param1 = make([]events.CommandResponse, len(params[1]))
- for u, param := range params[1] {
- _param1[u] = param.(events.CommandResponse)
- }
- }
- return
-}
diff --git a/server/events/mocks/mock_gitlab_merge_request_getter.go b/server/events/mocks/mock_gitlab_merge_request_getter.go
new file mode 100644
index 0000000000..afdbaf14af
--- /dev/null
+++ b/server/events/mocks/mock_gitlab_merge_request_getter.go
@@ -0,0 +1,84 @@
+// Automatically generated by pegomock. DO NOT EDIT!
+// Source: github.com/hootsuite/atlantis/server/events (interfaces: GitlabMergeRequestGetter)
+
+package mocks
+
+import (
+ "reflect"
+
+ go_gitlab "github.com/lkysow/go-gitlab"
+ pegomock "github.com/petergtz/pegomock"
+)
+
+type MockGitlabMergeRequestGetter struct {
+ fail func(message string, callerSkip ...int)
+}
+
+func NewMockGitlabMergeRequestGetter() *MockGitlabMergeRequestGetter {
+ return &MockGitlabMergeRequestGetter{fail: pegomock.GlobalFailHandler}
+}
+
+func (mock *MockGitlabMergeRequestGetter) GetMergeRequest(repoFullName string, pullNum int) (*go_gitlab.MergeRequest, error) {
+ params := []pegomock.Param{repoFullName, pullNum}
+ result := pegomock.GetGenericMockFrom(mock).Invoke("GetMergeRequest", params, []reflect.Type{reflect.TypeOf((**go_gitlab.MergeRequest)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
+ var ret0 *go_gitlab.MergeRequest
+ var ret1 error
+ if len(result) != 0 {
+ if result[0] != nil {
+ ret0 = result[0].(*go_gitlab.MergeRequest)
+ }
+ if result[1] != nil {
+ ret1 = result[1].(error)
+ }
+ }
+ return ret0, ret1
+}
+
+func (mock *MockGitlabMergeRequestGetter) VerifyWasCalledOnce() *VerifierGitlabMergeRequestGetter {
+ return &VerifierGitlabMergeRequestGetter{mock, pegomock.Times(1), nil}
+}
+
+func (mock *MockGitlabMergeRequestGetter) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierGitlabMergeRequestGetter {
+ return &VerifierGitlabMergeRequestGetter{mock, invocationCountMatcher, nil}
+}
+
+func (mock *MockGitlabMergeRequestGetter) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierGitlabMergeRequestGetter {
+ return &VerifierGitlabMergeRequestGetter{mock, invocationCountMatcher, inOrderContext}
+}
+
+type VerifierGitlabMergeRequestGetter struct {
+ mock *MockGitlabMergeRequestGetter
+ invocationCountMatcher pegomock.Matcher
+ inOrderContext *pegomock.InOrderContext
+}
+
+func (verifier *VerifierGitlabMergeRequestGetter) GetMergeRequest(repoFullName string, pullNum int) *GitlabMergeRequestGetter_GetMergeRequest_OngoingVerification {
+ params := []pegomock.Param{repoFullName, pullNum}
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "GetMergeRequest", params)
+ return &GitlabMergeRequestGetter_GetMergeRequest_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+}
+
+type GitlabMergeRequestGetter_GetMergeRequest_OngoingVerification struct {
+ mock *MockGitlabMergeRequestGetter
+ methodInvocations []pegomock.MethodInvocation
+}
+
+func (c *GitlabMergeRequestGetter_GetMergeRequest_OngoingVerification) GetCapturedArguments() (string, int) {
+ repoFullName, pullNum := c.GetAllCapturedArguments()
+ return repoFullName[len(repoFullName)-1], pullNum[len(pullNum)-1]
+}
+
+func (c *GitlabMergeRequestGetter_GetMergeRequest_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 []int) {
+ params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
+ if len(params) > 0 {
+ _param0 = make([]string, len(params[0]))
+ for u, param := range params[0] {
+ _param0[u] = param.(string)
+ }
+ _param1 = make([]int, len(params[1]))
+ for u, param := range params[1] {
+ _param1[u] = param.(int)
+ }
+ }
+ return
+}
diff --git a/server/events/mocks/mock_lock_url_generator.go b/server/events/mocks/mock_lock_url_generator.go
index 02432224ce..17883a968d 100644
--- a/server/events/mocks/mock_lock_url_generator.go
+++ b/server/events/mocks/mock_lock_url_generator.go
@@ -4,8 +4,9 @@
package mocks
import (
- pegomock "github.com/petergtz/pegomock"
"reflect"
+
+ pegomock "github.com/petergtz/pegomock"
)
type MockLockURLGenerator struct {
diff --git a/server/events/mocks/mock_modified_project_finder.go b/server/events/mocks/mock_modified_project_finder.go
index 84de124453..cc199c9b6f 100644
--- a/server/events/mocks/mock_modified_project_finder.go
+++ b/server/events/mocks/mock_modified_project_finder.go
@@ -19,7 +19,7 @@ func NewMockModifiedProjectFinder() *MockModifiedProjectFinder {
return &MockModifiedProjectFinder{fail: pegomock.GlobalFailHandler}
}
-func (mock *MockModifiedProjectFinder) GetModified(log *logging.SimpleLogger, modifiedFiles []string, repoFullName string) []models.Project {
+func (mock *MockModifiedProjectFinder) FindModified(log *logging.SimpleLogger, modifiedFiles []string, repoFullName string) []models.Project {
params := []pegomock.Param{log, modifiedFiles, repoFullName}
result := pegomock.GetGenericMockFrom(mock).Invoke("FindModified", params, []reflect.Type{reflect.TypeOf((*[]models.Project)(nil)).Elem()})
var ret0 []models.Project
@@ -49,23 +49,23 @@ type VerifierModifiedProjectFinder struct {
inOrderContext *pegomock.InOrderContext
}
-func (verifier *VerifierModifiedProjectFinder) GetModified(log *logging.SimpleLogger, modifiedFiles []string, repoFullName string) *ModifiedProjectFinder_GetModified_OngoingVerification {
+func (verifier *VerifierModifiedProjectFinder) FindModified(log *logging.SimpleLogger, modifiedFiles []string, repoFullName string) *ModifiedProjectFinder_FindModified_OngoingVerification {
params := []pegomock.Param{log, modifiedFiles, repoFullName}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "FindModified", params)
- return &ModifiedProjectFinder_GetModified_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+ return &ModifiedProjectFinder_FindModified_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
-type ModifiedProjectFinder_GetModified_OngoingVerification struct {
+type ModifiedProjectFinder_FindModified_OngoingVerification struct {
mock *MockModifiedProjectFinder
methodInvocations []pegomock.MethodInvocation
}
-func (c *ModifiedProjectFinder_GetModified_OngoingVerification) GetCapturedArguments() (*logging.SimpleLogger, []string, string) {
+func (c *ModifiedProjectFinder_FindModified_OngoingVerification) GetCapturedArguments() (*logging.SimpleLogger, []string, string) {
log, modifiedFiles, repoFullName := c.GetAllCapturedArguments()
return log[len(log)-1], modifiedFiles[len(modifiedFiles)-1], repoFullName[len(repoFullName)-1]
}
-func (c *ModifiedProjectFinder_GetModified_OngoingVerification) GetAllCapturedArguments() (_param0 []*logging.SimpleLogger, _param1 [][]string, _param2 []string) {
+func (c *ModifiedProjectFinder_FindModified_OngoingVerification) GetAllCapturedArguments() (_param0 []*logging.SimpleLogger, _param1 [][]string, _param2 []string) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([]*logging.SimpleLogger, len(params[0]))
diff --git a/server/events/mocks/mock_planner.go b/server/events/mocks/mock_planner.go
deleted file mode 100644
index ccbbfa0232..0000000000
--- a/server/events/mocks/mock_planner.go
+++ /dev/null
@@ -1,107 +0,0 @@
-// Automatically generated by pegomock. DO NOT EDIT!
-// Source: github.com/hootsuite/atlantis/server/events (interfaces: Planner)
-
-package mocks
-
-import (
- events "github.com/hootsuite/atlantis/server/events"
- pegomock "github.com/petergtz/pegomock"
- "reflect"
-)
-
-type MockPlanner struct {
- fail func(message string, callerSkip ...int)
-}
-
-func NewMockPlanner() *MockPlanner {
- return &MockPlanner{fail: pegomock.GlobalFailHandler}
-}
-
-func (mock *MockPlanner) Execute(ctx *events.CommandContext) events.CommandResponse {
- params := []pegomock.Param{ctx}
- result := pegomock.GetGenericMockFrom(mock).Invoke("Execute", params, []reflect.Type{reflect.TypeOf((*events.CommandResponse)(nil)).Elem()})
- var ret0 events.CommandResponse
- if len(result) != 0 {
- if result[0] != nil {
- ret0 = result[0].(events.CommandResponse)
- }
- }
- return ret0
-}
-
-func (mock *MockPlanner) SetLockURL(_param0 func(string) string) {
- params := []pegomock.Param{_param0}
- pegomock.GetGenericMockFrom(mock).Invoke("SetLockURL", params, []reflect.Type{})
-}
-
-func (mock *MockPlanner) VerifyWasCalledOnce() *VerifierPlanner {
- return &VerifierPlanner{mock, pegomock.Times(1), nil}
-}
-
-func (mock *MockPlanner) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierPlanner {
- return &VerifierPlanner{mock, invocationCountMatcher, nil}
-}
-
-func (mock *MockPlanner) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierPlanner {
- return &VerifierPlanner{mock, invocationCountMatcher, inOrderContext}
-}
-
-type VerifierPlanner struct {
- mock *MockPlanner
- invocationCountMatcher pegomock.Matcher
- inOrderContext *pegomock.InOrderContext
-}
-
-func (verifier *VerifierPlanner) Execute(ctx *events.CommandContext) *Planner_Execute_OngoingVerification {
- params := []pegomock.Param{ctx}
- methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Execute", params)
- return &Planner_Execute_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
-}
-
-type Planner_Execute_OngoingVerification struct {
- mock *MockPlanner
- methodInvocations []pegomock.MethodInvocation
-}
-
-func (c *Planner_Execute_OngoingVerification) GetCapturedArguments() *events.CommandContext {
- ctx := c.GetAllCapturedArguments()
- return ctx[len(ctx)-1]
-}
-
-func (c *Planner_Execute_OngoingVerification) GetAllCapturedArguments() (_param0 []*events.CommandContext) {
- params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
- if len(params) > 0 {
- _param0 = make([]*events.CommandContext, len(params[0]))
- for u, param := range params[0] {
- _param0[u] = param.(*events.CommandContext)
- }
- }
- return
-}
-
-func (verifier *VerifierPlanner) SetLockURL(_param0 func(string) string) *Planner_SetLockURL_OngoingVerification {
- params := []pegomock.Param{_param0}
- methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "SetLockURL", params)
- return &Planner_SetLockURL_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
-}
-
-type Planner_SetLockURL_OngoingVerification struct {
- mock *MockPlanner
- methodInvocations []pegomock.MethodInvocation
-}
-
-func (c *Planner_SetLockURL_OngoingVerification) GetCapturedArguments() func(string) string {
- _param0 := c.GetAllCapturedArguments()
- return _param0[len(_param0)-1]
-}
-
-func (c *Planner_SetLockURL_OngoingVerification) GetAllCapturedArguments() (_param0 []func(string) string) {
- params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
- if len(params) > 0 {
- _param0 = make([]func(string) string, len(params[0]))
- for u, param := range params[0] {
- _param0[u] = param.(func(string) string)
- }
- }
- return
-}
diff --git a/server/events/mocks/mock_project_config_reader.go b/server/events/mocks/mock_project_config_reader.go
index 46f5a2abbe..e3d3f5d241 100644
--- a/server/events/mocks/mock_project_config_reader.go
+++ b/server/events/mocks/mock_project_config_reader.go
@@ -4,9 +4,10 @@
package mocks
import (
+ "reflect"
+
events "github.com/hootsuite/atlantis/server/events"
pegomock "github.com/petergtz/pegomock"
- "reflect"
)
type MockProjectConfigReader struct {
diff --git a/server/events/mocks/mock_pull_cleaner.go b/server/events/mocks/mock_pull_cleaner.go
index 604c910d27..be60deea4a 100644
--- a/server/events/mocks/mock_pull_cleaner.go
+++ b/server/events/mocks/mock_pull_cleaner.go
@@ -4,9 +4,11 @@
package mocks
import (
+ "reflect"
+
models "github.com/hootsuite/atlantis/server/events/models"
+ vcs "github.com/hootsuite/atlantis/server/events/vcs"
pegomock "github.com/petergtz/pegomock"
- "reflect"
)
type MockPullCleaner struct {
@@ -17,8 +19,8 @@ func NewMockPullCleaner() *MockPullCleaner {
return &MockPullCleaner{fail: pegomock.GlobalFailHandler}
}
-func (mock *MockPullCleaner) CleanUpPull(repo models.Repo, pull models.PullRequest) error {
- params := []pegomock.Param{repo, pull}
+func (mock *MockPullCleaner) CleanUpPull(repo models.Repo, pull models.PullRequest, host vcs.Host) error {
+ params := []pegomock.Param{repo, pull, host}
result := pegomock.GetGenericMockFrom(mock).Invoke("CleanUpPull", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
var ret0 error
if len(result) != 0 {
@@ -47,8 +49,8 @@ type VerifierPullCleaner struct {
inOrderContext *pegomock.InOrderContext
}
-func (verifier *VerifierPullCleaner) CleanUpPull(repo models.Repo, pull models.PullRequest) *PullCleaner_CleanUpPull_OngoingVerification {
- params := []pegomock.Param{repo, pull}
+func (verifier *VerifierPullCleaner) CleanUpPull(repo models.Repo, pull models.PullRequest, host vcs.Host) *PullCleaner_CleanUpPull_OngoingVerification {
+ params := []pegomock.Param{repo, pull, host}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CleanUpPull", params)
return &PullCleaner_CleanUpPull_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
@@ -58,12 +60,12 @@ type PullCleaner_CleanUpPull_OngoingVerification struct {
methodInvocations []pegomock.MethodInvocation
}
-func (c *PullCleaner_CleanUpPull_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest) {
- repo, pull := c.GetAllCapturedArguments()
- return repo[len(repo)-1], pull[len(pull)-1]
+func (c *PullCleaner_CleanUpPull_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest, vcs.Host) {
+ repo, pull, host := c.GetAllCapturedArguments()
+ return repo[len(repo)-1], pull[len(pull)-1], host[len(host)-1]
}
-func (c *PullCleaner_CleanUpPull_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest) {
+func (c *PullCleaner_CleanUpPull_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest, _param2 []vcs.Host) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([]models.Repo, len(params[0]))
@@ -74,6 +76,10 @@ func (c *PullCleaner_CleanUpPull_OngoingVerification) GetAllCapturedArguments()
for u, param := range params[1] {
_param1[u] = param.(models.PullRequest)
}
+ _param2 = make([]vcs.Host, len(params[2]))
+ for u, param := range params[2] {
+ _param2[u] = param.(vcs.Host)
+ }
}
return
}
diff --git a/server/events/mocks/mock_workspace.go b/server/events/mocks/mock_workspace.go
index 73bad996b8..da54ac50f3 100644
--- a/server/events/mocks/mock_workspace.go
+++ b/server/events/mocks/mock_workspace.go
@@ -4,10 +4,11 @@
package mocks
import (
+ "reflect"
+
models "github.com/hootsuite/atlantis/server/events/models"
logging "github.com/hootsuite/atlantis/server/logging"
pegomock "github.com/petergtz/pegomock"
- "reflect"
)
type MockWorkspace struct {
diff --git a/server/events/models/fixtures/fixtures.go b/server/events/models/fixtures/fixtures.go
index d9ac78935f..dc9b96a85f 100644
--- a/server/events/models/fixtures/fixtures.go
+++ b/server/events/models/fixtures/fixtures.go
@@ -8,7 +8,6 @@ var Pull = models.PullRequest{
Branch: "branch",
Author: "lkysow",
URL: "url",
- BaseCommit: "8ed0280678d49d42cd286610aabcfceb5bb673c6",
}
var Repo = models.Repo{
diff --git a/server/events/models/models.go b/server/events/models/models.go
index c58c8a4f1c..20fc3234eb 100644
--- a/server/events/models/models.go
+++ b/server/events/models/models.go
@@ -8,7 +8,7 @@ import (
"time"
)
-// Repo is a GitHub repository.
+// Repo is a VCS repository.
type Repo struct {
// FullName is the owner and repo name separated
// by a "/", ex. "hootsuite/atlantis".
@@ -25,26 +25,35 @@ type Repo struct {
SanitizedCloneURL string
}
-// PullRequest is a GitHub pull request.
+// PullRequest is a VCS pull request.
+// GitLab calls these Merge Requests.
type PullRequest struct {
// Num is the pull request number or ID.
Num int
// HeadCommit points to the head of the branch that is being
// pull requested into the base.
HeadCommit string
- // BaseCommit points to the head of the branch that this
- // pull request will be merged into.
- BaseCommit string
// URL is the url of the pull request.
// ex. "https://github.com/hootsuite/atlantis/pull/1"
URL string
// Branch is the name of the head branch (not the base).
Branch string
- // Author is the GitHub username of the pull request author.
+ // Author is the username of the pull request author.
Author string
+ // State will be one of Open or Closed.
+ // Gitlab supports an additional "merged" state but Github doesn't so we map
+ // merged to Closed.
+ State PullRequestState
}
-// User is a GitHub user.
+type PullRequestState int
+
+const (
+ Open PullRequestState = iota
+ Closed
+)
+
+// User is a VCS user.
type User struct {
Username string
}
@@ -56,7 +65,7 @@ type ProjectLock struct {
// Pull is the pull request from which the command was run that
// created this lock.
Pull PullRequest
- // User is the GitHub username of the user that ran the command
+ // User is the username of the user that ran the command
// that created this lock.
User User
// Env is the Terraform environment that this
diff --git a/server/events/plan_executor.go b/server/events/plan_executor.go
index cbd5c3ff5a..a1cdf8e4c6 100644
--- a/server/events/plan_executor.go
+++ b/server/events/plan_executor.go
@@ -5,11 +5,11 @@ import (
"os"
"path/filepath"
- "github.com/hootsuite/atlantis/server/events/github"
"github.com/hootsuite/atlantis/server/events/locking"
"github.com/hootsuite/atlantis/server/events/models"
"github.com/hootsuite/atlantis/server/events/run"
"github.com/hootsuite/atlantis/server/events/terraform"
+ "github.com/hootsuite/atlantis/server/events/vcs"
"github.com/pkg/errors"
)
@@ -22,20 +22,19 @@ type LockURLGenerator interface {
}
// atlantisUserTFVar is the name of the variable we execute terraform
-// with containing the github username of who is running the command
+// with, containing the vcs username of who is running the command
const atlantisUserTFVar = "atlantis_user"
-// PlanExecutor handles everything related to running terraform plan
-// including integration with S3, Terraform, and GitHub
+// PlanExecutor handles everything related to running terraform plan.
type PlanExecutor struct {
- Github github.Client
+ VCSClient vcs.ClientProxy
Terraform terraform.Runner
Locker locking.Locker
LockURL func(id string) (url string)
Run run.Runner
Workspace Workspace
ProjectPreExecute ProjectPreExecutor
- ModifiedProject ModifiedProjectFinder
+ ProjectFinder ModifiedProjectFinder
}
type PlanSuccess struct {
@@ -49,12 +48,12 @@ func (p *PlanExecutor) SetLockURL(f func(id string) (url string)) {
func (p *PlanExecutor) Execute(ctx *CommandContext) CommandResponse {
// figure out what projects have been modified so we know where to run plan
- modifiedFiles, err := p.Github.GetModifiedFiles(ctx.BaseRepo, ctx.Pull)
+ modifiedFiles, err := p.VCSClient.GetModifiedFiles(ctx.BaseRepo, ctx.Pull, ctx.VCSHost)
if err != nil {
return CommandResponse{Error: errors.Wrap(err, "getting modified files")}
}
ctx.Log.Info("found %d files modified in this pull request", len(modifiedFiles))
- projects := p.ModifiedProject.FindModified(ctx.Log, modifiedFiles, ctx.BaseRepo.FullName)
+ projects := p.ProjectFinder.FindModified(ctx.Log, modifiedFiles, ctx.BaseRepo.FullName)
if len(projects) == 0 {
return CommandResponse{Failure: "No Terraform files were modified."}
}
diff --git a/server/events/plan_executor_test.go b/server/events/plan_executor_test.go
index 88ca85f0a7..ff3510c3c3 100644
--- a/server/events/plan_executor_test.go
+++ b/server/events/plan_executor_test.go
@@ -1,18 +1,17 @@
package events_test
import (
- "testing"
-
"errors"
+ "testing"
"github.com/hootsuite/atlantis/server/events"
- ghmocks "github.com/hootsuite/atlantis/server/events/github/mocks"
"github.com/hootsuite/atlantis/server/events/locking"
lmocks "github.com/hootsuite/atlantis/server/events/locking/mocks"
"github.com/hootsuite/atlantis/server/events/mocks"
"github.com/hootsuite/atlantis/server/events/models"
rmocks "github.com/hootsuite/atlantis/server/events/run/mocks"
tmocks "github.com/hootsuite/atlantis/server/events/terraform/mocks"
+ vcsmocks "github.com/hootsuite/atlantis/server/events/vcs/mocks"
"github.com/hootsuite/atlantis/server/logging"
. "github.com/hootsuite/atlantis/testing"
. "github.com/petergtz/pegomock"
@@ -32,10 +31,10 @@ var planCtx = events.CommandContext{
},
}
-func TestExecute_GithubErr(t *testing.T) {
+func TestExecute_ModifiedFilesErr(t *testing.T) {
t.Log("If GetModifiedFiles returns an error we return an error")
p, _, _ := setupPlanExecutorTest(t)
- When(p.Github.GetModifiedFiles(AnyRepo(), AnyPullRequest())).ThenReturn(nil, errors.New("err"))
+ When(p.VCSClient.GetModifiedFiles(AnyRepo(), AnyPullRequest(), AnyVCSHost())).ThenReturn(nil, errors.New("err"))
r := p.Execute(&planCtx)
Assert(t, r.Error != nil, "exp .Error to be set")
@@ -45,7 +44,7 @@ func TestExecute_GithubErr(t *testing.T) {
func TestExecute_NoModifiedProjects(t *testing.T) {
t.Log("If there are no modified projects we return a failure")
p, _, _ := setupPlanExecutorTest(t)
- // We don't need to actually mock Github.GetModifiedFiles because by
+ // We don't need to actually mock VCSClient.GetModifiedFiles because by
// default it will return an empty slice which is what we want for this test.
r := p.Execute(&planCtx)
@@ -55,7 +54,7 @@ func TestExecute_NoModifiedProjects(t *testing.T) {
func TestExecute_CloneErr(t *testing.T) {
t.Log("If Workspace.Clone returns an error we return an error")
p, _, _ := setupPlanExecutorTest(t)
- When(p.Github.GetModifiedFiles(AnyRepo(), AnyPullRequest())).ThenReturn([]string{"file.tf"}, nil)
+ When(p.VCSClient.GetModifiedFiles(AnyRepo(), AnyPullRequest(), AnyVCSHost())).ThenReturn([]string{"file.tf"}, nil)
When(p.Workspace.Clone(planCtx.Log, planCtx.BaseRepo, planCtx.HeadRepo, planCtx.Pull, "env")).ThenReturn("", errors.New("err"))
r := p.Execute(&planCtx)
@@ -66,7 +65,7 @@ func TestExecute_CloneErr(t *testing.T) {
func TestExecute_Success(t *testing.T) {
t.Log("If there are no errors, the plan should be returned")
p, runner, _ := setupPlanExecutorTest(t)
- When(p.Github.GetModifiedFiles(AnyRepo(), AnyPullRequest())).ThenReturn([]string{"file.tf"}, nil)
+ When(p.VCSClient.GetModifiedFiles(AnyRepo(), AnyPullRequest(), AnyVCSHost())).ThenReturn([]string{"file.tf"}, nil)
When(p.Workspace.Clone(planCtx.Log, planCtx.BaseRepo, planCtx.HeadRepo, planCtx.Pull, "env")).
ThenReturn("/tmp/clone-repo", nil)
When(p.ProjectPreExecute.Execute(&planCtx, "/tmp/clone-repo", models.Project{RepoFullName: "", Path: "."})).
@@ -95,7 +94,7 @@ func TestExecute_Success(t *testing.T) {
func TestExecute_PreExecuteResult(t *testing.T) {
t.Log("If ProjectPreExecute.Execute returns a ProjectResult we should return it")
p, _, _ := setupPlanExecutorTest(t)
- When(p.Github.GetModifiedFiles(AnyRepo(), AnyPullRequest())).ThenReturn([]string{"file.tf"}, nil)
+ When(p.VCSClient.GetModifiedFiles(AnyRepo(), AnyPullRequest(), AnyVCSHost())).ThenReturn([]string{"file.tf"}, nil)
When(p.Workspace.Clone(planCtx.Log, planCtx.BaseRepo, planCtx.HeadRepo, planCtx.Pull, "env")).
ThenReturn("/tmp/clone-repo", nil)
projectResult := events.ProjectResult{
@@ -114,7 +113,7 @@ func TestExecute_MultiProjectFailure(t *testing.T) {
t.Log("If is an error planning in one project it should be returned. It shouldn't affect another project though.")
p, runner, locker := setupPlanExecutorTest(t)
// Two projects have been modified so we should run plan in two paths.
- When(p.Github.GetModifiedFiles(AnyRepo(), AnyPullRequest())).ThenReturn([]string{"path1/file.tf", "path2/file.tf"}, nil)
+ When(p.VCSClient.GetModifiedFiles(AnyRepo(), AnyPullRequest(), AnyVCSHost())).ThenReturn([]string{"path1/file.tf", "path2/file.tf"}, nil)
When(p.Workspace.Clone(planCtx.Log, planCtx.BaseRepo, planCtx.HeadRepo, planCtx.Pull, "env")).
ThenReturn("/tmp/clone-repo", nil)
@@ -154,7 +153,7 @@ func TestExecute_MultiProjectFailure(t *testing.T) {
func TestExecute_PostPlanCommands(t *testing.T) {
t.Log("Should execute post-plan commands and return if there is an error")
p, _, _ := setupPlanExecutorTest(t)
- When(p.Github.GetModifiedFiles(AnyRepo(), AnyPullRequest())).ThenReturn([]string{"file.tf"}, nil)
+ When(p.VCSClient.GetModifiedFiles(AnyRepo(), AnyPullRequest(), AnyVCSHost())).ThenReturn([]string{"file.tf"}, nil)
When(p.Workspace.Clone(planCtx.Log, planCtx.BaseRepo, planCtx.HeadRepo, planCtx.Pull, "env")).
ThenReturn("/tmp/clone-repo", nil)
When(p.ProjectPreExecute.Execute(&planCtx, "/tmp/clone-repo", models.Project{RepoFullName: "", Path: "."})).
@@ -174,15 +173,15 @@ func TestExecute_PostPlanCommands(t *testing.T) {
func setupPlanExecutorTest(t *testing.T) (*events.PlanExecutor, *tmocks.MockRunner, *lmocks.MockLocker) {
RegisterMockTestingT(t)
- gh := ghmocks.NewMockClient()
+ vcsProxy := vcsmocks.NewMockClientProxy()
w := mocks.NewMockWorkspace()
ppe := mocks.NewMockProjectPreExecutor()
runner := tmocks.NewMockRunner()
locker := lmocks.NewMockLocker()
run := rmocks.NewMockRunner()
p := events.PlanExecutor{
- Github: gh,
- ModifiedProject: &events.ProjectFinder{},
+ VCSClient: vcsProxy,
+ ProjectFinder: &events.ProjectFinder{},
Workspace: w,
ProjectPreExecute: ppe,
Terraform: runner,
diff --git a/server/events/project_finder_test.go b/server/events/project_finder_test.go
index dee8cdba7f..835fbbc53a 100644
--- a/server/events/project_finder_test.go
+++ b/server/events/project_finder_test.go
@@ -8,7 +8,7 @@ import (
. "github.com/hootsuite/atlantis/testing"
)
-var log = logging.NewNoopLogger()
+var noopLogger = logging.NewNoopLogger()
var modifiedRepo = "owner/repo"
var m = events.ProjectFinder{}
@@ -66,7 +66,7 @@ func TestGetModified_NoFiles(t *testing.T) {
}
for _, c := range cases {
t.Log(c.description)
- projects := m.FindModified(log, c.files, modifiedRepo)
+ projects := m.FindModified(noopLogger, c.files, modifiedRepo)
// Extract the paths from the projects. We use a slice here instead of a
// map so we can test whether there are duplicates returned.
diff --git a/server/events/project_result.go b/server/events/project_result.go
index 4765c1af36..52a79666a0 100644
--- a/server/events/project_result.go
+++ b/server/events/project_result.go
@@ -1,5 +1,7 @@
package events
+import "github.com/hootsuite/atlantis/server/events/vcs"
+
type ProjectResult struct {
Path string
Error error
@@ -8,12 +10,12 @@ type ProjectResult struct {
ApplySuccess string
}
-func (p ProjectResult) Status() Status {
+func (p ProjectResult) Status() vcs.CommitStatus {
if p.Error != nil {
- return Error
+ return vcs.Failed
}
if p.Failure != "" {
- return Failure
+ return vcs.Failed
}
- return Success
+ return vcs.Success
}
diff --git a/server/events/pull_closed_executor.go b/server/events/pull_closed_executor.go
index a0c56f2c46..fff288ab29 100644
--- a/server/events/pull_closed_executor.go
+++ b/server/events/pull_closed_executor.go
@@ -8,21 +8,21 @@ import (
"sort"
- "github.com/hootsuite/atlantis/server/events/github"
"github.com/hootsuite/atlantis/server/events/locking"
"github.com/hootsuite/atlantis/server/events/models"
+ "github.com/hootsuite/atlantis/server/events/vcs"
"github.com/pkg/errors"
)
//go:generate pegomock generate --use-experimental-model-gen --package mocks -o mocks/mock_pull_cleaner.go PullCleaner
type PullCleaner interface {
- CleanUpPull(repo models.Repo, pull models.PullRequest) error
+ CleanUpPull(repo models.Repo, pull models.PullRequest, host vcs.Host) error
}
type PullClosedExecutor struct {
Locker locking.Locker
- Github github.Client
+ VCSClient vcs.ClientProxy
Workspace Workspace
}
@@ -36,7 +36,7 @@ var pullClosedTemplate = template.Must(template.New("").Parse(
"{{ range . }}\n" +
"- path: `{{ .Path }}` {{ .Envs }}{{ end }}"))
-func (p *PullClosedExecutor) CleanUpPull(repo models.Repo, pull models.PullRequest) error {
+func (p *PullClosedExecutor) CleanUpPull(repo models.Repo, pull models.PullRequest, host vcs.Host) error {
// delete the workspace
if err := p.Workspace.Delete(repo, pull); err != nil {
return errors.Wrap(err, "cleaning workspace")
@@ -60,11 +60,11 @@ func (p *PullClosedExecutor) CleanUpPull(repo models.Repo, pull models.PullReque
if err = pullClosedTemplate.Execute(&buf, templateData); err != nil {
return errors.Wrap(err, "rendering template for comment")
}
- return p.Github.CreateComment(repo, pull, buf.String())
+ return p.VCSClient.CreateComment(repo, pull, buf.String(), host)
}
// buildTemplateData formats the lock data into a slice that can easily be templated
-// for the GitHub comment. We organize all the environments by their respective project paths
+// for the VCS comment. We organize all the environments by their respective project paths
// so the comment can look like: path: {path}, environments: {all-envs}
func (p *PullClosedExecutor) buildTemplateData(locks []models.ProjectLock) []templatedProject {
envsByPath := make(map[string][]string)
diff --git a/server/events/pull_closed_executor_test.go b/server/events/pull_closed_executor_test.go
index 293b4bae26..d16df3158d 100644
--- a/server/events/pull_closed_executor_test.go
+++ b/server/events/pull_closed_executor_test.go
@@ -1,18 +1,19 @@
package events_test
import (
- "errors"
"reflect"
- "testing"
- "github.com/hootsuite/atlantis/server/events"
- ghmocks "github.com/hootsuite/atlantis/server/events/github/mocks"
- lockmocks "github.com/hootsuite/atlantis/server/events/locking/mocks"
- "github.com/hootsuite/atlantis/server/events/mocks"
"github.com/hootsuite/atlantis/server/events/models"
- "github.com/hootsuite/atlantis/server/events/models/fixtures"
. "github.com/hootsuite/atlantis/testing"
. "github.com/petergtz/pegomock"
+ "testing"
+ "github.com/hootsuite/atlantis/server/events/mocks"
+ lockmocks "github.com/hootsuite/atlantis/server/events/locking/mocks"
+ vcsmocks "github.com/hootsuite/atlantis/server/events/vcs/mocks"
+ "github.com/hootsuite/atlantis/server/events"
+ "errors"
+ "github.com/hootsuite/atlantis/server/events/vcs"
+ "github.com/hootsuite/atlantis/server/events/models/fixtures"
)
func TestCleanUpPullWorkspaceErr(t *testing.T) {
@@ -24,7 +25,7 @@ func TestCleanUpPullWorkspaceErr(t *testing.T) {
}
err := errors.New("err")
When(w.Delete(fixtures.Repo, fixtures.Pull)).ThenReturn(err)
- actualErr := pce.CleanUpPull(fixtures.Repo, fixtures.Pull)
+ actualErr := pce.CleanUpPull(fixtures.Repo, fixtures.Pull, vcs.Github)
Equals(t, "cleaning workspace: err", actualErr.Error())
}
@@ -39,7 +40,7 @@ func TestCleanUpPullUnlockErr(t *testing.T) {
}
err := errors.New("err")
When(l.UnlockByPull(fixtures.Repo.FullName, fixtures.Pull.Num)).ThenReturn(nil, err)
- actualErr := pce.CleanUpPull(fixtures.Repo, fixtures.Pull)
+ actualErr := pce.CleanUpPull(fixtures.Repo, fixtures.Pull, vcs.Github)
Equals(t, "cleaning up locks: err", actualErr.Error())
}
@@ -48,16 +49,16 @@ func TestCleanUpPullNoLocks(t *testing.T) {
RegisterMockTestingT(t)
w := mocks.NewMockWorkspace()
l := lockmocks.NewMockLocker()
- gh := ghmocks.NewMockClient()
+ cp := vcsmocks.NewMockClientProxy()
pce := events.PullClosedExecutor{
Locker: l,
- Github: gh,
+ VCSClient: cp,
Workspace: w,
}
When(l.UnlockByPull(fixtures.Repo.FullName, fixtures.Pull.Num)).ThenReturn(nil, nil)
- err := pce.CleanUpPull(fixtures.Repo, fixtures.Pull)
+ err := pce.CleanUpPull(fixtures.Repo, fixtures.Pull, vcs.Github)
Ok(t, err)
- gh.VerifyWasCalled(Never()).CreateComment(AnyRepo(), AnyPullRequest(), AnyString())
+ cp.VerifyWasCalled(Never()).CreateComment(AnyRepo(), AnyPullRequest(), AnyString(), AnyVCSHost())
}
func TestCleanUpPullComments(t *testing.T) {
@@ -127,18 +128,18 @@ func TestCleanUpPullComments(t *testing.T) {
}
for _, c := range cases {
w := mocks.NewMockWorkspace()
- gh := ghmocks.NewMockClient()
+ cp := vcsmocks.NewMockClientProxy()
l := lockmocks.NewMockLocker()
pce := events.PullClosedExecutor{
Locker: l,
- Github: gh,
+ VCSClient: cp,
Workspace: w,
}
t.Log("testing: " + c.Description)
When(l.UnlockByPull(fixtures.Repo.FullName, fixtures.Pull.Num)).ThenReturn(c.Locks, nil)
- err := pce.CleanUpPull(fixtures.Repo, fixtures.Pull)
+ err := pce.CleanUpPull(fixtures.Repo, fixtures.Pull, vcs.Github)
Ok(t, err)
- _, _, comment := gh.VerifyWasCalledOnce().CreateComment(AnyRepo(), AnyPullRequest(), AnyString()).GetCapturedArguments()
+ _, _, comment, _ := cp.VerifyWasCalledOnce().CreateComment(AnyRepo(), AnyPullRequest(), AnyString(), AnyVCSHost()).GetCapturedArguments()
expected := "Locks and plans deleted for the projects and environments modified in this pull request:\n\n" + c.Exp
Equals(t, expected, comment)
@@ -154,3 +155,4 @@ func AnyPullRequest() models.PullRequest {
RegisterMatcher(NewAnyMatcher(reflect.TypeOf(models.PullRequest{})))
return models.PullRequest{}
}
+
diff --git a/server/events/run/mocks/mock_runner.go b/server/events/run/mocks/mock_runner.go
index 0f1ca4991f..5c9d42ce42 100644
--- a/server/events/run/mocks/mock_runner.go
+++ b/server/events/run/mocks/mock_runner.go
@@ -4,10 +4,11 @@
package mocks
import (
+ "reflect"
+
go_version "github.com/hashicorp/go-version"
logging "github.com/hootsuite/atlantis/server/logging"
pegomock "github.com/petergtz/pegomock"
- "reflect"
)
type MockRunner struct {
diff --git a/server/events/terraform/mocks/mock_runner.go b/server/events/terraform/mocks/mock_runner.go
index 32c735368d..a71ab13166 100644
--- a/server/events/terraform/mocks/mock_runner.go
+++ b/server/events/terraform/mocks/mock_runner.go
@@ -4,10 +4,11 @@
package mocks
import (
+ "reflect"
+
go_version "github.com/hashicorp/go-version"
logging "github.com/hootsuite/atlantis/server/logging"
pegomock "github.com/petergtz/pegomock"
- "reflect"
)
type MockRunner struct {
diff --git a/server/events/vcs/client.go b/server/events/vcs/client.go
new file mode 100644
index 0000000000..9ef1a1eebc
--- /dev/null
+++ b/server/events/vcs/client.go
@@ -0,0 +1,15 @@
+package vcs
+
+import (
+ "github.com/hootsuite/atlantis/server/events/models"
+)
+
+//go:generate pegomock generate --use-experimental-model-gen --package mocks -o mocks/mock_client.go Client
+
+// Client is used to make API calls to a VCS host like GitHub or GitLab.
+type Client interface {
+ GetModifiedFiles(repo models.Repo, pull models.PullRequest) ([]string, error)
+ CreateComment(repo models.Repo, pull models.PullRequest, comment string) error
+ PullIsApproved(repo models.Repo, pull models.PullRequest) (bool, error)
+ UpdateStatus(repo models.Repo, pull models.PullRequest, state CommitStatus, description string) error
+}
diff --git a/server/events/github/client_test.go b/server/events/vcs/client_test.go
similarity index 78%
rename from server/events/github/client_test.go
rename to server/events/vcs/client_test.go
index f3013d35f3..8daf51d6c2 100644
--- a/server/events/github/client_test.go
+++ b/server/events/vcs/client_test.go
@@ -1,4 +1,4 @@
-package github_test
+package vcs
// todo: actually test
// purposefully empty to trigger coverage report
diff --git a/server/events/github/fixtures/fixtures.go b/server/events/vcs/fixtures/fixtures.go
similarity index 95%
rename from server/events/github/fixtures/fixtures.go
rename to server/events/vcs/fixtures/fixtures.go
index 913c535e93..250caec106 100644
--- a/server/events/github/fixtures/fixtures.go
+++ b/server/events/vcs/fixtures/fixtures.go
@@ -16,6 +16,7 @@ var Pull = github.PullRequest{
Login: github.String("user"),
},
Number: github.Int(1),
+ State: github.String("open"),
}
var Repo = github.Repository{
diff --git a/server/events/github/client.go b/server/events/vcs/github_client.go
similarity index 54%
rename from server/events/github/client.go
rename to server/events/vcs/github_client.go
index ec21d1fac5..9a02ffef43 100644
--- a/server/events/github/client.go
+++ b/server/events/vcs/github_client.go
@@ -1,9 +1,7 @@
-// Package github provides convenience wrappers around the go-github package.
-package github
+package vcs
import (
"context"
-
"fmt"
"net/url"
"strings"
@@ -13,24 +11,14 @@ import (
"github.com/pkg/errors"
)
-//go:generate pegomock generate --package mocks -o mocks/mock_client.go client.go
-
-type Client interface {
- GetModifiedFiles(repo models.Repo, pull models.PullRequest) ([]string, error)
- CreateComment(repo models.Repo, pull models.PullRequest, comment string) error
- PullIsApproved(repo models.Repo, pull models.PullRequest) (bool, error)
- GetPullRequest(repo models.Repo, num int) (*github.PullRequest, *github.Response, error)
- UpdateStatus(repo models.Repo, pull models.PullRequest, state string, description string, context string) error
-}
-
-// ConcreteClient is used to perform GitHub actions.
-type ConcreteClient struct {
+// GithubClient is used to perform GitHub actions.
+type GithubClient struct {
client *github.Client
ctx context.Context
}
-// NewClient returns a valid GitHub client.
-func NewClient(hostname string, user string, pass string) (*ConcreteClient, error) {
+// NewGithubClient returns a valid GitHub client.
+func NewGithubClient(hostname string, user string, pass string) (*GithubClient, error) {
tp := github.BasicAuthTransport{
Username: strings.TrimSpace(user),
Password: strings.TrimSpace(pass),
@@ -48,7 +36,7 @@ func NewClient(hostname string, user string, pass string) (*ConcreteClient, erro
client.BaseURL = base
}
- return &ConcreteClient{
+ return &GithubClient{
client: client,
ctx: context.Background(),
}, nil
@@ -56,7 +44,7 @@ func NewClient(hostname string, user string, pass string) (*ConcreteClient, erro
// GetModifiedFiles returns the names of files that were modified in the pull request.
// The names include the path to the file from the repo root, ex. parent/child/file.txt.
-func (c *ConcreteClient) GetModifiedFiles(repo models.Repo, pull models.PullRequest) ([]string, error) {
+func (g *GithubClient) GetModifiedFiles(repo models.Repo, pull models.PullRequest) ([]string, error) {
var files []string
nextPage := 0
for {
@@ -66,7 +54,7 @@ func (c *ConcreteClient) GetModifiedFiles(repo models.Repo, pull models.PullRequ
if nextPage != 0 {
opts.Page = nextPage
}
- pageFiles, resp, err := c.client.PullRequests.ListFiles(c.ctx, repo.Owner, repo.Name, pull.Num, &opts)
+ pageFiles, resp, err := g.client.PullRequests.ListFiles(g.ctx, repo.Owner, repo.Name, pull.Num, &opts)
if err != nil {
return files, err
}
@@ -82,14 +70,14 @@ func (c *ConcreteClient) GetModifiedFiles(repo models.Repo, pull models.PullRequ
}
// CreateComment creates a comment on the pull request.
-func (c *ConcreteClient) CreateComment(repo models.Repo, pull models.PullRequest, comment string) error {
- _, _, err := c.client.Issues.CreateComment(c.ctx, repo.Owner, repo.Name, pull.Num, &github.IssueComment{Body: &comment})
+func (g *GithubClient) CreateComment(repo models.Repo, pull models.PullRequest, comment string) error {
+ _, _, err := g.client.Issues.CreateComment(g.ctx, repo.Owner, repo.Name, pull.Num, &github.IssueComment{Body: &comment})
return err
}
// PullIsApproved returns true if the pull request was approved.
-func (c *ConcreteClient) PullIsApproved(repo models.Repo, pull models.PullRequest) (bool, error) {
- reviews, _, err := c.client.PullRequests.ListReviews(c.ctx, repo.Owner, repo.Name, pull.Num, nil)
+func (g *GithubClient) PullIsApproved(repo models.Repo, pull models.PullRequest) (bool, error) {
+ reviews, _, err := g.client.PullRequests.ListReviews(g.ctx, repo.Owner, repo.Name, pull.Num, nil)
if err != nil {
return false, errors.Wrap(err, "getting reviews")
}
@@ -102,17 +90,28 @@ func (c *ConcreteClient) PullIsApproved(repo models.Repo, pull models.PullReques
}
// GetPullRequest returns the pull request.
-func (c *ConcreteClient) GetPullRequest(repo models.Repo, num int) (*github.PullRequest, *github.Response, error) {
- return c.client.PullRequests.Get(c.ctx, repo.Owner, repo.Name, num)
+func (g *GithubClient) GetPullRequest(repo models.Repo, num int) (*github.PullRequest, error) {
+ pull, _, err := g.client.PullRequests.Get(g.ctx, repo.Owner, repo.Name, num)
+ return pull, err
}
// UpdateStatus updates the status badge on the pull request.
// See https://github.com/blog/1227-commit-status-api.
-func (c *ConcreteClient) UpdateStatus(repo models.Repo, pull models.PullRequest, state string, description string, context string) error {
+func (g *GithubClient) UpdateStatus(repo models.Repo, pull models.PullRequest, state CommitStatus, description string) error {
+ const statusContext = "Atlantis"
+ ghState := "error"
+ switch state {
+ case Pending:
+ ghState = "pending"
+ case Success:
+ ghState = "success"
+ case Failed:
+ ghState = "failure"
+ }
status := &github.RepoStatus{
- State: github.String(state),
+ State: github.String(ghState),
Description: github.String(description),
- Context: github.String(context)}
- _, _, err := c.client.Repositories.CreateStatus(c.ctx, repo.Owner, repo.Name, pull.HeadCommit, status)
+ Context: github.String(statusContext)}
+ _, _, err := g.client.Repositories.CreateStatus(g.ctx, repo.Owner, repo.Name, pull.HeadCommit, status)
return err
}
diff --git a/server/events/vcs/gitlab_client.go b/server/events/vcs/gitlab_client.go
new file mode 100644
index 0000000000..b603ab0eac
--- /dev/null
+++ b/server/events/vcs/gitlab_client.go
@@ -0,0 +1,92 @@
+package vcs
+
+import (
+ "fmt"
+ "net/url"
+
+ "github.com/hootsuite/atlantis/server/events/models"
+ "github.com/lkysow/go-gitlab"
+)
+
+type GitlabClient struct {
+ Client *gitlab.Client
+}
+
+// GetModifiedFiles returns the names of files that were modified in the merge request.
+// The names include the path to the file from the repo root, ex. parent/child/file.txt.
+func (g *GitlabClient) GetModifiedFiles(repo models.Repo, pull models.PullRequest) ([]string, error) {
+ const maxPerPage = 100
+ var files []string
+ nextPage := 1
+ // Constructing the api url by hand so we can do pagination.
+ apiURL := fmt.Sprintf("projects/%s/merge_requests/%d/changes", url.QueryEscape(repo.FullName), pull.Num)
+ for {
+ opts := gitlab.ListOptions{
+ Page: nextPage,
+ PerPage: maxPerPage,
+ }
+ req, err := g.Client.NewRequest("GET", apiURL, opts, nil)
+ if err != nil {
+ return nil, err
+ }
+ mr := new(gitlab.MergeRequest)
+ resp, err := g.Client.Do(req, mr)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, f := range mr.Changes {
+ files = append(files, f.NewPath)
+ }
+ if resp.NextPage == 0 {
+ break
+ }
+ nextPage = resp.NextPage
+ }
+
+ return files, nil
+}
+
+// CreateComment creates a comment on the merge request.
+func (g *GitlabClient) CreateComment(repo models.Repo, pull models.PullRequest, comment string) error {
+ _, _, err := g.Client.Notes.CreateMergeRequestNote(repo.FullName, pull.Num, &gitlab.CreateMergeRequestNoteOptions{Body: gitlab.String(comment)})
+ return err
+}
+
+// PullIsApproved returns true if the merge request was approved.
+func (g *GitlabClient) PullIsApproved(repo models.Repo, pull models.PullRequest) (bool, error) {
+ approvals, _, err := g.Client.MergeRequests.GetMergeRequestApprovals(repo.FullName, pull.Num)
+ if err != nil {
+ return false, err
+ }
+ if approvals.ApprovalsMissing > 0 {
+ return false, nil
+ }
+ return true, nil
+}
+
+// UpdateStatus updates the build status of a commit.
+func (g *GitlabClient) UpdateStatus(repo models.Repo, pull models.PullRequest, state CommitStatus, description string) error {
+ const statusContext = "Atlantis"
+
+ gitlabState := gitlab.Failed
+ switch state {
+ case Pending:
+ gitlabState = gitlab.Pending
+ case Failed:
+ gitlabState = gitlab.Failed
+ case Success:
+ gitlabState = gitlab.Success
+ }
+ _, _, err := g.Client.Commits.SetCommitStatus(repo.FullName, pull.HeadCommit, &gitlab.SetCommitStatusOptions{
+ State: gitlabState,
+ Context: gitlab.String(statusContext),
+ Description: gitlab.String(description),
+ })
+ return err
+}
+
+func (g *GitlabClient) GetMergeRequest(repoFullName string, pullNum int) (*gitlab.MergeRequest, error) {
+ mr, _, err := g.Client.MergeRequests.GetMergeRequest(repoFullName, pullNum)
+ return mr, err
+}
diff --git a/server/events/github/mocks/mock_client.go b/server/events/vcs/mocks/mock_client.go
similarity index 74%
rename from server/events/github/mocks/mock_client.go
rename to server/events/vcs/mocks/mock_client.go
index d4ae21cde4..a6c70c041d 100644
--- a/server/events/github/mocks/mock_client.go
+++ b/server/events/vcs/mocks/mock_client.go
@@ -1,13 +1,14 @@
// Automatically generated by pegomock. DO NOT EDIT!
-// Source: client.go
+// Source: github.com/hootsuite/atlantis/server/events/vcs (interfaces: Client)
package mocks
import (
- github "github.com/google/go-github/github"
+ "reflect"
+
models "github.com/hootsuite/atlantis/server/events/models"
+ vcs "github.com/hootsuite/atlantis/server/events/vcs"
pegomock "github.com/petergtz/pegomock"
- "reflect"
)
type MockClient struct {
@@ -62,28 +63,8 @@ func (mock *MockClient) PullIsApproved(repo models.Repo, pull models.PullRequest
return ret0, ret1
}
-func (mock *MockClient) GetPullRequest(repo models.Repo, num int) (*github.PullRequest, *github.Response, error) {
- params := []pegomock.Param{repo, num}
- result := pegomock.GetGenericMockFrom(mock).Invoke("GetPullRequest", params, []reflect.Type{reflect.TypeOf((**github.PullRequest)(nil)).Elem(), reflect.TypeOf((**github.Response)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
- var ret0 *github.PullRequest
- var ret1 *github.Response
- var ret2 error
- if len(result) != 0 {
- if result[0] != nil {
- ret0 = result[0].(*github.PullRequest)
- }
- if result[1] != nil {
- ret1 = result[1].(*github.Response)
- }
- if result[2] != nil {
- ret2 = result[2].(error)
- }
- }
- return ret0, ret1, ret2
-}
-
-func (mock *MockClient) UpdateStatus(repo models.Repo, pull models.PullRequest, state string, description string, context string) error {
- params := []pegomock.Param{repo, pull, state, description, context}
+func (mock *MockClient) UpdateStatus(repo models.Repo, pull models.PullRequest, state vcs.CommitStatus, description string) error {
+ params := []pegomock.Param{repo, pull, state, description}
result := pegomock.GetGenericMockFrom(mock).Invoke("UpdateStatus", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
var ret0 error
if len(result) != 0 {
@@ -209,39 +190,8 @@ func (c *Client_PullIsApproved_OngoingVerification) GetAllCapturedArguments() (_
return
}
-func (verifier *VerifierClient) GetPullRequest(repo models.Repo, num int) *Client_GetPullRequest_OngoingVerification {
- params := []pegomock.Param{repo, num}
- methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "GetPullRequest", params)
- return &Client_GetPullRequest_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
-}
-
-type Client_GetPullRequest_OngoingVerification struct {
- mock *MockClient
- methodInvocations []pegomock.MethodInvocation
-}
-
-func (c *Client_GetPullRequest_OngoingVerification) GetCapturedArguments() (models.Repo, int) {
- repo, num := c.GetAllCapturedArguments()
- return repo[len(repo)-1], num[len(num)-1]
-}
-
-func (c *Client_GetPullRequest_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []int) {
- params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
- if len(params) > 0 {
- _param0 = make([]models.Repo, len(params[0]))
- for u, param := range params[0] {
- _param0[u] = param.(models.Repo)
- }
- _param1 = make([]int, len(params[1]))
- for u, param := range params[1] {
- _param1[u] = param.(int)
- }
- }
- return
-}
-
-func (verifier *VerifierClient) UpdateStatus(repo models.Repo, pull models.PullRequest, state string, description string, context string) *Client_UpdateStatus_OngoingVerification {
- params := []pegomock.Param{repo, pull, state, description, context}
+func (verifier *VerifierClient) UpdateStatus(repo models.Repo, pull models.PullRequest, state vcs.CommitStatus, description string) *Client_UpdateStatus_OngoingVerification {
+ params := []pegomock.Param{repo, pull, state, description}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "UpdateStatus", params)
return &Client_UpdateStatus_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
@@ -251,12 +201,12 @@ type Client_UpdateStatus_OngoingVerification struct {
methodInvocations []pegomock.MethodInvocation
}
-func (c *Client_UpdateStatus_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest, string, string, string) {
- repo, pull, state, description, context := c.GetAllCapturedArguments()
- return repo[len(repo)-1], pull[len(pull)-1], state[len(state)-1], description[len(description)-1], context[len(context)-1]
+func (c *Client_UpdateStatus_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest, vcs.CommitStatus, string) {
+ repo, pull, state, description := c.GetAllCapturedArguments()
+ return repo[len(repo)-1], pull[len(pull)-1], state[len(state)-1], description[len(description)-1]
}
-func (c *Client_UpdateStatus_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest, _param2 []string, _param3 []string, _param4 []string) {
+func (c *Client_UpdateStatus_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest, _param2 []vcs.CommitStatus, _param3 []string) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([]models.Repo, len(params[0]))
@@ -267,18 +217,14 @@ func (c *Client_UpdateStatus_OngoingVerification) GetAllCapturedArguments() (_pa
for u, param := range params[1] {
_param1[u] = param.(models.PullRequest)
}
- _param2 = make([]string, len(params[2]))
+ _param2 = make([]vcs.CommitStatus, len(params[2]))
for u, param := range params[2] {
- _param2[u] = param.(string)
+ _param2[u] = param.(vcs.CommitStatus)
}
_param3 = make([]string, len(params[3]))
for u, param := range params[3] {
_param3[u] = param.(string)
}
- _param4 = make([]string, len(params[4]))
- for u, param := range params[4] {
- _param4[u] = param.(string)
- }
}
return
}
diff --git a/server/events/vcs/mocks/mock_proxy.go b/server/events/vcs/mocks/mock_proxy.go
new file mode 100644
index 0000000000..145ebe32b5
--- /dev/null
+++ b/server/events/vcs/mocks/mock_proxy.go
@@ -0,0 +1,246 @@
+// Automatically generated by pegomock. DO NOT EDIT!
+// Source: github.com/hootsuite/atlantis/server/events/vcs (interfaces: ClientProxy)
+
+package mocks
+
+import (
+ "reflect"
+
+ models "github.com/hootsuite/atlantis/server/events/models"
+ vcs "github.com/hootsuite/atlantis/server/events/vcs"
+ pegomock "github.com/petergtz/pegomock"
+)
+
+type MockClientProxy struct {
+ fail func(message string, callerSkip ...int)
+}
+
+func NewMockClientProxy() *MockClientProxy {
+ return &MockClientProxy{fail: pegomock.GlobalFailHandler}
+}
+
+func (mock *MockClientProxy) GetModifiedFiles(repo models.Repo, pull models.PullRequest, host vcs.Host) ([]string, error) {
+ params := []pegomock.Param{repo, pull, host}
+ result := pegomock.GetGenericMockFrom(mock).Invoke("GetModifiedFiles", params, []reflect.Type{reflect.TypeOf((*[]string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
+ var ret0 []string
+ var ret1 error
+ if len(result) != 0 {
+ if result[0] != nil {
+ ret0 = result[0].([]string)
+ }
+ if result[1] != nil {
+ ret1 = result[1].(error)
+ }
+ }
+ return ret0, ret1
+}
+
+func (mock *MockClientProxy) CreateComment(repo models.Repo, pull models.PullRequest, comment string, host vcs.Host) error {
+ params := []pegomock.Param{repo, pull, comment, host}
+ result := pegomock.GetGenericMockFrom(mock).Invoke("CreateComment", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
+ var ret0 error
+ if len(result) != 0 {
+ if result[0] != nil {
+ ret0 = result[0].(error)
+ }
+ }
+ return ret0
+}
+
+func (mock *MockClientProxy) PullIsApproved(repo models.Repo, pull models.PullRequest, host vcs.Host) (bool, error) {
+ params := []pegomock.Param{repo, pull, host}
+ result := pegomock.GetGenericMockFrom(mock).Invoke("PullIsApproved", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
+ var ret0 bool
+ var ret1 error
+ if len(result) != 0 {
+ if result[0] != nil {
+ ret0 = result[0].(bool)
+ }
+ if result[1] != nil {
+ ret1 = result[1].(error)
+ }
+ }
+ return ret0, ret1
+}
+
+func (mock *MockClientProxy) UpdateStatus(repo models.Repo, pull models.PullRequest, state vcs.CommitStatus, description string, host vcs.Host) error {
+ params := []pegomock.Param{repo, pull, state, description, host}
+ result := pegomock.GetGenericMockFrom(mock).Invoke("UpdateStatus", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
+ var ret0 error
+ if len(result) != 0 {
+ if result[0] != nil {
+ ret0 = result[0].(error)
+ }
+ }
+ return ret0
+}
+
+func (mock *MockClientProxy) VerifyWasCalledOnce() *VerifierClientProxy {
+ return &VerifierClientProxy{mock, pegomock.Times(1), nil}
+}
+
+func (mock *MockClientProxy) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierClientProxy {
+ return &VerifierClientProxy{mock, invocationCountMatcher, nil}
+}
+
+func (mock *MockClientProxy) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierClientProxy {
+ return &VerifierClientProxy{mock, invocationCountMatcher, inOrderContext}
+}
+
+type VerifierClientProxy struct {
+ mock *MockClientProxy
+ invocationCountMatcher pegomock.Matcher
+ inOrderContext *pegomock.InOrderContext
+}
+
+func (verifier *VerifierClientProxy) GetModifiedFiles(repo models.Repo, pull models.PullRequest, host vcs.Host) *ClientProxy_GetModifiedFiles_OngoingVerification {
+ params := []pegomock.Param{repo, pull, host}
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "GetModifiedFiles", params)
+ return &ClientProxy_GetModifiedFiles_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+}
+
+type ClientProxy_GetModifiedFiles_OngoingVerification struct {
+ mock *MockClientProxy
+ methodInvocations []pegomock.MethodInvocation
+}
+
+func (c *ClientProxy_GetModifiedFiles_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest, vcs.Host) {
+ repo, pull, host := c.GetAllCapturedArguments()
+ return repo[len(repo)-1], pull[len(pull)-1], host[len(host)-1]
+}
+
+func (c *ClientProxy_GetModifiedFiles_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest, _param2 []vcs.Host) {
+ params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
+ if len(params) > 0 {
+ _param0 = make([]models.Repo, len(params[0]))
+ for u, param := range params[0] {
+ _param0[u] = param.(models.Repo)
+ }
+ _param1 = make([]models.PullRequest, len(params[1]))
+ for u, param := range params[1] {
+ _param1[u] = param.(models.PullRequest)
+ }
+ _param2 = make([]vcs.Host, len(params[2]))
+ for u, param := range params[2] {
+ _param2[u] = param.(vcs.Host)
+ }
+ }
+ return
+}
+
+func (verifier *VerifierClientProxy) CreateComment(repo models.Repo, pull models.PullRequest, comment string, host vcs.Host) *ClientProxy_CreateComment_OngoingVerification {
+ params := []pegomock.Param{repo, pull, comment, host}
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CreateComment", params)
+ return &ClientProxy_CreateComment_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+}
+
+type ClientProxy_CreateComment_OngoingVerification struct {
+ mock *MockClientProxy
+ methodInvocations []pegomock.MethodInvocation
+}
+
+func (c *ClientProxy_CreateComment_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest, string, vcs.Host) {
+ repo, pull, comment, host := c.GetAllCapturedArguments()
+ return repo[len(repo)-1], pull[len(pull)-1], comment[len(comment)-1], host[len(host)-1]
+}
+
+func (c *ClientProxy_CreateComment_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest, _param2 []string, _param3 []vcs.Host) {
+ params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
+ if len(params) > 0 {
+ _param0 = make([]models.Repo, len(params[0]))
+ for u, param := range params[0] {
+ _param0[u] = param.(models.Repo)
+ }
+ _param1 = make([]models.PullRequest, len(params[1]))
+ for u, param := range params[1] {
+ _param1[u] = param.(models.PullRequest)
+ }
+ _param2 = make([]string, len(params[2]))
+ for u, param := range params[2] {
+ _param2[u] = param.(string)
+ }
+ _param3 = make([]vcs.Host, len(params[3]))
+ for u, param := range params[3] {
+ _param3[u] = param.(vcs.Host)
+ }
+ }
+ return
+}
+
+func (verifier *VerifierClientProxy) PullIsApproved(repo models.Repo, pull models.PullRequest, host vcs.Host) *ClientProxy_PullIsApproved_OngoingVerification {
+ params := []pegomock.Param{repo, pull, host}
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "PullIsApproved", params)
+ return &ClientProxy_PullIsApproved_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+}
+
+type ClientProxy_PullIsApproved_OngoingVerification struct {
+ mock *MockClientProxy
+ methodInvocations []pegomock.MethodInvocation
+}
+
+func (c *ClientProxy_PullIsApproved_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest, vcs.Host) {
+ repo, pull, host := c.GetAllCapturedArguments()
+ return repo[len(repo)-1], pull[len(pull)-1], host[len(host)-1]
+}
+
+func (c *ClientProxy_PullIsApproved_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest, _param2 []vcs.Host) {
+ params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
+ if len(params) > 0 {
+ _param0 = make([]models.Repo, len(params[0]))
+ for u, param := range params[0] {
+ _param0[u] = param.(models.Repo)
+ }
+ _param1 = make([]models.PullRequest, len(params[1]))
+ for u, param := range params[1] {
+ _param1[u] = param.(models.PullRequest)
+ }
+ _param2 = make([]vcs.Host, len(params[2]))
+ for u, param := range params[2] {
+ _param2[u] = param.(vcs.Host)
+ }
+ }
+ return
+}
+
+func (verifier *VerifierClientProxy) UpdateStatus(repo models.Repo, pull models.PullRequest, state vcs.CommitStatus, description string, host vcs.Host) *ClientProxy_UpdateStatus_OngoingVerification {
+ params := []pegomock.Param{repo, pull, state, description, host}
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "UpdateStatus", params)
+ return &ClientProxy_UpdateStatus_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+}
+
+type ClientProxy_UpdateStatus_OngoingVerification struct {
+ mock *MockClientProxy
+ methodInvocations []pegomock.MethodInvocation
+}
+
+func (c *ClientProxy_UpdateStatus_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest, vcs.CommitStatus, string, vcs.Host) {
+ repo, pull, state, description, host := c.GetAllCapturedArguments()
+ return repo[len(repo)-1], pull[len(pull)-1], state[len(state)-1], description[len(description)-1], host[len(host)-1]
+}
+
+func (c *ClientProxy_UpdateStatus_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest, _param2 []vcs.CommitStatus, _param3 []string, _param4 []vcs.Host) {
+ params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
+ if len(params) > 0 {
+ _param0 = make([]models.Repo, len(params[0]))
+ for u, param := range params[0] {
+ _param0[u] = param.(models.Repo)
+ }
+ _param1 = make([]models.PullRequest, len(params[1]))
+ for u, param := range params[1] {
+ _param1[u] = param.(models.PullRequest)
+ }
+ _param2 = make([]vcs.CommitStatus, len(params[2]))
+ for u, param := range params[2] {
+ _param2[u] = param.(vcs.CommitStatus)
+ }
+ _param3 = make([]string, len(params[3]))
+ for u, param := range params[3] {
+ _param3[u] = param.(string)
+ }
+ _param4 = make([]vcs.Host, len(params[4]))
+ for u, param := range params[4] {
+ _param4[u] = param.(vcs.Host)
+ }
+ }
+ return
+}
diff --git a/server/events/vcs/not_configured_vcs_client.go b/server/events/vcs/not_configured_vcs_client.go
new file mode 100644
index 0000000000..e6dde48dec
--- /dev/null
+++ b/server/events/vcs/not_configured_vcs_client.go
@@ -0,0 +1,31 @@
+package vcs
+
+import (
+ "fmt"
+
+ "github.com/hootsuite/atlantis/server/events/models"
+)
+
+// NotConfiguredVCSClient is used as a placeholder when Atlantis isn't configured
+// on startup to support a certain VCS host. For example, if there is no GitHub
+// config then this client will be used which will error if it's ever called.
+type NotConfiguredVCSClient struct {
+ Host Host
+}
+
+func (a *NotConfiguredVCSClient) GetModifiedFiles(repo models.Repo, pull models.PullRequest) ([]string, error) {
+ return nil, a.err()
+}
+func (a *NotConfiguredVCSClient) CreateComment(repo models.Repo, pull models.PullRequest, comment string) error {
+ return a.err()
+}
+func (a *NotConfiguredVCSClient) PullIsApproved(repo models.Repo, pull models.PullRequest) (bool, error) {
+ return false, a.err()
+}
+func (a *NotConfiguredVCSClient) UpdateStatus(repo models.Repo, pull models.PullRequest, state CommitStatus, description string) error {
+ return a.err()
+}
+func (a *NotConfiguredVCSClient) err() error {
+ //noinspection GoErrorStringFormat
+ return fmt.Errorf("Atlantis was not configured to support repos from %s", a.Host.String())
+}
diff --git a/server/events/vcs/proxy.go b/server/events/vcs/proxy.go
new file mode 100644
index 0000000000..fc911a0153
--- /dev/null
+++ b/server/events/vcs/proxy.go
@@ -0,0 +1,79 @@
+package vcs
+
+import (
+ "github.com/hootsuite/atlantis/server/events/models"
+ "github.com/pkg/errors"
+)
+
+//go:generate pegomock generate --use-experimental-model-gen --package mocks -o mocks/mock_proxy.go ClientProxy
+
+// ClientProxy proxies calls to the correct VCS client depending on which
+// VCS host is required.
+type ClientProxy interface {
+ GetModifiedFiles(repo models.Repo, pull models.PullRequest, host Host) ([]string, error)
+ CreateComment(repo models.Repo, pull models.PullRequest, comment string, host Host) error
+ PullIsApproved(repo models.Repo, pull models.PullRequest, host Host) (bool, error)
+ UpdateStatus(repo models.Repo, pull models.PullRequest, state CommitStatus, description string, host Host) error
+}
+
+// DefaultClientProxy proxies calls to the correct VCS client depending on which
+// VCS host is required.
+type DefaultClientProxy struct {
+ GithubClient Client
+ GitlabClient Client
+}
+
+func NewDefaultClientProxy(githubClient Client, gitlabClient Client) *DefaultClientProxy {
+ if githubClient == nil {
+ githubClient = &NotConfiguredVCSClient{}
+ }
+ if gitlabClient == nil {
+ gitlabClient = &NotConfiguredVCSClient{}
+ }
+ return &DefaultClientProxy{
+ GitlabClient: gitlabClient,
+ GithubClient: githubClient,
+ }
+}
+
+var invalidVCSErr = errors.New("Invalid VCS Host. This is a bug!")
+
+func (d *DefaultClientProxy) GetModifiedFiles(repo models.Repo, pull models.PullRequest, host Host) ([]string, error) {
+ switch host {
+ case Github:
+ return d.GithubClient.GetModifiedFiles(repo, pull)
+ case Gitlab:
+ return d.GitlabClient.GetModifiedFiles(repo, pull)
+ }
+ return nil, invalidVCSErr
+}
+
+func (d *DefaultClientProxy) CreateComment(repo models.Repo, pull models.PullRequest, comment string, host Host) error {
+ switch host {
+ case Github:
+ return d.GithubClient.CreateComment(repo, pull, comment)
+ case Gitlab:
+ return d.GitlabClient.CreateComment(repo, pull, comment)
+ }
+ return invalidVCSErr
+}
+
+func (d *DefaultClientProxy) PullIsApproved(repo models.Repo, pull models.PullRequest, host Host) (bool, error) {
+ switch host {
+ case Github:
+ return d.GithubClient.PullIsApproved(repo, pull)
+ case Gitlab:
+ return d.GitlabClient.PullIsApproved(repo, pull)
+ }
+ return false, invalidVCSErr
+}
+
+func (d *DefaultClientProxy) UpdateStatus(repo models.Repo, pull models.PullRequest, state CommitStatus, description string, host Host) error {
+ switch host {
+ case Github:
+ return d.GithubClient.UpdateStatus(repo, pull, state, description)
+ case Gitlab:
+ return d.GitlabClient.UpdateStatus(repo, pull, state, description)
+ }
+ return invalidVCSErr
+}
diff --git a/server/events/vcs/vcs.go b/server/events/vcs/vcs.go
new file mode 100644
index 0000000000..29d0cfa7b0
--- /dev/null
+++ b/server/events/vcs/vcs.go
@@ -0,0 +1,42 @@
+package vcs
+
+type Host int
+
+const (
+ Github Host = iota
+ Gitlab
+)
+
+func (h Host) String() string {
+ switch h {
+ case Github:
+ return "Github"
+ case Gitlab:
+ return "Gitlab"
+ }
+ return ""
+}
+
+// CommitStatus is the result of executing an Atlantis command for the commit.
+// In Github the options are: error, failure, pending, success.
+// In Gitlab the options are: failed, canceled, pending, running, success.
+// We only support Failed, Pending, Success.
+type CommitStatus int
+
+const (
+ Pending CommitStatus = iota
+ Success
+ Failed
+)
+
+func (s CommitStatus) String() string {
+ switch s {
+ case Pending:
+ return "pending"
+ case Success:
+ return "success"
+ case Failed:
+ return "failed"
+ }
+ return "failed"
+}
diff --git a/server/events_controller.go b/server/events_controller.go
index 326563befc..12996a7a2d 100644
--- a/server/events_controller.go
+++ b/server/events_controller.go
@@ -6,9 +6,15 @@ import (
"github.com/google/go-github/github"
"github.com/hootsuite/atlantis/server/events"
+ "github.com/hootsuite/atlantis/server/events/models"
+ "github.com/hootsuite/atlantis/server/events/vcs"
"github.com/hootsuite/atlantis/server/logging"
+ "github.com/lkysow/go-gitlab"
)
+const githubHeader = "X-Github-Event"
+const gitlabHeader = "X-Gitlab-Event"
+
type EventsController struct {
CommandRunner events.CommandRunner
PullCleaner events.PullCleaner
@@ -17,13 +23,67 @@ type EventsController struct {
// GithubWebHookSecret is the secret added to this webhook via the GitHub
// UI that identifies this call as coming from GitHub. If empty, no
// request validation is done.
- GithubWebHookSecret []byte
- Validator GHRequestValidator
+ GithubWebHookSecret []byte
+ GithubRequestValidator GithubRequestValidator
+ GitlabRequestParser GitlabRequestParser
+ // GitlabWebHookSecret is the secret added to this webhook via the GitLab
+ // UI that identifies this call as coming from GitLab. If empty, no
+ // request validation is done.
+ GitlabWebHookSecret []byte
+ // SupportedVCSHosts is which VCS hosts Atlantis was configured upon
+ // startup to support.
+ SupportedVCSHosts []vcs.Host
}
func (e *EventsController) Post(w http.ResponseWriter, r *http.Request) {
+ if r.Header.Get(githubHeader) != "" {
+ if !e.supportsHost(vcs.Github) {
+ e.respond(w, logging.Debug, http.StatusBadRequest, "Ignoring request since not configured to support GitHub")
+ return
+ }
+ e.handleGithubPost(w, r)
+ return
+ } else if r.Header.Get(gitlabHeader) != "" {
+ if !e.supportsHost(vcs.Gitlab) {
+ e.respond(w, logging.Debug, http.StatusBadRequest, "Ignoring request since not configured to support GitLab")
+ return
+ }
+ e.handleGitlabPost(w, r)
+ return
+ }
+ e.respond(w, logging.Debug, http.StatusBadRequest, "Ignoring request")
+}
+
+// supportsHost returns true if h is in e.SupportedVCSHosts and false otherwise
+func (e *EventsController) supportsHost(h vcs.Host) bool {
+ for _, supported := range e.SupportedVCSHosts {
+ if h == supported {
+ return true
+ }
+ }
+ return false
+}
+
+func (e *EventsController) handleGitlabPost(w http.ResponseWriter, r *http.Request) {
+ event, err := e.GitlabRequestParser.Validate(r, e.GitlabWebHookSecret)
+ if err != nil {
+ e.respond(w, logging.Warn, http.StatusBadRequest, err.Error())
+ return
+ }
+ switch event := event.(type) {
+ case gitlab.MergeCommentEvent:
+ e.HandleGitlabCommentEvent(w, event)
+ case gitlab.MergeEvent:
+ e.HandleGitlabMergeRequestEvent(w, event)
+ default:
+ e.respond(w, logging.Debug, http.StatusOK, "Ignoring unsupported event")
+ }
+
+}
+
+func (e *EventsController) handleGithubPost(w http.ResponseWriter, r *http.Request) {
// Validate the request against the optional webhook secret.
- payload, err := e.Validator.Validate(r, e.GithubWebHookSecret)
+ payload, err := e.GithubRequestValidator.Validate(r, e.GithubWebHookSecret)
if err != nil {
e.respond(w, logging.Warn, http.StatusBadRequest, err.Error())
return
@@ -33,62 +93,87 @@ func (e *EventsController) Post(w http.ResponseWriter, r *http.Request) {
event, _ := github.ParseWebHook(github.WebHookType(r), payload)
switch event := event.(type) {
case *github.IssueCommentEvent:
- e.HandleCommentEvent(w, event, githubReqID)
+ e.HandleGithubCommentEvent(w, event, githubReqID)
case *github.PullRequestEvent:
- e.HandlePullRequestEvent(w, event, githubReqID)
+ e.HandleGithubPullRequestEvent(w, event, githubReqID)
default:
e.respond(w, logging.Debug, http.StatusOK, "Ignoring unsupported event %s", githubReqID)
}
}
-func (e *EventsController) HandleCommentEvent(w http.ResponseWriter, event *github.IssueCommentEvent, githubReqID string) {
+func (e *EventsController) HandleGithubCommentEvent(w http.ResponseWriter, event *github.IssueCommentEvent, githubReqID string) {
if event.GetAction() != "created" {
e.respond(w, logging.Debug, http.StatusOK, "Ignoring comment event since action was not created %s", githubReqID)
return
}
- baseRepo, user, pull, err := e.Parser.ExtractCommentData(event)
+ baseRepo, user, pullNum, err := e.Parser.ParseGithubIssueCommentEvent(event)
if err != nil {
e.respond(w, logging.Error, http.StatusBadRequest, "Failed parsing event: %v %s", err, githubReqID)
return
}
- ctx := &events.CommandContext{}
- ctx.BaseRepo = baseRepo
- ctx.User = user
- ctx.Pull = pull
- command, err := e.Parser.DetermineCommand(event)
+ command, err := e.Parser.DetermineCommand(event.Comment.GetBody(), vcs.Github)
if err != nil {
e.respond(w, logging.Debug, http.StatusOK, "Ignoring: %s %s", err, githubReqID)
return
}
- ctx.Command = command
// Respond with success and then actually execute the command asynchronously.
// We use a goroutine so that this function returns and the connection is
// closed.
fmt.Fprintln(w, "Processing...")
- go e.CommandRunner.ExecuteCommand(ctx)
+ go e.CommandRunner.ExecuteCommand(baseRepo, models.Repo{}, user, pullNum, command, vcs.Github)
+}
+
+func (e *EventsController) HandleGitlabCommentEvent(w http.ResponseWriter, event gitlab.MergeCommentEvent) {
+ baseRepo, headRepo, user := e.Parser.ParseGitlabMergeCommentEvent(event)
+ command, err := e.Parser.DetermineCommand(event.ObjectAttributes.Note, vcs.Gitlab)
+ if err != nil {
+ e.respond(w, logging.Debug, http.StatusOK, "Ignoring: %s", err)
+ return
+ }
+
+ // Respond with success and then actually execute the command asynchronously.
+ // We use a goroutine so that this function returns and the connection is
+ // closed.
+ fmt.Fprintln(w, "Processing...")
+ go e.CommandRunner.ExecuteCommand(baseRepo, headRepo, user, event.MergeRequest.IID, command, vcs.Gitlab)
+}
+
+// HandleGitlabMergeRequestEvent will delete any locks associated with the merge request
+func (e *EventsController) HandleGitlabMergeRequestEvent(w http.ResponseWriter, event gitlab.MergeEvent) {
+ pull, repo := e.Parser.ParseGitlabMergeEvent(event)
+ if pull.State != models.Closed {
+ e.respond(w, logging.Debug, http.StatusOK, "Ignoring opened merge request event")
+ return
+ }
+ if err := e.PullCleaner.CleanUpPull(repo, pull, vcs.Gitlab); err != nil {
+ e.respond(w, logging.Error, http.StatusInternalServerError, "Error cleaning pull request: %s", err)
+ return
+ }
+ e.Logger.Info("deleted locks and workspace for repo %s, pull %d", repo.FullName, pull.Num)
+ fmt.Fprintln(w, "Merge request cleaned successfully")
}
-// HandlePullRequestEvent will delete any locks associated with the pull request
-func (e *EventsController) HandlePullRequestEvent(w http.ResponseWriter, pullEvent *github.PullRequestEvent, githubReqID string) {
+// HandleGithubPullRequestEvent will delete any locks associated with the pull request
+func (e *EventsController) HandleGithubPullRequestEvent(w http.ResponseWriter, pullEvent *github.PullRequestEvent, githubReqID string) {
if pullEvent.GetAction() != "closed" {
e.respond(w, logging.Debug, http.StatusOK, "Ignoring pull request event since action was not closed %s", githubReqID)
return
}
- pull, _, err := e.Parser.ExtractPullData(pullEvent.PullRequest)
+ pull, _, err := e.Parser.ParseGithubPull(pullEvent.PullRequest)
if err != nil {
e.respond(w, logging.Error, http.StatusBadRequest, "Error parsing pull data: %s", err)
return
}
- repo, err := e.Parser.ExtractRepoData(pullEvent.Repo)
+ repo, err := e.Parser.ParseGithubRepo(pullEvent.Repo)
if err != nil {
e.respond(w, logging.Error, http.StatusBadRequest, "Error parsing repo data: %s", err)
return
}
- if err := e.PullCleaner.CleanUpPull(repo, pull); err != nil {
+ if err := e.PullCleaner.CleanUpPull(repo, pull, vcs.Github); err != nil {
e.respond(w, logging.Error, http.StatusInternalServerError, "Error cleaning pull request: %s", err)
return
}
diff --git a/server/events_controller_test.go b/server/events_controller_test.go
index 40a25418af..72bb891a2e 100644
--- a/server/events_controller_test.go
+++ b/server/events_controller_test.go
@@ -14,185 +14,303 @@ import (
"github.com/hootsuite/atlantis/server/events"
emocks "github.com/hootsuite/atlantis/server/events/mocks"
"github.com/hootsuite/atlantis/server/events/models"
+ "github.com/hootsuite/atlantis/server/events/vcs"
"github.com/hootsuite/atlantis/server/logging"
"github.com/hootsuite/atlantis/server/mocks"
- . "github.com/hootsuite/atlantis/testing"
+ "github.com/lkysow/go-gitlab"
. "github.com/petergtz/pegomock"
)
-const secret = "secret"
+const githubHeader = "X-Github-Event"
+const gitlabHeader = "X-Gitlab-Event"
+var secret = []byte("secret")
var eventsReq *http.Request
-func TestPost_InvalidSecret(t *testing.T) {
- t.Log("when the payload can't be validated a 400 is returned")
- e, v, _, _, _ := setup(t)
+func TestPost_NotGithubOrGitlab(t *testing.T) {
+ t.Log("when the request is not for gitlab or github a 400 is returned")
+ e, _, _, _, _, _ := setup(t)
w := httptest.NewRecorder()
- When(v.Validate(eventsReq, []byte(secret))).ThenReturn(nil, errors.New("err"))
+ e.Post(w, eventsReq)
+ responseContains(t, w, http.StatusBadRequest, "Ignoring request")
+}
+
+func TestPost_UnsupportedVCSGithub(t *testing.T) {
+ t.Log("when the request is for an unsupported vcs a 400 is returned")
+ e, _, _, _, _, _ := setup(t)
+ e.SupportedVCSHosts = nil
+ eventsReq.Header.Set(githubHeader, "value")
+ w := httptest.NewRecorder()
+ e.Post(w, eventsReq)
+ responseContains(t, w, http.StatusBadRequest, "Ignoring request since not configured to support GitHub")
+}
+
+func TestPost_UnsupportedVCSGitlab(t *testing.T) {
+ t.Log("when the request is for an unsupported vcs a 400 is returned")
+ e, _, _, _, _, _ := setup(t)
+ e.SupportedVCSHosts = nil
+ eventsReq.Header.Set(gitlabHeader, "value")
+ w := httptest.NewRecorder()
+ e.Post(w, eventsReq)
+ responseContains(t, w, http.StatusBadRequest, "Ignoring request since not configured to support GitLab")
+}
+
+func TestPost_InvalidGithubSecret(t *testing.T) {
+ t.Log("when the github payload can't be validated a 400 is returned")
+ e, v, _, _, _, _ := setup(t)
+ w := httptest.NewRecorder()
+ eventsReq.Header.Set(githubHeader, "value")
+ When(v.Validate(eventsReq, secret)).ThenReturn(nil, errors.New("err"))
+ e.Post(w, eventsReq)
+ responseContains(t, w, http.StatusBadRequest, "err")
+}
+
+func TestPost_InvalidGitlabSecret(t *testing.T) {
+ t.Log("when the gitlab payload can't be validated a 400 is returned")
+ e, _, gl, _, _, _ := setup(t)
+ w := httptest.NewRecorder()
+ eventsReq.Header.Set(gitlabHeader, "value")
+ When(gl.Validate(eventsReq, secret)).ThenReturn(nil, errors.New("err"))
e.Post(w, eventsReq)
responseContains(t, w, http.StatusBadRequest, "err")
}
-func TestPost_UnsupportedEvent(t *testing.T) {
- t.Log("when the event type is unsupported we ignore it")
- e, v, _, _, _ := setup(t)
+func TestPost_UnsupportedGithubEvent(t *testing.T) {
+ t.Log("when the event type is an unsupported github event we ignore it")
+ e, v, _, _, _, _ := setup(t)
w := httptest.NewRecorder()
+ eventsReq.Header.Set(githubHeader, "value")
When(v.Validate(eventsReq, nil)).ThenReturn([]byte(`{"not an event": ""}`), nil)
e.Post(w, eventsReq)
responseContains(t, w, http.StatusOK, "Ignoring unsupported event")
}
-func TestPost_CommentNotCreated(t *testing.T) {
- t.Log("when the event is a comment but it's not a created event we ignore it")
- e, v, _, _, _ := setup(t)
- eventsReq.Header.Set("X-Github-Event", "issue_comment")
+func TestPost_UnsupportedGitlabEvent(t *testing.T) {
+ t.Log("when the event type is an unsupported gitlab event we ignore it")
+ e, _, gl, _, _, _ := setup(t)
+ w := httptest.NewRecorder()
+ eventsReq.Header.Set(gitlabHeader, "value")
+ When(gl.Validate(eventsReq, secret)).ThenReturn([]byte(`{"not an event": ""}`), nil)
+ e.Post(w, eventsReq)
+ responseContains(t, w, http.StatusOK, "Ignoring unsupported event")
+}
+
+func TestPost_GithubCommentNotCreated(t *testing.T) {
+ t.Log("when the event is a github comment but it's not a created event we ignore it")
+ e, v, _, _, _, _ := setup(t)
+ eventsReq.Header.Set(githubHeader, "issue_comment")
// comment action is deleted, not created
event := `{"action": "deleted"}`
- When(v.Validate(eventsReq, []byte(secret))).ThenReturn([]byte(event), nil)
+ When(v.Validate(eventsReq, secret)).ThenReturn([]byte(event), nil)
w := httptest.NewRecorder()
e.Post(w, eventsReq)
responseContains(t, w, http.StatusOK, "Ignoring comment event since action was not created")
}
-func TestPost_CommentInvalidComment(t *testing.T) {
- t.Log("when the event is a comment without all expected data we return a 400")
- e, v, p, _, _ := setup(t)
- eventsReq.Header.Set("X-Github-Event", "issue_comment")
+func TestPost_GithubInvalidComment(t *testing.T) {
+ t.Log("when the event is a github comment without all expected data we return a 400")
+ e, v, _, p, _, _ := setup(t)
+ eventsReq.Header.Set(githubHeader, "issue_comment")
event := `{"action": "created"}`
- When(v.Validate(eventsReq, []byte(secret))).ThenReturn([]byte(event), nil)
- When(p.ExtractCommentData(AnyComment())).ThenReturn(models.Repo{}, models.User{}, models.PullRequest{}, errors.New("err"))
+ When(v.Validate(eventsReq, secret)).ThenReturn([]byte(event), nil)
+ When(p.ParseGithubIssueCommentEvent(AnyComment())).ThenReturn(models.Repo{}, models.User{}, 1, errors.New("err"))
w := httptest.NewRecorder()
e.Post(w, eventsReq)
responseContains(t, w, http.StatusBadRequest, "Failed parsing event")
}
-func TestPost_CommentInvalidCommand(t *testing.T) {
- t.Log("when the event is a comment with an invalid command we ignore it")
- e, v, p, _, _ := setup(t)
- eventsReq.Header.Set("X-Github-Event", "issue_comment")
+func TestPost_GitlabCommentInvalidCommand(t *testing.T) {
+ t.Log("when the event is a gitlab comment with an invalid command we ignore it")
+ e, _, gl, p, _, _ := setup(t)
+ eventsReq.Header.Set(gitlabHeader, "value")
+ When(gl.Validate(eventsReq, secret)).ThenReturn(gitlab.MergeCommentEvent{}, nil)
+ When(p.DetermineCommand("", vcs.Gitlab)).ThenReturn(nil, errors.New("err"))
+ w := httptest.NewRecorder()
+ e.Post(w, eventsReq)
+ responseContains(t, w, http.StatusOK, "Ignoring: err")
+}
+
+func TestPost_GithubCommentInvalidCommand(t *testing.T) {
+ t.Log("when the event is a github comment with an invalid command we ignore it")
+ e, v, _, p, _, _ := setup(t)
+ eventsReq.Header.Set(githubHeader, "issue_comment")
event := `{"action": "created"}`
- When(v.Validate(eventsReq, []byte(secret))).ThenReturn([]byte(event), nil)
- When(p.ExtractCommentData(AnyComment())).ThenReturn(models.Repo{}, models.User{}, models.PullRequest{}, nil)
- When(p.DetermineCommand(AnyComment())).ThenReturn(nil, errors.New("err"))
+ When(v.Validate(eventsReq, secret)).ThenReturn([]byte(event), nil)
+ When(p.ParseGithubIssueCommentEvent(AnyComment())).ThenReturn(models.Repo{}, models.User{}, 1, nil)
+ When(p.DetermineCommand("", vcs.Github)).ThenReturn(nil, errors.New("err"))
w := httptest.NewRecorder()
e.Post(w, eventsReq)
responseContains(t, w, http.StatusOK, "Ignoring: err")
}
-func TestPost_CommentSuccess(t *testing.T) {
- t.Log("when the event is comment with a valid command we call the command handler")
- e, v, p, cr, _ := setup(t)
- eventsReq.Header.Set("X-Github-Event", "issue_comment")
+func TestPost_GitlabCommentSuccess(t *testing.T) {
+ t.Log("when the event is a gitlab comment with a valid command we call the command handler")
+ e, _, gl, _, cr, _ := setup(t)
+ eventsReq.Header.Set(gitlabHeader, "value")
+ When(gl.Validate(eventsReq, secret)).ThenReturn(gitlab.MergeCommentEvent{}, nil)
+ w := httptest.NewRecorder()
+ e.Post(w, eventsReq)
+ responseContains(t, w, http.StatusOK, "Processing...")
+
+ // wait for 200ms so goroutine is called
+ time.Sleep(200 * time.Millisecond)
+ cr.VerifyWasCalledOnce().ExecuteCommand(models.Repo{}, models.Repo{}, models.User{}, 0, nil, vcs.Gitlab)
+}
+
+func TestPost_GithubCommentSuccess(t *testing.T) {
+ t.Log("when the event is a github comment with a valid command we call the command handler")
+ e, v, _, p, cr, _ := setup(t)
+ eventsReq.Header.Set(githubHeader, "issue_comment")
event := `{"action": "created"}`
- When(v.Validate(eventsReq, []byte(secret))).ThenReturn([]byte(event), nil)
+ When(v.Validate(eventsReq, secret)).ThenReturn([]byte(event), nil)
baseRepo := models.Repo{}
user := models.User{}
- pull := models.PullRequest{}
cmd := events.Command{}
- When(p.ExtractCommentData(AnyComment())).ThenReturn(baseRepo, user, pull, nil)
- When(p.DetermineCommand(AnyComment())).ThenReturn(&cmd, nil)
+ When(p.ParseGithubIssueCommentEvent(AnyComment())).ThenReturn(baseRepo, user, 1, nil)
+ When(p.DetermineCommand("", vcs.Github)).ThenReturn(&cmd, nil)
w := httptest.NewRecorder()
e.Post(w, eventsReq)
responseContains(t, w, http.StatusOK, "Processing...")
// wait for 200ms so goroutine is called
time.Sleep(200 * time.Millisecond)
- ctx := cr.VerifyWasCalledOnce().ExecuteCommand(AnyCommandContext()).GetCapturedArguments()
- Equals(t, baseRepo, ctx.BaseRepo)
- Equals(t, user, ctx.User)
- Equals(t, pull, ctx.Pull)
- Equals(t, cmd, *ctx.Command)
+ cr.VerifyWasCalledOnce().ExecuteCommand(baseRepo, baseRepo, user, 1, &cmd, vcs.Github)
}
-func TestPost_PullRequestNotClosed(t *testing.T) {
- t.Log("when the event is pull reuqest but it's not a closed event we ignore it")
- e, v, _, _, _ := setup(t)
- eventsReq.Header.Set("X-Github-Event", "pull_request")
+func TestPost_GithubPullRequestNotClosed(t *testing.T) {
+ t.Log("when the event is a github pull reuqest but it's not a closed event we ignore it")
+ e, v, _, _, _, _ := setup(t)
+ eventsReq.Header.Set(githubHeader, "pull_request")
event := `{"action": "opened"}`
- When(v.Validate(eventsReq, []byte(secret))).ThenReturn([]byte(event), nil)
+ When(v.Validate(eventsReq, secret)).ThenReturn([]byte(event), nil)
w := httptest.NewRecorder()
e.Post(w, eventsReq)
responseContains(t, w, http.StatusOK, "Ignoring pull request event since action was not closed")
}
-func TestPost_PullRequestInvalid(t *testing.T) {
- t.Log("when the event is pull request with invalid data we return a 400")
- e, v, p, _, _ := setup(t)
- eventsReq.Header.Set("X-Github-Event", "pull_request")
+func TestPost_GitlabMergeRequestNotClosed(t *testing.T) {
+ t.Log("when the event is a gitlab merge request but it's not a closed event we ignore it")
+ e, _, gl, p, _, _ := setup(t)
+ eventsReq.Header.Set(gitlabHeader, "value")
+ event := gitlab.MergeEvent{}
+ When(gl.Validate(eventsReq, secret)).ThenReturn(event, nil)
+ When(p.ParseGitlabMergeEvent(event)).ThenReturn(models.PullRequest{State: models.Open}, models.Repo{})
+ w := httptest.NewRecorder()
+ e.Post(w, eventsReq)
+ responseContains(t, w, http.StatusOK, "Ignoring opened merge request event")
+}
+
+func TestPost_GithubPullRequestInvalid(t *testing.T) {
+ t.Log("when the event is a github pull request with invalid data we return a 400")
+ e, v, _, p, _, _ := setup(t)
+ eventsReq.Header.Set(githubHeader, "pull_request")
event := `{"action": "closed"}`
- When(v.Validate(eventsReq, []byte(secret))).ThenReturn([]byte(event), nil)
- When(p.ExtractPullData(AnyPull())).ThenReturn(models.PullRequest{}, models.Repo{}, errors.New("err"))
+ When(v.Validate(eventsReq, secret)).ThenReturn([]byte(event), nil)
+ When(p.ParseGithubPull(AnyPull())).ThenReturn(models.PullRequest{}, models.Repo{}, errors.New("err"))
w := httptest.NewRecorder()
e.Post(w, eventsReq)
responseContains(t, w, http.StatusBadRequest, "Error parsing pull data: err")
}
-func TestPost_PullRequestInvalidRepo(t *testing.T) {
- t.Log("when the event is pull reuqest with invalid repo data we return a 400")
- e, v, p, _, _ := setup(t)
- eventsReq.Header.Set("X-Github-Event", "pull_request")
+func TestPost_GithubPullRequestInvalidRepo(t *testing.T) {
+ t.Log("when the event is a github pull request with invalid repo data we return a 400")
+ e, v, _, p, _, _ := setup(t)
+ eventsReq.Header.Set(githubHeader, "pull_request")
event := `{"action": "closed"}`
- When(v.Validate(eventsReq, []byte(secret))).ThenReturn([]byte(event), nil)
- When(p.ExtractPullData(AnyPull())).ThenReturn(models.PullRequest{}, models.Repo{}, nil)
- When(p.ExtractRepoData(AnyRepo())).ThenReturn(models.Repo{}, errors.New("err"))
+ When(v.Validate(eventsReq, secret)).ThenReturn([]byte(event), nil)
+ When(p.ParseGithubPull(AnyPull())).ThenReturn(models.PullRequest{}, models.Repo{}, nil)
+ When(p.ParseGithubRepo(AnyRepo())).ThenReturn(models.Repo{}, errors.New("err"))
w := httptest.NewRecorder()
e.Post(w, eventsReq)
responseContains(t, w, http.StatusBadRequest, "Error parsing repo data: err")
}
-func TestPost_PullRequestErrCleaningPull(t *testing.T) {
+func TestPost_GithubPullRequestErrCleaningPull(t *testing.T) {
t.Log("when the event is a pull request and we have an error calling CleanUpPull we return a 503")
RegisterMockTestingT(t)
- e, v, p, _, c := setup(t)
- eventsReq.Header.Set("X-Github-Event", "pull_request")
+ e, v, _, p, _, c := setup(t)
+ eventsReq.Header.Set(githubHeader, "pull_request")
event := `{"action": "closed"}`
- When(v.Validate(eventsReq, []byte(secret))).ThenReturn([]byte(event), nil)
+ When(v.Validate(eventsReq, secret)).ThenReturn([]byte(event), nil)
repo := models.Repo{}
pull := models.PullRequest{}
- When(p.ExtractPullData(AnyPull())).ThenReturn(pull, repo, nil)
- When(p.ExtractRepoData(AnyRepo())).ThenReturn(repo, nil)
- When(c.CleanUpPull(repo, pull)).ThenReturn(errors.New("cleanup err"))
+ When(p.ParseGithubPull(AnyPull())).ThenReturn(pull, repo, nil)
+ When(p.ParseGithubRepo(AnyRepo())).ThenReturn(repo, nil)
+ When(c.CleanUpPull(repo, pull, vcs.Github)).ThenReturn(errors.New("cleanup err"))
w := httptest.NewRecorder()
e.Post(w, eventsReq)
responseContains(t, w, http.StatusInternalServerError, "Error cleaning pull request: cleanup err")
}
-func TestPost_PullRequestSuccess(t *testing.T) {
+func TestPost_GitlabMergeRequestErrCleaningPull(t *testing.T) {
+ t.Log("when the event is a gitlab merge request and an error occurs calling CleanUpPull we return a 503")
+ e, _, gl, p, _, c := setup(t)
+ eventsReq.Header.Set(gitlabHeader, "value")
+ event := gitlab.MergeEvent{}
+ When(gl.Validate(eventsReq, secret)).ThenReturn(event, nil)
+ repo := models.Repo{}
+ pullRequest := models.PullRequest{State: models.Closed}
+ When(p.ParseGitlabMergeEvent(event)).ThenReturn(pullRequest, repo)
+ When(c.CleanUpPull(repo, pullRequest, vcs.Gitlab)).ThenReturn(errors.New("err"))
+ w := httptest.NewRecorder()
+ e.Post(w, eventsReq)
+ responseContains(t, w, http.StatusInternalServerError, "Error cleaning pull request: err")
+}
+
+func TestPost_GithubPullRequestSuccess(t *testing.T) {
t.Log("when the event is a pull request and everything works we return a 200")
- e, v, p, _, c := setup(t)
- eventsReq.Header.Set("X-Github-Event", "pull_request")
+ e, v, _, p, _, c := setup(t)
+ eventsReq.Header.Set(githubHeader, "pull_request")
event := `{"action": "closed"}`
- When(v.Validate(eventsReq, []byte(secret))).ThenReturn([]byte(event), nil)
+ When(v.Validate(eventsReq, secret)).ThenReturn([]byte(event), nil)
repo := models.Repo{}
pull := models.PullRequest{}
- When(p.ExtractPullData(AnyPull())).ThenReturn(pull, repo, nil)
- When(p.ExtractRepoData(AnyRepo())).ThenReturn(repo, nil)
- When(c.CleanUpPull(repo, pull)).ThenReturn(nil)
+ When(p.ParseGithubPull(AnyPull())).ThenReturn(pull, repo, nil)
+ When(p.ParseGithubRepo(AnyRepo())).ThenReturn(repo, nil)
+ When(c.CleanUpPull(repo, pull, vcs.Github)).ThenReturn(nil)
w := httptest.NewRecorder()
e.Post(w, eventsReq)
responseContains(t, w, http.StatusOK, "Pull request cleaned successfully")
}
-func setup(t *testing.T) (server.EventsController, *mocks.MockGHRequestValidator, *emocks.MockEventParsing, *emocks.MockCommandRunner, *emocks.MockPullCleaner) {
+func TestPost_GitlabMergeRequestSuccess(t *testing.T) {
+ t.Log("when the event is a gitlab merge request and the cleanup works we return a 200")
+ e, _, gl, p, _, _ := setup(t)
+ eventsReq.Header.Set(gitlabHeader, "value")
+ event := gitlab.MergeEvent{}
+ When(gl.Validate(eventsReq, secret)).ThenReturn(event, nil)
+ repo := models.Repo{}
+ pullRequest := models.PullRequest{State: models.Closed}
+ When(p.ParseGitlabMergeEvent(event)).ThenReturn(pullRequest, repo)
+ w := httptest.NewRecorder()
+ e.Post(w, eventsReq)
+ responseContains(t, w, http.StatusOK, "Merge request cleaned successfully")
+}
+
+func setup(t *testing.T) (server.EventsController, *mocks.MockGithubRequestValidator, *mocks.MockGitlabRequestParser, *emocks.MockEventParsing, *emocks.MockCommandRunner, *emocks.MockPullCleaner) {
RegisterMockTestingT(t)
eventsReq, _ = http.NewRequest("GET", "", bytes.NewBuffer(nil))
- v := mocks.NewMockGHRequestValidator()
+ v := mocks.NewMockGithubRequestValidator()
+ gl := mocks.NewMockGitlabRequestParser()
p := emocks.NewMockEventParsing()
cr := emocks.NewMockCommandRunner()
c := emocks.NewMockPullCleaner()
e := server.EventsController{
- Logger: logging.NewNoopLogger(),
- Validator: v,
- Parser: p,
- CommandRunner: cr,
- PullCleaner: c,
- GithubWebHookSecret: []byte(secret),
+ Logger: logging.NewNoopLogger(),
+ GithubRequestValidator: v,
+ Parser: p,
+ CommandRunner: cr,
+ PullCleaner: c,
+ GithubWebHookSecret: secret,
+ SupportedVCSHosts: []vcs.Host{vcs.Github, vcs.Gitlab},
+ GitlabWebHookSecret: secret,
+ GitlabRequestParser: gl,
}
- return e, v, p, cr, c
+ return e, v, gl, p, cr, c
}
func AnyComment() *github.IssueCommentEvent {
@@ -209,8 +327,3 @@ func AnyRepo() *github.Repository {
RegisterMatcher(NewAnyMatcher(reflect.TypeOf(&github.Repository{})))
return &github.Repository{}
}
-
-func AnyCommandContext() *events.CommandContext {
- RegisterMatcher(NewAnyMatcher(reflect.TypeOf(&events.CommandContext{})))
- return &events.CommandContext{}
-}
diff --git a/server/gh_request_validation.go b/server/github_request_validator.go
similarity index 68%
rename from server/gh_request_validation.go
rename to server/github_request_validator.go
index 2376ad8577..cf4287ed51 100644
--- a/server/gh_request_validation.go
+++ b/server/github_request_validator.go
@@ -9,10 +9,10 @@ import (
"github.com/google/go-github/github"
)
-//go:generate pegomock generate --use-experimental-model-gen --package mocks -o mocks/mock_gh_request_validation.go GHRequestValidator
+//go:generate pegomock generate --use-experimental-model-gen --package mocks -o mocks/mock_gh_request_validation.go GithubRequestValidator
-// GHRequestValidator validates GitHub requests.
-type GHRequestValidator interface {
+// GithubRequestValidator validates GitHub requests.
+type GithubRequestValidator interface {
// Validate returns the JSON payload of the request.
// If secret is not empty, it checks that the request was signed
// by secret and returns an error if it was not.
@@ -20,21 +20,21 @@ type GHRequestValidator interface {
Validate(r *http.Request, secret []byte) ([]byte, error)
}
-// GHRequestValidation implements the GHRequestValidator interface.
-type GHRequestValidation struct{}
+// DefaultGithubRequestValidator validates GitHub requests.
+type DefaultGithubRequestValidator struct{}
// Validate returns the JSON payload of the request.
// If secret is not empty, it checks that the request was signed
// by secret and returns an error if it was not.
// If secret is empty, it does not check if the request was signed.
-func (g *GHRequestValidation) Validate(r *http.Request, secret []byte) ([]byte, error) {
+func (d *DefaultGithubRequestValidator) Validate(r *http.Request, secret []byte) ([]byte, error) {
if len(secret) != 0 {
- return g.validateAgainstSecret(r, secret)
+ return d.validateAgainstSecret(r, secret)
}
- return g.validateWithoutSecret(r)
+ return d.validateWithoutSecret(r)
}
-func (g *GHRequestValidation) validateAgainstSecret(r *http.Request, secret []byte) ([]byte, error) {
+func (d *DefaultGithubRequestValidator) validateAgainstSecret(r *http.Request, secret []byte) ([]byte, error) {
payload, err := github.ValidatePayload(r, secret)
if err != nil {
return nil, err
@@ -42,7 +42,7 @@ func (g *GHRequestValidation) validateAgainstSecret(r *http.Request, secret []by
return payload, nil
}
-func (g *GHRequestValidation) validateWithoutSecret(r *http.Request) ([]byte, error) {
+func (d *DefaultGithubRequestValidator) validateWithoutSecret(r *http.Request) ([]byte, error) {
switch ct := r.Header.Get("Content-Type"); ct {
case "application/json":
payload, err := ioutil.ReadAll(r.Body)
diff --git a/server/gh_request_validation_test.go b/server/github_request_validator_test.go
similarity index 92%
rename from server/gh_request_validation_test.go
rename to server/github_request_validator_test.go
index b70c3dd86e..9e2baf7d11 100644
--- a/server/gh_request_validation_test.go
+++ b/server/github_request_validator_test.go
@@ -14,7 +14,7 @@ import (
func TestValidate_WithSecretErr(t *testing.T) {
t.Log("if the request is not valid against the secret there is an error")
RegisterMockTestingT(t)
- g := server.GHRequestValidation{}
+ g := server.DefaultGithubRequestValidator{}
buf := bytes.NewBufferString("")
req, err := http.NewRequest("POST", "http://localhost/event", buf)
Ok(t, err)
@@ -29,7 +29,7 @@ func TestValidate_WithSecretErr(t *testing.T) {
func TestValidate_WithSecret(t *testing.T) {
t.Log("if the request is valid against the secret the payload is returned")
RegisterMockTestingT(t)
- g := server.GHRequestValidation{}
+ g := server.DefaultGithubRequestValidator{}
buf := bytes.NewBufferString(`{"yo":true}`)
req, err := http.NewRequest("POST", "http://localhost/event", buf)
Ok(t, err)
@@ -44,7 +44,7 @@ func TestValidate_WithSecret(t *testing.T) {
func TestValidate_WithoutSecretInvalidContentType(t *testing.T) {
t.Log("if the request has an invalid content type an error is returned")
RegisterMockTestingT(t)
- g := server.GHRequestValidation{}
+ g := server.DefaultGithubRequestValidator{}
buf := bytes.NewBufferString("")
req, err := http.NewRequest("POST", "http://localhost/event", buf)
Ok(t, err)
@@ -58,7 +58,7 @@ func TestValidate_WithoutSecretInvalidContentType(t *testing.T) {
func TestValidate_WithoutSecretJSON(t *testing.T) {
t.Log("if the request is JSON the body is returned")
RegisterMockTestingT(t)
- g := server.GHRequestValidation{}
+ g := server.DefaultGithubRequestValidator{}
buf := bytes.NewBufferString(`{"yo":true}`)
req, err := http.NewRequest("POST", "http://localhost/event", buf)
Ok(t, err)
@@ -72,7 +72,7 @@ func TestValidate_WithoutSecretJSON(t *testing.T) {
func TestValidate_WithoutSecretFormNoPayload(t *testing.T) {
t.Log("if the request is form encoded and does not contain a payload param an error is returned")
RegisterMockTestingT(t)
- g := server.GHRequestValidation{}
+ g := server.DefaultGithubRequestValidator{}
buf := bytes.NewBufferString("")
req, err := http.NewRequest("POST", "http://localhost/event", buf)
Ok(t, err)
@@ -86,7 +86,7 @@ func TestValidate_WithoutSecretFormNoPayload(t *testing.T) {
func TestValidate_WithoutSecretForm(t *testing.T) {
t.Log("if the request is form encoded and does not contain a payload param an error is returned")
RegisterMockTestingT(t)
- g := server.GHRequestValidation{}
+ g := server.DefaultGithubRequestValidator{}
form := url.Values{}
form.Set("payload", `{"yo":true}`)
buf := bytes.NewBufferString(form.Encode())
diff --git a/server/gitlab_request_parser.go b/server/gitlab_request_parser.go
new file mode 100644
index 0000000000..f20b87bc0b
--- /dev/null
+++ b/server/gitlab_request_parser.go
@@ -0,0 +1,79 @@
+package server
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+
+ "github.com/lkysow/go-gitlab"
+)
+
+const secretHeader = "X-Gitlab-Token"
+
+//go:generate pegomock generate --use-experimental-model-gen --package mocks -o mocks/mock_gitlab_request_parser.go GitlabRequestParser
+
+// GitlabRequestParser parses and validates GitLab requests.
+type GitlabRequestParser interface {
+ // Validate validates that the request has a token header matching secret.
+ // If the secret does not match it returns an error.
+ // If secret is empty it does not check the token header.
+ // It then parses the request as a gitlab object depending on the header
+ // provided by GitLab identifying the webhook type. If the webhook type
+ // is not recognized it will return nil but will not return an error.
+ // Usage:
+ // event, err := GitlabRequestParser.Validate(r, secret)
+ // if err != nil {
+ // return
+ // }
+ // switch event := event.(type) {
+ // case gitlab.MergeCommentEvent:
+ // // handle
+ // case gitlab.MergeEvent:
+ // // handle
+ // default:
+ // // unsupported event
+ // }
+ Validate(r *http.Request, secret []byte) (interface{}, error)
+}
+
+// DefaultGitlabRequestParser parses GitLab requests.
+type DefaultGitlabRequestParser struct{}
+
+// Validate returns the JSON payload of the request.
+// If secret is not empty, it checks that the request was signed
+// by secret and returns an error if it was not.
+// If secret is empty, it does not check if the request was signed.
+func (d *DefaultGitlabRequestParser) Validate(r *http.Request, secret []byte) (interface{}, error) {
+ const mergeEventHeader = "Merge Request Hook"
+ const noteEventHeader = "Note Hook"
+
+ // Validate secret if specified.
+ headerSecret := r.Header.Get(secretHeader)
+ secretStr := string(secret)
+ if len(secret) != 0 && headerSecret != secretStr {
+ return nil, fmt.Errorf("header %s=%s did not match expected secret", secretHeader, headerSecret)
+ }
+
+ // Parse request into a gitlab object based on the object type specified
+ // in the gitlabHeader.
+ bytes, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ return nil, err
+ }
+ switch r.Header.Get(gitlabHeader) {
+ case mergeEventHeader:
+ var m gitlab.MergeEvent
+ if err := json.Unmarshal(bytes, &m); err != nil {
+ return nil, err
+ }
+ return m, nil
+ case noteEventHeader:
+ var m gitlab.MergeCommentEvent
+ if err := json.Unmarshal(bytes, &m); err != nil {
+ return nil, err
+ }
+ return m, nil
+ }
+ return nil, nil
+}
diff --git a/server/gitlab_request_parser_test.go b/server/gitlab_request_parser_test.go
new file mode 100644
index 0000000000..d3022402ce
--- /dev/null
+++ b/server/gitlab_request_parser_test.go
@@ -0,0 +1,372 @@
+package server_test
+
+import (
+ "testing"
+ "github.com/hootsuite/atlantis/server"
+ "bytes"
+ . "github.com/hootsuite/atlantis/testing"
+ . "github.com/petergtz/pegomock"
+ "net/http"
+ "github.com/lkysow/go-gitlab"
+)
+
+var parser = server.DefaultGitlabRequestParser{}
+
+func TestValidate_InvalidSecret(t *testing.T) {
+ t.Log("If the secret header is set and doesn't match expected an error is returned")
+ RegisterMockTestingT(t)
+ buf := bytes.NewBufferString("")
+ req, err := http.NewRequest("POST", "http://localhost/event", buf)
+ Ok(t, err)
+ req.Header.Set("X-Gitlab-Token", "does-not-match")
+ _, err = parser.Validate(req, []byte("secret"))
+ Assert(t, err != nil , "should be an error")
+ Equals(t, "header X-Gitlab-Token=does-not-match did not match expected secret", err.Error())
+}
+
+func TestValidate_ValidSecret(t *testing.T) {
+ t.Log("If the secret header matches then the event is returned")
+ RegisterMockTestingT(t)
+ buf := bytes.NewBufferString(mergeEventJSON)
+ req, err := http.NewRequest("POST", "http://localhost/event", buf)
+ Ok(t, err)
+ req.Header.Set("X-Gitlab-Token", "secret")
+ req.Header.Set("X-Gitlab-Event", "Merge Request Hook")
+ b, err := parser.Validate(req, []byte("secret"))
+ Ok(t, err)
+ Equals(t, "Gitlab Test", b.(gitlab.MergeEvent).Project.Name)
+}
+
+func TestValidate_NoSecret(t *testing.T) {
+ t.Log("If there is no secret then we ignore the secret header and return the event")
+ RegisterMockTestingT(t)
+ buf := bytes.NewBufferString(mergeEventJSON)
+ req, err := http.NewRequest("POST", "http://localhost/event", buf)
+ Ok(t, err)
+ req.Header.Set("X-Gitlab-Token", "random secret")
+ req.Header.Set("X-Gitlab-Event", "Merge Request Hook")
+ b, err := parser.Validate(req, nil)
+ Ok(t, err)
+ Equals(t, "Gitlab Test", b.(gitlab.MergeEvent).Project.Name)
+}
+
+func TestValidate_InvalidMergeEvent(t *testing.T) {
+ t.Log("If the merge event is malformed there should be an error")
+ RegisterMockTestingT(t)
+ buf := bytes.NewBufferString("{")
+ req, err := http.NewRequest("POST", "http://localhost/event", buf)
+ Ok(t, err)
+ req.Header.Set("X-Gitlab-Event", "Merge Request Hook")
+ _, err = parser.Validate(req, nil)
+ Assert(t, err != nil , "should be an error")
+ Equals(t, "unexpected end of JSON input", err.Error())
+}
+
+func TestValidate_InvalidMergeCommentEvent(t *testing.T) {
+ t.Log("If the merge comment event is malformed there should be an error")
+ RegisterMockTestingT(t)
+ buf := bytes.NewBufferString("{")
+ req, err := http.NewRequest("POST", "http://localhost/event", buf)
+ Ok(t, err)
+ req.Header.Set("X-Gitlab-Event", "Note Hook")
+ _, err = parser.Validate(req, nil)
+ Assert(t, err != nil , "should be an error")
+ Equals(t, "unexpected end of JSON input", err.Error())
+}
+
+func TestValidate_UnrecognizedEvent(t *testing.T) {
+ t.Log("If the event is not one we care about we return nil")
+ RegisterMockTestingT(t)
+ buf := bytes.NewBufferString("")
+ req, err := http.NewRequest("POST", "http://localhost/event", buf)
+ Ok(t, err)
+ req.Header.Set("X-Gitlab-Event", "Random Event")
+ event, err := parser.Validate(req, nil)
+ Ok(t, err)
+ Equals(t, nil, event)
+}
+
+func TestValidate_ValidMergeEvent(t *testing.T) {
+ t.Log("If the merge event is valid it should be returned")
+ RegisterMockTestingT(t)
+ buf := bytes.NewBufferString(mergeEventJSON)
+ req, err := http.NewRequest("POST", "http://localhost/event", buf)
+ Ok(t, err)
+ req.Header.Set("X-Gitlab-Event", "Merge Request Hook")
+ b, err := parser.Validate(req, nil)
+ Ok(t, err)
+ Equals(t, "Gitlab Test", b.(gitlab.MergeEvent).Project.Name)
+ RegisterMockTestingT(t)
+}
+
+func TestValidate_ValidMergeCommentEvent(t *testing.T) {
+ t.Log("If the merge comment event is valid it should be returned")
+ RegisterMockTestingT(t)
+ buf := bytes.NewBufferString(mergeCommentEventJSON)
+ req, err := http.NewRequest("POST", "http://localhost/event", buf)
+ Ok(t, err)
+ req.Header.Set("X-Gitlab-Event", "Note Hook")
+ b, err := parser.Validate(req, nil)
+ Ok(t, err)
+ Equals(t, "Gitlab Test", b.(gitlab.MergeCommentEvent).Project.Name)
+ RegisterMockTestingT(t)
+}
+
+var mergeEventJSON = `{
+ "object_kind": "merge_request",
+ "user": {
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ },
+ "project": {
+ "id": 1,
+ "name":"Gitlab Test",
+ "description":"Aut reprehenderit ut est.",
+ "web_url":"http://example.com/gitlabhq/gitlab-test",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
+ "git_http_url":"https://example.com/gitlabhq/gitlab-test.git",
+ "namespace":"GitlabHQ",
+ "visibility_level":20,
+ "path_with_namespace":"gitlabhq/gitlab-test",
+ "default_branch":"master",
+ "homepage":"http://example.com/gitlabhq/gitlab-test",
+ "url":"https://example.com/gitlabhq/gitlab-test.git",
+ "ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
+ "http_url":"https://example.com/gitlabhq/gitlab-test.git"
+ },
+ "repository": {
+ "name": "Gitlab Test",
+ "url": "https://example.com/gitlabhq/gitlab-test.git",
+ "description": "Aut reprehenderit ut est.",
+ "homepage": "http://example.com/gitlabhq/gitlab-test"
+ },
+ "object_attributes": {
+ "id": 99,
+ "target_branch": "master",
+ "source_branch": "ms-viewport",
+ "source_project_id": 14,
+ "author_id": 51,
+ "assignee_id": 6,
+ "title": "MS-Viewport",
+ "created_at": "2013-12-03T17:23:34Z",
+ "updated_at": "2013-12-03T17:23:34Z",
+ "st_commits": null,
+ "st_diffs": null,
+ "milestone_id": null,
+ "state": "opened",
+ "merge_status": "unchecked",
+ "target_project_id": 14,
+ "iid": 1,
+ "description": "",
+ "source": {
+ "name":"Awesome Project",
+ "description":"Aut reprehenderit ut est.",
+ "web_url":"http://example.com/awesome_space/awesome_project",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:awesome_space/awesome_project.git",
+ "git_http_url":"http://example.com/awesome_space/awesome_project.git",
+ "namespace":"Awesome Space",
+ "visibility_level":20,
+ "path_with_namespace":"awesome_space/awesome_project",
+ "default_branch":"master",
+ "homepage":"http://example.com/awesome_space/awesome_project",
+ "url":"http://example.com/awesome_space/awesome_project.git",
+ "ssh_url":"git@example.com:awesome_space/awesome_project.git",
+ "http_url":"http://example.com/awesome_space/awesome_project.git"
+ },
+ "target": {
+ "name":"Awesome Project",
+ "description":"Aut reprehenderit ut est.",
+ "web_url":"http://example.com/awesome_space/awesome_project",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:awesome_space/awesome_project.git",
+ "git_http_url":"http://example.com/awesome_space/awesome_project.git",
+ "namespace":"Awesome Space",
+ "visibility_level":20,
+ "path_with_namespace":"awesome_space/awesome_project",
+ "default_branch":"master",
+ "homepage":"http://example.com/awesome_space/awesome_project",
+ "url":"http://example.com/awesome_space/awesome_project.git",
+ "ssh_url":"git@example.com:awesome_space/awesome_project.git",
+ "http_url":"http://example.com/awesome_space/awesome_project.git"
+ },
+ "last_commit": {
+ "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "message": "fixed readme",
+ "timestamp": "2012-01-03T23:36:29+02:00",
+ "url": "http://example.com/awesome_space/awesome_project/commits/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "author": {
+ "name": "GitLab dev user",
+ "email": "gitlabdev@dv6700.(none)"
+ }
+ },
+ "work_in_progress": false,
+ "url": "http://example.com/diaspora/merge_requests/1",
+ "action": "open",
+ "assignee": {
+ "name": "User1",
+ "username": "user1",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ }
+ },
+ "labels": [{
+ "id": 206,
+ "title": "API",
+ "color": "#ffffff",
+ "project_id": 14,
+ "created_at": "2013-12-03T17:15:43Z",
+ "updated_at": "2013-12-03T17:15:43Z",
+ "template": false,
+ "description": "API related issues",
+ "type": "ProjectLabel",
+ "group_id": 41
+ }],
+ "changes": {
+ "updated_by_id": [null, 1],
+ "updated_at": ["2017-09-15 16:50:55 UTC", "2017-09-15 16:52:00 UTC"],
+ "labels": {
+ "previous": [{
+ "id": 206,
+ "title": "API",
+ "color": "#ffffff",
+ "project_id": 14,
+ "created_at": "2013-12-03T17:15:43Z",
+ "updated_at": "2013-12-03T17:15:43Z",
+ "template": false,
+ "description": "API related issues",
+ "type": "ProjectLabel",
+ "group_id": 41
+ }],
+ "current": [{
+ "id": 205,
+ "title": "Platform",
+ "color": "#123123",
+ "project_id": 14,
+ "created_at": "2013-12-03T17:15:43Z",
+ "updated_at": "2013-12-03T17:15:43Z",
+ "template": false,
+ "description": "Platform related issues",
+ "type": "ProjectLabel",
+ "group_id": 41
+ }]
+ }
+ }
+}`
+
+var mergeCommentEventJSON = `{
+ "object_kind": "note",
+ "user": {
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ },
+ "project_id": 5,
+ "project":{
+ "id": 5,
+ "name":"Gitlab Test",
+ "description":"Aut reprehenderit ut est.",
+ "web_url":"http://example.com/gitlabhq/gitlab-test",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
+ "git_http_url":"https://example.com/gitlabhq/gitlab-test.git",
+ "namespace":"Gitlab Org",
+ "visibility_level":10,
+ "path_with_namespace":"gitlabhq/gitlab-test",
+ "default_branch":"master",
+ "homepage":"http://example.com/gitlabhq/gitlab-test",
+ "url":"https://example.com/gitlabhq/gitlab-test.git",
+ "ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
+ "http_url":"https://example.com/gitlabhq/gitlab-test.git"
+ },
+ "repository":{
+ "name": "Gitlab Test",
+ "url": "http://localhost/gitlab-org/gitlab-test.git",
+ "description": "Aut reprehenderit ut est.",
+ "homepage": "http://example.com/gitlab-org/gitlab-test"
+ },
+ "object_attributes": {
+ "id": 1244,
+ "note": "This MR needs work.",
+ "noteable_type": "MergeRequest",
+ "author_id": 1,
+ "created_at": "2015-05-17",
+ "updated_at": "2015-05-17",
+ "project_id": 5,
+ "attachment": null,
+ "line_code": null,
+ "commit_id": "",
+ "noteable_id": 7,
+ "system": false,
+ "st_diff": null,
+ "url": "http://example.com/gitlab-org/gitlab-test/merge_requests/1#note_1244"
+ },
+ "merge_request": {
+ "id": 7,
+ "target_branch": "markdown",
+ "source_branch": "master",
+ "source_project_id": 5,
+ "author_id": 8,
+ "assignee_id": 28,
+ "title": "Tempora et eos debitis quae laborum et.",
+ "created_at": "2015-03-01 20:12:53 UTC",
+ "updated_at": "2015-03-21 18:27:27 UTC",
+ "milestone_id": 11,
+ "state": "opened",
+ "merge_status": "cannot_be_merged",
+ "target_project_id": 5,
+ "iid": 1,
+ "description": "Et voluptas corrupti assumenda temporibus. Architecto cum animi eveniet amet asperiores. Vitae numquam voluptate est natus sit et ad id.",
+ "position": 0,
+ "source":{
+ "name":"Gitlab Test",
+ "description":"Aut reprehenderit ut est.",
+ "web_url":"http://example.com/gitlab-org/gitlab-test",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:gitlab-org/gitlab-test.git",
+ "git_http_url":"https://example.com/gitlab-org/gitlab-test.git",
+ "namespace":"Gitlab Org",
+ "visibility_level":10,
+ "path_with_namespace":"gitlab-org/gitlab-test",
+ "default_branch":"master",
+ "homepage":"http://example.com/gitlab-org/gitlab-test",
+ "url":"https://example.com/gitlab-org/gitlab-test.git",
+ "ssh_url":"git@example.com:gitlab-org/gitlab-test.git",
+ "http_url":"https://example.com/gitlab-org/gitlab-test.git",
+ "git_http_url":"https://example.com/gitlab-org/gitlab-test.git"
+ },
+ "target": {
+ "name":"Gitlab Test",
+ "description":"Aut reprehenderit ut est.",
+ "web_url":"http://example.com/gitlabhq/gitlab-test",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
+ "git_http_url":"https://example.com/gitlabhq/gitlab-test.git",
+ "namespace":"Gitlab Org",
+ "visibility_level":10,
+ "path_with_namespace":"gitlabhq/gitlab-test",
+ "default_branch":"master",
+ "homepage":"http://example.com/gitlabhq/gitlab-test",
+ "url":"https://example.com/gitlabhq/gitlab-test.git",
+ "ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
+ "http_url":"https://example.com/gitlabhq/gitlab-test.git"
+ },
+ "last_commit": {
+ "id": "562e173be03b8ff2efb05345d12df18815438a4b",
+ "message": "Merge branch 'another-branch' into 'master'\n\nCheck in this test\n",
+ "timestamp": "2002-10-02T10:00:00-05:00",
+ "url": "http://example.com/gitlab-org/gitlab-test/commit/562e173be03b8ff2efb05345d12df18815438a4b",
+ "author": {
+ "name": "John Smith",
+ "email": "john@example.com"
+ }
+ },
+ "work_in_progress": false,
+ "assignee": {
+ "name": "User1",
+ "username": "user1",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ }
+ }
+}`
diff --git a/server/logging/mocks/mock_simple_logging.go b/server/logging/mocks/mock_simple_logging.go
new file mode 100644
index 0000000000..c6bb99f994
--- /dev/null
+++ b/server/logging/mocks/mock_simple_logging.go
@@ -0,0 +1,335 @@
+// Automatically generated by pegomock. DO NOT EDIT!
+// Source: github.com/hootsuite/atlantis/server/logging (interfaces: SimpleLogging)
+
+package mocks
+
+import (
+ log "log"
+ "reflect"
+
+ logging "github.com/hootsuite/atlantis/server/logging"
+ pegomock "github.com/petergtz/pegomock"
+)
+
+type MockSimpleLogging struct {
+ fail func(message string, callerSkip ...int)
+}
+
+func NewMockSimpleLogging() *MockSimpleLogging {
+ return &MockSimpleLogging{fail: pegomock.GlobalFailHandler}
+}
+
+func (mock *MockSimpleLogging) Debug(format string, a ...interface{}) {
+ params := []pegomock.Param{format}
+ for _, param := range a {
+ params = append(params, param)
+ }
+ pegomock.GetGenericMockFrom(mock).Invoke("Debug", params, []reflect.Type{})
+}
+
+func (mock *MockSimpleLogging) Info(format string, a ...interface{}) {
+ params := []pegomock.Param{format}
+ for _, param := range a {
+ params = append(params, param)
+ }
+ pegomock.GetGenericMockFrom(mock).Invoke("Info", params, []reflect.Type{})
+}
+
+func (mock *MockSimpleLogging) Warn(format string, a ...interface{}) {
+ params := []pegomock.Param{format}
+ for _, param := range a {
+ params = append(params, param)
+ }
+ pegomock.GetGenericMockFrom(mock).Invoke("Warn", params, []reflect.Type{})
+}
+
+func (mock *MockSimpleLogging) Err(format string, a ...interface{}) {
+ params := []pegomock.Param{format}
+ for _, param := range a {
+ params = append(params, param)
+ }
+ pegomock.GetGenericMockFrom(mock).Invoke("Err", params, []reflect.Type{})
+}
+
+func (mock *MockSimpleLogging) Log(level logging.LogLevel, format string, a ...interface{}) {
+ params := []pegomock.Param{level, format}
+ for _, param := range a {
+ params = append(params, param)
+ }
+ pegomock.GetGenericMockFrom(mock).Invoke("Log", params, []reflect.Type{})
+}
+
+func (mock *MockSimpleLogging) Underlying() *log.Logger {
+ params := []pegomock.Param{}
+ result := pegomock.GetGenericMockFrom(mock).Invoke("Underlying", params, []reflect.Type{reflect.TypeOf((**log.Logger)(nil)).Elem()})
+ var ret0 *log.Logger
+ if len(result) != 0 {
+ if result[0] != nil {
+ ret0 = result[0].(*log.Logger)
+ }
+ }
+ return ret0
+}
+
+func (mock *MockSimpleLogging) GetLevel() logging.LogLevel {
+ params := []pegomock.Param{}
+ result := pegomock.GetGenericMockFrom(mock).Invoke("GetLevel", params, []reflect.Type{reflect.TypeOf((*logging.LogLevel)(nil)).Elem()})
+ var ret0 logging.LogLevel
+ if len(result) != 0 {
+ if result[0] != nil {
+ ret0 = result[0].(logging.LogLevel)
+ }
+ }
+ return ret0
+}
+
+func (mock *MockSimpleLogging) VerifyWasCalledOnce() *VerifierSimpleLogging {
+ return &VerifierSimpleLogging{mock, pegomock.Times(1), nil}
+}
+
+func (mock *MockSimpleLogging) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierSimpleLogging {
+ return &VerifierSimpleLogging{mock, invocationCountMatcher, nil}
+}
+
+func (mock *MockSimpleLogging) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierSimpleLogging {
+ return &VerifierSimpleLogging{mock, invocationCountMatcher, inOrderContext}
+}
+
+type VerifierSimpleLogging struct {
+ mock *MockSimpleLogging
+ invocationCountMatcher pegomock.Matcher
+ inOrderContext *pegomock.InOrderContext
+}
+
+func (verifier *VerifierSimpleLogging) Debug(format string, a ...interface{}) *SimpleLogging_Debug_OngoingVerification {
+ params := []pegomock.Param{format}
+ for _, param := range a {
+ params = append(params, param)
+ }
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Debug", params)
+ return &SimpleLogging_Debug_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+}
+
+type SimpleLogging_Debug_OngoingVerification struct {
+ mock *MockSimpleLogging
+ methodInvocations []pegomock.MethodInvocation
+}
+
+func (c *SimpleLogging_Debug_OngoingVerification) GetCapturedArguments() (string, []interface{}) {
+ format, a := c.GetAllCapturedArguments()
+ return format[len(format)-1], a[len(a)-1]
+}
+
+func (c *SimpleLogging_Debug_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 [][]interface{}) {
+ params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
+ if len(params) > 0 {
+ _param0 = make([]string, len(params[0]))
+ for u, param := range params[0] {
+ _param0[u] = param.(string)
+ }
+ _param1 = make([][]interface{}, len(params[1]))
+ for u := range params[0] {
+ _param1[u] = make([]interface{}, len(params)-1)
+ for x := 1; x < len(params); x++ {
+ if params[x][u] != nil {
+ _param1[u][x-1] = params[x][u].(interface{})
+ }
+ }
+ }
+ }
+ return
+}
+
+func (verifier *VerifierSimpleLogging) Info(format string, a ...interface{}) *SimpleLogging_Info_OngoingVerification {
+ params := []pegomock.Param{format}
+ for _, param := range a {
+ params = append(params, param)
+ }
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Info", params)
+ return &SimpleLogging_Info_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+}
+
+type SimpleLogging_Info_OngoingVerification struct {
+ mock *MockSimpleLogging
+ methodInvocations []pegomock.MethodInvocation
+}
+
+func (c *SimpleLogging_Info_OngoingVerification) GetCapturedArguments() (string, []interface{}) {
+ format, a := c.GetAllCapturedArguments()
+ return format[len(format)-1], a[len(a)-1]
+}
+
+func (c *SimpleLogging_Info_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 [][]interface{}) {
+ params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
+ if len(params) > 0 {
+ _param0 = make([]string, len(params[0]))
+ for u, param := range params[0] {
+ _param0[u] = param.(string)
+ }
+ _param1 = make([][]interface{}, len(params[1]))
+ for u := range params[0] {
+ _param1[u] = make([]interface{}, len(params)-1)
+ for x := 1; x < len(params); x++ {
+ if params[x][u] != nil {
+ _param1[u][x-1] = params[x][u].(interface{})
+ }
+ }
+ }
+ }
+ return
+}
+
+func (verifier *VerifierSimpleLogging) Warn(format string, a ...interface{}) *SimpleLogging_Warn_OngoingVerification {
+ params := []pegomock.Param{format}
+ for _, param := range a {
+ params = append(params, param)
+ }
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Warn", params)
+ return &SimpleLogging_Warn_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+}
+
+type SimpleLogging_Warn_OngoingVerification struct {
+ mock *MockSimpleLogging
+ methodInvocations []pegomock.MethodInvocation
+}
+
+func (c *SimpleLogging_Warn_OngoingVerification) GetCapturedArguments() (string, []interface{}) {
+ format, a := c.GetAllCapturedArguments()
+ return format[len(format)-1], a[len(a)-1]
+}
+
+func (c *SimpleLogging_Warn_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 [][]interface{}) {
+ params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
+ if len(params) > 0 {
+ _param0 = make([]string, len(params[0]))
+ for u, param := range params[0] {
+ _param0[u] = param.(string)
+ }
+ _param1 = make([][]interface{}, len(params[1]))
+ for u := range params[0] {
+ _param1[u] = make([]interface{}, len(params)-1)
+ for x := 1; x < len(params); x++ {
+ if params[x][u] != nil {
+ _param1[u][x-1] = params[x][u].(interface{})
+ }
+ }
+ }
+ }
+ return
+}
+
+func (verifier *VerifierSimpleLogging) Err(format string, a ...interface{}) *SimpleLogging_Err_OngoingVerification {
+ params := []pegomock.Param{format}
+ for _, param := range a {
+ params = append(params, param)
+ }
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Err", params)
+ return &SimpleLogging_Err_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+}
+
+type SimpleLogging_Err_OngoingVerification struct {
+ mock *MockSimpleLogging
+ methodInvocations []pegomock.MethodInvocation
+}
+
+func (c *SimpleLogging_Err_OngoingVerification) GetCapturedArguments() (string, []interface{}) {
+ format, a := c.GetAllCapturedArguments()
+ return format[len(format)-1], a[len(a)-1]
+}
+
+func (c *SimpleLogging_Err_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 [][]interface{}) {
+ params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
+ if len(params) > 0 {
+ _param0 = make([]string, len(params[0]))
+ for u, param := range params[0] {
+ _param0[u] = param.(string)
+ }
+ _param1 = make([][]interface{}, len(params[1]))
+ for u := range params[0] {
+ _param1[u] = make([]interface{}, len(params)-1)
+ for x := 1; x < len(params); x++ {
+ if params[x][u] != nil {
+ _param1[u][x-1] = params[x][u].(interface{})
+ }
+ }
+ }
+ }
+ return
+}
+
+func (verifier *VerifierSimpleLogging) Log(level logging.LogLevel, format string, a ...interface{}) *SimpleLogging_Log_OngoingVerification {
+ params := []pegomock.Param{level, format}
+ for _, param := range a {
+ params = append(params, param)
+ }
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Log", params)
+ return &SimpleLogging_Log_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+}
+
+type SimpleLogging_Log_OngoingVerification struct {
+ mock *MockSimpleLogging
+ methodInvocations []pegomock.MethodInvocation
+}
+
+func (c *SimpleLogging_Log_OngoingVerification) GetCapturedArguments() (logging.LogLevel, string, []interface{}) {
+ level, format, a := c.GetAllCapturedArguments()
+ return level[len(level)-1], format[len(format)-1], a[len(a)-1]
+}
+
+func (c *SimpleLogging_Log_OngoingVerification) GetAllCapturedArguments() (_param0 []logging.LogLevel, _param1 []string, _param2 [][]interface{}) {
+ params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
+ if len(params) > 0 {
+ _param0 = make([]logging.LogLevel, len(params[0]))
+ for u, param := range params[0] {
+ _param0[u] = param.(logging.LogLevel)
+ }
+ _param1 = make([]string, len(params[1]))
+ for u, param := range params[1] {
+ _param1[u] = param.(string)
+ }
+ _param2 = make([][]interface{}, len(params[2]))
+ for u := range params[0] {
+ _param2[u] = make([]interface{}, len(params)-2)
+ for x := 2; x < len(params); x++ {
+ if params[x][u] != nil {
+ _param2[u][x-2] = params[x][u].(interface{})
+ }
+ }
+ }
+ }
+ return
+}
+
+func (verifier *VerifierSimpleLogging) Underlying() *SimpleLogging_Underlying_OngoingVerification {
+ params := []pegomock.Param{}
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Underlying", params)
+ return &SimpleLogging_Underlying_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+}
+
+type SimpleLogging_Underlying_OngoingVerification struct {
+ mock *MockSimpleLogging
+ methodInvocations []pegomock.MethodInvocation
+}
+
+func (c *SimpleLogging_Underlying_OngoingVerification) GetCapturedArguments() {
+}
+
+func (c *SimpleLogging_Underlying_OngoingVerification) GetAllCapturedArguments() {
+}
+
+func (verifier *VerifierSimpleLogging) GetLevel() *SimpleLogging_GetLevel_OngoingVerification {
+ params := []pegomock.Param{}
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "GetLevel", params)
+ return &SimpleLogging_GetLevel_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+}
+
+type SimpleLogging_GetLevel_OngoingVerification struct {
+ mock *MockSimpleLogging
+ methodInvocations []pegomock.MethodInvocation
+}
+
+func (c *SimpleLogging_GetLevel_OngoingVerification) GetCapturedArguments() {
+}
+
+func (c *SimpleLogging_GetLevel_OngoingVerification) GetAllCapturedArguments() {
+}
diff --git a/server/logging/simple_logger.go b/server/logging/simple_logger.go
index 5fd65b394a..16f897fdaf 100644
--- a/server/logging/simple_logger.go
+++ b/server/logging/simple_logger.go
@@ -10,9 +10,23 @@ import (
"unicode"
)
+//go:generate pegomock generate --use-experimental-model-gen --package mocks -o mocks/mock_simple_logging.go SimpleLogging
+
+type SimpleLogging interface {
+ Debug(format string, a ...interface{})
+ Info(format string, a ...interface{})
+ Warn(format string, a ...interface{})
+ Err(format string, a ...interface{})
+ Log(level LogLevel, format string, a ...interface{})
+ // Underlying returns the underlying logger.
+ Underlying() *log.Logger
+ // GetLevel returns the current log level.
+ GetLevel() LogLevel
+}
+
// SimpleLogger wraps the standard logger with leveled logging
// and the ability to store log history for later adding it
-// to a GitHub comment.
+// to a VCS comment.
type SimpleLogger struct {
// Source is added as a prefix to each log entry.
// It's useful if you want to trace a log entry back to a
@@ -126,6 +140,14 @@ func (l *SimpleLogger) Log(level LogLevel, format string, a ...interface{}) {
}
}
+func (l *SimpleLogger) Underlying() *log.Logger {
+ return l.Logger
+}
+
+func (l *SimpleLogger) GetLevel() LogLevel {
+ return l.Level
+}
+
func (l *SimpleLogger) saveToHistory(level string, msg string) {
l.History.WriteString(fmt.Sprintf("[%s] %s\n", level, msg))
}
diff --git a/server/mocks/mock_gh_request_validation.go b/server/mocks/mock_gh_request_validation.go
index 76ba0ffe3b..f3c2e2dace 100644
--- a/server/mocks/mock_gh_request_validation.go
+++ b/server/mocks/mock_gh_request_validation.go
@@ -1,23 +1,24 @@
// Automatically generated by pegomock. DO NOT EDIT!
-// Source: github.com/hootsuite/atlantis/server (interfaces: GHRequestValidator)
+// Source: github.com/hootsuite/atlantis/server (interfaces: GithubRequestValidator)
package mocks
import (
- pegomock "github.com/petergtz/pegomock"
http "net/http"
"reflect"
+
+ pegomock "github.com/petergtz/pegomock"
)
-type MockGHRequestValidator struct {
+type MockGithubRequestValidator struct {
fail func(message string, callerSkip ...int)
}
-func NewMockGHRequestValidator() *MockGHRequestValidator {
- return &MockGHRequestValidator{fail: pegomock.GlobalFailHandler}
+func NewMockGithubRequestValidator() *MockGithubRequestValidator {
+ return &MockGithubRequestValidator{fail: pegomock.GlobalFailHandler}
}
-func (mock *MockGHRequestValidator) Validate(r *http.Request, secret []byte) ([]byte, error) {
+func (mock *MockGithubRequestValidator) Validate(r *http.Request, secret []byte) ([]byte, error) {
params := []pegomock.Param{r, secret}
result := pegomock.GetGenericMockFrom(mock).Invoke("Validate", params, []reflect.Type{reflect.TypeOf((*[]byte)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
var ret0 []byte
@@ -33,41 +34,41 @@ func (mock *MockGHRequestValidator) Validate(r *http.Request, secret []byte) ([]
return ret0, ret1
}
-func (mock *MockGHRequestValidator) VerifyWasCalledOnce() *VerifierGHRequestValidator {
- return &VerifierGHRequestValidator{mock, pegomock.Times(1), nil}
+func (mock *MockGithubRequestValidator) VerifyWasCalledOnce() *VerifierGithubRequestValidator {
+ return &VerifierGithubRequestValidator{mock, pegomock.Times(1), nil}
}
-func (mock *MockGHRequestValidator) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierGHRequestValidator {
- return &VerifierGHRequestValidator{mock, invocationCountMatcher, nil}
+func (mock *MockGithubRequestValidator) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierGithubRequestValidator {
+ return &VerifierGithubRequestValidator{mock, invocationCountMatcher, nil}
}
-func (mock *MockGHRequestValidator) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierGHRequestValidator {
- return &VerifierGHRequestValidator{mock, invocationCountMatcher, inOrderContext}
+func (mock *MockGithubRequestValidator) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierGithubRequestValidator {
+ return &VerifierGithubRequestValidator{mock, invocationCountMatcher, inOrderContext}
}
-type VerifierGHRequestValidator struct {
- mock *MockGHRequestValidator
+type VerifierGithubRequestValidator struct {
+ mock *MockGithubRequestValidator
invocationCountMatcher pegomock.Matcher
inOrderContext *pegomock.InOrderContext
}
-func (verifier *VerifierGHRequestValidator) Validate(r *http.Request, secret []byte) *GHRequestValidator_Validate_OngoingVerification {
+func (verifier *VerifierGithubRequestValidator) Validate(r *http.Request, secret []byte) *GithubRequestValidator_Validate_OngoingVerification {
params := []pegomock.Param{r, secret}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Validate", params)
- return &GHRequestValidator_Validate_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+ return &GithubRequestValidator_Validate_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
-type GHRequestValidator_Validate_OngoingVerification struct {
- mock *MockGHRequestValidator
+type GithubRequestValidator_Validate_OngoingVerification struct {
+ mock *MockGithubRequestValidator
methodInvocations []pegomock.MethodInvocation
}
-func (c *GHRequestValidator_Validate_OngoingVerification) GetCapturedArguments() (*http.Request, []byte) {
+func (c *GithubRequestValidator_Validate_OngoingVerification) GetCapturedArguments() (*http.Request, []byte) {
r, secret := c.GetAllCapturedArguments()
return r[len(r)-1], secret[len(secret)-1]
}
-func (c *GHRequestValidator_Validate_OngoingVerification) GetAllCapturedArguments() (_param0 []*http.Request, _param1 [][]byte) {
+func (c *GithubRequestValidator_Validate_OngoingVerification) GetAllCapturedArguments() (_param0 []*http.Request, _param1 [][]byte) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([]*http.Request, len(params[0]))
diff --git a/server/mocks/mock_gitlab_request_parser.go b/server/mocks/mock_gitlab_request_parser.go
new file mode 100644
index 0000000000..2f6e1143a0
--- /dev/null
+++ b/server/mocks/mock_gitlab_request_parser.go
@@ -0,0 +1,83 @@
+// Automatically generated by pegomock. DO NOT EDIT!
+// Source: github.com/hootsuite/atlantis/server (interfaces: GitlabRequestParser)
+
+package mocks
+
+import (
+ pegomock "github.com/petergtz/pegomock"
+ http "net/http"
+ "reflect"
+)
+
+type MockGitlabRequestParser struct {
+ fail func(message string, callerSkip ...int)
+}
+
+func NewMockGitlabRequestParser() *MockGitlabRequestParser {
+ return &MockGitlabRequestParser{fail: pegomock.GlobalFailHandler}
+}
+
+func (mock *MockGitlabRequestParser) Validate(r *http.Request, secret []byte) (interface{}, error) {
+ params := []pegomock.Param{r, secret}
+ result := pegomock.GetGenericMockFrom(mock).Invoke("Validate", params, []reflect.Type{reflect.TypeOf((*interface{})(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
+ var ret0 interface{}
+ var ret1 error
+ if len(result) != 0 {
+ if result[0] != nil {
+ ret0 = result[0].(interface{})
+ }
+ if result[1] != nil {
+ ret1 = result[1].(error)
+ }
+ }
+ return ret0, ret1
+}
+
+func (mock *MockGitlabRequestParser) VerifyWasCalledOnce() *VerifierGitlabRequestParser {
+ return &VerifierGitlabRequestParser{mock, pegomock.Times(1), nil}
+}
+
+func (mock *MockGitlabRequestParser) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierGitlabRequestParser {
+ return &VerifierGitlabRequestParser{mock, invocationCountMatcher, nil}
+}
+
+func (mock *MockGitlabRequestParser) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierGitlabRequestParser {
+ return &VerifierGitlabRequestParser{mock, invocationCountMatcher, inOrderContext}
+}
+
+type VerifierGitlabRequestParser struct {
+ mock *MockGitlabRequestParser
+ invocationCountMatcher pegomock.Matcher
+ inOrderContext *pegomock.InOrderContext
+}
+
+func (verifier *VerifierGitlabRequestParser) Validate(r *http.Request, secret []byte) *GitlabRequestParser_Validate_OngoingVerification {
+ params := []pegomock.Param{r, secret}
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Validate", params)
+ return &GitlabRequestParser_Validate_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+}
+
+type GitlabRequestParser_Validate_OngoingVerification struct {
+ mock *MockGitlabRequestParser
+ methodInvocations []pegomock.MethodInvocation
+}
+
+func (c *GitlabRequestParser_Validate_OngoingVerification) GetCapturedArguments() (*http.Request, []byte) {
+ r, secret := c.GetAllCapturedArguments()
+ return r[len(r)-1], secret[len(secret)-1]
+}
+
+func (c *GitlabRequestParser_Validate_OngoingVerification) GetAllCapturedArguments() (_param0 []*http.Request, _param1 [][]byte) {
+ params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
+ if len(params) > 0 {
+ _param0 = make([]*http.Request, len(params[0]))
+ for u, param := range params[0] {
+ _param0[u] = param.(*http.Request)
+ }
+ _param1 = make([][]byte, len(params[1]))
+ for u, param := range params[1] {
+ _param1[u] = param.([]byte)
+ }
+ }
+ return
+}
diff --git a/server/server.go b/server/server.go
index cf60f117b3..aa1e76425c 100644
--- a/server/server.go
+++ b/server/server.go
@@ -13,21 +13,24 @@ import (
"github.com/elazarl/go-bindata-assetfs"
"github.com/gorilla/mux"
"github.com/hootsuite/atlantis/server/events"
- "github.com/hootsuite/atlantis/server/events/github"
"github.com/hootsuite/atlantis/server/events/locking"
"github.com/hootsuite/atlantis/server/events/locking/boltdb"
"github.com/hootsuite/atlantis/server/events/run"
"github.com/hootsuite/atlantis/server/events/terraform"
+ "github.com/hootsuite/atlantis/server/events/vcs"
"github.com/hootsuite/atlantis/server/logging"
"github.com/hootsuite/atlantis/server/static"
+ "github.com/lkysow/go-gitlab"
"github.com/pkg/errors"
"github.com/urfave/cli"
"github.com/urfave/negroni"
+ "flag"
)
const LockRouteName = "lock-detail"
-// Server listens for GitHub events and runs the necessary Atlantis command
+// Server runs the Atlantis web server. It's used for webhook requests and the
+// Atlantis UI.
type Server struct {
Router *mux.Router
Port int
@@ -50,22 +53,43 @@ type Config struct {
GithubToken string `mapstructure:"gh-token"`
GithubUser string `mapstructure:"gh-user"`
GithubWebHookSecret string `mapstructure:"gh-webhook-secret"`
+ GitlabHostname string `mapstructure:"gitlab-hostname"`
+ GitlabToken string `mapstructure:"gitlab-token"`
+ GitlabUser string `mapstructure:"gitlab-user"`
+ GitlabWebHookSecret string `mapstructure:"gitlab-webhook-secret"`
LogLevel string `mapstructure:"log-level"`
Port int `mapstructure:"port"`
RequireApproval bool `mapstructure:"require-approval"`
}
func NewServer(config Config) (*Server, error) {
- githubClient, err := github.NewClient(config.GithubHostname, config.GithubUser, config.GithubToken)
- if err != nil {
- return nil, err
+ var supportedVCSHosts []vcs.Host
+ var githubClient *vcs.GithubClient
+ var gitlabClient *vcs.GitlabClient
+ if config.GithubUser != "" {
+ supportedVCSHosts = append(supportedVCSHosts, vcs.Github)
+ var err error
+ githubClient, err = vcs.NewGithubClient(config.GithubHostname, config.GithubUser, config.GithubToken)
+ if err != nil {
+ return nil, err
+ }
}
- githubStatus := &events.GithubStatus{Client: githubClient}
+ if config.GitlabUser != "" {
+ supportedVCSHosts = append(supportedVCSHosts, vcs.Gitlab)
+ gitlabClient = &vcs.GitlabClient{
+ Client: gitlab.NewClient(nil, config.GitlabToken),
+ }
+ }
+ vcsClient := vcs.NewDefaultClientProxy(githubClient, gitlabClient)
+ commitStatusUpdater := &events.DefaultCommitStatusUpdater{Client: vcsClient}
terraformClient, err := terraform.NewClient()
- if err != nil {
+ // The flag.Lookup call is to detect if we're running in a unit test. If we
+ // are, then we don't error out because we don't have/want terraform
+ // installed on our CI system where the unit tests run.
+ if err != nil && flag.Lookup("test.v") == nil {
return nil, errors.Wrap(err, "initializing terraform")
}
- githubComments := &events.GithubCommentRenderer{}
+ markdownRenderer := &events.MarkdownRenderer{}
boltdb, err := boltdb.New(config.DataDir)
if err != nil {
return nil, err
@@ -84,7 +108,7 @@ func NewServer(config Config) (*Server, error) {
Terraform: terraformClient,
}
applyExecutor := &events.ApplyExecutor{
- Github: githubClient,
+ VCSClient: vcsClient,
Terraform: terraformClient,
RequireApproval: config.RequireApproval,
Run: run,
@@ -92,17 +116,17 @@ func NewServer(config Config) (*Server, error) {
ProjectPreExecute: projectPreExecute,
}
planExecutor := &events.PlanExecutor{
- Github: githubClient,
+ VCSClient: vcsClient,
Terraform: terraformClient,
Run: run,
Workspace: workspace,
ProjectPreExecute: projectPreExecute,
Locker: lockingClient,
- ModifiedProject: &events.ProjectFinder{},
+ ProjectFinder: &events.ProjectFinder{},
}
helpExecutor := &events.HelpExecutor{}
pullClosedExecutor := &events.PullClosedExecutor{
- Github: githubClient,
+ VCSClient: vcsClient,
Locker: lockingClient,
Workspace: workspace,
}
@@ -110,26 +134,33 @@ func NewServer(config Config) (*Server, error) {
eventParser := &events.EventParser{
GithubUser: config.GithubUser,
GithubToken: config.GithubToken,
+ GitlabUser: config.GitlabUser,
+ GitlabToken: config.GitlabToken,
}
commandHandler := &events.CommandHandler{
- ApplyExecutor: applyExecutor,
- PlanExecutor: planExecutor,
- HelpExecutor: helpExecutor,
- LockURLGenerator: planExecutor,
- EventParser: eventParser,
- GHClient: githubClient,
- GHStatus: githubStatus,
- EnvLocker: concurrentRunLocker,
- GHCommentRenderer: githubComments,
- Logger: logger,
+ ApplyExecutor: applyExecutor,
+ PlanExecutor: planExecutor,
+ HelpExecutor: helpExecutor,
+ LockURLGenerator: planExecutor,
+ EventParser: eventParser,
+ VCSClient: vcsClient,
+ GithubPullGetter: githubClient,
+ GitlabMergeRequestGetter: gitlabClient,
+ CommitStatusUpdater: commitStatusUpdater,
+ EnvLocker: concurrentRunLocker,
+ MarkdownRenderer: markdownRenderer,
+ Logger: logger,
}
eventsController := &EventsController{
- CommandRunner: commandHandler,
- PullCleaner: pullClosedExecutor,
- Parser: eventParser,
- Logger: logger,
- GithubWebHookSecret: []byte(config.GithubWebHookSecret),
- Validator: &GHRequestValidation{},
+ CommandRunner: commandHandler,
+ PullCleaner: pullClosedExecutor,
+ Parser: eventParser,
+ Logger: logger,
+ GithubWebHookSecret: []byte(config.GithubWebHookSecret),
+ GithubRequestValidator: &DefaultGithubRequestValidator{},
+ GitlabRequestParser: &DefaultGitlabRequestParser{},
+ GitlabWebHookSecret: []byte(config.GitlabWebHookSecret),
+ SupportedVCSHosts: supportedVCSHosts,
}
router := mux.NewRouter()
return &Server{
@@ -270,7 +301,7 @@ func (s *Server) DeleteLock(w http.ResponseWriter, _ *http.Request, id string) {
}
// postEvents handles POST requests to our /events endpoint. These should be
-// GitHub webhook requests.
+// VCS webhook requests.
func (s *Server) postEvents(w http.ResponseWriter, r *http.Request) {
s.EventsController.Post(w, r)
}
diff --git a/server/server_test.go b/server/server_test.go
index 0576226204..63c4012bf3 100644
--- a/server/server_test.go
+++ b/server/server_test.go
@@ -20,6 +20,16 @@ import (
. "github.com/petergtz/pegomock"
)
+func TestNewServer(t *testing.T) {
+ t.Log("Run through NewServer constructor")
+ tmpDir, err := ioutil.TempDir("", "")
+ Ok(t, err)
+ _, err = server.NewServer(server.Config{
+ DataDir: tmpDir,
+ })
+ Ok(t, err)
+}
+
func TestIndex_LockErr(t *testing.T) {
t.Log("index should return a 503 if unable to list locks")
RegisterMockTestingT(t)
diff --git a/vendor/github.com/lkysow/go-gitlab/.gitignore b/vendor/github.com/lkysow/go-gitlab/.gitignore
new file mode 100644
index 0000000000..daf913b1b3
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/.gitignore
@@ -0,0 +1,24 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
diff --git a/vendor/github.com/lkysow/go-gitlab/.travis.yml b/vendor/github.com/lkysow/go-gitlab/.travis.yml
new file mode 100644
index 0000000000..f1d4ebc91b
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/.travis.yml
@@ -0,0 +1,5 @@
+language: go
+
+go:
+ - 1.7.x
+ - 1.8.x
diff --git a/vendor/github.com/lkysow/go-gitlab/CHANGELOG.md b/vendor/github.com/lkysow/go-gitlab/CHANGELOG.md
new file mode 100644
index 0000000000..29e93fff7f
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/CHANGELOG.md
@@ -0,0 +1,27 @@
+go-github CHANGELOG
+===================
+
+0.6.0
+-----
+- Add support for the V4 Gitlab API. This means the older V3 API is no longer fully supported
+ with this version. If you still need that version, please use the `f-api-v3` branch.
+
+0.4.0
+-----
+- Add support to use [`sudo`](https://docs.gitlab.com/ce/api/README.html#sudo) for all API calls.
+- Add support for the Notification Settings API.
+- Add support for the Time Tracking API.
+- Make sure that the error response correctly outputs any returned errors.
+- And a reasonable number of smaller enhanchements and bugfixes.
+
+0.3.0
+-----
+- Moved the tags related API calls to their own service, following the Gitlab API structure.
+
+0.2.0
+-----
+- Convert all Option structs to use pointers for their fields.
+
+0.1.0
+-----
+- Initial release.
diff --git a/vendor/github.com/lkysow/go-gitlab/LICENSE b/vendor/github.com/lkysow/go-gitlab/LICENSE
new file mode 100644
index 0000000000..e06d208186
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/LICENSE
@@ -0,0 +1,202 @@
+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.
+
diff --git a/vendor/github.com/lkysow/go-gitlab/README.md b/vendor/github.com/lkysow/go-gitlab/README.md
new file mode 100644
index 0000000000..dbbc10725a
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/README.md
@@ -0,0 +1,129 @@
+# go-gitlab
+
+A GitLab API client enabling Go programs to interact with GitLab in a simple and uniform way
+
+**Documentation:** [![GoDoc](https://godoc.org/github.com/xanzy/go-gitlab?status.svg)](https://godoc.org/github.com/xanzy/go-gitlab)
+**Build Status:** [![Build Status](https://travis-ci.org/xanzy/go-gitlab.svg?branch=master)](https://travis-ci.org/xanzy/go-gitlab)
+
+## NOTE
+
+Release v0.6.0 (released on 25-08-2017) no longer supports the older V3 Gitlab API. If
+you need V3 support, please use the `f-api-v3` branch. This release contains some backwards
+incompatible changes that were needed to fully support the V4 Gitlab API.
+
+## Coverage
+
+This API client package covers **100%** of the existing GitLab API calls! So this
+includes all calls to the following services:
+
+- [x] Branches
+- [x] Commits
+- [x] Deploy Keys
+- [x] Environments
+- [x] Groups
+- [x] Issues
+- [x] Labels
+- [x] Merge Requests
+- [x] Milestones
+- [x] Namespaces
+- [x] Notes (comments)
+- [x] Pipelines
+- [x] Project Snippets
+- [x] Projects (including setting Webhooks)
+- [x] Repositories
+- [x] Repository Files
+- [x] Services
+- [x] Session
+- [x] Settings
+- [x] System Hooks
+- [x] Users
+- [x] Version
+- [x] Wikis
+
+## Usage
+
+```go
+import "github.com/xanzy/go-gitlab"
+```
+
+Construct a new GitLab client, then use the various services on the client to
+access different parts of the GitLab API. For example, to list all
+users:
+
+```go
+git := gitlab.NewClient(nil, "yourtokengoeshere")
+//git.SetBaseURL("https://git.mydomain.com/api/v3")
+users, _, err := git.Users.ListUsers()
+```
+
+Some API methods have optional parameters that can be passed. For example,
+to list all projects for user "svanharmelen":
+
+```go
+git := gitlab.NewClient(nil)
+opt := &ListProjectsOptions{Search: gitlab.String("svanharmelen")}
+projects, _, err := git.Projects.ListProjects(opt)
+```
+
+### Examples
+
+The [examples](https://github.com/xanzy/go-gitlab/tree/master/examples) directory
+contains a couple for clear examples, of which one is partially listed here as well:
+
+```go
+package main
+
+import (
+ "log"
+
+ "github.com/xanzy/go-gitlab"
+)
+
+func main() {
+ git := gitlab.NewClient(nil, "yourtokengoeshere")
+
+ // Create new project
+ p := &gitlab.CreateProjectOptions{
+ Name: gitlab.String("My Project"),
+ Description: gitlab.String("Just a test project to play with"),
+ MergeRequestsEnabled: gitlab.Bool(true),
+ SnippetsEnabled: gitlab.Bool(true),
+ Visibility: gitlab.VisibilityLevel(gitlab.PublicVisibility),
+ }
+ project, _, err := git.Projects.CreateProject(p)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Add a new snippet
+ s := &gitlab.CreateSnippetOptions{
+ Title: gitlab.String("Dummy Snippet"),
+ FileName: gitlab.String("snippet.go"),
+ Code: gitlab.String("package main...."),
+ Visibility: gitlab.VisibilityLevel(gitlab.PublicVisibility),
+ }
+ _, _, err = git.ProjectSnippets.CreateSnippet(project.ID, s)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+```
+
+For complete usage of go-gitlab, see the full [package docs](https://godoc.org/github.com/xanzy/go-gitlab).
+
+## ToDo
+
+- The biggest thing this package still needs is tests :disappointed:
+
+## Issues
+
+- If you have an issue: report it on the [issue tracker](https://github.com/xanzy/go-gitlab/issues)
+
+## Author
+
+Sander van Harmelen ()
+
+## License
+
+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
diff --git a/vendor/github.com/lkysow/go-gitlab/branches.go b/vendor/github.com/lkysow/go-gitlab/branches.go
new file mode 100644
index 0000000000..a792e3f423
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/branches.go
@@ -0,0 +1,240 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// 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.
+//
+
+package gitlab
+
+import (
+ "fmt"
+ "net/url"
+)
+
+// BranchesService handles communication with the branch related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/branches.html
+type BranchesService struct {
+ client *Client
+}
+
+// Branch represents a GitLab branch.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/branches.html
+type Branch struct {
+ Commit *Commit `json:"commit"`
+ Name string `json:"name"`
+ Protected bool `json:"protected"`
+ Merged bool `json:"merged"`
+ DevelopersCanPush bool `json:"developers_can_push"`
+ DevelopersCanMerge bool `json:"developers_can_merge"`
+}
+
+func (b Branch) String() string {
+ return Stringify(b)
+}
+
+// ListBranchesOptions represents the available ListBranches() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/branches.html#list-repository-branches
+type ListBranchesOptions struct {
+ ListOptions
+}
+
+// ListBranches gets a list of repository branches from a project, sorted by
+// name alphabetically.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/branches.html#list-repository-branches
+func (s *BranchesService) ListBranches(pid interface{}, opts *ListBranchesOptions, options ...OptionFunc) ([]*Branch, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/branches", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, opts, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var b []*Branch
+ resp, err := s.client.Do(req, &b)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return b, resp, err
+}
+
+// GetBranch gets a single project repository branch.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/branches.html#get-single-repository-branch
+func (s *BranchesService) GetBranch(pid interface{}, branch string, options ...OptionFunc) (*Branch, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/branches/%s", url.QueryEscape(project), branch)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ b := new(Branch)
+ resp, err := s.client.Do(req, b)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return b, resp, err
+}
+
+// ProtectBranchOptions represents the available ProtectBranch() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/branches.html#protect-repository-branch
+type ProtectBranchOptions struct {
+ DevelopersCanPush *bool `url:"developers_can_push,omitempty" json:"developers_can_push,omitempty"`
+ DevelopersCanMerge *bool `url:"developers_can_merge,omitempty" json:"developers_can_merge,omitempty"`
+}
+
+// ProtectBranch protects a single project repository branch. This is an
+// idempotent function, protecting an already protected repository branch
+// still returns a 200 OK status code.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/branches.html#protect-repository-branch
+func (s *BranchesService) ProtectBranch(pid interface{}, branch string, opts *ProtectBranchOptions, options ...OptionFunc) (*Branch, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/branches/%s/protect", url.QueryEscape(project), branch)
+
+ req, err := s.client.NewRequest("PUT", u, opts, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ b := new(Branch)
+ resp, err := s.client.Do(req, b)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return b, resp, err
+}
+
+// UnprotectBranch unprotects a single project repository branch. This is an
+// idempotent function, unprotecting an already unprotected repository branch
+// still returns a 200 OK status code.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/branches.html#unprotect-repository-branch
+func (s *BranchesService) UnprotectBranch(pid interface{}, branch string, options ...OptionFunc) (*Branch, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/branches/%s/unprotect", url.QueryEscape(project), branch)
+
+ req, err := s.client.NewRequest("PUT", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ b := new(Branch)
+ resp, err := s.client.Do(req, b)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return b, resp, err
+}
+
+// CreateBranchOptions represents the available CreateBranch() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/branches.html#create-repository-branch
+type CreateBranchOptions struct {
+ Branch *string `url:"branch,omitempty" json:"branch,omitempty"`
+ Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
+}
+
+// CreateBranch creates branch from commit SHA or existing branch.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/branches.html#create-repository-branch
+func (s *BranchesService) CreateBranch(pid interface{}, opt *CreateBranchOptions, options ...OptionFunc) (*Branch, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/branches", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ b := new(Branch)
+ resp, err := s.client.Do(req, b)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return b, resp, err
+}
+
+// DeleteBranch deletes an existing branch.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/branches.html#delete-repository-branch
+func (s *BranchesService) DeleteBranch(pid interface{}, branch string, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/branches/%s", url.QueryEscape(project), branch)
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// DeleteMergedBranches deletes all branches that are merged into the project's default branch.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/branches.html#delete-merged-branches
+func (s *BranchesService) DeleteMergedBranches(pid interface{}, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/merged_branches", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/build_variables.go b/vendor/github.com/lkysow/go-gitlab/build_variables.go
new file mode 100644
index 0000000000..465042d89c
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/build_variables.go
@@ -0,0 +1,175 @@
+package gitlab
+
+import (
+ "fmt"
+ "net/url"
+)
+
+// BuildVariablesService handles communication with the project variables related methods
+// of the Gitlab API
+//
+// Gitlab API Docs : https://docs.gitlab.com/ce/api/build_variables.html
+type BuildVariablesService struct {
+ client *Client
+}
+
+// BuildVariable represents a variable available for each build of the given project
+//
+// Gitlab API Docs : https://docs.gitlab.com/ce/api/build_variables.html
+type BuildVariable struct {
+ Key string `json:"key"`
+ Value string `json:"value"`
+ Protected bool `json:"protected"`
+}
+
+func (v BuildVariable) String() string {
+ return Stringify(v)
+}
+
+// ListBuildVariablesOptions are the parameters to ListBuildVariables()
+//
+// Gitlab API Docs:
+// https://docs.gitlab.com/ce/api/build_variables.html#list-project-variables
+type ListBuildVariablesOptions struct {
+ ListOptions
+}
+
+// ListBuildVariables gets the a list of project variables in a project
+//
+// Gitlab API Docs:
+// https://docs.gitlab.com/ce/api/build_variables.html#list-project-variables
+func (s *BuildVariablesService) ListBuildVariables(pid interface{}, opts *ListBuildVariablesOptions, options ...OptionFunc) ([]*BuildVariable, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/variables", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, opts, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var v []*BuildVariable
+ resp, err := s.client.Do(req, &v)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return v, resp, err
+}
+
+// GetBuildVariable gets a single project variable of a project
+//
+// Gitlab API Docs:
+// https://docs.gitlab.com/ce/api/build_variables.html#show-variable-details
+func (s *BuildVariablesService) GetBuildVariable(pid interface{}, key string, options ...OptionFunc) (*BuildVariable, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/variables/%s", url.QueryEscape(project), key)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ v := new(BuildVariable)
+ resp, err := s.client.Do(req, v)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return v, resp, err
+}
+
+// CreateBuildVariableOptions are the parameters to CreateBuildVariable()
+//
+// Gitlab API Docs:
+// https://docs.gitlab.com/ce/api/build_variables.html#create-variable
+type CreateBuildVariableOptions struct {
+ Key *string `url:"key" json:"key"`
+ Value *string `url:"value" json:"value"`
+ Protected *bool `url:"protected,omitempty" json:"protected,omitempty"`
+}
+
+// CreateBuildVariable creates a variable for a given project
+//
+// Gitlab API Docs:
+// https://docs.gitlab.com/ce/api/build_variables.html#create-variable
+func (s *BuildVariablesService) CreateBuildVariable(pid interface{}, opt *CreateBuildVariableOptions, options ...OptionFunc) (*BuildVariable, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/variables", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ v := new(BuildVariable)
+ resp, err := s.client.Do(req, v)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return v, resp, err
+}
+
+// UpdateBuildVariableOptions are the parameters to UpdateBuildVariable()
+//
+// Gitlab API Docs:
+// https://docs.gitlab.com/ce/api/build_variables.html#update-variable
+type UpdateBuildVariableOptions struct {
+ Key *string `url:"key" json:"key"`
+ Value *string `url:"value" json:"value"`
+ Protected *bool `url:"protected,omitempty" json:"protected,omitempty"`
+}
+
+// UpdateBuildVariable updates an existing project variable
+// The variable key must exist
+//
+// Gitlab API Docs:
+// https://docs.gitlab.com/ce/api/build_variables.html#update-variable
+func (s *BuildVariablesService) UpdateBuildVariable(pid interface{}, key string, opt *UpdateBuildVariableOptions, options ...OptionFunc) (*BuildVariable, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/variables/%s", url.QueryEscape(project), key)
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ v := new(BuildVariable)
+ resp, err := s.client.Do(req, v)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return v, resp, err
+}
+
+// RemoveBuildVariable removes a project variable of a given project identified by its key
+//
+// Gitlab API Docs:
+// https://docs.gitlab.com/ce/api/build_variables.html#remove-variable
+func (s *BuildVariablesService) RemoveBuildVariable(pid interface{}, key string, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/variables/%s", url.QueryEscape(project), key)
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/build_variables_test.go b/vendor/github.com/lkysow/go-gitlab/build_variables_test.go
new file mode 100644
index 0000000000..0485515856
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/build_variables_test.go
@@ -0,0 +1,113 @@
+package gitlab
+
+import (
+ "fmt"
+ "net/http"
+ "reflect"
+ "testing"
+)
+
+const (
+ myKey = "MY_KEY"
+ myValue = "MY_VALUE"
+ myKey2 = "MY_KEY2"
+ myValue2 = "MY_VALUE2"
+ myNewValue = "MY_NEW_VALUE"
+)
+
+func TestListBuildVariables(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects/1/variables", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "GET")
+ fmt.Fprintf(w,
+ `[{"key":"%s","value":"%s"},{"key":"%s","value":"%s"}]`, myKey, myValue, myKey2, myValue2)
+ })
+
+ variables, _, err := client.BuildVariables.ListBuildVariables(1, nil)
+ if err != nil {
+ t.Errorf("ListBuildVariables returned error: %v", err)
+ }
+
+ want := []*BuildVariable{{Key: myKey, Value: myValue}, {Key: myKey2, Value: myValue2}}
+ if !reflect.DeepEqual(want, variables) {
+ t.Errorf("ListBuildVariables returned %+v, want %+v", variables, want)
+ }
+}
+
+func TestGetBuildVariable(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects/1/variables/"+myKey, func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "GET")
+ fmt.Fprintf(w, `{"key":"%s","value":"%s"}`, myKey, myValue)
+ })
+
+ variable, _, err := client.BuildVariables.GetBuildVariable(1, myKey)
+ if err != nil {
+ t.Errorf("GetBuildVariable returned error: %v", err)
+ }
+
+ want := &BuildVariable{Key: myKey, Value: myValue}
+ if !reflect.DeepEqual(want, variable) {
+ t.Errorf("GetBuildVariable returned %+v, want %+v", variable, want)
+ }
+}
+
+func TestCreateBuildVariable(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects/1/variables", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "POST")
+ fmt.Fprintf(w, `{"key":"%s","value":"%s", "protected": false}`, myKey, myValue)
+ })
+
+ opt := &CreateBuildVariableOptions{String(myKey), String(myValue), Bool(false)}
+ variable, _, err := client.BuildVariables.CreateBuildVariable(1, opt)
+ if err != nil {
+ t.Errorf("CreateBuildVariable returned error: %v", err)
+ }
+
+ want := &BuildVariable{Key: myKey, Value: myValue, Protected: false}
+ if !reflect.DeepEqual(want, variable) {
+ t.Errorf("CreateBuildVariable returned %+v, want %+v", variable, want)
+ }
+}
+
+func TestUpdateBuildVariable(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects/1/variables/"+myKey, func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "PUT")
+ fmt.Fprintf(w, `{"key":"%s","value":"%s", "protected": false}`, myKey, myNewValue)
+ })
+
+ opt := &UpdateBuildVariableOptions{String(myKey), String(myNewValue), Bool(false)}
+ variable, _, err := client.BuildVariables.UpdateBuildVariable(1, myKey, opt)
+ if err != nil {
+ t.Errorf("UpdateBuildVariable returned error: %v", err)
+ }
+
+ want := &BuildVariable{Key: myKey, Value: myNewValue, Protected: false}
+ if !reflect.DeepEqual(want, variable) {
+ t.Errorf("UpdateBuildVariable returned %+v, want %+v", variable, want)
+ }
+}
+
+func TestRemoveBuildVariable(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects/1/variables/"+myKey, func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "DELETE")
+ })
+
+ _, err := client.BuildVariables.RemoveBuildVariable(1, myKey)
+ if err != nil {
+ t.Errorf("RemoveBuildVariable returned error: %v", err)
+ }
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/commits.go b/vendor/github.com/lkysow/go-gitlab/commits.go
new file mode 100644
index 0000000000..3f4db783cf
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/commits.go
@@ -0,0 +1,444 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// 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.
+//
+
+package gitlab
+
+import (
+ "fmt"
+ "net/url"
+ "time"
+)
+
+// CommitsService handles communication with the commit related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html
+type CommitsService struct {
+ client *Client
+}
+
+// Commit represents a GitLab commit.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html
+type Commit struct {
+ ID string `json:"id"`
+ ShortID string `json:"short_id"`
+ Title string `json:"title"`
+ AuthorName string `json:"author_name"`
+ AuthorEmail string `json:"author_email"`
+ AuthoredDate *time.Time `json:"authored_date"`
+ CommitterName string `json:"committer_name"`
+ CommitterEmail string `json:"committer_email"`
+ CommittedDate *time.Time `json:"committed_date"`
+ CreatedAt *time.Time `json:"created_at"`
+ Message string `json:"message"`
+ ParentIDs []string `json:"parent_ids"`
+ Stats *CommitStats `json:"stats"`
+ Status *BuildState `json:"status"`
+}
+
+// CommitStats represents the number of added and deleted files in a commit.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html
+type CommitStats struct {
+ Additions int `json:"additions"`
+ Deletions int `json:"deletions"`
+ Total int `json:"total"`
+}
+
+func (c Commit) String() string {
+ return Stringify(c)
+}
+
+// ListCommitsOptions represents the available ListCommits() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#list-repository-commits
+type ListCommitsOptions struct {
+ ListOptions
+ RefName *string `url:"ref_name,omitempty" json:"ref_name,omitempty"`
+ Since time.Time `url:"since,omitempty" json:"since,omitempty"`
+ Until time.Time `url:"until,omitempty" json:"until,omitempty"`
+}
+
+// ListCommits gets a list of repository commits in a project.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#list-commits
+func (s *CommitsService) ListCommits(pid interface{}, opt *ListCommitsOptions, options ...OptionFunc) ([]*Commit, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/commits", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var c []*Commit
+ resp, err := s.client.Do(req, &c)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return c, resp, err
+}
+
+// FileAction represents the available actions that can be performed on a file.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
+type FileAction string
+
+// The available file actions.
+const (
+ FileCreate FileAction = "create"
+ FileDelete FileAction = "delete"
+ FileMove FileAction = "move"
+ FileUpdate FileAction = "update"
+)
+
+// CommitAction represents a single file action within a commit.
+type CommitAction struct {
+ Action FileAction `url:"action" json:"action"`
+ FilePath string `url:"file_path" json:"file_path"`
+ PreviousPath string `url:"previous_path,omitempty" json:"previous_path,omitempty"`
+ Content string `url:"content,omitempty" json:"content,omitempty"`
+ Encoding string `url:"encoding,omitempty" json:"encoding,omitempty"`
+}
+
+// GetCommit gets a specific commit identified by the commit hash or name of a
+// branch or tag.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#get-a-single-commit
+func (s *CommitsService) GetCommit(pid interface{}, sha string, options ...OptionFunc) (*Commit, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/commits/%s", url.QueryEscape(project), sha)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ c := new(Commit)
+ resp, err := s.client.Do(req, c)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return c, resp, err
+}
+
+// CreateCommitOptions represents the available options for a new commit.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
+type CreateCommitOptions struct {
+ Branch *string `url:"branch" json:"branch"`
+ CommitMessage *string `url:"commit_message" json:"commit_message"`
+ Actions []*CommitAction `url:"actions" json:"actions"`
+ AuthorEmail *string `url:"author_email,omitempty" json:"author_email,omitempty"`
+ AuthorName *string `url:"author_name,omitempty" json:"author_name,omitempty"`
+}
+
+// CreateCommit creates a commit with multiple files and actions.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
+func (s *CommitsService) CreateCommit(pid interface{}, opt *CreateCommitOptions, options ...OptionFunc) (*Commit, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/commits", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var c *Commit
+ resp, err := s.client.Do(req, &c)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return c, resp, err
+}
+
+// Diff represents a GitLab diff.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html
+type Diff struct {
+ Diff string `json:"diff"`
+ NewPath string `json:"new_path"`
+ OldPath string `json:"old_path"`
+ AMode string `json:"a_mode"`
+ BMode string `json:"b_mode"`
+ NewFile bool `json:"new_file"`
+ RenamedFile bool `json:"renamed_file"`
+ DeletedFile bool `json:"deleted_file"`
+}
+
+func (d Diff) String() string {
+ return Stringify(d)
+}
+
+// GetCommitDiff gets the diff of a commit in a project..
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/commits.html#get-the-diff-of-a-commit
+func (s *CommitsService) GetCommitDiff(pid interface{}, sha string, options ...OptionFunc) ([]*Diff, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/commits/%s/diff", url.QueryEscape(project), sha)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var d []*Diff
+ resp, err := s.client.Do(req, &d)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return d, resp, err
+}
+
+// CommitComment represents a GitLab commit comment.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html
+type CommitComment struct {
+ Note string `json:"note"`
+ Path string `json:"path"`
+ Line int `json:"line"`
+ LineType string `json:"line_type"`
+ Author Author `json:"author"`
+}
+
+// Author represents a GitLab commit author
+type Author struct {
+ ID int `json:"id"`
+ Username string `json:"username"`
+ Email string `json:"email"`
+ Name string `json:"name"`
+ State string `json:"state"`
+ Blocked bool `json:"blocked"`
+ CreatedAt *time.Time `json:"created_at"`
+}
+
+func (c CommitComment) String() string {
+ return Stringify(c)
+}
+
+// GetCommitComments gets the comments of a commit in a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/commits.html#get-the-comments-of-a-commit
+func (s *CommitsService) GetCommitComments(pid interface{}, sha string, options ...OptionFunc) ([]*CommitComment, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/commits/%s/comments", url.QueryEscape(project), sha)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var c []*CommitComment
+ resp, err := s.client.Do(req, &c)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return c, resp, err
+}
+
+// PostCommitCommentOptions represents the available PostCommitComment()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/commits.html#post-comment-to-commit
+type PostCommitCommentOptions struct {
+ Note *string `url:"note,omitempty" json:"note,omitempty"`
+ Path *string `url:"path" json:"path"`
+ Line *int `url:"line" json:"line"`
+ LineType *string `url:"line_type" json:"line_type"`
+}
+
+// PostCommitComment adds a comment to a commit. Optionally you can post
+// comments on a specific line of a commit. Therefor both path, line_new and
+// line_old are required.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/commits.html#post-comment-to-commit
+func (s *CommitsService) PostCommitComment(pid interface{}, sha string, opt *PostCommitCommentOptions, options ...OptionFunc) (*CommitComment, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/commits/%s/comments", url.QueryEscape(project), sha)
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ c := new(CommitComment)
+ resp, err := s.client.Do(req, c)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return c, resp, err
+}
+
+// GetCommitStatusesOptions represents the available GetCommitStatuses() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#get-the-status-of-a-commit
+type GetCommitStatusesOptions struct {
+ Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
+ Stage *string `url:"stage,omitempty" json:"stage,omitempty"`
+ Name *string `url:"name,omitempty" json:"name,omitempty"`
+ All *bool `url:"all,omitempty" json:"all,omitempty"`
+}
+
+// CommitStatus represents a GitLab commit status.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#get-the-status-of-a-commit
+type CommitStatus struct {
+ ID int `json:"id"`
+ SHA string `json:"sha"`
+ Ref string `json:"ref"`
+ Status string `json:"status"`
+ Name string `json:"name"`
+ TargetURL string `json:"target_url"`
+ Description string `json:"description"`
+ CreatedAt *time.Time `json:"created_at"`
+ StartedAt *time.Time `json:"started_at"`
+ FinishedAt *time.Time `json:"finished_at"`
+ Author Author `json:"author"`
+}
+
+// GetCommitStatuses gets the statuses of a commit in a project.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#get-the-status-of-a-commit
+func (s *CommitsService) GetCommitStatuses(pid interface{}, sha string, opt *GetCommitStatusesOptions, options ...OptionFunc) ([]*CommitStatus, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/commits/%s/statuses", url.QueryEscape(project), sha)
+
+ req, err := s.client.NewRequest("GET", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var cs []*CommitStatus
+ resp, err := s.client.Do(req, &cs)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return cs, resp, err
+}
+
+// SetCommitStatusOptions represents the available SetCommitStatus() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#post-the-status-to-commit
+type SetCommitStatusOptions struct {
+ State BuildState `url:"state" json:"state"`
+ Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
+ Name *string `url:"name,omitempty" json:"name,omitempty"`
+ Context *string `url:"context,omitempty" json:"context,omitempty"`
+ TargetURL *string `url:"target_url,omitempty" json:"target_url,omitempty"`
+ Description *string `url:"description,omitempty" json:"description,omitempty"`
+}
+
+// BuildState represents a GitLab build state.
+type BuildState string
+
+// These constants represent all valid build states.
+const (
+ Pending BuildState = "pending"
+ Running BuildState = "running"
+ Success BuildState = "success"
+ Failed BuildState = "failed"
+ Canceled BuildState = "canceled"
+)
+
+// SetCommitStatus sets the status of a commit in a project.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#post-the-status-to-commit
+func (s *CommitsService) SetCommitStatus(pid interface{}, sha string, opt *SetCommitStatusOptions, options ...OptionFunc) (*CommitStatus, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/statuses/%s", url.QueryEscape(project), sha)
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var cs *CommitStatus
+ resp, err := s.client.Do(req, &cs)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return cs, resp, err
+}
+
+// CherryPickCommitOptions represents the available options for cherry-picking a commit.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#cherry-pick-a-commit
+type CherryPickCommitOptions struct {
+ TargetBranch *string `url:"branch" json:"branch,omitempty"`
+}
+
+// CherryPickCommit sherry picks a commit to a given branch.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#cherry-pick-a-commit
+func (s *CommitsService) CherryPickCommit(pid interface{}, sha string, opt *CherryPickCommitOptions, options ...OptionFunc) (*Commit, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/commits/%s/cherry_pick",
+ url.QueryEscape(project), url.QueryEscape(sha))
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var c *Commit
+ resp, err := s.client.Do(req, &c)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return c, resp, err
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/commits_test.go b/vendor/github.com/lkysow/go-gitlab/commits_test.go
new file mode 100644
index 0000000000..ef645d4f6e
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/commits_test.go
@@ -0,0 +1,52 @@
+package gitlab
+
+import (
+ "fmt"
+ "net/http"
+ "reflect"
+ "testing"
+)
+
+func TestGetCommitStatuses(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects/1/repository/commits/b0b3a907f41409829b307a28b82fdbd552ee5a27/statuses", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "GET")
+ fmt.Fprint(w, `[{"id":1}]`)
+ })
+
+ opt := &GetCommitStatusesOptions{String("master"), String("test"), String("ci/jenkins"), Bool(true)}
+ statuses, _, err := client.Commits.GetCommitStatuses("1", "b0b3a907f41409829b307a28b82fdbd552ee5a27", opt)
+
+ if err != nil {
+ t.Errorf("Commits.GetCommitStatuses returned error: %v", err)
+ }
+
+ want := []*CommitStatus{{ID: 1}}
+ if !reflect.DeepEqual(want, statuses) {
+ t.Errorf("Commits.GetCommitStatuses returned %+v, want %+v", statuses, want)
+ }
+}
+
+func TestSetCommitStatus(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects/1/statuses/b0b3a907f41409829b307a28b82fdbd552ee5a27", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "POST")
+ fmt.Fprint(w, `{"id":1}`)
+ })
+
+ opt := &SetCommitStatusOptions{Running, String("master"), String("ci/jenkins"), String(""), String("http://abc"), String("build")}
+ status, _, err := client.Commits.SetCommitStatus("1", "b0b3a907f41409829b307a28b82fdbd552ee5a27", opt)
+
+ if err != nil {
+ t.Errorf("Commits.SetCommitStatus returned error: %v", err)
+ }
+
+ want := &CommitStatus{ID: 1}
+ if !reflect.DeepEqual(want, status) {
+ t.Errorf("Commits.SetCommitStatus returned %+v, want %+v", status, want)
+ }
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/deploy_keys.go b/vendor/github.com/lkysow/go-gitlab/deploy_keys.go
new file mode 100644
index 0000000000..248ce3f9e9
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/deploy_keys.go
@@ -0,0 +1,194 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// 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.
+//
+
+package gitlab
+
+import (
+ "fmt"
+ "net/url"
+ "time"
+)
+
+// DeployKeysService handles communication with the keys related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/deploy_keys.html
+type DeployKeysService struct {
+ client *Client
+}
+
+// DeployKey represents a GitLab deploy key.
+type DeployKey struct {
+ ID int `json:"id"`
+ Title string `json:"title"`
+ Key string `json:"key"`
+ CanPush *bool `json:"can_push"`
+ CreatedAt *time.Time `json:"created_at"`
+}
+
+func (k DeployKey) String() string {
+ return Stringify(k)
+}
+
+// ListAllDeployKeys gets a list of all deploy keys
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/deploy_keys.html#list-all-deploy-keys
+func (s *DeployKeysService) ListAllDeployKeys(options ...OptionFunc) ([]*DeployKey, *Response, error) {
+ req, err := s.client.NewRequest("GET", "deploy_keys", nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var ks []*DeployKey
+ resp, err := s.client.Do(req, &ks)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return ks, resp, err
+}
+
+// ListProjectDeployKeys gets a list of a project's deploy keys
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/deploy_keys.html#list-project-deploy-keys
+func (s *DeployKeysService) ListProjectDeployKeys(pid interface{}, options ...OptionFunc) ([]*DeployKey, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/deploy_keys", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var ks []*DeployKey
+ resp, err := s.client.Do(req, &ks)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return ks, resp, err
+}
+
+// GetDeployKey gets a single deploy key.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/deploy_keys.html#single-deploy-key
+func (s *DeployKeysService) GetDeployKey(pid interface{}, deployKey int, options ...OptionFunc) (*DeployKey, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/deploy_keys/%d", url.QueryEscape(project), deployKey)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ k := new(DeployKey)
+ resp, err := s.client.Do(req, k)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return k, resp, err
+}
+
+// AddDeployKeyOptions represents the available ADDDeployKey() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/deploy_keys.html#add-deploy-key
+type AddDeployKeyOptions struct {
+ Title *string `url:"title,omitempty" json:"title,omitempty"`
+ Key *string `url:"key,omitempty" json:"key,omitempty"`
+ CanPush *bool `url:"can_push,omitempty" json:"can_push,omitempty"`
+}
+
+// AddDeployKey creates a new deploy key for a project. If deploy key already
+// exists in another project - it will be joined to project but only if
+// original one was is accessible by same user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/deploy_keys.html#add-deploy-key
+func (s *DeployKeysService) AddDeployKey(pid interface{}, opt *AddDeployKeyOptions, options ...OptionFunc) (*DeployKey, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/deploy_keys", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ k := new(DeployKey)
+ resp, err := s.client.Do(req, k)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return k, resp, err
+}
+
+// DeleteDeployKey deletes a deploy key from a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/deploy_keys.html#delete-deploy-key
+func (s *DeployKeysService) DeleteDeployKey(pid interface{}, deployKey int, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/deploy_keys/%d", url.QueryEscape(project), deployKey)
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// EnableDeployKey enables a deploy key.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/deploy_keys.html#enable-deploy-key
+func (s *DeployKeysService) EnableDeployKey(pid interface{}, deployKey int, options ...OptionFunc) (*DeployKey, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/deploy_keys/%d/enable", url.QueryEscape(project), deployKey)
+
+ req, err := s.client.NewRequest("POST", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ k := new(DeployKey)
+ resp, err := s.client.Do(req, k)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return k, resp, err
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/environments.go b/vendor/github.com/lkysow/go-gitlab/environments.go
new file mode 100644
index 0000000000..2a75f57f92
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/environments.go
@@ -0,0 +1,168 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// 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.
+//
+
+package gitlab
+
+import (
+ "fmt"
+ "net/url"
+)
+
+// EnvironmentsService handles communication with the environment related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/environments.html
+type EnvironmentsService struct {
+ client *Client
+}
+
+// Environment represents a GitLab environment.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/environments.html
+type Environment struct {
+ ID int `json:"id"`
+ Name string `json:"name"`
+ Slug string `json:"slug"`
+ ExternalURL string `json:"external_url"`
+}
+
+func (env Environment) String() string {
+ return Stringify(env)
+}
+
+// ListEnvironmentsOptions represents the available ListEnvironments() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/environments.html#list-environments
+type ListEnvironmentsOptions struct {
+ ListOptions
+}
+
+// ListEnvironments gets a list of environments from a project, sorted by name
+// alphabetically.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/environments.html#list-environments
+func (s *EnvironmentsService) ListEnvironments(pid interface{}, opts *ListEnvironmentsOptions, options ...OptionFunc) ([]*Environment, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/environments", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, opts, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var envs []*Environment
+ resp, err := s.client.Do(req, &envs)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return envs, resp, err
+}
+
+// CreateEnvironmentOptions represents the available CreateEnvironment() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/environments.html#create-a-new-environment
+type CreateEnvironmentOptions struct {
+ Name *string `url:"name,omitempty" json:"name,omitempty"`
+ ExternalURL *string `url:"external_url,omitempty" json:"external_url,omitempty"`
+}
+
+// CreateEnvironment adds a environment to a project. This is an idempotent
+// method and can be called multiple times with the same parameters. Createing
+// an environment that is already a environment does not affect the
+// existing environmentship.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/environments.html#create-a-new-environment
+func (s *EnvironmentsService) CreateEnvironment(pid interface{}, opt *CreateEnvironmentOptions, options ...OptionFunc) (*Environment, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/environments", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ env := new(Environment)
+ resp, err := s.client.Do(req, env)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return env, resp, err
+}
+
+// EditEnvironmentOptions represents the available EditEnvironment() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/environments.html#edit-an-existing-environment
+type EditEnvironmentOptions struct {
+ Name *string `url:"name,omitempty" json:"name,omitempty"`
+ ExternalURL *string `url:"external_url,omitempty" json:"external_url,omitempty"`
+}
+
+// EditEnvironment updates a project team environment to a specified access level..
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/environments.html#edit-an-existing-environment
+func (s *EnvironmentsService) EditEnvironment(pid interface{}, environment int, opt *EditEnvironmentOptions, options ...OptionFunc) (*Environment, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/environments/%d", url.QueryEscape(project), environment)
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ env := new(Environment)
+ resp, err := s.client.Do(req, env)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return env, resp, err
+}
+
+// DeleteEnvironment removes a environment from a project team.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/environments.html#remove-a-environment-from-a-group-or-project
+func (s *EnvironmentsService) DeleteEnvironment(pid interface{}, environment int, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/environments/%d", url.QueryEscape(project), environment)
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/events.go b/vendor/github.com/lkysow/go-gitlab/events.go
new file mode 100644
index 0000000000..cf551e0e60
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/events.go
@@ -0,0 +1,613 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// 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.
+//
+
+package gitlab
+
+import "time"
+
+// PushEvent represents a push event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/web_hooks/web_hooks.html#push-events
+type PushEvent struct {
+ ObjectKind string `json:"object_kind"`
+ Before string `json:"before"`
+ After string `json:"after"`
+ Ref string `json:"ref"`
+ CheckoutSha string `json:"checkout_sha"`
+ UserID int `json:"user_id"`
+ UserName string `json:"user_name"`
+ UserEmail string `json:"user_email"`
+ UserAvatar string `json:"user_avatar"`
+ ProjectID int `json:"project_id"`
+ Project struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ AvatarURL string `json:"avatar_url"`
+ GitSSHURL string `json:"git_ssh_url"`
+ GitHTTPURL string `json:"git_http_url"`
+ Namespace string `json:"namespace"`
+ PathWithNamespace string `json:"path_with_namespace"`
+ DefaultBranch string `json:"default_branch"`
+ Homepage string `json:"homepage"`
+ URL string `json:"url"`
+ SSHURL string `json:"ssh_url"`
+ HTTPURL string `json:"http_url"`
+ WebURL string `json:"web_url"`
+ Visibility VisibilityValue `json:"visibility"`
+ } `json:"project"`
+ Repository *Repository `json:"repository"`
+ Commits []*Commit `json:"commits"`
+ TotalCommitsCount int `json:"total_commits_count"`
+}
+
+// TagEvent represents a tag event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/web_hooks/web_hooks.html#tag-events
+type TagEvent struct {
+ ObjectKind string `json:"object_kind"`
+ Before string `json:"before"`
+ After string `json:"after"`
+ Ref string `json:"ref"`
+ CheckoutSha string `json:"checkout_sha"`
+ UserID int `json:"user_id"`
+ UserName string `json:"user_name"`
+ UserAvatar string `json:"user_avatar"`
+ ProjectID int `json:"project_id"`
+ Project struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ AvatarURL string `json:"avatar_url"`
+ GitSSHURL string `json:"git_ssh_url"`
+ GitHTTPURL string `json:"git_http_url"`
+ Namespace string `json:"namespace"`
+ PathWithNamespace string `json:"path_with_namespace"`
+ DefaultBranch string `json:"default_branch"`
+ Homepage string `json:"homepage"`
+ URL string `json:"url"`
+ SSHURL string `json:"ssh_url"`
+ HTTPURL string `json:"http_url"`
+ WebURL string `json:"web_url"`
+ Visibility VisibilityValue `json:"visibility"`
+ } `json:"project"`
+ Repository *Repository `json:"repository"`
+ Commits []*Commit `json:"commits"`
+ TotalCommitsCount int `json:"total_commits_count"`
+}
+
+// IssueEvent represents a issue event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/web_hooks/web_hooks.html#issues-events
+type IssueEvent struct {
+ ObjectKind string `json:"object_kind"`
+ User *User `json:"user"`
+ Project struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ AvatarURL string `json:"avatar_url"`
+ GitSSHURL string `json:"git_ssh_url"`
+ GitHTTPURL string `json:"git_http_url"`
+ Namespace string `json:"namespace"`
+ PathWithNamespace string `json:"path_with_namespace"`
+ DefaultBranch string `json:"default_branch"`
+ Homepage string `json:"homepage"`
+ URL string `json:"url"`
+ SSHURL string `json:"ssh_url"`
+ HTTPURL string `json:"http_url"`
+ WebURL string `json:"web_url"`
+ Visibility VisibilityValue `json:"visibility"`
+ } `json:"project"`
+ Repository *Repository `json:"repository"`
+ ObjectAttributes struct {
+ ID int `json:"id"`
+ Title string `json:"title"`
+ AssigneeID int `json:"assignee_id"`
+ AuthorID int `json:"author_id"`
+ ProjectID int `json:"project_id"`
+ CreatedAt string `json:"created_at"` // Should be *time.Time (see Gitlab issue #21468)
+ UpdatedAt string `json:"updated_at"` // Should be *time.Time (see Gitlab issue #21468)
+ Position int `json:"position"`
+ BranchName string `json:"branch_name"`
+ Description string `json:"description"`
+ MilestoneID int `json:"milestone_id"`
+ State string `json:"state"`
+ IID int `json:"iid"`
+ URL string `json:"url"`
+ Action string `json:"action"`
+ } `json:"object_attributes"`
+ Assignee struct {
+ Name string `json:"name"`
+ Username string `json:"username"`
+ AvatarURL string `json:"avatar_url"`
+ } `json:"assignee"`
+}
+
+// CommitCommentEvent represents a comment on a commit event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/web_hooks/web_hooks.html#comment-on-commit
+type CommitCommentEvent struct {
+ ObjectKind string `json:"object_kind"`
+ User *User `json:"user"`
+ ProjectID int `json:"project_id"`
+ Project struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ AvatarURL string `json:"avatar_url"`
+ GitSSHURL string `json:"git_ssh_url"`
+ GitHTTPURL string `json:"git_http_url"`
+ Namespace string `json:"namespace"`
+ PathWithNamespace string `json:"path_with_namespace"`
+ DefaultBranch string `json:"default_branch"`
+ Homepage string `json:"homepage"`
+ URL string `json:"url"`
+ SSHURL string `json:"ssh_url"`
+ HTTPURL string `json:"http_url"`
+ WebURL string `json:"web_url"`
+ Visibility VisibilityValue `json:"visibility"`
+ } `json:"project"`
+ Repository *Repository `json:"repository"`
+ ObjectAttributes struct {
+ ID int `json:"id"`
+ Note string `json:"note"`
+ NoteableType string `json:"noteable_type"`
+ AuthorID int `json:"author_id"`
+ CreatedAt string `json:"created_at"`
+ UpdatedAt string `json:"updated_at"`
+ ProjectID int `json:"project_id"`
+ Attachment string `json:"attachment"`
+ LineCode string `json:"line_code"`
+ CommitID string `json:"commit_id"`
+ NoteableID int `json:"noteable_id"`
+ System bool `json:"system"`
+ StDiff struct {
+ Diff string `json:"diff"`
+ NewPath string `json:"new_path"`
+ OldPath string `json:"old_path"`
+ AMode string `json:"a_mode"`
+ BMode string `json:"b_mode"`
+ NewFile bool `json:"new_file"`
+ RenamedFile bool `json:"renamed_file"`
+ DeletedFile bool `json:"deleted_file"`
+ } `json:"st_diff"`
+ } `json:"object_attributes"`
+ Commit *Commit `json:"commit"`
+}
+
+// MergeCommentEvent represents a comment on a merge event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/web_hooks/web_hooks.html#comment-on-merge-request
+type MergeCommentEvent struct {
+ ObjectKind string `json:"object_kind"`
+ User *User `json:"user"`
+ ProjectID int `json:"project_id"`
+ Project struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ AvatarURL string `json:"avatar_url"`
+ GitSSHURL string `json:"git_ssh_url"`
+ GitHTTPURL string `json:"git_http_url"`
+ Namespace string `json:"namespace"`
+ PathWithNamespace string `json:"path_with_namespace"`
+ DefaultBranch string `json:"default_branch"`
+ Homepage string `json:"homepage"`
+ URL string `json:"url"`
+ SSHURL string `json:"ssh_url"`
+ HTTPURL string `json:"http_url"`
+ WebURL string `json:"web_url"`
+ Visibility VisibilityValue `json:"visibility"`
+ } `json:"project"`
+ Repository *Repository `json:"repository"`
+ ObjectAttributes struct {
+ ID int `json:"id"`
+ Note string `json:"note"`
+ NoteableType string `json:"noteable_type"`
+ AuthorID int `json:"author_id"`
+ CreatedAt string `json:"created_at"`
+ UpdatedAt string `json:"updated_at"`
+ ProjectID int `json:"project_id"`
+ Attachment string `json:"attachment"`
+ LineCode string `json:"line_code"`
+ CommitID string `json:"commit_id"`
+ NoteableID int `json:"noteable_id"`
+ System bool `json:"system"`
+ StDiff *Diff `json:"st_diff"`
+ URL string `json:"url"`
+ } `json:"object_attributes"`
+ MergeRequest struct {
+ ID int `json:"id"`
+ TargetBranch string `json:"target_branch"`
+ SourceBranch string `json:"source_branch"`
+ SourceProjectID int `json:"source_project_id"`
+ AuthorID int `json:"author_id"`
+ AssigneeID int `json:"assignee_id"`
+ Title string `json:"title"`
+ CreatedAt string `json:"created_at"` // Should be *time.Time (see Gitlab issue #21468)
+ UpdatedAt string `json:"updated_at"` // Should be *time.Time (see Gitlab issue #21468)
+ StCommits []*Commit `json:"st_commits"`
+ StDiffs []*Diff `json:"st_diffs"`
+ MilestoneID int `json:"milestone_id"`
+ State string `json:"state"`
+ MergeStatus string `json:"merge_status"`
+ TargetProjectID int `json:"target_project_id"`
+ IID int `json:"iid"`
+ Description string `json:"description"`
+ Position int `json:"position"`
+ LockedAt string `json:"locked_at"`
+ UpdatedByID int `json:"updated_by_id"`
+ MergeError string `json:"merge_error"`
+ MergeParams struct {
+ ForceRemoveSourceBranch string `json:"force_remove_source_branch"`
+ } `json:"merge_params"`
+ MergeWhenBuildSucceeds bool `json:"merge_when_build_succeeds"`
+ MergeUserID int `json:"merge_user_id"`
+ MergeCommitSha string `json:"merge_commit_sha"`
+ DeletedAt string `json:"deleted_at"`
+ ApprovalsBeforeMerge string `json:"approvals_before_merge"`
+ RebaseCommitSha string `json:"rebase_commit_sha"`
+ InProgressMergeCommitSha string `json:"in_progress_merge_commit_sha"`
+ LockVersion int `json:"lock_version"`
+ TimeEstimate int `json:"time_estimate"`
+ Source *Repository `json:"source"`
+ Target *Repository `json:"target"`
+ LastCommit struct {
+ ID string `json:"id"`
+ Message string `json:"message"`
+ Timestamp *time.Time `json:"timestamp"`
+ URL string `json:"url"`
+ Author *Author `json:"author"`
+ } `json:"last_commit"`
+ WorkInProgress bool `json:"work_in_progress"`
+ URL string `json:"url"`
+ Action string `json:"action"`
+ Assignee struct {
+ Name string `json:"name"`
+ Username string `json:"username"`
+ AvatarURL string `json:"avatar_url"`
+ } `json:"assignee"`
+ } `json:"merge_request"`
+}
+
+// IssueCommentEvent represents a comment on an issue event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/web_hooks/web_hooks.html#comment-on-issue
+type IssueCommentEvent struct {
+ ObjectKind string `json:"object_kind"`
+ User *User `json:"user"`
+ ProjectID int `json:"project_id"`
+ Project struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ AvatarURL string `json:"avatar_url"`
+ GitSSHURL string `json:"git_ssh_url"`
+ GitHTTPURL string `json:"git_http_url"`
+ Namespace string `json:"namespace"`
+ PathWithNamespace string `json:"path_with_namespace"`
+ DefaultBranch string `json:"default_branch"`
+ Homepage string `json:"homepage"`
+ URL string `json:"url"`
+ SSHURL string `json:"ssh_url"`
+ HTTPURL string `json:"http_url"`
+ WebURL string `json:"web_url"`
+ Visibility VisibilityValue `json:"visibility"`
+ } `json:"project"`
+ Repository *Repository `json:"repository"`
+ ObjectAttributes struct {
+ ID int `json:"id"`
+ Note string `json:"note"`
+ NoteableType string `json:"noteable_type"`
+ AuthorID int `json:"author_id"`
+ CreatedAt string `json:"created_at"`
+ UpdatedAt string `json:"updated_at"`
+ ProjectID int `json:"project_id"`
+ Attachment string `json:"attachment"`
+ LineCode string `json:"line_code"`
+ CommitID string `json:"commit_id"`
+ NoteableID int `json:"noteable_id"`
+ System bool `json:"system"`
+ StDiff []*Diff `json:"st_diff"`
+ URL string `json:"url"`
+ } `json:"object_attributes"`
+ Issue *Issue `json:"issue"`
+}
+
+// SnippetCommentEvent represents a comment on a snippet event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/web_hooks/web_hooks.html#comment-on-code-snippet
+type SnippetCommentEvent struct {
+ ObjectKind string `json:"object_kind"`
+ User *User `json:"user"`
+ ProjectID int `json:"project_id"`
+ Project struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ AvatarURL string `json:"avatar_url"`
+ GitSSHURL string `json:"git_ssh_url"`
+ GitHTTPURL string `json:"git_http_url"`
+ Namespace string `json:"namespace"`
+ PathWithNamespace string `json:"path_with_namespace"`
+ DefaultBranch string `json:"default_branch"`
+ Homepage string `json:"homepage"`
+ URL string `json:"url"`
+ SSHURL string `json:"ssh_url"`
+ HTTPURL string `json:"http_url"`
+ WebURL string `json:"web_url"`
+ Visibility VisibilityValue `json:"visibility"`
+ } `json:"project"`
+ Repository *Repository `json:"repository"`
+ ObjectAttributes struct {
+ ID int `json:"id"`
+ Note string `json:"note"`
+ NoteableType string `json:"noteable_type"`
+ AuthorID int `json:"author_id"`
+ CreatedAt string `json:"created_at"`
+ UpdatedAt string `json:"updated_at"`
+ ProjectID int `json:"project_id"`
+ Attachment string `json:"attachment"`
+ LineCode string `json:"line_code"`
+ CommitID string `json:"commit_id"`
+ NoteableID int `json:"noteable_id"`
+ System bool `json:"system"`
+ StDiff *Diff `json:"st_diff"`
+ URL string `json:"url"`
+ } `json:"object_attributes"`
+ Snippet *Snippet `json:"snippet"`
+}
+
+// MergeEvent represents a merge event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/web_hooks/web_hooks.html#merge-request-events
+type MergeEvent struct {
+ ObjectKind string `json:"object_kind"`
+ User *User `json:"user"`
+ Project struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ AvatarURL string `json:"avatar_url"`
+ GitSSHURL string `json:"git_ssh_url"`
+ GitHTTPURL string `json:"git_http_url"`
+ Namespace string `json:"namespace"`
+ PathWithNamespace string `json:"path_with_namespace"`
+ DefaultBranch string `json:"default_branch"`
+ Homepage string `json:"homepage"`
+ URL string `json:"url"`
+ SSHURL string `json:"ssh_url"`
+ HTTPURL string `json:"http_url"`
+ WebURL string `json:"web_url"`
+ Visibility VisibilityValue `json:"visibility"`
+ } `json:"project"`
+ ObjectAttributes struct {
+ ID int `json:"id"`
+ TargetBranch string `json:"target_branch"`
+ SourceBranch string `json:"source_branch"`
+ SourceProjectID int `json:"source_project_id"`
+ AuthorID int `json:"author_id"`
+ AssigneeID int `json:"assignee_id"`
+ Title string `json:"title"`
+ CreatedAt string `json:"created_at"` // Should be *time.Time (see Gitlab issue #21468)
+ UpdatedAt string `json:"updated_at"` // Should be *time.Time (see Gitlab issue #21468)
+ StCommits []*Commit `json:"st_commits"`
+ StDiffs []*Diff `json:"st_diffs"`
+ MilestoneID int `json:"milestone_id"`
+ State string `json:"state"`
+ MergeStatus string `json:"merge_status"`
+ TargetProjectID int `json:"target_project_id"`
+ IID int `json:"iid"`
+ Description string `json:"description"`
+ Position int `json:"position"`
+ LockedAt string `json:"locked_at"`
+ UpdatedByID int `json:"updated_by_id"`
+ MergeError string `json:"merge_error"`
+ MergeParams struct {
+ ForceRemoveSourceBranch string `json:"force_remove_source_branch"`
+ } `json:"merge_params"`
+ MergeWhenBuildSucceeds bool `json:"merge_when_build_succeeds"`
+ MergeUserID int `json:"merge_user_id"`
+ MergeCommitSha string `json:"merge_commit_sha"`
+ DeletedAt string `json:"deleted_at"`
+ ApprovalsBeforeMerge string `json:"approvals_before_merge"`
+ RebaseCommitSha string `json:"rebase_commit_sha"`
+ InProgressMergeCommitSha string `json:"in_progress_merge_commit_sha"`
+ LockVersion int `json:"lock_version"`
+ TimeEstimate int `json:"time_estimate"`
+ Source *Repository `json:"source"`
+ Target *Repository `json:"target"`
+ LastCommit struct {
+ ID string `json:"id"`
+ Message string `json:"message"`
+ Timestamp *time.Time `json:"timestamp"`
+ URL string `json:"url"`
+ Author *Author `json:"author"`
+ } `json:"last_commit"`
+ WorkInProgress bool `json:"work_in_progress"`
+ URL string `json:"url"`
+ Action string `json:"action"`
+ Assignee struct {
+ Name string `json:"name"`
+ Username string `json:"username"`
+ AvatarURL string `json:"avatar_url"`
+ } `json:"assignee"`
+ } `json:"object_attributes"`
+ Repository *Repository `json:"repository"`
+ Assignee struct {
+ Name string `json:"name"`
+ Username string `json:"username"`
+ AvatarURL string `json:"avatar_url"`
+ } `json:"assignee"`
+}
+
+// WikiPageEvent represents a wiki page event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/web_hooks/web_hooks.html#wiki-page-events
+type WikiPageEvent struct {
+ ObjectKind string `json:"object_kind"`
+ User *User `json:"user"`
+ Project struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ AvatarURL string `json:"avatar_url"`
+ GitSSHURL string `json:"git_ssh_url"`
+ GitHTTPURL string `json:"git_http_url"`
+ Namespace string `json:"namespace"`
+ PathWithNamespace string `json:"path_with_namespace"`
+ DefaultBranch string `json:"default_branch"`
+ Homepage string `json:"homepage"`
+ URL string `json:"url"`
+ SSHURL string `json:"ssh_url"`
+ HTTPURL string `json:"http_url"`
+ WebURL string `json:"web_url"`
+ Visibility VisibilityValue `json:"visibility"`
+ } `json:"project"`
+ Wiki struct {
+ WebURL string `json:"web_url"`
+ GitSSHURL string `json:"git_ssh_url"`
+ GitHTTPURL string `json:"git_http_url"`
+ PathWithNamespace string `json:"path_with_namespace"`
+ DefaultBranch string `json:"default_branch"`
+ } `json:"wiki"`
+ ObjectAttributes struct {
+ Title string `json:"title"`
+ Content string `json:"content"`
+ Format string `json:"format"`
+ Message string `json:"message"`
+ Slug string `json:"slug"`
+ URL string `json:"url"`
+ Action string `json:"action"`
+ } `json:"object_attributes"`
+}
+
+// PipelineEvent represents a pipeline event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/web_hooks/web_hooks.html#pipeline-events
+type PipelineEvent struct {
+ ObjectKind string `json:"object_kind"`
+ ObjectAttributes struct {
+ ID int `json:"id"`
+ Ref string `json:"ref"`
+ Tag bool `json:"tag"`
+ Sha string `json:"sha"`
+ BeforeSha string `json:"before_sha"`
+ Status string `json:"status"`
+ Stages []string `json:"stages"`
+ CreatedAt string `json:"created_at"`
+ FinishedAt string `json:"finished_at"`
+ Duration int `json:"duration"`
+ } `json:"object_attributes"`
+ User struct {
+ Name string `json:"name"`
+ Username string `json:"username"`
+ AvatarURL string `json:"avatar_url"`
+ } `json:"user"`
+ Project struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ AvatarURL string `json:"avatar_url"`
+ GitSSHURL string `json:"git_ssh_url"`
+ GitHTTPURL string `json:"git_http_url"`
+ Namespace string `json:"namespace"`
+ PathWithNamespace string `json:"path_with_namespace"`
+ DefaultBranch string `json:"default_branch"`
+ Homepage string `json:"homepage"`
+ URL string `json:"url"`
+ SSHURL string `json:"ssh_url"`
+ HTTPURL string `json:"http_url"`
+ WebURL string `json:"web_url"`
+ Visibility VisibilityValue `json:"visibility"`
+ } `json:"project"`
+ Commit struct {
+ ID string `json:"id"`
+ Message string `json:"message"`
+ Timestamp time.Time `json:"timestamp"`
+ URL string `json:"url"`
+ Author struct {
+ Name string `json:"name"`
+ Email string `json:"email"`
+ } `json:"author"`
+ } `json:"commit"`
+ Builds []struct {
+ ID int `json:"id"`
+ Stage string `json:"stage"`
+ Name string `json:"name"`
+ Status string `json:"status"`
+ CreatedAt string `json:"created_at"`
+ StartedAt string `json:"started_at"`
+ FinishedAt string `json:"finished_at"`
+ When string `json:"when"`
+ Manual bool `json:"manual"`
+ User struct {
+ Name string `json:"name"`
+ Username string `json:"username"`
+ AvatarURL string `json:"avatar_url"`
+ } `json:"user"`
+ Runner struct {
+ ID int `json:"id"`
+ Description string `json:"description"`
+ Active bool `json:"active"`
+ IsShared bool `json:"is_shared"`
+ } `json:"runner"`
+ ArtifactsFile struct {
+ Filename string `json:"filename"`
+ Size int `json:"size"`
+ } `json:"artifacts_file"`
+ } `json:"builds"`
+}
+
+//BuildEvent represents a build event
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/web_hooks/web_hooks.html#build-events
+type BuildEvent struct {
+ ObjectKind string `json:"object_kind"`
+ Ref string `json:"ref"`
+ Tag bool `json:"tag"`
+ BeforeSha string `json:"before_sha"`
+ Sha string `json:"sha"`
+ BuildID int `json:"build_id"`
+ BuildName string `json:"build_name"`
+ BuildStage string `json:"build_stage"`
+ BuildStatus string `json:"build_status"`
+ BuildStartedAt string `json:"build_started_at"`
+ BuildFinishedAt string `json:"build_finished_at"`
+ BuildDuration float64 `json:"build_duration"`
+ BuildAllowFailure bool `json:"build_allow_failure"`
+ ProjectID int `json:"project_id"`
+ ProjectName string `json:"project_name"`
+ User struct {
+ ID int `json:"id"`
+ Name string `json:"name"`
+ Email string `json:"email"`
+ } `json:"user"`
+ Commit struct {
+ ID int `json:"id"`
+ Sha string `json:"sha"`
+ Message string `json:"message"`
+ AuthorName string `json:"author_name"`
+ AuthorEmail string `json:"author_email"`
+ Status string `json:"status"`
+ Duration string `json:"duration"`
+ StartedAt string `json:"started_at"`
+ FinishedAt string `json:"finished_at"`
+ } `json:"commit"`
+ Repository *Repository `json:"repository"`
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/events_test.go b/vendor/github.com/lkysow/go-gitlab/events_test.go
new file mode 100644
index 0000000000..ce365a8497
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/events_test.go
@@ -0,0 +1,598 @@
+package gitlab
+
+import (
+ "encoding/json"
+ "testing"
+)
+
+func TestPushEventUnmarshal(t *testing.T) {
+ jsonObject := `{
+ "object_kind": "push",
+ "before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
+ "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "ref": "refs/heads/master",
+ "checkout_sha": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "user_id": 4,
+ "user_name": "John Smith",
+ "user_email": "john@example.com",
+ "user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80",
+ "project_id": 15,
+ "project":{
+ "name":"Diaspora",
+ "description":"",
+ "web_url":"http://example.com/mike/diaspora",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:mike/diaspora.git",
+ "git_http_url":"http://example.com/mike/diaspora.git",
+ "namespace":"Mike",
+ "visibility":"public",
+ "path_with_namespace":"mike/diaspora",
+ "default_branch":"master",
+ "homepage":"http://example.com/mike/diaspora",
+ "url":"git@example.com:mike/diaspora.git",
+ "ssh_url":"git@example.com:mike/diaspora.git",
+ "http_url":"http://example.com/mike/diaspora.git"
+ },
+ "repository":{
+ "name": "Diaspora",
+ "url": "git@example.com:mike/diaspora.git",
+ "description": "",
+ "homepage": "http://example.com/mike/diaspora",
+ "git_http_url":"http://example.com/mike/diaspora.git",
+ "git_ssh_url":"git@example.com:mike/diaspora.git",
+ "visibility":"public"
+ },
+ "commits": [
+ {
+ "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ "message": "Update Catalan translation to e38cb41.",
+ "timestamp": "2011-12-12T14:27:31+02:00",
+ "url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ "author": {
+ "name": "Jordi Mallach",
+ "email": "jordi@softcatala.org"
+ },
+ "added": ["CHANGELOG"],
+ "modified": ["app/controller/application.rb"],
+ "removed": []
+ },
+ {
+ "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "message": "fixed readme",
+ "timestamp": "2012-01-03T23:36:29+02:00",
+ "url": "http://example.com/mike/diaspora/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "author": {
+ "name": "GitLab dev user",
+ "email": "gitlabdev@dv6700.(none)"
+ },
+ "added": ["CHANGELOG"],
+ "modified": ["app/controller/application.rb"],
+ "removed": []
+ }
+ ],
+ "total_commits_count": 4
+}`
+ var event *PushEvent
+ err := json.Unmarshal([]byte(jsonObject), &event)
+
+ if err != nil {
+ t.Errorf("Push Event can not unmarshaled: %v\n ", err.Error())
+ }
+
+ if event == nil {
+ t.Errorf("Push Event is null")
+ }
+
+ if event.ProjectID != 15 {
+ t.Errorf("ProjectID is %v, want %v", event.ProjectID, 15)
+ }
+
+}
+
+func TestMergeEventUnmarshal(t *testing.T) {
+
+ jsonObject := `{
+ "object_kind": "merge_request",
+ "user": {
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ },
+ "object_attributes": {
+ "id": 99,
+ "target_branch": "master",
+ "source_branch": "ms-viewport",
+ "source_project_id": 14,
+ "author_id": 51,
+ "assignee_id": 6,
+ "title": "MS-Viewport",
+ "created_at": "2013-12-03T17:23:34Z",
+ "updated_at": "2013-12-03T17:23:34Z",
+ "st_commits": null,
+ "st_diffs": null,
+ "milestone_id": null,
+ "state": "opened",
+ "merge_status": "unchecked",
+ "target_project_id": 14,
+ "iid": 1,
+ "description": "",
+ "source":{
+ "name":"Awesome Project",
+ "description":"Aut reprehenderit ut est.",
+ "web_url":"http://example.com/awesome_space/awesome_project",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:awesome_space/awesome_project.git",
+ "git_http_url":"http://example.com/awesome_space/awesome_project.git",
+ "namespace":"Awesome Space",
+ "visibility":"private",
+ "path_with_namespace":"awesome_space/awesome_project",
+ "default_branch":"master",
+ "homepage":"http://example.com/awesome_space/awesome_project",
+ "url":"http://example.com/awesome_space/awesome_project.git",
+ "ssh_url":"git@example.com:awesome_space/awesome_project.git",
+ "http_url":"http://example.com/awesome_space/awesome_project.git"
+ },
+ "target": {
+ "name":"Awesome Project",
+ "description":"Aut reprehenderit ut est.",
+ "web_url":"http://example.com/awesome_space/awesome_project",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:awesome_space/awesome_project.git",
+ "git_http_url":"http://example.com/awesome_space/awesome_project.git",
+ "namespace":"Awesome Space",
+ "visibility":"private",
+ "path_with_namespace":"awesome_space/awesome_project",
+ "default_branch":"master",
+ "homepage":"http://example.com/awesome_space/awesome_project",
+ "url":"http://example.com/awesome_space/awesome_project.git",
+ "ssh_url":"git@example.com:awesome_space/awesome_project.git",
+ "http_url":"http://example.com/awesome_space/awesome_project.git"
+ },
+ "last_commit": {
+ "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "message": "fixed readme",
+ "timestamp": "2012-01-03T23:36:29+02:00",
+ "url": "http://example.com/awesome_space/awesome_project/commits/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "author": {
+ "name": "GitLab dev user",
+ "email": "gitlabdev@dv6700.(none)"
+ }
+ },
+ "work_in_progress": false,
+ "url": "http://example.com/diaspora/merge_requests/1",
+ "action": "open",
+ "assignee": {
+ "name": "User1",
+ "username": "user1",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ }
+ }
+}`
+
+ var event *MergeEvent
+ err := json.Unmarshal([]byte(jsonObject), &event)
+
+ if err != nil {
+ t.Errorf("Merge Event can not unmarshaled: %v\n ", err.Error())
+ }
+
+ if event == nil {
+ t.Errorf("Merge Event is null")
+ }
+
+ if event.ObjectAttributes.ID != 99 {
+ t.Errorf("ObjectAttributes.ID is %v, want %v", event.ObjectAttributes.ID, 99)
+ }
+
+ if event.ObjectAttributes.Source.Homepage != "http://example.com/awesome_space/awesome_project" {
+ t.Errorf("ObjectAttributes.Source.Homepage is %v, want %v", event.ObjectAttributes.Source.Homepage, "http://example.com/awesome_space/awesome_project")
+ }
+
+ if event.ObjectAttributes.LastCommit.ID != "da1560886d4f094c3e6c9ef40349f7d38b5d27d7" {
+ t.Errorf("ObjectAttributes.LastCommit.ID is %v, want %s", event.ObjectAttributes.LastCommit.ID, "da1560886d4f094c3e6c9ef40349f7d38b5d27d7")
+ }
+ if event.ObjectAttributes.Assignee.Name != "User1" {
+ t.Errorf("Assignee.Name is %v, want %v", event.ObjectAttributes.ID, "User1")
+ }
+
+ if event.ObjectAttributes.Assignee.Username != "user1" {
+ t.Errorf("ObjectAttributes is %v, want %v", event.ObjectAttributes.Assignee.Username, "user1")
+ }
+
+}
+
+func TestPipelineEventUnmarshal(t *testing.T) {
+ jsonObject := `{
+ "object_kind": "pipeline",
+ "object_attributes":{
+ "id": 31,
+ "ref": "master",
+ "tag": false,
+ "sha": "bcbb5ec396a2c0f828686f14fac9b80b780504f2",
+ "before_sha": "bcbb5ec396a2c0f828686f14fac9b80b780504f2",
+ "status": "success",
+ "stages":[
+ "build",
+ "test",
+ "deploy"
+ ],
+ "created_at": "2016-08-12 15:23:28 UTC",
+ "finished_at": "2016-08-12 15:26:29 UTC",
+ "duration": 63
+ },
+ "user":{
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
+ },
+ "project":{
+ "name": "Gitlab Test",
+ "description": "Atque in sunt eos similique dolores voluptatem.",
+ "web_url": "http://192.168.64.1:3005/gitlab-org/gitlab-test",
+ "avatar_url": null,
+ "git_ssh_url": "git@192.168.64.1:gitlab-org/gitlab-test.git",
+ "git_http_url": "http://192.168.64.1:3005/gitlab-org/gitlab-test.git",
+ "namespace": "Gitlab Org",
+ "visibility": "private",
+ "path_with_namespace": "gitlab-org/gitlab-test",
+ "default_branch": "master"
+ },
+ "commit":{
+ "id": "bcbb5ec396a2c0f828686f14fac9b80b780504f2",
+ "message": "test\n",
+ "timestamp": "2016-08-12T17:23:21+02:00",
+ "url": "http://example.com/gitlab-org/gitlab-test/commit/bcbb5ec396a2c0f828686f14fac9b80b780504f2",
+ "author":{
+ "name": "User",
+ "email": "user@gitlab.com"
+ }
+ },
+ "builds":[
+ {
+ "id": 380,
+ "stage": "deploy",
+ "name": "production",
+ "status": "skipped",
+ "created_at": "2016-08-12 15:23:28 UTC",
+ "started_at": null,
+ "finished_at": null,
+ "when": "manual",
+ "manual": true,
+ "user":{
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
+ },
+ "runner": null,
+ "artifacts_file":{
+ "filename": null,
+ "size": null
+ }
+ },
+ {
+ "id": 377,
+ "stage": "test",
+ "name": "test-image",
+ "status": "success",
+ "created_at": "2016-08-12 15:23:28 UTC",
+ "started_at": "2016-08-12 15:26:12 UTC",
+ "finished_at": null,
+ "when": "on_success",
+ "manual": false,
+ "user":{
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
+ },
+ "runner": {
+ "id": 6,
+ "description": "Kubernetes Runner",
+ "active": true,
+ "is_shared": true
+ },
+ "artifacts_file":{
+ "filename": "artifacts.zip",
+ "size": 1319148
+ }
+ },
+ {
+ "id": 378,
+ "stage": "test",
+ "name": "test-build",
+ "status": "success",
+ "created_at": "2016-08-12 15:23:28 UTC",
+ "started_at": "2016-08-12 15:26:12 UTC",
+ "finished_at": "2016-08-12 15:26:29 UTC",
+ "when": "on_success",
+ "manual": false,
+ "user":{
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
+ },
+ "runner": null,
+ "artifacts_file":{
+ "filename": null,
+ "size": null
+ }
+ },
+ {
+ "id": 376,
+ "stage": "build",
+ "name": "build-image",
+ "status": "success",
+ "created_at": "2016-08-12 15:23:28 UTC",
+ "started_at": "2016-08-12 15:24:56 UTC",
+ "finished_at": "2016-08-12 15:25:26 UTC",
+ "when": "on_success",
+ "manual": false,
+ "user":{
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
+ },
+ "runner": null,
+ "artifacts_file":{
+ "filename": null,
+ "size": null
+ }
+ },
+ {
+ "id": 379,
+ "stage": "deploy",
+ "name": "staging",
+ "status": "created",
+ "created_at": "2016-08-12 15:23:28 UTC",
+ "started_at": null,
+ "finished_at": null,
+ "when": "on_success",
+ "manual": false,
+ "user":{
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
+ },
+ "runner": null,
+ "artifacts_file":{
+ "filename": null,
+ "size": null
+ }
+ }
+ ]
+}`
+
+ var event *PipelineEvent
+ err := json.Unmarshal([]byte(jsonObject), &event)
+
+ if err != nil {
+ t.Errorf("Pipeline Event can not unmarshaled: %v\n ", err.Error())
+ }
+
+ if event == nil {
+ t.Errorf("Pipeline Event is null")
+ }
+
+ if event.ObjectAttributes.ID != 31 {
+ t.Errorf("ObjectAttributes is %v, want %v", event.ObjectAttributes.ID, 1977)
+ }
+
+}
+
+func TestBuildEventUnmarshal(t *testing.T) {
+ jsonObject := `{
+ "object_kind": "build",
+ "ref": "gitlab-script-trigger",
+ "tag": false,
+ "before_sha": "2293ada6b400935a1378653304eaf6221e0fdb8f",
+ "sha": "2293ada6b400935a1378653304eaf6221e0fdb8f",
+ "build_id": 1977,
+ "build_name": "test",
+ "build_stage": "test",
+ "build_status": "created",
+ "build_started_at": null,
+ "build_finished_at": null,
+ "build_duration": 23.265997,
+ "build_allow_failure": false,
+ "project_id": 380,
+ "project_name": "gitlab-org/gitlab-test",
+ "user": {
+ "id": 3,
+ "name": "User",
+ "email": "user@gitlab.com"
+ },
+ "commit": {
+ "id": 2366,
+ "sha": "2293ada6b400935a1378653304eaf6221e0fdb8f",
+ "message": "test\n",
+ "author_name": "User",
+ "author_email": "user@gitlab.com",
+ "status": "created",
+ "duration": null,
+ "started_at": null,
+ "finished_at": null
+ },
+ "repository": {
+ "name": "gitlab_test",
+ "git_ssh_url": "git@192.168.64.1:gitlab-org/gitlab-test.git",
+ "description": "Atque in sunt eos similique dolores voluptatem.",
+ "homepage": "http://192.168.64.1:3005/gitlab-org/gitlab-test",
+ "git_ssh_url": "git@192.168.64.1:gitlab-org/gitlab-test.git",
+ "git_http_url": "http://192.168.64.1:3005/gitlab-org/gitlab-test.git",
+ "visibility": "private"
+ }
+}`
+ var event *BuildEvent
+ err := json.Unmarshal([]byte(jsonObject), &event)
+
+ if err != nil {
+ t.Errorf("Build Event can not unmarshaled: %v\n ", err.Error())
+ }
+
+ if event == nil {
+ t.Errorf("Build Event is null")
+ }
+
+ if event.BuildID != 1977 {
+ t.Errorf("BuildID is %v, want %v", event.BuildID, 1977)
+ }
+}
+
+func TestMergeEventUnmarshalFromGroup(t *testing.T) {
+
+ jsonObject := `{
+ "object_kind": "merge_request",
+ "user": {
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/d22738dc40839e3d95fca77ca3eac067?s=80\u0026d=identicon"
+ },
+ "project": {
+ "name": "example-project",
+ "description": "",
+ "web_url": "http://example.com/exm-namespace/example-project",
+ "avatar_url": null,
+ "git_ssh_url": "git@example.com:exm-namespace/example-project.git",
+ "git_http_url": "http://example.com/exm-namespace/example-project.git",
+ "namespace": "exm-namespace",
+ "visibility": "public",
+ "path_with_namespace": "exm-namespace/example-project",
+ "default_branch": "master",
+ "homepage": "http://example.com/exm-namespace/example-project",
+ "url": "git@example.com:exm-namespace/example-project.git",
+ "ssh_url": "git@example.com:exm-namespace/example-project.git",
+ "http_url": "http://example.com/exm-namespace/example-project.git"
+ },
+ "object_attributes": {
+ "id": 15917,
+ "target_branch ": "master ",
+ "source_branch ": "source-branch-test ",
+ "source_project_id ": 87,
+ "author_id ": 15,
+ "assignee_id ": 29,
+ "title ": "source-branch-test ",
+ "created_at ": "2016 - 12 - 01 13: 11: 10 UTC ",
+ "updated_at ": "2016 - 12 - 01 13: 21: 20 UTC ",
+ "milestone_id ": null,
+ "state ": "merged ",
+ "merge_status ": "can_be_merged ",
+ "target_project_id ": 87,
+ "iid ": 1402,
+ "description ": "word doc support for e - ticket ",
+ "position ": 0,
+ "locked_at ": null,
+ "updated_by_id ": null,
+ "merge_error ": null,
+ "merge_params": {
+ "force_remove_source_branch": "0"
+ },
+ "merge_when_build_succeeds": false,
+ "merge_user_id": null,
+ "merge_commit_sha": "ac3ca1559bc39abf963586372eff7f8fdded646e",
+ "deleted_at": null,
+ "approvals_before_merge": null,
+ "rebase_commit_sha": null,
+ "in_progress_merge_commit_sha": null,
+ "lock_version": 0,
+ "time_estimate": 0,
+ "source": {
+ "name": "example-project",
+ "description": "",
+ "web_url": "http://example.com/exm-namespace/example-project",
+ "avatar_url": null,
+ "git_ssh_url": "git@example.com:exm-namespace/example-project.git",
+ "git_http_url": "http://example.com/exm-namespace/example-project.git",
+ "namespace": "exm-namespace",
+ "visibility": "public",
+ "path_with_namespace": "exm-namespace/example-project",
+ "default_branch": "master",
+ "homepage": "http://example.com/exm-namespace/example-project",
+ "url": "git@example.com:exm-namespace/example-project.git",
+ "ssh_url": "git@example.com:exm-namespace/example-project.git",
+ "http_url": "http://example.com/exm-namespace/example-project.git"
+ },
+ "target": {
+ "name": "example-project",
+ "description": "",
+ "web_url": "http://example.com/exm-namespace/example-project",
+ "avatar_url": null,
+ "git_ssh_url": "git@example.com:exm-namespace/example-project.git",
+ "git_http_url": "http://example.com/exm-namespace/example-project.git",
+ "namespace": "exm-namespace",
+ "visibility": "public",
+ "path_with_namespace": "exm-namespace/example-project",
+ "default_branch": "master",
+ "homepage": "http://example.com/exm-namespace/example-project",
+ "url": "git@example.com:exm-namespace/example-project.git",
+ "ssh_url": "git@example.com:exm-namespace/example-project.git",
+ "http_url": "http://example.com/exm-namespace/example-project.git"
+ },
+ "last_commit": {
+ "id": "61b6a0d35dbaf915760233b637622e383d3cc9ec",
+ "message": "commit message",
+ "timestamp": "2016-12-01T15:07:53+02:00",
+ "url": "http://example.com/exm-namespace/example-project/commit/61b6a0d35dbaf915760233b637622e383d3cc9ec",
+ "author": {
+ "name": "Test User",
+ "email": "test.user@mail.com"
+ }
+ },
+ "work_in_progress": false,
+ "url": "http://example.com/exm-namespace/example-project/merge_requests/1402",
+ "action": "merge"
+ },
+ "repository": {
+ "name": "example-project",
+ "url": "git@example.com:exm-namespace/example-project.git",
+ "description": "",
+ "homepage": "http://example.com/exm-namespace/example-project"
+ },
+ "assignee": {
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/d22738dc40839e3d95fca77ca3eac067?s=80\u0026d=identicon"
+ }
+}`
+
+ var event *MergeEvent
+ err := json.Unmarshal([]byte(jsonObject), &event)
+
+ if err != nil {
+ t.Errorf("Group Merge Event can not unmarshaled: %v\n ", err.Error())
+ }
+
+ if event == nil {
+ t.Errorf("Group Merge Event is null")
+ }
+
+ if event.ObjectKind != "merge_request" {
+ t.Errorf("ObjectKind is %v, want %v", event.ObjectKind, "merge_request")
+ }
+
+ if event.User.Username != "root" {
+ t.Errorf("User.Username is %v, want %v", event.User.Username, "root")
+ }
+
+ if event.Project.Name != "example-project" {
+ t.Errorf("Project.Name is %v, want %v", event.Project.Name, "example-project")
+ }
+
+ if event.ObjectAttributes.ID != 15917 {
+ t.Errorf("ObjectAttributes.ID is %v, want %v", event.ObjectAttributes.ID, 15917)
+ }
+
+ if event.ObjectAttributes.Source.Name != "example-project" {
+ t.Errorf("ObjectAttributes.Source.Name is %v, want %v", event.ObjectAttributes.Source.Name, "example-project")
+ }
+
+ if event.ObjectAttributes.LastCommit.Author.Email != "test.user@mail.com" {
+ t.Errorf("ObjectAttributes.LastCommit.Author.Email is %v, want %v", event.ObjectAttributes.LastCommit.Author.Email, "test.user@mail.com")
+ }
+
+ if event.Repository.Name != "example-project" {
+ t.Errorf("Repository.Name is %v, want %v", event.Repository.Name, "example-project")
+ }
+
+ if event.Assignee.Username != "root" {
+ t.Errorf("Assignee.Username is %v, want %v", event.Assignee, "root")
+ }
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/examples/impersonation.go b/vendor/github.com/lkysow/go-gitlab/examples/impersonation.go
new file mode 100644
index 0000000000..3fc037d898
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/examples/impersonation.go
@@ -0,0 +1,37 @@
+package main
+
+import (
+ "log"
+
+ "github.com/xanzy/go-gitlab"
+)
+
+func impersonationExample() {
+ git := gitlab.NewClient(nil, "yourtokengoeshere")
+
+ uid := 1
+
+ //list impersonation token from an user
+ tokens, _, err := git.Users.GetAllImpersonationTokens(
+ uid,
+ &gitlab.GetAllImpersonationTokensOptions{State: gitlab.String("active")},
+ )
+ if err != nil {
+ panic(err)
+ }
+
+ for _, token := range tokens {
+ log.Println(token.Token)
+ }
+
+ //create an impersonation token of an user
+ token, _, err := git.Users.CreateImpersonationToken(
+ uid,
+ &gitlab.CreateImpersonationTokenOptions{Scopes: &[]string{"api"}},
+ )
+ if err != nil {
+ panic(err)
+ }
+
+ log.Println(token.Token)
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/examples/labels.go b/vendor/github.com/lkysow/go-gitlab/examples/labels.go
new file mode 100644
index 0000000000..fc47ede0e9
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/examples/labels.go
@@ -0,0 +1,32 @@
+package main
+
+import (
+ "log"
+
+ "github.com/xanzy/go-gitlab"
+)
+
+func labelExample() {
+ git := gitlab.NewClient(nil, "yourtokengoeshere")
+
+ // Create new label
+ l := &gitlab.CreateLabelOptions{
+ Name: gitlab.String("My Label"),
+ Color: gitlab.String("#11FF22"),
+ }
+ label, _, err := git.Labels.CreateLabel("myname/myproject", l)
+ if err != nil {
+ log.Fatal(err)
+ }
+ log.Printf("Created label: %s\nWith color: %s\n", label.Name, label.Color)
+
+ // List all labels
+ labels, _, err := git.Labels.ListLabels("myname/myproject")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ for _, label := range labels {
+ log.Printf("Found label: %s", label.Name)
+ }
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/examples/main.go b/vendor/github.com/lkysow/go-gitlab/examples/main.go
new file mode 100644
index 0000000000..80fe975e39
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/examples/main.go
@@ -0,0 +1,6 @@
+package main
+
+func main() {
+ // See the separate files in this directory for the examples. This file is only
+ // here to provide a main() function for the `example` package, keeping Travis happy.
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/examples/pipelines.go b/vendor/github.com/lkysow/go-gitlab/examples/pipelines.go
new file mode 100644
index 0000000000..2fbc0330a8
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/examples/pipelines.go
@@ -0,0 +1,21 @@
+package main
+
+import (
+ "log"
+
+ "github.com/xanzy/go-gitlab"
+)
+
+func pipelineExample() {
+ git := gitlab.NewClient(nil, "yourtokengoeshere")
+ git.SetBaseURL("https://gitlab.com/api/v4")
+
+ pipelines, _, err := git.Pipelines.ListProjectPipelines(2743054)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ for _, pipeline := range pipelines {
+ log.Printf("Found pipeline: %v", pipeline)
+ }
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/examples/projects.go b/vendor/github.com/lkysow/go-gitlab/examples/projects.go
new file mode 100644
index 0000000000..ba89cd3808
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/examples/projects.go
@@ -0,0 +1,46 @@
+package main
+
+import (
+ "log"
+
+ "github.com/xanzy/go-gitlab"
+)
+
+func projectExample() {
+ git := gitlab.NewClient(nil, "yourtokengoeshere")
+
+ // Create new project
+ p := &gitlab.CreateProjectOptions{
+ Name: gitlab.String("My Project"),
+ Description: gitlab.String("Just a test project to play with"),
+ MergeRequestsEnabled: gitlab.Bool(true),
+ SnippetsEnabled: gitlab.Bool(true),
+ Visibility: gitlab.Visibility(gitlab.PublicVisibility),
+ }
+ project, _, err := git.Projects.CreateProject(p)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Add a new snippet
+ s := &gitlab.CreateSnippetOptions{
+ Title: gitlab.String("Dummy Snippet"),
+ FileName: gitlab.String("snippet.go"),
+ Code: gitlab.String("package main...."),
+ Visibility: gitlab.Visibility(gitlab.PublicVisibility),
+ }
+ _, _, err = git.ProjectSnippets.CreateSnippet(project.ID, s)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // List all project snippets
+ snippets, _, err := git.ProjectSnippets.ListSnippets(project.PathWithNamespace, &gitlab.ListSnippetsOptions{})
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ for _, snippet := range snippets {
+ log.Printf("Found snippet: %s", snippet.Title)
+ }
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/examples/repository_files.go b/vendor/github.com/lkysow/go-gitlab/examples/repository_files.go
new file mode 100644
index 0000000000..f29ec2fca5
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/examples/repository_files.go
@@ -0,0 +1,43 @@
+package main
+
+import (
+ "log"
+
+ "github.com/xanzy/go-gitlab"
+)
+
+func repositoryFileExample() {
+ git := gitlab.NewClient(nil, "yourtokengoeshere")
+
+ // Create a new repository file
+ cf := &gitlab.CreateFileOptions{
+ Branch: gitlab.String("master"),
+ Content: gitlab.String("My file contents"),
+ CommitMessage: gitlab.String("Adding a test file"),
+ }
+ file, _, err := git.RepositoryFiles.CreateFile("myname/myproject", "file.go", cf)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Update a repository file
+ uf := &gitlab.UpdateFileOptions{
+ Branch: gitlab.String("master"),
+ Content: gitlab.String("My file content"),
+ CommitMessage: gitlab.String("Fixing typo"),
+ }
+ _, _, err = git.RepositoryFiles.UpdateFile("myname/myproject", file.FilePath, uf)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ gf := &gitlab.GetFileOptions{
+ Ref: gitlab.String("master"),
+ }
+ f, _, err := git.RepositoryFiles.GetFile("myname/myproject", file.FilePath, gf)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ log.Printf("File contains: %s", f.Content)
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/feature_flags.go b/vendor/github.com/lkysow/go-gitlab/feature_flags.go
new file mode 100644
index 0000000000..b6380ab018
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/feature_flags.go
@@ -0,0 +1,79 @@
+package gitlab
+
+import (
+ "fmt"
+ "net/url"
+)
+
+// FeaturesService handles the communication with the application FeaturesService
+// related methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/features.html
+type FeaturesService struct {
+ client *Client
+}
+
+// Feature represents a GitLab feature flag.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/features.html
+type Feature struct {
+ Name string `json:"name"`
+ State string `json:"state"`
+ Gates []Gate
+}
+
+// Gate represents a gate of a GitLab feature flag.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/features.html
+type Gate struct {
+ Key string `json:"key"`
+ Value interface{} `json:"value"`
+}
+
+func (f Feature) String() string {
+ return Stringify(f)
+}
+
+// ListFeatures gets a list of feature flags
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/features.html#list-all-features
+func (s *FeaturesService) ListFeatures(options ...OptionFunc) ([]*Feature, *Response, error) {
+ req, err := s.client.NewRequest("GET", "features", nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var f []*Feature
+ resp, err := s.client.Do(req, &f)
+ if err != nil {
+ return nil, resp, err
+ }
+ return f, resp, err
+}
+
+// SetFeatureFlag sets or creates a feature flag gate
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/features.html#set-or-create-a-feature
+func (s *FeaturesService) SetFeatureFlag(name string, value interface{}, options ...OptionFunc) (*Feature, *Response, error) {
+ u := fmt.Sprintf("features/%s", url.QueryEscape(name))
+
+ opt := struct {
+ Value interface{} `url:"value" json:"value"`
+ }{
+ value,
+ }
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ f := &Feature{}
+ resp, err := s.client.Do(req, f)
+ if err != nil {
+ return nil, resp, err
+ }
+ return f, resp, err
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/feature_flags_test.go b/vendor/github.com/lkysow/go-gitlab/feature_flags_test.go
new file mode 100644
index 0000000000..1003920f80
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/feature_flags_test.go
@@ -0,0 +1,92 @@
+package gitlab
+
+import (
+ "fmt"
+ "net/http"
+ "reflect"
+ "testing"
+)
+
+func TestListFeatureFlags(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/features", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "GET")
+ fmt.Fprint(w, `
+ [
+ {
+ "name": "experimental_feature",
+ "state": "off",
+ "gates": [
+ {
+ "key": "boolean",
+ "value": false
+ }
+ ]
+ },
+ {
+ "name": "new_library",
+ "state": "on"
+ }
+ ]
+ `)
+ })
+
+ features, _, err := client.Features.ListFeatures()
+ if err != nil {
+ t.Errorf("Features.ListFeatures returned error: %v", err)
+ }
+
+ want := []*Feature{
+ {Name: "experimental_feature", State: "off", Gates: []Gate{
+ {Key: "boolean", Value: false},
+ }},
+ {Name: "new_library", State: "on"},
+ }
+ if !reflect.DeepEqual(want, features) {
+ t.Errorf("Features.ListFeatures returned %+v, want %+v", features, want)
+ }
+}
+
+func TestSetFeatureFlag(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/features/new_library", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "POST")
+ fmt.Fprint(w, `
+ {
+ "name": "new_library",
+ "state": "conditional",
+ "gates": [
+ {
+ "key": "boolean",
+ "value": false
+ },
+ {
+ "key": "percentage_of_time",
+ "value": 30
+ }
+ ]
+ }
+ `)
+ })
+
+ feature, _, err := client.Features.SetFeatureFlag("new_library", "30")
+ if err != nil {
+ t.Errorf("Features.SetFeatureFlag returned error: %v", err)
+ }
+
+ want := &Feature{
+ Name: "new_library",
+ State: "conditional",
+ Gates: []Gate{
+ {Key: "boolean", Value: false},
+ {Key: "percentage_of_time", Value: 30.0},
+ },
+ }
+ if !reflect.DeepEqual(want, feature) {
+ t.Errorf("Features.SetFeatureFlag returned %+v, want %+v", feature, want)
+ }
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/gitlab.go b/vendor/github.com/lkysow/go-gitlab/gitlab.go
new file mode 100644
index 0000000000..17617bd5db
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/gitlab.go
@@ -0,0 +1,620 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// 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.
+//
+
+package gitlab
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "sort"
+ "strconv"
+ "strings"
+
+ "github.com/google/go-querystring/query"
+)
+
+const (
+ libraryVersion = "0.2.0"
+ defaultBaseURL = "https://gitlab.com/api/v4/"
+ userAgent = "go-gitlab/" + libraryVersion
+)
+
+// tokenType represents a token type within GitLab.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/
+type tokenType int
+
+// List of available token type
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/
+const (
+ privateToken tokenType = iota
+ oAuthToken
+)
+
+// AccessLevelValue represents a permission level within GitLab.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/permissions/permissions.html
+type AccessLevelValue int
+
+// List of available access levels
+//
+// GitLab API docs: https://docs.gitlab.com/ce/permissions/permissions.html
+const (
+ GuestPermissions AccessLevelValue = 10
+ ReporterPermissions AccessLevelValue = 20
+ DeveloperPermissions AccessLevelValue = 30
+ MasterPermissions AccessLevelValue = 40
+ OwnerPermission AccessLevelValue = 50
+)
+
+// NotificationLevelValue represents a notification level.
+type NotificationLevelValue int
+
+// String implements the fmt.Stringer interface.
+func (l NotificationLevelValue) String() string {
+ return notificationLevelNames[l]
+}
+
+// MarshalJSON implements the json.Marshaler interface.
+func (l NotificationLevelValue) MarshalJSON() ([]byte, error) {
+ return json.Marshal(l.String())
+}
+
+// UnmarshalJSON implements the json.Unmarshaler interface.
+func (l *NotificationLevelValue) UnmarshalJSON(data []byte) error {
+ var raw interface{}
+ if err := json.Unmarshal(data, &raw); err != nil {
+ return err
+ }
+
+ switch raw := raw.(type) {
+ case float64:
+ *l = NotificationLevelValue(raw)
+ case string:
+ *l = notificationLevelTypes[raw]
+ default:
+ return fmt.Errorf("json: cannot unmarshal %T into Go value of type %T", raw, *l)
+ }
+
+ return nil
+}
+
+// List of valid notification levels.
+const (
+ DisabledNotificationLevel NotificationLevelValue = iota
+ ParticipatingNotificationLevel
+ WatchNotificationLevel
+ GlobalNotificationLevel
+ MentionNotificationLevel
+ CustomNotificationLevel
+)
+
+var notificationLevelNames = [...]string{
+ "disabled",
+ "participating",
+ "watch",
+ "global",
+ "mention",
+ "custom",
+}
+
+var notificationLevelTypes = map[string]NotificationLevelValue{
+ "disabled": DisabledNotificationLevel,
+ "participating": ParticipatingNotificationLevel,
+ "watch": WatchNotificationLevel,
+ "global": GlobalNotificationLevel,
+ "mention": MentionNotificationLevel,
+ "custom": CustomNotificationLevel,
+}
+
+// VisibilityValue represents a visibility level within GitLab.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/
+type VisibilityValue string
+
+// List of available visibility levels
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/
+const (
+ PrivateVisibility VisibilityValue = "private"
+ InternalVisibility VisibilityValue = "internal"
+ PublicVisibility VisibilityValue = "public"
+)
+
+// A Client manages communication with the GitLab API.
+type Client struct {
+ // HTTP client used to communicate with the API.
+ client *http.Client
+
+ // Base URL for API requests. Defaults to the public GitLab API, but can be
+ // set to a domain endpoint to use with aself hosted GitLab server. baseURL
+ // should always be specified with a trailing slash.
+ baseURL *url.URL
+
+ // token type used to make authenticated API calls.
+ tokenType tokenType
+
+ // token used to make authenticated API calls.
+ token string
+
+ // User agent used when communicating with the GitLab API.
+ UserAgent string
+
+ // Services used for talking to different parts of the GitLab API.
+ Branches *BranchesService
+ BuildVariables *BuildVariablesService
+ Commits *CommitsService
+ DeployKeys *DeployKeysService
+ Environments *EnvironmentsService
+ Features *FeaturesService
+ Groups *GroupsService
+ GroupMembers *GroupMembersService
+ Issues *IssuesService
+ Jobs *JobsService
+ Labels *LabelsService
+ MergeRequests *MergeRequestsService
+ Milestones *MilestonesService
+ Namespaces *NamespacesService
+ Notes *NotesService
+ NotificationSettings *NotificationSettingsService
+ Projects *ProjectsService
+ ProjectMembers *ProjectMembersService
+ ProjectSnippets *ProjectSnippetsService
+ Pipelines *PipelinesService
+ PipelineTriggers *PipelineTriggersService
+ Repositories *RepositoriesService
+ RepositoryFiles *RepositoryFilesService
+ Services *ServicesService
+ Session *SessionService
+ Settings *SettingsService
+ SystemHooks *SystemHooksService
+ Tags *TagsService
+ Todos *TodosService
+ Users *UsersService
+ Version *VersionService
+ Wikis *WikisService
+}
+
+// ListOptions specifies the optional parameters to various List methods that
+// support pagination.
+type ListOptions struct {
+ // For paginated result sets, page of results to retrieve.
+ Page int `url:"page,omitempty" json:"page,omitempty"`
+
+ // For paginated result sets, the number of results to include per page.
+ PerPage int `url:"per_page,omitempty" json:"per_page,omitempty"`
+}
+
+// NewClient returns a new GitLab API client. If a nil httpClient is
+// provided, http.DefaultClient will be used. To use API methods which require
+// authentication, provide a valid private token.
+func NewClient(httpClient *http.Client, token string) *Client {
+ return newClient(httpClient, privateToken, token)
+}
+
+// NewOAuthClient returns a new GitLab API client. If a nil httpClient is
+// provided, http.DefaultClient will be used. To use API methods which require
+// authentication, provide a valid oauth token.
+func NewOAuthClient(httpClient *http.Client, token string) *Client {
+ return newClient(httpClient, oAuthToken, token)
+}
+
+func newClient(httpClient *http.Client, tokenType tokenType, token string) *Client {
+ if httpClient == nil {
+ httpClient = http.DefaultClient
+ }
+
+ c := &Client{client: httpClient, tokenType: tokenType, token: token, UserAgent: userAgent}
+ if err := c.SetBaseURL(defaultBaseURL); err != nil {
+ // Should never happen since defaultBaseURL is our constant.
+ panic(err)
+ }
+
+ // Create the internal timeStats service.
+ timeStats := &timeStatsService{client: c}
+
+ // Create all the public services.
+ c.Branches = &BranchesService{client: c}
+ c.BuildVariables = &BuildVariablesService{client: c}
+ c.Commits = &CommitsService{client: c}
+ c.DeployKeys = &DeployKeysService{client: c}
+ c.Environments = &EnvironmentsService{client: c}
+ c.Features = &FeaturesService{client: c}
+ c.Groups = &GroupsService{client: c}
+ c.GroupMembers = &GroupMembersService{client: c}
+ c.Issues = &IssuesService{client: c, timeStats: timeStats}
+ c.Jobs = &JobsService{client: c}
+ c.Labels = &LabelsService{client: c}
+ c.MergeRequests = &MergeRequestsService{client: c, timeStats: timeStats}
+ c.Milestones = &MilestonesService{client: c}
+ c.Namespaces = &NamespacesService{client: c}
+ c.Notes = &NotesService{client: c}
+ c.NotificationSettings = &NotificationSettingsService{client: c}
+ c.Projects = &ProjectsService{client: c}
+ c.ProjectMembers = &ProjectMembersService{client: c}
+ c.ProjectSnippets = &ProjectSnippetsService{client: c}
+ c.Pipelines = &PipelinesService{client: c}
+ c.PipelineTriggers = &PipelineTriggersService{client: c}
+ c.Repositories = &RepositoriesService{client: c}
+ c.RepositoryFiles = &RepositoryFilesService{client: c}
+ c.Services = &ServicesService{client: c}
+ c.Session = &SessionService{client: c}
+ c.Settings = &SettingsService{client: c}
+ c.SystemHooks = &SystemHooksService{client: c}
+ c.Tags = &TagsService{client: c}
+ c.Todos = &TodosService{client: c}
+ c.Users = &UsersService{client: c}
+ c.Version = &VersionService{client: c}
+ c.Wikis = &WikisService{client: c}
+
+ return c
+}
+
+// BaseURL return a copy of the baseURL.
+func (c *Client) BaseURL() *url.URL {
+ u := *c.baseURL
+ return &u
+}
+
+// SetBaseURL sets the base URL for API requests to a custom endpoint. urlStr
+// should always be specified with a trailing slash.
+func (c *Client) SetBaseURL(urlStr string) error {
+ // Make sure the given URL end with a slash
+ if !strings.HasSuffix(urlStr, "/") {
+ urlStr += "/"
+ }
+
+ var err error
+ c.baseURL, err = url.Parse(urlStr)
+ return err
+}
+
+// NewRequest creates an API request. A relative URL path can be provided in
+// urlStr, in which case it is resolved relative to the base URL of the Client.
+// Relative URL paths should always be specified without a preceding slash. If
+// specified, the value pointed to by body is JSON encoded and included as the
+// request body.
+func (c *Client) NewRequest(method, path string, opt interface{}, options []OptionFunc) (*http.Request, error) {
+ u := *c.baseURL
+ // Set the encoded opaque data
+ u.Opaque = c.baseURL.Path + path
+
+ if opt != nil {
+ q, err := query.Values(opt)
+ if err != nil {
+ return nil, err
+ }
+ u.RawQuery = q.Encode()
+ }
+
+ req := &http.Request{
+ Method: method,
+ URL: &u,
+ Proto: "HTTP/1.1",
+ ProtoMajor: 1,
+ ProtoMinor: 1,
+ Header: make(http.Header),
+ Host: u.Host,
+ }
+
+ for _, fn := range options {
+ if err := fn(req); err != nil {
+ return nil, err
+ }
+ }
+
+ if method == "POST" || method == "PUT" {
+ bodyBytes, err := json.Marshal(opt)
+ if err != nil {
+ return nil, err
+ }
+ bodyReader := bytes.NewReader(bodyBytes)
+
+ u.RawQuery = ""
+ req.Body = ioutil.NopCloser(bodyReader)
+ req.ContentLength = int64(bodyReader.Len())
+ req.Header.Set("Content-Type", "application/json")
+ }
+
+ req.Header.Set("Accept", "application/json")
+
+ switch c.tokenType {
+ case privateToken:
+ req.Header.Set("PRIVATE-TOKEN", c.token)
+ case oAuthToken:
+ req.Header.Set("Authorization", "Bearer "+c.token)
+ }
+
+ if c.UserAgent != "" {
+ req.Header.Set("User-Agent", c.UserAgent)
+ }
+
+ return req, nil
+}
+
+// Response is a GitLab API response. This wraps the standard http.Response
+// returned from GitLab and provides convenient access to things like
+// pagination links.
+type Response struct {
+ *http.Response
+
+ // These fields provide the page values for paginating through a set of
+ // results. Any or all of these may be set to the zero value for
+ // responses that are not part of a paginated set, or for which there
+ // are no additional pages.
+
+ NextPage int
+ PrevPage int
+ FirstPage int
+ LastPage int
+}
+
+// newResponse creates a new Response for the provided http.Response.
+func newResponse(r *http.Response) *Response {
+ response := &Response{Response: r}
+ response.populatePageValues()
+ return response
+}
+
+// populatePageValues parses the HTTP Link response headers and populates the
+// various pagination link values in the Response.
+func (r *Response) populatePageValues() {
+ if links, ok := r.Response.Header["Link"]; ok && len(links) > 0 {
+ for _, link := range strings.Split(links[0], ",") {
+ segments := strings.Split(strings.TrimSpace(link), ";")
+
+ // link must at least have href and rel
+ if len(segments) < 2 {
+ continue
+ }
+
+ // ensure href is properly formatted
+ if !strings.HasPrefix(segments[0], "<") || !strings.HasSuffix(segments[0], ">") {
+ continue
+ }
+
+ // try to pull out page parameter
+ url, err := url.Parse(segments[0][1 : len(segments[0])-1])
+ if err != nil {
+ continue
+ }
+ page := url.Query().Get("page")
+ if page == "" {
+ continue
+ }
+
+ for _, segment := range segments[1:] {
+ switch strings.TrimSpace(segment) {
+ case `rel="next"`:
+ r.NextPage, _ = strconv.Atoi(page)
+ case `rel="prev"`:
+ r.PrevPage, _ = strconv.Atoi(page)
+ case `rel="first"`:
+ r.FirstPage, _ = strconv.Atoi(page)
+ case `rel="last"`:
+ r.LastPage, _ = strconv.Atoi(page)
+ }
+
+ }
+ }
+ }
+}
+
+// Do sends an API request and returns the API response. The API response is
+// JSON decoded and stored in the value pointed to by v, or returned as an
+// error if an API error has occurred. If v implements the io.Writer
+// interface, the raw response body will be written to v, without attempting to
+// first decode it.
+func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) {
+ resp, err := c.client.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ response := newResponse(resp)
+
+ err = CheckResponse(resp)
+ if err != nil {
+ // even though there was an error, we still return the response
+ // in case the caller wants to inspect it further
+ return response, err
+ }
+
+ if v != nil {
+ if w, ok := v.(io.Writer); ok {
+ _, err = io.Copy(w, resp.Body)
+ } else {
+ err = json.NewDecoder(resp.Body).Decode(v)
+ }
+ }
+
+ return response, err
+}
+
+// Helper function to accept and format both the project ID or name as project
+// identifier for all API calls.
+func parseID(id interface{}) (string, error) {
+ switch v := id.(type) {
+ case int:
+ return strconv.Itoa(v), nil
+ case string:
+ return v, nil
+ default:
+ return "", fmt.Errorf("invalid ID type %#v, the ID must be an int or a string", id)
+ }
+}
+
+// An ErrorResponse reports one or more errors caused by an API request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/README.html#data-validation-and-error-reporting
+type ErrorResponse struct {
+ Response *http.Response
+ Message string
+}
+
+func (e *ErrorResponse) Error() string {
+ path, _ := url.QueryUnescape(e.Response.Request.URL.Opaque)
+ u := fmt.Sprintf("%s://%s%s", e.Response.Request.URL.Scheme, e.Response.Request.URL.Host, path)
+ return fmt.Sprintf("%s %s: %d %s", e.Response.Request.Method, u, e.Response.StatusCode, e.Message)
+}
+
+// CheckResponse checks the API response for errors, and returns them if present.
+func CheckResponse(r *http.Response) error {
+ switch r.StatusCode {
+ case 200, 201, 202, 204, 304:
+ return nil
+ }
+
+ errorResponse := &ErrorResponse{Response: r}
+ data, err := ioutil.ReadAll(r.Body)
+ if err == nil && data != nil {
+ var raw interface{}
+ if err := json.Unmarshal(data, &raw); err != nil {
+ errorResponse.Message = "failed to parse unknown error format"
+ }
+
+ errorResponse.Message = parseError(raw)
+ }
+
+ return errorResponse
+}
+
+// Format:
+// {
+// "message": {
+// "": [
+// "",
+// "",
+// ...
+// ],
+// "": {
+// "": [
+// "",
+// "",
+// ...
+// ],
+// }
+// },
+// "error": ""
+// }
+func parseError(raw interface{}) string {
+ switch raw := raw.(type) {
+ case string:
+ return raw
+
+ case []interface{}:
+ var errs []string
+ for _, v := range raw {
+ errs = append(errs, parseError(v))
+ }
+ return fmt.Sprintf("[%s]", strings.Join(errs, ", "))
+
+ case map[string]interface{}:
+ var errs []string
+ for k, v := range raw {
+ errs = append(errs, fmt.Sprintf("{%s: %s}", k, parseError(v)))
+ }
+ sort.Strings(errs)
+ return strings.Join(errs, ", ")
+
+ default:
+ return fmt.Sprintf("failed to parse unexpected error type: %T", raw)
+ }
+}
+
+// OptionFunc can be passed to all API requests to make the API call as if you were
+// another user, provided your private token is from an administrator account.
+//
+// GitLab docs: https://docs.gitlab.com/ce/api/README.html#sudo
+type OptionFunc func(*http.Request) error
+
+// WithSudo takes either a username or user ID and sets the SUDO request header
+func WithSudo(uid interface{}) OptionFunc {
+ return func(req *http.Request) error {
+ user, err := parseID(uid)
+ if err != nil {
+ return err
+ }
+ req.Header.Set("SUDO", user)
+ return nil
+ }
+}
+
+// WithContext runs the request with the provided context
+func WithContext(ctx context.Context) OptionFunc {
+ return func(req *http.Request) error {
+ *req = *req.WithContext(ctx)
+ return nil
+ }
+}
+
+// Bool is a helper routine that allocates a new bool value
+// to store v and returns a pointer to it.
+func Bool(v bool) *bool {
+ p := new(bool)
+ *p = v
+ return p
+}
+
+// Int is a helper routine that allocates a new int32 value
+// to store v and returns a pointer to it, but unlike Int32
+// its argument value is an int.
+func Int(v int) *int {
+ p := new(int)
+ *p = v
+ return p
+}
+
+// String is a helper routine that allocates a new string value
+// to store v and returns a pointer to it.
+func String(v string) *string {
+ p := new(string)
+ *p = v
+ return p
+}
+
+// AccessLevel is a helper routine that allocates a new AccessLevelValue
+// to store v and returns a pointer to it.
+func AccessLevel(v AccessLevelValue) *AccessLevelValue {
+ p := new(AccessLevelValue)
+ *p = v
+ return p
+}
+
+// NotificationLevel is a helper routine that allocates a new NotificationLevelValue
+// to store v and returns a pointer to it.
+func NotificationLevel(v NotificationLevelValue) *NotificationLevelValue {
+ p := new(NotificationLevelValue)
+ *p = v
+ return p
+}
+
+// Visibility is a helper routine that allocates a new VisibilityValue
+// to store v and returns a pointer to it.
+func Visibility(v VisibilityValue) *VisibilityValue {
+ p := new(VisibilityValue)
+ *p = v
+ return p
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/gitlab_test.go b/vendor/github.com/lkysow/go-gitlab/gitlab_test.go
new file mode 100644
index 0000000000..8e8aa7673f
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/gitlab_test.go
@@ -0,0 +1,114 @@
+package gitlab
+
+import (
+ "context"
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+)
+
+// setup sets up a test HTTP server along with a gitlab.Client that is
+// configured to talk to that test server. Tests should register handlers on
+// mux which provide mock responses for the API method being tested.
+func setup() (*http.ServeMux, *httptest.Server, *Client) {
+ // mux is the HTTP request multiplexer used with the test server.
+ mux := http.NewServeMux()
+
+ // server is a test HTTP server used to provide mock API responses.
+ server := httptest.NewServer(mux)
+
+ // client is the Gitlab client being tested.
+ client := NewClient(nil, "")
+ client.SetBaseURL(server.URL)
+
+ return mux, server, client
+}
+
+// teardown closes the test HTTP server.
+func teardown(server *httptest.Server) {
+ server.Close()
+}
+
+func testURL(t *testing.T, r *http.Request, want string) {
+ if got := r.RequestURI; got != want {
+ t.Errorf("Request url: %+v, want %s", got, want)
+ }
+}
+
+func testMethod(t *testing.T, r *http.Request, want string) {
+ if got := r.Method; got != want {
+ t.Errorf("Request method: %s, want %s", got, want)
+ }
+}
+
+func TestNewClient(t *testing.T) {
+ c := NewClient(nil, "")
+
+ if c.BaseURL().String() != defaultBaseURL {
+ t.Errorf("NewClient BaseURL is %s, want %s", c.BaseURL().String(), defaultBaseURL)
+ }
+ if c.UserAgent != userAgent {
+ t.Errorf("NewClient UserAgent is %s, want %s", c.UserAgent, userAgent)
+ }
+}
+
+func TestCheckResponse(t *testing.T) {
+ req, err := NewClient(nil, "").NewRequest("GET", "test", nil, nil)
+ if err != nil {
+ t.Fatalf("Failed to create request: %v", err)
+ }
+
+ resp := &http.Response{
+ Request: req,
+ StatusCode: http.StatusBadRequest,
+ Body: ioutil.NopCloser(strings.NewReader(`
+ {
+ "message": {
+ "prop1": [
+ "message 1",
+ "message 2"
+ ],
+ "prop2":[
+ "message 3"
+ ],
+ "embed1": {
+ "prop3": [
+ "msg 1",
+ "msg2"
+ ]
+ },
+ "embed2": {
+ "prop4": [
+ "some msg"
+ ]
+ }
+ },
+ "error": "message 1"
+ }`)),
+ }
+
+ errResp := CheckResponse(resp)
+ if errResp == nil {
+ t.Fatal("Expected error response.")
+ }
+
+ want := "GET https://gitlab.com/api/v4/test: 400 {error: message 1}, {message: {embed1: {prop3: [msg 1, msg2]}}, {embed2: {prop4: [some msg]}}, {prop1: [message 1, message 2]}, {prop2: [message 3]}}"
+
+ if errResp.Error() != want {
+ t.Errorf("Expected error: %s, got %s", want, errResp.Error())
+ }
+}
+
+func TestRequestWithContext(t *testing.T) {
+ ctx := context.WithValue(context.Background(), interface{}("myKey"), interface{}("myValue"))
+ req, err := NewClient(nil, "").NewRequest("GET", "test", nil, []OptionFunc{WithContext(ctx)})
+ if err != nil {
+ t.Fatalf("Failed to create request: %v", err)
+ }
+
+ if req.Context() != ctx {
+ t.Fatal("Context was not set correctly")
+ }
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/group_members.go b/vendor/github.com/lkysow/go-gitlab/group_members.go
new file mode 100644
index 0000000000..d3f5876fed
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/group_members.go
@@ -0,0 +1,193 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// 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.
+//
+
+package gitlab
+
+import (
+ "fmt"
+ "net/url"
+ "time"
+)
+
+// GroupMembersService handles communication with the group members
+// related methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/members.html
+type GroupMembersService struct {
+ client *Client
+}
+
+// GroupMember represents a GitLab group member.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/members.html
+type GroupMember struct {
+ ID int `json:"id"`
+ Username string `json:"username"`
+ Email string `json:"email"`
+ Name string `json:"name"`
+ State string `json:"state"`
+ CreatedAt *time.Time `json:"created_at"`
+ AccessLevel AccessLevelValue `json:"access_level"`
+}
+
+// ListGroupMembersOptions represents the available ListGroupMembers()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#list-all-members-of-a-group-or-project
+type ListGroupMembersOptions struct {
+ ListOptions
+}
+
+// ListGroupMembers get a list of group members viewable by the authenticated
+// user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#list-all-members-of-a-group-or-project
+func (s *GroupsService) ListGroupMembers(gid interface{}, opt *ListGroupMembersOptions, options ...OptionFunc) ([]*GroupMember, *Response, error) {
+ group, err := parseID(gid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("groups/%s/members", url.QueryEscape(group))
+
+ req, err := s.client.NewRequest("GET", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var gm []*GroupMember
+ resp, err := s.client.Do(req, &gm)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return gm, resp, err
+}
+
+// AddGroupMemberOptions represents the available AddGroupMember() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#add-a-member-to-a-group-or-project
+type AddGroupMemberOptions struct {
+ UserID *int `url:"user_id,omitempty" json:"user_id,omitempty"`
+ AccessLevel *AccessLevelValue `url:"access_level,omitempty" json:"access_level,omitempty"`
+ ExpiresAt *string `url:"expires_at,omitempty" json:"expires_at"`
+}
+
+// GetGroupMember gets a member of a group.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#get-a-member-of-a-group-or-project
+func (s *GroupMembersService) GetGroupMember(gid interface{}, user int, options ...OptionFunc) (*GroupMember, *Response, error) {
+ group, err := parseID(gid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("groups/%s/members/%d", url.QueryEscape(group), user)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ gm := new(GroupMember)
+ resp, err := s.client.Do(req, gm)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return gm, resp, err
+}
+
+// AddGroupMember adds a user to the list of group members.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#add-a-member-to-a-group-or-project
+func (s *GroupMembersService) AddGroupMember(gid interface{}, opt *AddGroupMemberOptions, options ...OptionFunc) (*GroupMember, *Response, error) {
+ group, err := parseID(gid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("groups/%s/members", url.QueryEscape(group))
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ gm := new(GroupMember)
+ resp, err := s.client.Do(req, gm)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return gm, resp, err
+}
+
+// EditGroupMemberOptions represents the available EditGroupMember()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#edit-a-member-of-a-group-or-project
+type EditGroupMemberOptions struct {
+ AccessLevel *AccessLevelValue `url:"access_level,omitempty" json:"access_level,omitempty"`
+ ExpiresAt *string `url:"expires_at,omitempty" json:"expires_at"`
+}
+
+// EditGroupMember updates a member of a group.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#edit-a-member-of-a-group-or-project
+func (s *GroupMembersService) EditGroupMember(gid interface{}, user int, opt *EditGroupMemberOptions, options ...OptionFunc) (*GroupMember, *Response, error) {
+ group, err := parseID(gid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("groups/%s/members/%d", url.QueryEscape(group), user)
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ gm := new(GroupMember)
+ resp, err := s.client.Do(req, gm)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return gm, resp, err
+}
+
+// RemoveGroupMember removes user from user team.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#remove-a-member-from-a-group-or-project
+func (s *GroupMembersService) RemoveGroupMember(gid interface{}, user int, options ...OptionFunc) (*Response, error) {
+ group, err := parseID(gid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("groups/%s/members/%d", url.QueryEscape(group), user)
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/groups.go b/vendor/github.com/lkysow/go-gitlab/groups.go
new file mode 100644
index 0000000000..a24ec4876b
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/groups.go
@@ -0,0 +1,270 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// 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.
+//
+
+package gitlab
+
+import (
+ "fmt"
+ "net/url"
+)
+
+// GroupsService handles communication with the group related methods of
+// the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html
+type GroupsService struct {
+ client *Client
+}
+
+// Group represents a GitLab group.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html
+type Group struct {
+ ID int `json:"id"`
+ Name string `json:"name"`
+ Path string `json:"path"`
+ Description string `json:"description"`
+ Visibility *VisibilityValue `json:"visibility"`
+ LFSEnabled bool `json:"lfs_enabled"`
+ AvatarURL string `json:"avatar_url"`
+ WebURL string `json:"web_url"`
+ RequestAccessEnabled bool `json:"request_access_enabled"`
+ FullName string `json:"full_name"`
+ FullPath string `json:"full_path"`
+ ParentID int `json:"parent_id"`
+ Projects []*Project `json:"projects"`
+ Statistics *StorageStatistics `json:"statistics"`
+}
+
+// ListGroupsOptions represents the available ListGroups() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html#list-project-groups
+type ListGroupsOptions struct {
+ ListOptions
+ AllAvailable *bool `url:"all_available,omitempty" json:"all_available,omitempty"`
+ OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"`
+ Owned *bool `url:"owned,omitempty" json:"owned,omitempty"`
+ Search *string `url:"search,omitempty" json:"search,omitempty"`
+ Sort *string `url:"sort,omitempty" json:"sort,omitempty"`
+ Statistics *bool `url:"statistics,omitempty" json:"statistics,omitempty"`
+}
+
+// ListGroups gets a list of groups (as user: my groups, as admin: all groups).
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/groups.html#list-project-groups
+func (s *GroupsService) ListGroups(opt *ListGroupsOptions, options ...OptionFunc) ([]*Group, *Response, error) {
+ req, err := s.client.NewRequest("GET", "groups", opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var g []*Group
+ resp, err := s.client.Do(req, &g)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return g, resp, err
+}
+
+// GetGroup gets all details of a group.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html#details-of-a-group
+func (s *GroupsService) GetGroup(gid interface{}, options ...OptionFunc) (*Group, *Response, error) {
+ group, err := parseID(gid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("groups/%s", url.QueryEscape(group))
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ g := new(Group)
+ resp, err := s.client.Do(req, g)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return g, resp, err
+}
+
+// CreateGroupOptions represents the available CreateGroup() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html#new-group
+type CreateGroupOptions struct {
+ Name *string `url:"name,omitempty" json:"name,omitempty"`
+ Path *string `url:"path,omitempty" json:"path,omitempty"`
+ Description *string `url:"description,omitempty" json:"description,omitempty"`
+ Visibility *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"`
+ LFSEnabled *bool `url:"lfs_enabled,omitempty" json:"lfs_enabled,omitempty"`
+ RequestAccessEnabled *bool `url:"request_access_enabled,omitempty" json:"request_access_enabled,omitempty"`
+ ParentID *int `url:"parent_id,omitempty" json:"parent_id,omitempty"`
+}
+
+// CreateGroup creates a new project group. Available only for users who can
+// create groups.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html#new-group
+func (s *GroupsService) CreateGroup(opt *CreateGroupOptions, options ...OptionFunc) (*Group, *Response, error) {
+ req, err := s.client.NewRequest("POST", "groups", opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ g := new(Group)
+ resp, err := s.client.Do(req, g)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return g, resp, err
+}
+
+// TransferGroup transfers a project to the Group namespace. Available only
+// for admin.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/groups.html#transfer-project-to-group
+func (s *GroupsService) TransferGroup(gid interface{}, project int, options ...OptionFunc) (*Group, *Response, error) {
+ group, err := parseID(gid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("groups/%s/projects/%d", url.QueryEscape(group), project)
+
+ req, err := s.client.NewRequest("POST", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ g := new(Group)
+ resp, err := s.client.Do(req, g)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return g, resp, err
+}
+
+// UpdateGroupOptions represents the set of available options to update a Group;
+// as of today these are exactly the same available when creating a new Group.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html#update-group
+type UpdateGroupOptions CreateGroupOptions
+
+// UpdateGroup updates an existing group; only available to group owners and
+// administrators.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html#update-group
+func (s *GroupsService) UpdateGroup(gid interface{}, opt *UpdateGroupOptions, options ...OptionFunc) (*Group, *Response, error) {
+ group, err := parseID(gid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("groups/%s", url.QueryEscape(group))
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ g := new(Group)
+ resp, err := s.client.Do(req, g)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return g, resp, err
+}
+
+// DeleteGroup removes group with all projects inside.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html#remove-group
+func (s *GroupsService) DeleteGroup(gid interface{}, options ...OptionFunc) (*Response, error) {
+ group, err := parseID(gid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("groups/%s", url.QueryEscape(group))
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// SearchGroup get all groups that match your string in their name or path.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html#search-for-group
+func (s *GroupsService) SearchGroup(query string, options ...OptionFunc) ([]*Group, *Response, error) {
+ var q struct {
+ Search string `url:"search,omitempty" json:"search,omitempty"`
+ }
+ q.Search = query
+
+ req, err := s.client.NewRequest("GET", "groups", &q, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var g []*Group
+ resp, err := s.client.Do(req, &g)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return g, resp, err
+}
+
+// ListGroupProjectsOptions represents the available ListGroupProjects()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/groups.html#list-a-group-s-projects
+type ListGroupProjectsOptions struct {
+ ListOptions
+}
+
+// ListGroupProjects get a list of group projects
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/groups.html#list-a-group-s-projects
+func (s *GroupsService) ListGroupProjects(gid interface{}, opt *ListGroupProjectsOptions, options ...OptionFunc) ([]*Project, *Response, error) {
+ group, err := parseID(gid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("groups/%s/projects", url.QueryEscape(group))
+
+ req, err := s.client.NewRequest("GET", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var p []*Project
+ resp, err := s.client.Do(req, &p)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return p, resp, err
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/issues.go b/vendor/github.com/lkysow/go-gitlab/issues.go
new file mode 100644
index 0000000000..202e9319fe
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/issues.go
@@ -0,0 +1,362 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// 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.
+//
+
+package gitlab
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/url"
+ "strings"
+ "time"
+)
+
+// IssuesService handles communication with the issue related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html
+type IssuesService struct {
+ client *Client
+ timeStats *timeStatsService
+}
+
+// Issue represents a GitLab issue.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html
+type Issue struct {
+ ID int `json:"id"`
+ IID int `json:"iid"`
+ ProjectID int `json:"project_id"`
+ Title string `json:"title"`
+ Description string `json:"description"`
+ Labels []string `json:"labels"`
+ Milestone *Milestone `json:"milestone"`
+ Assignee struct {
+ ID int `json:"id"`
+ Username string `json:"username"`
+ Email string `json:"email"`
+ Name string `json:"name"`
+ State string `json:"state"`
+ CreatedAt *time.Time `json:"created_at"`
+ } `json:"assignee"`
+ Author struct {
+ ID int `json:"id"`
+ Username string `json:"username"`
+ Email string `json:"email"`
+ Name string `json:"name"`
+ State string `json:"state"`
+ CreatedAt *time.Time `json:"created_at"`
+ } `json:"author"`
+ State string `json:"state"`
+ UpdatedAt *time.Time `json:"updated_at"`
+ CreatedAt *time.Time `json:"created_at"`
+ Subscribed bool `json:"subscribed"`
+ UserNotesCount int `json:"user_notes_count"`
+ Confidential bool `json:"confidential"`
+ DueDate string `json:"due_date"`
+ WebURL string `json:"web_url"`
+}
+
+func (i Issue) String() string {
+ return Stringify(i)
+}
+
+// Labels is a custom type with specific marshaling characteristics.
+type Labels []string
+
+// MarshalJSON implements the json.Marshaler interface.
+func (l *Labels) MarshalJSON() ([]byte, error) {
+ return json.Marshal(strings.Join(*l, ","))
+}
+
+// ListIssuesOptions represents the available ListIssues() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#list-issues
+type ListIssuesOptions struct {
+ ListOptions
+ State *string `url:"state,omitempty" json:"state,omitempty"`
+ Labels Labels `url:"labels,comma,omitempty" json:"labels,omitempty"`
+ Milestone *string `url:"milestone,omitempty" json:"milestone,omitempty"`
+ Scope *string `url:"scope,omitempty" json:"scope,omitempty"`
+ AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"`
+ AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"`
+ MyReactionEmoji *string `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"`
+ IIDs []int `url:"iids[],omitempty" json:"iids,omitempty"`
+ OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"`
+ Sort *string `url:"sort,omitempty" json:"sort,omitempty"`
+ Search *string `url:"search,omitempty" json:"search,omitempty"`
+}
+
+// ListIssues gets all issues created by authenticated user. This function
+// takes pagination parameters page and per_page to restrict the list of issues.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#list-issues
+func (s *IssuesService) ListIssues(opt *ListIssuesOptions, options ...OptionFunc) ([]*Issue, *Response, error) {
+ req, err := s.client.NewRequest("GET", "issues", opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var i []*Issue
+ resp, err := s.client.Do(req, &i)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return i, resp, err
+}
+
+// ListGroupIssuesOptions represents the available ListGroupIssues() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#list-group-issues
+type ListGroupIssuesOptions struct {
+ ListOptions
+ State *string `url:"state,omitempty" json:"state,omitempty"`
+ Labels Labels `url:"labels,comma,omitempty" json:"labels,omitempty"`
+ IIDs []int `url:"iids[],omitempty" json:"iids,omitempty"`
+ Milestone *string `url:"milestone,omitempty" json:"milestone,omitempty"`
+ Scope *string `url:"scope,omitempty" json:"scope,omitempty"`
+ AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"`
+ AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"`
+ MyReactionEmoji *string `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"`
+ OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"`
+ Sort *string `url:"sort,omitempty" json:"sort,omitempty"`
+ Search *string `url:"search,omitempty" json:"search,omitempty"`
+}
+
+// ListGroupIssues gets a list of group issues. This function accepts
+// pagination parameters page and per_page to return the list of group issues.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#list-group-issues
+func (s *IssuesService) ListGroupIssues(pid interface{}, opt *ListGroupIssuesOptions, options ...OptionFunc) ([]*Issue, *Response, error) {
+ group, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("groups/%s/issues", url.QueryEscape(group))
+
+ req, err := s.client.NewRequest("GET", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var i []*Issue
+ resp, err := s.client.Do(req, &i)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return i, resp, err
+}
+
+// ListProjectIssuesOptions represents the available ListProjectIssues() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#list-project-issues
+type ListProjectIssuesOptions struct {
+ ListOptions
+ IIDs []int `url:"iids[],omitempty" json:"iids,omitempty"`
+ State *string `url:"state,omitempty" json:"state,omitempty"`
+ Labels Labels `url:"labels,comma,omitempty" json:"labels,omitempty"`
+ Milestone *string `url:"milestone,omitempty" json:"milestone,omitempty"`
+ Scope *string `url:"scope,omitempty" json:"scope,omitempty"`
+ AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"`
+ AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"`
+ MyReactionEmoji *string `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"`
+ OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"`
+ Sort *string `url:"sort,omitempty" json:"sort,omitempty"`
+ Search *string `url:"search,omitempty" json:"search,omitempty"`
+ CreatedAfter *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"`
+ CreatedBefore *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"`
+}
+
+// ListProjectIssues gets a list of project issues. This function accepts
+// pagination parameters page and per_page to return the list of project issues.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#list-project-issues
+func (s *IssuesService) ListProjectIssues(pid interface{}, opt *ListProjectIssuesOptions, options ...OptionFunc) ([]*Issue, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/issues", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var i []*Issue
+ resp, err := s.client.Do(req, &i)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return i, resp, err
+}
+
+// GetIssue gets a single project issue.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#single-issues
+func (s *IssuesService) GetIssue(pid interface{}, issue int, options ...OptionFunc) (*Issue, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/issues/%d", url.QueryEscape(project), issue)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ i := new(Issue)
+ resp, err := s.client.Do(req, i)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return i, resp, err
+}
+
+// CreateIssueOptions represents the available CreateIssue() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#new-issues
+type CreateIssueOptions struct {
+ Title *string `url:"title,omitempty" json:"title,omitempty"`
+ Description *string `url:"description,omitempty" json:"description,omitempty"`
+ AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"`
+ MilestoneID *int `url:"milestone_id,omitempty" json:"milestone_id,omitempty"`
+ Labels Labels `url:"labels,comma,omitempty" json:"labels,omitempty"`
+}
+
+// CreateIssue creates a new project issue.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#new-issues
+func (s *IssuesService) CreateIssue(pid interface{}, opt *CreateIssueOptions, options ...OptionFunc) (*Issue, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/issues", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ i := new(Issue)
+ resp, err := s.client.Do(req, i)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return i, resp, err
+}
+
+// UpdateIssueOptions represents the available UpdateIssue() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#edit-issues
+type UpdateIssueOptions struct {
+ Title *string `url:"title,omitempty" json:"title,omitempty"`
+ Description *string `url:"description,omitempty" json:"description,omitempty"`
+ AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"`
+ MilestoneID *int `url:"milestone_id,omitempty" json:"milestone_id,omitempty"`
+ Labels Labels `url:"labels,comma,omitempty" json:"labels,omitempty"`
+ StateEvent *string `url:"state_event,omitempty" json:"state_event,omitempty"`
+}
+
+// UpdateIssue updates an existing project issue. This function is also used
+// to mark an issue as closed.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#edit-issues
+func (s *IssuesService) UpdateIssue(pid interface{}, issue int, opt *UpdateIssueOptions, options ...OptionFunc) (*Issue, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/issues/%d", url.QueryEscape(project), issue)
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ i := new(Issue)
+ resp, err := s.client.Do(req, i)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return i, resp, err
+}
+
+// DeleteIssue deletes a single project issue.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#delete-an-issue
+func (s *IssuesService) DeleteIssue(pid interface{}, issue int, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/issues/%d", url.QueryEscape(project), issue)
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// SetTimeEstimate sets the time estimate for a single project issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/issues.html#set-a-time-estimate-for-an-issue
+func (s *IssuesService) SetTimeEstimate(pid interface{}, issue int, opt *SetTimeEstimateOptions, options ...OptionFunc) (*TimeStats, *Response, error) {
+ return s.timeStats.setTimeEstimate(pid, "issues", issue, opt, options...)
+}
+
+// ResetTimeEstimate resets the time estimate for a single project issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/issues.html#reset-the-time-estimate-for-an-issue
+func (s *IssuesService) ResetTimeEstimate(pid interface{}, issue int, options ...OptionFunc) (*TimeStats, *Response, error) {
+ return s.timeStats.resetTimeEstimate(pid, "issues", issue, options...)
+}
+
+// AddSpentTime adds spent time for a single project issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/issues.html#add-spent-time-for-an-issue
+func (s *IssuesService) AddSpentTime(pid interface{}, issue int, opt *AddSpentTimeOptions, options ...OptionFunc) (*TimeStats, *Response, error) {
+ return s.timeStats.addSpentTime(pid, "issues", issue, opt, options...)
+}
+
+// ResetSpentTime resets the spent time for a single project issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/issues.html#reset-spent-time-for-an-issue
+func (s *IssuesService) ResetSpentTime(pid interface{}, issue int, options ...OptionFunc) (*TimeStats, *Response, error) {
+ return s.timeStats.resetSpentTime(pid, "issues", issue, options...)
+}
+
+// GetTimeSpent gets the spent time for a single project issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/issues.html#get-time-tracking-stats
+func (s *IssuesService) GetTimeSpent(pid interface{}, issue int, options ...OptionFunc) (*TimeStats, *Response, error) {
+ return s.timeStats.getTimeSpent(pid, "issues", issue, options...)
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/jobs.go b/vendor/github.com/lkysow/go-gitlab/jobs.go
new file mode 100644
index 0000000000..e8b4f999fa
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/jobs.go
@@ -0,0 +1,349 @@
+//
+// Copyright 2017, Arkbriar
+//
+// 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.
+//
+
+package gitlab
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "net/url"
+ "time"
+)
+
+// ListJobsOptions are options for two list apis
+type ListJobsOptions struct {
+ ListOptions
+ Scope []BuildState `url:"scope,omitempty" json:"scope,omitempty"`
+}
+
+// JobsService handles communication with the ci builds related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/jobs.html
+type JobsService struct {
+ client *Client
+}
+
+// Job represents a ci build.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/jobs.html
+type Job struct {
+ Commit *Commit `json:"commit"`
+ CreatedAt *time.Time `json:"created_at"`
+ ArtifactsFile struct {
+ Filename string `json:"filename"`
+ Size int `json:"size"`
+ } `json:"artifacts_file"`
+ FinishedAt *time.Time `json:"finished_at"`
+ ID int `json:"id"`
+ Name string `json:"name"`
+ Ref string `json:"ref"`
+ Runner struct {
+ ID int `json:"id"`
+ Description string `json:"description"`
+ Active bool `json:"active"`
+ IsShared bool `json:"is_shared"`
+ Name string `json:"name"`
+ } `json:"runner"`
+ Stage string `json:"stage"`
+ StartedAt *time.Time `json:"started_at"`
+ Status string `json:"status"`
+ Tag bool `json:"tag"`
+ User *User `json:"user"`
+}
+
+// ListProjectJobs gets a list of jobs in a project.
+//
+// The scope of jobs to show, one or array of: created, pending, running,
+// failed, success, canceled, skipped; showing all jobs if none provided
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/jobs.html#list-project-jobs
+func (s *JobsService) ListProjectJobs(pid interface{}, opts *ListJobsOptions, options ...OptionFunc) ([]Job, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/jobs", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, opts, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var jobs []Job
+ resp, err := s.client.Do(req, &jobs)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return jobs, resp, err
+}
+
+// ListPipelineJobs gets a list of jobs for specific pipeline in a
+// project. If the pipeline ID is not found, it will respond with 404.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/jobs.html#list-pipeline-jobs
+func (s *JobsService) ListPipelineJobs(pid interface{}, pipelineID int, opts *ListJobsOptions, options ...OptionFunc) ([]Job, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/pipelines/%d/jobs", project, pipelineID)
+
+ req, err := s.client.NewRequest("GET", u, opts, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var jobs []Job
+ resp, err := s.client.Do(req, &jobs)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return jobs, resp, err
+}
+
+// GetJob gets a single job of a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/jobs.html#get-a-single-job
+func (s *JobsService) GetJob(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/jobs/%d", project, jobID)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ job := new(Job)
+ resp, err := s.client.Do(req, job)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return job, resp, err
+}
+
+// GetJobArtifacts get jobs artifacts of a project
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/jobs.html#get-job-artifacts
+func (s *JobsService) GetJobArtifacts(pid interface{}, jobID int, options ...OptionFunc) (io.Reader, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/jobs/%d/artifacts", project, jobID)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ artifactsBuf := new(bytes.Buffer)
+ resp, err := s.client.Do(req, artifactsBuf)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return artifactsBuf, resp, err
+}
+
+// DownloadArtifactsFile download the artifacts file from the given
+// reference name and job provided the job finished successfully.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/jobs.html#download-the-artifacts-file
+func (s *JobsService) DownloadArtifactsFile(pid interface{}, refName string, job string, options ...OptionFunc) (io.Reader, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/jobs/artifacts/%s/download?job=%s", project, refName, job)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ artifactsBuf := new(bytes.Buffer)
+ resp, err := s.client.Do(req, artifactsBuf)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return artifactsBuf, resp, err
+}
+
+// GetTraceFile gets a trace of a specific job of a project
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/jobs.html#get-a-trace-file
+func (s *JobsService) GetTraceFile(pid interface{}, jobID int, options ...OptionFunc) (io.Reader, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/jobs/%d/trace", project, jobID)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ traceBuf := new(bytes.Buffer)
+ resp, err := s.client.Do(req, traceBuf)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return traceBuf, resp, err
+}
+
+// CancelJob cancels a single job of a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/jobs.html#cancel-a-job
+func (s *JobsService) CancelJob(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/jobs/%d/cancel", project, jobID)
+
+ req, err := s.client.NewRequest("POST", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ job := new(Job)
+ resp, err := s.client.Do(req, job)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return job, resp, err
+}
+
+// RetryJob retries a single job of a project
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/jobs.html#retry-a-job
+func (s *JobsService) RetryJob(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/jobs/%d/retry", project, jobID)
+
+ req, err := s.client.NewRequest("POST", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ job := new(Job)
+ resp, err := s.client.Do(req, job)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return job, resp, err
+}
+
+// EraseJob erases a single job of a project, removes a job
+// artifacts and a job trace.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/jobs.html#erase-a-job
+func (s *JobsService) EraseJob(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/jobs/%d/erase", project, jobID)
+
+ req, err := s.client.NewRequest("POST", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ job := new(Job)
+ resp, err := s.client.Do(req, job)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return job, resp, err
+}
+
+// KeepArtifacts prevents artifacts from being deleted when
+// expiration is set.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/jobs.html#keep-artifacts
+func (s *JobsService) KeepArtifacts(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/jobs/%d/artifacts/keep", project, jobID)
+
+ req, err := s.client.NewRequest("POST", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ job := new(Job)
+ resp, err := s.client.Do(req, job)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return job, resp, err
+}
+
+// PlayJob triggers a nanual action to start a job.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/jobs.html#play-a-job
+func (s *JobsService) PlayJob(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/jobs/%d/play", project, jobID)
+
+ req, err := s.client.NewRequest("POST", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ job := new(Job)
+ resp, err := s.client.Do(req, job)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return job, resp, err
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/labels.go b/vendor/github.com/lkysow/go-gitlab/labels.go
new file mode 100644
index 0000000000..10ede6a7a9
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/labels.go
@@ -0,0 +1,164 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// 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.
+//
+
+package gitlab
+
+import (
+ "fmt"
+ "net/url"
+)
+
+// LabelsService handles communication with the label related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html
+type LabelsService struct {
+ client *Client
+}
+
+// Label represents a GitLab label.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html
+type Label struct {
+ Name string `json:"name"`
+ Color string `json:"color"`
+ Description string `json:"description"`
+ OpenIssuesCount int `json:"open_issues_count"`
+ ClosedIssuesCount int `json:"closed_issues_count"`
+ OpenMergeRequestsCount int `json:"open_merge_requests_count"`
+}
+
+func (l Label) String() string {
+ return Stringify(l)
+}
+
+// ListLabels gets all labels for given project.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#list-labels
+func (s *LabelsService) ListLabels(pid interface{}, options ...OptionFunc) ([]*Label, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/labels", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var l []*Label
+ resp, err := s.client.Do(req, &l)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return l, resp, err
+}
+
+// CreateLabelOptions represents the available CreateLabel() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#create-a-new-label
+type CreateLabelOptions struct {
+ Name *string `url:"name,omitempty" json:"name,omitempty"`
+ Color *string `url:"color,omitempty" json:"color,omitempty"`
+ Description *string `url:"description,omitempty" json:"description,omitempty"`
+}
+
+// CreateLabel creates a new label for given repository with given name and
+// color.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#create-a-new-label
+func (s *LabelsService) CreateLabel(pid interface{}, opt *CreateLabelOptions, options ...OptionFunc) (*Label, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/labels", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ l := new(Label)
+ resp, err := s.client.Do(req, l)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return l, resp, err
+}
+
+// DeleteLabelOptions represents the available DeleteLabel() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#delete-a-label
+type DeleteLabelOptions struct {
+ Name *string `url:"name,omitempty" json:"name,omitempty"`
+}
+
+// DeleteLabel deletes a label given by its name.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#delete-a-label
+func (s *LabelsService) DeleteLabel(pid interface{}, opt *DeleteLabelOptions, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/labels", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("DELETE", u, opt, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// UpdateLabelOptions represents the available UpdateLabel() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#delete-a-label
+type UpdateLabelOptions struct {
+ Name *string `url:"name,omitempty" json:"name,omitempty"`
+ NewName *string `url:"new_name,omitempty" json:"new_name,omitempty"`
+ Color *string `url:"color,omitempty" json:"color,omitempty"`
+ Description *string `url:"description,omitempty" json:"description,omitempty"`
+}
+
+// UpdateLabel updates an existing label with new name or now color. At least
+// one parameter is required, to update the label.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#edit-an-existing-label
+func (s *LabelsService) UpdateLabel(pid interface{}, opt *UpdateLabelOptions, options ...OptionFunc) (*Label, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/labels", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ l := new(Label)
+ resp, err := s.client.Do(req, l)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return l, resp, err
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/merge_requests.go b/vendor/github.com/lkysow/go-gitlab/merge_requests.go
new file mode 100644
index 0000000000..214d8e1a99
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/merge_requests.go
@@ -0,0 +1,485 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// 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.
+//
+
+package gitlab
+
+import (
+ "fmt"
+ "net/url"
+ "time"
+)
+
+// MergeRequestsService handles communication with the merge requests related
+// methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/merge_requests.html
+type MergeRequestsService struct {
+ client *Client
+ timeStats *timeStatsService
+}
+
+// MergeRequest represents a GitLab merge request.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/merge_requests.html
+type MergeRequest struct {
+ ID int `json:"id"`
+ IID int `json:"iid"`
+ ProjectID int `json:"project_id"`
+ Title string `json:"title"`
+ Description string `json:"description"`
+ WorkInProgress bool `json:"work_in_progress"`
+ State string `json:"state"`
+ CreatedAt string `json:"created_at"`
+ UpdatedAt string `json:"updated_at"`
+ TargetBranch string `json:"target_branch"`
+ SourceBranch string `json:"source_branch"`
+ Upvotes int `json:"upvotes"`
+ Downvotes int `json:"downvotes"`
+ Author struct {
+ Name string `json:"name"`
+ Username string `json:"username"`
+ ID int `json:"id"`
+ State string `json:"state"`
+ AvatarURL string `json:"avatar_url"`
+ } `json:"author"`
+ Assignee struct {
+ Name string `json:"name"`
+ Username string `json:"username"`
+ ID int `json:"id"`
+ State string `json:"state"`
+ AvatarURL string `json:"avatar_url"`
+ } `json:"assignee"`
+ SourceProjectID int `json:"source_project_id"`
+ TargetProjectID int `json:"target_project_id"`
+ Labels []string `json:"labels"`
+ Milestone struct {
+ ID int `json:"id"`
+ IID int `json:"iid"`
+ ProjectID int `json:"project_id"`
+ Title string `json:"title"`
+ Description string `json:"description"`
+ State string `json:"state"`
+ CreatedAt *time.Time `json:"created_at"`
+ UpdatedAt *time.Time `json:"updated_at"`
+ DueDate string `json:"due_date"`
+ } `json:"milestone"`
+ MergeWhenPipelineSucceeds bool `json:"merge_when_pipeline_succeeds"`
+ MergeStatus string `json:"merge_status"`
+ SHA string `json:"sha"`
+ Subscribed bool `json:"subscribed"`
+ UserNotesCount int `json:"user_notes_count"`
+ SouldRemoveSourceBranch bool `json:"should_remove_source_branch"`
+ ForceRemoveSourceBranch bool `json:"force_remove_source_branch"`
+ Changes []struct {
+ OldPath string `json:"old_path"`
+ NewPath string `json:"new_path"`
+ AMode string `json:"a_mode"`
+ BMode string `json:"b_mode"`
+ Diff string `json:"diff"`
+ NewFile bool `json:"new_file"`
+ RenamedFile bool `json:"renamed_file"`
+ DeletedFile bool `json:"deleted_file"`
+ } `json:"changes"`
+ WebURL string `json:"web_url"`
+}
+
+func (m MergeRequest) String() string {
+ return Stringify(m)
+}
+
+// MergeRequestApprovals represents GitLab merge request approvals.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_requests.html#merge-request-approvals
+type MergeRequestApprovals struct {
+ ID int `json:"id"`
+ ProjectID int `json:"project_id"`
+ Title string `json:"title"`
+ Description string `json:"description"`
+ State string `json:"state"`
+ CreatedAt *time.Time `json:"created_at"`
+ UpdatedAt *time.Time `json:"updated_at"`
+ MergeStatus string `json:"merge_status"`
+ ApprovalsRequired int `json:"approvals_required"`
+ ApprovalsMissing int `json:"approvals_missing"`
+ ApprovedBy []struct {
+ User struct {
+ Name string `json:"name"`
+ Username string `json:"username"`
+ ID int `json:"id"`
+ State string `json:"state"`
+ AvatarURL string `json:"avatar_url"`
+ WebURL string `json:"web_url"`
+ } `json:"user"`
+ } `json:"approved_by"`
+}
+
+func (m MergeRequestApprovals) String() string {
+ return Stringify(m)
+}
+
+// ListMergeRequestsOptions represents the available ListMergeRequests()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#list-merge-requests
+type ListMergeRequestsOptions struct {
+ ListOptions
+ State *string `url:"state,omitempty" json:"state,omitempty"`
+ OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"`
+ Sort *string `url:"sort,omitempty" json:"sort,omitempty"`
+ Milestone *string `url:"milestone,omitempty" json:"milestone,omitempty"`
+ View *string `url:"view,omitempty" json:"view,omitempty"`
+ Labels Labels `url:"labels,omitempty" json:"labels,omitempty"`
+ CreatedAfter *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"`
+ CreatedBefore *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"`
+ Scope *string `url:"scope,omitempty" json:"scope,omitempty"`
+ AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"`
+ AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"`
+ MyReactionEmoji *string `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"`
+}
+
+// ListMergeRequests gets all merge requests. The state
+// parameter can be used to get only merge requests with a given state (opened,
+// closed, or merged) or all of them (all). The pagination parameters page and
+// per_page can be used to restrict the list of merge requests.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#list-merge-requests
+func (s *MergeRequestsService) ListMergeRequests(opt *ListMergeRequestsOptions, options ...OptionFunc) ([]*MergeRequest, *Response, error) {
+ req, err := s.client.NewRequest("GET", "merge_requests", opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var m []*MergeRequest
+ resp, err := s.client.Do(req, &m)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return m, resp, err
+}
+
+// ListProjectMergeRequestsOptions represents the available ListMergeRequests()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#list-project-merge-requests
+type ListProjectMergeRequestsOptions struct {
+ ListOptions
+ IIDs []int `url:"iids[],omitempty" json:"iids,omitempty"`
+ State *string `url:"state,omitempty" json:"state,omitempty"`
+ OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"`
+ Sort *string `url:"sort,omitempty" json:"sort,omitempty"`
+ Milestone *string `url:"milestone,omitempty" json:"milestone,omitempty"`
+ View *string `url:"view,omitempty" json:"view,omitempty"`
+ Labels Labels `url:"labels,omitempty" json:"labels,omitempty"`
+ CreatedAfter *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"`
+ CreatedBefore *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"`
+ Scope *string `url:"scope,omitempty" json:"scope,omitempty"`
+ AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"`
+ AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"`
+ MyReactionEmoji *string `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"`
+}
+
+// ListProjectMergeRequests gets all merge requests for this project. The state
+// parameter can be used to get only merge requests with a given state (opened,
+// closed, or merged) or all of them (all). The pagination parameters page and
+// per_page can be used to restrict the list of merge requests.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#list-merge-requests
+func (s *MergeRequestsService) ListProjectMergeRequests(pid interface{}, opt *ListProjectMergeRequestsOptions, options ...OptionFunc) ([]*MergeRequest, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/merge_requests", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var m []*MergeRequest
+ resp, err := s.client.Do(req, &m)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return m, resp, err
+}
+
+// GetMergeRequest shows information about a single merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#get-single-mr
+func (s *MergeRequestsService) GetMergeRequest(pid interface{}, mergeRequest int, options ...OptionFunc) (*MergeRequest, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/merge_requests/%d", url.QueryEscape(project), mergeRequest)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ m := new(MergeRequest)
+ resp, err := s.client.Do(req, m)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return m, resp, err
+}
+
+// GetMergeRequestApprovals gets information about a merge requests approvals
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_requests.html#merge-request-approvals
+func (s *MergeRequestsService) GetMergeRequestApprovals(pid interface{}, mergeRequest int, options ...OptionFunc) (*MergeRequestApprovals, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/merge_requests/%d/approvals", url.QueryEscape(project), mergeRequest)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ a := new(MergeRequestApprovals)
+ resp, err := s.client.Do(req, a)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return a, resp, err
+}
+
+// GetMergeRequestCommits gets a list of merge request commits.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#get-single-mr-commits
+func (s *MergeRequestsService) GetMergeRequestCommits(pid interface{}, mergeRequest int, options ...OptionFunc) ([]*Commit, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/merge_requests/%d/commits", url.QueryEscape(project), mergeRequest)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var c []*Commit
+ resp, err := s.client.Do(req, &c)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return c, resp, err
+}
+
+// GetMergeRequestChanges shows information about the merge request including
+// its files and changes.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#get-single-mr-changes
+func (s *MergeRequestsService) GetMergeRequestChanges(pid interface{}, mergeRequest int, options ...OptionFunc) (*MergeRequest, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/merge_requests/%d/changes", url.QueryEscape(project), mergeRequest)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ m := new(MergeRequest)
+ resp, err := s.client.Do(req, m)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return m, resp, err
+}
+
+// CreateMergeRequestOptions represents the available CreateMergeRequest()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#create-mr
+type CreateMergeRequestOptions struct {
+ Title *string `url:"title,omitempty" json:"title,omitempty"`
+ Description *string `url:"description,omitempty" json:"description,omitempty"`
+ SourceBranch *string `url:"source_branch,omitempty" json:"source_branch,omitempty"`
+ TargetBranch *string `url:"target_branch,omitempty" json:"target_branch,omitempty"`
+ AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"`
+ TargetProjectID *int `url:"target_project_id,omitempty" json:"target_project_id,omitempty"`
+}
+
+// CreateMergeRequest creates a new merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#create-mr
+func (s *MergeRequestsService) CreateMergeRequest(pid interface{}, opt *CreateMergeRequestOptions, options ...OptionFunc) (*MergeRequest, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/merge_requests", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ m := new(MergeRequest)
+ resp, err := s.client.Do(req, m)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return m, resp, err
+}
+
+// UpdateMergeRequestOptions represents the available UpdateMergeRequest()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#update-mr
+type UpdateMergeRequestOptions struct {
+ Title *string `url:"title,omitempty" json:"title,omitempty"`
+ Description *string `url:"description,omitempty" json:"description,omitempty"`
+ TargetBranch *string `url:"target_branch,omitempty" json:"target_branch,omitempty"`
+ AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"`
+ Labels Labels `url:"labels,comma,omitempty" json:"labels,omitempty"`
+ MilestoneID *int `url:"milestone_id,omitempty" json:"milestone_id,omitempty"`
+ StateEvent *string `url:"state_event,omitempty" json:"state_event,omitempty"`
+}
+
+// UpdateMergeRequest updates an existing project milestone.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#update-mr
+func (s *MergeRequestsService) UpdateMergeRequest(pid interface{}, mergeRequest int, opt *UpdateMergeRequestOptions, options ...OptionFunc) (*MergeRequest, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/merge_requests/%d", url.QueryEscape(project), mergeRequest)
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ m := new(MergeRequest)
+ resp, err := s.client.Do(req, m)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return m, resp, err
+}
+
+// AcceptMergeRequestOptions represents the available AcceptMergeRequest()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#accept-mr
+type AcceptMergeRequestOptions struct {
+ MergeCommitMessage *string `url:"merge_commit_message,omitempty" json:"merge_commit_message,omitempty"`
+ ShouldRemoveSourceBranch *bool `url:"should_remove_source_branch,omitempty" json:"should_remove_source_branch,omitempty"`
+ MergeWhenPipelineSucceeds *bool `url:"merge_when_pipeline_succeeds,omitempty" json:"merge_when_pipeline_succeeds,omitempty"`
+ Sha *string `url:"sha,omitempty" json:"sha,omitempty"`
+}
+
+// AcceptMergeRequest merges changes submitted with MR using this API. If merge
+// success you get 200 OK. If it has some conflicts and can not be merged - you
+// get 405 and error message 'Branch cannot be merged'. If merge request is
+// already merged or closed - you get 405 and error message 'Method Not Allowed'
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#accept-mr
+func (s *MergeRequestsService) AcceptMergeRequest(pid interface{}, mergeRequest int, opt *AcceptMergeRequestOptions, options ...OptionFunc) (*MergeRequest, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/merge_requests/%d/merge", url.QueryEscape(project), mergeRequest)
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ m := new(MergeRequest)
+ resp, err := s.client.Do(req, m)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return m, resp, err
+}
+
+// SetTimeEstimate sets the time estimate for a single project merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#set-a-time-estimate-for-a-merge-request
+func (s *MergeRequestsService) SetTimeEstimate(pid interface{}, mergeRequest int, opt *SetTimeEstimateOptions, options ...OptionFunc) (*TimeStats, *Response, error) {
+ return s.timeStats.setTimeEstimate(pid, "merge_requests", mergeRequest, opt, options...)
+}
+
+// ResetTimeEstimate resets the time estimate for a single project merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#reset-the-time-estimate-for-a-merge-request
+func (s *MergeRequestsService) ResetTimeEstimate(pid interface{}, mergeRequest int, options ...OptionFunc) (*TimeStats, *Response, error) {
+ return s.timeStats.resetTimeEstimate(pid, "merge_requests", mergeRequest, options...)
+}
+
+// AddSpentTime adds spent time for a single project merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#add-spent-time-for-a-merge-request
+func (s *MergeRequestsService) AddSpentTime(pid interface{}, mergeRequest int, opt *AddSpentTimeOptions, options ...OptionFunc) (*TimeStats, *Response, error) {
+ return s.timeStats.addSpentTime(pid, "merge_requests", mergeRequest, opt, options...)
+}
+
+// ResetSpentTime resets the spent time for a single project merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#reset-spent-time-for-a-merge-request
+func (s *MergeRequestsService) ResetSpentTime(pid interface{}, mergeRequest int, options ...OptionFunc) (*TimeStats, *Response, error) {
+ return s.timeStats.resetSpentTime(pid, "merge_requests", mergeRequest, options...)
+}
+
+// GetTimeSpent gets the spent time for a single project merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#get-time-tracking-stats
+func (s *MergeRequestsService) GetTimeSpent(pid interface{}, mergeRequest int, options ...OptionFunc) (*TimeStats, *Response, error) {
+ return s.timeStats.getTimeSpent(pid, "merge_requests", mergeRequest, options...)
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/milestones.go b/vendor/github.com/lkysow/go-gitlab/milestones.go
new file mode 100644
index 0000000000..f862327b0c
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/milestones.go
@@ -0,0 +1,216 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// 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.
+//
+
+package gitlab
+
+import (
+ "fmt"
+ "net/url"
+ "time"
+)
+
+// MilestonesService handles communication with the milestone related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/milestones.html
+type MilestonesService struct {
+ client *Client
+}
+
+// Milestone represents a GitLab milestone.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/milestones.html
+type Milestone struct {
+ ID int `json:"id"`
+ IID int `json:"iid"`
+ ProjectID int `json:"project_id"`
+ Title string `json:"title"`
+ Description string `json:"description"`
+ StartDate string `json:"start_date"`
+ DueDate string `json:"due_date"`
+ State string `json:"state"`
+ UpdatedAt *time.Time `json:"updated_at"`
+ CreatedAt *time.Time `json:"created_at"`
+}
+
+func (m Milestone) String() string {
+ return Stringify(m)
+}
+
+// ListMilestonesOptions represents the available ListMilestones() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/milestones.html#list-project-milestones
+type ListMilestonesOptions struct {
+ ListOptions
+ IIDs []int `url:"iids,omitempty" json:"iids,omitempty"`
+}
+
+// ListMilestones returns a list of project milestones.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/milestones.html#list-project-milestones
+func (s *MilestonesService) ListMilestones(pid interface{}, opt *ListMilestonesOptions, options ...OptionFunc) ([]*Milestone, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/milestones", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var m []*Milestone
+ resp, err := s.client.Do(req, &m)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return m, resp, err
+}
+
+// GetMilestone gets a single project milestone.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/milestones.html#get-single-milestone
+func (s *MilestonesService) GetMilestone(pid interface{}, milestone int, options ...OptionFunc) (*Milestone, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/milestones/%d", url.QueryEscape(project), milestone)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ m := new(Milestone)
+ resp, err := s.client.Do(req, m)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return m, resp, err
+}
+
+// CreateMilestoneOptions represents the available CreateMilestone() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/milestones.html#create-new-milestone
+type CreateMilestoneOptions struct {
+ Title *string `url:"title,omitempty" json:"title,omitempty"`
+ Description *string `url:"description,omitempty" json:"description,omitempty"`
+ StartDate *string `url:"start_date,omitempty" json:"start_date,omitempty"`
+ DueDate *string `url:"due_date,omitempty" json:"due_date,omitempty"`
+}
+
+// CreateMilestone creates a new project milestone.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/milestones.html#create-new-milestone
+func (s *MilestonesService) CreateMilestone(pid interface{}, opt *CreateMilestoneOptions, options ...OptionFunc) (*Milestone, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/milestones", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ m := new(Milestone)
+ resp, err := s.client.Do(req, m)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return m, resp, err
+}
+
+// UpdateMilestoneOptions represents the available UpdateMilestone() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/milestones.html#edit-milestone
+type UpdateMilestoneOptions struct {
+ Title *string `url:"title,omitempty" json:"title,omitempty"`
+ Description *string `url:"description,omitempty" json:"description,omitempty"`
+ StartDate *string `url:"start_date,omitempty" json:"start_date,omitempty"`
+ DueDate *string `url:"due_date,omitempty" json:"due_date,omitempty"`
+ StateEvent *string `url:"state_event,omitempty" json:"state_event,omitempty"`
+}
+
+// UpdateMilestone updates an existing project milestone.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/milestones.html#edit-milestone
+func (s *MilestonesService) UpdateMilestone(pid interface{}, milestone int, opt *UpdateMilestoneOptions, options ...OptionFunc) (*Milestone, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/milestones/%d", url.QueryEscape(project), milestone)
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ m := new(Milestone)
+ resp, err := s.client.Do(req, m)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return m, resp, err
+}
+
+// GetMilestoneIssuesOptions represents the available GetMilestoneIssues() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/milestones.html#get-all-issues-assigned-to-a-single-milestone
+type GetMilestoneIssuesOptions struct {
+ ListOptions
+}
+
+// GetMilestoneIssues gets all issues assigned to a single project milestone.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/milestones.html#get-all-issues-assigned-to-a-single-milestone
+func (s *MilestonesService) GetMilestoneIssues(pid interface{}, milestone int, opt *GetMilestoneIssuesOptions, options ...OptionFunc) ([]*Issue, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/milestones/%d/issues", url.QueryEscape(project), milestone)
+
+ req, err := s.client.NewRequest("GET", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var i []*Issue
+ resp, err := s.client.Do(req, &i)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return i, resp, err
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/namespaces.go b/vendor/github.com/lkysow/go-gitlab/namespaces.go
new file mode 100644
index 0000000000..22ad7afcdf
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/namespaces.go
@@ -0,0 +1,89 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// 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.
+//
+
+package gitlab
+
+// NamespacesService handles communication with the namespace related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/namespaces.html
+type NamespacesService struct {
+ client *Client
+}
+
+// Namespace represents a GitLab namespace.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/namespaces.html
+type Namespace struct {
+ ID int `json:"id"`
+ Path string `json:"path"`
+ Kind string `json:"kind"`
+}
+
+func (n Namespace) String() string {
+ return Stringify(n)
+}
+
+// ListNamespacesOptions represents the available ListNamespaces() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/namespaces.html#list-namespaces
+type ListNamespacesOptions struct {
+ ListOptions
+ Search *string `url:"search,omitempty" json:"search,omitempty"`
+}
+
+// ListNamespaces gets a list of projects accessible by the authenticated user.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/namespaces.html#list-namespaces
+func (s *NamespacesService) ListNamespaces(opt *ListNamespacesOptions, options ...OptionFunc) ([]*Namespace, *Response, error) {
+ req, err := s.client.NewRequest("GET", "namespaces", opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var n []*Namespace
+ resp, err := s.client.Do(req, &n)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return n, resp, err
+}
+
+// SearchNamespace gets all namespaces that match your string in their name
+// or path.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/namespaces.html#search-for-namespace
+func (s *NamespacesService) SearchNamespace(query string, options ...OptionFunc) ([]*Namespace, *Response, error) {
+ var q struct {
+ Search string `url:"search,omitempty" json:"search,omitempty"`
+ }
+ q.Search = query
+
+ req, err := s.client.NewRequest("GET", "namespaces", &q, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var n []*Namespace
+ resp, err := s.client.Do(req, &n)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return n, resp, err
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/notes.go b/vendor/github.com/lkysow/go-gitlab/notes.go
new file mode 100644
index 0000000000..b8e9f32659
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/notes.go
@@ -0,0 +1,474 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// 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.
+//
+
+package gitlab
+
+import (
+ "fmt"
+ "net/url"
+ "time"
+)
+
+// NotesService handles communication with the notes related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/notes.html
+type NotesService struct {
+ client *Client
+}
+
+// Note represents a GitLab note.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/notes.html
+type Note struct {
+ ID int `json:"id"`
+ Body string `json:"body"`
+ Attachment string `json:"attachment"`
+ Title string `json:"title"`
+ FileName string `json:"file_name"`
+ Author struct {
+ ID int `json:"id"`
+ Username string `json:"username"`
+ Email string `json:"email"`
+ Name string `json:"name"`
+ State string `json:"state"`
+ CreatedAt *time.Time `json:"created_at"`
+ } `json:"author"`
+ System bool `json:"system"`
+ ExpiresAt *time.Time `json:"expires_at"`
+ UpdatedAt *time.Time `json:"updated_at"`
+ CreatedAt *time.Time `json:"created_at"`
+}
+
+func (n Note) String() string {
+ return Stringify(n)
+}
+
+// ListIssueNotesOptions represents the available ListIssueNotes() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#list-project-issue-notes
+type ListIssueNotesOptions struct {
+ ListOptions
+}
+
+// ListIssueNotes gets a list of all notes for a single issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#list-project-issue-notes
+func (s *NotesService) ListIssueNotes(pid interface{}, issue int, opt *ListIssueNotesOptions, options ...OptionFunc) ([]*Note, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/issues/%d/notes", url.QueryEscape(project), issue)
+
+ req, err := s.client.NewRequest("GET", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var n []*Note
+ resp, err := s.client.Do(req, &n)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return n, resp, err
+}
+
+// GetIssueNote returns a single note for a specific project issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#get-single-issue-note
+func (s *NotesService) GetIssueNote(pid interface{}, issue, note int, options ...OptionFunc) (*Note, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/issues/%d/notes/%d", url.QueryEscape(project), issue, note)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ n := new(Note)
+ resp, err := s.client.Do(req, n)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return n, resp, err
+}
+
+// CreateIssueNoteOptions represents the available CreateIssueNote()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#create-new-issue-note
+type CreateIssueNoteOptions struct {
+ Body *string `url:"body,omitempty" json:"body,omitempty"`
+}
+
+// CreateIssueNote creates a new note to a single project issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#create-new-issue-note
+func (s *NotesService) CreateIssueNote(pid interface{}, issue int, opt *CreateIssueNoteOptions, options ...OptionFunc) (*Note, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/issues/%d/notes", url.QueryEscape(project), issue)
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ n := new(Note)
+ resp, err := s.client.Do(req, n)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return n, resp, err
+}
+
+// UpdateIssueNoteOptions represents the available UpdateIssueNote()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#modify-existing-issue-note
+type UpdateIssueNoteOptions struct {
+ Body *string `url:"body,omitempty" json:"body,omitempty"`
+}
+
+// UpdateIssueNote modifies existing note of an issue.
+//
+// https://docs.gitlab.com/ce/api/notes.html#modify-existing-issue-note
+func (s *NotesService) UpdateIssueNote(pid interface{}, issue, note int, opt *UpdateIssueNoteOptions, options ...OptionFunc) (*Note, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/issues/%d/notes/%d", url.QueryEscape(project), issue, note)
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ n := new(Note)
+ resp, err := s.client.Do(req, n)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return n, resp, err
+}
+
+// DeleteIssueNote deletes an existing note of an issue.
+//
+// https://docs.gitlab.com/ce/api/notes.html#delete-an-issue-note
+func (s *NotesService) DeleteIssueNote(pid interface{}, issue, note int, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/issues/%d/notes/%d", url.QueryEscape(project), issue, note)
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// ListSnippetNotes gets a list of all notes for a single snippet. Snippet
+// notes are comments users can post to a snippet.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#list-all-snippet-notes
+func (s *NotesService) ListSnippetNotes(pid interface{}, snippet int, options ...OptionFunc) ([]*Note, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/snippets/%d/notes", url.QueryEscape(project), snippet)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var n []*Note
+ resp, err := s.client.Do(req, &n)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return n, resp, err
+}
+
+// GetSnippetNote returns a single note for a given snippet.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#get-single-snippet-note
+func (s *NotesService) GetSnippetNote(pid interface{}, snippet, note int, options ...OptionFunc) (*Note, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/snippets/%d/notes/%d", url.QueryEscape(project), snippet, note)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ n := new(Note)
+ resp, err := s.client.Do(req, n)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return n, resp, err
+}
+
+// CreateSnippetNoteOptions represents the available CreateSnippetNote()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#create-new-snippet-note
+type CreateSnippetNoteOptions struct {
+ Body *string `url:"body,omitempty" json:"body,omitempty"`
+}
+
+// CreateSnippetNote creates a new note for a single snippet. Snippet notes are
+// comments users can post to a snippet.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#create-new-snippet-note
+func (s *NotesService) CreateSnippetNote(pid interface{}, snippet int, opt *CreateSnippetNoteOptions, options ...OptionFunc) (*Note, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/snippets/%d/notes", url.QueryEscape(project), snippet)
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ n := new(Note)
+ resp, err := s.client.Do(req, n)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return n, resp, err
+}
+
+// UpdateSnippetNoteOptions represents the available UpdateSnippetNote()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#modify-existing-snippet-note
+type UpdateSnippetNoteOptions struct {
+ Body *string `url:"body,omitempty" json:"body,omitempty"`
+}
+
+// UpdateSnippetNote modifies existing note of a snippet.
+//
+// https://docs.gitlab.com/ce/api/notes.html#modify-existing-snippet-note
+func (s *NotesService) UpdateSnippetNote(pid interface{}, snippet, note int, opt *UpdateSnippetNoteOptions, options ...OptionFunc) (*Note, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/snippets/%d/notes/%d", url.QueryEscape(project), snippet, note)
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ n := new(Note)
+ resp, err := s.client.Do(req, n)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return n, resp, err
+}
+
+// DeleteSnippetNote deletes an existing note of a snippet.
+//
+// https://docs.gitlab.com/ce/api/notes.html#delete-a-snippet-note
+func (s *NotesService) DeleteSnippetNote(pid interface{}, snippet, note int, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/snippets/%d/notes/%d", url.QueryEscape(project), snippet, note)
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// ListMergeRequestNotes gets a list of all notes for a single merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#list-all-merge-request-notes
+func (s *NotesService) ListMergeRequestNotes(pid interface{}, mergeRequest int, options ...OptionFunc) ([]*Note, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/merge_requests/%d/notes", url.QueryEscape(project), mergeRequest)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var n []*Note
+ resp, err := s.client.Do(req, &n)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return n, resp, err
+}
+
+// GetMergeRequestNote returns a single note for a given merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#get-single-merge-request-note
+func (s *NotesService) GetMergeRequestNote(pid interface{}, mergeRequest, note int, options ...OptionFunc) (*Note, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/merge_requests/%d/notes/%d", url.QueryEscape(project), mergeRequest, note)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ n := new(Note)
+ resp, err := s.client.Do(req, n)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return n, resp, err
+}
+
+// CreateMergeRequestNoteOptions represents the available
+// CreateMergeRequestNote() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#create-new-merge-request-note
+type CreateMergeRequestNoteOptions struct {
+ Body *string `url:"body,omitempty" json:"body,omitempty"`
+}
+
+// CreateMergeRequestNote creates a new note for a single merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#create-new-merge-request-note
+func (s *NotesService) CreateMergeRequestNote(pid interface{}, mergeRequest int, opt *CreateMergeRequestNoteOptions, options ...OptionFunc) (*Note, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/merge_requests/%d/notes", url.QueryEscape(project), mergeRequest)
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ n := new(Note)
+ resp, err := s.client.Do(req, n)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return n, resp, err
+}
+
+// UpdateMergeRequestNoteOptions represents the available
+// UpdateMergeRequestNote() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#modify-existing-merge-request-note
+type UpdateMergeRequestNoteOptions struct {
+ Body *string `url:"body,omitempty" json:"body,omitempty"`
+}
+
+// UpdateMergeRequestNote modifies existing note of a merge request.
+//
+// https://docs.gitlab.com/ce/api/notes.html#modify-existing-merge-request-note
+func (s *NotesService) UpdateMergeRequestNote(pid interface{}, mergeRequest, note int, opt *UpdateMergeRequestNoteOptions, options ...OptionFunc) (*Note, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf(
+ "projects/%s/merge_requests/%d/notes/%d", url.QueryEscape(project), mergeRequest, note)
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ n := new(Note)
+ resp, err := s.client.Do(req, n)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return n, resp, err
+}
+
+// DeleteMergeRequestNote deletes an existing note of a merge request.
+//
+// https://docs.gitlab.com/ce/api/notes.html#delete-a-merge-request-note
+func (s *NotesService) DeleteMergeRequestNote(pid interface{}, mergeRequest, note int, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf(
+ "projects/%s/merge_requests/%d/notes/%d", url.QueryEscape(project), mergeRequest, note)
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/notifications.go b/vendor/github.com/lkysow/go-gitlab/notifications.go
new file mode 100644
index 0000000000..a5501dd738
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/notifications.go
@@ -0,0 +1,214 @@
+package gitlab
+
+import (
+ "errors"
+ "fmt"
+ "net/url"
+)
+
+// NotificationSettingsService handles communication with the notification settings
+// related methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/notification_settings.html
+type NotificationSettingsService struct {
+ client *Client
+}
+
+// NotificationSettings represents the Gitlab notification setting.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notification_settings.html#notification-settings
+type NotificationSettings struct {
+ Level NotificationLevelValue `json:"level"`
+ NotificationEmail string `json:"notification_email"`
+ Events *NotificationEvents `json:"events"`
+}
+
+// NotificationEvents represents the available notification setting events.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notification_settings.html#notification-settings
+type NotificationEvents struct {
+ CloseIssue bool `json:"close_issue"`
+ CloseMergeRequest bool `json:"close_merge_request"`
+ FailedPipeline bool `json:"failed_pipeline"`
+ MergeMergeRequest bool `json:"merge_merge_request"`
+ NewIssue bool `json:"new_issue"`
+ NewMergeRequest bool `json:"new_merge_request"`
+ NewNote bool `json:"new_note"`
+ ReassignIssue bool `json:"reassign_issue"`
+ ReassignMergeRequest bool `json:"reassign_merge_request"`
+ ReopenIssue bool `json:"reopen_issue"`
+ ReopenMergeRequest bool `json:"reopen_merge_request"`
+ SuccessPipeline bool `json:"success_pipeline"`
+}
+
+func (ns NotificationSettings) String() string {
+ return Stringify(ns)
+}
+
+// GetGlobalSettings returns current notification settings and email address.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notification_settings.html#global-notification-settings
+func (s *NotificationSettingsService) GetGlobalSettings(options ...OptionFunc) (*NotificationSettings, *Response, error) {
+ u := "notification_settings"
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ ns := new(NotificationSettings)
+ resp, err := s.client.Do(req, ns)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return ns, resp, err
+}
+
+// NotificationSettingsOptions represents the available options that can be passed
+// to the API when updating the notification settings.
+type NotificationSettingsOptions struct {
+ Level *NotificationLevelValue `url:"level,omitempty" json:"level,omitempty"`
+ NotificationEmail *string `url:"notification_email,omitempty" json:"notification_email,omitempty"`
+ CloseIssue *bool `url:"close_issue,omitempty" json:"close_issue,omitempty"`
+ CloseMergeRequest *bool `url:"close_merge_request,omitempty" json:"close_merge_request,omitempty"`
+ FailedPipeline *bool `url:"failed_pipeline,omitempty" json:"failed_pipeline,omitempty"`
+ MergeMergeRequest *bool `url:"merge_merge_request,omitempty" json:"merge_merge_request,omitempty"`
+ NewIssue *bool `url:"new_issue,omitempty" json:"new_issue,omitempty"`
+ NewMergeRequest *bool `url:"new_merge_request,omitempty" json:"new_merge_request,omitempty"`
+ NewNote *bool `url:"new_note,omitempty" json:"new_note,omitempty"`
+ ReassignIssue *bool `url:"reassign_issue,omitempty" json:"reassign_issue,omitempty"`
+ ReassignMergeRequest *bool `url:"reassign_merge_request,omitempty" json:"reassign_merge_request,omitempty"`
+ ReopenIssue *bool `url:"reopen_issue,omitempty" json:"reopen_issue,omitempty"`
+ ReopenMergeRequest *bool `url:"reopen_merge_request,omitempty" json:"reopen_merge_request,omitempty"`
+ SuccessPipeline *bool `url:"success_pipeline,omitempty" json:"success_pipeline,omitempty"`
+}
+
+// UpdateGlobalSettings updates current notification settings and email address.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notification_settings.html#update-global-notification-settings
+func (s *NotificationSettingsService) UpdateGlobalSettings(opt *NotificationSettingsOptions, options ...OptionFunc) (*NotificationSettings, *Response, error) {
+ if opt.Level != nil && *opt.Level == GlobalNotificationLevel {
+ return nil, nil, errors.New(
+ "notification level 'global' is not valid for global notification settings")
+ }
+
+ u := "notification_settings"
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ ns := new(NotificationSettings)
+ resp, err := s.client.Do(req, ns)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return ns, resp, err
+}
+
+// GetSettingsForGroup returns current group notification settings.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notification_settings.html#group-project-level-notification-settings
+func (s *NotificationSettingsService) GetSettingsForGroup(gid interface{}, options ...OptionFunc) (*NotificationSettings, *Response, error) {
+ group, err := parseID(gid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("groups/%s/notification_settings", url.QueryEscape(group))
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ ns := new(NotificationSettings)
+ resp, err := s.client.Do(req, ns)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return ns, resp, err
+}
+
+// GetSettingsForProject returns current project notification settings.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notification_settings.html#group-project-level-notification-settings
+func (s *NotificationSettingsService) GetSettingsForProject(pid interface{}, options ...OptionFunc) (*NotificationSettings, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/notification_settings", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ ns := new(NotificationSettings)
+ resp, err := s.client.Do(req, ns)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return ns, resp, err
+}
+
+// UpdateSettingsForGroup updates current group notification settings.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notification_settings.html#update-group-project-level-notification-settings
+func (s *NotificationSettingsService) UpdateSettingsForGroup(gid interface{}, opt *NotificationSettingsOptions, options ...OptionFunc) (*NotificationSettings, *Response, error) {
+ group, err := parseID(gid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("groups/%s/notification_settings", url.QueryEscape(group))
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ ns := new(NotificationSettings)
+ resp, err := s.client.Do(req, ns)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return ns, resp, err
+}
+
+// UpdateSettingsForProject updates current project notification settings.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notification_settings.html#update-group-project-level-notification-settings
+func (s *NotificationSettingsService) UpdateSettingsForProject(pid interface{}, opt *NotificationSettingsOptions, options ...OptionFunc) (*NotificationSettings, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/notification_settings", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ ns := new(NotificationSettings)
+ resp, err := s.client.Do(req, ns)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return ns, resp, err
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/pipeline_triggers.go b/vendor/github.com/lkysow/go-gitlab/pipeline_triggers.go
new file mode 100644
index 0000000000..bed82f3a36
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/pipeline_triggers.go
@@ -0,0 +1,234 @@
+package gitlab
+
+import (
+ "fmt"
+ "net/url"
+ "time"
+)
+
+// PipelineTriggersService handles Project pipeline triggers.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_triggers.html
+type PipelineTriggersService struct {
+ client *Client
+}
+
+// PipelineTrigger represents a project pipeline trigger.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_triggers.html#pipeline-triggers
+type PipelineTrigger struct {
+ ID int `json:"id"`
+ Description string `json:"description"`
+ CreatedAt *time.Time `json:"created_at"`
+ DeletedAt *time.Time `json:"deleted_at"`
+ LastUsed *time.Time `json:"last_used"`
+ Token string `json:"token"`
+ UpdatedAt *time.Time `json:"updated_at"`
+ Owner *User `json:"owner"`
+}
+
+// ListPipelineTriggersOptions represents the available ListPipelineTriggers() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_triggers.html#list-project-triggers
+type ListPipelineTriggersOptions struct {
+ ListOptions
+}
+
+// ListPipelineTriggers gets a list of project triggers.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_triggers.html#list-project-triggers
+func (s *PipelineTriggersService) ListPipelineTriggers(pid interface{}, opt *ListPipelineTriggersOptions, options ...OptionFunc) ([]*PipelineTrigger, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/triggers", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var pt []*PipelineTrigger
+ resp, err := s.client.Do(req, &pt)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return pt, resp, err
+}
+
+// GetPipelineTrigger gets a specific pipeline trigger for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_triggers.html#get-trigger-details
+func (s *PipelineTriggersService) GetPipelineTrigger(pid interface{}, trigger int, options ...OptionFunc) (*PipelineTrigger, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/triggers/%d", url.QueryEscape(project), trigger)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ pt := new(PipelineTrigger)
+ resp, err := s.client.Do(req, pt)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return pt, resp, err
+}
+
+// AddPipelineTriggerOptions represents the available AddPipelineTrigger() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_triggers.html#create-a-project-trigger
+type AddPipelineTriggerOptions struct {
+ Description *string `url:"description,omitempty" json:"description,omitempty"`
+}
+
+// AddPipelineTrigger adds a pipeline trigger to a specified project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_triggers.html#create-a-project-trigger
+func (s *PipelineTriggersService) AddPipelineTrigger(pid interface{}, opt *AddPipelineTriggerOptions, options ...OptionFunc) (*PipelineTrigger, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/triggers", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ pt := new(PipelineTrigger)
+ resp, err := s.client.Do(req, pt)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return pt, resp, err
+}
+
+// EditPipelineTriggerOptions represents the available EditPipelineTrigger() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_triggers.html#update-a-project-trigger
+type EditPipelineTriggerOptions struct {
+ Description *string `url:"description,omitempty" json:"description,omitempty"`
+}
+
+// EditPipelineTrigger edits a trigger for a specified project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_triggers.html#update-a-project-trigger
+func (s *PipelineTriggersService) EditPipelineTrigger(pid interface{}, trigger int, opt *EditPipelineTriggerOptions, options ...OptionFunc) (*PipelineTrigger, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/triggers/%d", url.QueryEscape(project), trigger)
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ pt := new(PipelineTrigger)
+ resp, err := s.client.Do(req, pt)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return pt, resp, err
+}
+
+// TakeOwnershipOfPipelineTrigger sets the owner of the specified
+// pipeline trigger to the user issuing the request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_triggers.html#take-ownership-of-a-project-trigger
+func (s *PipelineTriggersService) TakeOwnershipOfPipelineTrigger(pid interface{}, trigger int, options ...OptionFunc) (*PipelineTrigger, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/triggers/%d/take_ownership", url.QueryEscape(project), trigger)
+
+ req, err := s.client.NewRequest("POST", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ pt := new(PipelineTrigger)
+ resp, err := s.client.Do(req, pt)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return pt, resp, err
+}
+
+// DeletePipelineTrigger removes a trigger from a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_triggers.html#remove-a-project-trigger
+func (s *PipelineTriggersService) DeletePipelineTrigger(pid interface{}, trigger int, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/triggers/%d", url.QueryEscape(project), trigger)
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// RunPipelineTriggerOptions represents the available RunPipelineTrigger() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/ci/triggers/README.html#triggering-a-pipeline
+type RunPipelineTriggerOptions struct {
+ Ref *string `url:"ref" json:"ref"`
+ Token *string `url:"token" json:"token"`
+ Variables map[string]string `url:"variables,omitempty" json:"variables,omitempty"`
+}
+
+// RunPipelineTrigger starts a trigger from a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/ci/triggers/README.html#triggering-a-pipeline
+func (s *PipelineTriggersService) RunPipelineTrigger(pid interface{}, opt *RunPipelineTriggerOptions, options ...OptionFunc) (*Pipeline, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/trigger/pipeline", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ pt := new(Pipeline)
+ resp, err := s.client.Do(req, pt)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return pt, resp, err
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/pipeline_triggers_test.go b/vendor/github.com/lkysow/go-gitlab/pipeline_triggers_test.go
new file mode 100644
index 0000000000..c70d1b62ef
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/pipeline_triggers_test.go
@@ -0,0 +1,30 @@
+package gitlab
+
+import (
+ "fmt"
+ "net/http"
+ "reflect"
+ "testing"
+)
+
+func TestRunPipeline(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects/1/trigger/pipeline", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "POST")
+ fmt.Fprint(w, `{"id":1, "status":"pending"}`)
+ })
+
+ opt := &RunPipelineTriggerOptions{Ref: String("master")}
+ pipeline, _, err := client.PipelineTriggers.RunPipelineTrigger(1, opt)
+
+ if err != nil {
+ t.Errorf("PipelineTriggers.RunPipelineTrigger returned error: %v", err)
+ }
+
+ want := &Pipeline{ID: 1, Status: "pending"}
+ if !reflect.DeepEqual(want, pipeline) {
+ t.Errorf("PipelineTriggers.RunPipelineTrigger returned %+v, want %+v", pipeline, want)
+ }
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/pipelines.go b/vendor/github.com/lkysow/go-gitlab/pipelines.go
new file mode 100644
index 0000000000..5e88dad949
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/pipelines.go
@@ -0,0 +1,205 @@
+//
+// Copyright 2017, Igor Varavko
+//
+// 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.
+//
+
+package gitlab
+
+import (
+ "fmt"
+ "net/url"
+ "time"
+)
+
+// PipelinesService handles communication with the repositories related
+// methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html
+type PipelinesService struct {
+ client *Client
+}
+
+// Pipeline represents a GitLab pipeline.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html
+type Pipeline struct {
+ ID int `json:"id"`
+ Status string `json:"status"`
+ Ref string `json:"ref"`
+ Sha string `json:"sha"`
+ BeforeSha string `json:"before_sha"`
+ Tag bool `json:"tag"`
+ YamlErrors string `json:"yaml_errors"`
+ User struct {
+ Name string `json:"name"`
+ Username string `json:"username"`
+ ID int `json:"id"`
+ State string `json:"state"`
+ AvatarURL string `json:"avatar_url"`
+ WebURL string `json:"web_url"`
+ }
+ UpdatedAt *time.Time `json:"updated_at"`
+ CreatedAt *time.Time `json:"created_at"`
+ StartedAt *time.Time `json:"started_at"`
+ FinishedAt *time.Time `json:"finished_at"`
+ CommittedAt *time.Time `json:"committed_at"`
+ Duration int `json:"duration"`
+ Coverage string `json:"coverage"`
+}
+
+func (i Pipeline) String() string {
+ return Stringify(i)
+}
+
+// PipelineList represents a GitLab list project pipelines
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#list-project-pipelines
+type PipelineList []struct {
+ ID int `json:"id"`
+ Status string `json:"status"`
+ Ref string `json:"ref"`
+ Sha string `json:"sha"`
+}
+
+func (i PipelineList) String() string {
+ return Stringify(i)
+}
+
+// ListProjectPipelines gets a list of project piplines.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#list-project-pipelines
+func (s *PipelinesService) ListProjectPipelines(pid interface{}, options ...OptionFunc) (PipelineList, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/pipelines", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var p PipelineList
+ resp, err := s.client.Do(req, &p)
+ if err != nil {
+ return nil, resp, err
+ }
+ return p, resp, err
+}
+
+// GetPipeline gets a single project pipeline.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#get-a-single-pipeline
+func (s *PipelinesService) GetPipeline(pid interface{}, pipeline int, options ...OptionFunc) (*Pipeline, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/pipelines/%d", url.QueryEscape(project), pipeline)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ p := new(Pipeline)
+ resp, err := s.client.Do(req, p)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return p, resp, err
+}
+
+// CreatePipelineOptions represents the available CreatePipeline() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#create-a-new-pipeline
+type CreatePipelineOptions struct {
+ Ref *string `url:"ref,omitempty" json:"ref"`
+}
+
+// CreatePipeline creates a new project pipeline.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#create-a-new-pipeline
+func (s *PipelinesService) CreatePipeline(pid interface{}, opt *CreatePipelineOptions, options ...OptionFunc) (*Pipeline, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/pipeline", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ p := new(Pipeline)
+ resp, err := s.client.Do(req, p)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return p, resp, err
+}
+
+// RetryPipelineBuild retries failed builds in a pipeline
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipelines.html#retry-failed-builds-in-a-pipeline
+func (s *PipelinesService) RetryPipelineBuild(pid interface{}, pipelineID int, options ...OptionFunc) (*Pipeline, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/pipelines/%d/retry", project, pipelineID)
+
+ req, err := s.client.NewRequest("POST", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ p := new(Pipeline)
+ resp, err := s.client.Do(req, p)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return p, resp, err
+}
+
+// CancelPipelineBuild cancels a pipeline builds
+//
+// GitLab API docs:
+//https://docs.gitlab.com/ce/api/pipelines.html#cancel-a-pipelines-builds
+func (s *PipelinesService) CancelPipelineBuild(pid interface{}, pipelineID int, options ...OptionFunc) (*Pipeline, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/pipelines/%d/cancel", project, pipelineID)
+
+ req, err := s.client.NewRequest("POST", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ p := new(Pipeline)
+ resp, err := s.client.Do(req, p)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return p, resp, err
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/pipelines_test.go b/vendor/github.com/lkysow/go-gitlab/pipelines_test.go
new file mode 100644
index 0000000000..126554ffd4
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/pipelines_test.go
@@ -0,0 +1,110 @@
+package gitlab
+
+import (
+ "fmt"
+ "net/http"
+ "reflect"
+ "testing"
+)
+
+func TestListProjectPipelines(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects/1/pipelines", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "GET")
+ fmt.Fprint(w, `[{"id":1},{"id":2}]`)
+ })
+
+ piplines, _, err := client.Pipelines.ListProjectPipelines(1)
+ if err != nil {
+ t.Errorf("Pipelines.ListProjectPipelines returned error: %v", err)
+ }
+
+ want := PipelineList{{ID: 1}, {ID: 2}}
+ if !reflect.DeepEqual(want, piplines) {
+ t.Errorf("Pipelines.ListProjectPipelines returned %+v, want %+v", piplines, want)
+ }
+}
+
+func TestGetPipeline(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects/1/pipelines/5949167", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "GET")
+ fmt.Fprint(w, `{"id":1,"status":"success"}`)
+ })
+
+ pipeline, _, err := client.Pipelines.GetPipeline(1, 5949167)
+ if err != nil {
+ t.Errorf("Pipelines.GetPipeline returned error: %v", err)
+ }
+
+ want := &Pipeline{ID: 1, Status: "success"}
+ if !reflect.DeepEqual(want, pipeline) {
+ t.Errorf("Pipelines.GetPipeline returned %+v, want %+v", pipeline, want)
+ }
+}
+
+func TestCreatePipeline(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects/1/pipeline", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "POST")
+ fmt.Fprint(w, `{"id":1, "status":"pending"}`)
+ })
+
+ opt := &CreatePipelineOptions{Ref: String("master")}
+ pipeline, _, err := client.Pipelines.CreatePipeline(1, opt)
+
+ if err != nil {
+ t.Errorf("Pipelines.CreatePipeline returned error: %v", err)
+ }
+
+ want := &Pipeline{ID: 1, Status: "pending"}
+ if !reflect.DeepEqual(want, pipeline) {
+ t.Errorf("Pipelines.CreatePipeline returned %+v, want %+v", pipeline, want)
+ }
+}
+
+func TestRetryPipelineBuild(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects/1/pipelines/5949167/retry", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "POST")
+ fmt.Fprintln(w, `{"id":1, "status":"pending"}`)
+ })
+
+ pipeline, _, err := client.Pipelines.RetryPipelineBuild(1, 5949167)
+ if err != nil {
+ t.Errorf("Pipelines.RetryPipelineBuild returned error: %v", err)
+ }
+
+ want := &Pipeline{ID: 1, Status: "pending"}
+ if !reflect.DeepEqual(want, pipeline) {
+ t.Errorf("Pipelines.RetryPipelineBuild returned %+v, want %+v", pipeline, want)
+ }
+}
+
+func TestCancelPipelineBuild(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects/1/pipelines/5949167/cancel", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "POST")
+ fmt.Fprintln(w, `{"id":1, "status":"canceled"}`)
+ })
+
+ pipeline, _, err := client.Pipelines.CancelPipelineBuild(1, 5949167)
+ if err != nil {
+ t.Errorf("Pipelines.CancelPipelineBuild returned error: %v", err)
+ }
+
+ want := &Pipeline{ID: 1, Status: "canceled"}
+ if !reflect.DeepEqual(want, pipeline) {
+ t.Errorf("Pipelines.CancelPipelineBuild returned %+v, want %+v", pipeline, want)
+ }
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/project_members.go b/vendor/github.com/lkysow/go-gitlab/project_members.go
new file mode 100644
index 0000000000..70beb72d1c
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/project_members.go
@@ -0,0 +1,179 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// 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.
+//
+
+package gitlab
+
+import (
+ "fmt"
+ "net/url"
+)
+
+// ProjectMembersService handles communication with the project members
+// related methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/members.html
+type ProjectMembersService struct {
+ client *Client
+}
+
+// ListProjectMembersOptions represents the available ListProjectMembers()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#list-all-members-of-a-group-or-project
+type ListProjectMembersOptions struct {
+ ListOptions
+ Query *string `url:"query,omitempty" json:"query,omitempty"`
+}
+
+// ListProjectMembers gets a list of a project's team members.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#list-all-members-of-a-group-or-project
+func (s *ProjectMembersService) ListProjectMembers(pid interface{}, opt *ListProjectMembersOptions, options ...OptionFunc) ([]*ProjectMember, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/members", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var pm []*ProjectMember
+ resp, err := s.client.Do(req, &pm)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return pm, resp, err
+}
+
+// GetProjectMember gets a project team member.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#get-a-member-of-a-group-or-project
+func (s *ProjectMembersService) GetProjectMember(pid interface{}, user int, options ...OptionFunc) (*ProjectMember, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/members/%d", url.QueryEscape(project), user)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ pm := new(ProjectMember)
+ resp, err := s.client.Do(req, pm)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return pm, resp, err
+}
+
+// AddProjectMemberOptions represents the available AddProjectMember() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#add-a-member-to-a-group-or-project
+type AddProjectMemberOptions struct {
+ UserID *int `url:"user_id,omitempty" json:"user_id,omitempty"`
+ AccessLevel *AccessLevelValue `url:"access_level,omitempty" json:"access_level,omitempty"`
+}
+
+// AddProjectMember adds a user to a project team. This is an idempotent
+// method and can be called multiple times with the same parameters. Adding
+// team membership to a user that is already a member does not affect the
+// existing membership.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#add-a-member-to-a-group-or-project
+func (s *ProjectMembersService) AddProjectMember(pid interface{}, opt *AddProjectMemberOptions, options ...OptionFunc) (*ProjectMember, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/members", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ pm := new(ProjectMember)
+ resp, err := s.client.Do(req, pm)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return pm, resp, err
+}
+
+// EditProjectMemberOptions represents the available EditProjectMember() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#edit-a-member-of-a-group-or-project
+type EditProjectMemberOptions struct {
+ AccessLevel *AccessLevelValue `url:"access_level,omitempty" json:"access_level,omitempty"`
+}
+
+// EditProjectMember updates a project team member to a specified access level..
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#edit-a-member-of-a-group-or-project
+func (s *ProjectMembersService) EditProjectMember(pid interface{}, user int, opt *EditProjectMemberOptions, options ...OptionFunc) (*ProjectMember, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/members/%d", url.QueryEscape(project), user)
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ pm := new(ProjectMember)
+ resp, err := s.client.Do(req, pm)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return pm, resp, err
+}
+
+// DeleteProjectMember removes a user from a project team.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#remove-a-member-from-a-group-or-project
+func (s *ProjectMembersService) DeleteProjectMember(pid interface{}, user int, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/members/%d", url.QueryEscape(project), user)
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/project_snippets.go b/vendor/github.com/lkysow/go-gitlab/project_snippets.go
new file mode 100644
index 0000000000..8ebad47267
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/project_snippets.go
@@ -0,0 +1,231 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// 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.
+//
+
+package gitlab
+
+import (
+ "bytes"
+ "fmt"
+ "net/url"
+ "time"
+)
+
+// ProjectSnippetsService handles communication with the project snippets
+// related methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/project_snippets.html
+type ProjectSnippetsService struct {
+ client *Client
+}
+
+// Snippet represents a GitLab project snippet.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/project_snippets.html
+type Snippet struct {
+ ID int `json:"id"`
+ Title string `json:"title"`
+ FileName string `json:"file_name"`
+ Author struct {
+ ID int `json:"id"`
+ Username string `json:"username"`
+ Email string `json:"email"`
+ Name string `json:"name"`
+ State string `json:"state"`
+ CreatedAt *time.Time `json:"created_at"`
+ } `json:"author"`
+ UpdatedAt *time.Time `json:"updated_at"`
+ CreatedAt *time.Time `json:"created_at"`
+}
+
+func (s Snippet) String() string {
+ return Stringify(s)
+}
+
+// ListSnippetsOptions represents the available ListSnippets() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/project_snippets.html#list-snippets
+type ListSnippetsOptions struct {
+ ListOptions
+}
+
+// ListSnippets gets a list of project snippets.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/project_snippets.html#list-snippets
+func (s *ProjectSnippetsService) ListSnippets(pid interface{}, opt *ListSnippetsOptions, options ...OptionFunc) ([]*Snippet, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/snippets", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var ps []*Snippet
+ resp, err := s.client.Do(req, &ps)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return ps, resp, err
+}
+
+// GetSnippet gets a single project snippet
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/project_snippets.html#single-snippet
+func (s *ProjectSnippetsService) GetSnippet(pid interface{}, snippet int, options ...OptionFunc) (*Snippet, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/snippets/%d", url.QueryEscape(project), snippet)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ ps := new(Snippet)
+ resp, err := s.client.Do(req, ps)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return ps, resp, err
+}
+
+// CreateSnippetOptions represents the available CreateSnippet() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/project_snippets.html#create-new-snippet
+type CreateSnippetOptions struct {
+ Title *string `url:"title,omitempty" json:"title,omitempty"`
+ FileName *string `url:"file_name,omitempty" json:"file_name,omitempty"`
+ Code *string `url:"code,omitempty" json:"code,omitempty"`
+ Visibility *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"`
+}
+
+// CreateSnippet creates a new project snippet. The user must have permission
+// to create new snippets.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/project_snippets.html#create-new-snippet
+func (s *ProjectSnippetsService) CreateSnippet(pid interface{}, opt *CreateSnippetOptions, options ...OptionFunc) (*Snippet, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/snippets", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ ps := new(Snippet)
+ resp, err := s.client.Do(req, ps)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return ps, resp, err
+}
+
+// UpdateSnippetOptions represents the available UpdateSnippet() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/project_snippets.html#update-snippet
+type UpdateSnippetOptions struct {
+ Title *string `url:"title,omitempty" json:"title,omitempty"`
+ FileName *string `url:"file_name,omitempty" json:"file_name,omitempty"`
+ Code *string `url:"code,omitempty" json:"code,omitempty"`
+ Visibility *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"`
+}
+
+// UpdateSnippet updates an existing project snippet. The user must have
+// permission to change an existing snippet.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/project_snippets.html#update-snippet
+func (s *ProjectSnippetsService) UpdateSnippet(pid interface{}, snippet int, opt *UpdateSnippetOptions, options ...OptionFunc) (*Snippet, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/snippets/%d", url.QueryEscape(project), snippet)
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ ps := new(Snippet)
+ resp, err := s.client.Do(req, ps)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return ps, resp, err
+}
+
+// DeleteSnippet deletes an existing project snippet. This is an idempotent
+// function and deleting a non-existent snippet still returns a 200 OK status
+// code.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/project_snippets.html#delete-snippet
+func (s *ProjectSnippetsService) DeleteSnippet(pid interface{}, snippet int, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/snippets/%d", url.QueryEscape(project), snippet)
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// SnippetContent returns the raw project snippet as plain text.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/project_snippets.html#snippet-content
+func (s *ProjectSnippetsService) SnippetContent(pid interface{}, snippet int, options ...OptionFunc) ([]byte, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/snippets/%d/raw", url.QueryEscape(project), snippet)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var b bytes.Buffer
+ resp, err := s.client.Do(req, &b)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return b.Bytes(), resp, err
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/projects.go b/vendor/github.com/lkysow/go-gitlab/projects.go
new file mode 100644
index 0000000000..c8d2592378
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/projects.go
@@ -0,0 +1,890 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// 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.
+//
+
+package gitlab
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "mime/multipart"
+ "net/url"
+ "os"
+ "time"
+)
+
+// ProjectsService handles communication with the repositories related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html
+type ProjectsService struct {
+ client *Client
+}
+
+// Project represents a GitLab project.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html
+type Project struct {
+ ID int `json:"id"`
+ Description string `json:"description"`
+ DefaultBranch string `json:"default_branch"`
+ Public bool `json:"public"`
+ Visibility VisibilityValue `json:"visibility"`
+ SSHURLToRepo string `json:"ssh_url_to_repo"`
+ HTTPURLToRepo string `json:"http_url_to_repo"`
+ WebURL string `json:"web_url"`
+ TagList []string `json:"tag_list"`
+ Owner *User `json:"owner"`
+ Name string `json:"name"`
+ NameWithNamespace string `json:"name_with_namespace"`
+ Path string `json:"path"`
+ PathWithNamespace string `json:"path_with_namespace"`
+ IssuesEnabled bool `json:"issues_enabled"`
+ OpenIssuesCount int `json:"open_issues_count"`
+ MergeRequestsEnabled bool `json:"merge_requests_enabled"`
+ ApprovalsBeforeMerge int `json:"approvals_before_merge"`
+ JobsEnabled bool `json:"jobs_enabled"`
+ WikiEnabled bool `json:"wiki_enabled"`
+ SnippetsEnabled bool `json:"snippets_enabled"`
+ ContainerRegistryEnabled bool `json:"container_registry_enabled"`
+ CreatedAt *time.Time `json:"created_at,omitempty"`
+ LastActivityAt *time.Time `json:"last_activity_at,omitempty"`
+ CreatorID int `json:"creator_id"`
+ Namespace *ProjectNamespace `json:"namespace"`
+ Permissions *Permissions `json:"permissions"`
+ Archived bool `json:"archived"`
+ AvatarURL string `json:"avatar_url"`
+ SharedRunnersEnabled bool `json:"shared_runners_enabled"`
+ ForksCount int `json:"forks_count"`
+ StarCount int `json:"star_count"`
+ RunnersToken string `json:"runners_token"`
+ PublicJobs bool `json:"public_jobs"`
+ OnlyAllowMergeIfPipelineSucceeds bool `json:"only_allow_merge_if_pipeline_succeeds"`
+ OnlyAllowMergeIfAllDiscussionsAreResolved bool `json:"only_allow_merge_if_all_discussions_are_resolved"`
+ LFSEnabled bool `json:"lfs_enabled"`
+ RequestAccessEnabled bool `json:"request_access_enabled"`
+ ForkedFromProject *ForkParent `json:"forked_from_project"`
+ SharedWithGroups []struct {
+ GroupID int `json:"group_id"`
+ GroupName string `json:"group_name"`
+ GroupAccessLevel int `json:"group_access_level"`
+ } `json:"shared_with_groups"`
+ Statistics *ProjectStatistics `json:"statistics"`
+}
+
+// Repository represents a repository.
+type Repository struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ WebURL string `json:"web_url"`
+ AvatarURL string `json:"avatar_url"`
+ GitSSHURL string `json:"git_ssh_url"`
+ GitHTTPURL string `json:"git_http_url"`
+ Namespace string `json:"namespace"`
+ Visibility VisibilityValue `json:"visibility"`
+ PathWithNamespace string `json:"path_with_namespace"`
+ DefaultBranch string `json:"default_branch"`
+ Homepage string `json:"homepage"`
+ URL string `json:"url"`
+ SSHURL string `json:"ssh_url"`
+ HTTPURL string `json:"http_url"`
+}
+
+// ProjectNamespace represents a project namespace.
+type ProjectNamespace struct {
+ CreatedAt *time.Time `json:"created_at"`
+ Description string `json:"description"`
+ ID int `json:"id"`
+ Name string `json:"name"`
+ OwnerID int `json:"owner_id"`
+ Path string `json:"path"`
+ UpdatedAt *time.Time `json:"updated_at"`
+}
+
+// StorageStatistics represents a statistics record for a group or project.
+type StorageStatistics struct {
+ StorageSize int64 `json:"storage_size"`
+ RepositorySize int64 `json:"repository_size"`
+ LfsObjectsSize int64 `json:"lfs_objects_size"`
+ JobArtifactsSize int64 `json:"job_artifacts_size"`
+}
+
+// ProjectStatistics represents a statistics record for a project.
+type ProjectStatistics struct {
+ StorageStatistics
+ CommitCount int `json:"commit_count"`
+}
+
+// Permissions represents permissions.
+type Permissions struct {
+ ProjectAccess *ProjectAccess `json:"project_access"`
+ GroupAccess *GroupAccess `json:"group_access"`
+}
+
+// ProjectAccess represents project access.
+type ProjectAccess struct {
+ AccessLevel AccessLevelValue `json:"access_level"`
+ NotificationLevel NotificationLevelValue `json:"notification_level"`
+}
+
+// GroupAccess represents group access.
+type GroupAccess struct {
+ AccessLevel AccessLevelValue `json:"access_level"`
+ NotificationLevel NotificationLevelValue `json:"notification_level"`
+}
+
+// ForkParent represents the parent project when this is a fork.
+type ForkParent struct {
+ HTTPURLToRepo string `json:"http_url_to_repo"`
+ ID int `json:"id"`
+ Name string `json:"name"`
+ NameWithNamespace string `json:"name_with_namespace"`
+ Path string `json:"path"`
+ PathWithNamespace string `json:"path_with_namespace"`
+ WebURL string `json:"web_url"`
+}
+
+func (s Project) String() string {
+ return Stringify(s)
+}
+
+// ListProjectsOptions represents the available ListProjects() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#list-projects
+type ListProjectsOptions struct {
+ ListOptions
+ Archived *bool `url:"archived,omitempty" json:"archived,omitempty"`
+ OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"`
+ Sort *string `url:"sort,omitempty" json:"sort,omitempty"`
+ Search *string `url:"search,omitempty" json:"search,omitempty"`
+ Simple *bool `url:"simple,omitempty" json:"simple,omitempty"`
+ Owned *bool `url:"owned,omitempty" json:"owned,omitempty"`
+ Membership *bool `url:"membership,omitempty" json:"membership,omitempty"`
+ Starred *bool `url:"starred,omitempty" json:"starred,omitempty"`
+ Statistics *bool `url:"statistics,omitempty" json:"statistics,omitempty"`
+ Visibility *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"`
+}
+
+// ListProjects gets a list of projects accessible by the authenticated user.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#list-projects
+func (s *ProjectsService) ListProjects(opt *ListProjectsOptions, options ...OptionFunc) ([]*Project, *Response, error) {
+ req, err := s.client.NewRequest("GET", "projects", opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var p []*Project
+ resp, err := s.client.Do(req, &p)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return p, resp, err
+}
+
+// ListUserProjects gets a list of projects for the given user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#list-user-projects
+func (s *ProjectsService) ListUserProjects(uid interface{}, opt *ListProjectsOptions, options ...OptionFunc) ([]*Project, *Response, error) {
+ user, err := parseID(uid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("users/%s/projects", user)
+
+ req, err := s.client.NewRequest("GET", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var p []*Project
+ resp, err := s.client.Do(req, &p)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return p, resp, err
+}
+
+// GetProject gets a specific project, identified by project ID or
+// NAMESPACE/PROJECT_NAME, which is owned by the authenticated user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#get-single-project
+func (s *ProjectsService) GetProject(pid interface{}, options ...OptionFunc) (*Project, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ p := new(Project)
+ resp, err := s.client.Do(req, p)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return p, resp, err
+}
+
+// ProjectEvent represents a GitLab project event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#get-project-events
+type ProjectEvent struct {
+ Title interface{} `json:"title"`
+ ProjectID int `json:"project_id"`
+ ActionName string `json:"action_name"`
+ TargetID interface{} `json:"target_id"`
+ TargetType interface{} `json:"target_type"`
+ AuthorID int `json:"author_id"`
+ AuthorUsername string `json:"author_username"`
+ Data struct {
+ Before string `json:"before"`
+ After string `json:"after"`
+ Ref string `json:"ref"`
+ UserID int `json:"user_id"`
+ UserName string `json:"user_name"`
+ Repository *Repository `json:"repository"`
+ Commits []*Commit `json:"commits"`
+ TotalCommitsCount int `json:"total_commits_count"`
+ } `json:"data"`
+ TargetTitle interface{} `json:"target_title"`
+}
+
+func (s ProjectEvent) String() string {
+ return Stringify(s)
+}
+
+// GetProjectEventsOptions represents the available GetProjectEvents() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#get-project-events
+type GetProjectEventsOptions struct {
+ ListOptions
+}
+
+// GetProjectEvents gets the events for the specified project. Sorted from
+// newest to latest.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#get-project-events
+func (s *ProjectsService) GetProjectEvents(pid interface{}, opt *GetProjectEventsOptions, options ...OptionFunc) ([]*ProjectEvent, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/events", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var p []*ProjectEvent
+ resp, err := s.client.Do(req, &p)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return p, resp, err
+}
+
+// CreateProjectOptions represents the available CreateProjects() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#create-project
+type CreateProjectOptions struct {
+ Name *string `url:"name,omitempty" json:"name,omitempty"`
+ Path *string `url:"path,omitempty" json:"path,omitempty"`
+ DefaultBranch *string `url:"default_branch,omitempty" json:"default_branch,omitempty"`
+ NamespaceID *int `url:"namespace_id,omitempty" json:"namespace_id,omitempty"`
+ Description *string `url:"description,omitempty" json:"description,omitempty"`
+ IssuesEnabled *bool `url:"issues_enabled,omitempty" json:"issues_enabled,omitempty"`
+ MergeRequestsEnabled *bool `url:"merge_requests_enabled,omitempty" json:"merge_requests_enabled,omitempty"`
+ JobsEnabled *bool `url:"jobs_enabled,omitempty" json:"jobs_enabled,omitempty"`
+ WikiEnabled *bool `url:"wiki_enabled,omitempty" json:"wiki_enabled,omitempty"`
+ SnippetsEnabled *bool `url:"snippets_enabled,omitempty" json:"snippets_enabled,omitempty"`
+ ContainerRegistryEnabled *bool `url:"container_registry_enabled,omitempty" json:"container_registry_enabled,omitempty"`
+ SharedRunnersEnabled *bool `url:"shared_runners_enabled,omitempty" json:"shared_runners_enabled,omitempty"`
+ Visibility *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"`
+ ImportURL *string `url:"import_url,omitempty" json:"import_url,omitempty"`
+ PublicJobs *bool `url:"public_jobs,omitempty" json:"public_jobs,omitempty"`
+ OnlyAllowMergeIfPipelineSucceeds *bool `url:"only_allow_merge_if_pipeline_succeeds,omitempty" json:"only_allow_merge_if_pipeline_succeeds,omitempty"`
+ OnlyAllowMergeIfAllDiscussionsAreResolved *bool `url:"only_allow_merge_if_all_discussions_are_resolved,omitempty" json:"only_allow_merge_if_all_discussions_are_resolved,omitempty"`
+ LFSEnabled *bool `url:"lfs_enabled,omitempty" json:"lfs_enabled,omitempty"`
+ RequestAccessEnabled *bool `url:"request_access_enabled,omitempty" json:"request_access_enabled,omitempty"`
+}
+
+// CreateProject creates a new project owned by the authenticated user.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#create-project
+func (s *ProjectsService) CreateProject(opt *CreateProjectOptions, options ...OptionFunc) (*Project, *Response, error) {
+ req, err := s.client.NewRequest("POST", "projects", opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ p := new(Project)
+ resp, err := s.client.Do(req, p)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return p, resp, err
+}
+
+// CreateProjectForUserOptions represents the available CreateProjectForUser()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#create-project-for-user
+type CreateProjectForUserOptions CreateProjectOptions
+
+// CreateProjectForUser creates a new project owned by the specified user.
+// Available only for admins.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#create-project-for-user
+func (s *ProjectsService) CreateProjectForUser(user int, opt *CreateProjectForUserOptions, options ...OptionFunc) (*Project, *Response, error) {
+ u := fmt.Sprintf("projects/user/%d", user)
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ p := new(Project)
+ resp, err := s.client.Do(req, p)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return p, resp, err
+}
+
+// EditProjectOptions represents the available EditProject() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#edit-project
+type EditProjectOptions CreateProjectOptions
+
+// EditProject updates an existing project.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#edit-project
+func (s *ProjectsService) EditProject(pid interface{}, opt *EditProjectOptions, options ...OptionFunc) (*Project, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ p := new(Project)
+ resp, err := s.client.Do(req, p)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return p, resp, err
+}
+
+// ForkProject forks a project into the user namespace of the authenticated
+// user.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#fork-project
+func (s *ProjectsService) ForkProject(pid interface{}, options ...OptionFunc) (*Project, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/fork", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("POST", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ p := new(Project)
+ resp, err := s.client.Do(req, p)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return p, resp, err
+}
+
+// StarProject stars a given the project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#star-a-project
+func (s *ProjectsService) StarProject(pid interface{}, options ...OptionFunc) (*Project, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/star", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("POST", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ p := new(Project)
+ resp, err := s.client.Do(req, p)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return p, resp, err
+}
+
+// UnstarProject unstars a given project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#unstar-a-project
+func (s *ProjectsService) UnstarProject(pid interface{}, options ...OptionFunc) (*Project, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/unstar", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("POST", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ p := new(Project)
+ resp, err := s.client.Do(req, p)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return p, resp, err
+}
+
+// ArchiveProject archives the project if the user is either admin or the
+// project owner of this project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#archive-a-project
+func (s *ProjectsService) ArchiveProject(pid interface{}, options ...OptionFunc) (*Project, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/archive", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("POST", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ p := new(Project)
+ resp, err := s.client.Do(req, p)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return p, resp, err
+}
+
+// UnarchiveProject unarchives the project if the user is either admin or
+// the project owner of this project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#unarchive-a-project
+func (s *ProjectsService) UnarchiveProject(pid interface{}, options ...OptionFunc) (*Project, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/unarchive", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("POST", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ p := new(Project)
+ resp, err := s.client.Do(req, p)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return p, resp, err
+}
+
+// DeleteProject removes a project including all associated resources
+// (issues, merge requests etc.)
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#remove-project
+func (s *ProjectsService) DeleteProject(pid interface{}, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// ShareWithGroupOptions represents options to share project with groups
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#share-project-with-group
+type ShareWithGroupOptions struct {
+ GroupID *int `url:"group_id" json:"group_id"`
+ GroupAccess *AccessLevelValue `url:"group_access" json:"group_access"`
+ ExpiresAt *string `url:"expires_at" json:"expires_at"`
+}
+
+// ShareProjectWithGroup allows to share a project with a group.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#share-project-with-group
+func (s *ProjectsService) ShareProjectWithGroup(pid interface{}, opt *ShareWithGroupOptions, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/share", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// ProjectMember represents a project member.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#list-project-team-members
+type ProjectMember struct {
+ ID int `json:"id"`
+ Username string `json:"username"`
+ Email string `json:"email"`
+ Name string `json:"name"`
+ State string `json:"state"`
+ CreatedAt *time.Time `json:"created_at"`
+ AccessLevel AccessLevelValue `json:"access_level"`
+}
+
+// ProjectHook represents a project hook.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#list-project-hooks
+type ProjectHook struct {
+ ID int `json:"id"`
+ URL string `json:"url"`
+ ProjectID int `json:"project_id"`
+ PushEvents bool `json:"push_events"`
+ IssuesEvents bool `json:"issues_events"`
+ MergeRequestsEvents bool `json:"merge_requests_events"`
+ TagPushEvents bool `json:"tag_push_events"`
+ NoteEvents bool `json:"note_events"`
+ JobEvents bool `json:"job_events"`
+ PipelineEvents bool `json:"pipeline_events"`
+ WikiPageEvents bool `json:"wiki_page_events"`
+ EnableSSLVerification bool `json:"enable_ssl_verification"`
+ CreatedAt *time.Time `json:"created_at"`
+}
+
+// ListProjectHooksOptions represents the available ListProjectHooks() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#list-project-hooks
+type ListProjectHooksOptions struct {
+ ListOptions
+}
+
+// ListProjectHooks gets a list of project hooks.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#list-project-hooks
+func (s *ProjectsService) ListProjectHooks(pid interface{}, opt *ListProjectHooksOptions, options ...OptionFunc) ([]*ProjectHook, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/hooks", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var ph []*ProjectHook
+ resp, err := s.client.Do(req, &ph)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return ph, resp, err
+}
+
+// GetProjectHook gets a specific hook for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#get-project-hook
+func (s *ProjectsService) GetProjectHook(pid interface{}, hook int, options ...OptionFunc) (*ProjectHook, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/hooks/%d", url.QueryEscape(project), hook)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ ph := new(ProjectHook)
+ resp, err := s.client.Do(req, ph)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return ph, resp, err
+}
+
+// AddProjectHookOptions represents the available AddProjectHook() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#add-project-hook
+type AddProjectHookOptions struct {
+ URL *string `url:"url,omitempty" json:"url,omitempty"`
+ PushEvents *bool `url:"push_events,omitempty" json:"push_events,omitempty"`
+ IssuesEvents *bool `url:"issues_events,omitempty" json:"issues_events,omitempty"`
+ MergeRequestsEvents *bool `url:"merge_requests_events,omitempty" json:"merge_requests_events,omitempty"`
+ TagPushEvents *bool `url:"tag_push_events,omitempty" json:"tag_push_events,omitempty"`
+ NoteEvents *bool `url:"note_events,omitempty" json:"note_events,omitempty"`
+ JobEvents *bool `url:"job_events,omitempty" json:"job_events,omitempty"`
+ PipelineEvents *bool `url:"pipeline_events,omitempty" json:"pipeline_events,omitempty"`
+ WikiPageEvents *bool `url:"wiki_page_events,omitempty" json:"wiki_page_events,omitempty"`
+ EnableSSLVerification *bool `url:"enable_ssl_verification,omitempty" json:"enable_ssl_verification,omitempty"`
+ Token *string `url:"token,omitempty" json:"token,omitempty"`
+}
+
+// AddProjectHook adds a hook to a specified project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#add-project-hook
+func (s *ProjectsService) AddProjectHook(pid interface{}, opt *AddProjectHookOptions, options ...OptionFunc) (*ProjectHook, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/hooks", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ ph := new(ProjectHook)
+ resp, err := s.client.Do(req, ph)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return ph, resp, err
+}
+
+// EditProjectHookOptions represents the available EditProjectHook() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#edit-project-hook
+type EditProjectHookOptions struct {
+ URL *string `url:"url,omitempty" json:"url,omitempty"`
+ PushEvents *bool `url:"push_events,omitempty" json:"push_events,omitempty"`
+ IssuesEvents *bool `url:"issues_events,omitempty" json:"issues_events,omitempty"`
+ MergeRequestsEvents *bool `url:"merge_requests_events,omitempty" json:"merge_requests_events,omitempty"`
+ TagPushEvents *bool `url:"tag_push_events,omitempty" json:"tag_push_events,omitempty"`
+ NoteEvents *bool `url:"note_events,omitempty" json:"note_events,omitempty"`
+ JobEvents *bool `url:"job_events,omitempty" json:"job_events,omitempty"`
+ PipelineEvents *bool `url:"pipeline_events,omitempty" json:"pipeline_events,omitempty"`
+ WikiPageEvents *bool `url:"wiki_page_events,omitempty" json:"wiki_page_events,omitempty"`
+ EnableSSLVerification *bool `url:"enable_ssl_verification,omitempty" json:"enable_ssl_verification,omitempty"`
+ Token *string `url:"token,omitempty" json:"token,omitempty"`
+}
+
+// EditProjectHook edits a hook for a specified project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#edit-project-hook
+func (s *ProjectsService) EditProjectHook(pid interface{}, hook int, opt *EditProjectHookOptions, options ...OptionFunc) (*ProjectHook, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/hooks/%d", url.QueryEscape(project), hook)
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ ph := new(ProjectHook)
+ resp, err := s.client.Do(req, ph)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return ph, resp, err
+}
+
+// DeleteProjectHook removes a hook from a project. This is an idempotent
+// method and can be called multiple times. Either the hook is available or not.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#delete-project-hook
+func (s *ProjectsService) DeleteProjectHook(pid interface{}, hook int, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/hooks/%d", url.QueryEscape(project), hook)
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// ProjectForkRelation represents a project fork relationship.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#admin-fork-relation
+type ProjectForkRelation struct {
+ ID int `json:"id"`
+ ForkedToProjectID int `json:"forked_to_project_id"`
+ ForkedFromProjectID int `json:"forked_from_project_id"`
+ CreatedAt *time.Time `json:"created_at"`
+ UpdatedAt *time.Time `json:"updated_at"`
+}
+
+// CreateProjectForkRelation creates a forked from/to relation between
+// existing projects.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#create-a-forked-fromto-relation-between-existing-projects.
+func (s *ProjectsService) CreateProjectForkRelation(pid int, fork int, options ...OptionFunc) (*ProjectForkRelation, *Response, error) {
+ u := fmt.Sprintf("projects/%d/fork/%d", pid, fork)
+
+ req, err := s.client.NewRequest("POST", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ pfr := new(ProjectForkRelation)
+ resp, err := s.client.Do(req, pfr)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return pfr, resp, err
+}
+
+// DeleteProjectForkRelation deletes an existing forked from relationship.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#delete-an-existing-forked-from-relationship
+func (s *ProjectsService) DeleteProjectForkRelation(pid int, options ...OptionFunc) (*Response, error) {
+ u := fmt.Sprintf("projects/%d/fork", pid)
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// ProjectFile represents an uploaded project file
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#upload-a-file
+type ProjectFile struct {
+ Alt string `json:"alt"`
+ URL string `json:"url"`
+ Markdown string `json:"markdown"`
+}
+
+// UploadFile upload a file from disk
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#upload-a-file
+func (s *ProjectsService) UploadFile(pid interface{}, file string, options ...OptionFunc) (*ProjectFile, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/uploads", url.QueryEscape(project))
+
+ f, err := os.Open(file)
+ if err != nil {
+ return nil, nil, err
+ }
+ defer f.Close()
+
+ b := &bytes.Buffer{}
+ w := multipart.NewWriter(b)
+
+ fw, err := w.CreateFormFile("file", file)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ _, err = io.Copy(fw, f)
+ if err != nil {
+ return nil, nil, err
+ }
+ w.Close()
+
+ req, err := s.client.NewRequest("", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ req.Body = ioutil.NopCloser(b)
+ req.ContentLength = int64(b.Len())
+ req.Header.Set("Content-Type", w.FormDataContentType())
+ req.Method = "POST"
+
+ uf := &ProjectFile{}
+ resp, err := s.client.Do(req, uf)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return uf, resp, nil
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/projects_test.go b/vendor/github.com/lkysow/go-gitlab/projects_test.go
new file mode 100644
index 0000000000..32de2cc54e
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/projects_test.go
@@ -0,0 +1,235 @@
+package gitlab
+
+import (
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "reflect"
+ "strings"
+ "testing"
+)
+
+func TestListProjects(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "GET")
+ fmt.Fprint(w, `[{"id":1},{"id":2}]`)
+ })
+
+ opt := &ListProjectsOptions{
+ ListOptions: ListOptions{2, 3},
+ Archived: Bool(true),
+ OrderBy: String("name"),
+ Sort: String("asc"),
+ Search: String("query"),
+ Simple: Bool(true),
+ Visibility: Visibility(PublicVisibility),
+ }
+
+ projects, _, err := client.Projects.ListProjects(opt)
+ if err != nil {
+ t.Errorf("Projects.ListProjects returned error: %v", err)
+ }
+
+ want := []*Project{{ID: 1}, {ID: 2}}
+ if !reflect.DeepEqual(want, projects) {
+ t.Errorf("Projects.ListProjects returned %+v, want %+v", projects, want)
+ }
+}
+
+func TestListUserProjects(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/users/1/projects", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "GET")
+ fmt.Fprint(w, `[{"id":1},{"id":2}]`)
+ })
+
+ opt := &ListProjectsOptions{
+ ListOptions: ListOptions{2, 3},
+ Archived: Bool(true),
+ OrderBy: String("name"),
+ Sort: String("asc"),
+ Search: String("query"),
+ Simple: Bool(true),
+ Visibility: Visibility(PublicVisibility),
+ }
+
+ projects, _, err := client.Projects.ListUserProjects(1, opt)
+ if err != nil {
+ t.Errorf("Projects.ListUserProjects returned error: %v", err)
+ }
+
+ want := []*Project{{ID: 1}, {ID: 2}}
+ if !reflect.DeepEqual(want, projects) {
+ t.Errorf("Projects.ListUserProjects returned %+v, want %+v", projects, want)
+ }
+}
+
+func TestListOwnedProjects(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "GET")
+ fmt.Fprint(w, `[{"id":1},{"id":2}]`)
+ })
+
+ opt := &ListProjectsOptions{
+ ListOptions: ListOptions{2, 3},
+ Archived: Bool(true),
+ OrderBy: String("name"),
+ Sort: String("asc"),
+ Search: String("query"),
+ Simple: Bool(true),
+ Owned: Bool(true),
+ Visibility: Visibility(PublicVisibility),
+ }
+
+ projects, _, err := client.Projects.ListProjects(opt)
+ if err != nil {
+ t.Errorf("Projects.ListOwnedProjects returned error: %v", err)
+ }
+
+ want := []*Project{{ID: 1}, {ID: 2}}
+ if !reflect.DeepEqual(want, projects) {
+ t.Errorf("Projects.ListOwnedProjects returned %+v, want %+v", projects, want)
+ }
+}
+
+func TestListStarredProjects(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "GET")
+ fmt.Fprint(w, `[{"id":1},{"id":2}]`)
+ })
+
+ opt := &ListProjectsOptions{
+ ListOptions: ListOptions{2, 3},
+ Archived: Bool(true),
+ OrderBy: String("name"),
+ Sort: String("asc"),
+ Search: String("query"),
+ Simple: Bool(true),
+ Starred: Bool(true),
+ Visibility: Visibility(PublicVisibility),
+ }
+
+ projects, _, err := client.Projects.ListProjects(opt)
+ if err != nil {
+ t.Errorf("Projects.ListStarredProjects returned error: %v", err)
+ }
+
+ want := []*Project{{ID: 1}, {ID: 2}}
+ if !reflect.DeepEqual(want, projects) {
+ t.Errorf("Projects.ListStarredProjects returned %+v, want %+v", projects, want)
+ }
+}
+
+func TestGetProject_byID(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects/1", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "GET")
+ fmt.Fprint(w, `{"id":1}`)
+ })
+ want := &Project{ID: 1}
+
+ project, _, err := client.Projects.GetProject(1)
+ if err != nil {
+ t.Fatalf("Projects.GetProject returns an error: %v", err)
+ }
+
+ if !reflect.DeepEqual(want, project) {
+ t.Errorf("Projects.GetProject returned %+v, want %+v", project, want)
+ }
+}
+
+func TestGetProject_byName(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects/", func(w http.ResponseWriter, r *http.Request) {
+ testURL(t, r, "/projects/namespace%2Fname")
+ testMethod(t, r, "GET")
+ fmt.Fprint(w, `{"id":1}`)
+ })
+ want := &Project{ID: 1}
+
+ project, _, err := client.Projects.GetProject("namespace/name")
+ if err != nil {
+ t.Fatalf("Projects.GetProject returns an error: %v", err)
+ }
+
+ if !reflect.DeepEqual(want, project) {
+ t.Errorf("Projects.GetProject returned %+v, want %+v", project, want)
+ }
+}
+
+func TestCreateProject(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "POST")
+ fmt.Fprint(w, `{"id":1}`)
+ })
+
+ opt := &CreateProjectOptions{Name: String("n")}
+
+ project, _, err := client.Projects.CreateProject(opt)
+ if err != nil {
+ t.Errorf("Projects.CreateProject returned error: %v", err)
+ }
+
+ want := &Project{ID: 1}
+ if !reflect.DeepEqual(want, project) {
+ t.Errorf("Projects.CreateProject returned %+v, want %+v", project, want)
+ }
+}
+
+func TestUploadFile(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ tf, _ := ioutil.TempFile(os.TempDir(), "test")
+ defer os.Remove(tf.Name())
+
+ mux.HandleFunc("/projects/1/uploads", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, http.MethodPost)
+ if false == strings.Contains(r.Header.Get("Content-Type"), "multipart/form-data;") {
+ t.Fatalf("Prokects.UploadFile request content-type %+v want multipart/form-data;", r.Header.Get("Content-Type"))
+ }
+ if r.ContentLength == -1 {
+ t.Fatalf("Prokects.UploadFile request content-length is -1")
+ }
+ fmt.Fprint(w, `{
+ "alt": "dk",
+ "url": "/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.md",
+ "markdown": "![dk](/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png)"
+ }`)
+ })
+
+ want := &ProjectFile{
+ Alt: "dk",
+ URL: "/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.md",
+ Markdown: "![dk](/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png)",
+ }
+
+ file, _, err := client.Projects.UploadFile(1, tf.Name())
+
+ if err != nil {
+ t.Fatalf("Prokects.UploadFile returns an error: %v", err)
+ }
+
+ if !reflect.DeepEqual(want, file) {
+ t.Errorf("Prokects.UploadFile returned %+v, want %+v", file, want)
+ }
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/repositories.go b/vendor/github.com/lkysow/go-gitlab/repositories.go
new file mode 100644
index 0000000000..cb405ce265
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/repositories.go
@@ -0,0 +1,253 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// 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.
+//
+
+package gitlab
+
+import (
+ "bytes"
+ "fmt"
+ "net/url"
+)
+
+// RepositoriesService handles communication with the repositories related
+// methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/repositories.html
+type RepositoriesService struct {
+ client *Client
+}
+
+// TreeNode represents a GitLab repository file or directory.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/repositories.html
+type TreeNode struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Type string `json:"type"`
+ Path string `json:"path"`
+ Mode string `json:"mode"`
+}
+
+func (t TreeNode) String() string {
+ return Stringify(t)
+}
+
+// ListTreeOptions represents the available ListTree() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repositories.html#list-repository-tree
+type ListTreeOptions struct {
+ Path *string `url:"path,omitempty" json:"path,omitempty"`
+ Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
+ Recursive *bool `url:"recursive,omitempty" json:"recursive,omitempty"`
+}
+
+// ListTree gets a list of repository files and directories in a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repositories.html#list-repository-tree
+func (s *RepositoriesService) ListTree(pid interface{}, opt *ListTreeOptions, options ...OptionFunc) ([]*TreeNode, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/tree", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var t []*TreeNode
+ resp, err := s.client.Do(req, &t)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return t, resp, err
+}
+
+// RawFileContent gets the raw file contents for a file by commit SHA and path
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repositories.html#raw-file-content
+func (s *RepositoriesService) RawFileContent(pid interface{}, sha string, options ...OptionFunc) ([]byte, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/blobs/%s", url.QueryEscape(project), sha)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var b bytes.Buffer
+ resp, err := s.client.Do(req, &b)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return b.Bytes(), resp, err
+}
+
+// RawBlobContent gets the raw file contents for a blob by blob SHA.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repositories.html#raw-blob-content
+func (s *RepositoriesService) RawBlobContent(pid interface{}, sha string, options ...OptionFunc) ([]byte, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/blobs/%s/raw", url.QueryEscape(project), sha)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var b bytes.Buffer
+ resp, err := s.client.Do(req, &b)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return b.Bytes(), resp, err
+}
+
+// ArchiveOptions represents the available Archive() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repositories.html#get-file-archive
+type ArchiveOptions struct {
+ SHA *string `url:"sha,omitempty" json:"sha,omitempty"`
+}
+
+// Archive gets an archive of the repository.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repositories.html#get-file-archive
+func (s *RepositoriesService) Archive(pid interface{}, opt *ArchiveOptions, options ...OptionFunc) ([]byte, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/archive", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var b bytes.Buffer
+ resp, err := s.client.Do(req, &b)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return b.Bytes(), resp, err
+}
+
+// Compare represents the result of a comparison of branches, tags or commits.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repositories.html#compare-branches-tags-or-commits
+type Compare struct {
+ Commit *Commit `json:"commit"`
+ Commits []*Commit `json:"commits"`
+ Diffs []*Diff `json:"diffs"`
+ CompareTimeout bool `json:"compare_timeout"`
+ CompareSameRef bool `json:"compare_same_ref"`
+}
+
+func (c Compare) String() string {
+ return Stringify(c)
+}
+
+// CompareOptions represents the available Compare() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repositories.html#compare-branches-tags-or-commits
+type CompareOptions struct {
+ From *string `url:"from,omitempty" json:"from,omitempty"`
+ To *string `url:"to,omitempty" json:"to,omitempty"`
+}
+
+// Compare compares branches, tags or commits.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repositories.html#compare-branches-tags-or-commits
+func (s *RepositoriesService) Compare(pid interface{}, opt *CompareOptions, options ...OptionFunc) (*Compare, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/compare", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ c := new(Compare)
+ resp, err := s.client.Do(req, c)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return c, resp, err
+}
+
+// Contributor represents a GitLap contributor.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/repositories.html#contributer
+type Contributor struct {
+ Name string `json:"name,omitempty"`
+ Email string `json:"email,omitempty"`
+ Commits int `json:"commits,omitempty"`
+ Additions int `json:"additions,omitempty"`
+ Deletions int `json:"deletions,omitempty"`
+}
+
+func (c Contributor) String() string {
+ return Stringify(c)
+}
+
+// Contributors gets the repository contributors list.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/repositories.html#contributer
+func (s *RepositoriesService) Contributors(pid interface{}, options ...OptionFunc) ([]*Contributor, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/contributors", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var c []*Contributor
+ resp, err := s.client.Do(req, &c)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return c, resp, err
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/repository_files.go b/vendor/github.com/lkysow/go-gitlab/repository_files.go
new file mode 100644
index 0000000000..873fe73dd4
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/repository_files.go
@@ -0,0 +1,234 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// 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.
+//
+
+package gitlab
+
+import (
+ "fmt"
+ "net/url"
+)
+
+// RepositoryFilesService handles communication with the repository files
+// related methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html
+type RepositoryFilesService struct {
+ client *Client
+}
+
+// File represents a GitLab repository file.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html
+type File struct {
+ FileName string `json:"file_name"`
+ FilePath string `json:"file_path"`
+ Size int `json:"size"`
+ Encoding string `json:"encoding"`
+ Content string `json:"content"`
+ Ref string `json:"ref"`
+ BlobID string `json:"blob_id"`
+ CommitID string `json:"commit_id"`
+}
+
+func (r File) String() string {
+ return Stringify(r)
+}
+
+// GetFileOptions represents the available GetFile() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repository_files.html#get-file-from-repository
+type GetFileOptions struct {
+ Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
+}
+
+// GetFile allows you to receive information about a file in repository like
+// name, size, content. Note that file content is Base64 encoded.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repository_files.html#get-file-from-repository
+func (s *RepositoryFilesService) GetFile(pid interface{}, fileName string, opt *GetFileOptions, options ...OptionFunc) (*File, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/files/%s", url.QueryEscape(project), url.QueryEscape(fileName))
+
+ req, err := s.client.NewRequest("GET", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ f := new(File)
+ resp, err := s.client.Do(req, f)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return f, resp, err
+}
+
+// GetRawFileOptions represents the available GetRawFile() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repository_files.html#get-raw-file-from-repository
+type GetRawFileOptions struct {
+ Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
+}
+
+// GetRawFile allows you to receive the raw file in repository.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repository_files.html#get-raw-file-from-repository
+func (s *RepositoryFilesService) GetRawFile(pid interface{}, fileName string, opt *GetRawFileOptions, options ...OptionFunc) (*File, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/files/%s/raw", url.QueryEscape(project), url.QueryEscape(fileName))
+
+ req, err := s.client.NewRequest("GET", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ f := new(File)
+ resp, err := s.client.Do(req, f)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return f, resp, err
+}
+
+// FileInfo represents file details of a GitLab repository file.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html
+type FileInfo struct {
+ FilePath string `json:"file_path"`
+ Branch string `json:"branch"`
+}
+
+func (r FileInfo) String() string {
+ return Stringify(r)
+}
+
+// CreateFileOptions represents the available CreateFile() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repository_files.html#create-new-file-in-repository
+type CreateFileOptions struct {
+ Branch *string `url:"branch,omitempty" json:"branch,omitempty"`
+ Encoding *string `url:"encoding,omitempty" json:"encoding,omitempty"`
+ AuthorEmail *string `url:"author_email,omitempty" json:"author_email,omitempty"`
+ AuthorName *string `url:"author_name,omitempty" json:"author_name,omitempty"`
+ Content *string `url:"content,omitempty" json:"content,omitempty"`
+ CommitMessage *string `url:"commit_message,omitempty" json:"commit_message,omitempty"`
+}
+
+// CreateFile creates a new file in a repository.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repository_files.html#create-new-file-in-repository
+func (s *RepositoryFilesService) CreateFile(pid interface{}, fileName string, opt *CreateFileOptions, options ...OptionFunc) (*FileInfo, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/files/%s", url.QueryEscape(project), url.QueryEscape(fileName))
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ f := new(FileInfo)
+ resp, err := s.client.Do(req, f)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return f, resp, err
+}
+
+// UpdateFileOptions represents the available UpdateFile() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repository_files.html#update-existing-file-in-repository
+type UpdateFileOptions struct {
+ Branch *string `url:"branch,omitempty" json:"branch,omitempty"`
+ Encoding *string `url:"encoding,omitempty" json:"encoding,omitempty"`
+ AuthorEmail *string `url:"author_email,omitempty" json:"author_email,omitempty"`
+ AuthorName *string `url:"author_name,omitempty" json:"author_name,omitempty"`
+ Content *string `url:"content,omitempty" json:"content,omitempty"`
+ CommitMessage *string `url:"commit_message,omitempty" json:"commit_message,omitempty"`
+ LastCommitID *string `url:"last_commit_id,omitempty" json:"last_commit_id,omitempty"`
+}
+
+// UpdateFile updates an existing file in a repository
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repository_files.html#update-existing-file-in-repository
+func (s *RepositoryFilesService) UpdateFile(pid interface{}, fileName string, opt *UpdateFileOptions, options ...OptionFunc) (*FileInfo, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/files/%s", url.QueryEscape(project), url.QueryEscape(fileName))
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ f := new(FileInfo)
+ resp, err := s.client.Do(req, f)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return f, resp, err
+}
+
+// DeleteFileOptions represents the available DeleteFile() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repository_files.html#delete-existing-file-in-repository
+type DeleteFileOptions struct {
+ Branch *string `url:"branch,omitempty" json:"branch,omitempty"`
+ AuthorEmail *string `url:"author_email,omitempty" json:"author_email,omitempty"`
+ AuthorName *string `url:"author_name,omitempty" json:"author_name,omitempty"`
+ CommitMessage *string `url:"commit_message,omitempty" json:"commit_message,omitempty"`
+}
+
+// DeleteFile deletes an existing file in a repository
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repository_files.html#delete-existing-file-in-repository
+func (s *RepositoryFilesService) DeleteFile(pid interface{}, fileName string, opt *DeleteFileOptions, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/files/%s", url.QueryEscape(project), url.QueryEscape(fileName))
+
+ req, err := s.client.NewRequest("DELETE", u, opt, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/services.go b/vendor/github.com/lkysow/go-gitlab/services.go
new file mode 100644
index 0000000000..431bbf0af8
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/services.go
@@ -0,0 +1,420 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// 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.
+//
+
+package gitlab
+
+import (
+ "fmt"
+ "net/url"
+ "time"
+)
+
+// ServicesService handles communication with the services related methods of
+// the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/services.html
+type ServicesService struct {
+ client *Client
+}
+
+// Service represents a GitLab service.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/services.html
+type Service struct {
+ ID int `json:"id"`
+ Title string `json:"title"`
+ CreatedAt *time.Time `json:"created_at"`
+ UpdatedAt *time.Time `json:"updated_at"`
+ Active bool `json:"active"`
+ PushEvents bool `json:"push_events"`
+ IssuesEvents bool `json:"issues_events"`
+ MergeRequestsEvents bool `json:"merge_requests_events"`
+ TagPushEvents bool `json:"tag_push_events"`
+ NoteEvents bool `json:"note_events"`
+ PipelineEvents bool `json:"pipeline_events"`
+ JobEvents bool `json:"job_events"`
+}
+
+// SetGitLabCIServiceOptions represents the available SetGitLabCIService()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#edit-gitlab-ci-service
+type SetGitLabCIServiceOptions struct {
+ Token *string `url:"token,omitempty" json:"token,omitempty"`
+ ProjectURL *string `url:"project_url,omitempty" json:"project_url,omitempty"`
+}
+
+// SetGitLabCIService sets GitLab CI service for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#edit-gitlab-ci-service
+func (s *ServicesService) SetGitLabCIService(pid interface{}, opt *SetGitLabCIServiceOptions, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/services/gitlab-ci", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// DeleteGitLabCIService deletes GitLab CI service settings for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#delete-gitlab-ci-service
+func (s *ServicesService) DeleteGitLabCIService(pid interface{}, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/services/gitlab-ci", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// SetHipChatServiceOptions represents the available SetHipChatService()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#edit-hipchat-service
+type SetHipChatServiceOptions struct {
+ Token *string `url:"token,omitempty" json:"token,omitempty" `
+ Room *string `url:"room,omitempty" json:"room,omitempty"`
+}
+
+// SetHipChatService sets HipChat service for a project
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#edit-hipchat-service
+func (s *ServicesService) SetHipChatService(pid interface{}, opt *SetHipChatServiceOptions, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/services/hipchat", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// DeleteHipChatService deletes HipChat service for project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#delete-hipchat-service
+func (s *ServicesService) DeleteHipChatService(pid interface{}, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/services/hipchat", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// DroneCIService represents Drone CI service settings.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#drone-ci
+type DroneCIService struct {
+ Service
+ Properties *DroneCIServiceProperties `json:"properties"`
+}
+
+// DroneCIServiceProperties represents Drone CI specific properties.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#drone-ci
+type DroneCIServiceProperties struct {
+ Token string `json:"token"`
+ DroneURL string `json:"drone_url"`
+ EnableSSLVerification bool `json:"enable_ssl_verification"`
+}
+
+// GetDroneCIService gets Drone CI service settings for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#get-drone-ci-service-settings
+func (s *ServicesService) GetDroneCIService(pid interface{}, options ...OptionFunc) (*DroneCIService, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/services/drone-ci", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ svc := new(DroneCIService)
+ resp, err := s.client.Do(req, svc)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return svc, resp, err
+}
+
+// SetDroneCIServiceOptions represents the available SetDroneCIService()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#createedit-drone-ci-service
+type SetDroneCIServiceOptions struct {
+ Token *string `url:"token" json:"token" `
+ DroneURL *string `url:"drone_url" json:"drone_url"`
+ EnableSSLVerification *bool `url:"enable_ssl_verification,omitempty" json:"enable_ssl_verification,omitempty"`
+}
+
+// SetDroneCIService sets Drone CI service for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#createedit-drone-ci-service
+func (s *ServicesService) SetDroneCIService(pid interface{}, opt *SetDroneCIServiceOptions, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/services/drone-ci", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// DeleteDroneCIService deletes Drone CI service settings for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#delete-drone-ci-service
+func (s *ServicesService) DeleteDroneCIService(pid interface{}, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/services/drone-ci", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// SlackService represents Slack service settings.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#slack
+type SlackService struct {
+ Service
+ Properties *SlackServiceProperties `json:"properties"`
+}
+
+// SlackServiceProperties represents Slack specific properties.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#slack
+type SlackServiceProperties struct {
+ NotifyOnlyBrokenPipelines bool `json:"notify_only_broken_pipelines"`
+}
+
+// GetSlackService gets Slack service settings for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#get-slack-service-settings
+func (s *ServicesService) GetSlackService(pid interface{}, options ...OptionFunc) (*SlackService, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/services/slack", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ svc := new(SlackService)
+ resp, err := s.client.Do(req, svc)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return svc, resp, err
+}
+
+// SetSlackServiceOptions represents the available SetSlackService()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#edit-slack-service
+type SetSlackServiceOptions struct {
+ WebHook *string `url:"webhook,omitempty" json:"webhook,omitempty" `
+ Username *string `url:"username,omitempty" json:"username,omitempty" `
+ Channel *string `url:"channel,omitempty" json:"channel,omitempty"`
+}
+
+// SetSlackService sets Slack service for a project
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#edit-slack-service
+func (s *ServicesService) SetSlackService(pid interface{}, opt *SetSlackServiceOptions, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/services/slack", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// DeleteSlackService deletes Slack service for project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#delete-slack-service
+func (s *ServicesService) DeleteSlackService(pid interface{}, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/services/slack", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// JiraService represents Jira service settings.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#jira
+type JiraService struct {
+ Service
+ Properties *JiraServiceProperties `json:"properties"`
+}
+
+// JiraServiceProperties represents Jira specific properties.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#jira
+type JiraServiceProperties struct {
+ URL *string `url:"url,omitempty" json:"url,omitempty"`
+ ProjectKey *string `url:"project_key,omitempty" json:"project_key,omitempty" `
+ Username *string `url:"username,omitempty" json:"username,omitempty" `
+ Password *string `url:"password,omitempty" json:"password,omitempty" `
+ JiraIssueTransitionID *string `url:"jira_issue_transition_id,omitempty" json:"jira_issue_transition_id,omitempty"`
+}
+
+// GetJiraService gets Jira service settings for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#get-jira-service-settings
+func (s *ServicesService) GetJiraService(pid interface{}, options ...OptionFunc) (*JiraService, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/services/jira", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ svc := new(JiraService)
+ resp, err := s.client.Do(req, svc)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return svc, resp, err
+}
+
+// SetJiraServiceOptions represents the available SetJiraService()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#edit-jira-service
+type SetJiraServiceOptions JiraServiceProperties
+
+// SetJiraService sets Jira service for a project
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#edit-jira-service
+func (s *ServicesService) SetJiraService(pid interface{}, opt *SetJiraServiceOptions, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/services/jira", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// DeleteJiraService deletes Jira service for project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#delete-jira-service
+func (s *ServicesService) DeleteJiraService(pid interface{}, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/services/jira", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/services_test.go b/vendor/github.com/lkysow/go-gitlab/services_test.go
new file mode 100644
index 0000000000..03db4e7383
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/services_test.go
@@ -0,0 +1,165 @@
+package gitlab
+
+import (
+ "fmt"
+ "net/http"
+ "reflect"
+ "testing"
+)
+
+func TestGetDroneCIService(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects/1/services/drone-ci", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "GET")
+ fmt.Fprint(w, `{"id":1}`)
+ })
+ want := &DroneCIService{Service: Service{ID: 1}}
+
+ service, _, err := client.Services.GetDroneCIService(1)
+ if err != nil {
+ t.Fatalf("Services.GetDroneCIService returns an error: %v", err)
+ }
+ if !reflect.DeepEqual(want, service) {
+ t.Errorf("Services.GetDroneCIService returned %+v, want %+v", service, want)
+ }
+}
+
+func TestSetDroneCIService(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects/1/services/drone-ci", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "PUT")
+ })
+
+ opt := &SetDroneCIServiceOptions{String("t"), String("u"), Bool(true)}
+
+ _, err := client.Services.SetDroneCIService(1, opt)
+ if err != nil {
+ t.Fatalf("Services.SetDroneCIService returns an error: %v", err)
+ }
+}
+
+func TestDeleteDroneCIService(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects/1/services/drone-ci", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "DELETE")
+ })
+
+ _, err := client.Services.DeleteDroneCIService(1)
+ if err != nil {
+ t.Fatalf("Services.DeleteDroneCIService returns an error: %v", err)
+ }
+}
+
+func TestGetSlackService(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects/1/services/slack", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "GET")
+ fmt.Fprint(w, `{"id":1}`)
+ })
+ want := &SlackService{Service: Service{ID: 1}}
+
+ service, _, err := client.Services.GetSlackService(1)
+ if err != nil {
+ t.Fatalf("Services.GetSlackService returns an error: %v", err)
+ }
+ if !reflect.DeepEqual(want, service) {
+ t.Errorf("Services.GetSlackService returned %+v, want %+v", service, want)
+ }
+}
+
+func TestSetSlackService(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects/1/services/slack", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "PUT")
+ })
+
+ opt := &SetSlackServiceOptions{
+ WebHook: String("webhook_uri"),
+ Username: String("username"),
+ Channel: String("#development"),
+ }
+
+ _, err := client.Services.SetSlackService(1, opt)
+ if err != nil {
+ t.Fatalf("Services.SetSlackService returns an error: %v", err)
+ }
+}
+
+func TestDeleteSlackService(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects/1/services/slack", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "DELETE")
+ })
+
+ _, err := client.Services.DeleteSlackService(1)
+ if err != nil {
+ t.Fatalf("Services.DeleteSlackService returns an error: %v", err)
+ }
+}
+
+func TestGetJiraService(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects/1/services/jira", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "GET")
+ fmt.Fprint(w, `{"id":1}`)
+ })
+ want := &JiraService{Service: Service{ID: 1}}
+
+ service, _, err := client.Services.GetJiraService(1)
+ if err != nil {
+ t.Fatalf("Services.GetJiraService returns an error: %v", err)
+ }
+ if !reflect.DeepEqual(want, service) {
+ t.Errorf("Services.GetJiraService returned %+v, want %+v", service, want)
+ }
+}
+
+func TestSetJiraService(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects/1/services/jira", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "PUT")
+ })
+
+ opt := &SetJiraServiceOptions{
+ URL: String("asd"),
+ ProjectKey: String("as"),
+ Username: String("aas"),
+ Password: String("asd"),
+ JiraIssueTransitionID: String("asd"),
+ }
+
+ _, err := client.Services.SetJiraService(1, opt)
+ if err != nil {
+ t.Fatalf("Services.SetJiraService returns an error: %v", err)
+ }
+}
+
+func TestDeleteJiraService(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/projects/1/services/jira", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "DELETE")
+ })
+
+ _, err := client.Services.DeleteJiraService(1)
+ if err != nil {
+ t.Fatalf("Services.DeleteJiraService returns an error: %v", err)
+ }
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/session.go b/vendor/github.com/lkysow/go-gitlab/session.go
new file mode 100644
index 0000000000..f89fdbe5d7
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/session.go
@@ -0,0 +1,78 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// 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.
+//
+
+package gitlab
+
+import "time"
+
+// SessionService handles communication with the session related methods of
+// the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/session.html
+type SessionService struct {
+ client *Client
+}
+
+// Session represents a GitLab session.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/session.html#session
+type Session struct {
+ ID int `json:"id"`
+ Username string `json:"username"`
+ Email string `json:"email"`
+ Name string `json:"name"`
+ PrivateToken string `json:"private_token"`
+ Blocked bool `json:"blocked"`
+ CreatedAt *time.Time `json:"created_at"`
+ Bio interface{} `json:"bio"`
+ Skype string `json:"skype"`
+ Linkedin string `json:"linkedin"`
+ Twitter string `json:"twitter"`
+ WebsiteURL string `json:"website_url"`
+ DarkScheme bool `json:"dark_scheme"`
+ ThemeID int `json:"theme_id"`
+ IsAdmin bool `json:"is_admin"`
+ CanCreateGroup bool `json:"can_create_group"`
+ CanCreateTeam bool `json:"can_create_team"`
+ CanCreateProject bool `json:"can_create_project"`
+}
+
+// GetSessionOptions represents the available Session() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/session.html#session
+type GetSessionOptions struct {
+ Login *string `url:"login,omitempty" json:"login,omitempty"`
+ Email *string `url:"email,omitempty" json:"email,omitempty"`
+ Password *string `url:"password,omitempty" json:"password,omitempty"`
+}
+
+// GetSession logs in to get private token.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/session.html#session
+func (s *SessionService) GetSession(opt *GetSessionOptions, options ...OptionFunc) (*Session, *Response, error) {
+ req, err := s.client.NewRequest("POST", "session", opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ session := new(Session)
+ resp, err := s.client.Do(req, session)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return session, resp, err
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/settings.go b/vendor/github.com/lkysow/go-gitlab/settings.go
new file mode 100644
index 0000000000..d91b30581a
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/settings.go
@@ -0,0 +1,263 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// 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.
+//
+
+package gitlab
+
+import "time"
+
+// SettingsService handles communication with the application SettingsService
+// related methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/settings.html
+type SettingsService struct {
+ client *Client
+}
+
+// Settings represents the GitLab application settings.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/settings.html
+type Settings struct {
+ ID int `json:"id"`
+ CreatedAt *time.Time `json:"created_at"`
+ UpdatedAt *time.Time `json:"updated_at"`
+ AdminNotificationEmail string `json:"admin_notification_email"`
+ AfterSignOutPath string `json:"after_sign_out_path"`
+ AfterSignUpText string `json:"after_sign_up_text"`
+ AkismetApiKey string `json:"akismet_api_key"`
+ AkismetEnabled bool `json:"akismet_enabled"`
+ CircuitbreakerAccessRetries int `json:"circuitbreaker_access_retries"`
+ CircuitbreakerBackoffThreshold int `json:"circuitbreaker_backoff_threshold"`
+ CircuitbreakerFailureCountThreshold int `json:"circuitbreaker_failure_count_threshold"`
+ CircuitbreakerFailureResetTime int `json:"circuitbreaker_failure_reset_time"`
+ CircuitbreakerFailureWaitTime int `json:"circuitbreaker_failure_wait_time"`
+ CircuitbreakerStorageTimeout int `json:"circuitbreaker_storage_timeout"`
+ ClientsideSentryDSN string `json:"clientside_sentry_dsn"`
+ ClientsideSentryEnabled bool `json:"clientside_sentry_enabled"`
+ ContainerRegistryTokenExpireDelay int `json:"container_registry_token_expire_delay"`
+ DefaultArtifactsExpireIn string `json:"default_artifacts_expire_in"`
+ DefaultBranchProtection int `json:"default_branch_protection"`
+ DefaultGroupVisibility string `json:"default_group_visibility"`
+ DefaultProjectVisibility string `json:"default_project_visibility"`
+ DefaultProjectsLimit int `json:"default_projects_limit"`
+ DefaultSnippetVisibility string `json:"default_snippet_visibility"`
+ DisabledOauthSignInSources []string `json:"disabled_oauth_sign_in_sources"`
+ DomainBlacklistEnabled bool `json:"domain_blacklist_enabled"`
+ DomainBlacklist []string `json:"domain_blacklist"`
+ DomainWhitelist []string `json:"domain_whitelist"`
+ DSAKeyRestriction int `json:"dsa_key_restriction"`
+ ECDSAKeyRestriction int `json:"ecdsa_key_restriction"`
+ Ed25519KeyRestriction int `json:"ed25519_key_restriction"`
+ EmailAuthorInBody bool `json:"email_author_in_body"`
+ EnabledGitAccessProtocol string `json:"enabled_git_access_protocol"`
+ GravatarEnabled bool `json:"gravatar_enabled"`
+ HelpPageHideCommercialContent bool `json:"help_page_hide_commercial_content"`
+ HelpPageSupportURL string `json:"help_page_support_url"`
+ HomePageURL string `json:"home_page_url"`
+ HousekeepingBitmapsEnabled bool `json:"housekeeping_bitmaps_enabled"`
+ HousekeepingEnabled bool `json:"housekeeping_enabled"`
+ HousekeepingFullRepackPeriod int `json:"housekeeping_full_repack_period"`
+ HousekeepingGcPeriod int `json:"housekeeping_gc_period"`
+ HousekeepingIncrementalRepackPeriod int `json:"housekeeping_incremental_repack_period"`
+ HTMLEmailsEnabled bool `json:"html_emails_enabled"`
+ ImportSources []string `json:"import_sources"`
+ KodingEnabled bool `json:"koding_enabled"`
+ KodingURL string `json:"koding_url"`
+ MaxArtifactsSize int `json:"max_artifacts_size"`
+ MaxAttachmentSize int `json:"max_attachment_size"`
+ MaxPagesSize int `json:"max_pages_size"`
+ MetricsEnabled bool `json:"metrics_enabled"`
+ MetricsHost string `json:"metrics_host"`
+ MetricsMethodCallThreshold int `json:"metrics_method_call_threshold"`
+ MetricsPacketSize int `json:"metrics_packet_size"`
+ MetricsPoolSize int `json:"metrics_pool_size"`
+ MetricsPort int `json:"metrics_port"`
+ MetricsSampleInterval int `json:"metrics_sample_interval"`
+ MetricsTimeout int `json:"metrics_timeout"`
+ PasswordAuthenticationEnabled bool `json:"password_authentication_enabled"`
+ PerformanceBarAllowedGroupId string `json:"performance_bar_allowed_group_id"`
+ PerformanceBarEnabled bool `json:"performance_bar_enabled"`
+ PlantumlEnabled bool `json:"plantuml_enabled"`
+ PlantumlURL string `json:"plantuml_url"`
+ PollingIntervalMultiplier float64 `json:"polling_interval_multiplier"`
+ ProjectExportEnabled bool `json:"project_export_enabled"`
+ PrometheusMetricsEnabled bool `json:"prometheus_metrics_enabled"`
+ RecaptchaEnabled bool `json:"recaptcha_enabled"`
+ RecaptchaPrivateKey string `json:"recaptcha_private_key"`
+ RecaptchaSiteKey string `json:"recaptcha_site_key"`
+ RepositoryChecksEnabled bool `json:"repository_checks_enabled"`
+ RepositoryStorages []string `json:"repository_storages"`
+ RequireTwoFactorAuthentication bool `json:"require_two_factor_authentication"`
+ RestrictedVisibilityLevels []VisibilityValue `json:"restricted_visibility_levels"`
+ RsaKeyRestriction int `json:"rsa_key_restriction"`
+ SendUserConfirmationEmail bool `json:"send_user_confirmation_email"`
+ SentryDSN string `json:"sentry_dsn"`
+ SentryEnabled bool `json:"sentry_enabled"`
+ SessionExpireDelay int `json:"session_expire_delay"`
+ SharedRunnersEnabled bool `json:"shared_runners_enabled"`
+ SharedRunnersText string `json:"shared_runners_text"`
+ SidekiqThrottlingEnabled bool `json:"sidekiq_throttling_enabled"`
+ SidekiqThrottlingFactor float64 `json:"sidekiq_throttling_factor"`
+ SidekiqThrottlingQueues []string `json:"sidekiq_throttling_queues"`
+ SignInText string `json:"sign_in_text"`
+ SignupEnabled bool `json:"signup_enabled"`
+ TerminalMaxSessionTime int `json:"terminal_max_session_time"`
+ TwoFactorGracePeriod int `json:"two_factor_grace_period"`
+ UniqueIPsLimitEnabled bool `json:"unique_ips_limit_enabled"`
+ UniqueIPsLimitPerUser int `json:"unique_ips_limit_per_user"`
+ UniqueIPsLimitTimeWindow int `json:"unique_ips_limit_time_window"`
+ UsagePingEnabled bool `json:"usage_ping_enabled"`
+ UserDefaultExternal bool `json:"user_default_external"`
+ UserOauthApplications bool `json:"user_oauth_applications"`
+ VersionCheckEnabled bool `json:"version_check_enabled"`
+}
+
+func (s Settings) String() string {
+ return Stringify(s)
+}
+
+// GetSettings gets the current application settings.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/settings.html#get-current-application.settings
+func (s *SettingsService) GetSettings(options ...OptionFunc) (*Settings, *Response, error) {
+ req, err := s.client.NewRequest("GET", "application/settings", nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ as := new(Settings)
+ resp, err := s.client.Do(req, as)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return as, resp, err
+}
+
+// UpdateSettingsOptions represents the available UpdateSettings() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/settings.html#change-application.settings
+type UpdateSettingsOptions struct {
+ AdminNotificationEmail *string `url:"admin_notification_email,omitempty" json:"admin_notification_email,omitempty"`
+ AfterSignOutPath *string `url:"after_sign_out_path,omitempty" json:"after_sign_out_path,omitempty"`
+ AfterSignUpText *string `url:"after_sign_up_text,omitempty" json:"after_sign_up_text,omitempty"`
+ AkismetApiKey *string `url:"akismet_api_key,omitempty" json:"akismet_api_key,omitempty"`
+ AkismetEnabled *bool `url:"akismet_enabled,omitempty" json:"akismet_enabled,omitempty"`
+ CircuitbreakerAccessRetries *int `url:"circuitbreaker_access_retries,omitempty" json:"circuitbreaker_access_retries,omitempty"`
+ CircuitbreakerBackoffThreshold *int `url:"circuitbreaker_backoff_threshold,omitempty" json:"circuitbreaker_backoff_threshold,omitempty"`
+ CircuitbreakerFailureCountThreshold *int `url:"circuitbreaker_failure_count_threshold,omitempty" json:"circuitbreaker_failure_count_threshold,omitempty"`
+ CircuitbreakerFailureResetTime *int `url:"circuitbreaker_failure_reset_time,omitempty" json:"circuitbreaker_failure_reset_time,omitempty"`
+ CircuitbreakerFailureWaitTime *int `url:"circuitbreaker_failure_wait_time,omitempty" json:"circuitbreaker_failure_wait_time,omitempty"`
+ CircuitbreakerStorageTimeout *int `url:"circuitbreaker_storage_timeout,omitempty" json:"circuitbreaker_storage_timeout,omitempty"`
+ ClientsideSentryDSN *string `url:"clientside_sentry_dsn,omitempty" json:"clientside_sentry_dsn,omitempty"`
+ ClientsideSentryEnabled *bool `url:"clientside_sentry_enabled,omitempty" json:"clientside_sentry_enabled,omitempty"`
+ ContainerRegistryTokenExpireDelay *int `url:"container_registry_token_expire_delay,omitempty" json:"container_registry_token_expire_delay,omitempty"`
+ DefaultArtifactsExpireIn *string `url:"default_artifacts_expire_in,omitempty" json:"default_artifacts_expire_in,omitempty"`
+ DefaultBranchProtection *int `url:"default_branch_protection,omitempty" json:"default_branch_protection,omitempty"`
+ DefaultGroupVisibility *string `url:"default_group_visibility,omitempty" json:"default_group_visibility,omitempty"`
+ DefaultProjectVisibility *string `url:"default_project_visibility,omitempty" json:"default_project_visibility,omitempty"`
+ DefaultProjectsLimit *int `url:"default_projects_limit,omitempty" json:"default_projects_limit,omitempty"`
+ DefaultSnippetVisibility *string `url:"default_snippet_visibility,omitempty" json:"default_snippet_visibility,omitempty"`
+ DisabledOauthSignInSources []string `url:"disabled_oauth_sign_in_sources,omitempty" json:"disabled_oauth_sign_in_sources,omitempty"`
+ DomainBlacklistEnabled *bool `url:"domain_blacklist_enabled,omitempty" json:"domain_blacklist_enabled,omitempty"`
+ DomainBlacklist []string `url:"domain_blacklist,omitempty" json:"domain_blacklist,omitempty"`
+ DomainWhitelist []string `url:"domain_whitelist,omitempty" json:"domain_whitelist,omitempty"`
+ DSAKeyRestriction *int `url:"dsa_key_restriction,omitempty" json:"dsa_key_restriction,omitempty"`
+ ECDSAKeyRestriction *int `url:"ecdsa_key_restriction,omitempty" json:"ecdsa_key_restriction,omitempty"`
+ Ed25519KeyRestriction *int `url:"ed25519_key_restriction,omitempty" json:"ed25519_key_restriction,omitempty"`
+ EmailAuthorInBody *bool `url:"email_author_in_body,omitempty" json:"email_author_in_body,omitempty"`
+ EnabledGitAccessProtocol *string `url:"enabled_git_access_protocol,omitempty" json:"enabled_git_access_protocol,omitempty"`
+ GravatarEnabled *bool `url:"gravatar_enabled,omitempty" json:"gravatar_enabled,omitempty"`
+ HelpPageHideCommercialContent *bool `url:"help_page_hide_commercial_content,omitempty" json:"help_page_hide_commercial_content,omitempty"`
+ HelpPageSupportURL *string `url:"help_page_support_url,omitempty" json:"help_page_support_url,omitempty"`
+ HomePageURL *string `url:"home_page_url,omitempty" json:"home_page_url,omitempty"`
+ HousekeepingBitmapsEnabled *bool `url:"housekeeping_bitmaps_enabled,omitempty" json:"housekeeping_bitmaps_enabled,omitempty"`
+ HousekeepingEnabled *bool `url:"housekeeping_enabled,omitempty" json:"housekeeping_enabled,omitempty"`
+ HousekeepingFullRepackPeriod *int `url:"housekeeping_full_repack_period,omitempty" json:"housekeeping_full_repack_period,omitempty"`
+ HousekeepingGcPeriod *int `url:"housekeeping_gc_period,omitempty" json:"housekeeping_gc_period,omitempty"`
+ HousekeepingIncrementalRepackPeriod *int `url:"housekeeping_incremental_repack_period,omitempty" json:"housekeeping_incremental_repack_period,omitempty"`
+ HTMLEmailsEnabled *bool `url:"html_emails_enabled,omitempty" json:"html_emails_enabled,omitempty"`
+ ImportSources []string `url:"import_sources,omitempty" json:"import_sources,omitempty"`
+ KodingEnabled *bool `url:"koding_enabled,omitempty" json:"koding_enabled,omitempty"`
+ KodingURL *string `url:"koding_url,omitempty" json:"koding_url,omitempty"`
+ MaxArtifactsSize *int `url:"max_artifacts_size,omitempty" json:"max_artifacts_size,omitempty"`
+ MaxAttachmentSize *int `url:"max_attachment_size,omitempty" json:"max_attachment_size,omitempty"`
+ MaxPagesSize *int `url:"max_pages_size,omitempty" json:"max_pages_size,omitempty"`
+ MetricsEnabled *bool `url:"metrics_enabled,omitempty" json:"metrics_enabled,omitempty"`
+ MetricsHost *string `url:"metrics_host,omitempty" json:"metrics_host,omitempty"`
+ MetricsMethodCallThreshold *int `url:"metrics_method_call_threshold,omitempty" json:"metrics_method_call_threshold,omitempty"`
+ MetricsPacketSize *int `url:"metrics_packet_size,omitempty" json:"metrics_packet_size,omitempty"`
+ MetricsPoolSize *int `url:"metrics_pool_size,omitempty" json:"metrics_pool_size,omitempty"`
+ MetricsPort *int `url:"metrics_port,omitempty" json:"metrics_port,omitempty"`
+ MetricsSampleInterval *int `url:"metrics_sample_interval,omitempty" json:"metrics_sample_interval,omitempty"`
+ MetricsTimeout *int `url:"metrics_timeout,omitempty" json:"metrics_timeout,omitempty"`
+ PasswordAuthenticationEnabled *bool `url:"password_authentication_enabled,omitempty" json:"password_authentication_enabled,omitempty"`
+ PerformanceBarAllowedGroupId *string `url:"performance_bar_allowed_group_id,omitempty" json:"performance_bar_allowed_group_id,omitempty"`
+ PerformanceBarEnabled *bool `url:"performance_bar_enabled,omitempty" json:"performance_bar_enabled,omitempty"`
+ PlantumlEnabled *bool `url:"plantuml_enabled,omitempty" json:"plantuml_enabled,omitempty"`
+ PlantumlURL *string `url:"plantuml_url,omitempty" json:"plantuml_url,omitempty"`
+ PollingIntervalMultiplier *float64 `url:"polling_interval_multiplier,omitempty" json:"polling_interval_multiplier,omitempty"`
+ ProjectExportEnabled *bool `url:"project_export_enabled,omitempty" json:"project_export_enabled,omitempty"`
+ PrometheusMetricsEnabled *bool `url:"prometheus_metrics_enabled,omitempty" json:"prometheus_metrics_enabled,omitempty"`
+ RecaptchaEnabled *bool `url:"recaptcha_enabled,omitempty" json:"recaptcha_enabled,omitempty"`
+ RecaptchaPrivateKey *string `url:"recaptcha_private_key,omitempty" json:"recaptcha_private_key,omitempty"`
+ RecaptchaSiteKey *string `url:"recaptcha_site_key,omitempty" json:"recaptcha_site_key,omitempty"`
+ RepositoryChecksEnabled *bool `url:"repository_checks_enabled,omitempty" json:"repository_checks_enabled,omitempty"`
+ RepositoryStorages []string `url:"repository_storages,omitempty" json:"repository_storages,omitempty"`
+ RequireTwoFactorAuthentication *bool `url:"require_two_factor_authentication,omitempty" json:"require_two_factor_authentication,omitempty"`
+ RestrictedVisibilityLevels []VisibilityValue `url:"restricted_visibility_levels,omitempty" json:"restricted_visibility_levels,omitempty"`
+ RsaKeyRestriction *int `url:"rsa_key_restriction,omitempty" json:"rsa_key_restriction,omitempty"`
+ SendUserConfirmationEmail *bool `url:"send_user_confirmation_email,omitempty" json:"send_user_confirmation_email,omitempty"`
+ SentryDSN *string `url:"sentry_dsn,omitempty" json:"sentry_dsn,omitempty"`
+ SentryEnabled *bool `url:"sentry_enabled,omitempty" json:"sentry_enabled,omitempty"`
+ SessionExpireDelay *int `url:"session_expire_delay,omitempty" json:"session_expire_delay,omitempty"`
+ SharedRunnersEnabled *bool `url:"shared_runners_enabled,omitempty" json:"shared_runners_enabled,omitempty"`
+ SharedRunnersText *string `url:"shared_runners_text,omitempty" json:"shared_runners_text,omitempty"`
+ SidekiqThrottlingEnabled *bool `url:"sidekiq_throttling_enabled,omitempty" json:"sidekiq_throttling_enabled,omitempty"`
+ SidekiqThrottlingFactor *float64 `url:"sidekiq_throttling_factor,omitempty" json:"sidekiq_throttling_factor,omitempty"`
+ SidekiqThrottlingQueues []string `url:"sidekiq_throttling_queues,omitempty" json:"sidekiq_throttling_queues,omitempty"`
+ SignInText *string `url:"sign_in_text,omitempty" json:"sign_in_text,omitempty"`
+ SignupEnabled *bool `url:"signup_enabled,omitempty" json:"signup_enabled,omitempty"`
+ TerminalMaxSessionTime *int `url:"terminal_max_session_time,omitempty" json:"terminal_max_session_time,omitempty"`
+ TwoFactorGracePeriod *int `url:"two_factor_grace_period,omitempty" json:"two_factor_grace_period,omitempty"`
+ UniqueIPsLimitEnabled *bool `url:"unique_ips_limit_enabled,omitempty" json:"unique_ips_limit_enabled,omitempty"`
+ UniqueIPsLimitPerUser *int `url:"unique_ips_limit_per_user,omitempty" json:"unique_ips_limit_per_user,omitempty"`
+ UniqueIPsLimitTimeWindow *int `url:"unique_ips_limit_time_window,omitempty" json:"unique_ips_limit_time_window,omitempty"`
+ UsagePingEnabled *bool `url:"usage_ping_enabled,omitempty" json:"usage_ping_enabled,omitempty"`
+ UserDefaultExternal *bool `url:"user_default_external,omitempty" json:"user_default_external,omitempty"`
+ UserOauthApplications *bool `url:"user_oauth_applications,omitempty" json:"user_oauth_applications,omitempty"`
+ VersionCheckEnabled *bool `url:"version_check_enabled,omitempty" json:"version_check_enabled,omitempty"`
+}
+
+// UpdateSettings updates the application settings.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/settings.html#change-application.settings
+func (s *SettingsService) UpdateSettings(opt *UpdateSettingsOptions, options ...OptionFunc) (*Settings, *Response, error) {
+ req, err := s.client.NewRequest("PUT", "application/settings", opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ as := new(Settings)
+ resp, err := s.client.Do(req, as)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return as, resp, err
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/strings.go b/vendor/github.com/lkysow/go-gitlab/strings.go
new file mode 100644
index 0000000000..aeefb6b8dc
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/strings.go
@@ -0,0 +1,94 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// 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.
+//
+
+package gitlab
+
+import (
+ "bytes"
+ "fmt"
+
+ "reflect"
+)
+
+// Stringify attempts to create a reasonable string representation of types in
+// the GitHub library. It does things like resolve pointers to their values
+// and omits struct fields with nil values.
+func Stringify(message interface{}) string {
+ var buf bytes.Buffer
+ v := reflect.ValueOf(message)
+ stringifyValue(&buf, v)
+ return buf.String()
+}
+
+// stringifyValue was heavily inspired by the goprotobuf library.
+func stringifyValue(buf *bytes.Buffer, val reflect.Value) {
+ if val.Kind() == reflect.Ptr && val.IsNil() {
+ buf.WriteString("")
+ return
+ }
+
+ v := reflect.Indirect(val)
+
+ switch v.Kind() {
+ case reflect.String:
+ fmt.Fprintf(buf, `"%s"`, v)
+ case reflect.Slice:
+ buf.WriteByte('[')
+ for i := 0; i < v.Len(); i++ {
+ if i > 0 {
+ buf.WriteByte(' ')
+ }
+
+ stringifyValue(buf, v.Index(i))
+ }
+
+ buf.WriteByte(']')
+ return
+ case reflect.Struct:
+ if v.Type().Name() != "" {
+ buf.WriteString(v.Type().String())
+ }
+
+ buf.WriteByte('{')
+
+ var sep bool
+ for i := 0; i < v.NumField(); i++ {
+ fv := v.Field(i)
+ if fv.Kind() == reflect.Ptr && fv.IsNil() {
+ continue
+ }
+ if fv.Kind() == reflect.Slice && fv.IsNil() {
+ continue
+ }
+
+ if sep {
+ buf.WriteString(", ")
+ } else {
+ sep = true
+ }
+
+ buf.WriteString(v.Type().Field(i).Name)
+ buf.WriteByte(':')
+ stringifyValue(buf, fv)
+ }
+
+ buf.WriteByte('}')
+ default:
+ if v.CanInterface() {
+ fmt.Fprint(buf, v.Interface())
+ }
+ }
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/system_hooks.go b/vendor/github.com/lkysow/go-gitlab/system_hooks.go
new file mode 100644
index 0000000000..77e8a8b851
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/system_hooks.go
@@ -0,0 +1,143 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// 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.
+//
+
+package gitlab
+
+import (
+ "fmt"
+ "time"
+)
+
+// SystemHooksService handles communication with the system hooks related
+// methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/system_hooks.html
+type SystemHooksService struct {
+ client *Client
+}
+
+// Hook represents a GitLap system hook.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/system_hooks.html
+type Hook struct {
+ ID int `json:"id"`
+ URL string `json:"url"`
+ CreatedAt *time.Time `json:"created_at"`
+}
+
+func (h Hook) String() string {
+ return Stringify(h)
+}
+
+// ListHooks gets a list of system hooks.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/system_hooks.html#list-system-hooks
+func (s *SystemHooksService) ListHooks(options ...OptionFunc) ([]*Hook, *Response, error) {
+ req, err := s.client.NewRequest("GET", "hooks", nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var h []*Hook
+ resp, err := s.client.Do(req, &h)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return h, resp, err
+}
+
+// AddHookOptions represents the available AddHook() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/system_hooks.html#add-new-system-hook-hook
+type AddHookOptions struct {
+ URL *string `url:"url,omitempty" json:"url,omitempty"`
+}
+
+// AddHook adds a new system hook hook.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/system_hooks.html#add-new-system-hook-hook
+func (s *SystemHooksService) AddHook(opt *AddHookOptions, options ...OptionFunc) (*Hook, *Response, error) {
+ req, err := s.client.NewRequest("POST", "hooks", opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ h := new(Hook)
+ resp, err := s.client.Do(req, h)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return h, resp, err
+}
+
+// HookEvent represents an event triggert by a GitLab system hook.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/system_hooks.html
+type HookEvent struct {
+ EventName string `json:"event_name"`
+ Name string `json:"name"`
+ Path string `json:"path"`
+ ProjectID int `json:"project_id"`
+ OwnerName string `json:"owner_name"`
+ OwnerEmail string `json:"owner_email"`
+}
+
+func (h HookEvent) String() string {
+ return Stringify(h)
+}
+
+// TestHook tests a system hook.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/system_hooks.html#test-system-hook
+func (s *SystemHooksService) TestHook(hook int, options ...OptionFunc) (*HookEvent, *Response, error) {
+ u := fmt.Sprintf("hooks/%d", hook)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ h := new(HookEvent)
+ resp, err := s.client.Do(req, h)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return h, resp, err
+}
+
+// DeleteHook deletes a system hook. This is an idempotent API function and
+// returns 200 OK even if the hook is not available. If the hook is deleted it
+// is also returned as JSON.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/system_hooks.html#delete-system-hook
+func (s *SystemHooksService) DeleteHook(hook int, options ...OptionFunc) (*Response, error) {
+ u := fmt.Sprintf("hooks/%d", hook)
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/tags.go b/vendor/github.com/lkysow/go-gitlab/tags.go
new file mode 100644
index 0000000000..f24822a608
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/tags.go
@@ -0,0 +1,154 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// 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.
+//
+
+package gitlab
+
+import (
+ "fmt"
+ "net/url"
+)
+
+// TagsService handles communication with the tags related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/tags.html
+type TagsService struct {
+ client *Client
+}
+
+// Tag represents a GitLab tag.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/tags.html
+type Tag struct {
+ Commit *Commit `json:"commit"`
+ Release struct {
+ TagName string `json:"tag_name"`
+ Description string `json:"description"`
+ } `json:"release"`
+ Name string `json:"name"`
+ Message string `json:"message"`
+}
+
+func (t Tag) String() string {
+ return Stringify(t)
+}
+
+// ListTags gets a list of tags from a project, sorted by name in reverse
+// alphabetical order.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/tags.html#list-project-repository-tags
+func (s *TagsService) ListTags(pid interface{}, options ...OptionFunc) ([]*Tag, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/tags", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var t []*Tag
+ resp, err := s.client.Do(req, &t)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return t, resp, err
+}
+
+// GetTag a specific repository tag determined by its name. It returns 200 together
+// with the tag information if the tag exists. It returns 404 if the tag does not exist.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/tags.html#get-a-single-repository-tag
+func (s *TagsService) GetTag(pid interface{}, tag string, options ...OptionFunc) (*Tag, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/tags/%s", url.QueryEscape(project), tag)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var t *Tag
+ resp, err := s.client.Do(req, &t)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return t, resp, err
+}
+
+// CreateTagOptions represents the available CreateTag() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/tags.html#create-a-new-tag
+type CreateTagOptions struct {
+ TagName *string `url:"tag_name,omitempty" json:"tag_name,omitempty"`
+ Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
+ Message *string `url:"message,omitempty" json:"message,omitempty"`
+ ReleaseDescription *string `url:"release_description:omitempty" json:"release_description,omitempty"`
+}
+
+// CreateTag creates a new tag in the repository that points to the supplied ref.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/tags.html#create-a-new-tag
+func (s *TagsService) CreateTag(pid interface{}, opt *CreateTagOptions, options ...OptionFunc) (*Tag, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/tags", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ t := new(Tag)
+ resp, err := s.client.Do(req, t)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return t, resp, err
+}
+
+// DeleteTag deletes a tag of a repository with given name.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/tags.html#delete-a-tag
+func (s *TagsService) DeleteTag(pid interface{}, tag string, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/repository/tags/%s", url.QueryEscape(project), tag)
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/time_stats.go b/vendor/github.com/lkysow/go-gitlab/time_stats.go
new file mode 100644
index 0000000000..5e3bff9041
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/time_stats.go
@@ -0,0 +1,163 @@
+package gitlab
+
+import (
+ "fmt"
+ "net/url"
+)
+
+// timeStatsService handles communication with the time tracking related
+// methods of the GitLab API.
+//
+// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html
+type timeStatsService struct {
+ client *Client
+}
+
+// TimeStats represents the time estimates and time spent for an issue.
+//
+// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html
+type TimeStats struct {
+ HumanTimeEstimate string `json:"human_time_estimate"`
+ HumanTotalTimeSpent string `json:"human_total_time_spent"`
+ TimeEstimate int `json:"time_estimate"`
+ TotalTimeSpent int `json:"total_time_spent"`
+}
+
+func (t TimeStats) String() string {
+ return Stringify(t)
+}
+
+// SetTimeEstimateOptions represents the available SetTimeEstimate()
+// options.
+//
+// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html
+type SetTimeEstimateOptions struct {
+ Duration *string `url:"duration,omitempty" json:"duration,omitempty"`
+}
+
+// setTimeEstimate sets the time estimate for a single project issue.
+//
+// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html
+func (s *timeStatsService) setTimeEstimate(pid interface{}, entity string, issue int, opt *SetTimeEstimateOptions, options ...OptionFunc) (*TimeStats, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/%s/%d/time_estimate", url.QueryEscape(project), entity, issue)
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ t := new(TimeStats)
+ resp, err := s.client.Do(req, t)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return t, resp, err
+}
+
+// resetTimeEstimate resets the time estimate for a single project issue.
+//
+// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html
+func (s *timeStatsService) resetTimeEstimate(pid interface{}, entity string, issue int, options ...OptionFunc) (*TimeStats, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/%s/%d/reset_time_estimate", url.QueryEscape(project), entity, issue)
+
+ req, err := s.client.NewRequest("POST", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ t := new(TimeStats)
+ resp, err := s.client.Do(req, t)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return t, resp, err
+}
+
+// AddSpentTimeOptions represents the available AddSpentTime() options.
+//
+// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html
+type AddSpentTimeOptions struct {
+ Duration *string `url:"duration,omitempty" json:"duration,omitempty"`
+}
+
+// addSpentTime adds spent time for a single project issue.
+//
+// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html
+func (s *timeStatsService) addSpentTime(pid interface{}, entity string, issue int, opt *AddSpentTimeOptions, options ...OptionFunc) (*TimeStats, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/%s/%d/add_spent_time", url.QueryEscape(project), entity, issue)
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ t := new(TimeStats)
+ resp, err := s.client.Do(req, t)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return t, resp, err
+}
+
+// resetSpentTime resets the spent time for a single project issue.
+//
+// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html
+func (s *timeStatsService) resetSpentTime(pid interface{}, entity string, issue int, options ...OptionFunc) (*TimeStats, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/%s/%d/reset_spent_time", url.QueryEscape(project), entity, issue)
+
+ req, err := s.client.NewRequest("POST", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ t := new(TimeStats)
+ resp, err := s.client.Do(req, t)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return t, resp, err
+}
+
+// getTimeSpent gets the spent time for a single project issue.
+//
+// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html
+func (s *timeStatsService) getTimeSpent(pid interface{}, entity string, issue int, options ...OptionFunc) (*TimeStats, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/%s/%d/time_stats", url.QueryEscape(project), entity, issue)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ t := new(TimeStats)
+ resp, err := s.client.Do(req, t)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return t, resp, err
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/todos.go b/vendor/github.com/lkysow/go-gitlab/todos.go
new file mode 100644
index 0000000000..db58eddb7f
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/todos.go
@@ -0,0 +1,175 @@
+package gitlab
+
+import "time"
+import "fmt"
+
+// TodosService handles communication with the todos related methods of
+// the Gitlab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/todos.html
+type TodosService struct {
+ client *Client
+}
+
+// TodoAction represents the available actions that can be performed on a todo.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/todos.html
+type TodoAction string
+
+// The available todo actions.
+const (
+ TodoAssigned TodoAction = "assigned"
+ TodoMentioned TodoAction = "mentioned"
+ TodoBuildFailed TodoAction = "build_failed"
+ TodoMarked TodoAction = "marked"
+ TodoApprovalRequired TodoAction = "approval_required"
+ TodoDirectlyAddressed TodoAction = "directly_addressed"
+)
+
+// TodoTarget represents a todo target of type Issue or MergeRequest
+type TodoTarget struct {
+ // TODO: replace both Assignee and Author structs with v4 User struct
+ Assignee struct {
+ Name string `json:"name"`
+ Username string `json:"username"`
+ ID int `json:"id"`
+ State string `json:"state"`
+ AvatarURL string `json:"avatar_url"`
+ WebURL string `json:"web_url"`
+ } `json:"assignee"`
+ Author struct {
+ Name string `json:"name"`
+ Username string `json:"username"`
+ ID int `json:"id"`
+ State string `json:"state"`
+ AvatarURL string `json:"avatar_url"`
+ WebURL string `json:"web_url"`
+ } `json:"author"`
+ CreatedAt *time.Time `json:"created_at"`
+ Description string `json:"description"`
+ Downvotes int `json:"downvotes"`
+ ID int `json:"id"`
+ IID int `json:"iid"`
+ Labels []string `json:"labels"`
+ Milestone Milestone `json:"milestone"`
+ ProjectID int `json:"project_id"`
+ State string `json:"state"`
+ Subscribed bool `json:"subscribed"`
+ Title string `json:"title"`
+ UpdatedAt *time.Time `json:"updated_at"`
+ Upvotes int `json:"upvotes"`
+ UserNotesCount int `json:"user_notes_count"`
+ WebURL string `json:"web_url"`
+
+ // Only available for type Issue
+ Confidential bool `json:"confidential"`
+ DueDate string `json:"due_date"`
+ Weight int `json:"weight"`
+
+ // Only available for type MergeRequest
+ ApprovalsBeforeMerge bool `json:"approvals_before_merge"`
+ ForceRemoveSourceBranch bool `json:"force_remove_source_branch"`
+ MergeCommitSha string `json:"merge_commit_sha"`
+ MergeWhenPipelineSucceeds bool `json:"merge_when_pipeline_succeeds"`
+ MergeStatus string `json:"merge_status"`
+ Sha string `json:"sha"`
+ ShouldRemoveSourceBranch bool `json:"should_remove_source_branch"`
+ SourceBranch string `json:"source_branch"`
+ SourceProjectID int `json:"source_project_id"`
+ Squash bool `json:"squash"`
+ TargetBranch string `json:"target_branch"`
+ TargetProjectID int `json:"target_project_id"`
+ WorkInProgress bool `json:"work_in_progress"`
+}
+
+// Todo represents a GitLab todo.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/todos.html
+type Todo struct {
+ ID int `json:"id"`
+ Project struct {
+ ID int `json:"id"`
+ HTTPURLToRepo string `json:"http_url_to_repo"`
+ WebURL string `json:"web_url"`
+ Name string `json:"name"`
+ NameWithNamespace string `json:"name_with_namespace"`
+ Path string `json:"path"`
+ PathWithNamespace string `json:"path_with_namespace"`
+ } `json:"project"`
+ Author struct {
+ ID int `json:"id"`
+ Name string `json:"name"`
+ Username string `json:"username"`
+ State string `json:"state"`
+ AvatarURL string `json:"avatar_url"`
+ WebURL string `json:"web_url"`
+ } `json:"author"`
+ ActionName TodoAction `json:"action_name"`
+ TargetType string `json:"target_type"`
+ Target TodoTarget `json:"target"`
+ TargetURL string `json:"target_url"`
+ Body string `json:"body"`
+ State string `json:"state"`
+ CreatedAt *time.Time `json:"created_at"`
+}
+
+func (t Todo) String() string {
+ return Stringify(t)
+}
+
+// ListTodosOptions represents the available ListTodos() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/todos.html#get-a-list-of-todos
+type ListTodosOptions struct {
+ Action *TodoAction `url:"action,omitempty" json:"action,omitempty"`
+ AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"`
+ ProjectID *int `url:"project_id,omitempty" json:"project_id,omitempty"`
+ State *string `url:"state,omitempty" json:"state,omitempty"`
+ Type *string `url:"type,omitempty" json:"type,omitempty"`
+}
+
+// ListTodos lists all todos created by authenticated user.
+// When no filter is applied, it returns all pending todos for the current user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/todos.html#get-a-list-of-todos
+func (s *TodosService) ListTodos(opt *ListTodosOptions, options ...OptionFunc) ([]*Todo, *Response, error) {
+ req, err := s.client.NewRequest("GET", "todos", opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var t []*Todo
+ resp, err := s.client.Do(req, &t)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return t, resp, err
+}
+
+// MarkTodoAsDone marks a single pending todo given by its ID for the current user as done.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/todos.html#mark-a-todo-as-done
+func (s *TodosService) MarkTodoAsDone(id int, options ...OptionFunc) (*Response, error) {
+ u := fmt.Sprintf("todos/%d/mark_as_done", id)
+
+ req, err := s.client.NewRequest("POST", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// MarkAllTodosAsDone marks all pending todos for the current user as done.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/todos.html#mark-all-todos-as-done
+func (s *TodosService) MarkAllTodosAsDone(options ...OptionFunc) (*Response, error) {
+ req, err := s.client.NewRequest("POST", "todos/mark_as_done", nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/todos_test.go b/vendor/github.com/lkysow/go-gitlab/todos_test.go
new file mode 100644
index 0000000000..002a4053ec
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/todos_test.go
@@ -0,0 +1,62 @@
+package gitlab
+
+import (
+ "fmt"
+ "net/http"
+ "reflect"
+ "testing"
+)
+
+func TestListTodos(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/todos", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "GET")
+ fmt.Fprint(w, `[{"id":1,"state": "pending"},{"id":2,"state":"pending"}]`)
+ })
+
+ opts := &ListTodosOptions{}
+ todos, _, err := client.Todos.ListTodos(opts)
+
+ if err != nil {
+ t.Errorf("Todos.ListTodos returned error: %v", err)
+ }
+
+ want := []*Todo{{ID: 1, State: "pending"}, {ID: 2, State: "pending"}}
+ if !reflect.DeepEqual(want, todos) {
+ t.Errorf("Todos.ListTodos returned %+v, want %+v", todos, want)
+ }
+
+}
+
+func TestMarkAllTodosAsDone(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/todos/mark_as_done", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "POST")
+ w.WriteHeader(http.StatusNoContent)
+ })
+
+ _, err := client.Todos.MarkAllTodosAsDone()
+
+ if err != nil {
+ t.Fatalf("Todos.MarkTodosRead returns an error: %v", err)
+ }
+}
+
+func TestMarkTodoAsDone(t *testing.T) {
+ mux, server, client := setup()
+ defer teardown(server)
+
+ mux.HandleFunc("/todos/1/mark_as_done", func(w http.ResponseWriter, r *http.Request) {
+ testMethod(t, r, "POST")
+ })
+
+ _, err := client.Todos.MarkTodoAsDone(1)
+
+ if err != nil {
+ t.Fatalf("Todos.MarkTodoRead returns an error: %v", err)
+ }
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/users.go b/vendor/github.com/lkysow/go-gitlab/users.go
new file mode 100644
index 0000000000..7fe902dbac
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/users.go
@@ -0,0 +1,698 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// 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.
+//
+
+package gitlab
+
+import (
+ "errors"
+ "fmt"
+ "time"
+)
+
+// UsersService handles communication with the user related methods of
+// the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html
+type UsersService struct {
+ client *Client
+}
+
+// User represents a GitLab user.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html
+type User struct {
+ ID int `json:"id"`
+ Username string `json:"username"`
+ Email string `json:"email"`
+ Name string `json:"name"`
+ State string `json:"state"`
+ CreatedAt *time.Time `json:"created_at"`
+ Bio string `json:"bio"`
+ Skype string `json:"skype"`
+ Linkedin string `json:"linkedin"`
+ Twitter string `json:"twitter"`
+ WebsiteURL string `json:"website_url"`
+ ExternUID string `json:"extern_uid"`
+ Provider string `json:"provider"`
+ ThemeID int `json:"theme_id"`
+ ColorSchemeID int `json:"color_scheme_id"`
+ IsAdmin bool `json:"is_admin"`
+ AvatarURL string `json:"avatar_url"`
+ CanCreateGroup bool `json:"can_create_group"`
+ CanCreateProject bool `json:"can_create_project"`
+ ProjectsLimit int `json:"projects_limit"`
+ CurrentSignInAt *time.Time `json:"current_sign_in_at"`
+ LastSignInAt *time.Time `json:"last_sign_in_at"`
+ TwoFactorEnabled bool `json:"two_factor_enabled"`
+ Identities []*UserIdentity `json:"identities"`
+}
+
+// UserIdentity represents a user identity
+type UserIdentity struct {
+ Provider string `json:"provider"`
+ ExternUID string `json:"extern_uid"`
+}
+
+// ListUsersOptions represents the available ListUsers() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#list-users
+type ListUsersOptions struct {
+ ListOptions
+ Active *bool `url:"active,omitempty" json:"active,omitempty"`
+ Blocked *bool `url:"blocked,omitempty" json:"blocked,omitempty"`
+ Search *string `url:"search,omitempty" json:"search,omitempty"`
+ Username *string `url:"username,omitempty" json:"username,omitempty"`
+}
+
+// ListUsers gets a list of users.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#list-users
+func (s *UsersService) ListUsers(opt *ListUsersOptions, options ...OptionFunc) ([]*User, *Response, error) {
+ req, err := s.client.NewRequest("GET", "users", opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var usr []*User
+ resp, err := s.client.Do(req, &usr)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return usr, resp, err
+}
+
+// GetUser gets a single user.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#single-user
+func (s *UsersService) GetUser(user int, options ...OptionFunc) (*User, *Response, error) {
+ u := fmt.Sprintf("users/%d", user)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ usr := new(User)
+ resp, err := s.client.Do(req, usr)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return usr, resp, err
+}
+
+// CreateUserOptions represents the available CreateUser() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#user-creation
+type CreateUserOptions struct {
+ Email *string `url:"email,omitempty" json:"email,omitempty"`
+ Password *string `url:"password,omitempty" json:"password,omitempty"`
+ Username *string `url:"username,omitempty" json:"username,omitempty"`
+ Name *string `url:"name,omitempty" json:"name,omitempty"`
+ Skype *string `url:"skype,omitempty" json:"skype,omitempty"`
+ Linkedin *string `url:"linkedin,omitempty" json:"linkedin,omitempty"`
+ Twitter *string `url:"twitter,omitempty" json:"twitter,omitempty"`
+ WebsiteURL *string `url:"website_url,omitempty" json:"website_url,omitempty"`
+ ProjectsLimit *int `url:"projects_limit,omitempty" json:"projects_limit,omitempty"`
+ ExternUID *string `url:"extern_uid,omitempty" json:"extern_uid,omitempty"`
+ Provider *string `url:"provider,omitempty" json:"provider,omitempty"`
+ Bio *string `url:"bio,omitempty" json:"bio,omitempty"`
+ Admin *bool `url:"admin,omitempty" json:"admin,omitempty"`
+ CanCreateGroup *bool `url:"can_create_group,omitempty" json:"can_create_group,omitempty"`
+ SkipConfirmation *bool `url:"skip_confirmation,omitempty" json:"skip_confirmation,omitempty"`
+}
+
+// CreateUser creates a new user. Note only administrators can create new users.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#user-creation
+func (s *UsersService) CreateUser(opt *CreateUserOptions, options ...OptionFunc) (*User, *Response, error) {
+ req, err := s.client.NewRequest("POST", "users", opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ usr := new(User)
+ resp, err := s.client.Do(req, usr)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return usr, resp, err
+}
+
+// ModifyUserOptions represents the available ModifyUser() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#user-modification
+type ModifyUserOptions struct {
+ Email *string `url:"email,omitempty" json:"email,omitempty"`
+ Password *string `url:"password,omitempty" json:"password,omitempty"`
+ Username *string `url:"username,omitempty" json:"username,omitempty"`
+ Name *string `url:"name,omitempty" json:"name,omitempty"`
+ Skype *string `url:"skype,omitempty" json:"skype,omitempty"`
+ Linkedin *string `url:"linkedin,omitempty" json:"linkedin,omitempty"`
+ Twitter *string `url:"twitter,omitempty" json:"twitter,omitempty"`
+ WebsiteURL *string `url:"website_url,omitempty" json:"website_url,omitempty"`
+ ProjectsLimit *int `url:"projects_limit,omitempty" json:"projects_limit,omitempty"`
+ ExternUID *string `url:"extern_uid,omitempty" json:"extern_uid,omitempty"`
+ Provider *string `url:"provider,omitempty" json:"provider,omitempty"`
+ Bio *string `url:"bio,omitempty" json:"bio,omitempty"`
+ Admin *bool `url:"admin,omitempty" json:"admin,omitempty"`
+ CanCreateGroup *bool `url:"can_create_group,omitempty" json:"can_create_group,omitempty"`
+}
+
+// ModifyUser modifies an existing user. Only administrators can change attributes
+// of a user.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#user-modification
+func (s *UsersService) ModifyUser(user int, opt *ModifyUserOptions, options ...OptionFunc) (*User, *Response, error) {
+ u := fmt.Sprintf("users/%d", user)
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ usr := new(User)
+ resp, err := s.client.Do(req, usr)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return usr, resp, err
+}
+
+// DeleteUser deletes a user. Available only for administrators. This is an
+// idempotent function, calling this function for a non-existent user id still
+// returns a status code 200 OK. The JSON response differs if the user was
+// actually deleted or not. In the former the user is returned and in the
+// latter not.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#user-deletion
+func (s *UsersService) DeleteUser(user int, options ...OptionFunc) (*Response, error) {
+ u := fmt.Sprintf("users/%d", user)
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// CurrentUser gets currently authenticated user.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#current-user
+func (s *UsersService) CurrentUser(options ...OptionFunc) (*User, *Response, error) {
+ req, err := s.client.NewRequest("GET", "user", nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ usr := new(User)
+ resp, err := s.client.Do(req, usr)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return usr, resp, err
+}
+
+// SSHKey represents a SSH key.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#list-ssh-keys
+type SSHKey struct {
+ ID int `json:"id"`
+ Title string `json:"title"`
+ Key string `json:"key"`
+ CreatedAt *time.Time `json:"created_at"`
+}
+
+// ListSSHKeys gets a list of currently authenticated user's SSH keys.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#list-ssh-keys
+func (s *UsersService) ListSSHKeys(options ...OptionFunc) ([]*SSHKey, *Response, error) {
+ req, err := s.client.NewRequest("GET", "user/keys", nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var k []*SSHKey
+ resp, err := s.client.Do(req, &k)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return k, resp, err
+}
+
+// ListSSHKeysForUser gets a list of a specified user's SSH keys. Available
+// only for admin
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#list-ssh-keys-for-user
+func (s *UsersService) ListSSHKeysForUser(user int, options ...OptionFunc) ([]*SSHKey, *Response, error) {
+ u := fmt.Sprintf("users/%d/keys", user)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var k []*SSHKey
+ resp, err := s.client.Do(req, &k)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return k, resp, err
+}
+
+// GetSSHKey gets a single key.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#single-ssh-key
+func (s *UsersService) GetSSHKey(key int, options ...OptionFunc) (*SSHKey, *Response, error) {
+ u := fmt.Sprintf("user/keys/%d", key)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ k := new(SSHKey)
+ resp, err := s.client.Do(req, k)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return k, resp, err
+}
+
+// AddSSHKeyOptions represents the available AddSSHKey() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#add-ssh-key
+type AddSSHKeyOptions struct {
+ Title *string `url:"title,omitempty" json:"title,omitempty"`
+ Key *string `url:"key,omitempty" json:"key,omitempty"`
+}
+
+// AddSSHKey creates a new key owned by the currently authenticated user.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#add-ssh-key
+func (s *UsersService) AddSSHKey(opt *AddSSHKeyOptions, options ...OptionFunc) (*SSHKey, *Response, error) {
+ req, err := s.client.NewRequest("POST", "user/keys", opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ k := new(SSHKey)
+ resp, err := s.client.Do(req, k)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return k, resp, err
+}
+
+// AddSSHKeyForUser creates new key owned by specified user. Available only for
+// admin.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#add-ssh-key-for-user
+func (s *UsersService) AddSSHKeyForUser(user int, opt *AddSSHKeyOptions, options ...OptionFunc) (*SSHKey, *Response, error) {
+ u := fmt.Sprintf("users/%d/keys", user)
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ k := new(SSHKey)
+ resp, err := s.client.Do(req, k)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return k, resp, err
+}
+
+// DeleteSSHKey deletes key owned by currently authenticated user. This is an
+// idempotent function and calling it on a key that is already deleted or not
+// available results in 200 OK.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#delete-ssh-key-for-current-owner
+func (s *UsersService) DeleteSSHKey(key int, options ...OptionFunc) (*Response, error) {
+ u := fmt.Sprintf("user/keys/%d", key)
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// DeleteSSHKeyForUser deletes key owned by a specified user. Available only
+// for admin.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#delete-ssh-key-for-given-user
+func (s *UsersService) DeleteSSHKeyForUser(user, key int, options ...OptionFunc) (*Response, error) {
+ u := fmt.Sprintf("users/%d/keys/%d", user, key)
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// BlockUser blocks the specified user. Available only for admin.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#block-user
+func (s *UsersService) BlockUser(user int, options ...OptionFunc) error {
+ u := fmt.Sprintf("users/%d/block", user)
+
+ req, err := s.client.NewRequest("POST", u, nil, options)
+ if err != nil {
+ return err
+ }
+
+ resp, err := s.client.Do(req, nil)
+ if err != nil {
+ return err
+ }
+
+ switch resp.StatusCode {
+ case 200:
+ return nil
+ case 403:
+ return errors.New("Cannot block a user that is already blocked by LDAP synchronization")
+ case 404:
+ return errors.New("User does not exist")
+ default:
+ return fmt.Errorf("Received unexpected result code: %d", resp.StatusCode)
+ }
+}
+
+// UnblockUser unblocks the specified user. Available only for admin.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#unblock-user
+func (s *UsersService) UnblockUser(user int, options ...OptionFunc) error {
+ u := fmt.Sprintf("users/%d/unblock", user)
+
+ req, err := s.client.NewRequest("POST", u, nil, options)
+ if err != nil {
+ return err
+ }
+
+ resp, err := s.client.Do(req, nil)
+ if err != nil {
+ return err
+ }
+
+ switch resp.StatusCode {
+ case 200:
+ return nil
+ case 403:
+ return errors.New("Cannot unblock a user that is blocked by LDAP synchronization")
+ case 404:
+ return errors.New("User does not exist")
+ default:
+ return fmt.Errorf("Received unexpected result code: %d", resp.StatusCode)
+ }
+}
+
+// Email represents an Email.
+//
+// GitLab API docs: https://doc.gitlab.com/ce/api/users.html#list-emails
+type Email struct {
+ ID int `json:"id"`
+ Email string `json:"email"`
+}
+
+// ListEmails gets a list of currently authenticated user's Emails.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#list-emails
+func (s *UsersService) ListEmails(options ...OptionFunc) ([]*Email, *Response, error) {
+ req, err := s.client.NewRequest("GET", "user/emails", nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var e []*Email
+ resp, err := s.client.Do(req, &e)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return e, resp, err
+}
+
+// ListEmailsForUser gets a list of a specified user's Emails. Available
+// only for admin
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#list-emails-for-user
+func (s *UsersService) ListEmailsForUser(user int, options ...OptionFunc) ([]*Email, *Response, error) {
+ u := fmt.Sprintf("users/%d/emails", user)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var e []*Email
+ resp, err := s.client.Do(req, &e)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return e, resp, err
+}
+
+// GetEmail gets a single email.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#single-email
+func (s *UsersService) GetEmail(email int, options ...OptionFunc) (*Email, *Response, error) {
+ u := fmt.Sprintf("user/emails/%d", email)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ e := new(Email)
+ resp, err := s.client.Do(req, e)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return e, resp, err
+}
+
+// AddEmailOptions represents the available AddEmail() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#add-email
+type AddEmailOptions struct {
+ Email *string `url:"email,omitempty" json:"email,omitempty"`
+}
+
+// AddEmail creates a new email owned by the currently authenticated user.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#add-email
+func (s *UsersService) AddEmail(opt *AddEmailOptions, options ...OptionFunc) (*Email, *Response, error) {
+ req, err := s.client.NewRequest("POST", "user/emails", opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ e := new(Email)
+ resp, err := s.client.Do(req, e)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return e, resp, err
+}
+
+// AddEmailForUser creates new email owned by specified user. Available only for
+// admin.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#add-email-for-user
+func (s *UsersService) AddEmailForUser(user int, opt *AddEmailOptions, options ...OptionFunc) (*Email, *Response, error) {
+ u := fmt.Sprintf("users/%d/emails", user)
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ e := new(Email)
+ resp, err := s.client.Do(req, e)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return e, resp, err
+}
+
+// DeleteEmail deletes email owned by currently authenticated user. This is an
+// idempotent function and calling it on a key that is already deleted or not
+// available results in 200 OK.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#delete-email-for-current-owner
+func (s *UsersService) DeleteEmail(email int, options ...OptionFunc) (*Response, error) {
+ u := fmt.Sprintf("user/emails/%d", email)
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// DeleteEmailForUser deletes email owned by a specified user. Available only
+// for admin.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#delete-email-for-given-user
+func (s *UsersService) DeleteEmailForUser(user, email int, options ...OptionFunc) (*Response, error) {
+ u := fmt.Sprintf("users/%d/emails/%d", user, email)
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
+
+// ImpersonationToken represents an impersonation token.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#get-all-impersonation-tokens-of-a-user
+type ImpersonationToken struct {
+ ID int `json:"id"`
+ Name string `json:"name"`
+ Active bool `json:"active"`
+ Token string `json:"token"`
+ Scopes []string `json:"scopes"`
+ Revoked bool `json:"revoked"`
+ CreatedAt *time.Time `json:"created_at"`
+ ExpiresAt *time.Time `json:"expires_at"`
+}
+
+// GetAllImpersonationTokensOptions represents the available
+// GetAllImpersonationTokens() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#get-all-impersonation-tokens-of-a-user
+type GetAllImpersonationTokensOptions struct {
+ State *string `url:"state,omitempty" json:"state,omitempty"`
+}
+
+// GetAllImpersonationTokens retrieves all impersonation tokens of a user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#get-all-impersonation-tokens-of-a-user
+func (s *UsersService) GetAllImpersonationTokens(user int, opt *GetAllImpersonationTokensOptions, options ...OptionFunc) ([]*ImpersonationToken, *Response, error) {
+ u := fmt.Sprintf("users/%d/impersonation_tokens", user)
+
+ req, err := s.client.NewRequest("GET", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var ts []*ImpersonationToken
+ resp, err := s.client.Do(req, &ts)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return ts, resp, err
+}
+
+// GetImpersonationToken retrieves an impersonation token of a user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#get-an-impersonation-token-of-a-user
+func (s *UsersService) GetImpersonationToken(user, token int, options ...OptionFunc) (*ImpersonationToken, *Response, error) {
+ u := fmt.Sprintf("users/%d/impersonation_tokens/%d", user, token)
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ t := new(ImpersonationToken)
+ resp, err := s.client.Do(req, &t)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return t, resp, err
+}
+
+// CreateImpersonationTokenOptions represents the available
+// CreateImpersonationToken() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#create-an-impersonation-token
+type CreateImpersonationTokenOptions struct {
+ Name *string `url:"name,omitempty" json:"name,omitempty"`
+ Scopes *[]string `url:"scopes,omitempty" json:"scopes,omitempty"`
+ ExpiresAt *time.Time `url:"expires_at,omitempty" json:"expires_at,omitempty"`
+}
+
+// CreateImpersonationToken creates an impersonation token.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#create-an-impersonation-token
+func (s *UsersService) CreateImpersonationToken(user int, opt *CreateImpersonationTokenOptions, options ...OptionFunc) (*ImpersonationToken, *Response, error) {
+ u := fmt.Sprintf("users/%d/impersonation_tokens", user)
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ t := new(ImpersonationToken)
+ resp, err := s.client.Do(req, &t)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return t, resp, err
+}
+
+// RevokeImpersonationToken revokes an impersonation token.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#revoke-an-impersonation-token
+func (s *UsersService) RevokeImpersonationToken(user, token int, options ...OptionFunc) (*Response, error) {
+ u := fmt.Sprintf("users/%d/impersonation_tokens/%d", user, token)
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/version.go b/vendor/github.com/lkysow/go-gitlab/version.go
new file mode 100644
index 0000000000..f1a3a7f52e
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/version.go
@@ -0,0 +1,56 @@
+//
+// Copyright 2017, Andrea Funto'
+//
+// 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.
+//
+
+package gitlab
+
+// VersionService handles communication with the GitLab server instance to
+// retrieve its version information via the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/version.md
+type VersionService struct {
+ client *Client
+}
+
+// Version represents a GitLab instance version.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/version.md
+type Version struct {
+ Version string `json:"version"`
+ Revision string `json:"revision"`
+}
+
+func (s Version) String() string {
+ return Stringify(s)
+}
+
+// GetVersion gets a GitLab server instance version; it is only available to
+// authenticated users.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/version.md
+func (s *VersionService) GetVersion() (*Version, *Response, error) {
+ req, err := s.client.NewRequest("GET", "version", nil, nil)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ v := new(Version)
+ resp, err := s.client.Do(req, v)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return v, resp, err
+}
diff --git a/vendor/github.com/lkysow/go-gitlab/wikis.go b/vendor/github.com/lkysow/go-gitlab/wikis.go
new file mode 100644
index 0000000000..7288985138
--- /dev/null
+++ b/vendor/github.com/lkysow/go-gitlab/wikis.go
@@ -0,0 +1,204 @@
+// Copyright 2017, Stany MARCEL
+//
+// 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.
+
+package gitlab
+
+import (
+ "fmt"
+ "net/url"
+)
+
+// WikisService handles communication with the wikis related methods of
+// the Gitlab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/wikis.html
+type WikisService struct {
+ client *Client
+}
+
+// WikiFormat represents the available wiki formats.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/wikis.html
+type WikiFormat string
+
+// The available wiki formats.
+const (
+ WikiFormatMarkdown WikiFormat = "markdown"
+ WikiFormatRFoc WikiFormat = "rdoc"
+ WikiFormatASCIIDoc WikiFormat = "asciidoc"
+)
+
+// Wiki represents a GitLab wiki.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/wikis.html
+type Wiki struct {
+ Content string `json:"content"`
+ Format WikiFormat `json:"format"`
+ Slug string `json:"slug"`
+ Title string `json:"title"`
+}
+
+func (w Wiki) String() string {
+ return Stringify(w)
+}
+
+// ListWikisOptions represents the available ListWikis options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/wikis.html#list-wiki-pages
+type ListWikisOptions struct {
+ WithContent *bool `url:"with_content,omitempty" json:"with_content,omitempty"`
+}
+
+// ListWikis lists all pages of the wiki of the given project id.
+// When with_content is set, it also returns the content of the pages.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/wikis.html#list-wiki-pages
+func (s *WikisService) ListWikis(pid interface{}, opt *ListWikisOptions, options ...OptionFunc) ([]*Wiki, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/wikis", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("GET", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var w []*Wiki
+ resp, err := s.client.Do(req, &w)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return w, resp, err
+}
+
+// GetWikiPage gets a wiki page for a given project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/wikis.html#get-a-wiki-page
+func (s *WikisService) GetWikiPage(pid interface{}, slug string, options ...OptionFunc) (*Wiki, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/wikis/%s", url.QueryEscape(project), url.QueryEscape(slug))
+
+ req, err := s.client.NewRequest("GET", u, nil, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var w *Wiki
+ resp, err := s.client.Do(req, &w)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return w, resp, err
+}
+
+// CreateWikiPageOptions represents options to CreateWikiPage.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/wikis.html#create-a-new-wiki-page
+type CreateWikiPageOptions struct {
+ Content *string `url:"content" json:"content"`
+ Title *string `url:"title" json:"title"`
+ Format *string `url:"format,omitempty" json:"format,omitempty"`
+}
+
+// CreateWikiPage creates a new wiki page for the given repository with
+// the given title, slug, and content.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/wikis.html#create-a-new-wiki-page
+func (s *WikisService) CreateWikiPage(pid interface{}, opt *CreateWikiPageOptions, options ...OptionFunc) (*Wiki, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/wikis", url.QueryEscape(project))
+
+ req, err := s.client.NewRequest("POST", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ w := new(Wiki)
+ resp, err := s.client.Do(req, w)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return w, resp, err
+}
+
+// EditWikiPageOptions represents options to EditWikiPage.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/wikis.html#edit-an-existing-wiki-page
+type EditWikiPageOptions struct {
+ Content *string `url:"content" json:"content"`
+ Title *string `url:"title" json:"title"`
+ Format *string `url:"format,omitempty" json:"format,omitempty"`
+}
+
+// EditWikiPage Updates an existing wiki page. At least one parameter is
+// required to update the wiki page.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/wikis.html#edit-an-existing-wiki-page
+func (s *WikisService) EditWikiPage(pid interface{}, slug string, opt *EditWikiPageOptions, options ...OptionFunc) (*Wiki, *Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, nil, err
+ }
+ u := fmt.Sprintf("projects/%s/wikis/%s", url.QueryEscape(project), url.QueryEscape(slug))
+
+ req, err := s.client.NewRequest("PUT", u, opt, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ w := new(Wiki)
+ resp, err := s.client.Do(req, w)
+ if err != nil {
+ return nil, resp, err
+ }
+
+ return w, resp, err
+}
+
+// DeleteWikiPage deletes a wiki page with a given slug.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/wikis.html#delete-a-wiki-page
+func (s *WikisService) DeleteWikiPage(pid interface{}, slug string, options ...OptionFunc) (*Response, error) {
+ project, err := parseID(pid)
+ if err != nil {
+ return nil, err
+ }
+ u := fmt.Sprintf("projects/%s/wikis/%s", url.QueryEscape(project), url.QueryEscape(slug))
+
+ req, err := s.client.NewRequest("DELETE", u, nil, options)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.client.Do(req, nil)
+}