// ImagePush requests the docker host to push an image to a remote registry. // It executes the privileged function if the operation is unauthorized // and it tries one more time. // It's up to the caller to handle the io.ReadCloser and close it properly. func (cli *Client) ImagePush(ctx context.Context, ref string, options types.ImagePushOptions) (io.ReadCloser, error) { distributionRef, err := distreference.ParseNamed(ref) if err != nil { return nil, err } if _, isCanonical := distributionRef.(distreference.Canonical); isCanonical { return nil, errors.New("cannot push a digest reference") } tag := reference.GetTagFromNamedRef(distributionRef) query := url.Values{} query.Set("tag", tag) resp, err := cli.tryImagePush(ctx, distributionRef.Name(), query, options.RegistryAuth) if resp.statusCode == http.StatusUnauthorized { newAuthHeader, privilegeErr := options.PrivilegeFunc() if privilegeErr != nil { return nil, privilegeErr } resp, err = cli.tryImagePush(ctx, distributionRef.Name(), query, newAuthHeader) } if err != nil { return nil, err } return resp.body, nil }
// ImageTag tags an image in the docker host func (cli *Client) ImageTag(ctx context.Context, imageID, ref string) error { distributionRef, err := distreference.ParseNamed(ref) if err != nil { return fmt.Errorf("Error parsing reference: %q is not a valid repository/tag", ref) } if _, isCanonical := distributionRef.(distreference.Canonical); isCanonical { return errors.New("refusing to create a tag with a digest reference") } tag := reference.GetTagFromNamedRef(distributionRef) query := url.Values{} query.Set("repo", distributionRef.Name()) query.Set("tag", tag) resp, err := cli.post(ctx, "/images/"+imageID+"/tag", query, nil, nil) ensureReaderClosed(resp) return err }
// ContainerCommit applies changes into a container and creates a new tagged image. func (cli *Client) ContainerCommit(ctx context.Context, container string, options types.ContainerCommitOptions) (types.ContainerCommitResponse, error) { var repository, tag string if options.Reference != "" { distributionRef, err := distreference.ParseNamed(options.Reference) if err != nil { return types.ContainerCommitResponse{}, err } if _, isCanonical := distributionRef.(distreference.Canonical); isCanonical { return types.ContainerCommitResponse{}, errors.New("refusing to create a tag with a digest reference") } tag = reference.GetTagFromNamedRef(distributionRef) repository = distributionRef.Name() } query := url.Values{} query.Set("container", container) query.Set("repo", repository) query.Set("tag", tag) query.Set("comment", options.Comment) query.Set("author", options.Author) for _, change := range options.Changes { query.Add("changes", change) } if options.Pause != true { query.Set("pause", "0") } var response types.ContainerCommitResponse resp, err := cli.post(ctx, "/commit", query, options.Config, nil) if err != nil { return response, err } err = json.NewDecoder(resp.body).Decode(&response) ensureReaderClosed(resp) return response, err }
// Spool downloades Docker images from Distribution, builds base layer for Porto container func (b *Box) Spool(ctx context.Context, name string, opts isolate.Profile) (err error) { defer apexctx.GetLogger(ctx).WithField("name", name).Trace("spool").Stop(&err) profile, err := docker.ConvertProfile(opts) if err != nil { apexctx.GetLogger(ctx).WithError(err).WithField("name", name).Info("unbale to convert raw profile to Porto/Docker specific profile") return err } if profile.Registry == "" { apexctx.GetLogger(ctx).WithField("name", name).Error("Registry must be non empty") return fmt.Errorf("Registry must be non empty") } portoConn, err := portoConnect() if err != nil { apexctx.GetLogger(ctx).WithError(err).WithField("name", name).Error("Porto connection error") return err } named, err := reference.ParseNamed(filepath.Join(profile.Repository, profile.Repository, name)) if err != nil { apexctx.GetLogger(ctx).WithError(err).WithField("name", name).Error("name is invalid") return err } var tr http.RoundTripper if registryAuth, ok := b.config.RegistryAuth[profile.Registry]; ok { tr = transport.NewTransport(b.transport, transport.NewHeaderRequestModifier(http.Header{ "Authorization": []string{registryAuth}, })) } else { tr = b.transport } var registry = profile.Registry if !strings.HasPrefix(registry, "http") { registry = "https://" + registry } apexctx.GetLogger(ctx).Debugf("Image URI generated at spawn with data: %s and %s", registry, named) repo, err := client.NewRepository(ctx, named, registry, tr) if err != nil { return err } tagDescriptor, err := repo.Tags(ctx).Get(ctx, engineref.GetTagFromNamedRef(named)) if err != nil { return err } layerName := b.appLayerName(name) digest := tagDescriptor.Digest.String() if b.journal.In(layerName, digest) { apexctx.GetLogger(ctx).WithField("name", name).Infof("layer %s has been found in the cache", digest) return nil } manifests, err := repo.Manifests(ctx) if err != nil { return err } manifest, err := manifests.Get(ctx, tagDescriptor.Digest) if err != nil { return err } if err = portoConn.RemoveLayer(layerName); err != nil && !isEqualPortoError(err, portorpc.EError_LayerNotFound) { return err } apexctx.GetLogger(ctx).WithField("name", name).Infof("create a layer %s in Porto with merge", layerName) var order layersOrder switch manifest.(type) { case schema1.SignedManifest, *schema1.SignedManifest: order = layerOrderV1 case schema2.DeserializedManifest, *schema2.DeserializedManifest: order = layerOrderV2 default: return fmt.Errorf("unknown manifest type %T", manifest) } for _, descriptor := range order(manifest.References()) { blobPath, err := b.blobRepo.Get(ctx, repo, descriptor.Digest) if err != nil { return err } entry := apexctx.GetLogger(ctx).WithField("layer", layerName).Trace("ImportLayer with merge") err = portoConn.ImportLayer(layerName, blobPath, true) entry.Stop(&err) if err != nil { return err } } b.journal.Insert(layerName, digest) // NOTE: Not so fast, but it's important to debug journalContent.Set(b.journal.String()) return nil }