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  }