Skip to content

Commit 5fa3c15

Browse files
Merge pull request #1164 from CircleCI-Public/test-72/run-autodetect-env
TEST-72 Update run command to autodetect envars
2 parents 340a105 + c9bea5f commit 5fa3c15

File tree

4 files changed

+103
-21
lines changed

4 files changed

+103
-21
lines changed

api/project/project_rest.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import (
88
"github.com/CircleCI-Public/circleci-cli/settings"
99
)
1010

11-
type projectRestClient struct {
11+
type ProjectRestClient struct {
1212
client *rest.Client
1313
}
1414

15-
var _ ProjectClient = &projectRestClient{}
15+
var _ ProjectClient = &ProjectRestClient{}
1616

1717
type listProjectEnvVarsParams struct {
1818
vcs string
@@ -60,10 +60,10 @@ type projectInfo struct {
6060
Id string `json:"id"`
6161
}
6262

63-
// NewProjectRestClient returns a new projectRestClient satisfying the api.ProjectInterface
63+
// NewProjectRestClient returns a new ProjectRestClient satisfying the api.ProjectInterface
6464
// interface via the REST API.
65-
func NewProjectRestClient(config settings.Config) (*projectRestClient, error) {
66-
client := &projectRestClient{
65+
func NewProjectRestClient(config settings.Config) (*ProjectRestClient, error) {
66+
client := &ProjectRestClient{
6767
client: rest.NewFromConfig(config.Host, &config),
6868
}
6969
return client, nil
@@ -72,7 +72,7 @@ func NewProjectRestClient(config settings.Config) (*projectRestClient, error) {
7272
// ListAllEnvironmentVariables returns all of the environment variables owned by the
7373
// given project. Note that pagination is not supported - we get all
7474
// pages of env vars and return them all.
75-
func (p *projectRestClient) ListAllEnvironmentVariables(vcs, org, project string) ([]*ProjectEnvironmentVariable, error) {
75+
func (p *ProjectRestClient) ListAllEnvironmentVariables(vcs, org, project string) ([]*ProjectEnvironmentVariable, error) {
7676
res := make([]*ProjectEnvironmentVariable, 0)
7777
var nextPageToken string
7878
for {
@@ -102,7 +102,7 @@ func (p *projectRestClient) ListAllEnvironmentVariables(vcs, org, project string
102102
return res, nil
103103
}
104104

105-
func (c *projectRestClient) listEnvironmentVariables(params *listProjectEnvVarsParams) (*listAllProjectEnvVarsResponse, error) {
105+
func (c *ProjectRestClient) listEnvironmentVariables(params *listProjectEnvVarsParams) (*listAllProjectEnvVarsResponse, error) {
106106
path := fmt.Sprintf("project/%s/%s/%s/envvar", params.vcs, params.org, params.project)
107107
urlParams := url.Values{}
108108
if params.pageToken != "" {
@@ -124,7 +124,7 @@ func (c *projectRestClient) listEnvironmentVariables(params *listProjectEnvVarsP
124124

125125
// GetEnvironmentVariable retrieves and returns a variable with the given name.
126126
// If the response status code is 404, nil is returned.
127-
func (c *projectRestClient) GetEnvironmentVariable(vcs string, org string, project string, envName string) (*ProjectEnvironmentVariable, error) {
127+
func (c *ProjectRestClient) GetEnvironmentVariable(vcs string, org string, project string, envName string) (*ProjectEnvironmentVariable, error) {
128128
path := fmt.Sprintf("project/%s/%s/%s/envvar/%s", vcs, org, project, envName)
129129
req, err := c.client.NewRequest("GET", &url.URL{Path: path}, nil)
130130
if err != nil {
@@ -147,7 +147,7 @@ func (c *projectRestClient) GetEnvironmentVariable(vcs string, org string, proje
147147
}, nil
148148
}
149149

150-
func (c *projectRestClient) CreateProject(vcs string, org string, name string) (*CreateProjectInfo, error) {
150+
func (c *ProjectRestClient) CreateProject(vcs string, org string, name string) (*CreateProjectInfo, error) {
151151
orgSlug := fmt.Sprintf("%s/%s", vcs, org)
152152

153153
path := fmt.Sprintf("organization/%s/project", orgSlug)
@@ -178,7 +178,7 @@ func (c *projectRestClient) CreateProject(vcs string, org string, name string) (
178178

179179
// CreateEnvironmentVariable creates a variable on the given project.
180180
// This returns the variable if successfully created.
181-
func (c *projectRestClient) CreateEnvironmentVariable(vcs string, org string, project string, v ProjectEnvironmentVariable) (*ProjectEnvironmentVariable, error) {
181+
func (c *ProjectRestClient) CreateEnvironmentVariable(vcs string, org string, project string, v ProjectEnvironmentVariable) (*ProjectEnvironmentVariable, error) {
182182
path := fmt.Sprintf("project/%s/%s/%s/envvar", vcs, org, project)
183183
req, err := c.client.NewRequest("POST", &url.URL{Path: path}, &createProjectEnvVarRequest{
184184
Name: v.Name,
@@ -200,7 +200,7 @@ func (c *projectRestClient) CreateEnvironmentVariable(vcs string, org string, pr
200200
}
201201

202202
// ProjectInfo retrieves and returns the project info.
203-
func (c *projectRestClient) ProjectInfo(vcs string, org string, project string) (*ProjectInfo, error) {
203+
func (c *ProjectRestClient) ProjectInfo(vcs string, org string, project string) (*ProjectInfo, error) {
204204
path := fmt.Sprintf("project/%s/%s/%s", vcs, org, project)
205205
req, err := c.client.NewRequest("GET", &url.URL{Path: path}, nil)
206206
if err != nil {

cmd/run.go

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@ import (
44
"fmt"
55
"os"
66
"os/exec"
7+
"strings"
78

8-
"github.com/CircleCI-Public/circleci-cli/settings"
99
"github.com/spf13/cobra"
10+
11+
"github.com/CircleCI-Public/circleci-cli/api/project"
12+
"github.com/CircleCI-Public/circleci-cli/git"
13+
"github.com/CircleCI-Public/circleci-cli/settings"
1014
)
1115

1216
func newRunCommand(config *settings.Config) *cobra.Command {
@@ -36,6 +40,11 @@ you can run it with: circleci run foo [args...]`,
3640
pluginName := args[0]
3741
pluginArgs := args[1:]
3842

43+
projectClient, err := project.NewProjectRestClient(*config)
44+
if err != nil {
45+
return err
46+
}
47+
3948
// Construct the plugin binary name
4049
binaryName := fmt.Sprintf("circleci-%s", pluginName)
4150

@@ -47,11 +56,23 @@ you can run it with: circleci run foo [args...]`,
4756

4857
// Create the command to execute the plugin
4958
pluginCmd := exec.Command(binaryPath, pluginArgs...)
50-
5159
// Connect stdin, stdout, and stderr to the current process
5260
pluginCmd.Stdin = os.Stdin
53-
pluginCmd.Stdout = os.Stdout
54-
pluginCmd.Stderr = os.Stderr
61+
pluginCmd.Stdout = config.Stdout
62+
pluginCmd.Stderr = config.Stderr
63+
pluginCmd.Env = os.Environ()
64+
65+
info := autoDetectProject(projectClient)
66+
configEnv := map[string]string{
67+
"CIRCLE_URL": config.Host,
68+
"CIRCLE_TOKEN": config.Token,
69+
}
70+
if info != nil {
71+
configEnv["CIRCLE_PROJECT_ID"] = info.Id
72+
}
73+
for k, v := range configEnv {
74+
pluginCmd.Env = append(pluginCmd.Env, fmt.Sprintf("%s=%s", k, v))
75+
}
5576

5677
// Run the plugin
5778
if err := pluginCmd.Run(); err != nil {
@@ -64,3 +85,19 @@ you can run it with: circleci run foo [args...]`,
6485

6586
return runCmd
6687
}
88+
89+
func autoDetectProject(projectClient project.ProjectClient) *project.ProjectInfo {
90+
remote, err := git.InferProjectFromGitRemotes()
91+
if err != nil {
92+
_, _ = fmt.Fprintln(os.Stderr, "unable to autodetect project from git remotes")
93+
return nil
94+
}
95+
96+
info, err := projectClient.ProjectInfo(strings.ToLower(string(remote.VcsType)), remote.Organization, remote.Project)
97+
if err != nil {
98+
_, _ = fmt.Fprintf(os.Stderr, "failed to get project info: %s\n", err)
99+
return nil
100+
}
101+
102+
return info
103+
}

cmd/run_test.go

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
11
package cmd
22

33
import (
4+
"bytes"
5+
"net/http"
6+
"net/http/httptest"
47
"os"
58
"path/filepath"
69
"runtime"
710

8-
"github.com/CircleCI-Public/circleci-cli/settings"
911
. "github.com/onsi/ginkgo"
1012
. "github.com/onsi/gomega"
13+
14+
"github.com/CircleCI-Public/circleci-cli/settings"
1115
)
1216

1317
var _ = Describe("run", func() {
1418
var (
1519
tempDir string
1620
pluginPath string
1721
config *settings.Config
22+
fakeSrv *httptest.Server
23+
stdout bytes.Buffer
24+
stderr bytes.Buffer
1825
)
1926

2027
BeforeEach(func() {
@@ -23,23 +30,51 @@ var _ = Describe("run", func() {
2330
Expect(err).ToNot(HaveOccurred())
2431

2532
// Create a test plugin
33+
var pluginScript string
2634
pluginPath = filepath.Join(tempDir, "circleci-test-plugin")
2735
if runtime.GOOS == "windows" {
2836
pluginPath = pluginPath + ".bat"
29-
}
30-
pluginScript := `#!/bin/bash
37+
pluginScript = `#!/bin/bash
38+
echo "Plugin executed"
39+
echo "Args: %@%"
40+
echo "Project ID: %CIRCLE_PROJECT_ID%"
41+
echo "Circle URL: %CIRCLE_URL%"
42+
echo "Circle Token: %CIRCLE_TOKEN%"
43+
exit 0
44+
`
45+
} else {
46+
pluginScript = `#!/bin/bash
3147
echo "Plugin executed"
3248
echo "Args: $@"
49+
echo "Project ID: $CIRCLE_PROJECT_ID"
50+
echo "Circle URL: $CIRCLE_URL"
51+
echo "Circle Token: $CIRCLE_TOKEN"
3352
exit 0
3453
`
54+
}
3555
err = os.WriteFile(pluginPath, []byte(pluginScript), 0755)
3656
Expect(err).ToNot(HaveOccurred())
3757

38-
config = &settings.Config{}
58+
fakeSrv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
59+
w.Header().Set("Content-Type", "application/json")
60+
w.WriteHeader(http.StatusOK)
61+
_, err := w.Write([]byte(`{"id": "test-project-id"}`))
62+
Expect(err).ToNot(HaveOccurred())
63+
}))
64+
65+
config = &settings.Config{
66+
Host: fakeSrv.URL,
67+
Token: "test-token",
68+
Stdout: &stdout,
69+
Stderr: &stderr,
70+
}
71+
err = config.WithHTTPClient()
72+
Expect(err).ToNot(HaveOccurred())
3973
})
4074

4175
AfterEach(func() {
4276
os.RemoveAll(tempDir)
77+
fakeSrv.Close()
4378
})
4479

4580
Describe("plugin execution", func() {
@@ -52,6 +87,9 @@ exit 0
5287
cmd := newRunCommand(config)
5388
cmd.SetArgs([]string{"test-plugin", "arg1", "arg2"})
5489
err := cmd.Execute()
90+
Expect(stdout.String()).To(ContainSubstring("Project ID: test-project-id"))
91+
Expect(stdout.String()).To(ContainSubstring("Circle Token: test-token"))
92+
Expect(stdout.String()).To(ContainSubstring("Circle URL: %s", fakeSrv.URL))
5593
Expect(err).ToNot(HaveOccurred())
5694
})
5795

settings/settings.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"crypto/x509"
66
"errors"
77
"fmt"
8+
"io"
89
"net/http"
910
"net/url"
1011
"os"
@@ -14,11 +15,12 @@ import (
1415
"strings"
1516
"time"
1617

17-
yaml "gopkg.in/yaml.v3"
18+
"gopkg.in/yaml.v3"
19+
20+
"github.com/spf13/afero"
1821

1922
"github.com/CircleCI-Public/circleci-cli/api/header"
2023
"github.com/CircleCI-Public/circleci-cli/data"
21-
"github.com/spf13/afero"
2224
)
2325

2426
var (
@@ -50,6 +52,8 @@ type Config struct {
5052
MockTelemetry string `yaml:"-"`
5153
OrbPublishing OrbPublishingInfo `yaml:"orb_publishing"`
5254
TempDir string `yaml:"temp_dir,omitempty"`
55+
Stdout io.Writer `yaml:"-"`
56+
Stderr io.Writer `yaml:"-"`
5357
}
5458

5559
type OrbPublishingInfo struct {
@@ -162,6 +166,9 @@ func (cfg *Config) LoadFromDisk() error {
162166
return nil
163167
}
164168

169+
cfg.Stdout = os.Stdout
170+
cfg.Stderr = os.Stderr
171+
165172
return cfg.WithHTTPClient()
166173
}
167174

0 commit comments

Comments
 (0)