diff --git a/citrixadc/provider.go b/citrixadc/provider.go index 9ad099a9a..edc7bc1e1 100644 --- a/citrixadc/provider.go +++ b/citrixadc/provider.go @@ -129,6 +129,7 @@ func providerResources() map[string]*schema.Resource { "citrixadc_auditsyslogaction": resourceCitrixAdcAuditsyslogaction(), "citrixadc_auditsyslogpolicy": resourceCitrixAdcAuditsyslogpolicy(), "citrixadc_rebooter": resourceCitrixAdcRebooter(), + "citrixadc_installer": resourceCitrixAdcInstaller(), } } diff --git a/citrixadc/resource_citrixadc_installer.go b/citrixadc/resource_citrixadc_installer.go new file mode 100644 index 000000000..433078703 --- /dev/null +++ b/citrixadc/resource_citrixadc_installer.go @@ -0,0 +1,220 @@ +package citrixadc + +import ( + "github.com/chiradeep/go-nitro/config/utility" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + + "bytes" + "fmt" + "log" + "net/http" + "strings" + "time" +) + +func resourceCitrixAdcInstaller() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + Create: createInstallerFunc, + Read: schema.Noop, + Delete: schema.Noop, + Schema: map[string]*schema.Schema{ + "enhancedupgrade": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + ForceNew: true, + }, + "l": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + ForceNew: true, + }, + "resizeswapvar": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + ForceNew: true, + }, + "url": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "y": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + ForceNew: true, + }, + "wait_until_reachable": &schema.Schema{ + Type: schema.TypeBool, + Required: true, + ForceNew: true, + }, + "reachable_timeout": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "10m", + ForceNew: true, + }, + "reachable_poll_delay": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "60s", + ForceNew: true, + }, + "reachable_poll_interval": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "60s", + ForceNew: true, + }, + "reachable_poll_timeout": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "20s", + ForceNew: true, + }, + }, + } +} + +func createInstallerFunc(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] citrixadc-provider: In createInstallFunc") + installerId := resource.PrefixedUniqueId("tf-installer-") + + err := installerInstallBuild(d, meta) + if err != nil { + return err + } + + if d.Get("wait_until_reachable").(bool) { + err := installerWaitReachable(d, meta) + if err != nil { + return err + } + } + + d.SetId(installerId) + + return nil +} + +func installerInstallBuild(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] citrixadc-provider: In installerInstallBuild") + + client := meta.(*NetScalerNitroClient).client + + install := utility.Install{ + Enhancedupgrade: d.Get("enhancedupgrade").(bool), + L: d.Get("l").(bool), + Resizeswapvar: d.Get("resizeswapvar").(bool), + Url: d.Get("url").(string), + Y: d.Get("y").(bool), + } + + if err := client.ActOnResource("install", &install, ""); err != nil { + errorStr := err.Error() + if strings.HasSuffix(errorStr, "EOF") || strings.HasSuffix(errorStr, "connection reset by peer") { + // This is expected since the operation results in a TCP conection reset some times + // especially when y = true + log.Printf("[DEBUG] citrixadc-provider: Ignoring go-nitro error \"%s\"", errorStr) + return nil + } else { + return err + } + } + return nil +} + +func installerWaitReachable(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] citrixadc-provider: In installerWaitReachable") + + var err error + var timeout time.Duration + if timeout, err = time.ParseDuration(d.Get("reachable_timeout").(string)); err != nil { + return err + } + + var poll_interval time.Duration + if poll_interval, err = time.ParseDuration(d.Get("reachable_poll_interval").(string)); err != nil { + return err + } + + var poll_delay time.Duration + if poll_delay, err = time.ParseDuration(d.Get("reachable_poll_delay").(string)); err != nil { + return err + } + stateConf := &resource.StateChangeConf{ + Pending: []string{"unreachable"}, + Target: []string{"reachable"}, + Refresh: installerInstancePoll(d, meta), + Timeout: timeout, + PollInterval: poll_interval, + Delay: poll_delay, + } + + _, err = stateConf.WaitForState() + if err != nil { + return err + } + + return nil +} + +func installerPollLicense(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] citrixadc-provider: In installerPollLicense") + + username := meta.(*NetScalerNitroClient).Username + password := meta.(*NetScalerNitroClient).Password + endpoint := meta.(*NetScalerNitroClient).Endpoint + url := fmt.Sprintf("%s/nitro/v1/config/nslicense", endpoint) + + var timeout time.Duration + var err error + if timeout, err = time.ParseDuration(d.Get("reachable_poll_timeout").(string)); err != nil { + return err + } + c := http.Client{ + Timeout: timeout, + } + buff := &bytes.Buffer{} + req, _ := http.NewRequest("GET", url, buff) + req.Header.Set("X-NITRO-USER", username) + req.Header.Set("X-NITRO-PASS", password) + resp, err := c.Do(req) + if err != nil { + if !strings.Contains(err.Error(), "Client.Timeout exceeded") { + // Unexpected error + return err + } else { + // Expected timeout error + return fmt.Errorf("Timeout") + } + } else { + log.Printf("Status code is %v\n", resp.Status) + } + // No error + return nil +} + +func installerInstancePoll(d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + log.Printf("[DEBUG] citrixadc-provider: In installerInstancePoll") + err := installerPollLicense(d, meta) + if err != nil { + if err.Error() == "Timeout" { + return nil, "unreachable", nil + } else { + return nil, "unreachable", err + } + } + log.Printf("[DEBUG] citrixadc-provider: Returning \"reachable\"") + return "reachable", "reachable", nil + } +} diff --git a/citrixadc/resource_citrixadc_installer_test.go b/citrixadc/resource_citrixadc_installer_test.go new file mode 100644 index 000000000..3dca73c4c --- /dev/null +++ b/citrixadc/resource_citrixadc_installer_test.go @@ -0,0 +1,77 @@ +/* +Copyright 2016 Citrix Systems, Inc + +Licensed 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 citrixadc + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "testing" +) + +func TestAccInstaller_basic(t *testing.T) { + if isCpxRun { + t.Skip("Install not available in CPX") + } + if isCluster { + t.Skip("Install not available in Cluster") + } + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccInstaller_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckInstallExist("citrixadc_installer.tf_installer", nil), + ), + }, + }, + }) +} + +func testAccCheckInstallExist(n string, id *string) 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 lb vserver name is set") + } + + if id != nil { + if *id != "" && *id != rs.Primary.ID { + return fmt.Errorf("Resource ID has changed!") + } + + *id = rs.Primary.ID + } + + return nil + } +} + +const testAccInstaller_basic = ` +resource "citrixadc_installer" "tf_installer" { +#url = "file:///var/tmp/build_mana_52_24_nc_64.tgz" + url = "file:///var/tmp/build_mana_47_24_nc_64.tgz" + y = true + l = false + wait_until_reachable = true +} +` diff --git a/examples/installer/installer_only/provider.tf b/examples/installer/installer_only/provider.tf new file mode 100644 index 000000000..3d4508593 --- /dev/null +++ b/examples/installer/installer_only/provider.tf @@ -0,0 +1,3 @@ +provider "citrixadc" { + endpoint = "http://localhost:8080" +} diff --git a/examples/installer/installer_only/resources.tf b/examples/installer/installer_only/resources.tf new file mode 100644 index 000000000..ed0234fe3 --- /dev/null +++ b/examples/installer/installer_only/resources.tf @@ -0,0 +1,6 @@ +resource "citrixadc_installer" "tf_installer" { + url = "file:///var/tmp/build_mana_47_24_nc_64.tgz" + y = true + l = false + wait_until_reachable = true +} diff --git a/examples/installer/with_rebooter/provider.tf b/examples/installer/with_rebooter/provider.tf new file mode 100644 index 000000000..3d4508593 --- /dev/null +++ b/examples/installer/with_rebooter/provider.tf @@ -0,0 +1,3 @@ +provider "citrixadc" { + endpoint = "http://localhost:8080" +} diff --git a/examples/installer/with_rebooter/resources.tf b/examples/installer/with_rebooter/resources.tf new file mode 100644 index 000000000..845a846b1 --- /dev/null +++ b/examples/installer/with_rebooter/resources.tf @@ -0,0 +1,13 @@ +resource "citrixadc_installer" "tf_installer" { + url = "file:///var/tmp/build_mana_47_24_nc_64.tgz" + y = false + l = false + wait_until_reachable = false +} + +resource "citrixadc_rebooter" "tf_rebooter" { + timestamp = timestamp() + warm = false + wait_until_reachable = true + depends_on = [ citrixadc_installer.tf_installer ] +}