Skip to content
This repository has been archived by the owner on May 3, 2022. It is now read-only.

Commit

Permalink
Support for relocation mapping files (#778)
Browse files Browse the repository at this point in the history
* duffle relocate creates relocation mapping file

Ref #772

* Accept relocation mapping file on install, upgrade, and run

Fixes #772

* Address review comments
  • Loading branch information
glyn authored and jeremyrickard committed Jun 21, 2019
1 parent 0ff1344 commit 6b32ff8
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 130 deletions.
21 changes: 13 additions & 8 deletions cmd/duffle/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ Example:
Note: To install a bundle, use $ duffle bundle install or $ duffle install. They are aliases for the same action.
If the bundle has been relocated, you can pass the relocation mapping
file created by duffle relocate using the --relocation-mapping flag.
Different drivers are available for executing the duffle invocation
image. The following drivers are built-in:
Expand Down Expand Up @@ -56,13 +59,14 @@ type installCmd struct {
home home.Home
out io.Writer

driver string
credentialsFiles []string
valuesFile string
setParams []string
setFiles []string
bundleIsFile bool
name string
driver string
credentialsFiles []string
valuesFile string
setParams []string
setFiles []string
bundleIsFile bool
name string
relocationMapping string
}

func newInstallCmd(w io.Writer) *cobra.Command {
Expand All @@ -83,6 +87,7 @@ func newInstallCmd(w io.Writer) *cobra.Command {

f := cmd.Flags()
f.BoolVarP(&install.bundleIsFile, "bundle-is-file", "f", false, "Indicates that the bundle source is a file path")
f.StringVarP(&install.relocationMapping, "relocation-mapping", "m", "", "Path of relocation mapping JSON file")
f.StringVarP(&install.driver, "driver", "d", "docker", "Specify a driver name")
f.StringVarP(&install.valuesFile, "parameters", "p", "", "Specify file containing parameters. Formats: toml, MORE SOON")
f.StringArrayVarP(&install.credentialsFiles, "credentials", "c", []string{}, "Specify credentials to use inside the bundle. This can be a credentialset name or a path to a file.")
Expand Down Expand Up @@ -112,7 +117,7 @@ func (i *installCmd) run() error {
return err
}

driverImpl, err := prepareDriver(i.driver)
driverImpl, err := prepareDriver(i.driver, i.relocationMapping)
if err != nil {
return err
}
Expand Down
45 changes: 42 additions & 3 deletions cmd/duffle/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
Expand Down Expand Up @@ -96,10 +97,10 @@ func must(err error) {
}

// prepareDriver prepares a driver per the user's request.
func prepareDriver(driverName string) (driver.Driver, error) {
func prepareDriver(driverName string, relMap string) (driver.Driver, error) {
driverImpl, err := duffleDriver.Lookup(driverName)
if err != nil {
return driverImpl, err
return nil, err
}

// Load any driver-specific config out of the environment.
Expand All @@ -111,7 +112,45 @@ func prepareDriver(driverName string) (driver.Driver, error) {
configurable.SetConfig(driverCfg)
}

return driverImpl, err
rm, err := loadRelMapping(relMap)
if err != nil {
return nil, err
}

// wrap the driver so any relocation mapping is mounted
return &driverWithRelocationMapping{
driver: driverImpl,
relMapping: rm,
}, nil
}

type driverWithRelocationMapping struct {
driver driver.Driver
relMapping string
}

func (d *driverWithRelocationMapping) Run(op *driver.Operation) error {
// if there is a relocation mapping, ensure it is mounted
if d.relMapping != "" {
op.Files["/cnab/app/relocation-mapping.json"] = d.relMapping
}
return d.driver.Run(op)
}

func (d *driverWithRelocationMapping) Handles(it string) bool {
return d.driver.Handles(it)
}

func loadRelMapping(relMap string) (string, error) {
if relMap != "" {
data, err := ioutil.ReadFile(relMap)
if err != nil {
return "", fmt.Errorf("failed to read relocation mapping from %s: %v", relMap, err)
}
return string(data), nil
}

return "", nil
}

func loadBundle(bundleFile string) (*bundle.Bundle, error) {
Expand Down
84 changes: 30 additions & 54 deletions cmd/duffle/relocate.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package main

import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"path/filepath"
"strconv"
"strings"

Expand All @@ -23,26 +23,28 @@ import (
const (
relocateDesc = `
Relocates any docker and oci images, including invocation images, referenced by a bundle, tags and pushes the images to
a registry, and creates a new bundle with an updated invocation images section and an updated image map.
a registry, and creates a relocation mapping JSON file.
The --repository-prefix flag determines the repositories for the relocated images.
Each image is tagged with a name starting with the given prefix and pushed to the repository.
For example, if the repository-prefix is example.com/user, the image istio/proxyv2 is relocated
to a name starting with example.com/user/ and pushed to a repository hosted by example.com.
The generated relocation mapping file maps the original image references to their relocated counterparts. This file is
an optional input to the install, upgrade, and run commands.
`
invalidRepositoryChars = ":@\" "
)

type relocateCmd struct {
// args
inputBundle string
outputBundle string
inputBundle string

// flags
repoPrefix string
inputBundleIsFile bool
outputBundleIsFile bool
repoPrefix string
bundleIsFile bool
relocationMapping string

// context
home home.Home
Expand All @@ -60,10 +62,9 @@ func newRelocateCmd(w io.Writer) *cobra.Command {
Use: "relocate [INPUT-BUNDLE] [OUTPUT-BUNDLE]",
Short: "relocate images in a CNAB bundle",
Long: relocateDesc,
Example: `duffle relocate helloworld hellorelocated --repository-prefix example.com/user
duffle relocate path/to/bundle.json relocatedbundle --repository-prefix example.com/user --input-bundle-is-file
duffle relocate helloworld path/to/relocatedbundle.json --repository-prefix example.com/user --output-bundle-is-file`,
Args: cobra.ExactArgs(2),
Example: `duffle relocate helloworld --relocation-mapping path/to/relmap.json --repository-prefix example.com/user
duffle relocate path/to/bundle.json --relocation-mapping path/to/relmap.json --repository-prefix example.com/user --input-bundle-is-file`,
Args: cobra.ExactArgs(1),
PreRunE: func(cmd *cobra.Command, args []string) error {
// validate --repository-prefix if it is set, otherwise fall through so that cobra will report the missing flag in its usual manner
if cmd.Flags().Changed("repository-prefix") {
Expand All @@ -76,7 +77,6 @@ duffle relocate helloworld path/to/relocatedbundle.json --repository-prefix exam
},
RunE: func(cmd *cobra.Command, args []string) error {
relocate.inputBundle = args[0]
relocate.outputBundle = args[1]

relocate.home = home.Home(homePath())

Expand All @@ -88,9 +88,10 @@ duffle relocate helloworld path/to/relocatedbundle.json --repository-prefix exam
}

f := cmd.Flags()
f.BoolVarP(&relocate.inputBundleIsFile, "input-bundle-is-file", "", false, "Indicates that the input bundle source is a file path")
f.BoolVarP(&relocate.outputBundleIsFile, "output-bundle-is-file", "", false, "Indicates that the output bundle destination is a file path")
f.StringVarP(&relocate.repoPrefix, "repository-prefix", "r", "", "Prefix for relocated image names")
f.BoolVarP(&relocate.bundleIsFile, "bundle-is-file", "f", false, "Indicates that the input bundle source is a file path")
f.StringVarP(&relocate.relocationMapping, "relocation-mapping", "m", "", "Path for output relocation mapping JSON file")
cmd.MarkFlagRequired("relocation-mapping")
f.StringVarP(&relocate.repoPrefix, "repository-prefix", "p", "", "Prefix for relocated image names")
cmd.MarkFlagRequired("repository-prefix")

return cmd
Expand All @@ -102,22 +103,14 @@ func (r *relocateCmd) run() error {
return err
}

if err := r.relocate(bun); err != nil {
return err
}

return r.writeBundle(bun)
return r.relocate(bun)
}

func (r *relocateCmd) relocate(bun *bundle.Bundle) error {
// mutate the input bundle to become the output bundle
if !r.outputBundleIsFile {
bun.Name = r.outputBundle
}

relMap := make(map[string]string)
for i := range bun.InvocationImages {
ii := bun.InvocationImages[i]
modified, err := r.relocateImage(&ii.BaseImage)
modified, err := r.relocateImage(&ii.BaseImage, relMap)
if err != nil {
return err
}
Expand All @@ -128,7 +121,7 @@ func (r *relocateCmd) relocate(bun *bundle.Bundle) error {

for k := range bun.Images {
im := bun.Images[k]
modified, err := r.relocateImage(&im.BaseImage)
modified, err := r.relocateImage(&im.BaseImage, relMap)
if err != nil {
return err
}
Expand All @@ -137,10 +130,10 @@ func (r *relocateCmd) relocate(bun *bundle.Bundle) error {
}
}

return nil
return r.writeRelocationMapping(relMap)
}

func (r *relocateCmd) relocateImage(i *bundle.BaseImage) (bool, error) {
func (r *relocateCmd) relocateImage(i *bundle.BaseImage, relMap map[string]string) (bool, error) {
if !isOCI(i.ImageType) && !isDocker(i.ImageType) {
return false, nil
}
Expand All @@ -161,9 +154,9 @@ func (r *relocateCmd) relocateImage(i *bundle.BaseImage) (bool, error) {
return false, fmt.Errorf("digest of image %s not preserved: old digest %s; new digest %s", i.Image, i.Digest, dig.String())
}

// update the imagemap
i.OriginalImage = i.Image
i.Image = rn.String()
// update the relocation map
relMap[i.Image] = rn.String()

return true, nil
}

Expand All @@ -176,7 +169,7 @@ func isDocker(imageType string) bool {
}

func (r *relocateCmd) setup() (*bundle.Bundle, error) {
bundleFile, err := resolveBundleFilePath(r.inputBundle, r.home.String(), r.inputBundleIsFile)
bundleFile, err := resolveBundleFilePath(r.inputBundle, r.home.String(), r.bundleIsFile)
if err != nil {
return nil, err
}
Expand All @@ -193,30 +186,13 @@ func (r *relocateCmd) setup() (*bundle.Bundle, error) {
return bun, nil
}

func (r *relocateCmd) writeBundle(bf *bundle.Bundle) error {
data, digest, err := marshalBundle(bf)
func (r *relocateCmd) writeRelocationMapping(relMap map[string]string) error {
rm, err := json.Marshal(relMap)
if err != nil {
return fmt.Errorf("cannot marshal bundle: %v", err)
}

if r.outputBundleIsFile {
if err := ioutil.WriteFile(r.outputBundle, data, 0644); err != nil {
return fmt.Errorf("cannot write bundle to %s: %v", r.outputBundle, err)
}
return nil
}

if err := ioutil.WriteFile(filepath.Join(r.home.Bundles(), digest), data, 0644); err != nil {
return fmt.Errorf("cannot store bundle : %v", err)

}

// record the new bundle in repositories.json
if err := recordBundleReference(r.home, bf.Name, bf.Version, digest); err != nil {
return fmt.Errorf("cannot record bundle: %v", err)
return err
}

return nil
return ioutil.WriteFile(r.relocationMapping, rm, 0644)
}

func validateRepository(repo string) error {
Expand Down
Loading

0 comments on commit 6b32ff8

Please sign in to comment.