Skip to content

Commit

Permalink
Auto-skip: support FROM DOCKERFILE & remotes for WITH DOCKER --load (…
Browse files Browse the repository at this point in the history
…#3522)

Auto-skip support for targets that contain:

```
FROM DOCKERFILE -f /dist/Dockerfile /dist
````
```
FROM DOCKERFILE github.com/earthly/test-remote/from-dockerfile:40080b4fc1fd4881f5123f03ba030055efbbbafe+create-dockerfile/
```
```
FROM DOCKERFILE +create-dockerfile/
```
```
WITH DOCKER --load foo=github.com/earthly/earthly:6610b73131f94cbe594dba3b90665748f21a9b8d+license
```
  • Loading branch information
mikejholly authored Nov 22, 2023
1 parent 33d3fd5 commit f6afedc
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 5 deletions.
20 changes: 20 additions & 0 deletions inputgraph/hash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,23 @@ func TestHashTargetWithDockerNoAlias(t *testing.T) {
hex := fmt.Sprintf("%x", hash)
r.NotEmpty(hex)
}

func TestHashTargetWithDockerRemote(t *testing.T) {
r := require.New(t)
target := domain.Target{
LocalPath: "./testdata/with-docker",
Target: "with-docker-load-remote",
}

ctx := context.Background()
cons := conslogging.New(os.Stderr, &sync.Mutex{}, conslogging.NoColor, 0, conslogging.Info)

hashOpt := HashOpt{Console: cons, Target: target}
org, project, hash, err := HashTarget(ctx, hashOpt)
r.NoError(err)
r.Equal("earthly-technologies", org)
r.Equal("core", project)

hex := fmt.Sprintf("%x", hash)
r.NotEmpty(hex)
}
48 changes: 43 additions & 5 deletions inputgraph/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,6 @@ func (l *loader) handleCopySrc(ctx context.Context, src string, mustExist bool)
return nil
}

// Remote targets aren't supported.
if artifactSrc.Target.IsRemote() {
if supportedRemoteTarget(artifactSrc.Target) {
l.hasher.HashString(artifactSrc.Target.StringCanonical())
Expand Down Expand Up @@ -339,11 +338,32 @@ func (l *loader) handleCommand(ctx context.Context, cmd spec.Command) error {
return l.handleCopy(ctx, cmd)
case command.Arg:
return l.handleArg(ctx, cmd)
case command.FromDockerfile:
return l.handleFromDockerfile(ctx, cmd)
default:
return nil
}
}

func (l *loader) handleFromDockerfile(ctx context.Context, cmd spec.Command) error {
opts := commandflag.FromDockerfileOpts{}
args, err := flagutil.ParseArgsCleaned(command.FromDockerfile, &opts, flagutil.GetArgsCopy(cmd))
if err != nil {
return err
}
if opts.Path != "" {
if err := l.handleCopySrc(ctx, opts.Path, false); err != nil {
return err
}
}
if len(args) > 0 {
if err := l.handleCopySrc(ctx, args[0], false); err != nil {
return err
}
}
return nil
}

func (l *loader) handleArg(ctx context.Context, cmd spec.Command) error {
opts, key, valueOrNil, err := flagutil.ParseArgArgs(ctx, cmd, l.isBaseTarget, l.features.ExplicitGlobal)
if err != nil {
Expand Down Expand Up @@ -388,22 +408,36 @@ func (l *loader) handleWithDocker(ctx context.Context, cmd spec.Command) error {
if err != nil {
return err
}

l.hashCommand(cmd)
opts := commandflag.WithDockerOpts{}

_, err = flagutil.ParseArgsCleaned("WITH DOCKER", &opts, flagutil.GetArgsCopy(cmd))
if err != nil {
return errors.New("failed to parse WITH DOCKER flags")
}

for _, load := range opts.Loads {
_, target, extraArgs, err := earthfile2llb.ParseLoad(load)
if err != nil {
return errors.Wrap(err, "failed to parse --load value")
}

t, err := domain.ParseTarget(target)
if err == nil && t.IsRemote() {
if supportedRemoteTarget(t) {
l.hasher.HashString(t.StringCanonical())
return nil
}
return errUnsupportedRemoteTarget
}

err = l.loadTargetFromString(ctx, target, extraArgs, false)
if err != nil {
return err
}
}

return nil
}

Expand All @@ -412,17 +446,20 @@ func (l *loader) handleIf(ctx context.Context, ifStmt spec.IfStatement) error {
if err := l.loadBlock(ctx, ifStmt.IfBody); err != nil {
return err
}

if ifStmt.ElseBody != nil {
if err := l.loadBlock(ctx, *ifStmt.ElseBody); err != nil {
return err
}
}

for _, elseIf := range ifStmt.ElseIf {
l.hashElseIf(elseIf)
if err := l.loadBlock(ctx, elseIf.Body); err != nil {
return err
}
}

return nil
}

Expand Down Expand Up @@ -578,17 +615,18 @@ func (l *loader) loadTargetFromString(ctx context.Context, targetName string, ar
return errors.Errorf("dynamic target %q cannot be resolved", targetName)
}

relTarget, err := domain.ParseTarget(targetName)
target, err := domain.ParseTarget(targetName)
if err != nil {
return errors.Wrapf(err, "parse target name %s", targetName)
}

targetRef, err := domain.JoinReferences(l.target, relTarget)
targetRef, err := domain.JoinReferences(l.target, target)
if err != nil {
return errors.Wrapf(err, "failed to join %s and %s", l.target, relTarget)
return errors.Wrapf(err, "failed to join %s and %s", l.target, target)
}

target := targetRef.(domain.Target)
target = targetRef.(domain.Target)

fullTargetName := target.String()
if fullTargetName == "" {
return fmt.Errorf("missing target string")
Expand Down
7 changes: 7 additions & 0 deletions inputgraph/testdata/with-docker/Earthfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,10 @@ with-docker-load-args:
WITH DOCKER --load foo=(+load-target --foo=2)
RUN echo "loaded"
END

with-docker-load-remote:
BUILD +load-target
FROM earthly/dind:alpine-3.18-docker-23.0.6-r7
WITH DOCKER --load foo=github.com/earthly/earthly:6610b73131f94cbe594dba3b90665748f21a9b8d+license
RUN echo "loaded"
END
4 changes: 4 additions & 0 deletions tests/autoskip/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FROM alpine:3.18

RUN ls /tmp
ENTRYPOINT ["echo", "hello"]
15 changes: 15 additions & 0 deletions tests/autoskip/Earthfile
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ test-all:
BUILD +test-shell-out
BUILD +test-copy-if-exists
BUILD +test-remote-targets
BUILD +test-from-dockerfile

test-files:
RUN echo hello > my-file
Expand Down Expand Up @@ -193,6 +194,20 @@ test-remote-targets:

DO --pass-args +RUN_EARTHLY_ARGS --earthfile=remote-target.earth --target=+invalid-build --output_contains="complete Git SHA or tag"

test-from-dockerfile:
RUN mkdir /dist
COPY Dockerfile /dist
RUN echo "foo" > /dist/my-file

DO --pass-args +RUN_EARTHLY_ARGS --earthfile=from-dockerfile.earth --target=+local
DO --pass-args +RUN_EARTHLY_ARGS --earthfile=from-dockerfile.earth --target=+local --output_contains="target .* has already been run; exiting"

DO --pass-args +RUN_EARTHLY_ARGS --earthfile=from-dockerfile.earth --target=+local-target
DO --pass-args +RUN_EARTHLY_ARGS --earthfile=from-dockerfile.earth --target=+local-target --output_contains="target .* has already been run; exiting"

DO --pass-args +RUN_EARTHLY_ARGS --earthfile=from-dockerfile.earth --target=+remote
DO --pass-args +RUN_EARTHLY_ARGS --earthfile=from-dockerfile.earth --target=+remote --output_contains="target .* has already been run; exiting"

RUN_EARTHLY_ARGS:
COMMAND
ARG earthfile
Expand Down
29 changes: 29 additions & 0 deletions tests/autoskip/from-dockerfile.earth
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
VERSION 0.7

PROJECT earthly-technologies/core

FROM alpine

local:
FROM DOCKERFILE -f /dist/Dockerfile /dist
RUN echo "hi"

local-target:
FROM DOCKERFILE +create-dockerfile/
RUN echo "hi"

remote:
FROM DOCKERFILE github.com/earthly/test-remote/from-dockerfile:40080b4fc1fd4881f5123f03ba030055efbbbafe+create-dockerfile/
RUN echo "hi"

create-dockerfile:
FROM alpine:3.18
RUN mkdir dist
RUN echo "
FROM alpine:3.18
ARG my_arg=default
RUN echo \${my_arg}
RUN echo \${my_arg} >/arg-value
" > dist/Dockerfile
RUN cat dist/Dockerfile
SAVE ARTIFACT dist/*

0 comments on commit f6afedc

Please sign in to comment.