code.gitea.io/gitea@v1.19.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  	MultiArch        map[string]string `json:"multiarch,omitempty"`
    66  }
    67  
    68  // ParseImageConfig parses the metadata of an image config
    69  func ParseImageConfig(mt string, r io.Reader) (*Metadata, error) {
    70  	if strings.EqualFold(mt, helm.ConfigMediaType) {
    71  		return parseHelmConfig(r)
    72  	}
    73  
    74  	// fallback to OCI Image Config
    75  	return parseOCIImageConfig(r)
    76  }
    77  
    78  func parseOCIImageConfig(r io.Reader) (*Metadata, error) {
    79  	var image oci.Image
    80  	if err := json.NewDecoder(r).Decode(&image); err != nil {
    81  		return nil, err
    82  	}
    83  
    84  	platform := DefaultPlatform
    85  	if image.OS != "" && image.Architecture != "" {
    86  		platform = fmt.Sprintf("%s/%s", image.OS, image.Architecture)
    87  		if image.Variant != "" {
    88  			platform = fmt.Sprintf("%s/%s", platform, image.Variant)
    89  		}
    90  	}
    91  
    92  	imageLayers := make([]string, 0, len(image.History))
    93  	for _, history := range image.History {
    94  		cmd := history.CreatedBy
    95  		if i := strings.Index(cmd, "#(nop) "); i != -1 {
    96  			cmd = strings.TrimSpace(cmd[i+7:])
    97  		}
    98  		if cmd != "" {
    99  			imageLayers = append(imageLayers, cmd)
   100  		}
   101  	}
   102  
   103  	metadata := &Metadata{
   104  		Type:             TypeOCI,
   105  		Platform:         platform,
   106  		Licenses:         image.Config.Labels[labelLicenses],
   107  		ProjectURL:       image.Config.Labels[labelURL],
   108  		RepositoryURL:    image.Config.Labels[labelSource],
   109  		DocumentationURL: image.Config.Labels[labelDocumentation],
   110  		Description:      image.Config.Labels[labelDescription],
   111  		Labels:           image.Config.Labels,
   112  		ImageLayers:      imageLayers,
   113  	}
   114  
   115  	if authors, ok := image.Config.Labels[labelAuthors]; ok {
   116  		metadata.Authors = []string{authors}
   117  	}
   118  
   119  	if !validation.IsValidURL(metadata.ProjectURL) {
   120  		metadata.ProjectURL = ""
   121  	}
   122  	if !validation.IsValidURL(metadata.RepositoryURL) {
   123  		metadata.RepositoryURL = ""
   124  	}
   125  	if !validation.IsValidURL(metadata.DocumentationURL) {
   126  		metadata.DocumentationURL = ""
   127  	}
   128  
   129  	return metadata, nil
   130  }
   131  
   132  func parseHelmConfig(r io.Reader) (*Metadata, error) {
   133  	var config helm.Metadata
   134  	if err := json.NewDecoder(r).Decode(&config); err != nil {
   135  		return nil, err
   136  	}
   137  
   138  	metadata := &Metadata{
   139  		Type:        TypeHelm,
   140  		Description: config.Description,
   141  		ProjectURL:  config.Home,
   142  	}
   143  
   144  	if len(config.Maintainers) > 0 {
   145  		authors := make([]string, 0, len(config.Maintainers))
   146  		for _, maintainer := range config.Maintainers {
   147  			authors = append(authors, maintainer.Name)
   148  		}
   149  		metadata.Authors = authors
   150  	}
   151  
   152  	if len(config.Sources) > 0 && validation.IsValidURL(config.Sources[0]) {
   153  		metadata.RepositoryURL = config.Sources[0]
   154  	}
   155  	if !validation.IsValidURL(metadata.ProjectURL) {
   156  		metadata.ProjectURL = ""
   157  	}
   158  
   159  	return metadata, nil
   160  }