Add a gh variable get FOO command (#9106)
Closes #9103. --------- Co-authored-by: William Martin <williammartin@github.com>
This commit is contained in:
parent
99568e6345
commit
08a5589abe
3 changed files with 336 additions and 0 deletions
132
pkg/cmd/variable/get/get.go
Normal file
132
pkg/cmd/variable/get/get.go
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
package get
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"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 getVariableResponse struct {
|
||||
Value string `json:"value"`
|
||||
// Other available but unused fields
|
||||
// Name string `json:"name"`
|
||||
// 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()
|
||||
|
||||
var response getVariableResponse
|
||||
if err = client.REST(host, "GET", path, nil, &response); err != nil {
|
||||
var httpErr api.HTTPError
|
||||
if errors.As(err, &httpErr) && httpErr.StatusCode == http.StatusNotFound {
|
||||
return fmt.Errorf("variable %s was not found", opts.VariableName)
|
||||
}
|
||||
|
||||
return fmt.Errorf("failed to get variable %s: %w", opts.VariableName, err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(opts.IO.Out, "%s\n", response.Value)
|
||||
|
||||
return nil
|
||||
}
|
||||
202
pkg/cmd/variable/get/get_test.go
Normal file
202
pkg/cmd/variable/get/get_test.go
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
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"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewCmdGet(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cli string
|
||||
wants GetOptions
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "repo",
|
||||
cli: "FOO",
|
||||
wants: GetOptions{
|
||||
OrgName: "",
|
||||
VariableName: "FOO",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "org",
|
||||
cli: "-o TestOrg BAR",
|
||||
wants: GetOptions{
|
||||
OrgName: "TestOrg",
|
||||
VariableName: "BAR",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "env",
|
||||
cli: "-e Development BAZ",
|
||||
wants: GetOptions{
|
||||
EnvName: "Development",
|
||||
VariableName: "BAZ",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "org and env",
|
||||
cli: "-o TestOrg -e Development QUX",
|
||||
wantErr: cmdutil.FlagErrorf("%s", "specify only one of `--org` or `--env`"),
|
||||
},
|
||||
}
|
||||
|
||||
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()
|
||||
if tt.wantErr != nil {
|
||||
require.Equal(t, err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, tt.wants.OrgName, gotOpts.OrgName)
|
||||
require.Equal(t, tt.wants.EnvName, gotOpts.EnvName)
|
||||
require.Equal(t, tt.wants.VariableName, gotOpts.VariableName)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getRun(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
opts *GetOptions
|
||||
httpStubs func(*httpmock.Registry)
|
||||
wantOut string
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "getting repo variable",
|
||||
opts: &GetOptions{
|
||||
VariableName: "VARIABLE_ONE",
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", "repos/owner/repo/actions/variables/VARIABLE_ONE"),
|
||||
httpmock.JSONResponse(getVariableResponse{
|
||||
Value: "repo_var",
|
||||
}))
|
||||
},
|
||||
wantOut: "repo_var\n",
|
||||
},
|
||||
{
|
||||
name: "getting org variable",
|
||||
opts: &GetOptions{
|
||||
OrgName: "TestOrg",
|
||||
VariableName: "VARIABLE_ONE",
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", "orgs/TestOrg/actions/variables/VARIABLE_ONE"),
|
||||
httpmock.JSONResponse(getVariableResponse{
|
||||
Value: "org_var",
|
||||
}))
|
||||
},
|
||||
wantOut: "org_var\n",
|
||||
},
|
||||
{
|
||||
name: "getting env variable",
|
||||
opts: &GetOptions{
|
||||
EnvName: "Development",
|
||||
VariableName: "VARIABLE_ONE",
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", "repos/owner/repo/environments/Development/variables/VARIABLE_ONE"),
|
||||
httpmock.JSONResponse(getVariableResponse{
|
||||
Value: "env_var",
|
||||
}))
|
||||
},
|
||||
wantOut: "env_var\n",
|
||||
},
|
||||
{
|
||||
name: "when the variable is not found, an error is returned",
|
||||
opts: &GetOptions{
|
||||
VariableName: "VARIABLE_ONE",
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", "repos/owner/repo/actions/variables/VARIABLE_ONE"),
|
||||
httpmock.StatusStringResponse(404, "not found"),
|
||||
)
|
||||
},
|
||||
wantErr: fmt.Errorf("variable VARIABLE_ONE was not found"),
|
||||
},
|
||||
{
|
||||
name: "when getting any variable from API fails, the error is bubbled with context",
|
||||
opts: &GetOptions{
|
||||
VariableName: "VARIABLE_ONE",
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", "repos/owner/repo/actions/variables/VARIABLE_ONE"),
|
||||
httpmock.StatusStringResponse(400, "not found"),
|
||||
)
|
||||
},
|
||||
wantErr: fmt.Errorf("failed to get variable VARIABLE_ONE: HTTP 400 (https://api.github.com/repos/owner/repo/actions/variables/VARIABLE_ONE)"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
var runTest = func(tty bool) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
reg := &httpmock.Registry{}
|
||||
tt.httpStubs(reg)
|
||||
defer reg.Verify(t)
|
||||
|
||||
ios, _, stdout, _ := iostreams.Test()
|
||||
ios.SetStdoutTTY(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)
|
||||
if err != nil {
|
||||
require.EqualError(t, tt.wantErr, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.wantOut, stdout.String())
|
||||
}
|
||||
}
|
||||
|
||||
t.Run(tt.name+" tty", runTest(true))
|
||||
t.Run(tt.name+" no-tty", runTest(false))
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package variable
|
|||
import (
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
cmdDelete "github.com/cli/cli/v2/pkg/cmd/variable/delete"
|
||||
cmdGet "github.com/cli/cli/v2/pkg/cmd/variable/get"
|
||||
cmdList "github.com/cli/cli/v2/pkg/cmd/variable/list"
|
||||
cmdSet "github.com/cli/cli/v2/pkg/cmd/variable/set"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
|
|
@ -21,6 +22,7 @@ func NewCmdVariable(f *cmdutil.Factory) *cobra.Command {
|
|||
|
||||
cmdutil.EnableRepoOverride(cmd, f)
|
||||
|
||||
cmd.AddCommand(cmdGet.NewCmdGet(f, nil))
|
||||
cmd.AddCommand(cmdSet.NewCmdSet(f, nil))
|
||||
cmd.AddCommand(cmdList.NewCmdList(f, nil))
|
||||
cmd.AddCommand(cmdDelete.NewCmdDelete(f, nil))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue