mirror of
https://github.com/project-zot/zot.git
synced 2026-06-18 05:28:07 +08:00
2402296e9a
* fix: migrate to Go module v2 for proper semantic versioning This change updates the module path from 'zotregistry.dev/zot' to 'zotregistry.dev/zot/v2' to comply with Go's semantic versioning rules. According to Go's module versioning requirements, major version v2+ must include the major version in the module path. The current module path 'zotregistry.dev/zot' only supports v0.x.x and v1.x.x versions, making existing v2.x.x tags (like v2.1.8) unusable. Changes: - Updated go.mod module path to zotregistry.dev/zot/v2 - Updated all internal import paths across 280+ Go source files - Updated configuration files (golangcilint.yaml, gqlgen.yml) - Updated README.md Go reference badge This fix enables proper use of existing v2.x.x Git tags and allows external packages to import zot v2+ versions without compatibility errors. Resolves: Go module import compatibility for v2+ versions Fixes: #3071 Signed-off-by: Luca Muscariello <muscariello@ieee.org> * fix: regenerate GraphQL files with updated v2 import paths The gqlgen tool needs to regenerate the GraphQL schema files after the module path change to use the new v2 imports. Signed-off-by: Luca Muscariello <muscariello@ieee.org> --------- Signed-off-by: Luca Muscariello <muscariello@ieee.org>
270 lines
6.4 KiB
Go
270 lines
6.4 KiB
Go
package image
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
godigest "github.com/opencontainers/go-digest"
|
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"gopkg.in/resty.v1"
|
|
|
|
tcommon "zotregistry.dev/zot/v2/pkg/test/common"
|
|
"zotregistry.dev/zot/v2/pkg/test/inject"
|
|
)
|
|
|
|
var (
|
|
ErrPostBlob = errors.New("can't post blob")
|
|
ErrPutBlob = errors.New("can't put blob")
|
|
ErrPutIndex = errors.New("can't put index")
|
|
)
|
|
|
|
func UploadImage(img Image, baseURL, repo, ref string) error {
|
|
if ref == "" {
|
|
ref = img.DigestStr()
|
|
}
|
|
|
|
digestAlgorithm := img.digestAlgorithm
|
|
|
|
if digestAlgorithm == "" {
|
|
digestAlgorithm = godigest.Canonical
|
|
}
|
|
|
|
for _, blob := range img.Layers {
|
|
resp, err := resty.R().Post(baseURL + "/v2/" + repo + "/blobs/uploads/")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if resp.StatusCode() != http.StatusAccepted {
|
|
return ErrPostBlob
|
|
}
|
|
|
|
loc := resp.Header().Get("Location")
|
|
|
|
digest := digestAlgorithm.FromBytes(blob).String()
|
|
|
|
resp, err = resty.R().
|
|
SetHeader("Content-Length", strconv.Itoa(len(blob))).
|
|
SetHeader("Content-Type", "application/octet-stream").
|
|
SetQueryParam("digest", digest).
|
|
SetBody(blob).
|
|
Put(baseURL + loc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if resp.StatusCode() != http.StatusCreated {
|
|
return ErrPutBlob
|
|
}
|
|
}
|
|
|
|
var err error
|
|
|
|
cblob := img.ConfigDescriptor.Data
|
|
|
|
// we'll remove this check once we make the full transition to the new way of generating test images
|
|
if len(cblob) == 0 {
|
|
cblob, err = json.Marshal(img.Config)
|
|
if err = inject.Error(err); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
cdigest := digestAlgorithm.FromBytes(cblob)
|
|
|
|
if img.Manifest.Config.MediaType == ispec.MediaTypeEmptyJSON ||
|
|
img.Manifest.Config.Digest == ispec.DescriptorEmptyJSON.Digest {
|
|
cblob = ispec.DescriptorEmptyJSON.Data
|
|
cdigest = ispec.DescriptorEmptyJSON.Digest
|
|
}
|
|
|
|
resp, err := resty.R().
|
|
Post(baseURL + "/v2/" + repo + "/blobs/uploads/")
|
|
if err = inject.Error(err); err != nil {
|
|
return err
|
|
}
|
|
|
|
if inject.ErrStatusCode(resp.StatusCode()) != http.StatusAccepted || inject.ErrStatusCode(resp.StatusCode()) == -1 {
|
|
return ErrPostBlob
|
|
}
|
|
|
|
loc := tcommon.Location(baseURL, resp)
|
|
|
|
// uploading blob should get 201
|
|
resp, err = resty.R().
|
|
SetHeader("Content-Length", strconv.Itoa(len(cblob))).
|
|
SetHeader("Content-Type", "application/octet-stream").
|
|
SetQueryParam("digest", cdigest.String()).
|
|
SetBody(cblob).
|
|
Put(loc)
|
|
if err = inject.Error(err); err != nil {
|
|
return err
|
|
}
|
|
|
|
if inject.ErrStatusCode(resp.StatusCode()) != http.StatusCreated || inject.ErrStatusCode(resp.StatusCode()) == -1 {
|
|
return ErrPostBlob
|
|
}
|
|
|
|
manifestBlob := img.ManifestDescriptor.Data
|
|
|
|
// we'll remove this check once we make the full transition to the new way of generating test images
|
|
if len(manifestBlob) == 0 {
|
|
manifestBlob, err = json.Marshal(img.Manifest)
|
|
if err = inject.Error(err); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
resp, err = resty.R().
|
|
SetHeader("Content-type", ispec.MediaTypeImageManifest).
|
|
SetBody(manifestBlob).
|
|
Put(baseURL + "/v2/" + repo + "/manifests/" + ref)
|
|
|
|
if inject.ErrStatusCode(resp.StatusCode()) != http.StatusCreated {
|
|
return ErrPutBlob
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func UploadImageWithBasicAuth(img Image, baseURL, repo, ref, user, password string) error {
|
|
digestAlgorithm := img.digestAlgorithm
|
|
|
|
if digestAlgorithm == "" {
|
|
digestAlgorithm = godigest.Canonical
|
|
}
|
|
|
|
for _, blob := range img.Layers {
|
|
resp, err := resty.R().
|
|
SetBasicAuth(user, password).
|
|
Post(baseURL + "/v2/" + repo + "/blobs/uploads/")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if resp.StatusCode() != http.StatusAccepted {
|
|
return ErrPostBlob
|
|
}
|
|
|
|
loc := resp.Header().Get("Location")
|
|
|
|
digest := digestAlgorithm.FromBytes(blob).String()
|
|
|
|
resp, err = resty.R().
|
|
SetBasicAuth(user, password).
|
|
SetHeader("Content-Length", strconv.Itoa(len(blob))).
|
|
SetHeader("Content-Type", "application/octet-stream").
|
|
SetQueryParam("digest", digest).
|
|
SetBody(blob).
|
|
Put(baseURL + loc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if resp.StatusCode() != http.StatusCreated {
|
|
return ErrPutBlob
|
|
}
|
|
}
|
|
// upload config
|
|
cblob, err := json.Marshal(img.Config)
|
|
if err = inject.Error(err); err != nil {
|
|
return err
|
|
}
|
|
|
|
cdigest := digestAlgorithm.FromBytes(cblob)
|
|
|
|
if img.Manifest.Config.MediaType == ispec.MediaTypeEmptyJSON {
|
|
cblob = ispec.DescriptorEmptyJSON.Data
|
|
cdigest = ispec.DescriptorEmptyJSON.Digest
|
|
}
|
|
|
|
resp, err := resty.R().
|
|
SetBasicAuth(user, password).
|
|
Post(baseURL + "/v2/" + repo + "/blobs/uploads/")
|
|
if err = inject.Error(err); err != nil {
|
|
return err
|
|
}
|
|
|
|
if inject.ErrStatusCode(resp.StatusCode()) != http.StatusAccepted || inject.ErrStatusCode(resp.StatusCode()) == -1 {
|
|
return ErrPostBlob
|
|
}
|
|
|
|
loc := tcommon.Location(baseURL, resp)
|
|
|
|
// uploading blob should get 201
|
|
resp, err = resty.R().
|
|
SetBasicAuth(user, password).
|
|
SetHeader("Content-Length", strconv.Itoa(len(cblob))).
|
|
SetHeader("Content-Type", "application/octet-stream").
|
|
SetQueryParam("digest", cdigest.String()).
|
|
SetBody(cblob).
|
|
Put(loc)
|
|
if err = inject.Error(err); err != nil {
|
|
return err
|
|
}
|
|
|
|
if inject.ErrStatusCode(resp.StatusCode()) != http.StatusCreated || inject.ErrStatusCode(resp.StatusCode()) == -1 {
|
|
return ErrPostBlob
|
|
}
|
|
|
|
// put manifest
|
|
manifestBlob, err := json.Marshal(img.Manifest)
|
|
if err = inject.Error(err); err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = resty.R().
|
|
SetBasicAuth(user, password).
|
|
SetHeader("Content-type", "application/vnd.oci.image.manifest.v1+json").
|
|
SetBody(manifestBlob).
|
|
Put(baseURL + "/v2/" + repo + "/manifests/" + ref)
|
|
|
|
return err
|
|
}
|
|
|
|
func UploadMultiarchImage(multiImage MultiarchImage, baseURL string, repo, ref string) error {
|
|
for _, image := range multiImage.Images {
|
|
err := UploadImage(image, baseURL, repo, image.DigestStr())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// put manifest
|
|
indexBlob := multiImage.IndexDescriptor.Data
|
|
|
|
if len(indexBlob) == 0 {
|
|
var err error
|
|
|
|
indexBlob, err = json.Marshal(multiImage.Index)
|
|
if err = inject.Error(err); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
resp, err := resty.R().
|
|
SetHeader("Content-type", ispec.MediaTypeImageIndex).
|
|
SetBody(indexBlob).
|
|
Put(baseURL + "/v2/" + repo + "/manifests/" + ref)
|
|
|
|
if resp.StatusCode() != http.StatusCreated {
|
|
return ErrPutIndex
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func DeleteImage(repo, reference, baseURL string) (int, error) {
|
|
resp, err := resty.R().Delete(
|
|
fmt.Sprintf(baseURL+"/v2/%s/manifests/%s", repo, reference),
|
|
)
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
|
|
return resp.StatusCode(), err
|
|
}
|