github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/pkg/client/inspect_image.go (about) 1 package client 2 3 import ( 4 "context" 5 "strings" 6 7 "github.com/Masterminds/semver" 8 "github.com/buildpacks/lifecycle/buildpack" 9 "github.com/buildpacks/lifecycle/launch" 10 "github.com/buildpacks/lifecycle/platform" 11 "github.com/buildpacks/lifecycle/platform/files" 12 "github.com/pkg/errors" 13 14 "github.com/buildpacks/pack/pkg/dist" 15 "github.com/buildpacks/pack/pkg/image" 16 ) 17 18 // ImageInfo is a collection of metadata describing 19 // an app image built using Cloud Native Buildpacks. 20 type ImageInfo struct { 21 // Stack Identifier used when building this image 22 StackID string 23 24 // List of buildpacks that passed detection, ran their build 25 // phases and made a contribution to this image. 26 Buildpacks []buildpack.GroupElement 27 28 // List of extensions that passed detection, ran their generate 29 // phases and made a contribution to this image. 30 Extensions []buildpack.GroupElement 31 32 // Base includes two references to the run image, 33 // - the Run Image ID, 34 // - the hash of the last layer in the app image that belongs to the run image. 35 // A way to visualize this is given an image with n layers: 36 // 37 // last layer in run image 38 // v 39 // [1, ..., k, k+1, ..., n] 40 // ^ 41 // first layer added by buildpacks 42 // 43 // the first 1 to k layers all belong to the run image, 44 // the last k+1 to n layers are added by buildpacks. 45 // the sum of all of these is our app image. 46 Base files.RunImageForRebase 47 48 // BOM or Bill of materials, contains dependency and 49 // version information provided by each buildpack. 50 BOM []buildpack.BOMEntry 51 52 // Stack includes the run image name, and a list of image mirrors, 53 // where the run image is hosted. 54 Stack files.Stack 55 56 // Processes lists all processes contributed by buildpacks. 57 Processes ProcessDetails 58 59 // If the image can be rebased 60 Rebasable bool 61 } 62 63 // ProcessDetails is a collection of all start command metadata 64 // on an image. 65 type ProcessDetails struct { 66 // An Images default start command. 67 DefaultProcess *launch.Process 68 69 // List of all start commands contributed by buildpacks. 70 OtherProcesses []launch.Process 71 } 72 73 // Deserialize just the subset of fields we need to avoid breaking changes 74 type layersMetadata struct { 75 RunImage files.RunImageForRebase `json:"runImage" toml:"run-image"` 76 Stack files.Stack `json:"stack" toml:"stack"` 77 } 78 79 const ( 80 platformAPIEnv = "CNB_PLATFORM_API" 81 cnbProcessEnv = "CNB_PROCESS_TYPE" 82 launcherEntrypoint = "/cnb/lifecycle/launcher" 83 windowsLauncherEntrypoint = `c:\cnb\lifecycle\launcher.exe` 84 entrypointPrefix = "/cnb/process/" 85 windowsEntrypointPrefix = `c:\cnb\process\` 86 defaultProcess = "web" 87 fallbackPlatformAPI = "0.3" 88 windowsPrefix = "c:" 89 ) 90 91 // InspectImage reads the Label metadata of an image. It initializes a ImageInfo object 92 // using this metadata, and returns it. 93 // If daemon is true, first the local registry will be searched for the image. 94 // Otherwise it assumes the image is remote. 95 func (c *Client) InspectImage(name string, daemon bool) (*ImageInfo, error) { 96 img, err := c.imageFetcher.Fetch(context.Background(), name, image.FetchOptions{Daemon: daemon, PullPolicy: image.PullNever}) 97 if err != nil { 98 if errors.Cause(err) == image.ErrNotFound { 99 return nil, nil 100 } 101 return nil, err 102 } 103 104 var layersMd layersMetadata 105 if _, err := dist.GetLabel(img, platform.LifecycleMetadataLabel, &layersMd); err != nil { 106 return nil, err 107 } 108 109 var buildMD files.BuildMetadata 110 if _, err := dist.GetLabel(img, platform.BuildMetadataLabel, &buildMD); err != nil { 111 return nil, err 112 } 113 114 minimumBaseImageReferenceVersion := semver.MustParse("0.5.0") 115 actualLauncherVersion, err := semver.NewVersion(buildMD.Launcher.Version) 116 117 if err == nil && actualLauncherVersion.LessThan(minimumBaseImageReferenceVersion) { 118 layersMd.RunImage.Reference = "" 119 } 120 121 stackID, err := img.Label(platform.StackIDLabel) 122 if err != nil { 123 return nil, err 124 } 125 126 rebasable, err := getRebasableLabel(img) 127 if err != nil { 128 return nil, err 129 } 130 131 platformAPI, err := img.Env(platformAPIEnv) 132 if err != nil { 133 return nil, errors.Wrap(err, "reading platform api") 134 } 135 136 if platformAPI == "" { 137 platformAPI = fallbackPlatformAPI 138 } 139 140 platformAPIVersion, err := semver.NewVersion(platformAPI) 141 if err != nil { 142 return nil, errors.Wrap(err, "parsing platform api version") 143 } 144 145 var defaultProcessType string 146 if platformAPIVersion.LessThan(semver.MustParse("0.4")) { 147 defaultProcessType, err = img.Env(cnbProcessEnv) 148 if err != nil || defaultProcessType == "" { 149 defaultProcessType = defaultProcess 150 } 151 } else { 152 entrypoint, err := img.Entrypoint() 153 if err != nil { 154 return nil, errors.Wrap(err, "reading entrypoint") 155 } 156 157 if len(entrypoint) > 0 && entrypoint[0] != launcherEntrypoint && entrypoint[0] != windowsLauncherEntrypoint { 158 process := entrypoint[0] 159 if strings.HasPrefix(process, windowsPrefix) { 160 process = strings.TrimPrefix(process, windowsEntrypointPrefix) 161 process = strings.TrimSuffix(process, ".exe") // Trim .exe for Windows support 162 } else { 163 process = strings.TrimPrefix(process, entrypointPrefix) 164 } 165 166 defaultProcessType = process 167 } 168 } 169 170 workingDir, err := img.WorkingDir() 171 if err != nil { 172 return nil, errors.Wrap(err, "reading WorkingDir") 173 } 174 175 var processDetails ProcessDetails 176 for _, proc := range buildMD.Processes { 177 proc := proc 178 if proc.WorkingDirectory == "" { 179 proc.WorkingDirectory = workingDir 180 } 181 if proc.Type == defaultProcessType { 182 processDetails.DefaultProcess = &proc 183 continue 184 } 185 processDetails.OtherProcesses = append(processDetails.OtherProcesses, proc) 186 } 187 188 var stackCompat files.Stack 189 if layersMd.RunImage.Image != "" { 190 stackCompat = layersMd.RunImage.ToStack() 191 } else { 192 stackCompat = layersMd.Stack 193 } 194 195 if buildMD.Extensions != nil { 196 return &ImageInfo{ 197 StackID: stackID, 198 Stack: stackCompat, 199 Base: layersMd.RunImage, 200 BOM: buildMD.BOM, 201 Buildpacks: buildMD.Buildpacks, 202 Extensions: buildMD.Extensions, 203 Processes: processDetails, 204 Rebasable: rebasable, 205 }, nil 206 } 207 208 return &ImageInfo{ 209 StackID: stackID, 210 Stack: stackCompat, 211 Base: layersMd.RunImage, 212 BOM: buildMD.BOM, 213 Buildpacks: buildMD.Buildpacks, 214 Processes: processDetails, 215 Rebasable: rebasable, 216 }, nil 217 } 218 219 func getRebasableLabel(labeled dist.Labeled) (bool, error) { 220 var rebasableOutput bool 221 isPresent, err := dist.GetLabel(labeled, platform.RebasableLabel, &rebasableOutput) 222 if err != nil { 223 return false, err 224 } 225 226 if !isPresent { 227 rebasableOutput = true 228 } 229 230 return rebasableOutput, nil 231 }