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
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,25 @@ export SHUTTLE_CACHE_DURATION_MIN=60 # Cache a plan for 60 minutes
This feature caches pr. repo, as such the cache isn't shared between working
repositories.

#### Skipping the pull

Every shuttle invocation pulls the plan again, which is wasted time in CI where
the plan was just cloned and cannot have changed during the job. Shuttle
therefore skips pulling an already cloned plan when the `CI` environment
variable is set, as most CI systems do.

Use `SHUTTLE_SKIP_PULL` to control this explicitly. It takes precedence over
`CI`, so it can also force pulling back on:

```bash
export SHUTTLE_SKIP_PULL=true # never pull an already cloned plan
export SHUTTLE_SKIP_PULL=false # always pull, even when CI is set
```

The `--skip-pull` flag does the same for a single invocation. Note that none of
these prevent the initial clone; a plan that isn't available locally yet is
always cloned.

### Overloading the plan

It is possible to overload the plan specified in `shuttle.yaml` file by using
Expand Down
33 changes: 32 additions & 1 deletion pkg/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,34 @@ var gitRegex = regexp.MustCompile(

const cacheDurationMinKey = "SHUTTLE_CACHE_DURATION_MIN"

const skipPullKey = "SHUTTLE_SKIP_PULL"

// skipPullFromEnv reports whether plan pulling should be skipped based on the
// environment. SHUTTLE_SKIP_PULL is the explicit opt in and takes precedence,
// so it can also force pulling back on with a falsy value. Otherwise CI is
// honoured, as CI jobs start from a fresh clone and pulling the plan again on
// every shuttle invocation only costs time.
func skipPullFromEnv(uii *ui.UI) bool {
if v, ok := os.LookupEnv(skipPullKey); ok {
skip, err := strconv.ParseBool(v)
if err != nil {
uii.Verboseln("%s is not a boolean, treating '%s' as false", skipPullKey, v)
return false
}
if skip {
uii.Verboseln("Skipping git plan pulling because %s=%s", skipPullKey, v)
}
return skip
Comment thread
MadsBogeskov marked this conversation as resolved.
}

if os.Getenv("CI") != "" {
uii.Verboseln("Skipping git plan pulling because CI is set")
return true
}

return false
}

func ParsePlan(plan string) Plan {
if !gitRegex.MatchString(plan) {
return Plan{
Expand Down Expand Up @@ -121,6 +149,9 @@ func GetGitPlan(
uii.Verboseln("Skipping git plan pulling")
return planPath, nil
}
if skipPullFromEnv(uii) {
return planPath, nil
}
valid, err := cacheIsValid(planPath)
if err != nil {
return "", err
Expand Down Expand Up @@ -172,7 +203,7 @@ func GetGitPlan(
if cloneToken != "" {
uii.Verboseln("Found clone token in env, but shuttle path was ssh-based. This override will not work.")
}

cloneArg = parsedGitPlan.User + "@" + parsedGitPlan.Repository
} else {
panic(fmt.Sprintf("Unknown protocol '%s'", parsedGitPlan.Protocol))
Expand Down
86 changes: 86 additions & 0 deletions pkg/git/skip_pull_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package git

import (
"io"
"os"
"testing"

"github.com/lunarway/shuttle/pkg/ui"
"github.com/stretchr/testify/assert"
)

func TestSkipPullFromEnv(t *testing.T) {
tt := []struct {
name string
skipPull *string
ci *string
skipsPull bool
}{
{
name: "nothing set",
skipsPull: false,
},
{
name: "CI set",
ci: strPtr("true"),
skipsPull: true,
},
{
name: "CI set to any non-empty value",
ci: strPtr("1"),
skipsPull: true,
},
{
name: "CI set but empty",
ci: strPtr(""),
skipsPull: false,
},
{
name: "SHUTTLE_SKIP_PULL set",
skipPull: strPtr("true"),
skipsPull: true,
},
{
name: "SHUTTLE_SKIP_PULL set but empty is treated as false",
skipPull: strPtr(""),
skipsPull: false,
},
{
name: "SHUTTLE_SKIP_PULL takes precedence over CI",
skipPull: strPtr("false"),
ci: strPtr("true"),
skipsPull: false,
},
{
name: "SHUTTLE_SKIP_PULL with an unparsable value is treated as false",
skipPull: strPtr("yes-please"),
skipsPull: false,
},
}

for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
// Unset by default so the developer's own environment does not leak
// into the test.
t.Setenv(skipPullKey, "")
os.Unsetenv(skipPullKey)
t.Setenv("CI", "")
os.Unsetenv("CI")

if tc.skipPull != nil {
t.Setenv(skipPullKey, *tc.skipPull)
}
if tc.ci != nil {
t.Setenv("CI", *tc.ci)
}

uii := ui.Create(io.Discard, io.Discard)

assert.Equal(t, tc.skipsPull, skipPullFromEnv(uii))
})
}
}

func strPtr(s string) *string {
return &s
}
Loading