feat(ci): add compile command to show full ci config

This commit is contained in:
Sebastian Gumprich 2024-02-13 15:05:10 +00:00 committed by Oscar Tovar
parent b0ef650202
commit 323ee63bd1
8 changed files with 304 additions and 8 deletions

View File

@ -2,6 +2,7 @@ package ci
import (
jobArtifactCmd "gitlab.com/gitlab-org/cli/commands/ci/artifact"
ciConfigCmd "gitlab.com/gitlab-org/cli/commands/ci/config"
pipeDeleteCmd "gitlab.com/gitlab-org/cli/commands/ci/delete"
pipeGetCmd "gitlab.com/gitlab-org/cli/commands/ci/get"
legacyCICmd "gitlab.com/gitlab-org/cli/commands/ci/legacyci"
@ -40,5 +41,6 @@ func NewCmdCI(f *cmdutils.Factory) *cobra.Command {
ciCmd.AddCommand(pipeTriggerCmd.NewCmdTrigger(f))
ciCmd.AddCommand(jobArtifactCmd.NewCmdRun(f))
ciCmd.AddCommand(pipeGetCmd.NewCmdGet(f))
ciCmd.AddCommand(ciConfigCmd.NewCmdConfig(f))
return ciCmd
}

View File

@ -0,0 +1,83 @@
package compile
import (
"fmt"
"os"
"strings"
"gitlab.com/gitlab-org/cli/api"
"gitlab.com/gitlab-org/cli/commands/cmdutils"
"github.com/MakeNowJust/heredoc"
"github.com/spf13/cobra"
)
func NewCmdConfigCompile(f *cmdutils.Factory) *cobra.Command {
configCompileCmd := &cobra.Command{
Use: "compile",
Short: "View the fully expanded CI/CD configuration.",
Args: cobra.MaximumNArgs(1),
Example: heredoc.Doc(`
# Uses .gitlab-ci.yml in the current directory
$ glab ci config compile
$ glab ci config compile .gitlab-ci.yml
$ glab ci config compile path/to/.gitlab-ci.yml
`),
RunE: func(cmd *cobra.Command, args []string) error {
path := ".gitlab-ci.yml"
if len(args) == 1 {
path = args[0]
}
return compileRun(f, path)
},
}
configCompileCmd.SetHelpFunc(func(command *cobra.Command, strings []string) {
// Hide "repo"-flag for this command, because it cannot be used on repositories but only on gitlab-ci files
_ = configCompileCmd.Flags().MarkHidden("repo")
configCompileCmd.Parent().HelpFunc()(command, strings)
})
return configCompileCmd
}
func compileRun(f *cmdutils.Factory, path string) error {
var err error
apiClient, err := f.HttpClient()
if err != nil {
return err
}
repo, err := f.BaseRepo()
if err != nil {
return fmt.Errorf("You must be in a GitLab project repository for this action: %w", err)
}
project, err := repo.Project(apiClient)
if err != nil {
return fmt.Errorf("You must be in a GitLab project repository for this action: %w", err)
}
content, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("reading ci config at %s: %w", path, err)
}
compiledResult, err := api.ProjectNamespaceLint(apiClient, project.ID, string(content), "", false, false)
if err != nil {
return err
}
if !compiledResult.Valid {
errorsStr := strings.Join(compiledResult.Errors, ", ")
return fmt.Errorf("could not compile %s: %s", path, errorsStr)
}
fmt.Print(compiledResult.MergedYaml)
return nil
}

View File

@ -0,0 +1,130 @@
package compile
import (
"fmt"
"net/http"
"path"
"testing"
"gitlab.com/gitlab-org/cli/commands/cmdtest"
"gitlab.com/gitlab-org/cli/internal/glrepo"
"gitlab.com/gitlab-org/cli/pkg/httpmock"
"gitlab.com/gitlab-org/cli/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_compileRun(t *testing.T) {
type httpMock struct {
method string
path string
status int
body string
}
tests := []struct {
name string
testFile string
StdOut string
wantErr bool
errMsg string
httpMocks []httpMock
showHaveBaseRepo bool
}{
{
name: "with invalid path specified",
testFile: "WRONG_PATH",
StdOut: "",
wantErr: true,
errMsg: "WRONG_PATH: no such file or directory",
showHaveBaseRepo: true,
httpMocks: []httpMock{
{
http.MethodGet,
"/api/v4/projects/OWNER/REPO",
http.StatusOK,
`{
"id": 123,
"iid": 123
}`,
},
},
},
{
name: "without base repo",
testFile: ".gitlab.ci.yml",
StdOut: "",
wantErr: true,
errMsg: "You must be in a GitLab project repository for this action: no base repository present",
showHaveBaseRepo: false,
httpMocks: []httpMock{},
},
{
name: "when a valid path is specified and yaml is valid",
testFile: ".gitlab-ci.yml",
StdOut: "",
wantErr: false,
errMsg: "",
showHaveBaseRepo: true,
httpMocks: []httpMock{
{
http.MethodGet,
"/api/v4/projects/OWNER/REPO",
http.StatusOK,
`{
"id": 123,
"iid": 123
}`,
},
{
http.MethodPost,
"/api/v4/projects/123/ci/lint",
http.StatusOK,
`{
"valid": true
}`,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fakeHTTP := httpmock.New()
defer fakeHTTP.Verify(t)
for _, mock := range tt.httpMocks {
fakeHTTP.RegisterResponder(mock.method, mock.path, httpmock.NewStringResponse(mock.status, mock.body))
}
args := path.Join(cmdtest.ProjectPath, "test/testdata", tt.testFile)
result, err := runCommand(t, fakeHTTP, false, args, tt.showHaveBaseRepo)
if tt.wantErr {
require.Contains(t, err.Error(), tt.errMsg)
return
}
require.NoError(t, err)
assert.Equal(t, tt.StdOut, result.String())
})
}
}
func runCommand(t *testing.T, rt http.RoundTripper, isTTY bool, cli string, showHaveBaseRepo bool) (*test.CmdOut, error) {
ios, _, stdout, stderr := cmdtest.InitIOStreams(isTTY, "")
factory := cmdtest.InitFactory(ios, rt)
if !showHaveBaseRepo {
factory.BaseRepo = func() (glrepo.Interface, error) {
return nil, fmt.Errorf("no base repository present")
}
}
_, err := factory.HttpClient()
require.Nil(t, err)
cmd := NewCmdConfigCompile(factory)
return cmdtest.ExecuteCommand(cmd, cli, stdout, stderr)
}

View File

@ -0,0 +1,18 @@
package config
import (
ConfigCompileCmd "gitlab.com/gitlab-org/cli/commands/ci/config/compile"
"gitlab.com/gitlab-org/cli/commands/cmdutils"
"github.com/spf13/cobra"
)
func NewCmdConfig(f *cmdutils.Factory) *cobra.Command {
ConfigCmd := &cobra.Command{
Use: "config <command> [flags]",
Short: `Work with GitLab CI/CD configuration.`,
Long: ``,
}
ConfigCmd.AddCommand(ConfigCompileCmd.NewCmdConfigCompile(f))
return ConfigCmd
}

View File

@ -29,7 +29,7 @@ import (
)
var (
projectPath string
ProjectPath string
GlabBinaryPath string
CachedTestFactory *cmdutils.Factory
)
@ -47,9 +47,9 @@ func init() {
if err != nil {
log.Fatalln("Failed to get root directory: ", err)
}
projectPath = strings.TrimSuffix(path.String(), "\n")
if !strings.HasSuffix(projectPath, "/") {
projectPath += "/"
ProjectPath = strings.TrimSuffix(path.String(), "\n")
if !strings.HasSuffix(ProjectPath, "/") {
ProjectPath += "/"
}
}
@ -57,11 +57,11 @@ func InitTest(m *testing.M, suffix string) {
// Build a glab binary with test symbols. If the parent test binary was run
// with coverage enabled, enable coverage on the child binary, too.
var err error
GlabBinaryPath, err = filepath.Abs(os.ExpandEnv(projectPath + "testdata/glab.test"))
GlabBinaryPath, err = filepath.Abs(os.ExpandEnv(ProjectPath + "testdata/glab.test"))
if err != nil {
log.Fatal(err)
}
testCmd := []string{"test", "-c", "-o", GlabBinaryPath, projectPath + "cmd/glab"}
testCmd := []string{"test", "-c", "-o", GlabBinaryPath, ProjectPath + "cmd/glab"}
if coverMode := testing.CoverMode(); coverMode != "" {
testCmd = append(testCmd, "-covermode", coverMode, "-coverpkg", "./...")
}
@ -187,11 +187,11 @@ func CopyTestRepo(log fatalLogger, name string) string {
if name == "" {
name = strconv.Itoa(int(rand.Uint64()))
}
dest, err := filepath.Abs(os.ExpandEnv(projectPath + "test/testdata-" + name))
dest, err := filepath.Abs(os.ExpandEnv(ProjectPath + "test/testdata-" + name))
if err != nil {
log.Fatal(err)
}
src, err := filepath.Abs(os.ExpandEnv(projectPath + "test/testdata"))
src, err := filepath.Abs(os.ExpandEnv(ProjectPath + "test/testdata"))
if err != nil {
log.Fatal(err)
}

View File

@ -0,0 +1,37 @@
---
stage: Create
group: Code Review
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
<!--
This documentation is auto generated by a script.
Please do not edit this file directly. Run `make gen-docs` instead.
-->
# `glab ci config compile`
View the fully expanded CI/CD configuration.
```plaintext
glab ci config compile [flags]
```
## Examples
```plaintext
# Uses .gitlab-ci.yml in the current directory
$ glab ci config compile
$ glab ci config compile .gitlab-ci.yml
$ glab ci config compile path/to/.gitlab-ci.yml
```
## Options inherited from parent commands
```plaintext
--help Show help for command
-R, --repo OWNER/REPO Select another repository using the OWNER/REPO or `GROUP/NAMESPACE/REPO` format or full URL or git URL
```

View File

@ -0,0 +1,25 @@
---
stage: Create
group: Code Review
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
<!--
This documentation is auto generated by a script.
Please do not edit this file directly. Run `make gen-docs` instead.
-->
# `glab ci config`
Work with GitLab CI/CD configuration.
## Options inherited from parent commands
```plaintext
--help Show help for command
-R, --repo OWNER/REPO Select another repository using the OWNER/REPO or `GROUP/NAMESPACE/REPO` format or full URL or git URL
```
## Subcommands
- [`compile`](compile.md)

View File

@ -36,6 +36,7 @@ pipeline
- [`artifact`](artifact.md)
- [`ci`](ci/index.md)
- [`config`](config/index.md)
- [`delete`](delete.md)
- [`get`](get.md)
- [`lint`](lint.md)