Files
codit/backend/internal/docker/browse.go

288 lines
6.2 KiB
Go

package docker
import "encoding/json"
import "errors"
import "io/fs"
import "os"
import "path/filepath"
import "strings"
type TagInfo struct {
Tag string `json:"tag"`
Digest string `json:"digest"`
Size int64 `json:"size"`
MediaType string `json:"media_type"`
}
type ManifestDetail struct {
Reference string `json:"reference"`
Digest string `json:"digest"`
MediaType string `json:"media_type"`
Size int64 `json:"size"`
Config ImageConfig `json:"config"`
Layers []ociDescriptor `json:"layers"`
}
type ImageConfig struct {
Created string `json:"created"`
Architecture string `json:"architecture"`
OS string `json:"os"`
}
type manifestPayload struct {
SchemaVersion int `json:"schemaVersion"`
MediaType string `json:"mediaType"`
Config ociDescriptor `json:"config"`
Layers []ociDescriptor `json:"layers"`
}
func ListTags(repoPath string) ([]TagInfo, error) {
var tags []string
var err error
var list []TagInfo
var i int
var tag string
var desc ociDescriptor
tags, err = listTags(repoPath)
if err != nil {
return nil, err
}
list = make([]TagInfo, 0, len(tags))
for i = 0; i < len(tags); i++ {
tag = tags[i]
desc, err = resolveTag(repoPath, tag)
if err != nil {
continue
}
list = append(list, TagInfo{
Tag: tag,
Digest: desc.Digest,
Size: desc.Size,
MediaType: desc.MediaType,
})
}
return list, nil
}
func DeleteTag(repoPath string, tag string) error {
var idx ociIndex
var err error
var updated []ociDescriptor
var i int
var desc ociDescriptor
var keep bool
idx, err = loadIndex(repoPath)
if err != nil {
return err
}
updated = make([]ociDescriptor, 0, len(idx.Manifests))
for i = 0; i < len(idx.Manifests); i++ {
desc = idx.Manifests[i]
keep = true
if desc.Annotations != nil && desc.Annotations[ociTagAnnotation] == tag {
keep = false
}
if keep {
updated = append(updated, desc)
}
}
idx.Manifests = updated
return saveIndex(repoPath, idx)
}
func DeleteImage(repoPath string, image string) error {
var imagePath string
imagePath = ImagePath(repoPath, image)
return os.RemoveAll(imagePath)
}
func RenameTag(repoPath string, from string, to string) error {
var idx ociIndex
var err error
var i int
var desc ociDescriptor
var hasFrom bool
var hasTo bool
if from == "" || to == "" {
return errors.New("tag required")
}
if from == to {
return errors.New("tag unchanged")
}
idx, err = loadIndex(repoPath)
if err != nil {
return err
}
for i = 0; i < len(idx.Manifests); i++ {
desc = idx.Manifests[i]
if desc.Annotations == nil {
continue
}
if desc.Annotations[ociTagAnnotation] == to {
hasTo = true
}
}
if hasTo {
return errors.New("tag already exists")
}
for i = 0; i < len(idx.Manifests); i++ {
desc = idx.Manifests[i]
if desc.Annotations == nil {
continue
}
if desc.Annotations[ociTagAnnotation] == from {
desc.Annotations[ociTagAnnotation] = to
idx.Manifests[i] = desc
hasFrom = true
break
}
}
if !hasFrom {
return ErrNotFound
}
return saveIndex(repoPath, idx)
}
func RenameImage(repoPath string, from string, to string) error {
var srcPath string
var destPath string
var err error
var info os.FileInfo
var dir string
if from == to {
return errors.New("image unchanged")
}
if IsReservedImagePath(to) {
return errors.New("invalid image name")
}
srcPath = ImagePath(repoPath, from)
destPath = ImagePath(repoPath, to)
info, err = os.Stat(srcPath)
if err != nil {
if os.IsNotExist(err) {
return ErrNotFound
}
return err
}
if info == nil || !info.IsDir() {
return errors.New("source is not a directory")
}
_, err = os.Stat(destPath)
if err == nil {
return errors.New("target already exists")
}
if err != nil && !os.IsNotExist(err) {
return err
}
dir = filepath.Dir(destPath)
err = os.MkdirAll(dir, 0o755)
if err != nil {
return err
}
return os.Rename(srcPath, destPath)
}
func ListImages(repoPath string) ([]string, error) {
var images []string
var err error
var seen map[string]struct{}
var root string
var ok bool
var dummy struct{}
seen = map[string]struct{}{}
root = filepath.Clean(repoPath)
err = filepath.WalkDir(root, func(path string, entry fs.DirEntry, walkErr error) error {
var base string
var rel string
var dir string
if walkErr != nil {
return walkErr
}
if entry.IsDir() {
base = entry.Name()
if base == "blobs" || base == "uploads" {
return filepath.SkipDir
}
return nil
}
if entry.Name() != "oci-layout" {
return nil
}
dir = filepath.Dir(path)
rel, err = filepath.Rel(root, dir)
if err != nil {
return err
}
if rel == "." {
rel = ""
}
rel = filepath.ToSlash(rel)
if rel == ".root" {
rel = ""
}
dummy, ok = seen[rel]
_ = dummy
if !ok {
seen[rel] = struct{}{}
images = append(images, rel)
}
return nil
})
if err != nil && !os.IsNotExist(err) {
return nil, err
}
return images, nil
}
func ImagePath(repoPath string, image string) string {
var cleaned string
var normalized string
cleaned = strings.Trim(image, "/")
if cleaned == "" {
return filepath.Join(repoPath, ".root")
}
normalized = strings.ReplaceAll(cleaned, "\\", "/")
if normalized == ".root" {
return filepath.Join(repoPath, ".root")
}
return filepath.Join(repoPath, filepath.FromSlash(cleaned))
}
func GetManifestDetail(repoPath string, reference string) (ManifestDetail, error) {
var detail ManifestDetail
var desc ociDescriptor
var err error
var data []byte
var payload manifestPayload
var configData []byte
var config ImageConfig
desc, err = resolveManifest(repoPath, reference)
if err != nil {
return detail, err
}
data, err = ReadBlob(repoPath, desc.Digest)
if err != nil {
return detail, err
}
err = json.Unmarshal(data, &payload)
if err != nil {
return detail, err
}
if payload.MediaType != "" {
desc.MediaType = payload.MediaType
}
configData, err = ReadBlob(repoPath, payload.Config.Digest)
if err == nil {
_ = json.Unmarshal(configData, &config)
}
detail = ManifestDetail{
Reference: reference,
Digest: desc.Digest,
MediaType: desc.MediaType,
Size: int64(len(data)),
Config: config,
Layers: payload.Layers,
}
return detail, nil
}