Skip to content
Closed
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
2 changes: 1 addition & 1 deletion cmd/registry-replacer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ func replacer(
}

func ensureReplacement(image *api.ProjectDirectoryImageBuildStepConfiguration, dockerfile []byte) ([]cidockerfile.OrgRepoTag, error) {
toReplace := cidockerfile.ExtractRegistryReferences(dockerfile, "")
toReplace := cidockerfile.ExtractRegistryReferences(dockerfile)
var result []cidockerfile.OrgRepoTag
for _, toReplace := range toReplace {
orgRepoTag, err := cidockerfile.OrgRepoTagFromPullString(toReplace)
Expand Down
2 changes: 1 addition & 1 deletion pkg/defaults/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -980,7 +980,7 @@ func readDockerfileForImage(image api.ProjectDirectoryImageBuildStepConfiguratio
// required InputImageTagStepConfiguration steps to import them, as well as an updated
// image configuration with the detected inputs added
func processDetectedBaseImages(baseImages map[string]api.ImageStreamTagReference, image api.ProjectDirectoryImageBuildStepConfiguration, details dockerfileDetails) ([]api.StepConfiguration, api.ProjectDirectoryImageBuildStepConfiguration) {
detectedBaseImages := dockerfile.DetectInputsFromDockerfile(details.content, image.Inputs, image.From)
detectedBaseImages := dockerfile.DetectInputsFromDockerfile(details.content, image.Inputs, image.From, baseImages)
if len(detectedBaseImages) == 0 {
return nil, image
}
Expand Down
17 changes: 1 addition & 16 deletions pkg/dockerfile/extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,9 @@ func (ort OrgRepoTag) String() string {
}

// ExtractRegistryReferences finds all registry.ci.openshift.org and quay-proxy.ci.openshift.org references in the Dockerfile
func ExtractRegistryReferences(dockerfile []byte, from api.PipelineImageStreamTagReference) []string {
func ExtractRegistryReferences(dockerfile []byte) []string {
var refs []string
seen := sets.Set[string]{}
lastFromRef := ""

for _, line := range bytes.Split(dockerfile, []byte("\n")) {
upper := bytes.ToUpper(line)
Expand All @@ -40,25 +39,11 @@ func ExtractRegistryReferences(dockerfile []byte, from api.PipelineImageStreamTa
continue
}
ref := string(match)
if bytes.HasPrefix(upper, []byte("FROM")) {
lastFromRef = ref
}

if !seen.Has(ref) {
refs = append(refs, ref)
seen.Insert(ref)
}
}
if from != "" {
// If from is specified, remove the last detected FROM ref, it will be replaced
var newRefs []string
for _, ref := range refs {
if ref != lastFromRef {
newRefs = append(newRefs, ref)
}
}
refs = newRefs
}
return refs
}

Expand Down
29 changes: 24 additions & 5 deletions pkg/dockerfile/inputs.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ import (
// DetectInputsFromDockerfile parses a Dockerfile and detects registry references that need to be added as base images
// Returns a map of base image names to ImageStreamTagReferences
// The ImageStreamTagReference.As field contains the original registry reference from the Dockerfile
func DetectInputsFromDockerfile(dockerfile []byte, existingInputs map[string]api.ImageBuildInputs, from api.PipelineImageStreamTagReference) map[string]api.ImageStreamTagReference {
registryRefs := ExtractRegistryReferences(dockerfile, from)
baseImages := make(map[string]api.ImageStreamTagReference)
func DetectInputsFromDockerfile(dockerfile []byte, existingInputs map[string]api.ImageBuildInputs, from api.PipelineImageStreamTagReference, baseImages map[string]api.ImageStreamTagReference) map[string]api.ImageStreamTagReference {
registryRefs := ExtractRegistryReferences(dockerfile)
detected := make(map[string]api.ImageStreamTagReference)

for _, ref := range registryRefs {
if from != "" && matchesFromBaseImage(ref, from, baseImages) {
logrus.WithField("reference", ref).WithField("from", from).Debug("Skipping Dockerfile input already provided by image from")
continue
}
if HasManualReplacementFor(existingInputs, ref) {
logrus.WithField("reference", ref).Debug("Skipping Dockerfile inputs detection: manual replacement exists")
continue
Expand All @@ -24,13 +28,28 @@ func DetectInputsFromDockerfile(dockerfile []byte, existingInputs map[string]api
continue
}
baseImageKey := orgRepoTag.String()
baseImages[baseImageKey] = api.ImageStreamTagReference{
detected[baseImageKey] = api.ImageStreamTagReference{
Namespace: orgRepoTag.Org,
Name: orgRepoTag.Repo,
Tag: orgRepoTag.Tag,
As: ref,
}
}

return baseImages
return detected
}

func matchesFromBaseImage(ref string, from api.PipelineImageStreamTagReference, baseImages map[string]api.ImageStreamTagReference) bool {
if from == "" || baseImages == nil {
return false
}
base, ok := baseImages[string(from)]
if !ok {
return false
}
orgRepoTag, err := OrgRepoTagFromPullString(ref)
if err != nil {
return false
}
return orgRepoTag.Org == base.Namespace && orgRepoTag.Repo == base.Name && orgRepoTag.Tag == base.Tag
}
49 changes: 39 additions & 10 deletions pkg/dockerfile/inputs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ func TestDetectInputsFromDockerfile(t *testing.T) {
name string
dockerfile string
existingInputs map[string]api.ImageBuildInputs
baseImages map[string]api.ImageStreamTagReference
expected map[string]api.ImageStreamTagReference
from api.PipelineImageStreamTagReference
}{
Expand Down Expand Up @@ -142,20 +143,26 @@ FROM registry.ci.openshift.org/ocp/4.19:base AS runtime
},
},
{
name: "from: is specified - should exclude the last and only detected FROM ref",
name: "from matches base image - skip duplicate",
dockerfile: `FROM registry.ci.openshift.org/ocp/4.19:base
RUN echo "hello"
`,
from: "src",
from: "src",
baseImages: map[string]api.ImageStreamTagReference{
"src": {Namespace: "ocp", Name: "4.19", Tag: "base"},
},
expected: map[string]api.ImageStreamTagReference{},
},
{
name: "from: is specified - should exclude the last detected FROM ref",
name: "from matches only its base image in multi-stage",
dockerfile: `FROM registry.ci.openshift.org/ocp/4.18:base AS builder
FROM registry.ci.openshift.org/ocp/4.19:base
RUN echo "hello"
`,
from: "src",
baseImages: map[string]api.ImageStreamTagReference{
"src": {Namespace: "ocp", Name: "4.19", Tag: "base"},
},
expected: map[string]api.ImageStreamTagReference{
"ocp_4.18_base": {
Namespace: "ocp",
Expand All @@ -166,33 +173,55 @@ RUN echo "hello"
},
},
{
name: "from: is specified - should exclude the last detected FROM ref with COPY",
name: "multi-stage cli with unrelated from",
dockerfile: `FROM registry.ci.openshift.org/ocp/4.14:cli AS cli
FROM quay.io/centos/centos:stream9
COPY --from=cli /usr/bin/oc /usr/bin/
`,
from: "stream9",
baseImages: map[string]api.ImageStreamTagReference{
"stream9": {Namespace: "openshift", Name: "centos", Tag: "stream9"},
},
expected: map[string]api.ImageStreamTagReference{
"ocp_4.14_cli": {
Namespace: "ocp",
Name: "4.14",
Tag: "cli",
As: "registry.ci.openshift.org/ocp/4.14:cli",
},
},
},
{
name: "from matches only its base image with COPY",
dockerfile: `FROM registry.ci.openshift.org/ocp/4.18:base AS builder
FROM registry.ci.openshift.org/openshift/release:rhel-9-release-golang-1.24-openshift-4.21
COPY --from=registry.ci.openshift.org/ocp/4.19:base /something /somewhere
RUN echo "hello"
`,
from: "src",
baseImages: map[string]api.ImageStreamTagReference{
"src": {Namespace: "ocp", Name: "4.19", Tag: "base"},
},
expected: map[string]api.ImageStreamTagReference{
"ocp_4.18_base": {
Namespace: "ocp",
Name: "4.18",
Tag: "base",
As: "registry.ci.openshift.org/ocp/4.18:base",
},
"ocp_4.19_base": {
Namespace: "ocp",
Name: "4.19",
Tag: "base",
As: "registry.ci.openshift.org/ocp/4.19:base",
"openshift_release_rhel-9-release-golang-1.24-openshift-4.21": {
Namespace: "openshift",
Name: "release",
Tag: "rhel-9-release-golang-1.24-openshift-4.21",
As: "registry.ci.openshift.org/openshift/release:rhel-9-release-golang-1.24-openshift-4.21",
},
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := DetectInputsFromDockerfile([]byte(tc.dockerfile), tc.existingInputs, tc.from)
result := DetectInputsFromDockerfile([]byte(tc.dockerfile), tc.existingInputs, tc.from, tc.baseImages)

if diff := cmp.Diff(tc.expected, result); diff != "" {
t.Errorf("result differs from expected:\n%s", diff)
Expand Down
63 changes: 43 additions & 20 deletions pkg/steps/input_image_tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,12 @@ func (s *inputImageTagStep) Inputs() (api.InputDefinition, error) {
}
s.imageName = from.Image.Name
} else {
imageName := api.QuayImageReference(s.config.BaseImage)
logrus.Debugf("Resolved %s to %s.", s.config.BaseImage.ISTagName(), imageName)
s.imageName = imageName
_, _, pullSpec, err := s.resolveOfficialImport(context.TODO())
if err != nil {
return nil, err
}
s.imageName = pullSpec
logrus.Debugf("Resolved %s to %s.", s.config.BaseImage.ISTagName(), s.imageName)
}

return api.InputDefinition{s.imageName}, nil
Expand All @@ -80,25 +83,31 @@ func (s *inputImageTagStep) run(ctx context.Context) error {
return fmt.Errorf("could not resolve inputs for image tag step: %w", err)
}

var objectReferenceName string
var from *coreapi.ObjectReference
var refPolicy imagev1.TagReferencePolicyType
var sourcePullSpec string

if s.config.ExternalImage != nil {
externalPullSpec := externalImageReference(s.config)
logrus.Infof("Tagging %s into %s:%s.", externalPullSpec, api.PipelineImageStream, s.config.To)
objectReferenceName = externalPullSpec
} else {
sourcePullSpec = externalImageReference(s.config)
logrus.Infof("Tagging %s into %s:%s.", sourcePullSpec, api.PipelineImageStream, s.config.To)
from = &coreapi.ObjectReference{Kind: "DockerImage", Name: sourcePullSpec}
refPolicy = imagev1.SourceTagReferencePolicy
} else if api.IsCreatedForClusterBotJob(s.config.BaseImage.Namespace) {
logrus.Infof("Tagging %s into %s:%s.", s.config.BaseImage.ISTagName(), api.PipelineImageStream, s.config.To)
objectReferenceName = api.QuayImageReference(s.config.BaseImage)
}
from := &coreapi.ObjectReference{
Kind: "DockerImage",
Name: objectReferenceName,
}
if api.IsCreatedForClusterBotJob(s.config.BaseImage.Namespace) {
from = &coreapi.ObjectReference{
Kind: "ImageStreamImage",
Name: fmt.Sprintf("%s@%s", s.config.BaseImage.Name, s.imageName),
Namespace: s.config.BaseImage.Namespace,
}
sourcePullSpec = s.imageName
refPolicy = imagev1.SourceTagReferencePolicy
} else {
logrus.Infof("Tagging %s into %s:%s.", s.config.BaseImage.ISTagName(), api.PipelineImageStream, s.config.To)
var err error
from, refPolicy, sourcePullSpec, err = s.resolveOfficialImport(ctx)
if err != nil {
return err
}
}

ist := &imagev1.ImageStreamTag{
Expand All @@ -107,10 +116,8 @@ func (s *inputImageTagStep) run(ctx context.Context) error {
Namespace: s.jobSpec.Namespace(),
},
Tag: &imagev1.TagReference{
ReferencePolicy: imagev1.TagReferencePolicy{
Type: imagev1.SourceTagReferencePolicy,
},
From: from,
ReferencePolicy: imagev1.TagReferencePolicy{Type: refPolicy},
From: from,
ImportPolicy: imagev1.TagImportPolicy{
ImportMode: imagev1.ImportModePreserveOriginal,
},
Expand All @@ -137,7 +144,7 @@ func (s *inputImageTagStep) run(ctx context.Context) error {
ImageStreamName: api.PipelineImageStream,
TagName: string(s.config.To),
FullTagName: s.jobSpec.Namespace() + "/" + api.PipelineImageStream + ":" + string(s.config.To),
SourceImage: objectReferenceName,
SourceImage: sourcePullSpec,
SourceImageKind: from.Kind,
StartTime: startTime,
CompletionTime: time.Now(),
Expand All @@ -147,6 +154,22 @@ func (s *inputImageTagStep) run(ctx context.Context) error {
return nil
}

func (s *inputImageTagStep) resolveOfficialImport(ctx context.Context) (*coreapi.ObjectReference, imagev1.TagReferencePolicyType, string, error) {
from, ok, err := utils.ResolveOfficialInputFrom(ctx, s.client, s.jobSpec.Namespace(), s.config.BaseImage)
if err != nil {
return nil, imagev1.SourceTagReferencePolicy, "", err
}
if !ok {
pullSpec := api.QuayImageReference(s.config.BaseImage)
return &coreapi.ObjectReference{Kind: "DockerImage", Name: pullSpec}, imagev1.SourceTagReferencePolicy, pullSpec, nil
}
pullSpec := from.Name
if from.Kind != "DockerImage" && from.Namespace != "" {
pullSpec = fmt.Sprintf("%s/%s", from.Namespace, from.Name)
}
return from, imagev1.LocalTagReferencePolicy, pullSpec, nil
}

// waitForTagInSpec waits for the tag on the image stream are to show in spec
func waitForTagInSpec(ctx context.Context, client ctrlruntimeclient.WithWatch, ns, name, tag string, timeout time.Duration) error {
obj := &imagev1.ImageStream{}
Expand Down
Loading