github.com/docker/app@v0.9.1-beta3.0.20210611140623-a48f773ab002/internal/commands/push.go (about) 1 package commands 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "os" 9 "strings" 10 11 "github.com/docker/app/internal/image" 12 "github.com/docker/app/internal/store" 13 14 "github.com/containerd/containerd/platforms" 15 "github.com/docker/app/internal" 16 "github.com/docker/app/internal/log" 17 "github.com/docker/cli/cli" 18 "github.com/docker/cli/cli/command" 19 "github.com/docker/cnab-to-oci/remotes" 20 "github.com/docker/distribution/reference" 21 "github.com/docker/docker/pkg/term" 22 "github.com/morikuni/aec" 23 ocischemav1 "github.com/opencontainers/image-spec/specs-go/v1" 24 "github.com/pkg/errors" 25 "github.com/sirupsen/logrus" 26 "github.com/spf13/cobra" 27 ) 28 29 const ( // Docker specific annotations and values 30 // DockerAppFormatAnnotation is the top level annotation specifying the kind of the App AppImage 31 DockerAppFormatAnnotation = "io.docker.app.format" 32 // DockerAppFormatCNAB is the DockerAppFormatAnnotation value for CNAB 33 DockerAppFormatCNAB = "cnab" 34 35 // DockerTypeAnnotation is the annotation that designates the type of the application 36 DockerTypeAnnotation = "io.docker.type" 37 // DockerTypeApp is the value used to fill DockerTypeAnnotation when targeting a docker-app 38 DockerTypeApp = "app" 39 ) 40 41 func pushCmd(dockerCli command.Cli) *cobra.Command { 42 cmd := &cobra.Command{ 43 Use: "push APP_IMAGE", 44 Short: "Push an App image to a registry", 45 Example: `$ docker app push myrepo/myapp:mytag`, 46 Args: cli.ExactArgs(1), 47 RunE: func(cmd *cobra.Command, args []string) error { 48 return runPush(dockerCli, args[0]) 49 }, 50 } 51 return cmd 52 } 53 54 func runPush(dockerCli command.Cli, name string) error { 55 defer muteDockerCli(dockerCli)() 56 imageStore, err := prepareImageStore() 57 if err != nil { 58 return err 59 } 60 61 // Get the bundle 62 ref, err := imageStore.LookUp(name) 63 if err != nil { 64 return errors.Wrapf(err, "could not push %q", name) 65 } 66 named, ok := ref.(reference.Named) 67 if !ok { 68 return fmt.Errorf("could not push by ID. first tag your app image") 69 } 70 71 bndl, err := resolveReferenceAndBundle(imageStore, named) 72 if err != nil { 73 return err 74 } 75 76 cnabRef := reference.TagNameOnly(named) 77 78 // Push the bundle 79 return pushBundle(dockerCli, bndl, cnabRef) 80 } 81 82 func resolveReferenceAndBundle(imageStore store.ImageStore, ref reference.Reference) (*image.AppImage, error) { 83 bndl, err := imageStore.Read(ref) 84 if err != nil { 85 return nil, errors.Wrapf(err, "could not push %q: no such App image", reference.FamiliarString(ref)) 86 } 87 88 if err := bndl.Validate(); err != nil { 89 return nil, err 90 } 91 92 return bndl, err 93 } 94 95 func pushBundle(dockerCli command.Cli, bndl *image.AppImage, cnabRef reference.Named) error { 96 insecureRegistries, err := internal.InsecureRegistriesFromEngine(dockerCli) 97 if err != nil { 98 return errors.Wrap(err, "could not retrieve insecure registries") 99 } 100 resolver := remotes.CreateResolver(dockerCli.ConfigFile(), insecureRegistries...) 101 var display fixupDisplay = &plainDisplay{out: os.Stdout} 102 if term.IsTerminal(os.Stdout.Fd()) { 103 display = &interactiveDisplay{out: os.Stdout} 104 } 105 fixupOptions := []remotes.FixupOption{ 106 remotes.WithEventCallback(display.onEvent), 107 remotes.WithAutoBundleUpdate(), 108 remotes.WithPushImages(dockerCli.Client(), dockerCli.Out()), 109 remotes.WithRelocationMap(bndl.RelocationMap), 110 } 111 // bundle fixup 112 relocationMap, err := remotes.FixupBundle(context.Background(), bndl.Bundle, cnabRef, resolver, fixupOptions...) 113 if err != nil { 114 return errors.Wrapf(err, "fixing up %q for push", cnabRef) 115 } 116 bndl.RelocationMap = relocationMap 117 // push bundle manifest 118 logrus.Debugf("Pushing the bundle %q", cnabRef) 119 descriptor, err := remotes.Push(log.WithLogContext(context.Background()), bndl.Bundle, bndl.RelocationMap, cnabRef, resolver, true, withAppAnnotations) 120 if err != nil { 121 return errors.Wrapf(err, "pushing to %q", cnabRef) 122 } 123 fmt.Fprintf(os.Stdout, "Successfully pushed bundle to %s. Digest is %s.\n", cnabRef, descriptor.Digest) 124 return nil 125 } 126 127 func withAppAnnotations(index *ocischemav1.Index) error { 128 if index.Annotations == nil { 129 index.Annotations = make(map[string]string) 130 } 131 index.Annotations[DockerAppFormatAnnotation] = DockerAppFormatCNAB 132 index.Annotations[DockerTypeAnnotation] = DockerTypeApp 133 return nil 134 } 135 136 type fixupDisplay interface { 137 onEvent(remotes.FixupEvent) 138 } 139 140 type interactiveDisplay struct { 141 out io.Writer 142 previousLineCount int 143 images []interactiveImageState 144 } 145 146 func (r *interactiveDisplay) onEvent(ev remotes.FixupEvent) { 147 out := bytes.NewBuffer(nil) 148 for i := 0; i < r.previousLineCount; i++ { 149 fmt.Fprint(out, aec.NewBuilder(aec.Up(1), aec.EraseLine(aec.EraseModes.All)).ANSI) 150 } 151 switch ev.EventType { 152 case remotes.FixupEventTypeCopyImageStart: 153 r.images = append(r.images, interactiveImageState{name: ev.SourceImage}) 154 case remotes.FixupEventTypeCopyImageEnd: 155 r.images[r.imageIndex(ev.SourceImage)].done = true 156 case remotes.FixupEventTypeProgress: 157 r.images[r.imageIndex(ev.SourceImage)].onProgress(ev.Progress) 158 } 159 r.previousLineCount = 0 160 for _, s := range r.images { 161 r.previousLineCount += s.print(out) 162 } 163 r.out.Write(out.Bytes()) //nolint:errcheck // nothing much we can do with an error to write to output. 164 } 165 166 func (r *interactiveDisplay) imageIndex(name string) int { 167 for ix, state := range r.images { 168 if state.name == name { 169 return ix 170 } 171 } 172 return 0 173 } 174 175 type interactiveImageState struct { 176 name string 177 progress remotes.ProgressSnapshot 178 done bool 179 } 180 181 func (s *interactiveImageState) onProgress(p remotes.ProgressSnapshot) { 182 s.progress = p 183 } 184 185 func (s *interactiveImageState) print(out io.Writer) int { 186 if s.done { 187 fmt.Fprint(out, aec.Apply(s.name, aec.BlueF)) 188 } else { 189 fmt.Fprint(out, s.name) 190 } 191 fmt.Fprint(out, "\n") 192 lineCount := 1 193 194 for _, p := range s.progress.Roots { 195 lineCount += printDescriptorProgress(out, &p, 1) 196 } 197 return lineCount 198 } 199 200 func printDescriptorProgress(out io.Writer, p *remotes.DescriptorProgressSnapshot, depth int) int { 201 fmt.Fprint(out, strings.Repeat(" ", depth)) 202 name := p.MediaType 203 if p.Platform != nil { 204 name = platforms.Format(*p.Platform) 205 } 206 if len(p.Children) == 0 { 207 name = fmt.Sprintf("%s...: %s", p.Digest.String()[:15], p.Action) 208 } 209 doneCount := 0 210 for _, c := range p.Children { 211 if c.Done { 212 doneCount++ 213 } 214 } 215 display := name 216 if len(p.Children) > 0 { 217 display = fmt.Sprintf("%s [%d/%d] (%s...)", name, doneCount, len(p.Children), p.Digest.String()[:15]) 218 } 219 if p.Done { 220 display = aec.Apply(display, aec.BlueF) 221 } 222 if hasError(p) { 223 display = aec.Apply(display, aec.RedF) 224 } 225 fmt.Fprintln(out, display) 226 lineCount := 1 227 if p.Done { 228 return lineCount 229 } 230 for _, c := range p.Children { 231 lineCount += printDescriptorProgress(out, &c, depth+1) 232 } 233 return lineCount 234 } 235 236 func hasError(p *remotes.DescriptorProgressSnapshot) bool { 237 if p.Error != nil { 238 return true 239 } 240 for _, c := range p.Children { 241 if hasError(&c) { 242 return true 243 } 244 } 245 return false 246 } 247 248 type plainDisplay struct { 249 out io.Writer 250 } 251 252 func (r *plainDisplay) onEvent(ev remotes.FixupEvent) { 253 switch ev.EventType { 254 case remotes.FixupEventTypeCopyImageStart: 255 fmt.Fprintf(r.out, "Handling image %s...", ev.SourceImage) 256 case remotes.FixupEventTypeCopyImageEnd: 257 if ev.Error != nil { 258 fmt.Fprintf(r.out, "\nFailure: %s\n", ev.Error) 259 } else { 260 fmt.Fprint(r.out, " done!\n") 261 } 262 } 263 }