Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@ PLANS.md

# unikraft
.unikraft/
test-fetch-on-stale-sidecar.log
2 changes: 1 addition & 1 deletion internal/cmd/sidecar.go
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ func newSidecarSyncCmd() *cobra.Command {

cmd.Flags().StringVar(&sidecarID, "sidecar-id", "", "Sidecar ID (defaults to active sidecar)")
cmd.Flags().StringVar(&identityFile, "identity-file", "", "SSH identity file")
cmd.Flags().StringVar(&workdir, "workdir", "", "Destination path on sidecar (auto-detected as ~/workspace/<repo> when omitted)")
cmd.Flags().StringVar(&workdir, "workdir", "", "Destination path on sidecar (defaults to /home/user/<repo> when omitted)")

return cmd
}
Expand Down
7 changes: 5 additions & 2 deletions internal/cmd/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func newValidateCmd() *cobra.Command {
cmd.Flags().StringVar(&opts.sidecarID, "sidecar-id", "", "Sidecar ID for remote execution")
cmd.Flags().StringVar(&opts.orgID, "org-id", "", "Organization ID (used when creating a new sidecar)")
cmd.Flags().StringVar(&opts.identityFile, "identity-file", "", "SSH identity file (uses ssh-agent or ~/.ssh/chunk_ai when omitted)")
cmd.Flags().StringVar(&opts.workdir, "workdir", "", "Working directory on sidecar (reads from sidecar.json, defaults to ./workspace)")
cmd.Flags().StringVar(&opts.workdir, "workdir", "", "Working directory on sidecar (reads from sidecar.json, defaults to /home/user/<repo>)")
cmd.Flags().BoolVar(&opts.dryRun, "dry-run", false, "Show commands without executing")
cmd.Flags().BoolVar(&opts.list, "list", false, "List all configured commands")
cmd.Flags().BoolVar(&opts.jsonOut, "json", false, "Output as JSON (only applies with --list)")
Expand Down Expand Up @@ -423,7 +423,10 @@ func openSSHSession(ctx context.Context, client *circleci.Client, sidecarID, ide
}
cwd, _ := os.Getwd()
_, repo, _ := gitremote.DetectOrgAndRepo(cwd)
dest := sidecar.ResolveWorkspace(ctx, workdir, repo)
dest, err := sidecar.ResolveWorkspace(ctx, workdir, repo)
if err != nil {
return nil, "", &userError{msg: "Could not determine workspace path.", err: err}
}
merged := hostForwardEnv(rc.CircleCIToken)
if merged == nil {
merged = make(map[string]string, len(envVars))
Expand Down
32 changes: 28 additions & 4 deletions internal/sidecar/active_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,8 @@ func TestResolveWorkspaceCLIFlagWins(t *testing.T) {
ctx := context.Background()
assert.NilError(t, SaveActive(ctx, ActiveSidecar{SidecarID: "sb-1", Workspace: "/workspace/saved"}))

got := ResolveWorkspace(ctx, "/workspace/override", "myrepo")
got, err := ResolveWorkspace(ctx, "/workspace/override", "myrepo")
assert.NilError(t, err)
assert.Equal(t, got, "/workspace/override")
}

Expand All @@ -228,7 +229,8 @@ func TestResolveWorkspaceSidecarFallback(t *testing.T) {
ctx := context.Background()
assert.NilError(t, SaveActive(ctx, ActiveSidecar{SidecarID: "sb-1", Workspace: "/workspace/saved"}))

got := ResolveWorkspace(ctx, "", "myrepo")
got, err := ResolveWorkspace(ctx, "", "myrepo")
assert.NilError(t, err)
assert.Equal(t, got, "/workspace/saved")
}

Expand All @@ -237,6 +239,28 @@ func TestResolveWorkspaceDefaultFallback(t *testing.T) {
t.Chdir(dir)
setupXDGData(t)

got := ResolveWorkspace(context.Background(), "", "myrepo")
assert.Equal(t, got, "./workspace/myrepo")
got, err := ResolveWorkspace(context.Background(), "", "myrepo")
assert.NilError(t, err)
assert.Equal(t, got, "/home/user/myrepo")
}

func TestResolveWorkspaceEmptyRepoErrors(t *testing.T) {
dir := t.TempDir()
t.Chdir(dir)
setupXDGData(t)

_, err := ResolveWorkspace(context.Background(), "", "")
assert.ErrorContains(t, err, "repo name is empty")
}

func TestResolveWorkspaceSidecarHomeEnvVar(t *testing.T) {
dir := t.TempDir()
t.Chdir(dir)
setupXDGData(t)

t.Setenv("CHUNK_SIDECAR_HOME", "/home/runner")

got, err := ResolveWorkspace(context.Background(), "", "myrepo")
assert.NilError(t, err)
assert.Equal(t, got, "/home/runner/myrepo")
}
31 changes: 22 additions & 9 deletions internal/sidecar/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,31 @@ import (
"github.com/CircleCI-Public/chunk-cli/internal/iostream"
)

const workspaceDir = "./workspace"
// sidecarHome returns the base home directory on the sidecar. It reads
// CHUNK_SIDECAR_HOME so the default "/home/user" can be overridden when the
// image uses a different OS user.
func sidecarHome() string {
if h := os.Getenv("CHUNK_SIDECAR_HOME"); h != "" {
return h
}
return "/home/user"
}

// ResolveWorkspace determines the workspace path. Priority:
// 1. CLI --workdir flag 2. sidecar.json workspace 3. default ./workspace/<repo>.
func ResolveWorkspace(ctx context.Context, cliWorkdir, repo string) string {
// 1. CLI --workdir flag 2. sidecar.json workspace 3. default <sidecarHome>/<repo>.
// Returns an error if no repo-specific path can be determined (repo empty and no
// saved workspace), because the bare home dir is not safe to pass to rm -rf.
func ResolveWorkspace(ctx context.Context, cliWorkdir, repo string) (string, error) {
if cliWorkdir != "" {
return cliWorkdir
return cliWorkdir, nil
}
if active, err := LoadActive(ctx); err == nil && active != nil && active.Workspace != "" {
return active.Workspace
return active.Workspace, nil
}
if repo == "" {
return workspaceDir
return "", fmt.Errorf("sync: cannot determine workspace: repo name is empty and no workspace is saved")
}
return workspaceDir + "/" + repo
return sidecarHome() + "/" + repo, nil
}

// persistWorkspace saves the resolved workspace back to the sidecar file if it
Expand All @@ -48,7 +58,7 @@ func persistWorkspace(ctx context.Context, workspace string) error {
// Sync synchronises local changes to a sidecar over SSH.
// It ensures the workspace base exists, clones the repo into workdir if absent,
// then resets to the remote base and applies a patch of local changes.
// workdir overrides the destination path; defaults to /workspace/<repo>.
// workdir overrides the destination path; defaults to /home/user/<repo>.
func Sync(ctx context.Context,
client *circleci.Client, sidecarID, identityFile, authSock, workdir string, status iostream.StatusFunc) error {

Expand All @@ -67,7 +77,10 @@ func Sync(ctx context.Context,
return fmt.Errorf("sync: %w", err)
}

repoPath := ResolveWorkspace(ctx, workdir, repo)
repoPath, err := ResolveWorkspace(ctx, workdir, repo)
if err != nil {
return err
}

if err := persistWorkspace(ctx, repoPath); err != nil {
status(iostream.LevelWarn, fmt.Sprintf("Could not save workspace: %v", err))
Expand Down