package operator import ( "bytes" "context" "errors" "fmt" "io" "net/http" "strconv" "mstore/app/descr" "mstore/pkg/auxoci" ocidigest "github.com/opencontainers/go-digest" ) const ( ddmMimeType = "application/vnd.docker.distribution.manifest.v2+json" oimMimeType = "application/vnd.oci.image.manifest.v1+json" ) type ManifestExistsParams struct { Name string Reference string } type ManifestExistsResult struct { ContentLength string ContentType string DockerContentDigest string Exists bool } func (oper *Operator) ManifestExists(ctx context.Context, params *ManifestExistsParams) (*ManifestExistsResult, int, error) { var err error res := &ManifestExistsResult{} if params.Name == "" { err = fmt.Errorf("Empty name") return res, http.StatusBadRequest, err } if params.Reference == "" { err = fmt.Errorf("Empty reference") return res, http.StatusBadRequest, err } oper.logg.Debugf("Head manifest [%s:%s]", params.Name, params.Reference) var manifest descr.Manifest var exists bool if stringLikeSHA256Digest(params.Reference) { digest := fmt.Sprintf("%s:%s", sha256prefix, params.Reference) oper.logg.Debugf("Find manifest %s by digest %s", params.Name, params.Reference) exists, manifest, err = oper.mdb.GetManifestByDigest(ctx, params.Name, digest) if err != nil { return res, http.StatusInternalServerError, err } if !exists { return res, http.StatusNotFound, err } } else { oper.logg.Debugf("Find manifest %s by reference %s", params.Name, params.Reference) exists, manifest, err = oper.mdb.GetManifestByReference(ctx, params.Name, params.Reference) if err != nil { return res, http.StatusInternalServerError, err } if !exists { return res, http.StatusNotFound, err } } digest := ocidigest.SHA256.FromString(manifest.Payload) payloadSize := len(manifest.Payload) res.ContentLength = strconv.FormatInt(int64(payloadSize), 10) res.ContentType = manifest.ContentType res.DockerContentDigest = digest.String() res.Exists = exists return res, http.StatusOK, err } type PutManifestParams struct { ContentType string Name string Reference string Reader io.Reader } type PutManifestResult struct { Location string } // TODO: control size 413 Payload Too Large func (oper *Operator) PutManifest(ctx context.Context, params *PutManifestParams) (*PutManifestResult, int, error) { var err error res := &PutManifestResult{} oper.logg.Debugf("Put manifest %s:%s", params.Name, params.Reference) if params.Reference == "" { err = fmt.Errorf("Empty reference") return res, http.StatusBadRequest, err } if params.Name == "" { err = fmt.Errorf("Empty name") return res, http.StatusBadRequest, err } // Check Content-Type if params.ContentType != oimMimeType { err = fmt.Errorf("Unknown or empty Content-Type: %s", params.ContentType) return res, http.StatusNotFound, err } // Copy manifest data buffer := bytes.NewBuffer(nil) _, err = io.Copy(buffer, params.Reader) if err != nil { return res, http.StatusInternalServerError, err } incomingManifestBytes := buffer.Bytes() oper.logg.Debugf("Manifest data: [%s]", string(incomingManifestBytes)) incomingManifest, err := auxoci.ParseOCIManifest(incomingManifestBytes) if err != nil { err = fmt.Errorf("Parsing OCI manifest error: %v", err) return res, http.StatusInternalServerError, err } if incomingManifest.MediaType != params.ContentType { err := fmt.Errorf("Mismatch Content-Type header and manifest MediaType: %s vs %s", params.ContentType, incomingManifest.MediaType) return res, http.StatusInternalServerError, err } manifestExists, _, err := oper.mdb.GetManifestByReference(ctx, params.Name, params.Reference) if err != nil { return res, http.StatusInternalServerError, err } if !manifestExists { name := params.Name reference := params.Reference manifestDescr, layerDescrs, err := descrsFromManifest(name, reference, incomingManifest, incomingManifestBytes) // Check layers var blobError error for _, layer := range layerDescrs { layerExists, _, err := oper.store.BlobFileExists(layer.Digest) if err != nil { return res, http.StatusInternalServerError, err } if !layerExists { err := fmt.Errorf("Layer %s not found", layer.Digest) blobError = errors.Join(blobError, err) } } if blobError != nil { return res, http.StatusInternalServerError, blobError } // Store manifest and layesrs data err = oper.mdb.InsertManifestWithLayers(ctx, &manifestDescr, layerDescrs) if err != nil { return res, http.StatusInternalServerError, err } } /* exists, existingManifestDescr, err := lg.mdb.GetManifestByReference(params.Name, params.Reference) if err != nil { return res, http.StatusInternalServerError, err } if exists { existingManifestBytes := []byte(existingManifestDescr.Payload) // Exist if incoming and existing manyfest is equal if bytes.Equal(existingManifestBytes, incomingManifestBytes) { return res, http.StatusCreated, err } name := params.Name reference := params.Reference manifestDescr, newBlobDescrs, delBlobDescrs, err := blobsDiff(name, reference, existingManifestBytes, incomingManifestBytes) if err != nil { return res, http.StatusInternalServerError, err } err = lg.maindb.UpdateManifest(ctx, &manifestDescr, newBlobDescrs, delBlobDescrs) if err != nil { return res, http.StatusInternalServerError, err } // Clean blobs for _, blob := range delBlobDescrs { exists, _, err = lg.st.BlobFileExists(blob.Digest) if err != nil { return res, http.StatusInternalServerError, err } blobUsage, err := lg.maindb.GetBlobUsage(blob.Digest) if err != nil { return res, http.StatusInternalServerError, err } if exists && blobUsage == 0 { lg.log.Debugf("Delete file %s:%s blob %s", params.Name, params.Reference, blob.Digest) err = lg.st.DeleteBlobFile(blob.Digest) if err != nil { return res, http.StatusInternalServerError, err } } } } else { name := params.Name reference := params.Reference manifestDescr, configDescr, layerDescrs, err := descrsFromManifestBytes(name, reference, incomingManifestBytes) // Check layer blobs var blobError error for _, layer := range layerDescrs { exists, _, err = lg.st.BlobFileExists(layer.Digest) if !exists { err := fmt.Errorf("Blob %s not exists", layer.Digest) blobError = errors.Join(blobError, err) } } if blobError != nil { return res, http.StatusInternalServerError, blobError } // Store manifest and blobs data err = lg.maindb.InsertManifest(ctx, &manifestDescr, &configDescr, layerDescrs) if err != nil { return res, http.StatusInternalServerError, err } } */ res.Location = fmt.Sprintf(`/v2/%s/manifests/%s`, params.Name, params.Reference) return res, http.StatusCreated, err }