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  }