diff --git a/README.md b/README.md index 7aacfaca2..89a645361 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # go-scm +[![Documentation](https://godoc.org/github.com/jenkins-x/go-scm?status.svg)](https://pkg.go.dev/mod/github.com/jenkins-x/go-scm) +[![Go Report Card](https://goreportcard.com/badge/github.com/jenkins-x/go-scm)](https://goreportcard.com/report/github.com/jenkins-x/go-scm) + + A small library with minimal depenencies for working with Webhooks, Commits, Issues, Pull Requests, Comments, Reviews, Teams and more on multiple git provider: * [GitHub](https://github.com/jenkins-x/go-scm/blob/master/scm/driver/github/github.go#L46) diff --git a/scm/client.go b/scm/client.go index 8ac0b2e86..d1b1ea6ec 100644 --- a/scm/client.go +++ b/scm/client.go @@ -94,8 +94,11 @@ type ( // Services used for communicating with the API. Driver Driver + Apps AppService Contents ContentService + Deployments DeploymentService Git GitService + GraphQL GraphQLService Organizations OrganizationService Issues IssueService PullRequests PullRequestService @@ -103,8 +106,6 @@ type ( Reviews ReviewService Users UserService Webhooks WebhookService - GraphQL GraphQLService - Apps AppService // DumpResponse optionally specifies a function to // dump the the response body for debugging purposes. diff --git a/scm/deploy.go b/scm/deploy.go new file mode 100644 index 000000000..212b3a2f9 --- /dev/null +++ b/scm/deploy.go @@ -0,0 +1,93 @@ +package scm + +import ( + "context" + "time" +) + +type ( + // Deployment represents a request to deploy a version/ref/sha in some environment + Deployment struct { + ID string + Namespace string + Name string + Link string + Sha string + Ref string + FullName string + Description string + OriginalEnvironment string + Environment string + RepositoryLink string + StatusLink string + Author *User + Created time.Time + Updated time.Time + TransientEnvironment bool + ProductionEnvironment bool + } + + // DeploymentInput the input to create a new deployment + DeploymentInput struct { + Ref string + Task string + Payload string + Environment string + Description string + RequiredContexts []string + AutoMerge bool + TransientEnvironment bool + ProductionEnvironment bool + } + + // DeploymentStatus represents the status of a deployment + DeploymentStatus struct { + ID string + State string + Author *User + Description string + Environment string + DeploymentLink string + EnvironmentLink string + LogLink string + RepositoryLink string + TargetLink string + Created time.Time + Updated time.Time + } + + // DeploymentStatusInput the input to creating a status of a deployment + DeploymentStatusInput struct { + State string + TargetLink string + LogLink string + Description string + Environment string + EnvironmentLink string + AutoInactive bool + } + + // DeploymentService a service for working with deployments and deployment services + DeploymentService interface { + // Find find a deployment by id. + Find(ctx context.Context, repoFullName string, deploymentID string) (*Deployment, *Response, error) + + // List returns a list of deployments. + List(ctx context.Context, repoFullName string, opts ListOptions) ([]*Deployment, *Response, error) + + // Create creates a new deployment. + Create(ctx context.Context, repoFullName string, deployment *DeploymentInput) (*Deployment, *Response, error) + + // Delete deletes a deployment. + Delete(ctx context.Context, repoFullName string, deploymentID string) (*Response, error) + + // FindStatus find a deployment status by id. + FindStatus(ctx context.Context, repoFullName string, deploymentID string, statusID string) (*DeploymentStatus, *Response, error) + + // List returns a list of deployments. + ListStatus(ctx context.Context, repoFullName string, deploymentID string, options ListOptions) ([]*DeploymentStatus, *Response, error) + + // Create creates a new deployment. + CreateStatus(ctx context.Context, repoFullName string, deploymentID string, deployment *DeploymentStatusInput) (*DeploymentStatus, *Response, error) + } +) diff --git a/scm/driver/fake/repo.go b/scm/driver/fake/repo.go index 7a07e10fd..ad105bee9 100644 --- a/scm/driver/fake/repo.go +++ b/scm/driver/fake/repo.go @@ -2,6 +2,7 @@ package fake import ( "context" + "fmt" "strings" "time" @@ -106,10 +107,12 @@ func (s *repositoryService) ListStatus(ctx context.Context, repo string, ref str func (s *repositoryService) Create(ctx context.Context, input *scm.RepositoryInput) (*scm.Repository, *scm.Response, error) { s.data.CreateRepositories = append(s.data.CreateRepositories, input) + fullName := scm.Join(input.Namespace, input.Name) repo := &scm.Repository{ Namespace: input.Namespace, Name: input.Name, - FullName: scm.Join(input.Namespace, input.Name), + FullName: fullName, + Link: fmt.Sprintf("https://fake.com/%s.git", fullName), Created: time.Now(), } s.data.Repositories = append(s.data.Repositories, repo) diff --git a/scm/driver/github/deploy.go b/scm/driver/github/deploy.go new file mode 100644 index 000000000..810509011 --- /dev/null +++ b/scm/driver/github/deploy.go @@ -0,0 +1,207 @@ +package github + +import ( + "context" + "fmt" + "strconv" + "strings" + "time" + + "github.com/jenkins-x/go-scm/scm" +) + +type deploymentService struct { + client *wrapper +} + +type deployment struct { + Namespace string + Name string + FullName string + ID int `json:"id"` + Link string `json:"url"` + Sha string `json:"sha"` + Ref string `json:"ref"` + Description string `json:"description"` + OriginalEnvironment string `json:"original_environment"` + Environment string `json:"environment"` + RepositoryLink string `json:"repository_url"` + StatusLink string `json:"statuses_url"` + Author *user `json:"creator"` + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` + TransientEnvironment bool `json:"transient_environment"` + ProductionEnvironment bool `json:"production_environment"` +} + +type deploymentInput struct { + Ref string `json:"ref"` + Task string `json:"task"` + Payload string `json:"payload"` + Environment string `json:"environment"` + Description string `json:"description"` + RequiredContexts []string `json:"required_contexts"` + AutoMerge bool `json:"auto_merge"` + TransientEnvironment bool `json:"transient_environment"` + ProductionEnvironment bool `json:"production_environment"` +} + +type deploymentStatus struct { + ID int `json:"id"` + State string `json:"state"` + Author *user `json:"creator"` + Description string `json:"description"` + Environment string `json:"environment"` + DeploymentLink string `json:"deployment_url"` + EnvironmentLink string `json:"environment_url"` + LogLink string `json:"log_url"` + RepositoryLink string `json:"repository_url"` + TargetLink string `json:"target_url"` + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` +} + +type deploymentStatusInput struct { + State string `json:"state"` + TargetLink string `json:"target_url"` + LogLink string `json:"log_url"` + Description string `json:"description"` + Environment string `json:"environment"` + EnvironmentLink string `json:"environment_url"` + AutoInactive bool `json:"auto_inactive"` +} + +func (s *deploymentService) Find(ctx context.Context, repoFullName string, deploymentID string) (*scm.Deployment, *scm.Response, error) { + path := fmt.Sprintf("repos/%s/deployments/%s", repoFullName, deploymentID) + out := new(deployment) + res, err := s.client.do(ctx, "GET", path, nil, out) + return convertDeployment(out, repoFullName), res, err +} + +func (s *deploymentService) List(ctx context.Context, repoFullName string, opts scm.ListOptions) ([]*scm.Deployment, *scm.Response, error) { + path := fmt.Sprintf("repos/%s/deployments?%s", repoFullName, encodeListOptions(opts)) + out := []*deployment{} + res, err := s.client.do(ctx, "GET", path, nil, &out) + return convertDeploymentList(out, repoFullName), res, err +} + +func (s *deploymentService) Create(ctx context.Context, repoFullName string, deploymentInput *scm.DeploymentInput) (*scm.Deployment, *scm.Response, error) { + path := fmt.Sprintf("repos/%s/deployments", repoFullName) + in := convertToDeploymentInput(deploymentInput) + out := new(deployment) + res, err := s.client.do(ctx, "POST", path, in, out) + return convertDeployment(out, repoFullName), res, err +} + +func (s *deploymentService) Delete(ctx context.Context, repoFullName string, deploymentID string) (*scm.Response, error) { + path := fmt.Sprintf("repos/%s/deployments/%s", repoFullName, deploymentID) + return s.client.do(ctx, "DELETE", path, nil, nil) +} + +func (s *deploymentService) FindStatus(ctx context.Context, repoFullName string, deploymentID string, statusID string) (*scm.DeploymentStatus, *scm.Response, error) { + path := fmt.Sprintf("repos/%s/deployments/%s/statuses/%s", repoFullName, deploymentID, statusID) + out := new(deploymentStatus) + res, err := s.client.do(ctx, "GET", path, nil, out) + return convertDeploymentStatus(out), res, err +} + +func (s *deploymentService) ListStatus(ctx context.Context, repoFullName string, deploymentID string, opts scm.ListOptions) ([]*scm.DeploymentStatus, *scm.Response, error) { + path := fmt.Sprintf("repos/%s/deployments/%s/statuses?%s", repoFullName, deploymentID, encodeListOptions(opts)) + out := []*deploymentStatus{} + res, err := s.client.do(ctx, "GET", path, nil, &out) + return convertDeploymentStatusList(out), res, err +} + +func (s *deploymentService) CreateStatus(ctx context.Context, repoFullName string, deploymentID string, deploymentStatusInput *scm.DeploymentStatusInput) (*scm.DeploymentStatus, *scm.Response, error) { + path := fmt.Sprintf("repos/%s/deployments/%s/statuses", repoFullName, deploymentID) + in := convertToDeploymentStatusInput(deploymentStatusInput) + out := new(deploymentStatus) + res, err := s.client.do(ctx, "POST", path, in, out) + return convertDeploymentStatus(out), res, err +} + +func convertDeploymentList(out []*deployment, fullName string) []*scm.Deployment { + answer := []*scm.Deployment{} + for _, o := range out { + answer = append(answer, convertDeployment(o, fullName)) + } + return answer +} + +func convertDeploymentStatusList(out []*deploymentStatus) []*scm.DeploymentStatus { + answer := []*scm.DeploymentStatus{} + for _, o := range out { + answer = append(answer, convertDeploymentStatus(o)) + } + return answer + +} + +func convertToDeploymentInput(from *scm.DeploymentInput) *deploymentInput { + return &deploymentInput{ + Ref: from.Ref, + Task: from.Task, + Payload: from.Payload, + Environment: from.Environment, + Description: from.Description, + RequiredContexts: from.RequiredContexts, + AutoMerge: from.AutoMerge, + TransientEnvironment: from.TransientEnvironment, + ProductionEnvironment: from.ProductionEnvironment, + } +} + +func convertDeployment(from *deployment, fullName string) *scm.Deployment { + dst := &scm.Deployment{ + ID: strconv.Itoa(from.ID), + Link: from.Link, + Sha: from.Sha, + Ref: from.Ref, + FullName: fullName, + Description: from.Description, + OriginalEnvironment: from.OriginalEnvironment, + Environment: from.Environment, + RepositoryLink: from.RepositoryLink, + StatusLink: from.StatusLink, + Author: convertUser(from.Author), + Created: from.Created, + Updated: from.Updated, + TransientEnvironment: from.TransientEnvironment, + ProductionEnvironment: from.ProductionEnvironment, + } + names := strings.Split(fullName, "/") + if len(names) > 1 { + dst.Namespace = names[0] + dst.Name = names[1] + } + return dst +} + +func convertDeploymentStatus(from *deploymentStatus) *scm.DeploymentStatus { + return &scm.DeploymentStatus{ + ID: strconv.Itoa(from.ID), + State: from.State, + Author: convertUser(from.Author), + Description: from.Description, + Environment: from.Environment, + DeploymentLink: from.DeploymentLink, + EnvironmentLink: from.EnvironmentLink, + LogLink: from.LogLink, + RepositoryLink: from.RepositoryLink, + TargetLink: from.TargetLink, + Created: from.Created, + Updated: from.Updated, + } +} + +func convertToDeploymentStatusInput(from *scm.DeploymentStatusInput) *deploymentStatusInput { + return &deploymentStatusInput{ + State: from.State, + TargetLink: from.TargetLink, + LogLink: from.LogLink, + Description: from.Description, + Environment: from.Environment, + EnvironmentLink: from.EnvironmentLink, + AutoInactive: from.AutoInactive, + } +} diff --git a/scm/driver/github/deploy_test.go b/scm/driver/github/deploy_test.go new file mode 100644 index 000000000..09b8b4025 --- /dev/null +++ b/scm/driver/github/deploy_test.go @@ -0,0 +1,246 @@ +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "io/ioutil" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/h2non/gock" + "github.com/jenkins-x/go-scm/scm" +) + +func TestDeploymentFind(t *testing.T) { + defer gock.Off() + + gock.New("https://api.github.com"). + Get("/repos/octocat/example/deployments/1"). + Reply(200). + Type("application/json"). + SetHeaders(mockHeaders). + File("testdata/deploy.json") + + client := NewDefault() + got, res, err := client.Deployments.Find(context.Background(), "octocat/example", "1") + if err != nil { + t.Error(err) + return + } + + want := new(scm.Deployment) + raw, _ := ioutil.ReadFile("testdata/deploy.json.golden") + json.Unmarshal(raw, want) + + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("Unexpected Results") + t.Log(diff) + + logGot(t, got) + } + + t.Run("Request", testRequest(res)) + t.Run("Rate", testRate(res)) +} + +func TestDeploymentNotFound(t *testing.T) { + defer gock.Off() + + gock.New("https://api.github.com"). + Get("/repos/dev/null/deployments/999"). + Reply(404). + Type("application/json"). + SetHeaders(mockHeaders). + File("testdata/error.json") + + client := NewDefault() + _, _, err := client.Deployments.Find(context.Background(), "dev/null", "999") + if err == nil { + t.Errorf("Expect Not Found error") + return + } + if got, want := err.Error(), "Not Found"; got != want { + t.Errorf("Want error %q, got %q", want, got) + } +} + +func TestDeploymentList(t *testing.T) { + defer gock.Off() + + gock.New("https://api.github.com"). + Get("/repos/octocat/example/deployments"). + MatchParam("page", "1"). + MatchParam("per_page", "30"). + Reply(200). + Type("application/json"). + SetHeaders(mockHeaders). + SetHeaders(mockPageHeaders). + File("testdata/deploys.json") + + client := NewDefault() + got, res, err := client.Deployments.List(context.Background(), "octocat/example", scm.ListOptions{Page: 1, Size: 30}) + if err != nil { + t.Error(err) + return + } + + want := []*scm.Deployment{} + raw, _ := ioutil.ReadFile("testdata/deploys.json.golden") + json.Unmarshal(raw, &want) + + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("Unexpected Results") + t.Log(diff) + + logGot(t, got) + } + + t.Run("Request", testRequest(res)) + t.Run("Rate", testRate(res)) + t.Run("Page", testPage(res)) +} + +func TestDeploymentCreate(t *testing.T) { + defer gock.Off() + + gock.New("https://api.github.com"). + Post("/repos/octocat/example/deployments"). + Reply(201). + Type("application/json"). + SetHeaders(mockHeaders). + File("testdata/deploy_create.json") + + in := &scm.DeploymentInput{} + + client := NewDefault() + got, res, err := client.Deployments.Create(context.Background(), "octocat/example", in) + if err != nil { + t.Error(err) + return + } + + want := new(scm.Deployment) + raw, _ := ioutil.ReadFile("testdata/deploy_create.json.golden") + json.Unmarshal(raw, want) + + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("Unexpected Results") + t.Log(diff) + + logGot(t, got) + } + + t.Run("Request", testRequest(res)) + t.Run("Rate", testRate(res)) +} + +func TestDeploymentStatusList(t *testing.T) { + defer gock.Off() + + gock.New("https://api.github.com"). + Get("/repos/octocat/example/deployments/1/statuses"). + MatchParam("page", "1"). + MatchParam("per_page", "30"). + Reply(200). + Type("application/json"). + SetHeaders(mockHeaders). + SetHeaders(mockPageHeaders). + File("testdata/deploy_statuses.json") + + client := NewDefault() + got, res, err := client.Deployments.ListStatus(context.Background(), "octocat/example", "1", scm.ListOptions{Page: 1, Size: 30}) + if err != nil { + t.Error(err) + return + } + + want := []*scm.DeploymentStatus{} + raw, _ := ioutil.ReadFile("testdata/deploy_statuses.json.golden") + json.Unmarshal(raw, &want) + + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("Unexpected Results") + t.Log(diff) + + logGot(t, got) + } + + t.Run("Request", testRequest(res)) + t.Run("Rate", testRate(res)) + t.Run("Page", testPage(res)) +} + +func TestDeploymentStatusFind(t *testing.T) { + defer gock.Off() + + gock.New("https://api.github.com"). + Get("/repos/octocat/example/deployments/1/statuses/1"). + Reply(200). + Type("application/json"). + SetHeaders(mockHeaders). + File("testdata/deploy_status.json") + + client := NewDefault() + got, res, err := client.Deployments.FindStatus(context.Background(), "octocat/example", "1", "1") + if err != nil { + t.Error(err) + return + } + + want := new(scm.DeploymentStatus) + raw, _ := ioutil.ReadFile("testdata/deploy_status.json.golden") + json.Unmarshal(raw, want) + + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("Unexpected Results") + t.Log(diff) + + logGot(t, got) + } + + t.Run("Request", testRequest(res)) + t.Run("Rate", testRate(res)) +} + +func TestDeploymentStatusCreate(t *testing.T) { + defer gock.Off() + + gock.New("https://api.github.com"). + Post("repos/octocat/example/deployments/1/statuses"). + Reply(201). + Type("application/json"). + SetHeaders(mockHeaders). + File("testdata/deploy_status_create.json") + + in := &scm.DeploymentStatusInput{} + + client := NewDefault() + got, res, err := client.Deployments.CreateStatus(context.Background(), "octocat/example", "1", in) + if err != nil { + t.Error(err) + return + } + + want := new(scm.DeploymentStatus) + raw, _ := ioutil.ReadFile("testdata/deploy_status_create.json.golden") + json.Unmarshal(raw, want) + + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("Unexpected Results") + t.Log(diff) + + logGot(t, got) + } + + t.Run("Request", testRequest(res)) + t.Run("Rate", testRate(res)) +} + +func logGot(t *testing.T, got interface{}) { + data, _ := json.Marshal(got) + t.Log("got JSON:") + t.Log(string(data)) +} diff --git a/scm/driver/github/github.go b/scm/driver/github/github.go index 429539809..3e51c7199 100644 --- a/scm/driver/github/github.go +++ b/scm/driver/github/github.go @@ -39,6 +39,7 @@ func New(uri string) (*scm.Client, error) { // initialize services client.Driver = scm.DriverGithub client.Contents = &contentService{client} + client.Deployments = &deploymentService{client} client.Git = &gitService{client} client.Issues = &issueService{client} client.Organizations = &organizationService{client} diff --git a/scm/driver/github/repo.go b/scm/driver/github/repo.go index 0fb31fc0c..0ef7bef21 100644 --- a/scm/driver/github/repo.go +++ b/scm/driver/github/repo.go @@ -221,7 +221,6 @@ func (s *repositoryService) Create(ctx context.Context, input *scm.RepositoryInp out := new(repository) res, err := s.client.do(ctx, "POST", path, in, out) return convertRepository(out), res, err - } // CreateHook creates a new repository webhook. diff --git a/scm/driver/github/testdata/deploy.json b/scm/driver/github/testdata/deploy.json new file mode 100644 index 000000000..2b961b007 --- /dev/null +++ b/scm/driver/github/testdata/deploy.json @@ -0,0 +1,40 @@ +{ + "url": "https://api.github.com/repos/octocat/example/deployments/1", + "id": 1, + "node_id": "MDEwOkRlcGxveW1lbnQx", + "sha": "a84d88e7554fc1fa21bcbc4efae3c782a70d2b9d", + "ref": "topic-branch", + "task": "deploy", + "payload": { + "deploy": "migrate" + }, + "original_environment": "staging", + "environment": "production", + "description": "Deploy request from hubot", + "creator": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "created_at": "2012-07-20T01:19:13Z", + "updated_at": "2012-07-20T01:19:13Z", + "statuses_url": "https://api.github.com/repos/octocat/example/deployments/1/statuses", + "repository_url": "https://api.github.com/repos/octocat/example", + "transient_environment": false, + "production_environment": true +} \ No newline at end of file diff --git a/scm/driver/github/testdata/deploy.json.golden b/scm/driver/github/testdata/deploy.json.golden new file mode 100644 index 000000000..cbbe592d4 --- /dev/null +++ b/scm/driver/github/testdata/deploy.json.golden @@ -0,0 +1,27 @@ +{ + "ID": "1", + "Namespace": "octocat", + "Name": "example", + "Link": "https://api.github.com/repos/octocat/example/deployments/1", + "Sha": "a84d88e7554fc1fa21bcbc4efae3c782a70d2b9d", + "Ref": "topic-branch", + "FullName": "octocat/example", + "Description": "Deploy request from hubot", + "OriginalEnvironment": "staging", + "Environment": "production", + "RepositoryLink": "https://api.github.com/repos/octocat/example", + "StatusLink": "https://api.github.com/repos/octocat/example/deployments/1/statuses", + "Author": { + "Login": "octocat", + "Name": "", + "Email": "", + "Avatar": "https://github.com/images/error/octocat_happy.gif", + "Link": "https://github.com/octocat", + "Created": "0001-01-01T00:00:00Z", + "Updated": "0001-01-01T00:00:00Z" + }, + "Created": "2012-07-20T01:19:13Z", + "Updated": "2012-07-20T01:19:13Z", + "TransientEnvironment": false, + "ProductionEnvironment": true +} \ No newline at end of file diff --git a/scm/driver/github/testdata/deploy_create.json b/scm/driver/github/testdata/deploy_create.json new file mode 100644 index 000000000..2b961b007 --- /dev/null +++ b/scm/driver/github/testdata/deploy_create.json @@ -0,0 +1,40 @@ +{ + "url": "https://api.github.com/repos/octocat/example/deployments/1", + "id": 1, + "node_id": "MDEwOkRlcGxveW1lbnQx", + "sha": "a84d88e7554fc1fa21bcbc4efae3c782a70d2b9d", + "ref": "topic-branch", + "task": "deploy", + "payload": { + "deploy": "migrate" + }, + "original_environment": "staging", + "environment": "production", + "description": "Deploy request from hubot", + "creator": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "created_at": "2012-07-20T01:19:13Z", + "updated_at": "2012-07-20T01:19:13Z", + "statuses_url": "https://api.github.com/repos/octocat/example/deployments/1/statuses", + "repository_url": "https://api.github.com/repos/octocat/example", + "transient_environment": false, + "production_environment": true +} \ No newline at end of file diff --git a/scm/driver/github/testdata/deploy_create.json.golden b/scm/driver/github/testdata/deploy_create.json.golden new file mode 100644 index 000000000..cbbe592d4 --- /dev/null +++ b/scm/driver/github/testdata/deploy_create.json.golden @@ -0,0 +1,27 @@ +{ + "ID": "1", + "Namespace": "octocat", + "Name": "example", + "Link": "https://api.github.com/repos/octocat/example/deployments/1", + "Sha": "a84d88e7554fc1fa21bcbc4efae3c782a70d2b9d", + "Ref": "topic-branch", + "FullName": "octocat/example", + "Description": "Deploy request from hubot", + "OriginalEnvironment": "staging", + "Environment": "production", + "RepositoryLink": "https://api.github.com/repos/octocat/example", + "StatusLink": "https://api.github.com/repos/octocat/example/deployments/1/statuses", + "Author": { + "Login": "octocat", + "Name": "", + "Email": "", + "Avatar": "https://github.com/images/error/octocat_happy.gif", + "Link": "https://github.com/octocat", + "Created": "0001-01-01T00:00:00Z", + "Updated": "0001-01-01T00:00:00Z" + }, + "Created": "2012-07-20T01:19:13Z", + "Updated": "2012-07-20T01:19:13Z", + "TransientEnvironment": false, + "ProductionEnvironment": true +} \ No newline at end of file diff --git a/scm/driver/github/testdata/deploy_status.json b/scm/driver/github/testdata/deploy_status.json new file mode 100644 index 000000000..c2781a41e --- /dev/null +++ b/scm/driver/github/testdata/deploy_status.json @@ -0,0 +1,35 @@ +{ + "url": "https://api.github.com/repos/octocat/example/deployments/42/statuses/1", + "id": 1, + "node_id": "MDE2OkRlcGxveW1lbnRTdGF0dXMx", + "state": "success", + "creator": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "description": "Deployment finished successfully.", + "environment": "production", + "target_url": "https://example.com/deployment/42/output", + "created_at": "2012-07-20T01:19:13Z", + "updated_at": "2012-07-20T01:19:13Z", + "deployment_url": "https://api.github.com/repos/octocat/example/deployments/42", + "repository_url": "https://api.github.com/repos/octocat/example", + "environment_url": "", + "log_url": "https://example.com/deployment/42/output" +} \ No newline at end of file diff --git a/scm/driver/github/testdata/deploy_status.json.golden b/scm/driver/github/testdata/deploy_status.json.golden new file mode 100644 index 000000000..8ad7899fb --- /dev/null +++ b/scm/driver/github/testdata/deploy_status.json.golden @@ -0,0 +1,22 @@ +{ + "ID": "1", + "State": "success", + "Author": { + "Login": "octocat", + "Name": "", + "Email": "", + "Avatar": "https://github.com/images/error/octocat_happy.gif", + "Link": "https://github.com/octocat", + "Created": "0001-01-01T00:00:00Z", + "Updated": "0001-01-01T00:00:00Z" + }, + "Description": "Deployment finished successfully.", + "Environment": "production", + "DeploymentLink": "https://api.github.com/repos/octocat/example/deployments/42", + "EnvironmentLink": "", + "LogLink": "https://example.com/deployment/42/output", + "RepositoryLink": "https://api.github.com/repos/octocat/example", + "TargetLink": "https://example.com/deployment/42/output", + "Created": "2012-07-20T01:19:13Z", + "Updated": "2012-07-20T01:19:13Z" +} \ No newline at end of file diff --git a/scm/driver/github/testdata/deploy_status_create.json b/scm/driver/github/testdata/deploy_status_create.json new file mode 100644 index 000000000..c2781a41e --- /dev/null +++ b/scm/driver/github/testdata/deploy_status_create.json @@ -0,0 +1,35 @@ +{ + "url": "https://api.github.com/repos/octocat/example/deployments/42/statuses/1", + "id": 1, + "node_id": "MDE2OkRlcGxveW1lbnRTdGF0dXMx", + "state": "success", + "creator": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "description": "Deployment finished successfully.", + "environment": "production", + "target_url": "https://example.com/deployment/42/output", + "created_at": "2012-07-20T01:19:13Z", + "updated_at": "2012-07-20T01:19:13Z", + "deployment_url": "https://api.github.com/repos/octocat/example/deployments/42", + "repository_url": "https://api.github.com/repos/octocat/example", + "environment_url": "", + "log_url": "https://example.com/deployment/42/output" +} \ No newline at end of file diff --git a/scm/driver/github/testdata/deploy_status_create.json.golden b/scm/driver/github/testdata/deploy_status_create.json.golden new file mode 100644 index 000000000..8ad7899fb --- /dev/null +++ b/scm/driver/github/testdata/deploy_status_create.json.golden @@ -0,0 +1,22 @@ +{ + "ID": "1", + "State": "success", + "Author": { + "Login": "octocat", + "Name": "", + "Email": "", + "Avatar": "https://github.com/images/error/octocat_happy.gif", + "Link": "https://github.com/octocat", + "Created": "0001-01-01T00:00:00Z", + "Updated": "0001-01-01T00:00:00Z" + }, + "Description": "Deployment finished successfully.", + "Environment": "production", + "DeploymentLink": "https://api.github.com/repos/octocat/example/deployments/42", + "EnvironmentLink": "", + "LogLink": "https://example.com/deployment/42/output", + "RepositoryLink": "https://api.github.com/repos/octocat/example", + "TargetLink": "https://example.com/deployment/42/output", + "Created": "2012-07-20T01:19:13Z", + "Updated": "2012-07-20T01:19:13Z" +} \ No newline at end of file diff --git a/scm/driver/github/testdata/deploy_statuses.json b/scm/driver/github/testdata/deploy_statuses.json new file mode 100644 index 000000000..ec14e2c5a --- /dev/null +++ b/scm/driver/github/testdata/deploy_statuses.json @@ -0,0 +1,37 @@ +[ + { + "url": "https://api.github.com/repos/octocat/example/deployments/42/statuses/1", + "id": 1, + "node_id": "MDE2OkRlcGxveW1lbnRTdGF0dXMx", + "state": "success", + "creator": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "description": "Deployment finished successfully.", + "environment": "production", + "target_url": "https://example.com/deployment/42/output", + "created_at": "2012-07-20T01:19:13Z", + "updated_at": "2012-07-20T01:19:13Z", + "deployment_url": "https://api.github.com/repos/octocat/example/deployments/42", + "repository_url": "https://api.github.com/repos/octocat/example", + "environment_url": "", + "log_url": "https://example.com/deployment/42/output" + } +] \ No newline at end of file diff --git a/scm/driver/github/testdata/deploy_statuses.json.golden b/scm/driver/github/testdata/deploy_statuses.json.golden new file mode 100644 index 000000000..c2bf2829d --- /dev/null +++ b/scm/driver/github/testdata/deploy_statuses.json.golden @@ -0,0 +1,24 @@ +[ + { + "ID": "1", + "State": "success", + "Author": { + "Login": "octocat", + "Name": "", + "Email": "", + "Avatar": "https://github.com/images/error/octocat_happy.gif", + "Link": "https://github.com/octocat", + "Created": "0001-01-01T00:00:00Z", + "Updated": "0001-01-01T00:00:00Z" + }, + "Description": "Deployment finished successfully.", + "Environment": "production", + "DeploymentLink": "https://api.github.com/repos/octocat/example/deployments/42", + "EnvironmentLink": "", + "LogLink": "https://example.com/deployment/42/output", + "RepositoryLink": "https://api.github.com/repos/octocat/example", + "TargetLink": "https://example.com/deployment/42/output", + "Created": "2012-07-20T01:19:13Z", + "Updated": "2012-07-20T01:19:13Z" + } +] \ No newline at end of file diff --git a/scm/driver/github/testdata/deploys.json b/scm/driver/github/testdata/deploys.json new file mode 100644 index 000000000..e7486c1dc --- /dev/null +++ b/scm/driver/github/testdata/deploys.json @@ -0,0 +1,42 @@ +[ + { + "url": "https://api.github.com/repos/octocat/example/deployments/1", + "id": 1, + "node_id": "MDEwOkRlcGxveW1lbnQx", + "sha": "a84d88e7554fc1fa21bcbc4efae3c782a70d2b9d", + "ref": "topic-branch", + "task": "deploy", + "payload": { + "deploy": "migrate" + }, + "original_environment": "staging", + "environment": "production", + "description": "Deploy request from hubot", + "creator": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "created_at": "2012-07-20T01:19:13Z", + "updated_at": "2012-07-20T01:19:13Z", + "statuses_url": "https://api.github.com/repos/octocat/example/deployments/1/statuses", + "repository_url": "https://api.github.com/repos/octocat/example", + "transient_environment": false, + "production_environment": true + } +] \ No newline at end of file diff --git a/scm/driver/github/testdata/deploys.json.golden b/scm/driver/github/testdata/deploys.json.golden new file mode 100644 index 000000000..cfc5cebf1 --- /dev/null +++ b/scm/driver/github/testdata/deploys.json.golden @@ -0,0 +1,29 @@ +[ + { + "ID": "1", + "Namespace": "octocat", + "Name": "example", + "Link": "https://api.github.com/repos/octocat/example/deployments/1", + "Sha": "a84d88e7554fc1fa21bcbc4efae3c782a70d2b9d", + "Ref": "topic-branch", + "FullName": "octocat/example", + "Description": "Deploy request from hubot", + "OriginalEnvironment": "staging", + "Environment": "production", + "RepositoryLink": "https://api.github.com/repos/octocat/example", + "StatusLink": "https://api.github.com/repos/octocat/example/deployments/1/statuses", + "Author": { + "Login": "octocat", + "Name": "", + "Email": "", + "Avatar": "https://github.com/images/error/octocat_happy.gif", + "Link": "https://github.com/octocat", + "Created": "0001-01-01T00:00:00Z", + "Updated": "0001-01-01T00:00:00Z" + }, + "Created": "2012-07-20T01:19:13Z", + "Updated": "2012-07-20T01:19:13Z", + "TransientEnvironment": false, + "ProductionEnvironment": true + } +] \ No newline at end of file diff --git a/scm/driver/github/user.go b/scm/driver/github/user.go index 9c3e82323..8bac3f52c 100644 --- a/scm/driver/github/user.go +++ b/scm/driver/github/user.go @@ -47,6 +47,9 @@ type user struct { } func convertUser(from *user) *scm.User { + if from == nil { + return nil + } return &scm.User{ Avatar: from.Avatar, Email: from.Email.String,