github.com/replicatedhq/ship@v0.55.0/pkg/lifecycle/render/docker/step.go (about)

     1  package docker
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/url"
     7  	"path"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/go-kit/kit/log"
    12  	"github.com/go-kit/kit/log/level"
    13  	"github.com/pkg/errors"
    14  	"github.com/replicatedhq/libyaml"
    15  	"github.com/replicatedhq/ship/pkg/api"
    16  	"github.com/replicatedhq/ship/pkg/images"
    17  	"github.com/replicatedhq/ship/pkg/lifecycle/render/root"
    18  	"github.com/replicatedhq/ship/pkg/templates"
    19  	"github.com/replicatedhq/ship/pkg/util"
    20  	"github.com/spf13/viper"
    21  )
    22  
    23  // A Renderer can execute a "docker" step in the lifecycle
    24  type Renderer interface {
    25  	Execute(
    26  		rootFs root.Fs,
    27  		asset api.DockerAsset,
    28  		meta api.ReleaseMetadata,
    29  		doWithProgress func(ch chan interface{}, debug log.Logger) error,
    30  		// kind of a hack/shortcut, the abstraction is leaking,
    31  		// but we reuse this step in dockerlayer,
    32  		// so allow for overriding the save destination
    33  		saveDest string,
    34  		templateContext map[string]interface{},
    35  		configGroups []libyaml.ConfigGroup,
    36  	) func(ctx context.Context) error
    37  }
    38  
    39  var _ Renderer = &DefaultStep{}
    40  
    41  // DefaultStep is the default implementation of Renderer
    42  type DefaultStep struct {
    43  	Logger         log.Logger
    44  	URLResolver    images.PullURLResolver
    45  	ImageSaver     images.ImageSaver
    46  	Viper          *viper.Viper
    47  	BuilderBuilder *templates.BuilderBuilder
    48  }
    49  
    50  // NewStep gets a new Renderer with the default impl
    51  func NewStep(
    52  	logger log.Logger,
    53  	resolver images.PullURLResolver,
    54  	saver images.ImageSaver,
    55  	v *viper.Viper,
    56  	bb *templates.BuilderBuilder,
    57  ) Renderer {
    58  	return &DefaultStep{
    59  		Logger:         logger,
    60  		URLResolver:    resolver,
    61  		ImageSaver:     saver,
    62  		Viper:          v,
    63  		BuilderBuilder: bb,
    64  	}
    65  }
    66  
    67  // Execute runs the step for an asset
    68  func (p *DefaultStep) Execute(
    69  	rootFs root.Fs,
    70  	asset api.DockerAsset,
    71  	meta api.ReleaseMetadata,
    72  	doWithProgress func(ch chan interface{}, debug log.Logger) error,
    73  	dest string,
    74  	templateContext map[string]interface{},
    75  	configGroups []libyaml.ConfigGroup,
    76  ) func(ctx context.Context) error {
    77  
    78  	if dest == "" {
    79  		dest = asset.Dest
    80  	}
    81  
    82  	return func(ctx context.Context) error {
    83  		debug := level.Debug(log.With(p.Logger, "step.type", "render", "render.phase", "execute", "asset.type", "docker", "dest", dest, "description", asset.Description))
    84  		debug.Log("event", "execute")
    85  
    86  		builder, err := p.BuilderBuilder.FullBuilder(meta, configGroups, templateContext)
    87  		if err != nil {
    88  			return errors.Wrap(err, "init builder")
    89  		}
    90  
    91  		builtDest, err := builder.String(dest)
    92  		if err != nil {
    93  			return errors.Wrap(err, "building dest")
    94  		}
    95  
    96  		destinationURL, err := url.Parse(builtDest)
    97  		if err != nil {
    98  			return errors.Wrapf(err, "parse destination URL %s", dest)
    99  		}
   100  		destIsDockerURL := destinationURL.Scheme == "docker"
   101  		if !destIsDockerURL {
   102  			err = util.IsLegalPath(dest)
   103  			if err != nil {
   104  				return errors.Wrap(err, "find docker image dest")
   105  			}
   106  
   107  			basePath := filepath.Dir(dest)
   108  			debug.Log("event", "mkdirall.attempt", "dest", dest, "basePath", basePath)
   109  			if err := rootFs.MkdirAll(basePath, 0755); err != nil {
   110  				debug.Log("event", "mkdirall.fail", "err", err, "dest", dest, "basePath", basePath)
   111  				return errors.Wrapf(err, "write directory to %s", dest)
   112  			}
   113  		}
   114  		pullURL, err := p.URLResolver.ResolvePullURL(asset, meta)
   115  		if err != nil {
   116  			return errors.Wrapf(err, "resolve pull url")
   117  		}
   118  
   119  		// first try with registry secret
   120  		// TODO remove this once registry is updated to read installation ID
   121  		userName := meta.CustomerID
   122  		if userName == "" {
   123  			userName = meta.LicenseID
   124  		}
   125  		registrySecretSaveOpts := images.SaveOpts{
   126  			PullURL:   pullURL,
   127  			SaveURL:   asset.Image,
   128  			IsPrivate: asset.Source != "public" && asset.Source != "",
   129  			Username:  userName,
   130  			Password:  meta.RegistrySecret,
   131  		}
   132  
   133  		if destIsDockerURL {
   134  			registrySecretSaveOpts.DestinationURL = destinationURL
   135  		} else {
   136  			registrySecretSaveOpts.Filename = path.Join(rootFs.RootPath, dest)
   137  		}
   138  
   139  		ch := p.ImageSaver.SaveImage(ctx, registrySecretSaveOpts)
   140  		saveError := doWithProgress(ch, debug)
   141  
   142  		if saveError == nil {
   143  			debug.Log("event", "execute.succeed")
   144  			return nil
   145  		}
   146  
   147  		debug.Log("event", "execute.fail.withRegistrySecret", "err", saveError)
   148  		debug.Log("event", "execute.try.withInstallationID")
   149  
   150  		if strings.Contains(saveError.Error(), "not found") {
   151  			return errors.Wrap(saveError, "pull with RegistrySecret")
   152  		}
   153  
   154  		if meta.CustomerID == "" {
   155  			return fmt.Errorf("exhausted all authentication methods for %s", pullURL)
   156  		}
   157  
   158  		// next try with installationID for password
   159  		installationIDSaveOpts := images.SaveOpts{
   160  			PullURL:   pullURL,
   161  			SaveURL:   asset.Image,
   162  			IsPrivate: asset.Source != "public" && asset.Source != "",
   163  			Username:  meta.CustomerID,
   164  			Password:  p.Viper.GetString("installation-id"),
   165  		}
   166  
   167  		if destIsDockerURL {
   168  			installationIDSaveOpts.DestinationURL = destinationURL
   169  		} else {
   170  			installationIDSaveOpts.Filename = path.Join(rootFs.RootPath, dest)
   171  		}
   172  
   173  		ch = p.ImageSaver.SaveImage(ctx, installationIDSaveOpts)
   174  		saveError = doWithProgress(ch, debug)
   175  
   176  		if saveError != nil {
   177  			debug.Log("event", "execute.fail.withInstallationID", "detail", "both docker auth methods failed", "err", saveError)
   178  			return errors.Wrap(saveError, "docker save image, both auth methods failed")
   179  		}
   180  
   181  		return nil
   182  	}
   183  }