-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Closes cli#9103.
- Loading branch information
Showing
3 changed files
with
318 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
package get | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"time" | ||
|
||
"github.com/MakeNowJust/heredoc" | ||
"github.com/cli/cli/v2/api" | ||
"github.com/cli/cli/v2/internal/gh" | ||
"github.com/cli/cli/v2/internal/ghrepo" | ||
"github.com/cli/cli/v2/pkg/cmd/variable/shared" | ||
"github.com/cli/cli/v2/pkg/cmdutil" | ||
"github.com/cli/cli/v2/pkg/iostreams" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
type GetOptions struct { | ||
HttpClient func() (*http.Client, error) | ||
IO *iostreams.IOStreams | ||
Config func() (gh.Config, error) | ||
BaseRepo func() (ghrepo.Interface, error) | ||
|
||
VariableName string | ||
OrgName string | ||
EnvName string | ||
} | ||
|
||
type Variable struct { | ||
Name string `json:"name"` | ||
Value string `json:"value"` | ||
UpdatedAt time.Time `json:"updated_at"` | ||
Visibility shared.Visibility `json:"visibility"` | ||
SelectedReposURL string `json:"selected_repositories_url"` | ||
NumSelectedRepos int `json:"num_selected_repos"` | ||
} | ||
|
||
func NewCmdGet(f *cmdutil.Factory, runF func(*GetOptions) error) *cobra.Command { | ||
opts := &GetOptions{ | ||
IO: f.IOStreams, | ||
Config: f.Config, | ||
HttpClient: f.HttpClient, | ||
} | ||
|
||
cmd := &cobra.Command{ | ||
Use: "get <variable-name>", | ||
Short: "Get variables", | ||
Long: heredoc.Doc(` | ||
Get a variable on one of the following levels: | ||
- repository (default): available to GitHub Actions runs or Dependabot in a repository | ||
- environment: available to GitHub Actions runs for a deployment environment in a repository | ||
- organization: available to GitHub Actions runs or Dependabot within an organization | ||
`), | ||
Args: cobra.ExactArgs(1), | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
// support `-R, --repo` override | ||
opts.BaseRepo = f.BaseRepo | ||
|
||
if err := cmdutil.MutuallyExclusive("specify only one of `--org` or `--env`", opts.OrgName != "", opts.EnvName != ""); err != nil { | ||
return err | ||
} | ||
|
||
opts.VariableName = args[0] | ||
|
||
if runF != nil { | ||
return runF(opts) | ||
} | ||
|
||
return getRun(opts) | ||
}, | ||
} | ||
cmd.Flags().StringVarP(&opts.OrgName, "org", "o", "", "Get a variable for an organization") | ||
cmd.Flags().StringVarP(&opts.EnvName, "env", "e", "", "Get a variable for an environment") | ||
|
||
return cmd | ||
} | ||
|
||
func getRun(opts *GetOptions) error { | ||
c, err := opts.HttpClient() | ||
if err != nil { | ||
return fmt.Errorf("could not create http client: %w", err) | ||
} | ||
client := api.NewClientFromHTTP(c) | ||
|
||
orgName := opts.OrgName | ||
envName := opts.EnvName | ||
|
||
variableEntity, err := shared.GetVariableEntity(orgName, envName) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var baseRepo ghrepo.Interface | ||
if variableEntity == shared.Repository || variableEntity == shared.Environment { | ||
baseRepo, err = opts.BaseRepo() | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
var path string | ||
switch variableEntity { | ||
case shared.Organization: | ||
path = fmt.Sprintf("orgs/%s/actions/variables/%s", orgName, opts.VariableName) | ||
case shared.Environment: | ||
path = fmt.Sprintf("repos/%s/environments/%s/variables/%s", ghrepo.FullName(baseRepo), envName, opts.VariableName) | ||
case shared.Repository: | ||
path = fmt.Sprintf("repos/%s/actions/variables/%s", ghrepo.FullName(baseRepo), opts.VariableName) | ||
} | ||
|
||
cfg, err := opts.Config() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
host, _ := cfg.Authentication().DefaultHost() | ||
|
||
response := &Variable{} | ||
|
||
err = client.REST(host, "GET", path, nil, &response) | ||
if err != nil { | ||
return fmt.Errorf("failed to get variable %s: %w", opts.VariableName, err) | ||
} | ||
|
||
fmt.Fprintf(opts.IO.Out, "%s", response.Value) | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
package get | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"net/http" | ||
"testing" | ||
|
||
"github.com/cli/cli/v2/internal/config" | ||
"github.com/cli/cli/v2/internal/gh" | ||
"github.com/cli/cli/v2/internal/ghrepo" | ||
"github.com/cli/cli/v2/pkg/cmdutil" | ||
"github.com/cli/cli/v2/pkg/httpmock" | ||
"github.com/cli/cli/v2/pkg/iostreams" | ||
"github.com/google/shlex" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestNewCmdList(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
cli string | ||
wants GetOptions | ||
}{ | ||
{ | ||
name: "repo", | ||
cli: "FOO", | ||
wants: GetOptions{ | ||
OrgName: "", | ||
VariableName: "FOO", | ||
}, | ||
}, | ||
{ | ||
name: "org", | ||
cli: "-oUmbrellaCorporation BAR", | ||
wants: GetOptions{ | ||
OrgName: "UmbrellaCorporation", | ||
VariableName: "BAR", | ||
}, | ||
}, | ||
{ | ||
name: "env", | ||
cli: "-eDevelopment BAZ", | ||
wants: GetOptions{ | ||
EnvName: "Development", | ||
VariableName: "BAZ", | ||
}, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
ios, _, _, _ := iostreams.Test() | ||
f := &cmdutil.Factory{ | ||
IOStreams: ios, | ||
} | ||
|
||
argv, err := shlex.Split(tt.cli) | ||
assert.NoError(t, err) | ||
|
||
var gotOpts *GetOptions | ||
cmd := NewCmdGet(f, func(opts *GetOptions) error { | ||
gotOpts = opts | ||
return nil | ||
}) | ||
cmd.SetArgs(argv) | ||
cmd.SetIn(&bytes.Buffer{}) | ||
cmd.SetOut(&bytes.Buffer{}) | ||
cmd.SetErr(&bytes.Buffer{}) | ||
|
||
_, err = cmd.ExecuteC() | ||
assert.NoError(t, err) | ||
|
||
assert.Equal(t, tt.wants.OrgName, gotOpts.OrgName) | ||
assert.Equal(t, tt.wants.EnvName, gotOpts.EnvName) | ||
assert.Equal(t, tt.wants.VariableName, gotOpts.VariableName) | ||
}) | ||
} | ||
} | ||
|
||
func Test_getRun(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
tty bool | ||
opts *GetOptions | ||
wantOut string | ||
}{ | ||
{ | ||
name: "repo tty", | ||
tty: true, | ||
opts: &GetOptions{ | ||
VariableName: "VARIABLE_ONE", | ||
}, | ||
wantOut: "one", | ||
}, | ||
{ | ||
name: "repo not tty", | ||
tty: false, | ||
opts: &GetOptions{ | ||
VariableName: "VARIABLE_ONE", | ||
}, | ||
wantOut: "one", | ||
}, | ||
{ | ||
name: "org tty", | ||
tty: true, | ||
opts: &GetOptions{ | ||
OrgName: "UmbrellaCorporation", | ||
VariableName: "VARIABLE_ONE", | ||
}, | ||
wantOut: "org_one", | ||
}, | ||
{ | ||
name: "org not tty", | ||
tty: false, | ||
opts: &GetOptions{ | ||
OrgName: "UmbrellaCorporation", | ||
VariableName: "VARIABLE_ONE", | ||
}, | ||
wantOut: "org_one", | ||
}, | ||
{ | ||
name: "env tty", | ||
tty: true, | ||
opts: &GetOptions{ | ||
EnvName: "Development", | ||
VariableName: "VARIABLE_ONE", | ||
}, | ||
wantOut: "one", | ||
}, | ||
{ | ||
name: "env not tty", | ||
tty: false, | ||
opts: &GetOptions{ | ||
EnvName: "Development", | ||
VariableName: "VARIABLE_ONE", | ||
}, | ||
wantOut: "one", | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
reg := &httpmock.Registry{} | ||
defer reg.Verify(t) | ||
|
||
path := fmt.Sprintf("repos/owner/repo/actions/variables/%s", tt.opts.VariableName) | ||
if tt.opts.EnvName != "" { | ||
path = fmt.Sprintf("repos/owner/repo/environments/%s/variables/%s", tt.opts.EnvName, tt.opts.VariableName) | ||
} else if tt.opts.OrgName != "" { | ||
path = fmt.Sprintf("orgs/%s/actions/variables/%s", tt.opts.OrgName, tt.opts.VariableName) | ||
} | ||
|
||
payload := Variable{ | ||
Name: "VARIABLE_ONE", | ||
Value: "one", | ||
} | ||
if tt.opts.OrgName != "" { | ||
payload = Variable{ | ||
Name: "VARIABLE_ONE", | ||
Value: "org_one", | ||
} | ||
} | ||
|
||
reg.Register(httpmock.REST("GET", path), httpmock.JSONResponse(payload)) | ||
|
||
ios, _, stdout, _ := iostreams.Test() | ||
|
||
ios.SetStdoutTTY(tt.tty) | ||
|
||
tt.opts.IO = ios | ||
tt.opts.BaseRepo = func() (ghrepo.Interface, error) { | ||
return ghrepo.FromFullName("owner/repo") | ||
} | ||
tt.opts.HttpClient = func() (*http.Client, error) { | ||
return &http.Client{Transport: reg}, nil | ||
} | ||
tt.opts.Config = func() (gh.Config, error) { | ||
return config.NewBlankConfig(), nil | ||
} | ||
|
||
err := getRun(tt.opts) | ||
assert.NoError(t, err) | ||
|
||
assert.Equal(t, tt.wantOut, stdout.String()) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters