* Copyright (c) 2026 Huawei Technologies Co., Ltd.
* installer is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
******************************************************************/
package oci
import (
"archive/tar"
"bytes"
"context"
"crypto/tls"
"fmt"
"io"
"net/http"
"strings"
"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"
"sigs.k8s.io/yaml"
)
type Client struct {
opts []remote.Option
}
func NewClient(opts ...remote.Option) *Client {
return &Client{opts: opts}
}
type ClientConfig struct {
InsecureSkipTLSVerify bool
Username string
Password string
}
func RemoteOptions(cfg ClientConfig) []remote.Option {
var opts []remote.Option
if cfg.InsecureSkipTLSVerify {
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.TLSClientConfig = &tls.Config{
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: true,
}
opts = append(opts, remote.WithTransport(tr))
}
if cfg.Username != "" && cfg.Password != "" {
opts = append(opts, remote.WithAuth(&authn.Basic{
Username: cfg.Username,
Password: cfg.Password,
}))
}
return opts
}
func NewClientFromConfig(cfg ClientConfig) *Client {
return NewClient(RemoteOptions(cfg)...)
}
func parseRef(ociRef string) (name.Reference, error) {
return name.ParseReference(ociRef)
}
func (c *Client) GetDigest(ctx context.Context, ociRef string) (string, error) {
ref, err := parseRef(ociRef)
if err != nil {
return "", fmt.Errorf("failed to parse reference %s: %w", ociRef, err)
}
desc, err := remote.Get(ref, c.opts...)
if err != nil {
return "", fmt.Errorf("failed to get descriptor for %s: %w", ociRef, err)
}
return desc.Descriptor.Digest.String(), nil
}
func (c *Client) Pull(ctx context.Context, ociRef string) (*Image, error) {
ref, err := parseRef(ociRef)
if err != nil {
return nil, fmt.Errorf("failed to parse reference %s: %w", ociRef, err)
}
img, err := remote.Image(ref, c.opts...)
if err != nil {
return nil, fmt.Errorf("failed to pull image %s: %w", ociRef, err)
}
return &Image{inner: img}, nil
}
type Image struct {
inner v1.Image
}
type Layer struct {
Path string
Content []byte
}
func (l *Layer) UnmarshalYAML(obj interface{}) error {
return yaml.Unmarshal(l.Content, obj)
}
func (i *Image) GetLayerByPath(path string) (*Layer, error) {
manifest, err := i.inner.Manifest()
if err != nil {
return nil, fmt.Errorf("failed to get manifest: %w", err)
}
for _, layerDesc := range manifest.Layers {
if layerDesc.Annotations != nil {
if filePath, ok := layerDesc.Annotations["org.opencontainers.image.title"]; ok && filePath == path {
layer, err := i.inner.LayerByDigest(layerDesc.Digest)
if err != nil {
return nil, fmt.Errorf("failed to get layer by digest: %w", err)
}
content, err := layer.Uncompressed()
if err != nil {
return nil, fmt.Errorf("failed to get uncompressed content: %w", err)
}
defer content.Close()
data, err := io.ReadAll(content)
if err != nil {
return nil, fmt.Errorf("failed to read layer content: %w", err)
}
return &Layer{Path: path, Content: data}, nil
}
}
}
return i.extractFileFromLayers(path)
}
func (i *Image) GetLayersByPrefix(prefix string) ([]Layer, error) {
manifest, err := i.inner.Manifest()
if err != nil {
return nil, fmt.Errorf("failed to get manifest: %w", err)
}
var layers []Layer
seen := make(map[string]struct{})
for _, layerDesc := range manifest.Layers {
if layerDesc.Annotations != nil {
filePath, ok := layerDesc.Annotations["org.opencontainers.image.title"]
if !ok {
continue
}
if strings.HasPrefix(filePath, prefix) {
layer, err := i.inner.LayerByDigest(layerDesc.Digest)
if err != nil {
return nil, fmt.Errorf("failed to get layer by digest: %w", err)
}
content, err := layer.Uncompressed()
if err != nil {
return nil, fmt.Errorf("failed to get uncompressed content: %w", err)
}
data, err := io.ReadAll(content)
content.Close()
if err != nil {
return nil, fmt.Errorf("failed to read layer content: %w", err)
}
seen[filePath] = struct{}{}
layers = append(layers, Layer{Path: filePath, Content: data})
}
}
}
tarLayers, err := i.extractFilesFromLayersByPrefix(prefix)
if err != nil {
return nil, fmt.Errorf("failed to extract files from layers by prefix %s: %w", prefix, err)
}
for _, l := range tarLayers {
if _, exists := seen[l.Path]; !exists {
seen[l.Path] = struct{}{}
layers = append(layers, l)
}
}
return layers, nil
}
func (i *Image) GetFilesByExtensions(exts ...string) ([]Layer, error) {
manifest, err := i.inner.Manifest()
if err != nil {
return nil, fmt.Errorf("failed to get manifest: %w", err)
}
var layers []Layer
seen := make(map[string]struct{})
for _, layerDesc := range manifest.Layers {
if layerDesc.Annotations == nil {
continue
}
filePath, ok := layerDesc.Annotations["org.opencontainers.image.title"]
if !ok || !hasAnySuffix(filePath, exts) {
continue
}
layer, err := i.inner.LayerByDigest(layerDesc.Digest)
if err != nil {
return nil, fmt.Errorf("failed to get layer by digest: %w", err)
}
content, err := layer.Uncompressed()
if err != nil {
return nil, fmt.Errorf("failed to get uncompressed content: %w", err)
}
data, readErr := io.ReadAll(content)
content.Close()
if readErr != nil {
return nil, fmt.Errorf("failed to read layer content: %w", readErr)
}
normalizedPath := strings.TrimPrefix(filePath, "/")
seen[normalizedPath] = struct{}{}
layers = append(layers, Layer{Path: normalizedPath, Content: data})
}
tarLayers, err := i.extractFilesFromLayersByExtensions(exts...)
if err != nil {
return nil, fmt.Errorf("failed to extract files from layers by extensions %v: %w", exts, err)
}
for _, l := range tarLayers {
if _, exists := seen[l.Path]; !exists {
seen[l.Path] = struct{}{}
layers = append(layers, l)
}
}
return layers, nil
}
func readLayerContent(layer v1.Layer) ([]byte, error) {
rc, err := layer.Uncompressed()
if err != nil {
return nil, err
}
content, err := io.ReadAll(rc)
rc.Close()
return content, err
}
func (i *Image) extractFileFromLayers(path string) (*Layer, error) {
layers, err := i.inner.Layers()
if err != nil {
return nil, fmt.Errorf("file %s not found in image: %w", path, err)
}
normalizedPath := strings.TrimPrefix(path, "/")
for _, layer := range layers {
content, err := readLayerContent(layer)
if err != nil {
continue
}
result, err := findFileInTar(content, normalizedPath)
if err == nil && result != nil {
return result, nil
}
}
return nil, fmt.Errorf("file %s not found in any layer", path)
}
func (i *Image) extractFilesFromLayersByPrefix(prefix string) ([]Layer, error) {
layers, err := i.inner.Layers()
if err != nil {
return nil, fmt.Errorf("failed to list layers: %w", err)
}
normalizedPrefix := strings.TrimPrefix(prefix, "/")
var result []Layer
for _, layer := range layers {
content, err := readLayerContent(layer)
if err != nil {
continue
}
found, err := findFilesInTarByPrefix(content, normalizedPrefix)
if err != nil {
continue
}
result = append(result, found...)
}
return result, nil
}
func (i *Image) extractFilesFromLayersByExtensions(exts ...string) ([]Layer, error) {
layers, err := i.inner.Layers()
if err != nil {
return nil, fmt.Errorf("failed to list layers: %w", err)
}
var result []Layer
for _, layer := range layers {
content, err := readLayerContent(layer)
if err != nil {
continue
}
found, err := findFilesInTarByExtensions(content, exts...)
if err != nil {
continue
}
result = append(result, found...)
}
return result, nil
}
func findFileInTar(tarData []byte, normalizedPath string) (*Layer, error) {
tr := tar.NewReader(bytes.NewReader(tarData))
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
break
}
hdrName := strings.TrimPrefix(hdr.Name, "/")
if hdrName == normalizedPath {
data, err := io.ReadAll(tr)
if err != nil {
continue
}
return &Layer{Path: normalizedPath, Content: data}, nil
}
}
return nil, fmt.Errorf("file %s not found in tar", normalizedPath)
}
func findFilesInTarByPrefix(tarData []byte, normalizedPrefix string) ([]Layer, error) {
return findFilesInTar(tarData, func(path string, hdr *tar.Header) bool {
return strings.HasPrefix(path, normalizedPrefix)
})
}
func findFilesInTarByExtensions(tarData []byte, exts ...string) ([]Layer, error) {
return findFilesInTar(tarData, func(path string, hdr *tar.Header) bool {
return !hdr.FileInfo().IsDir() && hasAnySuffix(path, exts)
})
}
func findFilesInTar(tarData []byte, match func(string, *tar.Header) bool) ([]Layer, error) {
tr := tar.NewReader(bytes.NewReader(tarData))
var result []Layer
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
break
}
hdrName := strings.TrimPrefix(hdr.Name, "/")
if !match(hdrName, hdr) {
continue
}
data, err := io.ReadAll(tr)
if err != nil {
continue
}
result = append(result, Layer{Path: hdrName, Content: data})
}
return result, nil
}
func hasAnySuffix(path string, suffixes []string) bool {
for _, suffix := range suffixes {
if strings.HasSuffix(path, suffix) {
return true
}
}
return false
}