get.porter.sh/porter@v1.3.0/pkg/plugins/pluggable/loader.go (about) 1 package pluggable 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "io" 9 "time" 10 11 "get.porter.sh/porter/pkg/config" 12 "get.porter.sh/porter/pkg/plugins" 13 "get.porter.sh/porter/pkg/tracing" 14 "go.opentelemetry.io/otel/attribute" 15 ) 16 17 const ( 18 // PluginStartTimeoutDefault is the default amount of time to wait for a plugin 19 // to start. Override with PluginStartTimeoutEnvVar. 20 PluginStartTimeoutDefault = 5 * time.Second 21 22 // PluginStopTimeoutDefault is the default amount of time to wait for a plugin 23 // to stop (kill). Override with PluginStopTimeoutEnvVar. 24 PluginStopTimeoutDefault = 5 * time.Second 25 26 // PluginStartTimeoutEnvVar is the environment variable used to override 27 // PluginStartTimeoutDefault. 28 PluginStartTimeoutEnvVar = "PORTER_PLUGIN_START_TIMEOUT" 29 30 // PluginStopTimeoutEnvVar is the environment variable used to override 31 // PluginStopTimeoutDefault. 32 PluginStopTimeoutEnvVar = "PORTER_PLUGIN_STOP_TIMEOUT" 33 ) 34 35 // PluginLoader handles finding, configuring and loading porter plugins. 36 type PluginLoader struct { 37 // config is the Porter configuration 38 config *config.Config 39 40 // selectedPluginKey is the loaded plugin. 41 selectedPluginKey *plugins.PluginKey 42 43 // selectedPluginConfig is the relevant section of the Porter config file containing 44 // the plugin's configuration. 45 selectedPluginConfig interface{} 46 } 47 48 func NewPluginLoader(c *config.Config) *PluginLoader { 49 return &PluginLoader{ 50 config: c, 51 } 52 } 53 54 // Load a plugin, returning the plugin's interface which the caller must then cast to 55 // the typed interface, a cleanup function to stop the plugin when finished communicating with it, 56 // and an error if the plugin could not be loaded. 57 func (l *PluginLoader) Load(ctx context.Context, pluginType PluginTypeConfig) (*PluginConnection, error) { 58 ctx, span := tracing.StartSpan(ctx, 59 attribute.String("plugin-interface", pluginType.Interface), 60 attribute.String("requested-protocol-version", fmt.Sprintf("%v", pluginType.ProtocolVersion))) 61 defer span.EndSpan() 62 63 err := l.selectPlugin(ctx, pluginType) 64 if err != nil { 65 return nil, err 66 } 67 68 // quick check to detect that we are running as porter, and not a plugin already 69 if l.config.IsInternalPlugin { 70 err := fmt.Errorf("the internal plugin %s tried to load the %s plugin. Report this error to https://github.com/getporter/porter", l.config.InternalPluginKey, l.selectedPluginKey) 71 return nil, span.Error(err) 72 } 73 74 span.SetAttributes(attribute.String("plugin-key", l.selectedPluginKey.String())) 75 76 configReader, err := l.readPluginConfig() 77 if err != nil { 78 return nil, span.Error(err) 79 } 80 81 conn := NewPluginConnection(l.config, pluginType, *l.selectedPluginKey) 82 if err = conn.Start(ctx, configReader); err != nil { 83 return nil, err 84 } 85 86 return conn, nil 87 } 88 89 // selectPlugin picks the plugin to use and loads its configuration. 90 func (l *PluginLoader) selectPlugin(ctx context.Context, cfg PluginTypeConfig) error { 91 _, span := tracing.StartSpan(ctx) 92 defer span.EndSpan() 93 94 l.selectedPluginKey = nil 95 l.selectedPluginConfig = nil 96 97 var pluginKey string 98 99 defaultStore := cfg.GetDefaultPluggable(l.config) 100 if defaultStore != "" { 101 span.SetAttributes(attribute.String("default-plugin", defaultStore)) 102 103 is, err := cfg.GetPluggable(l.config, defaultStore) 104 if err != nil { 105 return span.Error(err) 106 } 107 108 pluginKey = is.GetPluginSubKey() 109 l.selectedPluginConfig = is.GetConfig() 110 if l.selectedPluginConfig == nil { 111 span.Debug("No plugin config defined") 112 } 113 } 114 115 // If there isn't a specific plugin configured for this plugin type, fall back to the default plugin for this type 116 if pluginKey == "" { 117 pluginKey = cfg.GetDefaultPlugin(l.config) 118 span.Debug("Selected default plugin", attribute.String("plugin-key", pluginKey)) 119 } else { 120 span.Debug("Selected configured plugin", attribute.String("plugin-key", pluginKey)) 121 } 122 123 key, err := plugins.ParsePluginKey(pluginKey) 124 if err != nil { 125 return span.Error(err) 126 } 127 l.selectedPluginKey = &key 128 l.selectedPluginKey.Interface = cfg.Interface 129 130 return nil 131 } 132 133 func (l *PluginLoader) readPluginConfig() (io.Reader, error) { 134 if l.selectedPluginConfig == nil { 135 return &bytes.Buffer{}, nil 136 } 137 138 b, err := json.Marshal(l.selectedPluginConfig) 139 if err != nil { 140 return nil, fmt.Errorf("could not marshal plugin config for %s: %w", l.selectedPluginKey, err) 141 } 142 143 return bytes.NewBuffer(b), nil 144 }