Repository: xanzy/go-gitlab
Pull request #979 (2020-11-24 14:18:26)

Validate the CI YAML configuration by project
https://docs.gitlab.com/ee/api/lint.html#validate-a-projects-ci-configuration


Patch:
diff --git a/commits.go b/commits.go
index 1a89f0e..36b355b 100644
--- a/commits.go
+++ b/commits.go
@@ -389,18 +389,17 @@ type GetCommitStatusesOptions struct {
 //
 // 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"`
-	CreatedAt    *time.Time `json:"created_at"`
-	StartedAt    *time.Time `json:"started_at"`
-	FinishedAt   *time.Time `json:"finished_at"`
-	Name         string     `json:"name"`
-	AllowFailure bool       `json:"allow_failure"`
-	Author       Author     `json:"author"`
-	Description  string     `json:"description"`
-	TargetURL    string     `json:"target_url"`
+	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.
diff --git a/jobs.go b/jobs.go
index b4e4d3b..0aaa583 100644
--- a/jobs.go
+++ b/jobs.go
@@ -76,45 +76,6 @@ type Job struct {
 	User   *User  `json:"user"`
 }
 
-// Bridge represents a pipeline bridge.
-//
-// GitLab API docs: https://docs.gitlab.com/ce/api/jobs.html#list-pipeline-bridges
-type Bridge struct {
-	Commit       *Commit    `json:"commit"`
-	Coverage     float64    `json:"coverage"`
-	AllowFailure bool       `json:"allow_failure"`
-	CreatedAt    *time.Time `json:"created_at"`
-	StartedAt    *time.Time `json:"started_at"`
-	FinishedAt   *time.Time `json:"finished_at"`
-	Duration     float64    `json:"duration"`
-	ID           int        `json:"id"`
-	Name         string     `json:"name"`
-	Pipeline     struct {
-		ID        int        `json:"id"`
-		Ref       string     `json:"ref"`
-		Sha       string     `json:"sha"`
-		Status    string     `json:"status"`
-		CreatedAt *time.Time `json:"created_at"`
-		UpdateAt  *time.Time `json:"updated_at"`
-		WebURL    string     `json:"web_url"`
-	} `json:"pipeline"`
-	Ref                string `json:"ref"`
-	Stage              string `json:"stage"`
-	Status             string `json:"status"`
-	Tag                bool   `json:"tag"`
-	WebURL             string `json:"web_url"`
-	User               *User  `json:"user"`
-	DownstreamPipeline struct {
-		ID        int        `json:"id"`
-		Sha       string     `json:"sha"`
-		Ref       string     `json:"ref"`
-		Status    string     `json:"status"`
-		CreatedAt *time.Time `json:"created_at"`
-		UpdateAt  *time.Time `json:"updated_at"`
-		WebURL    string     `json:"web_url"`
-	} `json:"downstream_pipeline"`
-}
-
 // ListJobsOptions are options for two list apis
 type ListJobsOptions struct {
 	ListOptions
@@ -128,7 +89,7 @@ type ListJobsOptions struct {
 //
 // GitLab API docs:
 // https://docs.gitlab.com/ce/api/jobs.html#list-project-jobs
-func (s *JobsService) ListProjectJobs(pid interface{}, opts *ListJobsOptions, options ...RequestOptionFunc) ([]*Job, *Response, error) {
+func (s *JobsService) ListProjectJobs(pid interface{}, opts *ListJobsOptions, options ...RequestOptionFunc) ([]Job, *Response, error) {
 	project, err := parseID(pid)
 	if err != nil {
 		return nil, nil, err
@@ -140,7 +101,7 @@ func (s *JobsService) ListProjectJobs(pid interface{}, opts *ListJobsOptions, op
 		return nil, nil, err
 	}
 
-	var jobs []*Job
+	var jobs []Job
 	resp, err := s.client.Do(req, &jobs)
 	if err != nil {
 		return nil, resp, err
@@ -175,32 +136,6 @@ func (s *JobsService) ListPipelineJobs(pid interface{}, pipelineID int, opts *Li
 	return jobs, resp, err
 }
 
-// ListPipelineBridges gets a list of bridges for specific pipeline in a
-// project.
-//
-// GitLab API docs:
-// https://docs.gitlab.com/ce/api/jobs.html#list-pipeline-jobs
-func (s *JobsService) ListPipelineBridges(pid interface{}, pipelineID int, opts *ListJobsOptions, options ...RequestOptionFunc) ([]*Bridge, *Response, error) {
-	project, err := parseID(pid)
-	if err != nil {
-		return nil, nil, err
-	}
-	u := fmt.Sprintf("projects/%s/pipelines/%d/bridges", pathEscape(project), pipelineID)
-
-	req, err := s.client.NewRequest("GET", u, opts, options)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	var bridges []*Bridge
-	resp, err := s.client.Do(req, &bridges)
-	if err != nil {
-		return nil, resp, err
-	}
-
-	return bridges, resp, err
-}
-
 // GetJob gets a single job of a project.
 //
 // GitLab API docs:
diff --git a/projects.go b/projects.go
index 84e790d..5d5ca8c 100644
--- a/projects.go
+++ b/projects.go
@@ -101,7 +101,6 @@ type Project struct {
 	SnippetsAccessLevel                       AccessControlValue `json:"snippets_access_level"`
 	PagesAccessLevel                          AccessControlValue `json:"pages_access_level"`
 	AutocloseReferencedIssues                 bool               `json:"autoclose_referenced_issues"`
-	CIForwardDeploymentEnabled                bool               `json:"ci_forward_deployment_enabled"`
 	SharedWithGroups                          []struct {
 		GroupID          int    `json:"group_id"`
 		GroupName        string `json:"group_name"`
@@ -500,7 +499,6 @@ type CreateProjectOptions struct {
 	AutoCancelPendingPipelines                *string             `url:"auto_cancel_pending_pipelines,omitempty" json:"auto_cancel_pending_pipelines,omitempty"`
 	BuildCoverageRegex                        *string             `url:"build_coverage_regex,omitempty" json:"build_coverage_regex,omitempty"`
 	CIConfigPath                              *string             `url:"ci_config_path,omitempty" json:"ci_config_path,omitempty"`
-	CIForwardDeploymentEnabled                *bool               `url:"ci_forward_deployment_enabled,omitempty" json:"ci_forward_deployment_enabled,omitempty"`
 	AutoDevopsEnabled                         *bool               `url:"auto_devops_enabled,omitempty" json:"auto_devops_enabled,omitempty"`
 	AutoDevopsDeployStrategy                  *string             `url:"auto_devops_deploy_strategy,omitempty" json:"auto_devops_deploy_strategy,omitempty"`
 	ApprovalsBeforeMerge                      *int                `url:"approvals_before_merge,omitempty" json:"approvals_before_merge,omitempty"`
@@ -606,7 +604,6 @@ type EditProjectOptions struct {
 	AutoCancelPendingPipelines                *string             `url:"auto_cancel_pending_pipelines,omitempty" json:"auto_cancel_pending_pipelines,omitempty"`
 	BuildCoverageRegex                        *string             `url:"build_coverage_regex,omitempty" json:"build_coverage_regex,omitempty"`
 	CIConfigPath                              *string             `url:"ci_config_path,omitempty" json:"ci_config_path,omitempty"`
-	CIForwardDeploymentEnabled                *bool               `url:"ci_forward_deployment_enabled,omitempty" json:"ci_forward_deployment_enabled,omitempty"`
 	CIDefaultGitDepth                         *int                `url:"ci_default_git_depth,omitempty" json:"ci_default_git_depth,omitempty"`
 	AutoDevopsEnabled                         *bool               `url:"auto_devops_enabled,omitempty" json:"auto_devops_enabled,omitempty"`
 	AutoDevopsDeployStrategy                  *string             `url:"auto_devops_deploy_strategy,omitempty" json:"auto_devops_deploy_strategy,omitempty"`
diff --git a/validate.go b/validate.go
index 099484e..df34f65 100644
--- a/validate.go
+++ b/validate.go
@@ -1,5 +1,7 @@
 package gitlab
 
+import "fmt"
+
 // ValidateService handles communication with the validation related methods of
 // the GitLab API.
 //
@@ -16,6 +18,16 @@ type LintResult struct {
 	Errors []string `json:"errors"`
 }
 
+// ProjectLintResult represents the linting results by project.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/lint.html#validate-a-projects-ci-configuration
+type ProjectLintResult struct {
+	Valid      bool     `json:"valid"`
+	Errors     []string `json:"errors"`
+	Warnings   []string `json:"warnings"`
+	MergedYaml string   `json:"merged_yaml"`
+}
+
 // Lint validates .gitlab-ci.yml content.
 //
 // GitLab API docs: https://docs.gitlab.com/ce/api/lint.html
@@ -38,3 +50,34 @@ func (s *ValidateService) Lint(content string, options ...RequestOptionFunc) (*L
 
 	return l, resp, nil
 }
+
+// ProjectLintOptions represents the available ProjectLint() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/lint.html#validate-a-projects-ci-configuration
+type ProjectLintOptions struct {
+	DryRun *bool `url:"dry_run,omitempty" json:"dry_run,omitempty"`
+}
+
+// Lint validates .gitlab-ci.yml content by project.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/lint.html#validate-a-projects-ci-configuration
+func (s *ValidateService) ProjectLint(pid interface{}, opt *ProjectLintOptions, options ...RequestOptionFunc) (*ProjectLintResult, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/ci/lint", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, &opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	l := new(ProjectLintResult)
+	resp, err := s.client.Do(req, l)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return l, resp, nil
+}


Test patch:
diff --git a/validate_test.go b/validate_test.go
index f8ee8af..c8e92c1 100644
--- a/validate_test.go
+++ b/validate_test.go
@@ -69,3 +69,66 @@ func TestValidate(t *testing.T) {
 		})
 	}
 }
+
+func TestValidateProject(t *testing.T) {
+	testCases := []struct {
+		description string
+		response    string
+		want        *ProjectLintResult
+	}{
+		{
+			description: "valid",
+			response: `{
+				"valid": true,
+				"errors": [],
+				"warnings": [],
+				"merged_yaml": 	"---\n:build:\n  :script:\n  - echo build"
+			}`,
+			want: &ProjectLintResult{
+				Valid:      true,
+				Warnings:   []string{},
+				Errors:     []string{},
+				MergedYaml: "---\n:build:\n  :script:\n  - echo build",
+			},
+		},
+		{
+			description: "invalid",
+			response: `{
+				"valid": false,
+				"errors": ["jobs:build config contains unknown keys: bad_key"],
+				"warnings": [],
+				"merged_yaml": 	"---\n:build:\n  :script:\n  - echo build\n  :bad_key: value"
+			}`,
+			want: &ProjectLintResult{
+				Valid:      false,
+				Warnings:   []string{},
+				Errors:     []string{"jobs:build config contains unknown keys: bad_key"},
+				MergedYaml: "---\n:build:\n  :script:\n  - echo build\n  :bad_key: value",
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.description, func(t *testing.T) {
+			mux, server, client := setup(t)
+			defer teardown(server)
+
+			mux.HandleFunc("/api/v4/projects/1/ci/lint", func(w http.ResponseWriter, r *http.Request) {
+				testMethod(t, r, "GET")
+				fmt.Fprint(w, tc.response)
+			})
+
+			opt := &ProjectLintOptions{}
+			got, _, err := client.Validate.ProjectLint(1, opt)
+
+			if err != nil {
+				t.Errorf("Validate returned error: %v", err)
+			}
+
+			want := tc.want
+			if !reflect.DeepEqual(got, want) {
+				t.Errorf("Validate returned \ngot:\n%v\nwant:\n%v", Stringify(got), Stringify(want))
+			}
+		})
+	}
+}