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

add podman artifact extract #25238

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

Conversation

Luap99
Copy link
Member

@Luap99 Luap99 commented Feb 5, 2025

Add a new command to extract the blob content of the artifact store to a local path.

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

Does this PR introduce a user-facing change?

Added a new podman artifact extract command.

Add a new command to extract the blob content of the artifact store to a
local path.

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

Signed-off-by: Paul Holzinger <[email protected]>
@openshift-ci openshift-ci bot added release-note do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. labels Feb 5, 2025
Copy link
Contributor

openshift-ci bot commented Feb 5, 2025

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: Luap99

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

The pull request process is described 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

@openshift-ci openshift-ci bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Feb 5, 2025
@Luap99
Copy link
Member Author

Luap99 commented Feb 5, 2025

Tests are still missing, will work on it tomorrow. Opening this to allow some early review.

cc @baude @Honny1 @mtrmac

Copy link

Ephemeral COPR build failed. @containers/packit-build please check.

must be used to select only a single blob. If the file already exists it will be
overwritten.

If the target is an directory (it must exist) all blobs will be copied to the
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
If the target is an directory (it must exist) all blobs will be copied to the
If the target is an directory (it must exist), all blobs will be copied to the

overwritten.

If the target is an directory (it must exist) all blobs will be copied to the
target directory. As target file name the value from `org.opencontainers.image.title`
Copy link
Member

Choose a reason for hiding this comment

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

Seems like a typo here maybe ... did you mean "a" or "the" target file ?


If the target is an directory (it must exist) all blobs will be copied to the
target directory. As target file name the value from `org.opencontainers.image.title`
annotation is used. If the annotation is missing the target file name will be
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
annotation is used. If the annotation is missing the target file name will be
annotation is used. If the annotation is missing, the target file name will be

target directory. As target file name the value from `org.opencontainers.image.title`
annotation is used. If the annotation is missing the target file name will be
the digest of the blob (with `:` replaced by `-` in the name).
If the target file already exists in the directory it will be overwritten.
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
If the target file already exists in the directory it will be overwritten.
If the target file already exists in the directory, it will be overwritten.

Copy link
Collaborator

@mtrmac mtrmac left a comment

Choose a reason for hiding this comment

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

A fairly brief skim; ACK overall, some more design questions.

Note the ⚠️ .

Comment on lines +17 to +18
path is a file or does not exists the artifact must either consists of one blob
or if it has multiple blobs (layers) then the **--digest** or **--title** option
Copy link
Collaborator

Choose a reason for hiding this comment

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

s/consists/consist/

The (layers) explanation should probably happen with the first use of the word “blob”.


## DESCRIPTION

Extract the blobs of an OCI artifact to a local file or directory. If the target
Copy link
Collaborator

Choose a reason for hiding this comment

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

Consider a paragraph break after the first sentence; then there would be a paragraph for files, and a paragraph for directories.

overwritten.

If the target is an directory (it must exist) all blobs will be copied to the
target directory. As target file name the value from `org.opencontainers.image.title`
Copy link
Collaborator

Choose a reason for hiding this comment

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

With --digest, the file name probably should be always digest-based, so that the user knows where to find the output.

(Same with --title, but that happens ~automatically.)

Copy link
Member Author

Choose a reason for hiding this comment

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

That makes the code actually simpler, I had the in my first version but then thought it would not be consistent. But yeah I guess you are right it makes sense to stick with the digest for users so they can predict the target name.

@@ -14,6 +14,13 @@ type ArtifactAddOptions struct {
ArtifactType string
}

type ArtifactExtractOptions struct {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Depending on what we decide about the option semantics, the comments here might need more detail (or to just refer to the man page for details)

if err != nil {
return err
}
return artStore.Extract(ctx, name, target, (*types.ExtractOptions)(opts))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we avoid the cast? My reading of the Go spec is that this is actually safe (Go requires the two structs to have the same fields, in the same order), but to my old C eyes it looks like undefined behavior.

It would also make working with IDEs harder — tools to “find all references” of a field can’t trace how ArtifactExtractOptions.Digest becomes ExtractOptions.Digest.

(Disregard if this is a common pattern in Podman.)

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 don't think it is common, I just found it shorter to write then assigning each filed individually. I can totally do that.

return nil
}

func generateArtifactName(title string, digest digest.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.

Something like

Suggested change
func generateArtifactName(title string, digest digest.Digest) string {
func generateArtifactBlobName(title string, digest digest.Digest) string {

? It is not a name of the whole artifact.

)
for _, l := range arty.Manifest.Layers {
if options.Digest == l.Digest.String() {
if len(digest.String()) > 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

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

(Absolutely non-blocking: IIRC digest != "" compiles to the same code, we can let the compiler turn that into a len check. Applies throughout.)

Copy link
Member Author

Choose a reason for hiding this comment

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

Fair but AFAICT we have no common style on these check empty string conditions in Podman. some do ==/!= "" and some do len() ==/!= 0 instead. I think I went consitently with len() here.

Also compare already existing

if len(dest) == 0 {
	return nil, ErrEmptyArtifactName
}

title string
)
for _, l := range arty.Manifest.Layers {
if options.Digest == l.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.

Non-blocking: This probably can be structured as if layerMatchesOptions(…) { duplicate check; set digest/title }). This works fine as is, and it is short enough that it is not that necessary.

// on all platforms replace it with "-".
filename = strings.ReplaceAll(digest.String(), ":", "-")
}
return filename
Copy link
Collaborator

Choose a reason for hiding this comment

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

⚠️

This must protect against .. in the path, maybe using filepath-securejoin, maybe outright rejecting paths with /, maybe something else.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks, yes I totally forget about that. Let's reject /, we only support single files. Supporting actual paths with directories would means we have to mkdir them which means more code/tests, etc...
We can reconsider if users have a strong use case for them.


Extract the blobs of an OCI artifact to a local file or directory. If the target
path is a file or does not exists the artifact must either consists of one blob
or if it has multiple blobs (layers) then the **--digest** or **--title** option
Copy link
Collaborator

Choose a reason for hiding this comment

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

Late thought: this does not allow extracting the config. I think that’s fine for the file-destination case, we can always add an option later.

For the directory-destination case, maybe we should establish the behavior right now. (It would not be a disaster not to handle that either, we can add an --include-config option later, but it seems worth thinking about.)

Copy link
Member Author

Choose a reason for hiding this comment

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

We do not allow setting the config either, AFAICT we also have {} as config. Extracting that always seems pointless because of that.

Note all man pages say:

## WARNING: Experimental command
*This command is considered experimental and still in development. Inputs, options, and outputs are all
subject to change.*

As such even if we merge it we can reconsider until we remove that warning so I rather to expand the scope of this PR to deal with configs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. release-note
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants