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

Support build artifact dependencies #4752

Closed
wants to merge 20 commits into from

Conversation

gsquared94
Copy link
Collaborator

@gsquared94 gsquared94 commented Sep 1, 2020

Related: #4713

Description:

  • Added ArtifactDependency struct to Artifact. This will allow us to define a requires section in the skaffold config
build:
 artifacts:
   - image: leeroy-app
     requires:
       - alias: BASE
         image: simple-go-app
  • Added skaffold config validations:

    • Artifact dependencies don't result in a circular dependency
    • The same alias or image name aren't repeated in a single dependency list
  • Replaced InSequence and InParallel build controllers with an InOrder function in scheduler.go

// InOrder builds a list of artifacts in dependency order.
// `concurrency` specifies the max number of builds that can run at any one time. If concurrency is 0, then it's set to the length of the `artifacts` slice.
// Each artifact build runs in its own goroutine. It limits the max concurrency using a buffered channel like a semaphore.
// At the same time, it uses the `artifactChanModel` to model the artifacts dependency graph and to make each artifact build wait for its required artifacts' builds.
func InOrder(ctx context.Context, out io.Writer, tags tag.ImageTags, artifacts []*latest.Artifact, buildArtifact ArtifactBuilder, concurrency int) ([]Artifact, error) {

Next steps:

  • Inject the image dependencies into the various builders. Will start with docker and buildpacks builder where it's obvious that this info will be passed in as build args and env variable respectively.

@codecov
Copy link

codecov bot commented Sep 1, 2020

Codecov Report

Merging #4752 into master will increase coverage by 0.16%.
The diff coverage is 95.50%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #4752      +/-   ##
==========================================
+ Coverage   73.81%   73.97%   +0.16%     
==========================================
  Files         347      346       -1     
  Lines       13761    13848      +87     
==========================================
+ Hits        10157    10244      +87     
  Misses       2971     2971              
  Partials      633      633              
Impacted Files Coverage Δ
pkg/skaffold/build/cluster/cluster.go 26.47% <0.00%> (ø)
pkg/skaffold/build/cluster/kaniko.go 0.00% <0.00%> (ø)
pkg/skaffold/build/gcb/cloud_build.go 0.00% <0.00%> (ø)
pkg/skaffold/schema/latest/config.go 100.00% <ø> (ø)
pkg/skaffold/build/scheduler.go 98.79% <96.42%> (ø)
pkg/skaffold/build/local/local.go 63.63% <100.00%> (ø)
pkg/skaffold/diagnose/diagnose.go 58.33% <100.00%> (ø)
pkg/skaffold/docker/context.go 80.00% <100.00%> (ø)
pkg/skaffold/docker/image.go 73.36% <100.00%> (ø)
pkg/skaffold/schema/defaults/defaults.go 91.21% <100.00%> (+0.21%) ⬆️
... and 10 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 677d665...743f483. Read the comment docs.

@gsquared94 gsquared94 marked this pull request as ready for review September 1, 2020 17:36
@gsquared94 gsquared94 requested a review from a team as a code owner September 1, 2020 17:36
Copy link
Member

@briandealwis briandealwis left a comment

Choose a reason for hiding this comment

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

Looking good!

pkg/skaffold/schema/latest/config.go Outdated Show resolved Hide resolved
pkg/skaffold/schema/latest/config.go Outdated Show resolved Hide resolved
pkg/skaffold/schema/validation/validation.go Outdated Show resolved Hide resolved
Comment on lines 145 to 150
if namesMap[d.ImageName] == seen {
errs = append(errs, fmt.Errorf("invalid build dependency for artifact %q: image name %q repeated", a.ImageName, d.ImageName))
namesMap[d.ImageName] = recorded
} else if namesMap[d.ImageName] == unseen {
namesMap[d.ImageName] = seen
}
Copy link
Member

Choose a reason for hiding this comment

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

Why do we disallow repeated image names? We do know some people have several artifacts using the same context.

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe this is just validating that for each artifact, all dependency aliases/image names are unique. I don't think we want to allow two distinct dependencies which share an image name (and are then effectively the same dependency), right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

exactly what @nkubala said

Copy link
Member

Choose a reason for hiding this comment

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

This code is validating that

  1. all aliases are unique, and
  2. all image references are unique

I agree with (1). But people use multi-stage Dockerfiles with a number of build images with names. We can let that decision be set in the skaffold.yaml.

artifacts:
- image: our/jarbuild
- image: our/app
   requires:
   - alias: lib1
      image: our/jarbuild
   - alias: lib2
      image: our/jarbuild

Or let me turn this around: what's the compelling reason why we cannot let the same image have multiple aliases?

Copy link
Member

Choose a reason for hiding this comment

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

^^^ (Github didn't unresolve the conversation when I submitted this comment as part of a review)

Copy link
Collaborator Author

@gsquared94 gsquared94 Sep 15, 2020

Choose a reason for hiding this comment

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

@briandealwis I think I get your point now.
The dockerfile:

FROM $builder as builder1
RUN foo1
FROM $builder as builder2
RUN foo2
FROM bar
COPY --from=builder1 foobar

can be simplified into:

FROM $builder1
RUN foo1
FROM $builder2
RUN foo2
FROM bar
COPY --from=$builder1 foobar

where $builder1 and $builder2 both reference the same image that $builder referenced earlier.
Is this what you mean? Although would this really work since $builder1 and $builder2 resolve to the same value wouldn't it be ambiguous?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

There's no compelling reason for this validation, I just thought that it didn't seem valuable to permit multiple aliases referencing the same image. But I'll remove it anyway.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Removed this extra validation in latest commit

pkg/skaffold/schema/validation/validation.go Outdated Show resolved Hide resolved
pkg/skaffold/schema/validation/validation_test.go Outdated Show resolved Hide resolved
pkg/skaffold/build/scheduler.go Outdated Show resolved Hide resolved
pkg/skaffold/build/scheduler.go Outdated Show resolved Hide resolved
pkg/skaffold/build/scheduler.go Outdated Show resolved Hide resolved
pkg/skaffold/build/scheduler_test.go Show resolved Hide resolved
Copy link
Contributor

@nkubala nkubala left a comment

Choose a reason for hiding this comment

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

a few comments but in general this is looking good

for _, dep := range a.requiredArtifactChans {
// wait for dependency to complete build
select {
case <-ctx.Done():
Copy link
Contributor

Choose a reason for hiding this comment

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

do we need to catch ctx.Cancelled() here as well? or is that handled somewhere further down/up?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm not sure I follow. context here has already been setup with cancellation so when cancel gets called in the main go routine it'll close this done channel.
From docs WithCancel arranges for Done to be closed when cancel is called;

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I get it know.

case <-ctx.Done():
    if ctx.Err() == ctx.Cancelled():
      ...

I'll add that. Thanks!

pkg/skaffold/build/scheduler.go Outdated Show resolved Hide resolved
pkg/skaffold/build/scheduler.go Show resolved Hide resolved
pkg/skaffold/build/scheduler_test.go Outdated Show resolved Hide resolved
pkg/skaffold/schema/latest/config.go Outdated Show resolved Hide resolved
for _, artifact := range artifacts {
if err := dfs(artifact, visited, make(map[string]bool), m); err != nil {
errs = append(errs, err)
return
Copy link
Contributor

Choose a reason for hiding this comment

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

since this is part of a larger error collection block that finishes all checks and surfaces all errors, should we not fail fast here and let this run its course?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

since this validation is for cycle detection, we'll get stuck on a specific cycle if it exists and it seemed non-trivial to exit that cycle and then try to find the other cycles. So we won't be able to collect all cycle violations at a go which is why I thought to just surface out the first violation and the user can fix it one by one. WDYT?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Comment on lines 145 to 150
if namesMap[d.ImageName] == seen {
errs = append(errs, fmt.Errorf("invalid build dependency for artifact %q: image name %q repeated", a.ImageName, d.ImageName))
namesMap[d.ImageName] = recorded
} else if namesMap[d.ImageName] == unseen {
namesMap[d.ImageName] = seen
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe this is just validating that for each artifact, all dependency aliases/image names are unique. I don't think we want to allow two distinct dependencies which share an image name (and are then effectively the same dependency), right?

@gsquared94
Copy link
Collaborator Author

PTAL again @nkubala and @briandealwis

@briandealwis briandealwis changed the title Schema change for build artifact dependencies; new "in order" build controller; validations Support build artifact dependencies Sep 14, 2020
Copy link
Member

@briandealwis briandealwis left a comment

Choose a reason for hiding this comment

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

What happens / should happen if there when one of the artifacts encounters a build error?

pkg/skaffold/schema/validation/validation_test.go Outdated Show resolved Hide resolved
Comment on lines 145 to 150
if namesMap[d.ImageName] == seen {
errs = append(errs, fmt.Errorf("invalid build dependency for artifact %q: image name %q repeated", a.ImageName, d.ImageName))
namesMap[d.ImageName] = recorded
} else if namesMap[d.ImageName] == unseen {
namesMap[d.ImageName] = seen
}
Copy link
Member

Choose a reason for hiding this comment

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

This code is validating that

  1. all aliases are unique, and
  2. all image references are unique

I agree with (1). But people use multi-stage Dockerfiles with a number of build images with names. We can let that decision be set in the skaffold.yaml.

artifacts:
- image: our/jarbuild
- image: our/app
   requires:
   - alias: lib1
      image: our/jarbuild
   - alias: lib2
      image: our/jarbuild

Or let me turn this around: what's the compelling reason why we cannot let the same image have multiple aliases?

pkg/skaffold/build/scheduler_test.go Outdated Show resolved Hide resolved
pkg/skaffold/build/scheduler.go Outdated Show resolved Hide resolved
pkg/skaffold/build/scheduler.go Outdated Show resolved Hide resolved
pkg/skaffold/build/scheduler.go Outdated Show resolved Hide resolved
@briandealwis
Copy link
Member

Handling dependency errors can be done in runBuild().

@gsquared94
Copy link
Collaborator Author

What happens / should happen if there when one of the artifacts encounters a build error?

Currently it just runs all other builds even if a required build has failed since I haven't introduced the dependency aliases into the builders yet. Once the builders actually read the ArtifactDependency and search for their images it'll fail with a required image not found sort of error.

I'm thinking about changing the current behavior where we just report about the first failed build (see here) and instead string.Join(errors, "\n") and report all the failed builds. If I did that it'd probably be even better to report the errors in the order that they were built. I'll add details about that in the follow up PR. WDYT?

@gsquared94
Copy link
Collaborator Author

gsquared94 commented Sep 15, 2020

Handling dependency errors can be done in runBuild().

yes, that's the intention. Let me know if you want me to add code to handle dependency build failures in this PR itself. Otherwise I'll do that in the followup

@gsquared94 gsquared94 marked this pull request as draft September 15, 2020 16:02
@gsquared94 gsquared94 marked this pull request as ready for review September 15, 2020 17:12
@gsquared94
Copy link
Collaborator Author

closing this for now, will reopen after incorporating changes from #4794 when that gets approved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants