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 }