package azure import ( "errors" "fmt" "os" "os/exec" "strings" azure "github.com/MSOpenTech/azure-sdk-for-go" "github.com/MSOpenTech/azure-sdk-for-go/clients/vmClient" "github.com/docker/machine/libmachine/drivers" "github.com/docker/machine/libmachine/log" "github.com/docker/machine/libmachine/mcnflag" "github.com/docker/machine/libmachine/mcnutils" "github.com/docker/machine/libmachine/ssh" "github.com/docker/machine/libmachine/state" ) type Driver struct { *drivers.BaseDriver SubscriptionID string SubscriptionCert string PublishSettingsFilePath string Location string Size string UserPassword string Image string DockerPort int DockerSwarmMasterPort int } const ( defaultDockerPort = 2376 defaultSwarmMasterPort = 3376 defaultLocation = "West US" defaultSize = "Small" defaultSSHPort = 22 defaultSSHUsername = "ubuntu" ) // GetCreateFlags registers the flags this d adds to // "docker hosts create" func (d *Driver) GetCreateFlags() []mcnflag.Flag { return []mcnflag.Flag{ mcnflag.IntFlag{ Name: "azure-docker-port", Usage: "Azure Docker port", Value: defaultDockerPort, }, mcnflag.IntFlag{ Name: "azure-docker-swarm-master-port", Usage: "Azure Docker Swarm master port", Value: defaultSwarmMasterPort, }, mcnflag.StringFlag{ EnvVar: "AZURE_IMAGE", Name: "azure-image", Usage: "Azure image name. Default is Ubuntu 14.04 LTS x64", }, mcnflag.StringFlag{ EnvVar: "AZURE_LOCATION", Name: "azure-location", Usage: "Azure location", Value: defaultLocation, }, mcnflag.StringFlag{ Name: "azure-password", Usage: "Azure user password", }, mcnflag.StringFlag{ EnvVar: "AZURE_PUBLISH_SETTINGS_FILE", Name: "azure-publish-settings-file", Usage: "Azure publish settings file", }, mcnflag.StringFlag{ EnvVar: "AZURE_SIZE", Name: "azure-size", Usage: "Azure size", Value: defaultSize, }, mcnflag.IntFlag{ Name: "azure-ssh-port", Usage: "Azure SSH port", Value: defaultSSHPort, }, mcnflag.StringFlag{ EnvVar: "AZURE_SUBSCRIPTION_CERT", Name: "azure-subscription-cert", Usage: "Azure subscription cert", }, mcnflag.StringFlag{ EnvVar: "AZURE_SUBSCRIPTION_ID", Name: "azure-subscription-id", Usage: "Azure subscription ID", }, mcnflag.StringFlag{ Name: "azure-username", Usage: "Azure username", Value: defaultSSHUsername, }, } } func NewDriver(hostName, storePath string) drivers.Driver { d := &Driver{ DockerPort: defaultDockerPort, DockerSwarmMasterPort: defaultSwarmMasterPort, Location: defaultLocation, Size: defaultSize, BaseDriver: &drivers.BaseDriver{ SSHPort: defaultSSHPort, SSHUser: defaultSSHUsername, MachineName: hostName, StorePath: storePath, }, } return d } func (d *Driver) GetSSHHostname() (string, error) { return d.GetIP() } func (d *Driver) GetSSHUsername() string { if d.SSHUser == "" { d.SSHUser = "ubuntu" } return d.SSHUser } func (d *Driver) DriverName() string { return "azure" } func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error { d.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 } d.SubscriptionCert = cert } if publishSettings != "" { if _, err := os.Stat(publishSettings); os.IsNotExist(err) { return err } d.PublishSettingsFilePath = publishSettings } if (d.SubscriptionID == "" || d.SubscriptionCert == "") && d.PublishSettingsFilePath == "" { return errors.New("Please specify azure subscription params using options: --azure-subscription-id and --azure-subscription-cert or --azure-publish-settings-file") } if image == "" { d.Image = "b39f27a8b8c64d52b05eac6a62ebad85__Ubuntu-14_04_1-LTS-amd64-server-20140927-en-us-30GB" } else { d.Image = image } d.Location = flags.String("azure-location") d.Size = flags.String("azure-size") if strings.ToLower(username) == "docker" { return errors.New("'docker' is not valid user name for docker host. Please specify another user name") } d.SSHUser = username d.UserPassword = flags.String("azure-password") d.DockerPort = flags.Int("azure-docker-port") d.DockerSwarmMasterPort = flags.Int("azure-docker-swarm-master-port") d.SSHPort = flags.Int("azure-ssh-port") d.SwarmMaster = flags.Bool("swarm-master") d.SwarmHost = flags.String("swarm-host") d.SwarmDiscovery = flags.String("swarm-discovery") return nil } func (d *Driver) PreCreateCheck() error { if err := d.setUserSubscription(); err != nil { return err } // check azure DNS to make sure name is available available, response, err := vmClient.CheckHostedServiceNameAvailability(d.MachineName) if err != nil { return err } if !available { return errors.New(response) } return nil } func (d *Driver) Create() error { if err := d.setUserSubscription(); err != nil { return err } log.Info("Creating Azure machine...") vmConfig, err := vmClient.CreateAzureVMConfiguration(d.MachineName, d.Size, d.Image, d.Location) if err != nil { return err } log.Debug("Generating certificate for Azure...") if err := d.generateCertForAzure(); err != nil { return err } log.Debug("Adding Linux provisioning...") vmConfig, err = vmClient.AddAzureLinuxProvisioningConfig(vmConfig, d.GetSSHUsername(), d.UserPassword, d.azureCertPath(), d.SSHPort) if err != nil { return err } log.Debug("Authorizing ports...") if err := d.addDockerEndpoints(vmConfig); err != nil { return err } log.Debug("Creating VM...") if err := vmClient.CreateAzureVM(vmConfig, d.MachineName, d.Location); err != nil { return err } return nil } func (d *Driver) GetURL() (string, error) { url := fmt.Sprintf("tcp://%s:%v", d.getHostname(), d.DockerPort) return url, nil } func (d *Driver) GetIP() (string, error) { return d.getHostname(), nil } func (d *Driver) GetState() (state.State, error) { if err := d.setUserSubscription(); err != nil { return state.Error, err } dockerVM, err := vmClient.GetVMDeployment(d.MachineName, d.MachineName) if err != nil { if strings.Contains(err.Error(), "Code: ResourceNotFound") { return state.Error, errors.New("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 (d *Driver) Start() error { if err := d.setUserSubscription(); err != nil { return err } if vmState, err := d.GetState(); err != nil { return err } else if vmState == state.Running || vmState == state.Starting { log.Infof("Host is already running or starting") return nil } log.Debugf("starting %s", d.MachineName) if err := vmClient.StartRole(d.MachineName, d.MachineName, d.MachineName); err != nil { return err } var err error d.IPAddress, err = d.GetIP() return err } func (d *Driver) Stop() error { if err := d.setUserSubscription(); err != nil { return err } if vmState, err := d.GetState(); err != nil { return err } else if vmState == state.Stopped { log.Infof("Host is already stopped") return nil } log.Debugf("stopping %s", d.MachineName) if err := vmClient.ShutdownRole(d.MachineName, d.MachineName, d.MachineName); err != nil { return err } d.IPAddress = "" return nil } func (d *Driver) Remove() error { if err := d.setUserSubscription(); err != nil { return err } if available, _, err := vmClient.CheckHostedServiceNameAvailability(d.MachineName); err != nil { return err } else if available { return nil } log.Debugf("removing %s", d.MachineName) return vmClient.DeleteHostedService(d.MachineName) } func (d *Driver) Restart() error { err := d.setUserSubscription() if err != nil { return err } if vmState, err := d.GetState(); err != nil { return err } else if vmState == state.Stopped { return errors.New("Host is already stopped, use start command to run it") } log.Debugf("restarting %s", d.MachineName) if err := vmClient.RestartRole(d.MachineName, d.MachineName, d.MachineName); err != nil { return err } d.IPAddress, err = d.GetIP() return err } func (d *Driver) Kill() error { if err := d.setUserSubscription(); err != nil { return err } if vmState, err := d.GetState(); err != nil { return err } else if vmState == state.Stopped { log.Infof("Host is already stopped") return nil } log.Debugf("killing %s", d.MachineName) if err := vmClient.ShutdownRole(d.MachineName, d.MachineName, d.MachineName); err != nil { return err } d.IPAddress = "" return nil } func generateVMName() string { randomID := mcnutils.TruncateID(mcnutils.GenerateRandomID()) return fmt.Sprintf("docker-host-%s", randomID) } func (d *Driver) setUserSubscription() error { if d.PublishSettingsFilePath != "" { return azure.ImportPublishSettingsFile(d.PublishSettingsFilePath) } return azure.ImportPublishSettings(d.SubscriptionID, d.SubscriptionCert) } func (d *Driver) addDockerEndpoints(vmConfig *vmClient.Role) error { configSets := vmConfig.ConfigurationSets.ConfigurationSet if len(configSets) == 0 { return errors.New("no configuration set") } for i := 0; i < len(configSets); i++ { if configSets[i].ConfigurationSetType != "NetworkConfiguration" { continue } ep := vmClient.InputEndpoint{ Name: "docker", Protocol: "tcp", Port: d.DockerPort, LocalPort: d.DockerPort, } if d.SwarmMaster { swarm_ep := vmClient.InputEndpoint{ Name: "docker swarm", Protocol: "tcp", Port: d.DockerSwarmMasterPort, LocalPort: d.DockerSwarmMasterPort, } configSets[i].InputEndpoints.InputEndpoint = append(configSets[i].InputEndpoints.InputEndpoint, swarm_ep) log.Debugf("added Docker swarm master endpoint (port %d) to configuration", d.DockerSwarmMasterPort) } configSets[i].InputEndpoints.InputEndpoint = append(configSets[i].InputEndpoints.InputEndpoint, ep) log.Debugf("added Docker endpoint (port %d) to configuration", d.DockerPort) } return nil } func (d *Driver) generateCertForAzure() error { if err := ssh.GenerateSSHKey(d.GetSSHKeyPath()); err != nil { return err } cmd := exec.Command("openssl", "req", "-x509", "-key", d.GetSSHKeyPath(), "-nodes", "-days", "365", "-newkey", "rsa:2048", "-out", d.azureCertPath(), "-subj", "/C=AU/ST=Some-State/O=InternetWidgitsPtyLtd/CN=\\*") return cmd.Run() } func (d *Driver) azureCertPath() string { return d.ResolveStorePath("azure_cert.pem") } func (d *Driver) getHostname() string { return d.MachineName + ".cloudapp.net" }