code.gitea.io/gitea@v1.22.3/modules/packages/container/metadata.go (about)

     1  // Copyright 2022 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package container
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"strings"
    10  
    11  	"code.gitea.io/gitea/modules/json"
    12  	"code.gitea.io/gitea/modules/packages/container/helm"
    13  	"code.gitea.io/gitea/modules/validation"
    14  
    15  	oci "github.com/opencontainers/image-spec/specs-go/v1"
    16  )
    17  
    18  const (
    19  	PropertyRepository        = "container.repository"
    20  	PropertyDigest            = "container.digest"
    21  	PropertyMediaType         = "container.mediatype"
    22  	PropertyManifestTagged    = "container.manifest.tagged"
    23  	PropertyManifestReference = "container.manifest.reference"
    24  
    25  	DefaultPlatform = "linux/amd64"
    26  
    27  	labelLicenses      = "org.opencontainers.image.licenses"
    28  	labelURL           = "org.opencontainers.image.url"
    29  	labelSource        = "org.opencontainers.image.source"
    30  	labelDocumentation = "org.opencontainers.image.documentation"
    31  	labelDescription   = "org.opencontainers.image.description"
    32  	labelAuthors       = "org.opencontainers.image.authors"
    33  )
    34  
    35  type ImageType string
    36  
    37  const (
    38  	TypeOCI  ImageType = "oci"
    39  	TypeHelm ImageType = "helm"
    40  )
    41  
    42  // Name gets the name of the image type
    43  func (it ImageType) Name() string {
    44  	switch it {
    45  	case TypeHelm:
    46  		return "Helm Chart"
    47  	default:
    48  		return "OCI / Docker"
    49  	}
    50  }
    51  
    52  // Metadata represents the metadata of a Container package
    53  type Metadata struct {
    54  	Type             ImageType         `json:"type"`
    55  	IsTagged         bool              `json:"is_tagged"`
    56  	Platform         string            `json:"platform,omitempty"`
    57  	Description      string            `json:"description,omitempty"`
    58  	Authors          []string          `json:"authors,omitempty"`
    59  	Licenses         string            `json:"license,omitempty"`
    60  	ProjectURL       string            `json:"project_url,omitempty"`
    61  	RepositoryURL    string            `json:"repository_url,omitempty"`
    62  	DocumentationURL string            `json:"documentation_url,omitempty"`
    63  	Labels           map[string]string `json:"labels,omitempty"`
    64  	ImageLayers      []string          `json:"layer_creation,omitempty"`
    65  	Manifests        []*Manifest       `json:"manifests,omitempty"`
    66  }
    67  
    68  type Manifest struct {
    69  	Platform string `json:"platform"`
    70  	Digest   string `json:"digest"`
    71  	Size     int64  `json:"size"`
    72  }
    73  
    74  // ParseImageConfig parses the metadata of an image config
    75  func ParseImageConfig(mt string, r io.Reader) (*Metadata, error) {
    76  	if strings.EqualFold(mt, helm.ConfigMediaType) {
    77  		return parseHelmConfig(r)
    78  	}
    79  
    80  	// fallback to OCI Image Config
    81  	return parseOCIImageConfig(r)
    82  }
    83  
    84  func parseOCIImageConfig(r io.Reader) (*Metadata, error) {
    85  	var image oci.Image
    86  	if err := json.NewDecoder(r).Decode(&image); err != nil {
    87  		return nil, err
    88  	}
    89  
    90  	platform := DefaultPlatform
    91  	if image.OS != "" && image.Architecture != "" {
    92  		platform = fmt.Sprintf("%s/%s", image.OS, image.Architecture)
    93  		if image.Variant != "" {
    94  			platform = fmt.Sprintf("%s/%s", platform, image.Variant)
    95  		}
    96  	}
    97  
    98  	imageLayers := make([]string, 0, len(image.History))
    99  	for _, history := range image.History {
   100  		cmd := history.CreatedBy
   101  		if i := strings.Index(cmd, "#(nop) "); i != -1 {
   102  			cmd = strings.TrimSpace(cmd[i+7:])
   103  		}
   104  		if cmd != "" {
   105  			imageLayers = append(imageLayers, cmd)
   106  		}
   107  	}
   108  
   109  	metadata := &Metadata{
   110  		Type:             TypeOCI,
   111  		Platform:         platform,
   112  		Licenses:         image.Config.Labels[labelLicenses],
   113  		ProjectURL:       image.Config.Labels[labelURL],
   114  		RepositoryURL:    image.Config.Labels[labelSource],
   115  		DocumentationURL: image.Config.Labels[labelDocumentation],
   116  		Description:      image.Config.Labels[labelDescription],
   117  		Labels:           image.Config.Labels,
   118  		ImageLayers:      imageLayers,
   119  	}
   120  
   121  	if authors, ok := image.Config.Labels[labelAuthors]; ok {
   122  		metadata.Authors = []string{authors}
   123  	}
   124  
   125  	if !validation.IsValidURL(metadata.ProjectURL) {
   126  		metadata.ProjectURL = ""
   127  	}
   128  	if !validation.IsValidURL(metadata.RepositoryURL) {
   129  		metadata.RepositoryURL = ""
   130  	}
   131  	if !validation.IsValidURL(metadata.DocumentationURL) {
   132  		metadata.DocumentationURL = ""
   133  	}
   134  
   135  	return metadata, nil
   136  }
   137  
   138  func parseHelmConfig(r io.Reader) (*Metadata, error) {
   139  	var config helm.Metadata
   140  	if err := json.NewDecoder(r).Decode(&config); err != nil {
   141  		return nil, err
   142  	}
   143  
   144  	metadata := &Metadata{
   145  		Type:        TypeHelm,
   146  		Description: config.Description,
   147  		ProjectURL:  config.Home,
   148  	}
   149  
   150  	if len(config.Maintainers) > 0 {
   151  		authors := make([]string, 0, len(config.Maintainers))
   152  		for _, maintainer := range config.Maintainers {
   153  			authors = append(authors, maintainer.Name)
   154  		}
   155  		metadata.Authors = authors
   156  	}
   157  
   158  	if len(config.Sources) > 0 && validation.IsValidURL(config.Sources[0]) {
   159  		metadata.RepositoryURL = config.Sources[0]
   160  	}
   161  	if !validation.IsValidURL(metadata.ProjectURL) {
   162  		metadata.ProjectURL = ""
   163  	}
   164  
   165  	return metadata, nil
   166  }