Full Code of drone/autoscaler for AI

master b22b87077a04 cached
212 files
731.3 KB
266.4k tokens
1056 symbols
1 requests
Download .txt
Showing preview only (798K chars total). Download the full file or copy to clipboard to get everything.
Repository: drone/autoscaler
Branch: master
Commit: b22b87077a04
Files: 212
Total size: 731.3 KB

Directory structure:
gitextract_xkto3c2c/

├── .drone.sh
├── .drone.yml
├── .github/
│   ├── issue_template.md
│   └── pull_request_template.md
├── .gitignore
├── BUILDING
├── CHANGELOG.md
├── COPYRIGHT
├── Dockerfile
├── LICENSE.md
├── README.md
├── cmd/
│   └── drone-autoscaler/
│       └── main.go
├── config/
│   ├── config.go
│   ├── load.go
│   └── load_test.go
├── drivers/
│   ├── amazon/
│   │   ├── create.go
│   │   ├── create_test.go
│   │   ├── destroy.go
│   │   ├── destroy_test.go
│   │   ├── option.go
│   │   ├── option_test.go
│   │   ├── provider.go
│   │   ├── provider_test.go
│   │   ├── setup.go
│   │   ├── setup_test.go
│   │   ├── util.go
│   │   └── util_test.go
│   ├── azure/
│   │   ├── create.go
│   │   ├── create_test.go
│   │   ├── destroy.go
│   │   ├── destroy_test.go
│   │   ├── option.go
│   │   ├── option_test.go
│   │   ├── provider.go
│   │   └── provider_test.go
│   ├── digitalocean/
│   │   ├── create.go
│   │   ├── create_test.go
│   │   ├── destroy.go
│   │   ├── destroy_test.go
│   │   ├── option.go
│   │   ├── option_test.go
│   │   ├── provider.go
│   │   ├── provider_test.go
│   │   ├── setup.go
│   │   ├── setup_test.go
│   │   └── userdata.go
│   ├── google/
│   │   ├── create.go
│   │   ├── create_test.go
│   │   ├── destroy.go
│   │   ├── destroy_test.go
│   │   ├── option.go
│   │   ├── option_test.go
│   │   ├── provider.go
│   │   ├── provider_test.go
│   │   ├── setup.go
│   │   └── setup_test.go
│   ├── hetznercloud/
│   │   ├── create.go
│   │   ├── create_test.go
│   │   ├── destroy.go
│   │   ├── destroy_test.go
│   │   ├── option.go
│   │   ├── option_test.go
│   │   ├── provider.go
│   │   ├── provider_test.go
│   │   ├── setup.go
│   │   └── setup_test.go
│   ├── internal/
│   │   └── userdata/
│   │       ├── userdata.go
│   │       └── userdata_test.go
│   ├── openstack/
│   │   ├── create.go
│   │   ├── create_test.go
│   │   ├── destroy.go
│   │   ├── destroy_test.go
│   │   ├── doc.go
│   │   ├── option.go
│   │   ├── option_test.go
│   │   ├── provider.go
│   │   ├── provider_test.go
│   │   ├── setup.go
│   │   ├── setup_test.go
│   │   └── testdata/
│   │       ├── associateresp1.json
│   │       ├── authresp1.json
│   │       ├── fipresp1.json
│   │       ├── flavorlistresp1.json
│   │       ├── imagelistresp1.json
│   │       ├── servercreateresp1.json
│   │       ├── serverstatusresp1.json
│   │       └── tokenresp1.json
│   ├── packet/
│   │   ├── create.go
│   │   ├── create_test.go
│   │   ├── destroy.go
│   │   ├── destroy_test.go
│   │   ├── option.go
│   │   ├── option_test.go
│   │   ├── provider.go
│   │   ├── provider_test.go
│   │   ├── setup.go
│   │   └── setup_test.go
│   └── scaleway/
│       ├── create.go
│       ├── create_test.go
│       ├── destroy.go
│       ├── destroy_test.go
│       ├── option.go
│       ├── option_test.go
│       ├── provider.go
│       ├── provider_test.go
│       └── setup.go
├── engine/
│   ├── alloc.go
│   ├── alloc_test.go
│   ├── calc.go
│   ├── calc_test.go
│   ├── certs/
│   │   ├── cert.go
│   │   └── cert_test.go
│   ├── collect.go
│   ├── collect_test.go
│   ├── docker.go
│   ├── engine.go
│   ├── install.go
│   ├── install_test.go
│   ├── pinger.go
│   ├── pinger_test.go
│   ├── planner.go
│   ├── planner_test.go
│   ├── reaper.go
│   ├── reaper_test.go
│   ├── sort.go
│   └── sort_test.go
├── engine.go
├── go.mod
├── go.sum
├── licenses/
│   ├── Polyform-Free-Trial.md
│   └── Polyform-Small-Business.md
├── logger/
│   ├── context.go
│   ├── context_test.go
│   ├── history/
│   │   ├── history.go
│   │   └── history_test.go
│   ├── logger.go
│   ├── logger_test.go
│   ├── logrus.go
│   ├── logrus_test.go
│   └── request/
│       └── request.go
├── metrics/
│   ├── metrics.go
│   ├── server_capacity.go
│   ├── server_capacity_test.go
│   ├── server_count.go
│   ├── server_count_test.go
│   ├── server_create.go
│   ├── server_create_test.go
│   ├── server_delete.go
│   └── server_delete_test.go
├── mocks/
│   ├── mock_docker.go
│   ├── mock_drone.go
│   ├── mock_engine.go
│   ├── mock_metrics.go
│   ├── mock_provider.go
│   ├── mock_server.go
│   └── mocks.go
├── provider.go
├── server/
│   ├── auth.go
│   ├── auth_test.go
│   ├── engine.go
│   ├── engine_test.go
│   ├── healthz.go
│   ├── healthz_test.go
│   ├── metrics.go
│   ├── metrics_test.go
│   ├── servers.go
│   ├── servers_test.go
│   ├── varz.go
│   ├── varz_test.go
│   ├── version.go
│   ├── version_test.go
│   ├── web/
│   │   ├── handler.go
│   │   ├── nocache.go
│   │   ├── nocache_test.go
│   │   ├── render.go
│   │   ├── render_test.go
│   │   ├── static/
│   │   │   ├── files/
│   │   │   │   ├── reset.css
│   │   │   │   ├── style.css
│   │   │   │   └── timeago.js
│   │   │   ├── static.go
│   │   │   └── static_gen.go
│   │   └── template/
│   │       ├── files/
│   │       │   ├── index.tmpl
│   │       │   └── logs.tmpl
│   │       ├── server.go
│   │       ├── template.go
│   │       ├── template_gen.go
│   │       └── testdata/
│   │           ├── logs.json
│   │           ├── logs_empty.json
│   │           ├── servers.json
│   │           └── servers_empty.json
│   ├── writer.go
│   └── writer_test.go
├── server.go
├── slack/
│   ├── slack.go
│   └── slack_test.go
└── store/
    ├── db.go
    ├── db_test.go
    ├── lock.go
    ├── migrate/
    │   ├── migrate.go
    │   ├── mysql/
    │   │   ├── ddl.go
    │   │   ├── ddl_gen.go
    │   │   └── files/
    │   │       └── 001_create_table_servers.sql
    │   ├── postgres/
    │   │   ├── ddl.go
    │   │   ├── ddl_gen.go
    │   │   └── files/
    │   │       └── 001_create_table_servers.sql
    │   └── sqlite/
    │       ├── ddl.go
    │       ├── ddl_gen.go
    │       └── files/
    │           └── 001_create_table_servers.sql
    ├── servers.go
    ├── servers_test.go
    ├── util.go
    └── util_test.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .drone.sh
================================================
#!/bin/sh

set -e
set -x

COMMIT="-X main.commit=${DRONE_COMMIT_SHA}"
VERSION="-X main.version=${DRONE_TAG=latest}"

go build \
    -ldflags "-extldflags \"-static\" $COMMIT $VERSION"   \
	-o release/linux/amd64/drone-autoscaler \
	github.com/drone/autoscaler/cmd/drone-autoscaler


================================================
FILE: .drone.yml
================================================
---
kind: pipeline
name: default
type: vm

pool:
  use: ubuntu

platform:
  os: linux
  arch: amd64

steps:
- name: test
  pull: default
  image: golang
  volumes:
  - name: deps
    path: /go
  commands:
  - go get
  - go test -v -cover ./...

- name: test_postgres
  pull: default
  image: golang
  volumes:
  - name: deps
    path: /go
  commands:
  - cd store
  - go test -v
  environment:
    DATABASE_CONFIG: host=postgres user=postgres password=password dbname=test sslmode=disable
    DATABASE_DRIVER: postgres

- name: test_mysql
  pull: default
  image: golang
  volumes:
  - name: deps
    path: /go
  commands:
  - cd store
  - go test -v
  environment:
    DATABASE_CONFIG: "root:password@tcp(mysql:3306)/test?parseTime=true"
    DATABASE_DRIVER: mysql

- name: build
  pull: default
  image: golang
  volumes:
  - name: deps
    path: /go
  commands:
  - sh .drone.sh

- name: publish
  pull: default
  image: plugins/docker
  settings:
    auto_tag: true
    repo: drone/autoscaler
    password:
      from_secret: docker_password
    username:
      from_secret: docker_username
  when:
    event:
    - push
    - tag

volumes:
- name: deps
  temp: {}

services:
- name: postgres
  pull: default
  image: postgres:9
  environment:
    POSTGRES_DB: test
    POSTGRES_PASSWORD: password

- name: mysql
  pull: default
  image: mysql:5
  environment:
    MYSQL_DATABASE: test
    MYSQL_ROOT_PASSWORD: password

...


================================================
FILE: .github/issue_template.md
================================================


================================================
FILE: .github/pull_request_template.md
================================================
<!-- IMPORTANT NOTICE

By submitting a pull request, you acknowledge that your contribution
will be under the terms of the BSD-3-Clause license.

    https://opensource.org/licenses/BSD-3-Clause

-->

================================================
FILE: .gitignore
================================================
./drone-autoscaler
NOTES.md
release
vendor
*.sqlite
*.sqlite3
*.bak
*.out
*.db
*.env


================================================
FILE: BUILDING
================================================
1. Install go 1.11 or later
2. Install dependencies:

    go get

3. Compile and test:

    go install ./...
    go test ./...


================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.7.5]
### Fixed
- ignore unset environment variables when configuring runners, by [@bradrydzewski](https://github.com/bradrydzewski). [d26b8e41](https://github.com/drone/autoscaler/commit/6db28505572d90df9a271404440789043c7b378b).

## [1.7.4]
### Fixed
- support colon in map values sourced from environment variables, by [@UnAfraid](https://github.com/kelseyhightower/envconfig/pull/185)

## [1.7.3]
### Added
- parameter to configure env file for remote runners. [#74](https://github.com/drone/autoscaler/pull/74).

### Fixed
- the content-type should be set before the status is written to the http response. [89780e6](https://github.com/drone/autoscaler/commit/89780e6b9585e8116249524fe6fe6dffe3904fd6).
- pending instance count is excluded from determining available capacity when reducing pool size. [d26b8e41](https://github.com/drone/autoscaler/commit/d26b8e41fd178595fd00d739d1d3b27f2a870314).
- custom scopes not passed to google cloud configuration. [#79](https://github.com/drone/autoscaler/pull/79).

### Changed
- docker.NewClient was deprecated; migrate to docker.NewClientWithOpts. [#72](https://github.com/drone/autoscaler/pull/72).

## [1.7.2]
### Fixed
- captuare instance private IP when google compute private IP is enabled, by [ademariag](https://github.com/ademariag). [#68](https://github.com/drone/autoscaler/pull/68).

## [1.7.2]
### Added
- support for aws fallback instance types, by [bradrydzewski](https://github.com/bradrydzewski). [d524689b].(https://github.com/drone/autoscaler/commit/d524689bbd1ed73ef8ee77cb3e0c5e6e6f786158).

## [1.7.1]
### Added
- support for google compute private ip, by [swjclarke](https://github.com/swjclarke).
- support for google compute service accounts, by [ademariag](https://github.com/ademariag).

### Fixed
- google compute instance scopes being ignored, by [ademariag](https://github.com/ademariag).

## [1.7.0]
### Added
- parameter to configure docker stop timeout duration.
- parameter to configure aws volume iops, by [ttousai](https://github.com/ttousai).
- parameter to configure gcp scopes, by [imranismail](https://github.com/imranismail).
- metrics to track server boot errors
- metrics to track server boot time
- metrics to track server installation errors
- metrics to track server installation time
- metrics to track server creation errors
- metrics to track server creation time

### Fixed
- do not run docker stop if the instance was not created.
- do not run docker stop if the instance was not assigned an IP.

## [1.6.1]
### Added
- support for instance not found errors in gcp, by [frebib](https://github.com/frebib).

### Fixed
- resume instance removal when autoscaler unexpectedly restarted, by [@bradrydzewski](https://github.com/bradrydzewski).

## [1.6.0]
### Changed
- Use logrus for logging instead of zerolog, by [@bradrydzewski](https://github.com/bradrydzewski).

### Added
- Read only user interface to visualize servers and logs, by [@bradrydzewski](https://github.com/bradrydzewski). 
- Support for configuring subnetworks with GCP, by [@nsigarora](https://github.com/nsigarora).
- Support for handling  ErrInstanceNotFound with Hetzner, by [@tboerger](https://github.com/tboerger).

## [1.5.0]
### Changed
- Use the new Docker runner image and deprecate the agent, by [@bradrydzewski](https://github.com/bradrydzewski).
- Enable Digital Ocean private IP addresses, by [@barrypeng6](https://github.com/barrypeng6).

## [1.4.3]
### Fixed
- Expired context preventing database updates, by [@bradrydzewski](https://github.com/bradrydzewski).

## [1.4.2]
### Added
- Log errors updating the instance state, by [@bradrydzewski](https://github.com/bradrydzewski).
- Add mutex to database operations for sqlite, by [@bradrydzewski](https://github.com/bradrydzewski).

## [1.4.1] - 2019-10-10
### Fixed
- Support for arm machines on Scaleway, by [@tboerger](https://github.com/tboerger).

## [1.4.0] - 2019-09-23
### Added
- Ability to configure the reaper internal, by [@msaizar](https://github.com/msaizar).
- Ability to configure the install check deadline, by [@bradrydzewski](https://github.com/bradrydzewski).
- Ability to configure the install check interval, by [@bradrydzewski](https://github.com/bradrydzewski).

## [1.3.0] - 2019-09-11
### Added

- Added support for Scaleway, by [@frebib](https://github.com/frebib). [#45](https://github.com/drone/autoscaler/pull/45).

### Fixed

- Fixed issue where non-existing instance could not be destroyed, by [@jlesage](https://github.com/jlesage). [#50](https://github.com/drone/autoscaler/pull/50).
- Added timeout when attempting to ping the instance, by [@bradrydzewski](https://github.com/bradrydzewski).

## [1.2.2] - 2019-08-29
### Added

- Support for loading runner environment variables from file, by [@bradrydzewski](https://github.com/bradrydzewski).
- Basic support for configuring windows agents, by [@bradrydzewski](https://github.com/bradrydzewski).

### Fixed

- Pull garbage collector image before creating the container, by [@msaizar](https://github.com/msaizar).
- Handle nil pointer caused by empty or missing interface in AWS driver, by [@bradrydzewski](https://github.com/bradrydzewski).

## [1.2.1] - 2019-08-14
### Added

- Added postgres driver, by [@mmuehlberger](https://github.com/mmuehlberger).
- Support for capacity buffer, by [@jones2026](https://github.com/jones2026). [#39](https://github.com/drone/autoscaler/pull/39).

### Fixed

- Close docker client after server ping, by [@msaizar](https://github.com/msaizar), [#42](https://github.com/drone/autoscaler/pull/42).

## [1.2.0] - 2019-07-29
### Added

- Support for agent label assignment and matching, by [@logikone](https://github.com/logikone).
- Allow Hetzner to choose datacenter when none specified, by [@tboerger](https://github.com/tboerger).

### Fixed

- Upgraded zerolog to fix duplicate keys in json output, by [@krtx](https://github.com/krtx).

## [1.1.0] - 2019-05-29
### Added

- Create AWS instances with Name tag set to agent unique id, from [@bradrydzewski](https://github.com/bradrydzewski).
- Handle AWS instance not found errors, from [@andy-trimble](https://github.com/andy-trimble).
- Remove hard-coded DNS servers from the default Docker configuration, from [jones2026](https://github.com/jones2026).

## [1.0.0] - 2019-05-06
### Added

- Optional support for watchtower from [@bradrydzewski](https://github.com/bradrydzewski).
- Optional support for drone/gc from [@bradrydzewski](https://github.com/bradrydzewski). 
- Update the default agent image to 1.0 stable, from [@bradrydzewski](https://github.com/bradrydzewski).
- Configure agent environment variables from [@bradrydzewski](https://github.com/bradrydzewski).
- Configure agent host volume mounts from [@patrickjahns](https://github.com/patrickjahns).
- Update Digital Ocean default image from [@jlesage](https://github.com/jlesage).
- Fix problems using custom Digital Ocean image from [@jlesage](https://github.com/jlesage).


================================================
FILE: COPYRIGHT
================================================
Copyright 2018 Drone.IO Inc
Use of this software is governed by the Polyform License
that can be found in the LICENSE file.

================================================
FILE: Dockerfile
================================================
FROM alpine:3.20 as alpine
RUN apk add -U --no-cache ca-certificates

FROM alpine:3.20
EXPOSE 8080 80 443
VOLUME /data

ENV GODEBUG netdns=go
ENV XDG_CACHE_HOME /data
ENV DRONE_DATABASE_DRIVER sqlite3
ENV DRONE_DATABASE_DATASOURCE /data/database.sqlite?cache=shared&mode=rwc&_busy_timeout=9999999

COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

ADD release/linux/amd64/drone-autoscaler /bin/
ENTRYPOINT ["/bin/drone-autoscaler"]


================================================
FILE: LICENSE.md
================================================
[Polyform-Small-Business-1.0.0](https://polyformproject.org/licenses/small-business/1.0.0) OR
[Polyform-Free-Trial-1.0.0](https://polyformproject.org/licenses/free-trial/1.0.0)

================================================
FILE: README.md
================================================
[![Build Status](https://cloud.drone.io/api/badges/drone/autoscaler/status.svg)](https://cloud.drone.io/drone/autoscaler)

Drone Autoscale is a lightweight daemon that elastically increases and decreases your compute resources based on your build volume. Integrates with [DigitalOcean](https://m.do.co/c/00500d28741b), [Amazon Web services](http://autoscale.drone.io/intro/amazon/), Hetzner and more.

Documentation:<br/>
https://autoscale.drone.io

Technical Support:<br/>
https://discourse.drone.io

Issue Tracker and Roadmap:<br/>
https://trello.com/b/ttae5E5o/drone


================================================
FILE: cmd/drone-autoscaler/main.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package main

import (
	"context"
	"errors"
	"net/http"
	"net/url"
	"os"

	"github.com/drone/autoscaler"
	"github.com/drone/autoscaler/config"
	"github.com/drone/autoscaler/drivers/amazon"
	"github.com/drone/autoscaler/drivers/digitalocean"
	"github.com/drone/autoscaler/drivers/google"
	"github.com/drone/autoscaler/drivers/hetznercloud"
	"github.com/drone/autoscaler/drivers/openstack"
	"github.com/drone/autoscaler/drivers/packet"
	"github.com/drone/autoscaler/drivers/scaleway"
	"github.com/drone/autoscaler/engine"
	"github.com/drone/autoscaler/logger"
	"github.com/drone/autoscaler/logger/history"
	"github.com/drone/autoscaler/logger/request"
	"github.com/drone/autoscaler/metrics"
	"github.com/drone/autoscaler/server"
	"github.com/drone/autoscaler/server/web"
	"github.com/drone/autoscaler/server/web/static"
	"github.com/drone/autoscaler/slack"
	"github.com/drone/autoscaler/store"
	"github.com/drone/drone-go/drone"
	"github.com/drone/signal"

	"github.com/99designs/basicauth-go"
	"github.com/go-chi/chi"
	"github.com/sirupsen/logrus"
	"golang.org/x/crypto/acme/autocert"
	"golang.org/x/oauth2"
	"golang.org/x/sync/errgroup"

	_ "github.com/go-sql-driver/mysql"
	_ "github.com/joho/godotenv/autoload"
	_ "github.com/lib/pq"
	_ "github.com/mattn/go-sqlite3"
)

var (
	source  = "https://github.com/drone/autoscaler.git"
	version string
	commit  string
)

func main() {
	conf := config.MustLoad()
	setupLogging(conf)

	provider, err := setupProvider(conf)
	if err != nil {
		logrus.WithError(err).
			Fatalln("Invalid or missing hosting provider")
	}

	// instruments the provider with prometheus metrics.
	provider = metrics.ServerCreate(provider)
	provider = metrics.ServerDelete(provider)

	db, err := store.Connect(
		conf.Database.Driver,
		conf.Database.Datasource,
		conf.Database.MaxIdle,
		conf.Database.MaxLifetime,
	)
	if err != nil {
		logrus.WithError(err).
			Fatalln("Cannot establish database connection")
	}

	mu := store.NewLocker(conf.Database.Driver)
	servers := store.NewServerStore(db, mu)

	// instruments the provider with slack notifications
	// instance creation and termination events.
	if conf.Slack.Webhook != "" {
		servers = slack.New(conf, servers)
	}
	servers = metrics.ServerCount(servers)
	defer db.Close()

	client := setupClient(conf)

	enginex := engine.New(
		client,
		conf,
		servers,
		provider,
		metrics.New(),
	)

	//
	// Setup the router
	//

	r := chi.NewRouter()
	r.Use(request.Logger)

	// middleware to require basic authentication.
	auth := basicauth.New(conf.UI.Realm, map[string][]string{
		conf.UI.Username: {conf.UI.Password},
	})

	r.Route(conf.HTTP.Root, func(root chi.Router) {
		// handler to serve static assets for the dashboard.
		fs := http.FileServer(static.New())

		root.Handle("/", http.RedirectHandler("/ui", http.StatusSeeOther))
		root.Get("/metrics", server.HandleMetrics(conf.Prometheus.AuthToken))
		root.Get("/version", server.HandleVersion(source, version, commit))
		root.Get("/healthz", server.HandleHealthz())
		root.Get("/varz", server.HandleVarz(enginex))
		root.Handle("/static/*", http.StripPrefix("/static/", fs))

		if conf.UI.Password != "" {
			// register the history handler
			history := history.New()
			logrus.AddHook(history)

			root.Route("/ui", func(ui chi.Router) {
				ui.Use(auth)
				ui.Get("/", web.HandleServers(servers))
				ui.Get("/logs", web.HandleLogging(history))
			})
		}
		root.Route("/api", func(api chi.Router) {
			api.Use(server.CheckDrone(conf))

			api.Post("/pause", server.HandleEnginePause(enginex))
			api.Post("/resume", server.HandleEngineResume(enginex))
			api.Get("/servers", server.HandleServerList(servers))
			api.Post("/servers", server.HandleServerCreate(servers, conf))
			api.Get("/servers/{name}", server.HandleServerFind(servers))
			api.Delete("/servers/{name}", server.HandleServerDelete(servers))
		})
	})

	//
	// starts the web server.
	//

	srv := &http.Server{
		Handler: r,
	}

	ctx := context.Background()
	ctx = signal.WithContextFunc(ctx, func() {
		logrus.Println("Program terminating, interrupt received")
		srv.Shutdown(ctx)
	})

	var g errgroup.Group
	g.Go(func() error {
		if conf.TLS.Autocert {
			return srv.Serve(
				autocert.NewListener(conf.HTTP.Host),
			)
		} else if conf.TLS.Cert != "" {
			return srv.ListenAndServeTLS(
				conf.TLS.Cert,
				conf.TLS.Key,
			)
		}
		srv.Addr = conf.HTTP.Port

		logrus.WithField("addr", conf.HTTP.Port).
			Infoln("starting the server")

		return srv.ListenAndServe()
	})

	//
	// starts the auto-scaler routine.
	//

	g.Go(func() error {
		enginex.Start(ctx)
		return nil
	})

	if err := g.Wait(); err != nil {
		// terminate with non-zero exit code on error
		logrus.WithError(err).Fatalln("Program terminated")
	} else {
		logrus.Println("Program terminated")
	}
}

// helper funciton configures the logging.
func setupLogging(c config.Config) {
	logger.Default = logger.Logrus(
		logrus.NewEntry(
			logrus.StandardLogger(),
		),
	)
	if c.Logs.Debug {
		logrus.SetLevel(logrus.DebugLevel)
	}
	if c.Logs.Trace {
		logrus.SetLevel(logrus.TraceLevel)
	}
	if c.Logs.Pretty == false {
		logrus.SetFormatter(&logrus.JSONFormatter{})
	}
}

// helper function configures the drone client.
func setupClient(c config.Config) drone.Client {
	config := new(oauth2.Config)
	auther := config.Client(
		oauth2.NoContext,
		&oauth2.Token{
			AccessToken: c.Server.Token,
		},
	)
	uri := new(url.URL)
	uri.Scheme = c.Server.Proto
	uri.Host = c.Server.Host
	return drone.NewClient(uri.String(), auther)
}

// helper function configures the hosting provider.
func setupProvider(c config.Config) (autoscaler.Provider, error) {
	switch {
	case c.Google.Project != "":
		return google.New(
			google.WithDiskSize(c.Google.DiskSize),
			google.WithDiskType(c.Google.DiskType),
			google.WithMachineImage(c.Google.MachineImage),
			google.WithMachineType(c.Google.MachineType),
			google.WithLabels(c.Google.Labels),
			google.WithNetwork(c.Google.Network),
			google.WithSubnetwork(c.Google.Subnetwork),
			google.WithStackType(c.Google.StackType),
			google.WithPrivateIP(c.Google.PrivateIP),
			google.WithServiceAccountEmail(c.Google.ServiceAccountEmail),
			google.WithProject(c.Google.Project),
			google.WithTags(c.Google.Tags...),
			google.WithScopes(c.Google.Scopes...),
			google.WithUserData(c.Google.UserData),
			google.WithUserDataFile(c.Google.UserDataFile),
			google.WithZones(c.Google.Zone...),
			google.WithUserDataKey(c.Google.UserDataKey),
			google.WithRateLimit(c.Google.RateLimit),
		)
	case c.DigitalOcean.Token != "":
		return digitalocean.New(
			digitalocean.WithSSHKey(c.DigitalOcean.SSHKey),
			digitalocean.WithImage(c.DigitalOcean.Image),
			digitalocean.WithRegion(c.DigitalOcean.Region),
			digitalocean.WithSize(c.DigitalOcean.Size),
			digitalocean.WithFirewall(c.DigitalOcean.Firewall),
			digitalocean.WithUserDataFile(c.DigitalOcean.UserDataFile),
			digitalocean.WithUserData(c.DigitalOcean.UserData),
			digitalocean.WithToken(c.DigitalOcean.Token),
			digitalocean.WithPrivateIP(c.DigitalOcean.PrivateIP),
			digitalocean.WithTags(c.DigitalOcean.Tags...),
		), nil
	case c.Scaleway.AccessKey != "":
		return scaleway.New(
			scaleway.WithAccessKey(c.Scaleway.AccessKey),
			scaleway.WithSecretKey(c.Scaleway.SecretKey),
			scaleway.WithOrganisationID(c.Scaleway.OrganisationID),
			scaleway.WithZone(c.Scaleway.Zone),
			scaleway.WithSize(c.Scaleway.Size),
			scaleway.WithImage(c.Scaleway.Image),
			scaleway.WithDynamicIP(c.Scaleway.DynamicIP),
			scaleway.WithTags(c.Scaleway.Tags...),
			scaleway.WithUserData(c.Scaleway.UserData),
			scaleway.WithUserDataFile(c.Scaleway.UserDataFile),
		)
	case c.HetznerCloud.Token != "":
		return hetznercloud.New(
			hetznercloud.WithDatacenter(c.HetznerCloud.Datacenter),
			hetznercloud.WithImage(c.HetznerCloud.Image),
			hetznercloud.WithUserDataFile(c.HetznerCloud.UserDataFile),
			hetznercloud.WithUserData(c.HetznerCloud.UserData),
			hetznercloud.WithServerType(c.HetznerCloud.Type),
			hetznercloud.WithSSHKey(c.HetznerCloud.SSHKey),
			hetznercloud.WithToken(c.HetznerCloud.Token),
		), nil
	case c.Packet.APIKey != "":
		return packet.New(
			packet.WithAPIKey(c.Packet.APIKey),
			packet.WithFacility(c.Packet.Facility),
			packet.WithProject(c.Packet.ProjectID),
			packet.WithPlan(c.Packet.Plan),
			packet.WithOS(c.Packet.OS),
			packet.WithSSHKey(c.Packet.SSHKey),
			packet.WithUserData(c.Packet.UserData),
			packet.WithUserDataFile(c.Packet.UserDataFile),
			packet.WithHostname(c.Packet.Hostname),
			packet.WithTags(c.Packet.Tags...),
		), nil
	case os.Getenv("AWS_ACCESS_KEY_ID") != "" || os.Getenv("AWS_IAM") != "":
		return amazon.New(
			amazon.WithDeviceName(c.Amazon.DeviceName),
			amazon.WithImage(c.Amazon.Image),
			amazon.WithRegion(c.Amazon.Region),
			amazon.WithRetries(c.Amazon.Retries),
			amazon.WithPrivateIP(c.Amazon.PrivateIP),
			amazon.WithSSHKey(c.Amazon.SSHKey),
			amazon.WithSecurityGroup(c.Amazon.SecurityGroup...),
			amazon.WithSize(c.Amazon.Instance),
			amazon.WithSizeAlt(c.Amazon.InstanceAlt),
			amazon.WithSubnets(append([]string{c.Amazon.SubnetID}, c.Amazon.SubnetIDsAlt...)),
			amazon.WithTags(c.Amazon.Tags),
			amazon.WithUserData(c.Amazon.UserData),
			amazon.WithUserDataFile(c.Amazon.UserDataFile),
			amazon.WithVolumeSize(c.Amazon.VolumeSize),
			amazon.WithVolumeType(c.Amazon.VolumeType),
			amazon.WithVolumeIops(c.Amazon.VolumeIops),
			amazon.WithVolumeThroughput(c.Amazon.VolumeThroughput),
			amazon.WithIamProfileArn(c.Amazon.IamProfileArn),
			amazon.WithMarketType(c.Amazon.MarketType),
			amazon.WithInstanceMetadataTokens(c.Amazon.IMDSTokens),
		), nil
	case os.Getenv("OS_USERNAME") != "":
		return openstack.New(
			openstack.WithImage(c.OpenStack.Image),
			openstack.WithRegion(c.OpenStack.Region),
			openstack.WithFlavor(c.OpenStack.Flavor),
			openstack.WithNetwork(c.OpenStack.Network),
			openstack.WithFloatingIpPool(c.OpenStack.Pool),
			openstack.WithSSHKey(c.OpenStack.SSHKey),
			openstack.WithSecurityGroup(c.OpenStack.SecurityGroup...),
			openstack.WithMetadata(c.OpenStack.Metadata),
			openstack.WithUserData(c.OpenStack.UserData),
			openstack.WithUserDataFile(c.OpenStack.UserDataFile),
		)
	default:
		return nil, errors.New("missing provider configuration")
	}
}


================================================
FILE: config/config.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package config

import "time"

// TODO change DRONE_HTTP_PORT to DRONE_HTTP_BIND

type (
	// Config stores the configuration settings.
	Config struct {
		Interval       time.Duration `default:"5m"`
		CapacityBuffer int           `default:"0" split_words:"true"`

		Timeout struct {
			Stop time.Duration `envconfig:"DRONE_TIMEOUT_STOP" default:"1h"`
		}

		Slack struct {
			Webhook string
			Create  bool `default:"true"`
			Destroy bool `default:"true"`
			Error   bool `default:"true"`
		}

		Logs struct {
			Debug  bool `default:"true"`
			Trace  bool
			Pretty bool
		}

		Pool struct {
			Min    int           `default:"2"`
			Max    int           `default:"4"`
			MinAge time.Duration `default:"55m" split_words:"true"`
		}

		Check struct {
			Interval time.Duration `envconfig:"DRONE_INSTALL_CHECK_INTERVAL" default:"1m"`
			Deadline time.Duration `envconfig:"DRONE_INSTALL_CHECK_DEADLINE" default:"30m"`
		}

		Server struct {
			Host  string
			Proto string
			Token string
		}

		Agent struct {
			Token       string
			Image       string `default:"drone/drone-runner-docker:1"`
			Concurrency int    `default:"2"`
			OS          string `default:"linux"`
			Arch        string `default:"amd64"`
			Version     string
			Kernel      string
			EnvironFile string `envconfig:"DRONE_AGENT_ENV_FILE"`
			Environ     []string
			Volumes     []string
			Ports       []string          `envconfig:"DRONE_AGENT_PUBLISHED_PORTS"`
			Labels      map[string]string `envconfig:"DRONE_AGENT_LABELS"`
			NamePrefix  string            `envconfig:"DRONE_AGENT_NAME_PREFIX" default:"agent-"`
		}

		Runner Runner

		GC struct {
			Enabled  bool          `envconfig:"DRONE_GC_ENABLED"`
			Image    string        `envconfig:"DRONE_GC_IMAGE" default:"drone/gc"`
			Debug    bool          `envconfig:"DRONE_GC_DEBUG"`
			Images   []string      `envconfig:"DRONE_GC_IGNORE_IMAGES"`
			Interval time.Duration `envconfig:"DRONE_GC_INTERVAL" default:"30m"`
			Cache    string        `envconfig:"DRONE_GC_CACHE" default:"10gb"`
		}

		Reaper struct {
			Enabled  bool          `envconfig:"DRONE_REAPER_ENABLED", default:"false"`
			Interval time.Duration `envconfig:"DRONE_REAPER_INTERVAL" default:"1h"`
		}

		Pinger struct {
			Enabled  bool          `envconfig:"DRONE_PINGER_ENABLED", default:"false"`
			Interval time.Duration `envconfig:"DRONE_PINGER_INTERVAL" default:"10m"`
		}

		Watchtower struct {
			Enabled       bool          `envconfig:"DRONE_WATCHTOWER_ENABLED"`
			SignalEnabled bool          `envconfig:"DRONE_WATCHTOWER_SIGNAL_ENABLED" default:"true"`
			Signal        string        `envconfig:"DRONE_WATCHTOWER_STOP_SIGNAL" default:"SIGHUP"`
			Image         string        `envconfig:"DRONE_WATCHTOWER_IMAGE" default:"webhippie/watchtower"`
			Interval      int           `envconfig:"DRONE_WATCHTOWER_INTERVAL" default:"300"`
			Timeout       time.Duration `envconfig:"DRONE_WATCHTOWER_TIMEOUT" default:"120m"`
		}

		HTTP struct {
			Proto string `envconfig:"DRONE_HTTP_PROTO" default:"http"`
			Host  string `envconfig:"DRONE_HTTP_HOST"`
			Port  string `envconfig:"DRONE_HTTP_PORT" default:":8080"`
			Root  string `envconfig:"DRONE_HTTP_ROOT" default:"/"`
		}

		UI struct {
			Username string `envconfig:"DRONE_UI_USERNAME"`
			Password string `envconfig:"DRONE_UI_PASSWORD"`
			Realm    string `envconfig:"DRONE_UI_REALM" default:"Autoscaler"`
		}

		TLS struct {
			Autocert bool
			Cert     string
			Key      string
		}

		Prometheus struct {
			AuthToken string `split_words:"true"`
		}

		Database struct {
			Driver      string        `default:"sqlite3"`
			Datasource  string        `default:"database.sqlite?cache=shared&mode=rwc&_busy_timeout=9999999"`
			MaxIdle     int           `envconfig:"DRONE_DATABASE_MAX_IDLE" default:"0"`
			MaxLifetime time.Duration `envconfig:"DRONE_DATABASE_MAX_LIFETIME"`
		}

		Amazon struct {
			DeviceName       string `envconfig:"DRONE_AMAZON_DEVICE_NAME"`
			Image            string `envconfig:"DRONE_AMAZON_IMAGE"`
			Instance         string `envconfig:"DRONE_AMAZON_INSTANCE"`
			InstanceAlt      string `envconfig:"DRONE_AMAZON_INSTANCE_ALT"`
			PrivateIP        bool   `split_words:"true"`
			Region           string
			Retries          int
			SSHKey           string
			SubnetID         string   `split_words:"true"`
			SubnetIDsAlt     []string `envconfig:"DRONE_AMAZON_SUBNET_IDS_ALT"` // In the same manner as InstanceAlt, allows fallback to other subnets if provisioning in the main one fails
			SecurityGroup    []string `split_words:"true"`
			Tags             map[string]string
			UserData         string `envconfig:"DRONE_AMAZON_USERDATA"`
			UserDataFile     string `envconfig:"DRONE_AMAZON_USERDATA_FILE"`
			VolumeSize       int64  `envconfig:"DRONE_AMAZON_VOLUME_SIZE"`
			VolumeType       string `envconfig:"DRONE_AMAZON_VOLUME_TYPE"`
			VolumeIops       int64  `envconfig:"DRONE_AMAZON_VOLUME_IOPS"`
			VolumeThroughput int64  `envconfig:"DRONE_AMAZON_VOLUME_THROUGHPUT"`
			IamProfileArn    string `envconfig:"DRONE_AMAZON_IAM_PROFILE_ARN"`
			MarketType       string `envconfig:"DRONE_AMAZON_MARKET_TYPE"`
			IMDSTokens       string `envconfig:"DRONE_AMAZON_IMDS_TOKENS"`
		}

		DigitalOcean struct {
			Token        string
			Image        string
			Region       string
			SSHKey       string
			Size         string
			Firewall     string
			Tags         []string
			PrivateIP    bool   `split_words:"true"`
			UserData     string `envconfig:"DRONE_DIGITALOCEAN_USERDATA"`
			UserDataFile string `envconfig:"DRONE_DIGITALOCEAN_USERDATA_FILE"`
		}

		Google struct {
			MachineType         string            `envconfig:"DRONE_GOOGLE_MACHINE_TYPE"`
			MachineImage        string            `envconfig:"DRONE_GOOGLE_MACHINE_IMAGE"`
			Network             string            `envconfig:"DRONE_GOOGLE_NETWORK"`
			Subnetwork          string            `envconfig:"DRONE_GOOGLE_SUBNETWORK"`
			StackType           string            `envconfig:"DRONE_GOOGLE_STACK_TYPE"`
			Labels              map[string]string `envconfig:"DRONE_GOOGLE_LABELS"`
			Scopes              []string          `envconfig:"DRONE_GOOGLE_SCOPES"`
			ServiceAccountEmail string            `envconfig:"DRONE_GOOGLE_SERVICE_ACCOUNT_EMAIL"`
			DiskSize            int64             `envconfig:"DRONE_GOOGLE_DISK_SIZE"`
			DiskType            string            `envconfig:"DRONE_GOOGLE_DISK_TYPE"`
			Project             string            `envconfig:"DRONE_GOOGLE_PROJECT"`
			PrivateIP           bool              `split_words:"true"`
			Tags                []string          `envconfig:"DRONE_GOOGLE_TAGS"`
			UserData            string            `envconfig:"DRONE_GOOGLE_USERDATA"`
			UserDataFile        string            `envconfig:"DRONE_GOOGLE_USERDATA_FILE"`
			Zone                []string          `envconfig:"DRONE_GOOGLE_ZONE"`
			UserDataKey         string            `envconfig:"DRONE_GOOGLE_USERDATA_KEY" default:"user-data"`
			RateLimit           int               `envconfig:"DRONE_GOOGLE_READ_RATELIMIT" default:"25"`
		}

		HetznerCloud struct {
			Datacenter   string
			Image        string
			SSHKey       int
			Token        string
			Type         string
			UserData     string `envconfig:"DRONE_HETZNERCLOUD_USERDATA"`
			UserDataFile string `envconfig:"DRONE_HETZNERCLOUD_USERDATA_FILE"`
		}

		Packet struct {
			APIKey       string
			Facility     string
			Plan         string
			OS           string
			ProjectID    string `split_words:"true"`
			Tags         []string
			SSHKey       string
			UserData     string `envconfig:"DRONE_PACKET_USERDATA"`
			UserDataFile string `envconfig:"DRONE_PACKET_USERDATA_FILE"`
			Hostname     string
		}

		OpenStack struct {
			Region        string `envconfig:"OS_REGION_NAME"`
			Image         string
			Flavor        string
			Network       string
			Pool          string   `envconfig:"DRONE_OPENSTACK_IP_POOL"`
			SecurityGroup []string `split_words:"true"`
			SSHKey        string
			Metadata      map[string]string
			UserData      string `envconfig:"DRONE_OPENSTACK_USERDATA"`
			UserDataFile  string `envconfig:"DRONE_OPENSTACK_USERDATA_FILE"`
		}

		Scaleway struct {
			AccessKey      string `split_words:"true"`
			SecretKey      string `split_words:"true"`
			OrganisationID string `split_words:"true"`
			Zone           string
			Size           string
			Image          string
			DynamicIP      bool `split_words:"true"`
			Tags           []string
			UserData       string `envconfig:"DRONE_SCALEWAY_USERDATA"`
			UserDataFile   string `envconfig:"DRONE_SCALEWAY_USERDATA_FILE"`
		}
	}

	Runner struct {
		Volumes    string
		Devices    string
		Privileged string
		EnvFile    string
	}
)


================================================
FILE: config/load.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package config

import (
	"fmt"
	"os"
	"strings"

	"github.com/drone/envconfig"
	"github.com/joho/godotenv"
)

// legacy environment variables. the key is the legacy
// variable name, and the value is the new variable name.
var legacy = map[string]string{
	"DRONE_ENABLE_PINGER": "DRONE_PINGER_ENABLED",
	"DRONE_ENABLE_REAPER": "DRONE_REAPER_ENABLED",
}

func init() {
	// loop through legacy environment variable and, if set
	// rewrite to the new variable name.
	for k, v := range legacy {
		if s, ok := os.LookupEnv(k); ok {
			os.Setenv(v, s)
		}
	}
}

// Load loads the configuration from the environment.
func Load() (Config, error) {
	config := Config{}
	if err := envconfig.Process("DRONE", &config); err != nil {
		return config, err
	}
	if path := config.Agent.EnvironFile; path != "" {
		envs, _ := godotenv.Read(path)
		for k, v := range envs {
			config.Agent.Environ = append(
				config.Agent.Environ,
				fmt.Sprintf("%s=%s", k, v),
			)
		}
	}
	// If environment variables don't contain `=`, we consider that it's an environment name, we fetch and expose the value
	for i, env := range config.Agent.Environ {
		if !strings.Contains(env, "=") {
			config.Agent.Environ[i] = fmt.Sprintf("%s=%s", env, os.Getenv(env))
		}
	}
	godotenv.Load()
	return config, nil
}

// MustLoad loads the configuration from the environmnet
// and panics if an error is encountered.
func MustLoad() Config {
	config, err := Load()
	if err != nil {
		panic(err)
	}
	return config
}


================================================
FILE: config/load_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package config

import (
	"encoding/json"
	"io/ioutil"
	"os"
	"reflect"
	"testing"
	"time"

	"github.com/kr/pretty"
)

func TestDefaults(t *testing.T) {
	conf := MustLoad()
	if got, want := conf.Logs.Debug, true; got != want {
		t.Errorf("Want default DRONE_LOGS_DEBUG of %v, got %v", want, got)
	}
	if got, want := conf.Interval, time.Minute*5; got != want {
		t.Errorf("Want default DRONE_INTERVAL of %s, got %s", want, got)
	}
	if got, want := conf.CapacityBuffer, 0; got != want {
		t.Errorf("Want default DRONE_CAPACITY_BUFFER of %d, got %d", want, got)
	}
	if got, want := conf.Pool.Max, 4; got != want {
		t.Errorf("Want default DRONE_POOL_MIN of %d, got %d", want, got)
	}
	if got, want := conf.Pool.Min, 2; got != want {
		t.Errorf("Want default DRONE_POOL_MAX of %d, got %d", want, got)
	}
	if got, want := conf.Pool.MinAge, time.Minute*55; got != want {
		t.Errorf("Want default DRONE_POOL_MIN_AGE of %d, got %d", want, got)
	}

	if got, want := conf.Check.Interval, time.Minute; got != want {
		t.Errorf("Want default DRONE_INSTALL_CHECK_INTERVAL of %s, got %s", want, got)
	}
	if got, want := conf.Check.Deadline, time.Minute*30; got != want {
		t.Errorf("Want default DRONE_INSTALL_CHECK_DEADLINE of %s, got %s", want, got)
	}

	if got, want := conf.HTTP.Port, ":8080"; got != want {
		t.Errorf("Want default DRONE_HTTP_PORT of %s, got %s", want, got)
	}
	if got, want := conf.HTTP.Root, "/"; got != want {
		t.Errorf("Want default DRONE_HTTP_ROOT of %s, got %s", want, got)
	}
	if got, want := conf.Database.Driver, "sqlite3"; got != want {
		t.Errorf("Want default DRONE_DATABASE_DRIVER of %s, got %s", want, got)
	}
	if got, want := conf.Database.Datasource, "database.sqlite?cache=shared&mode=rwc&_busy_timeout=9999999"; got != want {
		t.Errorf("Want default DRONE_DATABASE_DATASOURCE of %s, got %s", want, got)
	}
	if got, want := conf.Agent.Concurrency, 2; got != want {
		t.Errorf("Want default DRONE_AGENT_CONCURRENCY of %d, got %d", want, got)
	}
	if got, want := conf.Agent.Image, "drone/drone-runner-docker:1"; got != want {
		t.Errorf("Want default DRONE_AGENT_IMAGE of %s, got %s", want, got)
	}
}

func TestLoad(t *testing.T) {
	environ := map[string]string{
		"DRONE_INTERVAL":                   "1m",
		"DRONE_SLACK_WEBHOOK":              "https://hooks.slack.com/services/XXX/YYY/ZZZ",
		"DRONE_SLACK_CREATE":               "false",
		"DRONE_SLACK_DESTROY":              "false",
		"DRONE_LOGS_DEBUG":                 "true",
		"DRONE_LOGS_COLOR":                 "true",
		"DRONE_LOGS_PRETTY":                "true",
		"DRONE_CAPACITY_BUFFER":            "3",
		"DRONE_POOL_MIN_AGE":               "1h",
		"DRONE_POOL_MIN":                   "1",
		"DRONE_POOL_MAX":                   "5",
		"DRONE_SERVER_HOST":                "drone.company.com",
		"DRONE_SERVER_PROTO":               "http",
		"DRONE_SERVER_TOKEN":               "633eb230f5",
		"DRONE_HTTP_HOST":                  "autoscaler.drone.company.com",
		"DRONE_HTTP_PORT":                  "633eb230f5",
		"DRONE_HTTP_ROOT":                  "/autoscaler",
		"DRONE_AGENT_TOKEN":                "f5064039f5",
		"DRONE_AGENT_IMAGE":                "drone/drone-runner-docker:latest",
		"DRONE_AGENT_CONCURRENCY":          "2",
		"DRONE_AGENT_ARCH":                 "arm64",
		"DRONE_TLS_AUTOCERT":               "true",
		"DRONE_TLS_CERT":                   "/path/to/cert.crt",
		"DRONE_TLS_KEY":                    "/path/to/cert.key",
		"DRONE_PROMETHEUS_AUTH_TOKEN":      "b359e05e8",
		"DRONE_DATABASE_DRIVER":            "mysql",
		"DRONE_DATABASE_DATASOURCE":        "user:password@/dbname",
		"DRONE_DIGITALOCEAN_TOKEN":         "2573633eb",
		"DRONE_DIGITALOCEAN_IMAGE":         "docker-18-04",
		"DRONE_DIGITALOCEAN_REGION":        "ncy1",
		"DRONE_DIGITALOCEAN_SSHKEY":        "/path/to/ssh/key",
		"DRONE_DIGITALOCEAN_SIZE":          "s-1vcpu-1gb",
		"DRONE_DIGITALOCEAN_IPV6":          "true",
		"DRONE_DIGITALOCEAN_PRIVATE_IP":    "false",
		"DRONE_DIGITALOCEAN_FIREWALL":      "",
		"DRONE_DIGITALOCEAN_TAGS":          "drone,agent,prod",
		"DRONE_DIGITALOCEAN_USERDATA":      "#cloud-init",
		"DRONE_DIGITALOCEAN_USERDATA_FILE": "/path/to/cloud/init.yml",
		"DRONE_GOOGLE_ZONE":                "us-central1-b,us-central1-a",
		"DRONE_GOOGLE_MACHINE_TYPE":        "f1-micro",
		"DRONE_GOOGLE_MACHINE_IMAGE":       "ubuntu-1510-wily-v20151114",
		"DRONE_GOOGLE_DISK_TYPE":           "pd-standard",
		"DRONE_GOOGLE_NETWORK":             "default",
		"DRONE_GOOGLE_SUBNETWORK":          "",
		"DRONE_GOOGLE_PRIVATE_IP":          "false",
		"DRONE_GOOGLE_PREEMPTIBLE":         "true",
		"DRONE_GOOGLE_SCOPES":              "devstorage.read_only,pubsub",
		"DRONE_GOOGLE_DISK_SIZE":           "10",
		"DRONE_GOOGLE_PROJECT":             "project-foo",
		"DRONE_GOOGLE_TAGS":                "drone,agent,prod",
		"DRONE_GOOGLE_USERDATA":            "#cloud-init",
		"DRONE_GOOGLE_USERDATA_FILE":       "/path/to/cloud/init.yml",
		"DRONE_GOOGLE_READ_RATELIMIT":      "20",
		"DRONE_AMAZON_IMAGE":               "ami-07f84a50d2dec2fa4",
		"DRONE_AMAZON_INSTANCE":            "t3.medium",
		"DRONE_AMAZON_PRIVATE_IP":          "true",
		"DRONE_AMAZON_RETRIES":             "1",
		"DRONE_AMAZON_REGION":              "us-east-2",
		"DRONE_AMAZON_SSHKEY":              "id_rsa",
		"DRONE_AMAZON_SUBNET_ID":           "subnet-0b32177f",
		"DRONE_AMAZON_SUBNET_IDS_ALT":      "subnet-abcd,subnet-efgh",
		"DRONE_AMAZON_SECURITY_GROUP":      "sg-770eabe1",
		"DRONE_AMAZON_TAGS":                "os:linux,arch:amd64",
		"DRONE_AMAZON_USERDATA":            "#cloud-init",
		"DRONE_AMAZON_USERDATA_FILE":       "/path/to/cloud/init.yml",
		"DRONE_HETZNERCLOUD_TOKEN":         "12345678",
		"DRONE_HETZNERCLOUD_IMAGE":         "ubuntu-16.04",
		"DRONE_HETZNERCLOUD_DATACENTER":    "nbg1-dc3",
		"DRONE_HETZNERCLOUD_SSHKEY":        "12345",
		"DRONE_HETZNERCLOUD_TYPE":          "cx11",
		"DRONE_HETZNERCLOUD_USERDATA":      "#cloud-init",
		"DRONE_HETZNERCLOUD_USERDATA_FILE": "/path/to/cloud/init.yml",
		"DRONE_PACKET_APIKEY":              "12345678",
		"DRONE_PACKET_FACILITY":            "facility",
		"DRONE_PACKET_PROJECT_ID":          "project",
		"DRONE_PACKET_PLAN":                "plan",
		"DRONE_PACKET_OS":                  "ubuntu",
		"DRONE_PACKET_SSHKEY":              "id_rsa",
		"DRONE_PACKET_USERDATA":            "#cloud-init",
		"DRONE_PACKET_USERDATA_FILE":       "/path/to/cloud/init.yml",
		"DRONE_PACKET_HOSTNAME":            "agent",
		"DRONE_PACKET_TAGS":                "drone,agent,prod",
		"DRONE_OPENSTACK_NETWORK":          "my-subnet-1",
		"DRONE_OPENSTACK_IP_POOL":          "ext-ips-1",
		"DRONE_OPENSTACK_SSHKEY":           "drone-ci",
		"DRONE_OPENSTACK_SECURITY_GROUP":   "secgrp-feedface",
		"DRONE_OPENSTACK_FLAVOR":           "t1.medium",
		"DRONE_OPENSTACK_IMAGE":            "ubuntu-16.04-server-latest",
		"DRONE_OPENSTACK_METADATA":         "name:agent,owner:drone-ci",
		"DRONE_OPENSTACK_USERDATA":         "#cloud-init",
		"DRONE_OPENSTACK_USERDATA_FILE":    "/path/to/cloud/init.yml",
		"OS_REGION_NAME":                   "sto-01",
		"DRONE_WATCHTOWER_SIGNAL_ENABLED":  "false",
		"DRONE_WATCHTOWER_STOP_SIGNAL":     "",
	}

	defer func() {
		// reset the environment.
		for k := range environ {
			os.Unsetenv(k)
		}
	}()

	// set test environment variables
	for k, v := range environ {
		os.Setenv(k, v)
	}

	a := MustLoad()
	b := Config{}
	err := json.Unmarshal(jsonConfig, &b)
	if err != nil {
		t.Error(err)
		return
	}

	if !reflect.DeepEqual(a, b) {
		t.Errorf("configuration mismatch")
		pretty.Ldiff(t, a, b)
	}
}

var jsonConfig = []byte(`{
  "Interval": 60000000000,
  "CapacityBuffer": 3,
  "Timeout": {
    "Stop": 3600000000000
  },
  "Slack": {
    "Webhook": "https://hooks.slack.com/services/XXX/YYY/ZZZ",
    "Create": false,
    "Destroy": false,
    "Error": true
  },
  "Logs": {
    "Color": true,
    "Debug": true,
    "Pretty": true
  },
  "Pool": {
    "Min": 1,
    "Max": 5,
    "MinAge": 3600000000000
  },
  "Server": {
    "Host": "drone.company.com",
    "Proto": "http",
    "Token": "633eb230f5"
  },
  "Agent": {
    "OS": "linux",
    "Arch": "arm64",
    "Token": "f5064039f5",
    "Image": "drone/drone-runner-docker:latest",
    "Concurrency": 2,
    "KeepaliveTime": 360000000000,
    "KeepaliveTimeout": 30000000000,
    "NamePrefix": "agent-"
  },
  "HTTP": {
    "Proto": "http",
    "Host": "autoscaler.drone.company.com",
    "Port": "633eb230f5",
    "Root": "/autoscaler"
  },
  "UI": {
    "Realm": "Autoscaler"
  },
  "TLS": {
    "Autocert": true,
    "Cert": "/path/to/cert.crt",
    "Key": "/path/to/cert.key"
  },
  "Prometheus": {
    "AuthToken": "b359e05e8"
  },
  "Database": {
    "Driver": "mysql",
    "Datasource": "user:password@/dbname"
  },
  "DigitalOcean": {
    "Token": "2573633eb",
    "Image": "docker-18-04",
    "Region": "ncy1",
    "SSHKey": "/path/to/ssh/key",
    "Size": "s-1vcpu-1gb",
    "Tags": [
      "drone",
      "agent",
      "prod"
    ],
    "UserData": "#cloud-init",
    "UserDataFile": "/path/to/cloud/init.yml"
  },
  "Amazon": {
    "Image": "ami-07f84a50d2dec2fa4",
    "Instance": "t3.medium",
    "PrivateIP": true,
    "Retries": 1,
    "Region": "us-east-2",
    "SSHKey": "id_rsa",
    "SubnetID": "subnet-0b32177f",
    "SubnetIDsAlt": [
		"subnet-abcd",
		"subnet-efgh"
	],
    "SecurityGroup": [
      "sg-770eabe1"
    ],
    "tags": {
      "os": "linux",
      "arch": "amd64"
    },
    "UserData": "#cloud-init",
    "UserDataFile": "/path/to/cloud/init.yml"
  },
  "Google": {
    "Zone": ["us-central1-b","us-central1-a"],
    "MachineType": "f1-micro",
    "MachineImage": "ubuntu-1510-wily-v20151114",
    "DiskType": "pd-standard",
    "Address": "",
    "Network": "default",
    "Subnetwork": "",
    "Preemptible": true,
    "Scopes": [
      "devstorage.read_only",
      "pubsub"
    ],
    "DiskSize": 10,
    "Project": "project-foo",
    "Tags": [
      "drone",
      "agent",
      "prod"
    ],
    "UserData": "#cloud-init",
    "UserDataFile": "/path/to/cloud/init.yml",
    "UserDataKey": "user-data",
	"RateLimit": 20
  },
  "HetznerCloud": {
    "Token": "12345678",
    "Image": "ubuntu-16.04",
    "Datacenter": "nbg1-dc3",
    "SSHKey": 12345,
    "Type": "cx11",
    "UserData": "#cloud-init",
    "UserDataFile": "/path/to/cloud/init.yml"
  },
  "Packet": {
    "APIKey": "12345678",
    "Facility": "facility",
    "ProjectID": "project",
    "Plan": "plan",
    "OS": "ubuntu",
    "SSHKey": "id_rsa",
    "UserData": "#cloud-init",
    "UserDataFile": "/path/to/cloud/init.yml",
    "Hostname": "agent",
    "Tags": [
      "drone",
      "agent",
      "prod"
    ]
  },
  "OpenStack": {
    "Region": "sto-01",
    "Image": "ubuntu-16.04-server-latest",
    "Flavor": "t1.medium",
    "Network": "my-subnet-1",
    "Pool": "ext-ips-1",
    "SecurityGroup": [
      "secgrp-feedface"
    ],
    "SSHKey": "drone-ci",
    "Metadata": {
      "name": "agent",
      "owner": "drone-ci"
    },
    "UserData": "#cloud-init",
    "UserDataFile": "/path/to/cloud/init.yml"
  },
  "Watchtower": {
    "Image": "webhippie/watchtower",
    "Interval": 300,
    "Timeout": 7200000000000
  },
  "GC": {
    "Image": "drone/gc",
    "Interval": 1800000000000,
    "Cache": "10gb"
  },
  "Reaper": {
    "Interval": 3600000000000
  },
  "Pinger": {
    "Interval": 600000000000
  },
  "Check": {
    "Interval": 60000000000,
    "Deadline": 1800000000000
  }
}`)

func TestLoadEnvVariables(t *testing.T) {
	f, err := ioutil.TempFile("", "autoscaler-env-file-test")
	if err != nil {
		t.Error(err)
	}
	f.WriteString("ENV_FROM_FILE=FILE_VALUE")
	defer os.Remove(f.Name())

	environ := map[string]string{
		"ENV_FROM_HOST":        "HOST_VALUE",
		"DRONE_AGENT_ENVIRON":  `ENV=VALUE,ENV_FROM_HOST`,
		"DRONE_AGENT_ENV_FILE": f.Name(),
	}

	defer func() {
		// reset the environment.
		for k := range environ {
			os.Unsetenv(k)
		}
	}()

	// set test environment variables
	for k, v := range environ {
		os.Setenv(k, v)
	}

	a := MustLoad()
	want := []string{
		"ENV=VALUE",
		"ENV_FROM_HOST=HOST_VALUE",
		"ENV_FROM_FILE=FILE_VALUE",
	}
	if got, want := len(a.Agent.Environ), len(want); got != want {
		t.Errorf("Should have an environment of length %d, got %d", want, got)
	}
	for i := range a.Agent.Environ {
		if got, wantV := a.Agent.Environ[i], want[i]; got != wantV {
			t.Errorf("Wanted environ %s at index %d, got %s", wantV, i, got)
		}
	}
}


================================================
FILE: drivers/amazon/create.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package amazon

import (
	"bytes"
	"context"
	"encoding/base64"
	"fmt"
	"time"

	"github.com/drone/autoscaler"
	"github.com/drone/autoscaler/logger"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ec2"
)

type attemptOverrides struct {
	attempt int
	size    string
	subnet  string
}

func (p *provider) Create(ctx context.Context, opts autoscaler.InstanceCreateOpts) (*autoscaler.Instance, error) {
	attemptOverrides := attemptOverrides{
		attempt: 1,
		size:    p.size,
	}

	tryCreateInAllSubnets := func() (*autoscaler.Instance, error) {
		var (
			instance *autoscaler.Instance
			err      error
		)
		for _, subnet := range p.subnets {
			attemptOverrides.subnet = subnet

			instance, err = p.create(ctx, opts, attemptOverrides)
			// if the instance was provisioned (with or without errors), return the instance.
			if instance != nil {
				return instance, err
			}

			attemptOverrides.attempt++
		}

		return nil, fmt.Errorf("failed to create instance in all subnets: %w", err)
	}

	instance, err := tryCreateInAllSubnets()

	// if the instance was provisioned (with or without errors), return the instance.
	if instance != nil {
		return instance, err
	}

	// if the instance was not provisioned, and fallback
	// parameters were provided, retry using the fallback
	if p.sizeAlt != "" {
		attemptOverrides.size = p.sizeAlt
		instance, err = tryCreateInAllSubnets()
	}

	// if there is no fallback logic do not retry
	return instance, err
}

func (p *provider) create(ctx context.Context, opts autoscaler.InstanceCreateOpts, overrides attemptOverrides) (*autoscaler.Instance, error) {
	p.init.Do(func() {
		p.setup(ctx)
	})

	buf := new(bytes.Buffer)
	err := p.userdata.Execute(buf, &opts)
	if err != nil {
		return nil, err
	}

	client := p.getClient()

	var iamProfile *ec2.IamInstanceProfileSpecification

	if p.iamProfileArn != "" {
		iamProfile = &ec2.IamInstanceProfileSpecification{
			Arn: &p.iamProfileArn,
		}
	}

	var marketOptions *ec2.InstanceMarketOptionsRequest

	if p.spotInstance == true {
		marketOptions = &ec2.InstanceMarketOptionsRequest{
			MarketType: aws.String("spot"),
		}
	}

	tags := createCopy(p.tags)
	tags["Name"] = opts.Name

	var metadataOptions *ec2.InstanceMetadataOptionsRequest
	if p.imdsTokens != "" {
		metadataOptions = &ec2.InstanceMetadataOptionsRequest{
			HttpTokens: aws.String(p.imdsTokens),
		}
	}

	in := &ec2.RunInstancesInput{
		KeyName:               aws.String(p.key),
		ImageId:               aws.String(p.image),
		InstanceType:          aws.String(overrides.size),
		MinCount:              aws.Int64(1),
		MaxCount:              aws.Int64(1),
		InstanceMarketOptions: marketOptions,
		IamInstanceProfile:    iamProfile,
		UserData:              aws.String(base64.StdEncoding.EncodeToString(buf.Bytes())),
		MetadataOptions:       metadataOptions,
		NetworkInterfaces: []*ec2.InstanceNetworkInterfaceSpecification{
			{
				AssociatePublicIpAddress: aws.Bool(!p.privateIP),
				DeviceIndex:              aws.Int64(0),
				SubnetId:                 aws.String(overrides.subnet),
				Groups:                   aws.StringSlice(p.groups),
			},
		},
		TagSpecifications: []*ec2.TagSpecification{
			{
				ResourceType: aws.String("instance"),
				Tags:         convertTags(tags),
			},
			{
				ResourceType: aws.String("volume"),
				Tags:         convertTags(tags),
			},
		},
		BlockDeviceMappings: []*ec2.BlockDeviceMapping{
			{
				DeviceName: aws.String(p.deviceName),
				Ebs: &ec2.EbsBlockDevice{
					VolumeSize:          aws.Int64(p.volumeSize),
					VolumeType:          aws.String(p.volumeType),
					DeleteOnTermination: aws.Bool(true),
				},
			},
		},
	}

	if p.volumeType == "io1" || p.volumeType == "io2" || p.volumeType == "gp3" {
		for _, blockDeviceMapping := range in.BlockDeviceMappings {
			if p.volumeIops > 0 {
				blockDeviceMapping.Ebs.Iops = aws.Int64(p.volumeIops)
			}
		}
	}

	if p.volumeType == "gp3" {
		for _, blockDeviceMapping := range in.BlockDeviceMappings {
			if p.volumeThroughput > 0 {
				blockDeviceMapping.Ebs.Throughput = aws.Int64(p.volumeThroughput)
			}
		}
	}

	logger := logger.FromContext(ctx).
		WithField("attempt", overrides.attempt).
		WithField("size", overrides.size).
		WithField("subnet", overrides.subnet).
		WithField("region", p.region).
		WithField("image", p.image).
		WithField("name", opts.Name)
	logger.Debug("instance create")

	results, err := client.RunInstances(in)
	if err != nil {
		logger.WithError(err).
			Error("instance create failed")
		return nil, err
	}

	amazonInstance := results.Instances[0]

	instance := &autoscaler.Instance{
		Provider: autoscaler.ProviderAmazon,
		ID:       *amazonInstance.InstanceId,
		Name:     opts.Name,
		Size:     *amazonInstance.InstanceType,
		Region:   *amazonInstance.Placement.AvailabilityZone,
		Image:    *amazonInstance.ImageId,
	}

	logger.WithField("name", instance.Name).
		Infoln("instance create success")

	// poll the amazon endpoint for server updates
	// and exit when a network address is allocated.
	interval := time.Duration(0)
poller:
	for {
		select {
		case <-ctx.Done():
			logger.WithField("name", instance.Name).
				Debugln("instance network deadline exceeded")

			return instance, ctx.Err()
		case <-time.After(interval):
			interval = time.Minute

			logger.WithField("name", instance.Name).
				Debugln("check instance network")

			desc, err := client.DescribeInstances(
				&ec2.DescribeInstancesInput{
					InstanceIds: []*string{
						amazonInstance.InstanceId,
					},
				},
			)
			if err != nil {
				logger.WithError(err).
					Warnln("instance details failed")
				continue
			}

			if len(desc.Reservations) == 0 {
				logger.Warnln("empty reservations in details")
				continue
			}
			if len(desc.Reservations[0].Instances) == 0 {
				logger.Warnln("empty instances in reservations")
				continue
			}

			amazonInstance = desc.Reservations[0].Instances[0]

			if p.privateIP {
				if amazonInstance.PrivateIpAddress != nil {
					instance.Address = *amazonInstance.PrivateIpAddress
					break poller
				}
			}

			if amazonInstance.PublicIpAddress != nil {
				instance.Address = *amazonInstance.PublicIpAddress
				break poller
			}
		}
	}

	logger.
		WithField("name", instance.Name).
		WithField("ip", instance.Address).
		Debugln("instance network ready")

	return instance, nil
}


================================================
FILE: drivers/amazon/create_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package amazon


================================================
FILE: drivers/amazon/destroy.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package amazon

import (
	"context"
	"os"

	"github.com/drone/autoscaler"
	"github.com/drone/autoscaler/logger"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/ec2"
)

func (p *provider) Destroy(ctx context.Context, instance *autoscaler.Instance) error {
	if os.Getenv("DRONE_FLAG_ALTERNATE_DESTROY") == "true" {
		return p.destroy2(ctx, instance)
	}

	logger := logger.FromContext(ctx).
		WithField("id", instance.ID).
		WithField("ip", instance.Address).
		WithField("name", instance.Name).
		WithField("zone", instance.Region)

	logger.Debugln("terminate instance")

	input := &ec2.TerminateInstancesInput{
		InstanceIds: []*string{
			aws.String(instance.ID),
		},
	}
	_, err := p.getClient().TerminateInstances(input)
	if awsErr, ok := err.(awserr.Error); ok {
		switch awsErr.Code() {
		case ec2.UnsuccessfulInstanceCreditSpecificationErrorCodeInvalidInstanceIdMalformed:
			fallthrough
		case ec2.UnsuccessfulInstanceCreditSpecificationErrorCodeInvalidInstanceIdNotFound:
			logger.Debugln("instance does not exist")
			return autoscaler.ErrInstanceNotFound
		}
	}
	if err != nil {
		logger.WithError(err).
			Errorln("cannot terminate instance")
		return err
	}

	logger.Debugln("terminated")

	return nil
}

func (p *provider) destroy2(ctx context.Context, instance *autoscaler.Instance) error {
	logger := logger.FromContext(ctx).
		WithField("id", instance.ID).
		WithField("ip", instance.Address).
		WithField("name", instance.Name).
		WithField("zone", instance.Region)

	logger.Debugln("terminate instance")

	input := &ec2.TerminateInstancesInput{
		InstanceIds: []*string{
			aws.String(instance.ID),
		},
	}
	_, err := p.getClient().TerminateInstances(input)
	if err == nil {
		logger.Debugln("terminated")
		return nil
	}

	// if terminate instance returns an error indicating
	// the instance no longer exists, return a not found
	// error.
	if awsErr, ok := err.(awserr.Error); ok {
		switch awsErr.Code() {
		case ec2.UnsuccessfulInstanceCreditSpecificationErrorCodeInvalidInstanceIdMalformed:
			logger.Debugln("instance does not exist")
			return autoscaler.ErrInstanceNotFound
		case ec2.UnsuccessfulInstanceCreditSpecificationErrorCodeInvalidInstanceIdNotFound:
			logger.Debugln("instance does not exist")
			return autoscaler.ErrInstanceNotFound
		}
	}
	if err != nil {
		logger.WithError(err).
			Errorln("cannot terminate instance")
	}

	logger.Debugln("describe instance")

	describe := &ec2.DescribeInstancesInput{
		InstanceIds: []*string{
			aws.String(instance.ID),
		},
	}
	_, desErr := p.getClient().DescribeInstances(describe)
	// if we are able to describe the instance it confirms the
	// instance still exists and could not be terminated. Return
	// an error so that the instance is flagged as being in an
	// error state and requires manual attention.
	if desErr == nil {
		logger.Errorln("describe instance was successful. instance still exists")
		return err
	}

	// if the ware unable to describe the instance because the
	// instance no longer exists, we can return a not found error.
	// this will result in the instance being deleted from the
	// system, since we will have confirmed it no longer exists.
	if awsErr, ok := desErr.(awserr.Error); ok {
		switch awsErr.Code() {
		case ec2.UnsuccessfulInstanceCreditSpecificationErrorCodeInvalidInstanceIdMalformed:
			logger.Debugln("instance does not exist")
			return autoscaler.ErrInstanceNotFound
		case ec2.UnsuccessfulInstanceCreditSpecificationErrorCodeInvalidInstanceIdNotFound:
			logger.Debugln("instance does not exist")
			return autoscaler.ErrInstanceNotFound
		}
	}

	// otherwise we return the original error returned when
	// attempting to delete the instance.
	return err
}


================================================
FILE: drivers/amazon/destroy_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package amazon

import (
	"context"
	"os"
	"testing"

	"github.com/drone/autoscaler"

	"github.com/h2non/gock"
)

func TestDestroy(t *testing.T) {
	defer gock.Off()

	os.Setenv("AWS_ACCESS_KEY_ID", "your_access_key_id")
	os.Setenv("AWS_SECRET_ACCESS_KEY", "your_secret_access_key")
	defer func() {
		os.Unsetenv("AWS_ACCESS_KEY_ID")
		os.Unsetenv("AWS_SECRET_ACCESS_KEY")
	}()

	gock.New("https://ec2.us-east-1.amazonaws.com").
		Post("/").
		Reply(200)

	mockContext := context.TODO()
	mockInstance := &autoscaler.Instance{
		ID: "i-1234567890abcdef0",
	}

	p := New(
		WithRegion("us-east-1"),
	).(*provider)
	p.retries = 1

	err := p.Destroy(mockContext, mockInstance)
	if err != nil {
		t.Error(err)
	}
}

func TestDestroyDeleteError(t *testing.T) {
	mockContext := context.TODO()
	mockInstance := &autoscaler.Instance{
		ID: "i-1234567890abcdef0",
	}

	p := New(
		WithRegion("us-east-1"),
	).(*provider)
	p.retries = 1

	err := p.Destroy(mockContext, mockInstance)
	if err == nil {
		t.Errorf("Expect error returned from aws")
	}
}

func TestDestroyNotFound(t *testing.T) {
	defer gock.Off()

	os.Setenv("AWS_ACCESS_KEY_ID", "your_access_key_id")
	os.Setenv("AWS_SECRET_ACCESS_KEY", "your_secret_access_key")
	defer func() {
		os.Unsetenv("AWS_ACCESS_KEY_ID")
		os.Unsetenv("AWS_SECRET_ACCESS_KEY")
	}()

	gock.New("https://ec2.us-east-1.amazonaws.com").
		Post("/").
		Reply(400).
		BodyString(`<Response><Errors><Error><Code>InvalidInstanceID.NotFound</Code><Message>The instance ID 'i-1a2b3c4d' does not exist</Message></Error></Errors><RequestID>ea966190-f9aa-478e-9ede-example</RequestID></Response>`)

	mockContext := context.TODO()
	mockInstance := &autoscaler.Instance{
		ID: "i-1234567890abcdef0",
	}

	p := New(
		WithRegion("us-east-1"),
	).(*provider)
	p.retries = 1

	err := p.Destroy(mockContext, mockInstance)
	if err == nil {
		t.Errorf("Expect error returned from aws")
	}
	if err != autoscaler.ErrInstanceNotFound {
		t.Errorf("Expect instance not found returned from aws")
	}
}


================================================
FILE: drivers/amazon/option.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package amazon

import (
	"io/ioutil"

	"github.com/drone/autoscaler/drivers/internal/userdata"
)

// Option configures a Digital Ocean provider option.
type Option func(*provider)

// WithDeviceName returns an option to set the device name.
func WithDeviceName(n string) Option {
	return func(p *provider) {
		p.deviceName = n
	}
}

// WithImage returns an option to set the image.
func WithImage(image string) Option {
	return func(p *provider) {
		p.image = image
	}
}

// WithPrivateIP returns an option to set the private IP address.
func WithPrivateIP(private bool) Option {
	return func(p *provider) {
		p.privateIP = private
	}
}

// WithRetries returns an option to set the retry count.
func WithRetries(retries int) Option {
	return func(p *provider) {
		p.retries = retries
	}
}

// WithRegion returns an option to set the target region.
func WithRegion(region string) Option {
	return func(p *provider) {
		p.region = region
	}
}

// WithSecurityGroup returns an option to set the instance size.
func WithSecurityGroup(group ...string) Option {
	return func(p *provider) {
		p.groups = group
	}
}

// WithSize returns an option to set the instance size.
func WithSize(size string) Option {
	return func(p *provider) {
		p.size = size
	}
}

// WithSizeAlt returns an option to set the alternate instance
// size. If instance creation fails, the system will attempt to
// provision a second instance using the alternate size.
func WithSizeAlt(size string) Option {
	return func(p *provider) {
		p.sizeAlt = size
	}
}

// WithSSHKey returns an option to set the ssh key.
func WithSSHKey(key string) Option {
	return func(p *provider) {
		p.key = key
	}
}

// WithSubnets returns an option to set the subnet ids.
func WithSubnets(ids []string) Option {
	return func(p *provider) {
		p.subnets = ids
	}
}

// WithTags returns an option to set the image.
func WithTags(tags map[string]string) Option {
	return func(p *provider) {
		p.tags = tags
	}
}

// WithUserData returns an option to set the cloud-init
// template from text.
func WithUserData(text string) Option {
	return func(p *provider) {
		if text != "" {
			p.userdata = userdata.Parse(text)
		}
	}
}

// WithUserDataFile returns an option to set the cloud-init
// template from file.
func WithUserDataFile(filepath string) Option {
	return func(p *provider) {
		if filepath != "" {
			b, err := ioutil.ReadFile(filepath)
			if err != nil {
				panic(err)
			}
			p.userdata = userdata.Parse(string(b))
		}
	}
}

// WithVolumeSize returns an option to set the volume size
// in gigabytes.
func WithVolumeSize(s int64) Option {
	return func(p *provider) {
		p.volumeSize = s
	}
}

// WithVolumeType returns an option to set the volume type.
func WithVolumeType(t string) Option {
	return func(p *provider) {
		p.volumeType = t
	}
}

// WithVolumeIops returns an option to set the volume iops.
func WithVolumeIops(i int64) Option {
	return func(p *provider) {
		p.volumeIops = i
	}
}

// WithVolumeThroughput returns an option to set the volume throughput.
func WithVolumeThroughput(i int64) Option {
	return func(p *provider) {
		p.volumeThroughput = i
	}
}

// WithIamProfileArn returns an option to set the iam profile arn.
func WithIamProfileArn(t string) Option {
	return func(p *provider) {
		p.iamProfileArn = t
	}
}

// WithInstanceMetadataTokens returns an option to set the instance metadata service tokens requiment.
func WithInstanceMetadataTokens(t string) Option {
	return func(p *provider) {
		p.imdsTokens = t
	}
}

// WithMarketType returns an option to set the instance market type.
func WithMarketType(t string) Option {
	return func(p *provider) {
		p.spotInstance = t == "spot"
	}
}


================================================
FILE: drivers/amazon/option_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package amazon

import "testing"

func TestOptions(t *testing.T) {
	p := New(
		WithDeviceName("/dev/sda2"),
		WithImage("ami-0aab355e1bfa1e72e"),
		WithPrivateIP(true),
		WithRegion("us-west-2"),
		WithRetries(10),
		WithSecurityGroup("sg-770eabe1"),
		WithSize("t3.2xlarge"),
		WithSSHKey("id_rsa"),
		WithSubnets([]string{"subnet-0b32177f"}),
		WithTags(map[string]string{"foo": "bar", "baz": "qux"}),
		WithVolumeSize(64),
		WithVolumeType("io1"),
	).(*provider)

	if got, want := p.deviceName, "/dev/sda2"; got != want {
		t.Errorf("Want device name %q, got %q", want, got)
	}
	if got, want := p.image, "ami-0aab355e1bfa1e72e"; got != want {
		t.Errorf("Want image %q, got %q", want, got)
	}
	if got, want := p.region, "us-west-2"; got != want {
		t.Errorf("Want region %q, got %q", want, got)
	}
	if got, want := p.size, "t3.2xlarge"; got != want {
		t.Errorf("Want size %q, got %q", want, got)
	}
	if got, want := p.key, "id_rsa"; got != want {
		t.Errorf("Want key %q, got %q", want, got)
	}
	if got, want := p.groups[0], "sg-770eabe1"; got != want {
		t.Errorf("Want security groups %q, got %q", want, got)
	}
	if got, want := p.subnets, []string{"subnet-0b32177f"}; len(got) != 1 || got[0] != want[0] {
		t.Errorf("Want subnet %q, got %q", want, got)
	}
	if got, want := p.retries, 10; got != want {
		t.Errorf("Want %d retries, got %d", want, got)
	}
	if got, want := p.privateIP, true; got != want {
		t.Errorf("Want %v privateIP, got %v", want, got)
	}
	if got, want := len(p.tags), 2; got != want {
		t.Errorf("Want %d tags, got %d", want, got)
	}
	if got, want := p.volumeSize, int64(64); got != want {
		t.Errorf("Want volume size %d, got %d", want, got)
	}
	if got, want := p.volumeType, "io1"; got != want {
		t.Errorf("Want volume type %q, got %q", want, got)
	}
}


================================================
FILE: drivers/amazon/provider.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package amazon

import (
	"sync"
	"text/template"

	"github.com/drone/autoscaler"
	"github.com/drone/autoscaler/drivers/internal/userdata"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ec2"
)

type provider struct {
	init sync.Once

	deviceName       string
	volumeSize       int64
	volumeType       string
	volumeIops       int64
	volumeThroughput int64
	retries          int
	key              string
	region           string
	image            string
	privateIP        bool
	userdata         *template.Template
	size             string
	sizeAlt          string
	subnets          []string
	groups           []string
	tags             map[string]string
	iamProfileArn    string
	spotInstance     bool
	imdsTokens       string
}

func (p *provider) getClient() *ec2.EC2 {
	config := aws.NewConfig()
	config = config.WithRegion(p.region)
	config = config.WithMaxRetries(p.retries)
	session, _ := session.NewSession(config)
	return ec2.New(session)
}

// New returns a new Digital Ocean provider.
func New(opts ...Option) autoscaler.Provider {
	p := new(provider)
	for _, opt := range opts {
		opt(p)
	}
	if p.retries == 0 {
		p.retries = 10
	}
	if p.region == "" {
		p.region = "us-east-1"
	}
	if p.size == "" {
		p.size = "t3.medium"
	}
	if p.image == "" {
		p.image = defaultImage(p.region)
	}
	if p.deviceName == "" {
		p.deviceName = "/dev/sda1"
	}
	if p.volumeSize == 0 {
		p.volumeSize = 32
	}
	if p.volumeType == "" {
		p.volumeType = "gp2"
	}
	if (p.volumeType == "io1" || p.volumeType == "io2") && p.volumeIops == 0 {
		p.volumeIops = 100
	}
	if p.volumeType == "gp3" && p.volumeIops == 0 {
		p.volumeIops = 3000 // 3000 is the minimum for gp3
	}
	if p.volumeType == "gp3" && p.volumeThroughput == 0 {
		p.volumeThroughput = 125 // 125 is the minimum for gp3
	}
	if p.userdata == nil {
		p.userdata = userdata.T
	}
	return p
}


================================================
FILE: drivers/amazon/provider_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package amazon


================================================
FILE: drivers/amazon/setup.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package amazon

import (
	"context"
	"errors"

	"github.com/drone/autoscaler/logger"

	"github.com/aws/aws-sdk-go/service/ec2"
	"golang.org/x/sync/errgroup"
)

func (p *provider) setup(ctx context.Context) error {
	var g errgroup.Group
	if p.key == "" {
		g.Go(func() error {
			return p.setupKeypair(ctx)
		})
	}
	if len(p.subnets) == 0 {
		// TODO: find or create subnet
	}
	if len(p.groups) == 0 {
		// TODO: find or create security groups
	}
	return g.Wait()
}

func (p *provider) setupKeypair(ctx context.Context) error {
	logger := logger.FromContext(ctx)

	logger.Debugln("finding default ssh key")

	opts := new(ec2.DescribeKeyPairsInput)
	keys, err := p.getClient().DescribeKeyPairs(opts)
	if err != nil {
		return err
	}

	index := map[string]string{}
	for _, key := range keys.KeyPairs {
		index[*key.KeyName] = *key.KeyFingerprint
	}

	// if the account has multiple keys configured we will
	// attempt to use an existing key based on naming convention.
	for _, name := range []string{"drone", "id_rsa_drone"} {
		fingerprint, ok := index[name]
		if !ok {
			continue
		}
		p.key = name

		logger.
			WithField("name", name).
			WithField("fingerprint", fingerprint).
			Debugln("using default ssh key")
		return nil
	}

	// if there were no matches but the account has at least
	// one keypair already created we will select the first
	// in the list.
	if len(keys.KeyPairs) > 0 {
		key := keys.KeyPairs[0]
		p.key = *key.KeyName

		logger.
			WithField("name", *key.KeyName).
			WithField("fingerprint", *key.KeyFingerprint).
			Debugln("using default ssh key")
		return nil
	}

	return errors.New("No matching keys")
}


================================================
FILE: drivers/amazon/setup_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package amazon


================================================
FILE: drivers/amazon/util.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package amazon

import (
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ec2"
)

// helper function converts an array of tags in string
// format to an array of ec2 tags.
func convertTags(in map[string]string) []*ec2.Tag {
	var out []*ec2.Tag
	for k, v := range in {
		out = append(out, &ec2.Tag{
			Key:   aws.String(k),
			Value: aws.String(v),
		})
	}
	return out
}

// helper function creates a copy of map[string]string
func createCopy(in map[string]string) map[string]string {
	out := map[string]string{}
	for k, v := range in {
		out[k] = v
	}
	return out
}

// helper function returns the default image based on the
// selected region.
func defaultImage(region string) string {
	return images[region]
}

// static ami id list for Ubuntu Server 20.04 LTS
// source: https://cloud-images.ubuntu.com/locator/
// filters:
// - Cloud: Amazon AWS, Amazon GovCloud, Amazon AWS China
// - Version: 20.04
// - Instance Type: hvm-ssd
var images = map[string]string{
	// AWS Regions: Ubuntu Server 20.04 LTS
	// Upstream release version: 20220706
	"af-south-1":     "ami-0f5298ccab965edeb",
	"ap-east-1":      "ami-0dfad1f1f65cd083b",
	"ap-northeast-1": "ami-0986c991cc80c6ad9",
	"ap-northeast-2": "ami-0565d651769eb3de5",
	"ap-northeast-3": "ami-0e6078093a109801c",
	"ap-south-1":     "ami-0325e3016099f9112",
	"ap-southeast-1": "ami-0eaf04122a1ae7b3b",
	"ap-southeast-2": "ami-048a2d001938101dd",
	"ap-southeast-3": "ami-09915141a4f1dafdd",
	"ca-central-1":   "ami-04a579d2f00bb4001",
	"eu-central-1":   "ami-06cac34c3836ff90b",
	"eu-north-1":     "ami-0ede84a5f28ec932a",
	"eu-south-1":     "ami-0a39f417b8836bc59",
	"eu-west-1":      "ami-0141514361b6a3c1b",
	"eu-west-2":      "ami-014b642f603e350c3",
	"eu-west-3":      "ami-0d0b8d91779dec1e5",
	"me-south-1":     "ami-0c769d841005394ee",
	"sa-east-1":      "ami-088afbba294231fe0",
	"us-east-1":      "ami-0070c5311b7677678",
	"us-east-2":      "ami-07f84a50d2dec2fa4",
	"us-west-1":      "ami-040a251ee9d7d1a9b",
	"us-west-2":      "ami-0aab355e1bfa1e72e",

	// AWS GovCloud (US): Ubuntu Server 20.04 LTS
	// Upstream release version: 20220627.1
	"us-gov-east-1": "ami-0d8ee446ec886f5cf",
	"us-gov-west-1": "ami-0cbaf57cea1d72aec",

	// AWS China: Ubuntu Server 20.04 LTS
	// Upstream release version: 20210720
	"cn-north-1":     "ami-0741e7b8b4fb0001c",
	"cn-northwest-1": "ami-0883e8062ff31f727",
}


================================================
FILE: drivers/amazon/util_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package amazon

import (
	"reflect"
	"testing"

	"github.com/kr/pretty"
)

func TestConvertTags(t *testing.T) {
	a := map[string]string{"foo": "bar", "baz": "qux"}
	b := map[string]string{}

	tags := convertTags(a)

	if got, want := len(tags), 2; got != want {
		t.Errorf("Want %d tags, got %d", want, got)
	}

	for _, tag := range tags {
		b[*tag.Key] = *tag.Value
	}

	if !reflect.DeepEqual(a, b) {
		t.Errorf("unexpected tag conversion")
		pretty.Ldiff(t, a, b)
	}
}


================================================
FILE: drivers/azure/create.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package azure


================================================
FILE: drivers/azure/create_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package azure


================================================
FILE: drivers/azure/destroy.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package azure


================================================
FILE: drivers/azure/destroy_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package azure


================================================
FILE: drivers/azure/option.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package azure


================================================
FILE: drivers/azure/option_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package azure


================================================
FILE: drivers/azure/provider.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package azure


================================================
FILE: drivers/azure/provider_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package azure


================================================
FILE: drivers/digitalocean/create.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package digitalocean

import (
	"bytes"
	"context"
	"strconv"
	"time"

	"github.com/drone/autoscaler"
	"github.com/drone/autoscaler/logger"

	"github.com/digitalocean/godo"
)

func (p *provider) Create(ctx context.Context, opts autoscaler.InstanceCreateOpts) (*autoscaler.Instance, error) {
	p.init.Do(func() {
		p.setup(ctx)
	})

	buf := new(bytes.Buffer)
	err := p.userdata.Execute(buf, &opts)
	if err != nil {
		return nil, err
	}

	req := &godo.DropletCreateRequest{
		Name:              opts.Name,
		Region:            p.region,
		Size:              p.size,
		Tags:              p.tags,
		IPv6:              false,
		PrivateNetworking: p.privateIP,
		UserData:          buf.String(),

		SSHKeys: []godo.DropletCreateSSHKey{
			{Fingerprint: p.key},
		},
		Image: godo.DropletCreateImage{
			Slug: p.image,
		},
	}

	logger := logger.FromContext(ctx).
		WithField("region", req.Region).
		WithField("image", req.Image.Slug).
		WithField("size", req.Size).
		WithField("name", req.Name)

	logger.Debugln("instance create")

	client := newClient(ctx, p.token)
	droplet, _, err := client.Droplets.Create(ctx, req)
	if err != nil {
		logger.WithError(err).
			Errorln("cannot create instance")
		return nil, err
	}

	if p.firewall != "" {
		_, err := client.Firewalls.AddDroplets(ctx, p.firewall, droplet.ID)
		if err != nil {
			logger.WithError(err).
				Errorln("cannot assign instance to firewall")
			return nil, err
		}
	}

	instance := &autoscaler.Instance{
		Provider: autoscaler.ProviderDigitalOcean,
		ID:       strconv.Itoa(droplet.ID),
		Name:     droplet.Name,
		Size:     req.Size,
		Region:   req.Region,
		Image:    req.Image.Slug,
	}

	logger.WithField("name", instance.Name).
		Infoln("instance created")

	// poll the digitalocean endpoint for server updates
	// and exit when a network address is allocated.
	interval := time.Duration(0)
poller:
	for {
		select {
		case <-ctx.Done():
			logger.WithField("name", instance.Name).
				Debugln("cannot ascertain network")

			return instance, ctx.Err()
		case <-time.After(interval):
			interval = time.Minute

			logger.WithField("name", instance.Name).
				Debugln("find instance network")

			droplet, _, err = client.Droplets.Get(ctx, droplet.ID)
			if err != nil {
				logger.WithError(err).
					Errorln("cannot find instance")
				return instance, err
			}

			for _, network := range droplet.Networks.V4 {
				if network.Type == "public" {
					instance.Address = network.IPAddress
				}
			}

			if instance.Address != "" {
				break poller
			}
		}
	}

	logger.
		WithField("name", instance.Name).
		WithField("ip", instance.Address).
		Debugln("instance network ready")

	return instance, nil
}


================================================
FILE: drivers/digitalocean/create_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package digitalocean

import (
	"context"
	"testing"
	"time"

	"github.com/digitalocean/godo"
	"github.com/drone/autoscaler"

	"github.com/h2non/gock"
)

func TestCreate(t *testing.T) {
	defer gock.Off()

	gock.New("https://api.digitalocean.com").
		Post("/v2/droplets").
		Reply(200).
		BodyString(respDropletCreate)

	gock.New("https://api.digitalocean.com").
		Get("/v2/droplets/3164494").
		Reply(200).
		BodyString(respDropletDesc)

	p := New(
		WithSSHKey("58:8e:30:66:fc:e2:ff:ad:4f:6f:02:4b:af:28:0d:c7"),
		WithToken("77e027c7447f468068a7d4fea41e7149a75a94088082c66fcf555de3977f69d3"),
	).(*provider)
	p.init.Do(func() {}) // prevent init function

	instance, err := p.Create(context.TODO(), autoscaler.InstanceCreateOpts{Name: "agent1"})
	if err != nil {
		t.Error(err)
	}

	if !gock.IsDone() {
		t.Errorf("Expected http requests not detected")
	}

	t.Run("Attributes", testInstance(instance))
}

func TestCreate_CreateError(t *testing.T) {
	defer gock.Off()

	gock.New("https://api.digitalocean.com").
		Post("/v2/droplets").
		Reply(500)

	p := New(
		WithSSHKey("58:8e:30:66:fc:e2:ff:ad:4f:6f:02:4b:af:28:0d:c7"),
		WithToken("77e027c7447f468068a7d4fea41e7149a75a94088082c66fcf555de3977f69d3"),
	).(*provider)
	p.init.Do(func() {}) // prevent init function

	_, err := p.Create(context.TODO(), autoscaler.InstanceCreateOpts{Name: "agent1"})
	if err == nil {
		t.Errorf("Expect error returned from digital ocean")
	} else if _, ok := err.(*godo.ErrorResponse); !ok {
		t.Errorf("Expect ErrorResponse digital ocean")
	}

	if !gock.IsDone() {
		t.Errorf("Expected http requests not detected")
	}
}

func TestCreate_DescribeError(t *testing.T) {
	defer gock.Off()

	gock.New("https://api.digitalocean.com").
		Post("/v2/droplets").
		Reply(200).
		BodyString(respDropletCreate)

	gock.New("https://api.digitalocean.com").
		Get("/v2/droplets/3164494").
		Reply(500)

	p := New(
		WithSSHKey("58:8e:30:66:fc:e2:ff:ad:4f:6f:02:4b:af:28:0d:c7"),
		WithToken("77e027c7447f468068a7d4fea41e7149a75a94088082c66fcf555de3977f69d3"),
	).(*provider)
	p.init.Do(func() {}) // prevent init function

	instance, err := p.Create(context.TODO(), autoscaler.InstanceCreateOpts{Name: "agent1"})
	if err == nil {
		t.Errorf("Expect error returned from digital ocean")
	} else if _, ok := err.(*godo.ErrorResponse); !ok {
		t.Errorf("Expect ErrorResponse digital ocean")
	}

	if !gock.IsDone() {
		t.Errorf("Expected http requests not detected")
	}

	t.Run("Attributes", testInstance(instance))
}

func TestCreate_DescribeTimeout(t *testing.T) {
	defer gock.Off()

	gock.New("https://api.digitalocean.com").
		Post("/v2/droplets").
		Reply(200).
		BodyString(respDropletCreate)

	gock.New("https://api.digitalocean.com").
		Get("/v2/droplets/3164494").
		Reply(200).
		BodyString(respDropletCreate) // no network data

	p := New(
		WithSSHKey("58:8e:30:66:fc:e2:ff:ad:4f:6f:02:4b:af:28:0d:c7"),
		WithToken("77e027c7447f468068a7d4fea41e7149a75a94088082c66fcf555de3977f69d3"),
	).(*provider)
	p.init.Do(func() {}) // prevent init function

	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()

	instance, err := p.Create(ctx, autoscaler.InstanceCreateOpts{Name: "agent1"})
	if err == nil {
		t.Errorf("Expected context deadline exceeded, got nil")
	} else if err.Error() != "context deadline exceeded" {
		t.Errorf("Expected context deadline exceeded, got %s", err)
	}

	if !gock.IsDone() {
		t.Errorf("Expected http requests not detected")
	}

	t.Run("Attributes", testInstance(instance))
}

func testInstance(instance *autoscaler.Instance) func(t *testing.T) {
	return func(t *testing.T) {
		if instance == nil {
			t.Errorf("Expect non-nil instance even if error")
		}
		if got, want := instance.ID, "3164494"; got != want {
			t.Errorf("Want droplet ID %v, got %v", want, got)
		}
		if got, want := instance.Image, "docker-18-04"; got != want {
			t.Errorf("Want droplet Image %v, got %v", want, got)
		}
		if got, want := instance.Name, "example.com"; got != want {
			t.Errorf("Want droplet Name %v, got %v", want, got)
		}
		if got, want := instance.Region, "nyc1"; got != want {
			t.Errorf("Want droplet Region %v, got %v", want, got)
		}
		if got, want := instance.Provider, autoscaler.ProviderDigitalOcean; got != want {
			t.Errorf("Want droplet Provider %v, got %v", want, got)
		}
	}
}

func testInstanceAddress(instance *autoscaler.Instance) func(t *testing.T) {
	return func(t *testing.T) {
		if instance == nil {
			t.Errorf("Expect non-nil instance even if error")
		}
		if got, want := instance.Address, "104.131.186.241"; got != want {
			t.Errorf("Want droplet Address %v, got %v", want, got)
		}
	}
}

// sample response for POST /v2/droplets
const respDropletCreate = `
{
  "droplet": {
    "id": 3164494,
    "name": "example.com",
    "memory": 1024,
    "vcpus": 1,
    "disk": 25,
    "locked": true,
    "status": "new",
    "kernel": {
      "id": 2233,
      "name": "Ubuntu 14.04 x64 vmlinuz-3.13.0-37-generic",
      "version": "3.13.0-37-generic"
    },
    "created_at": "2014-11-14T16:36:31Z",
    "features": [
      "virtio"
    ],
    "backup_ids": [
      
    ],
    "snapshot_ids": [
      
    ],
    "image": {
      
    },
    "volume_ids": [
      
    ],
    "size": {
      
    },
    "size_slug": "s-1vcpu-1gb",
    "networks": {

    },
    "region": {
      
    },
    "tags": [
      "web"
    ]
  },
  "links": {
    "actions": [
      {
        "id": 36805096,
        "rel": "create",
        "href": "https:\/\/api.digitalocean.com\/v2\/actions\/36805096"
      }
    ]
  }
}
`

// sample response for POST /v2/droplets/:id
const respDropletDesc = `
{
  "droplet": {
    "id": 3164494,
    "name": "example.com",
    "memory": 1024,
    "vcpus": 1,
    "disk": 25,
    "locked": true,
    "status": "new",
    "kernel": {
      "id": 2233,
      "name": "Ubuntu 14.04 x64 vmlinuz-3.13.0-37-generic",
      "version": "3.13.0-37-generic"
    },
    "created_at": "2014-11-14T16:36:31Z",
    "features": [
      "virtio"
    ],
    "backup_ids": [
      
    ],
    "snapshot_ids": [
      
    ],
    "image": {
      
    },
    "volume_ids": [
      
    ],
    "size": {
      
    },
    "size_slug": "s-1vcpu-1gb",
    "networks": {
      "v4": [
        {
          "ip_address": "104.131.186.241",
          "netmask": "255.255.240.0",
          "gateway": "104.131.176.1",
          "type": "public"
        }
      ]
    },
    "region": {
      
    },
    "tags": [
      "web"
    ]
  },
  "links": {
    "actions": [
      {
        "id": 36805096,
        "rel": "create",
        "href": "https:\/\/api.digitalocean.com\/v2\/actions\/36805096"
      }
    ]
  }
}
`


================================================
FILE: drivers/digitalocean/destroy.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package digitalocean

import (
	"context"
	"strconv"

	"github.com/drone/autoscaler"
	"github.com/drone/autoscaler/logger"
)

func (p *provider) Destroy(ctx context.Context, instance *autoscaler.Instance) error {
	logger := logger.FromContext(ctx).
		WithField("region", instance.Region).
		WithField("image", instance.Image).
		WithField("size", instance.Size).
		WithField("name", instance.Name)

	client := newClient(ctx, p.token)
	id, err := strconv.Atoi(instance.ID)
	if err != nil {
		return err
	}

	_, res, err := client.Droplets.Get(ctx, id)
	if err != nil && res.StatusCode == 404 {
		logger.WithError(err).
			Warnln("droplet does not exist")
		return autoscaler.ErrInstanceNotFound
	} else if err != nil {
		logger.WithError(err).
			Errorln("cannot find droplet")
		return err
	}

	logger.Debugln("deleting droplet")

	_, err = client.Droplets.Delete(ctx, id)
	if err != nil {
		logger.WithError(err).
			Errorln("deleting droplet failed")
		return err
	}

	logger.Debugln("droplet deleted")

	return nil
}


================================================
FILE: drivers/digitalocean/destroy_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package digitalocean

import (
	"context"
	"strconv"
	"testing"

	"github.com/digitalocean/godo"
	"github.com/drone/autoscaler"

	"github.com/golang/mock/gomock"
	"github.com/h2non/gock"
)

func TestDestroy(t *testing.T) {
	defer gock.Off()

	gock.New("https://api.digitalocean.com").
		Get("/v2/droplets/3164494").
		Reply(200).
		BodyString(respDropletCreate)

	gock.New("https://api.digitalocean.com").
		Delete("/v2/droplets/3164494").
		Reply(204)

	mockContext := context.TODO()
	mockInstance := &autoscaler.Instance{
		ID: "3164494",
	}

	p := New(
		WithSSHKey("58:8e:30:66:fc:e2:ff:ad:4f:6f:02:4b:af:28:0d:c7"),
		WithToken("77e027c7447f468068a7d4fea41e7149a75a94088082c66fcf555de3977f69d3"),
	)

	err := p.Destroy(mockContext, mockInstance)
	if err != nil {
		t.Error(err)
	}

	if !gock.IsDone() {
		t.Errorf("Expected http requests not detected")
	}
}

func TestDestroyDeleteError(t *testing.T) {
	controller := gomock.NewController(t)
	defer controller.Finish()

	defer gock.Off()

	gock.New("https://api.digitalocean.com").
		Get("/v2/droplets/3164494").
		Reply(200).
		BodyString(respDropletCreate)

	gock.New("https://api.digitalocean.com").
		Delete("/v2/droplets/3164494").
		Reply(500)

	mockContext := context.TODO()
	mockInstance := &autoscaler.Instance{
		ID: "3164494",
	}

	p := New(
		WithSSHKey("58:8e:30:66:fc:e2:ff:ad:4f:6f:02:4b:af:28:0d:c7"),
		WithToken("77e027c7447f468068a7d4fea41e7149a75a94088082c66fcf555de3977f69d3"),
	)

	err := p.Destroy(mockContext, mockInstance)
	if err == nil {
		t.Errorf("Expect error returned from digital ocean")
	} else if _, ok := err.(*godo.ErrorResponse); !ok {
		t.Errorf("Expect ErrorResponse digital ocean")
	}

	if !gock.IsDone() {
		t.Errorf("Expected http requests not detected")
	}
}

func TestDestroyFindError(t *testing.T) {
	controller := gomock.NewController(t)
	defer controller.Finish()

	defer gock.Off()

	gock.New("https://api.digitalocean.com").
		Get("/v2/droplets/3164494").
		Reply(500)

	mockContext := context.TODO()
	mockInstance := &autoscaler.Instance{
		ID: "3164494",
	}

	p := New(
		WithSSHKey("58:8e:30:66:fc:e2:ff:ad:4f:6f:02:4b:af:28:0d:c7"),
		WithToken("77e027c7447f468068a7d4fea41e7149a75a94088082c66fcf555de3977f69d3"),
	)

	err := p.Destroy(mockContext, mockInstance)
	if err == nil {
		t.Errorf("Expect error returned from digital ocean")
	} else if _, ok := err.(*godo.ErrorResponse); !ok {
		t.Errorf("Expect ErrorResponse digital ocean")
	}

	if !gock.IsDone() {
		t.Errorf("Expected http requests not detected")
	}
}

func TestDestroyNotFound(t *testing.T) {
	controller := gomock.NewController(t)
	defer controller.Finish()

	defer gock.Off()

	gock.New("https://api.digitalocean.com").
		Get("/v2/droplets/3164494").
		Reply(404)

	mockContext := context.TODO()
	mockInstance := &autoscaler.Instance{
		ID: "3164494",
	}

	p := New(
		WithSSHKey("58:8e:30:66:fc:e2:ff:ad:4f:6f:02:4b:af:28:0d:c7"),
		WithToken("77e027c7447f468068a7d4fea41e7149a75a94088082c66fcf555de3977f69d3"),
	)

	err := p.Destroy(mockContext, mockInstance)
	if err == nil {
		t.Errorf("Expect error returned from digital ocean")
	} else if err != autoscaler.ErrInstanceNotFound {
		t.Errorf("Expect ErrInstanceNotFound")
	}

	if !gock.IsDone() {
		t.Errorf("Expected http requests not detected")
	}
}

func TestDestroyInvalidInput(t *testing.T) {
	i := &autoscaler.Instance{}
	p := provider{}
	err := p.Destroy(context.TODO(), i)
	if _, ok := err.(*strconv.NumError); !ok {
		t.Errorf("Expected invalid or missing ID error")
	}
}


================================================
FILE: drivers/digitalocean/option.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package digitalocean

import (
	"io/ioutil"

	"github.com/drone/autoscaler/drivers/internal/userdata"
)

// Option configures a Digital Ocean provider option.
type Option func(*provider)

// WithImage returns an option to set the image.
func WithImage(image string) Option {
	return func(p *provider) {
		p.image = image
	}
}

// WithRegion returns an option to set the target region.
func WithRegion(region string) Option {
	return func(p *provider) {
		p.region = region
	}
}

// WithSize returns an option to set the instance size.
func WithSize(size string) Option {
	return func(p *provider) {
		p.size = size
	}
}

// WithSSHKey returns an option to set the ssh key.
func WithSSHKey(key string) Option {
	return func(p *provider) {
		p.key = key
	}
}

// WithTags returns an option to set the image.
func WithTags(tags ...string) Option {
	return func(p *provider) {
		p.tags = tags
	}
}

// WithToken returns an option to set the auth token.
func WithToken(token string) Option {
	return func(p *provider) {
		p.token = token
	}
}

// WithFirewall returns an option to set the droplet firewall.
func WithFirewall(firewall string) Option {
	return func(p *provider) {
		p.firewall = firewall
	}
}

// WithPrivateIP returns an option to set the private IP address.
func WithPrivateIP(private bool) Option {
	return func(p *provider) {
		p.privateIP = private
	}
}

// WithUserData returns an option to set the cloud-init
// template from text.
func WithUserData(text string) Option {
	return func(p *provider) {
		if text != "" {
			p.userdata = userdata.Parse(text)
		}
	}
}

// WithUserDataFile returns an option to set the cloud-init
// template from file.
func WithUserDataFile(filepath string) Option {
	return func(p *provider) {
		if filepath != "" {
			b, err := ioutil.ReadFile(filepath)
			if err != nil {
				panic(err)
			}
			p.userdata = userdata.Parse(string(b))
		}
	}
}


================================================
FILE: drivers/digitalocean/option_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package digitalocean

import "testing"

func TestOptions(t *testing.T) {
	p := New(
		WithImage("ubuntu-18-04-x64"),
		WithRegion("nyc3"),
		WithSize("s-8vcpu-32gb"),
		WithSSHKey("58:8e:30:66:fc:e2:ff:ad:4f:6f:02:4b:af:28:0d:c7"),
		WithTags("drone", "agent"),
		WithFirewall("f33e7128-f3e7-4229-b6cc-a4751381a104"),
		WithToken("77e027c7447f468068a7d4fea41e7149a75a94088082c66fcf555de3977f69d3"),
		WithPrivateIP(false),
	).(*provider)

	if got, want := p.image, "ubuntu-18-04-x64"; got != want {
		t.Errorf("Want image %q, got %q", want, got)
	}
	if got, want := p.region, "nyc3"; got != want {
		t.Errorf("Want region %q, got %q", want, got)
	}
	if got, want := p.size, "s-8vcpu-32gb"; got != want {
		t.Errorf("Want size %q, got %q", want, got)
	}
	if got, want := p.key, "58:8e:30:66:fc:e2:ff:ad:4f:6f:02:4b:af:28:0d:c7"; got != want {
		t.Errorf("Want key %q, got %q", want, got)
	}
	if got, want := p.token, "77e027c7447f468068a7d4fea41e7149a75a94088082c66fcf555de3977f69d3"; got != want {
		t.Errorf("Want token %q, got %q", want, got)
	}
	if got, want := p.firewall, "f33e7128-f3e7-4229-b6cc-a4751381a104"; got != want {
		t.Errorf("Want token %q, got %q", want, got)
	}
	if got, want := p.privateIP, false; got != want {
		t.Errorf("Want %v privateIP, got %v", want, got)
	}
	if got, want := len(p.tags), 2; got != want {
		t.Errorf("Want %d tags, got %d", want, got)
	}
}


================================================
FILE: drivers/digitalocean/provider.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package digitalocean

import (
	"context"
	"sync"
	"text/template"

	"github.com/drone/autoscaler"

	"github.com/digitalocean/godo"
	"golang.org/x/oauth2"
)

// provider implements a DigitalOcean provider.
type provider struct {
	init sync.Once

	key       string
	region    string
	token     string
	size      string
	image     string
	firewall  string
	privateIP bool
	userdata  *template.Template
	tags      []string
}

// New returns a new Digital Ocean provider.
func New(opts ...Option) autoscaler.Provider {
	p := new(provider)
	for _, opt := range opts {
		opt(p)
	}
	if p.region == "" {
		p.region = "nyc1"
	}
	if p.size == "" {
		p.size = "s-2vcpu-4gb"
	}
	if p.image == "" {
		p.image = "docker-18-04"
	}
	if p.userdata == nil {
		p.userdata = userdataT
	}
	return p
}

// helper function returns a new digitalocean client.
func newClient(ctx context.Context, token string) *godo.Client {
	return godo.NewClient(
		oauth2.NewClient(ctx, oauth2.StaticTokenSource(
			&oauth2.Token{
				AccessToken: token,
			},
		)),
	)
}


================================================
FILE: drivers/digitalocean/provider_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package digitalocean

import "testing"

func TestDefaults(t *testing.T) {
	p := New().(*provider)
	if got, want := p.image, "docker-18-04"; got != want {
		t.Errorf("Want image %q, got %q", want, got)
	}
	if got, want := p.region, "nyc1"; got != want {
		t.Errorf("Want region %q, got %q", want, got)
	}
	if got, want := p.size, "s-2vcpu-4gb"; got != want {
		t.Errorf("Want size %q, got %q", want, got)
	}
	if got, want := p.key, ""; got != want {
		t.Errorf("Want key %q, got %q", want, got)
	}
	if got, want := p.token, ""; got != want {
		t.Errorf("Want token %q, got %q", want, got)
	}
	if got, want := len(p.tags), 0; got != want {
		t.Errorf("Want %d tags, got %d", want, got)
	}
}


================================================
FILE: drivers/digitalocean/setup.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package digitalocean

import (
	"context"
	"errors"

	"github.com/digitalocean/godo"
	"github.com/drone/autoscaler/logger"

	"golang.org/x/sync/errgroup"
)

func (p *provider) setup(ctx context.Context) error {
	var g errgroup.Group
	if p.key == "" {
		g.Go(func() error {
			return p.setupKeypair(ctx)
		})
	}
	return g.Wait()
}

func (p *provider) setupKeypair(ctx context.Context) error {
	logger := logger.FromContext(ctx)

	logger.Debugln("finding default ssh key")

	client := newClient(ctx, p.token)
	keys, _, err := client.Keys.List(ctx, &godo.ListOptions{})
	if err != nil {
		return err
	}

	index := map[string]string{}
	for _, key := range keys {
		index[key.Name] = key.Fingerprint
	}

	// if the account has multiple keys configured we will
	// attempt to use an existing key based on naming convention.
	for _, name := range []string{"drone", "id_rsa_drone"} {
		fingerprint, ok := index[name]
		if !ok {
			continue
		}
		p.key = fingerprint

		logger.
			WithField("name", name).
			WithField("fingerprint", fingerprint).
			Debugln("using default ssh key")
		return nil
	}

	// if there were no matches but the account has at least
	// one keypair already created we will select the first
	// in the list.
	if len(keys) > 0 {
		key := keys[0]
		p.key = key.Fingerprint

		logger.
			WithField("name", key.Name).
			WithField("fingerprint", key.Fingerprint).
			Debugln("using default ssh key")
		return nil
	}

	return errors.New("No matching keys")
}


================================================
FILE: drivers/digitalocean/setup_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package digitalocean

import (
	"context"
	"testing"

	"github.com/h2non/gock"
)

func TestSetupKey_Single(t *testing.T) {
	defer gock.Off()

	gock.New("https://api.digitalocean.com").
		Get("/v2/account/keys").
		Reply(200).
		BodyString(respSingleKey)

	p := New(
		WithToken("77e027c7447f468068a7d4fea41e7149a75a94088082c66fcf555de3977f69d3"),
	).(*provider)

	err := p.setup(context.TODO())
	if err != nil {
		t.Error(err)
	}

	if got, want := p.key, "3b:16:bf:e4:8b:00:8b:b8:59:8c:a9:d3:f0:19:45:fa"; got != want {
		t.Errorf("Want fingerprint %s, got %s", want, got)
	}
}

func TestSetupKey_FoundMatch(t *testing.T) {
	defer gock.Off()

	gock.New("https://api.digitalocean.com").
		Get("/v2/account/keys").
		Reply(200).
		BodyString(respMultiKey)

	p := New(
		WithToken("77e027c7447f468068a7d4fea41e7149a75a94088082c66fcf555de3977f69d3"),
	).(*provider)

	err := p.setup(context.TODO())
	if err != nil {
		t.Error(err)
	}

	if got, want := p.key, "00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff"; got != want {
		t.Errorf("Want fingerprint %s, got %s", want, got)
	}

	if !gock.IsDone() {
		t.Errorf("Expected http requests not detected")
	}
}

func TestSetupKey_NoMatch(t *testing.T) {
	defer gock.Off()

	gock.New("https://api.digitalocean.com").
		Get("/v2/account/keys").
		Reply(200).
		BodyString(respMultiKeyNoMatch)

	p := New(
		WithToken("77e027c7447f468068a7d4fea41e7149a75a94088082c66fcf555de3977f69d3"),
	).(*provider)

	err := p.setup(context.TODO())
	if err != nil {
		t.Error(err)
	}

	if got, want := p.key, "3b:16:bf:e4:8b:00:8b:b8:59:8c:a9:d3:f0:19:45:fa"; got != want {
		t.Errorf("Want fingerprint %s, got %s", want, got)
	}

	if !gock.IsDone() {
		t.Errorf("Expected http requests not detected")
	}
}

var respSingleKey = `
{
  "ssh_keys": [
    {
      "id": 512189,
      "fingerprint": "3b:16:bf:e4:8b:00:8b:b8:59:8c:a9:d3:f0:19:45:fa",
      "public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAQQDDHr/jh2Jy4yALcK4JyWbVkPRaWmhck3IgCoeOO3z1e2dBowLh64QAM+Qb72pxekALga2oi4GvT+TlWNhzPH4V example",
      "name": "My SSH Public Key"
    }
  ],
  "links": {
  },
  "meta": {
    "total": 1
  }
}
`

var respMultiKey = `
{
  "ssh_keys": [
    {
      "id": 512189,
      "fingerprint": "3b:16:bf:e4:8b:00:8b:b8:59:8c:a9:d3:f0:19:45:fa",
      "public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAQQDDHr/jh2Jy4yALcK4JyWbVkPRaWmhck3IgCoeOO3z1e2dBowLh64QAM+Qb72pxekALga2oi4GvT+TlWNhzPH4V example",
      "name": "My SSH Public Key"
    },
    {
      "id": 513199,
      "fingerprint": "00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff",
      "public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAQQDDHr/jh2Jy4yALcK4JyWbVkPRaWmhck3IgCoeOO3z1e2dBowLh64QAM+Qb72pxekALga2oi4GvT+TlWNhzPH4V example",
      "name": "id_rsa_drone"
    }
  ],
  "links": {
  },
  "meta": {
    "total": 2
  }
}
`

var respMultiKeyNoMatch = `
{
  "ssh_keys": [
    {
      "id": 512189,
      "fingerprint": "3b:16:bf:e4:8b:00:8b:b8:59:8c:a9:d3:f0:19:45:fa",
      "public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAQQDDHr/jh2Jy4yALcK4JyWbVkPRaWmhck3IgCoeOO3z1e2dBowLh64QAM+Qb72pxekALga2oi4GvT+TlWNhzPH4V example",
      "name": "My SSH Public Key"
    },
    {
      "id": 513199,
      "fingerprint": "00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff",
      "public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAQQDDHr/jh2Jy4yALcK4JyWbVkPRaWmhck3IgCoeOO3z1e2dBowLh64QAM+Qb72pxekALga2oi4GvT+TlWNhzPH4V example",
      "name": "My SSH Public Key2"
    }
  ],
  "links": {
  },
  "meta": {
    "total": 2
  }
}
`


================================================
FILE: drivers/digitalocean/userdata.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package digitalocean

import "github.com/drone/autoscaler/drivers/internal/userdata"

var userdataT = userdata.Parse(`#cloud-config
write_files:
  - path: /etc/systemd/system/docker.service.d/override.conf
    content: |
      [Service]
      ExecStart=
      ExecStart=/usr/bin/dockerd
  - path: /etc/default/docker
    content: |
      DOCKER_OPTS=""
  - path: /etc/docker/daemon.json
    content: |
      {
        "dns": [ "8.8.8.8", "8.8.4.4" ],
        "hosts": [ "0.0.0.0:2376", "unix:///var/run/docker.sock" ],
        "tls": true,
        "tlsverify": true,
        "tlscacert": "/etc/docker/ca.pem",
        "tlscert": "/etc/docker/server-cert.pem",
        "tlskey": "/etc/docker/server-key.pem"
      }
  - path: /etc/docker/ca.pem
    encoding: b64
    content: {{ .CACert | base64 }}
  - path: /etc/docker/server-cert.pem
    encoding: b64
    content: {{ .TLSCert | base64 }}
  - path: /etc/docker/server-key.pem
    encoding: b64
    content: {{ .TLSKey | base64 }}

runcmd:
  - [ systemctl, daemon-reload ]
  - [ systemctl, restart, docker ]
`)


================================================
FILE: drivers/google/create.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package google

import (
	"bytes"
	"context"
	"fmt"
	"math/rand"
	"strings"

	"github.com/drone/autoscaler"
	"github.com/drone/autoscaler/logger"

	"google.golang.org/api/compute/v1"
	"google.golang.org/api/googleapi"
)

func (p *provider) Create(ctx context.Context, opts autoscaler.InstanceCreateOpts) (*autoscaler.Instance, error) {
	p.init.Do(func() {
		p.setup(ctx)
	})

	buf := new(bytes.Buffer)
	err := p.userdata.Execute(buf, &opts)
	if err != nil {
		return nil, err
	}

	name := strings.ToLower(opts.Name)

	// select random zone from the list
	zone := p.zones[rand.Intn(len(p.zones))]

	logger := logger.FromContext(ctx).
		WithField("zone", zone).
		WithField("image", p.image).
		WithField("size", p.size).
		WithField("name", opts.Name)

	logger.Debugln("instance insert")

	networkConfig := []*compute.AccessConfig{}
	if !p.privateIP {
		networkConfig = []*compute.AccessConfig{
			{
				Name: "External NAT",
				Type: "ONE_TO_ONE_NAT",
			},
		}
	}

	in := &compute.Instance{
		Name:           name,
		Zone:           fmt.Sprintf("projects/%s/zones/%s", p.project, zone),
		MinCpuPlatform: "Automatic",
		MachineType:    fmt.Sprintf("projects/%s/zones/%s/machineTypes/%s", p.project, zone, p.size),
		Metadata: &compute.Metadata{
			Items: []*compute.MetadataItems{
				{
					Key:   p.userdataKey,
					Value: googleapi.String(buf.String()),
				},
			},
		},
		Tags: &compute.Tags{
			Items: p.tags,
		},
		Disks: []*compute.AttachedDisk{
			{
				Type:       "PERSISTENT",
				Boot:       true,
				Mode:       "READ_WRITE",
				AutoDelete: true,
				DeviceName: name,
				InitializeParams: &compute.AttachedDiskInitializeParams{
					SourceImage: fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s", p.image),
					DiskType:    fmt.Sprintf("projects/%s/zones/%s/diskTypes/%s", p.project, zone, p.diskType),
					DiskSizeGb:  p.diskSize,
				},
			},
		},
		CanIpForward: false,
		NetworkInterfaces: []*compute.NetworkInterface{
			{
				Network:       p.network,
				Subnetwork:    p.subnetwork,
				StackType:     p.stackType,
				AccessConfigs: networkConfig,
			},
		},
		Labels: p.labels,
		Scheduling: &compute.Scheduling{
			Preemptible:       false,
			OnHostMaintenance: "MIGRATE",
			AutomaticRestart:  googleapi.Bool(true),
		},
		DeletionProtection: false,
		ServiceAccounts: []*compute.ServiceAccount{
			{
				Scopes: p.scopes,
				Email:  p.serviceAccountEmail,
			},
		},
	}

	// Cannot add this in the same way as v4 access configs since the instance creation
	// fails if any v6 access configs are specified for an instance with IPV4_ONLY stack type
	if p.stackType == "IPV4_IPV6" {
		in.NetworkInterfaces[0].Ipv6AccessConfigs = []*compute.AccessConfig{
			{
				Name:        "external-ipv6",
				Type:        "DIRECT_IPV6",
				NetworkTier: "PREMIUM",
			},
		}
	}

	op, err := p.service.Instances.Insert(p.project, zone, in).Do()
	if err != nil {
		logger.WithError(err).
			Errorln("instance insert failed")
		return nil, err
	}

	logger.Debugln("pending instance insert operation")

	err = p.waitZoneOperation(ctx, op.Name, zone)
	if err != nil {
		logger.WithError(err).
			Errorln("instance insert operation failed")
		return nil, err
	}

	logger.Debugln("instance insert operation complete")

	resp, err := p.service.Instances.Get(p.project, zone, name).Do()
	if err != nil {
		logger.WithError(err).
			Errorln("cannot get instance details")
		return nil, err
	}

	address := resp.NetworkInterfaces[0].NetworkIP

	if !p.privateIP {
		address = resp.NetworkInterfaces[0].AccessConfigs[0].NatIP
	}

	instance := &autoscaler.Instance{
		Provider:            autoscaler.ProviderGoogle,
		ID:                  name,
		Name:                opts.Name,
		Image:               p.image,
		Region:              zone,
		Size:                p.size,
		Address:             address,
		ServiceAccountEmail: p.serviceAccountEmail,
		Scopes:              p.scopes,
	}

	logger.
		WithField("name", instance.Name).
		WithField("ip", instance.Address).
		Debugln("instance inserted")

	return instance, nil
}


================================================
FILE: drivers/google/create_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package google

import (
	"context"
	"net/http"
	"testing"

	"github.com/drone/autoscaler"
	"github.com/h2non/gock"

	"google.golang.org/api/compute/v1"
	"google.golang.org/api/googleapi"
)

func TestCreate(t *testing.T) {
	defer gock.Off()

	gock.New("https://compute.googleapis.com").
		Post("/compute/v1/projects/my-project/zones/us-central1-a/instances").
		JSON(insertInstanceMock).
		Reply(200).
		BodyString(`{ "name": "operation-name" }`)

	gock.New("https://compute.googleapis.com").
		Get("/compute/v1/projects/my-project/zones/us-central1-a/instances/agent-807jvfwj").
		Reply(200).
		BodyString(`{ "networkInterfaces": [ { "accessConfigs": [ { "natIP": "1.2.3.4" } ] } ] }`)

	gock.New("https://compute.googleapis.com").
		Get("/compute/v1/projects/my-project/zones/us-central1-a/operations/operation-name").
		Reply(200).
		BodyString(`{ "status": "DONE" }`)

	v, err := New(
		WithClient(http.DefaultClient),
		WithZones("us-central1-a"),
		WithProject("my-project"),
		WithUserData("#cloud-init"),
	)
	if err != nil {
		t.Error(err)
		return
	}
	p := v.(*provider)
	p.init.Do(func() {})

	instance, err := p.Create(context.TODO(), autoscaler.InstanceCreateOpts{Name: "agent-807jVFwj"})
	if err != nil {
		t.Error(err)
	}

	if want, got := instance.Address, "1.2.3.4"; got != want {
		t.Errorf("Want instance IP %q, got %q", want, got)
	}
	if want, got := instance.Image, "ubuntu-os-cloud/global/images/ubuntu-2004-focal-v20220712"; got != want {
		t.Errorf("Want instance ID %q, got %q", want, got)
	}
	if want, got := instance.ID, "agent-807jvfwj"; got != want {
		t.Errorf("Want instance ID %q, got %q", want, got)
	}
	if want, got := instance.Name, "agent-807jVFwj"; got != want {
		t.Errorf("Want instance Name %q, got %q", want, got)
	}
	if want, got := instance.Provider, autoscaler.ProviderGoogle; got != want {
		t.Errorf("Want google Provider type")
	}
	if want, got := instance.Region, "us-central1-a"; got != want {
		t.Errorf("Want instance Region %q, got %q", want, got)
	}
	if want, got := instance.Size, "n1-standard-1"; got != want {
		t.Errorf("Want instance Size %q, got %q", want, got)
	}
	if want, got := instance.ServiceAccountEmail, "default"; got != want {
		t.Errorf("Want service account email  %q, got %q", want, got)
	}
}

func TestCreateWithMultiZones(t *testing.T) {
	defer gock.Off()

	gock.New("https://compute.googleapis.com").
		Post("/compute/v1/projects/my-project/zones/us-central1-b/instances").
		JSON(insertInstanceMockB).
		Reply(200).
		BodyString(`{ "name": "operation-name" }`)

	gock.New("https://compute.googleapis.com").
		Get("/compute/v1/projects/my-project/zones/us-central1-b/instances/agent-807jvfwj").
		Reply(200).
		BodyString(`{ "networkInterfaces": [ { "accessConfigs": [ { "natIP": "1.2.3.4" } ] } ] }`)

	gock.New("https://compute.googleapis.com").
		Get("/compute/v1/projects/my-project/zones/us-central1-b/operations/operation-name").
		Reply(200).
		BodyString(`{ "status": "DONE" }`)

	v, err := New(
		WithClient(http.DefaultClient),
		WithZones("us-central1-b"),
		WithProject("my-project"),
		WithUserData("#cloud-init"),
	)
	if err != nil {
		t.Error(err)
		return
	}
	p := v.(*provider)
	p.init.Do(func() {})

	instance, err := p.Create(context.TODO(), autoscaler.InstanceCreateOpts{Name: "agent-807jVFwj"})
	if err != nil {
		t.Error(err)
	}

	if want, got := instance.Region, "us-central1-b"; got != want {
		t.Errorf("Want region %q, got %q", want, got)
	}
}

var insertInstanceMock = &compute.Instance{
	Name:           "agent-807jvfwj",
	Zone:           "projects/my-project/zones/us-central1-a",
	MinCpuPlatform: "Automatic",
	MachineType:    "projects/my-project/zones/us-central1-a/machineTypes/n1-standard-1",
	Metadata: &compute.Metadata{
		Items: []*compute.MetadataItems{
			{
				Key:   "user-data",
				Value: googleapi.String(`#cloud-init`),
			},
		},
	},
	Tags: &compute.Tags{
		Items: []string{"allow-docker"},
	},
	Disks: []*compute.AttachedDisk{
		{
			Type:       "PERSISTENT",
			Boot:       true,
			Mode:       "READ_WRITE",
			AutoDelete: true,
			DeviceName: "agent-807jvfwj",
			InitializeParams: &compute.AttachedDiskInitializeParams{
				SourceImage: "https://www.googleapis.com/compute/v1/projects/ubuntu-os-cloud/global/images/ubuntu-2004-focal-v20220712",
				DiskType:    "projects/my-project/zones/us-central1-a/diskTypes/pd-standard",
				DiskSizeGb:  50,
			},
		},
	},
	CanIpForward: false,
	NetworkInterfaces: []*compute.NetworkInterface{
		{
			Network:   "global/networks/default",
			StackType: "IPV4_ONLY",
			AccessConfigs: []*compute.AccessConfig{
				{
					Name: "External NAT",
					Type: "ONE_TO_ONE_NAT",
				},
			},
		},
	},
	Labels: map[string]string{},
	Scheduling: &compute.Scheduling{
		Preemptible:       false,
		OnHostMaintenance: "MIGRATE",
		AutomaticRestart:  googleapi.Bool(true),
	},
	DeletionProtection: false,
	ServiceAccounts: []*compute.ServiceAccount{
		{
			Email: "default",
			Scopes: []string{
				"https://www.googleapis.com/auth/devstorage.read_only",
				"https://www.googleapis.com/auth/logging.write",
				"https://www.googleapis.com/auth/monitoring.write",
				"https://www.googleapis.com/auth/trace.append",
			},
		},
	},
}

var insertInstanceMockB = &compute.Instance{
	Name:           "agent-807jvfwj",
	Zone:           "projects/my-project/zones/us-central1-b",
	MinCpuPlatform: "Automatic",
	MachineType:    "projects/my-project/zones/us-central1-b/machineTypes/n1-standard-1",
	Metadata: &compute.Metadata{
		Items: []*compute.MetadataItems{
			{
				Key:   "user-data",
				Value: googleapi.String(`#cloud-init`),
			},
		},
	},
	Tags: &compute.Tags{
		Items: []string{"allow-docker"},
	},
	Disks: []*compute.AttachedDisk{
		{
			Type:       "PERSISTENT",
			Boot:       true,
			Mode:       "READ_WRITE",
			AutoDelete: true,
			DeviceName: "agent-807jvfwj",
			InitializeParams: &compute.AttachedDiskInitializeParams{
				SourceImage: "https://www.googleapis.com/compute/v1/projects/ubuntu-os-cloud/global/images/ubuntu-2004-focal-v20220712",
				DiskType:    "projects/my-project/zones/us-central1-b/diskTypes/pd-standard",
				DiskSizeGb:  50,
			},
		},
	},
	CanIpForward: false,
	NetworkInterfaces: []*compute.NetworkInterface{
		{
			Network:   "global/networks/default",
			StackType: "IPV4_ONLY",
			AccessConfigs: []*compute.AccessConfig{
				{
					Name: "External NAT",
					Type: "ONE_TO_ONE_NAT",
				},
			},
		},
	},
	Labels: map[string]string{},
	Scheduling: &compute.Scheduling{
		Preemptible:       false,
		OnHostMaintenance: "MIGRATE",
		AutomaticRestart:  googleapi.Bool(true),
	},
	DeletionProtection: false,
	ServiceAccounts: []*compute.ServiceAccount{
		{
			Email: "default",
			Scopes: []string{
				"https://www.googleapis.com/auth/devstorage.read_only",
				"https://www.googleapis.com/auth/logging.write",
				"https://www.googleapis.com/auth/monitoring.write",
				"https://www.googleapis.com/auth/trace.append",
			},
		},
	},
}


================================================
FILE: drivers/google/destroy.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package google

import (
	"context"
	"net/http"

	"github.com/drone/autoscaler"
	"google.golang.org/api/googleapi"
)

func (p *provider) Destroy(ctx context.Context, instance *autoscaler.Instance) error {
	// An instance's Region is actually a Zone in the google provider
	op, err := p.service.Instances.Delete(p.project, instance.Region, instance.ID).Do()
	if err != nil {
		// https://github.com/googleapis/google-api-go-client/blob/master/googleapi/googleapi.go#L135
		if gerr, ok := err.(*googleapi.Error); ok &&
			gerr.Code == http.StatusNotFound {
			return autoscaler.ErrInstanceNotFound
		}
		return err
	}
	return p.waitZoneOperation(ctx, op.Name, instance.Region)
}


================================================
FILE: drivers/google/destroy_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package google

import (
	"context"
	"net/http"
	"testing"

	"github.com/drone/autoscaler"
	"github.com/h2non/gock"
)

func TestDestroy(t *testing.T) {
	defer gock.Off()

	gock.New("https://compute.googleapis.com").
		Delete("/compute/v1/projects/my-project/zones/us-central1-a/instances/my-instance").
		Reply(200).
		BodyString(`{ "name": "operation-name" }`)

	gock.New("https://compute.googleapis.com").
		Get("/compute/v1/projects/my-project/zones/us-central1-a/operations/operation-name").
		Reply(200).
		BodyString(`{ "status": "DONE" }`)

	mockContext := context.TODO()
	mockInstance := &autoscaler.Instance{
		ID:     "my-instance",
		Region: "us-central1-a",
	}

	p, err := New(
		WithClient(http.DefaultClient),
		WithZones("us-central1-a"),
		WithProject("my-project"),
	)
	if err != nil {
		t.Error(err)
		return
	}

	err = p.Destroy(mockContext, mockInstance)
	if err != nil {
		t.Error(err)
	}
}

func TestDestroy_Error(t *testing.T) {
	defer gock.Off()

	gock.New("https://compute.googleapis.com").
		Delete("/compute/v1/projects/my-project/zones/us-central1-a/instances/my-instance").
		Reply(404)

	mockContext := context.TODO()
	mockInstance := &autoscaler.Instance{
		ID:     "my-instance",
		Region: "us-central1-a",
	}

	p, err := New(
		WithClient(http.DefaultClient),
		WithZones("us-central1-a"),
		WithProject("my-project"),
	)
	if err != nil {
		t.Error(err)
		return
	}

	err = p.Destroy(mockContext, mockInstance)
	if err == nil {
		t.Errorf("Expect error deleting server")
	}
}


================================================
FILE: drivers/google/option.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package google

import (
	"io/ioutil"
	"net/http"
	"time"

	"github.com/drone/autoscaler/drivers/internal/userdata"
	"golang.org/x/time/rate"

	"google.golang.org/api/compute/v1"
)

// Option configures a Digital Ocean provider option.
type Option func(*provider)

// WithClient returns an option to set the default http
// Client used with the Google Compute provider.
func WithClient(client *http.Client) Option {
	return func(p *provider) {
		service, err := compute.New(client)
		if err != nil {
			panic(err)
		}
		p.service = service
	}
}

// WithDiskSize returns an option to set the instance disk
// size in gigabytes.
func WithDiskSize(diskSize int64) Option {
	return func(p *provider) {
		p.diskSize = diskSize
	}
}

// WithDiskType returns an option to set the instance disk type.
func WithDiskType(diskType string) Option {
	return func(p *provider) {
		p.diskType = diskType
	}
}

// WithLabels returns an option to set the metadata labels.
func WithLabels(labels map[string]string) Option {
	return func(p *provider) {
		p.labels = labels
	}
}

// WithMachineImage returns an option to set the image.
func WithMachineImage(image string) Option {
	return func(p *provider) {
		p.image = image
	}
}

// WithMachineType returns an option to set the instance type.
func WithMachineType(size string) Option {
	return func(p *provider) {
		p.size = size
	}
}

// WithNetwork returns an option to set the network.
func WithNetwork(network string) Option {
	return func(p *provider) {
		p.network = network
	}
}

// WithSubNetwork returns an option to set the subnetwork.
func WithSubnetwork(subnetwork string) Option {
	return func(p *provider) {
		p.subnetwork = subnetwork
	}
}

// WithStackType returns an option to set the stack type for the instance.
func WithStackType(stackType string) Option {
	return func(p *provider) {
		p.stackType = stackType
	}
}

// WithPrivateIP returns an option to set the private IP address.
func WithPrivateIP(private bool) Option {
	return func(p *provider) {
		p.privateIP = private
	}
}

// WithProject returns an option to set the project.
func WithProject(project string) Option {
	return func(p *provider) {
		p.project = project
	}
}

// WithTags returns an option to set the resource tags.
func WithTags(tags ...string) Option {
	return func(p *provider) {
		p.tags = tags
	}
}

// WithUserData returns an option to set the cloud-init
// template from text.
func WithUserData(text string) Option {
	return func(p *provider) {
		if text != "" {
			p.userdata = userdata.Parse(text)
		}
	}
}

// WithUserDataFile returns an option to set the cloud-init
// template from file.
func WithUserDataFile(filepath string) Option {
	return func(p *provider) {
		if filepath != "" {
			b, err := ioutil.ReadFile(filepath)
			if err != nil {
				panic(err)
			}
			p.userdata = userdata.Parse(string(b))
		}
	}
}

// WithUserDataKey allows to set the user data key for Google Cloud Platform
// This allows user to set either user-data or a startup script
func WithUserDataKey(text string) Option {
	return func(p *provider) {
		if text != "" {
			p.userdataKey = text
		}
	}
}

// WithZone returns an option to set the target zone.
func WithZones(zones ...string) Option {
	return func(p *provider) {
		p.zones = zones
	}
}

// WithScopes returns an option to set the scopes.
func WithScopes(scopes ...string) Option {
	return func(p *provider) {
		p.scopes = scopes
	}
}

// WithServiceAccountEmail returns an option to set the ServiceAccountEmail.
func WithServiceAccountEmail(email string) Option {
	return func(p *provider) {
		p.serviceAccountEmail = email
	}
}

func WithRateLimit(limitAmount int) Option {
	return func(p *provider) {
		limit := rate.Every(1 * time.Second / time.Duration(limitAmount))
		p.rateLimiter = rate.NewLimiter(limit, 1)
	}
}


================================================
FILE: drivers/google/option_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package google

import (
	"net/http"
	"reflect"
	"testing"
)

func TestOptions(t *testing.T) {
	v, err := New(
		WithClient(http.DefaultClient),
		WithDiskSize(100),
		WithDiskType("local-ssd"),
		WithMachineImage("ubuntu-1604-lts"),
		WithMachineType("c3.large"),
		WithNetwork("global/defaults/foo"),
		WithPrivateIP(false),
		WithServiceAccountEmail("default"),
		WithProject("my-project"),
		WithTags("drone", "agent"),
		WithZones("us-central1-f"),
		WithScopes("scope1", "scope2"),
		WithUserDataKey("test-key"),
	)
	if err != nil {
		t.Error(err)
		return
	}
	p := v.(*provider)

	if got, want := p.diskSize, int64(100); got != want {
		t.Errorf("Want diskSize %d, got %d", want, got)
	}
	if got, want := p.diskType, "local-ssd"; got != want {
		t.Errorf("Want diskType %s, got %s", want, got)
	}
	if got, want := p.image, "ubuntu-1604-lts"; got != want {
		t.Errorf("Want image %q, got %q", want, got)
	}
	if got, want := p.network, "global/defaults/foo"; got != want {
		t.Errorf("Want network %q, got %q", want, got)
	}
	if got, want := p.privateIP, false; got != want {
		t.Errorf("Want %v privateIP, got %v", want, got)
	}
	if got, want := p.project, "my-project"; got != want {
		t.Errorf("Want project %q, got %q", want, got)
	}
	if got, want := p.size, "c3.large"; got != want {
		t.Errorf("Want size %q, got %q", want, got)
	}
	if got, want := len(p.tags), 2; got != want {
		t.Errorf("Want %d tags, got %d", want, got)
	}
	if got, want := p.zones, []string{"us-central1-f"}; !reflect.DeepEqual(want, got) {
		t.Errorf("Want zone %q, got %q", want, got)
	}
	if got, want := len(p.scopes), 2; got != want {
		t.Errorf("Want %d scopes, got %d", want, got)
	}
	if got, want := p.serviceAccountEmail, "default"; got != want {
		t.Errorf("Want service account name %q, got %q", want, got)
	}
	if got, want := p.userdataKey, "test-key"; got != want {
		t.Errorf("Want userdata key %q, got %q", want, got)
	}
}


================================================
FILE: drivers/google/provider.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package google

import (
	"context"
	"errors"
	"net/http"
	"sync"
	"text/template"
	"time"

	"github.com/drone/autoscaler"
	"github.com/drone/autoscaler/drivers/internal/userdata"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	"golang.org/x/time/rate"
	compute "google.golang.org/api/compute/v1"
	"google.golang.org/api/googleapi"
)

var (
	defaultTags = []string{
		"allow-docker",
	}

	defaultScopes = []string{
		"https://www.googleapis.com/auth/devstorage.read_only",
		"https://www.googleapis.com/auth/logging.write",
		"https://www.googleapis.com/auth/monitoring.write",
		"https://www.googleapis.com/auth/trace.append",
	}
)

// provider implements a Google Cloud Platform provider.
type provider struct {
	init sync.Once

	diskSize            int64
	diskType            string
	image               string
	labels              map[string]string
	network             string
	subnetwork          string
	stackType           string
	project             string
	privateIP           bool
	scopes              []string
	serviceAccountEmail string
	size                string
	tags                []string
	zones               []string
	userdata            *template.Template
	userdataKey         string

	rateLimiter *rate.Limiter

	service *compute.Service
}

// New returns a new Google Cloud Platform provider.
func New(opts ...Option) (autoscaler.Provider, error) {
	p := new(provider)
	for _, opt := range opts {
		opt(p)
	}
	if p.diskSize == 0 {
		p.diskSize = 50
	}
	if p.diskType == "" {
		p.diskType = "pd-standard"
	}
	if len(p.zones) == 0 {
		p.zones = []string{"us-central1-a"}
	}
	if p.size == "" {
		p.size = "n1-standard-1"
	}
	if p.image == "" {
		p.image = "ubuntu-os-cloud/global/images/ubuntu-2004-focal-v20220712"
	}
	if p.network == "" {
		p.network = "global/networks/default"
	}
	if p.stackType == "" {
		p.stackType = "IPV4_ONLY"
	}
	if p.userdata == nil {
		p.userdata = userdata.T
	}
	if p.userdataKey == "" {
		p.userdataKey = "user-data"
	}
	if len(p.tags) == 0 {
		p.tags = defaultTags
	}
	if len(p.scopes) == 0 {
		p.scopes = defaultScopes
	}
	if p.serviceAccountEmail == "" {
		p.serviceAccountEmail = "default"
	}

	if p.rateLimiter == nil {
		// If unspecified, set to the max read rate limit for the API 25/s
		// Source: https://cloud.google.com/compute/docs/api-rate-limits
		p.rateLimiter = rate.NewLimiter(rate.Every(time.Second/25), 1)
	}

	if p.service == nil {
		client, err := google.DefaultClient(oauth2.NoContext, compute.ComputeScope)
		if err != nil {
			return nil, err
		}
		p.service, err = compute.New(client)
		if err != nil {
			return nil, err
		}
	}
	return p, nil
}

func (p *provider) waitZoneOperation(ctx context.Context, name string, zone string) error {
	for {
		if p.rateLimiter.Allow() {
			op, err := p.service.ZoneOperations.Get(p.project, zone, name).Do()
			if err != nil {
				if gerr, ok := err.(*googleapi.Error); ok &&
					gerr.Code == http.StatusNotFound {
					return autoscaler.ErrInstanceNotFound
				}
				return err
			}
			if op.Error != nil {
				return errors.New(op.Error.Errors[0].Message)
			}
			if op.Status == "DONE" {
				return nil
			}
		}
		time.Sleep(time.Second)
	}
}

func (p *provider) waitGlobalOperation(ctx context.Context, name string) error {
	for {
		if p.rateLimiter.Allow() {
			op, err := p.service.GlobalOperations.Get(p.project, name).Do()
			if err != nil {
				return err
			}
			if op.Error != nil {
				return errors.New(op.Error.Errors[0].Message)
			}
			if op.Status == "DONE" {
				return nil
			}
		}
		time.Sleep(time.Second)
	}
}


================================================
FILE: drivers/google/provider_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package google

import (
	"net/http"
	"reflect"
	"testing"

	"github.com/drone/autoscaler/drivers/internal/userdata"
)

func TestDefaults(t *testing.T) {
	v, err := New(
		WithClient(http.DefaultClient),
	)
	if err != nil {
		t.Error(err)
		return
	}
	p := v.(*provider)

	if got, want := p.diskSize, int64(50); got != want {
		t.Errorf("Want diskSize %d, got %d", want, got)
	}
	if got, want := p.diskType, "pd-standard"; got != want {
		t.Errorf("Want diskType %s, got %s", want, got)
	}
	if got, want := p.image, "ubuntu-os-cloud/global/images/ubuntu-2004-focal-v20220712"; got != want {
		t.Errorf("Want image %q, got %q", want, got)
	}
	if got, want := p.network, "global/networks/default"; got != want {
		t.Errorf("Want network %q, got %q", want, got)
	}
	if !reflect.DeepEqual(p.scopes, defaultScopes) {
		t.Errorf("Want default scopes")
	}
	if got, want := p.size, "n1-standard-1"; got != want {
		t.Errorf("Want size %q, got %q", want, got)
	}
	if !reflect.DeepEqual(p.tags, defaultTags) {
		t.Errorf("Want default tags")
	}
	if p.userdata != userdata.T {
		t.Errorf("Want default userdata template")
	}
	if p.userdataKey != "user-data" {
		t.Errorf("Want default userdata key")
	}
	if got, want := p.zones, []string{"us-central1-a"}; !reflect.DeepEqual(got, want) {
		t.Errorf("Want region %q, got %q", want, got)
	}
}


================================================
FILE: drivers/google/setup.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package google

import (
	"context"
	"reflect"

	"github.com/drone/autoscaler/logger"

	compute "google.golang.org/api/compute/v1"
)

func (p *provider) setup(ctx context.Context) error {
	if reflect.DeepEqual(p.tags, defaultTags) {
		return p.setupFirewall(ctx)
	}
	return nil
}

func (p *provider) setupFirewall(ctx context.Context) error {
	logger := logger.FromContext(ctx)

	logger.Debugln("finding default firewall rules")

	_, err := p.service.Firewalls.Get(p.project, "default-allow-docker").Context(ctx).Do()
	if err == nil {
		logger.Debugln("found default firewall rule")
		return nil
	}

	rule := &compute.Firewall{
		Allowed: []*compute.FirewallAllowed{
			{
				IPProtocol: "tcp",
				Ports:      []string{"2376"},
			},
		},
		Direction:    "INGRESS",
		Name:         "default-allow-docker",
		Network:      p.network,
		Priority:     1000,
		SourceRanges: []string{"0.0.0.0/0"},
		TargetTags:   []string{"allow-docker"},
	}

	op, err := p.service.Firewalls.Insert(p.project, rule).Context(ctx).Do()
	if err != nil {
		logger.WithError(err).
			Errorln("cannot create firewall operation")
		return err
	}

	err = p.waitGlobalOperation(ctx, op.Name)
	if err != nil {
		logger.WithError(err).
			Errorln("cannot create firewall rule")
	}

	return err
}


================================================
FILE: drivers/google/setup_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package google

import (
	"context"
	"net/http"
	"testing"

	"github.com/h2non/gock"
	compute "google.golang.org/api/compute/v1"
)

func TestSetupFirewall(t *testing.T) {
	defer gock.Off()

	gock.New("https://compute.googleapis.com").
		Get("/compute/v1/projects/my-project/global/firewalls/default-allow-docker").
		Reply(404)

	gock.New("https://compute.googleapis.com").
		Post("/compute/v1/projects/my-project/global/firewalls").
		JSON(createFirewallMock).
		Reply(200).
		BodyString(`{ "name": "operation-name" }`)

	gock.New("https://compute.googleapis.com").
		Get("/compute/v1/projects/my-project/global/operations/operation-name").
		Reply(200).
		BodyString(`{ "status": "DONE" }`)

	p, err := New(
		WithClient(http.DefaultClient),
		WithZones("us-central1-a"),
		WithProject("my-project"),
	)
	if err != nil {
		t.Error(err)
		return
	}

	err = p.(*provider).setupFirewall(context.TODO())
	if err != nil {
		t.Error(err)
	}
}

func TestSetupFirewall_Exists(t *testing.T) {
	defer gock.Off()

	gock.New("https://compute.googleapis.com").
		Get("/compute/v1/projects/my-project/global/firewalls/default-allow-docker").
		Reply(200).
		BodyString(findFirewallRes)

	p, err := New(
		WithClient(http.DefaultClient),
		WithZones("us-central1-a"),
		WithProject("my-project"),
	)
	if err != nil {
		t.Error(err)
		return
	}

	err = p.(*provider).setupFirewall(context.TODO())
	if err != nil {
		t.Error(err)
	}
}

var createFirewallMock = &compute.Firewall{
	Allowed: []*compute.FirewallAllowed{
		{
			IPProtocol: "tcp",
			Ports:      []string{"2376"},
		},
	},
	Direction:    "INGRESS",
	Name:         "default-allow-docker",
	Network:      "global/networks/default",
	Priority:     1000,
	SourceRanges: []string{"0.0.0.0/0"},
	TargetTags:   []string{"allow-docker"},
}

var findFirewallRes = `
{
  "allowed": [
    {
      "IPProtocol": "tcp",
      "ports": [
        "2376"
      ]
    }
  ],
  "creationTimestamp": "2018-03-10T11:31:09.445-08:00",
  "description": "",
  "direction": "INGRESS",
  "id": "3206167972979853122",
  "kind": "compute#firewall",
  "name": "default-allow-docker",
  "network": "projects/my-project/global/networks/default",
  "priority": 1000,
  "selfLink": "projects/my-project/global/firewalls/default-allow-docker",
  "sourceRanges": [
    "0.0.0.0/0"
  ],
  "targetTags": [
    "allow-docker"
  ]
}
`


================================================
FILE: drivers/hetznercloud/create.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package hetznercloud

import (
	"bytes"
	"context"
	"strconv"

	"github.com/drone/autoscaler"
	"github.com/drone/autoscaler/logger"

	"github.com/hetznercloud/hcloud-go/hcloud"
)

func (p *provider) Create(ctx context.Context, opts autoscaler.InstanceCreateOpts) (*autoscaler.Instance, error) {
	p.init.Do(func() {
		p.setup(ctx)
	})

	buf := new(bytes.Buffer)
	err := p.userdata.Execute(buf, &opts)
	if err != nil {
		return nil, err
	}

	req := hcloud.ServerCreateOpts{
		Name:     opts.Name,
		UserData: buf.String(),
		ServerType: &hcloud.ServerType{
			Name: p.serverType,
		},
		Image: &hcloud.Image{
			Name: p.image,
		},
		SSHKeys: []*hcloud.SSHKey{
			{
				ID: p.key,
			},
		},
	}

	datacenter := "unknown"

	if p.datacenter != "" {
		req.Datacenter = &hcloud.Datacenter{
			Name: p.datacenter,
		}

		datacenter = p.datacenter
	}

	logger := logger.FromContext(ctx).
		WithField("datacenter", datacenter).
		WithField("image", req.Image.Name).
		WithField("serverType", req.ServerType.Name).
		WithField("name", req.Name)

	logger.Debugln("instance create")

	resp, _, err := p.client.Server.Create(ctx, req)
	if err != nil {
		logger.WithError(err).
			Errorln("cannot create instance")
		return nil, err
	}

	logger.
		WithField("name", req.Name).
		Infoln("instance created")

	return &autoscaler.Instance{
		Provider: autoscaler.ProviderHetznerCloud,
		ID:       strconv.Itoa(resp.Server.ID),
		Name:     resp.Server.Name,
		Address:  resp.Server.PublicNet.IPv4.IP.String(),
		Size:     req.ServerType.Name,
		Region:   datacenter,
		Image:    req.Image.Name,
	}, nil
}


================================================
FILE: drivers/hetznercloud/create_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package hetznercloud

import (
	"context"
	"testing"

	"github.com/drone/autoscaler"

	"github.com/h2non/gock"
)

func TestCreate(t *testing.T) {
	defer gock.Off()

	gock.New("https://api.hetzner.cloud").
		Post("/v1/servers").
		Reply(200).
		BodyString(respInstanceCreate)

	p := New(
		WithToken("LRK9DAWQ1ZAEFSrCNEEzLCUwhYX1U3g7wMg4dTlkkDC96fyDuyJ39nVbVjCKSDfj"),
	).(*provider)
	p.init.Do(func() {}) // pre-initialize

	instance, err := p.Create(context.TODO(), autoscaler.InstanceCreateOpts{Name: "agent1"})
	if err != nil {
		t.Error(err)
	}

	t.Run("Attributes", testInstance(instance))
}

func TestCreate_CreateError(t *testing.T) {
	defer gock.Off()

	gock.New("https://api.hetzner.cloud").
		Post("/v1/servers").
		Reply(500)

	p := New(
		WithToken("LRK9DAWQ1ZAEFSrCNEEzLCUwhYX1U3g7wMg4dTlkkDC96fyDuyJ39nVbVjCKSDfj"),
	).(*provider)
	p.init.Do(func() {}) // pre-initialize

	_, err := p.Create(context.TODO(), autoscaler.InstanceCreateOpts{Name: "agent1"})
	if err == nil {
		t.Errorf("Expect error returned from hetzner cloud")
	}
}

func testInstance(instance *autoscaler.Instance) func(t *testing.T) {
	return func(t *testing.T) {
		if instance == nil {
			t.Errorf("Expect non-nil instance even if error")
		}
		if got, want := instance.ID, "544037"; got != want {
			t.Errorf("Want instance ID %v, got %v", want, got)
		}
		if got, want := instance.Image, "ubuntu-20.04"; got != want {
			t.Errorf("Want instance Image %v, got %v", want, got)
		}
		if got, want := instance.Name, "test"; got != want {
			t.Errorf("Want instance Name %v, got %v", want, got)
		}
		if got, want := instance.Region, "unknown"; got != want {
			t.Errorf("Want instance Region %v, got %v", want, got)
		}
		if got, want := instance.Provider, autoscaler.ProviderHetznerCloud; got != want {
			t.Errorf("Want instance Provider %v, got %v", want, got)
		}
	}
}

func testInstanceAddress(instance *autoscaler.Instance) func(t *testing.T) {
	return func(t *testing.T) {
		if instance == nil {
			t.Errorf("Expect non-nil instance even if error")
		}
		if got, want := instance.Address, "195.201.93.137"; got != want {
			t.Errorf("Want instance Address %v, got %v", want, got)
		}
	}
}

// sample response for POST /v1/servers
const respInstanceCreate = `
{
  "server": {
    "id": 544037,
    "name": "test",
    "status": "initializing",
    "created": "2018-03-02T08:44:07+00:00",
    "public_net": {
      "ipv4": {
        "ip": "195.201.93.137",
        "blocked": false,
        "dns_ptr": "static.137.93.201.195.clients.your-server.de"
      },
      "ipv6": {
        "ip": "2a01:4f8:1c0c:6996::/64",
        "blocked": false,
        "dns_ptr": []
      },
      "floating_ips": []
    },
    "server_type": {
      "id": 1,
      "name": "cx11",
      "description": "CX11",
      "cores": 1,
      "memory": 2.0,
      "disk": 20,
      "prices": [
        {
          "location": "fsn1",
          "price_hourly": {
            "net": "0.0040000000",
            "gross": "0.0047600000000000"
          },
          "price_monthly": {
            "net": "2.4900000000",
            "gross": "2.9631000000000000"
          }
        },
        {
          "location": "nbg1",
          "price_hourly": {
            "net": "0.0040000000",
            "gross": "0.0047600000000000"
          },
          "price_monthly": {
            "net": "2.4900000000",
            "gross": "2.9631000000000000"
          }
        }
      ],
      "storage_type": "local"
    },
    "datacenter": {
      "id": 2,
      "name": "nbg1-dc3",
      "description": "Nuremberg 1 DC 3",
      "location": {
        "id": 2,
        "name": "nbg1",
        "description": "Nuremberg DC Park 1",
        "country": "DE",
        "city": "Nuremberg",
        "latitude": 49.452102,
        "longitude": 11.076665
      },
      "server_types": {
        "supported": [
          1,
          2,
          3,
          4,
          5,
          6,
          7,
          8,
          9,
          10
        ],
        "available": [
          1,
          2,
          3,
          4,
          5,
          6,
          7,
          8,
          9,
          10
        ]
      }
    },
    "image": {
      "id": 1,
      "type": "system",
      "status": "available",
      "name": "ubuntu-20.04",
      "description": "Ubuntu 20.04",
      "image_size": null,
      "disk_size": 5,
      "created": "2018-01-15T11:34:45+00:00",
      "created_from": null,
      "bound_to": null,
      "os_flavor": "ubuntu",
      "os_version": "20.04",
      "rapid_deploy": true
    },
    "iso": null,
    "rescue_enabled": false,
    "locked": false,
    "backup_window": null,
    "outgoing_traffic": 0,
    "ingoing_traffic": 0,
    "included_traffic": 21990232555520
  },
  "action": {
    "id": 279192,
    "command": "create_server",
    "status": "running",
    "progress": 0,
    "started": "2018-03-02T08:44:07+00:00",
    "finished": null,
    "resources": [
      {
        "id": 544037,
        "type": "server"
      }
    ],
    "error": null
  },
  "root_password": null
}
`


================================================
FILE: drivers/hetznercloud/destroy.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package hetznercloud

import (
	"context"
	"strconv"

	"github.com/drone/autoscaler"
	"github.com/drone/autoscaler/logger"

	"github.com/hetznercloud/hcloud-go/hcloud"
)

func (p *provider) Destroy(ctx context.Context, instance *autoscaler.Instance) error {
	logger := logger.FromContext(ctx).
		WithField("region", instance.Region).
		WithField("image", instance.Image).
		WithField("size", instance.Size).
		WithField("name", instance.Name)

	id, err := strconv.Atoi(instance.ID)
	if err != nil {
		return err
	}

	logger.Debugln("deleting instance")

	_, err = p.client.Server.Delete(ctx, &hcloud.Server{ID: id})

	if err != nil {
		if err.Error() == "hcloud: server responded with status code 404" {
			logger.WithError(err).
				Debugln("instance does not exist")
			return autoscaler.ErrInstanceNotFound
		}

		logger.WithError(err).
			Errorln("deleting instance failed")
		return err
	}

	logger.Debugln("instance deleted")

	return nil
}


================================================
FILE: drivers/hetznercloud/destroy_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package hetznercloud

import (
	"context"
	"strconv"
	"testing"

	"github.com/drone/autoscaler"

	"github.com/h2non/gock"
)

func TestDestroy(t *testing.T) {
	defer gock.Off()

	gock.New("https://api.hetzner.cloud").
		Delete("/v1/servers/3164494").
		Reply(200)

	mockContext := context.TODO()
	mockInstance := &autoscaler.Instance{
		ID: "3164494",
	}

	p := New(
		WithToken("LRK9DAWQ1ZAEFSrCNEEzLCUwhYX1U3g7wMg4dTlkkDC96fyDuyJ39nVbVjCKSDfj"),
	)
	err := p.Destroy(mockContext, mockInstance)
	if err != nil {
		t.Error(err)
	}
}

func TestDestroyDeleteError(t *testing.T) {
	defer gock.Off()

	gock.New("https://api.hetzner.cloud").
		Delete("/v1/servers/3164494").
		Reply(500)

	mockContext := context.TODO()
	mockInstance := &autoscaler.Instance{
		ID: "3164494",
	}

	p := New(
		WithToken("LRK9DAWQ1ZAEFSrCNEEzLCUwhYX1U3g7wMg4dTlkkDC96fyDuyJ39nVbVjCKSDfj"),
	)
	err := p.Destroy(mockContext, mockInstance)
	if err == nil {
		t.Errorf("Expect error returned from hetzner cloud")
	}
}

func TestDestroyNotFound(t *testing.T) {
	defer gock.Off()

	gock.New("https://api.hetzner.cloud").
		Delete("/v1/servers/3164494").
		Reply(404).
		BodyString(destroyNotFoundResponse)

	mockContext := context.TODO()
	mockInstance := &autoscaler.Instance{
		ID: "3164494",
	}

	p := New(
		WithToken("LRK9DAWQ1ZAEFSrCNEEzLCUwhYX1U3g7wMg4dTlkkDC96fyDuyJ39nVbVjCKSDfj"),
	)

	err := p.Destroy(mockContext, mockInstance)
	if err == nil {
		t.Errorf("Expect error returned from hetzner cloud")
	}
	if err != autoscaler.ErrInstanceNotFound {
		t.Errorf("Expect instance not found returned from hetzner cloud")
	}
}

func TestDestroyInvalidInput(t *testing.T) {
	i := &autoscaler.Instance{}
	p := provider{}
	err := p.Destroy(context.TODO(), i)
	if _, ok := err.(*strconv.NumError); !ok {
		t.Errorf("Expected invalid or missing ID error")
	}
}

var destroyNotFoundResponse = `{
  "error": {
    "message": "server with ID '3164494' not found",
    "code": "not_found",
    "details": null
  }
}`


================================================
FILE: drivers/hetznercloud/option.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package hetznercloud

import (
	"io/ioutil"

	"github.com/drone/autoscaler/drivers/internal/userdata"
	"github.com/hetznercloud/hcloud-go/hcloud"
)

// Option configures a Digital Ocean provider option.
type Option func(*provider)

// WithClient returns an option to set the Hetzner client.
func WithClient(client *hcloud.Client) Option {
	return func(p *provider) {
		p.client = client
	}
}

// WithDatacenter returns an option to set the datacenter.
func WithDatacenter(datacenter string) Option {
	return func(p *provider) {
		p.datacenter = datacenter
	}
}

// WithImage returns an option to set the image.
func WithImage(image string) Option {
	return func(p *provider) {
		p.image = image
	}
}

// WithServerType returns an option to set the server type.
func WithServerType(serverType string) Option {
	return func(p *provider) {
		p.serverType = serverType
	}
}

// WithSSHKey returns an option to set the ssh key.
func WithSSHKey(key int) Option {
	return func(p *provider) {
		p.key = key
	}
}

// WithToken returns an option to set the auth token.
func WithToken(token string) Option {
	return WithClient(
		hcloud.NewClient(
			hcloud.WithToken(
				token,
			),
		),
	)
}

// WithUserData returns an option to set the cloud-init
// template from text.
func WithUserData(text string) Option {
	return func(p *provider) {
		if text != "" {
			p.userdata = userdata.Parse(text)
		}
	}
}

// WithUserDataFile returns an option to set the cloud-init
// template from file.
func WithUserDataFile(filepath string) Option {
	return func(p *provider) {
		if filepath != "" {
			b, err := ioutil.ReadFile(filepath)
			if err != nil {
				panic(err)
			}
			p.userdata = userdata.Parse(string(b))
		}
	}
}


================================================
FILE: drivers/hetznercloud/option_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package hetznercloud

import "testing"

func TestOptions(t *testing.T) {
	p := New(
		WithImage("ubuntu-17.04"),
		WithDatacenter("fsn1-dc8"),
		WithServerType("cx20"),
		WithSSHKey(23234),
	).(*provider)

	if got, want := p.image, "ubuntu-17.04"; got != want {
		t.Errorf("Want image %q, got %q", want, got)
	}
	if got, want := p.datacenter, "fsn1-dc8"; got != want {
		t.Errorf("Want region %q, got %q", want, got)
	}
	if got, want := p.serverType, "cx20"; got != want {
		t.Errorf("Want serverType %q, got %q", want, got)
	}
	if got, want := p.key, 23234; got != want {
		t.Errorf("Want key %d, got %d", want, got)
	}
}


================================================
FILE: drivers/hetznercloud/provider.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package hetznercloud

import (
	"sync"
	"text/template"

	"github.com/drone/autoscaler"
	"github.com/drone/autoscaler/drivers/internal/userdata"

	"github.com/hetznercloud/hcloud-go/hcloud"
)

// provider implement a Hetzner Cloud provider.
type provider struct {
	init sync.Once

	token      string
	datacenter string
	serverType string
	image      string
	userdata   *template.Template
	key        int

	client *hcloud.Client
}

// New returns a new Digital Ocean provider.
func New(opts ...Option) autoscaler.Provider {
	p := new(provider)
	for _, opt := range opts {
		opt(p)
	}
	if p.serverType == "" {
		p.serverType = "cx11"
	}
	if p.image == "" {
		p.image = "ubuntu-20.04"
	}
	if p.userdata == nil {
		p.userdata = userdata.T
	}
	return p
}


================================================
FILE: drivers/hetznercloud/provider_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package hetznercloud

import "testing"

func TestDefaults(t *testing.T) {
	p := New().(*provider)
	if got, want := p.image, "ubuntu-20.04"; got != want {
		t.Errorf("Want image %q, got %q", want, got)
	}
	if got, want := p.datacenter, ""; got != want {
		t.Errorf("Want datacenter %q, got %q", want, got)
	}
	if got, want := p.serverType, "cx11"; got != want {
		t.Errorf("Want server type %q, got %q", want, got)
	}
}


================================================
FILE: drivers/hetznercloud/setup.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package hetznercloud

import (
	"context"
	"errors"

	"github.com/drone/autoscaler/logger"
	"github.com/hetznercloud/hcloud-go/hcloud"
	"golang.org/x/sync/errgroup"
)

func (p *provider) setup(ctx context.Context) error {
	var g errgroup.Group
	if p.key == 0 {
		g.Go(func() error {
			return p.setupKeypair(ctx)
		})
	}
	return g.Wait()
}

func (p *provider) setupKeypair(ctx context.Context) error {
	logger := logger.FromContext(ctx)

	logger.Debugln("finding default ssh key")

	keys, _, err := p.client.SSHKey.List(ctx, hcloud.SSHKeyListOpts{})
	if err != nil {
		return err
	}

	index := map[string]*hcloud.SSHKey{}
	for _, key := range keys {
		index[key.Name] = key
	}

	// if the account has multiple keys configured we will
	// attempt to use an existing key based on naming convention.
	for _, name := range []string{"drone", "id_rsa_drone"} {
		key, ok := index[name]
		if !ok {
			continue
		}
		p.key = key.ID

		logger.
			WithField("name", name).
			WithField("fingerprint", key.Fingerprint).
			Debugln("using default ssh key")
		return nil
	}

	// if there were no matches but the account has at least
	// one keypair already created we will select the first
	// in the list.
	if len(keys) > 0 {
		key := keys[0]
		p.key = key.ID

		logger.
			WithField("name", key.Name).
			WithField("fingerprint", key.Fingerprint).
			Debugln("using default ssh key")
		return nil
	}

	return errors.New("No matching keys")
}


================================================
FILE: drivers/hetznercloud/setup_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package hetznercloud

import (
	"context"
	"testing"

	"github.com/h2non/gock"
)

func TestSetupKey_ChooseFirst(t *testing.T) {
	defer gock.Off()

	gock.New("https://api.hetzner.cloud").
		Get("/v1/ssh_keys").
		Reply(200).
		BodyString(respSingleKey)

	p := New(
		WithToken("LRK9DAWQ1ZAEFSrCNEEzLCUwhYX1U3g7wMg4dTlkkDC96fyDuyJ39nVbVjCKSDfj"),
	).(*provider)

	err := p.setup(context.TODO())
	if err != nil {
		t.Error(err)
	}

	if got, want := p.key, 2323; got != want {
		t.Errorf("Want key id %d, got %d", want, got)
	}
}

func TestSetupKey_ChooseMatch(t *testing.T) {
	defer gock.Off()

	gock.New("https://api.hetzner.cloud").
		Get("/v1/ssh_keys").
		Reply(200).
		BodyString(respMultiKey)

	p := New(
		WithToken("LRK9DAWQ1ZAEFSrCNEEzLCUwhYX1U3g7wMg4dTlkkDC96fyDuyJ39nVbVjCKSDfj"),
	).(*provider)

	err := p.setup(context.TODO())
	if err != nil {
		t.Error(err)
	}

	if got, want := p.key, 2324; got != want {
		t.Errorf("Want key id %d, got %d", want, got)
	}
}

const respSingleKey = `
{
  "ssh_keys": [
    {
      "id": 2323,
      "name": "My ssh key",
      "fingerprint": "b7:2f:30:a0:2f:6c:58:6c:21:04:58:61:ba:06:3b:2f",
      "public_key": "ssh-rsa AAAjjk76kgf...Xt"
    }
  ]
}
`

const respMultiKey = `
{
  "ssh_keys": [
    {
      "id": 2323,
      "name": "My ssh key",
      "fingerprint": "b7:2f:30:a0:2f:6c:58:6c:21:04:58:61:ba:06:3b:2f",
      "public_key": "ssh-rsa AAAjjk76kgf...Xt"
    },
    {
      "id": 2324,
      "name": "drone",
      "fingerprint": "b7:2f:30:a0:2f:6c:58:6c:21:04:58:61:ba:06:3b:2f",
      "public_key": "ssh-rsa AAAjjk76kgf...Xt"
    }
  ]
}
`


================================================
FILE: drivers/internal/userdata/userdata.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package userdata

import (
	"encoding/base64"
	"text/template"

	"github.com/drone/funcmap"
)

var funcs = map[string]interface{}{
	"base64": func(src []byte) string {
		return base64.StdEncoding.EncodeToString(src)
	},
}

// Parse parses the userdata template.
func Parse(text string) *template.Template {
	if decoded, err := base64.StdEncoding.DecodeString(text); err == nil {
		return template.Must(
			template.New("_").Funcs(funcs).Funcs(funcmap.Funcs).Parse(string(decoded)),
		)
	}

	return template.Must(
		template.New("_").Funcs(funcs).Funcs(funcmap.Funcs).Parse(text),
	)
}

// T is the default userdata template.
var T = Parse(`#cloud-config

apt_reboot_if_required: false
package_update: false
package_upgrade: false

apt:
  sources:
    docker.list:
      source: deb [arch=amd64] https://download.docker.com/linux/ubuntu $RELEASE stable
      keyid: 0EBFCD88

packages:
  - docker-ce

write_files:
  - path: /etc/systemd/system/docker.service.d/override.conf
    content: |
      [Service]
      ExecStart=
      ExecStart=/usr/bin/dockerd
  - path: /etc/default/docker
    content: |
      DOCKER_OPTS=""
  - path: /etc/docker/daemon.json
    content: |
      {
        "hosts": [ "0.0.0.0:2376", "unix:///var/run/docker.sock" ],
        "tls": true,
        "tlsverify": true,
        "tlscacert": "/etc/docker/ca.pem",
        "tlscert": "/etc/docker/server-cert.pem",
        "tlskey": "/etc/docker/server-key.pem"
      }
  - path: /etc/docker/ca.pem
    encoding: b64
    content: {{ .CACert | base64 }}
  - path: /etc/docker/server-cert.pem
    encoding: b64
    content: {{ .TLSCert | base64 }}
  - path: /etc/docker/server-key.pem
    encoding: b64
    content: {{ .TLSKey | base64 }}

runcmd:
  - [ systemctl, daemon-reload ]
  - [ systemctl, restart, docker ]
`)


================================================
FILE: drivers/internal/userdata/userdata_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package userdata

import (
	"bytes"
	"fmt"
	"testing"

	"github.com/drone/autoscaler"
)

func TestUserdata(t *testing.T) {
	buf := new(bytes.Buffer)
	err := T.Execute(buf, &autoscaler.InstanceCreateOpts{
		Name:    "agent-123456",
		CACert:  []byte(dummyCA),
		TLSKey:  []byte(dummykey),
		TLSCert: []byte(dummyCert),
	})
	if err != nil {
		t.Error(err)
		return
	}
}

func TestUserdataFuncmap(t *testing.T) {
	buf := new(bytes.Buffer)
	err := UD.Execute(buf, &map[string]interface{}{
		"Content": "foo",
	})
	fmt.Println(buf.String())
	if err != nil {
		t.Error(err)
		return
	}
	if buf.String() != UDExpected {
		t.Errorf("expected '%s', got '%s'", UDExpected, buf.String())
	}
}

var dummyCA = `-----BEGIN CERTIFICATE-----
MIIGOTCCBCGgAwIBAgIJAOE/vJd8EB24MA0GCSqGSIb3DQEBBQUAMIGyMQswCQYD
VQQGEwJGUjEPMA0GA1UECAwGQWxzYWNlMRMwEQYDVQQHDApTdHJhc2JvdXJnMRgw
FgYDVQQKDA93d3cuZnJlZWxhbi5vcmcxEDAOBgNVBAsMB2ZyZWVsYW4xLTArBgNV
BAMMJEZyZWVsYW4gU2FtcGxlIENlcnRpZmljYXRlIEF1dGhvcml0eTEiMCAGCSqG
KvbxUcDaVvXB0EU0bg==
-----END CERTIFICATE-----`

var dummykey = `-----BEGIN RSA PRIVATE KEY-----
MIIJKQIBAAKCAgEA3W29+ID6194bH6ejLrIC4hb2Ugo8v6ZC+Mrck2dNYMNPjcOK
ABvxxEtBamnSaeU/IY7FC/giN622LEtV/3oDcrua0+yWuVafyxmZyTKUb4/GUgaf
RQPf/eiX9urWurtIK7XgNGFNUjYPq4dSJQPPhwCHE/LKAykWnZBXRrX0Dq4XyApN
ku0IpjIjEXH+8ixE12wH8wt7DEvdO7T3N3CfUbaITl1qBX+Nm2Z6q4Ag/u5rl8NJ
v3TGd3xXD9yQIjmugNgxNiwAZzhJs/ZJy++fPSJ1XQxbd9qPghgGoe/ff6G7
-----END RSA PRIVATE KEY-----`

var dummyCert = `-----BEGIN CERTIFICATE-----
MIIGJzCCBA+gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBsjELMAkGA1UEBhMCRlIx
d3d3LmZyZWVsYW4ub3JnMRAwDgYDVQQLDAdmcmVlbGFuMS0wKwYDVQQDDCRGcmVl
bGFuIFNhbXBsZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxIjAgBgkqhkiG9w0BCQEW
E2NvbnRhY3RAZnJlZWxhbi5vcmcwHhcNMTIwNDI3MTAzMTE4WhcNMjIwNDI1MTAz
DiH5uEqBXExjrj0FslxcVKdVj5glVcSmkLwZKbEU1OKwleT/iXFhvooWhQ==
-----END CERTIFICATE-----`

var UD = Parse(`#cloud-config

apt_reboot_if_required: 
package_update: false
package_upgrade: false

write_files:
  - path: /etc/systemd/system/docker.service.d/override.conf
    content: | {{nindent .Content 6 }}
`)

var UDExpected = `#cloud-config

apt_reboot_if_required: 
package_update: false
package_upgrade: false

write_files:
  - path: /etc/systemd/system/docker.service.d/override.conf
    content: | 
      foo
`


================================================
FILE: drivers/openstack/create.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package openstack

import (
	"bytes"
	"context"

	"github.com/drone/autoscaler"
	"github.com/drone/autoscaler/logger"
	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips"
	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs"
	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
	"github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
	"github.com/gophercloud/gophercloud/pagination"
)

// Create creates an OpenStack instance
func (p *provider) Create(ctx context.Context, opts autoscaler.InstanceCreateOpts) (*autoscaler.Instance, error) {
	p.init.Do(func() {
		_ = p.setup(ctx)
	})

	buf := new(bytes.Buffer)
	err := p.userdata.Execute(buf, &opts)
	if err != nil {
		return nil, err
	}

	logger := logger.FromContext(ctx).
		WithField("region", p.region).
		WithField("image", p.image).
		WithField("flavor", p.flavor).
		WithField("network", p.network).
		WithField("pool", p.pool).
		WithField("name", opts.Name)

	logger.Debugln("instance create")

	nets := make([]servers.Network, 0)

	if p.network != "" {
		network, err := networks.Get(p.networkClient, p.network).Extract()
		if err != nil {
			logger.WithError(err).
				Debugln("failed to find network")
			return nil, err
		}

		nets = append(nets, servers.Network{
			UUID: network.ID,
		})
	}

	serverCreateOpts := servers.CreateOpts{
		Name:           opts.Name,
		ImageRef:       p.image,
		FlavorRef:      p.flavor,
		Networks:       nets,
		UserData:       buf.Bytes(),
		ServiceClient:  p.computeClient,
		Metadata:       p.metadata,
		SecurityGroups: p.groups,
	}
	createOpts := keypairs.CreateOptsExt{
		CreateOptsBuilder: serverCreateOpts,
		KeyName:           p.key,
	}
	server, err := servers.Create(p.computeClient, createOpts).Extract()
	if err != nil {
		logger.WithError(err).
			Debugln("failed to create server")
		return nil, err
	}

	err = servers.WaitForStatus(p.computeClient, server.ID, "ACTIVE", 300)
	if err != nil {
		logger.WithError(err).
			Debugln("failed waiting for server")
		return nil, err
	}

	instance := &autoscaler.Instance{
		Provider: autoscaler.ProviderOpenStack,
		ID:       server.ID,
		Name:     server.Name,
		Region:   p.region,
		Image:    p.image,
		Size:     p.flavor,
	}

	if p.network != "" {
		network, err := networks.Get(p.networkClient, p.network).Extract()
		if err != nil {
			logger.WithError(err).
				Debugln("failed to find network")
			return nil, err
		}

		if err := servers.ListAddresses(p.computeClient, server.ID).EachPage(func(page pagination.Page) (bool, error) {
			result, err := servers.ExtractAddresses(page)
			if err != nil {
				return false, err
			}

			for name, addresses := range result {
				if name == network.Name {
					for _, address := range addresses {
						instance.Address = address.Address
						return true, nil
					}
				}

			}

			return false, nil
		}); err != nil {
			logger.WithError(err).
				Debugln("failed to fetch address")
			return nil, err
		}
	}

	if p.pool != "" {
		ip, err := floatingips.Create(p.computeClient, floatingips.CreateOpts{
			Pool: p.pool,
		}).Extract()
		if err != nil {
			logger.WithError(err).
				Debugln("failed to create floating ip")
			return nil, err
		}

		if err := floatingips.AssociateInstance(p.computeClient, server.ID, floatingips.AssociateOpts{
			FloatingIP: ip.IP,
		}).ExtractErr(); err != nil {
			logger.WithError(err).
				Debugln("failed to associate floating ip")
			return nil, err
		}

		instance.Address = ip.IP
	}

	logger.
		WithField("name", instance.Name).
		WithField("ip", instance.Address).
		Debugln("instance network ready")

	return instance, nil
}


================================================
FILE: drivers/openstack/create_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package openstack

import (
	"context"
	"os"
	"testing"

	"github.com/drone/autoscaler"
	"github.com/h2non/gock"
)

func TestCreate(t *testing.T) {
	defer gock.Off()
	setupEnv(t)

	authResp1 := helperLoad(t, "authresp1.json")
	gock.New("http://ops.my.cloud").
		Get("/identity").
		Reply(300).
		SetHeader("Content-Type", "application/json").
		BodyString(string(authResp1))

	tokenResp1 := helperLoad(t, "tokenresp1.json")
	gock.New("http://ops.my.cloud").
		Post("/identity/v3/auth/tokens").
		Reply(201).
		SetHeader("Content-Type", "application/json").
		SetHeader("X-Subject-Token", authToken).
		BodyString(string(tokenResp1))

	authResp2 := helperLoad(t, "authresp1.json")
	gock.New("http://ops.my.cloud").
		Get("/identity").
		Reply(300).
		SetHeader("Content-Type", "application/json").
		BodyString(string(authResp2))

	tokenResp2 := helperLoad(t, "tokenresp1.json")
	gock.New("http://ops.my.cloud").
		Post("/identity/v3/auth/tokens").
		Reply(201).
		SetHeader("Content-Type", "application/json").
		SetHeader("X-Subject-Token", authToken).
		BodyString(string(tokenResp2))

	fipResp1 := helperLoad(t, "fipresp1.json")
	gock.New("http://ops.my.cloud").
		Post("/compute/v2.1/os-floating-ips").
		MatchHeader("X-Auth-Token", authToken).
		Reply(200).
		SetHeader("Content-Type", "application/json").
		BodyString(string(fipResp1))

	imageListResp := helperLoad(t, "imagelistresp1.json")
	gock.New("http://ops.my.cloud").
		Get("/compute/v2.1/images/detail").
		MatchHeader("X-Auth-Token", authToken).
		Reply(200).
		SetHeader("Content-Type", "application/json").
		BodyString(string(imageListResp))

	flavorListResp1 := helperLoad(t, "flavorlistresp1.json")
	gock.New("http://ops.my.cloud").
		Get("/compute/v2.1/flavors/detail").
		MatchHeader("X-Auth-Token", authToken).
		Reply(200).
		SetHeader("Content-Type", "application/json").
		BodyString(string(flavorListResp1))

	serverCreateResp1 := helperLoad(t, "servercreateresp1.json")
	gock.New("http://ops.my.cloud").
		Post("/compute/v2.1/servers").
		MatchHeader("X-Auth-Token", authToken).
		Reply(202).
		SetHeader("Content-Type", "application/json").
		BodyString(string(serverCreateResp1))

	serverStatusResp1 := helperLoad(t, "serverstatusresp1.json")
	gock.New("http://ops.my.cloud").
		Get("/compute/v2.1/servers/56046f6d-3184-495b-938b-baa450db970d").
		MatchHeader("X-Auth-Token", authToken).
		Reply(200).
		SetHeader("Content-Type", "application/json").
		BodyString(string(serverStatusResp1))

	associateResp1 := helperLoad(t, "associateresp1.json")
	gock.New("http://ops.my.cloud").
		Post("/compute/v2.1/servers/56046f6d-3184-495b-938b-baa450db970d/action").
		MatchHeader("X-Auth-Token", authToken).
		BodyString(string("{\"addFloatingIp\":{\"address\":\"172.24.4.5\"}}")).
		Reply(202).
		SetHeader("Content-Type", "application/json").
		BodyString(string(associateResp1))

	v, err := New(
		WithRegion("RegionOne"),
		WithFlavor("m1.small"),
		WithImage("ubuntu-16.04-server-latest"),
		WithFloatingIpPool("public"),
		WithSSHKey("drone-ci-key"),
	)
	if err != nil {
		t.Error(err)
		return
	}
	p := v.(*provider)
	p.init.Do(func() {}) // prevent init function

	instance, err := p.Create(context.TODO(), autoscaler.InstanceCreateOpts{Name: "agent-RjISb5v1"})
	if err != nil {
		t.Error(err)
	}

	if !gock.IsDone() {
		t.Error("Not all expected http requests completed")
	}
	t.Run("Instance Attributes", testInstance(instance))
}

func TestAuthFail(t *testing.T) {
	defer gock.Off()
	setupEnv(t)

	err := os.Setenv("OS_PASSWORD", "BAADF00D")
	if err != nil {
		t.Error("Unable to set OS_PASSWORD")
	}

	authResp1 := helperLoad(t, "authresp1.json")
	gock.New("http://ops.my.cloud").
		Get("/identity").
		Reply(300).
		SetHeader("Content-Type", "application/json").
		BodyString(string(authResp1))

	gock.New("http://ops.my.cloud").
		Post("/identity/v3/auth/tokens").
		Reply(401)

	_, err = New(
		WithRegion("RegionOne"),
		WithFlavor("m1.small"),
		WithImage("ubuntu-16.04-server-latest"),
		WithFloatingIpPool("public"),
		WithSSHKey("drone-ci-key"),
	)

	if err == nil {
		t.Error("Expected authentication error from OpenStack")
	}

	if !gock.IsDone() {
		t.Error("Not all expected http requests completed")
	}
}

func TestCreateFail(t *testing.T) {
	defer gock.Off()
	setupEnv(t)

	authResp1 := helperLoad(t, "authresp1.json")
	gock.New("http://ops.my.cloud").
		Get("/identity").
		Reply(300).
		SetHeader("Content-Type", "application/json").
		BodyString(string(authResp1))

	tokenResp1 := helperLoad(t, "tokenresp1.json")
	gock.New("http://ops.my.cloud").
		Post("/identity/v3/auth/tokens").
		Reply(201).
		SetHeader("Content-Type", "application/json").
		SetHeader("X-Subject-Token", authToken).
		BodyString(string(tokenResp1))

	authResp2 := helperLoad(t, "authresp1.json")
	gock.New("http://ops.my.cloud").
		Get("/identity").
		Reply(300).
		SetHeader("Content-Type", "application/json").
		BodyString(string(authResp2))

	tokenResp2 := helperLoad(t, "tokenresp1.json")
	gock.New("http://ops.my.cloud").
		Post("/identity/v3/auth/tokens").
		Reply(201).
		SetHeader("Content-Type", "application/json").
		SetHeader("X-Subject-Token", authToken).
		BodyString(string(tokenResp2))

	imageListResp := helperLoad(t, "imagelistresp1.json")
	gock.New("http://ops.my.cloud").
		Get("/compute/v2.1/images/detail").
		MatchHeader("X-Auth-Token", authToken).
		Reply(200).
		SetHeader("Content-Type", "application/json").
		BodyString(string(imageListResp))

	flavorListResp1 := helperLoad(t, "flavorlistresp1.json")
	gock.New("http://ops.my.cloud").
		Get("/compute/v2.1/flavors/detail").
		MatchHeader("X-Auth-Token", authToken).
		Reply(200).
		SetHeader("Content-Type", "application/json").
		BodyString(string(flavorListResp1))

	gock.New("http://ops.my.cloud").
		Post("/compute/v2.1/servers").
		MatchHeader("X-Auth-Token", authToken).
		Reply(500)

	v, err := New(
		WithRegion("RegionOne"),
		WithFlavor("m1.small"),
		WithImage("ubuntu-16.04-server-latest"),
		WithFloatingIpPool("public"),
		WithSSHKey("drone-ci-key"),
	)
	if err != nil {
		t.Error(err)
		return
	}
	p := v.(*provider)
	p.init.Do(func() {}) // prevent init function

	_, err = p.Create(context.TODO(), autoscaler.InstanceCreateOpts{Name: "agent-RjISb5v1"})
	if err == nil {
		t.Error("Expected error creating instance")
	}

	if !gock.IsDone() {
		t.Error("Not all expected http requests completed")
	}
}

func setupEnv(t *testing.T) {
	err := os.Setenv("OS_AUTH_URL", "http://ops.my.cloud/identity")
	if err != nil {
		t.Error("Unable to set OS_AUTH_URL")
	}
	err = os.Setenv("OS_USERNAME", "admin")
	if err != nil {
		t.Error("Unable to set OS_USERNAME")
	}
	err = os.Setenv("OS_PASSWORD", "admin")
	if err != nil {
		t.Error("Unable to set OS_USERNAME")
	}
	err = os.Setenv("OS_DOMAIN_NAME", "demo")
	if err != nil {
		t.Error("Unable to set OS_DOMAIN_NAME")
	}
}

func testInstance(instance *autoscaler.Instance) func(t *testing.T) {
	return func(t *testing.T) {
		if instance == nil {
			t.Errorf("Expect non-nil instance even if error")
		}
		if want, got := instance.Address, "172.24.4.5"; got != want {
			t.Errorf("Want instance IP %q, got %q", want, got)
		}
		if want, got := instance.Image, "4ef19958-ee2d-44a7-a100-de0b8afdbc8e"; got != want {
			t.Errorf("Want instance ID %q, got %q", want, got)
		}
		if want, got := instance.ID, "56046f6d-3184-495b-938b-baa450db970d"; got != want {
			t.Errorf("Want instance ID %q, got %q", want, got)
		}
		if want, got := instance.Name, "agent-RjISb5v1"; got != want {
			t.Errorf("Want instance Name %q, got %q", want, got)
		}
		if want, got := instance.Provider, autoscaler.ProviderOpenStack; got != want {
			t.Errorf("Want OpenStack Provider type")
		}
		if want, got := instance.Region, "RegionOne"; got != want {
			t.Errorf("Want instance Region %q, got %q", want, got)
		}
		if want, got := instance.Size, "29e3cce3-d771-4220-80fe-3edf0e8dd466"; got != want {
			t.Errorf("Want instance Size %q, got %q", want, got)
		}
	}
}


================================================
FILE: drivers/openstack/destroy.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package openstack

import (
	"context"
	"fmt"

	"github.com/drone/autoscaler"
	"github.com/drone/autoscaler/logger"

	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips"
	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
	"github.com/gophercloud/gophercloud/pagination"
)

func (p *provider) Destroy(ctx context.Context, instance *autoscaler.Instance) error {
	logger := logger.FromContext(ctx).
		WithField("region", instance.Region).
		WithField("image", instance.Image).
		WithField("flavor", instance.Size).
		WithField("name", instance.Name)

	logger.Debugln("deleting instance")

	err := p.deleteFloatingIps(instance)
	if err != nil {
		logger.WithError(err).
			Debugln("failed to delete floating ips")

		return err
	}

	err = servers.Delete(p.computeClient, instance.ID).ExtractErr()
	if err == nil {
		logger.Debugln("instance deleted")
		return nil
	}

	if err.Error() == "Resource not found" {
		logger.WithError(err).
			Debugln("instance does not exist")
		return autoscaler.ErrInstanceNotFound
	}

	logger.WithError(err).
		Errorln("attempting to force delete")

	err = servers.ForceDelete(p.computeClient, instance.ID).ExtractErr()
	if err == nil {
		logger.Debugln("instance deleted")
		return nil
	}

	if err.Error() == "Resource not found" {
		logger.WithError(err).
			Debugln("instance does not exist")
		return autoscaler.ErrInstanceNotFound
	}

	logger.WithError(err).
		Errorln("force-deleting instance failed")

	return err
}

func (p *provider) deleteFloatingIps(instance *autoscaler.Instance) error {
	return floatingips.List(p.computeClient).EachPage(func(page pagination.Page) (bool, error) {
		ips, err := floatingips.ExtractFloatingIPs(page)
		if err != nil {
			return false, err
		}

		for _, ip := range ips {
			if ip.InstanceID == instance.ID {
				if err := floatingips.DisassociateInstance(p.computeClient, instance.ID, floatingips.DisassociateOpts{
					FloatingIP: ip.IP,
				}).ExtractErr(); err != nil {
					return false, fmt.Errorf("failed to disassociate floating ip: %s", err)
				}

				if err := floatingips.Delete(p.computeClient, ip.ID).ExtractErr(); err != nil {
					return false, fmt.Errorf("failed to delete floating ip: %s", err)
				}
			}
		}

		return true, nil
	})
}


================================================
FILE: drivers/openstack/destroy_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package openstack

import (
	"context"
	"testing"

	"github.com/drone/autoscaler"
	"github.com/h2non/gock"
)

func TestDestroy(t *testing.T) {
	defer gock.Off()
	setupEnv(t)

	authResp1 := helperLoad(t, "authresp1.json")
	gock.New("http://ops.my.cloud").
		Get("/identity").
		Reply(300).
		SetHeader("Content-Type", "application/json").
		BodyString(string(authResp1))

	tokenResp1 := helperLoad(t, "tokenresp1.json")
	gock.New("http://ops.my.cloud").
		Post("/identity/v3/auth/tokens").
		Reply(201).
		SetHeader("Content-Type", "application/json").
		SetHeader("X-Subject-Token", authToken).
		BodyString(string(tokenResp1))

	authResp2 := helperLoad(t, "authresp1.json")
	gock.New("http://ops.my.cloud").
		Get("/identity").
		Reply(300).
		SetHeader("Content-Type", "application/json").
		BodyString(string(authResp2))

	tokenResp2 := helperLoad(t, "tokenresp1.json")
	gock.New("http://ops.my.cloud").
		Post("/identity/v3/auth/tokens").
		Reply(201).
		SetHeader("Content-Type", "application/json").
		SetHeader("X-Subject-Token", authToken).
		BodyString(string(tokenResp2))

	fipResp1 := helperLoad(t, "fipresp1.json")
	gock.New("http://ops.my.cloud").
		MatchHeader("X-Auth-Token", authToken).
		Get("/compute/v2.1/os-floating-ips").
		Reply(200).
		SetHeader("Content-Type", "application/json").
		BodyString(string(fipResp1))

	gock.New("http://ops.my.cloud").
		MatchHeader("X-Auth-Token", authToken).
		Delete("/compute/v2.1/servers/56046f6d-3184-495b-938b-baa450db970d").
		Reply(204)

	imageListResp := helperLoad(t, "imagelistresp1.json")
	gock.New("http://ops.my.cloud").
		Get("/compute/v2.1/images/detail").
		MatchHeader("X-Auth-Token", authToken).
		Reply(200).
		SetHeader("Content-Type", "application/json").
		BodyString(string(imageListResp))

	flavorListResp1 := helperLoad(t, "flavorlistresp1.json")
	gock.New("http://ops.my.cloud").
		Get("/compute/v2.1/flavors/detail").
		MatchHeader("X-Auth-Token", authToken).
		Reply(200).
		SetHeader("Content-Type", "application/json").
		BodyString(string(flavorListResp1))

	mockContext := context.TODO()
	mockInstance := &autoscaler.Instance{
		ID:      "56046f6d-3184-495b-938b-baa450db970d",
		Address: "172.24.4.5",
	}

	v, err := New(
		WithRegion("RegionOne"),
		WithFlavor("m1.small"),
		WithImage("ubuntu-16.04-server-latest"),
		WithFloatingIpPool("public"),
		WithSSHKey("drone-ci-key"),
	)
	if err != nil {
		t.Error(err)
		return
	}
	p := v.(*provider)
	p.init.Do(func() {}) //

	err = p.Destroy(mockContext, mockInstance)
	if err != nil {
		t.Error(err)
	}

	if !gock.IsDone() {
		t.Error("Not all expected http requests completed")
	}
}


================================================
FILE: drivers/openstack/doc.go
================================================
/*
Package openstack contains a autoscaler driver for OpenStack
Configuration:

Authenticate with the usual OpenStack environment variables.
(Not all of these may be necessary:
see https://github.com/gophercloud/gophercloud/blob/master/openstack/auth_env.go)

OS_AUTH_URL=https://my.openstack.cloud:5000
OS_ENDPOINT_TYPE=publicURL
OS_IDENTITY_API_VERSION=2
OS_PASSWORD=<mypassword>
OS_DOMAIN_ID=default
OS_REGION_NAME=my-region
OS_TENANT_ID=my-tenant-id
OS_TENANT_NAME=my-tenant-name
OS_USERNAME=my-username

Configure driver with:
DRONE_OPENSTACK_SSHKEY=drone-key-name
DRONE_OPENSTACK_SECURITY_GROUP=my-security-group
# Pool for floating ips
DRONE_OPENSTACK_IP_POOL=my-ip-pool
DRONE_OPENSTACK_FLAVOR=v1-standard-2
DRONE_OPENSTACK_IMAGE=ubuntu-16.04-server-latest
DRONE_OPENSTACK_METADATA=name:agent,owner:drone-ci

*/
package openstack


================================================
FILE: drivers/openstack/option.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package openstack

import (
	"io/ioutil"

	"github.com/drone/autoscaler/drivers/internal/userdata"
	"github.com/gophercloud/gophercloud"
)

type Option func(*provider)

// WithImage returns an option to set the instance image.
func WithImage(image string) Option {
	return func(p *provider) {
		p.image = image
	}
}

// WithRegion returns an option to set the OpenStack target region.
func WithRegion(region string) Option {
	return func(p *provider) {
		p.region = region
	}
}

// WithFlavor returns an option to set the instance flavor.
func WithFlavor(flavor string) Option {
	return func(p *provider) {
		p.flavor = flavor
	}
}

// WithSecurityGroup returns an option to set the instance security groups.
func WithSecurityGroup(group ...string) Option {
	return func(p *provider) {
		p.groups = group
	}
}

// WithComputeClient returns an option to set the
// GopherCloud ServiceClient.
func WithComputeClient(computeClient *gophercloud.ServiceClient) Option {
	return func(p *provider) {
		p.computeClient = computeClient
	}
}

// WithNetworkClient returns an option to set the
// GopherCloud ServiceClient.
func WithNetworkClient(networkClient *gophercloud.ServiceClient) Option {
	return func(p *provider) {
		p.networkClient = networkClient
	}
}

// WithSSHKey returns an option to set the ssh key.
func WithSSHKey(key string) Option {
	return func(p *provider) {
		p.key = key
	}
}

// WithNetwork returns an option to set the network id.
func WithNetwork(id string) Option {
	return func(p *provider) {
		p.network = id
	}
}

func WithFloatingIpPool(pool string) Option {
	return func(p *provider) {
		p.pool = pool
	}
}

// WithMetadata returns an option to set the instance metadata.
func WithMetadata(metadata map[string]string) Option {
	return func(p *provider) {
		p.metadata = metadata
	}
}

// WithUserData returns an option to set the cloud-init
// template from text.
func WithUserData(text string) Option {
	return func(p *provider) {
		if text != "" {
			p.userdata = userdata.Parse(text)
		}
	}
}

// WithUserDataFile returns an option to set the cloud-init
// template from file.
func WithUserDataFile(filepath string) Option {
	return func(p *provider) {
		if filepath != "" {
			b, err := ioutil.ReadFile(filepath)
			if err != nil {
				panic(err)
			}
			p.userdata = userdata.Parse(string(b))
		}
	}
}


================================================
FILE: drivers/openstack/option_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package openstack

import (
	"testing"

	"github.com/gophercloud/gophercloud"
)

func TestOptions(t *testing.T) {
	v, err := New(
		WithComputeClient(&gophercloud.ServiceClient{}),
		WithNetworkClient(&gophercloud.ServiceClient{}),
		WithFloatingIpPool("ext-ips-1"),
		WithFlavor("053dc448-045b-4c15-a4a0-1908b6b9310d"),
		WithSecurityGroup("drone-ci"),
		WithSSHKey("drone-ci"),
		WithRegion("sto-01"),
		WithImage("0e9fe318-568f-417e-b2c1-f1218aa2712f"),
		WithMetadata(map[string]string{"foo": "bar", "baz": "qux"}),
		WithNetwork("c7d172c8-96e6-40ab-aaaa-4a555e247c73"),
	)
	if err != nil {
		t.Error(err)
		return
	}
	p := v.(*provider)

	if got, want := p.pool, "ext-ips-1"; got != want {
		t.Errorf("Want pool %q, got %q", want, got)
	}
	if got, want := p.region, "sto-01"; got != want {
		t.Errorf("Want region %q, got %q", want, got)
	}
	if got, want := p.flavor, "053dc448-045b-4c15-a4a0-1908b6b9310d"; got != want {
		t.Errorf("Want flavor %q, got %q", want, got)
	}
	if got, want := p.image, "0e9fe318-568f-417e-b2c1-f1218aa2712f"; got != want {
		t.Errorf("Want image %q, got %q", want, got)
	}
	if got, want := p.network, "c7d172c8-96e6-40ab-aaaa-4a555e247c73"; got != want {
		t.Errorf("Want network %q, got %q", want, got)
	}
	if got, want := p.key, "drone-ci"; got != want {
		t.Errorf("Want key %q, got %q", want, got)
	}
	if got, want := len(p.metadata), 2; got != want {
		t.Errorf("Want %d tags, got %d", want, got)
	}
	if got, want := p.metadata["foo"], "bar"; got != want {
		t.Errorf("Want foo=%q metadata, got foo=%q", want, got)
	}
	if got, want := p.metadata["baz"], "qux"; got != want {
		t.Errorf("Want baz=%q metadata, got baz=%q", want, got)
	}
}


================================================
FILE: drivers/openstack/provider.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package openstack

import (
	"regexp"
	"sync"
	"text/template"

	"github.com/drone/autoscaler"
	"github.com/drone/autoscaler/drivers/internal/userdata"
	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack"
	"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
	"github.com/gophercloud/gophercloud/openstack/compute/v2/images"
	"github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
)

// provider implements an OpenStack provider
type provider struct {
	init sync.Once

	key      string
	region   string
	image    string
	flavor   string
	network  string
	pool     string
	userdata *template.Template
	groups   []string
	metadata map[string]string

	computeClient *gophercloud.ServiceClient
	networkClient *gophercloud.ServiceClient
}

// New returns a new OpenStack provider.
func New(opts ...Option) (autoscaler.Provider, error) {
	p := new(provider)
	for _, opt := range opts {
		opt(p)
	}

	if p.userdata == nil {
		p.userdata = userdata.T
	}

	if p.computeClient == nil {
		authOpts, err := openstack.AuthOptionsFromEnv()
		if err != nil {
			return nil, err
		}

		authClient, err := openstack.AuthenticatedClient(authOpts)
		if err != nil {
			return nil, err
		}

		p.computeClient, err = openstack.NewComputeV2(authClient, gophercloud.EndpointOpts{
			Region: p.region,
		})
		if err != nil {
			return nil, err
		}
	}

	if p.networkClient == nil {
		authOpts, err := openstack.AuthOptionsFromEnv()
		if err != nil {
			return nil, err
		}

		authClient, err := openstack.AuthenticatedClient(authOpts)
		if err != nil {
			return nil, err
		}

		p.networkClient, err = openstack.NewNetworkV2(authClient, gophercloud.EndpointOpts{
			Region: p.region,
		})
		if err != nil {
			return nil, err
		}
	}

	if p.image != "" && !isUUID(p.image) {
		uuid, err := images.IDFromName(p.computeClient, p.image)
		if err != nil {
			return nil, err
		}
		p.image = uuid
	}

	if p.flavor != "" && !isUUID(p.flavor) {
		uuid, err := flavors.IDFromName(p.computeClient, p.flavor)
		if err != nil {
			return nil, err
		}
		p.flavor = uuid
	}

	if p.network != "" && !isUUID(p.network) {
		uuid, err := networks.IDFromName(p.networkClient, p.network)
		if err != nil {
			return nil, err
		}
		p.network = uuid
	}

	return p, nil
}

func isUUID(uuid string) bool {
	r := regexp.MustCompile("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$")
	return r.MatchString(uuid)
}


================================================
FILE: drivers/openstack/provider_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package openstack

import (
	"testing"

	"github.com/gophercloud/gophercloud"
)

func TestDefaults(t *testing.T) {
	v, err := New(
		WithComputeClient(&gophercloud.ServiceClient{}),
		WithNetworkClient(&gophercloud.ServiceClient{}),
	)
	if err != nil {
		t.Error(err)
		return
	}
	p := v.(*provider)
	// Add tests if we set some actual defaults in the future.
	_ = p
}


================================================
FILE: drivers/openstack/setup.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package openstack

import (
	"context"
	"errors"

	"github.com/drone/autoscaler/logger"

	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs"
	"golang.org/x/sync/errgroup"
)

func (p *provider) setup(ctx context.Context) error {
	var g errgroup.Group
	if p.key == "" {
		g.Go(func() error {
			return p.findKeyPair(ctx)
		})
	}

	return g.Wait()
}

func (p *provider) findKeyPair(ctx context.Context) error {
	logger := logger.FromContext(ctx)

	logger.Debugln("finding default ssh key")

	allPages, err := keypairs.List(p.computeClient).AllPages()
	if err != nil {
		return err
	}
	keys, err := keypairs.ExtractKeyPairs(allPages)

	if err != nil {
		return err
	}

	index := map[string]keypairs.KeyPair{}
	for _, key := range keys {
		index[key.Name] = key
	}

	// if the account has multiple keys configured we will
	// attempt to use an existing key based on naming convention.
	for _, name := range []string{"drone", "id_rsa_drone"} {
		key, ok := index[name]
		if !ok {
			continue
		}
		p.key = key.Name

		logger.
			WithField("name", name).
			WithField("fingerprint", key.Fingerprint).
			Debugln("using default ssh key")
		return nil
	}

	// if there were no matches but the account has at least
	// one keypair already created we will select the first
	// in the list.
	if len(keys) > 0 {
		key := keys[0]
		p.key = key.Name

		logger.
			WithField("name", key.Name).
			WithField("fingerprint", key.Fingerprint).
			Debugln("using default ssh key")
		return nil
	}
	return errors.New("no matching keys")
}


================================================
FILE: drivers/openstack/setup_test.go
================================================
// Copyright 2018 Drone.IO Inc
// Use of this source code is governed by the Polyform License
// that can be found in the LICENSE file.

package openstack

import (
	"io/ioutil"
	"path/filepath"
	"testing"
)

func helperLoad(t *testing.T, name string) []byte {
	path := filepath.Join("testdata", name) // relative path
	bytes, err := ioutil.ReadFile(path)
	if err != nil {
		t.Fatal(err)
	}
	return bytes
}

const authToken = "gAAAAABb1tQPtYVBv68airR0dgKC2vXpkLNfEHx0w1EL89dOOjKrtdYHR7IZrDd4VjwZapC5Sri4CndpPscw-nHoh0VQsrvFjtuvT6M64RdrrOljmJbvP0o7PbV713-Pi8OpRIfunvsQFnEQ2DxDH56QC6fsLEcF14VtogOQwTRBod0SkeOCpi4"

================================================
FILE: drivers/openstack/testdata/associateresp1.json
================================================


================================================
FILE: drivers/openstack/testdata/authresp1.json
================================================
{
  "versions": {
    "values": [
      {
        "status": "stable",
        "updated": "2018-10-15T00:00:00Z",
        "media-types": [
          {
            "base": "application/json",
            "type": "application/vnd.openstack.identity-v3+json"
          }
        ],
        "id": "v3.11",
        "links": [
          {
            "href": "http://ops.my.cloud/identity/v3/",
            "rel": "self"
          }
        ]
      }
    ]
  }
}


================================================
FILE: drivers/openstack/testdata/fipresp1.json
================================================
{
  "floating_ip": {
    "instance_id": null,
    "ip": "172.24.4.5",
    "fixed_ip": null,
    "id": "0f013e62-42b1-461c-af7c-8aa3c705ff29",
    "pool": "public"
  }
}


================================================
FILE: drivers/openstack/testdata/flavorlistresp1.json
================================================
{
  "flavors": [
    {
      "name": "m1.tiny",
      "links": [
        {
          "href": "http://ops.my.cloud/compute/v2.1/flavors/20f8acd8-5660-45d2-a176-1dafe98d591f",
          "rel": "self"
        },
        {
          "href": "http://ops.my.cloud/compute/flavors/20f8acd8-5660-45d2-a176-1dafe98d591f",
          "rel": "bookmark"
        }
      ],
      "ram": 512,
      "OS-FLV-DISABLED:disabled": false,
      "vcpus": 1,
      "swap": "",
      "os-flavor-access:is_public": true,
      "rxtx_factor": 1.0,
      "OS-FLV-EXT-DATA:ephemeral": 0,
      "disk": 1,
      "id": "20f8acd8-5660-45d2-a176-1dafe98d591f"
    },
    {
      "name": "m1.small",
      "links": [
        {
          "href": "http://ops.my.cloud/compute/v2.1/flavors/29e3cce3-d771-4220-80fe-3edf0e8dd466",
          "rel": "self"
        },
        {
          "href": "http://ops.my.cloud/compute/flavors/29e3cce3-d771-4220-80fe-3edf0e8dd466",
          "rel": "bookmark"
        }
      ],
      "ram": 2048,
      "OS-FLV-DISABLED:disabled": false,
      "vcpus": 1,
      "swap": "",
      "os-flavor-access:is_public": true,
      "rxtx_factor": 1.0,
      "OS-FLV-EXT-DATA:ephemeral": 0,
      "disk": 20,
      "id": "29e3cce3-d771-4220-80fe-3edf0e8dd466"
    },
    {
      "name": "m1.medium",
      "links": [
        {
          "href": "http://ops.my.cloud/compute/v2.1/flavors/2fe9e665-cf0f-4bff-a2a7-a0c19b15da7b",
          "rel": "self"
        },
        {
          "href": "http://ops.my.cloud/compute/flavors/2fe9e665-cf0f-4bff-a2a7-a0c19b15da7b",
          "rel": "bookmark"
        }
      ],
      "ram": 4096,
      "OS-FLV-DISABLED:disabled": false,
      "vcpus": 2,
      "swap": "",
      "os-flavor-access:is_public": true,
      "rxtx_factor": 1.0,
      "OS-FLV-EXT-DATA:ephemeral": 0,
      "disk": 40,
      "id": "2fe9e665-cf0f-4bff-a2a7-a0c19b15da7b"
    },
    {
      "name": "m1.large",
      "links": [
        {
          "href": "http://ops.my.cloud/compute/v2.1/flavors/43832d64-56ed-401f-953c-d4d4c156f33a",
          "rel": "self"
        },
        {
          "href": "http://ops.my.cloud/compute/flavors/43832d64-56ed-401f-953c-d4d4c156f33a",
          "rel": "bookmark"
        }
      ],
      "ram": 8192,
      "OS-FLV-DISABLED:disabled": false,
      "vcpus": 4,
      "swap": "",
      "os-flavor-access:is_public": true,
      "rxtx_factor": 1.0,
      "OS-FLV-EXT-DATA:ephemeral": 0,
      "disk": 80,
      "id": "43832d64-56ed-401f-953c-d4d4c156f33a"
    },
    {
      "name": "m1.xlarge",
      "links": [
        {
          "href": "http://ops.my.cloud/compute/v2.1/flavors/618945e9-beb4-4f20-88fd-e044a228156f",
          "rel": "self"
        },
        {
          "href": "http://ops.my.cloud/compute/flavors/618945e9-beb4-4f20-88fd-e044a228156f",
          "rel": "bookmark"
        }
      ],
      "ram": 16384,
      "OS-FLV-DISABLED:disabled": false,
      "vcpus": 8,
      "swap": "",
      "os-flavor-access:is_public": true,
      "rxtx_factor": 1.0,
      "OS-FLV-EXT-DATA:ephemeral": 0,
      "disk": 160,
      "id": "618945e9-beb4-4f20-88fd-e044a228156f"
    },
    {
      "name": "cirros256",
      "links": [
        {
          "href": "http://ops.my.cloud/compute/v2.1/flavors/67a446b2-53c3-460b-a2da-533ce9a0c527",
          "rel": "self"
        },
        {
          "href": "http://ops.my.cloud/compute/flavors/67a446b2-53c3-460b-a2da-533ce9a0c527",
          "rel": "bookmark"
        }
      ],
      "ram": 256,
      "OS-FLV-DISABLED:disabled": false,
      "vcpus": 1,
      "swap": "",
      "os-flavor-access:is_public": true,
      "rxtx_factor": 1.0,
      "OS-FLV-EXT-DATA:ephemeral": 0,
      "disk": 0,
      "id": "67a446b2-53c3-460b-a2da-533ce9a0c527"
    },
    {
      "name": "ds512M",
      "links": [
        {
          "href": "http://ops.my.cloud/compute/v2.1/flavors/c7d172c8-96e6-40ab-aaaa-4a555e247c73",
          "rel": "self"
        },
        {
          "href": "http://ops.my.cloud/compute/flavors/c7d172c8-96e6-40ab-aaaa-4a555e247c73",
          "rel": "bookmark"
        }
      ],
      "ram": 512,
      "OS-FLV-DISABLED:disabled": false,
      "vcpus": 1,
      "swap": "",
      "os-flavor-access:is_public": true,
      "rxtx_factor": 1.0,
      "OS-FLV-EXT-DATA:ephemeral": 0,
      "disk": 5,
      "id": "c7d172c8-96e6-40ab-aaaa-4a555e247c73"
    },
    {
      "name": "ds1G",
      "links": [
        {
          "href": "http://ops.my.cloud/compute/v2.1/flavors/c3a01655-1b6a-44aa-b095-cc28dd407e70",
          "rel": "self"
        },
        {
          "href": "http://ops.my.cloud/compute/flavors/c3a01655-1b6a-44aa-b095-cc28dd407e70",
          "rel": "bookmark"
        }
      ],
      "ram": 1024,
      "OS-FLV-DISABLED:disabled": false,
      "vcpus": 1,
      "swap": "",
      "os-flavor-access:is_public": true,
      "rxtx_factor": 1.0,
      "OS-FLV-EXT-DATA:ephemeral": 0,
      "disk": 10,
      "id": "c3a01655-1b6a-44aa-b095-cc28dd407e70"
    },
    {
      "name": "ds2G",
      "links": [
        {
          "href": "http://ops.my.cloud/compute/v2.1/flavors/053dc448-045b-4c15-a4a0-1908b6b9310d",
          "rel": "self"
        },
        {
          "href": "http://ops.my.cloud/compute/flavors/053dc448-045b-4c15-a4a0-1908b6b9310d",
          "rel": "bookmark"
        }
      ],
      "ram": 2048,
      "OS-FLV-DISABLED:disabled": false,
      "vcpus": 2,
      "swap": "",
      "os-flavor-access:is_public": true,
      "rxtx_factor": 1.0,
      "OS-FLV-EXT-DATA:ephemeral": 0,
      "disk": 10,
      "id": "053dc448-045b-4c15-a4a0-1908b6b9310d"
    },
    {
      "name": "ds4G",
      "links": [
        {
          "href": "http://ops.my.cloud/compute/v2.1/flavors/0e9fe318-568f-417e-b2c1-f1218aa2712f",
          "rel": "self"
        },
        {
          "href": "http://ops.my.cloud/compute/flavors/0e9fe318-568f-417e-b2c1-f1218aa2712f",
          "rel": "bookmark"
        }
      ],
      "ram": 4096,
      "OS-FLV-DISABLED:disabled": false,
      "vcpus": 4,
      "swap": "",
      "os-flavor-access:is_public": true,
      "rxtx_factor": 1.0,
      "OS-FLV-EXT-DATA:ephemeral": 0,
      "disk": 20,
      "id": "0e9fe318-568f-417e-b2c1-f1218aa2712f"
    }
  ]
}


================================================
FILE: drivers/openstack/testdata/imagelistresp1.json
================================================
{
  "images": [
    {
      "status": "ACTIVE",
      "updated": "2018-10-26T14:29:41Z",
      "links": [
        {
          "href": "http://ops.my.cloud/compute/v2.1/images/ee7d6850-0592-4036-bd6e-198b41df7381",
          "rel": "self"
        },
        {
          "href": "http://ops.my.cloud/compute/images/ee7d6850-0592-4036-bd6e-198b41df7381",
          "rel": "bookmark"
        },
        {
          "href": "http://ops.my.cloud/image/images/ee7d6850-0592-4036-bd6e-198b41df7381",
          "type": "application/vnd.openstack.image",
          "rel": "alternate"
        }
      ],
      "id": "ee7d6850-0592-4036-bd6e-198b41df7381",
      "OS-EXT-IMG-SIZE:size": 74448896,
      "name": "rancheros-v1.4.1",
      "created": "2018-10-26T14:29:39Z",
      "minDisk": 0,
      "progress": 100,
      "minRam": 0,
      "metadata": {
        "description": "RancherOS v1.4.1"
      }
    },
    {
      "status": "ACTIVE",
      "updated": "2018-10-23T13:20:03Z",
      "links": [
        {
          "href": "http://ops.my.cloud/compute/v2.1/images/4ef19958-ee2d-44a7-a100-de0b8afdbc8e",
          "rel": "self"
        },
        {
          "href": "http://ops.my.cloud/compute/images/4ef19958-ee2d-44a7-a100-de0b8afdbc8e",
          "rel": "bookmark"
        },
        {
          "href": "http://ops.my.cloud/image/images/4ef19958-ee2d-44a7-a100-de0b8afdbc8e",
          "type": "application/vnd.openstack.image",
          "rel": "alternate"
        }
      ],
      "id": "4ef19958-ee2d-44a7-a100-de0b8afdbc8e",
      "OS-EXT-IMG-SIZE:size": 296943616,
      "name": "ubuntu-16.04-server-latest",
      "created": "2018-10-23T13:19:58Z",
      "minDisk": 0,
      "progress": 100,
      "minRam": 0,
      "metadata": {
        "description": "Ubuntu 16.04 LTS"
      }
    },
    {
      "status": "ACTIVE",
      "updated": "2018-10-22T12:03:52Z",
      "links": [
        {
          "href": "http://ops.my.cloud/compute/v2.1/images/7fd93141-c387-4859-bc79-b92fac420473",
          "rel": "self"
        },
        {
          "href": "http://ops.my.cloud/compute/images/7fd93141-c387-4859-bc79-b92fac420473",
          "rel": "bookmark"
        },
        {
          "href": "http://ops.my.cloud/image/images/7fd93141-c387-4859-bc79-b92fac420473",
          "type": "application/vnd.openstack.image",
          "rel": "alternate"
        }
      ],
      "id": "7fd93141-c387-4859-bc79-b92fac420473",
      "OS-EXT-IMG-SIZE:size": 13267968,
      "name": "cirros-0.3.5-x86_64-disk",
      "created": "2018-10-22T12:03:51Z",
      "minDisk": 0,
      "progress": 100,
      "minRam": 0,
      "metadata": {}
    }
  ]
}

================================================
FILE: drivers/openstack/testdata/servercreateresp1.json
================================================
{
  "server": {
    "OS-EXT-STS:task_state": null,
    "addresses": {
      "private": [
        {
          "OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:7a:f3:1f",
          "version": 4,
          "addr": "10.0.0.14",
          "OS-EXT-IPS:type": "fixed"
        }
      ]
    },
    "links": [
      {
        "href": "http://ops.my.cloud/compute/v2.1/servers/56046f6d-3184-495b-938b-baa450db970d",
        "rel": "self"
      },
      {
        "href": "http://ops.my.cloud/compute/servers/56046f6d-3184-495b-938b-baa450db970d",
        "rel": "bookmark"
      }
    ],
    "image": {
      "id": "4ef19958-ee2d-44a7-a100-de0b8afdbc8e",
      "links": [
        {
          "href": "http://ops.my.cloud/compute/images/4ef19958-ee2d-44a7-a100-de0b8afdbc8e",
          "rel": "bookmark"
        }
      ]
    },
    "OS-EXT-STS:vm_state": "active",
    "OS-EXT-SRV-ATTR:instance_name": "instance-0000000d",
    "OS-SRV-USG:launched_at": "2018-10-29T09:37:05.000000",
    "flavor": {
      "id": "2",
      "links": [
        {
          "href": "http://ops.my.cloud/compute/flavors/2",
          "rel": "bookmark"
        }
      ]
    },
    "id": "56046f6d-3184-495b-938b-baa450db970d",
    "security_groups": [
      {
        "name": "drone-agent"
      }
    ],
    "user_id": "898384bb1b5e4d5a9ff816f7ea911943",
    "OS-DCF:diskConfig": "MANUAL",
    "accessIPv4": "",
    "accessIPv6": "",
    "progress": 0,
    "OS-EXT-STS:power_state": 1,
    "OS-EXT-AZ:availability_zone": "nova",
    "config_drive": "",
    "status": "ACTIVE",
    "updated": "2018-10-29T09:37:06Z",
    "hostId": "1e678c454d7593d464d1a0c1c15111119ae841d11d3f7ba66f9aaee9",
    "OS-EXT-SRV-ATTR:host": "devstack",
    "OS-SRV-USG:terminated_at": null,
    "key_name": "drone-ci-key",
    "OS-EXT-SRV-ATTR:hypervisor_hostname": "devstack",
    "name": "agent-RjISb5v1",
    "created": "2018-10-29T09:37:01Z",
    "tenant_id": "661f707340b0486caf878f9cc2bc1fab",
    "os-extended-volumes:volumes_attached": [],
    "metadata": {
      "owner": "drone-ci",
      "name": "agent"
    }
  }
}


================================================
FILE: drivers/openstack/testdata/serverstatusresp1.json
================================================
{
  "server": {
    "OS-EXT-STS:task_state": null,
    "addresses": {
      "private": [
        {
          "OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:7a:f3:1f",
          "version": 4,
          "addr": "10.0.0.14",
          "OS-EXT-IPS:type": "fixed"
        }
      ]
    },
    "links": [
      {
        "href": "http://ops.my.cloud/compute/v2.1/servers/56046f6d-3184-495b-938b-baa450db970d",
        "rel": "self"
      },
      {
        "href": "http://ops.my.cloud/compute/servers/56046f6d-3184-495b-938b-baa450db970d",
        "rel": "bookmark"
      }
    ],
    "image": {
      "id": "4ef19958-ee2d-44a7-a100-de0b8afdbc8e",
      "links": [
        {
          "href": "http://ops.my.cloud/compute/images/4ef19958-ee2d-44a7-a100-de0b8afdbc8e",
          "rel": "bookmark"
        }
      ]
    },
    "OS-EXT-STS:vm_state": "active",
    "OS-EXT-SRV-ATTR:instance_name": "instance-0000000d",
    "OS-SRV-USG:launched_at": "2018-10-29T09:37:05.000000",
    "flavor": {
      "id": "2",
      "links": [
        {
          "href": "http://ops.my.cloud/compute/flavors/2",
          "rel": "bookmark"
        }
      ]
    },
    "id": "56046f6d-3184-495b-938b-baa450db970d",
    "security_groups": [
      {
        "name": "drone-agent"
      }
    ],
    "user_id": "898384bb1b5e4d5a9ff816f7ea911943",
    "OS-DCF:diskConfig": "MANUAL",
    "accessIPv4": "",
    "accessIPv6": "",
    "progress": 0,
    "OS-EXT-STS:power_state": 1,
    "OS-EXT-AZ:availability_zone": "nova",
    "config_drive": "",
    "status": "ACTIVE",
    "updated": "2018-1
Download .txt
gitextract_xkto3c2c/

├── .drone.sh
├── .drone.yml
├── .github/
│   ├── issue_template.md
│   └── pull_request_template.md
├── .gitignore
├── BUILDING
├── CHANGELOG.md
├── COPYRIGHT
├── Dockerfile
├── LICENSE.md
├── README.md
├── cmd/
│   └── drone-autoscaler/
│       └── main.go
├── config/
│   ├── config.go
│   ├── load.go
│   └── load_test.go
├── drivers/
│   ├── amazon/
│   │   ├── create.go
│   │   ├── create_test.go
│   │   ├── destroy.go
│   │   ├── destroy_test.go
│   │   ├── option.go
│   │   ├── option_test.go
│   │   ├── provider.go
│   │   ├── provider_test.go
│   │   ├── setup.go
│   │   ├── setup_test.go
│   │   ├── util.go
│   │   └── util_test.go
│   ├── azure/
│   │   ├── create.go
│   │   ├── create_test.go
│   │   ├── destroy.go
│   │   ├── destroy_test.go
│   │   ├── option.go
│   │   ├── option_test.go
│   │   ├── provider.go
│   │   └── provider_test.go
│   ├── digitalocean/
│   │   ├── create.go
│   │   ├── create_test.go
│   │   ├── destroy.go
│   │   ├── destroy_test.go
│   │   ├── option.go
│   │   ├── option_test.go
│   │   ├── provider.go
│   │   ├── provider_test.go
│   │   ├── setup.go
│   │   ├── setup_test.go
│   │   └── userdata.go
│   ├── google/
│   │   ├── create.go
│   │   ├── create_test.go
│   │   ├── destroy.go
│   │   ├── destroy_test.go
│   │   ├── option.go
│   │   ├── option_test.go
│   │   ├── provider.go
│   │   ├── provider_test.go
│   │   ├── setup.go
│   │   └── setup_test.go
│   ├── hetznercloud/
│   │   ├── create.go
│   │   ├── create_test.go
│   │   ├── destroy.go
│   │   ├── destroy_test.go
│   │   ├── option.go
│   │   ├── option_test.go
│   │   ├── provider.go
│   │   ├── provider_test.go
│   │   ├── setup.go
│   │   └── setup_test.go
│   ├── internal/
│   │   └── userdata/
│   │       ├── userdata.go
│   │       └── userdata_test.go
│   ├── openstack/
│   │   ├── create.go
│   │   ├── create_test.go
│   │   ├── destroy.go
│   │   ├── destroy_test.go
│   │   ├── doc.go
│   │   ├── option.go
│   │   ├── option_test.go
│   │   ├── provider.go
│   │   ├── provider_test.go
│   │   ├── setup.go
│   │   ├── setup_test.go
│   │   └── testdata/
│   │       ├── associateresp1.json
│   │       ├── authresp1.json
│   │       ├── fipresp1.json
│   │       ├── flavorlistresp1.json
│   │       ├── imagelistresp1.json
│   │       ├── servercreateresp1.json
│   │       ├── serverstatusresp1.json
│   │       └── tokenresp1.json
│   ├── packet/
│   │   ├── create.go
│   │   ├── create_test.go
│   │   ├── destroy.go
│   │   ├── destroy_test.go
│   │   ├── option.go
│   │   ├── option_test.go
│   │   ├── provider.go
│   │   ├── provider_test.go
│   │   ├── setup.go
│   │   └── setup_test.go
│   └── scaleway/
│       ├── create.go
│       ├── create_test.go
│       ├── destroy.go
│       ├── destroy_test.go
│       ├── option.go
│       ├── option_test.go
│       ├── provider.go
│       ├── provider_test.go
│       └── setup.go
├── engine/
│   ├── alloc.go
│   ├── alloc_test.go
│   ├── calc.go
│   ├── calc_test.go
│   ├── certs/
│   │   ├── cert.go
│   │   └── cert_test.go
│   ├── collect.go
│   ├── collect_test.go
│   ├── docker.go
│   ├── engine.go
│   ├── install.go
│   ├── install_test.go
│   ├── pinger.go
│   ├── pinger_test.go
│   ├── planner.go
│   ├── planner_test.go
│   ├── reaper.go
│   ├── reaper_test.go
│   ├── sort.go
│   └── sort_test.go
├── engine.go
├── go.mod
├── go.sum
├── licenses/
│   ├── Polyform-Free-Trial.md
│   └── Polyform-Small-Business.md
├── logger/
│   ├── context.go
│   ├── context_test.go
│   ├── history/
│   │   ├── history.go
│   │   └── history_test.go
│   ├── logger.go
│   ├── logger_test.go
│   ├── logrus.go
│   ├── logrus_test.go
│   └── request/
│       └── request.go
├── metrics/
│   ├── metrics.go
│   ├── server_capacity.go
│   ├── server_capacity_test.go
│   ├── server_count.go
│   ├── server_count_test.go
│   ├── server_create.go
│   ├── server_create_test.go
│   ├── server_delete.go
│   └── server_delete_test.go
├── mocks/
│   ├── mock_docker.go
│   ├── mock_drone.go
│   ├── mock_engine.go
│   ├── mock_metrics.go
│   ├── mock_provider.go
│   ├── mock_server.go
│   └── mocks.go
├── provider.go
├── server/
│   ├── auth.go
│   ├── auth_test.go
│   ├── engine.go
│   ├── engine_test.go
│   ├── healthz.go
│   ├── healthz_test.go
│   ├── metrics.go
│   ├── metrics_test.go
│   ├── servers.go
│   ├── servers_test.go
│   ├── varz.go
│   ├── varz_test.go
│   ├── version.go
│   ├── version_test.go
│   ├── web/
│   │   ├── handler.go
│   │   ├── nocache.go
│   │   ├── nocache_test.go
│   │   ├── render.go
│   │   ├── render_test.go
│   │   ├── static/
│   │   │   ├── files/
│   │   │   │   ├── reset.css
│   │   │   │   ├── style.css
│   │   │   │   └── timeago.js
│   │   │   ├── static.go
│   │   │   └── static_gen.go
│   │   └── template/
│   │       ├── files/
│   │       │   ├── index.tmpl
│   │       │   └── logs.tmpl
│   │       ├── server.go
│   │       ├── template.go
│   │       ├── template_gen.go
│   │       └── testdata/
│   │           ├── logs.json
│   │           ├── logs_empty.json
│   │           ├── servers.json
│   │           └── servers_empty.json
│   ├── writer.go
│   └── writer_test.go
├── server.go
├── slack/
│   ├── slack.go
│   └── slack_test.go
└── store/
    ├── db.go
    ├── db_test.go
    ├── lock.go
    ├── migrate/
    │   ├── migrate.go
    │   ├── mysql/
    │   │   ├── ddl.go
    │   │   ├── ddl_gen.go
    │   │   └── files/
    │   │       └── 001_create_table_servers.sql
    │   ├── postgres/
    │   │   ├── ddl.go
    │   │   ├── ddl_gen.go
    │   │   └── files/
    │   │       └── 001_create_table_servers.sql
    │   └── sqlite/
    │       ├── ddl.go
    │       ├── ddl_gen.go
    │       └── files/
    │           └── 001_create_table_servers.sql
    ├── servers.go
    ├── servers_test.go
    ├── util.go
    └── util_test.go
Download .txt
SYMBOL INDEX (1056 symbols across 154 files)

FILE: cmd/drone-autoscaler/main.go
  function main (line 55) | func main() {
  function setupLogging (line 199) | func setupLogging(c config.Config) {
  function setupClient (line 217) | func setupClient(c config.Config) drone.Client {
  function setupProvider (line 232) | func setupProvider(c config.Config) (autoscaler.Provider, error) {

FILE: config/config.go
  type Config (line 13) | type Config struct
  type Runner (line 235) | type Runner struct

FILE: config/load.go
  function init (line 23) | func init() {
  function Load (line 34) | func Load() (Config, error) {
  function MustLoad (line 60) | func MustLoad() Config {

FILE: config/load_test.go
  function TestDefaults (line 18) | func TestDefaults(t *testing.T) {
  function TestLoad (line 66) | func TestLoad(t *testing.T) {
  function TestLoadEnvVariables (line 373) | func TestLoadEnvVariables(t *testing.T) {

FILE: drivers/amazon/create.go
  type attemptOverrides (line 21) | type attemptOverrides struct
  method Create (line 27) | func (p *provider) Create(ctx context.Context, opts autoscaler.InstanceC...
  method create (line 71) | func (p *provider) create(ctx context.Context, opts autoscaler.InstanceC...

FILE: drivers/amazon/destroy.go
  method Destroy (line 19) | func (p *provider) Destroy(ctx context.Context, instance *autoscaler.Ins...
  method destroy2 (line 58) | func (p *provider) destroy2(ctx context.Context, instance *autoscaler.In...

FILE: drivers/amazon/destroy_test.go
  function TestDestroy (line 17) | func TestDestroy(t *testing.T) {
  function TestDestroyDeleteError (line 47) | func TestDestroyDeleteError(t *testing.T) {
  function TestDestroyNotFound (line 64) | func TestDestroyNotFound(t *testing.T) {

FILE: drivers/amazon/option.go
  type Option (line 14) | type Option
  function WithDeviceName (line 17) | func WithDeviceName(n string) Option {
  function WithImage (line 24) | func WithImage(image string) Option {
  function WithPrivateIP (line 31) | func WithPrivateIP(private bool) Option {
  function WithRetries (line 38) | func WithRetries(retries int) Option {
  function WithRegion (line 45) | func WithRegion(region string) Option {
  function WithSecurityGroup (line 52) | func WithSecurityGroup(group ...string) Option {
  function WithSize (line 59) | func WithSize(size string) Option {
  function WithSizeAlt (line 68) | func WithSizeAlt(size string) Option {
  function WithSSHKey (line 75) | func WithSSHKey(key string) Option {
  function WithSubnets (line 82) | func WithSubnets(ids []string) Option {
  function WithTags (line 89) | func WithTags(tags map[string]string) Option {
  function WithUserData (line 97) | func WithUserData(text string) Option {
  function WithUserDataFile (line 107) | func WithUserDataFile(filepath string) Option {
  function WithVolumeSize (line 121) | func WithVolumeSize(s int64) Option {
  function WithVolumeType (line 128) | func WithVolumeType(t string) Option {
  function WithVolumeIops (line 135) | func WithVolumeIops(i int64) Option {
  function WithVolumeThroughput (line 142) | func WithVolumeThroughput(i int64) Option {
  function WithIamProfileArn (line 149) | func WithIamProfileArn(t string) Option {
  function WithInstanceMetadataTokens (line 156) | func WithInstanceMetadataTokens(t string) Option {
  function WithMarketType (line 163) | func WithMarketType(t string) Option {

FILE: drivers/amazon/option_test.go
  function TestOptions (line 9) | func TestOptions(t *testing.T) {

FILE: drivers/amazon/provider.go
  type provider (line 19) | type provider struct
    method getClient (line 43) | func (p *provider) getClient() *ec2.EC2 {
  function New (line 52) | func New(opts ...Option) autoscaler.Provider {

FILE: drivers/amazon/setup.go
  method setup (line 17) | func (p *provider) setup(ctx context.Context) error {
  method setupKeypair (line 33) | func (p *provider) setupKeypair(ctx context.Context) error {

FILE: drivers/amazon/util.go
  function convertTags (line 14) | func convertTags(in map[string]string) []*ec2.Tag {
  function createCopy (line 26) | func createCopy(in map[string]string) map[string]string {
  function defaultImage (line 36) | func defaultImage(region string) string {

FILE: drivers/amazon/util_test.go
  function TestConvertTags (line 14) | func TestConvertTags(t *testing.T) {

FILE: drivers/digitalocean/create.go
  method Create (line 19) | func (p *provider) Create(ctx context.Context, opts autoscaler.InstanceC...

FILE: drivers/digitalocean/create_test.go
  function TestCreate (line 18) | func TestCreate(t *testing.T) {
  function TestCreate_CreateError (line 49) | func TestCreate_CreateError(t *testing.T) {
  function TestCreate_DescribeError (line 74) | func TestCreate_DescribeError(t *testing.T) {
  function TestCreate_DescribeTimeout (line 106) | func TestCreate_DescribeTimeout(t *testing.T) {
  function testInstance (line 142) | func testInstance(instance *autoscaler.Instance) func(t *testing.T) {
  function testInstanceAddress (line 165) | func testInstanceAddress(instance *autoscaler.Instance) func(t *testing....
  constant respDropletCreate (line 177) | respDropletCreate = `
  constant respDropletDesc (line 235) | respDropletDesc = `

FILE: drivers/digitalocean/destroy.go
  method Destroy (line 15) | func (p *provider) Destroy(ctx context.Context, instance *autoscaler.Ins...

FILE: drivers/digitalocean/destroy_test.go
  function TestDestroy (line 19) | func TestDestroy(t *testing.T) {
  function TestDestroyDeleteError (line 51) | func TestDestroyDeleteError(t *testing.T) {
  function TestDestroyFindError (line 88) | func TestDestroyFindError(t *testing.T) {
  function TestDestroyNotFound (line 120) | func TestDestroyNotFound(t *testing.T) {
  function TestDestroyInvalidInput (line 152) | func TestDestroyInvalidInput(t *testing.T) {

FILE: drivers/digitalocean/option.go
  type Option (line 14) | type Option
  function WithImage (line 17) | func WithImage(image string) Option {
  function WithRegion (line 24) | func WithRegion(region string) Option {
  function WithSize (line 31) | func WithSize(size string) Option {
  function WithSSHKey (line 38) | func WithSSHKey(key string) Option {
  function WithTags (line 45) | func WithTags(tags ...string) Option {
  function WithToken (line 52) | func WithToken(token string) Option {
  function WithFirewall (line 59) | func WithFirewall(firewall string) Option {
  function WithPrivateIP (line 66) | func WithPrivateIP(private bool) Option {
  function WithUserData (line 74) | func WithUserData(text string) Option {
  function WithUserDataFile (line 84) | func WithUserDataFile(filepath string) Option {

FILE: drivers/digitalocean/option_test.go
  function TestOptions (line 9) | func TestOptions(t *testing.T) {

FILE: drivers/digitalocean/provider.go
  type provider (line 19) | type provider struct
  function New (line 34) | func New(opts ...Option) autoscaler.Provider {
  function newClient (line 55) | func newClient(ctx context.Context, token string) *godo.Client {

FILE: drivers/digitalocean/provider_test.go
  function TestDefaults (line 9) | func TestDefaults(t *testing.T) {

FILE: drivers/digitalocean/setup.go
  method setup (line 17) | func (p *provider) setup(ctx context.Context) error {
  method setupKeypair (line 27) | func (p *provider) setupKeypair(ctx context.Context) error {

FILE: drivers/digitalocean/setup_test.go
  function TestSetupKey_Single (line 14) | func TestSetupKey_Single(t *testing.T) {
  function TestSetupKey_FoundMatch (line 36) | func TestSetupKey_FoundMatch(t *testing.T) {
  function TestSetupKey_NoMatch (line 62) | func TestSetupKey_NoMatch(t *testing.T) {

FILE: drivers/google/create.go
  method Create (line 21) | func (p *provider) Create(ctx context.Context, opts autoscaler.InstanceC...

FILE: drivers/google/create_test.go
  function TestCreate (line 19) | func TestCreate(t *testing.T) {
  function TestCreateWithMultiZones (line 82) | func TestCreateWithMultiZones(t *testing.T) {

FILE: drivers/google/destroy.go
  method Destroy (line 15) | func (p *provider) Destroy(ctx context.Context, instance *autoscaler.Ins...

FILE: drivers/google/destroy_test.go
  function TestDestroy (line 16) | func TestDestroy(t *testing.T) {
  function TestDestroy_Error (line 51) | func TestDestroy_Error(t *testing.T) {

FILE: drivers/google/option.go
  type Option (line 19) | type Option
  function WithClient (line 23) | func WithClient(client *http.Client) Option {
  function WithDiskSize (line 35) | func WithDiskSize(diskSize int64) Option {
  function WithDiskType (line 42) | func WithDiskType(diskType string) Option {
  function WithLabels (line 49) | func WithLabels(labels map[string]string) Option {
  function WithMachineImage (line 56) | func WithMachineImage(image string) Option {
  function WithMachineType (line 63) | func WithMachineType(size string) Option {
  function WithNetwork (line 70) | func WithNetwork(network string) Option {
  function WithSubnetwork (line 77) | func WithSubnetwork(subnetwork string) Option {
  function WithStackType (line 84) | func WithStackType(stackType string) Option {
  function WithPrivateIP (line 91) | func WithPrivateIP(private bool) Option {
  function WithProject (line 98) | func WithProject(project string) Option {
  function WithTags (line 105) | func WithTags(tags ...string) Option {
  function WithUserData (line 113) | func WithUserData(text string) Option {
  function WithUserDataFile (line 123) | func WithUserDataFile(filepath string) Option {
  function WithUserDataKey (line 137) | func WithUserDataKey(text string) Option {
  function WithZones (line 146) | func WithZones(zones ...string) Option {
  function WithScopes (line 153) | func WithScopes(scopes ...string) Option {
  function WithServiceAccountEmail (line 160) | func WithServiceAccountEmail(email string) Option {
  function WithRateLimit (line 166) | func WithRateLimit(limitAmount int) Option {

FILE: drivers/google/option_test.go
  function TestOptions (line 13) | func TestOptions(t *testing.T) {

FILE: drivers/google/provider.go
  type provider (line 38) | type provider struct
    method waitZoneOperation (line 125) | func (p *provider) waitZoneOperation(ctx context.Context, name string,...
    method waitGlobalOperation (line 147) | func (p *provider) waitGlobalOperation(ctx context.Context, name strin...
  function New (line 64) | func New(opts ...Option) (autoscaler.Provider, error) {

FILE: drivers/google/provider_test.go
  function TestDefaults (line 15) | func TestDefaults(t *testing.T) {

FILE: drivers/google/setup.go
  method setup (line 16) | func (p *provider) setup(ctx context.Context) error {
  method setupFirewall (line 23) | func (p *provider) setupFirewall(ctx context.Context) error {

FILE: drivers/google/setup_test.go
  function TestSetupFirewall (line 16) | func TestSetupFirewall(t *testing.T) {
  function TestSetupFirewall_Exists (line 50) | func TestSetupFirewall_Exists(t *testing.T) {

FILE: drivers/hetznercloud/create.go
  method Create (line 18) | func (p *provider) Create(ctx context.Context, opts autoscaler.InstanceC...

FILE: drivers/hetznercloud/create_test.go
  function TestCreate (line 16) | func TestCreate(t *testing.T) {
  function TestCreate_CreateError (line 37) | func TestCreate_CreateError(t *testing.T) {
  function testInstance (line 55) | func testInstance(instance *autoscaler.Instance) func(t *testing.T) {
  function testInstanceAddress (line 78) | func testInstanceAddress(instance *autoscaler.Instance) func(t *testing....
  constant respInstanceCreate (line 90) | respInstanceCreate = `

FILE: drivers/hetznercloud/destroy.go
  method Destroy (line 17) | func (p *provider) Destroy(ctx context.Context, instance *autoscaler.Ins...

FILE: drivers/hetznercloud/destroy_test.go
  function TestDestroy (line 17) | func TestDestroy(t *testing.T) {
  function TestDestroyDeleteError (line 38) | func TestDestroyDeleteError(t *testing.T) {
  function TestDestroyNotFound (line 59) | func TestDestroyNotFound(t *testing.T) {
  function TestDestroyInvalidInput (line 85) | func TestDestroyInvalidInput(t *testing.T) {

FILE: drivers/hetznercloud/option.go
  type Option (line 15) | type Option
  function WithClient (line 18) | func WithClient(client *hcloud.Client) Option {
  function WithDatacenter (line 25) | func WithDatacenter(datacenter string) Option {
  function WithImage (line 32) | func WithImage(image string) Option {
  function WithServerType (line 39) | func WithServerType(serverType string) Option {
  function WithSSHKey (line 46) | func WithSSHKey(key int) Option {
  function WithToken (line 53) | func WithToken(token string) Option {
  function WithUserData (line 65) | func WithUserData(text string) Option {
  function WithUserDataFile (line 75) | func WithUserDataFile(filepath string) Option {

FILE: drivers/hetznercloud/option_test.go
  function TestOptions (line 9) | func TestOptions(t *testing.T) {

FILE: drivers/hetznercloud/provider.go
  type provider (line 18) | type provider struct
  function New (line 32) | func New(opts ...Option) autoscaler.Provider {

FILE: drivers/hetznercloud/provider_test.go
  function TestDefaults (line 9) | func TestDefaults(t *testing.T) {

FILE: drivers/hetznercloud/setup.go
  method setup (line 16) | func (p *provider) setup(ctx context.Context) error {
  method setupKeypair (line 26) | func (p *provider) setupKeypair(ctx context.Context) error {

FILE: drivers/hetznercloud/setup_test.go
  function TestSetupKey_ChooseFirst (line 14) | func TestSetupKey_ChooseFirst(t *testing.T) {
  function TestSetupKey_ChooseMatch (line 36) | func TestSetupKey_ChooseMatch(t *testing.T) {
  constant respSingleKey (line 58) | respSingleKey = `
  constant respMultiKey (line 71) | respMultiKey = `

FILE: drivers/internal/userdata/userdata.go
  function Parse (line 21) | func Parse(text string) *template.Template {

FILE: drivers/internal/userdata/userdata_test.go
  function TestUserdata (line 15) | func TestUserdata(t *testing.T) {
  function TestUserdataFuncmap (line 29) | func TestUserdataFuncmap(t *testing.T) {

FILE: drivers/openstack/create.go
  method Create (line 21) | func (p *provider) Create(ctx context.Context, opts autoscaler.InstanceC...

FILE: drivers/openstack/create_test.go
  function TestCreate (line 16) | func TestCreate(t *testing.T) {
  function TestAuthFail (line 124) | func TestAuthFail(t *testing.T) {
  function TestCreateFail (line 161) | func TestCreateFail(t *testing.T) {
  function setupEnv (line 240) | func setupEnv(t *testing.T) {
  function testInstance (line 259) | func testInstance(instance *autoscaler.Instance) func(t *testing.T) {

FILE: drivers/openstack/destroy.go
  method Destroy (line 19) | func (p *provider) Destroy(ctx context.Context, instance *autoscaler.Ins...
  method deleteFloatingIps (line 69) | func (p *provider) deleteFloatingIps(instance *autoscaler.Instance) error {

FILE: drivers/openstack/destroy_test.go
  function TestDestroy (line 15) | func TestDestroy(t *testing.T) {

FILE: drivers/openstack/option.go
  type Option (line 14) | type Option
  function WithImage (line 17) | func WithImage(image string) Option {
  function WithRegion (line 24) | func WithRegion(region string) Option {
  function WithFlavor (line 31) | func WithFlavor(flavor string) Option {
  function WithSecurityGroup (line 38) | func WithSecurityGroup(group ...string) Option {
  function WithComputeClient (line 46) | func WithComputeClient(computeClient *gophercloud.ServiceClient) Option {
  function WithNetworkClient (line 54) | func WithNetworkClient(networkClient *gophercloud.ServiceClient) Option {
  function WithSSHKey (line 61) | func WithSSHKey(key string) Option {
  function WithNetwork (line 68) | func WithNetwork(id string) Option {
  function WithFloatingIpPool (line 74) | func WithFloatingIpPool(pool string) Option {
  function WithMetadata (line 81) | func WithMetadata(metadata map[string]string) Option {
  function WithUserData (line 89) | func WithUserData(text string) Option {
  function WithUserDataFile (line 99) | func WithUserDataFile(filepath string) Option {

FILE: drivers/openstack/option_test.go
  function TestOptions (line 13) | func TestOptions(t *testing.T) {

FILE: drivers/openstack/provider.go
  type provider (line 22) | type provider struct
  function New (line 40) | func New(opts ...Option) (autoscaler.Provider, error) {
  function isUUID (line 115) | func isUUID(uuid string) bool {

FILE: drivers/openstack/provider_test.go
  function TestDefaults (line 13) | func TestDefaults(t *testing.T) {

FILE: drivers/openstack/setup.go
  method setup (line 17) | func (p *provider) setup(ctx context.Context) error {
  method findKeyPair (line 28) | func (p *provider) findKeyPair(ctx context.Context) error {

FILE: drivers/openstack/setup_test.go
  function helperLoad (line 13) | func helperLoad(t *testing.T, name string) []byte {
  constant authToken (line 22) | authToken = "gAAAAABb1tQPtYVBv68airR0dgKC2vXpkLNfEHx0w1EL89dOOjKrtdYHR7I...

FILE: drivers/packet/create.go
  method Create (line 18) | func (p *provider) Create(ctx context.Context, opts autoscaler.InstanceC...

FILE: drivers/packet/create_test.go
  function TestCreate (line 17) | func TestCreate(t *testing.T) {
  function testInstance (line 43) | func testInstance(instance *autoscaler.Instance) func(t *testing.T) {
  function TestCreate_Timeout (line 66) | func TestCreate_Timeout(t *testing.T) {
  function TestCreate_Erro (line 88) | func TestCreate_Erro(t *testing.T) {
  function TestCreate_WaitToBecomeActive (line 104) | func TestCreate_WaitToBecomeActive(t *testing.T) {

FILE: drivers/packet/destroy.go
  method Destroy (line 13) | func (p *provider) Destroy(ctx context.Context, instance *autoscaler.Ins...

FILE: drivers/packet/destroy_test.go
  function TestDestroyError (line 17) | func TestDestroyError(t *testing.T) {

FILE: drivers/packet/option.go
  type Option (line 14) | type Option
  function WithAPIKey (line 17) | func WithAPIKey(apikey string) Option {
  function WithFacility (line 24) | func WithFacility(facility string) Option {
  function WithPlan (line 31) | func WithPlan(plan string) Option {
  function WithOS (line 38) | func WithOS(os string) Option {
  function WithProject (line 45) | func WithProject(project string) Option {
  function WithSSHKey (line 52) | func WithSSHKey(sshkey string) Option {
  function WithHostname (line 59) | func WithHostname(hostname string) Option {
  function WithTags (line 68) | func WithTags(tags ...string) Option {
  function WithUserData (line 76) | func WithUserData(text string) Option {
  function WithUserDataFile (line 86) | func WithUserDataFile(filepath string) Option {

FILE: drivers/packet/option_test.go
  function TestOptions (line 9) | func TestOptions(t *testing.T) {

FILE: drivers/packet/provider.go
  constant consumerToken (line 16) | consumerToken = "24e70949af5ecd17fe8e867b335fc88e7de8bd4ad617c0403d8769a...
  type provider (line 19) | type provider struct
  function New (line 37) | func New(opts ...Option) autoscaler.Provider {

FILE: drivers/packet/provider_test.go
  function TestDefaults (line 13) | func TestDefaults(t *testing.T) {

FILE: drivers/packet/setup.go
  method setup (line 17) | func (p *provider) setup(ctx context.Context) error {
  method setupKeypair (line 30) | func (p *provider) setupKeypair(ctx context.Context) error {

FILE: drivers/packet/setup_test.go
  constant baseURL (line 16) | baseURL      = "https://api.packet.net/"
  constant getDevice (line 17) | getDevice    = "/devices"
  constant getSSH (line 18) | getSSH       = "/ssh-keys"
  constant projectID (line 19) | projectID    = "x"
  constant createDevice (line 20) | createDevice = "/projects/" + projectID + getDevice
  constant instanceID (line 21) | instanceID   = "92b0facf-189e-4bbf-81a8-bc56c0c4dc88"
  constant apiKey (line 22) | apiKey       = "apiKey"
  constant sshKey (line 23) | sshKey       = "sshKey"
  constant hostname (line 24) | hostname     = "hostname"
  constant tag (line 25) | tag          = "tag"
  function TestMain (line 35) | func TestMain(m *testing.M) {
  function TestSetup_Keypair (line 108) | func TestSetup_Keypair(t *testing.T) {

FILE: drivers/scaleway/create.go
  method Create (line 20) | func (p *provider) Create(ctx context.Context, opts autoscaler.InstanceC...
  function serverPowerAction (line 101) | func serverPowerAction(api *instance.API, ctx context.Context, action in...

FILE: drivers/scaleway/destroy.go
  method Destroy (line 17) | func (p *provider) Destroy(ctx context.Context, inst *autoscaler.Instanc...

FILE: drivers/scaleway/option.go
  type Option (line 15) | type Option
  function WithAccessKey (line 18) | func WithAccessKey(accessKey string) Option {
  function WithSecretKey (line 26) | func WithSecretKey(secretKey string) Option {
  function WithOrganisationID (line 34) | func WithOrganisationID(orgId string) Option {
  function WithImage (line 42) | func WithImage(image string) Option {
  function WithDynamicIP (line 50) | func WithDynamicIP(dynamicIP bool) Option {
  function WithTags (line 58) | func WithTags(tags ...string) Option {
  function WithZone (line 66) | func WithZone(name string) Option {
  function WithSize (line 81) | func WithSize(size string) Option {
  function WithUserData (line 90) | func WithUserData(text string) Option {
  function WithUserDataFile (line 101) | func WithUserDataFile(filepath string) Option {

FILE: drivers/scaleway/provider.go
  type provider (line 18) | type provider struct
  function New (line 36) | func New(opts ...Option) (autoscaler.Provider, error) {

FILE: drivers/scaleway/setup.go
  method setup (line 14) | func (p *provider) setup(ctx context.Context) error {
  method newClient (line 24) | func (p *provider) newClient(ctx context.Context) error {

FILE: engine.go
  type Engine (line 12) | type Engine interface

FILE: engine/alloc.go
  type allocator (line 18) | type allocator struct
    method Allocate (line 26) | func (a *allocator) Allocate(ctx context.Context) error {
    method allocate (line 54) | func (a *allocator) allocate(ctx context.Context, server *autoscaler.S...

FILE: engine/alloc_test.go
  function TestAllocate (line 19) | func TestAllocate(t *testing.T) {
  function TestAllocate_ServerCreateError (line 49) | func TestAllocate_ServerCreateError(t *testing.T) {
  function TestAllocate_ServerListError (line 76) | func TestAllocate_ServerListError(t *testing.T) {
  function TestAllocate_ServerUpdateError (line 92) | func TestAllocate_ServerUpdateError(t *testing.T) {

FILE: engine/calc.go
  function abs (line 10) | func abs(x int) int {
  function max (line 18) | func max(x, y int) int {
  function serverDiff (line 27) | func serverDiff(pending, available, concurrency int) int {
  function serverCeil (line 38) | func serverCeil(count, additions, ceiling int) int {
  function serverFloor (line 47) | func serverFloor(count, deletions, floor int) int {

FILE: engine/calc_test.go
  function TestAbs (line 9) | func TestAbs(t *testing.T) {
  function TestMax (line 24) | func TestMax(t *testing.T) {
  function TestServerDiff (line 41) | func TestServerDiff(t *testing.T) {
  function TestSeverCeil (line 138) | func TestSeverCeil(t *testing.T) {
  function TestSeverFloor (line 207) | func TestSeverFloor(t *testing.T) {

FILE: engine/certs/cert.go
  constant size (line 20) | size = 2048
  constant organization (line 23) | organization = "drone.autoscaler.generated"
  type Certificate (line 27) | type Certificate struct
  function GenerateCert (line 33) | func GenerateCert(host string, ca *Certificate) (*Certificate, error) {
  function GenerateCA (line 80) | func GenerateCA() (*Certificate, error) {
  function newCertificate (line 120) | func newCertificate(org string) (*x509.Certificate, error) {

FILE: engine/certs/cert_test.go
  function TestGenerate (line 11) | func TestGenerate(t *testing.T) {

FILE: engine/collect.go
  type collector (line 17) | type collector struct
    method Collect (line 26) | func (c *collector) Collect(ctx context.Context) error {
    method collect (line 54) | func (c *collector) collect(ctx context.Context, server *autoscaler.Se...

FILE: engine/collect_test.go
  function TestCollect (line 20) | func TestCollect(t *testing.T) {
  function TestCollect_DockerStopError (line 62) | func TestCollect_DockerStopError(t *testing.T) {
  function TestCollect_ServerDestroyError (line 105) | func TestCollect_ServerDestroyError(t *testing.T) {
  function TestCollect_ServerListError (line 146) | func TestCollect_ServerListError(t *testing.T) {
  function TestCollect_ServerUpdateError (line 162) | func TestCollect_ServerUpdateError(t *testing.T) {
  function TestCollect_ServerNeverProvisioned (line 189) | func TestCollect_ServerNeverProvisioned(t *testing.T) {

FILE: engine/docker.go
  type clientFunc (line 21) | type clientFunc
  function newDockerClient (line 25) | func newDockerClient(server *autoscaler.Server) (docker.APIClient, io.Cl...

FILE: engine/engine.go
  constant purge (line 21) | purge = time.Hour * 24
  type engine (line 23) | type engine struct
    method Pause (line 121) | func (e *engine) Pause() {
    method Paused (line 128) | func (e *engine) Paused() bool {
    method Resume (line 135) | func (e *engine) Resume() {
    method Start (line 141) | func (e *engine) Start(ctx context.Context) {
    method allocate (line 178) | func (e *engine) allocate(ctx context.Context) {
    method install (line 191) | func (e *engine) install(ctx context.Context) {
    method collect (line 204) | func (e *engine) collect(ctx context.Context) {
    method plan (line 217) | func (e *engine) plan(ctx context.Context) {
    method ping (line 231) | func (e *engine) ping(ctx context.Context) {
    method purge (line 244) | func (e *engine) purge(ctx context.Context) {
    method reap (line 262) | func (e *engine) reap(ctx context.Context) {
    method reset (line 275) | func (e *engine) reset(ctx context.Context) {
  function New (line 39) | func New(

FILE: engine/install.go
  type installer (line 29) | type installer struct
    method Install (line 68) | func (i *installer) Install(ctx context.Context) error {
    method install (line 96) | func (i *installer) install(ctx context.Context, instance *autoscaler....
    method setupWatchtower (line 317) | func (i *installer) setupWatchtower(ctx context.Context, client docker...
    method setupGarbageCollector (line 344) | func (i *installer) setupGarbageCollector(ctx context.Context, client ...
    method errorUpdate (line 394) | func (i *installer) errorUpdate(ctx context.Context, server *autoscale...
  function toVol (line 412) | func toVol(paths []string) map[string]struct{} {
  function splitVolumeParts (line 428) | func splitVolumeParts(volumeParts string) ([]string, error) {

FILE: engine/install_test.go
  function TestSplitVolumeParts (line 12) | func TestSplitVolumeParts(t *testing.T) {

FILE: engine/pinger.go
  type pinger (line 16) | type pinger struct
    method Ping (line 25) | func (p *pinger) Ping(ctx context.Context) error {
    method ping (line 47) | func (p *pinger) ping(ctx context.Context, server *autoscaler.Server) ...

FILE: engine/planner.go
  type planner (line 22) | type planner struct
    method Plan (line 39) | func (p *planner) Plan(ctx context.Context) error {
    method alloc (line 108) | func (p *planner) alloc(ctx context.Context, n int) error {
    method mark (line 135) | func (p *planner) mark(ctx context.Context, n int) error {
    method count (line 220) | func (p *planner) count(ctx context.Context) (pending, running int, er...
    method capacity (line 240) | func (p *planner) capacity(ctx context.Context) (capacity, count int, ...
    method listBusy (line 258) | func (p *planner) listBusy(ctx context.Context) (map[string]struct{}, ...
    method match (line 277) | func (p *planner) match(stage *drone.Stage) bool {
  function checkLabels (line 291) | func checkLabels(a, b map[string]string) bool {
  function timeDiff (line 303) | func timeDiff(t time.Time, start time.Time) time.Duration {

FILE: engine/planner_test.go
  function TestPlan_Noop (line 23) | func TestPlan_Noop(t *testing.T) {
  function TestPlan_MinBufferCapacity (line 59) | func TestPlan_MinBufferCapacity(t *testing.T) {
  function TestPlan_MaxBufferCapacity (line 97) | func TestPlan_MaxBufferCapacity(t *testing.T) {
  function TestPlan_MoreBufferCapacity (line 142) | func TestPlan_MoreBufferCapacity(t *testing.T) {
  function TestPlan_MaxCapacity (line 185) | func TestPlan_MaxCapacity(t *testing.T) {
  function TestPlan_MoreCapacity (line 237) | func TestPlan_MoreCapacity(t *testing.T) {
  function TestPlan_MinPool (line 288) | func TestPlan_MinPool(t *testing.T) {
  function TestPlan_NoIdle (line 324) | func TestPlan_NoIdle(t *testing.T) {
  function TestScale_MinAge (line 365) | func TestScale_MinAge(t *testing.T) {
  function TestPlan_ShutdownIdle (line 402) | func TestPlan_ShutdownIdle(t *testing.T) {
  function TestPlan_ExcludePendingWhenTerminating (line 441) | func TestPlan_ExcludePendingWhenTerminating(t *testing.T) {
  function TestListBusy (line 482) | func TestListBusy(t *testing.T) {
  function TestListBusyWithPendingAndRunning (line 511) | func TestListBusyWithPendingAndRunning(t *testing.T) {
  function TestCapacity (line 544) | func TestCapacity(t *testing.T) {
  function TestCount (line 575) | func TestCount(t *testing.T) {
  function TestMatch (line 605) | func TestMatch(t *testing.T) {

FILE: engine/reaper.go
  type reaper (line 25) | type reaper struct
    method Reap (line 34) | func (r *reaper) Reap(ctx context.Context) error {
    method reap (line 56) | func (r *reaper) reap(ctx context.Context, server *autoscaler.Server) ...

FILE: engine/sort.go
  type byCreated (line 10) | type byCreated
    method Len (line 12) | func (a byCreated) Len() int           { return len(a) }
    method Swap (line 13) | func (a byCreated) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
    method Less (line 14) | func (a byCreated) Less(i, j int) bool { return a[i].Created < a[j].Cr...

FILE: engine/sort_test.go
  function TestSortByCreated (line 14) | func TestSortByCreated(t *testing.T) {

FILE: logger/context.go
  type loggerKey (line 12) | type loggerKey struct
  function WithContext (line 16) | func WithContext(ctx context.Context, logger Logger) context.Context {
  function FromContext (line 21) | func FromContext(ctx context.Context) Logger {
  function FromRequest (line 31) | func FromRequest(r *http.Request) Logger {

FILE: logger/context_test.go
  function TestContext (line 13) | func TestContext(t *testing.T) {
  function TestEmptyContext (line 24) | func TestEmptyContext(t *testing.T) {
  function TestRequest (line 34) | func TestRequest(t *testing.T) {

FILE: logger/history/history.go
  constant defaultLimit (line 16) | defaultLimit = 250
  type Level (line 19) | type Level
  constant LevelError (line 23) | LevelError = Level("error")
  constant LevelWarn (line 24) | LevelWarn  = Level("warn")
  constant LevelInfo (line 25) | LevelInfo  = Level("info")
  constant LevelDebug (line 26) | LevelDebug = Level("debug")
  constant LevelTrace (line 27) | LevelTrace = Level("trace")
  type Entry (line 31) | type Entry struct
  type Hook (line 39) | type Hook struct
    method Fire (line 57) | func (h *Hook) Fire(e *logrus.Entry) error {
    method Levels (line 77) | func (h *Hook) Levels() []logrus.Level {
    method Entries (line 82) | func (h *Hook) Entries() []*Entry {
    method Filter (line 94) | func (h *Hook) Filter(filter func(*Entry) bool) []*Entry {
  function New (line 46) | func New() *Hook {
  function NewLimit (line 52) | func NewLimit(limit int) *Hook {
  function copyEntry (line 107) | func copyEntry(src *Entry) *Entry {
  function convertLevel (line 118) | func convertLevel(level logrus.Level) Level {
  function convertFields (line 138) | func convertFields(src logrus.Fields) map[string]interface{} {

FILE: logger/history/history_test.go
  function TestLevels (line 15) | func TestLevels(t *testing.T) {
  function TestConvertLevels (line 23) | func TestConvertLevels(t *testing.T) {
  function TestLimit (line 46) | func TestLimit(t *testing.T) {
  function TestHistory (line 58) | func TestHistory(t *testing.T) {
  function TestFilter (line 111) | func TestFilter(t *testing.T) {

FILE: logger/logger.go
  type Logger (line 11) | type Logger interface
  function Discard (line 40) | func Discard() Logger {
  type discard (line 44) | type discard struct
    method Debug (line 46) | func (*discard) Debug(args ...interface{})                 {}
    method Debugf (line 47) | func (*discard) Debugf(format string, args ...interface{}) {}
    method Debugln (line 48) | func (*discard) Debugln(args ...interface{})               {}
    method Error (line 49) | func (*discard) Error(args ...interface{})                 {}
    method Errorf (line 50) | func (*discard) Errorf(format string, args ...interface{}) {}
    method Errorln (line 51) | func (*discard) Errorln(args ...interface{})               {}
    method Info (line 52) | func (*discard) Info(args ...interface{})                  {}
    method Infof (line 53) | func (*discard) Infof(format string, args ...interface{})  {}
    method Infoln (line 54) | func (*discard) Infoln(args ...interface{})                {}
    method Trace (line 55) | func (*discard) Trace(args ...interface{})                 {}
    method Tracef (line 56) | func (*discard) Tracef(format string, args ...interface{}) {}
    method Traceln (line 57) | func (*discard) Traceln(args ...interface{})               {}
    method Warn (line 58) | func (*discard) Warn(args ...interface{})                  {}
    method Warnf (line 59) | func (*discard) Warnf(format string, args ...interface{})  {}
    method Warnln (line 60) | func (*discard) Warnln(args ...interface{})                {}
    method WithError (line 61) | func (d *discard) WithError(error) Logger                  { return d }
    method WithField (line 62) | func (d *discard) WithField(string, interface{}) Logger    { return d }

FILE: logger/logger_test.go
  function TestWithError (line 9) | func TestWithError(t *testing.T) {
  function TestWithField (line 16) | func TestWithField(t *testing.T) {

FILE: logger/logrus.go
  function Logrus (line 10) | func Logrus(entry *logrus.Entry) Logger {
  type wrapLogrus (line 14) | type wrapLogrus struct
    method WithError (line 18) | func (w *wrapLogrus) WithError(err error) Logger {
    method WithField (line 22) | func (w *wrapLogrus) WithField(key string, value interface{}) Logger {

FILE: logger/logrus_test.go
  function TestLogrus (line 13) | func TestLogrus(t *testing.T) {

FILE: logger/request/request.go
  function Logger (line 17) | func Logger(next http.Handler) http.Handler {

FILE: metrics/metrics.go
  type Collector (line 17) | type Collector interface
  type Prometheus (line 46) | type Prometheus struct
    method TrackServerCreateTime (line 96) | func (m *Prometheus) TrackServerCreateTime(start time.Time) {
    method TrackServerInitTime (line 105) | func (m *Prometheus) TrackServerInitTime(start time.Time) {
    method TrackServerSetupTime (line 113) | func (m *Prometheus) TrackServerSetupTime(start time.Time) {
    method IncrServerCreateError (line 121) | func (m *Prometheus) IncrServerCreateError() {
    method IncrServerInitError (line 128) | func (m *Prometheus) IncrServerInitError() {
    method IncrServerSetupError (line 134) | func (m *Prometheus) IncrServerSetupError() {
  function New (line 56) | func New() *Prometheus {
  type NopCollector (line 139) | type NopCollector struct
    method TrackServerCreateTime (line 143) | func (*NopCollector) TrackServerCreateTime(start time.Time) {}
    method TrackServerInitTime (line 148) | func (*NopCollector) TrackServerInitTime(start time.Time) {}
    method TrackServerSetupTime (line 152) | func (*NopCollector) TrackServerSetupTime(start time.Time) {}
    method IncrServerCreateError (line 156) | func (*NopCollector) IncrServerCreateError() {}
    method IncrServerInitError (line 161) | func (*NopCollector) IncrServerInitError() {}
    method IncrServerSetupError (line 165) | func (*NopCollector) IncrServerSetupError() {}

FILE: metrics/server_capacity.go
  function ServerCapacity (line 13) | func ServerCapacity(store autoscaler.ServerStore) autoscaler.ServerStore {

FILE: metrics/server_capacity_test.go
  function TestServerCapacity (line 17) | func TestServerCapacity(t *testing.T) {

FILE: metrics/server_count.go
  function ServerCount (line 13) | func ServerCount(store autoscaler.ServerStore) autoscaler.ServerStore {

FILE: metrics/server_count_test.go
  function TestServerCount (line 17) | func TestServerCount(t *testing.T) {

FILE: metrics/server_create.go
  function ServerCreate (line 15) | func ServerCreate(provider autoscaler.Provider) autoscaler.Provider {
  type providerWrapCreate (line 34) | type providerWrapCreate struct
    method Create (line 40) | func (p *providerWrapCreate) Create(ctx context.Context, opts autoscal...

FILE: metrics/server_create_test.go
  function TestServerCreate (line 17) | func TestServerCreate(t *testing.T) {

FILE: metrics/server_delete.go
  function ServerDelete (line 15) | func ServerDelete(provider autoscaler.Provider) autoscaler.Provider {
  type providerWrapDestroy (line 34) | type providerWrapDestroy struct
    method Destroy (line 40) | func (p *providerWrapDestroy) Destroy(ctx context.Context, instance *a...

FILE: metrics/server_delete_test.go
  function TestServerDelete (line 17) | func TestServerDelete(t *testing.T) {

FILE: mocks/mock_docker.go
  type MockAPIClient (line 32) | type MockAPIClient struct
    method EXPECT (line 50) | func (m *MockAPIClient) EXPECT() *MockAPIClientMockRecorder {
    method BuildCachePrune (line 55) | func (m *MockAPIClient) BuildCachePrune(arg0 context.Context, arg1 typ...
    method BuildCancel (line 70) | func (m *MockAPIClient) BuildCancel(arg0 context.Context, arg1 string)...
    method CheckpointCreate (line 84) | func (m *MockAPIClient) CheckpointCreate(arg0 context.Context, arg1 st...
    method CheckpointDelete (line 98) | func (m *MockAPIClient) CheckpointDelete(arg0 context.Context, arg1 st...
    method CheckpointList (line 112) | func (m *MockAPIClient) CheckpointList(arg0 context.Context, arg1 stri...
    method ClientVersion (line 127) | func (m *MockAPIClient) ClientVersion() string {
    method Close (line 141) | func (m *MockAPIClient) Close() error {
    method ConfigCreate (line 155) | func (m *MockAPIClient) ConfigCreate(arg0 context.Context, arg1 swarm....
    method ConfigInspectWithRaw (line 170) | func (m *MockAPIClient) ConfigInspectWithRaw(arg0 context.Context, arg...
    method ConfigList (line 186) | func (m *MockAPIClient) ConfigList(arg0 context.Context, arg1 types.Co...
    method ConfigRemove (line 201) | func (m *MockAPIClient) ConfigRemove(arg0 context.Context, arg1 string...
    method ConfigUpdate (line 215) | func (m *MockAPIClient) ConfigUpdate(arg0 context.Context, arg1 string...
    method ContainerAttach (line 229) | func (m *MockAPIClient) ContainerAttach(arg0 context.Context, arg1 str...
    method ContainerCommit (line 244) | func (m *MockAPIClient) ContainerCommit(arg0 context.Context, arg1 str...
    method ContainerCreate (line 259) | func (m *MockAPIClient) ContainerCreate(arg0 context.Context, arg1 *co...
    method ContainerDiff (line 274) | func (m *MockAPIClient) ContainerDiff(arg0 context.Context, arg1 strin...
    method ContainerExecAttach (line 289) | func (m *MockAPIClient) ContainerExecAttach(arg0 context.Context, arg1...
    method ContainerExecCreate (line 304) | func (m *MockAPIClient) ContainerExecCreate(arg0 context.Context, arg1...
    method ContainerExecInspect (line 319) | func (m *MockAPIClient) ContainerExecInspect(arg0 context.Context, arg...
    method ContainerExecResize (line 334) | func (m *MockAPIClient) ContainerExecResize(arg0 context.Context, arg1...
    method ContainerExecStart (line 348) | func (m *MockAPIClient) ContainerExecStart(arg0 context.Context, arg1 ...
    method ContainerExport (line 362) | func (m *MockAPIClient) ContainerExport(arg0 context.Context, arg1 str...
    method ContainerInspect (line 377) | func (m *MockAPIClient) ContainerInspect(arg0 context.Context, arg1 st...
    method ContainerInspectWithRaw (line 392) | func (m *MockAPIClient) ContainerInspectWithRaw(arg0 context.Context, ...
    method ContainerKill (line 408) | func (m *MockAPIClient) ContainerKill(arg0 context.Context, arg1, arg2...
    method ContainerList (line 422) | func (m *MockAPIClient) ContainerList(arg0 context.Context, arg1 conta...
    method ContainerLogs (line 437) | func (m *MockAPIClient) ContainerLogs(arg0 context.Context, arg1 strin...
    method ContainerPause (line 452) | func (m *MockAPIClient) ContainerPause(arg0 context.Context, arg1 stri...
    method ContainerRemove (line 466) | func (m *MockAPIClient) ContainerRemove(arg0 context.Context, arg1 str...
    method ContainerRename (line 480) | func (m *MockAPIClient) ContainerRename(arg0 context.Context, arg1, ar...
    method ContainerResize (line 494) | func (m *MockAPIClient) ContainerResize(arg0 context.Context, arg1 str...
    method ContainerRestart (line 508) | func (m *MockAPIClient) ContainerRestart(arg0 context.Context, arg1 st...
    method ContainerStart (line 522) | func (m *MockAPIClient) ContainerStart(arg0 context.Context, arg1 stri...
    method ContainerStatPath (line 536) | func (m *MockAPIClient) ContainerStatPath(arg0 context.Context, arg1, ...
    method ContainerStats (line 551) | func (m *MockAPIClient) ContainerStats(arg0 context.Context, arg1 stri...
    method ContainerStatsOneShot (line 566) | func (m *MockAPIClient) ContainerStatsOneShot(arg0 context.Context, ar...
    method ContainerStop (line 581) | func (m *MockAPIClient) ContainerStop(arg0 context.Context, arg1 strin...
    method ContainerTop (line 595) | func (m *MockAPIClient) ContainerTop(arg0 context.Context, arg1 string...
    method ContainerUnpause (line 610) | func (m *MockAPIClient) ContainerUnpause(arg0 context.Context, arg1 st...
    method ContainerUpdate (line 624) | func (m *MockAPIClient) ContainerUpdate(arg0 context.Context, arg1 str...
    method ContainerWait (line 639) | func (m *MockAPIClient) ContainerWait(arg0 context.Context, arg1 strin...
    method ContainersPrune (line 654) | func (m *MockAPIClient) ContainersPrune(arg0 context.Context, arg1 fil...
    method CopyFromContainer (line 669) | func (m *MockAPIClient) CopyFromContainer(arg0 context.Context, arg1, ...
    method CopyToContainer (line 685) | func (m *MockAPIClient) CopyToContainer(arg0 context.Context, arg1, ar...
    method DaemonHost (line 699) | func (m *MockAPIClient) DaemonHost() string {
    method DialHijack (line 713) | func (m *MockAPIClient) DialHijack(arg0 context.Context, arg1, arg2 st...
    method Dialer (line 728) | func (m *MockAPIClient) Dialer() func(context.Context) (net.Conn, erro...
    method DiskUsage (line 742) | func (m *MockAPIClient) DiskUsage(arg0 context.Context, arg1 types.Dis...
    method DistributionInspect (line 757) | func (m *MockAPIClient) DistributionInspect(arg0 context.Context, arg1...
    method Events (line 772) | func (m *MockAPIClient) Events(arg0 context.Context, arg1 events.ListO...
    method HTTPClient (line 787) | func (m *MockAPIClient) HTTPClient() *http.Client {
    method ImageBuild (line 801) | func (m *MockAPIClient) ImageBuild(arg0 context.Context, arg1 io.Reade...
    method ImageCreate (line 816) | func (m *MockAPIClient) ImageCreate(arg0 context.Context, arg1 string,...
    method ImageHistory (line 831) | func (m *MockAPIClient) ImageHistory(arg0 context.Context, arg1 string...
    method ImageImport (line 851) | func (m *MockAPIClient) ImageImport(arg0 context.Context, arg1 image.I...
    method ImageInspect (line 866) | func (m *MockAPIClient) ImageInspect(arg0 context.Context, arg1 string...
    method ImageInspectWithRaw (line 886) | func (m *MockAPIClient) ImageInspectWithRaw(arg0 context.Context, arg1...
    method ImageList (line 902) | func (m *MockAPIClient) ImageList(arg0 context.Context, arg1 image.Lis...
    method ImageLoad (line 917) | func (m *MockAPIClient) ImageLoad(arg0 context.Context, arg1 io.Reader...
    method ImagePull (line 937) | func (m *MockAPIClient) ImagePull(arg0 context.Context, arg1 string, a...
    method ImagePush (line 952) | func (m *MockAPIClient) ImagePush(arg0 context.Context, arg1 string, a...
    method ImageRemove (line 967) | func (m *MockAPIClient) ImageRemove(arg0 context.Context, arg1 string,...
    method ImageSave (line 982) | func (m *MockAPIClient) ImageSave(arg0 context.Context, arg1 []string,...
    method ImageSearch (line 1002) | func (m *MockAPIClient) ImageSearch(arg0 context.Context, arg1 string,...
    method ImageTag (line 1017) | func (m *MockAPIClient) ImageTag(arg0 context.Context, arg1, arg2 stri...
    method ImagesPrune (line 1031) | func (m *MockAPIClient) ImagesPrune(arg0 context.Context, arg1 filters...
    method Info (line 1046) | func (m *MockAPIClient) Info(arg0 context.Context) (system.Info, error) {
    method NegotiateAPIVersion (line 1061) | func (m *MockAPIClient) NegotiateAPIVersion(arg0 context.Context) {
    method NegotiateAPIVersionPing (line 1073) | func (m *MockAPIClient) NegotiateAPIVersionPing(arg0 types.Ping) {
    method NetworkConnect (line 1085) | func (m *MockAPIClient) NetworkConnect(arg0 context.Context, arg1, arg...
    method NetworkCreate (line 1099) | func (m *MockAPIClient) NetworkCreate(arg0 context.Context, arg1 strin...
    method NetworkDisconnect (line 1114) | func (m *MockAPIClient) NetworkDisconnect(arg0 context.Context, arg1, ...
    method NetworkInspect (line 1128) | func (m *MockAPIClient) NetworkInspect(arg0 context.Context, arg1 stri...
    method NetworkInspectWithRaw (line 1143) | func (m *MockAPIClient) NetworkInspectWithRaw(arg0 context.Context, ar...
    method NetworkList (line 1159) | func (m *MockAPIClient) NetworkList(arg0 context.Context, arg1 network...
    method NetworkRemove (line 1174) | func (m *MockAPIClient) NetworkRemove(arg0 context.Context, arg1 strin...
    method NetworksPrune (line 1188) | func (m *MockAPIClient) NetworksPrune(arg0 context.Context, arg1 filte...
    method NodeInspectWithRaw (line 1203) | func (m *MockAPIClient) NodeInspectWithRaw(arg0 context.Context, arg1 ...
    method NodeList (line 1219) | func (m *MockAPIClient) NodeList(arg0 context.Context, arg1 types.Node...
    method NodeRemove (line 1234) | func (m *MockAPIClient) NodeRemove(arg0 context.Context, arg1 string, ...
    method NodeUpdate (line 1248) | func (m *MockAPIClient) NodeUpdate(arg0 context.Context, arg1 string, ...
    method Ping (line 1262) | func (m *MockAPIClient) Ping(arg0 context.Context) (types.Ping, error) {
    method PluginCreate (line 1277) | func (m *MockAPIClient) PluginCreate(arg0 context.Context, arg1 io.Rea...
    method PluginDisable (line 1291) | func (m *MockAPIClient) PluginDisable(arg0 context.Context, arg1 strin...
    method PluginEnable (line 1305) | func (m *MockAPIClient) PluginEnable(arg0 context.Context, arg1 string...
    method PluginInspectWithRaw (line 1319) | func (m *MockAPIClient) PluginInspectWithRaw(arg0 context.Context, arg...
    method PluginInstall (line 1335) | func (m *MockAPIClient) PluginInstall(arg0 context.Context, arg1 strin...
    method PluginList (line 1350) | func (m *MockAPIClient) PluginList(arg0 context.Context, arg1 filters....
    method PluginPush (line 1365) | func (m *MockAPIClient) PluginPush(arg0 context.Context, arg1, arg2 st...
    method PluginRemove (line 1380) | func (m *MockAPIClient) PluginRemove(arg0 context.Context, arg1 string...
    method PluginSet (line 1394) | func (m *MockAPIClient) PluginSet(arg0 context.Context, arg1 string, a...
    method PluginUpgrade (line 1408) | func (m *MockAPIClient) PluginUpgrade(arg0 context.Context, arg1 strin...
    method RegistryLogin (line 1423) | func (m *MockAPIClient) RegistryLogin(arg0 context.Context, arg1 regis...
    method SecretCreate (line 1438) | func (m *MockAPIClient) SecretCreate(arg0 context.Context, arg1 swarm....
    method SecretInspectWithRaw (line 1453) | func (m *MockAPIClient) SecretInspectWithRaw(arg0 context.Context, arg...
    method SecretList (line 1469) | func (m *MockAPIClient) SecretList(arg0 context.Context, arg1 types.Se...
    method SecretRemove (line 1484) | func (m *MockAPIClient) SecretRemove(arg0 context.Context, arg1 string...
    method SecretUpdate (line 1498) | func (m *MockAPIClient) SecretUpdate(arg0 context.Context, arg1 string...
    method ServerVersion (line 1512) | func (m *MockAPIClient) ServerVersion(arg0 context.Context) (types.Ver...
    method ServiceCreate (line 1527) | func (m *MockAPIClient) ServiceCreate(arg0 context.Context, arg1 swarm...
    method ServiceInspectWithRaw (line 1542) | func (m *MockAPIClient) ServiceInspectWithRaw(arg0 context.Context, ar...
    method ServiceList (line 1558) | func (m *MockAPIClient) ServiceList(arg0 context.Context, arg1 types.S...
    method ServiceLogs (line 1573) | func (m *MockAPIClient) ServiceLogs(arg0 context.Context, arg1 string,...
    method ServiceRemove (line 1588) | func (m *MockAPIClient) ServiceRemove(arg0 context.Context, arg1 strin...
    method ServiceUpdate (line 1602) | func (m *MockAPIClient) ServiceUpdate(arg0 context.Context, arg1 strin...
    method SwarmGetUnlockKey (line 1617) | func (m *MockAPIClient) SwarmGetUnlockKey(arg0 context.Context) (types...
    method SwarmInit (line 1632) | func (m *MockAPIClient) SwarmInit(arg0 context.Context, arg1 swarm.Ini...
    method SwarmInspect (line 1647) | func (m *MockAPIClient) SwarmInspect(arg0 context.Context) (swarm.Swar...
    method SwarmJoin (line 1662) | func (m *MockAPIClient) SwarmJoin(arg0 context.Context, arg1 swarm.Joi...
    method SwarmLeave (line 1676) | func (m *MockAPIClient) SwarmLeave(arg0 context.Context, arg1 bool) er...
    method SwarmUnlock (line 1690) | func (m *MockAPIClient) SwarmUnlock(arg0 context.Context, arg1 swarm.U...
    method SwarmUpdate (line 1704) | func (m *MockAPIClient) SwarmUpdate(arg0 context.Context, arg1 swarm.V...
    method TaskInspectWithRaw (line 1718) | func (m *MockAPIClient) TaskInspectWithRaw(arg0 context.Context, arg1 ...
    method TaskList (line 1734) | func (m *MockAPIClient) TaskList(arg0 context.Context, arg1 types.Task...
    method TaskLogs (line 1749) | func (m *MockAPIClient) TaskLogs(arg0 context.Context, arg1 string, ar...
    method VolumeCreate (line 1764) | func (m *MockAPIClient) VolumeCreate(arg0 context.Context, arg1 volume...
    method VolumeInspect (line 1779) | func (m *MockAPIClient) VolumeInspect(arg0 context.Context, arg1 strin...
    method VolumeInspectWithRaw (line 1794) | func (m *MockAPIClient) VolumeInspectWithRaw(arg0 context.Context, arg...
    method VolumeList (line 1810) | func (m *MockAPIClient) VolumeList(arg0 context.Context, arg1 volume.L...
    method VolumeRemove (line 1825) | func (m *MockAPIClient) VolumeRemove(arg0 context.Context, arg1 string...
    method VolumeUpdate (line 1839) | func (m *MockAPIClient) VolumeUpdate(arg0 context.Context, arg1 string...
    method VolumesPrune (line 1853) | func (m *MockAPIClient) VolumesPrune(arg0 context.Context, arg1 filter...
  type MockAPIClientMockRecorder (line 38) | type MockAPIClientMockRecorder struct
    method BuildCachePrune (line 64) | func (mr *MockAPIClientMockRecorder) BuildCachePrune(arg0, arg1 interf...
    method BuildCancel (line 78) | func (mr *MockAPIClientMockRecorder) BuildCancel(arg0, arg1 interface{...
    method CheckpointCreate (line 92) | func (mr *MockAPIClientMockRecorder) CheckpointCreate(arg0, arg1, arg2...
    method CheckpointDelete (line 106) | func (mr *MockAPIClientMockRecorder) CheckpointDelete(arg0, arg1, arg2...
    method CheckpointList (line 121) | func (mr *MockAPIClientMockRecorder) CheckpointList(arg0, arg1, arg2 i...
    method ClientVersion (line 135) | func (mr *MockAPIClientMockRecorder) ClientVersion() *gomock.Call {
    method Close (line 149) | func (mr *MockAPIClientMockRecorder) Close() *gomock.Call {
    method ConfigCreate (line 164) | func (mr *MockAPIClientMockRecorder) ConfigCreate(arg0, arg1 interface...
    method ConfigInspectWithRaw (line 180) | func (mr *MockAPIClientMockRecorder) ConfigInspectWithRaw(arg0, arg1 i...
    method ConfigList (line 195) | func (mr *MockAPIClientMockRecorder) ConfigList(arg0, arg1 interface{}...
    method ConfigRemove (line 209) | func (mr *MockAPIClientMockRecorder) ConfigRemove(arg0, arg1 interface...
    method ConfigUpdate (line 223) | func (mr *MockAPIClientMockRecorder) ConfigUpdate(arg0, arg1, arg2, ar...
    method ContainerAttach (line 238) | func (mr *MockAPIClientMockRecorder) ContainerAttach(arg0, arg1, arg2 ...
    method ContainerCommit (line 253) | func (mr *MockAPIClientMockRecorder) ContainerCommit(arg0, arg1, arg2 ...
    method ContainerCreate (line 268) | func (mr *MockAPIClientMockRecorder) ContainerCreate(arg0, arg1, arg2,...
    method ContainerDiff (line 283) | func (mr *MockAPIClientMockRecorder) ContainerDiff(arg0, arg1 interfac...
    method ContainerExecAttach (line 298) | func (mr *MockAPIClientMockRecorder) ContainerExecAttach(arg0, arg1, a...
    method ContainerExecCreate (line 313) | func (mr *MockAPIClientMockRecorder) ContainerExecCreate(arg0, arg1, a...
    method ContainerExecInspect (line 328) | func (mr *MockAPIClientMockRecorder) ContainerExecInspect(arg0, arg1 i...
    method ContainerExecResize (line 342) | func (mr *MockAPIClientMockRecorder) ContainerExecResize(arg0, arg1, a...
    method ContainerExecStart (line 356) | func (mr *MockAPIClientMockRecorder) ContainerExecStart(arg0, arg1, ar...
    method ContainerExport (line 371) | func (mr *MockAPIClientMockRecorder) ContainerExport(arg0, arg1 interf...
    method ContainerInspect (line 386) | func (mr *MockAPIClientMockRecorder) ContainerInspect(arg0, arg1 inter...
    method ContainerInspectWithRaw (line 402) | func (mr *MockAPIClientMockRecorder) ContainerInspectWithRaw(arg0, arg...
    method ContainerKill (line 416) | func (mr *MockAPIClientMockRecorder) ContainerKill(arg0, arg1, arg2 in...
    method ContainerList (line 431) | func (mr *MockAPIClientMockRecorder) ContainerList(arg0, arg1 interfac...
    method ContainerLogs (line 446) | func (mr *MockAPIClientMockRecorder) ContainerLogs(arg0, arg1, arg2 in...
    method ContainerPause (line 460) | func (mr *MockAPIClientMockRecorder) ContainerPause(arg0, arg1 interfa...
    method ContainerRemove (line 474) | func (mr *MockAPIClientMockRecorder) ContainerRemove(arg0, arg1, arg2 ...
    method ContainerRename (line 488) | func (mr *MockAPIClientMockRecorder) ContainerRename(arg0, arg1, arg2 ...
    method ContainerResize (line 502) | func (mr *MockAPIClientMockRecorder) ContainerResize(arg0, arg1, arg2 ...
    method ContainerRestart (line 516) | func (mr *MockAPIClientMockRecorder) ContainerRestart(arg0, arg1, arg2...
    method ContainerStart (line 530) | func (mr *MockAPIClientMockRecorder) ContainerStart(arg0, arg1, arg2 i...
    method ContainerStatPath (line 545) | func (mr *MockAPIClientMockRecorder) ContainerStatPath(arg0, arg1, arg...
    method ContainerStats (line 560) | func (mr *MockAPIClientMockRecorder) ContainerStats(arg0, arg1, arg2 i...
    method ContainerStatsOneShot (line 575) | func (mr *MockAPIClientMockRecorder) ContainerStatsOneShot(arg0, arg1 ...
    method ContainerStop (line 589) | func (mr *MockAPIClientMockRecorder) ContainerStop(arg0, arg1, arg2 in...
    method ContainerTop (line 604) | func (mr *MockAPIClientMockRecorder) ContainerTop(arg0, arg1, arg2 int...
    method ContainerUnpause (line 618) | func (mr *MockAPIClientMockRecorder) ContainerUnpause(arg0, arg1 inter...
    method ContainerUpdate (line 633) | func (mr *MockAPIClientMockRecorder) ContainerUpdate(arg0, arg1, arg2 ...
    method ContainerWait (line 648) | func (mr *MockAPIClientMockRecorder) ContainerWait(arg0, arg1, arg2 in...
    method ContainersPrune (line 663) | func (mr *MockAPIClientMockRecorder) ContainersPrune(arg0, arg1 interf...
    method CopyFromContainer (line 679) | func (mr *MockAPIClientMockRecorder) CopyFromContainer(arg0, arg1, arg...
    method CopyToContainer (line 693) | func (mr *MockAPIClientMockRecorder) CopyToContainer(arg0, arg1, arg2,...
    method DaemonHost (line 707) | func (mr *MockAPIClientMockRecorder) DaemonHost() *gomock.Call {
    method DialHijack (line 722) | func (mr *MockAPIClientMockRecorder) DialHijack(arg0, arg1, arg2, arg3...
    method Dialer (line 736) | func (mr *MockAPIClientMockRecorder) Dialer() *gomock.Call {
    method DiskUsage (line 751) | func (mr *MockAPIClientMockRecorder) DiskUsage(arg0, arg1 interface{})...
    method DistributionInspect (line 766) | func (mr *MockAPIClientMockRecorder) DistributionInspect(arg0, arg1, a...
    method Events (line 781) | func (mr *MockAPIClientMockRecorder) Events(arg0, arg1 interface{}) *g...
    method HTTPClient (line 795) | func (mr *MockAPIClientMockRecorder) HTTPClient() *gomock.Call {
    method ImageBuild (line 810) | func (mr *MockAPIClientMockRecorder) ImageBuild(arg0, arg1, arg2 inter...
    method ImageCreate (line 825) | func (mr *MockAPIClientMockRecorder) ImageCreate(arg0, arg1, arg2 inte...
    method ImageHistory (line 844) | func (mr *MockAPIClientMockRecorder) ImageHistory(arg0, arg1 interface...
    method ImageImport (line 860) | func (mr *MockAPIClientMockRecorder) ImageImport(arg0, arg1, arg2, arg...
    method ImageInspect (line 879) | func (mr *MockAPIClientMockRecorder) ImageInspect(arg0, arg1 interface...
    method ImageInspectWithRaw (line 896) | func (mr *MockAPIClientMockRecorder) ImageInspectWithRaw(arg0, arg1 in...
    method ImageList (line 911) | func (mr *MockAPIClientMockRecorder) ImageList(arg0, arg1 interface{})...
    method ImageLoad (line 930) | func (mr *MockAPIClientMockRecorder) ImageLoad(arg0, arg1 interface{},...
    method ImagePull (line 946) | func (mr *MockAPIClientMockRecorder) ImagePull(arg0, arg1, arg2 interf...
    method ImagePush (line 961) | func (mr *MockAPIClientMockRecorder) ImagePush(arg0, arg1, arg2 interf...
    method ImageRemove (line 976) | func (mr *MockAPIClientMockRecorder) ImageRemove(arg0, arg1, arg2 inte...
    method ImageSave (line 995) | func (mr *MockAPIClientMockRecorder) ImageSave(arg0, arg1 interface{},...
    method ImageSearch (line 1011) | func (mr *MockAPIClientMockRecorder) ImageSearch(arg0, arg1, arg2 inte...
    method ImageTag (line 1025) | func (mr *MockAPIClientMockRecorder) ImageTag(arg0, arg1, arg2 interfa...
    method ImagesPrune (line 1040) | func (mr *MockAPIClientMockRecorder) ImagesPrune(arg0, arg1 interface{...
    method Info (line 1055) | func (mr *MockAPIClientMockRecorder) Info(arg0 interface{}) *gomock.Ca...
    method NegotiateAPIVersion (line 1067) | func (mr *MockAPIClientMockRecorder) NegotiateAPIVersion(arg0 interfac...
    method NegotiateAPIVersionPing (line 1079) | func (mr *MockAPIClientMockRecorder) NegotiateAPIVersionPing(arg0 inte...
    method NetworkConnect (line 1093) | func (mr *MockAPIClientMockRecorder) NetworkConnect(arg0, arg1, arg2, ...
    method NetworkCreate (line 1108) | func (mr *MockAPIClientMockRecorder) NetworkCreate(arg0, arg1, arg2 in...
    method NetworkDisconnect (line 1122) | func (mr *MockAPIClientMockRecorder) NetworkDisconnect(arg0, arg1, arg...
    method NetworkInspect (line 1137) | func (mr *MockAPIClientMockRecorder) NetworkInspect(arg0, arg1, arg2 i...
    method NetworkInspectWithRaw (line 1153) | func (mr *MockAPIClientMockRecorder) NetworkInspectWithRaw(arg0, arg1,...
    method NetworkList (line 1168) | func (mr *MockAPIClientMockRecorder) NetworkList(arg0, arg1 interface{...
    method NetworkRemove (line 1182) | func (mr *MockAPIClientMockRecorder) NetworkRemove(arg0, arg1 interfac...
    method NetworksPrune (line 1197) | func (mr *MockAPIClientMockRecorder) NetworksPrune(arg0, arg1 interfac...
    method NodeInspectWithRaw (line 1213) | func (mr *MockAPIClientMockRecorder) NodeInspectWithRaw(arg0, arg1 int...
    method NodeList (line 1228) | func (mr *MockAPIClientMockRecorder) NodeList(arg0, arg1 interface{}) ...
    method NodeRemove (line 1242) | func (mr *MockAPIClientMockRecorder) NodeRemove(arg0, arg1, arg2 inter...
    method NodeUpdate (line 1256) | func (mr *MockAPIClientMockRecorder) NodeUpdate(arg0, arg1, arg2, arg3...
    method Ping (line 1271) | func (mr *MockAPIClientMockRecorder) Ping(arg0 interface{}) *gomock.Ca...
    method PluginCreate (line 1285) | func (mr *MockAPIClientMockRecorder) PluginCreate(arg0, arg1, arg2 int...
    method PluginDisable (line 1299) | func (mr *MockAPIClientMockRecorder) PluginDisable(arg0, arg1, arg2 in...
    method PluginEnable (line 1313) | func (mr *MockAPIClientMockRecorder) PluginEnable(arg0, arg1, arg2 int...
    method PluginInspectWithRaw (line 1329) | func (mr *MockAPIClientMockRecorder) PluginInspectWithRaw(arg0, arg1 i...
    method PluginInstall (line 1344) | func (mr *MockAPIClientMockRecorder) PluginInstall(arg0, arg1, arg2 in...
    method PluginList (line 1359) | func (mr *MockAPIClientMockRecorder) PluginList(arg0, arg1 interface{}...
    method PluginPush (line 1374) | func (mr *MockAPIClientMockRecorder) PluginPush(arg0, arg1, arg2 inter...
    method PluginRemove (line 1388) | func (mr *MockAPIClientMockRecorder) PluginRemove(arg0, arg1, arg2 int...
    method PluginSet (line 1402) | func (mr *MockAPIClientMockRecorder) PluginSet(arg0, arg1, arg2 interf...
    method PluginUpgrade (line 1417) | func (mr *MockAPIClientMockRecorder) PluginUpgrade(arg0, arg1, arg2 in...
    method RegistryLogin (line 1432) | func (mr *MockAPIClientMockRecorder) RegistryLogin(arg0, arg1 interfac...
    method SecretCreate (line 1447) | func (mr *MockAPIClientMockRecorder) SecretCreate(arg0, arg1 interface...
    method SecretInspectWithRaw (line 1463) | func (mr *MockAPIClientMockRecorder) SecretInspectWithRaw(arg0, arg1 i...
    method SecretList (line 1478) | func (mr *MockAPIClientMockRecorder) SecretList(arg0, arg1 interface{}...
    method SecretRemove (line 1492) | func (mr *MockAPIClientMockRecorder) SecretRemove(arg0, arg1 interface...
    method SecretUpdate (line 1506) | func (mr *MockAPIClientMockRecorder) SecretUpdate(arg0, arg1, arg2, ar...
    method ServerVersion (line 1521) | func (mr *MockAPIClientMockRecorder) ServerVersion(arg0 interface{}) *...
    method ServiceCreate (line 1536) | func (mr *MockAPIClientMockRecorder) ServiceCreate(arg0, arg1, arg2 in...
    method ServiceInspectWithRaw (line 1552) | func (mr *MockAPIClientMockRecorder) ServiceInspectWithRaw(arg0, arg1,...
    method ServiceList (line 1567) | func (mr *MockAPIClientMockRecorder) ServiceList(arg0, arg1 interface{...
    method ServiceLogs (line 1582) | func (mr *MockAPIClientMockRecorder) ServiceLogs(arg0, arg1, arg2 inte...
    method ServiceRemove (line 1596) | func (mr *MockAPIClientMockRecorder) ServiceRemove(arg0, arg1 interfac...
    method ServiceUpdate (line 1611) | func (mr *MockAPIClientMockRecorder) ServiceUpdate(arg0, arg1, arg2, a...
    method SwarmGetUnlockKey (line 1626) | func (mr *MockAPIClientMockRecorder) SwarmGetUnlockKey(arg0 interface{...
    method SwarmInit (line 1641) | func (mr *MockAPIClientMockRecorder) SwarmInit(arg0, arg1 interface{})...
    method SwarmInspect (line 1656) | func (mr *MockAPIClientMockRecorder) SwarmInspect(arg0 interface{}) *g...
    method SwarmJoin (line 1670) | func (mr *MockAPIClientMockRecorder) SwarmJoin(arg0, arg1 interface{})...
    method SwarmLeave (line 1684) | func (mr *MockAPIClientMockRecorder) SwarmLeave(arg0, arg1 interface{}...
    method SwarmUnlock (line 1698) | func (mr *MockAPIClientMockRecorder) SwarmUnlock(arg0, arg1 interface{...
    method SwarmUpdate (line 1712) | func (mr *MockAPIClientMockRecorder) SwarmUpdate(arg0, arg1, arg2, arg...
    method TaskInspectWithRaw (line 1728) | func (mr *MockAPIClientMockRecorder) TaskInspectWithRaw(arg0, arg1 int...
    method TaskList (line 1743) | func (mr *MockAPIClientMockRecorder) TaskList(arg0, arg1 interface{}) ...
    method TaskLogs (line 1758) | func (mr *MockAPIClientMockRecorder) TaskLogs(arg0, arg1, arg2 interfa...
    method VolumeCreate (line 1773) | func (mr *MockAPIClientMockRecorder) VolumeCreate(arg0, arg1 interface...
    method VolumeInspect (line 1788) | func (mr *MockAPIClientMockRecorder) VolumeInspect(arg0, arg1 interfac...
    method VolumeInspectWithRaw (line 1804) | func (mr *MockAPIClientMockRecorder) VolumeInspectWithRaw(arg0, arg1 i...
    method VolumeList (line 1819) | func (mr *MockAPIClientMockRecorder) VolumeList(arg0, arg1 interface{}...
    method VolumeRemove (line 1833) | func (mr *MockAPIClientMockRecorder) VolumeRemove(arg0, arg1, arg2 int...
    method VolumeUpdate (line 1847) | func (mr *MockAPIClientMockRecorder) VolumeUpdate(arg0, arg1, arg2, ar...
    method VolumesPrune (line 1862) | func (mr *MockAPIClientMockRecorder) VolumesPrune(arg0, arg1 interface...
  function NewMockAPIClient (line 43) | func NewMockAPIClient(ctrl *gomock.Controller) *MockAPIClient {

FILE: mocks/mock_drone.go
  type MockClient (line 16) | type MockClient struct
    method EXPECT (line 34) | func (m *MockClient) EXPECT() *MockClientMockRecorder {
    method Approve (line 39) | func (m *MockClient) Approve(arg0, arg1 string, arg2, arg3 int) error {
    method AutoscalePause (line 53) | func (m *MockClient) AutoscalePause() error {
    method AutoscaleResume (line 67) | func (m *MockClient) AutoscaleResume() error {
    method AutoscaleVersion (line 81) | func (m *MockClient) AutoscaleVersion() (*drone.Version, error) {
    method Build (line 96) | func (m *MockClient) Build(arg0, arg1 string, arg2 int) (*drone.Build,...
    method BuildCancel (line 111) | func (m *MockClient) BuildCancel(arg0, arg1 string, arg2 int) error {
    method BuildLast (line 125) | func (m *MockClient) BuildLast(arg0, arg1, arg2 string) (*drone.Build,...
    method BuildList (line 140) | func (m *MockClient) BuildList(arg0, arg1 string, arg2 drone.ListOptio...
    method BuildPurge (line 155) | func (m *MockClient) BuildPurge(arg0, arg1 string, arg2 int) error {
    method BuildRestart (line 169) | func (m *MockClient) BuildRestart(arg0, arg1 string, arg2 int, arg3 ma...
    method Cron (line 184) | func (m *MockClient) Cron(arg0, arg1, arg2 string) (*drone.Cron, error) {
    method CronCreate (line 199) | func (m *MockClient) CronCreate(arg0, arg1 string, arg2 *drone.Cron) (...
    method CronDelete (line 214) | func (m *MockClient) CronDelete(arg0, arg1, arg2 string) error {
    method CronList (line 228) | func (m *MockClient) CronList(arg0, arg1 string) ([]*drone.Cron, error) {
    method CronUpdate (line 243) | func (m *MockClient) CronUpdate(arg0, arg1, arg2 string, arg3 *drone.C...
    method Decline (line 258) | func (m *MockClient) Decline(arg0, arg1 string, arg2, arg3 int) error {
    method Encrypt (line 272) | func (m *MockClient) Encrypt(arg0, arg1 string, arg2 *drone.Secret) (s...
    method Logs (line 287) | func (m *MockClient) Logs(arg0, arg1 string, arg2, arg3, arg4 int) ([]...
    method LogsPurge (line 302) | func (m *MockClient) LogsPurge(arg0, arg1 string, arg2, arg3, arg4 int...
    method Node (line 316) | func (m *MockClient) Node(arg0 string) (*drone.Node, error) {
    method NodeCreate (line 331) | func (m *MockClient) NodeCreate(arg0 *drone.Node) (*drone.Node, error) {
    method NodeDelete (line 346) | func (m *MockClient) NodeDelete(arg0 string) error {
    method NodeList (line 360) | func (m *MockClient) NodeList() ([]*drone.Node, error) {
    method NodeUpdate (line 375) | func (m *MockClient) NodeUpdate(arg0 string, arg1 *drone.NodePatch) (*...
    method OrgSecret (line 390) | func (m *MockClient) OrgSecret(arg0, arg1 string) (*drone.Secret, erro...
    method OrgSecretCreate (line 405) | func (m *MockClient) OrgSecretCreate(arg0 string, arg1 *drone.Secret) ...
    method OrgSecretDelete (line 420) | func (m *MockClient) OrgSecretDelete(arg0, arg1 string) error {
    method OrgSecretList (line 434) | func (m *MockClient) OrgSecretList(arg0 string) ([]*drone.Secret, erro...
    method OrgSecretListAll (line 449) | func (m *MockClient) OrgSecretListAll() ([]*drone.Secret, error) {
    method OrgSecretUpdate (line 464) | func (m *MockClient) OrgSecretUpdate(arg0 string, arg1 *drone.Secret) ...
    method Promote (line 479) | func (m *MockClient) Promote(arg0, arg1 string, arg2 int, arg3 string,...
    method Queue (line 494) | func (m *MockClient) Queue() ([]*drone.Stage, error) {
    method QueuePause (line 509) | func (m *MockClient) QueuePause() error {
    method QueueResume (line 523) | func (m *MockClient) QueueResume() error {
    method Repo (line 537) | func (m *MockClient) Repo(arg0, arg1 string) (*drone.Repo, error) {
    method RepoChown (line 552) | func (m *MockClient) RepoChown(arg0, arg1 string) (*drone.Repo, error) {
    method RepoDelete (line 567) | func (m *MockClient) RepoDelete(arg0, arg1 string) error {
    method RepoDisable (line 581) | func (m *MockClient) RepoDisable(arg0, arg1 string) error {
    method RepoEnable (line 595) | func (m *MockClient) RepoEnable(arg0, arg1 string) (*drone.Repo, error) {
    method RepoList (line 610) | func (m *MockClient) RepoList() ([]*drone.Repo, error) {
    method RepoListSync (line 625) | func (m *MockClient) RepoListSync() ([]*drone.Repo, error) {
    method RepoRepair (line 640) | func (m *MockClient) RepoRepair(arg0, arg1 string) error {
    method RepoUpdate (line 654) | func (m *MockClient) RepoUpdate(arg0, arg1 string, arg2 *drone.RepoPat...
    method Rollback (line 669) | func (m *MockClient) Rollback(arg0, arg1 string, arg2 int, arg3 string...
    method Secret (line 684) | func (m *MockClient) Secret(arg0, arg1, arg2 string) (*drone.Secret, e...
    method SecretCreate (line 699) | func (m *MockClient) SecretCreate(arg0, arg1 string, arg2 *drone.Secre...
    method SecretDelete (line 714) | func (m *MockClient) SecretDelete(arg0, arg1, arg2 string) error {
    method SecretList (line 728) | func (m *MockClient) SecretList(arg0, arg1 string) ([]*drone.Secret, e...
    method SecretUpdate (line 743) | func (m *MockClient) SecretUpdate(arg0, arg1 string, arg2 *drone.Secre...
    method Self (line 758) | func (m *MockClient) Self() (*drone.User, error) {
    method Server (line 773) | func (m *MockClient) Server(arg0 string) (*drone.Server, error) {
    method ServerCreate (line 788) | func (m *MockClient) ServerCreate() (*drone.Server, error) {
    method ServerDelete (line 803) | func (m *MockClient) ServerDelete(arg0 string) error {
    method ServerList (line 817) | func (m *MockClient) ServerList() ([]*drone.Server, error) {
    method SetAddress (line 832) | func (m *MockClient) SetAddress(arg0 string) {
    method SetClient (line 844) | func (m *MockClient) SetClient(arg0 *http.Client) {
    method Sign (line 856) | func (m *MockClient) Sign(arg0, arg1, arg2 string) (string, error) {
    method User (line 871) | func (m *MockClient) User(arg0 string) (*drone.User, error) {
    method UserCreate (line 886) | func (m *MockClient) UserCreate(arg0 *drone.User) (*drone.User, error) {
    method UserDelete (line 901) | func (m *MockClient) UserDelete(arg0 string) error {
    method UserList (line 915) | func (m *MockClient) UserList() ([]*drone.User, error) {
    method UserUpdate (line 930) | func (m *MockClient) UserUpdate(arg0 string, arg1 *drone.UserPatch) (*...
    method Verify (line 945) | func (m *MockClient) Verify(arg0, arg1, arg2 string) error {
  type MockClientMockRecorder (line 22) | type MockClientMockRecorder struct
    method Approve (line 47) | func (mr *MockClientMockRecorder) Approve(arg0, arg1, arg2, arg3 inter...
    method AutoscalePause (line 61) | func (mr *MockClientMockRecorder) AutoscalePause() *gomock.Call {
    method AutoscaleResume (line 75) | func (mr *MockClientMockRecorder) AutoscaleResume() *gomock.Call {
    method AutoscaleVersion (line 90) | func (mr *MockClientMockRecorder) AutoscaleVersion() *gomock.Call {
    method Build (line 105) | func (mr *MockClientMockRecorder) Build(arg0, arg1, arg2 interface{}) ...
    method BuildCancel (line 119) | func (mr *MockClientMockRecorder) BuildCancel(arg0, arg1, arg2 interfa...
    method BuildLast (line 134) | func (mr *MockClientMockRecorder) BuildLast(arg0, arg1, arg2 interface...
    method BuildList (line 149) | func (mr *MockClientMockRecorder) BuildList(arg0, arg1, arg2 interface...
    method BuildPurge (line 163) | func (mr *MockClientMockRecorder) BuildPurge(arg0, arg1, arg2 interfac...
    method BuildRestart (line 178) | func (mr *MockClientMockRecorder) BuildRestart(arg0, arg1, arg2, arg3 ...
    method Cron (line 193) | func (mr *MockClientMockRecorder) Cron(arg0, arg1, arg2 interface{}) *...
    method CronCreate (line 208) | func (mr *MockClientMockRecorder) CronCreate(arg0, arg1, arg2 interfac...
    method CronDelete (line 222) | func (mr *MockClientMockRecorder) CronDelete(arg0, arg1, arg2 interfac...
    method CronList (line 237) | func (mr *MockClientMockRecorder) CronList(arg0, arg1 interface{}) *go...
    method CronUpdate (line 252) | func (mr *MockClientMockRecorder) CronUpdate(arg0, arg1, arg2, arg3 in...
    method Decline (line 266) | func (mr *MockClientMockRecorder) Decline(arg0, arg1, arg2, arg3 inter...
    method Encrypt (line 281) | func (mr *MockClientMockRecorder) Encrypt(arg0, arg1, arg2 interface{}...
    method Logs (line 296) | func (mr *MockClientMockRecorder) Logs(arg0, arg1, arg2, arg3, arg4 in...
    method LogsPurge (line 310) | func (mr *MockClientMockRecorder) LogsPurge(arg0, arg1, arg2, arg3, ar...
    method Node (line 325) | func (mr *MockClientMockRecorder) Node(arg0 interface{}) *gomock.Call {
    method NodeCreate (line 340) | func (mr *MockClientMockRecorder) NodeCreate(arg0 interface{}) *gomock...
    method NodeDelete (line 354) | func (mr *MockClientMockRecorder) NodeDelete(arg0 interface{}) *gomock...
    method NodeList (line 369) | func (mr *MockClientMockRecorder) NodeList() *gomock.Call {
    method NodeUpdate (line 384) | func (mr *MockClientMockRecorder) NodeUpdate(arg0, arg1 interface{}) *...
    method OrgSecret (line 399) | func (mr *MockClientMockRecorder) OrgSecret(arg0, arg1 interface{}) *g...
    method OrgSecretCreate (line 414) | func (mr *MockClientMockRecorder) OrgSecretCreate(arg0, arg1 interface...
    method OrgSecretDelete (line 428) | func (mr *MockClientMockRecorder) OrgSecretDelete(arg0, arg1 interface...
    method OrgSecretList (line 443) | func (mr *MockClientMockRecorder) OrgSecretList(arg0 interface{}) *gom...
    method OrgSecretListAll (line 458) | func (mr *MockClientMockRecorder) OrgSecretListAll() *gomock.Call {
    method OrgSecretUpdate (line 473) | func (mr *MockClientMockRecorder) OrgSecretUpdate(arg0, arg1 interface...
    method Promote (line 488) | func (mr *MockClientMockRecorder) Promote(arg0, arg1, arg2, arg3, arg4...
    method Queue (line 503) | func (mr *MockClientMockRecorder) Queue() *gomock.Call {
    method QueuePause (line 517) | func (mr *MockClientMockRecorder) QueuePause() *gomock.Call {
    method QueueResume (line 531) | func (mr *MockClientMockRecorder) QueueResume() *gomock.Call {
    method Repo (line 546) | func (mr *MockClientMockRecorder) Repo(arg0, arg1 interface{}) *gomock...
    method RepoChown (line 561) | func (mr *MockClientMockRecorder) RepoChown(arg0, arg1 interface{}) *g...
    method RepoDelete (line 575) | func (mr *MockClientMockRecorder) RepoDelete(arg0, arg1 interface{}) *...
    method RepoDisable (line 589) | func (mr *MockClientMockRecorder) RepoDisable(arg0, arg1 interface{}) ...
    method RepoEnable (line 604) | func (mr *MockClientMockRecorder) RepoEnable(arg0, arg1 interface{}) *...
    method RepoList (line 619) | func (mr *MockClientMockRecorder) RepoList() *gomock.Call {
    method RepoListSync (line 634) | func (mr *MockClientMockRecorder) RepoListSync() *gomock.Call {
    method RepoRepair (line 648) | func (mr *MockClientMockRecorder) RepoRepair(arg0, arg1 interface{}) *...
    method RepoUpdate (line 663) | func (mr *MockClientMockRecorder) RepoUpdate(arg0, arg1, arg2 interfac...
    method Rollback (line 678) | func (mr *MockClientMockRecorder) Rollback(arg0, arg1, arg2, arg3, arg...
    method Secret (line 693) | func (mr *MockClientMockRecorder) Secret(arg0, arg1, arg2 interface{})...
    method SecretCreate (line 708) | func (mr *MockClientMockRecorder) SecretCreate(arg0, arg1, arg2 interf...
    method SecretDelete (line 722) | func (mr *MockClientMockRecorder) SecretDelete(arg0, arg1, arg2 interf...
    method SecretList (line 737) | func (mr *MockClientMockRecorder) SecretList(arg0, arg1 interface{}) *...
    method SecretUpdate (line 752) | func (mr *MockClientMockRecorder) SecretUpdate(arg0, arg1, arg2 interf...
    method Self (line 767) | func (mr *MockClientMockRecorder) Self() *gomock.Call {
    method Server (line 782) | func (mr *MockClientMockRecorder) Server(arg0 interface{}) *gomock.Call {
    method ServerCreate (line 797) | func (mr *MockClientMockRecorder) ServerCreate() *gomock.Call {
    method ServerDelete (line 811) | func (mr *MockClientMockRecorder) ServerDelete(arg0 interface{}) *gomo...
    method ServerList (line 826) | func (mr *MockClientMockRecorder) ServerList() *gomock.Call {
    method SetAddress (line 838) | func (mr *MockClientMockRecorder) SetAddress(arg0 interface{}) *gomock...
    method SetClient (line 850) | func (mr *MockClientMockRecorder) SetClient(arg0 interface{}) *gomock....
    method Sign (line 865) | func (mr *MockClientMockRecorder) Sign(arg0, arg1, arg2 interface{}) *...
    method User (line 880) | func (mr *MockClientMockRecorder) User(arg0 interface{}) *gomock.Call {
    method UserCreate (line 895) | func (mr *MockClientMockRecorder) UserCreate(arg0 interface{}) *gomock...
    method UserDelete (line 909) | func (mr *MockClientMockRecorder) UserDelete(arg0 interface{}) *gomock...
    method UserList (line 924) | func (mr *MockClientMockRecorder) UserList() *gomock.Call {
    method UserUpdate (line 939) | func (mr *MockClientMockRecorder) UserUpdate(arg0, arg1 interface{}) *...
    method Verify (line 953) | func (mr *MockClientMockRecorder) Verify(arg0, arg1, arg2 interface{})...
  function NewMockClient (line 27) | func NewMockClient(ctrl *gomock.Controller) *MockClient {

FILE: mocks/mock_engine.go
  type MockEngine (line 15) | type MockEngine struct
    method EXPECT (line 33) | func (m *MockEngine) EXPECT() *MockEngineMockRecorder {
    method Pause (line 38) | func (m *MockEngine) Pause() {
    method Paused (line 50) | func (m *MockEngine) Paused() bool {
    method Resume (line 64) | func (m *MockEngine) Resume() {
    method Start (line 76) | func (m *MockEngine) Start(arg0 context.Context) {
  type MockEngineMockRecorder (line 21) | type MockEngineMockRecorder struct
    method Pause (line 44) | func (mr *MockEngineMockRecorder) Pause() *gomock.Call {
    method Paused (line 58) | func (mr *MockEngineMockRecorder) Paused() *gomock.Call {
    method Resume (line 70) | func (mr *MockEngineMockRecorder) Resume() *gomock.Call {
    method Start (line 82) | func (mr *MockEngineMockRecorder) Start(arg0 interface{}) *gomock.Call {
  function NewMockEngine (line 26) | func NewMockEngine(ctrl *gomock.Controller) *MockEngine {

FILE: mocks/mock_metrics.go
  type MockCollector (line 15) | type MockCollector struct
    method EXPECT (line 33) | func (m *MockCollector) EXPECT() *MockCollectorMockRecorder {
    method IncrServerCreateError (line 38) | func (m *MockCollector) IncrServerCreateError() {
    method IncrServerInitError (line 50) | func (m *MockCollector) IncrServerInitError() {
    method IncrServerSetupError (line 62) | func (m *MockCollector) IncrServerSetupError() {
    method TrackServerCreateTime (line 74) | func (m *MockCollector) TrackServerCreateTime(arg0 time.Time) {
    method TrackServerInitTime (line 86) | func (m *MockCollector) TrackServerInitTime(arg0 time.Time) {
    method TrackServerSetupTime (line 98) | func (m *MockCollector) TrackServerSetupTime(arg0 time.Time) {
  type MockCollectorMockRecorder (line 21) | type MockCollectorMockRecorder struct
    method IncrServerCreateError (line 44) | func (mr *MockCollectorMockRecorder) IncrServerCreateError() *gomock.C...
    method IncrServerInitError (line 56) | func (mr *MockCollectorMockRecorder) IncrServerInitError() *gomock.Call {
    method IncrServerSetupError (line 68) | func (mr *MockCollectorMockRecorder) IncrServerSetupError() *gomock.Ca...
    method TrackServerCreateTime (line 80) | func (mr *MockCollectorMockRecorder) TrackServerCreateTime(arg0 interf...
    method TrackServerInitTime (line 92) | func (mr *MockCollectorMockRecorder) TrackServerInitTime(arg0 interfac...
    method TrackServerSetupTime (line 104) | func (mr *MockCollectorMockRecorder) TrackServerSetupTime(arg0 interfa...
  function NewMockCollector (line 26) | func NewMockCollector(ctrl *gomock.Controller) *MockCollector {

FILE: mocks/mock_provider.go
  type MockProvider (line 16) | type MockProvider struct
    method EXPECT (line 34) | func (m *MockProvider) EXPECT() *MockProviderMockRecorder {
    method Create (line 39) | func (m *MockProvider) Create(arg0 context.Context, arg1 autoscaler.In...
    method Destroy (line 54) | func (m *MockProvider) Destroy(arg0 context.Context, arg1 *autoscaler....
  type MockProviderMockRecorder (line 22) | type MockProviderMockRecorder struct
    method Create (line 48) | func (mr *MockProviderMockRecorder) Create(arg0, arg1 interface{}) *go...
    method Destroy (line 62) | func (mr *MockProviderMockRecorder) Destroy(arg0, arg1 interface{}) *g...
  function NewMockProvider (line 27) | func NewMockProvider(ctrl *gomock.Controller) *MockProvider {

FILE: mocks/mock_server.go
  type MockServerStore (line 16) | type MockServerStore struct
    method EXPECT (line 34) | func (m *MockServerStore) EXPECT() *MockServerStoreMockRecorder {
    method Create (line 39) | func (m *MockServerStore) Create(arg0 context.Context, arg1 *autoscale...
    method Delete (line 53) | func (m *MockServerStore) Delete(arg0 context.Context, arg1 *autoscale...
    method Find (line 67) | func (m *MockServerStore) Find(arg0 context.Context, arg1 string) (*au...
    method List (line 82) | func (m *MockServerStore) List(arg0 context.Context) ([]*autoscaler.Se...
    method ListState (line 97) | func (m *MockServerStore) ListState(arg0 context.Context, arg1 autosca...
    method Purge (line 112) | func (m *MockServerStore) Purge(arg0 context.Context, arg1 int64) error {
    method Update (line 126) | func (m *MockServerStore) Update(arg0 context.Context, arg1 *autoscale...
  type MockServerStoreMockRecorder (line 22) | type MockServerStoreMockRecorder struct
    method Create (line 47) | func (mr *MockServerStoreMockRecorder) Create(arg0, arg1 interface{}) ...
    method Delete (line 61) | func (mr *MockServerStoreMockRecorder) Delete(arg0, arg1 interface{}) ...
    method Find (line 76) | func (mr *MockServerStoreMockRecorder) Find(arg0, arg1 interface{}) *g...
    method List (line 91) | func (mr *MockServerStoreMockRecorder) List(arg0 interface{}) *gomock....
    method ListState (line 106) | func (mr *MockServerStoreMockRecorder) ListState(arg0, arg1 interface{...
    method Purge (line 120) | func (mr *MockServerStoreMockRecorder) Purge(arg0, arg1 interface{}) *...
    method Update (line 134) | func (mr *MockServerStoreMockRecorder) Update(arg0, arg1 interface{}) ...
  function NewMockServerStore (line 27) | func NewMockServerStore(ctrl *gomock.Controller) *MockServerStore {

FILE: provider.go
  type ProviderType (line 14) | type ProviderType
    method Value (line 17) | func (s ProviderType) Value() (driver.Value, error) {
  constant ProviderAmazon (line 23) | ProviderAmazon       = ProviderType("amazon")
  constant ProviderAzure (line 24) | ProviderAzure        = ProviderType("azure")
  constant ProviderDigitalOcean (line 25) | ProviderDigitalOcean = ProviderType("digitalocean")
  constant ProviderGoogle (line 26) | ProviderGoogle       = ProviderType("google")
  constant ProviderHetznerCloud (line 27) | ProviderHetznerCloud = ProviderType("hetznercloud")
  constant ProviderLinode (line 28) | ProviderLinode       = ProviderType("linode")
  constant ProviderOpenStack (line 29) | ProviderOpenStack    = ProviderType("openstack")
  constant ProviderPacket (line 30) | ProviderPacket       = ProviderType("packet")
  constant ProviderScaleway (line 31) | ProviderScaleway     = ProviderType("scaleway")
  constant ProviderVultr (line 32) | ProviderVultr        = ProviderType("vultr")
  type Provider (line 41) | type Provider interface
  type Instance (line 50) | type Instance struct
  type InstanceCreateOpts (line 64) | type InstanceCreateOpts struct
  type InstanceError (line 74) | type InstanceError struct
    method Error (line 80) | func (e *InstanceError) Error() string {

FILE: server.go
  type ServerState (line 14) | type ServerState
    method Value (line 17) | func (s ServerState) Value() (driver.Value, error) {
  constant StatePending (line 23) | StatePending  = ServerState("pending")
  constant StateCreating (line 24) | StateCreating = ServerState("creating")
  constant StateCreated (line 25) | StateCreated  = ServerState("created")
  constant StateStaging (line 26) | StateStaging  = ServerState("staging")
  constant StateRunning (line 27) | StateRunning  = ServerState("running")
  constant StateShutdown (line 28) | StateShutdown = ServerState("shutdown")
  constant StateStopping (line 29) | StateStopping = ServerState("stopping")
  constant StateStopped (line 30) | StateStopped  = ServerState("stopped")
  constant StateError (line 31) | StateError    = ServerState("error")
  type ServerStore (line 39) | type ServerStore interface
  type Server (line 63) | type Server struct

FILE: server/auth.go
  function CheckDrone (line 20) | func CheckDrone(conf config.Config) func(http.Handler) http.Handler {

FILE: server/auth_test.go
  function TestAuthorize (line 19) | func TestAuthorize(t *testing.T) {
  function TestAuthorizeMissingToken (line 52) | func TestAuthorizeMissingToken(t *testing.T) {
  function TestAuthorizeNotFound (line 79) | func TestAuthorizeNotFound(t *testing.T) {
  function TestAuthorizeNonAdmin (line 112) | func TestAuthorizeNonAdmin(t *testing.T) {

FILE: server/engine.go
  function HandleEnginePause (line 15) | func HandleEnginePause(engine autoscaler.Engine) http.HandlerFunc {
  function HandleEngineResume (line 24) | func HandleEngineResume(engine autoscaler.Engine) http.HandlerFunc {

FILE: server/engine_test.go
  function TestHandleEnginePause (line 15) | func TestHandleEnginePause(t *testing.T) {
  function TestHandleEngineResume (line 32) | func TestHandleEngineResume(t *testing.T) {

FILE: server/healthz.go
  function HandleHealthz (line 14) | func HandleHealthz() http.HandlerFunc {

FILE: server/healthz_test.go
  function TestHandleHealthz (line 14) | func TestHandleHealthz(t *testing.T) {

FILE: server/metrics.go
  function HandleMetrics (line 15) | func HandleMetrics(token string) http.HandlerFunc {

FILE: server/metrics_test.go
  function TestHandleMetrics (line 12) | func TestHandleMetrics(t *testing.T) {
  function TestHandleMetricsUnprotected (line 28) | func TestHandleMetricsUnprotected(t *testing.T) {
  function TestHandleMetricsMissingToken (line 43) | func TestHandleMetricsMissingToken(t *testing.T) {
  function TestHandleMetricsInvalidToken (line 54) | func TestHandleMetricsInvalidToken(t *testing.T) {

FILE: server/servers.go
  function HandleServerList (line 21) | func HandleServerList(servers autoscaler.ServerStore) http.HandlerFunc {
  function HandleServerFind (line 38) | func HandleServerFind(servers autoscaler.ServerStore) http.HandlerFunc {
  function HandleServerDelete (line 57) | func HandleServerDelete(
  function HandleServerCreate (line 121) | func HandleServerCreate(

FILE: server/servers_test.go
  function TestHandleServerList (line 22) | func TestHandleServerList(t *testing.T) {
  function TestHandleServerListErr (line 51) | func TestHandleServerListErr(t *testing.T) {
  function TestHandleServerFind (line 75) | func TestHandleServerFind(t *testing.T) {
  function TestHandleServerFindErr (line 102) | func TestHandleServerFindErr(t *testing.T) {
  function TestHandleServerCreate (line 128) | func TestHandleServerCreate(t *testing.T) {
  function TestHandleServerCreateFailure (line 145) | func TestHandleServerCreateFailure(t *testing.T) {
  function TestHandleServerDelete (line 170) | func TestHandleServerDelete(t *testing.T) {
  function TestHandleServerDeleteNotFound (line 200) | func TestHandleServerDeleteNotFound(t *testing.T) {
  function TestHandleServerDeleteFailure (line 227) | func TestHandleServerDeleteFailure(t *testing.T) {
  function TestHandleServerDeleteErrorState (line 262) | func TestHandleServerDeleteErrorState(t *testing.T) {
  function TestHandleServerForceDeleteErrorState (line 291) | func TestHandleServerForceDeleteErrorState(t *testing.T) {

FILE: server/varz.go
  type varz (line 13) | type varz struct
  function HandleVarz (line 19) | func HandleVarz(engine autoscaler.Engine) http.HandlerFunc {

FILE: server/varz_test.go
  function TestHandleVarz (line 19) | func TestHandleVarz(t *testing.T) {

FILE: server/version.go
  type versionInfo (line 13) | type versionInfo struct
  function HandleVersion (line 21) | func HandleVersion(source, version, commit string) http.HandlerFunc {

FILE: server/version_test.go
  function TestHandleVersion (line 17) | func TestHandleVersion(t *testing.T) {

FILE: server/web/handler.go
  function HandleServers (line 18) | func HandleServers(servers autoscaler.ServerStore) http.HandlerFunc {
  function HandleLogging (line 36) | func HandleLogging(t *history.Hook) http.HandlerFunc {

FILE: server/web/nocache.go
  function nocache (line 24) | func nocache(w http.ResponseWriter) {

FILE: server/web/render.go
  function render (line 14) | func render(w http.ResponseWriter, t string, v interface{}) {

FILE: server/web/static/files/timeago.js
  function n (line 1) | function n(s,n){if(0===n)return["just now","right now"];var e=a[~~(n/2)]...
  function e (line 1) | function e(s,n){if(0===n)return["刚刚","片刻后"];var e=t[~~(n/2)];return[s+" ...
  function u (line 1) | function u(s,n){i[s]=n}
  function r (line 1) | function r(s){return i[s]||i.en_US}
  function m (line 1) | function m(s){return s instanceof Date?s:!isNaN(s)||/^\d+$/.test(s)?new ...
  function d (line 1) | function d(s,n){for(var e=s<0?1:0,a=s=Math.abs(s),t=0;s>=o[t]&&t<o.lengt...
  function c (line 1) | function c(s,n){return(+(n=n?m(n):new Date)-+m(s))/1e3}
  function h (line 1) | function h(s){return parseInt(s.getAttribute(l))}
  function p (line 1) | function p(s,n,e,a){f(h(s));var t=a.relativeDate,u=a.minInterval,r=c(n,t...
  function v (line 1) | function v(s,n,e,a,t){var u=t%10,r=a;return 1===t?r=s:1==u&&20<t?r=n:1<u...
  function M (line 1) | function M(s){var n=["۰","۱","۲","۳","۴","۵","۶","۷","۸","۹"];return s.t...
  function T (line 1) | function T(s,n,e,a,t){var u=t%10,r=a;return 1===t?r=s:1==u&&20<t?r=n:1<u...
  function J (line 1) | function J(s,n,e,a,t){var u=t%10,r=t%100;return 1==t?s:1==u&&11!=r?n:2<=...
  function Z (line 1) | function Z(s,n,e,a,t){var u=t%10,r=a;return 1===t?r=s:1==u&&20<t?r=n:1<u...

FILE: server/web/static/static_gen.go
  type fileSystem (line 11) | type fileSystem struct
    method Open (line 15) | func (fs *fileSystem) Open(name string) (http.File, error) {
  type file (line 29) | type file struct
  type fileInfo (line 34) | type fileInfo struct
    method Name (line 44) | func (f *fileInfo) Name() string {
    method Size (line 48) | func (f *fileInfo) Size() int64 {
    method Mode (line 52) | func (f *fileInfo) Mode() os.FileMode {
    method ModTime (line 56) | func (f *fileInfo) ModTime() time.Time {
    method IsDir (line 60) | func (f *fileInfo) IsDir() bool {
    method Readdir (line 64) | func (f *fileInfo) Readdir(count int) ([]os.FileInfo, error) {
    method Sys (line 68) | func (f *fileInfo) Sys() interface{} {
  function newHTTPFile (line 72) | func newHTTPFile(file file, isDir bool) *httpFile {
  type httpFile (line 80) | type httpFile struct
    method Read (line 87) | func (f *httpFile) Read(p []byte) (n int, err error) {
    method Seek (line 91) | func (f *httpFile) Seek(offset int64, whence int) (ret int64, err erro...
    method Stat (line 95) | func (f *httpFile) Stat() (os.FileInfo, error) {
    method IsDir (line 99) | func (f *httpFile) IsDir() bool {
    method Readdir (line 103) | func (f *httpFile) Readdir(count int) ([]os.FileInfo, error) {
    method Close (line 107) | func (f *httpFile) Close() error {
  function New (line 112) | func New() http.FileSystem {
  function Lookup (line 119) | func Lookup(path string) ([]byte, error) {
  function MustLookup (line 129) | func MustLookup(path string) []byte {

FILE: server/web/template/server.go
  function main (line 19) | func main() {

FILE: server/web/template/template_gen.go
  function init (line 22) | func init() {

FILE: server/writer.go
  function init (line 18) | func init() {
  type Error (line 39) | type Error struct
  function writeErrorCode (line 44) | func writeErrorCode(w http.ResponseWriter, err error, status int) {
  function writeError (line 50) | func writeError(w http.ResponseWriter, err error) {
  function writeNotFound (line 56) | func writeNotFound(w http.ResponseWriter, err error) {
  function writeUnauthorized (line 62) | func writeUnauthorized(w http.ResponseWriter, err error) {
  function writeForbidden (line 68) | func writeForbidden(w http.ResponseWriter, err error) {
  function writeBadRequest (line 74) | func writeBadRequest(w http.ResponseWriter, err error) {
  function writeJSON (line 80) | func writeJSON(w http.ResponseWriter, v interface{}, status int) {

FILE: server/writer_test.go
  function TestWriteError (line 15) | func TestWriteError(t *testing.T) {
  function TestWriteErrorCode (line 32) | func TestWriteErrorCode(t *testing.T) {
  function TestWriteNotFound (line 49) | func TestWriteNotFound(t *testing.T) {
  function TestWriteUnauthorized (line 66) | func TestWriteUnauthorized(t *testing.T) {
  function TestWriteForbidden (line 83) | func TestWriteForbidden(t *testing.T) {
  function TestWriteBadRequest (line 100) | func TestWriteBadRequest(t *testing.T) {
  function TestWriteJSON (line 117) | func TestWriteJSON(t *testing.T) {

FILE: slack/slack.go
  function New (line 23) | func New(config config.Config, base autoscaler.ServerStore) autoscaler.S...
  type notifier (line 33) | type notifier struct
    method Update (line 42) | func (n *notifier) Update(ctx context.Context, server *autoscaler.Serv...
    method notifyCreate (line 55) | func (n *notifier) notifyCreate(server *autoscaler.Server) error {
    method notifyDestroy (line 84) | func (n *notifier) notifyDestroy(server *autoscaler.Server) error {
    method notifyError (line 118) | func (n *notifier) notifyError(server *autoscaler.Server) error {
  function humanizeTime (line 142) | func humanizeTime(unix int64) string {

FILE: slack/slack_test.go
  function TestHumanizeTime (line 24) | func TestHumanizeTime(t *testing.T) {
  function TestUpdateRunning (line 32) | func TestUpdateRunning(t *testing.T) {
  function TestUpdateStopped (line 68) | func TestUpdateStopped(t *testing.T) {
  function TestUpdateError (line 104) | func TestUpdateError(t *testing.T) {
  function TestIntegration (line 186) | func TestIntegration(t *testing.T) {

FILE: store/db.go
  function Connect (line 20) | func Connect(driver, datasource string, maxconn int, maxlifetime time.Du...
  function Must (line 46) | func Must(db *sqlx.DB, err error) *sqlx.DB {
  function pingDatabase (line 56) | func pingDatabase(db *sqlx.DB) (err error) {
  function setupDatabase (line 69) | func setupDatabase(db *sqlx.DB) error {

FILE: store/db_test.go
  function connect (line 19) | func connect() (*sqlx.DB, error) {
  function locker (line 32) | func locker() sync.Locker {

FILE: store/lock.go
  function NewLocker (line 11) | func NewLocker(driver string) sync.Locker {
  type noopLocker (line 20) | type noopLocker struct
    method Lock (line 22) | func (*noopLocker) Lock()   {}
    method Unlock (line 23) | func (*noopLocker) Unlock() {}

FILE: store/migrate/migrate.go
  function Migrate (line 12) | func Migrate(db *sqlx.DB) error {

FILE: store/migrate/mysql/ddl_gen.go
  function Migrate (line 27) | func Migrate(db *sql.DB) error {
  function createTable (line 52) | func createTable(db *sql.DB) error {
  function insertMigration (line 57) | func insertMigration(db *sql.DB, name string) error {
  function selectCompleted (line 62) | func selectCompleted(db *sql.DB) (map[string]struct{}, error) {

FILE: store/migrate/mysql/files/001_create_table_servers.sql
  type servers (line 3) | CREATE TABLE servers (
  type ix_servers_id (line 28) | CREATE INDEX ix_servers_id ON servers (server_id)
  type ix_servers_state (line 32) | CREATE INDEX ix_servers_state ON servers (server_state)

FILE: store/migrate/postgres/ddl_gen.go
  function Migrate (line 27) | func Migrate(db *sql.DB) error {
  function createTable (line 52) | func createTable(db *sql.DB) error {
  function insertMigration (line 57) | func insertMigration(db *sql.DB, name string) error {
  function selectCompleted (line 62) | func selectCompleted(db *sql.DB) (map[string]struct{}, error) {

FILE: store/migrate/postgres/files/001_create_table_servers.sql
  type servers (line 3) | CREATE TABLE servers (
  type ix_servers_id (line 28) | CREATE INDEX ix_servers_id ON servers (server_id)
  type ix_servers_state (line 32) | CREATE INDEX ix_servers_state ON servers (server_state)

FILE: store/migrate/sqlite/ddl_gen.go
  function Migrate (line 27) | func Migrate(db *sql.DB) error {
  function createTable (line 52) | func createTable(db *sql.DB) error {
  function insertMigration (line 57) | func insertMigration(db *sql.DB, name string) error {
  function selectCompleted (line 62) | func selectCompleted(db *sql.DB) (map[string]struct{}, error) {

FILE: store/migrate/sqlite/files/001_create_table_servers.sql
  type servers (line 3) | CREATE TABLE IF NOT EXISTS servers (
  type ix_servers_id (line 28) | CREATE INDEX IF NOT EXISTS ix_servers_id ON servers (server_id)
  type ix_servers_state (line 32) | CREATE INDEX IF NOT EXISTS ix_servers_state ON servers (server_state)

FILE: store/servers.go
  function NewServerStore (line 20) | func NewServerStore(db *sqlx.DB, mu sync.Locker) autoscaler.ServerStore {
  type serverStore (line 24) | type serverStore struct
    method Find (line 29) | func (s *serverStore) Find(_ context.Context, name string) (*autoscale...
    method List (line 42) | func (s *serverStore) List(_ context.Context) ([]*autoscaler.Server, e...
    method ListState (line 51) | func (s *serverStore) ListState(_ context.Context, state autoscaler.Se...
    method Create (line 67) | func (s *serverStore) Create(_ context.Context, server *autoscaler.Ser...
    method create (line 82) | func (s *serverStore) create(server *autoscaler.Server) error {
    method Update (line 96) | func (s *serverStore) Update(_ context.Context, server *autoscaler.Ser...
    method update (line 111) | func (s *serverStore) update(server *autoscaler.Server) error {
    method Delete (line 124) | func (s *serverStore) Delete(_ context.Context, server *autoscaler.Ser...
    method Purge (line 136) | func (s *serverStore) Purge(_ context.Context, before int64) error {
  constant serverFindStmt (line 148) | serverFindStmt = `
  constant serverListStmt (line 174) | serverListStmt = `
  constant serverListStateStmt (line 200) | serverListStateStmt = `
  constant serverInsertStmt (line 227) | serverInsertStmt = `
  constant serverUpdateStmt (line 273) | serverUpdateStmt = `
  constant serverDeleteStmt (line 296) | serverDeleteStmt = `
  constant serverPurgeStmt (line 300) | serverPurgeStmt = `

FILE: store/servers_test.go
  function TestServer (line 16) | func TestServer(t *testing.T) {
  function testServerCreate (line 35) | func testServerCreate(store *serverStore) func(t *testing.T) {
  function testServerFind (line 53) | func testServerFind(store *serverStore) func(t *testing.T) {
  function testServerList (line 64) | func testServerList(store *serverStore) func(t *testing.T) {
  function testServerListState (line 79) | func testServerListState(store *serverStore) func(t *testing.T) {
  function testServerUpdate (line 105) | func testServerUpdate(store *serverStore) func(t *testing.T) {
  function testServerDelete (line 131) | func testServerDelete(store *serverStore) func(t *testing.T) {
  function testServerPurge (line 152) | func testServerPurge(store *serverStore) func(t *testing.T) {
  function testServer (line 181) | func testServer(server *autoscaler.Server) func(t *testing.T) {

FILE: store/util.go
  function isConnReset (line 11) | func isConnReset(err error) bool {

FILE: store/util_test.go
  function TestConnectionReset (line 13) | func TestConnectionReset(t *testing.T) {
Condensed preview — 212 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (831K chars).
[
  {
    "path": ".drone.sh",
    "chars": 281,
    "preview": "#!/bin/sh\n\nset -e\nset -x\n\nCOMMIT=\"-X main.commit=${DRONE_COMMIT_SHA}\"\nVERSION=\"-X main.version=${DRONE_TAG=latest}\"\n\ngo "
  },
  {
    "path": ".drone.yml",
    "chars": 1429,
    "preview": "---\nkind: pipeline\nname: default\ntype: vm\n\npool:\n  use: ubuntu\n\nplatform:\n  os: linux\n  arch: amd64\n\nsteps:\n- name: test"
  },
  {
    "path": ".github/issue_template.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": ".github/pull_request_template.md",
    "chars": 199,
    "preview": "<!-- IMPORTANT NOTICE\n\nBy submitting a pull request, you acknowledge that your contribution\nwill be under the terms of t"
  },
  {
    "path": ".gitignore",
    "chars": 85,
    "preview": "./drone-autoscaler\nNOTES.md\nrelease\nvendor\n*.sqlite\n*.sqlite3\n*.bak\n*.out\n*.db\n*.env\n"
  },
  {
    "path": "BUILDING",
    "chars": 127,
    "preview": "1. Install go 1.11 or later\n2. Install dependencies:\n\n    go get\n\n3. Compile and test:\n\n    go install ./...\n    go test"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 7202,
    "preview": "# Changelog\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changel"
  },
  {
    "path": "COPYRIGHT",
    "chars": 123,
    "preview": "Copyright 2018 Drone.IO Inc\nUse of this software is governed by the Polyform License\nthat can be found in the LICENSE fi"
  },
  {
    "path": "Dockerfile",
    "chars": 453,
    "preview": "FROM alpine:3.20 as alpine\nRUN apk add -U --no-cache ca-certificates\n\nFROM alpine:3.20\nEXPOSE 8080 80 443\nVOLUME /data\n\n"
  },
  {
    "path": "LICENSE.md",
    "chars": 176,
    "preview": "[Polyform-Small-Business-1.0.0](https://polyformproject.org/licenses/small-business/1.0.0) OR\n[Polyform-Free-Trial-1.0.0"
  },
  {
    "path": "README.md",
    "chars": 570,
    "preview": "[![Build Status](https://cloud.drone.io/api/badges/drone/autoscaler/status.svg)](https://cloud.drone.io/drone/autoscaler"
  },
  {
    "path": "cmd/drone-autoscaler/main.go",
    "chars": 10374,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "config/config.go",
    "chars": 8743,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "config/load.go",
    "chars": 1613,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "config/load_test.go",
    "chars": 12556,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/amazon/create.go",
    "chars": 6465,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/amazon/create_test.go",
    "chars": 152,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/amazon/destroy.go",
    "chars": 3885,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/amazon/destroy_test.go",
    "chars": 2140,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/amazon/option.go",
    "chars": 3807,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/amazon/option_test.go",
    "chars": 1921,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/amazon/provider.go",
    "chars": 2035,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/amazon/provider_test.go",
    "chars": 152,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/amazon/setup.go",
    "chars": 1771,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/amazon/setup_test.go",
    "chars": 152,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/amazon/util.go",
    "chars": 2514,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/amazon/util_test.go",
    "chars": 607,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/azure/create.go",
    "chars": 151,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/azure/create_test.go",
    "chars": 151,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/azure/destroy.go",
    "chars": 151,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/azure/destroy_test.go",
    "chars": 151,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/azure/option.go",
    "chars": 151,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/azure/option_test.go",
    "chars": 151,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/azure/provider.go",
    "chars": 151,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/azure/provider_test.go",
    "chars": 151,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/digitalocean/create.go",
    "chars": 2810,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/digitalocean/create_test.go",
    "chars": 6797,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/digitalocean/destroy.go",
    "chars": 1157,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/digitalocean/destroy_test.go",
    "chars": 3648,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/digitalocean/option.go",
    "chars": 2029,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/digitalocean/option_test.go",
    "chars": 1521,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/digitalocean/provider.go",
    "chars": 1170,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/digitalocean/provider_test.go",
    "chars": 826,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/digitalocean/setup.go",
    "chars": 1607,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/digitalocean/setup_test.go",
    "chars": 3639,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/digitalocean/userdata.go",
    "chars": 1199,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/google/create.go",
    "chars": 4185,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/google/create_test.go",
    "chars": 7066,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/google/destroy.go",
    "chars": 814,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/google/destroy_test.go",
    "chars": 1646,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/google/option.go",
    "chars": 3936,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/google/option_test.go",
    "chars": 2057,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/google/provider.go",
    "chars": 3691,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/google/provider_test.go",
    "chars": 1467,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/google/setup.go",
    "chars": 1403,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/google/setup_test.go",
    "chars": 2482,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/hetznercloud/create.go",
    "chars": 1724,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/hetznercloud/create_test.go",
    "chars": 5201,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/hetznercloud/destroy.go",
    "chars": 1084,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/hetznercloud/destroy_test.go",
    "chars": 2120,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/hetznercloud/option.go",
    "chars": 1845,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/hetznercloud/option_test.go",
    "chars": 760,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/hetznercloud/provider.go",
    "chars": 887,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/hetznercloud/provider_test.go",
    "chars": 556,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/hetznercloud/setup.go",
    "chars": 1568,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/hetznercloud/setup_test.go",
    "chars": 1735,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/internal/userdata/userdata.go",
    "chars": 1926,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/internal/userdata/userdata_test.go",
    "chars": 2392,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/openstack/create.go",
    "chars": 3797,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/openstack/create_test.go",
    "chars": 8093,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/openstack/destroy.go",
    "chars": 2408,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/openstack/destroy_test.go",
    "chars": 2758,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/openstack/doc.go",
    "chars": 837,
    "preview": "/*\nPackage openstack contains a autoscaler driver for OpenStack\nConfiguration:\n\nAuthenticate with the usual OpenStack en"
  },
  {
    "path": "drivers/openstack/option.go",
    "chars": 2468,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/openstack/option_test.go",
    "chars": 1815,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/openstack/provider.go",
    "chars": 2603,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/openstack/provider_test.go",
    "chars": 506,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/openstack/setup.go",
    "chars": 1681,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/openstack/setup_test.go",
    "chars": 611,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/openstack/testdata/associateresp1.json",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "drivers/openstack/testdata/authresp1.json",
    "chars": 456,
    "preview": "{\n  \"versions\": {\n    \"values\": [\n      {\n        \"status\": \"stable\",\n        \"updated\": \"2018-10-15T00:00:00Z\",\n       "
  },
  {
    "path": "drivers/openstack/testdata/fipresp1.json",
    "chars": 169,
    "preview": "{\n  \"floating_ip\": {\n    \"instance_id\": null,\n    \"ip\": \"172.24.4.5\",\n    \"fixed_ip\": null,\n    \"id\": \"0f013e62-42b1-461"
  },
  {
    "path": "drivers/openstack/testdata/flavorlistresp1.json",
    "chars": 6226,
    "preview": "{\n  \"flavors\": [\n    {\n      \"name\": \"m1.tiny\",\n      \"links\": [\n        {\n          \"href\": \"http://ops.my.cloud/comput"
  },
  {
    "path": "drivers/openstack/testdata/imagelistresp1.json",
    "chars": 2639,
    "preview": "{\n  \"images\": [\n    {\n      \"status\": \"ACTIVE\",\n      \"updated\": \"2018-10-26T14:29:41Z\",\n      \"links\": [\n        {\n    "
  },
  {
    "path": "drivers/openstack/testdata/servercreateresp1.json",
    "chars": 2061,
    "preview": "{\n  \"server\": {\n    \"OS-EXT-STS:task_state\": null,\n    \"addresses\": {\n      \"private\": [\n        {\n          \"OS-EXT-IPS"
  },
  {
    "path": "drivers/openstack/testdata/serverstatusresp1.json",
    "chars": 2061,
    "preview": "{\n  \"server\": {\n    \"OS-EXT-STS:task_state\": null,\n    \"addresses\": {\n      \"private\": [\n        {\n          \"OS-EXT-IPS"
  },
  {
    "path": "drivers/openstack/testdata/tokenresp1.json",
    "chars": 3554,
    "preview": "{\n  \"token\": {\n    \"is_domain\": false,\n    \"methods\": [\n      \"password\"\n    ],\n    \"roles\": [\n      {\n        \"id\": \"ed"
  },
  {
    "path": "drivers/packet/create.go",
    "chars": 2349,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/packet/create_test.go",
    "chars": 3172,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/packet/destroy.go",
    "chars": 357,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/packet/destroy_test.go",
    "chars": 730,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/packet/option.go",
    "chars": 2039,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/packet/option_test.go",
    "chars": 1311,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/packet/provider.go",
    "chars": 1191,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/packet/provider_test.go",
    "chars": 783,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/packet/setup.go",
    "chars": 1764,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/packet/setup_test.go",
    "chars": 2170,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/scaleway/create.go",
    "chars": 3482,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/scaleway/create_test.go",
    "chars": 154,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/scaleway/destroy.go",
    "chars": 1499,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/scaleway/destroy_test.go",
    "chars": 154,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/scaleway/option.go",
    "chars": 2418,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/scaleway/option_test.go",
    "chars": 154,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/scaleway/provider.go",
    "chars": 1108,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/scaleway/provider_test.go",
    "chars": 154,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "drivers/scaleway/setup.go",
    "chars": 701,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "engine/alloc.go",
    "chars": 2825,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "engine/alloc_test.go",
    "chars": 3412,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "engine/calc.go",
    "chars": 1228,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "engine/calc_test.go",
    "chars": 4506,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "engine/certs/cert.go",
    "chars": 3270,
    "preview": "// Copyright Docker.IO, Inc. All rights reserved.\n// https://github.com/docker/machine\n\npackage certs\n\nimport (\n\t\"bytes\""
  },
  {
    "path": "engine/certs/cert_test.go",
    "chars": 349,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "engine/collect.go",
    "chars": 4286,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "engine/collect_test.go",
    "chars": 5810,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "engine/docker.go",
    "chars": 1348,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "engine/engine.go",
    "chars": 7195,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "engine/install.go",
    "chars": 11724,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "engine/install_test.go",
    "chars": 1629,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "engine/pinger.go",
    "chars": 2825,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "engine/pinger_test.go",
    "chars": 152,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "engine/planner.go",
    "chars": 7541,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "engine/planner_test.go",
    "chars": 17874,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "engine/reaper.go",
    "chars": 3149,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "engine/reaper_test.go",
    "chars": 152,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "engine/sort.go",
    "chars": 482,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "engine/sort_test.go",
    "chars": 621,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "engine.go",
    "chars": 593,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "go.mod",
    "chars": 4773,
    "preview": "module github.com/drone/autoscaler\n\ngo 1.22.4\n\nreplace (\n\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp "
  },
  {
    "path": "go.sum",
    "chars": 72378,
    "preview": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1"
  },
  {
    "path": "licenses/Polyform-Free-Trial.md",
    "chars": 3129,
    "preview": "# Polyform Free Trial License 1.0.0\n\n<https://polyformproject.org/licenses/free-trial/1.0.0>\n\n## Acceptance\n\nIn order to"
  },
  {
    "path": "licenses/Polyform-Small-Business.md",
    "chars": 4387,
    "preview": "# Polyform Small Business License 1.0.0\n\n<https://polyformproject.org/licenses/small-business/1.0.0>\n\n## Acceptance\n\nIn "
  },
  {
    "path": "logger/context.go",
    "chars": 898,
    "preview": "// Copyright 2019 Drone.IO Inc. All rights reserved.\n// Use of this source code is governed by the Polyform License\n// t"
  },
  {
    "path": "logger/context_test.go",
    "chars": 909,
    "preview": "// Copyright 2019 Drone.IO Inc. All rights reserved.\n// Use of this source code is governed by the Polyform License\n// t"
  },
  {
    "path": "logger/history/history.go",
    "chars": 3030,
    "preview": "// Copyright 2019 Drone.IO Inc. All rights reserved.\n// Use of this source code is governed by the Polyform License\n// t"
  },
  {
    "path": "logger/history/history_test.go",
    "chars": 3167,
    "preview": "// Copyright 2019 Drone.IO Inc. All rights reserved.\n// Use of this source code is governed by the Polyform License\n// t"
  },
  {
    "path": "logger/logger.go",
    "chars": 2202,
    "preview": "// Copyright 2019 Drone.IO Inc. All rights reserved.\n// Use of this source code is governed by the Polyform License\n// t"
  },
  {
    "path": "logger/logger_test.go",
    "chars": 483,
    "preview": "// Copyright 2019 Drone.IO Inc. All rights reserved.\n// Use of this source code is governed by the Polyform License\n// t"
  },
  {
    "path": "logger/logrus.go",
    "chars": 603,
    "preview": "// Copyright 2019 Drone.IO Inc. All rights reserved.\n// Use of this source code is governed by the Polyform License\n// t"
  },
  {
    "path": "logger/logrus_test.go",
    "chars": 626,
    "preview": "// Copyright 2019 Drone.IO Inc. All rights reserved.\n// Use of this source code is governed by the Polyform License\n// t"
  },
  {
    "path": "logger/request/request.go",
    "chars": 1109,
    "preview": "// Copyright 2019 Drone.IO Inc. All rights reserved.\n// Use of this source code is governed by the Polyform License\n// t"
  },
  {
    "path": "metrics/metrics.go",
    "chars": 5688,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "metrics/server_capacity.go",
    "chars": 775,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "metrics/server_capacity_test.go",
    "chars": 1574,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "metrics/server_count.go",
    "chars": 667,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "metrics/server_count_test.go",
    "chars": 1564,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "metrics/server_create.go",
    "chars": 1271,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "metrics/server_create_test.go",
    "chars": 2124,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "metrics/server_delete.go",
    "chars": 1232,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "metrics/server_delete_test.go",
    "chars": 2007,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "mocks/mock_docker.go",
    "chars": 78211,
    "preview": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/docker/docker/client (interfaces: APIClient)\n\n// Packag"
  },
  {
    "path": "mocks/mock_drone.go",
    "chars": 34329,
    "preview": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/drone/drone-go/drone (interfaces: Client)\n\n// Package m"
  },
  {
    "path": "mocks/mock_engine.go",
    "chars": 2356,
    "preview": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/drone/autoscaler (interfaces: Engine)\n\n// Package mocks"
  },
  {
    "path": "mocks/mock_metrics.go",
    "chars": 3877,
    "preview": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/drone/autoscaler/metrics (interfaces: Collector)\n\n// Pa"
  },
  {
    "path": "mocks/mock_provider.go",
    "chars": 2038,
    "preview": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/drone/autoscaler (interfaces: Provider)\n\n// Package moc"
  },
  {
    "path": "mocks/mock_server.go",
    "chars": 4693,
    "preview": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/drone/autoscaler (interfaces: ServerStore)\n\n// Package "
  },
  {
    "path": "mocks/mocks.go",
    "chars": 793,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "provider.go",
    "chars": 2219,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "server/auth.go",
    "chars": 2076,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "server/auth_test.go",
    "chars": 3636,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "server/engine.go",
    "chars": 706,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "server/engine_test.go",
    "chars": 1063,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "server/healthz.go",
    "chars": 523,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "server/healthz_test.go",
    "chars": 555,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "server/metrics.go",
    "chars": 880,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "server/metrics_test.go",
    "chars": 1789,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "server/servers.go",
    "chars": 3735,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "server/servers_test.go",
    "chars": 8738,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "server/varz.go",
    "chars": 560,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "server/varz_test.go",
    "chars": 1040,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "server/version.go",
    "chars": 768,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "server/version_test.go",
    "chars": 997,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "server/web/handler.go",
    "chars": 1164,
    "preview": "// Copyright 2019 Drone.IO Inc. All rights reserved.\n// Use of this source code is governed by the Polyform License\n// t"
  },
  {
    "path": "server/web/nocache.go",
    "chars": 643,
    "preview": "// Copyright 2019 Drone.IO Inc. All rights reserved.\n// Use of this source code is governed by the Polyform License\n// t"
  },
  {
    "path": "server/web/nocache_test.go",
    "chars": 171,
    "preview": "// Copyright 2019 Drone.IO Inc. All rights reserved.\n// Use of this source code is governed by the Polyform License\n// t"
  },
  {
    "path": "server/web/render.go",
    "chars": 446,
    "preview": "// Copyright 2019 Drone.IO Inc. All rights reserved.\n// Use of this source code is governed by the Polyform License\n// t"
  },
  {
    "path": "server/web/render_test.go",
    "chars": 171,
    "preview": "// Copyright 2019 Drone.IO Inc. All rights reserved.\n// Use of this source code is governed by the Polyform License\n// t"
  },
  {
    "path": "server/web/static/files/reset.css",
    "chars": 990,
    "preview": "html, body, div, span, applet, object, iframe,\nh1, h2, h3, h4, h5, h6, p, blockquote, pre,\na, abbr, acronym, address, bi"
  },
  {
    "path": "server/web/static/files/style.css",
    "chars": 11015,
    "preview": ":root {\n    --font-sans: -apple-system,BlinkMacSystemFont,\"Segoe UI\",\"Roboto\",\"Oxygen\",\"Ubuntu\",\"Cantarell\",\"Fira Sans\","
  },
  {
    "path": "server/web/static/files/timeago.js",
    "chars": 22950,
    "preview": "!function(s,n){\"object\"==typeof exports&&\"undefined\"!=typeof module?n(exports):\"function\"==typeof define&&define.amd?def"
  },
  {
    "path": "server/web/static/static.go",
    "chars": 237,
    "preview": "// Copyright 2019 Drone.IO Inc. All rights reserved.\n// Use of this source code is governed by the Polyform License\n// t"
  },
  {
    "path": "server/web/static/static_gen.go",
    "chars": 80383,
    "preview": "package static\n\nimport (\n\t\"bytes\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n)\n\ntype fileSystem struct {\n\tfiles map[string]fil"
  },
  {
    "path": "server/web/template/files/index.tmpl",
    "chars": 3834,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"UTF-8\">\n<meta http-equiv=\"refresh\" content=\"30\">\n<title>Dashboard</title>\n<"
  },
  {
    "path": "server/web/template/files/logs.tmpl",
    "chars": 2053,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"UTF-8\">\n<title>Dashboard</title>\n<link rel=\"stylesheet\" type=\"text/css\" hre"
  },
  {
    "path": "server/web/template/server.go",
    "chars": 1940,
    "preview": "// Copyright 2019 Drone.IO Inc. All rights reserved.\n// Use of this source code is governed by the Polyform License\n// t"
  },
  {
    "path": "server/web/template/template.go",
    "chars": 425,
    "preview": "// Copyright 2019 Drone.IO Inc. All rights reserved.\n// Use of this source code is governed by the Polyform License\n// t"
  },
  {
    "path": "server/web/template/template_gen.go",
    "chars": 6423,
    "preview": "package template\n\nimport \"html/template\"\n\n// list of embedded template files.\nvar files = []struct {\n\tname string\n\tdata "
  },
  {
    "path": "server/web/template/testdata/logs.json",
    "chars": 971,
    "preview": "{\n    \"Entries\": [\n        {\n            \"Level\": \"trace\",\n            \"Message\": \"this is a test trace message\",\n      "
  },
  {
    "path": "server/web/template/testdata/logs_empty.json",
    "chars": 21,
    "preview": "{\n    \"Entries\": []\n}"
  },
  {
    "path": "server/web/template/testdata/servers.json",
    "chars": 2738,
    "preview": "{\n    \"Items\": [\n        {\n            \"ID\": \"agent-123456789\",\n            \"Name\": \"i-5203422c\",\n            \"Provider\""
  },
  {
    "path": "server/web/template/testdata/servers_empty.json",
    "chars": 19,
    "preview": "{\n    \"Items\": []\n}"
  },
  {
    "path": "server/writer.go",
    "chars": 2501,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "server/writer_test.go",
    "chars": 3643,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "server.go",
    "chars": 2923,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "slack/slack.go",
    "chars": 3199,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "slack/slack_test.go",
    "chars": 4616,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "store/db.go",
    "chars": 1577,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "store/db_test.go",
    "chars": 799,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "store/lock.go",
    "chars": 500,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  },
  {
    "path": "store/migrate/migrate.go",
    "chars": 456,
    "preview": "package ddl\n\nimport (\n\t\"github.com/drone/autoscaler/store/migrate/mysql\"\n\t\"github.com/drone/autoscaler/store/migrate/pos"
  },
  {
    "path": "store/migrate/mysql/ddl.go",
    "chars": 205,
    "preview": "// Copyright 2018 Drone.IO Inc\n// Use of this source code is governed by the Polyform License\n// that can be found in th"
  }
]

// ... and 12 more files (download for full content)

About this extraction

This page contains the full source code of the drone/autoscaler GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 212 files (731.3 KB), approximately 266.4k tokens, and a symbol index with 1056 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!