Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[dockerfile2llb] fix daemon crash: check for circular dependency in convert #999

Merged
merged 4 commits into from
May 12, 2019
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
47 changes: 43 additions & 4 deletions frontend/dockerfile/dockerfile2llb/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,6 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State,
}
}

if len(allDispatchStates.states) == 1 {
allDispatchStates.states[0].stageName = ""
}

var target *dispatchState
if opt.Target == "" {
target = allDispatchStates.lastTarget()
Expand Down Expand Up @@ -207,6 +203,14 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State,
}
}

if has, state := hasCircularDependency(allDispatchStates.states); has {
return nil, nil, fmt.Errorf("circular dependency detected on stage: %s", state.stageName)
}

if len(allDispatchStates.states) == 1 {
allDispatchStates.states[0].stageName = ""
}

eg, ctx := errgroup.WithContext(ctx)
for i, d := range allDispatchStates.states {
reachable := isReachable(target, d)
Expand Down Expand Up @@ -1130,6 +1134,41 @@ func isReachable(from, to *dispatchState) (ret bool) {
return false
}

func hasCircularDependency(states []*dispatchState) (bool, *dispatchState) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have UT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, added "TestDockerfileCircularDependencies"

var visit func(state *dispatchState) bool
if states == nil {
return false, nil
}
visited := make(map[*dispatchState]struct{})
path := make(map[*dispatchState]struct{})

visit = func(state *dispatchState) bool {
_, ok := visited[state]
if ok {
return false
}
visited[state] = struct{}{}
path[state] = struct{}{}
for dep := range state.deps {
_, ok = path[dep]
if ok {
return true
}
if visit(dep) {
return true
}
}
delete(path, state)
return false
}
for _, state := range states {
if visit(state) {
return true, state
}
}
return false, nil
}

func parseUser(str string) (uid uint32, gid uint32, err error) {
if str == "" {
return 0, 0, nil
Expand Down
20 changes: 20 additions & 0 deletions frontend/dockerfile/dockerfile2llb/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,23 @@ func TestToEnvList(t *testing.T) {
resutl = toEnvMap(args, env)
assert.Equal(t, map[string]string{"key1": "val1", "key2": "v1"}, resutl)
}

func TestDockerfileCircularDependencies(t *testing.T) {
// single stage depends on itself
df := `FROM busybox AS stage0
COPY --from=stage0 f1 /sub/
`
_, _, err := Dockerfile2LLB(appcontext.Context(), []byte(df), ConvertOpt{})
assert.EqualError(t, err, "circular dependency detected on stage: stage0")

// multiple stages with circular dependency
df = `FROM busybox AS stage0
COPY --from=stage2 f1 /sub/
FROM busybox AS stage1
COPY --from=stage0 f2 /sub/
FROM busybox AS stage2
COPY --from=stage1 f2 /sub/
`
_, _, err = Dockerfile2LLB(appcontext.Context(), []byte(df), ConvertOpt{})
assert.EqualError(t, err, "circular dependency detected on stage: stage0")
}