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}}\n
Log\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) +}