get.porter.sh/porter@v1.3.0/pkg/porter/apply.go (about) 1 package porter 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 8 "get.porter.sh/porter/pkg/encoding" 9 "get.porter.sh/porter/pkg/portercontext" 10 "get.porter.sh/porter/pkg/printer" 11 "get.porter.sh/porter/pkg/storage" 12 "get.porter.sh/porter/pkg/tracing" 13 "go.opentelemetry.io/otel/attribute" 14 "go.uber.org/zap/zapcore" 15 ) 16 17 type ApplyOptions struct { 18 Namespace string 19 File string 20 21 // Force the installation to be re-applied regardless of anything being changed or not 22 Force bool 23 24 // DryRun only checks if the changes would trigger a bundle run 25 DryRun bool 26 } 27 28 const ApplyDefaultFormat = printer.FormatPlaintext 29 30 var ApplyAllowedFormats = printer.Formats{printer.FormatPlaintext, printer.FormatYaml, printer.FormatJson} 31 32 func (o *ApplyOptions) Validate(cxt *portercontext.Context, args []string) error { 33 switch len(args) { 34 case 0: 35 return errors.New("a file argument is required") 36 case 1: 37 o.File = args[0] 38 default: 39 return errors.New("only one file argument may be specified") 40 } 41 42 info, err := cxt.FileSystem.Stat(o.File) 43 if err != nil { 44 return fmt.Errorf("invalid file argument %s: %w", o.File, err) 45 } 46 if info.IsDir() { 47 return fmt.Errorf("invalid file argument %s, must be a file not a directory", o.File) 48 } 49 50 return nil 51 } 52 53 func (p *Porter) InstallationApply(ctx context.Context, opts ApplyOptions) error { 54 ctx, log := tracing.StartSpan(ctx) 55 defer log.EndSpan() 56 57 log.Debugf("Reading input file %s", opts.File) 58 59 namespace, err := p.getNamespaceFromFile(opts) 60 if err != nil { 61 return log.Error(err) 62 } 63 64 if log.ShouldLog(zapcore.DebugLevel) { 65 // ignoring any error here, printing debug info isn't critical 66 contents, _ := p.FileSystem.ReadFile(opts.File) 67 log.Debug("read input file", attribute.String("contents", string(contents))) 68 } 69 70 var input DisplayInstallation 71 if err := encoding.UnmarshalFile(p.FileSystem, opts.File, &input); err != nil { 72 return log.Errorf("unable to parse %s as an installation document: %w", opts.File, err) 73 } 74 input.Namespace = namespace 75 inputInstallation, err := input.ConvertToInstallation() 76 if err != nil { 77 return log.Error(err) 78 } 79 80 installation, err := p.Installations.GetInstallation(ctx, inputInstallation.Namespace, inputInstallation.Name) 81 if err != nil { 82 if !errors.Is(err, storage.ErrNotFound{}) { 83 return log.Errorf("could not query for an existing installation document for %s: %w", inputInstallation, err) 84 } 85 86 // Create a new installation 87 installation = storage.NewInstallation(input.Namespace, input.Name) 88 log.Info("Creating a new installation", attribute.String("installation", installation.String())) 89 } else { 90 log.Infof("Updating %s installation\n", installation) 91 } 92 93 installation.Apply(inputInstallation.InstallationSpec) 94 checkStrategy := p.GetSchemaCheckStrategy(ctx) 95 if err := installation.Validate(ctx, checkStrategy); err != nil { 96 return log.Errorf("invalid installation: %w", err) 97 } 98 99 reconcileOpts := ReconcileOptions{ 100 Namespace: input.Namespace, 101 Name: input.Name, 102 Installation: installation, 103 Force: opts.Force, 104 DryRun: opts.DryRun, 105 } 106 return p.ReconcileInstallation(ctx, reconcileOpts) 107 }