From 574f6571fe9e555de348c7a13a248954790d874a Mon Sep 17 00:00:00 2001 From: He Guimin Date: Mon, 11 Dec 2017 09:15:10 +0800 Subject: [PATCH] add new resource alicloud_container_cluster --- CHANGELOG.md | 63 ++- alicloud/config.go | 68 +++- alicloud/errors.go | 52 ++- .../import_alicloud_container_cluster_test.go | 29 ++ alicloud/provider.go | 1 + .../resource_alicloud_container_cluster.go | 258 ++++++++++++ ...esource_alicloud_container_cluster_test.go | 162 ++++++++ .../resource_alicloud_ess_scalinggroup.go | 58 ++- ...resource_alicloud_ess_scalinggroup_test.go | 5 +- .../resource_alicloud_security_group_rule.go | 86 ++-- ...ource_alicloud_security_group_rule_test.go | 133 ++++++- alicloud/service_alicloud_ecs.go | 268 +++++++------ alicloud/service_alicloud_rds.go | 8 +- alicloud/validators.go | 371 +++++++++++++++++- .../alicloud-container-cluster/README.md | 18 + .../alicloud-container-cluster/main.tf | 41 ++ .../alicloud-container-cluster/outputs.tf | 14 + .../alicloud-container-cluster/variables.tf | 48 +++ .../denverdino/aliyungo/common/client.go | 163 +++++++- .../denverdino/aliyungo/common/regions.go | 7 +- .../denverdino/aliyungo/common/request.go | 6 +- .../denverdino/aliyungo/cs/client.go | 192 +++++++++ .../denverdino/aliyungo/cs/clusters.go | 188 +++++++++ .../denverdino/aliyungo/cs/projects.go | 186 +++++++++ .../denverdino/aliyungo/cs/projects_client.go | 156 ++++++++ .../denverdino/aliyungo/cs/services.go | 143 +++++++ .../denverdino/aliyungo/cs/signature.go | 60 +++ .../denverdino/aliyungo/cs/volumes.go | 89 +++++ .../denverdino/aliyungo/ecs/client.go | 76 +++- .../denverdino/aliyungo/ecs/disks.go | 24 ++ .../github.com/denverdino/aliyungo/ecs/eni.go | 108 +++++ .../denverdino/aliyungo/ecs/images.go | 9 + .../denverdino/aliyungo/ecs/instances.go | 35 +- .../aliyungo/ecs/router_interface.go | 30 ++ .../aliyungo/ecs/security_groups.go | 9 +- .../denverdino/aliyungo/ecs/snapshots.go | 2 + .../denverdino/aliyungo/ess/configuration.go | 19 + .../denverdino/aliyungo/ess/group.go | 34 +- .../denverdino/aliyungo/ess/rule.go | 22 ++ .../denverdino/aliyungo/ram/client.go | 21 +- .../denverdino/aliyungo/rds/instances.go | 7 +- .../denverdino/aliyungo/slb/loadbalancers.go | 2 + .../denverdino/aliyungo/util/encoding.go | 12 +- vendor/vendor.json | 60 +-- website/alicloud.erb | 127 +++++- .../docs/r/container_cluster.html.markdown | 53 +++ 46 files changed, 3203 insertions(+), 320 deletions(-) create mode 100644 alicloud/import_alicloud_container_cluster_test.go create mode 100644 alicloud/resource_alicloud_container_cluster.go create mode 100644 alicloud/resource_alicloud_container_cluster_test.go create mode 100644 examples/container-cluster/alicloud-container-cluster/README.md create mode 100644 examples/container-cluster/alicloud-container-cluster/main.tf create mode 100644 examples/container-cluster/alicloud-container-cluster/outputs.tf create mode 100644 examples/container-cluster/alicloud-container-cluster/variables.tf create mode 100644 vendor/github.com/denverdino/aliyungo/cs/client.go create mode 100644 vendor/github.com/denverdino/aliyungo/cs/clusters.go create mode 100644 vendor/github.com/denverdino/aliyungo/cs/projects.go create mode 100644 vendor/github.com/denverdino/aliyungo/cs/projects_client.go create mode 100644 vendor/github.com/denverdino/aliyungo/cs/services.go create mode 100644 vendor/github.com/denverdino/aliyungo/cs/signature.go create mode 100644 vendor/github.com/denverdino/aliyungo/cs/volumes.go create mode 100644 vendor/github.com/denverdino/aliyungo/ecs/eni.go create mode 100644 website/docs/r/container_cluster.html.markdown diff --git a/CHANGELOG.md b/CHANGELOG.md index 35a38f11fdd..eb444f78322 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,29 +1,58 @@ -## 0.1.1 (Unreleased) +## 1.0.0 (Unreleased) IMPROVEMENTS: -- *New Resource:* _alicloud_oss_bucket_ [GH-10] -- *New Resource*: _alicloud_oss_bucket_object [GH-14] -- *New output_file* option for data sources: export data to a specified file [GH-29] -- resource/rds: add ability to change instance password [GH-17] -- resource/rds: Add ability to import existing RDS resources [GH-16] -- datasource/alicloud_zones: Add more options for filtering [GH-19] +- *New Resource*: _alicloud_container_cluster_ [GH-47] +- *New Resource:* _alicloud_ram_policy_ [GH-46] +- *New Resource*: _alicloud_ram_user_policy_attachment_ [GH-46] +- *New Resource* _alicloud_ram_user_ [GH-44] +- *New Datasource* _alicloud_ram_policies_ [GH-46] +- *New Datasource* _alicloud_ram_users_ [GH-44] + +- Added support for importing: + - `alicloud_container_cluster` [GH-47] + - `alicloud_ram_policy` [GH-46] + - `alicloud_ram_user` [GH-44] + + +## 0.1.1 (December 11, 2017) + +IMPROVEMENTS: + +- *New Resource:* _alicloud_key_pair_ ([#27](https://github.com/terraform-providers/terraform-provider-alicloud/pull/27)) +- *New Resource*: _alicloud_key_pair_attachment_ ([#28](https://github.com/terraform-providers/terraform-provider-alicloud/pull/28)) +- *New Resource*: _alicloud_router_interface_ ([#40](https://github.com/terraform-providers/terraform-provider-alicloud/pull/40)) +- *New Resource:* _alicloud_oss_bucket_ ([#10](https://github.com/terraform-providers/terraform-provider-alicloud/pull/10)) +- *New Resource*: _alicloud_oss_bucket_object_ ([#14](https://github.com/terraform-providers/terraform-provider-alicloud/pull/14)) +- *New Datasource* _alicloud_key_pairs_ ([#30](https://github.com/terraform-providers/terraform-provider-alicloud/pull/30)) +- *New Datasource* _alicloud_vpcs_ ([#34](https://github.com/terraform-providers/terraform-provider-alicloud/pull/34)) +- *New output_file* option for data sources: export data to a specified file ([#29](https://github.com/terraform-providers/terraform-provider-alicloud/pull/29)) +- resource/instance:add new parameter `key_name` ([#31](https://github.com/terraform-providers/terraform-provider-alicloud/pull/31)) +- resource/route_entry: new nexthop type 'RouterInterface' for route entry ([#41](https://github.com/terraform-providers/terraform-provider-alicloud/pull/41)) +- resource/security_group_rule: Remove `cidr_ip` contribute "ConflictsWith" ([#39](https://github.com/terraform-providers/terraform-provider-alicloud/pull/39)) +- resource/rds: add ability to change instance password ([#17](https://github.com/terraform-providers/terraform-provider-alicloud/pull/17)) +- resource/rds: Add ability to import existing RDS resources ([#16](https://github.com/terraform-providers/terraform-provider-alicloud/pull/16)) +- datasource/alicloud_zones: Add more options for filtering ([#19](https://github.com/terraform-providers/terraform-provider-alicloud/pull/19)) - Added support for importing: - - `alicloud_nat_gateway` - - `alicloud_ess_schedule` - - `alicloud_ess_scaling_group` - - `alicloud_instance` - - `alicloud_eip` - - `alicloud_disk` + - `alicloud_vpc` ([#32](https://github.com/terraform-providers/terraform-provider-alicloud/pull/32)) + - `alicloud_route_entry` ([#33](https://github.com/terraform-providers/terraform-provider-alicloud/pull/33)) + - `alicloud_nat_gateway` ([#26](https://github.com/terraform-providers/terraform-provider-alicloud/pull/26)) + - `alicloud_ess_schedule` ([#25](https://github.com/terraform-providers/terraform-provider-alicloud/pull/25)) + - `alicloud_ess_scaling_group` ([#24](https://github.com/terraform-providers/terraform-provider-alicloud/pull/24)) + - `alicloud_instance` ([#23](https://github.com/terraform-providers/terraform-provider-alicloud/pull/23)) + - `alicloud_eip` ([#22](https://github.com/terraform-providers/terraform-provider-alicloud/pull/22)) + - `alicloud_disk` ([#21](https://github.com/terraform-providers/terraform-provider-alicloud/pull/21)) BUG FIXES: -- resource/disk_attachment: Fix issue attaching multiple disks and set disk_attachment's parameter 'device_name' as deprecated [GH-9] -- resource/rds: Fix diff error about rds security_ips [GH-13] -- resource/security_group_rule: Fix diff error when authorizing security group rules [GH-15] +- resource/disk_attachment: Fix issue attaching multiple disks and set disk_attachment's parameter 'device_name' as deprecated ([#9](https://github.com/terraform-providers/terraform-provider-alicloud/pull/9)) +- resource/rds: Fix diff error about rds security_ips ([#13](https://github.com/terraform-providers/terraform-provider-alicloud/pull/13)) +- resource/security_group_rule: Fix diff error when authorizing security group rules ([#15](https://github.com/terraform-providers/terraform-provider-alicloud/pull/15)) +- resource/security_group_rule: Fix diff bug by modifying 'DestCidrIp' to 'DestGroupId' when running read ([#35](https://github.com/terraform-providers/terraform-provider-alicloud/pull/35)) + ## 0.1.0 (June 20, 2017) NOTES: -* Same functionality as that of Terraform 0.9.8. Repacked as part of [Provider Splitout](https://www.hashicorp.com/blog/upcoming-provider-changes-in-terraform-0-10/) +* Same functionality as that of Terraform 0.9.8. Repacked as part of [Provider Splitout](https://www.hashicorp.com/blog/upcoming-provider-changes-in-terraform-0-10/) \ No newline at end of file diff --git a/alicloud/config.go b/alicloud/config.go index 4743ac9e4d9..5f14c34d3cf 100644 --- a/alicloud/config.go +++ b/alicloud/config.go @@ -4,12 +4,17 @@ import ( "fmt" "github.com/aliyun/aliyun-oss-go-sdk/oss" + "github.com/denverdino/aliyungo/cdn" "github.com/denverdino/aliyungo/common" + "github.com/denverdino/aliyungo/dns" "github.com/denverdino/aliyungo/ecs" "github.com/denverdino/aliyungo/ess" "github.com/denverdino/aliyungo/location" + "github.com/denverdino/aliyungo/ram" "github.com/denverdino/aliyungo/rds" "github.com/denverdino/aliyungo/slb" + + "github.com/denverdino/aliyungo/cs" "github.com/hashicorp/terraform/terraform" "log" "strings" @@ -33,6 +38,10 @@ type AliyunClient struct { vpcconn *ecs.Client slbconn *slb.Client ossconn *oss.Client + dnsconn *dns.Client + ramconn ram.RamClientInterface + csconn *cs.Client + cdnconn *cdn.CdnClient } // Client for AliyunClient @@ -72,11 +81,27 @@ func (c *Config) Client() (*AliyunClient, error) { if err != nil { return nil, err } - ossconn, err := c.ossConn() if err != nil { return nil, err } + dnsconn, err := c.dnsConn() + if err != nil { + return nil, err + } + ramconn, err := c.ramConn() + if err != nil { + return nil, err + } + csconn, err := c.csConn() + if err != nil { + return nil, err + } + cdnconn, err := c.cdnConn() + if err != nil { + return nil, err + } + return &AliyunClient{ Region: c.Region, ecsconn: ecsconn, @@ -86,6 +111,10 @@ func (c *Config) Client() (*AliyunClient, error) { rdsconn: rdsconn, essconn: essconn, ossconn: ossconn, + dnsconn: dnsconn, + ramconn: ramconn, + csconn: csconn, + cdnconn: cdnconn, }, nil } @@ -116,9 +145,7 @@ func (c *Config) ecsConn() (*ecs.Client, error) { client.SetBusinessInfo(BusinessInfoKey) client.SetUserAgent(getUserAgent()) - _, err := client.DescribeRegions() - - if err != nil { + if _, err := client.DescribeRegions(); err != nil { return nil, err } @@ -166,17 +193,46 @@ func (c *Config) ossConn() (*oss.Client, error) { return nil, fmt.Errorf("Describe endpoint using region: %#v got an error: %#v.", c.Region, err) } endpointItem := endpoints.Endpoints.Endpoint + var endpoint string if endpointItem == nil || len(endpointItem) <= 0 { - return nil, fmt.Errorf("Cannot find endpoint in the region: %#v", c.Region) + // return nil, fmt.Errorf("Cannot find endpoint in the region: %#v", c.Region") + log.Printf("Cannot find endpoint in the region: %#v", c.Region) + endpoint = "" + } else { + endpoint = strings.ToLower(endpointItem[0].Protocols.Protocols[0]) + "://" + endpointItem[0].Endpoint } - endpoint := strings.ToLower(endpointItem[0].Protocols.Protocols[0]) + "://" + endpointItem[0].Endpoint log.Printf("[DEBUG] Instantiate OSS client using endpoint: %#v", endpoint) client, err := oss.New(endpoint, c.AccessKey, c.SecretKey, oss.UserAgent(getUserAgent())) return client, err } +func (c *Config) dnsConn() (*dns.Client, error) { + client := dns.NewClientNew(c.AccessKey, c.SecretKey) + client.SetBusinessInfo(BusinessInfoKey) + client.SetUserAgent(getUserAgent()) + return client, nil +} + +func (c *Config) ramConn() (ram.RamClientInterface, error) { + client := ram.NewClient(c.AccessKey, c.SecretKey) + return client, nil +} + +func (c *Config) csConn() (*cs.Client, error) { + client := cs.NewClient(c.AccessKey, c.SecretKey) + client.SetUserAgent(getUserAgent()) + return client, nil +} + +func (c *Config) cdnConn() (*cdn.CdnClient, error) { + client := cdn.NewClient(c.AccessKey, c.SecretKey) + client.SetBusinessInfo(BusinessInfoKey) + client.SetUserAgent(getUserAgent()) + return client, nil +} + func getUserAgent() string { return fmt.Sprintf("HashiCorp-Terraform-v%s", terraform.VersionString()) } diff --git a/alicloud/errors.go b/alicloud/errors.go index e30b1e3b889..468a8b5efd4 100644 --- a/alicloud/errors.go +++ b/alicloud/errors.go @@ -23,9 +23,12 @@ const ( InstanceIncorrectStatus = "IncorrectInstanceStatus" HaVipIncorrectStatus = "IncorrectHaVipStatus" // slb - LoadBalancerNotFound = "InvalidLoadBalancerId.NotFound" - UnsupportedProtocalPort = "UnsupportedOperationonfixedprotocalport" - + LoadBalancerNotFound = "InvalidLoadBalancerId.NotFound" + UnsupportedProtocalPort = "UnsupportedOperationonfixedprotocalport" + ListenerNotFound = "The specified resource does not exist" + ListenerAlreadyExists = "ListenerAlreadyExists" + ServiceIsConfiguring = "ServiceIsConfiguring" + BackendServerconfiguring = "BackendServer.configuring" // security_group InvalidInstanceIdAlreadyExists = "InvalidInstanceId.AlreadyExists" InvalidSecurityGroupIdNotFound = "InvalidSecurityGroupId.NotFound" @@ -41,10 +44,15 @@ const ( VpcQuotaExceeded = "QuotaExceeded.Vpc" // vswitch VswitcInvalidRegionId = "InvalidRegionId.NotFound" + //vroute entry + IncorrectRouteEntryStatus = "IncorrectRouteEntryStatus" + TaskConflict = "TaskConflict" + RouterEntryForbbiden = "Forbbiden" // ess InvalidScalingGroupIdNotFound = "InvalidScalingGroupId.NotFound" IncorrectScalingConfigurationLifecycleState = "IncorrectScalingConfigurationLifecycleState" + IncorrectScalingGroupStatus = "IncorrectScalingGroupStatus" // oss OssBucketNotFound = "NoSuchBucket" @@ -54,6 +62,32 @@ const ( RamInstanceNotFound = "Forbidden.InstanceNotFound" AliyunGoClientFailure = "AliyunGoClientFailure" + // dns + RecordForbiddenDNSChange = "RecordForbidden.DNSChange" + FobiddenNotEmptyGroup = "Fobidden.NotEmptyGroup" + + // ram user + DeleteConflictUserGroup = "DeleteConflict.User.Group" + DeleteConflictUserAccessKey = "DeleteConflict.User.AccessKey" + DeleteConflictUserLoginProfile = "DeleteConflict.User.LoginProfile" + DeleteConflictUserMFADevice = "DeleteConflict.User.MFADevice" + DeleteConflictUserPolicy = "DeleteConflict.User.Policy" + + // ram mfa + DeleteConflictVirtualMFADeviceUser = "DeleteConflict.VirtualMFADevice.User" + + // ram group + DeleteConflictGroupUser = "DeleteConflict.Group.User" + DeleteConflictGroupPolicy = "DeleteConflict.Group.Policy" + + // ram role + DeleteConflictRolePolicy = "DeleteConflict.Role.Policy" + + // ram policy + DeleteConflictPolicyUser = "DeleteConflict.Policy.User" + DeleteConflictPolicyGroup = "DeleteConflict.Policy.Group" + DeleteConflictPolicyVersion = "DeleteConflict.Policy.Version" + //unknown Error UnknownError = "UnknownError" @@ -67,6 +101,11 @@ const ( // cdn ServiceBusy = "ServiceBusy" + // + InvalidRamRoleNotFound = "InvalidRamRole.NotFound" + RoleAttachmentUnExpectedJson = "unexpected end of JSON input" + InvalidInstanceIdNotFound = "InvalidInstanceId.NotFound" + RouterInterfaceIncorrectStatus = "IncorrectStatus" DependencyViolationRouterInterfaceReferedByRouteEntry = "DependencyViolation.RouterInterfaceReferedByRouteEntry" ) @@ -98,3 +137,10 @@ func IsExceptedError(err error, expectCode string) bool { return false } + +func RamEntityNotExist(err error) bool { + if e, ok := err.(*common.Error); ok && strings.Contains(e.Code, "EntityNotExist") { + return true + } + return false +} diff --git a/alicloud/import_alicloud_container_cluster_test.go b/alicloud/import_alicloud_container_cluster_test.go new file mode 100644 index 00000000000..8e347ff5d00 --- /dev/null +++ b/alicloud/import_alicloud_container_cluster_test.go @@ -0,0 +1,29 @@ +package alicloud + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccAlicloudContainerCluster_importBasic(t *testing.T) { + resourceName := "alicloud_container_cluster.cs_vpc" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckContainerClusterDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccContainerCluster_vpc, + }, + + resource.TestStep{ + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"cidr_block", "disk_category", "disk_size", "image_id", "instance_type", "password"}, + }, + }, + }) +} diff --git a/alicloud/provider.go b/alicloud/provider.go index f3e28518dc8..968f7faf0a6 100644 --- a/alicloud/provider.go +++ b/alicloud/provider.go @@ -67,6 +67,7 @@ func Provider() terraform.ResourceProvider { "alicloud_oss_bucket_object": resourceAlicloudOssBucketObject(), "alicloud_key_pair": resourceAlicloudKeyPair(), "alicloud_key_pair_attachment": resourceAlicloudKeyPairAttachment(), + "alicloud_container_cluster": resourceAlicloudContainerCluster(), "alicloud_router_interface": resourceAlicloudRouterInterface(), }, diff --git a/alicloud/resource_alicloud_container_cluster.go b/alicloud/resource_alicloud_container_cluster.go new file mode 100644 index 00000000000..eb247440821 --- /dev/null +++ b/alicloud/resource_alicloud_container_cluster.go @@ -0,0 +1,258 @@ +package alicloud + +import ( + "fmt" + "github.com/denverdino/aliyungo/cs" + "github.com/denverdino/aliyungo/ecs" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "time" +) + +func resourceAlicloudContainerCluster() *schema.Resource { + return &schema.Resource{ + Create: resourceAlicloudContainerClusterCreate, + Read: resourceAlicloudContainerClusterRead, + Update: resourceAlicloudContainerClusterUpdate, + Delete: resourceAlicloudContainerClusterDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validateContainerClusterName, + ConflictsWith: []string{"name_prefix"}, + }, + "name_prefix": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validateContainerClusterNamePrefix, + }, + "size": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 1, + ValidateFunc: validateIntegerInRange(1, 20), + }, + "cidr_block": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "instance_type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateInstanceType, + }, + "vswitch_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "password": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + Sensitive: true, + }, + "disk_size": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + Default: 20, + ValidateFunc: validateIntegerInRange(20, 32768), + }, + "disk_category": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: ecs.DiskCategoryCloudEfficiency, + ForceNew: true, + ValidateFunc: validateDiskCategory, + }, + "image_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "network_mode": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceAlicloudContainerClusterCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*AliyunClient) + conn := client.csconn + + // Ensure instance_type is generation three + _, err := meta.(*AliyunClient).CheckParameterValidity(d, meta) + if err != nil { + return err + } + + var clusterName string + if v, ok := d.GetOk("name"); ok { + clusterName = v.(string) + } else if v, ok := d.GetOk("name_prefix"); ok { + clusterName = resource.PrefixedUniqueId(v.(string)) + } else { + clusterName = resource.UniqueId() + } + + args := &cs.ClusterCreationArgs{ + Name: clusterName, + InstanceType: d.Get("instance_type").(string), + Password: d.Get("password").(string), + Size: int64(d.Get("size").(int)), + IOOptimized: ecs.IoOptimized("true"), + DataDiskCategory: ecs.DiskCategory(d.Get("disk_category").(string)), + DataDiskSize: int64(d.Get("disk_size").(int)), + } + + if v, ok := d.GetOk("vswitch_id"); ok && v.(string) != "" { + cidr, cidr_ok := d.GetOk("cidr_block") + if !cidr_ok || cidr.(string) == "" { + return fmt.Errorf("When launching container in the VPC, the 'cidr_block' must be specified.") + } + args.NetworkMode = cs.VPCNetwork + args.VSwitchID = v.(string) + args.SubnetCIDR = cidr.(string) + + vswInfo, _, err := client.vpcconn.DescribeVSwitches(&ecs.DescribeVSwitchesArgs{ + RegionId: getRegion(d, meta), + VSwitchId: v.(string), + }) + if err != nil { + return fmt.Errorf("Error DescribeVSwitches: %#v", err) + } + if len(vswInfo) < 1 { + return fmt.Errorf("There is not found specified vswitch: %s, please check and try again.", v.(string)) + } + args.VPCID = vswInfo[0].VpcId + } else { + args.NetworkMode = cs.ClassicNetwork + } + + if imageId, ok := d.GetOk("image_id"); ok { + connection := client.ecsconn + argsImage := &ecs.DescribeImagesArgs{ + RegionId: getRegion(d, meta), + ImageId: imageId.(string), + } + if _, _, err := connection.DescribeImages(argsImage); err != nil { + return err + } + + args.ECSImageID = imageId.(string) + } + + region := getRegion(d, meta) + cluster, err := conn.CreateCluster(region, args) + + if err != nil { + return fmt.Errorf("Creating container Cluster got an error: %#v", err) + } + + err = conn.WaitForClusterAsyn(cluster.ClusterID, cs.Running, 500) + + if err != nil { + return fmt.Errorf("Waitting for container Cluster %#v got an error: %#v", cs.Running, err) + } + + d.SetId(cluster.ClusterID) + + return resourceAlicloudContainerClusterUpdate(d, meta) +} + +func resourceAlicloudContainerClusterUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AliyunClient).csconn + d.Partial(true) + if d.HasChange("size") && !d.IsNewResource() { + o, n := d.GetChange("size") + oi := o.(int) + ni := n.(int) + if ni <= oi { + return fmt.Errorf("The new size of clusters must greater than the current. The cluster's current size is %d.", oi) + } + d.SetPartial("size") + err := conn.ResizeCluster(d.Id(), &cs.ClusterResizeArgs{ + Size: int64(ni), + InstanceType: d.Get("instance_type").(string), + Password: d.Get("password").(string), + DataDiskCategory: ecs.DiskCategory(d.Get("disk_category").(string)), + DataDiskSize: int64(d.Get("disk_size").(int)), + ECSImageID: d.Get("image_id").(string), + IOOptimized: ecs.IoOptimized("true"), + }) + if err != nil { + return fmt.Errorf("Resize Cluster got an error: %#v", err) + } + + err = conn.WaitForClusterAsyn(d.Id(), cs.Running, 500) + + if err != nil { + return fmt.Errorf("Waitting for container Cluster %#v got an error: %#v", cs.Running, err) + } + } + d.Partial(false) + + return resourceAlicloudContainerClusterRead(d, meta) +} + +func resourceAlicloudContainerClusterRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AliyunClient).csconn + + cluster, err := conn.DescribeCluster(d.Id()) + + if err != nil { + return err + } + + d.Set("name", cluster.Name) + d.Set("size", cluster.Size) + d.Set("network_mode", cluster.NetworkMode) + if cluster.VPCID != "" { + d.Set("vpc_id", cluster.VPCID) + } + if cluster.VSwitchID != "" { + d.Set("vswitch_id", cluster.VSwitchID) + } + + return nil +} + +func resourceAlicloudContainerClusterDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AliyunClient).csconn + + return resource.Retry(3*time.Minute, func() *resource.RetryError { + err := conn.DeleteCluster(d.Id()) + if err != nil { + if IsExceptedError(err, ErrorClusterNotFound) { + return nil + } + return resource.RetryableError(fmt.Errorf("Cluster in use 1- trying again while it is deleted.")) + } + + resp, err := conn.DescribeCluster(d.Id()) + if err != nil { + if IsExceptedError(err, ErrorClusterNotFound) { + return nil + } + return resource.NonRetryableError(fmt.Errorf("Deleting container cluster got an error: %#v", err)) + } + if resp.ClusterID == "" { + return nil + } + + return resource.RetryableError(fmt.Errorf("Cluster in use 2- trying again while it is deleted.")) + }) +} diff --git a/alicloud/resource_alicloud_container_cluster_test.go b/alicloud/resource_alicloud_container_cluster_test.go new file mode 100644 index 00000000000..a50a179264d --- /dev/null +++ b/alicloud/resource_alicloud_container_cluster_test.go @@ -0,0 +1,162 @@ +package alicloud + +import ( + "testing" + + "fmt" + "github.com/denverdino/aliyungo/cs" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "log" +) + +func TestAccAlicloudContainerCluster_classic(t *testing.T) { + var container cs.ClusterType + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + + IDRefreshName: "alicloud_container_cluster.cs_classic", + + Providers: testAccProviders, + CheckDestroy: testAccCheckContainerClusterDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccContainerCluster_classic, + Check: resource.ComposeTestCheckFunc( + testAccCheckContainerClusterExists("alicloud_container_cluster.cs_classic", &container), + resource.TestCheckResourceAttr("alicloud_container_cluster.cs_classic", "size", "2"), + ), + }, + }, + }) +} + +func TestAccAlicloudContainerCluster_vpc(t *testing.T) { + var container cs.ClusterType + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + + IDRefreshName: "alicloud_container_cluster.cs_vpc", + + Providers: testAccProviders, + CheckDestroy: testAccCheckContainerClusterDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccContainerCluster_vpc, + Check: resource.ComposeTestCheckFunc( + testAccCheckContainerClusterExists("alicloud_container_cluster.cs_vpc", &container), + resource.TestCheckResourceAttr("alicloud_container_cluster.cs_vpc", "size", "2"), + ), + }, + }, + }) +} + +func testAccCheckContainerClusterExists(n string, d *cs.ClusterType) resource.TestCheckFunc { + return func(s *terraform.State) error { + cluster, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found:%s", n) + } + + if cluster.Primary.ID == "" { + return fmt.Errorf("No Container cluster ID is set") + } + + client := testAccProvider.Meta().(*AliyunClient).csconn + attr, err := client.DescribeCluster(cluster.Primary.ID) + log.Printf("[DEBUG] check cluster %s attribute %#v", cluster.Primary.ID, attr) + + if err != nil { + return err + } + + if attr.ClusterID == "" { + return fmt.Errorf("DB Instance not found") + } + + *d = attr + return nil + } +} + +func testAccCheckContainerClusterDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*AliyunClient).csconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "alicloud_container_cluster" { + continue + } + + cluster, err := client.DescribeCluster(rs.Primary.ID) + + if err != nil { + if IsExceptedError(err, ErrorClusterNotFound) { + return nil + } + return err + } + + if cluster.ClusterID == "" { + return nil + } + } + + return nil +} + +const testAccContainerCluster_classic = ` +data "alicloud_images" main { + most_recent = true + name_regex = "^centos_6\\w{1,5}[64].*" +} + +resource "alicloud_container_cluster" "cs_classic" { + password = "Just$test" + instance_type = "ecs.n4.small" + name = "ClusterOfClassicTest" + size = 2 + disk_category = "cloud_efficiency" + disk_size = 20 + image_id = "${data.alicloud_images.main.images.0.id}" +} +` +const testAccContainerCluster_vpc = ` +data "alicloud_images" main { + most_recent = true + name_regex = "^centos_6\\w{1,5}[64].*" +} + +data "alicloud_zones" main { + available_resource_creation = "VSwitch" +} + +resource "alicloud_vpc" "foo" { + name = "tf_test_image" + cidr_block = "10.1.0.0/21" +} + +resource "alicloud_vswitch" "foo" { + vpc_id = "${alicloud_vpc.foo.id}" + cidr_block = "10.1.1.0/24" + availability_zone = "${data.alicloud_zones.main.zones.0.id}" +} + +resource "alicloud_container_cluster" "cs_vpc" { + password = "Just$test" + instance_type = "ecs.n4.small" + name = "ClusterOfVpcTest" + size = 2 + disk_category = "cloud_efficiency" + disk_size = 20 + cidr_block = "172.18.0.0/24" + image_id = "${data.alicloud_images.main.images.0.id}" + vswitch_id = "${alicloud_vswitch.foo.id}" +} +` diff --git a/alicloud/resource_alicloud_ess_scalinggroup.go b/alicloud/resource_alicloud_ess_scalinggroup.go index f188dc2f698..006f7242c4d 100644 --- a/alicloud/resource_alicloud_ess_scalinggroup.go +++ b/alicloud/resource_alicloud_ess_scalinggroup.go @@ -2,12 +2,9 @@ package alicloud import ( "fmt" - "github.com/denverdino/aliyungo/common" "github.com/denverdino/aliyungo/ess" - "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "strings" - "time" ) func resourceAlicloudEssScalingGroup() *schema.Resource { @@ -91,7 +88,7 @@ func resourceAliyunEssScalingGroupRead(d *schema.ResourceData, meta interface{}) scaling, err := client.DescribeScalingGroupById(d.Id()) if err != nil { - if e, ok := err.(*common.Error); ok && e.Code == InstanceNotfound { + if NotFoundError(err) { d.SetId("") return nil } @@ -102,7 +99,7 @@ func resourceAliyunEssScalingGroupRead(d *schema.ResourceData, meta interface{}) d.Set("max_size", scaling.MaxSize) d.Set("scaling_group_name", scaling.ScalingGroupName) d.Set("default_cooldown", scaling.DefaultCooldown) - d.Set("removal_policies", scaling.RemovalPolicies) + d.Set("removal_policies", scaling.RemovalPolicies.RemovalPolicy) d.Set("db_instance_ids", scaling.DBInstanceIds) d.Set("loadbalancer_ids", scaling.LoadBalancerId) @@ -115,69 +112,64 @@ func resourceAliyunEssScalingGroupUpdate(d *schema.ResourceData, meta interface{ args := &ess.ModifyScalingGroupArgs{ ScalingGroupId: d.Id(), } + d.Partial(true) if d.HasChange("scaling_group_name") { args.ScalingGroupName = d.Get("scaling_group_name").(string) + d.SetPartial("scaling_group_name") } if d.HasChange("min_size") { - args.MinSize = d.Get("min_size").(int) + minsize := d.Get("min_size").(int) + args.MinSize = &minsize + d.SetPartial("min_size") } if d.HasChange("max_size") { - args.MaxSize = d.Get("max_size").(int) + maxsize := d.Get("max_size").(int) + args.MaxSize = &maxsize + d.SetPartial("max_size") } if d.HasChange("default_cooldown") { - args.DefaultCooldown = d.Get("default_cooldown").(int) + cooldown := d.Get("default_cooldown").(int) + args.DefaultCooldown = &cooldown + d.SetPartial("default_cooldown") } if d.HasChange("removal_policies") { policyStrings := d.Get("removal_policies").([]interface{}) args.RemovalPolicy = expandStringList(policyStrings) + d.SetPartial("removal_policies") } if _, err := conn.ModifyScalingGroup(args); err != nil { return err } + d.Partial(false) + return resourceAliyunEssScalingGroupRead(d, meta) } func resourceAliyunEssScalingGroupDelete(d *schema.ResourceData, meta interface{}) error { - client := meta.(*AliyunClient) - - return resource.Retry(2*time.Minute, func() *resource.RetryError { - err := client.DeleteScalingGroupById(d.Id()) - - if err != nil { - e, _ := err.(*common.Error) - if e.ErrorResponse.Code != InvalidScalingGroupIdNotFound { - return resource.RetryableError(fmt.Errorf("Scaling group in use - trying again while it is deleted.")) - } - } - _, err = client.DescribeScalingGroupById(d.Id()) - if err != nil { - if notFoundError(err) { - return nil - } - return resource.NonRetryableError(err) - } - - return resource.RetryableError(fmt.Errorf("Scaling group in use - trying again while it is deleted.")) - }) + return meta.(*AliyunClient).DeleteScalingGroupById(d.Id()) } func buildAlicloudEssScalingGroupArgs(d *schema.ResourceData, meta interface{}) (*ess.CreateScalingGroupArgs, error) { client := meta.(*AliyunClient) args := &ess.CreateScalingGroupArgs{ - RegionId: getRegion(d, meta), - MinSize: d.Get("min_size").(int), - MaxSize: d.Get("max_size").(int), - DefaultCooldown: d.Get("default_cooldown").(int), + RegionId: getRegion(d, meta), } + minsize := d.Get("min_size").(int) + maxsize := d.Get("max_size").(int) + cooldown := d.Get("default_cooldown").(int) + args.MinSize = &minsize + args.MaxSize = &maxsize + args.DefaultCooldown = &cooldown + if v := d.Get("scaling_group_name").(string); v != "" { args.ScalingGroupName = v } diff --git a/alicloud/resource_alicloud_ess_scalinggroup_test.go b/alicloud/resource_alicloud_ess_scalinggroup_test.go index e707035b1f0..b973c60a770 100644 --- a/alicloud/resource_alicloud_ess_scalinggroup_test.go +++ b/alicloud/resource_alicloud_ess_scalinggroup_test.go @@ -210,7 +210,7 @@ func testAccCheckEssScalingGroupDestroy(s *terraform.State) error { if err != nil { // Verify the error is what we want e, _ := err.(*common.Error) - if e.ErrorResponse.Code == InstanceNotfound { + if e.Code == InstanceNotFound { continue } return err @@ -287,8 +287,7 @@ resource "alicloud_ess_scaling_configuration" "foo" { enable = true image_id = "${data.alicloud_images.ecs_image.images.0.id}" - instance_type = "ecs.n1.medium" - io_optimized = "optimized" + instance_type = "ecs.n4.large" system_disk_category = "cloud_efficiency" internet_charge_type = "PayByTraffic" internet_max_bandwidth_out = 10 diff --git a/alicloud/resource_alicloud_security_group_rule.go b/alicloud/resource_alicloud_security_group_rule.go index f728b465350..a8b2f32aff1 100644 --- a/alicloud/resource_alicloud_security_group_rule.go +++ b/alicloud/resource_alicloud_security_group_rule.go @@ -5,6 +5,7 @@ import ( "github.com/denverdino/aliyungo/ecs" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" + "strconv" "strings" "time" ) @@ -43,6 +44,7 @@ func resourceAliyunSecurityGroupRule() *schema.Resource { Type: schema.TypeString, Optional: true, ForceNew: true, + Default: GroupRulePolicyAccept, ValidateFunc: validateSecurityRulePolicy, }, @@ -56,6 +58,7 @@ func resourceAliyunSecurityGroupRule() *schema.Resource { Type: schema.TypeInt, Optional: true, ForceNew: true, + Default: 1, ValidateFunc: validateSecurityPriority, }, @@ -96,6 +99,8 @@ func resourceAliyunSecurityGroupRuleCreate(d *schema.ResourceData, meta interfac ptl := d.Get("ip_protocol").(string) port := d.Get("port_range").(string) nicType := d.Get("nic_type").(string) + policy := d.Get("policy").(string) + priority := d.Get("priority").(int) if _, ok := d.GetOk("cidr_ip"); !ok { if _, ok := d.GetOk("source_security_group_id"); !ok { @@ -104,14 +109,14 @@ func resourceAliyunSecurityGroupRuleCreate(d *schema.ResourceData, meta interfac } var autherr error - switch GroupRuleDirection(direction) { - case GroupRuleIngress: + switch ecs.Direction(direction) { + case ecs.DirectionIngress: args, err := buildAliyunSecurityIngressArgs(d, meta) if err != nil { return err } autherr = conn.AuthorizeSecurityGroup(args) - case GroupRuleEgress: + case ecs.DirectionEgress: args, err := buildAliyunSecurityEgressArgs(d, meta) if err != nil { return err @@ -133,7 +138,7 @@ func resourceAliyunSecurityGroupRuleCreate(d *schema.ResourceData, meta interfac } else { cidr_ip = d.Get("source_security_group_id").(string) } - d.SetId(sgId + ":" + direction + ":" + ptl + ":" + port + ":" + nicType + ":" + cidr_ip) + d.SetId(sgId + ":" + direction + ":" + ptl + ":" + port + ":" + nicType + ":" + cidr_ip + ":" + policy + ":" + strconv.Itoa(priority)) return resourceAliyunSecurityGroupRuleRead(d, meta) } @@ -141,14 +146,24 @@ func resourceAliyunSecurityGroupRuleCreate(d *schema.ResourceData, meta interfac func resourceAliyunSecurityGroupRuleRead(d *schema.ResourceData, meta interface{}) error { client := meta.(*AliyunClient) parts := strings.Split(d.Id(), ":") + policy := parseSecurityRuleId(d, meta, 6) + strPriority := parseSecurityRuleId(d, meta, 7) + var priority int + if policy == "" || strPriority == "" { + policy = d.Get("policy").(string) + priority = d.Get("priority").(int) + d.SetId(d.Id() + ":" + policy + ":" + strconv.Itoa(priority)) + } else { + prior, err := strconv.Atoi(strPriority) + if err != nil { + return fmt.Errorf("SecrityGroupRuleRead parse rule id gets an error: %#v", err) + } + priority = prior + } sgId := parts[0] direction := parts[1] - ip_protocol := parts[2] - port_range := parts[3] - nic_type := parts[4] - cidr_ip := parts[5] - rules, err := client.DescribeSecurityByAttr(sgId, direction, nic_type) + rule, err := client.DescribeSecurityGroupRule(sgId, direction, parts[2], parts[3], parts[4], parts[5], policy, priority) if err != nil { if NotFoundError(err) { d.SetId("") @@ -157,26 +172,6 @@ func resourceAliyunSecurityGroupRuleRead(d *schema.ResourceData, meta interface{ return fmt.Errorf("Error SecurityGroup rule: %#v", err) } - // Filter security group rule according to its attribute - var rule ecs.PermissionType - for _, ru := range rules.Permissions.Permission { - if strings.ToLower(string(ru.IpProtocol)) == ip_protocol && ru.PortRange == port_range { - cidr := ru.SourceCidrIp - if GroupRuleDirection(direction) == GroupRuleIngress && cidr == "" { - cidr = ru.SourceGroupId - } - if GroupRuleDirection(direction) == GroupRuleEgress { - if cidr = ru.DestCidrIp; cidr == "" { - cidr = ru.DestGroupId - } - } - if cidr == cidr_ip { - rule = ru - break - } - } - } - d.Set("type", rule.Direction) d.Set("ip_protocol", strings.ToLower(string(rule.IpProtocol))) d.Set("nic_type", rule.NicType) @@ -185,7 +180,7 @@ func resourceAliyunSecurityGroupRuleRead(d *schema.ResourceData, meta interface{ d.Set("priority", rule.Priority) d.Set("security_group_id", sgId) //support source and desc by type - if GroupRuleDirection(direction) == GroupRuleIngress { + if ecs.Direction(direction) == ecs.DirectionIngress { d.Set("cidr_ip", rule.SourceCidrIp) d.Set("source_security_group_id", rule.SourceGroupId) d.Set("source_group_owner_account", rule.SourceGroupOwnerAccount) @@ -194,7 +189,6 @@ func resourceAliyunSecurityGroupRuleRead(d *schema.ResourceData, meta interface{ d.Set("source_security_group_id", rule.DestGroupId) d.Set("source_group_owner_account", rule.DestGroupOwnerAccount) } - return nil } @@ -202,7 +196,7 @@ func deleteSecurityGroupRule(d *schema.ResourceData, meta interface{}) error { client := meta.(*AliyunClient) ruleType := d.Get("type").(string) - if GroupRuleDirection(ruleType) == GroupRuleIngress { + if ecs.Direction(ruleType) == ecs.DirectionIngress { args, err := buildAliyunSecurityIngressArgs(d, meta) if err != nil { return err @@ -227,7 +221,20 @@ func deleteSecurityGroupRule(d *schema.ResourceData, meta interface{}) error { func resourceAliyunSecurityGroupRuleDelete(d *schema.ResourceData, meta interface{}) error { client := meta.(*AliyunClient) parts := strings.Split(d.Id(), ":") - sgId, direction, ip_protocol, port_range, nic_type := parts[0], parts[1], parts[2], parts[3], parts[4] + policy := parseSecurityRuleId(d, meta, 6) + strPriority := parseSecurityRuleId(d, meta, 7) + var priority int + if policy == "" || strPriority == "" { + policy = d.Get("policy").(string) + priority = d.Get("priority").(int) + d.SetId(d.Id() + ":" + policy + ":" + strconv.Itoa(priority)) + } else { + prior, err := strconv.Atoi(strPriority) + if err != nil { + return fmt.Errorf("SecrityGroupRuleRead parse rule id gets an error: %#v", err) + } + priority = prior + } return resource.Retry(5*time.Minute, func() *resource.RetryError { err := deleteSecurityGroupRule(d, meta) @@ -236,7 +243,7 @@ func resourceAliyunSecurityGroupRuleDelete(d *schema.ResourceData, meta interfac resource.RetryableError(fmt.Errorf("Security group rule in use - trying again while it is deleted.")) } - _, err = client.DescribeSecurityGroupRule(sgId, direction, nic_type, ip_protocol, port_range) + _, err = client.DescribeSecurityGroupRule(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], policy, priority) if err != nil { if NotFoundError(err) { return nil @@ -371,3 +378,14 @@ func buildAliyunSecurityEgressArgs(d *schema.ResourceData, meta interface{}) (*e return args, nil } + +func parseSecurityRuleId(d *schema.ResourceData, meta interface{}, index int) (result string) { + parts := strings.Split(d.Id(), ":") + defer func() { + if e := recover(); e != nil { + fmt.Printf("Panicing %s\r\n", e) + result = "" + } + }() + return parts[index] +} diff --git a/alicloud/resource_alicloud_security_group_rule_test.go b/alicloud/resource_alicloud_security_group_rule_test.go index 2b3f965e3bf..231086d0262 100644 --- a/alicloud/resource_alicloud_security_group_rule_test.go +++ b/alicloud/resource_alicloud_security_group_rule_test.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/terraform/terraform" "log" "regexp" + "strconv" "strings" "testing" ) @@ -270,6 +271,45 @@ func TestAccAlicloudSecurityGroupRule_Multi(t *testing.T) { } +func TestAccAlicloudSecurityGroupRule_MultiAttri(t *testing.T) { + var pt ecs.PermissionType + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + + // module name + IDRefreshName: "alicloud_security_group_rule.ingress_allow_tcp_22_sg", + Providers: testAccProviders, + CheckDestroy: testAccCheckSecurityGroupRuleDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccSecurityGroupRuleMultiAttri, + Check: resource.ComposeTestCheckFunc( + testAccCheckSecurityGroupRuleExists( + "alicloud_security_group_rule.ingress_allow_tcp_22_sg", &pt), + resource.TestCheckResourceAttr( + "alicloud_security_group_rule.ingress_allow_tcp_22_sg", "port_range", "22/22"), + testAccCheckSecurityGroupRuleExists( + "alicloud_security_group_rule.ingress_allow_tcp_22", &pt), + testAccCheckSecurityGroupRuleExists( + "alicloud_security_group_rule.ingress_deny_tcp_22", &pt), + testAccCheckSecurityGroupRuleExists( + "alicloud_security_group_rule.ingress_allow_tcp_22_prior", &pt), + testAccCheckSecurityGroupRuleExists( + "alicloud_security_group_rule.ingress_deny_tcp_22_prior", &pt), + resource.TestCheckResourceAttr( + "alicloud_security_group_rule.ingress_deny_tcp_22_prior", + "port_range", + "22/22"), + ), + }, + }, + }) + +} + func testAccCheckSecurityGroupRuleExists(n string, m *ecs.PermissionType) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -284,8 +324,11 @@ func testAccCheckSecurityGroupRuleExists(n string, m *ecs.PermissionType) resour client := testAccProvider.Meta().(*AliyunClient) log.Printf("[WARN]get sg rule %s", rs.Primary.ID) parts := strings.Split(rs.Primary.ID, ":") - // securityGroupId, direction, nicType, ipProtocol, portRange - rule, err := client.DescribeSecurityGroupRule(parts[0], parts[1], parts[4], parts[2], parts[3]) + prior, err := strconv.Atoi(parts[7]) + if err != nil { + return fmt.Errorf("testSecrityGroupRuleExists parse rule id gets an error: %#v", err) + } + rule, err := client.DescribeSecurityGroupRule(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], parts[6], prior) if err != nil { return err @@ -309,7 +352,11 @@ func testAccCheckSecurityGroupRuleDestroy(s *terraform.State) error { } parts := strings.Split(rs.Primary.ID, ":") - rule, err := client.DescribeSecurityGroupRule(parts[0], parts[1], parts[4], parts[2], parts[3]) + prior, err := strconv.Atoi(parts[7]) + if err != nil { + return fmt.Errorf("testSecrityGroupRuleDestroy parse rule id gets an error: %#v", err) + } + rule, err := client.DescribeSecurityGroupRule(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], parts[6], prior) if rule != nil { return fmt.Errorf("Error SecurityGroup Rule still exist") @@ -497,3 +544,83 @@ resource "alicloud_security_group_rule" "ingress" { source_security_group_id = "${alicloud_security_group.foo.id}" } ` +const testAccSecurityGroupRuleMultiAttri = ` +variable "name" { + default = "test_ssh" +} + +variable "source_cidr_blocks" { + type = "list" + default = ["0.0.0.0/0"] +} + + +resource "alicloud_vpc" "vpc" { + name = "test-ssh" + cidr_block = "172.16.0.0/24" +} + +resource "alicloud_security_group" "main" { + name = "${var.name}" + vpc_id = "${alicloud_vpc.vpc.id}" +} +resource "alicloud_security_group" "source" { + name = "${var.name}-2" + vpc_id = "${alicloud_vpc.vpc.id}" +} + +resource "alicloud_security_group_rule" "ingress_allow_tcp_22_sg" { + type = "ingress" + ip_protocol = "tcp" + nic_type = "intranet" + policy = "accept" + port_range = "22/22" + priority = 1 + security_group_id = "${alicloud_security_group.main.id}" + source_security_group_id = "${alicloud_security_group.source.id}" +} + +resource "alicloud_security_group_rule" "ingress_allow_tcp_22" { + count = "${length(var.source_cidr_blocks)}" + type = "ingress" + ip_protocol = "tcp" + nic_type = "intranet" + policy = "accept" + port_range = "22/22" + priority = 1 + security_group_id = "${alicloud_security_group.main.id}" + cidr_ip = "${element(var.source_cidr_blocks, count.index)}" +} + +resource "alicloud_security_group_rule" "ingress_deny_tcp_22" { + type = "ingress" + ip_protocol = "tcp" + nic_type = "intranet" + policy = "drop" + port_range = "22/22" + priority = 1 + security_group_id = "${alicloud_security_group.main.id}" + cidr_ip = "0.0.0.0/0" +} + +resource "alicloud_security_group_rule" "ingress_allow_tcp_22_prior" { + type = "ingress" + ip_protocol = "tcp" + nic_type = "intranet" + policy = "accept" + port_range = "22/22" + priority = 100 + security_group_id = "${alicloud_security_group.main.id}" + cidr_ip = "0.0.0.0/0" +} +resource "alicloud_security_group_rule" "ingress_deny_tcp_22_prior" { + type = "ingress" + ip_protocol = "tcp" + nic_type = "intranet" + policy = "drop" + port_range = "22/22" + priority = 100 + security_group_id = "${alicloud_security_group.main.id}" + cidr_ip = "0.0.0.0/0" +} +` diff --git a/alicloud/service_alicloud_ecs.go b/alicloud/service_alicloud_ecs.go index 8ee0bb0bfca..850bf3a6bc8 100644 --- a/alicloud/service_alicloud_ecs.go +++ b/alicloud/service_alicloud_ecs.go @@ -6,6 +6,7 @@ import ( "github.com/denverdino/aliyungo/common" "github.com/denverdino/aliyungo/ecs" "github.com/hashicorp/terraform/helper/schema" + "log" "strings" ) @@ -222,29 +223,36 @@ func (client *AliyunClient) DescribeSecurity(securityGroupId string) (*ecs.Descr return client.ecsconn.DescribeSecurityGroupAttribute(args) } -func (client *AliyunClient) DescribeSecurityByAttr(securityGroupId, direction, nicType string) (*ecs.DescribeSecurityGroupAttributeResponse, error) { - - args := &ecs.DescribeSecurityGroupAttributeArgs{ +func (client *AliyunClient) DescribeSecurityGroupRule(groupId, direction, ipProtocol, portRange, nicType, cidr_ip, policy string, priority int) (*ecs.PermissionType, error) { + rules, err := client.ecsconn.DescribeSecurityGroupAttribute(&ecs.DescribeSecurityGroupAttributeArgs{ RegionId: client.Region, - SecurityGroupId: securityGroupId, - Direction: direction, + SecurityGroupId: groupId, + Direction: ecs.Direction(direction), NicType: ecs.NicType(nicType), - } - - return client.ecsconn.DescribeSecurityGroupAttribute(args) -} + }) -func (client *AliyunClient) DescribeSecurityGroupRule(securityGroupId, direction, nicType, ipProtocol, portRange string) (*ecs.PermissionType, error) { - sg, err := client.DescribeSecurityByAttr(securityGroupId, direction, nicType) if err != nil { return nil, err } - for _, p := range sg.Permissions.Permission { - if strings.ToLower(string(p.IpProtocol)) == ipProtocol && p.PortRange == portRange { - return &p, nil + for _, ru := range rules.Permissions.Permission { + if strings.ToLower(string(ru.IpProtocol)) == ipProtocol && ru.PortRange == portRange { + cidr := ru.SourceCidrIp + if ecs.Direction(direction) == ecs.DirectionIngress && cidr == "" { + cidr = ru.SourceGroupId + } + if ecs.Direction(direction) == ecs.DirectionEgress { + if cidr = ru.DestCidrIp; cidr == "" { + cidr = ru.DestGroupId + } + } + + if cidr == cidr_ip && strings.ToLower(string(ru.Policy)) == policy && ru.Priority == priority { + return &ru, nil + } } } + return nil, GetNotFoundErrorFromString("Security group rule not found") } @@ -295,14 +303,70 @@ func (client *AliyunClient) CheckParameterValidity(d *schema.ResourceData, meta // Retrieve series instance type family ioOptimized := ecs.IoOptimizedOptimized var expectedFamilies []string - outdatedFamiliesMap, expectedFamiliesMap, err := client.FetchSpecifiedInstanceTypeFamily(getRegion(d, meta), zoneId, []string{GenerationOne, GenerationTwo}, zones) + mapOutdatedInstanceFamilies, mapUpgradedInstanceFamilies, err := client.FetchSpecifiedInstanceTypeFamily(getRegion(d, meta), zoneId, []string{GenerationOne, GenerationTwo}, zones) if err != nil { return nil, err } - for key := range expectedFamiliesMap { + for key := range mapUpgradedInstanceFamilies { expectedFamilies = append(expectedFamilies, key) } + validData := make(map[ResourceKeyType]interface{}) + mapZones := make(map[string]ecs.ZoneType) + mapSupportedInstanceTypes := make(map[string]string) + mapUpgradedInstanceTypes := make(map[string]string) + mapOutdatedInstanceTypes := make(map[string]string) + mapOutdatedDiskCategories := make(map[ecs.DiskCategory]ecs.DiskCategory) + mapDiskCategories := make(map[ecs.DiskCategory]ecs.DiskCategory) + for _, zone := range zones { + //Filter and get all instance types in the zones + for _, insType := range zone.AvailableInstanceTypes.InstanceTypes { + if _, ok := mapSupportedInstanceTypes[insType]; !ok { + insTypeSplit := strings.Split(insType, DOT_SEPARATED) + mapSupportedInstanceTypes[insType] = string(insTypeSplit[0] + DOT_SEPARATED + insTypeSplit[1]) + } + } + if len(zone.AvailableDiskCategories.DiskCategories) < 1 { + continue + } + //Filter and get all instance types in the zones + for _, category := range zone.AvailableDiskCategories.DiskCategories { + if _, ok := SupportedDiskCategory[category]; ok { + mapDiskCategories[category] = category + } + if _, ok := OutdatedDiskCategory[category]; ok { + mapOutdatedDiskCategories[category] = category + } + } + resources := zone.AvailableResources.ResourcesInfo + if len(resources) < 1 { + continue + } + mapZones[zone.ZoneId] = zone + } + //separate all instance types according generation 3 in the zones + for key, _ := range mapSupportedInstanceTypes { + find := false + for out, _ := range mapOutdatedInstanceFamilies { + if strings.HasPrefix(key, out) { + mapOutdatedInstanceTypes[key] = out + mapSupportedInstanceTypes[key] = out + find = true + break + } + } + if find { + continue + } + for upgrade, _ := range mapUpgradedInstanceFamilies { + if strings.HasPrefix(key, upgrade) { + mapUpgradedInstanceTypes[key] = upgrade + mapSupportedInstanceTypes[key] = upgrade + break + } + } + } + var instanceType string if insType, ok := d.GetOk("available_instance_type"); ok { instanceType = insType.(string) @@ -310,35 +374,36 @@ func (client *AliyunClient) CheckParameterValidity(d *schema.ResourceData, meta instanceType = insType.(string) } if instanceType != "" { - - instanceTypeSplit := strings.Split(instanceType, DOT_SEPARATED) - prefix := string(instanceTypeSplit[0] + DOT_SEPARATED + instanceTypeSplit[1]) + if !strings.HasPrefix(instanceType, "ecs.") { + return nil, fmt.Errorf("Invalid instance_type: %s. Please modify it and try again.", instanceType) + } var instanceTypeObject ecs.InstanceTypeItemType - _, outdatedOk := outdatedFamiliesMap[prefix] - _, expectedOk := expectedFamiliesMap[prefix] - if outdatedOk || expectedOk { - mapInstanceTypes, err := client.FetchSpecifiedInstanceTypesByFamily(zoneId, prefix, zones) + targetFamily, ok := mapSupportedInstanceTypes[instanceType] + if ok { + mapInstanceTypes, err := client.FetchSpecifiedInstanceTypesByFamily(zoneId, targetFamily, zones) if err != nil { return nil, err } - if value, ok := mapInstanceTypes[instanceType]; ok { - instanceTypeObject = value - } + var validInstanceTypes []string for key, value := range mapInstanceTypes { if instanceType == key { instanceTypeObject = mapInstanceTypes[key] break } - validInstanceTypes = append(validInstanceTypes, fmt.Sprintf("%s(%dCPU,%.0fGB)", key, value.CpuCoreCount, value.MemorySize)) + core := "Core" + if value.CpuCoreCount > 1 { + core = "Cores" + } + validInstanceTypes = append(validInstanceTypes, fmt.Sprintf("%s(%d%s,%.0fGB)", key, value.CpuCoreCount, core, value.MemorySize)) } if instanceTypeObject.InstanceTypeId == "" { if zoneId == "" { return nil, fmt.Errorf("Instance type %s is not supported in the region %s. Expected instance types of family %s: %s.", - instanceType, getRegion(d, meta), prefix, strings.Join(validInstanceTypes, ", ")) + instanceType, getRegion(d, meta), targetFamily, strings.Join(validInstanceTypes, ", ")) } return nil, fmt.Errorf("Instance type %s is not supported in the availability zone %s. Expected instance types of family %s: %s.", - instanceType, zoneId, prefix, strings.Join(validInstanceTypes, ", ")) + instanceType, zoneId, targetFamily, strings.Join(validInstanceTypes, ", ")) } outDisk := false @@ -346,6 +411,9 @@ func (client *AliyunClient) CheckParameterValidity(d *schema.ResourceData, meta _, outDisk = OutdatedDiskCategory[ecs.DiskCategory(disk.(string))] } + _, outdatedOk := mapOutdatedInstanceTypes[instanceType] + _, expectedOk := mapUpgradedInstanceTypes[instanceType] + if expectedOk && outDisk { return nil, fmt.Errorf("Instance type %s can't support 'cloud' as instance system disk. "+ "Please change your disk category to efficient disk '%s' or '%s'.", instanceType, ecs.DiskCategoryCloudSSD, ecs.DiskCategoryCloudEfficiency) @@ -353,16 +421,20 @@ func (client *AliyunClient) CheckParameterValidity(d *schema.ResourceData, meta if outdatedOk { var expectedEqualCpus []string var expectedEqualMoreCpus []string - for _, fam := range expectedFamilies { + for _, fam := range mapUpgradedInstanceTypes { mapInstanceTypes, err := client.FetchSpecifiedInstanceTypesByFamily(zoneId, fam, zones) if err != nil { return nil, err } for _, value := range mapInstanceTypes { + core := "Core" + if value.CpuCoreCount > 1 { + core = "Cores" + } if instanceTypeObject.CpuCoreCount == value.CpuCoreCount { - expectedEqualCpus = append(expectedEqualCpus, fmt.Sprintf("%s(%dCPU,%.0fGB)", value.InstanceTypeId, value.CpuCoreCount, value.MemorySize)) + expectedEqualCpus = append(expectedEqualCpus, fmt.Sprintf("%s(%d%s,%.0fGB)", value.InstanceTypeId, value.CpuCoreCount, core, value.MemorySize)) } else if instanceTypeObject.CpuCoreCount*2 == value.CpuCoreCount { - expectedEqualMoreCpus = append(expectedEqualMoreCpus, fmt.Sprintf("%s(%dCPU,%.0fGB)", value.InstanceTypeId, value.CpuCoreCount, value.MemorySize)) + expectedEqualMoreCpus = append(expectedEqualMoreCpus, fmt.Sprintf("%s(%d%s,%.0fGB)", value.InstanceTypeId, value.CpuCoreCount, core, value.MemorySize)) } } } @@ -372,13 +444,17 @@ func (client *AliyunClient) CheckParameterValidity(d *schema.ResourceData, meta } if out, ok := d.GetOk("is_outdated"); !(ok && out.(bool)) { - return nil, fmt.Errorf("The current instance type %s(%dCPU,%.0fGB) has been outdated. Expect to use the upgraded instance types: %s. You can keep the instance type %s by setting 'is_outdated' to true.", - instanceType, instanceTypeObject.CpuCoreCount, instanceTypeObject.MemorySize, strings.Join(expectedInstanceTypes, ", "), instanceType) + core := "Core" + if instanceTypeObject.CpuCoreCount > 1 { + core = "Cores" + } + return nil, fmt.Errorf("The current instance type %s(%d%s,%.0fGB) has been outdated. Expect to use the upgraded instance types: %s. You can keep the instance type %s by setting 'is_outdated' to true.", + instanceType, instanceTypeObject.CpuCoreCount, core, instanceTypeObject.MemorySize, strings.Join(expectedInstanceTypes, ", "), instanceType) } else { // Check none io optimized and cloud _, typeOk := NoneIoOptimizedInstanceType[instanceType] - _, famOk := NoneIoOptimizedFamily[prefix] - _, halfOk := HalfIoOptimizedFamily[prefix] + _, famOk := NoneIoOptimizedFamily[targetFamily] + _, halfOk := HalfIoOptimizedFamily[targetFamily] if typeOk || famOk { if outDisk { ioOptimized = ecs.IoOptimizedNone @@ -397,93 +473,32 @@ func (client *AliyunClient) CheckParameterValidity(d *schema.ResourceData, meta } } - } else { - var validFamilies []string - for key := range expectedFamiliesMap { - validFamilies = append(validFamilies, key) - } - - if len(validFamilies) < 1 { - return nil, fmt.Errorf("There is no available instance type in the current availability zone." + - "Please change availability zone or region and try again.") - } - if zoneId == "" { - return nil, fmt.Errorf("Instance type family %s is not supported in the region %s. Expected instance type families: %s.", - prefix, getRegion(d, meta), strings.Join(validFamilies, ", ")) - } - return nil, fmt.Errorf("Instance type family %s is not supported in the availability zone %s. Expected instance type families: %s.", - prefix, zoneId, strings.Join(validFamilies, ", ")) + } else if err := getExpectInstanceTypesAndFormatOut(zoneId, targetFamily, getRegion(d, meta), mapUpgradedInstanceFamilies); err != nil { + return nil, err } } if instanceTypeFamily, ok := d.GetOk("instance_type_family"); ok { - _, outdatedOk := outdatedFamiliesMap[instanceTypeFamily.(string)] - _, expectedOk := expectedFamiliesMap[instanceTypeFamily.(string)] - if outdatedOk || !expectedOk { - var validFamilies []string - for key := range expectedFamiliesMap { - validFamilies = append(validFamilies, key) - } - if len(validFamilies) < 1 { - return nil, fmt.Errorf("There is no available instance type family in the current availability zone." + - "Please change availability zone or region and try again.") - } - if zoneId == "" { - return nil, fmt.Errorf("Instance type family %s is not supported in the region %s. Expected instance type families: %s.", - instanceTypeFamily, getRegion(d, meta), strings.Join(validFamilies, ", ")) - } - return nil, fmt.Errorf("Instance type family %s is not supported in the availability zone %s. Expected instance type families: %s.", - instanceTypeFamily, zoneId, strings.Join(validFamilies, ", ")) + if !strings.HasPrefix(instanceTypeFamily.(string), "ecs.") { + return nil, fmt.Errorf("Invalid instance_type_family: %s. Please modify it and try again.", instanceTypeFamily.(string)) } - } - validData := make(map[ResourceKeyType]interface{}) - mapZones := make(map[string]ecs.ZoneType) - mapSupportedInstanceTypes := make(map[string]string) - mapUpgradedInstanceTypes := make(map[string]string) - mapOutdatedInstanceTypes := make(map[string]string) - mapOutdatedDiskCategories := make(map[ecs.DiskCategory]ecs.DiskCategory) - mapDiskCategories := make(map[ecs.DiskCategory]ecs.DiskCategory) - for _, zone := range zones { - //var validInstanceTypes []string - for _, insType := range zone.AvailableInstanceTypes.InstanceTypes { - insTypeSplit := strings.Split(insType, DOT_SEPARATED) - prefix := string(insTypeSplit[0] + DOT_SEPARATED + insTypeSplit[1]) - if _, ok := outdatedFamiliesMap[prefix]; ok { - mapOutdatedInstanceTypes[insType] = prefix - } - if _, ok := expectedFamiliesMap[prefix]; ok { - mapUpgradedInstanceTypes[insType] = prefix - } - mapSupportedInstanceTypes[insType] = prefix - } - if len(zone.AvailableDiskCategories.DiskCategories) < 1 { - continue - } - //var validDiskCategories []ecs.DiskCategory - for _, category := range zone.AvailableDiskCategories.DiskCategories { - if _, ok := SupportedDiskCategory[category]; ok { - mapDiskCategories[category] = category - } - if _, ok := OutdatedDiskCategory[category]; ok { - mapOutdatedDiskCategories[category] = category + _, outdatedOk := mapOutdatedInstanceFamilies[instanceTypeFamily.(string)] + _, expectedOk := mapUpgradedInstanceFamilies[instanceTypeFamily.(string)] + if outdatedOk || !expectedOk { + if err := getExpectInstanceTypesAndFormatOut(zoneId, instanceTypeFamily.(string), getRegion(d, meta), mapUpgradedInstanceFamilies); err != nil { + return nil, err } } - resources := zone.AvailableResources.ResourcesInfo - if len(resources) < 1 { - continue - } - mapZones[zone.ZoneId] = zone - } validData[ZoneKey] = mapZones validData[InstanceTypeKey] = mapSupportedInstanceTypes validData[UpgradedInstanceTypeKey] = mapUpgradedInstanceTypes validData[OutdatedInstanceTypeKey] = mapOutdatedInstanceTypes - validData[UpgradedInstanceTypeFamilyKey] = expectedFamiliesMap - validData[OutdatedInstanceTypeFamilyKey] = outdatedFamiliesMap + validData[UpgradedInstanceTypeFamilyKey] = mapUpgradedInstanceFamilies + validData[OutdatedInstanceTypeFamilyKey] = mapOutdatedInstanceFamilies validData[OutdatedDiskCategoryKey] = mapOutdatedDiskCategories validData[DiskCategoryKey] = mapDiskCategories validData[IoOptimizedKey] = ioOptimized @@ -493,14 +508,16 @@ func (client *AliyunClient) CheckParameterValidity(d *schema.ResourceData, meta func (client *AliyunClient) FetchSpecifiedInstanceTypeFamily(regionId common.Region, zoneId string, generations []string, all_zones []ecs.ZoneType) (map[string]ecs.InstanceTypeFamily, map[string]ecs.InstanceTypeFamily, error) { // Describe specified series instance type families - outdatedFamiliesMap := make(map[string]ecs.InstanceTypeFamily) - upgradedFamiliesMap := make(map[string]ecs.InstanceTypeFamily) + mapOutdatedInstanceFamilies := make(map[string]ecs.InstanceTypeFamily) + mapUpgradedInstanceFamilies := make(map[string]ecs.InstanceTypeFamily) response, err := client.ecsconn.DescribeInstanceTypeFamilies(&ecs.DescribeInstanceTypeFamiliesArgs{ RegionId: regionId, }) if err != nil { return nil, nil, fmt.Errorf("Error DescribeInstanceTypeFamilies: %#v.", err) } + log.Printf("All the instance families in the region %s: %#v", regionId, response) + tempOutdatedMap := make(map[string]string) for _, gen := range generations { tempOutdatedMap[gen] = gen @@ -508,10 +525,10 @@ func (client *AliyunClient) FetchSpecifiedInstanceTypeFamily(regionId common.Reg for _, family := range response.InstanceTypeFamilies.InstanceTypeFamily { if _, ok := tempOutdatedMap[family.Generation]; ok { - outdatedFamiliesMap[family.InstanceTypeFamilyId] = family + mapOutdatedInstanceFamilies[family.InstanceTypeFamilyId] = family continue } - upgradedFamiliesMap[family.InstanceTypeFamilyId] = family + mapUpgradedInstanceFamilies[family.InstanceTypeFamilyId] = family } // Filter specified zone's instance type families, and make them fit for specified generation @@ -523,10 +540,10 @@ func (client *AliyunClient) FetchSpecifiedInstanceTypeFamily(regionId common.Reg for _, resource := range zone.AvailableResources.ResourcesInfo { families := resource.InstanceTypeFamilies[ecs.SupportedInstanceTypeFamily] for _, familyId := range families { - if val, ok := outdatedFamiliesMap[familyId]; ok { + if val, ok := mapOutdatedInstanceFamilies[familyId]; ok { outdatedValidFamilies[familyId] = val } - if val, ok := upgradedFamiliesMap[familyId]; ok { + if val, ok := mapUpgradedInstanceFamilies[familyId]; ok { upgradedValidFamilies[familyId] = val } } @@ -536,7 +553,9 @@ func (client *AliyunClient) FetchSpecifiedInstanceTypeFamily(regionId common.Reg } } } - return outdatedFamiliesMap, upgradedFamiliesMap, nil + log.Printf("New generation instance families: %#v.\n Outdated instance families: %#v.", + mapUpgradedInstanceFamilies, mapOutdatedInstanceFamilies) + return mapOutdatedInstanceFamilies, mapUpgradedInstanceFamilies, nil } func (client *AliyunClient) FetchSpecifiedInstanceTypesByFamily(zoneId, instanceTypeFamily string, all_zones []ecs.ZoneType) (map[string]ecs.InstanceTypeItemType, error) { @@ -547,6 +566,7 @@ func (client *AliyunClient) FetchSpecifiedInstanceTypesByFamily(zoneId, instance if err != nil { return nil, fmt.Errorf("Error DescribeInstanceTypes: %#v.", err) } + log.Printf("All the instance types of family %s: %#v", instanceTypeFamily, types) instanceTypes := make(map[string]ecs.InstanceTypeItemType) for _, ty := range types { instanceTypes[ty.InstanceTypeId] = ty @@ -573,6 +593,24 @@ func (client *AliyunClient) FetchSpecifiedInstanceTypesByFamily(zoneId, instance return instanceTypes, nil } +func getExpectInstanceTypesAndFormatOut(zoneId, instanceTypeFamily string, regionId common.Region, mapInstanceFamilies map[string]ecs.InstanceTypeFamily) error { + var validFamilies []string + + for key := range mapInstanceFamilies { + validFamilies = append(validFamilies, key) + } + if len(validFamilies) < 1 { + return fmt.Errorf("There is no available instance type family in the current availability zone." + + "Please change availability zone or region and try again.") + } + if zoneId == "" { + return fmt.Errorf("Instance type family %s is not supported in the region %s. Expected instance type families: %s.", + instanceTypeFamily, regionId, strings.Join(validFamilies, ", ")) + } + return fmt.Errorf("Instance type family %s is not supported in the availability zone %s. Expected instance type families: %s.", + instanceTypeFamily, zoneId, strings.Join(validFamilies, ", ")) +} + func (client *AliyunClient) QueryInstancesWithKeyPair(region common.Region, instanceIds, keypair string) ([]interface{}, []ecs.InstanceAttributesType, error) { var instance_ids []interface{} var instanceList []ecs.InstanceAttributesType diff --git a/alicloud/service_alicloud_rds.go b/alicloud/service_alicloud_rds.go index 931f1cb4518..b7b7d412053 100644 --- a/alicloud/service_alicloud_rds.go +++ b/alicloud/service_alicloud_rds.go @@ -141,13 +141,9 @@ func (client *AliyunClient) ConfigDBBackup(instanceId, backupTime, backupPeriod } func (client *AliyunClient) ModifyDBSecurityIps(instanceId, ips string) error { - sargs := rds.DBInstanceIPArray{ - SecurityIps: ips, - } - args := rds.ModifySecurityIpsArgs{ - DBInstanceId: instanceId, - DBInstanceIPArray: sargs, + DBInstanceId: instanceId, + SecurityIps: ips, } if _, err := client.rdsconn.ModifySecurityIps(&args); err != nil { diff --git a/alicloud/validators.go b/alicloud/validators.go index 52b323d41f5..913459ee823 100644 --- a/alicloud/validators.go +++ b/alicloud/validators.go @@ -10,8 +10,11 @@ import ( "time" "github.com/aliyun/aliyun-oss-go-sdk/oss" + "github.com/denverdino/aliyungo/cdn" "github.com/denverdino/aliyungo/common" + "github.com/denverdino/aliyungo/dns" "github.com/denverdino/aliyungo/ecs" + "github.com/denverdino/aliyungo/ram" "github.com/denverdino/aliyungo/slb" "github.com/hashicorp/terraform/helper/schema" ) @@ -126,9 +129,9 @@ func validateSecurityGroupDescription(v interface{}, k string) (ws []string, err } func validateSecurityRuleType(v interface{}, k string) (ws []string, errors []error) { - rt := GroupRuleDirection(v.(string)) - if rt != GroupRuleIngress && rt != GroupRuleEgress { - errors = append(errors, fmt.Errorf("%s must be one of %s %s", k, GroupRuleIngress, GroupRuleEgress)) + rt := ecs.Direction(v.(string)) + if rt != ecs.DirectionIngress && rt != ecs.DirectionEgress { + errors = append(errors, fmt.Errorf("%s must be one of %s %s", k, ecs.DirectionIngress, ecs.DirectionEgress)) } return @@ -374,9 +377,9 @@ func validateSlbListenerCookie(v interface{}, k string) (ws []string, errors []e func validateSlbListenerCookieTimeout(v interface{}, k string) (ws []string, errors []error) { value := v.(int) - if value < 0 || value > 86400 { + if value < 1 || value > 86400 { errors = append(errors, fmt.Errorf( - "%q must be a valid load balancer cookie timeout between 0 and 86400", + "%q must be a valid load balancer cookie timeout between 1 and 86400", k)) return } @@ -396,9 +399,11 @@ func validateSlbListenerPersistenceTimeout(v interface{}, k string) (ws []string func validateSlbListenerHealthCheckDomain(v interface{}, k string) (ws []string, errors []error) { if value := v.(string); value != "" { - //the len add "$_ip",so to max is 84 - if len(value) < 1 || len(value) > 84 { - errors = append(errors, fmt.Errorf("%q cannot be longer than 84 characters", k)) + if value == "$_ip" { + errors = append(errors, fmt.Errorf("%q value '$_ip' has been deprecated, and empty string will replace it.", k)) + } + if reg := regexp.MustCompile(`^[\w\-.]{1,80}$`); !reg.MatchString(value) { + errors = append(errors, fmt.Errorf("%q length is limited to 1-80 and only characters such as letters, digits, '-' and '.' are allowed", k)) } } return @@ -632,6 +637,84 @@ func validateOssBucketObjectServerSideEncryption(v interface{}, k string) (ws [] return } +func validateDomainName(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + + if vp := strings.Split(value, "."); len(vp) > 1 { + mainDomain := strings.Join(vp[:len(vp)-1], ".") + if len(mainDomain) > 63 || len(mainDomain) < 1 { + errors = append(errors, fmt.Errorf("Main domain cannot be longer than 63 characters or less than 1 character")) + } + } + + if strings.HasSuffix(value, ".sh") || strings.HasSuffix(value, ".tel") { + errors = append(errors, fmt.Errorf("Domain ends with .sh or .tel is not supported.")) + } + + if strings.HasPrefix(value, "-") || strings.HasSuffix(value, "-") { + errors = append(errors, fmt.Errorf("Domain name is invalid, it can not starts or ends with '-'")) + } + return +} + +func validateDomainRecordType(v interface{}, k string) (ws []string, errors []error) { + // Valid Record types + // A, NS, MX, TXT, CNAME, SRV, AAAA, REDIRECT_URL, FORWORD_URL + validTypes := map[string]string{ + dns.ARecord: "", + dns.NSRecord: "", + dns.MXRecord: "", + dns.TXTRecord: "", + dns.CNAMERecord: "", + dns.SRVRecord: "", + dns.AAAARecord: "", + dns.RedirectURLRecord: "", + dns.ForwordURLRecord: "", + } + + value := v.(string) + if _, ok := validTypes[value]; !ok { + errors = append(errors, fmt.Errorf("%q must be one of [A, NS, MX, TXT, CNAME, SRV, AAAA, REDIRECT_URL, FORWORD_URL]", k)) + } + return +} + +func validateDomainRecordPriority(v interface{}, k string) (ws []string, errors []error) { + value := v.(int) + if value > 10 || value < 1 { + errors = append(errors, fmt.Errorf("%q value is 1-10.", k)) + } + return +} + +func validateRR(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + + if strings.HasPrefix(value, "-") || strings.HasSuffix(value, "-") { + errors = append(errors, fmt.Errorf("RR is invalid, it can not starts or ends with '-'")) + } + + if len(value) > 253 { + errors = append(errors, fmt.Errorf("RR can not longer than 253 characters.")) + } + + for _, part := range strings.Split(value, ".") { + if len(part) > 63 { + errors = append(errors, fmt.Errorf("Each part of RR split with . can not longer than 63 characters.")) + return + } + } + return +} + +func validateDomainRecordLine(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "default" && value != "telecom" && value != "unicom" && value != "mobile" && value != "oversea" && value != "edu" { + errors = append(errors, fmt.Errorf("Record parsing line must be one of [default, telecom, unicom, mobile, oversea, edu].")) + } + return +} + func validateKeyPairName(v interface{}, k string) (ws []string, errors []error) { value := v.(string) if len(value) < 2 || len(value) > 128 { @@ -655,6 +738,62 @@ func validateKeyPairPrefix(v interface{}, k string) (ws []string, errors []error return } +func validateRamName(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + + if len(value) > 64 { + errors = append(errors, fmt.Errorf("%q can not be longer than 64 characters.", k)) + } + + pattern := `^[a-zA-Z0-9\.@\-_]+$` + if match, _ := regexp.Match(pattern, []byte(value)); !match { + errors = append(errors, fmt.Errorf("%q is invalid.", k)) + } + return +} + +func validateRamDisplayName(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + + pattern := `^[a-zA-Z0-9\.@\-\p{Han}]{1,12}$` + if match, _ := regexp.Match(pattern, []byte(value)); !match { + errors = append(errors, fmt.Errorf("%q is invalid.", k)) + } + return +} + +func validateComment(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + + if len(value) > 128 { + errors = append(errors, fmt.Errorf("%q can not be longer than 128 characters.", k)) + } + return +} + +func validateRamDesc(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + + if len(value) > 1024 { + errors = append(errors, fmt.Errorf("%q can not be longer than 1024 characters.", k)) + } + return +} + +func validateRamPolicyName(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + + if len(value) > 128 { + errors = append(errors, fmt.Errorf("%q can not be longer than 128 characters.", k)) + } + + pattern := `^[a-zA-Z0-9\-]+$` + if match, _ := regexp.Match(pattern, []byte(value)); !match { + errors = append(errors, fmt.Errorf("%q is invalid.", k)) + } + return +} + // Takes a value containing JSON string and passes it through // the JSON parser to normalize it, returns either a parsing // error or normalized JSON string. @@ -679,6 +818,214 @@ func normalizeJsonString(jsonString interface{}) (string, error) { return string(bytes[:]), nil } +func validateJsonString(v interface{}, k string) (ws []string, errors []error) { + if _, err := normalizeJsonString(v); err != nil { + errors = append(errors, fmt.Errorf("%q contains an invalid JSON: %s", k, err)) + } + if strings.Contains(v.(string), " ") || strings.Contains(v.(string), "\n") { + errors = append(errors, fmt.Errorf("%q can not contain any space or newline character.", k)) + } + return +} + +func validatePolicyType(v interface{}, k string) (ws []string, errors []error) { + value := ram.Type(v.(string)) + + if value != ram.System && value != ram.Custom { + errors = append(errors, fmt.Errorf("%q must be '%s' or '%s'.", k, ram.System, ram.Custom)) + } + return +} + +func validateRamGroupName(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + + if len(value) > 64 { + errors = append(errors, fmt.Errorf("%q can not be longer than 64 characters.", k)) + } + + pattern := `^[a-zA-Z0-9\-]+$` + if match, _ := regexp.Match(pattern, []byte(value)); !match { + errors = append(errors, fmt.Errorf("%q is invalid.", k)) + } + return +} + +func validateRamAlias(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + + if len(value) > 32 || len(value) < 2 { + errors = append(errors, fmt.Errorf("%q can not be longer than 32 or less than 2 characters.", k)) + } + return +} + +func validateRamAKStatus(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + + if value != "Active" && value != "Inactive" { + errors = append(errors, fmt.Errorf("%q must be 'Active' or 'Inactive'.", k)) + } + return +} + +func validateContainerClusterName(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if len(value) < 1 || len(value) > 64 { + errors = append(errors, fmt.Errorf("%q cannot be longer than 64 characters and less than 1", k)) + } + + if strings.HasPrefix(value, "http://") || strings.HasPrefix(value, "https://") { + errors = append(errors, fmt.Errorf("%s cannot starts with http:// or https://", k)) + } + + return +} + +func validateContainerClusterNamePrefix(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if len(value) > 38 { + errors = append(errors, fmt.Errorf( + "%q cannot be longer than 38 characters, name is limited to 64", k)) + } + + return +} + +func validateCdnChargeType(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + + if value != "PayByTraffic" && value != "PayByBandwidth" { + errors = append(errors, fmt.Errorf("%q must be 'PayByTraffic' or 'PayByBandwidth'.", k)) + } + return +} + +func validateCdnType(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + for _, val := range cdn.CdnTypes { + if val == value { + return + } + } + errors = append(errors, fmt.Errorf("%q must be one of %v.", k, cdn.CdnTypes)) + return +} + +func validateCdnSourceType(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + for _, val := range cdn.SourceTypes { + if val == value { + return + } + } + errors = append(errors, fmt.Errorf("%q must be one of %v.", k, cdn.SourceTypes)) + return +} + +func validateCdnScope(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + for _, val := range cdn.Scopes { + if val == value { + return + } + } + errors = append(errors, fmt.Errorf("%q must be one of %v.", k, cdn.Scopes)) + return +} + +func validateCdnSourcePort(v interface{}, k string) (ws []string, errors []error) { + value := v.(int) + if value != 80 && value != 443 { + errors = append(errors, fmt.Errorf("%q must be one 80 or 443.", k)) + } + return +} + +func validateCdnHttpHeader(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + for _, val := range cdn.HeaderKeys { + if val == value { + return + } + } + errors = append(errors, fmt.Errorf("%q must be one of %v.", k, cdn.HeaderKeys)) + return +} + +func validateCacheType(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "suffix" && value != "path" { + errors = append(errors, fmt.Errorf("%q must be 'suffix' or 'path'.", k)) + } + return +} + +func validateCdnEnable(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "on" && value != "off" { + errors = append(errors, fmt.Errorf("%q must be 'on' or 'off'.", k)) + } + return +} + +func validateCdnHashKeyArg(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if strings.Contains(value, ",") { + errors = append(errors, fmt.Errorf("%q can not contains any ','.", k)) + } + return +} + +func validateCdnPage404Type(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "default" && value != "charity" && value != "other" { + errors = append(errors, fmt.Errorf("%q must be one of ['default', 'charity', 'other'].", k)) + } + return +} + +func validateCdnRedirectType(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "Off" && value != "Http" && value != "Https" { + errors = append(errors, fmt.Errorf("%q must be one of ['Off', 'Http', 'Https'].", k)) + } + return +} + +func validateCdnReferType(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "block" && value != "allow" { + errors = append(errors, fmt.Errorf("%q must be 'block' or 'allow'.", k)) + } + return +} + +func validateCdnAuthType(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "no_auth" && value != "type_a" && value != "type_b" && value != "type_c" { + errors = append(errors, fmt.Errorf("%q must be one of ['no_auth', 'type_a', 'type_b', 'type_c']", k)) + } + return +} + +func validateCdnAuthKey(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + pattern := `^[a-zA-Z0-9]{6,32}$` + if match, _ := regexp.Match(pattern, []byte(value)); !match { + errors = append(errors, fmt.Errorf("%q can only consists of alphanumeric characters and can not be longer than 32 or less than 6 characters.", k)) + } + return +} + +func validatePolicyDocVersion(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "1" { + errors = append(errors, fmt.Errorf("%q can only be '1' so far.", k)) + } + return +} + func validateRouterInterfaceDescription(v interface{}, k string) (ws []string, errors []error) { value := v.(string) if len(value) < 2 || len(value) > 256 { @@ -690,3 +1037,11 @@ func validateRouterInterfaceDescription(v interface{}, k string) (ws []string, e } return } + +func validateInstanceType(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if !strings.HasPrefix(value, "ecs.") { + errors = append(errors, fmt.Errorf("Invalid %q: %s. It must be 'ecs.' as prefix.", k, value)) + } + return +} diff --git a/examples/container-cluster/alicloud-container-cluster/README.md b/examples/container-cluster/alicloud-container-cluster/README.md new file mode 100644 index 00000000000..106bcce240b --- /dev/null +++ b/examples/container-cluster/alicloud-container-cluster/README.md @@ -0,0 +1,18 @@ +### VPC Example + +The example to bulid a container cluster and its dependence resource, such as VPC, VSwitch. The variables.tf can let you create specify parameter resources, such as instance_type, cidr_block etc. + +### Get up and running + +* Planning phase + + terraform plan + +* Apply phase + + terraform apply + + +* Destroy + + terraform destroy \ No newline at end of file diff --git a/examples/container-cluster/alicloud-container-cluster/main.tf b/examples/container-cluster/alicloud-container-cluster/main.tf new file mode 100644 index 00000000000..3b46d86e2d5 --- /dev/null +++ b/examples/container-cluster/alicloud-container-cluster/main.tf @@ -0,0 +1,41 @@ +data "alicloud_images" main { + most_recent = "${var.most_recent}" + owners = "${var.image_owners}" + name_regex = "${var.name_regex}" +} + +data "alicloud_zones" main { + available_resource_creation = "VSwitch" +} + +data "alicloud_instance_types" main { + availability_zone = "${data.alicloud_zones.main.zones.0.id}" + cpu_core_count = "${var.cpu_core_count}" + memory_size = "${var.memory_size}" +} + +resource "alicloud_vpc" "main" { + name = "${var.vpc_name}" + cidr_block = "${var.vpc_cidr}" +} + +resource "alicloud_vswitch" "main" { + vpc_id = "${alicloud_vpc.main.id}" + cidr_block = "${var.vswitch_cidr}" + availability_zone = "${data.alicloud_zones.main.zones.0.id}" + depends_on = [ + "alicloud_vpc.main"] +} + +resource "alicloud_container_cluster" "cs_vpc" { + password = "${var.password}" + instance_type = "${data.alicloud_instance_types.main.instance_types.0.id}" + name = "${var.cluster_name}" + size = "${var.node_number}" + disk_category = "${var.disk_category}" + disk_size = "${var.disk_size}" + cidr_block = "${var.cidr_block}" + image_id = "${data.alicloud_images.main.images.0.id}" + vswitch_id = "${alicloud_vswitch.main.id}" +} + diff --git a/examples/container-cluster/alicloud-container-cluster/outputs.tf b/examples/container-cluster/alicloud-container-cluster/outputs.tf new file mode 100644 index 00000000000..49e0c2fa747 --- /dev/null +++ b/examples/container-cluster/alicloud-container-cluster/outputs.tf @@ -0,0 +1,14 @@ +output "cluster_id" { + value = "${alicloud_container_cluster.cs_vpc.id}" +} +output "vpc_id" { + value = "${alicloud_vpc.main.id}" +} + +output "vswitch_id" { + value = "${alicloud_vswitch.main.id}" +} + +output "availability_zone" { + value = "${alicloud_vswitch.main.availability_zone}" +} diff --git a/examples/container-cluster/alicloud-container-cluster/variables.tf b/examples/container-cluster/alicloud-container-cluster/variables.tf new file mode 100644 index 00000000000..29d1ff633bc --- /dev/null +++ b/examples/container-cluster/alicloud-container-cluster/variables.tf @@ -0,0 +1,48 @@ +variable "most_recent" { + default = true +} + +variable "image_owners" { + default = "" +} + +variable "name_regex" { + default = "^centos_6\\w{1,5}[64].*" +} + +variable "cpu_core_count" { + default = 1 +} +variable "memory_size" { + default = 2 +} + +variable "vpc_name" { + default = "alicloud_vpc" +} +variable "vpc_cidr" { + default = "10.1.0.0/21" +} + +variable "vswitch_cidr" { + default = "10.1.1.0/24" +} + +variable "password" { + default = "Test12345" +} +variable "cidr_block" { + default = "172.18.0.0/24" +} +variable "cluster_name" { + default = "Alicloud-cluster" +} +variable "node_number" { + default = 1 +} +variable "disk_category" { + default = "cloud_efficiency" +} +variable "disk_size" { + default = 40 +} \ No newline at end of file diff --git a/vendor/github.com/denverdino/aliyungo/common/client.go b/vendor/github.com/denverdino/aliyungo/common/client.go index a59789fee44..80841eb5f12 100755 --- a/vendor/github.com/denverdino/aliyungo/common/client.go +++ b/vendor/github.com/denverdino/aliyungo/common/client.go @@ -3,10 +3,14 @@ package common import ( "bytes" "encoding/json" + "errors" + "fmt" "io/ioutil" "log" "net/http" "net/url" + "os" + "strconv" "strings" "time" @@ -25,6 +29,7 @@ type UnderlineString string type Client struct { AccessKeyId string //Access Key Id AccessKeySecret string //Access Key Secret + securityToken string debug bool httpClient *http.Client endpoint string @@ -32,19 +37,30 @@ type Client struct { serviceCode string regionID Region businessInfo string - userAgent string + userAgent string } -// NewClient creates a new instance of ECS client +// Initialize properties of a client instance func (client *Client) Init(endpoint, version, accessKeyId, accessKeySecret string) { client.AccessKeyId = accessKeyId client.AccessKeySecret = accessKeySecret + "&" client.debug = false - client.httpClient = &http.Client{} + handshakeTimeout, err := strconv.Atoi(os.Getenv("TLSHandshakeTimeout")) + if err != nil { + handshakeTimeout = 0 + } + if handshakeTimeout == 0 { + client.httpClient = &http.Client{} + } else { + t := &http.Transport{ + TLSHandshakeTimeout: time.Duration(handshakeTimeout) * time.Second} + client.httpClient = &http.Client{Transport: t} + } client.endpoint = endpoint client.version = version } +// Initialize properties of a client instance including regionID func (client *Client) NewInit(endpoint, version, accessKeyId, accessKeySecret, serviceCode string, regionID Region) { client.Init(endpoint, version, accessKeyId, accessKeySecret) client.serviceCode = serviceCode @@ -52,6 +68,29 @@ func (client *Client) NewInit(endpoint, version, accessKeyId, accessKeySecret, s client.setEndpointByLocation(regionID, serviceCode, accessKeyId, accessKeySecret) } +// Intialize client object when all properties are ready +func (client *Client) InitClient() *Client { + client.debug = false + handshakeTimeout, err := strconv.Atoi(os.Getenv("TLSHandshakeTimeout")) + if err != nil { + handshakeTimeout = 0 + } + if handshakeTimeout == 0 { + client.httpClient = &http.Client{} + } else { + t := &http.Transport{ + TLSHandshakeTimeout: time.Duration(handshakeTimeout) * time.Second} + client.httpClient = &http.Client{Transport: t} + } + client.setEndpointByLocation(client.regionID, client.serviceCode, client.AccessKeyId, client.AccessKeySecret) + return client +} + +func (client *Client) NewInitForAssumeRole(endpoint, version, accessKeyId, accessKeySecret, serviceCode string, regionID Region, securityToken string) { + client.NewInit(endpoint, version, accessKeyId, accessKeySecret, serviceCode, regionID) + client.securityToken = securityToken +} + //NewClient using location service func (client *Client) setEndpointByLocation(region Region, serviceCode, accessKeyId, accessKeySecret string) { locationClient := NewLocationClient(accessKeyId, accessKeySecret) @@ -65,6 +104,95 @@ func (client *Client) setEndpointByLocation(region Region, serviceCode, accessKe } } +// Ensure all necessary properties are valid +func (client *Client) ensureProperties() error { + var msg string + + if client.endpoint == "" { + msg = fmt.Sprintf("endpoint cannot be empty!") + } else if client.version == "" { + msg = fmt.Sprintf("version cannot be empty!") + } else if client.AccessKeyId == "" { + msg = fmt.Sprintf("AccessKeyId cannot be empty!") + } else if client.AccessKeySecret == "" { + msg = fmt.Sprintf("AccessKeySecret cannot be empty!") + } + + if msg != "" { + return errors.New(msg) + } + + return nil +} + +// ---------------------------------------------------- +// WithXXX methods +// ---------------------------------------------------- + +// WithEndpoint sets custom endpoint +func (client *Client) WithEndpoint(endpoint string) *Client { + client.SetEndpoint(endpoint) + return client +} + +// WithVersion sets custom version +func (client *Client) WithVersion(version string) *Client { + client.SetVersion(version) + return client +} + +// WithRegionID sets Region ID +func (client *Client) WithRegionID(regionID Region) *Client { + client.SetRegionID(regionID) + return client +} + +//WithServiceCode sets serviceCode +func (client *Client) WithServiceCode(serviceCode string) *Client { + client.SetServiceCode(serviceCode) + return client +} + +// WithAccessKeyId sets new AccessKeyId +func (client *Client) WithAccessKeyId(id string) *Client { + client.SetAccessKeyId(id) + return client +} + +// WithAccessKeySecret sets new AccessKeySecret +func (client *Client) WithAccessKeySecret(secret string) *Client { + client.SetAccessKeySecret(secret) + return client +} + +// WithSecurityToken sets securityToken +func (client *Client) WithSecurityToken(securityToken string) *Client { + client.SetSecurityToken(securityToken) + return client +} + +// WithDebug sets debug mode to log the request/response message +func (client *Client) WithDebug(debug bool) *Client { + client.SetDebug(debug) + return client +} + +// WithBusinessInfo sets business info to log the request/response message +func (client *Client) WithBusinessInfo(businessInfo string) *Client { + client.SetBusinessInfo(businessInfo) + return client +} + +// WithUserAgent sets user agent to the request/response message +func (client *Client) WithUserAgent(userAgent string) *Client { + client.SetUserAgent(userAgent) + return client +} + +// ---------------------------------------------------- +// SetXXX methods +// ---------------------------------------------------- + // SetEndpoint sets custom endpoint func (client *Client) SetEndpoint(endpoint string) { client.endpoint = endpoint @@ -75,6 +203,7 @@ func (client *Client) SetVersion(version string) { client.version = version } +// SetEndpoint sets Region ID func (client *Client) SetRegionID(regionID Region) { client.regionID = regionID } @@ -113,11 +242,19 @@ func (client *Client) SetUserAgent(userAgent string) { client.userAgent = userAgent } +//set SecurityToken +func (client *Client) SetSecurityToken(securityToken string) { + client.securityToken = securityToken +} + // Invoke sends the raw HTTP request for ECS services func (client *Client) Invoke(action string, args interface{}, response interface{}) error { + if err := client.ensureProperties(); err != nil { + return err + } request := Request{} - request.init(client.version, action, client.AccessKeyId) + request.init(client.version, action, client.AccessKeyId, client.securityToken, client.regionID) query := util.ConvertToQueryValues(request) util.SetQueryValues(args, &query) @@ -137,7 +274,7 @@ func (client *Client) Invoke(action string, args interface{}, response interface // TODO move to util and add build val flag httpReq.Header.Set("X-SDK-Client", `AliyunGO/`+Version+client.businessInfo) - httpReq.Header.Set("User-Agent", httpReq.UserAgent()+ " " +client.userAgent) + httpReq.Header.Set("User-Agent", httpReq.UserAgent()+" "+client.userAgent) t0 := time.Now() httpResp, err := client.httpClient.Do(httpReq) @@ -185,9 +322,12 @@ func (client *Client) Invoke(action string, args interface{}, response interface // Invoke sends the raw HTTP request for ECS services func (client *Client) InvokeByFlattenMethod(action string, args interface{}, response interface{}) error { + if err := client.ensureProperties(); err != nil { + return err + } request := Request{} - request.init(client.version, action, client.AccessKeyId) + request.init(client.version, action, client.AccessKeyId, client.securityToken, client.regionID) query := util.ConvertToQueryValues(request) @@ -208,7 +348,7 @@ func (client *Client) InvokeByFlattenMethod(action string, args interface{}, res // TODO move to util and add build val flag httpReq.Header.Set("X-SDK-Client", `AliyunGO/`+Version+client.businessInfo) - httpReq.Header.Set("User-Agent", httpReq.UserAgent()+ " " +client.userAgent) + httpReq.Header.Set("User-Agent", httpReq.UserAgent()+" "+client.userAgent) t0 := time.Now() httpResp, err := client.httpClient.Do(httpReq) @@ -258,10 +398,12 @@ func (client *Client) InvokeByFlattenMethod(action string, args interface{}, res //改进了一下上面那个方法,可以使用各种Http方法 //2017.1.30 增加了一个path参数,用来拓展访问的地址 func (client *Client) InvokeByAnyMethod(method, action, path string, args interface{}, response interface{}) error { + if err := client.ensureProperties(); err != nil { + return err + } request := Request{} - request.init(client.version, action, client.AccessKeyId) - + request.init(client.version, action, client.AccessKeyId, client.securityToken, client.regionID) data := util.ConvertToQueryValues(request) util.SetQueryValues(args, &data) @@ -290,8 +432,7 @@ func (client *Client) InvokeByAnyMethod(method, action, path string, args interf // TODO move to util and add build val flag httpReq.Header.Set("X-SDK-Client", `AliyunGO/`+Version+client.businessInfo) - - httpReq.Header.Set("User-Agent", httpReq.Header.Get("User-Agent")+ " " +client.userAgent) + httpReq.Header.Set("User-Agent", httpReq.Header.Get("User-Agent")+" "+client.userAgent) t0 := time.Now() httpResp, err := client.httpClient.Do(httpReq) diff --git a/vendor/github.com/denverdino/aliyungo/common/regions.go b/vendor/github.com/denverdino/aliyungo/common/regions.go index 62e6e9d814f..199ee5915d3 100644 --- a/vendor/github.com/denverdino/aliyungo/common/regions.go +++ b/vendor/github.com/denverdino/aliyungo/common/regions.go @@ -16,6 +16,7 @@ const ( APSouthEast1 = Region("ap-southeast-1") APNorthEast1 = Region("ap-northeast-1") APSouthEast2 = Region("ap-southeast-2") + APSouthEast3 = Region("ap-southeast-3") USWest1 = Region("us-west-1") USEast1 = Region("us-east-1") @@ -23,12 +24,16 @@ const ( MEEast1 = Region("me-east-1") EUCentral1 = Region("eu-central-1") + + ShenZhenFinance = Region("cn-shenzhen-finance-1") + ShanghaiFinance = Region("cn-shanghai-finance-1") ) var ValidRegions = []Region{ Hangzhou, Qingdao, Beijing, Shenzhen, Hongkong, Shanghai, Zhangjiakou, USWest1, USEast1, - APNorthEast1, APSouthEast1, APSouthEast2, + APNorthEast1, APSouthEast1, APSouthEast2, APSouthEast3, MEEast1, EUCentral1, + ShenZhenFinance, ShanghaiFinance, } diff --git a/vendor/github.com/denverdino/aliyungo/common/request.go b/vendor/github.com/denverdino/aliyungo/common/request.go index 2a883f19b20..f35c2990def 100644 --- a/vendor/github.com/denverdino/aliyungo/common/request.go +++ b/vendor/github.com/denverdino/aliyungo/common/request.go @@ -20,7 +20,9 @@ const ( type Request struct { Format string Version string + RegionId Region AccessKeyId string + SecurityToken string Signature string SignatureMethod string Timestamp util.ISO6801Time @@ -30,7 +32,7 @@ type Request struct { Action string } -func (request *Request) init(version string, action string, AccessKeyId string) { +func (request *Request) init(version string, action string, AccessKeyId string, securityToken string, regionId Region) { request.Format = JSONResponseFormat request.Timestamp = util.NewISO6801Time(time.Now().UTC()) request.Version = version @@ -39,6 +41,8 @@ func (request *Request) init(version string, action string, AccessKeyId string) request.SignatureNonce = util.CreateRandomString() request.Action = action request.AccessKeyId = AccessKeyId + request.SecurityToken = securityToken + request.RegionId = regionId } type Response struct { diff --git a/vendor/github.com/denverdino/aliyungo/cs/client.go b/vendor/github.com/denverdino/aliyungo/cs/client.go new file mode 100644 index 00000000000..ab71ccde4fa --- /dev/null +++ b/vendor/github.com/denverdino/aliyungo/cs/client.go @@ -0,0 +1,192 @@ +package cs + +import ( + "bytes" + "crypto/md5" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "net/url" + "time" + + "github.com/denverdino/aliyungo/common" + "github.com/denverdino/aliyungo/util" +) + +const ( + // CRMDefaultEndpoint is the default API endpoint of CRM services + CSDefaultEndpoint = "https://cs.aliyuncs.com" + CSAPIVersion = "2015-12-15" +) + +// The Client type encapsulates operations with an OSS region. +type Client struct { + AccessKeyId string + AccessKeySecret string + SecurityToken string + endpoint string + Version string + debug bool + userAgent string + httpClient *http.Client +} + +type Response struct { + RequestId string `json:"request_id"` +} + +// NewClient creates a new instance of CRM client +func NewClient(accessKeyId, accessKeySecret string) *Client { + return &Client{ + AccessKeyId: accessKeyId, + AccessKeySecret: accessKeySecret, + endpoint: CSDefaultEndpoint, + Version: CSAPIVersion, + httpClient: &http.Client{}, + } +} + +func NewClientForAussumeRole(accessKeyId, accessKeySecret, securityToken string) *Client { + return &Client{ + AccessKeyId: accessKeyId, + AccessKeySecret: accessKeySecret, + SecurityToken: securityToken, + endpoint: CSDefaultEndpoint, + Version: CSAPIVersion, + httpClient: &http.Client{}, + } +} + +// SetDebug sets debug mode to log the request/response message +func (client *Client) SetDebug(debug bool) { + client.debug = debug +} + +// SetUserAgent sets user agent to log the request/response message +func (client *Client) SetUserAgent(userAgent string) { + client.userAgent = userAgent +} + +type Request struct { + Method string + URL string + Version string + Region common.Region + Signature string + SignatureMethod string + SignatureNonce string + Timestamp util.ISO6801Time + Body []byte +} + +// Invoke sends the raw HTTP request for ECS services +func (client *Client) Invoke(region common.Region, method string, path string, query url.Values, args interface{}, response interface{}) error { + + var reqBody []byte + var err error + var contentType string + var contentMD5 string + + if args != nil { + reqBody, err = json.Marshal(args) + if err != nil { + return err + } + contentType = "application/json" + hasher := md5.New() + hasher.Write(reqBody) + contentMD5 = base64.StdEncoding.EncodeToString(hasher.Sum(nil)) + } + + requestURL := client.endpoint + path + if query != nil && len(query) > 0 { + requestURL = requestURL + "?" + util.Encode(query) + } + var bodyReader io.Reader + if reqBody != nil { + bodyReader = bytes.NewReader(reqBody) + } + httpReq, err := http.NewRequest(method, requestURL, bodyReader) + if err != nil { + return common.GetClientError(err) + } + + if region != "" { + httpReq.Header.Set("x-acs-region-id", string(region)) + } + + if contentType != "" { + httpReq.Header.Set("Content-Type", contentType) + } + if contentMD5 != "" { + httpReq.Header.Set("Content-MD5", contentMD5) + } + // TODO move to util and add build val flag + httpReq.Header.Set("Date", util.GetGMTime()) + httpReq.Header.Set("Accept", "application/json") + //httpReq.Header.Set("x-acs-version", client.Version) + httpReq.Header["x-acs-signature-version"] = []string{"1.0"} + httpReq.Header["x-acs-signature-nonce"] = []string{util.CreateRandomString()} + httpReq.Header["x-acs-signature-method"] = []string{"HMAC-SHA1"} + + fmt.Printf("Header = %++v", httpReq.Header) + + if client.userAgent != "" { + httpReq.Header.Set("User-Agent", client.userAgent) + } + + if client.SecurityToken != "" { + httpReq.Header["x-acs-security-token"] = []string{client.SecurityToken} + } + + client.signRequest(httpReq) + + t0 := time.Now() + httpResp, err := client.httpClient.Do(httpReq) + t1 := time.Now() + if err != nil { + return common.GetClientError(err) + } + statusCode := httpResp.StatusCode + + if client.debug { + log.Printf("Invoke %s %s %d (%v)", method, requestURL, statusCode, t1.Sub(t0)) + } + + defer httpResp.Body.Close() + body, err := ioutil.ReadAll(httpResp.Body) + + if err != nil { + return common.GetClientError(err) + } + + if client.debug { + var prettyJSON bytes.Buffer + err = json.Indent(&prettyJSON, body, "", " ") + log.Println(string(prettyJSON.Bytes())) + } + + if statusCode >= 400 && statusCode <= 599 { + errorResponse := common.ErrorResponse{} + err = json.Unmarshal(body, &errorResponse) + ecsError := &common.Error{ + ErrorResponse: errorResponse, + StatusCode: statusCode, + } + return ecsError + } + + if response != nil { + err = json.Unmarshal(body, response) + //log.Printf("%++v", response) + if err != nil { + return common.GetClientError(err) + } + } + + return nil +} diff --git a/vendor/github.com/denverdino/aliyungo/cs/clusters.go b/vendor/github.com/denverdino/aliyungo/cs/clusters.go new file mode 100644 index 00000000000..9fd462f10c4 --- /dev/null +++ b/vendor/github.com/denverdino/aliyungo/cs/clusters.go @@ -0,0 +1,188 @@ +package cs + +import ( + "net/http" + "net/url" + + "github.com/denverdino/aliyungo/common" + "github.com/denverdino/aliyungo/ecs" + "github.com/denverdino/aliyungo/util" + "math" + "time" +) + +type ClusterState string + +const ( + Initial = ClusterState("initial") + Running = ClusterState("running") + Updating = ClusterState("updating") + Scaling = ClusterState("scaling") + Failed = ClusterState("failed") + Deleting = ClusterState("deleting") + DeleteFailed = ClusterState("deleteFailed") + Deleted = ClusterState("deleted") + InActive = ClusterState("inactive") +) + +type NodeStatus struct { + Health int64 `json:"health"` + Unhealth int64 `json:"unhealth"` +} + +type NetworkModeType string + +const ( + ClassicNetwork = NetworkModeType("classic") + VPCNetwork = NetworkModeType("vpc") +) + +// https://help.aliyun.com/document_detail/26053.html +type ClusterType struct { + AgentVersion string `json:"agent_version"` + ClusterID string `json:"cluster_id"` + Name string `json:"name"` + Created util.ISO6801Time `json:"created"` + ExternalLoadbalancerID string `json:"external_loadbalancer_id"` + MasterURL string `json:"master_url"` + NetworkMode NetworkModeType `json:"network_mode"` + RegionID common.Region `json:"region_id"` + SecurityGroupID string `json:"security_group_id"` + Size int64 `json:"size"` + State ClusterState `json:"state"` + Updated util.ISO6801Time `json:"updated"` + VPCID string `json:"vpc_id"` + VSwitchID string `json:"vswitch_id"` + NodeStatus string `json:"node_status"` + DockerVersion string `json:"docker_version"` +} + +func (client *Client) DescribeClusters(nameFilter string) (clusters []ClusterType, err error) { + query := make(url.Values) + + if nameFilter != "" { + query.Add("name", nameFilter) + } + + err = client.Invoke("", http.MethodGet, "/clusters", query, nil, &clusters) + return +} + +func (client *Client) DescribeCluster(id string) (cluster ClusterType, err error) { + err = client.Invoke("", http.MethodGet, "/clusters/"+id, nil, nil, &cluster) + return +} + +type ClusterCreationArgs struct { + Name string `json:"name"` + Size int64 `json:"size"` + NetworkMode NetworkModeType `json:"network_mode"` + SubnetCIDR string `json:"subnet_cidr,omitempty"` + InstanceType string `json:"instance_type"` + VPCID string `json:"vpc_id,omitempty"` + VSwitchID string `json:"vswitch_id,omitempty"` + Password string `json:"password"` + DataDiskSize int64 `json:"data_disk_size"` + DataDiskCategory ecs.DiskCategory `json:"data_disk_category"` + ECSImageID string `json:"ecs_image_id,omitempty"` + IOOptimized ecs.IoOptimized `json:"io_optimized"` +} + +type ClusterCreationResponse struct { + Response + ClusterID string `json:"cluster_id"` +} + +func (client *Client) CreateCluster(region common.Region, args *ClusterCreationArgs) (cluster ClusterCreationResponse, err error) { + err = client.Invoke(region, http.MethodPost, "/clusters", nil, args, &cluster) + return +} + +type ClusterResizeArgs struct { + Size int64 `json:"size"` + InstanceType string `json:"instance_type"` + Password string `json:"password"` + DataDiskSize int64 `json:"data_disk_size"` + DataDiskCategory ecs.DiskCategory `json:"data_disk_category"` + ECSImageID string `json:"ecs_image_id,omitempty"` + IOOptimized ecs.IoOptimized `json:"io_optimized"` +} + +func (client *Client) ResizeCluster(clusterID string, args *ClusterResizeArgs) error { + return client.Invoke("", http.MethodPut, "/clusters/"+clusterID, nil, args, nil) +} + +func (client *Client) DeleteCluster(clusterID string) error { + return client.Invoke("", http.MethodDelete, "/clusters/"+clusterID, nil, nil, nil) +} + +type ClusterCerts struct { + CA string `json:"ca,omitempty"` + Key string `json:"key,omitempty"` + Cert string `json:"cert,omitempty"` +} + +func (client *Client) GetClusterCerts(id string) (certs ClusterCerts, err error) { + err = client.Invoke("", http.MethodGet, "/clusters/"+id+"/certs", nil, nil, &certs) + return +} + +const ClusterDefaultTimeout = 300 +const DefaultWaitForInterval = 10 +const DefaultPreSleepTime = 240 + +// WaitForCluster waits for instance to given status +// when instance.NotFound wait until timeout +func (client *Client) WaitForClusterAsyn(clusterId string, status ClusterState, timeout int) error { + if timeout <= 0 { + timeout = ClusterDefaultTimeout + } + cluster, err := client.DescribeCluster(clusterId) + if err != nil { + return err + } else if cluster.State == status { + //TODO + return nil + } + // Create or Reset cluster usually cost at least 4 min, so there will sleep a long time before polling + sleep := math.Min(float64(timeout), float64(DefaultPreSleepTime)) + time.Sleep(time.Duration(sleep) * time.Second) + + for { + cluster, err := client.DescribeCluster(clusterId) + if err != nil { + return err + } else if cluster.State == status { + //TODO + break + } + timeout = timeout - DefaultWaitForInterval + if timeout <= 0 { + return common.GetClientErrorFromString("Timeout") + } + time.Sleep(DefaultWaitForInterval * time.Second) + } + return nil +} + +func (client *Client) GetProjectClient(clusterId string) (projectClient *ProjectClient, err error) { + cluster, err := client.DescribeCluster(clusterId) + if err != nil { + return + } + + certs, err := client.GetClusterCerts(clusterId) + if err != nil { + return + } + + projectClient, err = NewProjectClient(clusterId, cluster.MasterURL, certs) + + if err != nil { + return + } + + projectClient.SetDebug(client.debug) + + return +} diff --git a/vendor/github.com/denverdino/aliyungo/cs/projects.go b/vendor/github.com/denverdino/aliyungo/cs/projects.go new file mode 100644 index 00000000000..c2e18793301 --- /dev/null +++ b/vendor/github.com/denverdino/aliyungo/cs/projects.go @@ -0,0 +1,186 @@ +package cs + +import ( + "errors" + "net/http" + "net/url" + "strconv" + "strings" + "time" +) + +type Project struct { + Name string `json:"name"` + Description string `json:"description"` + Template string `json:"template"` + Version string `json:"version"` + Created string `json:"created"` + Updated string `json:"updated"` + DesiredState string `json:"desired_state"` + CurrentState string `json:"current_state"` + Environment map[string]string `json:"environment"` + Services []Service `json:"services"` +} + +type GetProjectsResponse []Project + +type GetProjectResponse Project + +type Port struct { + HostIP string `json:"host_ip"` + HostPort string `json:"host_port"` +} + +type Labels map[string]string + +type Definition struct { + Environment []string `json:"environment"` + Image string `json:"image"` + KernelMemory int `json:"kernel_memory"` + Labels Labels `json:"labels"` + MemLimit int `json:"mem_limit"` + MemswapLimit int `json:"memswap_limit"` + MemswapReservation int `json:"memswap_reservation"` + OomKillDisable bool `json:"oom_kill_disable"` + Restart string `json:"restart"` + ShmSize int `json:"shm_size"` + Volumes []string `json:"volumes"` +} + +type ProjectCreationArgs struct { + Name string `json:"name"` + Description string `json:"description"` + Template string `json:"template"` + Version string `json:"version"` + Environment map[string]string `json:"environment"` + LatestImage bool `json:"latest_image"` +} + +type ProjectUpdationArgs struct { + Name string `json:"-"` + Description string `json:"description"` + Template string `json:"template"` + Version string `json:"version"` + Environment map[string]string `json:"environment"` + LatestImage bool `json:"latest_image"` +} + +func (client *ProjectClient) GetProjects(q string, services, containers bool) (projects GetProjectsResponse, err error) { + query := make(url.Values) + + if len(q) != 0 { + query.Add("q", q) + } + + query.Add("services", strconv.FormatBool(services)) + query.Add("containers", strconv.FormatBool(containers)) + + err = client.Invoke(http.MethodGet, "/projects/", query, nil, &projects) + + return +} + +func (client *ProjectClient) GetProject(name string) (project GetProjectResponse, err error) { + + if len(name) == 0 { + err = errors.New("project name is empty") + return + } + + err = client.Invoke(http.MethodGet, "/projects/"+name, nil, nil, &project) + + return +} + +func (client *ProjectClient) StartProject(name string) (err error) { + + if len(name) == 0 { + err = errors.New("project name is empty") + return + } + + err = client.Invoke(http.MethodPost, "/projects/"+name+"/start", nil, nil, nil) + + return +} + +func (client *ProjectClient) StopProject(name string, timeout ...time.Duration) (err error) { + + if len(name) == 0 { + err = errors.New("project name is empty") + return + } + + query := make(url.Values) + + if len(timeout) > 0 { + if timeout[0] > 0 { + query.Add("t", strconv.Itoa(int(timeout[0].Seconds()))) + } + } + + err = client.Invoke(http.MethodPost, "/projects/"+name+"/stop", query, nil, nil) + + return +} + +func (client *ProjectClient) KillProject(name string, signal ...string) (err error) { + + if len(name) == 0 { + err = errors.New("project name is empty") + return + } + + query := make(url.Values) + + if len(signal) > 0 { + if len(signal[0]) > 0 { + query.Add("signal", signal[0]) + } + } + + err = client.Invoke(http.MethodPost, "/projects/"+name+"/kill", query, nil, nil) + + return +} + +func (client *ProjectClient) CreateProject(args *ProjectCreationArgs) (err error) { + + args.Template = strings.TrimSpace(args.Template) + + err = client.Invoke(http.MethodPost, "/projects/", nil, args, nil) + + return +} + +func (client *ProjectClient) UpdateProject(args *ProjectUpdationArgs) (err error) { + + if len(args.Name) == 0 { + err = errors.New("project name is empty") + return + } + + args.Template = strings.TrimSpace(args.Template) + + err = client.Invoke(http.MethodPost, "/projects/"+args.Name+"/update", nil, args, nil) + + return +} + +func (client *ProjectClient) DeleteProject(name string, forceDelete, deleteVolume bool) (err error) { + + if len(name) == 0 { + err = errors.New("project name is empty") + return + } + + query := make(url.Values) + + query.Add("name", name) + query.Add("force", strconv.FormatBool(forceDelete)) + query.Add("volume", strconv.FormatBool(deleteVolume)) + + err = client.Invoke(http.MethodDelete, "/projects/"+name, query, nil, nil) + + return +} diff --git a/vendor/github.com/denverdino/aliyungo/cs/projects_client.go b/vendor/github.com/denverdino/aliyungo/cs/projects_client.go new file mode 100644 index 00000000000..0890fbcd46a --- /dev/null +++ b/vendor/github.com/denverdino/aliyungo/cs/projects_client.go @@ -0,0 +1,156 @@ +package cs + +import ( + "bytes" + "crypto/tls" + "crypto/x509" + "encoding/json" + "io" + "io/ioutil" + "log" + "net/http" + "net/url" + "time" + + "github.com/denverdino/aliyungo/common" + "github.com/denverdino/aliyungo/util" +) + +type ProjectClient struct { + clusterId string + endpoint string + debug bool + userAgent string + httpClient *http.Client +} + +func NewProjectClient(clusterId, endpoint string, clusterCerts ClusterCerts) (client *ProjectClient, err error) { + + certs, err := tls.X509KeyPair([]byte(clusterCerts.Cert), []byte(clusterCerts.Key)) + + if err != nil { + return + } + + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM([]byte(clusterCerts.CA)) + + httpClient := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + Certificates: []tls.Certificate{certs}, + ClientCAs: caCertPool, + ClientAuth: tls.RequireAndVerifyClientCert, + }, + }, + } + + client = &ProjectClient{ + endpoint: endpoint, + httpClient: httpClient, + } + + return +} + +// SetDebug sets debug mode to log the request/response message +func (client *ProjectClient) SetDebug(debug bool) { + client.debug = debug +} + +// SetUserAgent sets user agent to log the request/response message +func (client *ProjectClient) SetUserAgent(userAgent string) { + client.userAgent = userAgent +} + +func (client *ProjectClient) ClusterId() string { + return client.clusterId +} + +func (client *ProjectClient) Endpoint() string { + return client.endpoint +} + +func (client *ProjectClient) Invoke(method string, path string, query url.Values, args interface{}, response interface{}) error { + var reqBody []byte + var err error + var contentType string + + if args != nil { + reqBody, err = json.Marshal(args) + if err != nil { + return err + } + contentType = "application/json" + } + + requestURL := client.endpoint + path + if query != nil && len(query) > 0 { + requestURL = requestURL + "?" + util.Encode(query) + } + var bodyReader io.Reader + if reqBody != nil { + bodyReader = bytes.NewReader(reqBody) + } + + httpReq, err := http.NewRequest(method, requestURL, bodyReader) + if err != nil { + return common.GetClientError(err) + } + + httpReq.Header.Set("Date", util.GetGMTime()) + httpReq.Header.Set("Accept", "application/json") + + if contentType != "" { + httpReq.Header.Set("Content-Type", contentType) + } + + if client.userAgent != "" { + httpReq.Header.Set("User-Agent", client.userAgent) + } + + t0 := time.Now() + httpResp, err := client.httpClient.Do(httpReq) + t1 := time.Now() + if err != nil { + return common.GetClientError(err) + } + statusCode := httpResp.StatusCode + + if client.debug { + log.Printf("Invoke %s %s %d (%v)", method, requestURL, statusCode, t1.Sub(t0)) + } + + defer httpResp.Body.Close() + body, err := ioutil.ReadAll(httpResp.Body) + + if err != nil { + return common.GetClientError(err) + } + + if client.debug { + var prettyJSON bytes.Buffer + err = json.Indent(&prettyJSON, body, "", " ") + log.Println(string(prettyJSON.Bytes())) + } + + if statusCode >= 400 && statusCode <= 599 { + errorResponse := common.ErrorResponse{} + err = json.Unmarshal(body, &errorResponse) + ecsError := &common.Error{ + ErrorResponse: errorResponse, + StatusCode: statusCode, + } + return ecsError + } + + if response != nil { + err = json.Unmarshal(body, response) + if err != nil { + return common.GetClientError(err) + } + } + + return nil +} diff --git a/vendor/github.com/denverdino/aliyungo/cs/services.go b/vendor/github.com/denverdino/aliyungo/cs/services.go new file mode 100644 index 00000000000..37f779a9051 --- /dev/null +++ b/vendor/github.com/denverdino/aliyungo/cs/services.go @@ -0,0 +1,143 @@ +package cs + +import ( + "errors" + "net/http" + "net/url" + "strconv" + "time" +) + +type Container struct { + Name string `json:"name"` + Node string `json:"node"` + VMID string `json:"vm_id"` + IP string `json:"ip"` + Running bool `json:"running"` + Status string `json:"status"` + Health string `json:"health"` + StatusExt string `json:"status_ext"` + FailCount int `json:"fail_count"` + Ports map[string][]Port `json:"ports"` +} + +type Service struct { + ID string `json:"id"` + Name string `json:"name"` + Project string `json:"project"` + ComposeVersion string `json:"compose_version"` + Containers map[string]Container `json:"containers"` + Created time.Time `json:"created"` + CurrentState string `json:"current_state"` + Definition Definition `json:"definition"` + DesiredState string `json:"desired_state"` + Extensions map[string]interface{} `json:"extensions"` + Hash string `json:"hash"` + Updated time.Time `json:"updated"` + Version string `json:"version"` +} + +type ScaleType string + +const ( + ScaleTo ScaleType = "scale_to" +) + +type ScaleServiceArgs struct { + ServiceId string `json:"-"` + Type ScaleType `json:"type"` + Value int `json:"value"` +} + +type GetServiceResponse Service +type GetServicesResponse []Service + +func (client *ProjectClient) GetServices(q string, containers bool) (services GetServicesResponse, err error) { + query := make(url.Values) + + if len(q) != 0 { + query.Add("q", q) + } + + query.Add("containers", strconv.FormatBool(containers)) + + err = client.Invoke(http.MethodGet, "/services/", query, nil, &services) + + return +} + +func (client *ProjectClient) GetService(serviceId string) (service GetServiceResponse, err error) { + + if len(serviceId) == 0 { + err = errors.New("service id is empty") + return + } + + err = client.Invoke(http.MethodGet, "/services/"+serviceId, nil, nil, &service) + + return +} + +func (client *ProjectClient) StartService(serviceId string) (err error) { + + if len(serviceId) == 0 { + err = errors.New("service id is empty") + return + } + + err = client.Invoke(http.MethodPost, "/services/"+serviceId+"/start", nil, nil, nil) + + return +} + +func (client *ProjectClient) StopService(serviceId string, timeout ...time.Duration) (err error) { + + if len(serviceId) == 0 { + err = errors.New("service id is empty") + return + } + + query := make(url.Values) + + if len(timeout) > 0 { + if timeout[0] > 0 { + query.Add("t", strconv.Itoa(int(timeout[0].Seconds()))) + } + } + + err = client.Invoke(http.MethodPost, "/services/"+serviceId+"/stop", query, nil, nil) + + return +} + +func (client *ProjectClient) KillService(serviceId string, signal ...string) (err error) { + + if len(serviceId) == 0 { + err = errors.New("service id is empty") + return + } + + query := make(url.Values) + + if len(signal) > 0 { + if len(signal[0]) > 0 { + query.Add("signal", signal[0]) + } + } + + err = client.Invoke(http.MethodPost, "/services/"+serviceId+"/kill", query, nil, nil) + + return +} + +func (client *ProjectClient) ScaleService(args *ScaleServiceArgs) (err error) { + + if len(args.ServiceId) == 0 { + err = errors.New("service id is empty") + return + } + + err = client.Invoke(http.MethodPost, "/services/"+args.ServiceId+"/scale", nil, args, nil) + + return +} diff --git a/vendor/github.com/denverdino/aliyungo/cs/signature.go b/vendor/github.com/denverdino/aliyungo/cs/signature.go new file mode 100644 index 00000000000..e8d94b5a60c --- /dev/null +++ b/vendor/github.com/denverdino/aliyungo/cs/signature.go @@ -0,0 +1,60 @@ +package cs + +import ( + "fmt" + "net/http" + "sort" + "strings" + + "github.com/denverdino/aliyungo/util" +) + +func (client *Client) signRequest(request *http.Request) { + + headers := request.Header + contentMd5 := headers.Get("Content-Md5") + contentType := headers.Get("Content-Type") + accept := headers.Get("Accept") + date := headers.Get("Date") + + canonicalizedResource := request.URL.RequestURI() + + _, canonicalizedHeader := canonicalizeHeader(headers) + + stringToSign := request.Method + "\n" + accept + "\n" + contentMd5 + "\n" + contentType + "\n" + date + "\n" + canonicalizedHeader + canonicalizedResource + + fmt.Printf("stringToSign = %s: ", stringToSign) + signature := util.CreateSignature(stringToSign, client.AccessKeySecret) + headers.Set("Authorization", "acs "+client.AccessKeyId+":"+signature) +} + +const headerOSSPrefix = "x-acs-" + +//Have to break the abstraction to append keys with lower case. +func canonicalizeHeader(headers http.Header) (newHeaders http.Header, result string) { + var canonicalizedHeaders []string + newHeaders = http.Header{} + + for k, v := range headers { + if lower := strings.ToLower(k); strings.HasPrefix(lower, headerOSSPrefix) { + newHeaders[lower] = v + canonicalizedHeaders = append(canonicalizedHeaders, lower) + } else { + newHeaders[k] = v + } + } + + sort.Strings(canonicalizedHeaders) + + var canonicalizedHeader string + + for _, k := range canonicalizedHeaders { + v := "" + if len(headers[k]) > 0 { + v = headers[k][0] + } + canonicalizedHeader += k + ":" + v + "\n" + } + + return newHeaders, canonicalizedHeader +} diff --git a/vendor/github.com/denverdino/aliyungo/cs/volumes.go b/vendor/github.com/denverdino/aliyungo/cs/volumes.go new file mode 100644 index 00000000000..1b9912b17c0 --- /dev/null +++ b/vendor/github.com/denverdino/aliyungo/cs/volumes.go @@ -0,0 +1,89 @@ +package cs + +import ( + "net/http" +) + +type VolumeDriverType string + +const ( + OSSFSDriver VolumeDriverType = "ossfs" + NASDriver VolumeDriverType = "nas" +) + +type DriverOptions interface { + driverOptions() +} + +type OSSOpts struct { + Bucket string `json:"bucket"` + AccessKeyId string `json:"ak_id"` + AccessKeySecret string `json:"ak_secret"` + URL string `json:"url"` + NoStatCache string `json:"no_stat_cache"` + OtherOpts string `json:"other_opts"` +} + +func (*OSSOpts) driverOptions() {} + +type NASOpts struct { + DiskId string `json:"disk_id"` + Host string `json:"host"` + Path string `json:"path"` + Mode string `json:"mode"` +} + +func (*NASOpts) driverOptions() {} + +type VolumeRef struct { + Name string `json:"Name"` + ID string `json:"ID"` +} + +type VolumeCreationArgs struct { + Name string `json:"name"` + Driver VolumeDriverType `json:"driver"` + DriverOpts DriverOptions `json:"driverOpts"` +} + +type VolumeCreationResponse struct { + Name string `json:"Name"` + Driver string `json:"Driver"` + Mountpoint string `json:"Mountpoint"` + Labels map[string]string `json:"Labels"` + Scope string `json:"Scope"` +} + +type GetVolumeResponse struct { + Name string `json:"Name"` + Driver string `json:"Driver"` + Mountpoint string `json:"Mountpoint"` + Labels map[string]string `json:"Labels"` + Scope string `json:"Scope"` + Node string `json:"Node"` + Refs []VolumeRef `json:"Refs"` +} + +type GetVolumesResponse struct { + Volumes []GetVolumeResponse `json:"Volumes"` +} + +func (client *ProjectClient) CreateVolume(args *VolumeCreationArgs) (err error) { + err = client.Invoke(http.MethodPost, "/volumes/create", nil, args, nil) + return +} + +func (client *ProjectClient) GetVolume(name string) (volume GetVolumeResponse, err error) { + err = client.Invoke(http.MethodGet, "/volumes/"+name, nil, nil, &volume) + return +} + +func (client *ProjectClient) GetVolumes() (volumes GetVolumesResponse, err error) { + err = client.Invoke(http.MethodGet, "/volumes", nil, nil, &volumes) + return +} + +func (client *ProjectClient) DeleteVolume(name string) (err error) { + err = client.Invoke(http.MethodDelete, "/volumes/"+name, nil, nil, nil) + return +} diff --git a/vendor/github.com/denverdino/aliyungo/ecs/client.go b/vendor/github.com/denverdino/aliyungo/ecs/client.go index d70a1554eb2..5b9d62619ba 100644 --- a/vendor/github.com/denverdino/aliyungo/ecs/client.go +++ b/vendor/github.com/denverdino/aliyungo/ecs/client.go @@ -20,8 +20,7 @@ const ( // ECSDefaultEndpoint is the default API endpoint of ECS services ECSDefaultEndpoint = "https://ecs-cn-hangzhou.aliyuncs.com" ECSAPIVersion = "2014-05-26" - - ECSServiceCode = "ecs" + ECSServiceCode = "ecs" VPCDefaultEndpoint = "https://vpc.aliyuncs.com" VPCAPIVersion = "2016-04-28" @@ -37,38 +36,89 @@ func NewClient(accessKeyId, accessKeySecret string) *Client { return NewClientWithEndpoint(endpoint, accessKeyId, accessKeySecret) } +func NewClientWithRegion(endpoint string, accessKeyId string, accessKeySecret string, regionID common.Region) *Client { + client := &Client{} + client.NewInit(endpoint, ECSAPIVersion, accessKeyId, accessKeySecret, ECSServiceCode, regionID) + return client +} + +func NewClientWithEndpoint(endpoint string, accessKeyId string, accessKeySecret string) *Client { + client := &Client{} + client.Init(endpoint, ECSAPIVersion, accessKeyId, accessKeySecret) + return client +} + +// --------------------------------------- +// NewECSClient creates a new instance of ECS client +// --------------------------------------- func NewECSClient(accessKeyId, accessKeySecret string, regionID common.Region) *Client { + return NewECSClientWithSecurityToken(accessKeyId, accessKeySecret, "", regionID) +} + +func NewECSClientWithSecurityToken(accessKeyId string, accessKeySecret string, securityToken string, regionID common.Region) *Client { endpoint := os.Getenv("ECS_ENDPOINT") if endpoint == "" { endpoint = ECSDefaultEndpoint } - return NewClientWithRegion(endpoint, accessKeyId, accessKeySecret, regionID) + return NewECSClientWithEndpointAndSecurityToken(endpoint, accessKeyId, accessKeySecret, securityToken, regionID) } -func NewClientWithRegion(endpoint string, accessKeyId, accessKeySecret string, regionID common.Region) *Client { - client := &Client{} - client.NewInit(endpoint, ECSAPIVersion, accessKeyId, accessKeySecret, ECSServiceCode, regionID) - return client +func NewECSClientWithEndpoint(endpoint string, accessKeyId string, accessKeySecret string, regionID common.Region) *Client { + return NewECSClientWithEndpointAndSecurityToken(endpoint, accessKeyId, accessKeySecret, "", regionID) } -func NewClientWithEndpoint(endpoint string, accessKeyId, accessKeySecret string) *Client { +func NewECSClientWithEndpointAndSecurityToken(endpoint string, accessKeyId string, accessKeySecret string, securityToken string, regionID common.Region) *Client { client := &Client{} - client.Init(endpoint, ECSAPIVersion, accessKeyId, accessKeySecret) + client.WithEndpoint(endpoint). + WithVersion(ECSAPIVersion). + WithAccessKeyId(accessKeyId). + WithAccessKeySecret(accessKeySecret). + WithSecurityToken(securityToken). + WithServiceCode(ECSServiceCode). + WithRegionID(regionID). + InitClient() return client } -func NewVPCClient(accessKeyId, accessKeySecret string, regionID common.Region) *Client { +// --------------------------------------- +// NewVPCClient creates a new instance of VPC client +// --------------------------------------- +func NewVPCClient(accessKeyId string, accessKeySecret string, regionID common.Region) *Client { + return NewVPCClientWithSecurityToken(accessKeyId, accessKeySecret, "", regionID) +} + +func NewVPCClientWithSecurityToken(accessKeyId string, accessKeySecret string, securityToken string, regionID common.Region) *Client { endpoint := os.Getenv("VPC_ENDPOINT") if endpoint == "" { endpoint = VPCDefaultEndpoint } - return NewVPCClientWithRegion(endpoint, accessKeyId, accessKeySecret, regionID) + return NewVPCClientWithEndpointAndSecurityToken(endpoint, accessKeyId, accessKeySecret, securityToken, regionID) } -func NewVPCClientWithRegion(endpoint string, accessKeyId, accessKeySecret string, regionID common.Region) *Client { +func NewVPCClientWithEndpoint(endpoint string, accessKeyId string, accessKeySecret string, regionID common.Region) *Client { + return NewVPCClientWithEndpointAndSecurityToken(endpoint, accessKeyId, accessKeySecret, "", regionID) +} + +func NewVPCClientWithEndpointAndSecurityToken(endpoint string, accessKeyId string, accessKeySecret string, securityToken string, regionID common.Region) *Client { client := &Client{} - client.NewInit(endpoint, VPCAPIVersion, accessKeyId, accessKeySecret, VPCServiceCode, regionID) + client.WithEndpoint(endpoint). + WithVersion(VPCAPIVersion). + WithAccessKeyId(accessKeyId). + WithAccessKeySecret(accessKeySecret). + WithSecurityToken(securityToken). + WithServiceCode(VPCServiceCode). + WithRegionID(regionID). + InitClient() return client } + +// --------------------------------------- +// NewVPCClientWithRegion creates a new instance of VPC client automatically get endpoint +// --------------------------------------- +func NewVPCClientWithRegion(endpoint string, accessKeyId string, accessKeySecret string, regionID common.Region) *Client { + client := &Client{} + client.NewInit(endpoint, VPCAPIVersion, accessKeyId, accessKeySecret, VPCServiceCode, regionID) + return client +} \ No newline at end of file diff --git a/vendor/github.com/denverdino/aliyungo/ecs/disks.go b/vendor/github.com/denverdino/aliyungo/ecs/disks.go index 2cfbffc379f..6b898c60d6d 100644 --- a/vendor/github.com/denverdino/aliyungo/ecs/disks.go +++ b/vendor/github.com/denverdino/aliyungo/ecs/disks.go @@ -241,6 +241,29 @@ func (client *Client) DetachDisk(instanceId string, diskId string) error { return err } +type ResizeDiskArgs struct { + DiskId string + NewSize int +} + +type ResizeDiskResponse struct { + common.Response +} + +// +// ResizeDisk can only support to enlarge disk size +// You can read doc at https://help.aliyun.com/document_detail/25522.html +func (client *Client) ResizeDisk(diskId string, sizeGB int) error { + args := ResizeDiskArgs{ + DiskId:diskId, + NewSize:sizeGB, + } + response := ResizeDiskResponse{} + err := client.Invoke("ResizeDisk", &args, &response) + return err +} + + type ResetDiskArgs struct { DiskId string SnapshotId string @@ -250,6 +273,7 @@ type ResetDiskResponse struct { common.Response } + // ResetDisk resets disk to original status // // You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/disk&resetdisk diff --git a/vendor/github.com/denverdino/aliyungo/ecs/eni.go b/vendor/github.com/denverdino/aliyungo/ecs/eni.go new file mode 100644 index 00000000000..38173a029cd --- /dev/null +++ b/vendor/github.com/denverdino/aliyungo/ecs/eni.go @@ -0,0 +1,108 @@ +package ecs + +import "github.com/denverdino/aliyungo/common" + +type CreateNetworkInterfaceArgs struct { + RegionId common.Region + VSwitchId string + PrimaryIpAddress string // optional + SecurityGroupId string + NetworkInterfaceName string // optional + Description string // optional + ClientToken string // optional +} + +type CreateNetworkInterfaceResponse struct { + common.Response + NetworkInterfaceId string +} +type DeleteNetworkInterfaceArgs struct { + RegionId common.Region + NetworkInterfaceId string +} + +type DeleteNetworkInterfaceResponse struct { + common.Response +} + +type DescribeNetworkInterfacesArgs struct { + RegionId common.Region + VSwitchId string + PrimaryIpAddress string + SecurityGroupId string + NetworkInterfaceName string + Type string + InstanceId string + NetworkInterfaceId []string + PageNumber int + PageSize int +} +type NetworkInterfaceType struct { + NetworkInterfaceId string + PrimaryIpAddress string + MacAddress string +} + +type DescribeNetworkInterfacesResponse struct { + common.Response + NetworkInterfaceSet []NetworkInterfaceType + TotalCount int + PageNumber int + PageSize int +} +type AttachNetworkInterfaceArgs struct { + RegionId common.Region + NetworkInterfaceId string + InstanceId string +} + +type AttachNetworkInterfaceResponse common.Response + +type DetachNetworkInterfaceArgs AttachNetworkInterfaceArgs + +type DetachNetworkInterfaceResponse common.Response + +type ModifyNetworkInterfaceAttributeArgs struct { + RegionId common.Region + NetworkInterfaceId string + SecurityGroupId []string + NetworkInterfaceName string + Description string +} +type ModifyNetworkInterfaceAttributeResponse common.Response + +func (client *Client) CreateNetworkInterface(args *CreateNetworkInterfaceArgs) (resp *CreateNetworkInterfaceResponse, err error) { + resp = &CreateNetworkInterfaceResponse{} + err = client.Invoke("CreateNetworkInterface", args, resp) + return resp, err +} + +func (client *Client) DeleteNetworkInterface(args *DeleteNetworkInterfaceArgs) (resp *DeleteNetworkInterfaceResponse, err error) { + resp = &DeleteNetworkInterfaceResponse{} + err = client.Invoke("DeleteNetworkInterface", args, resp) + return resp, err +} + +func (client *Client) DescribeNetworkInterfaces(args *DescribeNetworkInterfacesArgs) (resp *DescribeNetworkInterfacesResponse, err error) { + resp = &DescribeNetworkInterfacesResponse{} + err = client.Invoke("DescribeNetworkInterfaces", args, resp) + return resp, err +} + +func (client *Client) AttachNetworkInterface(args *AttachNetworkInterfaceArgs) (resp *AttachNetworkInterfaceResponse, err error) { + resp = &AttachNetworkInterfaceResponse{} + err = client.Invoke("AttachNetworkInterface", args, resp) + return resp, err +} + +func (client *Client) DetachNetworkInterface(args *DetachNetworkInterfaceArgs) (resp *DetachNetworkInterfaceResponse, err error) { + resp = &DetachNetworkInterfaceResponse{} + err = client.Invoke("DetachNetworkInterface", args, resp) + return resp, err +} + +func (client *Client) ModifyNetworkInterfaceAttribute(args *ModifyNetworkInterfaceAttributeArgs) (resp *ModifyNetworkInterfaceAttributeResponse, err error) { + resp = &ModifyNetworkInterfaceAttributeResponse{} + err = client.Invoke("ModifyNetworkInterfaceAttribute", args, resp) + return resp, err +} diff --git a/vendor/github.com/denverdino/aliyungo/ecs/images.go b/vendor/github.com/denverdino/aliyungo/ecs/images.go index 54fe86defe9..9dea891dd8b 100644 --- a/vendor/github.com/denverdino/aliyungo/ecs/images.go +++ b/vendor/github.com/denverdino/aliyungo/ecs/images.go @@ -37,6 +37,13 @@ const ( ImageUsageNone = ImageUsage("none") ) +type ImageFormatType string + +const ( + RAW = ImageFormatType("RAW") + VHD = ImageFormatType("VHD") +) + // DescribeImagesArgs repsents arguments to describe images type DescribeImagesArgs struct { RegionId common.Region @@ -64,6 +71,8 @@ type DiskDeviceMapping struct { SnapshotId string //Why Size Field is string-type. Size string + // Now the key Size change to DiskImageSize + DiskImageSize string Device string //For import images Format string diff --git a/vendor/github.com/denverdino/aliyungo/ecs/instances.go b/vendor/github.com/denverdino/aliyungo/ecs/instances.go index a10bcc05357..74be6e50130 100644 --- a/vendor/github.com/denverdino/aliyungo/ecs/instances.go +++ b/vendor/github.com/denverdino/aliyungo/ecs/instances.go @@ -224,6 +224,7 @@ type SpotStrategyType string const ( NoSpot = SpotStrategyType("NoSpot") SpotWithPriceLimit = SpotStrategyType("SpotWithPriceLimit") + SpotAsPriceGo = SpotStrategyType("SpotAsPriceGo") ) // @@ -244,7 +245,7 @@ type InstanceAttributesType struct { SerialNumber string Status InstanceStatus OperationLocks OperationLocksType - SecurityGroupIds struct { + SecurityGroupIds struct { SecurityGroupId []string } PublicIpAddress IpAddressSetType @@ -259,11 +260,12 @@ type InstanceAttributesType struct { IoOptimized StringOrBool InstanceChargeType common.InstanceChargeType ExpiredTime util.ISO6801Time - Tags struct { + Tags struct { Tag []TagItemType } - SpotStrategy SpotStrategyType - KeyPairName string + SpotStrategy SpotStrategyType + SpotPriceLimit float64 + KeyPairName string } type DescribeInstanceAttributeResponse struct { @@ -438,6 +440,28 @@ func (client *Client) DescribeInstancesWithRaw(args *DescribeInstancesArgs) (res return response, nil } +type ModifyInstanceAutoReleaseTimeArgs struct { + InstanceId string + AutoReleaseTime string +} + +type ModifyInstanceAutoReleaseTimeResponse struct { + common.Response +} + +// 对给定的实例设定自动释放时间。 +// +// You can read doc at https://help.aliyun.com/document_detail/47576.html +func (client *Client) ModifyInstanceAutoReleaseTime(instanceId, time string) error { + args := ModifyInstanceAutoReleaseTimeArgs{ + InstanceId: instanceId, + AutoReleaseTime: time, + } + response := ModifyInstanceAutoReleaseTimeResponse{} + err := client.Invoke("ModifyInstanceAutoReleaseTime", &args, &response) + return err +} + type DeleteInstanceArgs struct { InstanceId string } @@ -535,6 +559,7 @@ type CreateInstanceArgs struct { AutoRenew bool AutoRenewPeriod int SpotStrategy SpotStrategyType + SpotPriceLimit float64 KeyPairName string RamRoleName string } @@ -658,7 +683,7 @@ func (client *Client) DetachInstanceRamRole(args *AttachInstancesArgs) (err erro type DescribeInstanceRamRoleResponse struct { common.Response - InstanceRamRoleSets struct{ + InstanceRamRoleSets struct { InstanceRamRoleSet []InstanceRamRoleSetType } } diff --git a/vendor/github.com/denverdino/aliyungo/ecs/router_interface.go b/vendor/github.com/denverdino/aliyungo/ecs/router_interface.go index 62af6786023..3cdef2f99ed 100644 --- a/vendor/github.com/denverdino/aliyungo/ecs/router_interface.go +++ b/vendor/github.com/denverdino/aliyungo/ecs/router_interface.go @@ -2,6 +2,7 @@ package ecs import ( "github.com/denverdino/aliyungo/common" + "time" ) type EcsCommonResponse struct { @@ -19,6 +20,8 @@ const ( Idl = InterfaceStatus("Idl") Active = InterfaceStatus("Active") Inactive = InterfaceStatus("Inactive") + // 'Idle' means the router interface is not connected. 'Idl' may be a incorrect status. + Idle = InterfaceStatus("Idle") InitiatingSide = Role("InitiatingSide") AcceptingSide = Role("AcceptingSide") @@ -225,3 +228,30 @@ func (client *Client) DeleteRouterInterface(args *OperateRouterInterfaceArgs) (r } return response, nil } + +// WaitForRouterInterface waits for router interface to given status +func (client *Client) WaitForRouterInterfaceAsyn(regionId common.Region, interfaceId string, status InterfaceStatus, timeout int) error { + if timeout <= 0 { + timeout = InstanceDefaultTimeout + } + for { + interfaces, err := client.DescribeRouterInterfaces(&DescribeRouterInterfacesArgs{ + RegionId: regionId, + Filter: []Filter{Filter{Key: "RouterInterfaceId", Value: []string{interfaceId}}}, + + }) + if err != nil { + return err + } else if interfaces != nil && InterfaceStatus(interfaces.RouterInterfaceSet.RouterInterfaceType[0].Status) == status { + //TODO + break + } + timeout = timeout - DefaultWaitForInterval + if timeout <= 0 { + return common.GetClientErrorFromString("Timeout") + } + time.Sleep(DefaultWaitForInterval * time.Second) + + } + return nil +} \ No newline at end of file diff --git a/vendor/github.com/denverdino/aliyungo/ecs/security_groups.go b/vendor/github.com/denverdino/aliyungo/ecs/security_groups.go index 54af3a79824..a35de940756 100644 --- a/vendor/github.com/denverdino/aliyungo/ecs/security_groups.go +++ b/vendor/github.com/denverdino/aliyungo/ecs/security_groups.go @@ -6,10 +6,15 @@ import ( ) type NicType string +type Direction string const ( NicTypeInternet = NicType("internet") NicTypeIntranet = NicType("intranet") + + DirectionIngress = Direction("ingress") + DirectionEgress = Direction("egress") + DirectionAll = Direction("all") ) type IpProtocol string @@ -32,8 +37,8 @@ const ( type DescribeSecurityGroupAttributeArgs struct { SecurityGroupId string RegionId common.Region - NicType NicType //enum for internet (default) |intranet - Direction string // enum ingress egress + NicType NicType //enum for internet (default) |intranet + Direction Direction // enum ingress egress } // diff --git a/vendor/github.com/denverdino/aliyungo/ecs/snapshots.go b/vendor/github.com/denverdino/aliyungo/ecs/snapshots.go index f3c1b09c55e..0b7cbe87bf0 100644 --- a/vendor/github.com/denverdino/aliyungo/ecs/snapshots.go +++ b/vendor/github.com/denverdino/aliyungo/ecs/snapshots.go @@ -26,6 +26,8 @@ type SnapshotType struct { SourceDiskSize int SourceDiskType string //enum for System | Data ProductCode string + Status string + Usage string CreationTime util.ISO6801Time } diff --git a/vendor/github.com/denverdino/aliyungo/ess/configuration.go b/vendor/github.com/denverdino/aliyungo/ess/configuration.go index 59fae142b8b..28d1c291a72 100644 --- a/vendor/github.com/denverdino/aliyungo/ess/configuration.go +++ b/vendor/github.com/denverdino/aliyungo/ess/configuration.go @@ -3,6 +3,7 @@ package ess import ( "github.com/denverdino/aliyungo/common" "github.com/denverdino/aliyungo/ecs" + "encoding/base64" ) type CreateScalingConfigurationArgs struct { @@ -18,6 +19,10 @@ type CreateScalingConfigurationArgs struct { SystemDisk_Category common.UnderlineString SystemDisk_Size common.UnderlineString DataDisk []DataDiskType + UserData string + KeyPairName string + RamRoleName string + Tags string } type DataDiskType struct { @@ -36,6 +41,10 @@ type CreateScalingConfigurationResponse struct { // // You can read doc at https://help.aliyun.com/document_detail/25944.html?spm=5176.doc25942.6.625.KcE5ir func (client *Client) CreateScalingConfiguration(args *CreateScalingConfigurationArgs) (resp *CreateScalingConfigurationResponse, err error) { + if args.UserData != "" { + // Encode to base64 string + args.UserData = base64.StdEncoding.EncodeToString([]byte(args.UserData)) + } response := CreateScalingConfigurationResponse{} err = client.InvokeByFlattenMethod("CreateScalingConfiguration", args, &response) @@ -60,6 +69,10 @@ type DescribeScalingConfigurationsResponse struct { ScalingConfiguration []ScalingConfigurationItemType } } +type TagItemType struct { + Key string + Value string +} type ScalingConfigurationItemType struct { ScalingConfigurationId string @@ -78,6 +91,12 @@ type ScalingConfigurationItemType struct { DataDisks struct { DataDisk []DataDiskItemType } + KeyPairName string + RamRoleName string + UserData string + Tags struct { + Tag []TagItemType + } } type DataDiskItemType struct { diff --git a/vendor/github.com/denverdino/aliyungo/ess/group.go b/vendor/github.com/denverdino/aliyungo/ess/group.go index 4441beb5ef9..709601d7938 100644 --- a/vendor/github.com/denverdino/aliyungo/ess/group.go +++ b/vendor/github.com/denverdino/aliyungo/ess/group.go @@ -6,7 +6,7 @@ type LifecycleState string const ( Active = LifecycleState("Active") - Inacitve = LifecycleState("Inacitve") + Inacitve = LifecycleState("Inactive") Deleting = LifecycleState("Deleting") InService = LifecycleState("InService") Pending = LifecycleState("Pending") @@ -19,9 +19,10 @@ type CreateScalingGroupArgs struct { LoadBalancerId string VpcId string VSwitchId string - MaxSize int - MinSize int - DefaultCooldown int + // NOTE: Set MinSize, MaxSize and DefaultCooldown type to int pointer to distinguish zero value from unset value. + MinSize *int + MaxSize *int + DefaultCooldown *int RemovalPolicy common.FlattenArray DBInstanceId common.FlattenArray } @@ -48,10 +49,11 @@ type ModifyScalingGroupArgs struct { ScalingGroupId string ScalingGroupName string ActiveScalingConfigurationId string - MinSize int - MaxSize int - DefaultCooldown int - RemovalPolicy common.FlattenArray + // NOTE: Set MinSize/MaxSize type to int pointer to distinguish zero value from unset value. + MinSize *int + MaxSize *int + DefaultCooldown *int + RemovalPolicy common.FlattenArray } type ModifyScalingGroupResponse struct { @@ -251,6 +253,16 @@ type AttachInstancesResponse struct { ScalingActivityId string } +type RemoveInstancesArgs struct { + ScalingGroupId string + InstanceId common.FlattenArray +} + +type RemoveInstancesResponse struct { + common.Response + ScalingActivityId string +} + // AttachInstances attach instances to scaling group // // You can read doc at https://help.aliyun.com/document_detail/25954.html?spm=5176.product25855.6.633.y5gmzX @@ -267,12 +279,12 @@ func (client *Client) AttachInstances(args *AttachInstancesArgs) (resp *AttachIn // RemoveInstances detach instances from scaling group // // You can read doc at https://help.aliyun.com/document_detail/25955.html?spm=5176.doc25954.6.634.GtpzuJ -func (client *Client) RemoveInstances(args *AttachInstancesArgs) (resp *AttachInstancesResponse, err error) { - response := AttachInstancesResponse{} +func (client *Client) RemoveInstances(args *RemoveInstancesArgs) (resp *RemoveInstancesResponse, err error) { + response := RemoveInstancesResponse{} err = client.InvokeByFlattenMethod("RemoveInstances", args, &response) if err != nil { return nil, err } return &response, nil -} \ No newline at end of file +} diff --git a/vendor/github.com/denverdino/aliyungo/ess/rule.go b/vendor/github.com/denverdino/aliyungo/ess/rule.go index b6ce2900243..399ec1ae7c1 100644 --- a/vendor/github.com/denverdino/aliyungo/ess/rule.go +++ b/vendor/github.com/denverdino/aliyungo/ess/rule.go @@ -128,3 +128,25 @@ func (client *Client) DeleteScalingRule(args *DeleteScalingRuleArgs) (resp *Dele } return &response, nil } + +type ExecuteScalingRuleArgs struct { + ScalingRuleAri string + ClientToken string +} + +type ExecuteScalingRuleResponse struct { + common.Response + ScalingActivityId string +} + +// ExecuteScalingRule execute scaling rule +// +// You can read doc at https://help.aliyun.com/document_detail/25953.html?spm=5176.doc25961.6.632.7sXDx6 +func (client *Client) ExecuteScalingRule(args *ExecuteScalingRuleArgs) (*ExecuteScalingRuleResponse, error) { + resp := ExecuteScalingRuleResponse{} + err := client.InvokeByFlattenMethod("ExecuteScalingRule", args, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} diff --git a/vendor/github.com/denverdino/aliyungo/ram/client.go b/vendor/github.com/denverdino/aliyungo/ram/client.go index 974bc023f95..2a29bd804eb 100644 --- a/vendor/github.com/denverdino/aliyungo/ram/client.go +++ b/vendor/github.com/denverdino/aliyungo/ram/client.go @@ -1,8 +1,9 @@ package ram import ( - "github.com/denverdino/aliyungo/common" "os" + + "github.com/denverdino/aliyungo/common" ) const ( @@ -16,15 +17,29 @@ type RamClient struct { } func NewClient(accessKeyId string, accessKeySecret string) RamClientInterface { + return NewClientWithSecurityToken(accessKeyId, accessKeySecret, "") +} + +func NewClientWithSecurityToken(accessKeyId string, accessKeySecret string, securityToken string) RamClientInterface { endpoint := os.Getenv("RAM_ENDPOINT") if endpoint == "" { endpoint = RAMDefaultEndpoint } - return NewClientWithEndpoint(endpoint, accessKeyId, accessKeySecret) + + return NewClientWithEndpointAndSecurityToken(endpoint, accessKeyId, accessKeySecret, securityToken) } func NewClientWithEndpoint(endpoint string, accessKeyId string, accessKeySecret string) RamClientInterface { + return NewClientWithEndpointAndSecurityToken(endpoint, accessKeyId, accessKeySecret, "") +} + +func NewClientWithEndpointAndSecurityToken(endpoint string, accessKeyId string, accessKeySecret string, securityToken string) RamClientInterface { client := &RamClient{} - client.Init(endpoint, RAMAPIVersion, accessKeyId, accessKeySecret) + client.WithEndpoint(endpoint). + WithVersion(RAMAPIVersion). + WithAccessKeyId(accessKeyId). + WithAccessKeySecret(accessKeySecret). + WithSecurityToken(securityToken). + InitClient() return client } diff --git a/vendor/github.com/denverdino/aliyungo/rds/instances.go b/vendor/github.com/denverdino/aliyungo/rds/instances.go index f73e4e58e3b..55b13ba57de 100644 --- a/vendor/github.com/denverdino/aliyungo/rds/instances.go +++ b/vendor/github.com/denverdino/aliyungo/rds/instances.go @@ -14,8 +14,10 @@ type DBInstanceIPArray struct { // ref: https://help.aliyun.com/document_detail/26242.html type ModifySecurityIpsArgs struct { - DBInstanceId string - DBInstanceIPArray + DBInstanceId string + SecurityIps string + DBInstanceIPArrayName string + DBInstanceIPArrayAttribute string } func (client *Client) ModifySecurityIps(args *ModifySecurityIpsArgs) (resp common.Response, err error) { @@ -229,7 +231,6 @@ type DBInstanceAttribute struct { VpcId string } - type ReadOnlyDBInstanceIds struct { ReadOnlyDBInstanceId []ReadOnlyDBInstanceId } diff --git a/vendor/github.com/denverdino/aliyungo/slb/loadbalancers.go b/vendor/github.com/denverdino/aliyungo/slb/loadbalancers.go index d090570cee4..e30fdb53497 100644 --- a/vendor/github.com/denverdino/aliyungo/slb/loadbalancers.go +++ b/vendor/github.com/denverdino/aliyungo/slb/loadbalancers.go @@ -27,6 +27,8 @@ type CreateLoadBalancerArgs struct { InternetChargeType InternetChargeType Bandwidth int ClientToken string + MasterZoneId string + SlaveZoneId string } type CreateLoadBalancerResponse struct { diff --git a/vendor/github.com/denverdino/aliyungo/util/encoding.go b/vendor/github.com/denverdino/aliyungo/util/encoding.go index 8cb58828803..99a508f5b46 100644 --- a/vendor/github.com/denverdino/aliyungo/util/encoding.go +++ b/vendor/github.com/denverdino/aliyungo/util/encoding.go @@ -66,24 +66,26 @@ func setQueryValues(i interface{}, values *url.Values, prefix string) { // TODO Use Tag for validation // tag := typ.Field(i).Tag.Get("tagname") kind := field.Kind() + isPtr := false if (kind == reflect.Ptr || kind == reflect.Array || kind == reflect.Slice || kind == reflect.Map || kind == reflect.Chan) && field.IsNil() { continue } if kind == reflect.Ptr { field = field.Elem() kind = field.Kind() + isPtr = true } var value string //switch field.Interface().(type) { switch kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: i := field.Int() - if i != 0 { + if i != 0 || isPtr { value = strconv.FormatInt(i, 10) } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: i := field.Uint() - if i != 0 { + if i != 0 || isPtr { value = strconv.FormatUint(i, 10) } case reflect.Float32: @@ -197,12 +199,14 @@ func setQueryValuesByFlattenMethod(i interface{}, values *url.Values, prefix str // tag := typ.Field(i).Tag.Get("tagname") kind := field.Kind() + isPtr := false if (kind == reflect.Ptr || kind == reflect.Array || kind == reflect.Slice || kind == reflect.Map || kind == reflect.Chan) && field.IsNil() { continue } if kind == reflect.Ptr { field = field.Elem() kind = field.Kind() + isPtr = true } var value string @@ -210,12 +214,12 @@ func setQueryValuesByFlattenMethod(i interface{}, values *url.Values, prefix str switch kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: i := field.Int() - if i != 0 { + if i != 0 || isPtr { value = strconv.FormatInt(i, 10) } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: i := field.Uint() - if i != 0 { + if i != 0 || isPtr { value = strconv.FormatUint(i, 10) } case reflect.Float32: diff --git a/vendor/vendor.json b/vendor/vendor.json index 15fde1038ce..82c6b6c9f48 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -235,62 +235,68 @@ { "checksumSHA1": "9JK6TQ6tSG68roB1KWs3NIxw6zY=", "path": "github.com/denverdino/aliyungo/cdn", - "revision": "199fa376135951b938a53634048bb32e5dc823c1", - "revisionTime": "2017-10-16T07:52:46Z" + "revision": "d7c7e038ca2b9ffd3c2b5525d9eae4a31b0704c7", + "revisionTime": "2017-12-07T06:26:40Z" }, { - "checksumSHA1": "b2m7aICkBOz9JqmnX2kdDN4wgjI=", + "checksumSHA1": "qvFutFkayxTxJN3E1AENo4kHEzA=", "path": "github.com/denverdino/aliyungo/common", - "revision": "199fa376135951b938a53634048bb32e5dc823c1", - "revisionTime": "2017-10-16T07:52:46Z" + "revision": "d7c7e038ca2b9ffd3c2b5525d9eae4a31b0704c7", + "revisionTime": "2017-12-07T06:26:40Z" + }, + { + "checksumSHA1": "iINyIl3tdnJ1149OjjDIYM8o7gk=", + "path": "github.com/denverdino/aliyungo/cs", + "revision": "d7c7e038ca2b9ffd3c2b5525d9eae4a31b0704c7", + "revisionTime": "2017-12-07T06:26:40Z" }, { "checksumSHA1": "zHBzMaiaG1TbhrU15Wp/7Rbl4ZY=", "path": "github.com/denverdino/aliyungo/dns", - "revision": "199fa376135951b938a53634048bb32e5dc823c1", - "revisionTime": "2017-10-16T07:52:46Z" + "revision": "d7c7e038ca2b9ffd3c2b5525d9eae4a31b0704c7", + "revisionTime": "2017-12-07T06:26:40Z" }, { - "checksumSHA1": "t+oOAxTaWYS0iZVry7iaFgYX5Eo=", + "checksumSHA1": "dRCnl3Jccqs69IUO8s90ZUez/Vc=", "path": "github.com/denverdino/aliyungo/ecs", - "revision": "199fa376135951b938a53634048bb32e5dc823c1", - "revisionTime": "2017-10-16T07:52:46Z" + "revision": "d7c7e038ca2b9ffd3c2b5525d9eae4a31b0704c7", + "revisionTime": "2017-12-07T06:26:40Z" }, { - "checksumSHA1": "P9SMbUnwF+sBLZc1BspupVholKc=", + "checksumSHA1": "0qcm0qCFWDyeYjG+y596rgwEcQ4=", "path": "github.com/denverdino/aliyungo/ess", - "revision": "199fa376135951b938a53634048bb32e5dc823c1", - "revisionTime": "2017-10-16T07:52:46Z" + "revision": "d7c7e038ca2b9ffd3c2b5525d9eae4a31b0704c7", + "revisionTime": "2017-12-07T06:26:40Z" }, { "checksumSHA1": "CPuR3s7LzQkT57Z20zMHXQM2MYQ=", "path": "github.com/denverdino/aliyungo/location", - "revision": "199fa376135951b938a53634048bb32e5dc823c1", - "revisionTime": "2017-10-16T07:52:46Z" + "revision": "d7c7e038ca2b9ffd3c2b5525d9eae4a31b0704c7", + "revisionTime": "2017-12-07T06:26:40Z" }, { - "checksumSHA1": "Cc/BNEn/OKuBLhztBhg00YI23Zo=", + "checksumSHA1": "sievsBvgtVF2iZ2FjmDZppH3+Ro=", "path": "github.com/denverdino/aliyungo/ram", - "revision": "199fa376135951b938a53634048bb32e5dc823c1", - "revisionTime": "2017-10-16T07:52:46Z" + "revision": "d7c7e038ca2b9ffd3c2b5525d9eae4a31b0704c7", + "revisionTime": "2017-12-07T06:26:40Z" }, { - "checksumSHA1": "EaiPS3gTXG0g4DZGeTB07SARwr0=", + "checksumSHA1": "5muc4YGlXvIK6BwWNXQppIEcEkg=", "path": "github.com/denverdino/aliyungo/rds", - "revision": "199fa376135951b938a53634048bb32e5dc823c1", - "revisionTime": "2017-10-16T07:52:46Z" + "revision": "d7c7e038ca2b9ffd3c2b5525d9eae4a31b0704c7", + "revisionTime": "2017-12-07T06:26:40Z" }, { - "checksumSHA1": "pQHH9wpyS0e4wpW0erxe3D7OILM=", + "checksumSHA1": "lCoboKg3pzrdnhtP+mcu6F48+UY=", "path": "github.com/denverdino/aliyungo/slb", - "revision": "199fa376135951b938a53634048bb32e5dc823c1", - "revisionTime": "2017-10-16T07:52:46Z" + "revision": "d7c7e038ca2b9ffd3c2b5525d9eae4a31b0704c7", + "revisionTime": "2017-12-07T06:26:40Z" }, { - "checksumSHA1": "piZlmhWPLGxYkXLysTrjcXllO4c=", + "checksumSHA1": "cKVBRn7GKT+0IqfGUc/NnKDWzCw=", "path": "github.com/denverdino/aliyungo/util", - "revision": "199fa376135951b938a53634048bb32e5dc823c1", - "revisionTime": "2017-10-16T07:52:46Z" + "revision": "d7c7e038ca2b9ffd3c2b5525d9eae4a31b0704c7", + "revisionTime": "2017-12-07T06:26:40Z" }, { "checksumSHA1": "BCv50o5pDkoSG3vYKOSai1Z8p3w=", diff --git a/website/alicloud.erb b/website/alicloud.erb index f324c6db5cd..eab45a48ece 100644 --- a/website/alicloud.erb +++ b/website/alicloud.erb @@ -10,7 +10,7 @@ Alicloud Provider - > + > Data Sources - > + > ECS Resources - > + > SLB Resources - > + > VPC Resources - > + > RDS Resources - > + > ESS Resources + > + Container Resources + + + + > + DNS Resources + + + + > + RAM Resources + + + + > + CDN Resources + + + diff --git a/website/docs/r/container_cluster.html.markdown b/website/docs/r/container_cluster.html.markdown new file mode 100644 index 00000000000..2358c4cce7e --- /dev/null +++ b/website/docs/r/container_cluster.html.markdown @@ -0,0 +1,53 @@ +--- +layout: "alicloud" +page_title: "Alicloud: alicloud_container_cluster" +sidebar_current: "docs-alicloud-resource-container-cluster" +description: |- + Provides a Alicloud container cluster resource. +--- + +# alicloud\_container\_cluster + +Provides a container cluster resource. + +## Example Usage + +Basic Usage + +``` +resource "alicloud_container_cluster" "my_cluster" { + password = "Test12345" + instance_type = "ecs.n4.small" + name = "ClusterFromAlicloud" + size = 2 + disk_category = "cloud_efficiency" + disk_size = 20 + cidr_block = "172.18.0.0/24" + image_id = "${var.image_id}" + vswitch_id = "${var.vswitch_id}" +} +``` +## Argument Reference + +The following arguments are supported: + +* `name` - (Force new resource) The container cluster's name. It is the only in one Alicloud account. +* `name_prefix` - (Force new resource) The container cluster name's prefix. It is conflict with `name`. If it is specified, terraform will using it to build the only cluster name. +* `size` - The ECS node number of the container cluster. Its value choices are 1~20, and default to 1. +* `cidr_block` - (Required, Force new resource) The CIDR block for the Container. Its valid value are `192.168.X.0/24` or `172.16.X.0/24` ~ `172.31.X.0/24`, and it can't be conflict with VSwitch's. +* `image_id` - (Force new resource) The image ID of ECS instance node used. Default to System automate allocated. +* `instance_type` - (Required, Force new resource) The type of ECS instance node. +* `password` - (Required, Force new resource) The password of ECS instance node. +* `disk_category` - (Force new resource) The data disk category of ECS instance node. Its valid value are `cloud_ssd` and `cloud_efficiency`. Default to `cloud_efficiency`. +* `disk_size` - (Force new resource) The data disk size of ECS instance node. Its valid value is 20~32768 GB. Default to 20. +* `vswitch_id` - (Force new resource) The password of ECS instance node. If it is not specified, the container cluster's network mode will be `Classic`. + + +## Attributes Reference + +The following attributes are exported: + +* `name` - The name of the container cluster. +* `size` The ECS instance node number in the current container cluster. +* `vpc_id` - The ID of VPC that current cluster launched. +* `vswitch_id` - The ID of VSwitch that current cluster launched.