mirror of https://github.com/docker/docs.git
677 lines
16 KiB
Go
677 lines
16 KiB
Go
package azure
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
azure "github.com/MSOpenTech/azure-sdk-for-go"
|
|
"github.com/MSOpenTech/azure-sdk-for-go/clients/vmClient"
|
|
|
|
log "github.com/Sirupsen/logrus"
|
|
"github.com/codegangsta/cli"
|
|
"github.com/docker/docker/utils"
|
|
"github.com/docker/machine/drivers"
|
|
"github.com/docker/machine/ssh"
|
|
"github.com/docker/machine/state"
|
|
)
|
|
|
|
const (
|
|
dockerConfigDir = "/etc/docker"
|
|
)
|
|
|
|
type Driver struct {
|
|
MachineName string
|
|
SubscriptionID string
|
|
SubscriptionCert string
|
|
PublishSettingsFilePath string
|
|
Name string
|
|
Location string
|
|
Size string
|
|
UserName string
|
|
UserPassword string
|
|
Image string
|
|
SSHPort int
|
|
DockerPort int
|
|
storePath string
|
|
}
|
|
|
|
func init() {
|
|
drivers.Register("azure", &drivers.RegisteredDriver{
|
|
New: NewDriver,
|
|
GetCreateFlags: GetCreateFlags,
|
|
})
|
|
}
|
|
|
|
// GetCreateFlags registers the flags this driver adds to
|
|
// "docker hosts create"
|
|
func GetCreateFlags() []cli.Flag {
|
|
return []cli.Flag{
|
|
cli.IntFlag{
|
|
Name: "azure-docker-port",
|
|
Usage: "Azure Docker port",
|
|
Value: 2376,
|
|
},
|
|
cli.StringFlag{
|
|
EnvVar: "AZURE_IMAGE",
|
|
Name: "azure-image",
|
|
Usage: "Azure image name. Default is Ubuntu 14.04 LTS x64",
|
|
},
|
|
cli.StringFlag{
|
|
EnvVar: "AZURE_LOCATION",
|
|
Name: "azure-location",
|
|
Usage: "Azure location",
|
|
Value: "West US",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "azure-name",
|
|
Usage: "Azure cloud service name",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "azure-password",
|
|
Usage: "Azure user password",
|
|
},
|
|
cli.StringFlag{
|
|
EnvVar: "AZURE_PUBLISH_SETTINGS_FILE",
|
|
Name: "azure-publish-settings-file",
|
|
Usage: "Azure publish settings file",
|
|
},
|
|
cli.StringFlag{
|
|
EnvVar: "AZURE_SIZE",
|
|
Name: "azure-size",
|
|
Usage: "Azure size",
|
|
Value: "Small",
|
|
},
|
|
cli.IntFlag{
|
|
Name: "azure-ssh-port",
|
|
Usage: "Azure SSH port",
|
|
Value: 22,
|
|
},
|
|
|
|
cli.StringFlag{
|
|
EnvVar: "AZURE_SUBSCRIPTION_CERT",
|
|
Name: "azure-subscription-cert",
|
|
Usage: "Azure subscription cert",
|
|
},
|
|
cli.StringFlag{
|
|
EnvVar: "AZURE_SUBSCRIPTION_ID",
|
|
Name: "azure-subscription-id",
|
|
Usage: "Azure subscription ID",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "azure-username",
|
|
Usage: "Azure username",
|
|
Value: "ubuntu",
|
|
},
|
|
}
|
|
}
|
|
|
|
func NewDriver(machineName string, storePath string) (drivers.Driver, error) {
|
|
driver := &Driver{MachineName: machineName, storePath: storePath}
|
|
return driver, nil
|
|
}
|
|
|
|
func (driver *Driver) DriverName() string {
|
|
return "azure"
|
|
}
|
|
|
|
func (driver *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
|
|
driver.SubscriptionID = flags.String("azure-subscription-id")
|
|
|
|
cert := flags.String("azure-subscription-cert")
|
|
publishSettings := flags.String("azure-publish-settings-file")
|
|
image := flags.String("azure-image")
|
|
username := flags.String("azure-username")
|
|
|
|
if cert != "" {
|
|
if _, err := os.Stat(cert); os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
driver.SubscriptionCert = cert
|
|
}
|
|
|
|
if publishSettings != "" {
|
|
if _, err := os.Stat(publishSettings); os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
driver.PublishSettingsFilePath = publishSettings
|
|
}
|
|
|
|
if (driver.SubscriptionID == "" || driver.SubscriptionCert == "") && driver.PublishSettingsFilePath == "" {
|
|
return fmt.Errorf("Please specify azure subscription params using options: --azure-subscription-id and --azure-subscription-cert or --azure-publish-settings-file")
|
|
}
|
|
|
|
if image == "" {
|
|
driver.Image = "b39f27a8b8c64d52b05eac6a62ebad85__Ubuntu-14_04_1-LTS-amd64-server-20140927-en-us-30GB"
|
|
} else {
|
|
driver.Image = image
|
|
}
|
|
|
|
driver.Location = flags.String("azure-location")
|
|
driver.Size = flags.String("azure-size")
|
|
|
|
if strings.ToLower(username) == "docker" {
|
|
return fmt.Errorf("'docker' is not valid user name for docker host. Please specify another user name")
|
|
}
|
|
|
|
driver.UserName = username
|
|
driver.UserPassword = flags.String("azure-password")
|
|
driver.DockerPort = flags.Int("azure-docker-port")
|
|
driver.SSHPort = flags.Int("azure-ssh-port")
|
|
|
|
return nil
|
|
}
|
|
|
|
func (driver *Driver) Create() error {
|
|
if err := driver.setUserSubscription(); err != nil {
|
|
return err
|
|
}
|
|
|
|
t := time.Now().Format("20060102150405")
|
|
name := fmt.Sprintf("%s-%s", driver.MachineName, t)
|
|
|
|
log.Infof("Creating Azure host...")
|
|
vmConfig, err := vmClient.CreateAzureVMConfiguration(name, driver.Size, driver.Image, driver.Location)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := driver.generateCertForAzure(); err != nil {
|
|
return err
|
|
}
|
|
|
|
vmConfig, err = vmClient.AddAzureLinuxProvisioningConfig(vmConfig, driver.UserName, driver.UserPassword, driver.azureCertPath(), driver.SSHPort)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := driver.addDockerEndpoint(vmConfig); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := vmClient.CreateAzureVM(vmConfig, name, driver.Location); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Infof("Waiting for SSH...")
|
|
|
|
if err := ssh.WaitForTCP(fmt.Sprintf("%s:%d", driver.getHostname(), driver.SSHPort)); err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd, err := driver.GetSSHCommand("if [ ! -e /usr/bin/docker ]; then curl get.docker.io | sudo sh -; fi")
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd, err = driver.GetSSHCommand("sudo stop docker")
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Debugf("HACK: Downloading version of Docker with identity auth...")
|
|
|
|
cmd, err = driver.GetSSHCommand("sudo curl -sS -o /usr/bin/docker https://bfirsh.s3.amazonaws.com/docker/docker-1.3.1-dev-identity-auth")
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Debugf("Updating /etc/default/docker to use identity auth...")
|
|
|
|
cmd, err = driver.GetSSHCommand("echo 'export DOCKER_OPTS=\"--auth=identity --host=tcp://0.0.0.0:2376 --auth-authorized-dir=/root/.docker/authorized-keys.d\"' | sudo tee -a /etc/default/docker")
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Debugf("Adding key to authorized-keys.d...")
|
|
|
|
// HACK: temporarily chown to ssh user for providers using non-root accounts
|
|
cmd, err = driver.GetSSHCommand(fmt.Sprintf("sudo mkdir -p /root/.docker && sudo chown -R %s /root/.docker", driver.UserName))
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
f, err := os.Open(filepath.Join(os.Getenv("HOME"), ".docker/public-key.json"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
cmdString := fmt.Sprintf("sudo mkdir -p %q && sudo tee -a %q", "/root/.docker/authorized-keys.d", "/root/.docker/authorized-keys.d/docker-host.json")
|
|
cmd, err = driver.GetSSHCommand(cmdString)
|
|
cmd.Stdin = f
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// HACK: change back ownership
|
|
cmd, err = driver.GetSSHCommand("sudo mkdir -p /root/.docker && sudo chown -R root /root/.docker")
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd, err = driver.GetSSHCommand("sudo start docker")
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (driver *Driver) hackForIdentityAuth() error {
|
|
|
|
log.Debugf("HACK: Downloading version of Docker with identity auth...")
|
|
|
|
/* We need to add retries to every SSH call we make, because Azure has some weird networking bug:
|
|
sometimes when it comes to communication between VMs or with Azure itself, Azure API throws an error.
|
|
So when we are running remote commands via SSH, sometimes they fail for no reason.
|
|
This issue is fixed by repeating SSH calls few times before throwing an error.
|
|
*/
|
|
numberOfRetries := 3
|
|
if err := driver.runSSHCommand("sudo stop docker", numberOfRetries); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := driver.runSSHCommand("sudo bash -c \"curl -sS https://ehazlett.s3.amazonaws.com/public/docker/linux/docker-1.4.1-136b351e-identity > /usr/bin/docker\"", numberOfRetries); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Debugf("Updating /etc/default/docker to use identity auth...")
|
|
|
|
cmdString := fmt.Sprintf(`sudo bash -c 'cat <<EOF > /etc/default/docker
|
|
export DOCKER_OPTS="--auth=identity --host=tcp://0.0.0.0:%v"
|
|
EOF'`, driver.DockerPort)
|
|
if err := driver.runSSHCommand(cmdString, numberOfRetries); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Debugf("Adding key to authorized-keys.d...")
|
|
|
|
if err := driver.addPublicKeyToAuthorizedHosts("/tmp/.docker/authorized-keys.d", numberOfRetries); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := driver.runSSHCommand("sudo cp -a /tmp/.docker/ /", numberOfRetries); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := driver.runSSHCommand("rm -r /tmp/.docker/", numberOfRetries); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := driver.runSSHCommand("sudo start docker", numberOfRetries); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (driver *Driver) addPublicKeyToAuthorizedHosts(authorizedKeysPath string, retries int) error {
|
|
if err := drivers.AddPublicKeyToAuthorizedHosts(driver, authorizedKeysPath); err != nil {
|
|
if err.Error() == "exit status 255" {
|
|
if retries == 0 {
|
|
return err
|
|
}
|
|
return driver.addPublicKeyToAuthorizedHosts(authorizedKeysPath, retries-1)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (driver *Driver) runSSHCommand(command string, retries int) error {
|
|
cmd, err := driver.GetSSHCommand(command)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := cmd.Run(); err != nil {
|
|
if err.Error() == "exit status 255" {
|
|
if retries == 0 {
|
|
return err
|
|
}
|
|
return driver.runSSHCommand(command, retries-1)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (driver *Driver) GetURL() (string, error) {
|
|
url := fmt.Sprintf("tcp://%s:%v", driver.getHostname(), driver.DockerPort)
|
|
return url, nil
|
|
}
|
|
|
|
func (driver *Driver) GetIP() (string, error) {
|
|
return driver.getHostname(), nil
|
|
}
|
|
|
|
func (driver *Driver) GetState() (state.State, error) {
|
|
err := driver.setUserSubscription()
|
|
if err != nil {
|
|
return state.Error, err
|
|
}
|
|
|
|
dockerVM, err := vmClient.GetVMDeployment(driver.MachineName, driver.MachineName)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "Code: ResourceNotFound") {
|
|
return state.Error, fmt.Errorf("Azure host was not found. Please check your Azure subscription.")
|
|
}
|
|
|
|
return state.Error, err
|
|
}
|
|
|
|
vmState := dockerVM.RoleInstanceList.RoleInstance[0].PowerState
|
|
switch vmState {
|
|
case "Started":
|
|
return state.Running, nil
|
|
case "Starting":
|
|
return state.Starting, nil
|
|
case "Stopped":
|
|
return state.Stopped, nil
|
|
}
|
|
|
|
return state.None, nil
|
|
}
|
|
|
|
func (driver *Driver) Start() error {
|
|
err := driver.setUserSubscription()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
vmState, err := driver.GetState()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if vmState == state.Running || vmState == state.Starting {
|
|
log.Infof("Host is already running or starting")
|
|
return nil
|
|
}
|
|
|
|
log.Debugf("starting %s", driver.MachineName)
|
|
|
|
err = vmClient.StartRole(driver.MachineName, driver.MachineName, driver.MachineName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = driver.waitForSSH()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = driver.waitForDocker()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (driver *Driver) Stop() error {
|
|
err := driver.setUserSubscription()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
vmState, err := driver.GetState()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if vmState == state.Stopped {
|
|
log.Infof("Host is already stopped")
|
|
return nil
|
|
}
|
|
|
|
log.Debugf("stopping %s", driver.MachineName)
|
|
|
|
err = vmClient.ShutdownRole(driver.MachineName, driver.MachineName, driver.MachineName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (driver *Driver) Remove() error {
|
|
err := driver.setUserSubscription()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
available, _, err := vmClient.CheckHostedServiceNameAvailability(driver.MachineName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if available {
|
|
return nil
|
|
}
|
|
|
|
log.Debugf("removing %s", driver.MachineName)
|
|
|
|
err = vmClient.DeleteHostedService(driver.MachineName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (driver *Driver) Restart() error {
|
|
err := driver.setUserSubscription()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
vmState, err := driver.GetState()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if vmState == state.Stopped {
|
|
return fmt.Errorf("Host is already stopped, use start command to run it")
|
|
}
|
|
|
|
log.Debugf("restarting %s", driver.MachineName)
|
|
|
|
err = vmClient.RestartRole(driver.MachineName, driver.MachineName, driver.MachineName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = driver.waitForSSH()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = driver.waitForDocker()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (driver *Driver) Kill() error {
|
|
err := driver.setUserSubscription()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
vmState, err := driver.GetState()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if vmState == state.Stopped {
|
|
log.Infof("Host is already stopped")
|
|
return nil
|
|
}
|
|
|
|
log.Debugf("killing %s", driver.MachineName)
|
|
|
|
err = vmClient.ShutdownRole(driver.MachineName, driver.MachineName, driver.MachineName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) StartDocker() error {
|
|
log.Debug("Starting Docker...")
|
|
|
|
cmd, err := d.GetSSHCommand("sudo service docker start")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) StopDocker() error {
|
|
log.Debug("Stopping Docker...")
|
|
|
|
cmd, err := d.GetSSHCommand("sudo service docker stop")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) GetDockerConfigDir() string {
|
|
return dockerConfigDir
|
|
}
|
|
|
|
func (driver *Driver) GetSSHCommand(args ...string) (*exec.Cmd, error) {
|
|
err := driver.setUserSubscription()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
vmState, err := driver.GetState()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if vmState == state.Stopped {
|
|
return nil, fmt.Errorf("Azure host is stopped. Please start it before using ssh command.")
|
|
}
|
|
|
|
return ssh.GetSSHCommand(driver.getHostname(), driver.SSHPort, driver.UserName, driver.sshKeyPath(), args...), nil
|
|
}
|
|
|
|
func (driver *Driver) Upgrade() error {
|
|
return nil
|
|
}
|
|
|
|
func generateVMName() string {
|
|
randomID := utils.TruncateID(utils.GenerateRandomID())
|
|
return fmt.Sprintf("docker-host-%s", randomID)
|
|
}
|
|
|
|
func (driver *Driver) setUserSubscription() error {
|
|
if len(driver.PublishSettingsFilePath) != 0 {
|
|
err := azure.ImportPublishSettingsFile(driver.PublishSettingsFilePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
err := azure.ImportPublishSettings(driver.SubscriptionID, driver.SubscriptionCert)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (driver *Driver) addDockerEndpoint(vmConfig *vmClient.Role) error {
|
|
configSets := vmConfig.ConfigurationSets.ConfigurationSet
|
|
if len(configSets) == 0 {
|
|
return fmt.Errorf("no configuration set")
|
|
|
|
}
|
|
for i := 0; i < len(configSets); i++ {
|
|
if configSets[i].ConfigurationSetType != "NetworkConfiguration" {
|
|
continue
|
|
}
|
|
ep := vmClient.InputEndpoint{}
|
|
ep.Name = "docker"
|
|
ep.Protocol = "tcp"
|
|
ep.Port = driver.DockerPort
|
|
ep.LocalPort = driver.DockerPort
|
|
configSets[i].InputEndpoints.InputEndpoint = append(configSets[i].InputEndpoints.InputEndpoint, ep)
|
|
log.Debugf("added Docker endpoint to configuration")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (driver *Driver) waitForSSH() error {
|
|
log.Infof("Waiting for SSH...")
|
|
err := ssh.WaitForTCP(fmt.Sprintf("%s:%v", driver.getHostname(), driver.SSHPort))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (driver *Driver) waitForDocker() error {
|
|
log.Infof("Waiting for docker daemon on host to be available...")
|
|
maxRepeats := 48
|
|
url := fmt.Sprintf("%s:%v", driver.getHostname(), driver.DockerPort)
|
|
success := waitForDockerEndpoint(url, maxRepeats)
|
|
if !success {
|
|
return fmt.Errorf("Can not run docker daemon on remote machine. Please try again.")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func waitForDockerEndpoint(url string, maxRepeats int) bool {
|
|
counter := 0
|
|
for {
|
|
conn, err := net.Dial("tcp", url)
|
|
if err != nil {
|
|
time.Sleep(10 * time.Second)
|
|
counter++
|
|
if counter == maxRepeats {
|
|
return false
|
|
}
|
|
continue
|
|
}
|
|
defer conn.Close()
|
|
break
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (driver *Driver) generateCertForAzure() error {
|
|
if err := ssh.GenerateSSHKey(driver.sshKeyPath()); err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd := exec.Command("openssl", "req", "-x509", "-key", driver.sshKeyPath(), "-nodes", "-days", "365", "-newkey", "rsa:2048", "-out", driver.azureCertPath(), "-subj", "/C=AU/ST=Some-State/O=InternetWidgitsPtyLtd/CN=\\*")
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (driver *Driver) sshKeyPath() string {
|
|
return filepath.Join(driver.storePath, "id_rsa")
|
|
}
|
|
|
|
func (driver *Driver) publicSSHKeyPath() string {
|
|
return driver.sshKeyPath() + ".pub"
|
|
}
|
|
|
|
func (driver *Driver) azureCertPath() string {
|
|
return filepath.Join(driver.storePath, "azure_cert.pem")
|
|
}
|
|
|
|
func (driver *Driver) getHostname() string {
|
|
return driver.MachineName + ".cloudapp.net"
|
|
}
|