Skip to content

Commit

Permalink
Set the default platform for select sources based on host arch (#152)
Browse files Browse the repository at this point in the history
* set the default platform for select sources based on host arch

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* decompose into smaller function and add tests for setting default platform

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

---------

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
  • Loading branch information
wagoodman authored Mar 23, 2023
1 parent 92d7aa6 commit d7551b7
Show file tree
Hide file tree
Showing 2 changed files with 222 additions and 13 deletions.
44 changes: 31 additions & 13 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package stereoscope
import (
"context"
"fmt"
"runtime"

"github.com/anchore/go-logger"

Expand Down Expand Up @@ -101,13 +102,13 @@ func GetImageFromSource(ctx context.Context, imgStr string, source image.Source,
func selectImageProvider(imgStr string, source image.Source, cfg config) (image.Provider, error) {
var provider image.Provider
tempDirGenerator := rootTempDirGenerator.NewGenerator()
platformSelectionUnsupported := fmt.Errorf("specified platform=%q however image source=%q does not support selecting platform", cfg.Platform.String(), source.String())

if err := setPlatform(source, &cfg, runtime.GOARCH); err != nil {
return nil, err
}

switch source {
case image.DockerTarballSource:
if cfg.Platform != nil {
return nil, platformSelectionUnsupported
}
// note: the imgStr is the path on disk to the tar file
provider = docker.NewProviderFromTarball(imgStr, tempDirGenerator)
case image.DockerDaemonSource:
Expand All @@ -129,28 +130,45 @@ func selectImageProvider(imgStr string, source image.Source, cfg config) (image.
return nil, err
}
case image.OciDirectorySource:
if cfg.Platform != nil {
return nil, platformSelectionUnsupported
}
provider = oci.NewProviderFromPath(imgStr, tempDirGenerator)
case image.OciTarballSource:
if cfg.Platform != nil {
return nil, platformSelectionUnsupported
}
provider = oci.NewProviderFromTarball(imgStr, tempDirGenerator)
case image.OciRegistrySource:
provider = oci.NewProviderFromRegistry(imgStr, tempDirGenerator, cfg.Registry, cfg.Platform)
case image.SingularitySource:
if cfg.Platform != nil {
return nil, platformSelectionUnsupported
}
provider = sif.NewProviderFromPath(imgStr, tempDirGenerator)
default:
return nil, fmt.Errorf("unable to determine image source")
}
return provider, nil
}

func setPlatform(source image.Source, cfg *config, defaultArch string) error {
// we should override the platform based on the host architecture if the user did not specify a platform
// see https://github.com/anchore/stereoscope/issues/149 for more details
defaultPlatform, err := image.NewPlatform(defaultArch)
if err != nil {
log.WithFields("error", err).Warnf("unable to set default platform to %q", runtime.GOARCH)
defaultPlatform = nil
}

switch source {
case image.DockerTarballSource, image.OciDirectorySource, image.OciTarballSource, image.SingularitySource:
if cfg.Platform != nil {
return fmt.Errorf("specified platform=%q however image source=%q does not support selecting platform", cfg.Platform.String(), source.String())
}

case image.DockerDaemonSource, image.PodmanDaemonSource, image.OciRegistrySource:
if cfg.Platform == nil {
cfg.Platform = defaultPlatform
}

default:
return fmt.Errorf("unable to determine image source to select platform")
}
return nil
}

// GetImage parses the user provided image string and provides an image object;
// note: the source where the image should be referenced from is automatically inferred.
func GetImage(ctx context.Context, userStr string, options ...Option) (*image.Image, error) {
Expand Down
191 changes: 191 additions & 0 deletions client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package stereoscope

import (
"github.com/anchore/stereoscope/pkg/image"
"github.com/scylladb/go-set/i8set"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)

func Test_setPlatform(t *testing.T) {

expectedSources := i8set.New()
for _, s := range image.AllSources {
expectedSources.Add(int8(s))
}
actualSources := i8set.New()

tests := []struct {
name string
source image.Source
defaultArch string
initialPlatform *image.Platform
wantPlatform *image.Platform
wantErr require.ErrorAssertionFunc
}{
// allow defaults ---------------------------------------------------------
{
name: "docker daemon",
source: image.DockerDaemonSource,
defaultArch: "amd64",
initialPlatform: nil,
wantPlatform: &image.Platform{
Architecture: "amd64",
OS: "linux",
},
},
{
name: "docker daemon (do not override)",
source: image.DockerDaemonSource,
defaultArch: "amd64",
initialPlatform: &image.Platform{
Architecture: "arm64", // not different than default arch
OS: "linux",
},
wantPlatform: &image.Platform{
Architecture: "arm64", // note: did not change
OS: "linux",
},
},
{
name: "podman daemon",
source: image.PodmanDaemonSource,
defaultArch: "amd64",
initialPlatform: nil,
wantPlatform: &image.Platform{
Architecture: "amd64",
OS: "linux",
},
},
{
name: "podman daemon (do not override)",
source: image.PodmanDaemonSource,
defaultArch: "amd64",
initialPlatform: &image.Platform{
Architecture: "arm64", // not different than default arch
OS: "linux",
},
wantPlatform: &image.Platform{
Architecture: "arm64", // note: did not change
OS: "linux",
},
},
{
name: "OCI registry",
source: image.OciRegistrySource,
defaultArch: "amd64",
initialPlatform: nil,
wantPlatform: &image.Platform{
Architecture: "amd64",
OS: "linux",
},
},
{
name: "OCI registry (do not override)",
source: image.OciRegistrySource,
defaultArch: "amd64",
initialPlatform: &image.Platform{
Architecture: "arm64", // not different than default arch
OS: "linux",
},
wantPlatform: &image.Platform{
Architecture: "arm64", // note: did not change
OS: "linux",
},
},
// disallow defaults ---------------------------------------------------------
{
name: "docker tarball",
source: image.DockerTarballSource,
defaultArch: "amd64",
initialPlatform: nil,
wantPlatform: nil,
},
{
name: "docker tarball (override fails)",
source: image.DockerTarballSource,
defaultArch: "amd64",
initialPlatform: &image.Platform{
Architecture: "amd64",
OS: "linux",
},
wantErr: require.Error,
},
{
name: "OCI dir",
source: image.OciDirectorySource,
defaultArch: "amd64",
initialPlatform: nil,
wantPlatform: nil,
},
{
name: "OCI dir (override fails)",
source: image.OciDirectorySource,
defaultArch: "amd64",
initialPlatform: &image.Platform{
Architecture: "amd64",
OS: "linux",
},
wantErr: require.Error,
},
{
name: "OCI tarball",
source: image.OciTarballSource,
defaultArch: "amd64",
initialPlatform: nil,
wantPlatform: nil,
},
{
name: "OCI tarball (override fails)",
source: image.OciTarballSource,
defaultArch: "amd64",
initialPlatform: &image.Platform{
Architecture: "amd64",
OS: "linux",
},
wantErr: require.Error,
},
{
name: "singularity",
source: image.SingularitySource,
defaultArch: "amd64",
initialPlatform: nil,
wantPlatform: nil,
},
{
name: "singularity (override fails)",
source: image.SingularitySource,
defaultArch: "amd64",
initialPlatform: &image.Platform{
Architecture: "amd64",
OS: "linux",
},
wantErr: require.Error,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.wantErr == nil {
tt.wantErr = require.NoError
}

actualSources.Add(int8(tt.source))
cfg := config{
Platform: tt.initialPlatform,
}
err := setPlatform(tt.source, &cfg, tt.defaultArch)
tt.wantErr(t, err)
if err != nil {
return
}

assert.Equal(t, tt.wantPlatform, cfg.Platform)
})
}

diff := i8set.Difference(expectedSources, actualSources)
if !diff.IsEmpty() {
t.Errorf("missing test cases for sources: %v", diff.List())
}
}

0 comments on commit d7551b7

Please sign in to comment.