diff --git a/.gitignore b/.gitignore index 5982d2c6..b6b0f366 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,5 @@ website/vendor # Test exclusions !command/test-fixtures/**/*.tfstate !command/test-fixtures/**/.terraform/ +.terraform.lock.hcl +provider.tf diff --git a/cloudstack/provider.go b/cloudstack/provider.go index 534e03a0..4949c2f9 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -90,6 +90,7 @@ func Provider() terraform.ResourceProvider { "cloudstack_instance": resourceCloudStackInstance(), "cloudstack_ipaddress": resourceCloudStackIPAddress(), "cloudstack_kubernetes_cluster": resourceCloudStackKubernetesCluster(), + "cloudstack_kubernetes_version": resourceCloudStackKubernetesVersion(), "cloudstack_loadbalancer_rule": resourceCloudStackLoadBalancerRule(), "cloudstack_network": resourceCloudStackNetwork(), "cloudstack_network_acl": resourceCloudStackNetworkACL(), diff --git a/cloudstack/resource_cloudstack_kubernetes_version.go b/cloudstack/resource_cloudstack_kubernetes_version.go new file mode 100644 index 00000000..098ab904 --- /dev/null +++ b/cloudstack/resource_cloudstack_kubernetes_version.go @@ -0,0 +1,208 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "fmt" + "log" + "strings" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceCloudStackKubernetesVersion() *schema.Resource { + return &schema.Resource{ + Create: resourceCloudStackKubernetesVersionCreate, + Read: resourceCloudStackKubernetesVersionRead, + Update: resourceCloudStackKubernetesVersionUpdate, + Delete: resourceCloudStackKubernetesVersionDelete, + Importer: &schema.ResourceImporter{ + State: importStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + + "semantic_version": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "url": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "min_cpu": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + + "min_memory": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + + // Optional Params + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "zone": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "checksum": { + Type: schema.TypeString, + Optional: true, + }, + + "state": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + } +} + +func resourceCloudStackKubernetesVersionCreate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + // State is always Enabled when created + if state, ok := d.GetOk("state"); ok { + if state.(string) != "Enabled" { + return fmt.Errorf("State must be 'Enabled' when first adding an ISO") + } + } + + semanticVersion := d.Get("semantic_version").(string) + url := d.Get("url").(string) + minCpu := d.Get("min_cpu").(int) + minMemory := d.Get("min_memory").(int) + + p := cs.Kubernetes.NewAddKubernetesSupportedVersionParams(minCpu, minMemory, semanticVersion) + p.SetUrl(url) + + if name, ok := d.GetOk("name"); ok { + p.SetName(name.(string)) + } + if checksum, ok := d.GetOk("checksum"); ok { + p.SetName(checksum.(string)) + } + if zone, ok := d.GetOk("zone"); ok { + zoneID, e := retrieveID(cs, "zone", zone.(string)) + if e != nil { + return e.Error() + } + p.SetZoneid(zoneID) + } + + log.Printf("[DEBUG] Creating Kubernetes Version %s", semanticVersion) + r, err := cs.Kubernetes.AddKubernetesSupportedVersion(p) + if err != nil { + return err + } + + log.Printf("[DEBUG] Kubernetes Version %s successfully created", semanticVersion) + d.SetId(r.Id) + return resourceCloudStackKubernetesVersionRead(d, meta) +} + +func resourceCloudStackKubernetesVersionRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + log.Printf("[DEBUG] Retrieving Kubernetes Version %s", d.Get("semantic_version").(string)) + + // Get the Kubernetes Version details + version, count, err := cs.Kubernetes.GetKubernetesSupportedVersionByID( + d.Id(), + ) + if err != nil { + if count == 0 { + log.Printf("[DEBUG] Kubernetes Version %s does not longer exist", d.Get("semantic_version").(string)) + d.SetId("") + return nil + } + + return err + } + + // Update the config + d.SetId(version.Id) + d.Set("semantic_version", version.Semanticversion) + d.Set("name", version.Name) + d.Set("min_cpu", version.Mincpunumber) + d.Set("min_memory", version.Minmemory) + d.Set("state", version.State) + + setValueOrID(d, "zone", version.Zonename, version.Zoneid) + return nil +} + +func resourceCloudStackKubernetesVersionUpdate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + d.Partial(true) + + if d.HasChange("state") { + p := cs.Kubernetes.NewUpdateKubernetesSupportedVersionParams(d.Id(), d.Get("state").(string)) + _, err := cs.Kubernetes.UpdateKubernetesSupportedVersion(p) + if err != nil { + return fmt.Errorf( + "Error Updating Kubernetes Version %s: %s", d.Id(), err) + } + d.SetPartial("state") + } + + d.Partial(false) + return resourceCloudStackKubernetesVersionRead(d, meta) +} + +func resourceCloudStackKubernetesVersionDelete(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + // Create a new parameter struct + p := cs.Kubernetes.NewDeleteKubernetesSupportedVersionParams(d.Id()) + + // Delete the Kubernetes Version + _, err := cs.Kubernetes.DeleteKubernetesSupportedVersion(p) + if err != nil { + // This is a very poor way to be told the ID does no longer exist :( + if strings.Contains(err.Error(), fmt.Sprintf( + "Invalid parameter id value=%s due to incorrect long value format, "+ + "or entity does not exist", d.Id())) { + return nil + } + + return fmt.Errorf("Error deleting Kubernetes Version: %s", err) + } + + return nil +} diff --git a/cloudstack/resource_cloudstack_kubernetes_version_test.go b/cloudstack/resource_cloudstack_kubernetes_version_test.go new file mode 100644 index 00000000..e6416492 --- /dev/null +++ b/cloudstack/resource_cloudstack_kubernetes_version_test.go @@ -0,0 +1,166 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "fmt" + "testing" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccCloudStackKubernetesVersion_basic(t *testing.T) { + var version cloudstack.KubernetesSupportedVersion + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackKubernetesVersionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackKubernetesVersion_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackKubernetesVersionExists("cloudstack_kubernetes_version.foo", &version), + testAccCheckCloudStackKubernetesVersionAttributes(&version), + ), + }, + }, + }) +} + +func TestAccCloudStackKubernetesVersion_update(t *testing.T) { + var version cloudstack.KubernetesSupportedVersion + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackKubernetesVersionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackKubernetesVersion_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackKubernetesVersionExists("cloudstack_kubernetes_version.foo", &version), + testAccCheckCloudStackKubernetesVersionAttributes(&version), + resource.TestCheckResourceAttr( + "cloudstack_kubernetes_version.foo", "state", "Enabled"), + ), + }, + + { + Config: testAccCloudStackKubernetesVersion_update, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackKubernetesVersionExists("cloudstack_kubernetes_version.foo", &version), + testAccCheckCloudStackKubernetesVersionAttributes(&version), + resource.TestCheckResourceAttr( + "cloudstack_kubernetes_version.foo", "state", "Disabled"), + ), + }, + }, + }) +} + +func testAccCheckCloudStackKubernetesVersionExists( + n string, version *cloudstack.KubernetesSupportedVersion) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No kubernetes version ID is set") + } + + cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) + ver, _, err := cs.Kubernetes.GetKubernetesSupportedVersionByID(rs.Primary.ID) + + if err != nil { + return err + } + + if ver.Id != rs.Primary.ID { + return fmt.Errorf("Kubernetes Version not found") + } + + *version = *ver + + return nil + } +} + +func testAccCheckCloudStackKubernetesVersionAttributes( + kubernetesVersion *cloudstack.KubernetesSupportedVersion) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if kubernetesVersion.Semanticversion != "1.23.3" { + return fmt.Errorf("Bad semantic version: %s", kubernetesVersion.Name) + } + + if kubernetesVersion.Mincpunumber != 2 { + return fmt.Errorf("Bad min cpu: %d", kubernetesVersion.Mincpunumber) + } + + if kubernetesVersion.Minmemory != 2048 { + return fmt.Errorf("Bad min memory: %d", kubernetesVersion.Minmemory) + } + + return nil + } +} + +func testAccCheckCloudStackKubernetesVersionDestroy(s *terraform.State) error { + cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "cloudstack_kubernetes_version" { + continue + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No kubernetes version ID is set") + } + + _, _, err := cs.Kubernetes.GetKubernetesSupportedVersionByID(rs.Primary.ID) + if err == nil { + return fmt.Errorf("Kubernetes Version %s still exists", rs.Primary.ID) + } + } + + return nil +} + +const testAccCloudStackKubernetesVersion_basic = ` +resource "cloudstack_kubernetes_version" "foo" { + semantic_version = "1.23.3" + url = "http://download.cloudstack.org/cks/setup-1.23.3.iso" + min_cpu = 2 + min_memory = 2048 +}` + +const testAccCloudStackKubernetesVersion_update = ` +resource "cloudstack_kubernetes_version" "foo" { + semantic_version = "1.23.3" + url = "http://download.cloudstack.org/cks/setup-1.23.3.iso" + min_cpu = 2 + min_memory = 2048 + state = "Disabled" +}` diff --git a/go.mod b/go.mod index d91a136f..17d0821f 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,14 @@ module github.com/terraform-providers/terraform-provider-cloudstack require ( - github.com/apache/cloudstack-go/v2 v2.13.1 + github.com/apache/cloudstack-go/v2 v2.13.2 github.com/go-ini/ini v1.40.0 github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect github.com/hashicorp/go-multierror v1.0.0 github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/terraform v0.12.0 github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect - golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 // indirect golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef // indirect gopkg.in/ini.v1 v1.40.0 // indirect ) diff --git a/go.sum b/go.sum index 45648b4f..c99f07a2 100644 --- a/go.sum +++ b/go.sum @@ -22,10 +22,8 @@ github.com/agl/ed25519 v0.0.0-20150830182803-278e1ec8e8a6/go.mod h1:WPjqKcmVOxf0 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antchfx/xpath v0.0.0-20190129040759-c8489ed3251e/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk= github.com/antchfx/xquery v0.0.0-20180515051857-ad5b8c7a47b0/go.mod h1:LzD22aAzDP8/dyiCKFp31He4m2GPjl0AFyzDtZzUu9M= -github.com/apache/cloudstack-go/v2 v2.11.0 h1:IHekkdpeN/i4LY0/FkJX/PUR19ZthLza7eooz00T6fs= -github.com/apache/cloudstack-go/v2 v2.11.0/go.mod h1:/u2vUqwD9endDgacTn4d2XxxVtu648f9edcYsM9wKGg= -github.com/apache/cloudstack-go/v2 v2.13.1 h1:UHhNJ+5coUsgk9D5WBbqbY8hYfJ1bXgNxaSg2uGz4Ns= -github.com/apache/cloudstack-go/v2 v2.13.1/go.mod h1:aosD8Svfu5nhH5Sp4zcsVV1hT5UGt3mTgRXM8YqTKe0= +github.com/apache/cloudstack-go/v2 v2.13.2 h1:Y06CXNle++Gs24YjeNI7Ot8ZUQjLix2oPn/CMuVr/TU= +github.com/apache/cloudstack-go/v2 v2.13.2/go.mod h1:aosD8Svfu5nhH5Sp4zcsVV1hT5UGt3mTgRXM8YqTKe0= github.com/apparentlymart/go-cidr v1.0.0 h1:lGDvXx8Lv9QHjrAVP7jyzleG4F9+FkRhJcEsDFxeb8w= github.com/apparentlymart/go-cidr v1.0.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= @@ -88,7 +86,6 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=