-
Notifications
You must be signed in to change notification settings - Fork 291
ci-operator: add automatic dockerfile inputs detection #4851
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,7 @@ import ( | |
|
|
||
| "github.com/openshift/ci-tools/pkg/api" | ||
| "github.com/openshift/ci-tools/pkg/api/configresolver" | ||
| "github.com/openshift/ci-tools/pkg/dockerfile" | ||
| "github.com/openshift/ci-tools/pkg/kubernetes" | ||
| "github.com/openshift/ci-tools/pkg/labeledclient" | ||
| "github.com/openshift/ci-tools/pkg/lease" | ||
|
|
@@ -1035,9 +1036,128 @@ func runtimeStepConfigsForBuild( | |
|
|
||
| buildSteps = append(buildSteps, getSourceStepsForJobSpec(jobSpec, injectedTest)...) | ||
|
|
||
| // Detect Dockerfile inputs for project images and add InputImageTagStepConfiguration entries | ||
| dockerfileInputSteps, err := detectDockerfileInputs(config, readFile) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to detect Dockerfile inputs: %w", err) | ||
| } | ||
| buildSteps = append(buildSteps, dockerfileInputSteps...) | ||
|
|
||
|
Comment on lines
+1039
to
+1045
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make Dockerfile-input detection best-effort instead of failing the build graph Right now any error from Given this feature is an additive convenience, it’s safer to degrade gracefully on detection errors and continue without auto-generated Consider handling errors like this instead: - // Detect Dockerfile inputs for project images and add InputImageTagStepConfiguration entries
- dockerfileInputSteps, err := detectDockerfileInputs(config, readFile)
- if err != nil {
- return nil, fmt.Errorf("failed to detect Dockerfile inputs: %w", err)
- }
- buildSteps = append(buildSteps, dockerfileInputSteps...)
+ // Detect Dockerfile inputs for project images and add InputImageTagStepConfiguration entries.
+ // This is best-effort and should not break existing configurations.
+ dockerfileInputSteps, err := detectDockerfileInputs(config, readFile)
+ if err != nil {
+ logrus.WithError(err).Warn("Dockerfile inputs detection failed; continuing without auto-detected base images")
+ } else {
+ buildSteps = append(buildSteps, dockerfileInputSteps...)
+ }This keeps the new functionality while avoiding regressions for configs with unusual but valid image references. Also applies to: 1042-1131 🤖 Prompt for AI Agents |
||
| return buildSteps, nil | ||
| } | ||
|
|
||
| // detectDockerfileInputs reads Dockerfiles for project images and detects registry.ci.openshift.org | ||
| // references that should be added as base images | ||
| func detectDockerfileInputs(config *api.ReleaseBuildConfiguration, readFile readFile) ([]api.StepConfiguration, error) { | ||
| var steps []api.StepConfiguration | ||
|
|
||
| for i := range config.Images { | ||
| image := &config.Images[i] | ||
| dockerfileContent, dockerfilePath, err := readDockerfileForImage(image, readFile) | ||
| if err != nil { | ||
| logrus.WithError(err).WithField("image", image.To).Debug("Failed to read Dockerfile for input detection, skipping") | ||
| continue | ||
| } | ||
| if dockerfileContent == nil { | ||
| continue | ||
| } | ||
|
|
||
| imageSteps, err := processDetectedBaseImages(config, image, dockerfileContent, dockerfilePath) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| steps = append(steps, imageSteps...) | ||
| } | ||
|
|
||
| return steps, nil | ||
| } | ||
|
|
||
| // readDockerfileForImage reads the Dockerfile content for a given image configuration | ||
| // Returns content, a description of the source, and any error | ||
| func readDockerfileForImage(image *api.ProjectDirectoryImageBuildStepConfiguration, readFile readFile) ([]byte, string, error) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The nesting levels of this body can be decreased by doing so: func readDockerfileForImage(image *api.ProjectDirectoryImageBuildStepConfiguration, readFile readFile) ([]byte, string, error) {
if image.DockerfileLiteral != nil {
return []byte(*image.DockerfileLiteral), "dockerfile_literal"
}
// Everything else here, no `else`
} |
||
|
|
||
| if image.DockerfileLiteral != nil { | ||
| return []byte(*image.DockerfileLiteral), "dockerfile_literal", nil | ||
| } | ||
| dockerfilePath := "Dockerfile" | ||
| if image.DockerfilePath != "" { | ||
| dockerfilePath = image.DockerfilePath | ||
| } | ||
| if image.ContextDir != "" { | ||
| dockerfilePath = fmt.Sprintf("%s/%s", image.ContextDir, dockerfilePath) | ||
| } | ||
|
|
||
| dockerfileContent, err := readFile(dockerfilePath) | ||
| if err != nil { | ||
| return nil, dockerfilePath, fmt.Errorf("failed to read Dockerfile at %s: %w", dockerfilePath, err) | ||
| } | ||
|
|
||
| return dockerfileContent, dockerfilePath, nil | ||
| } | ||
|
|
||
| // processDetectedBaseImages detects base images from a Dockerfile and updates the configuration | ||
| func processDetectedBaseImages( | ||
| config *api.ReleaseBuildConfiguration, | ||
| image *api.ProjectDirectoryImageBuildStepConfiguration, | ||
| dockerfileContent []byte, | ||
| dockerfilePath string, | ||
| ) ([]api.StepConfiguration, error) { | ||
| detectedBaseImages, err := dockerfile.DetectInputsFromDockerfile(dockerfileContent, image.Inputs) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to detect inputs from Dockerfile %s: %w", dockerfilePath, err) | ||
| } | ||
| if len(detectedBaseImages) == 0 { | ||
| return nil, nil | ||
| } | ||
|
|
||
| if image.Inputs == nil { | ||
| image.Inputs = make(map[string]api.ImageBuildInputs) | ||
| } | ||
|
|
||
| var steps []api.StepConfiguration | ||
|
|
||
| for alias, baseImage := range detectedBaseImages { | ||
| if config.BaseImages == nil { | ||
| config.BaseImages = make(map[string]api.ImageStreamTagReference) | ||
| } | ||
|
|
||
| if presentAlias, presentBaseImage := isBaseImagePresent(config, baseImage); presentAlias != "" { | ||
| image.Inputs[presentAlias] = api.ImageBuildInputs{ | ||
| As: []string{presentBaseImage.As}, | ||
| } | ||
| logrus.Infof("Detected base image for \"%s\" from \"%s\" matches existing base image \"%s\"", image.To, dockerfilePath, presentAlias) | ||
| continue | ||
| } | ||
|
|
||
| config.BaseImages[alias] = baseImage | ||
| image.Inputs[alias] = api.ImageBuildInputs{ | ||
| As: []string{baseImage.As}, | ||
| } | ||
|
|
||
| stepConfig := api.InputImageTagStepConfiguration{ | ||
| InputImage: api.InputImage{ | ||
| BaseImage: defaultImageFromReleaseTag(alias, baseImage, config.ReleaseTagConfiguration), | ||
| To: api.PipelineImageStreamTagReference(alias), | ||
| }, | ||
| Sources: []api.ImageStreamSource{{SourceType: api.ImageStreamSourceBase, Name: alias}}, | ||
| } | ||
| steps = append(steps, api.StepConfiguration{InputImageTagStepConfiguration: &stepConfig}) | ||
|
|
||
| logrus.Infof("Detected base image for \"%s\" from \"%s\": will tag into pipeline:%s", image.To, dockerfilePath, alias) | ||
| } | ||
|
|
||
| return steps, nil | ||
| } | ||
|
|
||
| func isBaseImagePresent(config *api.ReleaseBuildConfiguration, baseImage api.ImageStreamTagReference) (string, api.ImageStreamTagReference) { | ||
| for presentAlias, presentBaseImage := range config.BaseImages { | ||
| if baseImage.Name == presentBaseImage.Name && baseImage.Namespace == presentBaseImage.Namespace && baseImage.Tag == presentBaseImage.Tag { | ||
| return presentAlias, presentBaseImage | ||
| } | ||
| } | ||
| return "", api.ImageStreamTagReference{} | ||
| } | ||
|
|
||
| func getSourceStepsForJobSpec(jobSpec *api.JobSpec, injectedTest bool) []api.StepConfiguration { | ||
| var sourceSteps []api.StepConfiguration | ||
| primaryRef := determinePrimaryRef(jobSpec, injectedTest) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Detection errors should not fail the build graph
Per earlier feedback,
detectDockerfileInputserrors should be logged as warnings, not propagated as fatal errors. This keeps the auto-detection feature additive without breaking existing configurations.🔎 Proposed fix
// Detect Dockerfile inputs for project images and add InputImageTagStepConfiguration entries dockerfileInputSteps, err := detectDockerfileInputs(config, readFile) if err != nil { - return nil, fmt.Errorf("failed to detect Dockerfile inputs: %w", err) + logrus.WithError(err).Warn("Dockerfile inputs detection failed; continuing without auto-detected base images") + } else { + buildSteps = append(buildSteps, dockerfileInputSteps...) } - buildSteps = append(buildSteps, dockerfileInputSteps...)📝 Committable suggestion
🤖 Prompt for AI Agents