Combining two extensions in a single codebase

If your team is using Drone extensions, you may want to combine multiple extensions into a single codebase and microservice. In the below example we create a registry extension (returning registry credentials used when pulling private images) and an environment extension (returning environment variables automatically injected into the pipeline).

package main

import (
	"net/http"

	"github.com/drone/drone-go/plugin/environ"
	"github.com/drone/drone-go/plugin/registry"
	"github.com/foo/bar/plugin"

	_ "github.com/joho/godotenv/autoload"
	"github.com/kelseyhightower/envconfig"
	"github.com/sirupsen/logrus"
)

// spec provides the plugin settings.
type spec struct {
	Bind   string `envconfig:"DRONE_BIND"`
	Debug  bool   `envconfig:"DRONE_DEBUG"`
	Secret string `envconfig:"DRONE_SECRET"`
}

func main() {
	spec := new(spec)
	err := envconfig.Process("", spec)
	if err != nil {
		logrus.Fatal(err)
	}

	if spec.Debug {
		logrus.SetLevel(logrus.DebugLevel)
	}
	if spec.Secret == "" {
		logrus.Fatalln("missing secret key")
	}
	if spec.Bind == "" {
		spec.Bind = ":3000"
	}

	// create a handler for our registry extension.
	h1 := registry.Handler(
		spec.Secret,
		plugin.NewRegistry(),
		logrus.StandardLogger(),
	)

	// create a handler for our environment extension.
	h2 := environ.Handler(
		spec.Secret,
		plugins.NewEnviron()
		logrus.StandardLogger(),
	)

	logrus.Infof("server listening on address %s", spec.Bind)

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		// when the runner makes an API call to the extension
		// it uses the Accept header to limit the request to a
		// specific data type. This can therefore be used to
		// route requests to a single server to different extension
		// handlers.
		switch r.Header.Get("Accept"): {
		case registry.V1:
			h1.ServeHTTP(w, r)
		case environ.V1:
			h2.ServeHTTP(w, r)
		case environ.V2:
			h2.ServeHTTP(w, r)
		}
	})

	logrus.Fatal(http.ListenAndServe(spec.Bind, nil))
}

Here is our registry plugin:

package plugin

import (
	"context"

	"github.com/drone/drone-go/drone"
	"github.com/drone/drone-go/plugin/registry"
)

// New returns a new registry plugin.
func NewRegistry() registry.Plugin {
	return &registryPlugin{}
}

type registryPlugin struct {
}

func (p *registryPlugin) List(ctx context.Context, req *registry.Request) ([]*drone.Registry, error) {

	// TODO replace or remove
	// we can return a list of credentials for
	// multiple registries.
	credentials := []*drone.Registry{
		{
			Address:  "index.docker.io",
			Username: "octocat",
			Password: "correct-horse-battery-staple",
		},
	}

	return credentials, nil
}

and here is our environment plugin:

package plugin

import (
	"context"

	"github.com/drone/drone-go/plugin/environ"
)

// NewEnviron returns a new environ plugin.
func NewEnviron() environ.Plugin {
	return &environPlugin{}
}

type environPlugin struct {
}

func (p *environPlugin) List(ctx context.Context, req *environ.Request) ([]*environ.Variable, error) {

	// TODO replace or remove
	// return a list of static environment variables
	// this is for demo purposes only.
	environ := []*environ.Variable{
		{
			Name: "foo",
			Data: "bar",
			Mask: false,
		},
	}

	return environ, nil
}

Thanks for sharing I will give it a try. :slight_smile: