Skip to content

Commit 3059c00

Browse files
Update Porter to handle relocation mapping on pull/operations (#719)
* Update Porter to handle relocation mapping on pull/operations This PR updates Porter to handle a relocation mapping if it is generated on a bundle pull. If generated, it is stored in the bundle cache and then passed along via an oper config to add the file to an operation before it is run. Also overwrites the invocation image, if it is found in the relocation mapping
1 parent 06e6b67 commit 3059c00

22 files changed

+318
-85
lines changed

pkg/cache/cache.go

+46-13
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,19 @@ package cache
33
import (
44
"crypto/md5"
55
"encoding/hex"
6+
"encoding/json"
67
"os"
78
"path/filepath"
89

910
"github.com/deislabs/cnab-go/bundle"
1011
"github.com/deislabs/porter/pkg/config"
12+
"github.com/docker/cnab-to-oci/relocation"
1113
"github.com/pkg/errors"
1214
)
1315

1416
type BundleCache interface {
15-
FindBundle(string) (string, bool, error)
16-
StoreBundle(string, *bundle.Bundle) (string, error)
17+
FindBundle(string) (string, string, bool, error)
18+
StoreBundle(string, *bundle.Bundle, relocation.ImageRelocationMap) (string, string, error)
1719
GetCacheDir() (string, error)
1820
}
1921

@@ -29,42 +31,73 @@ func New(cfg *config.Config) BundleCache {
2931

3032
// FindBundle looks for a given bundle tag in the Porter bundle cache and
3133
// returns the path to the bundle if it exists. If it is not found, an
32-
// empty string and the boolean false value are returned.
33-
func (c *cache) FindBundle(bundleTag string) (string, bool, error) {
34+
// empty string and the boolean false value are returned. If the bundle is found,
35+
// and a relocation mapping file is present, it will be returned as well. If the relocation
36+
// is not found, an empty string is returned.
37+
func (c *cache) FindBundle(bundleTag string) (string, string, bool, error) {
3438
bid := getBundleID(bundleTag)
3539
bundleCnabDir, err := c.getCachedBundleCNABDir(bid)
3640
cachedBundlePath := filepath.Join(bundleCnabDir, "bundle.json")
41+
cachedReloPath := filepath.Join(bundleCnabDir, "relocation-mapping.json")
3742
bExists, err := c.FileSystem.Exists(cachedBundlePath)
3843
if err != nil {
39-
return "", false, errors.Wrapf(err, "unable to read bundle %s at %s", bundleTag, cachedBundlePath)
44+
return "", "", false, errors.Wrapf(err, "unable to read bundle %s at %s", bundleTag, cachedBundlePath)
4045
}
4146
if !bExists {
42-
return "", false, nil
47+
return "", "", false, nil
4348
}
44-
return cachedBundlePath, true, nil
49+
//check for a relocation mapping next to it
50+
rExists, err := c.FileSystem.Exists(cachedReloPath)
51+
if err != nil {
52+
return "", "", false, errors.Wrapf(err, "unable to read relocation mapping %s at %s", bundleTag, cachedReloPath)
53+
}
54+
if rExists {
55+
return cachedBundlePath, cachedReloPath, true, nil
56+
}
57+
return cachedBundlePath, "", true, nil
4558

4659
}
4760

4861
// StoreBundle will write a given bundle to the bundle cache, in a location derived
49-
// from the bundleTag.
50-
func (c *cache) StoreBundle(bundleTag string, bun *bundle.Bundle) (string, error) {
62+
// from the bundleTag. If a relocation mapping is provided, it will be stored along side
63+
// the bundle. If successful, returns the path to the bundle, along with the path to a
64+
// relocation mapping, if provided. Otherwise, returns an error.
65+
func (c *cache) StoreBundle(bundleTag string, bun *bundle.Bundle, reloMap relocation.ImageRelocationMap) (string, string, error) {
5166
bid := getBundleID(bundleTag)
5267
bundleCnabDir, err := c.getCachedBundleCNABDir(bid)
5368
cachedBundlePath := filepath.Join(bundleCnabDir, "bundle.json")
5469
err = c.FileSystem.MkdirAll(bundleCnabDir, os.ModePerm)
5570
if err != nil {
56-
return "", errors.Wrap(err, "unable to create cache directory")
71+
return "", "", errors.Wrap(err, "unable to create cache directory")
5772
}
5873
f, err := c.FileSystem.OpenFile(cachedBundlePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
5974
defer f.Close()
6075
if err != nil {
61-
return "", errors.Wrapf(err, "error creating cnab/bundle.json for %s", bundleTag)
76+
return "", "", errors.Wrapf(err, "error creating cnab/bundle.json for %s", bundleTag)
6277
}
6378
_, err = bun.WriteTo(f)
6479
if err != nil {
65-
return "", errors.Wrapf(err, "error writing to cnab/bundle.json for %s", bundleTag)
80+
return "", "", errors.Wrapf(err, "error writing to cnab/bundle.json for %s", bundleTag)
81+
}
82+
// we wrote the bundle, now lets store a relocation mapping in cnab/ and return the path
83+
if len(reloMap) > 0 {
84+
cachedReloPath := filepath.Join(bundleCnabDir, "relocation-mapping.json")
85+
f, err = c.FileSystem.OpenFile(cachedReloPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
86+
defer f.Close()
87+
if err != nil {
88+
return "", "", errors.Wrapf(err, "error creating cnab/relocation-mapping.json for %s", bundleTag)
89+
}
90+
b, err := json.Marshal(reloMap)
91+
if err != nil {
92+
return "", "", errors.Wrapf(err, "couldn't marshall relocation mapping for %s", bundleTag)
93+
}
94+
_, err = f.Write(b)
95+
if err != nil {
96+
return "", "", errors.Wrapf(err, "couldn't write relocation mapping for %s", bundleTag)
97+
}
98+
return cachedBundlePath, cachedReloPath, nil
6699
}
67-
return cachedBundlePath, nil
100+
return cachedBundlePath, "", nil
68101
}
69102

70103
func (c *cache) GetCacheDir() (string, error) {

pkg/cache/cache_test.go

+52-6
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ package cache
22

33
import (
44
"bytes"
5+
"fmt"
56
"path/filepath"
67
"testing"
78

89
"github.com/deislabs/cnab-go/bundle"
910
"github.com/deislabs/porter/pkg/config"
11+
"github.com/docker/cnab-to-oci/relocation"
1012
"github.com/stretchr/testify/assert"
1113
"github.com/stretchr/testify/require"
1214
)
@@ -36,7 +38,7 @@ func TestFindBundleCacheExists(t *testing.T) {
3638
cfg.TestContext.AddTestDirectory("testdata", cacheDir)
3739
c := New(cfg.Config)
3840

39-
_, ok, err := c.FindBundle("deislabs/kubekahn:latest")
41+
_, _, ok, err := c.FindBundle("deislabs/kubekahn:latest")
4042
assert.NoError(t, err, "the cache dir should exist, no error should have happened")
4143
assert.False(t, ok, "the bundle shouldn't exist")
4244
}
@@ -49,7 +51,7 @@ func TestFindBundleCacheDoesNotExist(t *testing.T) {
4951
cfg.TestContext.AddTestDirectory("testdata", cacheDir)
5052
c := New(cfg.Config)
5153

52-
_, ok, err := c.FindBundle("deislabs/kubekahn:latest")
54+
_, _, ok, err := c.FindBundle("deislabs/kubekahn:latest")
5355
assert.NoError(t, err, "the cache dir doesn't exist, but this shouldn't be an error")
5456
assert.False(t, ok, "the bundle shouldn't exist")
5557
}
@@ -66,7 +68,7 @@ func TestFindBundleBundleCached(t *testing.T) {
6668
foundIt, err := cfg.Config.FileSystem.Exists(expectedCacheFile)
6769
require.True(t, foundIt, "test data not loaded")
6870
c := New(cfg.Config)
69-
path, ok, err := c.FindBundle(kahn1dot01)
71+
path, _, ok, err := c.FindBundle(kahn1dot01)
7072
assert.NoError(t, err, "the cache dir should exist, no error should have happened")
7173
assert.True(t, ok, "the bundle should exist")
7274
assert.Equal(t, expectedCacheFile, path)
@@ -75,7 +77,7 @@ func TestFindBundleBundleCached(t *testing.T) {
7577
func TestFindBundleBundleNotCached(t *testing.T) {
7678
cfg := config.NewTestConfig(t)
7779
c := New(cfg.Config)
78-
path, ok, err := c.FindBundle(kahnlatest)
80+
path, _, ok, err := c.FindBundle(kahnlatest)
7981
assert.NoError(t, err, "the cache dir should exist, no error should have happened")
8082
assert.False(t, ok, "the bundle should not exist")
8183
assert.Empty(t, path, "should not have a path")
@@ -89,7 +91,8 @@ func TestCacheWriteNoCacheDir(t *testing.T) {
8991
require.NoError(t, err, "bundle should have been valid")
9092

9193
c := New(cfg.Config)
92-
path, err := c.StoreBundle(kahn1dot01, &bun)
94+
var reloMap relocation.ImageRelocationMap
95+
path, _, err := c.StoreBundle(kahn1dot01, &bun, reloMap)
9396

9497
home, err := cfg.Config.GetHomeDir()
9598
require.NoError(t, err, "should have had a porter home dir")
@@ -114,7 +117,8 @@ func TestCacheWriteCacheDirExists(t *testing.T) {
114117
require.NoError(t, err, "bundle should have been valid")
115118

116119
c := New(cfg.Config)
117-
path, err := c.StoreBundle(kahn1dot01, &bun)
120+
var reloMap relocation.ImageRelocationMap
121+
path, _, err := c.StoreBundle(kahn1dot01, &bun, reloMap)
118122

119123
expectedCacheDirectory := filepath.Join(cacheDir, kahn1dot0Hash)
120124
expectedCacheCNABDirectory := filepath.Join(expectedCacheDirectory, "cnab")
@@ -123,3 +127,45 @@ func TestCacheWriteCacheDirExists(t *testing.T) {
123127
assert.Equal(t, expectedCacheFile, path)
124128
assert.NoError(t, err, "storing bundle should have succeeded")
125129
}
130+
131+
func TestStoreRelocationMapping(t *testing.T) {
132+
133+
cfg := config.NewTestConfig(t)
134+
home, _ := cfg.Config.GetHomeDir()
135+
cacheDir := filepath.Join(home, "cache")
136+
137+
tests := []struct {
138+
name string
139+
relocationMapping relocation.ImageRelocationMap
140+
tag string
141+
bundle *bundle.Bundle
142+
wantedReloPath string
143+
err error
144+
}{
145+
{
146+
name: "relocation file gets a path",
147+
bundle: &bundle.Bundle{},
148+
tag: kahn1dot01,
149+
relocationMapping: relocation.ImageRelocationMap{
150+
"asd": "asdf",
151+
},
152+
wantedReloPath: filepath.Join(cacheDir, kahn1dot0Hash, "cnab", "relocation-mapping.json"),
153+
},
154+
{
155+
name: "no relocation file gets no path",
156+
tag: kahnlatest,
157+
bundle: &bundle.Bundle{},
158+
wantedReloPath: "",
159+
},
160+
}
161+
162+
c := New(cfg.Config)
163+
for _, test := range tests {
164+
_, reloPath, err := c.StoreBundle(test.tag, test.bundle, test.relocationMapping)
165+
assert.NoError(t, err, fmt.Sprintf("didn't expect storage error for test %s", test.name))
166+
assert.Equal(t, test.wantedReloPath, reloPath, "didn't get expected path for store on test: %s", test.name)
167+
_, fetchedPath, _, err := c.FindBundle(test.tag)
168+
assert.Equal(t, test.wantedReloPath, fetchedPath, "didn't get expected path for load on test: %s", test.name)
169+
170+
}
171+
}

pkg/cache/testdata/cnab/bundle.json

+7-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@
55
}
66
},
77
"description": "",
8-
"images": null,
8+
"images": {
9+
"my-microservice":{
10+
"contentDigest":"sha256:cca460afa270d4c527981ef9ca4989346c56cf9b20217dcea37df1ece8120687",
11+
"description":"my microservice",
12+
"image":"gabrtv/microservice@sha256:cca460afa270d4c527981ef9ca4989346c56cf9b20217dcea37df1ece8120687"
13+
}
14+
},
915
"invocationImages": [{
1016
"digest": "sha256:f858bc025ad34099fe67ebe6152e03b4c91b34cc7a77d1aa10aaf1dc1389c2c2",
1117
"image": "jeremyrickard/porter-mysql@sha256:f858bc025ad34099fe67ebe6152e03b4c91b34cc7a77d1aa10aaf1dc1389c2c2",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"gabrtv/microservice@sha256:cca460afa270d4c527981ef9ca4989346c56cf9b20217dcea37df1ece8120687": "my.registry/microservice@sha256:cca460afa270d4c527981ef9ca4989346c56cf9b20217dcea37df1ece8120687"
3+
}

pkg/cnab/cnab-to-oci/registry.go

+8-6
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ package cnabtooci
33
import (
44
"context"
55
"fmt"
6+
67
"strings"
78

89
containerdRemotes "github.com/containerd/containerd/remotes"
910
"github.com/deislabs/cnab-go/bundle"
1011
"github.com/docker/cli/cli/command"
1112
dockerconfig "github.com/docker/cli/cli/config"
1213
cliflags "github.com/docker/cli/cli/flags"
14+
"github.com/docker/cnab-to-oci/relocation"
1315
"github.com/docker/cnab-to-oci/remotes"
1416
"github.com/docker/distribution/reference"
1517
"github.com/docker/docker/api/types"
@@ -31,11 +33,11 @@ func NewRegistry(c *portercontext.Context) *Registry {
3133
}
3234
}
3335

34-
// PullBundle pulls a bundle from an OCI registry.
35-
func (r *Registry) PullBundle(tag string, insecureRegistry bool) (*bundle.Bundle, error) {
36+
// PullBundle pulls a bundle from an OCI registry. Returns the bundle, and an optional image relocation mapping, if applicable.
37+
func (r *Registry) PullBundle(tag string, insecureRegistry bool) (*bundle.Bundle, relocation.ImageRelocationMap, error) {
3638
ref, err := reference.ParseNormalizedNamed(tag)
3739
if err != nil {
38-
return nil, errors.Wrap(err, "invalid bundle tag format, expected REGISTRY/name:tag")
40+
return nil, nil, errors.Wrap(err, "invalid bundle tag format, expected REGISTRY/name:tag")
3941
}
4042

4143
var insecureRegistries []string
@@ -44,11 +46,11 @@ func (r *Registry) PullBundle(tag string, insecureRegistry bool) (*bundle.Bundle
4446
insecureRegistries = append(insecureRegistries, reg)
4547
}
4648

47-
bun, _, err := remotes.Pull(context.Background(), ref, r.createResolver(insecureRegistries))
49+
bun, reloMap, err := remotes.Pull(context.Background(), ref, r.createResolver(insecureRegistries))
4850
if err != nil {
49-
return nil, errors.Wrap(err, "unable to pull remote bundle")
51+
return nil, nil, errors.Wrap(err, "unable to pull remote bundle")
5052
}
51-
return bun, nil
53+
return bun, reloMap, nil
5254
}
5355

5456
func (r *Registry) PushBundle(bun *bundle.Bundle, tag string, insecureRegistry bool) error {

pkg/cnab/provider/action.go

+32
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package cnabprovider
22

33
import (
4+
"encoding/json"
5+
46
"github.com/deislabs/cnab-go/action"
57
"github.com/deislabs/cnab-go/driver"
8+
"github.com/docker/cnab-to-oci/relocation"
9+
"github.com/pkg/errors"
610
)
711

812
// Shared arguments for all CNAB actions
@@ -28,12 +32,16 @@ type ActionArguments struct {
2832

2933
// Driver is the CNAB-compliant driver used to run bundle actions.
3034
Driver string
35+
36+
// Path to an optional relocation mapping file
37+
RelocationMapping string
3138
}
3239

3340
func (d *Runtime) ApplyConfig(args ActionArguments) action.OperationConfigs {
3441
return action.OperationConfigs{
3542
d.SetOutput(),
3643
d.AddFiles(args),
44+
d.AddRelocation(args),
3745
}
3846
}
3947

@@ -52,3 +60,27 @@ func (d *Runtime) AddFiles(args ActionArguments) action.OperationConfigFunc {
5260
return nil
5361
}
5462
}
63+
64+
// AddRelocation operates on an ActionArguments and adds any provided relocation mapping
65+
// to the operation's files.
66+
func (d *Runtime) AddRelocation(args ActionArguments) action.OperationConfigFunc {
67+
return func(op *driver.Operation) error {
68+
if args.RelocationMapping != "" {
69+
b, err := d.FileSystem.ReadFile(args.RelocationMapping)
70+
if err != nil {
71+
return errors.Wrap(err, "unable to add relocation mapping")
72+
}
73+
op.Files["/cnab/app/relocation-mapping.json"] = string(b)
74+
var reloMap relocation.ImageRelocationMap
75+
err = json.Unmarshal(b, &reloMap)
76+
// If the invocation image is present in the relocation mapping, we need
77+
// to update the operation and set the new image reference. Unfortunately,
78+
// the relocation mapping is just reference => reference, so there isn't a
79+
// great way to check for the invocation image.
80+
if mappedInvo, ok := reloMap[op.Image.Image]; ok {
81+
op.Image.Image = mappedInvo
82+
}
83+
}
84+
return nil
85+
}
86+
}

pkg/cnab/provider/action_test.go

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package cnabprovider
2+
3+
import (
4+
"github.com/deislabs/cnab-go/bundle"
5+
"io/ioutil"
6+
"testing"
7+
8+
"github.com/deislabs/cnab-go/driver"
9+
"github.com/deislabs/porter/pkg/config"
10+
instancestorageprovider "github.com/deislabs/porter/pkg/instance-storage/provider"
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func TestAddReloccation(t *testing.T) {
16+
data, err := ioutil.ReadFile("testdata/relocation-mapping.json")
17+
require.NoError(t, err)
18+
19+
c := config.NewTestConfig(t)
20+
instanceStorage := instancestorageprovider.NewPluginDelegator(c.Config)
21+
d := NewRuntime(c.Config, instanceStorage)
22+
23+
args := ActionArguments{
24+
RelocationMapping: "/cnab/app/relocation-mapping.json",
25+
}
26+
27+
c.TestContext.AddTestFile("testdata/relocation-mapping.json", "/cnab/app/relocation-mapping.json")
28+
29+
opConf := d.AddRelocation(args)
30+
31+
invoImage := bundle.InvocationImage{}
32+
invoImage.Image = "gabrtv/microservice@sha256:cca460afa270d4c527981ef9ca4989346c56cf9b20217dcea37df1ece8120687"
33+
34+
op := &driver.Operation{
35+
Files: make(map[string]string),
36+
Image: invoImage,
37+
}
38+
err = opConf(op)
39+
assert.NoError(t, err)
40+
41+
mapping, ok := op.Files["/cnab/app/relocation-mapping.json"]
42+
assert.True(t, ok)
43+
assert.Equal(t, string(data), mapping)
44+
assert.Equal(t, "my.registry/microservice@sha256:cca460afa270d4c527981ef9ca4989346c56cf9b20217dcea37df1ece8120687", op.Image.Image)
45+
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"gabrtv/microservice@sha256:cca460afa270d4c527981ef9ca4989346c56cf9b20217dcea37df1ece8120687": "my.registry/microservice@sha256:cca460afa270d4c527981ef9ca4989346c56cf9b20217dcea37df1ece8120687",
3+
"technosophos/helloworld:0.1.0": "my.registry/helloworld:0.1.0"
4+
}

0 commit comments

Comments
 (0)