-
Notifications
You must be signed in to change notification settings - Fork 0
Remove skopeo dependency #83
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
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
de955d4
Use Go packages to get rid of the skopeo runtime dependency entirely
637fb78
Rename from skopeohelper to ocihelper
699b928
Use os.Root
8f8e08f
Rename InspectImage -> VerifyImageExistence
dd5796d
Update internal/ocihelper/ocihelper.go
mclasmeier d92922e
Update internal/ocihelper/ocihelper.go
mclasmeier cf7e036
Use MkdirAll instead of Mkdir and remove IsExist check
cb5b721
Remove file path cleaning
5504ebe
Log on unsupported entry types
c023099
Review feedback
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,179 @@ | ||
| package ocihelper | ||
|
|
||
| import ( | ||
| "archive/tar" | ||
| "context" | ||
| "fmt" | ||
| "io" | ||
| "os" | ||
| "path/filepath" | ||
|
|
||
| "github.com/google/go-containerregistry/pkg/authn" | ||
| "github.com/google/go-containerregistry/pkg/name" | ||
| v1 "github.com/google/go-containerregistry/pkg/v1" | ||
| "github.com/google/go-containerregistry/pkg/v1/remote" | ||
|
|
||
| "github.com/stackrox/roxie/internal/logger" | ||
| ) | ||
|
|
||
| // VerifyImageExistence verifies that an OCI image is accessible. | ||
| // Authentication is handled automatically from ~/.docker/config.json or $REGISTRY_AUTH_FILE. | ||
| func VerifyImageExistence(ctx context.Context, log *logger.Logger, imageRef string) error { | ||
| log.Dimf("Inspecting image %s", imageRef) | ||
|
|
||
| ref, err := name.ParseReference(imageRef) | ||
| if err != nil { | ||
| return fmt.Errorf("invalid image reference: %w", err) | ||
| } | ||
|
|
||
| // Use HEAD request to verify image exists without downloading | ||
| _, err = remote.Head(ref, | ||
| remote.WithContext(ctx), | ||
| remote.WithAuthFromKeychain(authn.DefaultKeychain)) | ||
| if err != nil { | ||
| return fmt.Errorf("image inspection failed: %w", err) | ||
| } | ||
|
|
||
| log.Dim("✓ Image is accessible") | ||
| return nil | ||
| } | ||
|
|
||
| // ExtractManifestsFromImage extracts the /manifests/ directory from an operator bundle image. | ||
| // Authentication is handled automatically from ~/.docker/config.json or $REGISTRY_AUTH_FILE. | ||
| func ExtractManifestsFromImage(ctx context.Context, log *logger.Logger, imageRef, destDir string) error { | ||
|
mclasmeier marked this conversation as resolved.
|
||
| tempDir, err := os.MkdirTemp("", "oci-image-") | ||
| if err != nil { | ||
| return fmt.Errorf("failed to create temp dir: %w", err) | ||
| } | ||
| defer os.RemoveAll(tempDir) | ||
|
|
||
| log.Dimf("Using temporary directory: %s", tempDir) | ||
|
|
||
| img, err := fetchImage(ctx, log, imageRef) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| log.Dim("Extracting /manifests/ directory from image layers...") | ||
| if err := extractManifestsFromImage(log, img, tempDir, destDir); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| log.Dimf("✓ Manifests extracted to: %s", destDir) | ||
| return nil | ||
| } | ||
|
|
||
| // fetchImage downloads an OCI image from a registry. | ||
| func fetchImage(ctx context.Context, log *logger.Logger, imageRef string) (v1.Image, error) { | ||
| log.Dimf("Fetching image %s", imageRef) | ||
|
|
||
| ref, err := name.ParseReference(imageRef) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("invalid image reference: %w", err) | ||
| } | ||
|
|
||
| // For operator bundles, we fetch linux/amd64 by default as they contain | ||
| // platform-agnostic YAML files. | ||
| platform := v1.Platform{ | ||
| OS: "linux", | ||
| Architecture: "amd64", | ||
| } | ||
|
|
||
| img, err := remote.Image(ref, | ||
| remote.WithContext(ctx), | ||
| remote.WithAuthFromKeychain(authn.DefaultKeychain), | ||
| remote.WithPlatform(platform)) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to fetch image: %w", err) | ||
| } | ||
|
|
||
| log.Dim("✓ Image fetched successfully") | ||
| return img, nil | ||
| } | ||
|
|
||
| // extractManifestsFromImage extracts /manifests/ from an OCI image. | ||
| func extractManifestsFromImage(log *logger.Logger, img v1.Image, tempExtractDir, destDir string) error { | ||
| layers, err := img.Layers() | ||
| if err != nil { | ||
| return fmt.Errorf("failed to get image layers: %w", err) | ||
| } | ||
|
|
||
| log.Dimf("Found %d layer(s) in image", len(layers)) | ||
|
|
||
| // Extract all layers into tempExtractDir | ||
|
mclasmeier marked this conversation as resolved.
|
||
| for i, layer := range layers { | ||
| log.Dimf("Extracting layer %d/%d...", i+1, len(layers)) | ||
|
mclasmeier marked this conversation as resolved.
|
||
| if err := extractLayerToDir(log, layer, tempExtractDir); err != nil { | ||
| return fmt.Errorf("failed to extract layer %d: %w", i+1, err) | ||
| } | ||
| } | ||
|
|
||
| // From the directory into which all layers have been extracted, copy the | ||
| // /manifests/ directory to the destination. | ||
| log.Dim("Copying manifests to destination...") | ||
| manifestsDir := filepath.Join(tempExtractDir, "manifests") | ||
| if _, err := os.Stat(manifestsDir); err != nil { | ||
| return fmt.Errorf("no /manifests directory found in image: %w", err) | ||
| } | ||
|
|
||
| return os.CopyFS(destDir, os.DirFS(manifestsDir)) | ||
| } | ||
|
|
||
| // extractLayerToDir extracts a single image layer to a directory. | ||
| func extractLayerToDir(log *logger.Logger, layer v1.Layer, destDir string) error { | ||
| rc, err := layer.Uncompressed() | ||
| if err != nil { | ||
| return fmt.Errorf("failed to get layer contents: %w", err) | ||
| } | ||
| defer rc.Close() | ||
|
|
||
| return extractTarToDir(log, rc, destDir) | ||
| } | ||
|
|
||
| // extractTarToDir extracts an uncompressed tar stream to a directory. | ||
| func extractTarToDir(log *logger.Logger, r io.Reader, destDir string) error { | ||
| // Open a Root directory to prevent path traversal attacks. | ||
| root, err := os.OpenRoot(destDir) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to open directory %q as root directory: %w", destDir, err) | ||
| } | ||
| defer root.Close() | ||
|
|
||
| tr := tar.NewReader(r) | ||
|
|
||
| for { | ||
| header, err := tr.Next() | ||
| if err == io.EOF { | ||
| break | ||
| } | ||
| if err != nil { | ||
| return fmt.Errorf("failed to read tar header: %w", err) | ||
| } | ||
|
|
||
| entryName := header.Name | ||
|
|
||
| switch header.Typeflag { | ||
|
mclasmeier marked this conversation as resolved.
|
||
| case tar.TypeDir: | ||
| err := root.MkdirAll(entryName, 0755) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| case tar.TypeReg: | ||
| outFile, err := root.OpenFile(entryName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(header.Mode)) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to create file %s: %w", entryName, err) | ||
| } | ||
|
|
||
| if _, err := io.Copy(outFile, tr); err != nil { | ||
| outFile.Close() | ||
| return fmt.Errorf("failed to write file %s: %w", entryName, err) | ||
| } | ||
| outFile.Close() | ||
| default: | ||
| log.Dimf("Skipping unsupported tar entry %s of type %c", entryName, header.Typeflag) | ||
| continue | ||
| } | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.