Skip to content

Commit

Permalink
feat(cli): numerous status command for displaying app workload status
Browse files Browse the repository at this point in the history
  • Loading branch information
jfeo committed Dec 11, 2024
1 parent 109ed63 commit 878fd00
Show file tree
Hide file tree
Showing 13 changed files with 637 additions and 70 deletions.
18 changes: 9 additions & 9 deletions cmd/deploy/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,15 @@ var cmdArgs struct {
func run(cmd *cobra.Command, args []string) error {
sc := gql.NewSubscriptionClient().WithSyncMode(true)
service := app.New(gql.NewClient(), sc, http.DefaultClient)
input := DeployInput{
AppDir: cmdArgs.appDir,
ProjectDir: cmdArgs.projectDir,
OrgSlug: cmdArgs.appIdent.OrganizationSlug,
AppSlug: cmdArgs.appIdent.AppSlug,
Message: cmdArgs.message,
Version: cmdArgs.version,
Verbose: cmdArgs.verbose,
Follow: cmdArgs.follow,
input := deployInput{
appDir: cmdArgs.appDir,
projectDir: cmdArgs.projectDir,
orgSlug: cmdArgs.appIdent.OrganizationSlug,
appSlug: cmdArgs.appIdent.AppSlug,
message: cmdArgs.message,
version: cmdArgs.version,
verbose: cmdArgs.verbose,
follow: cmdArgs.follow,
}
err := deploy(cmd.Context(), service, input)

Expand Down
84 changes: 42 additions & 42 deletions cmd/deploy/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func (e *deployBuildError) Error() string {
return e.Message
}

type AppService interface {
type appService interface {
ReadApp(ctx context.Context, input app.ReadAppInput) (app.ReadAppOutput, error)
Create(ctx context.Context, input app.CreateAppInput) (app.CreateAppOutput, error)
CreateVersion(ctx context.Context, input app.CreateAppVersionInput) (app.CreateAppVersionOutput, error)
Expand All @@ -47,21 +47,21 @@ type AppService interface {
AppDeployLogs(appident.AppIdentifier) (chan app.AppDeployLogEntry, error)
}

type DeployInput struct {
AppDir string
ProjectDir string
OrgSlug string
AppSlug string
Version string
Message string
Verbose bool
Follow bool
type deployInput struct {
appDir string
projectDir string
orgSlug string
appSlug string
version string
message string
verbose bool
follow bool
}

func deploy(ctx context.Context, apps AppService, input DeployInput) error {
func deploy(ctx context.Context, apps appService, input deployInput) error {
appRelativePath, err := findAppRelativePath(input)
if err != nil {
output.PrintError("Project directory %q must be a parent of app directory %q", "", input.ProjectDir, input.AppDir)
output.PrintError("Project directory %q must be a parent of app directory %q", "", input.projectDir, input.appDir)
return err
}

Expand Down Expand Up @@ -98,7 +98,7 @@ func deploy(ctx context.Context, apps AppService, input DeployInput) error {

output.PrintlnOK("Access your app at: " + links.GetAppURL(orgSlug, appSlug))

if input.Follow {
if input.follow {
output.Notify("Following logs of %s/%s:", "", orgSlug, appSlug)
if err := followLogs(ctx, apps, orgSlug, appSlug); err != nil {
return err
Expand All @@ -110,13 +110,13 @@ func deploy(ctx context.Context, apps AppService, input DeployInput) error {
fmt.Println("Or you can use the " + output.Highlight("--follow") + " flag:")

projectDirArg := ""
if input.ProjectDir != "" {
projectDirArg = " --project-dir=" + input.ProjectDir
if input.projectDir != "" {
projectDirArg = " --project-dir=" + input.projectDir
}

appDirArg := ""
if input.AppDir != "" {
appDirArg = " " + input.AppDir
if input.appDir != "" {
appDirArg = " " + input.appDir
}

fmt.Println(" " + output.Highlight("numerous deploy --follow --organization="+orgSlug+" --app="+appSlug+projectDirArg+appDirArg))
Expand All @@ -125,17 +125,17 @@ func deploy(ctx context.Context, apps AppService, input DeployInput) error {
return nil
}

func findAppRelativePath(input DeployInput) (string, error) {
if input.ProjectDir == "" {
func findAppRelativePath(input deployInput) (string, error) {
if input.projectDir == "" {
return "", nil
}

absProjectDir, err := filepath.Abs(input.ProjectDir)
absProjectDir, err := filepath.Abs(input.projectDir)
if err != nil {
return "", err
}

absAppDir, err := filepath.Abs(input.AppDir)
absAppDir, err := filepath.Abs(input.appDir)
if err != nil {
return "", err
}
Expand All @@ -152,24 +152,24 @@ func findAppRelativePath(input DeployInput) (string, error) {
return filepath.ToSlash(rel), nil
}

func loadAppConfiguration(input DeployInput) (*manifest.Manifest, map[string]string, error) {
func loadAppConfiguration(input deployInput) (*manifest.Manifest, map[string]string, error) {
task := output.StartTask("Loading app configuration")
m, err := manifest.Load(filepath.Join(input.AppDir, manifest.ManifestFileName))
m, err := manifest.Load(filepath.Join(input.appDir, manifest.ManifestFileName))
if err != nil {
task.Error()
output.PrintErrorAppNotInitialized(input.AppDir)
output.PrintErrorAppNotInitialized(input.appDir)
output.PrintManifestTOMLError(err)

return nil, nil, err
}

secrets := loadSecretsFromEnv(input.AppDir)
secrets := loadSecretsFromEnv(input.appDir)

// for validation
ai, err := appident.GetAppIdentifier(input.AppDir, m, input.OrgSlug, input.AppSlug)
ai, err := appident.GetAppIdentifier(input.appDir, m, input.orgSlug, input.appSlug)
if err != nil {
task.Error()
appident.PrintGetAppIdentifierError(err, input.AppDir, ai)
appident.PrintGetAppIdentifierError(err, input.appDir, ai)

return nil, nil, err
}
Expand All @@ -179,10 +179,10 @@ func loadAppConfiguration(input DeployInput) (*manifest.Manifest, map[string]str
return m, secrets, nil
}

func createAppArchive(input DeployInput, manifest *manifest.Manifest) (*os.File, error) {
srcPath := input.AppDir
if input.ProjectDir != "" {
srcPath = input.ProjectDir
func createAppArchive(input deployInput, manifest *manifest.Manifest) (*os.File, error) {
srcPath := input.appDir
if input.projectDir != "" {
srcPath = input.projectDir
}

task := output.StartTask("Creating app archive")
Expand All @@ -208,10 +208,10 @@ func createAppArchive(input DeployInput, manifest *manifest.Manifest) (*os.File,
return archive, nil
}

func registerAppVersion(ctx context.Context, apps AppService, input DeployInput, manifest *manifest.Manifest) (app.CreateAppVersionOutput, string, string, error) {
ai, err := appident.GetAppIdentifier("", manifest, input.OrgSlug, input.AppSlug)
func registerAppVersion(ctx context.Context, apps appService, input deployInput, manifest *manifest.Manifest) (app.CreateAppVersionOutput, string, string, error) {
ai, err := appident.GetAppIdentifier("", manifest, input.orgSlug, input.appSlug)
if err != nil {
appident.PrintGetAppIdentifierError(err, input.AppDir, ai)
appident.PrintGetAppIdentifierError(err, input.appDir, ai)
return app.CreateAppVersionOutput{}, "", "", err
}

Expand All @@ -229,7 +229,7 @@ func registerAppVersion(ctx context.Context, apps AppService, input DeployInput,
return app.CreateAppVersionOutput{}, "", "", err
}

appVersionInput := app.CreateAppVersionInput{AppID: appID, Version: input.Version, Message: input.Message}
appVersionInput := app.CreateAppVersionInput{AppID: appID, Version: input.version, Message: input.message}
appVersionOutput, err := apps.CreateVersion(ctx, appVersionInput)
if err != nil {
task.Error()
Expand All @@ -242,7 +242,7 @@ func registerAppVersion(ctx context.Context, apps AppService, input DeployInput,
return appVersionOutput, ai.OrganizationSlug, ai.AppSlug, nil
}

func readOrCreateApp(ctx context.Context, apps AppService, ai appident.AppIdentifier, manifest *manifest.Manifest) (string, error) {
func readOrCreateApp(ctx context.Context, apps appService, ai appident.AppIdentifier, manifest *manifest.Manifest) (string, error) {
appReadInput := app.ReadAppInput{
OrganizationSlug: ai.OrganizationSlug,
AppSlug: ai.AppSlug,
Expand Down Expand Up @@ -298,7 +298,7 @@ func (pr *progressReader) report(eof bool) {
pr.task.Progress(percent)
}

func uploadAppArchive(ctx context.Context, apps AppService, archive *os.File, appVersionID string) error {
func uploadAppArchive(ctx context.Context, apps appService, archive *os.File, appVersionID string) error {
task := output.StartTask("Uploading app archive")
uploadURLInput := app.AppVersionUploadURLInput(app.AppVersionUploadURLInput{AppVersionID: appVersionID})
uploadURLOutput, err := apps.AppVersionUploadURL(ctx, uploadURLInput)
Expand Down Expand Up @@ -399,7 +399,7 @@ func printAppSourceUploadErr(appSourceUploadErr *app.AppSourceUploadError) {
)
}

func deployApp(ctx context.Context, appVersionOutput app.CreateAppVersionOutput, secrets map[string]string, apps AppService, input DeployInput, appRelativePath string) error {
func deployApp(ctx context.Context, appVersionOutput app.CreateAppVersionOutput, secrets map[string]string, apps appService, input deployInput, appRelativePath string) error {
task := output.StartTask("Deploying app")

deployAppInput := app.DeployAppInput{AppVersionID: appVersionOutput.AppVersionID, Secrets: secrets, AppRelativePath: appRelativePath}
Expand All @@ -411,19 +411,19 @@ func deployApp(ctx context.Context, appVersionOutput app.CreateAppVersionOutput,
return err
}

appDeploymentStatusEventUpdater := statusUpdater{verbose: input.Verbose, task: task}
appDeploymentStatusEventUpdater := statusUpdater{verbose: input.verbose, task: task}
eventsInput := app.DeployEventsInput{
DeploymentVersionID: deployAppOutput.DeploymentVersionID,
Handler: func(de app.DeployEvent) error {
switch de.Typename {
case "AppBuildMessageEvent":
if input.Verbose {
if input.verbose {
for _, l := range strings.Split(de.BuildMessage.Message, "\n") {
task.AddLine("Build", l)
}
}
case "AppBuildErrorEvent":
if input.Verbose {
if input.verbose {
for _, l := range strings.Split(de.BuildError.Message, "\n") {
task.AddLine("Error", l)
}
Expand Down Expand Up @@ -495,7 +495,7 @@ func loadSecretsFromEnv(appDir string) map[string]string {
return env
}

func followLogs(ctx context.Context, apps AppService, orgSlug, appSlug string) error {
func followLogs(ctx context.Context, apps appService, orgSlug, appSlug string) error {
ai := appident.AppIdentifier{OrganizationSlug: orgSlug, AppSlug: appSlug}
ch, err := apps.AppDeployLogs(ai)
if err != nil {
Expand Down
28 changes: 14 additions & 14 deletions cmd/deploy/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func TestDeploy(t *testing.T) {
test.CopyDir(t, "../../testdata/streamlit_app", appDir)
apps := mockAppNotExists()

input := DeployInput{AppDir: appDir, OrgSlug: slug, AppSlug: appSlug}
input := deployInput{appDir: appDir, orgSlug: slug, appSlug: appSlug}
err := deploy(context.TODO(), apps, input)

assert.NoError(t, err)
Expand All @@ -74,7 +74,7 @@ func TestDeploy(t *testing.T) {
test.CopyDir(t, "../../testdata/streamlit_app", appDir)
apps := mockAppExists()

input := DeployInput{AppDir: appDir, OrgSlug: slug, AppSlug: appSlug}
input := deployInput{appDir: appDir, orgSlug: slug, appSlug: appSlug}
err := deploy(context.TODO(), apps, input)

assert.NoError(t, err)
Expand All @@ -84,7 +84,7 @@ func TestDeploy(t *testing.T) {
t.Run("given dir without numerous.toml then it returns error", func(t *testing.T) {
dir := t.TempDir()

input := DeployInput{AppDir: dir, OrgSlug: slug, AppSlug: appSlug}
input := deployInput{appDir: dir, orgSlug: slug, appSlug: appSlug}
err := deploy(context.TODO(), nil, input)

assert.EqualError(t, err, "open "+dir+"/numerous.toml: no such file or directory")
Expand All @@ -94,7 +94,7 @@ func TestDeploy(t *testing.T) {
appDir := t.TempDir()
test.CopyDir(t, "../../testdata/streamlit_app", appDir)

input := DeployInput{AppDir: appDir, OrgSlug: "Some Invalid Organization Slug", AppSlug: appSlug}
input := deployInput{appDir: appDir, orgSlug: "Some Invalid Organization Slug", appSlug: appSlug}
err := deploy(context.TODO(), nil, input)

assert.ErrorIs(t, err, appident.ErrInvalidOrganizationSlug)
Expand All @@ -109,7 +109,7 @@ func TestDeploy(t *testing.T) {
appDir := t.TempDir()
test.CopyDir(t, "../../testdata/streamlit_app_without_deploy", appDir)

input := DeployInput{AppDir: appDir, AppSlug: appSlug}
input := deployInput{appDir: appDir, appSlug: appSlug}
err := deploy(context.TODO(), nil, input)

assert.ErrorIs(t, err, appident.ErrMissingOrganizationSlug)
Expand All @@ -120,7 +120,7 @@ func TestDeploy(t *testing.T) {
test.CopyDir(t, "../../testdata/streamlit_app_without_deploy", appDir)
apps := mockAppNotExists()

input := DeployInput{AppDir: appDir, AppSlug: "app-slug-in-argument", OrgSlug: "organization-slug-in-argument"}
input := deployInput{appDir: appDir, appSlug: "app-slug-in-argument", orgSlug: "organization-slug-in-argument"}
err := deploy(context.TODO(), apps, input)

if assert.NoError(t, err) {
Expand All @@ -133,7 +133,7 @@ func TestDeploy(t *testing.T) {
appDir := t.TempDir()
test.CopyDir(t, "../../testdata/streamlit_app", appDir)

input := DeployInput{AppDir: appDir, OrgSlug: "organization-slug", AppSlug: "Some Invalid App Name"}
input := deployInput{appDir: appDir, orgSlug: "organization-slug", appSlug: "Some Invalid App Name"}
err := deploy(context.TODO(), nil, input)

assert.ErrorIs(t, err, appident.ErrInvalidAppSlug)
Expand All @@ -144,7 +144,7 @@ func TestDeploy(t *testing.T) {
test.CopyDir(t, "../../testdata/streamlit_app_without_deploy", appDir)
apps := mockAppNotExists()

input := DeployInput{AppDir: appDir, OrgSlug: "organization-slug"}
input := deployInput{appDir: appDir, orgSlug: "organization-slug"}
err := deploy(context.TODO(), apps, input)

expectedAppSlug := "streamlit-app-without-deploy"
Expand All @@ -159,7 +159,7 @@ func TestDeploy(t *testing.T) {
test.CopyDir(t, "../../testdata/streamlit_app", appDir)
apps := mockAppNotExists()

err := deploy(context.TODO(), apps, DeployInput{AppDir: appDir})
err := deploy(context.TODO(), apps, deployInput{appDir: appDir})

if assert.NoError(t, err) {
expectedInput := app.CreateAppInput{OrganizationSlug: "organization-slug-in-manifest", AppSlug: "app-slug-in-manifest", DisplayName: "Streamlit App With Deploy"}
Expand All @@ -172,7 +172,7 @@ func TestDeploy(t *testing.T) {
test.CopyDir(t, "../../testdata/streamlit_app", appDir)
apps := mockAppNotExists()

input := DeployInput{AppDir: appDir, OrgSlug: "organization-slug-in-argument", AppSlug: "app-slug-in-argument"}
input := deployInput{appDir: appDir, orgSlug: "organization-slug-in-argument", appSlug: "app-slug-in-argument"}
err := deploy(context.TODO(), apps, input)

if assert.NoError(t, err) {
Expand All @@ -188,7 +188,7 @@ func TestDeploy(t *testing.T) {
expectedMessage := "expected message"
apps := mockAppExists()

input := DeployInput{AppDir: appDir, OrgSlug: slug, AppSlug: appSlug, Version: expectedVersion, Message: expectedMessage}
input := deployInput{appDir: appDir, orgSlug: slug, appSlug: appSlug, version: expectedVersion, message: expectedMessage}
err := deploy(context.TODO(), apps, input)

if assert.NoError(t, err) {
Expand All @@ -206,7 +206,7 @@ func TestDeploy(t *testing.T) {
test.CopyDir(t, "../../testdata/streamlit_app", appDir)
apps := mockAppExists()

input := DeployInput{AppDir: appDir, OrgSlug: slug, AppSlug: appSlug}
input := deployInput{appDir: appDir, orgSlug: slug, appSlug: appSlug}
err := deploy(context.TODO(), apps, input)

if assert.NoError(t, err) {
Expand All @@ -230,7 +230,7 @@ func TestDeploy(t *testing.T) {
input.Handler(app.DeployEvent{Typename: "AppDeploymentStatusEvent", DeploymentStatus: app.AppDeploymentStatusEvent{Status: "RUNNING"}}) // nolint:errcheck
})

input := DeployInput{AppDir: appDir, OrgSlug: slug, AppSlug: appSlug, Version: expectedVersion, Message: expectedMessage, Verbose: true}
input := deployInput{appDir: appDir, orgSlug: slug, appSlug: appSlug, version: expectedVersion, message: expectedMessage, verbose: true}
stdoutR, err := test.RunEWithPatchedStdout(t, func() error {
return deploy(context.TODO(), apps, input)
})
Expand Down Expand Up @@ -279,7 +279,7 @@ func TestDeploy(t *testing.T) {
close(ch)
apps.On("AppDeployLogs", appident.AppIdentifier{OrganizationSlug: slug, AppSlug: appSlug}).Once().Return(ch, nil)

input := DeployInput{AppDir: appDir, OrgSlug: slug, AppSlug: appSlug, Version: expectedVersion, Message: expectedMessage, Verbose: true, Follow: true}
input := deployInput{appDir: appDir, orgSlug: slug, appSlug: appSlug, version: expectedVersion, message: expectedMessage, verbose: true, follow: true}
stdoutR, err := test.RunEWithPatchedStdout(t, func() error {
return deploy(context.TODO(), apps, input)
})
Expand Down
2 changes: 1 addition & 1 deletion cmd/deploy/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/mock"
)

var _ AppService = &mockAppService{}
var _ appService = &mockAppService{}

type mockAppService struct {
mock.Mock
Expand Down
2 changes: 2 additions & 0 deletions cmd/root/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"numerous.com/cli/cmd/logs"
"numerous.com/cli/cmd/organization"
"numerous.com/cli/cmd/output"
"numerous.com/cli/cmd/status"
"numerous.com/cli/cmd/token"
cmdversion "numerous.com/cli/cmd/version"
"numerous.com/cli/internal/logging"
Expand Down Expand Up @@ -76,6 +77,7 @@ func init() {
cmdversion.Cmd,
app.Cmd,
config.Cmd,
status.Cmd,

// dummy commands to display helpful messages for legacy commands
dummyLegacyCmd("push"),
Expand Down
Loading

0 comments on commit 878fd00

Please sign in to comment.