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 }