Skip to content

APP-8173: Ability to test Viamapps locally by standing a small proxy through the CLI #5116

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Jul 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion app/viam_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ func CreateViamClientWithOptions(ctx context.Context, options Options, logger lo
} else if !strings.HasPrefix(options.BaseURL, "http://") && !strings.HasPrefix(options.BaseURL, "https://") {
return nil, errors.New("use valid URL")
}
serviceHost, err := url.Parse(options.BaseURL + ":443")
if !strings.HasSuffix(options.BaseURL, ":443") {
options.BaseURL += ":443"
}
serviceHost, err := url.Parse(options.BaseURL)
if err != nil {
return nil, err
}
Expand Down
32 changes: 32 additions & 0 deletions cli/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -2479,6 +2479,38 @@ Note: There is no progress meter while copying is in progress.
UsageText: createUsageText("module", nil, false, true),
HideHelpCommand: true,
Subcommands: []*cli.Command{
{
Name: "local-app-testing",
Usage: "test your viam application locally",
UsageText: createUsageText("module local-app-testing",
[]string{"app-url", "machine-id", "machine-api-key", "machine-api-key-id"}, false, false),
Flags: []cli.Flag{
&cli.StringFlag{
Name: "app-url",
Usage: "url where local app is running (including port number), e.g http://localhost:5000",
Required: true,
},
&cli.StringFlag{
Name: "machine-id",
Usage: "machine ID of the machine you want to test with, you can get it at " +
"https://app.viam.com/fleet/machines",
Required: true,
},
&cli.StringFlag{
Name: "machine-api-key-id",
Usage: "machine API key ID for the machine you provided the ID of, you can get it at " +
"https://app.viam.comm/machine/<machineID>/connect/api-keys",
Required: true,
},
&cli.StringFlag{
Name: "machine-api-key",
Usage: "machine API key for the machine you provided the ID of, you can get it at " +
"https://app.viam.comm/machine/<machineID>/connect/api-keys",
Required: true,
},
},
Action: createCommandWithT[localAppTestingArgs](LocalAppTestingAction),
},
{
Name: "create",
Usage: "create & register a module on app.viam.com",
Expand Down
73 changes: 41 additions & 32 deletions cli/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -1803,16 +1803,52 @@ func isProdBaseURL(baseURL *url.URL) bool {
return strings.HasSuffix(baseURL.Hostname(), "viam.com")
}

// Creates a new viam client, defaulting to _not_ passing the `disableBrowerOpen` arg (which
// users don't even have an option of setting for any CLI method currently except `Login`).
func newViamClient(c *cli.Context) (*viamClient, error) {
client, err := newViamClientInner(c, false)
if err != nil {
return nil, err
}
if err := client.ensureLoggedIn(); err != nil {
return nil, err
}
return client, nil
}

func newViamClientInner(c *cli.Context, disableBrowserOpen bool) (*viamClient, error) {
globalArgs, err := getGlobalArgs(c)
baseURL, conf, err := getBaseURL(c)
if err != nil {
return nil, err
}

var authFlow *authFlow
if isProdBaseURL(baseURL) {
authFlow = newCLIAuthFlow(c.App.Writer, disableBrowserOpen)
} else {
authFlow = newStgCLIAuthFlow(c.App.Writer, disableBrowserOpen)
}

return &viamClient{
c: c,
conf: conf,
baseURL: baseURL,
selectedOrg: &apppb.Organization{},
selectedLoc: &apppb.Location{},
authFlow: authFlow,
}, nil
}

func getBaseURL(c *cli.Context) (*url.URL, *Config, error) {
globalArgs, err := getGlobalArgs(c)
if err != nil {
return nil, nil, err
}
conf, err := ConfigFromCache(c)
if err != nil {
if !os.IsNotExist(err) {
debugf(c.App.Writer, globalArgs.Debug, "Cached config parse error: %v", err)
return nil, errors.New("failed to parse cached config. Please log in again")
return nil, nil, errors.New("failed to parse cached config. Please log in again")
}
conf = &Config{}
whichProfile, _ := whichProfile(globalArgs)
Expand All @@ -1830,7 +1866,7 @@ func newViamClientInner(c *cli.Context, disableBrowserOpen bool) (*viamClient, e
case conf.BaseURL == "" && baseURLArg != "":
conf.BaseURL = baseURLArg
case baseURLArg != "" && conf.BaseURL != "" && conf.BaseURL != baseURLArg:
return nil, fmt.Errorf("cached base URL for this session is %q. "+
return nil, nil, fmt.Errorf("cached base URL for this session is %q. "+
"Please logout and login again to use provided base URL %q", conf.BaseURL, baseURLArg)
}

Expand All @@ -1839,37 +1875,10 @@ func newViamClientInner(c *cli.Context, disableBrowserOpen bool) (*viamClient, e
}
baseURL, _, err := parseBaseURL(conf.BaseURL, true)
if err != nil {
return nil, err
}

var authFlow *authFlow
if isProdBaseURL(baseURL) {
authFlow = newCLIAuthFlow(c.App.Writer, disableBrowserOpen)
} else {
authFlow = newStgCLIAuthFlow(c.App.Writer, disableBrowserOpen)
return nil, nil, err
}

return &viamClient{
c: c,
conf: conf,
baseURL: baseURL,
selectedOrg: &apppb.Organization{},
selectedLoc: &apppb.Location{},
authFlow: authFlow,
}, nil
}

// Creates a new viam client, defaulting to _not_ passing the `disableBrowerOpen` arg (which
// users don't even have an option of setting for any CLI method currently except `Login`).
func newViamClient(c *cli.Context) (*viamClient, error) {
client, err := newViamClientInner(c, false)
if err != nil {
return nil, err
}
if err := client.ensureLoggedIn(); err != nil {
return nil, err
}
return client, nil
return baseURL, conf, nil
}

func (c *viamClient) loadOrganizations() error {
Expand Down
Loading
Loading