get.porter.sh/porter@v1.3.0/pkg/porter/plugins.go (about) 1 package porter 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "strings" 8 9 "get.porter.sh/porter/pkg/encoding" 10 "get.porter.sh/porter/pkg/pkgmgmt" 11 "get.porter.sh/porter/pkg/plugins" 12 "get.porter.sh/porter/pkg/printer" 13 "get.porter.sh/porter/pkg/tracing" 14 "github.com/olekukonko/tablewriter" 15 "go.opentelemetry.io/otel/attribute" 16 "go.uber.org/zap/zapcore" 17 ) 18 19 // PrintPluginsOptions represent options for the PrintPlugins function 20 type PrintPluginsOptions struct { 21 printer.PrintOptions 22 } 23 24 // ShowPluginOptions represent options for showing a particular plugin. 25 type ShowPluginOptions struct { 26 printer.PrintOptions 27 Name string 28 } 29 30 func (o *ShowPluginOptions) Validate(args []string) error { 31 err := o.validateName(args) 32 if err != nil { 33 return err 34 } 35 36 return o.ParseFormat() 37 } 38 39 // validateName grabs the name from the first positional argument. 40 func (o *ShowPluginOptions) validateName(args []string) error { 41 switch len(args) { 42 case 0: 43 return fmt.Errorf("no name was specified") 44 case 1: 45 o.Name = strings.ToLower(args[0]) 46 return nil 47 default: 48 return fmt.Errorf("only one positional argument may be specified, the name, but multiple were received: %s", args) 49 50 } 51 } 52 53 func (p *Porter) PrintPlugins(ctx context.Context, opts PrintPluginsOptions) error { 54 installedPlugins, err := p.ListPlugins(ctx) 55 if err != nil { 56 return err 57 } 58 59 switch opts.Format { 60 case printer.FormatPlaintext: 61 printRow := 62 func(v interface{}) []string { 63 m, ok := v.(plugins.Metadata) 64 if !ok { 65 return nil 66 } 67 return []string{m.Name, m.VersionInfo.Version, m.VersionInfo.Author} 68 } 69 return printer.PrintTable(p.Out, installedPlugins, printRow, "Name", "Version", "Author") 70 case printer.FormatJson: 71 return printer.PrintJson(p.Out, installedPlugins) 72 case printer.FormatYaml: 73 return printer.PrintYaml(p.Out, installedPlugins) 74 default: 75 return fmt.Errorf("invalid format: %s", opts.Format) 76 } 77 } 78 79 func (p *Porter) ListPlugins(ctx context.Context) ([]plugins.Metadata, error) { 80 // List out what is installed on the file system 81 names, err := p.Plugins.List() 82 if err != nil { 83 return nil, err 84 } 85 86 // Query each plugin and fill out their metadata, handle the 87 // cast from the PackageMetadata interface to the concrete type 88 installedPlugins := make([]plugins.Metadata, len(names)) 89 for i, name := range names { 90 plugin, err := p.Plugins.GetMetadata(ctx, name) 91 if err != nil { 92 fmt.Fprintf(os.Stderr, "could not get version from plugin %s: %s\n ", name, err.Error()) 93 continue 94 } 95 96 meta, _ := plugin.(*plugins.Metadata) 97 installedPlugins[i] = *meta 98 } 99 100 return installedPlugins, nil 101 } 102 103 func (p *Porter) ShowPlugin(ctx context.Context, opts ShowPluginOptions) error { 104 plugin, err := p.GetPlugin(ctx, opts.Name) 105 if err != nil { 106 return err 107 } 108 109 switch opts.Format { 110 case printer.FormatPlaintext: 111 // Build and configure our tablewriter 112 // TODO: make this a function and reuse it in printer/table.go 113 table := tablewriter.NewWriter(p.Out) 114 table.SetCenterSeparator("") 115 table.SetColumnSeparator("") 116 table.SetAlignment(tablewriter.ALIGN_LEFT) 117 table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) 118 table.SetBorders(tablewriter.Border{Left: false, Right: false, Bottom: false, Top: true}) 119 table.SetAutoFormatHeaders(false) 120 121 // First, print the plugin metadata 122 fmt.Fprintf(p.Out, "Name: %s\n", plugin.Name) 123 fmt.Fprintf(p.Out, "Version: %s\n", plugin.Version) 124 fmt.Fprintf(p.Out, "Commit: %s\n", plugin.Commit) 125 fmt.Fprintf(p.Out, "Author: %s\n\n", plugin.Author) 126 127 table.SetHeader([]string{"Type", "Implementation"}) 128 for _, row := range plugin.Implementations { 129 table.Append([]string{row.Type, row.Name}) 130 } 131 table.Render() 132 return nil 133 134 case printer.FormatJson: 135 return printer.PrintJson(p.Out, plugin) 136 case printer.FormatYaml: 137 return printer.PrintYaml(p.Out, plugin) 138 default: 139 return fmt.Errorf("invalid format: %s", opts.Format) 140 } 141 } 142 143 func (p *Porter) GetPlugin(ctx context.Context, name string) (*plugins.Metadata, error) { 144 meta, err := p.Plugins.GetMetadata(ctx, name) 145 if err != nil { 146 return nil, err 147 } 148 149 plugin, ok := meta.(*plugins.Metadata) 150 if !ok { 151 return nil, fmt.Errorf("could not cast plugin %s to plugins.Metadata", name) 152 } 153 154 return plugin, nil 155 } 156 157 func (p *Porter) InstallPlugin(ctx context.Context, opts plugins.InstallOptions) error { 158 ctx, log := tracing.StartSpan(ctx) 159 defer log.EndSpan() 160 161 installOpts, err := p.getPluginInstallOptions(ctx, opts) 162 if err != nil { 163 return err 164 } 165 for _, opt := range installOpts { 166 err := p.Plugins.Install(ctx, opt) 167 if err != nil { 168 return err 169 } 170 171 plugin, err := p.Plugins.GetMetadata(ctx, opt.Name) 172 if err != nil { 173 return fmt.Errorf("failed to get plugin metadata: %w", err) 174 } 175 176 v := plugin.GetVersionInfo() 177 fmt.Fprintf(p.Out, "installed %s plugin %s (%s)\n", opt.Name, v.Version, v.Commit) 178 } 179 180 return nil 181 } 182 183 func (p *Porter) UninstallPlugin(ctx context.Context, opts pkgmgmt.UninstallOptions) error { 184 err := p.Plugins.Uninstall(ctx, opts) 185 if err != nil { 186 return err 187 } 188 189 fmt.Fprintf(p.Out, "Uninstalled %s plugin", opts.Name) 190 191 return nil 192 } 193 194 func (p *Porter) getPluginInstallOptions(ctx context.Context, opts plugins.InstallOptions) ([]pkgmgmt.InstallOptions, error) { 195 _, log := tracing.StartSpan(ctx) 196 defer log.EndSpan() 197 198 var installConfigs []pkgmgmt.InstallOptions 199 if opts.File != "" { 200 var data plugins.InstallPluginsSpec 201 if log.ShouldLog(zapcore.DebugLevel) { 202 // ignoring any error here, printing debug info isn't critical 203 contents, _ := p.FileSystem.ReadFile(opts.File) 204 log.Debug("read input file", attribute.String("contents", string(contents))) 205 } 206 207 if err := encoding.UnmarshalFile(p.FileSystem, opts.File, &data); err != nil { 208 return nil, fmt.Errorf("unable to parse %s as an installation document: %w", opts.File, err) 209 } 210 211 if err := data.Validate(); err != nil { 212 return nil, err 213 } 214 215 sortedCfgs := plugins.NewInstallPluginConfigs(data.Plugins) 216 217 for _, config := range sortedCfgs.Values() { 218 // if user specified a feed url or mirror using the flags, it will become 219 // the default value and apply to empty values parsed from the provided file 220 if config.FeedURL == "" { 221 config.FeedURL = opts.FeedURL 222 } 223 if config.Mirror == "" { 224 config.Mirror = opts.Mirror 225 } 226 227 if err := config.Validate([]string{config.Name}); err != nil { 228 return nil, err 229 } 230 installConfigs = append(installConfigs, config) 231 232 } 233 234 return installConfigs, nil 235 } 236 237 installConfigs = append(installConfigs, opts.InstallOptions) 238 return installConfigs, nil 239 }