get.porter.sh/porter@v1.3.0/pkg/porter/outputs.go (about) 1 package porter 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "sort" 8 9 "get.porter.sh/porter/pkg/cnab" 10 "get.porter.sh/porter/pkg/portercontext" 11 "get.porter.sh/porter/pkg/printer" 12 "get.porter.sh/porter/pkg/storage" 13 ) 14 15 // OutputShowOptions represent options for a bundle output show command 16 type OutputShowOptions struct { 17 installationOptions 18 Output string 19 } 20 21 // OutputListOptions represent options for a bundle output list command 22 type OutputListOptions struct { 23 installationOptions 24 printer.PrintOptions 25 } 26 27 // Validate validates the provided args, using the provided context, 28 // setting attributes of OutputShowOptions as applicable 29 func (o *OutputShowOptions) Validate(args []string, cxt *portercontext.Context) error { 30 switch len(args) { 31 case 0: 32 return errors.New("an output name must be provided") 33 case 1: 34 o.Output = args[0] 35 default: 36 return fmt.Errorf("only one positional argument may be specified, the output name, but multiple were received: %s", args) 37 } 38 39 // If not provided, attempt to derive installation name from context 40 if o.installationOptions.Name == "" { 41 err := o.installationOptions.defaultBundleFiles(cxt) 42 if err != nil { 43 return errors.New("installation name must be provided via [--installation|-i INSTALLATION]") 44 } 45 } 46 47 return nil 48 } 49 50 // Validate validates the provided args, using the provided context, 51 // setting attributes of OutputListOptions as applicable 52 func (o *OutputListOptions) Validate(args []string, cxt *portercontext.Context) error { 53 // Ensure only one argument exists (installation name) if args length non-zero 54 err := o.installationOptions.validateInstallationName(args) 55 if err != nil { 56 return err 57 } 58 59 // Attempt to derive installation name from context 60 err = o.installationOptions.defaultBundleFiles(cxt) 61 if err != nil { 62 return fmt.Errorf("installation name must be provided: %w", err) 63 } 64 65 return o.ParseFormat() 66 } 67 68 // ShowBundleOutput shows a bundle output value, according to the provided options 69 func (p *Porter) ShowBundleOutput(ctx context.Context, opts *OutputShowOptions) error { 70 err := p.applyDefaultOptions(ctx, &opts.installationOptions) 71 if err != nil { 72 return err 73 } 74 75 output, err := p.ReadBundleOutput(ctx, opts.Output, opts.Name, opts.Namespace) 76 if err != nil { 77 return fmt.Errorf("unable to read output '%s' for installation '%s/%s': %w", opts.Output, opts.Namespace, opts.Name, err) 78 } 79 80 fmt.Fprint(p.Out, output) 81 return nil 82 } 83 84 func NewDisplayValuesFromOutputs(bun cnab.ExtendedBundle, outputs storage.Outputs) DisplayValues { 85 // Iterate through all Bundle Outputs, fetch their metadata 86 // via their corresponding Definitions and add to rows 87 displayOutputs := make(DisplayValues, 0, outputs.Len()) 88 for i := 0; i < outputs.Len(); i++ { 89 output, _ := outputs.GetByIndex(i) 90 91 if bun.IsInternalOutput(output.Name) { 92 continue 93 } 94 95 do := &DisplayValue{Name: output.Name} 96 do.SetValue(output.Value) 97 schema, ok := output.GetSchema(bun) 98 if ok { 99 do.Type = bun.GetParameterType(&schema) 100 if schema.WriteOnly != nil && *schema.WriteOnly { 101 do.Sensitive = true 102 } 103 } else { 104 // Skip outputs not defined in the bundle, e.g. io.cnab.outputs.invocationImageLogs 105 continue 106 } 107 108 displayOutputs = append(displayOutputs, *do) 109 } 110 111 sort.Sort(displayOutputs) 112 return displayOutputs 113 } 114 115 // ListBundleOutputs lists the outputs for a given bundle according to the 116 // provided display format 117 func (p *Porter) ListBundleOutputs(ctx context.Context, opts *OutputListOptions) (DisplayValues, error) { 118 err := p.applyDefaultOptions(ctx, &opts.installationOptions) 119 if err != nil { 120 return nil, err 121 } 122 123 outputs, err := p.Installations.GetLastOutputs(ctx, opts.Namespace, opts.Name) 124 if err != nil { 125 return nil, err 126 } 127 128 resolved, err := p.Sanitizer.RestoreOutputs(ctx, outputs) 129 if err != nil { 130 return nil, err 131 } 132 133 c, err := p.Installations.GetLastRun(ctx, opts.Namespace, opts.Name) 134 if err != nil { 135 return nil, err 136 } 137 138 bun := cnab.NewBundle(c.Bundle) 139 140 displayOutputs := NewDisplayValuesFromOutputs(bun, resolved) 141 if err != nil { 142 return nil, err 143 } 144 145 return displayOutputs, nil 146 } 147 148 func (p *Porter) PrintBundleOutputs(ctx context.Context, opts OutputListOptions) error { 149 outputs, err := p.ListBundleOutputs(ctx, &opts) 150 if err != nil { 151 return err 152 } 153 154 switch opts.Format { 155 case printer.FormatJson: 156 return printer.PrintJson(p.Out, outputs) 157 case printer.FormatYaml: 158 return printer.PrintYaml(p.Out, outputs) 159 case printer.FormatPlaintext: 160 return p.printDisplayValuesTable(outputs) 161 default: 162 return fmt.Errorf("invalid format: %s", opts.Format) 163 } 164 } 165 166 // ReadBundleOutput reads a bundle output from an installation 167 func (p *Porter) ReadBundleOutput(ctx context.Context, outputName, installation, namespace string) (string, error) { 168 o, err := p.Installations.GetLastOutput(ctx, namespace, installation, outputName) 169 if err != nil { 170 return "", err 171 } 172 173 o, err = p.Sanitizer.RestoreOutput(ctx, o) 174 if err != nil { 175 return "", err 176 } 177 178 return fmt.Sprintf("%v", string(o.Value)), nil 179 } 180 181 func truncateString(str string, num int) string { 182 truncated := str 183 if len(str) > num { 184 if num > 3 { 185 num -= 3 186 } 187 truncated = str[0:num] + "..." 188 } 189 return truncated 190 }