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

Create --append flag to add file to existing artifact using podman artifact add command #25179

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

Honny1
Copy link
Member

@Honny1 Honny1 commented Jan 31, 2025

This PR creates a --append flag to add a file to an existing artifact using the podman artifact add command.

Fixes: https://issues.redhat.com/browse/RUN-2444

Does this PR introduce a user-facing change?

`podman artifact add` has new option: `--append`

@openshift-ci openshift-ci bot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. release-note labels Jan 31, 2025
Copy link
Contributor

openshift-ci bot commented Jan 31, 2025

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: Honny1
Once this PR has been reviewed and has the lgtm label, please assign ashley-cui for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@Honny1 Honny1 force-pushed the artifact-add-append branch 2 times, most recently from a23f466 to 1619f7f Compare January 31, 2025 13:29
pkg/libartifact/artifact.go Outdated Show resolved Hide resolved
test/e2e/common_test.go Outdated Show resolved Hide resolved
@openshift-merge-robot openshift-merge-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jan 31, 2025
@Honny1 Honny1 force-pushed the artifact-add-append branch from 1619f7f to 1e16026 Compare February 3, 2025 13:48
@openshift-merge-robot openshift-merge-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Feb 3, 2025
@Honny1 Honny1 force-pushed the artifact-add-append branch from 1e16026 to cc2a56e Compare February 3, 2025 14:11
@Honny1
Copy link
Member Author

Honny1 commented Feb 4, 2025

@baude After the rebase, I reworked the code and extended the tests to test the --append option with the --annotation option and made the --append option incompatible with the artifact --type option.

@Honny1 Honny1 marked this pull request as ready for review February 4, 2025 19:18
@openshift-ci openshift-ci bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Feb 4, 2025
@Honny1 Honny1 requested review from baude and Luap99 February 4, 2025 20:01
@Honny1
Copy link
Member Author

Honny1 commented Feb 6, 2025

cc @baude @Luap99 @mtrmac

Copy link
Member

@Luap99 Luap99 left a comment

Choose a reason for hiding this comment

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

just some minor comments, mostly LGTM

@@ -29,6 +29,7 @@ import (

var (
ErrEmptyArtifactName = errors.New("artifact name cannot be empty")
SchemaVersion = 2
Copy link
Member

Choose a reason for hiding this comment

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

This should be put into a const block, and maybe name it ManifestSchemaVersion so we now want schema it is talking about.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I’m not truly convinced this is worth making a constant. For specV1.Manifest.Version, the only valid value is 2, and that’s unlikely to change (a change would certainly mean a new MIME type and probably a new Go type).

It’s not a parameter that can be tuned without changing the type references.

The clean location where this constant should exist is somewhere in the opencontainers/image-spec repo.

All that said, now that the code to declare a constant exists, I’m not going to ask that it should be replaced by a hard-coded 2.

for _, path := range paths {
fileName := filepath.Base(path)
if _, ok := fileNames[fileName]; ok {
return nil, fmt.Errorf("file: %s already exist in artifact", fileName)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
return nil, fmt.Errorf("file: %s already exist in artifact", fileName)
return nil, fmt.Errorf("file %q already exist in artifact", fileName)

Comment on lines 207 to 219
index, err := as.readIndex()
if err != nil {
return nil, err
}
for _, manifest := range index.Manifests {
if manifest.Annotations[specV1.AnnotationRefName] == artifact.Name {
instanceDigest = &manifest.Digest
break
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to read the index? AFAICT artifact.GetDigest() should return the same digest.

That said it seems GetDigest() reads the full manifest again and calculates the digest so using the index like you did might actually be the best thing here

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, you're right when I tried to explain why I'm doing it there. I've found that my approach is not a good one.

Comment on lines 259 to 260
Expect(appendFail).Should(Exit(125))
Expect(appendFail.ErrorToString()).Should(Equal(fmt.Sprintf("Error: file: %s already exist in artifact", filepath.Base(artifact1File))))
Copy link
Member

Choose a reason for hiding this comment

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

Not blocking but generally error checks should be combined with ExitWithError(code, "msg") so it only takes one line and so it is not forgotten to check the error message.

same in the other places below

newLayer := specV1.Descriptor{
MediaType: detectedType,
Digest: newBlobDigest,
Size: newBlobSize,
Annotations: annots,
Annotations: annotations,
Copy link
Member

Choose a reason for hiding this comment

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

pre existing, what I just noticed in my extract PR is that this must copy the map here.
Reason is the map is shared so each iteration is overwriting the title and then all files end up with the same title. Looks like like we have a testing gap there, I am happy to push at as separate PR after this though.

Copy link
Member Author

Choose a reason for hiding this comment

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

I moved the cloning of annotations to the loop where we insert files into the store. I'll add a check to test this case.

Copy link
Member Author

Choose a reason for hiding this comment

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

@Honny1 Honny1 force-pushed the artifact-add-append branch 3 times, most recently from 3a89f36 to cae7671 Compare February 6, 2025 17:05
pkg/libartifact/store/store.go Outdated Show resolved Hide resolved
pkg/libartifact/store/store.go Outdated Show resolved Hide resolved
pkg/libartifact/store/store.go Outdated Show resolved Hide resolved
pkg/libartifact/store/store.go Outdated Show resolved Hide resolved
pkg/libartifact/store/store.go Show resolved Hide resolved
pkg/libartifact/store/store.go Outdated Show resolved Hide resolved
}

for _, l := range lrs {
if digest.String() == l.ManifestDescriptor.Digest.String() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

If two artifacts happen to have the same contents, and the same manifest (but differ in AnnotationRefName), this would delete both, not just the one being updated.

AFAICS the NewReference call always uses an AnnotationRefName-based reference, and in that case PutManifest replaces the previous manifest with the same name, already.

So, I think this only does something if there are two manifests with the same digest (== if this loses data). If the only manifest with oldDigest was replaced in the index, it will not be found by this List.

= I think this never does what we want.


I do think there is something we need to do — the original manifest blob should be deleted. But that should be an automatic part of imageDest.PutManifest (or Commit?), not something higher-level callers should need to do explicitly.

Copy link
Member Author

@Honny1 Honny1 Feb 7, 2025

Choose a reason for hiding this comment

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

PutManifest takes the instanceDigest argument. With this argument, we can replace the contents of the previous manifest. Still, the file will not be renamed so that the filename will be the original digest and the original digest will remain in the index.json repository.

In the case of adding multiple files to the artifact as separate runs of the command, the digest will need to be retrieved from index.json.

I think this is not a good approach. WDYT? @mtrmac


Current implementation: PutManifest with nil instanceDigest creates a new manifest file and writes a new entry to index.json. If the name of the new item already exists in index.json, specV1.AnnotationRefName is removed from the existing entry, and a new item with the name is inserted into index.json. This causes two artifacts to exist, one with a name (the latest version) and one without a name (the previous name). Therefore, I decided to remove the artifact according to the digest. I will extend this by checking if an empty name is present. To ensure that there are no duplicate artifacts with different names.

Copy link
Collaborator

Choose a reason for hiding this comment

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

PutManifest takes the instanceDigest argument. With this argument, we can replace the contents of the previous manifest.

I don’t understand this at all. (The instanceDigest parameter exists to support multi-platform images, typically so that when writing “to a tag”, the tag is not moved by writes of the per-platform manifests, but only by the write of the final top-level one. It is both used by the generic c/image/copy code, and a part of the public stable API; we can’t just redefine its meaning for one transport. [We could add a new transport-specific API, the way PutBlobFromLocalFile was added for artifacts.])

Anyway, the implementation of PutManifest knows whether/what it is replacing.

Still, the file will not be renamed so that the filename will be the original digest

That doesn’t work. The OCI data storage does not (apart from the top level) differentiate between manifests and data, it is possible for the same file (same digest) to be both a layer in one manifest, and a manifest elsewhere. The contents of the blob must match the digest.


Current implementation: PutManifest with nil instanceDigest creates a new manifest file and writes a new entry to index.json. If the name of the new item already exists in index.json, specV1.AnnotationRefName is removed from the existing entry, and a new item with the name is inserted into index.json. This causes two artifacts to exist, one with a name (the latest version) and one without a name (the previous name). Therefore, I decided to remove the artifact according to the digest. I will extend this by checking if an empty name is present. To ensure that there are no duplicate artifacts with different names.

That might be correct enough for the artifact store, but it is incorrect in general (the OCI layout store is not prohibited from containing N entries pointing to the same digest, some with no name).

Really, c/image/oci/layout is responsible for keeping the layout consistent. And it, nowadays, has code to reference-count the blobs and to tell whether there are any other users, i.e. whether the overwritten manifest can/should be deleted.

test/e2e/artifact_test.go Show resolved Hide resolved
test/e2e/artifact_test.go Outdated Show resolved Hide resolved
@Honny1 Honny1 force-pushed the artifact-add-append branch from cae7671 to e4a3435 Compare February 7, 2025 12:45
@Honny1 Honny1 force-pushed the artifact-add-append branch from e4a3435 to f29ac6a Compare February 7, 2025 14:52
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.

5 participants