Skip to content
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
39 changes: 20 additions & 19 deletions cmd/internal/agentcontainer/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -425,26 +425,8 @@ func (cmd *SetupContainerCmd) parseWorkspaceAndSetupInfo() (*provider2.Container

func (cmd *SetupContainerCmd) syncMounts(sctx *setupContext) error {
mounts := config.GetMounts(sctx.setupInfo)

// Snapshot restore runs regardless of the StreamMounts flag: StreamMounts
// only gates the legacy host-streaming path (forced true for non-docker
// drivers), but a snapshot-sourced workspace needs its volumes restored
// on every driver, docker included.
if sctx.workspaceInfo.Source.Snapshot != "" {
if !sctx.workspaceInfo.CLIOptions.Reset && len(mounts) == 1 &&
skipSnapshotRestore(mounts[0].Target) {
return nil
}
log.Infof("restoring snapshot volumes from %s", sctx.workspaceInfo.Source.Snapshot)
if err := agentsnapshot.RestoreVolumes(
sctx.ctx,
sctx.workspaceInfo.Source.Snapshot,
mounts,
sctx.workspaceInfo.CLIOptions.Reset,
); err != nil {
return fmt.Errorf("restore snapshot volumes: %w", err)
}
return nil
return restoreSnapshotMounts(sctx, mounts)
}

if !cmd.StreamMounts {
Expand Down Expand Up @@ -474,6 +456,25 @@ func (cmd *SetupContainerCmd) syncMounts(sctx *setupContext) error {
return nil
}

// restoreSnapshotMounts restores a snapshot-sourced workspace's volumes,
// skipping the restore if the sole mount target already has real content.
func restoreSnapshotMounts(sctx *setupContext, mounts []*config.Mount) error {
if !sctx.workspaceInfo.CLIOptions.Reset && len(mounts) == 1 &&
skipSnapshotRestore(mounts[0].Target) {
return nil
}
log.Infof("restoring snapshot volumes from %s", sctx.workspaceInfo.Source.Snapshot)
if err := agentsnapshot.RestoreVolumes(
sctx.ctx,
sctx.workspaceInfo.Source.Snapshot,
mounts,
sctx.workspaceInfo.CLIOptions.Reset,
); err != nil {
return fmt.Errorf("restore snapshot volumes: %w", err)
}
return nil
}

// synthesizedDevContainerName is the devcontainer.json devsy synthesizes for
// image/none-sourced workspaces (pkg/devcontainer's saveSynthesizedConfig). A
// snapshot-sourced restore gets one too, written into the mount target before
Expand Down
21 changes: 13 additions & 8 deletions cmd/provider/configure_shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,14 +195,7 @@ func initProvider(
}
defer func() { _ = lock.Unlock() }()

if devsyConfig.Current().Providers == nil {
devsyConfig.Current().Providers = map[string]*config.ProviderConfig{}
}
if devsyConfig.Current().Providers[provider.Name] == nil {
devsyConfig.Current().Providers[provider.Name] = &config.ProviderConfig{}
}
entry := devsyConfig.Current().Providers[provider.Name]

entry := providerConfigEntry(devsyConfig, provider.Name)
entry.InitAttempted = true
entry.InitError = ""
entry.Initialized = false
Expand Down Expand Up @@ -231,6 +224,18 @@ func initProvider(
return nil
}

// providerConfigEntry returns the config.ProviderConfig entry for name,
// creating the Providers map and/or the entry itself if either is unset.
func providerConfigEntry(devsyConfig *config.Config, name string) *config.ProviderConfig {
if devsyConfig.Current().Providers == nil {
devsyConfig.Current().Providers = map[string]*config.ProviderConfig{}
}
if devsyConfig.Current().Providers[name] == nil {
devsyConfig.Current().Providers[name] = &config.ProviderConfig{}
}
return devsyConfig.Current().Providers[name]
}

const maxInitErrorLen = 500

func truncateInitError(msg string) string {
Expand Down
25 changes: 16 additions & 9 deletions cmd/provider/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,21 @@ func (cmd *ListCmd) runAvailable(ctx context.Context) error {
return err
}

providers := availableProviderNames(jsonResult)

switch mode {
case output.ModePlain:
return cmd.renderAvailablePlain(providers)
case output.ModeJSON:
return cmd.renderAvailableJSON(providers)
}

return nil
}

// availableProviderNames extracts provider names from repo JSON entries,
// stripping the config.ProviderPrefix and skipping non-matching repos.
func availableProviderNames(jsonResult []map[string]any) []string {
var providers []string
for _, v := range jsonResult {
name, ok := v["name"].(string)
Expand All @@ -187,15 +202,7 @@ func (cmd *ListCmd) runAvailable(ctx context.Context) error {
providers = append(providers, after)
}
}

switch mode {
case output.ModePlain:
return cmd.renderAvailablePlain(providers)
case output.ModeJSON:
return cmd.renderAvailableJSON(providers)
}

return nil
return providers
}

// renderAvailablePlain renders available providers in plain text format.
Expand Down
15 changes: 12 additions & 3 deletions cmd/provider/set_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
cliflags "github.com/devsy-org/devsy/pkg/flags"
"github.com/devsy-org/devsy/pkg/flags/names"
"github.com/devsy-org/devsy/pkg/log"
"github.com/devsy-org/devsy/pkg/provider"
"github.com/devsy-org/devsy/pkg/status"
"github.com/devsy-org/devsy/pkg/workspace"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -89,9 +90,17 @@ func (cmd *SetSourceCmd) Run(ctx context.Context, devsyConfig *config.Config, ar
return nil
}

// Preserve previously user-provided values (default DiscardPriorValues=false).
// The resolver prunes keys absent from the new schema and re-resolves values
// that fail validation, so stale data cannot leak through this path.
return cmd.activateProvider(ctx, devsyConfig, providerConfig, reporter)
}

// activateProvider configures and activates a newly sourced provider,
// preserving previously user-provided option values.
func (cmd *SetSourceCmd) activateProvider(
ctx context.Context,
devsyConfig *config.Config,
providerConfig *provider.ProviderConfig,
reporter status.Reporter,
) error {
if err := ConfigureProvider(ctx, ProviderOptionsConfig{
Provider: providerConfig,
ContextName: devsyConfig.DefaultContext,
Expand Down
82 changes: 49 additions & 33 deletions cmd/workspace/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,25 +128,10 @@ func (cmd *ImportCmd) importWorkspace(
devsyConfig *config.Config,
exportConfig *provider.ExportConfig,
) error {
workspaceDir, err := provider.GetWorkspaceDir(devsyConfig.DefaultContext, cmd.WorkspaceID)
if err != nil {
return fmt.Errorf("get workspace dir: %w", err)
}

// #nosec G301 -- TODO Consider using a more secure permission setting and ownership if needed.
err = os.MkdirAll(workspaceDir, 0o755)
if err != nil {
return fmt.Errorf("create workspace dir: %w", err)
}

decoded, err := base64.RawStdEncoding.DecodeString(exportConfig.Workspace.Data)
if err != nil {
return fmt.Errorf("decode workspace data: %w", err)
}

err = extract.Extract(bytes.NewReader(decoded), workspaceDir)
if err != nil {
return fmt.Errorf("extract workspace data: %w", err)
if err := extractWorkspaceData(
devsyConfig, cmd.WorkspaceID, exportConfig.Workspace.Data,
); err != nil {
return err
}

// exchange config
Expand All @@ -163,21 +148,9 @@ func (cmd *ImportCmd) importWorkspace(
workspaceConfig.Provider.Name = cmd.ProviderID

if exportConfig.SnapshotRef != "" {
sourceStr, devContainerSource, err := snapshotpkg.RestoreComposition(
exportConfig.SnapshotRef,
)
if err != nil {
return fmt.Errorf("parse snapshot ref: %w", err)
if err := applySnapshotSource(workspaceConfig, exportConfig.SnapshotRef); err != nil {
return err
}
parsedSource := provider.ParseWorkspaceSource(sourceStr)
if parsedSource == nil {
return fmt.Errorf(
"compose workspace source from snapshot ref: unexpected source %q",
sourceStr,
)
}
workspaceConfig.Source = *parsedSource
workspaceConfig.DevContainerSource = devContainerSource
}

// save machine config
Expand All @@ -190,6 +163,49 @@ func (cmd *ImportCmd) importWorkspace(
return nil
}

// extractWorkspaceData creates workspaceID's workspace dir and extracts the
// base64-encoded, archived workspace data into it.
func extractWorkspaceData(devsyConfig *config.Config, workspaceID, data string) error {
workspaceDir, err := provider.GetWorkspaceDir(devsyConfig.DefaultContext, workspaceID)
if err != nil {
return fmt.Errorf("get workspace dir: %w", err)
}

// #nosec G301
if err := os.MkdirAll(workspaceDir, 0o755); err != nil {
return fmt.Errorf("create workspace dir: %w", err)
}

decoded, err := base64.RawStdEncoding.DecodeString(data)
if err != nil {
return fmt.Errorf("decode workspace data: %w", err)
}

if err := extract.Extract(bytes.NewReader(decoded), workspaceDir); err != nil {
return fmt.Errorf("extract workspace data: %w", err)
}
return nil
}

// applySnapshotSource resolves snapshotRef into a workspace source and dev
// container source, applying both to workspaceConfig.
func applySnapshotSource(workspaceConfig *provider.Workspace, snapshotRef string) error {
sourceStr, devContainerSource, err := snapshotpkg.RestoreComposition(snapshotRef)
if err != nil {
return fmt.Errorf("parse snapshot ref: %w", err)
}
parsedSource := provider.ParseWorkspaceSource(sourceStr)
if parsedSource == nil {
return fmt.Errorf(
"compose workspace source from snapshot ref: unexpected source %q",
sourceStr,
)
}
workspaceConfig.Source = *parsedSource
workspaceConfig.DevContainerSource = devContainerSource
return nil
}

func (cmd *ImportCmd) importMachine(
devsyConfig *config.Config,
exportConfig *provider.ExportConfig,
Expand Down
33 changes: 24 additions & 9 deletions cmd/workspace/up/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/devsy-org/devsy/pkg/output"
provider2 "github.com/devsy-org/devsy/pkg/provider"
"github.com/devsy-org/devsy/pkg/status"
"github.com/devsy-org/devsy/pkg/task"
"github.com/devsy-org/devsy/pkg/telemetry"
"github.com/devsy-org/devsy/pkg/util"
"github.com/devsy-org/devsy/pkg/workspace"
Expand Down Expand Up @@ -241,16 +242,9 @@ func (cmd *UpCmd) Run(
out := cmd.stdout()
cmd.statusReporter = newStatusReporter(emitJSON, out)

t, err := cmd.openTask()
t, err := cmd.setUpTask(client, emitJSON, out)
if err != nil {
return reportErr(err, emitJSON, out)
}
if t != nil {
cmd.statusReporter = status.Tee(cmd.statusReporter, t.Reporter())
if err := t.SetWorkspaceID(client.Workspace()); err != nil {
failTask(t, err)
return reportErr(err, emitJSON, out)
}
return err
}

wctx, err := cmd.executeDevsyUp(ctx, devsyConfig, client)
Expand Down Expand Up @@ -278,6 +272,27 @@ func (cmd *UpCmd) Run(
return nil
}

// setUpTask opens the run's task (if any), tees the status reporter into it,
// and records the workspace ID on it.
func (cmd *UpCmd) setUpTask(
client client2.BaseWorkspaceClient,
emitJSON bool,
out io.Writer,
) (*task.Task, error) {
t, err := cmd.openTask()
if err != nil {
return nil, reportErr(err, emitJSON, out)
}
if t != nil {
cmd.statusReporter = status.Tee(cmd.statusReporter, t.Reporter())
if err := t.SetWorkspaceID(client.Workspace()); err != nil {
failTask(t, err)
return nil, reportErr(err, emitJSON, out)
}
}
return t, nil
}

// reporter falls back to a no-op when Run hasn't set one yet.
func (cmd *UpCmd) reporter() status.Reporter {
if cmd.statusReporter == nil {
Expand Down
20 changes: 15 additions & 5 deletions cmd/workspace/up/up_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,25 @@ func (cmd *UpCmd) prepareClient(
if err != nil {
return nil, err
}
if !cmd.Platform.Enabled {
proInstance := workspace2.GetProInstance(devsyConfig, client.Provider())
if err := workspace2.CheckProviderUpdate(ctx, devsyConfig, proInstance); err != nil {
return nil, err
}
if err := cmd.checkProviderUpdate(ctx, devsyConfig, client); err != nil {
return nil, err
}
return client, nil
}

// checkProviderUpdate checks for a provider update, unless running in platform mode.
func (cmd *UpCmd) checkProviderUpdate(
ctx context.Context,
devsyConfig *config.Config,
client client2.BaseWorkspaceClient,
) error {
if cmd.Platform.Enabled {
return nil
}
proInstance := workspace2.GetProInstance(devsyConfig, client.Provider())
return workspace2.CheckProviderUpdate(ctx, devsyConfig, proInstance)
}

// ensureArgsForFromSnapshot returns args unchanged unless --from-snapshot is
// set and args is empty, in which case it synthesizes a placeholder arg.
// resolveWorkspace only takes its create-new-workspace path when args is
Expand Down
Loading
Loading