diff --git a/.github/workflows/publish-cli.yml b/.github/workflows/publish-cli.yml index fa0e6f9..bc92d38 100644 --- a/.github/workflows/publish-cli.yml +++ b/.github/workflows/publish-cli.yml @@ -102,6 +102,7 @@ jobs: make build \ BINARY="${OUT_DIR}/${BINARY_NAME}" \ DEFAULT_API_URL="${CLI_DEFAULT_API_URL}" \ + DEFAULT_WEB_URL="${CLI_DEFAULT_WEB_URL}" \ FIRST_PARTY_DEVICE_CLIENT_ID="${CLI_FIRST_PARTY_DEVICE_CLIENT_ID}" \ VERSION="${CLI_VERSION}" \ DATE="${BUILD_DATE}" diff --git a/.gitignore b/.gitignore index 1beef1a..6be6ad5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /volcano /dist/ /.env +/.env.local /.env.* *.test *.out diff --git a/Makefile b/Makefile index cb8aebc..9fba68a 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo none) DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ) DEFAULT_API_URL ?= https://api.volcano.dev +DEFAULT_WEB_URL ?= https://volcano.dev FIRST_PARTY_DEVICE_CLIENT_ID ?= LDFLAGS := -s -w \ @@ -15,15 +16,36 @@ LDFLAGS := -s -w \ -X $(VERSION_PKG).Commit=$(COMMIT) \ -X $(VERSION_PKG).Date=$(DATE) \ -X $(CONFIG_PKG).compiledDefaultAPIURL=$(DEFAULT_API_URL) \ + -X $(CONFIG_PKG).compiledDefaultWebURL=$(DEFAULT_WEB_URL) \ -X $(CONFIG_PKG).compiledFirstPartyDeviceClientID=$(FIRST_PARTY_DEVICE_CLIENT_ID) -.PHONY: all build test api-e2e-smoke api-e2e-cloud localmode-e2e lint tidy check clean help +.PHONY: all build local test api-e2e-smoke api-e2e-cloud localmode-e2e lint tidy check clean help all: build build: ## Build the volcano binary into ./$(BINARY) go build -ldflags '$(LDFLAGS)' -o $(BINARY) ./cmd/volcano +local: ## Build volcano using variables loaded from .env.local + @if [ ! -f .env.local ]; then \ + echo ".env.local not found. Create one with VOLCANO_WEB_URL=... and VOLCANO_API_URL=..."; \ + exit 1; \ + fi; \ + set -a; source .env.local; set +a; \ + if [ -z "$${FIRST_PARTY_DEVICE_CLIENT_ID:-}" ] && [ -n "$${VOLCANO_FIRST_PARTY_DEVICE_CLIENT_ID:-}" ]; then \ + export FIRST_PARTY_DEVICE_CLIENT_ID="$${VOLCANO_FIRST_PARTY_DEVICE_CLIENT_ID}"; \ + fi; \ + if [ -z "$${DEFAULT_API_URL:-}" ] && [ -n "$${VOLCANO_API_URL:-}" ]; then \ + export DEFAULT_API_URL="$${VOLCANO_API_URL}"; \ + fi; \ + if [ -z "$${DEFAULT_WEB_URL:-}" ] && [ -n "$${VOLCANO_WEB_URL:-}" ]; then \ + export DEFAULT_WEB_URL="$${VOLCANO_WEB_URL}"; \ + fi; \ + if [ -z "$${DEFAULT_WEB_URL:-}" ] && [[ "$${VOLCANO_API_URL:-}" == http://localhost:* || "$${VOLCANO_API_URL:-}" == http://127.0.0.1:* ]]; then \ + export DEFAULT_WEB_URL="http://localhost:3000"; \ + fi; \ + $(MAKE) build + test: ## Run unit tests go test ./... diff --git a/internal/api/auth.go b/internal/api/auth.go index b9ebcc3..b45434e 100644 --- a/internal/api/auth.go +++ b/internal/api/auth.go @@ -3,7 +3,9 @@ package api import ( "context" + "errors" "fmt" + "net/url" "strings" "github.com/Kong/volcano-cli/internal/apiclient" @@ -81,3 +83,29 @@ func (c *Client) ExchangePlatformToken(ctx context.Context, authAccessToken, cli } return apiResult(resp.StatusCode(), resp.Body, resp.JSON200, resp.JSON403) } + +// WebSignupURL builds the Volcano Web signup URL used by the CLI signup flow. +func WebSignupURL(webURL, email, next string) (string, error) { + webURL = strings.TrimRight(strings.TrimSpace(webURL), "/") + if webURL == "" { + return "", errors.New("web url cannot be empty") + } + parsed, err := url.Parse(webURL) + if err != nil { + return "", fmt.Errorf("failed to parse web url: %w", err) + } + if parsed.Scheme != "http" && parsed.Scheme != "https" { + return "", errors.New("web url must use http:// or https:// scheme") + } + parsed.Path = strings.TrimRight(parsed.Path, "/") + "/signup" + query := parsed.Query() + if email = strings.TrimSpace(email); email != "" { + query.Set("email", email) + } + if next = strings.TrimSpace(next); next != "" { + query.Set("next", next) + } + query.Set("source", "cli") + parsed.RawQuery = query.Encode() + return parsed.String(), nil +} diff --git a/internal/api/client_test.go b/internal/api/client_test.go index e6f4cc5..b0fdd95 100644 --- a/internal/api/client_test.go +++ b/internal/api/client_test.go @@ -50,6 +50,12 @@ func TestStartDeviceAuthorizationNormalizesOAuthError(t *testing.T) { require.ErrorContains(t, err, "HTTP 400: client_id is required") } +func TestWebSignupURL(t *testing.T) { + signupURL, err := WebSignupURL("http://localhost:3000", " ted@example.com ", "/device?user_code=ABCD-EFGH") + require.NoError(t, err) + assert.Equal(t, "http://localhost:3000/signup?email=ted%40example.com&next=%2Fdevice%3Fuser_code%3DABCD-EFGH&source=cli", signupURL) +} + func TestNewClientPreservesAPIURLPathPrefix(t *testing.T) { var sawPath string var sawQuery string diff --git a/internal/auth/auth.go b/internal/auth/auth.go index b9bbe49..5decfce 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -57,6 +57,36 @@ func (s Service) LoginWithToken(ctx context.Context, cfg *config.Config, token s return Credentials{Token: token}, nil } +// Signup runs the device login flow but opens Volcano Web's signup page first. +func (s Service) Signup(ctx context.Context, cfg *config.Config, email string, w io.Writer) (Credentials, error) { + apiURL := s.apiURL(cfg) + clientID, err := resolveDeviceClientID(apiURL) + if err != nil { + return Credentials{}, err + } + client, err := s.sessions.APIClient(apiURL, "") + if err != nil { + return Credentials{}, err + } + + deviceAuth, err := client.StartDeviceAuthorization(ctx, clientID) + if err != nil { + return Credentials{}, err + } + + devicePath := "/device" + if userCode := strings.TrimSpace(deviceAuth.UserCode); userCode != "" { + devicePath = "/device?" + url.Values{"user_code": []string{userCode}}.Encode() + } + signupURL, err := api.WebSignupURL(cfg.WebURL(), email, devicePath) + if err != nil { + return Credentials{}, err + } + + fmt.Fprintln(w, "\nInitiating browser signup...") + return s.completeBrowserLogin(ctx, client, clientID, deviceAuth, w, signupURL) +} + // LoginWithBrowser runs the OAuth device flow and returns credentials to persist. func (s Service) LoginWithBrowser(ctx context.Context, cfg *config.Config, w io.Writer) (Credentials, error) { apiURL := s.apiURL(cfg) @@ -75,7 +105,11 @@ func (s Service) LoginWithBrowser(ctx context.Context, cfg *config.Config, w io. } fmt.Fprintln(w, "\nInitiating browser authentication...") - return s.completeBrowserLogin(ctx, client, clientID, deviceAuth, w) + verificationURL := strings.TrimSpace(deviceAuth.VerificationUriComplete) + if verificationURL == "" { + verificationURL = strings.TrimSpace(deviceAuth.VerificationUri) + } + return s.completeBrowserLogin(ctx, client, clientID, deviceAuth, w, verificationURL) } // resolveDeviceClientID returns the device OAuth client id for the login flow. @@ -117,16 +151,11 @@ func (s Service) apiURL(cfg *config.Config) string { return s.sessions.APIURL(cfg) } -func (s Service) completeBrowserLogin(ctx context.Context, client *api.Client, clientID string, deviceAuth *apiclient.DeviceAuthorizationResponse, w io.Writer) (Credentials, error) { - verificationURL := strings.TrimSpace(deviceAuth.VerificationUriComplete) - if verificationURL == "" { - verificationURL = strings.TrimSpace(deviceAuth.VerificationUri) - } - +func (s Service) completeBrowserLogin(ctx context.Context, client *api.Client, clientID string, deviceAuth *apiclient.DeviceAuthorizationResponse, w io.Writer, browserURL string) (Credentials, error) { fmt.Fprintf(w, "\nCode: %s\n", deviceAuth.UserCode) - fmt.Fprintf(w, "Opening browser: %s\n", verificationURL) + fmt.Fprintf(w, "Opening browser: %s\n", browserURL) - if err := cliruntime.OpenBrowser(s.deps, verificationURL); err != nil { //nolint:contextcheck // browser launch is fire-and-forget; auth ctx would cancel the spawned browser + if err := cliruntime.OpenBrowser(s.deps, browserURL); err != nil { //nolint:contextcheck // browser launch is fire-and-forget; auth ctx would cancel the spawned browser fmt.Fprintln(w, "\n(If browser didn't open, visit the URL above)") } diff --git a/internal/cmd/auth/auth.go b/internal/cmd/auth/auth.go index ce1cb36..3085060 100644 --- a/internal/cmd/auth/auth.go +++ b/internal/cmd/auth/auth.go @@ -2,9 +2,13 @@ package auth import ( + "bufio" "context" + "errors" "fmt" "io" + "net/mail" + "os/exec" "strings" "github.com/spf13/cobra" @@ -21,6 +25,12 @@ type loginOptions struct { out io.Writer } +type signupOptions struct { + deps cliruntime.Deps + in io.Reader + out io.Writer +} + // NewLogin returns the login command. func NewLogin(deps cliruntime.Deps) *cobra.Command { var tokenFlag string @@ -84,6 +94,93 @@ func runLogin(ctx context.Context, opts loginOptions) error { return nil } +// NewSignup returns the signup command. +func NewSignup(deps cliruntime.Deps) *cobra.Command { + return &cobra.Command{ + Use: "signup", + Short: "Create a Volcano account", + Long: `Create a Volcano account from the CLI. + +The command uses your git user.email as the default email address when available, +then opens Volcano's web signup flow in your browser.`, + RunE: func(cmd *cobra.Command, _ []string) error { + return runSignup(cmd.Context(), signupOptions{ + deps: deps, + in: cmd.InOrStdin(), + out: cmd.OutOrStdout(), + }) + }, + } +} + +func runSignup(ctx context.Context, opts signupOptions) error { + cfg, err := config.Load() + if err != nil { + return fmt.Errorf("failed to load config: %w", err) + } + + reader := bufio.NewReader(opts.in) + email, err := promptSignupEmail(ctx, opts.deps, reader, opts.out) + if err != nil { + return err + } + + credentials, err := cliauth.NewService(opts.deps).Signup(ctx, cfg, email, opts.out) + if err != nil { + return fmt.Errorf("signup failed: %w", err) + } + + cfg.UserToken = credentials.Token + cfg.UserID = credentials.UserID + if err := cfg.Save(); err != nil { + return fmt.Errorf("failed to save credentials: %w", err) + } + + output.Success(opts.out, "Signed up and logged in successfully") + output.Success(opts.out, "Credentials saved to ~/.volcano/config.json") + return nil +} + +func promptSignupEmail(ctx context.Context, deps cliruntime.Deps, reader *bufio.Reader, out io.Writer) (string, error) { + defaultEmail := gitConfigEmail(ctx, deps) + if defaultEmail != "" { + fmt.Fprintf(out, "Enter your email address (press enter to continue) [%s]: ", defaultEmail) + } else { + fmt.Fprint(out, "Enter your email address: ") + } + + input, err := reader.ReadString('\n') + if err != nil && !errors.Is(err, io.EOF) { + return "", err + } + email := strings.TrimSpace(input) + if email == "" { + email = defaultEmail + } + if email == "" { + return "", errors.New("email address is required") + } + parsed, err := mail.ParseAddress(email) + if err != nil { + return "", fmt.Errorf("invalid email address: %w", err) + } + return parsed.Address, nil +} + +func gitConfigEmail(ctx context.Context, deps cliruntime.Deps) string { + runner := deps.GitCommandRunner + if runner == nil { + runner = cliruntime.CommandRunnerFunc(func(ctx context.Context, name string, args ...string) ([]byte, error) { + return exec.CommandContext(ctx, name, args...).Output() //nolint:gosec // command name and args are static below + }) + } + out, err := runner.Run(ctx, "git", "config", "--global", "user.email") + if err != nil { + return "" + } + return strings.TrimSpace(string(out)) +} + // NewLogout returns the logout command. func NewLogout() *cobra.Command { return &cobra.Command{ diff --git a/internal/cmd/auth/auth_test.go b/internal/cmd/auth/auth_test.go index 4b863ce..670f33f 100644 --- a/internal/cmd/auth/auth_test.go +++ b/internal/cmd/auth/auth_test.go @@ -2,11 +2,13 @@ package auth import ( "bytes" + "context" "encoding/json" "net/http" "net/http/httptest" "os" "testing" + "time" "github.com/spf13/cobra" "github.com/stretchr/testify/assert" @@ -16,7 +18,10 @@ import ( cliruntime "github.com/Kong/volcano-cli/internal/runtime" ) -const authProjectID = "11111111-1111-4111-8111-111111111111" +const ( + authProjectID = "11111111-1111-4111-8111-111111111111" + authTestSignupURL = "http://localhost:3000/signup?email=ted%40example.com&next=%2Fdevice%3Fuser_code%3DABCD-EFGH&source=cli" +) func TestLoginTokenSuccessSavesConfig(t *testing.T) { setAuthTestHome(t) @@ -82,9 +87,50 @@ func TestLogoutDeletesConfig(t *testing.T) { assert.True(t, os.IsNotExist(err), "config exists after logout: %v", err) } +func TestSignupUsesGitEmailDefault(t *testing.T) { + setAuthTestHome(t) + t.Setenv("VOLCANO_WEB_URL", "http://localhost:3000") + deps, openedURL, pollTicker := signupBrowserDeps(t) + deps.GitCommandRunner = cliruntime.CommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) { + assert.Equal(t, "git", name) + assert.Equal(t, []string{"config", "--global", "user.email"}, args) + return []byte("ted@example.com\n"), nil + }) + + out, err := executeAuthCommandWithInputAndTick(t, NewSignup(deps), "\n", pollTicker) + require.NoError(t, err) + assert.Equal(t, authTestSignupURL, *openedURL) + assert.Contains(t, out, "[ted@example.com]") + assert.Contains(t, out, "Opening browser: "+authTestSignupURL) + assert.Contains(t, out, "Signed up and logged in successfully") + + cfg := loadAuthTestConfig(t) + assert.Equal(t, "platform-token", cfg.UserToken) + assert.Equal(t, "platform-user-1", cfg.UserID) +} + +func TestSignupAllowsEmailOverride(t *testing.T) { + setAuthTestHome(t) + t.Setenv("VOLCANO_WEB_URL", "http://localhost:3000") + deps, openedURL, pollTicker := signupBrowserDeps(t) + deps.GitCommandRunner = cliruntime.CommandRunnerFunc(func(context.Context, string, ...string) ([]byte, error) { + return []byte("ted@example.com\n"), nil + }) + + _, err := executeAuthCommandWithInputAndTick(t, NewSignup(deps), "marco@example.com\n", pollTicker) + require.NoError(t, err) + assert.Contains(t, *openedURL, "email=marco%40example.com") +} + func executeAuthCommand(t *testing.T, cmd *cobra.Command, args ...string) (string, error) { + t.Helper() + return executeAuthCommandWithInput(t, cmd, "", args...) +} + +func executeAuthCommandWithInput(t *testing.T, cmd *cobra.Command, input string, args ...string) (string, error) { t.Helper() var out bytes.Buffer + cmd.SetIn(bytes.NewBufferString(input)) cmd.SetOut(&out) cmd.SetErr(&out) cmd.SetArgs(args) @@ -92,12 +138,95 @@ func executeAuthCommand(t *testing.T, cmd *cobra.Command, args ...string) (strin return out.String(), err } +func executeAuthCommandWithInputAndTick(t *testing.T, cmd *cobra.Command, input string, ticker *authCmdFakeTicker, args ...string) (string, error) { + t.Helper() + var out bytes.Buffer + cmd.SetIn(bytes.NewBufferString(input)) + cmd.SetOut(&out) + cmd.SetErr(&out) + cmd.SetArgs(args) + done := make(chan error, 1) + go func() { done <- cmd.Execute() }() + ticker.tick() + select { + case err := <-done: + return out.String(), err + case <-time.After(2 * time.Second): + t.Fatal("command did not complete") + return out.String(), nil + } +} + +func signupBrowserDeps(t *testing.T) (cliruntime.Deps, *string, *authCmdFakeTicker) { + t.Helper() + pollTicker := newAuthCmdFakeTicker() + dotTicker := newAuthCmdFakeTicker() + timeoutTimer := newAuthCmdFakeTicker() + openedURL := "" + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/auth/device/authorize": + writeAuthJSON(t, w, http.StatusOK, map[string]any{ + "device_code": "device-code", + "user_code": "ABCD-EFGH", + "verification_uri": "https://volcano.dev/device", + "verification_uri_complete": "https://volcano.dev/device?user_code=ABCD-EFGH", + "expires_in": 120, + "interval": 1, + }) + case "/auth/device/token": + writeAuthJSON(t, w, http.StatusOK, map[string]any{"access_token": "auth-access-token"}) + case "/auth/platform/exchange": + writeAuthJSON(t, w, http.StatusOK, map[string]any{ + "token": "platform-token", + "user_id": "platform-user-1", + "token_id": "33333333-3333-4333-8333-333333333333", + "expires_at": time.Now().Add(time.Hour).UTC().Format(time.RFC3339), + }) + default: + http.NotFound(w, r) + } + })) + t.Cleanup(server.Close) + return cliruntime.Deps{ + HTTPClient: server.Client(), + APIBaseURL: server.URL, + OpenBrowser: func(rawURL string) error { + openedURL = rawURL + return nil + }, + NewTimer: func(time.Duration) cliruntime.Timer { return timeoutTimer }, + NewTicker: func(time.Duration) cliruntime.Ticker { + if !pollTicker.created { + pollTicker.created = true + return pollTicker + } + return dotTicker + }, + }, &openedURL, pollTicker +} + +type authCmdFakeTicker struct { + created bool + ch chan time.Time +} + +func newAuthCmdFakeTicker() *authCmdFakeTicker { + return &authCmdFakeTicker{ch: make(chan time.Time, 1)} +} + +func (t *authCmdFakeTicker) C() <-chan time.Time { return t.ch } +func (t *authCmdFakeTicker) Stop() {} +func (t *authCmdFakeTicker) Reset(time.Duration) {} +func (t *authCmdFakeTicker) tick() { t.ch <- time.Now() } + func setAuthTestHome(t *testing.T) { t.Helper() t.Setenv("HOME", t.TempDir()) t.Setenv("VOLCANO_TOKEN", "") t.Setenv("VOLCANO_PROJECT_ID", "") t.Setenv("VOLCANO_API_URL", "") + t.Setenv("VOLCANO_WEB_URL", "") t.Setenv("VOLCANO_FIRST_PARTY_DEVICE_CLIENT_ID", "") } diff --git a/internal/cmd/root/root.go b/internal/cmd/root/root.go index b6e47e9..936e119 100644 --- a/internal/cmd/root/root.go +++ b/internal/cmd/root/root.go @@ -42,6 +42,7 @@ func New(deps cliruntime.Deps) *cobra.Command { root.AddCommand(newVersionCmd()) root.AddCommand(upgradecmd.New(deps)) root.AddCommand(authcmd.NewLogin(deps)) + root.AddCommand(authcmd.NewSignup(deps)) root.AddCommand(authcmd.NewLogout()) root.AddCommand(initcmd.New()) root.AddCommand(projectcmd.NewProjects(deps)) diff --git a/internal/cmd/root/root_test.go b/internal/cmd/root/root_test.go index f0416fd..4ddde0a 100644 --- a/internal/cmd/root/root_test.go +++ b/internal/cmd/root/root_test.go @@ -28,6 +28,7 @@ func TestRootHelp(t *testing.T) { assert.Contains(t, out, "cloud") assert.Contains(t, out, "projects") assert.Contains(t, out, "restart") + assert.Contains(t, out, "signup") assert.Contains(t, out, "start") assert.Contains(t, out, "status") assert.Contains(t, out, "stop") diff --git a/internal/config/config.go b/internal/config/config.go index 0681303..5f2ebf6 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -14,12 +14,14 @@ const ( envToken = "VOLCANO_TOKEN" envProjectID = "VOLCANO_PROJECT_ID" envAPIURL = "VOLCANO_API_URL" + envWebURL = "VOLCANO_WEB_URL" envFirstPartyDeviceID = "VOLCANO_FIRST_PARTY_DEVICE_CLIENT_ID" defaultConfigDirName = ".volcano" defaultConfigFileName = "config.json" defaultConfigDirMode = 0o700 defaultConfigFileMode = 0o600 defaultCompiledAPIURL = "https://api.volcano.dev" + defaultCompiledWebURL = "https://volcano.dev" ) var ( @@ -32,6 +34,7 @@ var ( // These variables are intentionally settable with -ldflags -X. var ( compiledDefaultAPIURL = defaultCompiledAPIURL + compiledDefaultWebURL = defaultCompiledWebURL compiledFirstPartyDeviceClientID = "" ) @@ -190,6 +193,14 @@ func (c *Config) APIURL() string { return compiledDefaultAPIURL } +// WebURL returns the Volcano web URL with VOLCANO_WEB_URL taking precedence. +func (c *Config) WebURL() string { + if webURL := strings.TrimSpace(os.Getenv(envWebURL)); !c.IgnoreEnv && webURL != "" { + return webURL + } + return compiledDefaultWebURL +} + // FunctionAliasScope returns the config key for aliases bound to one API URL // and project ID. The API URL is trimmed so trailing slashes do not split scopes. func FunctionAliasScope(apiURL, projectID string) string { diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 6ae3083..10b037e 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -95,6 +95,12 @@ func TestFunctionInvokeTokenPrefersServiceKey(t *testing.T) { assert.Equal(t, "user-token", cfg.FunctionInvokeToken()) } +func TestWebURLFromEnv(t *testing.T) { + t.Setenv("VOLCANO_WEB_URL", "http://localhost:3000") + + assert.Equal(t, "http://localhost:3000", Default().WebURL()) +} + func TestFunctionAliasesPersistByScope(t *testing.T) { t.Setenv("HOME", t.TempDir()) diff --git a/internal/runtime/runtime.go b/internal/runtime/runtime.go index 6a775a2..ca1595a 100644 --- a/internal/runtime/runtime.go +++ b/internal/runtime/runtime.go @@ -23,6 +23,7 @@ type Deps struct { ConfigLoader func() (*config.Config, error) LocalCommandRunner CommandRunner UpdateCommandRunner CommandRunner + GitCommandRunner CommandRunner ExecutablePath string UpdateGitHubAPIURL string CommandPathPrefix string diff --git a/scripts/ci/resolve-cli-build-env.sh b/scripts/ci/resolve-cli-build-env.sh index 8ad656a..cad4081 100755 --- a/scripts/ci/resolve-cli-build-env.sh +++ b/scripts/ci/resolve-cli-build-env.sh @@ -14,6 +14,7 @@ if [ -z "${GITHUB_ENV:-}" ]; then fi CLI_DEFAULT_API_URL="https://api.volcano.dev" +CLI_DEFAULT_WEB_URL="https://volcano.dev" CLI_FIRST_PARTY_DEVICE_CLIENT_ID="" case "$REF" in @@ -30,6 +31,7 @@ case "$REF" in exit 1 fi CLI_DEFAULT_API_URL="https://api.volcano.dev" + CLI_DEFAULT_WEB_URL="https://volcano.dev" CLI_FIRST_PARTY_DEVICE_CLIENT_ID="${PRODUCTION_FIRST_PARTY_DEVICE_CLIENT_ID:-${VOLCANO_FIRST_PARTY_DEVICE_CLIENT_ID_PRODUCTION:-}}" REQUIRED_DEVICE_CLIENT_ID_VAR="VOLCANO_FIRST_PARTY_DEVICE_CLIENT_ID_PRODUCTION" CLI_VERSION="$REF_NAME" @@ -51,6 +53,7 @@ BUILD_DATE="${BUILD_DATE:-$(date -u +%Y-%m-%dT%H:%M:%SZ)}" { echo "CLI_DEFAULT_API_URL=${CLI_DEFAULT_API_URL}" + echo "CLI_DEFAULT_WEB_URL=${CLI_DEFAULT_WEB_URL}" echo "CLI_FIRST_PARTY_DEVICE_CLIENT_ID=${CLI_FIRST_PARTY_DEVICE_CLIENT_ID}" echo "CLI_VERSION=${CLI_VERSION}" echo "BUILD_DATE=${BUILD_DATE}"