get.porter.sh/porter@v1.3.0/pkg/porter/stamp.go (about) 1 package porter 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "os" 9 "strings" 10 11 "get.porter.sh/porter/pkg/build" 12 "get.porter.sh/porter/pkg/cnab" 13 cnabtooci "get.porter.sh/porter/pkg/cnab/cnab-to-oci" 14 configadapter "get.porter.sh/porter/pkg/cnab/config-adapter" 15 "get.porter.sh/porter/pkg/manifest" 16 "get.porter.sh/porter/pkg/tracing" 17 "go.opentelemetry.io/otel/attribute" 18 ) 19 20 // ensureLocalBundleIsUpToDate ensures that the bundle is up-to-date with the porter manifest, 21 // if it is out-of-date, performs a build of the bundle. 22 func (p *Porter) ensureLocalBundleIsUpToDate(ctx context.Context, opts BuildOptions) (cnab.BundleReference, error) { 23 ctx, log := tracing.StartSpan(ctx, 24 attribute.Bool("autobuild-disabled", opts.AutoBuildDisabled)) 25 defer log.EndSpan() 26 27 if opts.File == "" { 28 return cnab.BundleReference{}, nil 29 } 30 31 upToDate, err := p.IsBundleUpToDate(ctx, opts.BundleDefinitionOptions) 32 if err != nil { 33 log.Warnf("WARNING: %w", err) 34 } 35 36 if !upToDate { 37 if opts.AutoBuildDisabled { 38 log.Warn("WARNING: The bundle is out-of-date. Skipping autobuild because --autobuild-disabled was specified") 39 } else { 40 log.Info("Changes have been detected and the previously built bundle is out-of-date, rebuilding the bundle before proceeding...") 41 log.Info("Building bundle ===>") 42 // opts.File is non-empty, which overrides opts.CNABFile if set 43 // (which may be if a cached bundle is fetched e.g. when running an action) 44 opts.CNABFile = "" 45 buildOpts := opts 46 if err = buildOpts.Validate(p); err != nil { 47 return cnab.BundleReference{}, log.Errorf("Validation of build options when autobuilding the bundle failed: %w", err) 48 } 49 err := p.Build(ctx, buildOpts) 50 if err != nil { 51 return cnab.BundleReference{}, err 52 } 53 } 54 } 55 56 bun, err := cnab.LoadBundle(p.Context, build.LOCAL_BUNDLE) 57 if err != nil { 58 if errors.Is(err, os.ErrNotExist) && opts.AutoBuildDisabled { 59 return cnab.BundleReference{}, log.Errorf("Attempted to use a bundle from source without building it first when --autobuild-disabled is set. Build the bundle and try again: %w", err) 60 } 61 return cnab.BundleReference{}, log.Error(err) 62 } 63 64 return cnab.BundleReference{ 65 Definition: bun, 66 }, nil 67 } 68 69 // IsBundleUpToDate checks the hash of the manifest against the hash in cnab/bundle.json. 70 func (p *Porter) IsBundleUpToDate(ctx context.Context, opts BundleDefinitionOptions) (bool, error) { 71 ctx, span := tracing.StartSpan(ctx) 72 defer span.EndSpan() 73 74 span.Debugf("Checking if the bundle is up-to-date...") 75 76 // This is a prefix for any message that explains why the bundle is out-of-date 77 const rebuildMessagePrefix = "Bundle is out-of-date and must be rebuilt" 78 79 if opts.File == "" { 80 span.Debugf("%s because the current bundle was not specified. Please report this as a bug!", rebuildMessagePrefix) 81 return false, span.Errorf("File is required") 82 } 83 m, err := manifest.LoadManifestFrom(ctx, p.Config, opts.File) 84 if err != nil { 85 err = fmt.Errorf("the current bundle could not be read: %w", err) 86 span.Debugf("%s: %w", rebuildMessagePrefix, err) 87 return false, span.Error(err) 88 } 89 90 if exists, _ := p.FileSystem.Exists(opts.CNABFile); exists { 91 bun, err := cnab.LoadBundle(p.Context, opts.CNABFile) 92 if err != nil { 93 err = fmt.Errorf("the previously built bundle at %s could not be read: %w", opts.CNABFile, err) 94 span.Debugf("%s: %w", rebuildMessagePrefix, err) 95 return false, span.Error(err) 96 } 97 98 // Check whether bundle images exist in host registry. 99 for _, invocationImage := range bun.InvocationImages { 100 // if the invocationImage is built before using a random string tag, 101 // we should rebuild it with the new format 102 if strings.HasSuffix(invocationImage.Image, "-installer") { 103 span.Debugf("%s because it uses the old -installer suffixed image name (%s)", invocationImage.Image) 104 return false, nil 105 } 106 107 imgRef, err := cnab.ParseOCIReference(invocationImage.Image) 108 if err != nil { 109 err = fmt.Errorf("error parsing %s as an OCI image reference: %w", invocationImage.Image, err) 110 span.Debugf("%s: %w", rebuildMessagePrefix, err) 111 return false, span.Error(err) 112 } 113 114 _, err = p.Registry.GetCachedImage(ctx, imgRef) 115 if err != nil { 116 if errors.Is(err, cnabtooci.ErrNotFound{}) { 117 span.Debugf("%s because the bundle image %s doesn't exist in the local image cache", rebuildMessagePrefix, invocationImage.Image) 118 return false, nil 119 } 120 err = fmt.Errorf("an error occurred checking the Docker cache for the bundle image: %w", err) 121 span.Debugf("%s: %w", rebuildMessagePrefix, err) 122 return false, span.Error(err) 123 } 124 } 125 126 oldStamp, err := configadapter.LoadStamp(bun) 127 if err != nil { 128 err = fmt.Errorf("could not load stamp from %s: %w", opts.CNABFile, err) 129 span.Debugf("%s: %w", rebuildMessagePrefix) 130 return false, span.Error(err) 131 } 132 133 mixins, err := p.getUsedMixins(ctx, m) 134 if err != nil { 135 err = fmt.Errorf("an error occurred while listing used mixins: %w", err) 136 span.Debugf("%s: %w", rebuildMessagePrefix, err) 137 return false, span.Error(err) 138 } 139 140 converter := configadapter.NewManifestConverter(p.Config, m, nil, mixins, opts.PreserveTags) 141 newDigest, err := converter.DigestManifest() 142 if err != nil { 143 err = fmt.Errorf("the current manifest digest cannot be calculated: %w", err) 144 span.Debugf("%s: %w", rebuildMessagePrefix, err) 145 return false, span.Error(err) 146 } 147 148 preserveTagsChanged := oldStamp.PreserveTags != opts.PreserveTags 149 digestChanged := oldStamp.ManifestDigest != newDigest 150 manifestChanged := digestChanged || preserveTagsChanged 151 if manifestChanged { 152 if preserveTagsChanged { 153 span.Debugf("PreserveTags is set to %t in the stamp, but the build is being run with PreserveTags set to %t", oldStamp.PreserveTags, opts.PreserveTags) 154 } 155 if digestChanged { 156 span.Debugf("%s because the cached bundle is stale", rebuildMessagePrefix) 157 } 158 if span.IsTracingEnabled() { 159 previousStampB, _ := json.Marshal(oldStamp) 160 currentStamp, _ := converter.GenerateStamp(ctx, opts.PreserveTags) 161 currentStampB, _ := json.Marshal(currentStamp) 162 span.SetAttributes( 163 attribute.String("previous-stamp", string(previousStampB)), 164 attribute.String("current-stamp", string(currentStampB)), 165 ) 166 } 167 return false, nil 168 } 169 170 span.Debugf("Bundle is up-to-date!") 171 return true, nil 172 } 173 174 span.Debugf("%s because a previously built bundle was not found", rebuildMessagePrefix) 175 return false, nil 176 }