github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/packages/container/metadata.go (about)

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