From 453d43398d4582f16ff8c187758ce7482e167f9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Ferreira?= Date: Tue, 13 Oct 2020 12:04:30 +0100 Subject: [PATCH] Add GetInstanceByID to azure, aws, vsphere, gcp and openstack providers (#656) --- lepton/aws.go | 95 +++++++++++++++++++------------- lepton/azure.go | 80 ++++++++++++++++----------- lepton/digital_ocean.go | 5 ++ lepton/gcp.go | 62 +++++++++++++-------- lepton/onprem.go | 5 ++ lepton/openstack.go | 118 +++++++++++++++++++++++----------------- lepton/provider.go | 8 +++ lepton/vsphere.go | 57 ++++++++++++++----- lepton/vultr.go | 5 ++ 9 files changed, 280 insertions(+), 155 deletions(-) diff --git a/lepton/aws.go b/lepton/aws.go index e1e32aa2..a77a78f6 100644 --- a/lepton/aws.go +++ b/lepton/aws.go @@ -234,19 +234,58 @@ func getAWSImages(region string) (*ec2.DescribeImagesOutput, error) { return result, nil } -func getAWSInstances(region string) *ec2.DescribeInstancesOutput { +func getAWSInstances(region string, filter []*ec2.Filter) []CloudInstance { svc, err := session.NewSession(&aws.Config{ Region: aws.String(region)}, ) compute := ec2.New(svc) - request := ec2.DescribeInstancesInput{} + request := ec2.DescribeInstancesInput{ + Filters: filter, + } result, err := compute.DescribeInstances(&request) if err != nil { fmt.Println(err) } - return result + var cinstances []CloudInstance + + for _, reservation := range result.Reservations { + + for i := 0; i < len(reservation.Instances); i++ { + instance := reservation.Instances[i] + + instanceName := "unknown" + for x := 0; x < len(instance.Tags); x++ { + if aws.StringValue(instance.Tags[i].Key) == "Name" { + instanceName = aws.StringValue(instance.Tags[i].Value) + } + } + + var privateIps, publicIps []string + for _, ninterface := range instance.NetworkInterfaces { + privateIps = append(privateIps, aws.StringValue(ninterface.PrivateIpAddress)) + + if ninterface.Association != nil && ninterface.Association.PublicIp != nil { + publicIps = append(publicIps, aws.StringValue(ninterface.Association.PublicIp)) + } + } + + cinstance := CloudInstance{ + ID: aws.StringValue(instance.InstanceId), + Name: instanceName, + Status: aws.StringValue(instance.State.Name), + Created: aws.TimeValue(instance.LaunchTime).String(), + PublicIps: publicIps, + PrivateIps: privateIps, + } + + cinstances = append(cinstances, cinstance) + } + + } + + return cinstances } // GetImages return all images on AWS @@ -557,47 +596,25 @@ func (p *AWS) CreateSG(ctx *Context, svc *ec2.EC2, imgName string) (string, erro return aws.StringValue(createRes.GroupId), nil } -// GetInstances return all instances on AWS -// TODO -func (p *AWS) GetInstances(ctx *Context) ([]CloudInstance, error) { - var cinstances []CloudInstance - - result := getAWSInstances(ctx.config.CloudConfig.Zone) - - for _, reservation := range result.Reservations { +// GetInstanceByID returns the instance with the id passed by argument if it exists +func (p *AWS) GetInstanceByID(ctx *Context, id string) (*CloudInstance, error) { + var filters []*ec2.Filter - for i := 0; i < len(reservation.Instances); i++ { - instance := reservation.Instances[i] - - instanceName := "unknown" - for x := 0; x < len(instance.Tags); x++ { - if aws.StringValue(instance.Tags[i].Key) == "Name" { - instanceName = aws.StringValue(instance.Tags[i].Value) - } - } + filters = append(filters, &ec2.Filter{Name: aws.String("tag:Name"), Values: aws.StringSlice([]string{id})}) - var privateIps, publicIps []string - for _, ninterface := range instance.NetworkInterfaces { - privateIps = append(privateIps, aws.StringValue(ninterface.PrivateIpAddress)) + instances := getAWSInstances(ctx.config.CloudConfig.Zone, filters) - if ninterface.Association != nil && ninterface.Association.PublicIp != nil { - publicIps = append(publicIps, aws.StringValue(ninterface.Association.PublicIp)) - } - } - - cinstance := CloudInstance{ - ID: aws.StringValue(instance.InstanceId), - Name: instanceName, - Status: aws.StringValue(instance.State.Name), - Created: aws.TimeValue(instance.LaunchTime).String(), - PublicIps: publicIps, - PrivateIps: privateIps, - } + if len(instances) == 0 { + return nil, ErrInstanceNotFound(id) + } - cinstances = append(cinstances, cinstance) - } + return &instances[0], nil +} - } +// GetInstances return all instances on AWS +// TODO +func (p *AWS) GetInstances(ctx *Context) ([]CloudInstance, error) { + cinstances := getAWSInstances(ctx.config.CloudConfig.Zone, nil) return cinstances, nil } diff --git a/lepton/azure.go b/lepton/azure.go index b4e62d70..6050686f 100644 --- a/lepton/azure.go +++ b/lepton/azure.go @@ -517,6 +517,18 @@ func (a *Azure) CreateInstance(ctx *Context) error { return nil } +// GetInstanceByID returns the instance with the id passed by argument if it exists +func (a *Azure) GetInstanceByID(ctx *Context, id string) (*CloudInstance, error) { + vmClient := a.getVMClient() + + vm, err := vmClient.Get(context.TODO(), a.groupName, id, compute.InstanceView) + if err != nil { + return nil, err + } + + return a.convertToCloudInstance(&vm), nil +} + // GetInstances return all instances on Azure // TODO func (a *Azure) GetInstances(ctx *Context) ([]CloudInstance, error) { @@ -532,46 +544,52 @@ func (a *Azure) GetInstances(ctx *Context) ([]CloudInstance, error) { instances := vmlist.Values() for _, instance := range instances { - cinstance := CloudInstance{ - Name: *instance.Name, - } - privateIP := "" - publicIP := "" + cinstance := a.convertToCloudInstance(&instance) - if instance.VirtualMachineProperties != nil { - nifs := *((*(*instance.VirtualMachineProperties).NetworkProfile).NetworkInterfaces) + cinstances = append(cinstances, *cinstance) + } - for i := 0; i < len(nifs); i++ { - nicClient := a.getNicClient() - nic, err := nicClient.Get(context.TODO(), a.groupName, cinstance.Name, "") - if err != nil { - fmt.Println(err) - } + return cinstances, nil +} + +func (a *Azure) convertToCloudInstance(instance *compute.VirtualMachine) *CloudInstance { + cinstance := CloudInstance{ + Name: *instance.Name, + } + privateIP := "" + publicIP := "" + + if instance.VirtualMachineProperties != nil { + nifs := *((*(*instance.VirtualMachineProperties).NetworkProfile).NetworkInterfaces) + + for i := 0; i < len(nifs); i++ { + nicClient := a.getNicClient() + nic, err := nicClient.Get(context.TODO(), a.groupName, cinstance.Name, "") + if err != nil { + fmt.Println(err) + } - if nic.InterfacePropertiesFormat != nil { - ipconfig := *(*nic.InterfacePropertiesFormat).IPConfigurations - for x := 0; x < len(ipconfig); x++ { - format := *ipconfig[x].InterfaceIPConfigurationPropertiesFormat - privateIP = *format.PrivateIPAddress - - ipClient := a.getIPClient() - pubip, err := ipClient.Get(context.TODO(), a.groupName, cinstance.Name, "") - if err != nil { - fmt.Println(err) - } - publicIP = *(*pubip.PublicIPAddressPropertiesFormat).IPAddress + if nic.InterfacePropertiesFormat != nil { + ipconfig := *(*nic.InterfacePropertiesFormat).IPConfigurations + for x := 0; x < len(ipconfig); x++ { + format := *ipconfig[x].InterfaceIPConfigurationPropertiesFormat + privateIP = *format.PrivateIPAddress + + ipClient := a.getIPClient() + pubip, err := ipClient.Get(context.TODO(), a.groupName, cinstance.Name, "") + if err != nil { + fmt.Println(err) } + publicIP = *(*pubip.PublicIPAddressPropertiesFormat).IPAddress } - } - } - cinstance.PrivateIps = []string{privateIP} - cinstance.PublicIps = []string{publicIP} - cinstances = append(cinstances, cinstance) + } } + cinstance.PrivateIps = []string{privateIP} + cinstance.PublicIps = []string{publicIP} - return cinstances, nil + return &cinstance } // ListInstances lists instances on Azure diff --git a/lepton/digital_ocean.go b/lepton/digital_ocean.go index e60bbbc8..b65272e8 100644 --- a/lepton/digital_ocean.go +++ b/lepton/digital_ocean.go @@ -147,6 +147,11 @@ func (do *DigitalOcean) CreateInstance(ctx *Context) error { return nil } +// GetInstanceByID returns the instance with the id passed by argument if it exists +func (do *DigitalOcean) GetInstanceByID(ctx *Context, id string) (*CloudInstance, error) { + return nil, errors.New("un-implemented") +} + // GetInstances return all instances on DigitalOcean // TODO func (do *DigitalOcean) GetInstances(ctx *Context) ([]CloudInstance, error) { diff --git a/lepton/gcp.go b/lepton/gcp.go index e62a59cd..d7ff1f0a 100644 --- a/lepton/gcp.go +++ b/lepton/gcp.go @@ -455,6 +455,18 @@ func (p *GCloud) ListInstances(ctx *Context) error { return nil } +// GetInstanceByID returns the instance with the id passed by argument if it exists +func (p *GCloud) GetInstanceByID(ctx *Context, id string) (*CloudInstance, error) { + req := p.Service.Instances.Get(ctx.config.CloudConfig.ProjectID, ctx.config.CloudConfig.Zone, id) + + instance, err := req.Do() + if err != nil { + return nil, err + } + + return p.convertToCloudInstance(instance), nil +} + // GetInstances return all instances on GCloud func (p *GCloud) GetInstances(ctx *Context) ([]CloudInstance, error) { context := context.TODO() @@ -465,29 +477,8 @@ func (p *GCloud) GetInstances(ctx *Context) ([]CloudInstance, error) { if err := req.Pages(context, func(page *compute.InstanceList) error { for _, instance := range page.Items { - var ( - privateIps, publicIps []string - ) - for _, ninterface := range instance.NetworkInterfaces { - if ninterface.NetworkIP != "" { - privateIps = append(privateIps, ninterface.NetworkIP) - - } - for _, accessConfig := range ninterface.AccessConfigs { - if accessConfig.NatIP != "" { - publicIps = append(publicIps, accessConfig.NatIP) - } - } - } - - cinstance := CloudInstance{ - Name: instance.Name, - Status: instance.Status, - Created: instance.CreationTimestamp, - PublicIps: publicIps, - PrivateIps: privateIps, - } - cinstances = append(cinstances, cinstance) + cinstance := p.convertToCloudInstance(instance) + cinstances = append(cinstances, *cinstance) } return nil }); err != nil { @@ -497,6 +488,31 @@ func (p *GCloud) GetInstances(ctx *Context) ([]CloudInstance, error) { return cinstances, nil } +func (p *GCloud) convertToCloudInstance(instance *compute.Instance) *CloudInstance { + var ( + privateIps, publicIps []string + ) + for _, ninterface := range instance.NetworkInterfaces { + if ninterface.NetworkIP != "" { + privateIps = append(privateIps, ninterface.NetworkIP) + + } + for _, accessConfig := range ninterface.AccessConfigs { + if accessConfig.NatIP != "" { + publicIps = append(publicIps, accessConfig.NatIP) + } + } + } + + return &CloudInstance{ + Name: instance.Name, + Status: instance.Status, + Created: instance.CreationTimestamp, + PublicIps: publicIps, + PrivateIps: privateIps, + } +} + // DeleteInstance deletes instance from Gcloud func (p *GCloud) DeleteInstance(ctx *Context, instancename string) error { context := context.TODO() diff --git a/lepton/onprem.go b/lepton/onprem.go index c6ec462b..7023bdfe 100644 --- a/lepton/onprem.go +++ b/lepton/onprem.go @@ -166,6 +166,11 @@ func (p *OnPrem) CreateInstance(ctx *Context) error { return nil } +// GetInstanceByID returns the instance with the id passed by argument if it exists +func (p *OnPrem) GetInstanceByID(ctx *Context, id string) (*CloudInstance, error) { + return nil, errors.New("un-implemented") +} + // GetInstances return all instances on prem // TODO func (p *OnPrem) GetInstances(ctx *Context) ([]CloudInstance, error) { diff --git a/lepton/openstack.go b/lepton/openstack.go index e2b089d9..11857ffb 100644 --- a/lepton/openstack.go +++ b/lepton/openstack.go @@ -24,6 +24,59 @@ import ( "github.com/gophercloud/gophercloud/pagination" ) +func getOpenStackInstances(provider *gophercloud.ProviderClient, opts servers.ListOpts) ([]CloudInstance, error) { + cinstances := []CloudInstance{} + + client, err := openstack.NewComputeV2(provider, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) + if err != nil { + fmt.Println(err) + } + + pager := servers.List(client, opts) + + err = pager.EachPage(func(page pagination.Page) (bool, error) { + serverList, err := servers.ExtractServers(page) + if err != nil { + fmt.Println(err) + return false, err + } + + for _, s := range serverList { + // fugly + ipv4 := "" + // For some instances IP is not assigned. + z := s.Addresses["public"] + if z != nil { + for _, v := range z.([]interface{}) { + sz := v.(map[string]interface{}) + version := sz["version"].(float64) + if version == 4 { + ipv4 = sz["addr"].(string) + } + } + } else { + ipv4 = "NA" + } + + cinstance := CloudInstance{ + ID: s.ID, + Name: s.Name, + PublicIps: []string{ipv4}, + Status: s.Status, + Created: s.Created.Format("2006-01-02 15:04:05"), + } + + cinstances = append(cinstances, cinstance) + } + + return true, nil + }) + + return cinstances, nil +} + // OpenStack provides access to the OpenStack API. type OpenStack struct { Storage *Datastores @@ -366,61 +419,28 @@ func (o *OpenStack) addBootFromVolumeParams( } } -// GetInstances return all instances on OpenStack -// TODO -func (o *OpenStack) GetInstances(ctx *Context) ([]CloudInstance, error) { - cinstances := []CloudInstance{} +// GetInstanceByID returns the instance with the id passed by argument if it exists +func (o *OpenStack) GetInstanceByID(ctx *Context, id string) (*CloudInstance, error) { + opts := servers.ListOpts{ + Name: id, + } - client, err := openstack.NewComputeV2(o.provider, gophercloud.EndpointOpts{ - Region: os.Getenv("OS_REGION_NAME"), - }) + instances, err := getOpenStackInstances(o.provider, opts) if err != nil { - fmt.Println(err) + return nil, err } - opts := servers.ListOpts{} - - pager := servers.List(client, opts) - - err = pager.EachPage(func(page pagination.Page) (bool, error) { - serverList, err := servers.ExtractServers(page) - if err != nil { - fmt.Println(err) - return false, err - } - - for _, s := range serverList { - // fugly - ipv4 := "" - // For some instances IP is not assigned. - z := s.Addresses["public"] - if z != nil { - for _, v := range z.([]interface{}) { - sz := v.(map[string]interface{}) - version := sz["version"].(float64) - if version == 4 { - ipv4 = sz["addr"].(string) - } - } - } else { - ipv4 = "NA" - } - - cinstance := CloudInstance{ - ID: s.ID, - Name: s.Name, - PublicIps: []string{ipv4}, - Status: s.Status, - Created: s.Created.Format("2006-01-02 15:04:05"), - } - - cinstances = append(cinstances, cinstance) - } + if len(instances) == 0 { + return nil, ErrInstanceNotFound(id) + } - return true, nil - }) + return &instances[0], nil +} - return cinstances, nil +// GetInstances return all instances on OpenStack +// TODO +func (o *OpenStack) GetInstances(ctx *Context) ([]CloudInstance, error) { + return getOpenStackInstances(o.provider, servers.ListOpts{}) } // ListInstances lists instances on OpenStack. diff --git a/lepton/provider.go b/lepton/provider.go index e1c83a03..81119808 100644 --- a/lepton/provider.go +++ b/lepton/provider.go @@ -1,5 +1,12 @@ package lepton +import "fmt" + +var ( + // ErrInstanceNotFound is used when an instance doesn't exist in provider + ErrInstanceNotFound = func(id string) error { return fmt.Errorf("Instance with id %v not found", id) } +) + // Provider is an interface that provider must implement type Provider interface { Initialize() error @@ -17,6 +24,7 @@ type Provider interface { CreateInstance(ctx *Context) error ListInstances(ctx *Context) error GetInstances(ctx *Context) ([]CloudInstance, error) + GetInstanceByID(ctx *Context, id string) (*CloudInstance, error) DeleteInstance(ctx *Context, instancename string) error StopInstance(ctx *Context, instancename string) error StartInstance(ctx *Context, instancename string) error diff --git a/lepton/vsphere.go b/lepton/vsphere.go index 2365b0c8..7eccf57c 100644 --- a/lepton/vsphere.go +++ b/lepton/vsphere.go @@ -13,6 +13,7 @@ import ( "github.com/olekukonko/tablewriter" "github.com/vmware/govmomi/find" + "github.com/vmware/govmomi/property" "github.com/vmware/govmomi/govc/host/esxcli" "github.com/vmware/govmomi/object" @@ -418,6 +419,30 @@ func (v *Vsphere) CreateInstance(ctx *Context) error { return nil } +// GetInstanceByID returns the instance with the id passed by argument if it exists +func (v *Vsphere) GetInstanceByID(ctx *Context, id string) (*CloudInstance, error) { + m := view.NewManager(v.client) + + cv, err := m.CreateContainerView(context.TODO(), v.client.ServiceContent.RootFolder, []string{"VirtualMachine"}, true) + if err != nil { + return nil, err + } + + defer cv.Destroy(context.TODO()) + + var vms []mo.VirtualMachine + err = cv.RetrieveWithFilter(context.TODO(), []string{"VirtualMachine"}, []string{"summary"}, &vms, property.Filter{"name": id}) + if err != nil { + return nil, err + } + + if len(vms) == 0 { + return nil, ErrInstanceNotFound(id) + } + + return v.convertToCloudInstance(&vms[0]), nil +} + // GetInstances return all instances on vSphere // TODO func (v *Vsphere) GetInstances(ctx *Context) ([]CloudInstance, error) { @@ -439,24 +464,30 @@ func (v *Vsphere) GetInstances(ctx *Context) ([]CloudInstance, error) { } for _, vm := range vms { - cInstance := CloudInstance{ - Name: vm.Summary.Config.Name, - Status: string(vm.Summary.Runtime.PowerState), - } + cInstance := v.convertToCloudInstance(&vm) - if vm.Summary.Runtime.BootTime != nil { - cInstance.Created = vm.Summary.Runtime.BootTime.String() - } + cinstances = append(cinstances, *cInstance) + } - if cInstance.Status == "poweredOn" { - ip := v.ipFor(vm.Summary.Config.Name) - cInstance.PublicIps = []string{ip} - } + return cinstances, nil +} - cinstances = append(cinstances, cInstance) +func (v *Vsphere) convertToCloudInstance(vm *mo.VirtualMachine) *CloudInstance { + cInstance := CloudInstance{ + Name: vm.Summary.Config.Name, + Status: string(vm.Summary.Runtime.PowerState), } - return cinstances, nil + if vm.Summary.Runtime.BootTime != nil { + cInstance.Created = vm.Summary.Runtime.BootTime.String() + } + + if cInstance.Status == "poweredOn" { + ip := v.ipFor(vm.Summary.Config.Name) + cInstance.PublicIps = []string{ip} + } + + return &cInstance } // ListInstances lists instances on VSphere. diff --git a/lepton/vultr.go b/lepton/vultr.go index 4deac9bf..f33defe5 100644 --- a/lepton/vultr.go +++ b/lepton/vultr.go @@ -241,6 +241,11 @@ type vultrServer struct { Name string `json:"label"` } +// GetInstanceByID returns the instance with the id passed by argument if it exists +func (v *Vultr) GetInstanceByID(ctx *Context, id string) (*CloudInstance, error) { + return nil, errors.New("un-implemented") +} + // GetInstances return all instances on Vultr // TODO func (v *Vultr) GetInstances(ctx *Context) ([]CloudInstance, error) {