get.porter.sh/porter@v1.3.0/pkg/porter/generateManifest.go (about) 1 package porter 2 3 import ( 4 "context" 5 "fmt" 6 "strconv" 7 "strings" 8 9 cnabtooci "get.porter.sh/porter/pkg/cnab/cnab-to-oci" 10 "get.porter.sh/porter/pkg/experimental" 11 12 "get.porter.sh/porter/pkg" 13 "get.porter.sh/porter/pkg/build" 14 "get.porter.sh/porter/pkg/cnab" 15 "get.porter.sh/porter/pkg/manifest" 16 "get.porter.sh/porter/pkg/tracing" 17 "get.porter.sh/porter/pkg/yaml" 18 "github.com/distribution/reference" 19 "github.com/mikefarah/yq/v3/pkg/yqlib" 20 "github.com/opencontainers/go-digest" 21 "go.opentelemetry.io/otel/attribute" 22 ) 23 24 // MetadataOpts contain manifest fields eligible for dynamic 25 // updating prior to saving Porter's internal version of the manifest 26 type MetadataOpts struct { 27 Name string 28 Version string 29 } 30 31 // generateInternalManifest decodes the manifest designated by filepath and applies 32 // the provided generateInternalManifestOpts, saving the updated manifest to the path 33 // designated by build.LOCAL_MANIFEST 34 // if a referenced image does not have digest specified, update the manifest to use digest instead. 35 func (p *Porter) generateInternalManifest(ctx context.Context, opts BuildOptions) error { 36 ctx, span := tracing.StartSpan(ctx) 37 defer span.EndSpan() 38 39 // Create the local app dir if it does not already exist 40 err := p.FileSystem.MkdirAll(build.LOCAL_APP, pkg.FileModeDirectory) 41 if err != nil { 42 return span.Error(fmt.Errorf("unable to create directory %s: %w", build.LOCAL_APP, err)) 43 } 44 45 e := yaml.NewEditor(p.FileSystem) 46 err = e.ReadFile(opts.File) 47 if err != nil { 48 return span.Error(fmt.Errorf("unable to read manifest file %s: %w", opts.File, err)) 49 } 50 51 if opts.Name != "" { 52 if err = e.SetValue("name", opts.Name); err != nil { 53 return err 54 } 55 } 56 57 if opts.Version != "" { 58 if err = e.SetValue("version", opts.Version); err != nil { 59 return err 60 } 61 } 62 63 for k, v := range opts.parsedCustoms { 64 if err = e.SetValue("custom."+k, v); err != nil { 65 return err 66 } 67 } 68 69 regOpts := cnabtooci.RegistryOptions{ 70 InsecureRegistry: opts.InsecureRegistry, 71 } 72 73 // find all referenced images that does not have digest specified 74 // get the image digest for all of them and update the manifest with the digest 75 err = e.WalkNodes(ctx, "images.*", func(ctx context.Context, nc *yqlib.NodeContext) error { 76 ctx, span := tracing.StartSpanWithName(ctx, "updateReferencedImageTagToDigest") 77 defer span.EndSpan() 78 79 img := &manifest.MappedImage{} 80 if err := nc.Node.Decode(img); err != nil { 81 return span.Errorf("failed to deserialize referenced image in manifest: %w", err) 82 } 83 84 span.SetAttributes(attribute.String("image", img.Repository)) 85 86 // if image digest is specified in the manifest, we don't need to get it 87 // from registries 88 if img.Digest != "" { 89 return nil 90 } 91 92 ref, err := img.ToOCIReference() 93 if err != nil { 94 return span.Errorf("failed to parse image %s reference: %w", img.Repository, err) 95 } 96 if opts.PreserveTags { 97 if img.Tag == "" { 98 var path string 99 for _, p := range nc.PathStack { 100 switch t := p.(type) { 101 case string: 102 path += fmt.Sprintf("%s.", t) 103 case int: 104 path = strings.TrimSuffix(path, ".") 105 path += fmt.Sprintf("[%s].", strconv.Itoa(t)) 106 default: 107 continue 108 } 109 } 110 111 return e.SetValue(path+"tag", "latest") 112 } 113 } else { 114 115 digest, err := p.getImageDigest(ctx, ref, regOpts) 116 if err != nil { 117 return span.Error(err) 118 } 119 span.SetAttributes(attribute.String("digest", digest.Encoded())) 120 121 var path string 122 for _, p := range nc.PathStack { 123 switch t := p.(type) { 124 case string: 125 path += fmt.Sprintf("%s.", t) 126 case int: 127 path = strings.TrimSuffix(path, ".") 128 path += fmt.Sprintf("[%s].", strconv.Itoa(t)) 129 default: 130 continue 131 } 132 } 133 134 return e.SetValue(path+"digest", digest.String()) 135 } 136 137 return nil 138 }) 139 if err != nil { 140 return err 141 } 142 143 if p.IsFeatureEnabled(experimental.FlagDependenciesV2) { 144 if err = p.resolveDependencyDigest(ctx, e, regOpts); err != nil { 145 return err 146 } 147 } 148 149 return e.WriteFile(build.LOCAL_MANIFEST) 150 } 151 152 func (p *Porter) resolveDependencyDigest(ctx context.Context, e *yaml.Editor, opts cnabtooci.RegistryOptions) error { 153 // find all referenced dependencies that does not have digest specified 154 // get the digest for all of them and update the manifest with the digest 155 return e.WalkNodes(ctx, "dependencies.requires.*", func(ctx context.Context, nc *yqlib.NodeContext) error { 156 ctx, span := tracing.StartSpanWithName(ctx, "updateDependencyTagToDigest") 157 defer span.EndSpan() 158 159 dep := &manifest.Dependency{} 160 if err := nc.Node.Decode(dep); err != nil { 161 return span.Errorf("failed to deserialize dependency in manifest: %w", err) 162 } 163 164 span.SetAttributes(attribute.String("dependency", dep.Name)) 165 166 bundleOpts := BundleReferenceOptions{ 167 BundlePullOptions: BundlePullOptions{ 168 Reference: dep.Bundle.Reference, 169 InsecureRegistry: opts.InsecureRegistry, 170 }, 171 } 172 173 ref, err := cnab.ParseOCIReference(dep.Bundle.Reference) 174 if err != nil { 175 return span.Errorf("failed to parse OCI reference for dependency %s: %w", dep.Name, err) 176 } 177 178 if ref.Tag() == "" || ref.Tag() == "latest" { 179 return nil 180 } 181 182 bundleRef, err := p.resolveBundleReference(ctx, &bundleOpts) 183 if err != nil { 184 return span.Errorf("failed to resolve dependency %s: %w", dep.Name, err) 185 } 186 187 digest := bundleRef.Digest 188 span.SetAttributes(attribute.String("digest", digest.Encoded())) 189 190 var path string 191 for _, p := range nc.PathStack { 192 switch t := p.(type) { 193 case string: 194 path += fmt.Sprintf("%s.", t) 195 case int: 196 path = strings.TrimSuffix(path, ".") 197 path += fmt.Sprintf("[%s].", strconv.Itoa(t)) 198 default: 199 continue 200 } 201 } 202 203 newRef := cnab.OCIReference{ 204 Named: reference.TrimNamed(bundleRef.Reference.Named), 205 } 206 refWithDigest, err := newRef.WithDigest(digest) 207 if err != nil { 208 return span.Errorf("failed to set digest: %w", err) 209 } 210 211 return e.SetValue(path+"bundle.reference", refWithDigest.String()) 212 }) 213 } 214 215 // getImageDigest retrieves the repository digest associated with the specified image reference. 216 func (p *Porter) getImageDigest(ctx context.Context, img cnab.OCIReference, regOpts cnabtooci.RegistryOptions) (digest.Digest, error) { 217 ctx, span := tracing.StartSpan(ctx, attribute.String("image", img.String())) 218 defer span.EndSpan() 219 220 // if no image tag is specified, default to use latest 221 if img.Tag() == "" { 222 refWithTag, err := img.WithTag("latest") 223 if err != nil { 224 return "", span.Errorf("failed to create image reference %s with tag latest: %w", img.String(), err) 225 } 226 img = refWithTag 227 } 228 229 imgSummary, err := p.Registry.GetImageMetadata(ctx, img, regOpts) 230 if err != nil { 231 return "", err 232 } 233 234 imgDigest, err := imgSummary.GetRepositoryDigest() 235 if err != nil { 236 return "", span.Error(err) 237 } 238 239 span.SetAttributes(attribute.String("digest", imgDigest.String())) 240 return imgDigest, nil 241 }