Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/images/lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const (
type ProviderImages struct {
Package string
ControllerImage *string
LocalImage bool
}

// GetImagesFromEnvironmentOrPanic retrieves image information from the environment and panics if `E2E_IMAGES` is not set
Expand Down
1 change: 1 addition & 0 deletions pkg/setup/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ func (s *ClusterSetup) Configure(testEnv env.Environment, cluster *kind.Cluster)
ControllerImage: s.Images.ControllerImage,
ControllerConfig: s.ControllerConfig,
DeploymentRuntimeConfig: s.DeploymentRuntimeConfig,
LocalImage: s.Images.LocalImage,
}),
), firstSetup),
setupProviderCredentials(s),
Expand Down
48 changes: 35 additions & 13 deletions pkg/xpenvfuncs/xpenvfuncs.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,13 +210,14 @@ type InstallCrossplaneProviderOptions struct {
ControllerImage *string // TODO read from package
ControllerConfig *vendored.ControllerConfig
DeploymentRuntimeConfig *vendored.DeploymentRuntimeConfig
LocalImage bool
}

// InstallCrossplaneProvider returns an env.Func that is used to
// install a crossplane provider into the active cluster
func InstallCrossplaneProvider(clusterName string, opts InstallCrossplaneProviderOptions) env.Func {
return Compose(
loadCrossplanePackageToCluster(clusterName, opts.Package),
loadCrossplanePackageToCluster(clusterName, opts.LocalImage, opts.Package),
loadCrossplaneControllerImageToCluster(clusterName, opts.ControllerImage),
installCrossplaneProviderEnvFunc(clusterName, opts),
awaitProviderHealthy(opts.Name),
Expand Down Expand Up @@ -296,7 +297,7 @@ func setupCrossplanePackageCache(clusterName string, cacheName string) env.Func
}

// loadCrossplanePackageToCluster loads the crossplane config package into the given clusters package cache folder (/cache)
func loadCrossplanePackageToCluster(clusterName string, pkg string) env.Func {
func loadCrossplanePackageToCluster(clusterName string, localImage bool, pkg string) env.Func {
return func(ctx context.Context, cfg *envconf.Config) (context.Context, error) {
f, err := os.CreateTemp("", "xpkg")
if err != nil {
Expand All @@ -312,21 +313,11 @@ func loadCrossplanePackageToCluster(clusterName string, pkg string) env.Func {
return ctx, err
}

ref, err := name.ParseReference(pkg)
if err != nil {
return ctx, err
}

digest, err := retrieveDigest(ctx, pkg)
cacheKeys, err := generatePackageCacheKeys(ctx, pkg, localImage, retrieveDigest)
if err != nil {
return ctx, err
}

cacheKeys := []string{
fullyQualifiedPathName("/cache/xpkg/", pkg, ".gz"),
fullyQualifiedPathName("/cache/xpkg/", friendlyID(parsePackageSourceFromReference(ref), digest), ".gz"),
}

for _, key := range cacheKeys {
if err := docker.Exec(clusterControlPlaneName, "mkdir", "-m", "777", "-p", filepath.Dir(key)); err != nil {
return ctx, err
Expand All @@ -343,6 +334,29 @@ func loadCrossplanePackageToCluster(clusterName string, pkg string) env.Func {
}
}

type retrieveDigestFunc func(context.Context, string) (string, error)

func generatePackageCacheKeys(ctx context.Context, pkg string, localImage bool, digestFunc retrieveDigestFunc) ([]string, error) {
digest, err := digestFunc(ctx, pkg)
if err != nil {
return nil, err
}
var friendlyIdentifier string
if localImage {
friendlyIdentifier = friendlyID(pkg, digest)
} else {
ref, err := name.ParseReference(pkg)
if err != nil {
return nil, err
}
friendlyIdentifier = friendlyID(parsePackageSourceFromReference(ref), digest)
}
return []string{
fullyQualifiedPathName("/cache/xpkg/", pkg, ".gz"),
fullyQualifiedPathName("/cache/xpkg/", friendlyIdentifier, ".gz"),
}, nil
}

// (from crossplane internal/xpkg)
func fullyQualifiedPathName(cacheDir, packageName, ext string) string {
full := filepath.Join(cacheDir, packageName)
Expand Down Expand Up @@ -429,6 +443,14 @@ func installCrossplaneProviderEnvFunc(_ string, opts InstallCrossplaneProviderOp
return func(ctx context.Context, cfg *envconf.Config) (context.Context, error) {
klog.V(4).Infof("Installing crossplane provider %s: %s", opts.Name, opts.Package)

if opts.LocalImage {
digest, err := retrieveDigest(ctx, opts.Package)
if err != nil {
return ctx, err
}
opts.Package = fmt.Sprintf("%s@%s", opts.Package, digest)
}

data := struct {
Name string
Package string
Expand Down
83 changes: 83 additions & 0 deletions pkg/xpenvfuncs/xpenvfuncs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/crossplane-contrib/xp-testing/pkg/vendored"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/e2e-framework/pkg/env"
"sigs.k8s.io/e2e-framework/pkg/envconf"
Expand Down Expand Up @@ -404,3 +405,85 @@ func Test_ValidateTestSetup(t *testing.T) {
})
}
}

func Test_generatePackageCacheKeys(t *testing.T) {
tests := []struct {
name string // description of this test case
// Named input parameters for target function.
pkg string
localImage bool
digestFunc retrieveDigestFunc
want []string
wantErr bool
}{
{
name: "remote image",
pkg: "xpkg.upbound.io/crossplane-contrib/provider-nop:v0.2.0",
localImage: false,
digestFunc: func(ctx context.Context, s string) (string, error) {
return "sha256:552a394a8accd2b4d37fc5858abe93d311e727eafb3c00636e11c72572873e48", nil
},
want: []string{
// note that the pkg repo name .0 is interpreted as file extension pre v2.2 and is replaced with gz
"/cache/xpkg/xpkg.upbound.io/crossplane-contrib/provider-nop:v0.2.gz", // < v2.2
// note that the image tag is cut off
"/cache/xpkg/xpkg-upbound-io-crossplane-contrib-provider-nop-sha256-552a3.gz", // >= v2.2
},
wantErr: false,
},
{
name: "local image without repo digest",
pkg: "index.docker.io/build-908b1e2d/provider-nop:5eaddce-dirty",
localImage: true,
digestFunc: func(ctx context.Context, s string) (string, error) {
return localImageDigest, nil
},
want: []string{
// note that pkg repo name does not contain a file extension and remains completely the same
"/cache/xpkg/index.docker.io/build-908b1e2d/provider-nop:5eaddce-dirty.gz", // < v2.2
// note that the image tag is not cut off but truncated
"/cache/xpkg/index-docker-io-build-908b1e2d-provider-nop-5eaddc-sha256-00000.gz", // >= v2.2
},
wantErr: false,
},
{
name: "local image with repo digest",
pkg: "index.docker.io/build-908b1e2d/provider-nop:5eaddce-dirty",
localImage: true,
digestFunc: func(ctx context.Context, s string) (string, error) {
return "sha256:2605848b12fdd3a0f2b21e3b36acb8beaa0b01f93db834584956c58b0569157d", nil
},
want: []string{
// note that pkg repo name does not contain a file extension and remains completely the same
"/cache/xpkg/index.docker.io/build-908b1e2d/provider-nop:5eaddce-dirty.gz", // < v2.2
// note that the image tag is not cut off but truncated
"/cache/xpkg/index-docker-io-build-908b1e2d-provider-nop-5eaddc-sha256-26058.gz", // >= v2.2
},
wantErr: false,
},
{
name: "retrieve digest error",
pkg: "xpkg.upbound.io/crossplane-contrib/provider-nop:v0.2.0",
digestFunc: func(ctx context.Context, s string) (string, error) {
return "", errors.Errorf("error")
},
want: []string{},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, gotErr := generatePackageCacheKeys(context.Background(), tt.pkg, tt.localImage, tt.digestFunc)
if gotErr != nil {
if !tt.wantErr {
t.Errorf("generatePackageCacheKeys() failed: %v", gotErr)
}
return
}
if tt.wantErr {
t.Fatal("generatePackageCacheKeys() succeeded unexpectedly")
}
assert.ElementsMatch(t, tt.want, got)
})
}
}