Skip to content

Commit

Permalink
refactor: minimise containers/image imports
Browse files Browse the repository at this point in the history
Co-authored-by: David Trudgian <david.trudgian@sylabs.io>
Signed-off-by: jason yang <jasonyangshadow@gmail.com>
  • Loading branch information
JasonYangShadow and dtrudg committed Sep 6, 2024
1 parent 454fe4d commit afd8b1b
Show file tree
Hide file tree
Showing 18 changed files with 407 additions and 162 deletions.
7 changes: 4 additions & 3 deletions cmd/internal/cli/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/apptainer/apptainer/internal/pkg/client/oras"
"github.com/apptainer/apptainer/internal/pkg/client/shub"
"github.com/apptainer/apptainer/internal/pkg/instance"
"github.com/apptainer/apptainer/internal/pkg/ocitransport"
"github.com/apptainer/apptainer/internal/pkg/runtime/launch"
"github.com/apptainer/apptainer/internal/pkg/util/env"
"github.com/apptainer/apptainer/internal/pkg/util/uri"
Expand Down Expand Up @@ -79,7 +80,7 @@ func actionPreRun(cmd *cobra.Command, args []string) {
}

func handleOCI(ctx context.Context, imgCache *cache.Handle, cmd *cobra.Command, pullFrom string) (string, error) {
ociAuth, err := makeDockerCredentials(cmd)
ociAuth, err := makeOCICredentials(cmd)
if err != nil {
sylog.Fatalf("While creating Docker credentials: %v", err)
}
Expand All @@ -96,7 +97,7 @@ func handleOCI(ctx context.Context, imgCache *cache.Handle, cmd *cobra.Command,
}

func handleOras(ctx context.Context, imgCache *cache.Handle, cmd *cobra.Command, pullFrom string) (string, error) {
ociAuth, err := makeDockerCredentials(cmd)
ociAuth, err := makeOCICredentials(cmd)
if err != nil {
return "", fmt.Errorf("while creating docker credentials: %v", err)
}
Expand Down Expand Up @@ -157,7 +158,7 @@ func replaceURIWithImage(ctx context.Context, cmd *cobra.Command, args []string)
image, err = handleOras(ctx, imgCache, cmd, args[0])
case uri.Shub:
image, err = handleShub(ctx, imgCache, args[0])
case oci.IsSupported(t):
case ocitransport.SupportedTransport(t):
image, err = handleOCI(ctx, imgCache, cmd, args[0])
case uri.HTTP:
image, err = handleNet(ctx, imgCache, args[0])
Expand Down
12 changes: 6 additions & 6 deletions cmd/internal/cli/apptainer.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import (
"github.com/apptainer/apptainer/pkg/util/apptainerconf"
keyClient "github.com/apptainer/container-key-client/client"
libClient "github.com/apptainer/container-library-client/client"
ocitypes "github.com/containers/image/v5/types"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/spf13/cobra"
"golang.org/x/term"
)
Expand All @@ -56,9 +56,9 @@ var CurrentUser = getCurrentUser()
var currentRemoteEndpoint *endpoint.Config

var (
dockerAuthConfig ocitypes.DockerAuthConfig
dockerLogin bool
dockerHost string
authConfig authn.AuthConfig
dockerLogin bool
dockerHost string

encryptionPEMPath string
promptForPassphrase bool
Expand Down Expand Up @@ -138,7 +138,7 @@ var singVerboseFlag = cmdline.Flag{
// --docker-username
var dockerUsernameFlag = cmdline.Flag{
ID: "dockerUsernameFlag",
Value: &dockerAuthConfig.Username,
Value: &authConfig.Username,
DefaultValue: "",
Name: "docker-username",
Usage: "specify a username for docker authentication",
Expand All @@ -150,7 +150,7 @@ var dockerUsernameFlag = cmdline.Flag{
// --docker-password
var dockerPasswordFlag = cmdline.Flag{
ID: "dockerPasswordFlag",
Value: &dockerAuthConfig.Password,
Value: &authConfig.Password,
DefaultValue: "",
Name: "docker-password",
Usage: "specify a password for docker authentication",
Expand Down
29 changes: 16 additions & 13 deletions cmd/internal/cli/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
"github.com/apptainer/apptainer/pkg/cmdline"
"github.com/apptainer/apptainer/pkg/image"
"github.com/apptainer/apptainer/pkg/sylog"
ocitypes "github.com/containers/image/v5/types"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/spf13/cobra"
"golang.org/x/term"
)
Expand Down Expand Up @@ -469,34 +469,37 @@ func checkBuildTarget(path string) error {
return nil
}

// makeDockerCredentials creates an *ocitypes.DockerAuthConfig to use for
// OCI/Docker registry operation configuration. Note that if we don't have a
// username or password set it will return a nil pointer, as containers/image
// requires this to fall back to .docker/config based authentication.
func makeDockerCredentials(cmd *cobra.Command) (authConf *ocitypes.DockerAuthConfig, err error) {
// makeOCICredentials creates an *authn.AuthConfig that should be used for
// explicit OCI/Docker registry authentication when appropriate. If
// `--docker-login` has been specified then interactive authentication will be
// performed. If `--docker-login` has not been specified, and explicit
// credentials have not been supplied via env-vars/flags, then a nil AuthConfig
// will be returned.
func makeOCICredentials(cmd *cobra.Command) (*authn.AuthConfig, error) {
usernameFlag := cmd.Flags().Lookup("docker-username")
passwordFlag := cmd.Flags().Lookup("docker-password")

var err error
if dockerLogin {
if !usernameFlag.Changed {
dockerAuthConfig.Username, err = interactive.AskQuestion("Enter Docker Username: ")
authConfig.Username, err = interactive.AskQuestion("Enter Docker Username: ")
if err != nil {
return authConf, err
return &authConfig, nil
}
usernameFlag.Value.Set(dockerAuthConfig.Username)
usernameFlag.Value.Set(authConfig.Username)
usernameFlag.Changed = true
}

dockerAuthConfig.Password, err = interactive.AskQuestionNoEcho("Enter Docker Password: ")
authConfig.Password, err = interactive.AskQuestionNoEcho("Enter Docker Password: ")
if err != nil {
return authConf, err
return &authConfig, nil
}
passwordFlag.Value.Set(dockerAuthConfig.Password)
passwordFlag.Value.Set(authConfig.Password)
passwordFlag.Changed = true
}

if usernameFlag.Changed || passwordFlag.Changed {
return &dockerAuthConfig, nil
return &authConfig, nil
}

// If a username / password have not been explicitly set, return a nil
Expand Down
4 changes: 2 additions & 2 deletions cmd/internal/cli/build_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ func runBuildLocal(ctx context.Context, cmd *cobra.Command, dst, spec string, fa
sylog.Fatalf("Could not check build sections: %v", err)
}

authConf, err := makeDockerCredentials(cmd)
authConf, err := makeOCICredentials(cmd)
if err != nil {
sylog.Fatalf("While creating Docker credentials: %v", err)
}
Expand Down Expand Up @@ -369,7 +369,7 @@ func runBuildLocal(ctx context.Context, cmd *cobra.Command, dst, spec string, fa
LibraryAuthToken: authToken,
FakerootPath: fakerootPath,
KeyServerOpts: ko,
DockerAuthConfig: authConf,
OCIAuthConfig: authConf,
DockerDaemonHost: dockerHost,
EncryptionKeyInfo: keyInfo,
FixPerms: buildArgs.fixPerms,
Expand Down
7 changes: 4 additions & 3 deletions cmd/internal/cli/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/apptainer/apptainer/internal/pkg/client/oci"
"github.com/apptainer/apptainer/internal/pkg/client/oras"
"github.com/apptainer/apptainer/internal/pkg/client/shub"
"github.com/apptainer/apptainer/internal/pkg/ocitransport"
"github.com/apptainer/apptainer/internal/pkg/remote/endpoint"
"github.com/apptainer/apptainer/internal/pkg/util/uri"
"github.com/apptainer/apptainer/pkg/cmdline"
Expand Down Expand Up @@ -263,7 +264,7 @@ func pullRun(cmd *cobra.Command, args []string) {
sylog.Fatalf("While pulling shub image: %v\n", err)
}
case OrasProtocol:
ociAuth, err := makeDockerCredentials(cmd)
ociAuth, err := makeOCICredentials(cmd)
if err != nil {
sylog.Fatalf("Unable to make docker oci credentials: %s", err)
}
Expand All @@ -277,8 +278,8 @@ func pullRun(cmd *cobra.Command, args []string) {
if err != nil {
sylog.Fatalf("While pulling from image from http(s): %v\n", err)
}
case oci.IsSupported(transport):
ociAuth, err := makeDockerCredentials(cmd)
case ocitransport.SupportedTransport(transport):
ociAuth, err := makeOCICredentials(cmd)
if err != nil {
sylog.Fatalf("While creating Docker credentials: %v", err)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/internal/cli/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ var PushCmd = &cobra.Command{
if cmd.Flag(pushDescriptionFlag.Name).Changed {
sylog.Warningf("Description is not supported for push to oras. Ignoring it.")
}
ociAuth, err := makeDockerCredentials(cmd)
ociAuth, err := makeOCICredentials(cmd)
if err != nil {
sylog.Fatalf("Unable to make docker oci credentials: %s", err)
}
Expand Down
9 changes: 7 additions & 2 deletions internal/pkg/build/conveyorPacker.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"fmt"

"github.com/apptainer/apptainer/internal/pkg/build/sources"
"github.com/apptainer/apptainer/internal/pkg/ocitransport"
"github.com/apptainer/apptainer/pkg/build/types"
)

Expand All @@ -36,14 +37,18 @@ type ConveyorPacker interface {

// conveyorPacker returns a valid ConveyorPacker for the given image definition.
func conveyorPacker(def types.Definition) (ConveyorPacker, error) {
switch def.Header["bootstrap"] {
bs, ok := def.Header["bootstrap"]
if !ok {
return nil, fmt.Errorf("no bootstrap specification found")
}
switch bs {
case "library":
return &sources.LibraryConveyorPacker{}, nil
case "oras":
return &sources.OrasConveyorPacker{}, nil
case "shub":
return &sources.ShubConveyorPacker{}, nil
case "docker", "docker-archive", "docker-daemon", "oci", "oci-archive":
case ocitransport.SupportedTransport(bs):
return &sources.OCIConveyorPacker{}, nil
case "busybox":
return &sources.BusyBoxConveyorPacker{}, nil
Expand Down
94 changes: 25 additions & 69 deletions internal/pkg/build/oci/oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import (
"fmt"
"io"
"os"
"runtime"
"strings"

"github.com/apptainer/apptainer/internal/pkg/cache"
"github.com/apptainer/apptainer/internal/pkg/ocitransport"
"github.com/apptainer/apptainer/pkg/sylog"
"github.com/containers/image/v5/copy"
"github.com/containers/image/v5/docker"
Expand Down Expand Up @@ -76,22 +76,20 @@ var ArchMap = map[string]GoArch{
}

// ConvertReference converts a source reference into a cache.ImageReference to cache its blobs
func ConvertReference(ctx context.Context, imgCache *cache.Handle, src types.ImageReference, sys *types.SystemContext) (types.ImageReference, error) {
func ConvertReference(ctx context.Context, imgCache *cache.Handle, src types.ImageReference, topts *ocitransport.TransportOptions) (types.ImageReference, error) {
if imgCache == nil {
return nil, fmt.Errorf("undefined image cache")
}

if topts == nil {
// nolint:staticcheck
topts = ocitransport.TransportOptionsFromSystemContext(nil)
}

// Our cache dir is an OCI directory. We are using this as a 'blob pool'
// storing all incoming containers under unique tags, which are a hash of
// their source URI.
if sys == nil {
var err error
sys, err = defaultSysCtx()
if err != nil {
return nil, fmt.Errorf("unable to create default system context: %v", err)
}
}
cacheTag, err := getRefDigest(ctx, src, sys)
cacheTag, err := getRefDigest(ctx, src, topts)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -137,13 +135,13 @@ func (t *ImageReference) newImageSource(ctx context.Context, sys *types.SystemCo

// ParseImageName parses a uri (e.g. docker://ubuntu) into it's transport:reference
// combination and then returns the proper reference
func ParseImageName(ctx context.Context, imgCache *cache.Handle, uri string, sys *types.SystemContext) (types.ImageReference, error) {
func ParseImageName(ctx context.Context, imgCache *cache.Handle, uri string, topts *ocitransport.TransportOptions) (types.ImageReference, error) {
ref, _, err := parseURI(uri)
if err != nil {
return nil, fmt.Errorf("unable to parse image name %v: %v", uri, err)
}

return ConvertReference(ctx, imgCache, ref, sys)
return ConvertReference(ctx, imgCache, ref, topts)
}

func parseURI(uri string) (types.ImageReference, *GoArch, error) {
Expand All @@ -166,40 +164,25 @@ func parseURI(uri string) (types.ImageReference, *GoArch, error) {
}

// ImageDigest obtains the digest of a uri's manifest
func ImageDigest(ctx context.Context, uri string, sys *types.SystemContext) (digest string, err error) {
if sys == nil {
var err error
sys, err = defaultSysCtx()
if err != nil {
return "", fmt.Errorf("unable to create default system context: %v", err)
}
}
func ImageDigest(ctx context.Context, uri string, topts *ocitransport.TransportOptions) (digest string, err error) {
ref, arch, err := parseURI(uri)
if err != nil {
return "", fmt.Errorf("unable to parse image name %v: %v", uri, err)
}

if arch != nil && arch.Arch != sys.ArchitectureChoice {
sylog.Warningf("The `--arch` value: %s is not equal to the arch info extracted from uri: %s, will ignore the `--arch` value", sys.ArchitectureChoice, arch)
sys.ArchitectureChoice = arch.Arch
sys.VariantChoice = arch.Var
if arch != nil && arch.Arch != topts.Platform.Architecture {
sylog.Warningf("The `--arch` value: %s is not equal to the arch info extracted from uri: %s, will ignore the `--arch` value", topts.Platform.Architecture, arch)
topts.Platform.Architecture = arch.Arch
topts.Platform.Variant = arch.Var
}
return getRefDigest(ctx, ref, sys)
return getRefDigest(ctx, ref, topts)
}

// getRefDigest obtains the manifest digest for a ref.
func getRefDigest(ctx context.Context, ref types.ImageReference, sys *types.SystemContext) (digest string, err error) {
if sys.ArchitectureChoice == "" {
defaultCtx, err := defaultSysCtx()
if err != nil {
return "", fmt.Errorf("unable to create default system context: %v", err)
}
sys.ArchitectureChoice = defaultCtx.ArchitectureChoice
sys.VariantChoice = defaultCtx.VariantChoice
}
func getRefDigest(ctx context.Context, ref types.ImageReference, topts *ocitransport.TransportOptions) (digest string, err error) {
// Handle docker references specially, using a HEAD request to ensure we don't hit API limits
if ref.Transport().Name() == "docker" {
digest, err := getDockerRefDigest(ctx, ref, sys)
digest, err := getDockerRefDigest(ctx, ref, topts)
if err == nil {
sylog.Debugf("GetManifest digest for %s is %s", transports.ImageName(ref), digest)
return digest, err
Expand All @@ -210,7 +193,8 @@ func getRefDigest(ctx context.Context, ref types.ImageReference, sys *types.Syst
}

// Otherwise get the manifest and calculate sha256 over it
source, err := ref.NewImageSource(ctx, sys)
// nolint:staticcheck
source, err := ref.NewImageSource(ctx, ocitransport.SystemContextFromTransportOptions(topts))
if err != nil {
return "", err
}
Expand All @@ -226,28 +210,21 @@ func getRefDigest(ctx context.Context, ref types.ImageReference, sys *types.Syst
}

digest = fmt.Sprintf("%x", sha256.Sum256(man))
digest = fmt.Sprintf("%x", sha256.Sum256([]byte(digest+sys.ArchitectureChoice+sys.VariantChoice)))
digest = fmt.Sprintf("%x", sha256.Sum256([]byte(digest+topts.Platform.Architecture+topts.Platform.Variant)))
sylog.Debugf("GetManifest digest for %s is %s", transports.ImageName(ref), digest)
return digest, nil
}

// getDockerRefDigest obtains the manifest digest for a docker ref.
func getDockerRefDigest(ctx context.Context, ref types.ImageReference, sys *types.SystemContext) (digest string, err error) {
if sys.ArchitectureChoice == "" {
defaultCtx, err := defaultSysCtx()
if err != nil {
return "", fmt.Errorf("unable to create default system context: %v", err)
}
sys.ArchitectureChoice = defaultCtx.ArchitectureChoice
sys.VariantChoice = defaultCtx.VariantChoice
}
d, err := docker.GetDigest(ctx, sys, ref)
func getDockerRefDigest(ctx context.Context, ref types.ImageReference, topts *ocitransport.TransportOptions) (digest string, err error) {
// nolint:staticcheck
d, err := docker.GetDigest(ctx, ocitransport.SystemContextFromTransportOptions(topts), ref)
if err != nil {
return "", err
}
digest = d.Encoded()
sylog.Debugf("docker.GetDigest source image digest for %s is %s", transports.ImageName(ref), digest)
digest = fmt.Sprintf("%x", sha256.Sum256([]byte(digest+sys.ArchitectureChoice+sys.VariantChoice)))
digest = fmt.Sprintf("%x", sha256.Sum256([]byte(digest+topts.Platform.Architecture+topts.Platform.Variant)))
sylog.Debugf("docker.GetDigest digest for %s is %s", transports.ImageName(ref), digest)
return digest, nil
}
Expand Down Expand Up @@ -279,27 +256,6 @@ func getArchFromURI(uri string) (arch *GoArch) {
return
}

func defaultSysCtx() (*types.SystemContext, error) {
sysCtx := &types.SystemContext{
OSChoice: "linux",
}
switch runtime.GOARCH {
case "arm64":
sysCtx.ArchitectureChoice = runtime.GOARCH
sysCtx.VariantChoice = "v8"
case "arm":
variance, ok := os.LookupEnv("GOARM")
if !ok {
return nil, fmt.Errorf("could not get GOARM value")
}

sysCtx.ArchitectureChoice = runtime.GOARCH
sysCtx.VariantChoice = "v" + variance
default:
}
return sysCtx, nil
}

// Convert CLI options GOARCH and arch variant to recognized docker arch
func ConvertArch(arch, archVariant string) (string, error) {
supportedArchs := []string{"arm", "arm64", "amd64", "386", "ppc64le", "s390x"}
Expand Down
Loading

0 comments on commit afd8b1b

Please sign in to comment.