Skip to content

Commit

Permalink
feat(start-stop): support for start stop mechanics
Browse files Browse the repository at this point in the history
* driver will not throw an error when you request for machine stop, it will destroy it instead
* one can pass `terraform-lifecycle-on` flag in order to support more advanced lifecycle (start/stop) with `dm_lifecycle` variable
* breaking change: note that your terraform code should support server removal when `dm_lifecycle=="stopped"` is passed
  • Loading branch information
krzysztof-miemiec committed Sep 30, 2020
1 parent 642e1bb commit 8faf72b
Show file tree
Hide file tree
Showing 10 changed files with 111 additions and 417 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ The driver accepts the following arguments:
For example: `--terraform-variable variable1=foo --terraform-variable variable2=bar`
* `--terraform-variables-from` (Optional) - An optional file containing the JSON that represents additional variables for the Terraform configuration
* `--terraform-refresh` (Optional) - A flag which, if specified, will cause the driver to refresh the configuration after applying it
* `--terraform-lifecycle-on` (Optional) - A flag which, if specified, results in performing a custom apply on start/stop instead of just destroying machine on stop.

### Terraform configuration

Expand All @@ -32,6 +33,7 @@ The driver can work with a Terraform configuration in any of the following forma
It will supply the following values to the configuration as variables (in addition to any supplied via `--terraform-variables-file`):

* `dm_client_ip` - The public IP of the client machine (useful for configuring firewall rules)
* `dm_lifecycle` - Present only if `terraform-lifecycle-on` flag is present. Desired state of Docker machine: `running` (used during `create` and `start`) and `stopped` (used when `stop` or `remove` is ran); if your provider doesn't support start/stop, you can just delete a server resource using `count` parameter in Terraform.
* `dm_machine_name` - The name of the Docker machine being created
* `dm_ssh_user` - The SSH user name to use for authentication
* `dm_ssh_port` - The SSH port to use
Expand Down
69 changes: 49 additions & 20 deletions driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,12 @@ type Driver struct {
// The path of the directory containing the imported Terraform configuration.
ConfigDir string

// Lock timeout (in time units like 30s, 1m or 2h)
LockTimeout string

// Whether to pass dm_lifecycle variable and perform a custom apply on start/stop
LifecycleOn bool

// Additional variables for the Terraform configuration
ConfigVariables terraform.ConfigVariables

Expand Down Expand Up @@ -79,7 +83,12 @@ func (driver *Driver) GetCreateFlags() []mcnflag.Flag {
Name: "terraform-refresh",
Usage: "Refresh the configuration after applying it",
},
mcnflag.BoolFlag{
Name: "terraform-lifecycle-on",
Usage: "Enable full docker-machine lifecycle; otherwise stop means destroy and start means create",
},
mcnflag.StringFlag{
EnvVar: "TERRAFORM_LOCK_TIMEOUT",
Name: "terraform-lock-timeout",
Usage: "Terraform lock timeout. Set to \"0\" to disable locking",
Value: "10m",
Expand Down Expand Up @@ -125,6 +134,7 @@ func (driver *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
driver.AdditionalVariablesInline = flags.StringSlice("terraform-variable")
driver.AdditionalVariablesFile = flags.String("terraform-variables-from")
driver.LockTimeout = flags.String("terraform-lock-timeout")
driver.LifecycleOn = flags.Bool("terraform-lifecycle-on")

driver.RefreshAfterApply = flags.Bool("terraform-refresh")

Expand Down Expand Up @@ -194,56 +204,58 @@ func (driver *Driver) PreCreateCheck() error {
driver.ConfigVariables["dm_ssh_public_key_file"] = driver.SSHKeyPath + ".pub"
driver.ConfigVariables["dm_ssh_user"] = driver.SSHUser
driver.ConfigVariables["dm_ssh_port"] = driver.SSHPort
driver.ConfigVariables["dm_lifecycle"] = "running"

err = driver.readAdditionalVariables()
if err != nil {
return err
}

err = driver.writeVariables()
if err != nil {
return err
}

return nil
}

// Create a new Docker Machine instance on CloudControl.
func (driver *Driver) Create() error {
func (driver *Driver) Apply() (outputs terraform.Outputs, err error) {
err = driver.writeVariables()
if err != nil {
return
}
log.Infof("Applying Terraform configuration...")

terraformer, err := driver.getTerraformer()
if err != nil {
return err
return
}

success, err := terraformer.Apply()
if err != nil {
return err
return
}
if !success {
return errors.New("Failed to apply Terraform configuration")
err = errors.New("Failed to apply Terraform configuration")
return
}

if driver.RefreshAfterApply {
log.Infof("Refreshing Terraform configuration state...")
err = terraformer.Refresh()
if err != nil {
return err
return
}
}

outputs, err := terraformer.Output()
outputs, err = terraformer.Output()
if err != nil {
return err
return
}
if !success {
return fmt.Errorf("Failed to obtain Terraform outputs")
err = fmt.Errorf("Failed to obtain Terraform outputs")
return
}

output, ok := outputs["dm_machine_ip"]
if !ok {
return fmt.Errorf("Configuration does not declare required output 'dm_machine_ip'")
err = fmt.Errorf("Configuration does not declare required output 'dm_machine_ip'")
return
}
driver.IPAddress = output.Value.(string)

Expand All @@ -252,8 +264,16 @@ func (driver *Driver) Create() error {
driver.SSHUser = output.Value.(string)
}

log.Infof("Deployed host has IP '%s'.", driver.IPAddress)
log.Infof("Deployed host has SSH user '%s'.", driver.SSHUser)
log.Infof("created host: ip = %s, user %s", driver.IPAddress, driver.SSHUser)
return
}

// Create a new Docker Machine instance.
func (driver *Driver) Create() error {
_, err := driver.Apply()
if err != nil {
return err
}

return nil
}
Expand All @@ -276,6 +296,12 @@ func (driver *Driver) GetURL() (string, error) {

// Remove deletes the target machine.
func (driver *Driver) Remove() error {
driver.ConfigVariables["dm_lifecycle"] = "stopped"
err := driver.writeVariables()
if err != nil {
return err
}

log.Infof("Destroying terraform configuration...")

terraformer, err := driver.getTerraformer()
Expand All @@ -296,12 +322,14 @@ func (driver *Driver) Remove() error {

// Start the target machine.
func (driver *Driver) Start() error {
return errors.New("The Terraform driver does not support Start.")
return driver.Create()
}

// Stop the target machine (gracefully).
func (driver *Driver) Stop() error {
return errors.New("The Terraform driver does not support Stop.")
driver.ConfigVariables["dm_lifecycle"] = "stopped"
_, err := driver.Apply()
return err
}

// Restart the target machine.
Expand All @@ -315,7 +343,8 @@ func (driver *Driver) Restart() error {

// Kill the target machine (hard shutdown).
func (driver *Driver) Kill() error {
return errors.New("The Terraform driver does not support Kill.")
// It's the same as destroy.
return driver.Remove()
}

// GetSSHHostname returns the hostname for SSH
Expand Down
75 changes: 39 additions & 36 deletions examples/aws/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,94 +4,97 @@
*/

# Docker Machine variables
variable "dm_client_ip" { }
variable "dm_machine_name" { }
variable "dm_ssh_user" { }
variable "dm_ssh_port" { }
variable "dm_ssh_public_key_file" { }
variable "dm_lifecycle" {}
variable "dm_client_ip" {}
variable "dm_machine_name" {}
variable "dm_ssh_user" {}
variable "dm_ssh_port" {}
variable "dm_ssh_public_key_file" {}

# Additional variables
variable "region" { }
variable "region" {}

# AWS
provider "aws" {
region = "${var.region}"
region = var.region

# Access key and secret key come from AWS_ACCESS_KEY_ID and AWS_SECRET_KEY environment variables
}

# Look up OS image (AMI)
data "aws_ami" "ubuntu" {
owners = ["099720109477"] # Canonical
most_recent = true
owners = ["099720109477"] # Canonical
most_recent = true

filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-*"]
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-*"]
}

filter {
name = "virtualization-type"
values = ["hvm"]
name = "virtualization-type"
values = ["hvm"]
}
}

# Docker host
resource "aws_instance" "docker_machine" {
ami = "${data.aws_ami.ubuntu.id}"
count = var.dm_lifecycle == "running" ? 1 : 0

ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"

key_name = "${aws_key_pair.docker_machine.key_name}"
security_groups = [ "${aws_security_group.docker_machine.name}" ]
key_name = aws_key_pair.docker_machine.key_name
security_groups = [aws_security_group.docker_machine.name]

tags {
Name = "${var.dm_machine_name}"
tags = {
Name = var.dm_machine_name
}
}

# Security group (allow ingress for SSH and Docker API)
resource "aws_security_group" "docker_machine" {
name = "dm_${replace(var.dm_machine_name, "-", "_")}"
description = "Docker Machine (allow SSH and Docker API)"
name = "dm_${replace(var.dm_machine_name, "-", "_")}"
description = "Docker Machine (allow SSH and Docker API)"

# SSH (inbound)
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["${var.dm_client_ip}/0"]
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["${var.dm_client_ip}/32"]
}

# Docker (inbound)
ingress {
from_port = 2376
to_port = 2376
protocol = "tcp"
cidr_blocks = ["${var.dm_client_ip}/0"]
from_port = 2376
to_port = 2376
protocol = "tcp"
cidr_blocks = ["${var.dm_client_ip}/32"]
}

# All traffic (outbound)
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}

# SSH key pair
resource "aws_key_pair" "docker_machine" {
key_name = "${var.dm_machine_name}@docker-machine"
public_key = "${file(var.dm_ssh_public_key_file)}"
key_name = "${var.dm_machine_name}@docker-machine"
public_key = file(var.dm_ssh_public_key_file)
}

# Outputs for Docker Machine
output "dm_machine_ip" {
value = "${aws_instance.docker_machine.public_ip}"
value = var.dm_lifecycle == "running" ? aws_instance.docker_machine[0].public_ip : ""
}
output "dm_ssh_user" {
value = "ubuntu" # The Ubuntu AMI requires you to log in as "ubuntu", not "root"
}
output "dm_ssh_port" {
value = "${var.dm_ssh_port}"
value = var.dm_ssh_port
}
41 changes: 0 additions & 41 deletions examples/ddcloud/private_ip/README.md

This file was deleted.

7 changes: 0 additions & 7 deletions examples/ddcloud/private_ip/additional-variables.json

This file was deleted.

Loading

0 comments on commit 8faf72b

Please sign in to comment.