diff --git a/cli/cmd/deploy/cmd.go b/cli/cmd/deploy/cmd.go index 7030c6a..f5ad917 100644 --- a/cli/cmd/deploy/cmd.go +++ b/cli/cmd/deploy/cmd.go @@ -36,14 +36,16 @@ be pushed to the organization "organization-slug-a3ecfh2b", and the app name } var ( - slug string - appName string - verbose bool + slug string + appName string + verbose bool + appDir string = "." + projectDir string = "." ) func run(cmd *cobra.Command, args []string) { service := app.New(gql.NewClient(), gql.NewSubscriptionClient(), http.DefaultClient) - err := Deploy(cmd.Context(), ".", slug, verbose, appName, service) + err := Deploy(cmd.Context(), service, appDir, projectDir, slug, appName, verbose) if err != nil { os.Exit(1) @@ -57,6 +59,7 @@ func init() { flags.StringVarP(&slug, "organization", "o", "", "The organization slug identifier. List available organizations with 'numerous organization list'.") flags.StringVarP(&appName, "name", "n", "", "A unique name for the application to deploy.") flags.BoolVarP(&verbose, "verbose", "v", false, "Display detailed information about the app deployment.") + flags.StringVarP(&projectDir, "project-dir", "p", "", "The project directory, which is the build context if using a custom Dockerfile.") if err := DeployCmd.MarkFlagRequired("organization"); err != nil { panic(err.Error()) diff --git a/cli/cmd/deploy/deploy.go b/cli/cmd/deploy/deploy.go index 836afe5..f341224 100644 --- a/cli/cmd/deploy/deploy.go +++ b/cli/cmd/deploy/deploy.go @@ -33,7 +33,7 @@ var ( ErrInvalidAppName = errors.New("invalid app name") ) -func Deploy(ctx context.Context, dir string, slug string, verbose bool, appName string, apps AppService) error { +func Deploy(ctx context.Context, apps AppService, appDir, projectDir, slug string, appName string, verbose bool) error { if !validate.IsValidIdentifier(slug) { output.PrintError("Error: Invalid organization %q.", "Must contain only lower-case alphanumerical characters and dashes.", slug) return ErrInvalidSlug @@ -45,7 +45,7 @@ func Deploy(ctx context.Context, dir string, slug string, verbose bool, appName } task := output.StartTask("Loading app configuration") - manifest, err := manifest.LoadManifest(filepath.Join(dir, manifest.ManifestPath)) + manifest, err := manifest.LoadManifest(filepath.Join(appDir, manifest.ManifestPath)) if err != nil { task.Error() output.PrintErrorAppNotInitialized() @@ -53,7 +53,7 @@ func Deploy(ctx context.Context, dir string, slug string, verbose bool, appName return err } - secrets := loadSecretsFromEnv(dir) + secrets := loadSecretsFromEnv(appDir) task.Done() task = output.StartTask("Registering new version") @@ -74,8 +74,12 @@ func Deploy(ctx context.Context, dir string, slug string, verbose bool, appName task.Done() task = output.StartTask("Creating app archive") - tarPath := path.Join(dir, ".tmp_app_archive.tar") - err = archive.TarCreate(dir, tarPath, manifest.Exclude) + tarPath := path.Join(appDir, ".tmp_app_archive.tar") + tarSrcDir := appDir + if projectDir != "" { + tarSrcDir = projectDir + } + err = archive.TarCreate(tarSrcDir, tarPath, manifest.Exclude) if err != nil { task.Error() output.PrintErrorDetails("Error archiving app source", err) diff --git a/cli/cmd/deploy/deploy_test.go b/cli/cmd/deploy/deploy_test.go index f68b132..57be4ed 100644 --- a/cli/cmd/deploy/deploy_test.go +++ b/cli/cmd/deploy/deploy_test.go @@ -25,8 +25,8 @@ func TestDeploy(t *testing.T) { const deployVersionID = "deploy-version-id" t.Run("give no existing app then happy path can run", func(t *testing.T) { - dir := t.TempDir() - copyTo(t, "../../testdata/streamlit_app", dir) + appDir := t.TempDir() + copyTo(t, "../../testdata/streamlit_app", appDir) apps := &mockAppService{} apps.On("ReadApp", mock.Anything, mock.Anything).Return(app.ReadAppOutput{}, app.ErrAppNotFound) @@ -37,14 +37,14 @@ func TestDeploy(t *testing.T) { apps.On("DeployApp", mock.Anything, mock.Anything).Return(app.DeployAppOutput{DeploymentVersionID: deployVersionID}, nil) apps.On("DeployEvents", mock.Anything, mock.Anything).Return(nil) - err := Deploy(context.TODO(), dir, slug, false, appName, apps) + err := Deploy(context.TODO(), apps, appDir, "", slug, appName, false) assert.NoError(t, err) }) t.Run("give existing app then it does not create app", func(t *testing.T) { - dir := t.TempDir() - copyTo(t, "../../testdata/streamlit_app", dir) + appDir := t.TempDir() + copyTo(t, "../../testdata/streamlit_app", appDir) apps := &mockAppService{} apps.On("ReadApp", mock.Anything, mock.Anything).Return(app.ReadAppOutput{AppID: appID}, nil) @@ -54,7 +54,7 @@ func TestDeploy(t *testing.T) { apps.On("DeployApp", mock.Anything, mock.Anything).Return(app.DeployAppOutput{DeploymentVersionID: deployVersionID}, nil) apps.On("DeployEvents", mock.Anything, mock.Anything).Return(nil) - err := Deploy(context.TODO(), dir, slug, false, appName, apps) + err := Deploy(context.TODO(), apps, appDir, "", slug, appName, false) assert.NoError(t, err) }) @@ -62,19 +62,19 @@ func TestDeploy(t *testing.T) { t.Run("given dir without numerous.toml then it returns error", func(t *testing.T) { dir := t.TempDir() - err := Deploy(context.TODO(), dir, slug, false, appName, nil) + err := Deploy(context.TODO(), nil, dir, "", slug, appName, false) assert.EqualError(t, err, "open "+dir+"/numerous.toml: no such file or directory") }) t.Run("given invalid slug then it returns error", func(t *testing.T) { - err := Deploy(context.TODO(), ".", "Some Invalid Organization Slug", false, appName, nil) + err := Deploy(context.TODO(), nil, ".", "", "Some Invalid Organization Slug", appName, false) assert.ErrorIs(t, err, ErrInvalidSlug) }) t.Run("given invalid app name then it returns error", func(t *testing.T) { - err := Deploy(context.TODO(), ".", slug, false, "Some Invalid App Name", nil) + err := Deploy(context.TODO(), nil, ".", "", slug, "Some Invalid App Name", false) assert.ErrorIs(t, err, ErrInvalidAppName) }) diff --git a/python/src/numerous/generated/graphql/input_types.py b/python/src/numerous/generated/graphql/input_types.py index f25d994..d7168db 100644 --- a/python/src/numerous/generated/graphql/input_types.py +++ b/python/src/numerous/generated/graphql/input_types.py @@ -31,6 +31,7 @@ class AppCreateInfo(BaseModel): class AppDeployInput(BaseModel): + app_relative_path: Optional[str] = Field(alias="appRelativePath", default=None) secrets: Optional[List["AppSecret"]] = None diff --git a/shared/schema.gql b/shared/schema.gql index c08ddf3..0e03852 100644 --- a/shared/schema.gql +++ b/shared/schema.gql @@ -296,6 +296,7 @@ input AppCreateInfo { } input AppDeployInput { + appRelativePath: String secrets: [AppSecret!] }