Unverified Commit 03318991 authored by David Gageot's avatar David Gageot Committed by GitHub

Merge pull request #4457 from exoscale/exoscale/fixes

Exoscale/fixes
parents cb2b414a a61bafea
......@@ -129,8 +129,8 @@
[[projects]]
name = "github.com/exoscale/egoscale"
packages = ["."]
revision = "5c191f1d82540498eb0552346f4a4db03a63e657"
version = "v0.9.14"
revision = "432a702ab7d709538572f9a2a42eaf0ca0691698"
version = "v0.9.23"
[[projects]]
name = "github.com/go-ini/ini"
......@@ -342,6 +342,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "4bf60991beceac36c64aa2749d5150563e6f1bd2a0ec1f45aa20438755b7f219"
inputs-digest = "719314934f3d0bb8163ba9d459ebb4135c14e92b1750c74a37ffa668102dc81c"
solver-name = "gps-cdcl"
solver-version = 1
......@@ -16,7 +16,7 @@
[[constraint]]
name = "github.com/exoscale/egoscale"
version = "0.9.14"
version = "0.9.23"
[[constraint]]
branch = "master"
......
......@@ -39,6 +39,7 @@ type Driver struct {
Password string
PublicKey string
UserDataFile string
UserData []byte
ID string `json:"Id"`
}
......@@ -199,6 +200,7 @@ func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
d.SSHUser = flags.String("exoscale-ssh-user")
d.SSHKey = flags.String("exoscale-ssh-key")
d.UserDataFile = flags.String("exoscale-userdata")
d.UserData = []byte(defaultCloudInit)
d.SetSwarmConfigFromFlags(flags)
if d.URL == "" {
......@@ -416,47 +418,55 @@ func (d *Driver) Create() error {
zone := zones.Zone[0].ID
log.Debugf("Availability zone %v = %s", d.AvailabilityZone, zone)
// Image UUID
var tpl string
// Image
template := egoscale.Template{
IsFeatured: true,
ZoneID: "1", // GVA2
}
resp, err = client.RequestWithContext(context.TODO(), &egoscale.ListTemplates{
TemplateFilter: "featured",
ZoneID: "1", // GVA2
})
templates, err := client.ListWithContext(context.TODO(), &template)
if err != nil {
return err
}
image := strings.ToLower(d.Image)
re := regexp.MustCompile(`^Linux (?P<name>.+?) (?P<version>[0-9.]+)\b`)
for _, template := range resp.(*egoscale.ListTemplatesResponse).Template {
for _, t := range templates {
tpl := t.(*egoscale.Template)
// Keep only 10GiB images
if template.Size>>30 != 10 {
if tpl.Size>>30 != 10 {
continue
}
fullname := strings.ToLower(template.Name)
fullname := strings.ToLower(tpl.Name)
if image == fullname {
tpl = template.ID
template = *tpl
break
}
submatch := re.FindStringSubmatch(template.Name)
submatch := re.FindStringSubmatch(tpl.Name)
if len(submatch) > 0 {
name := strings.Replace(strings.ToLower(submatch[1]), " ", "-", -1)
version := submatch[2]
shortname := fmt.Sprintf("%s-%s", name, version)
if image == shortname {
tpl = template.ID
template = *tpl
break
}
}
}
if tpl == "" {
if template.ID == "" {
return fmt.Errorf("Unable to find image %v", d.Image)
}
log.Debugf("Image %v(10) = %s", d.Image, tpl)
// Reading the username from the template
if name, ok := template.Details["username"]; ok {
d.SSHUser = name
}
log.Debugf("Image %v(10) = %s (%s)", d.Image, template.ID, d.SSHUser)
// Profile UUID
resp, err = client.RequestWithContext(context.TODO(), &egoscale.ListServiceOfferings{
......@@ -531,8 +541,11 @@ func (d *Driver) Create() error {
return fmt.Errorf("SSH Key pair creation failed %s", err)
}
keyPair := resp.(*egoscale.CreateSSHKeyPairResponse).KeyPair
if err = os.MkdirAll(filepath.Dir(d.GetSSHKeyPath()), 0750); err != nil {
return fmt.Errorf("Cannot create the folder to store the SSH private key. %s", err)
}
if err = ioutil.WriteFile(d.GetSSHKeyPath(), []byte(keyPair.PrivateKey), 0600); err != nil {
return fmt.Errorf("SSH public key could not be written %s", err)
return fmt.Errorf("SSH private key could not be written. %s", err)
}
d.KeyPair = keyPairName
} else {
......@@ -574,12 +587,13 @@ ssh_authorized_keys:
log.Debugf("%s", string(cloudInit))
// Base64 encode the userdata
userData := base64.StdEncoding.EncodeToString(cloudInit)
d.UserData = cloudInit
encodedUserData := base64.StdEncoding.EncodeToString(d.UserData)
req := &egoscale.DeployVirtualMachine{
TemplateID: tpl,
TemplateID: template.ID,
ServiceOfferingID: profile,
UserData: userData,
UserData: encodedUserData,
ZoneID: zone,
Name: d.MachineName,
KeyPair: d.KeyPair,
......@@ -688,9 +702,10 @@ func (d *Driver) Remove() error {
// Build a cloud-init user data string that will install and run
// docker.
func (d *Driver) getCloudInit() ([]byte, error) {
var err error
if d.UserDataFile != "" {
return ioutil.ReadFile(d.UserDataFile)
d.UserData, err = ioutil.ReadFile(d.UserDataFile)
}
return []byte(defaultCloudInit), nil
return d.UserData, err
}
......@@ -8,13 +8,22 @@ go:
- "1.10"
- tip
env:
- DEP_VERSION=0.4.1
cache: apt
before_install:
- curl -L -s https://github.com/golang/dep/releases/download/v${DEP_VERSION}/dep-linux-amd64 -o $GOPATH/bin/dep
- chmod +x $GOPATH/bin/dep
- npm i -g codeclimate-test-reporter
script:
- dep ensure
- go test -race -coverprofile=coverage.out -covermode=atomic .
after_success:
- codeclimate-test-reporter < coverage.out
- >
if [ "${TRAVIS_GO_VERSION}" = "1.10" ]; then
codeclimate-test-reporter < coverage.out;
fi
......@@ -5,3 +5,4 @@ Marc-Aurèle Brothier
Sebastien Goasguen
Yoan Blanc
Stefano Marengo
Pierre-Emmanuel Jacquier
Changelog
=========
0.9.23
------
- feat: `booleanResponse` supports true booleans: https://github.com/apache/cloudstack/pull/2428
0.9.22
------
- feat: `ListUsers`, `CreateUser`, `UpdateUser`
- feat: `ListResourceDetails`
- feat: `SecurityGroup` helper `RuleByID`
- feat: `Sign` signs the payload
- feat: `UpdateNetworkOffering`
- feat: `GetVirtualMachineUserData`
- feat: `EnableAccount` and `DisableAccount` (admin stuff)
- feat: `AsyncRequest` and `AsyncRequestWithContext` to examine the polling
- fix: `AuthorizeSecurityGroupIngress` support for ICMPv6
- change: move `APIName()` into the `Client`, nice godoc
- change: `Payload` doesn't sign the request anymore
- change: `Client` exposes more of its underlying data
- change: requests are sent as GET unless it body size is too big
0.9.21
------
- feat: `Network` is `Listable`
- feat: `Zone` is `Gettable`
- feat: `Client.Payload` to help preview the HTTP parameters
- feat: generate command utility
- fix: `CreateSnapshot` was missing the `Name` attribute
- fix: `ListSnapshots` was missing the `IDs` attribute
- fix: `ListZones` was missing the `NetworkType` attribute
- fix: `ListAsyncJobs` was missing the `ListAll` attribute
- change: ICMP Type/Code are uint8 and TCP/UDP port are uint16
0.9.20
------
- feat: `Template` is `Listable`
- feat: `IPAddress` is `Listable`
- change: `List` and `Paginate` return pointers
- fix: `Template` was missing `tags`
0.9.19
------
- feat: `SSHKeyPair` is `Listable`
0.9.18
------
- feat: `VirtualMachine` is `Listable`
- feat: new `Client.Paginate` and `Client.PaginateWithContext`
- change: the inner logic of `Listable`
- remove: not working `Client.AsyncList`
0.9.17
------
- fix: `AuthorizeSecurityGroup(In|E)gress` startport may be zero
0.9.16
------
- feat: new `Listable` interface
- feat: `Nic` is `Listable`
- feat: `Volume` is `Listable`
- feat: `Zone` is `Listable`
- feat: `AffinityGroup` is `Listable`
- remove: deprecated methods `ListNics`, `AddIPToNic`, and `RemoveIPFromNic`
- remove: deprecated method `GetRootVolumeForVirtualMachine`
0.9.15
------
- feat: `IPAddress` is `Gettable` and `Deletable`
- fix: serialization of *bool
0.9.14
------
......
package egoscale
const (
// UserAccount represents a User
UserAccount = iota
// AdminAccount represents an Admin
AdminAccount
// DomainAdminAccount represents a Domain Admin
DomainAdminAccount
)
// AccountType represents the type of an Account
//
// http://docs.cloudstack.apache.org/projects/cloudstack-administration/en/4.8/accounts.html#accounts-users-and-domains
type AccountType int64
func (*ListAccounts) name() string {
return "listAccounts"
}
// Account provides the detailed account information
type Account struct {
ID string `json:"id"`
AccountType AccountType `json:"accounttype,omitempty"`
AccountDetails string `json:"accountdetails,omitempty"`
CPUAvailable string `json:"cpuavailable,omitempty"`
CPULimit string `json:"cpulimit,omitempty"`
CPUTotal int64 `json:"cputotal,omitempty"`
DefaultZoneID string `json:"defaultzoneid,omitempty"`
Domain string `json:"domain,omitempty"`
DomainID string `json:"domainid,omitempty"`
EIPLimit string `json:"eiplimit,omitempty"`
Groups []string `json:"groups,omitempty"`
IPAvailable string `json:"ipavailable,omitempty"`
IPLimit string `json:"iplimit,omitempty"`
IPTotal int64 `json:"iptotal,omitempty"`
IsDefault bool `json:"isdefault,omitempty"`
MemoryAvailable string `json:"memoryavailable,omitempty"`
MemoryLimit string `json:"memorylimit,omitempty"`
MemoryTotal int64 `json:"memorytotal,omitempty"`
Name string `json:"name,omitempty"`
NetworkAvailable string `json:"networkavailable,omitempty"`
NetworkDomain string `json:"networkdomain,omitempty"`
NetworkLimit string `json:"networklimit,omitempty"`
NetworkTotal int16 `json:"networktotal,omitempty"`
PrimaryStorageAvailable string `json:"primarystorageavailable,omitempty"`
PrimaryStorageLimit string `json:"primarystoragelimit,omitempty"`
PrimaryStorageTotal int64 `json:"primarystoragetotal,omitempty"`
ProjectAvailable string `json:"projectavailable,omitempty"`
ProjectLimit string `json:"projectlimit,omitempty"`
ProjectTotal int64 `json:"projecttotal,omitempty"`
SecondaryStorageAvailable string `json:"secondarystorageavailable,omitempty"`
SecondaryStorageLimit string `json:"secondarystoragelimit,omitempty"`
SecondaryStorageTotal int64 `json:"secondarystoragetotal,omitempty"`
SnapshotAvailable string `json:"snapshotavailable,omitempty"`
SnapshotLimit string `json:"snapshotlimit,omitempty"`
SnapshotTotal int64 `json:"snapshottotal,omitempty"`
State string `json:"state,omitempty"`
TemplateAvailable string `json:"templateavailable,omitempty"`
TemplateLimit string `json:"templatelimit,omitempty"`
TemplateTotal int64 `json:"templatetotal,omitempty"`
VMAvailable string `json:"vmavailable,omitempty"`
VMLimit string `json:"vmlimit,omitempty"`
VMTotal int64 `json:"vmtotal,omitempty"`
VolumeAvailable string `json:"volumeavailable,omitempty"`
VolumeLimit string `json:"volumelimit,omitempty"`
VolumeTotal int64 `json:"volumetotal,omitempty"`
VPCAvailable string `json:"vpcavailable,omitempty"`
VPCLimit string `json:"vpclimit,omitempty"`
VPCTotal int64 `json:"vpctotal,omitempty"`
User []User `json:"user,omitempty"`
func (*ListAccounts) response() interface{} {
return new(ListAccountsResponse)
}
// ListAccounts represents a query to display the accounts
//
// CloudStack API: http://cloudstack.apache.org/api/apidocs-4.10/apis/listAccounts.html
type ListAccounts struct {
AccountType int64 `json:"accounttype,omitempty"`
DomainID string `json:"domainid,omitempty"`
ID string `json:"id,omitempty"`
IsCleanUpRequired *bool `json:"iscleanuprequired,omitempty"`
IsRecursive *bool `json:"isrecursive,omitempty"`
Keyword string `json:"keyword,omitempty"`
ListAll *bool `json:"listall,omitempty"`
Page int `json:"page,omitempty"`
PageSize int `json:"pagesize,omitempty"`
State string `json:"state,omitempty"`
func (*EnableAccount) name() string {
return "enableAccount"
}
// APIName returns the CloudStack API command name
func (*ListAccounts) APIName() string {
return "listAccounts"
func (*EnableAccount) response() interface{} {
return new(EnableAccountResponse)
}
func (*ListAccounts) response() interface{} {
return new(ListAccountsResponse)
func (*DisableAccount) name() string {
return "disableAccount"
}
// ListAccountsResponse represents a list of accounts
type ListAccountsResponse struct {
Count int `json:"count"`
Account []Account `json:"account"`
func (*DisableAccount) asyncResponse() interface{} {
return new(DisableAccountResponse)
}
This diff is collapsed.
// Code generated by "stringer -type AccountType"; DO NOT EDIT.
package egoscale
import "strconv"
const _AccountType_name = "UserAccountAdminAccountDomainAdminAccount"
var _AccountType_index = [...]uint8{0, 11, 23, 41}
func (i AccountType) String() string {
if i < 0 || i >= AccountType(len(_AccountType_index)-1) {
return "AccountType(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _AccountType_name[_AccountType_index[i]:_AccountType_index[i+1]]
}
package egoscale
import "net"
// IPAddress represents an IP Address
type IPAddress struct {
ID string `json:"id"`
Account string `json:"account,omitempty"`
AllocatedAt string `json:"allocated,omitempty"`
AssociatedNetworkID string `json:"associatednetworkid,omitempty"`
AssociatedNetworkName string `json:"associatednetworkname,omitempty"`
DomainID string `json:"domainid,omitempty"`
DomainName string `json:"domainname,omitempty"`
ForDisplay bool `json:"fordisplay,omitempty"`
ForVirtualNetwork bool `json:"forvirtualnetwork,omitempty"`
IPAddress net.IP `json:"ipaddress"`
IsElastic bool `json:"iselastic,omitempty"`
IsPortable bool `json:"isportable,omitempty"`
IsSourceNat bool `json:"issourcenat,omitempty"`
IsSystem bool `json:"issystem,omitempty"`
NetworkID string `json:"networkid,omitempty"`
PhysicalNetworkID string `json:"physicalnetworkid,omitempty"`
Project string `json:"project,omitempty"`
ProjectID string `json:"projectid,omitempty"`
Purpose string `json:"purpose,omitempty"`
State string `json:"state,omitempty"`
VirtualMachineDisplayName string `json:"virtualmachinedisplayname,omitempty"`
VirtualMachineID string `json:"virtualmachineid,omitempty"`
VirtualMachineName string `json:"virtualmachineName,omitempty"`
VlanID string `json:"vlanid,omitempty"`
VlanName string `json:"vlanname,omitempty"`
VMIPAddress net.IP `json:"vmipaddress,omitempty"`
VpcID string `json:"vpcid,omitempty"`
ZoneID string `json:"zoneid,omitempty"`
ZoneName string `json:"zonename,omitempty"`
Tags []ResourceTag `json:"tags,omitempty"`
JobID string `json:"jobid,omitempty"`
JobStatus JobStatusType `json:"jobstatus,omitempty"`
import (
"context"
"fmt"
"github.com/jinzhu/copier"
)
// Get fetches the resource
func (ipaddress *IPAddress) Get(ctx context.Context, client *Client) error {
if ipaddress.ID == "" && ipaddress.IPAddress == nil {
return fmt.Errorf("An IPAddress may only be searched using ID or IPAddress")
}
req := &ListPublicIPAddresses{
ID: ipaddress.ID,
IPAddress: ipaddress.IPAddress,
Account: ipaddress.Account,
DomainID: ipaddress.DomainID,
ProjectID: ipaddress.ProjectID,
ZoneID: ipaddress.ZoneID,
}
if ipaddress.IsElastic {
req.IsElastic = &(ipaddress.IsElastic)
}
resp, err := client.RequestWithContext(ctx, req)
if err != nil {
return err
}
ips := resp.(*ListPublicIPAddressesResponse)
count := len(ips.PublicIPAddress)
if count == 0 {
return &ErrorResponse{
ErrorCode: ParamError,
ErrorText: fmt.Sprintf("PublicIPAddress not found. id: %s, ipaddress: %s", ipaddress.ID, ipaddress.IPAddress),
}
} else if count > 1 {
return fmt.Errorf("More than one PublicIPAddress was found")
}
return copier.Copy(ipaddress, ips.PublicIPAddress[0])
}
// Delete removes the resource
func (ipaddress *IPAddress) Delete(ctx context.Context, client *Client) error {
if ipaddress.ID == "" {
return fmt.Errorf("An IPAddress may only be deleted using ID")
}
return client.BooleanRequestWithContext(ctx, &DisassociateIPAddress{
ID: ipaddress.ID,
})
}
// ResourceType returns the type of the resource
......@@ -43,23 +61,8 @@ func (*IPAddress) ResourceType() string {
return "PublicIpAddress"
}
// AssociateIPAddress (Async) represents the IP creation
//
// CloudStack API: https://cloudstack.apache.org/api/apidocs-4.10/apis/associateIpAddress.html
type AssociateIPAddress struct {
Account string `json:"account,omitempty"`
DomainID string `json:"domainid,omitempty"`
ForDisplay *bool `json:"fordisplay,omitempty"`
IsPortable *bool `json:"isportable,omitempty"`
NetworkdID string `json:"networkid,omitempty"`
ProjectID string `json:"projectid,omitempty"`
RegionID string `json:"regionid,omitempty"`
VpcID string `json:"vpcid,omitempty"`
ZoneID string `json:"zoneid,omitempty"`
}
// APIName returns the CloudStack API command name
func (*AssociateIPAddress) APIName() string {
// name returns the CloudStack API command name
func (*AssociateIPAddress) name() string {
return "associateIpAddress"
}
......@@ -67,77 +70,24 @@ func (*AssociateIPAddress) asyncResponse() interface{} {
return new(AssociateIPAddressResponse)
}
// AssociateIPAddressResponse represents the response to the creation of an IPAddress
type AssociateIPAddressResponse struct {
IPAddress IPAddress `json:"ipaddress"`
}
// DisassociateIPAddress (Async) represents the IP deletion
//
// CloudStack API: https://cloudstack.apache.org/api/apidocs-4.10/apis/disassociateIpAddress.html
type DisassociateIPAddress struct {
ID string `json:"id"`
}
// APIName returns the CloudStack API command name
func (*DisassociateIPAddress) APIName() string {
// name returns the CloudStack API command name
func (*DisassociateIPAddress) name() string {
return "disassociateIpAddress"
}
func (*DisassociateIPAddress) asyncResponse() interface{} {
return new(booleanAsyncResponse)
}
// UpdateIPAddress (Async) represents the IP modification
//
// CloudStack API: https://cloudstack.apache.org/api/apidocs-4.10/apis/updateIpAddress.html
type UpdateIPAddress struct {
ID string `json:"id"`
CustomID string `json:"customid,omitempty"` // root only
ForDisplay *bool `json:"fordisplay,omitempty"`
return new(booleanResponse)
}
// APIName returns the CloudStack API command name
func (*UpdateIPAddress) APIName() string {
// name returns the CloudStack API command name
func (*UpdateIPAddress) name() string {
return "updateIpAddress"
}
func (*UpdateIPAddress) asyncResponse() interface{} {
return new(UpdateIPAddressResponse)
}
// UpdateIPAddressResponse represents the modified IP Address
type UpdateIPAddressResponse AssociateIPAddressResponse
// ListPublicIPAddresses represents a search for public IP addresses
//
// CloudStack API: https://cloudstack.apache.org/api/apidocs-4.10/apis/listPublicIpAddresses.html
type ListPublicIPAddresses struct {
Account string `json:"account,omitempty"`
AllocatedOnly *bool `json:"allocatedonly,omitempty"`
AllocatedNetworkID string `json:"allocatednetworkid,omitempty"`
DomainID string `json:"domainid,omitempty"`
ForDisplay *bool `json:"fordisplay,omitempty"`
ForLoadBalancing *bool `json:"forloadbalancing,omitempty"`
ForVirtualNetwork string `json:"forvirtualnetwork,omitempty"`
ID string `json:"id,omitempty"`
IPAddress net.IP `json:"ipaddress,omitempty"`
IsElastic *bool `json:"iselastic,omitempty"`
IsRecursive *bool `json:"isrecursive,omitempty"`
IsSourceNat *bool `json:"issourcenat,omitempty"`
IsStaticNat *bool `json:"isstaticnat,omitempty"`
Keyword string `json:"keyword,omitempty"`
ListAll *bool `json:"listall,omitempty"`
Page int `json:"page,omitempty"`
PageSize int `json:"pagesize,omitempty"`
PhysicalNetworkID string `json:"physicalnetworkid,omitempty"`
ProjectID string `json:"projectid,omitempty"`
Tags []ResourceTag `json:"tags,omitempty"`
VlanID string `json:"vlanid,omitempty"`
VpcID string `json:"vpcid,omitempty"`
ZoneID string `json:"zoneid,omitempty"`
}
// APIName returns the CloudStack API command name
func (*ListPublicIPAddresses) APIName() string {
// name returns the CloudStack API command name
func (*ListPublicIPAddresses) name() string {
return "listPublicIpAddresses"
}
......@@ -145,8 +95,48 @@ func (*ListPublicIPAddresses) response() interface{} {
return new(ListPublicIPAddressesResponse)
}
// ListPublicIPAddressesResponse represents a list of public IP addresses
type ListPublicIPAddressesResponse struct {
Count int `json:"count"`
PublicIPAddress []IPAddress `json:"publicipaddress"`
// ListRequest builds the ListAdresses request
func (ipaddress *IPAddress) ListRequest() (ListCommand, error) {
req := &ListPublicIPAddresses{
Account: ipaddress.Account,
AllocatedNetworkID: ipaddress.AssociatedNetworkID,
DomainID: ipaddress.DomainID,
ForDisplay: &ipaddress.ForDisplay,
//ForVirtualNetwork: ip.ForVirtualNetwork, change ForVirtualNetwork type for type bool
ID: ipaddress.ID,
IPAddress: ipaddress.IPAddress,
IsElastic: &ipaddress.IsElastic,
IsSourceNat: &ipaddress.IsSourceNat,
PhysicalNetworkID: ipaddress.PhysicalNetworkID,
ProjectID: ipaddress.ProjectID,
VlanID: ipaddress.VlanID,
VpcID: ipaddress.VpcID,
ZoneID: ipaddress.ZoneID,
}