diff --git a/cmd/connect-client/commands/accounts.go b/cmd/connect-client/commands/accounts.go index 0b362295e..7a7e122bc 100644 --- a/cmd/connect-client/commands/accounts.go +++ b/cmd/connect-client/commands/accounts.go @@ -4,8 +4,6 @@ package commands import ( "fmt" - "net/url" - "os" "time" "github.com/rstudio/connect-client/internal/accounts" @@ -13,26 +11,6 @@ import ( "github.com/rstudio/connect-client/internal/cli_types" ) -type addAccountCmd struct { - Name string `short:"n" help:"Nickname for the account."` - URL *url.URL `short:"u" help:"Server URL."` - APIKey string `short:"k" help:"API key."` - Certificate *os.File `help:"Path to CA certificate bundle."` - Insecure bool `help:"Don't validate server certificate."` -} - -func (cmd *addAccountCmd) Run(args *cli_types.CommonArgs) error { - return nil -} - -type removeAccountCmd struct { - Name string `short:"n" help:"Nickname of account to remove."` -} - -func (cmd *removeAccountCmd) Run(args *cli_types.CommonArgs) error { - return nil -} - type testAccountCmd struct { Name string `short:"n" help:"Nickname of account to test."` } @@ -101,8 +79,6 @@ func (cmd *listAccountsCmd) Run(args *cli_types.CommonArgs, ctx *cli_types.CLICo } type AccountCommands struct { - AddAccount addAccountCmd `kong:"cmd" help:"Add a publishing account."` - RemoveAccount removeAccountCmd `kong:"cmd" help:"Remove a publishing account. Specify by name or URL."` - ListAccounts listAccountsCmd `kong:"cmd" help:"List publishing accounts."` - TestAccount testAccountCmd `kong:"cmd" help:"Verify connectivity and credentials for a publishing account."` + ListAccounts listAccountsCmd `kong:"cmd" help:"List publishing accounts."` + TestAccount testAccountCmd `kong:"cmd" help:"Verify connectivity and credentials for a publishing account."` } diff --git a/cmd/connect-client/commands/publish.go b/cmd/connect-client/commands/publish.go index 5ee25156c..1536e056c 100644 --- a/cmd/connect-client/commands/publish.go +++ b/cmd/connect-client/commands/publish.go @@ -30,15 +30,15 @@ type StatefulCommand interface { } type BaseBundleCmd struct { - cli_types.PublishArgs + cli_types.PublishArgs `kong:"embed"` } func (cmd *BaseBundleCmd) getConfigName() string { if cmd.Config != "" { return cmd.Config } - if cmd.State.Target.AccountName != "" { - return cmd.State.Target.AccountName + if cmd.AccountName != "" { + return cmd.AccountName } return "" } @@ -49,13 +49,12 @@ func (cmd *BaseBundleCmd) LoadState(ctx *cli_types.CLIContext) error { if err != nil { return err } + cmd.State = state.NewDeployment() cmd.State.SourceDir = sourceDir cmd.Config = cmd.getConfigName() cliState := cmd.State - if cmd.New { - cmd.State = state.NewDeployment() - } else { + if !cmd.New { if cmd.Config != "" { // Config name provided, use that saved metadata. log.Info("Attempting to load metadata for selected account/configuration", @@ -74,16 +73,16 @@ func (cmd *BaseBundleCmd) LoadState(ctx *cli_types.CLIContext) error { // No saved metadata found. Use the default account. if cmd.State != nil { log.Info("Loaded most recent deployment", - "name", cmd.State.Target.AccountName) + "name", cmd.AccountName) } else { cmd.State = state.NewDeployment() accounts, err := ctx.Accounts.GetAccountsByServerType(accounts.ServerTypeConnect) if err != nil { return err } - cmd.State.Target.AccountName = getDefaultAccount(accounts) + cmd.AccountName = getDefaultAccount(accounts) log.Info("No saved metadata found; using the default account.", - "name", cmd.State.Target.AccountName) + "name", cmd.AccountName) } } } @@ -186,27 +185,35 @@ func (cmd *BaseBundleCmd) stateFromCLI(log logging.Logger) error { } log.Info("Deployment type", "Entrypoint", metadata.Entrypoint, "AppMode", metadata.AppMode) - manifestFiles, err := createManifestFileMapFromSourceDir(cmd.State.SourceDir, log) + initCommand := InitCommand{ + State: cmd.State, + } + pythonRequired, err := initCommand.requiresPython() if err != nil { return err } - manifest.Files = manifestFiles + if pythonRequired { + initCommand.inspectPython(log, manifest) + } - requiresPython, err := cmd.requiresPython() + manifestFiles, err := createManifestFileMapFromSourceDir(cmd.State.SourceDir, log) if err != nil { return err } - if requiresPython { - err = cmd.inspectPython(log, manifest) - if err != nil { - return err - } - } + manifest.Files = manifestFiles manifest.ResetEmptyFields() return nil } -func (cmd *BaseBundleCmd) requiresPython() (bool, error) { +// This is an incomplete implementation of InitCommand, +// created as a place to put the inspection code that was +// previously part of BaseBundleCmd. +type InitCommand struct { + Python util.Path `help:"Path to Python interpreter for this content. Required unless you specify --python-version and include a requirements.txt file. Default is the Python 3 on your PATH."` + State *state.Deployment `kong:"-"` +} + +func (cmd *InitCommand) requiresPython() (bool, error) { manifest := &cmd.State.Manifest if manifest.Metadata.AppMode.IsPythonContent() { return true, nil @@ -228,7 +235,7 @@ func (cmd *BaseBundleCmd) requiresPython() (bool, error) { return exists, nil } -func (cmd *BaseBundleCmd) inspectPython(log logging.Logger, manifest *bundles.Manifest) error { +func (cmd *InitCommand) inspectPython(log logging.Logger, manifest *bundles.Manifest) error { inspector := environment.NewPythonInspector(cmd.State.SourceDir, cmd.Python, log) if manifest.Python.Version == "" { pythonVersion, err := inspector.GetPythonVersion() @@ -250,31 +257,18 @@ func (cmd *BaseBundleCmd) inspectPython(log logging.Logger, manifest *bundles.Ma return nil } -type CreateBundleCmd struct { - *BaseBundleCmd `kong:"embed"` - BundleFile util.Path `help:"Path to a file where the bundle should be written." kong:"required"` -} - -func (cmd *CreateBundleCmd) Run(args *cli_types.CommonArgs, ctx *cli_types.CLIContext) error { - err := cmd.stateFromCLI(ctx.Logger) +func (cmd *InitCommand) Run(args *cli_types.CommonArgs, ctx *cli_types.CLIContext) error { + requiresPython, err := cmd.requiresPython() if err != nil { return err } - publisher := publish.New(&cmd.PublishArgs) - return publisher.CreateBundleFromDirectory(cmd.BundleFile, ctx.Logger) -} - -type WriteManifestCmd struct { - *BaseBundleCmd `kong:"embed"` -} - -func (cmd *WriteManifestCmd) Run(args *cli_types.CommonArgs, ctx *cli_types.CLIContext) error { - err := cmd.stateFromCLI(ctx.Logger) - if err != nil { - return err + if requiresPython { + err = cmd.inspectPython(ctx.Logger, &cmd.State.Manifest) + if err != nil { + return err + } } - publisher := publish.New(&cmd.PublishArgs) - return publisher.WriteManifestFromDirectory(ctx.Logger) + return nil } type PublishCmd struct { diff --git a/cmd/connect-client/commands/publish_test.go b/cmd/connect-client/commands/publish_test.go index b76a09e50..c77d15eb4 100644 --- a/cmd/connect-client/commands/publish_test.go +++ b/cmd/connect-client/commands/publish_test.go @@ -5,13 +5,11 @@ package commands import ( "encoding/json" "testing" - "time" "github.com/rstudio/connect-client/internal/accounts" "github.com/rstudio/connect-client/internal/cli_types" "github.com/rstudio/connect-client/internal/logging" "github.com/rstudio/connect-client/internal/state" - "github.com/rstudio/connect-client/internal/types" "github.com/rstudio/connect-client/internal/util" "github.com/rstudio/connect-client/internal/util/utiltest" "github.com/spf13/afero" @@ -26,19 +24,19 @@ func TestPublishCommandSuite(t *testing.T) { suite.Run(t, new(PublishCommandSuite)) } -func (s *PublishCommandSuite) createSavedState(path util.Path, accountName, configName string) { +func (s *PublishCommandSuite) createSavedState(path util.Path, accountName, configName string) *state.Deployment { // This fixture simulates executing a publish command, // which will create a saved state directory. deployment := state.NewDeployment() - deployment.Target.AccountName = accountName cmd := &PublishCmd{ BaseBundleCmd: &BaseBundleCmd{ PublishArgs: cli_types.PublishArgs{ - Path: path, - State: deployment, - Config: configName, - New: false, + Path: path, + AccountName: accountName, + Config: configName, + New: false, + State: deployment, }, }, } @@ -52,10 +50,9 @@ func (s *PublishCommandSuite) createSavedState(path util.Path, accountName, conf err := cmd.LoadState(ctx) s.NoError(err) - cmd.State.Target.DeployedAt = types.NewOptional(time.Now()) - err = cmd.SaveState(ctx) s.NoError(err) + return cmd.State } func (s *PublishCommandSuite) assertStateExists(path util.Path, name string) { @@ -73,7 +70,6 @@ func (s *PublishCommandSuite) assertStateExists(path util.Path, name string) { decoder := json.NewDecoder(f) decoder.DisallowUnknownFields() decoder.Decode(&target) - s.Equal("test", target.AccountName) } func (s *PublishCommandSuite) TestSaveState() { @@ -111,23 +107,23 @@ func (s *PublishCommandSuite) TestLoadStateDefaultAccountNoPriorDeployments() { } err := cmd.LoadState(ctx) s.NoError(err) - s.Equal("test", cmd.State.Target.AccountName) + s.Equal("test", cmd.AccountName) } func (s *PublishCommandSuite) TestLoadStateWithAccountNoPriorDeployments() { // Account name is provided on the CLI, but there are no prior deployments. deployment := state.NewDeployment() - deployment.Target.AccountName = "test" afs := afero.NewMemMapFs() path := util.NewPath("/", afs) cmd := &PublishCmd{ BaseBundleCmd: &BaseBundleCmd{ PublishArgs: cli_types.PublishArgs{ - Path: path, - State: deployment, - Config: "", - New: false, + Path: path, + AccountName: "test", + Config: "", + New: false, + State: deployment, }, }, } @@ -139,7 +135,7 @@ func (s *PublishCommandSuite) TestLoadStateWithAccountNoPriorDeployments() { } err := cmd.LoadState(ctx) s.NoError(err) - s.Equal("test", cmd.State.Target.AccountName) + s.Equal("test", cmd.AccountName) } func (s *PublishCommandSuite) TestLoadStateWithAccountAndPriorDeployments() { @@ -150,15 +146,15 @@ func (s *PublishCommandSuite) TestLoadStateWithAccountAndPriorDeployments() { s.createSavedState(path, "not-it", "") deployment := state.NewDeployment() - deployment.Target.AccountName = "test" cmd := &PublishCmd{ BaseBundleCmd: &BaseBundleCmd{ PublishArgs: cli_types.PublishArgs{ - Path: path, - State: deployment, - Config: "", - New: false, + Path: path, + AccountName: "test", + Config: "", + New: false, + State: deployment, }, }, } @@ -170,7 +166,7 @@ func (s *PublishCommandSuite) TestLoadStateWithAccountAndPriorDeployments() { } err := cmd.LoadState(ctx) s.NoError(err) - s.Equal("test", cmd.State.Target.AccountName) + s.Equal("test", cmd.AccountName) } func (s *PublishCommandSuite) TestLoadStateNoAccountAndPriorDeployments() { @@ -179,7 +175,7 @@ func (s *PublishCommandSuite) TestLoadStateNoAccountAndPriorDeployments() { afs := afero.NewMemMapFs() path := util.NewPath("/", afs) s.createSavedState(path, "older", "") - s.createSavedState(path, "newer", "") + newState := s.createSavedState(path, "newer", "") deployment := state.NewDeployment() @@ -201,7 +197,7 @@ func (s *PublishCommandSuite) TestLoadStateNoAccountAndPriorDeployments() { } err := cmd.LoadState(ctx) s.NoError(err) - s.Equal("newer", cmd.State.Target.AccountName) + s.Equal(newState, cmd.State) } func (s *PublishCommandSuite) TestGetDefaultAccountEmpty() { diff --git a/cmd/connect-client/main.go b/cmd/connect-client/main.go index e458e8248..869690a2a 100644 --- a/cmd/connect-client/main.go +++ b/cmd/connect-client/main.go @@ -20,11 +20,9 @@ type cliSpec struct { cli_types.CommonArgs commands.AccountCommands `group:"Accounts"` - Publish commands.PublishCmd `kong:"cmd" help:"Publish a project."` - PublishUI commands.PublishUICmd `kong:"cmd" help:"Publish a project using the UI."` - CreateBundle commands.CreateBundleCmd `kong:"cmd" help:"Create a bundle file for a project directory."` - WriteManifest commands.WriteManifestCmd `kong:"cmd" help:"Create a manifest.json file for a project directory."` - Version commands.VersionFlag `help:"Show the client software version and exit."` + Publish commands.PublishCmd `kong:"cmd" help:"Publish a project."` + PublishUI commands.PublishUICmd `kong:"cmd" help:"Publish a project using the UI."` + Version commands.VersionFlag `help:"Show the client software version and exit."` } func logVersion(log logging.Logger) { diff --git a/internal/bundles/manifest.go b/internal/bundles/manifest.go index 854b4fd3f..176c4bd33 100644 --- a/internal/bundles/manifest.go +++ b/internal/bundles/manifest.go @@ -29,26 +29,26 @@ const PythonRequirementsFilename = "requirements.txt" // The manifest describes the type of content (its dependencies, how its // environment can be recreated (if needed) and how it is served/executed). type Manifest struct { - Version int `json:"version" kong:"-"` // Manifest version (always 1) - Locale string `json:"locale" kong:"-"` // User's locale. Currently unused. - Platform string `json:"platform,omitempty" name:"r-version"` // Client R version - Metadata Metadata `json:"metadata" kong:"embed"` // Properties about this deployment. Ignored by shinyapps.io - Python *Python `json:"python,omitempty" kong:"embed,prefix='python-'"` // If non-null, specifies the Python version and dependencies - Jupyter *Jupyter `json:"jupyter,omitempty" kong:"embed"` // If non-null, specifies the Jupyter options - Quarto *Quarto `json:"quarto,omitempty" kong:"embed,prefix='quarto-'"` // If non-null, specifies the Quarto version and engines - Environment *Environment `json:"environment,omitempty" kong:"embed"` // Information about the execution environment - Packages PackageMap `json:"packages" kong:"-"` // Map of R package name to package details - Files ManifestFileMap `json:"files" kong:"-"` // List of file paths contained in the bundle + Version int `json:"version"` // Manifest version (always 1) + Locale string `json:"locale"` // User's locale. Currently unused. + Platform string `json:"platform,omitempty" name:"r-version"` // Client R version + Metadata Metadata `json:"metadata"` // Properties about this deployment. Ignored by shinyapps.io + Python *Python `json:"python,omitempty"` // If non-null, specifies the Python version and dependencies + Jupyter *Jupyter `json:"jupyter,omitempty"` // If non-null, specifies the Jupyter options + Quarto *Quarto `json:"quarto,omitempty"` // If non-null, specifies the Quarto version and engines + Environment *Environment `json:"environment,omitempty"` // Information about the execution environment + Packages PackageMap `json:"packages"` // Map of R package name to package details + Files ManifestFileMap `json:"files"` // List of file paths contained in the bundle } // Metadata contains details about this deployment (type, etc). type Metadata struct { - AppMode apptypes.AppMode `json:"appmode" short:"t" help:"Type of content being deployed. Default is to auto detect."` // Selects the runtime for this content. - ContentCategory string `json:"content_category,omitempty"` // A refinement of the AppMode used by plots and sites - Entrypoint string `json:"entrypoint,omitempty"` // The main file being deployed. - PrimaryRmd string `json:"primary_rmd,omitempty" kong:"-"` // The rendering target for Rmd deployments. - PrimaryHtml string `json:"primary_html,omitempty" kong:"-"` // The default document for static deployments. - HasParameters bool `json:"has_parameters,omitempty" kong:"-"` // True if this is content allows parameter customization. + AppMode apptypes.AppMode `json:"appmode" help:"Type of content being deployed. Default is to auto detect."` // Selects the runtime for this content. + ContentCategory string `json:"content_category,omitempty"` // A refinement of the AppMode used by plots and sites + Entrypoint string `json:"entrypoint,omitempty"` // The main file being deployed. + PrimaryRmd string `json:"primary_rmd,omitempty"` // The rendering target for Rmd deployments. + PrimaryHtml string `json:"primary_html,omitempty"` // The default document for static deployments. + HasParameters bool `json:"has_parameters,omitempty"` // True if this is content allows parameter customization. } type Environment struct { @@ -58,7 +58,7 @@ type Environment struct { type Python struct { Version string `json:"version"` // The Python version - PackageManager PythonPackageManager `json:"package_manager" kong:"embed"` + PackageManager PythonPackageManager `json:"package_manager"` } type Quarto struct { @@ -72,9 +72,9 @@ type Jupyter struct { } type PythonPackageManager struct { - Name string `json:"name" kong:"-"` // Which package manger (always "pip") - Version string `json:"version,omitempty" kong:"-"` // Package manager version - PackageFile string `json:"package_file"` // Filename listing dependencies; usually "requirements.txt" + Name string `json:"name"` + Version string `json:"version,omitempty"` // Package manager version + PackageFile string `json:"package_file"` // Filename listing dependencies; usually "requirements.txt" } type PackageMap map[string]Package @@ -144,9 +144,13 @@ func ReadManifestFile(path util.Path) (*Manifest, error) { func NewManifest() *Manifest { return &Manifest{ - Version: 1, - Packages: make(PackageMap), - Files: make(ManifestFileMap), + Version: 1, + Python: &Python{}, + Quarto: &Quarto{}, + Jupyter: &Jupyter{}, + Environment: &Environment{}, + Packages: make(PackageMap), + Files: make(ManifestFileMap), } } diff --git a/internal/bundles/manifest_test.go b/internal/bundles/manifest_test.go index 146e212c6..88e228b68 100644 --- a/internal/bundles/manifest_test.go +++ b/internal/bundles/manifest_test.go @@ -41,10 +41,6 @@ func (s *ManifestSuite) TestNewManifest() { s.Equal(1, manifest.Version) s.Empty(manifest.Packages) s.Empty(manifest.Files) - s.Nil(manifest.Python) - s.Nil(manifest.Jupyter) - s.Nil(manifest.Quarto) - s.Nil(manifest.Environment) } func (s *ManifestSuite) TestAddFile() { @@ -62,10 +58,14 @@ func (s *ManifestSuite) TestReadManifest() { manifest, err := ReadManifest(reader) s.Nil(err) s.Equal(&Manifest{ - Version: 1, - Platform: "4.1.0", - Packages: PackageMap{}, - Files: ManifestFileMap{}, + Version: 1, + Platform: "4.1.0", + Python: &Python{}, + Quarto: &Quarto{}, + Jupyter: &Jupyter{}, + Environment: &Environment{}, + Packages: PackageMap{}, + Files: ManifestFileMap{}, }, manifest) } @@ -100,10 +100,14 @@ func (s *ManifestSuite) TestReadManifestFile() { manifest, err := ReadManifestFile(manifestPath) s.Nil(err) s.Equal(&Manifest{ - Version: 1, - Platform: "4.1.0", - Packages: PackageMap{}, - Files: ManifestFileMap{}, + Version: 1, + Platform: "4.1.0", + Python: &Python{}, + Quarto: &Quarto{}, + Jupyter: &Jupyter{}, + Environment: &Environment{}, + Packages: PackageMap{}, + Files: ManifestFileMap{}, }, manifest) } diff --git a/internal/cli_types/base.go b/internal/cli_types/base.go index dbe25eb82..e1ba38ac5 100644 --- a/internal/cli_types/base.go +++ b/internal/cli_types/base.go @@ -13,7 +13,7 @@ import ( type CommonArgs struct { Debug bool `help:"Enable debug mode." env:"CONNECT_DEBUG"` - Profile string `help:"Enable CPU profiling"` + Profile string `help:"Enable CPU profiling" kong:"hidden"` } type Log interface { @@ -45,11 +45,11 @@ type UIArgs struct { } type PublishArgs struct { - Python util.Path `help:"Path to Python interpreter for this content. Required unless you specify --python-version and include a requirements.txt file. Default is the Python 3 on your PATH."` - Path util.Path `help:"Path to directory containing files to publish, or a file within that directory." arg:""` - Config string `help:"Name of metadata directory to load/save (see ./.posit/deployments/)."` - New bool `help:"Create a new deployment instead of updating the previous deployment."` + Path util.Path `help:"Path to directory containing files to publish, or a file within that directory." arg:""` + AccountName string `short:"n" help:"Nickname of destination publishing account."` + Config string `short:"c" help:"Configuration file name (in .posit/publish/). Default is deploy.toml."` + New bool `help:"Create a new deployment instead of updating the previous deployment."` // Store for the deployment State that will be served to the UI, // published, written to manifest and metadata files, etc. - State *state.Deployment `kong:"embed"` + State *state.Deployment `kong:"-"` } diff --git a/internal/publish/publish.go b/internal/publish/publish.go index d1f9c09c9..ef2b495ee 100644 --- a/internal/publish/publish.go +++ b/internal/publish/publish.go @@ -114,7 +114,7 @@ func (p *Publisher) publish( lister accounts.AccountList, log logging.Logger) error { - account, err := lister.GetAccountByName(p.args.State.Target.AccountName) + account, err := lister.GetAccountByName(p.args.AccountName) if err != nil { return err } @@ -220,13 +220,11 @@ func (p *Publisher) publishWithClient( p.args.State.Target = state.TargetID{ ServerType: account.ServerType, - AccountName: account.Name, ServerURL: account.URL, ContentId: contentID, ContentName: "", Username: account.AccountName, BundleId: types.NewOptional(bundleID), - DeployedAt: types.NewOptional(time.Now()), } taskID, err := withLog(events.PublishDeployBundleOp, "Initiating bundle deployment", "task_id", log, func() (types.TaskID, error) { diff --git a/internal/services/api/deployments/services.go b/internal/services/api/deployments/services.go index 776b30ee6..0dcc78725 100644 --- a/internal/services/api/deployments/services.go +++ b/internal/services/api/deployments/services.go @@ -53,7 +53,6 @@ func (s deploymentsService) SetDeploymentAccount(lister accounts.AccountList, na return s.deployment, err } - s.deployment.Target.AccountName = account.Name s.deployment.Target.ServerType = account.ServerType s.deployment.Target.ServerURL = account.URL diff --git a/internal/services/api/deployments/services_test.go b/internal/services/api/deployments/services_test.go index 4f35c7070..fbfef2082 100644 --- a/internal/services/api/deployments/services_test.go +++ b/internal/services/api/deployments/services_test.go @@ -60,7 +60,6 @@ func (s *ServicesSuite) TestSetDeploymentTitle() { func (s *ServicesSuite) TestSetDeploymentAccount() { src := state.NewDeployment() - s.Equal(src.Target.AccountName, "") s.Equal(src.Target.ServerType, accounts.ServerType("")) s.Equal(src.Target.ServerURL, "") @@ -79,7 +78,6 @@ func (s *ServicesSuite) TestSetDeploymentAccount() { res, err := service.SetDeploymentAccount(lister, "test") s.Nil(err) - s.Equal(res.Target.AccountName, result.Name) s.Equal(res.Target.ServerType, result.ServerType) s.Equal(res.Target.ServerURL, result.URL) diff --git a/internal/state/connect.go b/internal/state/connect.go index 67342b6a9..b803ef2da 100644 --- a/internal/state/connect.go +++ b/internal/state/connect.go @@ -9,8 +9,8 @@ import ( ) type ConnectDeployment struct { - Content ConnectContent `json:"content" kong:"embed"` - Environment []ConnectEnvironmentVariable `json:"environment" short:"E"` + Content ConnectContent `json:"content"` + Environment []ConnectEnvironmentVariable `json:"environment"` } type ConnectContent struct { diff --git a/internal/state/deployment.go b/internal/state/deployment.go index 24f2d7409..b1c716a47 100644 --- a/internal/state/deployment.go +++ b/internal/state/deployment.go @@ -15,16 +15,14 @@ import ( ) type TargetID struct { - AccountName string `json:"account_name" short:"n" help:"Nickname of destination publishing account."` // Nickname - ServerType accounts.ServerType `json:"server_type" kong:"-"` // Which type of API this server provides - ServerURL string `json:"server_url" kong:"-"` // Server URL - ContentId types.ContentID `json:"content_id" help:"Unique ID of content item to update."` // Content ID (GUID for Connect) - ContentName types.ContentName `json:"content_name" help:"Name of content item to update."` // Content Name (unique per user) + ServerType accounts.ServerType `json:"server_type"` // Which type of API this server provides + ServerURL string `json:"server_url"` // Server URL + ContentId types.ContentID `json:"content_id" help:"Unique ID of content item to update."` // Content ID (GUID for Connect) + ContentName types.ContentName `json:"content_name" help:"Name of content item to update."` // Content Name (unique per user) // These fields are informational and don't affect future deployments. - Username string `json:"username,omitempty" kong:"-"` // Username, if known - BundleId types.NullBundleID `json:"bundle_id" kong:"-"` // Bundle ID that was deployed - DeployedAt types.NullTime `json:"deployed_at" kong:"-"` // Date/time bundle was deployed + Username string `json:"username,omitempty"` // Username, if known + BundleId types.NullBundleID `json:"bundle_id"` // Bundle ID that was deployed } type LocalDeploymentID string @@ -38,12 +36,12 @@ func NewLocalID() (LocalDeploymentID, error) { } type Deployment struct { - LocalID LocalDeploymentID `json:"local_id" kong:"-"` // Unique ID of this publishing operation. Only valid for this run of the agent. - SourceDir util.Path `json:"source_path" kong:"-"` // Absolute path to source directory being published - Target TargetID `json:"target" kong:"embed"` // Identity of previous deployment - Manifest bundles.Manifest `json:"manifest" kong:"embed"` // manifest.json content for this deployment - Connect ConnectDeployment `json:"connect" kong:"embed"` // Connect metadata for this deployment, if target is Connect - PythonRequirements []byte `json:"python_requirements" kong:"-"` // Content of requirements.txt to include + LocalID LocalDeploymentID `json:"local_id"` // Unique ID of this publishing operation. Only valid for this run of the agent. + SourceDir util.Path `json:"source_path"` // Absolute path to source directory being published + Target TargetID `json:"target"` // Identity of previous deployment + Manifest bundles.Manifest `json:"manifest"` // manifest.json content for this deployment + Connect ConnectDeployment `json:"connect"` // Connect metadata for this deployment, if target is Connect + PythonRequirements []byte `json:"python_requirements"` // Content of requirements.txt to include } func NewDeployment() *Deployment { @@ -59,9 +57,6 @@ func (d *Deployment) Merge(other *Deployment) { if other.SourceDir.Path() != "" { d.SourceDir = other.SourceDir } - if other.Target.AccountName != "" { - d.Target.AccountName = other.Target.AccountName - } if other.Target.ServerType != "" { d.Target.ServerType = other.Target.ServerType } @@ -201,16 +196,7 @@ func listDeployments(sourceDir util.Path, log logging.Logger) ([]*Deployment, er } } sort.Slice(deployments, func(i, j int) bool { - // Sort in reverse order by deployment time - t1, t1valid := deployments[i].Target.DeployedAt.Get() - t2, t2valid := deployments[j].Target.DeployedAt.Get() - if t1valid && t2valid { - return t1.After(t2) - } else if t1valid { - return true - } else { - return false - } + return deployments[i].Target.ServerURL < deployments[j].Target.ServerURL }) return deployments, nil } diff --git a/internal/state/deployment_test.go b/internal/state/deployment_test.go index 839e3e349..fbb87b863 100644 --- a/internal/state/deployment_test.go +++ b/internal/state/deployment_test.go @@ -35,7 +35,6 @@ func (s *DeploymentSuite) TestMergeEmpty() { orig := NewDeployment() orig.SourceDir = util.NewPath("/my/dir", nil) orig.PythonRequirements = []byte("numpy\npandas\n") - orig.Target.AccountName = "my-account" orig.Target.ServerType = accounts.ServerTypeConnect orig.Target.ServerURL = "https://connect.example.com" orig.Target.ContentId = "abc123" @@ -51,7 +50,6 @@ func (s *DeploymentSuite) TestMergeNonEmpty() { orig := NewDeployment() orig.SourceDir = util.NewPath("/my/dir", nil) orig.PythonRequirements = []byte("numpy\npandas\n") - orig.Target.AccountName = "my-account" orig.Target.ServerType = accounts.ServerTypeConnect orig.Target.ServerURL = "https://connect.example.com" orig.Target.ContentId = "abc123" @@ -61,7 +59,6 @@ func (s *DeploymentSuite) TestMergeNonEmpty() { other := NewDeployment() other.SourceDir = util.NewPath("/other/dir", nil) other.PythonRequirements = []byte("flask\n") - other.Target.AccountName = "your-account" other.Target.ServerType = accounts.ServerTypeShinyappsIO other.Target.ServerURL = "https://shinyapps.io" other.Target.ContentId = types.ContentID("99") @@ -69,7 +66,6 @@ func (s *DeploymentSuite) TestMergeNonEmpty() { merged.Merge(other) s.Equal(other.SourceDir, merged.SourceDir) s.Equal([]byte("numpy\npandas\nflask\n"), merged.PythonRequirements) - s.Equal("your-account", merged.Target.AccountName) s.Equal(accounts.ServerTypeShinyappsIO, merged.Target.ServerType) s.Equal("https://shinyapps.io", merged.Target.ServerURL) s.Equal(types.ContentID("99"), merged.Target.ContentId) @@ -90,10 +86,14 @@ func (s *DeploymentSuite) TestLoadManifest() { err = deployment.LoadManifest(path, log) s.Nil(err) s.Equal(bundles.Manifest{ - Version: 1, - Platform: "4.1.0", - Packages: bundles.PackageMap{}, - Files: bundles.ManifestFileMap{}, + Version: 1, + Platform: "4.1.0", + Python: &bundles.Python{}, + Quarto: &bundles.Quarto{}, + Jupyter: &bundles.Jupyter{}, + Environment: &bundles.Environment{}, + Packages: bundles.PackageMap{}, + Files: bundles.ManifestFileMap{}, }, deployment.Manifest) } @@ -111,10 +111,14 @@ func (s *DeploymentSuite) TestLoadManifestDir() { err = deployment.LoadManifest(path, log) s.Nil(err) s.Equal(bundles.Manifest{ - Version: 1, - Platform: "4.1.0", - Packages: bundles.PackageMap{}, - Files: bundles.ManifestFileMap{}, + Version: 1, + Platform: "4.1.0", + Python: &bundles.Python{}, + Quarto: &bundles.Quarto{}, + Jupyter: &bundles.Jupyter{}, + Environment: &bundles.Environment{}, + Packages: bundles.PackageMap{}, + Files: bundles.ManifestFileMap{}, }, deployment.Manifest) } diff --git a/web/src/api/types/deployments.ts b/web/src/api/types/deployments.ts index b8bbc92f9..056d075e1 100644 --- a/web/src/api/types/deployments.ts +++ b/web/src/api/types/deployments.ts @@ -12,7 +12,6 @@ export type Target = { contentName: string; username: string; bundleId: string | null; - deployedAt: number | null; } export type Deployment = { diff --git a/web/tests/unit/components/configurePublish/FilesToPublish.test.ts b/web/tests/unit/components/configurePublish/FilesToPublish.test.ts index 8526a6742..ff18b6c3c 100644 --- a/web/tests/unit/components/configurePublish/FilesToPublish.test.ts +++ b/web/tests/unit/components/configurePublish/FilesToPublish.test.ts @@ -141,7 +141,6 @@ describe('description', () => { contentName: '', username: '', bundleId: null, - deployedAt: null }, manifest: { version: 1,