Write your own drone plugin from scratch

May 25, 2019
Last update: Sep 22, 2020
~ 4 min

Recently Drone released it 1.0 version update. It came with a cleaner and more useful UI and a new structure for declaring the YAML drone pipelines files. Perfect time for writing our own drone plugin. 🎉

TL;DR Get the code: https://github.com/CupCakeArmy/drone-deploy

Photo by asoggetti on Unsplash

This should be a starting point to guide you in the right direction towards writing your first drone plugin. It’s easier than it might seems. Let’s go 🚀

What do we want to achieve?

  • Login with an ssh key from drone secrets
  • copy files to a remote location
  • execute remote commands

How are Drone Plugins written?

Very simple actually: drone plugins are nothing more than a docker image. The process is as follows:

  1. The code gets mounted inside your image at `/drone/src`
  2. You make slack api calls, deploy, whatever you want.

Since the source is mounted inside a container you can use whatever language you prefer! Cool. For this small project I decided to go with Python, but you could use node, go, whatever runs inside Docker.

First lets look how a plugin is defined inside the .drone.yml file.

kind: pipeline
name: default

steps:

  # build...

  - name: deploy
    image: you/your-drone-plugin # public docker image
    settings:
      host: example.org
      user: root
      password: h4x0rz

How do we get those variables? Drone mounts them as environment variables inside the plugin container prefixed by PLUGIN_.
So host: example.org becomes $PLUGIN_HOST=example.org and so forth.

In Python
def main():
    host = os.environ.get('PLUGIN_HOST')
    port = os.environ.get('PLUGIN_PORT', 22)
    user = os.environ.get('PLUGIN_USER')
    password = os.environ.get('PLUGIN_PASSWORD')
    key = os.environ.get('PLUGIN_KEY')

main()
# example.org
# 22
# root
# h4x0rz
# None

How do I get secrets? We need to use the from_secret syntax. Supposing you have saved your private key as a repository secret under ssh_key the example would look as follows:

- name: deploy
  settings:
    key:
      from_secret: ssh_key

Easy 👌What about arrays? We want to copy a list of sources to the server.

- name: deploy
  settings:
    sources:
      - ./public
      - docker-compose.yml

This will result into PLUGIN_SOURCES=./public,docker-compose.yml. Basically we get a comma-separated-string.

# Takes a string, splits it at the comma and removes empty elements
def clean_array(s: str) -> List[str]:
    return list(filter(None, s.split(',')))

sources = clean_array(os.environ.get('PLUGIN_SOURCES', ''))

Putting it all together

import paramiko

def execute(c: SSHClient, cmd: str, path: str = None, env:dict = None) -> str:
    if path is not None:
        cmd = 'cd {}; {}'.format(path, cmd)
    stdin, stdout, stderr = c.exec_command(cmd, environment=env)
    return stdout.read().decode('utf-8').strip()


def main():

	# Takes a string, splits it at the comma and removes empty elements
    def clean_array(s: str) -> List[str]:
        return list(filter(None, s.split(',')))
	
    host = os.environ.get('PLUGIN_HOST')
    port = os.environ.get('PLUGIN_PORT', 22)
    user = os.environ.get('PLUGIN_USER')
    password = os.environ.get('PLUGIN_PASSWORD')
    key = os.environ.get('PLUGIN_KEY')
    target = os.environ.get('PLUGIN_TARGET')
    commands = clean_array(os.environ.get('PLUGIN_COMMANDS', ''))
    sources = clean_array(os.environ.get('PLUGIN_SOURCES', ''))

    ssh = paramiko.SSHClient()
    try:
        k = paramiko.RSAKey.from_private_key(io.StringIO(key))
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect(hostname=host, username=user, pkey=k, port=port, password=password)

        sftp = ssh.open_sftp()
        try:
			# Upload the files
			# for file in file....
			sftp.put(archive_local, archive_remote)
        finally:
            sftp.close()

        for command in commands:
            output = execute(ssh, command, target, envs)
            print(command)
            print(output)

    finally:
        ssh.close()

This of course simplifies the code a lot, but should give a general idea how it’s done.

For a full fledged example check out the repo: https://github.com/CupCakeArmy/drone-deploy

Enjoy! 👋

0.00