From 7c8b196d5b85811e218c99cc0befc487df93c8a7 Mon Sep 17 00:00:00 2001 From: Ralph Parkison Date: Wed, 2 Oct 2019 16:59:21 -0700 Subject: [PATCH] azure support --- .gitignore | 5 +- Gopkg.lock | 71 +++++++++++- Gopkg.toml | 8 ++ cmd/drone-autoscaler/main.go | 21 ++++ config/config.go | 22 ++++ drivers/azure/create.go | 113 +++++++++++++++++++ drivers/azure/destroy.go | 206 +++++++++++++++++++++++++++++++++++ drivers/azure/nic.go | 132 ++++++++++++++++++++++ drivers/azure/option.go | 110 +++++++++++++++++++ drivers/azure/provider.go | 70 ++++++++++++ engine/engine.go | 2 + engine/install.go | 22 +++- 12 files changed, 775 insertions(+), 7 deletions(-) create mode 100644 drivers/azure/nic.go diff --git a/.gitignore b/.gitignore index 5fe0227d..c18e247a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,7 @@ vendor *.bak *.out *.db -*.env \ No newline at end of file +*.env +*# +*.#* +*~ \ No newline at end of file diff --git a/Gopkg.lock b/Gopkg.lock index ac923c6f..e33b41fb 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -35,6 +35,37 @@ revision = "b3f5b5de7bbce0acc6a7fc0a4c2b88db678e262e" version = "v1.0.0" +[[projects]] + digest = "1:d98bb15320a88c3cf55c93fc9b78b6e9b3e0ec314cf9bae71231dbb101c1bfef" + name = "github.com/Azure/azure-sdk-for-go" + packages = [ + "services/compute/mgmt/2019-07-01/compute", + "services/network/mgmt/2019-06-01/network", + "version", + ] + pruneopts = "UT" + revision = "7a4bf9c092dec78d5d2e5615b2b1980cd9445e4b" + version = "v32.6.0" + +[[projects]] + digest = "1:c39603b93cda6859fa04dc23bffee64dc5db6f6f4fec93539ddef9d7e628a5da" + name = "github.com/Azure/go-autorest" + packages = [ + "autorest", + "autorest/adal", + "autorest/azure", + "autorest/azure/auth", + "autorest/azure/cli", + "autorest/date", + "autorest/to", + "autorest/validation", + "logger", + "tracing", + ] + pruneopts = "UT" + revision = "5e7a399d8bbf4953ab0c8e3167d7fd535fd74ce1" + version = "v13.0.0" + [[projects]] digest = "1:bf42be3cb1519bf8018dfd99720b1005ee028d947124cab3ccf965da59381df6" name = "github.com/Microsoft/go-winio" @@ -126,6 +157,14 @@ pruneopts = "UT" revision = "8902c56451e9b58ff940bbe5fec35d5f9c04584a" +[[projects]] + digest = "1:76dc72490af7174349349838f2fe118996381b31ea83243812a97e5a0fd5ed55" + name = "github.com/dgrijalva/jwt-go" + packages = ["."] + pruneopts = "UT" + revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e" + version = "v3.2.0" + [[projects]] digest = "1:00a495f7f252222ed239ec5a751cbd2ec680203bd512ab180bc1bfa151d370b3" name = "github.com/digitalocean/godo" @@ -137,6 +176,14 @@ revision = "77ea48de76a7b31b234d854f15d003c68bb2fb90" version = "v1.1.1" +[[projects]] + digest = "1:cf0d2e435fd4ce45b789e93ef24b5f08e86be0e9807a16beb3694e2d8c9af965" + name = "github.com/dimchansky/utfbom" + packages = ["."] + pruneopts = "UT" + revision = "d2133a1ce379ef6fa992b0514a77146c60db9d1c" + version = "v1.1.0" + [[projects]] digest = "1:4189ee6a3844f555124d9d2656fe7af02fca961c2a9bad9074789df13a0c62e0" name = "github.com/docker/distribution" @@ -231,12 +278,12 @@ version = "v1.0.0" [[projects]] - digest = "1:ffc060c551980d37ee9e428ef528ee2813137249ccebb0bfc412ef83071cac91" + digest = "1:573ca21d3669500ff845bdebee890eb7fc7f0f50c59f2132f2a0c6b03d85086a" name = "github.com/golang/protobuf" packages = ["proto"] pruneopts = "UT" - revision = "925541529c1fa6821df4e44ce2723319eb2be768" - version = "v1.0.0" + revision = "6c65a5562fc06764971b7c5d05c76c75e84bdbf7" + version = "v1.3.2" [[projects]] branch = "master" @@ -366,6 +413,14 @@ revision = "3247c84500bff8d9fb6d579d800f20b3e091582c" version = "v1.0.0" +[[projects]] + digest = "1:5d231480e1c64a726869bc4142d270184c419749d34f167646baa21008eb0a79" + name = "github.com/mitchellh/go-homedir" + packages = ["."] + pruneopts = "UT" + revision = "af06845cf3004701891bf4fdb884bfe4920b3727" + version = "v1.1.0" + [[projects]] digest = "1:93a54837eaeed8ae82e170bf8fa79554498f4562058f1b33198b70657bcca612" name = "github.com/o1egl/paseto" @@ -493,7 +548,7 @@ [[projects]] branch = "master" - digest = "1:6b042839b7d7f83211f2234dd2efaa6e0815a057a24a1738fd8166e6c8dbe358" + digest = "1:9ea222b6493f231f41ea2928f7ff09cb11809eeb115df19edbc07ce0e828b386" name = "golang.org/x/crypto" packages = [ "acme", @@ -502,6 +557,8 @@ "ed25519", "ed25519/internal/edwards25519", "hkdf", + "pkcs12", + "pkcs12/internal/rc2", ] pruneopts = "UT" revision = "0e37d006457bf46f9e6692014ba72ef82c33022c" @@ -594,10 +651,15 @@ "docker.io/go-docker/api/types/events", "docker.io/go-docker/api/types/filters", "docker.io/go-docker/api/types/image", + "docker.io/go-docker/api/types/mount", "docker.io/go-docker/api/types/network", "docker.io/go-docker/api/types/registry", "docker.io/go-docker/api/types/swarm", "docker.io/go-docker/api/types/volume", + "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute", + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network", + "github.com/Azure/go-autorest/autorest/azure/auth", + "github.com/Azure/go-autorest/autorest/to", "github.com/aws/aws-sdk-go/aws", "github.com/aws/aws-sdk-go/aws/awserr", "github.com/aws/aws-sdk-go/aws/session", @@ -619,6 +681,7 @@ "github.com/h2non/gock", "github.com/hetznercloud/hcloud-go/hcloud", "github.com/jmoiron/sqlx", + "github.com/joho/godotenv", "github.com/joho/godotenv/autoload", "github.com/kelseyhightower/envconfig", "github.com/kr/pretty", diff --git a/Gopkg.toml b/Gopkg.toml index 2ec583d4..7653eed4 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -108,3 +108,11 @@ [[constraint]] name = "github.com/lib/pq" version = "1.0.0" + +[[constraint]] + name = "github.com/Azure/azure-sdk-for-go" + version = "32.5.0" + +[[override]] + name = "github.com/Azure/go-autorest" + version = "13.0.0" diff --git a/cmd/drone-autoscaler/main.go b/cmd/drone-autoscaler/main.go index 24fcc465..7e1f5385 100644 --- a/cmd/drone-autoscaler/main.go +++ b/cmd/drone-autoscaler/main.go @@ -14,6 +14,7 @@ import ( "github.com/drone/autoscaler" "github.com/drone/autoscaler/config" "github.com/drone/autoscaler/drivers/amazon" + "github.com/drone/autoscaler/drivers/azure" "github.com/drone/autoscaler/drivers/digitalocean" "github.com/drone/autoscaler/drivers/google" "github.com/drone/autoscaler/drivers/hetznercloud" @@ -260,6 +261,26 @@ func setupProvider(c config.Config) (autoscaler.Provider, error) { amazon.WithIamProfileArn(c.Amazon.IamProfileArn), amazon.WithMarketType(c.Amazon.MarketType), ), nil + case os.Getenv("AZURE_CLIENT_ID") != "": + return azure.New( + azure.WithVMSize(c.Azure.VMSize), + azure.WithSubscriptionId(c.Azure.Subscription), + azure.WithResourceGroup(c.Azure.ResourceGroup), + azure.WithImage(c.Azure.Image), + azure.WithLocation(c.Azure.Location), + azure.WithAdminUsername(c.Azure.AdminUsername), + azure.WithAdminPassword(c.Azure.AdminPassword), + azure.WithVMName(c.Azure.VMName), + azure.WithSSHKey(c.Azure.SSHKey), + azure.WithVNet(c.Azure.VNet), + azure.WithNSG(c.Azure.NSG), + azure.WithSubnet(c.Azure.Subnet), + azure.WithVolumeSize(c.Azure.VolumeSize), + azure.WithImageOffer(c.Azure.ImageOffer), + azure.WithImagePublisher(c.Azure.ImagePub), + azure.WithImageSKU(c.Azure.ImageSKU), + azure.WithImageVersion(c.Azure.ImageVer), + ), nil case os.Getenv("OS_USERNAME") != "": return openstack.New( openstack.WithImage(c.OpenStack.Image), diff --git a/config/config.go b/config/config.go index 1eb6d28f..9b3aa5b2 100644 --- a/config/config.go +++ b/config/config.go @@ -52,6 +52,8 @@ type ( Environ []string Volumes []string Labels map[string]string `envconfig:"DRONE_AGENT_LABELS"` + DockerUsername string `envconfig:"DRONE_AGENT_DOCKER_USERNAME"` + DockerPassword string `envconfig:"DRONE_AGENT_DOCKER_PASSWORD"` } Runner Runner @@ -93,6 +95,26 @@ type ( Datasource string `default:"database.sqlite?cache=shared&mode=rwc&_busy_timeout=9999999"` } + Azure struct { + Subscription string `envconfig:"DRONE_AZURE_SUBSCRIPTION_ID"` + ResourceGroup string `envconfig:"DRONE_AZURE_RESOURCE_GROUP"` + Image string `envconfig:"DRONE_AZURE_IMAGE"` + Location string `envconfig:"DRONE_AZURE_LOCATION"` + AdminUsername string `envconfig:"DRONE_AZURE_ADMIN_USERNAME"` + AdminPassword string `envconfig:"DRONE_AZURE_ADMIN_PASSWORD"` + VMName string `envconfig:"DRONE_AZURE_VMNAME"` + SSHKey string `envconfig:"DRONE_AZURE_SSHKey"` + VolumeSize int32 `envconfig:"DRONE_AZURE_VOLUMESIZE"` + VMSize string `envconfig:"DRONE_AZURE_VMSIZE"` + VNet string `envconfig:"DRONE_AZURE_VNET"` // Virtual network + Subnet string `envconfig:"DRONE_AZURE_SUBNET"` // Subnetwork + NSG string `envconfig:"DRONE_AZURE_NSG"` // Network security Group + ImageOffer string `envconfig:"DRONE_AZURE_IMAGE_OFFER" default:"UbuntuServer"` + ImagePub string `envconfig:"DRONE_AZURE_IMAGE_PUBLISHER" default:"Canonical"` + ImageSKU string `envconfig:"DRONE_AZURE_IMAGE_SKU" default:"18.04-LTS"` + ImageVer string `envconfig:"DRONE_AZURE_IMAGE_VERSION" default:"latest"` + } + Amazon struct { DeviceName string `envconfig:"DRONE_AMAZON_DEVICE_NAME"` Image string diff --git a/drivers/azure/create.go b/drivers/azure/create.go index cdb364eb..325d1383 100644 --- a/drivers/azure/create.go +++ b/drivers/azure/create.go @@ -3,3 +3,116 @@ // that can be found in the LICENSE file. package azure + +import ( + "context" + "github.com/drone/autoscaler" + "github.com/rs/zerolog/log" + "fmt" + "time" + + "bytes" + "encoding/base64" + + "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute" + "github.com/Azure/go-autorest/autorest/to" +) + +func (p *provider) computeMachineRequest(ctx context.Context, base string, opts autoscaler.InstanceCreateOpts) (compute.VirtualMachine, *string, error) { + buf := new(bytes.Buffer) + err := p.userdata.Execute(buf, &opts) + cloudInit := base64.StdEncoding.EncodeToString(buf.Bytes()) + + nic, ip, err := p.CreateNIC(ctx, p.vnet, p.subnet, p.nsg, base+"-ip", base+"-nic") + if err != nil { + return compute.VirtualMachine{}, nil, err + } + + return compute.VirtualMachine{ + Location: to.StringPtr(p.location), + VirtualMachineProperties: &compute.VirtualMachineProperties{ + HardwareProfile: &compute.HardwareProfile{ + VMSize: p.vmSize, + }, + StorageProfile: &compute.StorageProfile{ + OsDisk: &compute.OSDisk{ + Name: to.StringPtr(base + "-" + "osdisk"), + CreateOption: compute.DiskCreateOptionTypesFromImage, + DiskSizeGB: to.Int32Ptr(p.volumeSize), + }, + ImageReference: &compute.ImageReference{ + Offer: to.StringPtr(p.imageOffer), + Publisher: to.StringPtr(p.imagePublisher), + Sku: to.StringPtr(p.imageSKU), + Version: to.StringPtr(p.imageVersion), + }, + }, + NetworkProfile: &compute.NetworkProfile{ + NetworkInterfaces: &[]compute.NetworkInterfaceReference{ + { + ID: nic.ID, + NetworkInterfaceReferenceProperties: &compute.NetworkInterfaceReferenceProperties{ + Primary: to.BoolPtr(true), + }, + }, + }, + }, + OsProfile: &compute.OSProfile{ + ComputerName: to.StringPtr(base + "-" + "vm"), + AdminUsername: to.StringPtr(p.adminUsername), + AdminPassword: to.StringPtr(p.adminPassword), + CustomData: &cloudInit, + }, + }, + }, ip.PublicIPAddressPropertiesFormat.IPAddress, nil +} + +func (p *provider) Create(ctx context.Context, opts autoscaler.InstanceCreateOpts) (*autoscaler.Instance, error) { + logger := log.Ctx(ctx) + logger.Debug().Msg("Azure creating an instance.") + + client, err := p.getClient() + if err != nil { + return nil, err + } + + base := opts.Name + computeRequest, ipaddress, err := p.computeMachineRequest(ctx, base, opts) + if err != nil { + return nil, err + } + + future, err := client.CreateOrUpdate( + ctx, + p.resourceGroup, + base+"-vm", + computeRequest, + ) + + err = future.WaitForCompletionRef(ctx, client.Client) + if err != nil { + return nil, err + } + + azureInstance, err := future.Result(client) + client.Start(ctx, p.resourceGroup, p.vmName) + + if err != nil { + return nil, err + } + + fmt.Printf("Starting sleep: %v\n", time.Now().Unix()) + time.Sleep(180 * time.Second) + fmt.Printf("Ending sleep: %v\n", time.Now().Unix()) + + instance := &autoscaler.Instance{ + Provider: autoscaler.ProviderAzure, + Address: *ipaddress, + ID: base, // Set it to name because required for deallocating. + Size: string(azureInstance.HardwareProfile.VMSize), + Region: *azureInstance.Location, + Image: *azureInstance.StorageProfile.ImageReference.Sku, + } + + return instance, nil +} diff --git a/drivers/azure/destroy.go b/drivers/azure/destroy.go index cdb364eb..cc9c9aa8 100644 --- a/drivers/azure/destroy.go +++ b/drivers/azure/destroy.go @@ -3,3 +3,209 @@ // that can be found in the LICENSE file. package azure + +import ( + "context" + "fmt" + "github.com/drone/autoscaler" + "github.com/rs/zerolog/log" + disks "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute" + "github.com/Azure/go-autorest/autorest/azure/auth" +) + +func (p *provider) getDiskClient() (disks.DisksClient, error) { + disksClient := disks.NewDisksClient(p.subscriptionId) + auth, err := auth.NewAuthorizerFromEnvironment() + if err != nil { + return disksClient, err + } + disksClient.Authorizer = auth + return disksClient, nil +} + +func (p *provider) deleteDisks(ctx context.Context, instance *autoscaler.Instance) error { + logger := log.Ctx(ctx).With(). + Str("Azure Destroy:", instance.ID). + Str("id", instance.ID). + Str("ip", instance.Address). + Str("name", instance.Name). + Str("zone", instance.Region). + Logger() + + diskClient, err := p.getDiskClient() + if err != nil { + logger.Error(). + Err(err). + Msg("cannot terminate instance") + return err + } + + future, err := diskClient.Delete(ctx, p.resourceGroup, instance.ID + "-osdisk") + err = future.WaitForCompletionRef(ctx, diskClient.Client) + logger.Debug().Msg("waitforcompletionref") + if err != nil { + logger.Error(). + Err(err). + Msg("cannot terminate instance") + return err + } + + if err != nil { + logger.Error(). + Err(err). + Msg("cannot terminate instance") + return err + } + + err = future.WaitForCompletionRef(ctx, diskClient.Client) + logger.Debug().Msg("waitforcompletionref") + if err != nil { + logger.Error(). + Err(err). + Msg("cannot terminate instance") + return err + } + return nil +} + +func (p *provider) deleteVM(ctx context.Context, instance *autoscaler.Instance) error { + logger := log.Ctx(ctx).With(). + Str("Azure Destroy:", instance.ID). + Str("id", instance.ID). + Str("ip", instance.Address). + Str("name", instance.Name). + Str("zone", instance.Region). + Logger() + + vmClient, err := p.getClient() + if err != nil { + return err + } + + future, err := vmClient.Delete(ctx, p.resourceGroup, instance.ID + "-vm") + fmt.Println(future, err) + logger.Debug().Msg("Deallocate call") + + if err != nil { + logger.Error(). + Err(err). + Msg("cannot terminate instance") + return err + } + + err = future.WaitForCompletionRef(ctx, vmClient.Client) + logger.Debug().Msg("waitforcompletionref") + if err != nil { + logger.Error(). + Err(err). + Msg("cannot terminate instance") + return err + } + + result, err := future.Result(vmClient) + fmt.Println(result, err) + logger.Debug().Msg("future.result") + return nil +} + +func (p *provider) deleteIP(ctx context.Context, instance *autoscaler.Instance) error { + logger := log.Ctx(ctx).With(). + Str("Azure Destroy:", instance.ID). + Str("id", instance.ID). + Str("ip", instance.Address). + Str("name", instance.Name). + Str("zone", instance.Region). + Logger() + + ipClient, err := p.getIPClient() + if err != nil { + return err + } + future, err := ipClient.Delete(ctx, p.resourceGroup, instance.ID + "-ip") + fmt.Println(future, err) + logger.Debug().Msg("Deallocate call") + + if err != nil { + logger.Error(). + Err(err). + Msg("cannot terminate instance") + return err + } + + err = future.WaitForCompletionRef(ctx, ipClient.Client) + logger.Debug().Msg("waitforcompletionref") + if err != nil { + logger.Error(). + Err(err). + Msg("cannot terminate instance") + return err + } + + result, err := future.Result(ipClient) + fmt.Println(result, err) + logger.Debug().Msg("future.result") + return nil +} + +func (p *provider) deleteNIC(ctx context.Context, instance *autoscaler.Instance) error { + logger := log.Ctx(ctx).With(). + Str("Azure Destroy:", instance.ID). + Str("id", instance.ID). + Str("ip", instance.Address). + Str("name", instance.Name). + Str("zone", instance.Region). + Logger() + + nicClient, err := p.getNicClient() + if err != nil { + return err + } + future, err := nicClient.Delete(ctx, p.resourceGroup, instance.ID + "-nic") + fmt.Println(future, err) + logger.Debug().Msg("Deallocate call") + + if err != nil { + logger.Error(). + Err(err). + Msg("cannot terminate instance") + return err + } + + err = future.WaitForCompletionRef(ctx, nicClient.Client) + logger.Debug().Msg("waitforcompletionref") + if err != nil { + logger.Error(). + Err(err). + Msg("cannot terminate instance") + return err + } + + result, err := future.Result(nicClient) + fmt.Println(result, err) + logger.Debug().Msg("future.result") + return nil +} + +func (p *provider) Destroy(ctx context.Context, instance *autoscaler.Instance) error { + err := p.deleteVM(ctx, instance) + if err != nil { + return err + } + + err = p.deleteNIC(ctx, instance) + if err != nil { + return err + } + + err = p.deleteIP(ctx, instance) + if err != nil { + return err + } + + err = p.deleteDisks(ctx, instance) + if err != nil { + return err + } + + return nil +} diff --git a/drivers/azure/nic.go b/drivers/azure/nic.go new file mode 100644 index 00000000..782e861e --- /dev/null +++ b/drivers/azure/nic.go @@ -0,0 +1,132 @@ +package azure + +import ( + "context" + "fmt" + "log" + + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network" + "github.com/Azure/go-autorest/autorest/azure/auth" + "github.com/Azure/go-autorest/autorest/to" +) + +func (p *provider) getSubnetsClient() (network.SubnetsClient, error) { + subnetsClient := network.NewSubnetsClient(p.subscriptionId) + auth, err := auth.NewAuthorizerFromEnvironment() + if err != nil { + return subnetsClient, err + } + subnetsClient.Authorizer = auth + return subnetsClient, nil +} + +// getVirtualNetworkSubnet returns an existing subnet from a virtual network +func (p *provider) getVirtualNetworkSubnet(ctx context.Context, vnetName string, subnetName string) (network.Subnet, error) { + subnetsClient, err := p.getSubnetsClient() + if err != nil { + return network.Subnet{}, err + } + return subnetsClient.Get(ctx, p.resourceGroup, vnetName, subnetName, "") +} + + +func (p *provider) getIPClient() (network.PublicIPAddressesClient, error) { + ipClient := network.NewPublicIPAddressesClient(p.subscriptionId) + auth, err := auth.NewAuthorizerFromEnvironment() + if err != nil { + return ipClient, err + } + ipClient.Authorizer = auth + return ipClient, nil +} + +// CreatePublicIP creates a new public IP +func (p *provider) createPublicIP(ctx context.Context, ipName string) (ip network.PublicIPAddress, err error) { + ipClient, err := p.getIPClient() + if err != nil { + return network.PublicIPAddress{}, err + } + future, err := ipClient.CreateOrUpdate( + ctx, + p.resourceGroup, + ipName, + network.PublicIPAddress{ + Name: to.StringPtr(ipName), + Location: to.StringPtr(p.location), + PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{ + PublicIPAddressVersion: network.IPv4, + PublicIPAllocationMethod: network.Static, + }, + }, + ) + + if err != nil { + return ip, fmt.Errorf("cannot create public ip address: %v", err) + } + + err = future.WaitForCompletionRef(ctx, ipClient.Client) + if err != nil { + return ip, fmt.Errorf("cannot get public ip address create or update future response: %v", err) + } + + return future.Result(ipClient) +} + +func (p *provider) getNicClient() (network.InterfacesClient, error) { + nicClient := network.NewInterfacesClient(p.subscriptionId) + auth, err := auth.NewAuthorizerFromEnvironment() + if err != nil { + return nicClient, err + } + + nicClient.Authorizer = auth + return nicClient, nil +} + +// CreateNIC creates a new network interface. The Network Security Group is not a required parameter +func (p *provider) CreateNIC(ctx context.Context, vnetName, subnetName, nsgName, ipName, nicName string) (nic network.Interface, ipres network.PublicIPAddress, err error) { + subnet, err := p.getVirtualNetworkSubnet(ctx, vnetName, subnetName) + if err != nil { + log.Fatalf("failed to get subnet: %v", err) + } + + ip, err := p.createPublicIP(ctx, ipName) + + if err != nil { + log.Fatalf("failed to get ip address: %v", err) + } + + nicParams := network.Interface{ + Name: to.StringPtr(nicName), + Location: to.StringPtr(p.location), + InterfacePropertiesFormat: &network.InterfacePropertiesFormat{ + IPConfigurations: &[]network.InterfaceIPConfiguration{ + { + Name: to.StringPtr("ipConfig1"), + InterfaceIPConfigurationPropertiesFormat: &network.InterfaceIPConfigurationPropertiesFormat{ + Subnet: &subnet, + PrivateIPAllocationMethod: network.Dynamic, + PublicIPAddress: &ip, + }, + }, + }, + }, + } + + nicClient, err := p.getNicClient() + if err != nil { + return nic, ip, err + } + future, err := nicClient.CreateOrUpdate(ctx, p.resourceGroup, nicName, nicParams) + if err != nil { + return nic, ip, fmt.Errorf("cannot create nic: %v", err) + } + + err = future.WaitForCompletionRef(ctx, nicClient.Client) + if err != nil { + return nic, ip, fmt.Errorf("cannot get nic create or update future response: %v", err) + } + + nicresponse, err := future.Result(nicClient) + return nicresponse, ip, err +} diff --git a/drivers/azure/option.go b/drivers/azure/option.go index cdb364eb..dc683ec5 100644 --- a/drivers/azure/option.go +++ b/drivers/azure/option.go @@ -3,3 +3,113 @@ // that can be found in the LICENSE file. package azure + +import ( + "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute" +) + + +type Option func(*provider) + +func WithSubscriptionId(n string) Option { + return func(p *provider) { + p.subscriptionId = n + } +} + +func WithResourceGroup(n string) Option { + return func(p *provider) { + p.resourceGroup = n + } +} + +func WithImage(n string) Option { + return func(p *provider) { + p.image = n + } +} + +func WithLocation(n string) Option { + return func(p *provider) { + p.location = n + } +} + +func WithAdminUsername(n string) Option { + return func(p *provider) { + p.adminUsername = n + } +} + +func WithAdminPassword(n string) Option { + return func(p *provider) { + p.adminPassword = n + } +} + +func WithVMName(n string) Option { + return func(p *provider) { + p.vmName = n + } +} + +func WithSSHKey(n string) Option { + return func(p *provider) { + p.sshKey = n + } +} + +func WithVMSize(n string) Option { + return func(p *provider) { + p.vmSize = compute.VirtualMachineSizeTypes(n) + } +} + +func WithVNet(n string) Option { + return func(p *provider) { + p.vnet = n + } +} + +func WithNSG(n string) Option { + return func(p *provider) { + p.nsg = n + } +} + +func WithSubnet(n string) Option { + return func(p *provider) { + p.subnet = n + } +} + +func WithVolumeSize(n int32) Option { + return func(p *provider) { + p.volumeSize = n + } +} + +func WithImageOffer(n string) Option { + return func(p *provider) { + p.imageOffer = n + } +} + +func WithImagePublisher(n string) Option { + return func(p *provider) { + p.imagePublisher = n + } +} + +func WithImageSKU(n string) Option { + return func(p *provider) { + p.imageSKU = n + } +} + + +func WithImageVersion(n string) Option { + return func(p *provider) { + p.imageVersion = n + } +} diff --git a/drivers/azure/provider.go b/drivers/azure/provider.go index cdb364eb..10d86dd6 100644 --- a/drivers/azure/provider.go +++ b/drivers/azure/provider.go @@ -3,3 +3,73 @@ // that can be found in the LICENSE file. package azure + +import ( + "sync" + "github.com/drone/autoscaler" + "text/template" + + "github.com/drone/autoscaler/drivers/internal/userdata" + + // Azure apis + "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute" + "github.com/Azure/go-autorest/autorest/azure/auth" +) + +type provider struct { + init sync.Once + + subscriptionId string + resourceGroup string + image string + location string + adminUsername string + adminPassword string + vmName string + sshKey string + vmSize compute.VirtualMachineSizeTypes + userdata *template.Template + vnet string + nsg string + subnet string + volumeSize int32 + imageOffer string + imagePublisher string + imageSKU string + imageVersion string +} + +// Note requires the following environment variables: +// - `AZURE_TENANT_ID`: Specifies the Tenant to which to authenticate. +// - `AZURE_CLIENT_ID`: Specifies the app client ID to use. +// - `AZURE_CLIENT_SECRET`: Specifies the app secret to use. + +func (p *provider) getClient() (compute.VirtualMachinesClient, error) { + vmClient := compute.NewVirtualMachinesClient(p.subscriptionId) + // pulled from [https://github.com/Azure/azure-sdk-for-go#more-authentication-details] + // Uses environment variables for the authorization client_secret/client_id + auth, err := auth.NewAuthorizerFromEnvironment() + if err != nil { + return compute.VirtualMachinesClient{}, err + } + + vmClient.Authorizer = auth + return vmClient, nil +} + +// New returns a new Digital Ocean provider. +func New(opts ...Option) autoscaler.Provider { + p := new(provider) + p.vmSize = compute.VirtualMachineSizeTypesBasicA0 + for _, opt := range opts { + opt(p) + } + if p.userdata == nil { + p.userdata = userdata.T + } + if p.volumeSize == 0 { + p.volumeSize = 1032 + } + + return p +} diff --git a/engine/engine.go b/engine/engine.go index 6b962d62..cb8ace12 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -76,6 +76,8 @@ func New( watchtowerImage: config.Watchtower.Image, watchtowerTimeout: config.Watchtower.Timeout, watchtowerInterval: config.Watchtower.Interval, + dockerUsername: config.Agent.DockerUsername, + dockerPassword: config.Agent.DockerPassword, }, pinger: &pinger{ servers: servers, diff --git a/engine/install.go b/engine/install.go index 3415ba5d..90b483ed 100644 --- a/engine/install.go +++ b/engine/install.go @@ -5,6 +5,8 @@ package engine import ( + "encoding/base64" + "encoding/json" "context" "fmt" "io" @@ -40,6 +42,8 @@ type installer struct { keepaliveTimeout time.Duration runner config.Runner labels map[string]string + dockerUsername string + dockerPassword string gcEnabled bool gcDebug bool @@ -134,9 +138,23 @@ poller: logger.Debug(). Str("image", i.image). - Msg("pull docker image") + Msg("pull docker image") - rc, err := client.ImagePull(ctx, i.image, types.ImagePullOptions{}) + options := types.ImagePullOptions{} + if i.dockerUsername != "" && i.dockerPassword != "" { + authConfig := types.AuthConfig{ + Username: i.dockerUsername, + Password: i.dockerPassword, + } + encodedJSON, err := json.Marshal(authConfig) + if err != nil { + panic(err) + } + authStr := base64.URLEncoding.EncodeToString(encodedJSON) + options = types.ImagePullOptions{RegistryAuth: authStr} + } + + rc, err := client.ImagePull(ctx, i.image, options) if err != nil { logger.Error().Err(err). Str("image", i.image).