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  }