gh codespace commands now respect the following environment variables: - GITHUB_SERVER_URL: typically "https://github.com" - GITHUB_API_URL: typically "https://api.github.com" - INTERNAL_VSCS_TARGET_URL: typically "https://online.visualstudio.com" - GITHUB_TOKEN when CODESPACES is set, even if the host connecting to isn't "github.com". This set of changes ensures that `gh codespace` commands automatically connect to the right API endpoints when gh is used within a codespace. Co-authored-by: Mislav Marohnić <mislav@github.com>
175 lines
5.4 KiB
Go
175 lines
5.4 KiB
Go
package root
|
|
|
|
import (
|
|
"net/http"
|
|
"os"
|
|
"sync"
|
|
|
|
"github.com/MakeNowJust/heredoc"
|
|
codespacesAPI "github.com/cli/cli/v2/internal/codespaces/api"
|
|
actionsCmd "github.com/cli/cli/v2/pkg/cmd/actions"
|
|
aliasCmd "github.com/cli/cli/v2/pkg/cmd/alias"
|
|
apiCmd "github.com/cli/cli/v2/pkg/cmd/api"
|
|
authCmd "github.com/cli/cli/v2/pkg/cmd/auth"
|
|
browseCmd "github.com/cli/cli/v2/pkg/cmd/browse"
|
|
codespaceCmd "github.com/cli/cli/v2/pkg/cmd/codespace"
|
|
completionCmd "github.com/cli/cli/v2/pkg/cmd/completion"
|
|
configCmd "github.com/cli/cli/v2/pkg/cmd/config"
|
|
extensionCmd "github.com/cli/cli/v2/pkg/cmd/extension"
|
|
"github.com/cli/cli/v2/pkg/cmd/factory"
|
|
gistCmd "github.com/cli/cli/v2/pkg/cmd/gist"
|
|
gpgKeyCmd "github.com/cli/cli/v2/pkg/cmd/gpg-key"
|
|
issueCmd "github.com/cli/cli/v2/pkg/cmd/issue"
|
|
prCmd "github.com/cli/cli/v2/pkg/cmd/pr"
|
|
releaseCmd "github.com/cli/cli/v2/pkg/cmd/release"
|
|
repoCmd "github.com/cli/cli/v2/pkg/cmd/repo"
|
|
creditsCmd "github.com/cli/cli/v2/pkg/cmd/repo/credits"
|
|
runCmd "github.com/cli/cli/v2/pkg/cmd/run"
|
|
secretCmd "github.com/cli/cli/v2/pkg/cmd/secret"
|
|
sshKeyCmd "github.com/cli/cli/v2/pkg/cmd/ssh-key"
|
|
versionCmd "github.com/cli/cli/v2/pkg/cmd/version"
|
|
workflowCmd "github.com/cli/cli/v2/pkg/cmd/workflow"
|
|
"github.com/cli/cli/v2/pkg/cmdutil"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
func NewCmdRoot(f *cmdutil.Factory, version, buildDate string) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "gh <command> <subcommand> [flags]",
|
|
Short: "GitHub CLI",
|
|
Long: `Work seamlessly with GitHub from the command line.`,
|
|
|
|
SilenceErrors: true,
|
|
SilenceUsage: true,
|
|
Example: heredoc.Doc(`
|
|
$ gh issue create
|
|
$ gh repo clone cli/cli
|
|
$ gh pr checkout 321
|
|
`),
|
|
Annotations: map[string]string{
|
|
"help:feedback": heredoc.Doc(`
|
|
Open an issue using 'gh issue create -R github.com/cli/cli'
|
|
`),
|
|
"help:environment": heredoc.Doc(`
|
|
See 'gh help environment' for the list of supported environment variables.
|
|
`),
|
|
},
|
|
}
|
|
|
|
cmd.SetOut(f.IOStreams.Out)
|
|
cmd.SetErr(f.IOStreams.ErrOut)
|
|
|
|
cmd.PersistentFlags().Bool("help", false, "Show help for command")
|
|
cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
|
|
rootHelpFunc(f, cmd, args)
|
|
})
|
|
cmd.SetUsageFunc(rootUsageFunc)
|
|
cmd.SetFlagErrorFunc(rootFlagErrorFunc)
|
|
|
|
formattedVersion := versionCmd.Format(version, buildDate)
|
|
cmd.SetVersionTemplate(formattedVersion)
|
|
cmd.Version = formattedVersion
|
|
cmd.Flags().Bool("version", false, "Show gh version")
|
|
|
|
// Child commands
|
|
cmd.AddCommand(versionCmd.NewCmdVersion(f, version, buildDate))
|
|
cmd.AddCommand(actionsCmd.NewCmdActions(f))
|
|
cmd.AddCommand(aliasCmd.NewCmdAlias(f))
|
|
cmd.AddCommand(authCmd.NewCmdAuth(f))
|
|
cmd.AddCommand(configCmd.NewCmdConfig(f))
|
|
cmd.AddCommand(creditsCmd.NewCmdCredits(f, nil))
|
|
cmd.AddCommand(gistCmd.NewCmdGist(f))
|
|
cmd.AddCommand(gpgKeyCmd.NewCmdGPGKey(f))
|
|
cmd.AddCommand(completionCmd.NewCmdCompletion(f.IOStreams))
|
|
cmd.AddCommand(extensionCmd.NewCmdExtension(f))
|
|
cmd.AddCommand(secretCmd.NewCmdSecret(f))
|
|
cmd.AddCommand(sshKeyCmd.NewCmdSSHKey(f))
|
|
cmd.AddCommand(newCodespaceCmd(f))
|
|
|
|
// the `api` command should not inherit any extra HTTP headers
|
|
bareHTTPCmdFactory := *f
|
|
bareHTTPCmdFactory.HttpClient = bareHTTPClient(f, version)
|
|
|
|
cmd.AddCommand(apiCmd.NewCmdApi(&bareHTTPCmdFactory, nil))
|
|
|
|
// below here at the commands that require the "intelligent" BaseRepo resolver
|
|
repoResolvingCmdFactory := *f
|
|
repoResolvingCmdFactory.BaseRepo = factory.SmartBaseRepoFunc(f)
|
|
|
|
cmd.AddCommand(browseCmd.NewCmdBrowse(&repoResolvingCmdFactory, nil))
|
|
cmd.AddCommand(prCmd.NewCmdPR(&repoResolvingCmdFactory))
|
|
cmd.AddCommand(issueCmd.NewCmdIssue(&repoResolvingCmdFactory))
|
|
cmd.AddCommand(releaseCmd.NewCmdRelease(&repoResolvingCmdFactory))
|
|
cmd.AddCommand(repoCmd.NewCmdRepo(&repoResolvingCmdFactory))
|
|
cmd.AddCommand(runCmd.NewCmdRun(&repoResolvingCmdFactory))
|
|
cmd.AddCommand(workflowCmd.NewCmdWorkflow(&repoResolvingCmdFactory))
|
|
|
|
// Help topics
|
|
cmd.AddCommand(NewHelpTopic("environment"))
|
|
cmd.AddCommand(NewHelpTopic("formatting"))
|
|
cmd.AddCommand(NewHelpTopic("mintty"))
|
|
referenceCmd := NewHelpTopic("reference")
|
|
referenceCmd.SetHelpFunc(referenceHelpFn(f.IOStreams))
|
|
cmd.AddCommand(referenceCmd)
|
|
|
|
cmdutil.DisableAuthCheck(cmd)
|
|
|
|
// this needs to appear last:
|
|
referenceCmd.Long = referenceLong(cmd)
|
|
return cmd
|
|
}
|
|
|
|
func bareHTTPClient(f *cmdutil.Factory, version string) func() (*http.Client, error) {
|
|
return func() (*http.Client, error) {
|
|
cfg, err := f.Config()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return factory.NewHTTPClient(f.IOStreams, cfg, version, false)
|
|
}
|
|
}
|
|
|
|
func newCodespaceCmd(f *cmdutil.Factory) *cobra.Command {
|
|
serverURL := os.Getenv("GITHUB_SERVER_URL")
|
|
apiURL := os.Getenv("GITHUB_API_URL")
|
|
vscsURL := os.Getenv("INTERNAL_VSCS_TARGET_URL")
|
|
app := codespaceCmd.NewApp(
|
|
f.IOStreams,
|
|
codespacesAPI.New(
|
|
serverURL,
|
|
apiURL,
|
|
vscsURL,
|
|
&lazyLoadedHTTPClient{factory: f},
|
|
),
|
|
)
|
|
cmd := codespaceCmd.NewRootCmd(app)
|
|
cmd.Use = "codespace"
|
|
cmd.Aliases = []string{"cs"}
|
|
cmd.Annotations = map[string]string{"IsCore": "true"}
|
|
return cmd
|
|
}
|
|
|
|
type lazyLoadedHTTPClient struct {
|
|
factory *cmdutil.Factory
|
|
|
|
httpClientMu sync.RWMutex // guards httpClient
|
|
httpClient *http.Client
|
|
}
|
|
|
|
func (l *lazyLoadedHTTPClient) Do(req *http.Request) (*http.Response, error) {
|
|
l.httpClientMu.RLock()
|
|
httpClient := l.httpClient
|
|
l.httpClientMu.RUnlock()
|
|
|
|
if httpClient == nil {
|
|
var err error
|
|
l.httpClientMu.Lock()
|
|
l.httpClient, err = l.factory.HttpClient()
|
|
l.httpClientMu.Unlock()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return l.httpClient.Do(req)
|
|
}
|