merge upstream master
This commit is contained in:
commit
8784f3de93
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -3,21 +3,32 @@
|
|||
BACKWARDS INCOMPATIBILITIES / NOTES:
|
||||
|
||||
* provider/aws: Users of aws_cloudfront_distributions with custom_origins have been broken due to changes in the AWS API requiring `OriginReadTimeout` being set for updates. This has been fixed and will show as a change in terraform plan / apply. [GH-13367]
|
||||
* provider/aws: Users of China and Gov clouds, cannot use the new tagging of volumes created as part of aws_instances [GH-14055]
|
||||
|
||||
FEATURES:
|
||||
|
||||
* **New Provider:** `gitlab` [GH-13898]
|
||||
* **New Resource:** `heroku_app_feature` [GH-14035]
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
* provider/aws: Add support for CustomOrigin timeouts to aws_cloudfront_distribution [GH-13367]
|
||||
* provider/azurerm: Expose the Private IP Address for a Load Balancer, if available [GH-13965]
|
||||
* provider/dnsimple: Add support for import for dnsimple_records [GH-9130]
|
||||
* provider/nomad: Add TLS options [GH-13956]
|
||||
* provider/triton: Add support for reading provider configuration from `TRITON_*` environment variables in addition to `SDC_*`[GH-14000]
|
||||
* provider/triton: Add `cloud_config` argument to `triton_machine` resources for Linux containers [GH-12840]
|
||||
* provider/triton: Add `insecure_skip_tls_verify` [GH-14077]
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
* provider/aws: Update aws_ebs_volume when attached [GH-14005]
|
||||
* provider/aws: Set aws_instance volume_tags to be Computed [GH-14007]
|
||||
* provider/aws: Fix issue getting partition for federated users [GH-13992]
|
||||
* provider/aws: aws_spot_instance_request not forcenew on volume_tags [GH-14046]
|
||||
* provider/aws: Exclude aws_instance volume tagging for China and Gov Clouds [GH-14055]
|
||||
* provider/digitalocean: Prevent diffs when using IDs of images instead of slugs [GH-13879]
|
||||
* provider/google: ignore certain project services that can't be enabled directly via the api [GH-13730]
|
||||
* providers/heroku: Configure buildpacks correctly for both Org Apps and non-org Apps [GH-13990]
|
||||
|
||||
## 0.9.4 (26th April 2017)
|
||||
|
|
4
Makefile
4
Makefile
|
@ -75,8 +75,8 @@ cover:
|
|||
# vet runs the Go source code static analysis tool `vet` to find
|
||||
# any common errors.
|
||||
vet:
|
||||
@echo "go vet ."
|
||||
@go vet $$(go list ./... | grep -v vendor/) ; if [ $$? -eq 1 ]; then \
|
||||
@echo 'go vet $$(go list ./... | grep -v /terraform/vendor/)'
|
||||
@go vet $$(go list ./... | grep -v /terraform/vendor/) ; if [ $$? -eq 1 ]; then \
|
||||
echo ""; \
|
||||
echo "Vet found suspicious constructs. Please check the reported constructs"; \
|
||||
echo "and fix them if necessary before submitting the code for review."; \
|
||||
|
|
|
@ -54,7 +54,7 @@ func GetAccountInfo(iamconn *iam.IAM, stsconn *sts.STS, authProviderName string)
|
|||
awsErr, ok := err.(awserr.Error)
|
||||
// AccessDenied and ValidationError can be raised
|
||||
// if credentials belong to federated profile, so we ignore these
|
||||
if !ok || (awsErr.Code() != "AccessDenied" && awsErr.Code() != "ValidationError") {
|
||||
if !ok || (awsErr.Code() != "AccessDenied" && awsErr.Code() != "ValidationError" && awsErr.Code() != "InvalidClientTokenId") {
|
||||
return "", "", fmt.Errorf("Failed getting account ID via 'iam:GetUser': %s", err)
|
||||
}
|
||||
log.Printf("[DEBUG] Getting account ID via iam:GetUser failed: %s", err)
|
||||
|
|
|
@ -171,6 +171,20 @@ func (c *AWSClient) DynamoDB() *dynamodb.DynamoDB {
|
|||
return c.dynamodbconn
|
||||
}
|
||||
|
||||
func (c *AWSClient) IsGovCloud() bool {
|
||||
if c.region == "us-gov-west-1" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *AWSClient) IsChinaCloud() bool {
|
||||
if c.region == "cn-north-1" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Client configures and returns a fully initialized AWSClient
|
||||
func (c *Config) Client() (interface{}, error) {
|
||||
// Get the auth and region. This can fail if keys/regions were not
|
||||
|
|
|
@ -432,32 +432,35 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
|
|||
runOpts.Ipv6Addresses = ipv6Addresses
|
||||
}
|
||||
|
||||
tagsSpec := make([]*ec2.TagSpecification, 0)
|
||||
restricted := meta.(*AWSClient).IsGovCloud() || meta.(*AWSClient).IsChinaCloud()
|
||||
if !restricted {
|
||||
tagsSpec := make([]*ec2.TagSpecification, 0)
|
||||
|
||||
if v, ok := d.GetOk("tags"); ok {
|
||||
tags := tagsFromMap(v.(map[string]interface{}))
|
||||
if v, ok := d.GetOk("tags"); ok {
|
||||
tags := tagsFromMap(v.(map[string]interface{}))
|
||||
|
||||
spec := &ec2.TagSpecification{
|
||||
ResourceType: aws.String("instance"),
|
||||
Tags: tags,
|
||||
spec := &ec2.TagSpecification{
|
||||
ResourceType: aws.String("instance"),
|
||||
Tags: tags,
|
||||
}
|
||||
|
||||
tagsSpec = append(tagsSpec, spec)
|
||||
}
|
||||
|
||||
tagsSpec = append(tagsSpec, spec)
|
||||
}
|
||||
if v, ok := d.GetOk("volume_tags"); ok {
|
||||
tags := tagsFromMap(v.(map[string]interface{}))
|
||||
|
||||
if v, ok := d.GetOk("volume_tags"); ok {
|
||||
tags := tagsFromMap(v.(map[string]interface{}))
|
||||
spec := &ec2.TagSpecification{
|
||||
ResourceType: aws.String("volume"),
|
||||
Tags: tags,
|
||||
}
|
||||
|
||||
spec := &ec2.TagSpecification{
|
||||
ResourceType: aws.String("volume"),
|
||||
Tags: tags,
|
||||
tagsSpec = append(tagsSpec, spec)
|
||||
}
|
||||
|
||||
tagsSpec = append(tagsSpec, spec)
|
||||
}
|
||||
|
||||
if len(tagsSpec) > 0 {
|
||||
runOpts.TagSpecifications = tagsSpec
|
||||
if len(tagsSpec) > 0 {
|
||||
runOpts.TagSpecifications = tagsSpec
|
||||
}
|
||||
}
|
||||
|
||||
// Create the instance
|
||||
|
@ -713,19 +716,24 @@ func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
|
|||
|
||||
d.Partial(true)
|
||||
|
||||
if d.HasChange("tags") && !d.IsNewResource() {
|
||||
if err := setTags(conn, d); err != nil {
|
||||
return err
|
||||
} else {
|
||||
d.SetPartial("tags")
|
||||
restricted := meta.(*AWSClient).IsGovCloud() || meta.(*AWSClient).IsChinaCloud()
|
||||
|
||||
if d.HasChange("tags") {
|
||||
if !d.IsNewResource() || !restricted {
|
||||
if err := setTags(conn, d); err != nil {
|
||||
return err
|
||||
} else {
|
||||
d.SetPartial("tags")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange("volume_tags") && !d.IsNewResource() {
|
||||
if err := setVolumeTags(conn, d); err != nil {
|
||||
return err
|
||||
} else {
|
||||
d.SetPartial("volume_tags")
|
||||
if d.HasChange("volume_tags") {
|
||||
if !d.IsNewResource() || !restricted {
|
||||
if err := setVolumeTags(conn, d); err != nil {
|
||||
return err
|
||||
} else {
|
||||
d.SetPartial("volume_tags")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -320,19 +320,33 @@ func updateKmsKeyStatus(conn *kms.KMS, id string, shouldBeEnabled bool) error {
|
|||
}
|
||||
|
||||
func updateKmsKeyRotationStatus(conn *kms.KMS, d *schema.ResourceData) error {
|
||||
var err error
|
||||
shouldEnableRotation := d.Get("enable_key_rotation").(bool)
|
||||
if shouldEnableRotation {
|
||||
log.Printf("[DEBUG] Enabling key rotation for KMS key %q", d.Id())
|
||||
_, err = conn.EnableKeyRotation(&kms.EnableKeyRotationInput{
|
||||
KeyId: aws.String(d.Id()),
|
||||
})
|
||||
} else {
|
||||
log.Printf("[DEBUG] Disabling key rotation for KMS key %q", d.Id())
|
||||
_, err = conn.DisableKeyRotation(&kms.DisableKeyRotationInput{
|
||||
KeyId: aws.String(d.Id()),
|
||||
})
|
||||
}
|
||||
|
||||
err := resource.Retry(5*time.Minute, func() *resource.RetryError {
|
||||
var err error
|
||||
if shouldEnableRotation {
|
||||
log.Printf("[DEBUG] Enabling key rotation for KMS key %q", d.Id())
|
||||
_, err = conn.EnableKeyRotation(&kms.EnableKeyRotationInput{
|
||||
KeyId: aws.String(d.Id()),
|
||||
})
|
||||
} else {
|
||||
log.Printf("[DEBUG] Disabling key rotation for KMS key %q", d.Id())
|
||||
_, err = conn.DisableKeyRotation(&kms.DisableKeyRotationInput{
|
||||
KeyId: aws.String(d.Id()),
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
awsErr, ok := err.(awserr.Error)
|
||||
if ok && awsErr.Code() == "DisabledException" {
|
||||
return resource.RetryableError(err)
|
||||
}
|
||||
|
||||
return resource.NonRetryableError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to set key rotation for %q to %t: %q",
|
||||
|
|
|
@ -25,7 +25,7 @@ func resourceAwsSpotInstanceRequest() *schema.Resource {
|
|||
|
||||
// Everything on a spot instance is ForceNew except tags
|
||||
for k, v := range s {
|
||||
if k == "tags" {
|
||||
if k == "tags" || k == "volume_tags" {
|
||||
continue
|
||||
}
|
||||
v.ForceNew = true
|
||||
|
|
|
@ -92,6 +92,11 @@ func resourceArmLoadBalancer() *schema.Resource {
|
|||
},
|
||||
},
|
||||
|
||||
"private_ip_address": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"tags": tagsSchema(),
|
||||
},
|
||||
}
|
||||
|
@ -172,7 +177,17 @@ func resourecArmLoadBalancerRead(d *schema.ResourceData, meta interface{}) error
|
|||
d.Set("resource_group_name", id.ResourceGroup)
|
||||
|
||||
if loadBalancer.LoadBalancerPropertiesFormat != nil && loadBalancer.LoadBalancerPropertiesFormat.FrontendIPConfigurations != nil {
|
||||
d.Set("frontend_ip_configuration", flattenLoadBalancerFrontendIpConfiguration(loadBalancer.LoadBalancerPropertiesFormat.FrontendIPConfigurations))
|
||||
ipconfigs := loadBalancer.LoadBalancerPropertiesFormat.FrontendIPConfigurations
|
||||
d.Set("frontend_ip_configuration", flattenLoadBalancerFrontendIpConfiguration(ipconfigs))
|
||||
|
||||
for _, config := range *ipconfigs {
|
||||
if config.FrontendIPConfigurationPropertiesFormat.PrivateIPAddress != nil {
|
||||
d.Set("private_ip_address", config.FrontendIPConfigurationPropertiesFormat.PrivateIPAddress)
|
||||
|
||||
// set the private IP address at most once
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flattenAndSetTags(d, loadBalancer.Tags)
|
||||
|
|
|
@ -2,9 +2,12 @@ package digitalocean
|
|||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"time"
|
||||
|
||||
"github.com/digitalocean/godo"
|
||||
"github.com/hashicorp/terraform/helper/logging"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
@ -21,11 +24,31 @@ func (c *Config) Client() (*godo.Client, error) {
|
|||
|
||||
client := godo.NewClient(oauth2.NewClient(oauth2.NoContext, tokenSrc))
|
||||
|
||||
if logging.IsDebugOrHigher() {
|
||||
client.OnRequestCompleted(logRequestAndResponse)
|
||||
}
|
||||
|
||||
log.Printf("[INFO] DigitalOcean Client configured for URL: %s", client.BaseURL.String())
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func logRequestAndResponse(req *http.Request, resp *http.Response) {
|
||||
reqData, err := httputil.DumpRequest(req, true)
|
||||
if err == nil {
|
||||
log.Printf("[DEBUG] "+logReqMsg, string(reqData))
|
||||
} else {
|
||||
log.Printf("[ERROR] DigitalOcean API Request error: %#v", err)
|
||||
}
|
||||
|
||||
respData, err := httputil.DumpResponse(resp, true)
|
||||
if err == nil {
|
||||
log.Printf("[DEBUG] "+logRespMsg, string(respData))
|
||||
} else {
|
||||
log.Printf("[ERROR] DigitalOcean API Response error: %#v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// waitForAction waits for the action to finish using the resource.StateChangeConf.
|
||||
func waitForAction(client *godo.Client, action *godo.Action) error {
|
||||
var (
|
||||
|
@ -61,3 +84,13 @@ func waitForAction(client *godo.Client, action *godo.Action) error {
|
|||
}).WaitForState()
|
||||
return err
|
||||
}
|
||||
|
||||
const logReqMsg = `DigitalOcean API Request Details:
|
||||
---[ REQUEST ]---------------------------------------
|
||||
%s
|
||||
-----------------------------------------------------`
|
||||
|
||||
const logRespMsg = `DigitalOcean API Response Details:
|
||||
---[ RESPONSE ]--------------------------------------
|
||||
%s
|
||||
-----------------------------------------------------`
|
||||
|
|
|
@ -260,10 +260,13 @@ func resourceDigitalOceanDropletRead(d *schema.ResourceData, meta interface{}) e
|
|||
return fmt.Errorf("Error retrieving droplet: %s", err)
|
||||
}
|
||||
|
||||
if droplet.Image.Slug != "" {
|
||||
d.Set("image", droplet.Image.Slug)
|
||||
} else {
|
||||
_, err = strconv.Atoi(d.Get("image").(string))
|
||||
if err == nil || droplet.Image.Slug == "" {
|
||||
// The image field is provided as an ID (number), or
|
||||
// the image bash no slug. In both cases we store it as an ID.
|
||||
d.Set("image", droplet.Image.ID)
|
||||
} else {
|
||||
d.Set("image", droplet.Image.Slug)
|
||||
}
|
||||
|
||||
d.Set("name", droplet.Name)
|
||||
|
|
|
@ -41,16 +41,31 @@ func TestAccDigitalOceanDroplet_Basic(t *testing.T) {
|
|||
resource.TestCheckResourceAttr(
|
||||
"digitalocean_droplet.foobar", "user_data", "foobar"),
|
||||
),
|
||||
Destroy: false,
|
||||
},
|
||||
{
|
||||
Config: testAccCheckDigitalOceanDropletConfig_basic(rInt),
|
||||
PlanOnly: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccDigitalOceanDroplet_WithID(t *testing.T) {
|
||||
var droplet godo.Droplet
|
||||
rInt := acctest.RandInt()
|
||||
// TODO: not hardcode this as it will change over time
|
||||
centosID := 22995941
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckDigitalOceanDropletDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccCheckDigitalOceanDropletConfig_withID(centosID, rInt),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckDigitalOceanDropletExists("digitalocean_droplet.foobar", &droplet),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
func TestAccDigitalOceanDroplet_withSSH(t *testing.T) {
|
||||
var droplet godo.Droplet
|
||||
rInt := acctest.RandInt()
|
||||
|
@ -504,6 +519,17 @@ resource "digitalocean_droplet" "foobar" {
|
|||
}`, rInt)
|
||||
}
|
||||
|
||||
func testAccCheckDigitalOceanDropletConfig_withID(imageID, rInt int) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "digitalocean_droplet" "foobar" {
|
||||
name = "foo-%d"
|
||||
size = "512mb"
|
||||
image = "%d"
|
||||
region = "nyc3"
|
||||
user_data = "foobar"
|
||||
}`, rInt, imageID)
|
||||
}
|
||||
|
||||
func testAccCheckDigitalOceanDropletConfig_withSSH(rInt int) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "digitalocean_ssh_key" "foobar" {
|
||||
|
|
|
@ -2,18 +2,21 @@ package google
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/acctest"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAccDataSourceGoogleNetwork(t *testing.T) {
|
||||
networkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: TestAccDataSourceGoogleNetworkConfig,
|
||||
Config: testAccDataSourceGoogleNetworkConfig(networkName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccDataSourceGoogleNetworkCheck("data.google_compute_network.my_network", "google_compute_network.foobar"),
|
||||
),
|
||||
|
@ -57,12 +60,14 @@ func testAccDataSourceGoogleNetworkCheck(data_source_name string, resource_name
|
|||
}
|
||||
}
|
||||
|
||||
var TestAccDataSourceGoogleNetworkConfig = `
|
||||
func testAccDataSourceGoogleNetworkConfig(name string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_compute_network" "foobar" {
|
||||
name = "network-test"
|
||||
name = "%s"
|
||||
description = "my-description"
|
||||
}
|
||||
|
||||
data "google_compute_network" "my_network" {
|
||||
name = "${google_compute_network.foobar.name}"
|
||||
}`
|
||||
}`, name)
|
||||
}
|
||||
|
|
|
@ -2,8 +2,10 @@ package google
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/acctest"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"google.golang.org/api/compute/v1"
|
||||
|
@ -11,7 +13,16 @@ import (
|
|||
|
||||
// Add two key value pairs
|
||||
func TestAccComputeProjectMetadata_basic(t *testing.T) {
|
||||
skipIfEnvNotSet(t,
|
||||
[]string{
|
||||
"GOOGLE_ORG",
|
||||
"GOOGLE_BILLING_ACCOUNT",
|
||||
}...,
|
||||
)
|
||||
|
||||
billingId := os.Getenv("GOOGLE_BILLING_ACCOUNT")
|
||||
var project compute.Project
|
||||
projectID := "terrafom-test-" + acctest.RandString(10)
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
|
@ -19,13 +30,13 @@ func TestAccComputeProjectMetadata_basic(t *testing.T) {
|
|||
CheckDestroy: testAccCheckComputeProjectMetadataDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccComputeProject_basic0_metadata,
|
||||
Config: testAccComputeProject_basic0_metadata(projectID, pname, org, billingId),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckComputeProjectExists(
|
||||
"google_compute_project_metadata.fizzbuzz", &project),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "banana", "orange"),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "sofa", "darwinism"),
|
||||
testAccCheckComputeProjectMetadataSize(&project, 2),
|
||||
"google_compute_project_metadata.fizzbuzz", projectID, &project),
|
||||
testAccCheckComputeProjectMetadataContains(projectID, "banana", "orange"),
|
||||
testAccCheckComputeProjectMetadataContains(projectID, "sofa", "darwinism"),
|
||||
testAccCheckComputeProjectMetadataSize(projectID, 2),
|
||||
),
|
||||
},
|
||||
},
|
||||
|
@ -34,7 +45,16 @@ func TestAccComputeProjectMetadata_basic(t *testing.T) {
|
|||
|
||||
// Add three key value pairs, then replace one and modify a second
|
||||
func TestAccComputeProjectMetadata_modify_1(t *testing.T) {
|
||||
skipIfEnvNotSet(t,
|
||||
[]string{
|
||||
"GOOGLE_ORG",
|
||||
"GOOGLE_BILLING_ACCOUNT",
|
||||
}...,
|
||||
)
|
||||
|
||||
billingId := os.Getenv("GOOGLE_BILLING_ACCOUNT")
|
||||
var project compute.Project
|
||||
projectID := "terrafom-test-" + acctest.RandString(10)
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
|
@ -42,26 +62,26 @@ func TestAccComputeProjectMetadata_modify_1(t *testing.T) {
|
|||
CheckDestroy: testAccCheckComputeProjectMetadataDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccComputeProject_modify0_metadata,
|
||||
Config: testAccComputeProject_modify0_metadata(projectID, pname, org, billingId),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckComputeProjectExists(
|
||||
"google_compute_project_metadata.fizzbuzz", &project),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "paper", "pen"),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "genghis_khan", "french bread"),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "happy", "smiling"),
|
||||
testAccCheckComputeProjectMetadataSize(&project, 3),
|
||||
"google_compute_project_metadata.fizzbuzz", projectID, &project),
|
||||
testAccCheckComputeProjectMetadataContains(projectID, "paper", "pen"),
|
||||
testAccCheckComputeProjectMetadataContains(projectID, "genghis_khan", "french bread"),
|
||||
testAccCheckComputeProjectMetadataContains(projectID, "happy", "smiling"),
|
||||
testAccCheckComputeProjectMetadataSize(projectID, 3),
|
||||
),
|
||||
},
|
||||
|
||||
resource.TestStep{
|
||||
Config: testAccComputeProject_modify1_metadata,
|
||||
Config: testAccComputeProject_modify1_metadata(projectID, pname, org, billingId),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckComputeProjectExists(
|
||||
"google_compute_project_metadata.fizzbuzz", &project),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "paper", "pen"),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "paris", "french bread"),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "happy", "laughing"),
|
||||
testAccCheckComputeProjectMetadataSize(&project, 3),
|
||||
"google_compute_project_metadata.fizzbuzz", projectID, &project),
|
||||
testAccCheckComputeProjectMetadataContains(projectID, "paper", "pen"),
|
||||
testAccCheckComputeProjectMetadataContains(projectID, "paris", "french bread"),
|
||||
testAccCheckComputeProjectMetadataContains(projectID, "happy", "laughing"),
|
||||
testAccCheckComputeProjectMetadataSize(projectID, 3),
|
||||
),
|
||||
},
|
||||
},
|
||||
|
@ -70,7 +90,16 @@ func TestAccComputeProjectMetadata_modify_1(t *testing.T) {
|
|||
|
||||
// Add two key value pairs, and replace both
|
||||
func TestAccComputeProjectMetadata_modify_2(t *testing.T) {
|
||||
skipIfEnvNotSet(t,
|
||||
[]string{
|
||||
"GOOGLE_ORG",
|
||||
"GOOGLE_BILLING_ACCOUNT",
|
||||
}...,
|
||||
)
|
||||
|
||||
billingId := os.Getenv("GOOGLE_BILLING_ACCOUNT")
|
||||
var project compute.Project
|
||||
projectID := "terraform-test-" + acctest.RandString(10)
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
|
@ -78,24 +107,24 @@ func TestAccComputeProjectMetadata_modify_2(t *testing.T) {
|
|||
CheckDestroy: testAccCheckComputeProjectMetadataDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccComputeProject_basic0_metadata,
|
||||
Config: testAccComputeProject_basic0_metadata(projectID, pname, org, billingId),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckComputeProjectExists(
|
||||
"google_compute_project_metadata.fizzbuzz", &project),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "banana", "orange"),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "sofa", "darwinism"),
|
||||
testAccCheckComputeProjectMetadataSize(&project, 2),
|
||||
"google_compute_project_metadata.fizzbuzz", projectID, &project),
|
||||
testAccCheckComputeProjectMetadataContains(projectID, "banana", "orange"),
|
||||
testAccCheckComputeProjectMetadataContains(projectID, "sofa", "darwinism"),
|
||||
testAccCheckComputeProjectMetadataSize(projectID, 2),
|
||||
),
|
||||
},
|
||||
|
||||
resource.TestStep{
|
||||
Config: testAccComputeProject_basic1_metadata,
|
||||
Config: testAccComputeProject_basic1_metadata(projectID, pname, org, billingId),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckComputeProjectExists(
|
||||
"google_compute_project_metadata.fizzbuzz", &project),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "kiwi", "papaya"),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "finches", "darwinism"),
|
||||
testAccCheckComputeProjectMetadataSize(&project, 2),
|
||||
"google_compute_project_metadata.fizzbuzz", projectID, &project),
|
||||
testAccCheckComputeProjectMetadataContains(projectID, "kiwi", "papaya"),
|
||||
testAccCheckComputeProjectMetadataContains(projectID, "finches", "darwinism"),
|
||||
testAccCheckComputeProjectMetadataSize(projectID, 2),
|
||||
),
|
||||
},
|
||||
},
|
||||
|
@ -105,15 +134,21 @@ func TestAccComputeProjectMetadata_modify_2(t *testing.T) {
|
|||
func testAccCheckComputeProjectMetadataDestroy(s *terraform.State) error {
|
||||
config := testAccProvider.Meta().(*Config)
|
||||
|
||||
project, err := config.clientCompute.Projects.Get(config.Project).Do()
|
||||
if err == nil && len(project.CommonInstanceMetadata.Items) > 0 {
|
||||
return fmt.Errorf("Error, metadata items still exist")
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "google_compute_project_metadata" {
|
||||
continue
|
||||
}
|
||||
|
||||
project, err := config.clientCompute.Projects.Get(rs.Primary.ID).Do()
|
||||
if err == nil && len(project.CommonInstanceMetadata.Items) > 0 {
|
||||
return fmt.Errorf("Error, metadata items still exist in %s", rs.Primary.ID)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckComputeProjectExists(n string, project *compute.Project) resource.TestCheckFunc {
|
||||
func testAccCheckComputeProjectExists(n, projectID string, project *compute.Project) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
if !ok {
|
||||
|
@ -126,8 +161,7 @@ func testAccCheckComputeProjectExists(n string, project *compute.Project) resour
|
|||
|
||||
config := testAccProvider.Meta().(*Config)
|
||||
|
||||
found, err := config.clientCompute.Projects.Get(
|
||||
config.Project).Do()
|
||||
found, err := config.clientCompute.Projects.Get(projectID).Do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -142,10 +176,10 @@ func testAccCheckComputeProjectExists(n string, project *compute.Project) resour
|
|||
}
|
||||
}
|
||||
|
||||
func testAccCheckComputeProjectMetadataContains(project *compute.Project, key string, value string) resource.TestCheckFunc {
|
||||
func testAccCheckComputeProjectMetadataContains(projectID, key, value string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
config := testAccProvider.Meta().(*Config)
|
||||
project, err := config.clientCompute.Projects.Get(config.Project).Do()
|
||||
project, err := config.clientCompute.Projects.Get(projectID).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error, failed to load project service for %s: %s", config.Project, err)
|
||||
}
|
||||
|
@ -161,14 +195,14 @@ func testAccCheckComputeProjectMetadataContains(project *compute.Project, key st
|
|||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("Error, key %s not present", key)
|
||||
return fmt.Errorf("Error, key %s not present in %s", key, project.SelfLink)
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckComputeProjectMetadataSize(project *compute.Project, size int) resource.TestCheckFunc {
|
||||
func testAccCheckComputeProjectMetadataSize(projectID string, size int) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
config := testAccProvider.Meta().(*Config)
|
||||
project, err := config.clientCompute.Projects.Get(config.Project).Do()
|
||||
project, err := config.clientCompute.Projects.Get(projectID).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error, failed to load project service for %s: %s", config.Project, err)
|
||||
}
|
||||
|
@ -182,36 +216,100 @@ func testAccCheckComputeProjectMetadataSize(project *compute.Project, size int)
|
|||
}
|
||||
}
|
||||
|
||||
const testAccComputeProject_basic0_metadata = `
|
||||
resource "google_compute_project_metadata" "fizzbuzz" {
|
||||
metadata {
|
||||
banana = "orange"
|
||||
sofa = "darwinism"
|
||||
}
|
||||
}`
|
||||
func testAccComputeProject_basic0_metadata(projectID, name, org, billing string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_project" "project" {
|
||||
project_id = "%s"
|
||||
name = "%s"
|
||||
org_id = "%s"
|
||||
billing_account = "%s"
|
||||
}
|
||||
|
||||
const testAccComputeProject_basic1_metadata = `
|
||||
resource "google_compute_project_metadata" "fizzbuzz" {
|
||||
metadata {
|
||||
kiwi = "papaya"
|
||||
finches = "darwinism"
|
||||
}
|
||||
}`
|
||||
resource "google_project_services" "services" {
|
||||
project = "${google_project.project.project_id}"
|
||||
services = ["compute-component.googleapis.com"]
|
||||
}
|
||||
|
||||
const testAccComputeProject_modify0_metadata = `
|
||||
resource "google_compute_project_metadata" "fizzbuzz" {
|
||||
metadata {
|
||||
paper = "pen"
|
||||
genghis_khan = "french bread"
|
||||
happy = "smiling"
|
||||
}
|
||||
}`
|
||||
project = "${google_project.project.project_id}"
|
||||
metadata {
|
||||
banana = "orange"
|
||||
sofa = "darwinism"
|
||||
}
|
||||
depends_on = ["google_project_services.services"]
|
||||
}`, projectID, name, org, billing)
|
||||
}
|
||||
|
||||
func testAccComputeProject_basic1_metadata(projectID, name, org, billing string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_project" "project" {
|
||||
project_id = "%s"
|
||||
name = "%s"
|
||||
org_id = "%s"
|
||||
billing_account = "%s"
|
||||
}
|
||||
|
||||
resource "google_project_services" "services" {
|
||||
project = "${google_project.project.project_id}"
|
||||
services = ["compute-component.googleapis.com"]
|
||||
}
|
||||
|
||||
const testAccComputeProject_modify1_metadata = `
|
||||
resource "google_compute_project_metadata" "fizzbuzz" {
|
||||
metadata {
|
||||
paper = "pen"
|
||||
paris = "french bread"
|
||||
happy = "laughing"
|
||||
}
|
||||
}`
|
||||
project = "${google_project.project.project_id}"
|
||||
metadata {
|
||||
kiwi = "papaya"
|
||||
finches = "darwinism"
|
||||
}
|
||||
depends_on = ["google_project_services.services"]
|
||||
}`, projectID, name, org, billing)
|
||||
}
|
||||
|
||||
func testAccComputeProject_modify0_metadata(projectID, name, org, billing string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_project" "project" {
|
||||
project_id = "%s"
|
||||
name = "%s"
|
||||
org_id = "%s"
|
||||
billing_account = "%s"
|
||||
}
|
||||
|
||||
resource "google_project_services" "services" {
|
||||
project = "${google_project.project.project_id}"
|
||||
services = ["compute-component.googleapis.com"]
|
||||
}
|
||||
|
||||
resource "google_compute_project_metadata" "fizzbuzz" {
|
||||
project = "${google_project.project.project_id}"
|
||||
metadata {
|
||||
paper = "pen"
|
||||
genghis_khan = "french bread"
|
||||
happy = "smiling"
|
||||
}
|
||||
depends_on = ["google_project_services.services"]
|
||||
}`, projectID, name, org, billing)
|
||||
}
|
||||
|
||||
func testAccComputeProject_modify1_metadata(projectID, name, org, billing string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_project" "project" {
|
||||
project_id = "%s"
|
||||
name = "%s"
|
||||
org_id = "%s"
|
||||
billing_account = "%s"
|
||||
}
|
||||
|
||||
resource "google_project_services" "services" {
|
||||
project = "${google_project.project.project_id}"
|
||||
services = ["compute-component.googleapis.com"]
|
||||
}
|
||||
|
||||
resource "google_compute_project_metadata" "fizzbuzz" {
|
||||
project = "${google_project.project.project_id}"
|
||||
metadata {
|
||||
paper = "pen"
|
||||
paris = "french bread"
|
||||
happy = "laughing"
|
||||
}
|
||||
depends_on = ["google_project_services.services"]
|
||||
}`, projectID, name, org, billing)
|
||||
}
|
||||
|
|
|
@ -31,6 +31,14 @@ func resourceGoogleProjectServices() *schema.Resource {
|
|||
}
|
||||
}
|
||||
|
||||
// These services can only be enabled as a side-effect of enabling other services,
|
||||
// so don't bother storing them in the config or using them for diffing.
|
||||
var ignore = map[string]struct{}{
|
||||
"containeranalysis.googleapis.com": struct{}{},
|
||||
"dataproc-control.googleapis.com": struct{}{},
|
||||
"source.googleapis.com": struct{}{},
|
||||
}
|
||||
|
||||
func resourceGoogleProjectServicesCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
pid := d.Get("project").(string)
|
||||
|
@ -160,7 +168,9 @@ func getApiServices(pid string, config *Config) ([]string, error) {
|
|||
return apiServices, err
|
||||
}
|
||||
for _, v := range svcResp.Services {
|
||||
apiServices = append(apiServices, v.ServiceName)
|
||||
if _, ok := ignore[v.ServiceName]; !ok {
|
||||
apiServices = append(apiServices, v.ServiceName)
|
||||
}
|
||||
}
|
||||
return apiServices, nil
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
@ -123,6 +124,46 @@ func TestAccGoogleProjectServices_authoritative2(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
// Test that services that can't be enabled on their own (such as dataproc-control.googleapis.com)
|
||||
// don't end up causing diffs when they are enabled as a side-effect of a different service's
|
||||
// enablement.
|
||||
func TestAccGoogleProjectServices_ignoreUnenablableServices(t *testing.T) {
|
||||
skipIfEnvNotSet(t,
|
||||
[]string{
|
||||
"GOOGLE_ORG",
|
||||
"GOOGLE_BILLING_ACCOUNT",
|
||||
}...,
|
||||
)
|
||||
|
||||
billingId := os.Getenv("GOOGLE_BILLING_ACCOUNT")
|
||||
pid := "terraform-" + acctest.RandString(10)
|
||||
services := []string{
|
||||
"dataproc.googleapis.com",
|
||||
// The following services are enabled as a side-effect of dataproc's enablement
|
||||
"storage-component.googleapis.com",
|
||||
"deploymentmanager.googleapis.com",
|
||||
"replicapool.googleapis.com",
|
||||
"replicapoolupdater.googleapis.com",
|
||||
"resourceviews.googleapis.com",
|
||||
"compute-component.googleapis.com",
|
||||
"container.googleapis.com",
|
||||
"storage-api.googleapis.com",
|
||||
}
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccGoogleProjectAssociateServicesBasic_withBilling(services, pid, pname, org, billingId),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testProjectServicesMatch(services, pid),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccGoogleProjectAssociateServicesBasic(services []string, pid, name, org string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_project" "acceptance" {
|
||||
|
@ -137,6 +178,21 @@ resource "google_project_services" "acceptance" {
|
|||
`, pid, name, org, testStringsToString(services))
|
||||
}
|
||||
|
||||
func testAccGoogleProjectAssociateServicesBasic_withBilling(services []string, pid, name, org, billing string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_project" "acceptance" {
|
||||
project_id = "%s"
|
||||
name = "%s"
|
||||
org_id = "%s"
|
||||
billing_account = "%s"
|
||||
}
|
||||
resource "google_project_services" "acceptance" {
|
||||
project = "${google_project.acceptance.project_id}"
|
||||
services = [%s]
|
||||
}
|
||||
`, pid, name, org, billing, testStringsToString(services))
|
||||
}
|
||||
|
||||
func testProjectServicesMatch(services []string, pid string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
config := testAccProvider.Meta().(*Config)
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package heroku
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
|
@ -25,12 +27,13 @@ func Provider() terraform.ResourceProvider {
|
|||
},
|
||||
|
||||
ResourcesMap: map[string]*schema.Resource{
|
||||
"heroku_addon": resourceHerokuAddon(),
|
||||
"heroku_app": resourceHerokuApp(),
|
||||
"heroku_cert": resourceHerokuCert(),
|
||||
"heroku_domain": resourceHerokuDomain(),
|
||||
"heroku_drain": resourceHerokuDrain(),
|
||||
"heroku_space": resourceHerokuSpace(),
|
||||
"heroku_addon": resourceHerokuAddon(),
|
||||
"heroku_app": resourceHerokuApp(),
|
||||
"heroku_app_feature": resourceHerokuAppFeature(),
|
||||
"heroku_cert": resourceHerokuCert(),
|
||||
"heroku_domain": resourceHerokuDomain(),
|
||||
"heroku_drain": resourceHerokuDrain(),
|
||||
"heroku_space": resourceHerokuSpace(),
|
||||
},
|
||||
|
||||
ConfigureFunc: providerConfigure,
|
||||
|
@ -46,3 +49,12 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
|
|||
log.Println("[INFO] Initializing Heroku client")
|
||||
return config.Client()
|
||||
}
|
||||
|
||||
func buildCompositeID(a, b string) string {
|
||||
return fmt.Sprintf("%s:%s", a, b)
|
||||
}
|
||||
|
||||
func parseCompositeID(id string) (string, string) {
|
||||
parts := strings.SplitN(id, ":", 2)
|
||||
return parts[0], parts[1]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
package heroku
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
heroku "github.com/cyberdelia/heroku-go/v3"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
func resourceHerokuAppFeature() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceHerokuAppFeatureCreate,
|
||||
Update: resourceHerokuAppFeatureUpdate,
|
||||
Read: resourceHerokuAppFeatureRead,
|
||||
Delete: resourceHerokuAppFeatureDelete,
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"app": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"name": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"enabled": {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceHerokuAppFeatureRead(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Service)
|
||||
|
||||
app, id := parseCompositeID(d.Id())
|
||||
|
||||
feature, err := client.AppFeatureInfo(context.TODO(), app, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.Set("app", app)
|
||||
d.Set("name", feature.Name)
|
||||
d.Set("enabled", feature.Enabled)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceHerokuAppFeatureCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Service)
|
||||
|
||||
app := d.Get("app").(string)
|
||||
featureName := d.Get("name").(string)
|
||||
enabled := d.Get("enabled").(bool)
|
||||
|
||||
opts := heroku.AppFeatureUpdateOpts{Enabled: enabled}
|
||||
|
||||
log.Printf("[DEBUG] Feature set configuration: %#v, %#v", featureName, opts)
|
||||
|
||||
feature, err := client.AppFeatureUpdate(context.TODO(), app, featureName, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.SetId(buildCompositeID(app, feature.ID))
|
||||
|
||||
return resourceHerokuAppFeatureRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceHerokuAppFeatureUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
if d.HasChange("enabled") {
|
||||
return resourceHerokuAppFeatureCreate(d, meta)
|
||||
}
|
||||
|
||||
return resourceHerokuAppFeatureRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceHerokuAppFeatureDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Service)
|
||||
|
||||
app, id := parseCompositeID(d.Id())
|
||||
featureName := d.Get("name").(string)
|
||||
|
||||
log.Printf("[INFO] Deleting app feature %s (%s) for app %s", featureName, id, app)
|
||||
opts := heroku.AppFeatureUpdateOpts{Enabled: false}
|
||||
_, err := client.AppFeatureUpdate(context.TODO(), app, id, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
package heroku
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
heroku "github.com/cyberdelia/heroku-go/v3"
|
||||
"github.com/hashicorp/terraform/helper/acctest"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestAccHerokuAppFeature(t *testing.T) {
|
||||
var feature heroku.AppFeatureInfoResult
|
||||
appName := fmt.Sprintf("tftest-%s", acctest.RandString(10))
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckHerokuFeatureDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccCheckHerokuFeature_basic(appName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckHerokuFeatureExists("heroku_app_feature.runtime_metrics", &feature),
|
||||
testAccCheckHerokuFeatureEnabled(&feature, true),
|
||||
resource.TestCheckResourceAttr(
|
||||
"heroku_app_feature.runtime_metrics", "enabled", "true",
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
Config: testAccCheckHerokuFeature_disabled(appName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckHerokuFeatureExists("heroku_app_feature.runtime_metrics", &feature),
|
||||
testAccCheckHerokuFeatureEnabled(&feature, false),
|
||||
resource.TestCheckResourceAttr(
|
||||
"heroku_app_feature.runtime_metrics", "enabled", "false",
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckHerokuFeatureDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.Meta().(*heroku.Service)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "heroku_app_feature" {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err := client.AppFeatureInfo(context.TODO(), rs.Primary.Attributes["app"], rs.Primary.ID)
|
||||
|
||||
if err == nil {
|
||||
return fmt.Errorf("Feature still exists")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckHerokuFeatureExists(n string, feature *heroku.AppFeatureInfoResult) 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 feature ID is set")
|
||||
}
|
||||
|
||||
app, id := parseCompositeID(rs.Primary.ID)
|
||||
if app != rs.Primary.Attributes["app"] {
|
||||
return fmt.Errorf("Bad app: %s", app)
|
||||
}
|
||||
|
||||
client := testAccProvider.Meta().(*heroku.Service)
|
||||
|
||||
foundFeature, err := client.AppFeatureInfo(context.TODO(), app, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if foundFeature.ID != id {
|
||||
return fmt.Errorf("Feature not found")
|
||||
}
|
||||
|
||||
*feature = *foundFeature
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckHerokuFeatureEnabled(feature *heroku.AppFeatureInfoResult, enabled bool) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if feature.Enabled != enabled {
|
||||
return fmt.Errorf("Bad enabled: %v", feature.Enabled)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckHerokuFeature_basic(appName string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "heroku_app" "example" {
|
||||
name = "%s"
|
||||
region = "us"
|
||||
}
|
||||
|
||||
resource "heroku_app_feature" "runtime_metrics" {
|
||||
app = "${heroku_app.example.name}"
|
||||
name = "log-runtime-metrics"
|
||||
}
|
||||
`, appName)
|
||||
}
|
||||
|
||||
func testAccCheckHerokuFeature_disabled(appName string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "heroku_app" "example" {
|
||||
name = "%s"
|
||||
region = "us"
|
||||
}
|
||||
|
||||
resource "heroku_app_feature" "runtime_metrics" {
|
||||
app = "${heroku_app.example.name}"
|
||||
name = "log-runtime-metrics"
|
||||
enabled = false
|
||||
}
|
||||
`, appName)
|
||||
}
|
|
@ -2,9 +2,12 @@ package heroku
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
heroku "github.com/cyberdelia/heroku-go/v3"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
|
@ -56,23 +59,32 @@ func resourceHerokuSpaceCreate(d *schema.ResourceData, meta interface{}) error {
|
|||
d.SetId(space.ID)
|
||||
log.Printf("[INFO] Space ID: %s", d.Id())
|
||||
|
||||
// The type conversion here can be dropped when the vendored version of
|
||||
// heroku-go is updated.
|
||||
setSpaceAttributes(d, (*heroku.Space)(space))
|
||||
return nil
|
||||
// Wait for the Space to be allocated
|
||||
log.Printf("[DEBUG] Waiting for Space (%s) to be allocated", d.Id())
|
||||
stateConf := &resource.StateChangeConf{
|
||||
Pending: []string{"allocating"},
|
||||
Target: []string{"allocated"},
|
||||
Refresh: SpaceStateRefreshFunc(client, d.Id()),
|
||||
Timeout: 20 * time.Minute,
|
||||
}
|
||||
|
||||
if _, err := stateConf.WaitForState(); err != nil {
|
||||
return fmt.Errorf("Error waiting for Space (%s) to become available: %s", d.Id(), err)
|
||||
}
|
||||
|
||||
return resourceHerokuSpaceRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceHerokuSpaceRead(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Service)
|
||||
|
||||
space, err := client.SpaceInfo(context.TODO(), d.Id())
|
||||
spaceRaw, _, err := SpaceStateRefreshFunc(client, d.Id())()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
space := spaceRaw.(*heroku.Space)
|
||||
|
||||
// The type conversion here can be dropped when the vendored version of
|
||||
// heroku-go is updated.
|
||||
setSpaceAttributes(d, (*heroku.Space)(space))
|
||||
setSpaceAttributes(d, space)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -115,3 +127,18 @@ func resourceHerokuSpaceDelete(d *schema.ResourceData, meta interface{}) error {
|
|||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
|
||||
// SpaceStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
|
||||
// a Space.
|
||||
func SpaceStateRefreshFunc(client *heroku.Service, id string) resource.StateRefreshFunc {
|
||||
return func() (interface{}, string, error) {
|
||||
space, err := client.SpaceInfo(context.TODO(), id)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// The type conversion here can be dropped when the vendored version of
|
||||
// heroku-go is updated.
|
||||
return (*heroku.Space)(space), space.State, nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,24 @@ func Provider() terraform.ResourceProvider {
|
|||
DefaultFunc: schema.EnvDefaultFunc("NOMAD_REGION", ""),
|
||||
Description: "Region of the target Nomad agent.",
|
||||
},
|
||||
"ca_file": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
DefaultFunc: schema.EnvDefaultFunc("NOMAD_CACERT", ""),
|
||||
Description: "A path to a PEM-encoded certificate authority used to verify the remote agent's certificate.",
|
||||
},
|
||||
"cert_file": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
DefaultFunc: schema.EnvDefaultFunc("NOMAD_CLIENT_CERT", ""),
|
||||
Description: "A path to a PEM-encoded certificate provided to the remote agent; requires use of key_file.",
|
||||
},
|
||||
"key_file": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
DefaultFunc: schema.EnvDefaultFunc("NOMAD_CLIENT_KEY", ""),
|
||||
Description: "A path to a PEM-encoded private key, required if cert_file is specified.",
|
||||
},
|
||||
},
|
||||
|
||||
ConfigureFunc: providerConfigure,
|
||||
|
@ -38,6 +56,9 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
|
|||
config := api.DefaultConfig()
|
||||
config.Address = d.Get("address").(string)
|
||||
config.Region = d.Get("region").(string)
|
||||
config.TLSConfig.CACert = d.Get("ca_file").(string)
|
||||
config.TLSConfig.ClientCert = d.Get("cert_file").(string)
|
||||
config.TLSConfig.ClientKey = d.Get("key_file").(string)
|
||||
|
||||
client, err := api.NewClient(config)
|
||||
if err != nil {
|
||||
|
|
|
@ -99,3 +99,59 @@ resource "test_resource" "foo" {
|
|||
},
|
||||
})
|
||||
}
|
||||
|
||||
// TestDataSource_dataSourceCountGrandChild tests that a grandchild data source
|
||||
// that is based off of count works, ie: dependency chain foo -> bar -> baz.
|
||||
// This was failing because CountBoundaryTransformer is being run during apply
|
||||
// instead of plan, which meant that it wasn't firing after data sources were
|
||||
// potentially changing state and causing diff/interpolation issues.
|
||||
//
|
||||
// This happens after the initial apply, after state is saved.
|
||||
func TestDataSource_dataSourceCountGrandChild(t *testing.T) {
|
||||
resource.UnitTest(t, resource.TestCase{
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: func(s *terraform.State) error {
|
||||
return nil
|
||||
},
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: dataSourceCountGrandChildConfig,
|
||||
},
|
||||
{
|
||||
Config: dataSourceCountGrandChildConfig,
|
||||
Check: func(s *terraform.State) error {
|
||||
for _, v := range []string{"foo", "bar", "baz"} {
|
||||
count := 0
|
||||
for k := range s.RootModule().Resources {
|
||||
if strings.HasPrefix(k, fmt.Sprintf("data.test_data_source.%s.", v)) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
if count != 2 {
|
||||
return fmt.Errorf("bad count for data.test_data_source.%s: %d", v, count)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const dataSourceCountGrandChildConfig = `
|
||||
data "test_data_source" "foo" {
|
||||
count = 2
|
||||
input = "one"
|
||||
}
|
||||
|
||||
data "test_data_source" "bar" {
|
||||
count = "${length(data.test_data_source.foo.*.id)}"
|
||||
input = "${data.test_data_source.foo.*.output[count.index]}"
|
||||
}
|
||||
|
||||
data "test_data_source" "baz" {
|
||||
count = "${length(data.test_data_source.bar.*.id)}"
|
||||
input = "${data.test_data_source.bar.*.output[count.index]}"
|
||||
}
|
||||
`
|
||||
|
|
|
@ -42,6 +42,12 @@ func Provider() terraform.ResourceProvider {
|
|||
Required: true,
|
||||
DefaultFunc: schema.MultiEnvDefaultFunc([]string{"TRITON_KEY_ID", "SDC_KEY_ID"}, ""),
|
||||
},
|
||||
|
||||
"insecure_skip_tls_verify": {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
DefaultFunc: schema.EnvDefaultFunc("TRITON_SKIP_TLS_VERIFY", ""),
|
||||
},
|
||||
},
|
||||
|
||||
ResourcesMap: map[string]*schema.Resource{
|
||||
|
@ -56,10 +62,11 @@ func Provider() terraform.ResourceProvider {
|
|||
}
|
||||
|
||||
type Config struct {
|
||||
Account string
|
||||
KeyMaterial string
|
||||
KeyID string
|
||||
URL string
|
||||
Account string
|
||||
KeyMaterial string
|
||||
KeyID string
|
||||
URL string
|
||||
InsecureSkipTLSVerify bool
|
||||
}
|
||||
|
||||
func (c Config) validate() error {
|
||||
|
@ -98,6 +105,10 @@ func (c Config) getTritonClient() (*triton.Client, error) {
|
|||
return nil, errwrap.Wrapf("Error Creating Triton Client: {{err}}", err)
|
||||
}
|
||||
|
||||
if c.InsecureSkipTLSVerify {
|
||||
client.InsecureSkipTLSVerify()
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
|
@ -106,6 +117,8 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
|
|||
Account: d.Get("account").(string),
|
||||
URL: d.Get("url").(string),
|
||||
KeyID: d.Get("key_id").(string),
|
||||
|
||||
InsecureSkipTLSVerify: d.Get("insecure_skip_tls_verify").(bool),
|
||||
}
|
||||
|
||||
if keyMaterial, ok := d.GetOk("key_material"); ok {
|
||||
|
|
|
@ -23,6 +23,7 @@ var (
|
|||
"user_script": "user-script",
|
||||
"user_data": "user-data",
|
||||
"administrator_pw": "administrator-pw",
|
||||
"cloud_config": "cloud-init:user-data",
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -182,6 +183,12 @@ func resourceMachine() *schema.Resource {
|
|||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
"cloud_config": {
|
||||
Description: "copied to machine on boot",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
"user_data": {
|
||||
Description: "Data copied to machine on boot",
|
||||
Type: schema.TypeString,
|
||||
|
|
|
@ -57,7 +57,7 @@ const (
|
|||
envDoesNotExist = `
|
||||
Environment %q doesn't exist!
|
||||
|
||||
You can create this environment with the "-new" option.`
|
||||
You can create this environment with the "new" option.`
|
||||
|
||||
envChanged = `[reset][green]Switched to environment %q!`
|
||||
|
||||
|
|
|
@ -3154,3 +3154,146 @@ func TestContext2Plan_ignoreChangesWithFlatmaps(t *testing.T) {
|
|||
t.Fatalf("bad:\n%s\n\nexpected\n\n%s", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
// TestContext2Plan_resourceNestedCount ensures resource sets that depend on
|
||||
// the count of another resource set (ie: count of a data source that depends
|
||||
// on another data source's instance count - data.x.foo.*.id) get properly
|
||||
// normalized to the indexes they should be. This case comes up when there is
|
||||
// an existing state (after an initial apply).
|
||||
func TestContext2Plan_resourceNestedCount(t *testing.T) {
|
||||
m := testModule(t, "nested-resource-count-plan")
|
||||
p := testProvider("aws")
|
||||
p.DiffFn = testDiffFn
|
||||
p.RefreshFn = func(i *InstanceInfo, is *InstanceState) (*InstanceState, error) {
|
||||
return is, nil
|
||||
}
|
||||
s := &State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: rootModulePath,
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.foo.0": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo0",
|
||||
Attributes: map[string]string{
|
||||
"id": "foo0",
|
||||
},
|
||||
},
|
||||
},
|
||||
"aws_instance.foo.1": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo1",
|
||||
Attributes: map[string]string{
|
||||
"id": "foo1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"aws_instance.bar.0": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Dependencies: []string{"aws_instance.foo.*"},
|
||||
Primary: &InstanceState{
|
||||
ID: "bar0",
|
||||
Attributes: map[string]string{
|
||||
"id": "bar0",
|
||||
},
|
||||
},
|
||||
},
|
||||
"aws_instance.bar.1": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Dependencies: []string{"aws_instance.foo.*"},
|
||||
Primary: &InstanceState{
|
||||
ID: "bar1",
|
||||
Attributes: map[string]string{
|
||||
"id": "bar1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"aws_instance.baz.0": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Dependencies: []string{"aws_instance.bar.*"},
|
||||
Primary: &InstanceState{
|
||||
ID: "baz0",
|
||||
Attributes: map[string]string{
|
||||
"id": "baz0",
|
||||
},
|
||||
},
|
||||
},
|
||||
"aws_instance.baz.1": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Dependencies: []string{"aws_instance.bar.*"},
|
||||
Primary: &InstanceState{
|
||||
ID: "baz1",
|
||||
Attributes: map[string]string{
|
||||
"id": "baz1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Module: m,
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(p),
|
||||
},
|
||||
State: s,
|
||||
})
|
||||
|
||||
w, e := ctx.Validate()
|
||||
if len(w) > 0 {
|
||||
t.Fatalf("warnings generated on validate: %#v", w)
|
||||
}
|
||||
if len(e) > 0 {
|
||||
t.Fatalf("errors generated on validate: %#v", e)
|
||||
}
|
||||
|
||||
_, err := ctx.Refresh()
|
||||
if err != nil {
|
||||
t.Fatalf("refresh err: %s", err)
|
||||
}
|
||||
|
||||
plan, err := ctx.Plan()
|
||||
if err != nil {
|
||||
t.Fatalf("plan err: %s", err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(plan.String())
|
||||
expected := strings.TrimSpace(`
|
||||
DIFF:
|
||||
|
||||
|
||||
|
||||
STATE:
|
||||
|
||||
aws_instance.bar.0:
|
||||
ID = bar0
|
||||
|
||||
Dependencies:
|
||||
aws_instance.foo.*
|
||||
aws_instance.bar.1:
|
||||
ID = bar1
|
||||
|
||||
Dependencies:
|
||||
aws_instance.foo.*
|
||||
aws_instance.baz.0:
|
||||
ID = baz0
|
||||
|
||||
Dependencies:
|
||||
aws_instance.bar.*
|
||||
aws_instance.baz.1:
|
||||
ID = baz1
|
||||
|
||||
Dependencies:
|
||||
aws_instance.bar.*
|
||||
aws_instance.foo.0:
|
||||
ID = foo0
|
||||
aws_instance.foo.1:
|
||||
ID = foo1
|
||||
`)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n%s\n\nexpected\n\n%s", actual, expected)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -113,6 +113,9 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
|||
// have to connect again later for providers and so on.
|
||||
&ReferenceTransformer{},
|
||||
|
||||
// Add the node to fix the state count boundaries
|
||||
&CountBoundaryTransformer{},
|
||||
|
||||
// Target
|
||||
&TargetsTransformer{Targets: b.Targets},
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ func TestPlanGraphBuilder(t *testing.T) {
|
|||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testPlanGraphBuilderStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad: %s", actual)
|
||||
t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,6 +61,14 @@ aws_load_balancer.weblb
|
|||
provider.aws
|
||||
aws_security_group.firewall
|
||||
provider.aws
|
||||
meta.count-boundary (count boundary fixup)
|
||||
aws_instance.web
|
||||
aws_load_balancer.weblb
|
||||
aws_security_group.firewall
|
||||
openstack_floating_ip.random
|
||||
provider.aws
|
||||
provider.openstack
|
||||
var.foo
|
||||
openstack_floating_ip.random
|
||||
provider.openstack
|
||||
provider.aws
|
||||
|
@ -75,6 +83,7 @@ provider.openstack (close)
|
|||
openstack_floating_ip.random
|
||||
provider.openstack
|
||||
root
|
||||
meta.count-boundary (count boundary fixup)
|
||||
provider.aws (close)
|
||||
provider.openstack (close)
|
||||
var.foo
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
resource "aws_instance" "foo" {
|
||||
count = 2
|
||||
}
|
||||
|
||||
resource "aws_instance" "bar" {
|
||||
count = "${length(aws_instance.foo.*.id)}"
|
||||
}
|
||||
|
||||
resource "aws_instance" "baz" {
|
||||
count = "${length(aws_instance.bar.*.id)}"
|
||||
}
|
|
@ -5,8 +5,9 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
)
|
||||
|
||||
type AccountsClient struct {
|
||||
|
@ -40,7 +41,8 @@ type Account struct {
|
|||
type GetAccountInput struct{}
|
||||
|
||||
func (client *AccountsClient) GetAccount(input *GetAccountInput) (*Account, error) {
|
||||
respReader, err := client.executeRequest(http.MethodGet, "/my", nil)
|
||||
path := fmt.Sprintf("/%s", client.accountName)
|
||||
respReader, err := client.executeRequest(http.MethodGet, path, nil)
|
||||
if respReader != nil {
|
||||
defer respReader.Close()
|
||||
}
|
||||
|
@ -58,17 +60,17 @@ func (client *AccountsClient) GetAccount(input *GetAccountInput) (*Account, erro
|
|||
}
|
||||
|
||||
type UpdateAccountInput struct {
|
||||
Email string `json:"email,omitempty"`
|
||||
CompanyName string `json:"companyName,omitempty"`
|
||||
FirstName string `json:"firstName,omitempty"`
|
||||
LastName string `json:"lastName,omitempty"`
|
||||
Address string `json:"address,omitempty"`
|
||||
PostalCode string `json:"postalCode,omitempty"`
|
||||
City string `json:"city,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Country string `json:"country,omitempty"`
|
||||
Phone string `json:"phone,omitempty"`
|
||||
TritonCNSEnabled bool `json:"triton_cns_enabled,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
CompanyName string `json:"companyName,omitempty"`
|
||||
FirstName string `json:"firstName,omitempty"`
|
||||
LastName string `json:"lastName,omitempty"`
|
||||
Address string `json:"address,omitempty"`
|
||||
PostalCode string `json:"postalCode,omitempty"`
|
||||
City string `json:"city,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Country string `json:"country,omitempty"`
|
||||
Phone string `json:"phone,omitempty"`
|
||||
TritonCNSEnabled bool `json:"triton_cns_enabled,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateAccount updates your account details with the given parameters.
|
||||
|
|
|
@ -2,6 +2,7 @@ package triton
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -45,16 +46,7 @@ func NewClient(endpoint string, accountName string, signers ...authentication.Si
|
|||
}
|
||||
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
DisableKeepAlives: true,
|
||||
MaxIdleConnsPerHost: -1,
|
||||
},
|
||||
Transport: httpTransport(false),
|
||||
CheckRedirect: doNotFollowRedirects,
|
||||
}
|
||||
|
||||
|
@ -75,6 +67,34 @@ func NewClient(endpoint string, accountName string, signers ...authentication.Si
|
|||
}, nil
|
||||
}
|
||||
|
||||
// InsecureSkipTLSVerify turns off TLS verification for the client connection. This
|
||||
// allows connection to an endpoint with a certificate which was signed by a non-
|
||||
// trusted CA, such as self-signed certificates. This can be useful when connecting
|
||||
// to temporary Triton installations such as Triton Cloud-On-A-Laptop.
|
||||
func (c *Client) InsecureSkipTLSVerify() {
|
||||
if c.client == nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.client.HTTPClient.Transport = httpTransport(true)
|
||||
}
|
||||
|
||||
func httpTransport(insecureSkipTLSVerify bool) *http.Transport {
|
||||
return &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
DisableKeepAlives: true,
|
||||
MaxIdleConnsPerHost: -1,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: insecureSkipTLSVerify,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func doNotFollowRedirects(*http.Request, []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
|
|
|
@ -28,7 +28,8 @@ type DataCenter struct {
|
|||
type ListDataCentersInput struct{}
|
||||
|
||||
func (client *DataCentersClient) ListDataCenters(*ListDataCentersInput) ([]*DataCenter, error) {
|
||||
respReader, err := client.executeRequest(http.MethodGet, "/my/datacenters", nil)
|
||||
path := fmt.Sprintf("/%s/datacenters", client.accountName)
|
||||
respReader, err := client.executeRequest(http.MethodGet, path, nil)
|
||||
if respReader != nil {
|
||||
defer respReader.Close()
|
||||
}
|
||||
|
@ -68,7 +69,8 @@ type GetDataCenterInput struct {
|
|||
}
|
||||
|
||||
func (client *DataCentersClient) GetDataCenter(input *GetDataCenterInput) (*DataCenter, error) {
|
||||
resp, err := client.executeRequestRaw(http.MethodGet, fmt.Sprintf("/my/datacenters/%s", input.Name), nil)
|
||||
path := fmt.Sprintf("/%s/datacenters/%s", client.accountName, input.Name)
|
||||
resp, err := client.executeRequestRaw(http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("Error executing GetDatacenter request: {{err}}", err)
|
||||
}
|
||||
|
|
|
@ -27,7 +27,8 @@ type FabricVLAN struct {
|
|||
type ListFabricVLANsInput struct{}
|
||||
|
||||
func (client *FabricsClient) ListFabricVLANs(*ListFabricVLANsInput) ([]*FabricVLAN, error) {
|
||||
respReader, err := client.executeRequest(http.MethodGet, "/my/fabrics/default/vlans", nil)
|
||||
path := fmt.Sprintf("/%s/fabrics/default/vlans", client.accountName)
|
||||
respReader, err := client.executeRequest(http.MethodGet, path, nil)
|
||||
if respReader != nil {
|
||||
defer respReader.Close()
|
||||
}
|
||||
|
|
|
@ -39,7 +39,8 @@ type FirewallRule struct {
|
|||
type ListFirewallRulesInput struct{}
|
||||
|
||||
func (client *FirewallClient) ListFirewallRules(*ListFirewallRulesInput) ([]*FirewallRule, error) {
|
||||
respReader, err := client.executeRequest(http.MethodGet, "/my/fwrules", nil)
|
||||
path := fmt.Sprintf("/%s/fwrules", client.accountName)
|
||||
respReader, err := client.executeRequest(http.MethodGet, path, nil)
|
||||
if respReader != nil {
|
||||
defer respReader.Close()
|
||||
}
|
||||
|
@ -194,7 +195,8 @@ type ListMachineFirewallRulesInput struct {
|
|||
}
|
||||
|
||||
func (client *FirewallClient) ListMachineFirewallRules(input *ListMachineFirewallRulesInput) ([]*FirewallRule, error) {
|
||||
respReader, err := client.executeRequest(http.MethodGet, fmt.Sprintf("/my/machines/%s/firewallrules", input.MachineID), nil)
|
||||
path := fmt.Sprintf("/%s/machines/%s/firewallrules", client.accountName, input.MachineID)
|
||||
respReader, err := client.executeRequest(http.MethodGet, path, nil)
|
||||
if respReader != nil {
|
||||
defer respReader.Close()
|
||||
}
|
||||
|
|
|
@ -49,7 +49,8 @@ type Image struct {
|
|||
type ListImagesInput struct{}
|
||||
|
||||
func (client *ImagesClient) ListImages(*ListImagesInput) ([]*Image, error) {
|
||||
respReader, err := client.executeRequest(http.MethodGet, "/my/images", nil)
|
||||
path := fmt.Sprintf("/%s/images", client.accountName)
|
||||
respReader, err := client.executeRequest(http.MethodGet, path, nil)
|
||||
if respReader != nil {
|
||||
defer respReader.Close()
|
||||
}
|
||||
|
@ -148,7 +149,7 @@ type CreateImageFromMachineInput struct {
|
|||
HomePage string `json:"homepage,omitempty"`
|
||||
EULA string `json:"eula,omitempty"`
|
||||
ACL []string `json:"acl,omitempty"`
|
||||
tags map[string]string `json:"tags,omitempty"`
|
||||
Tags map[string]string `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
func (client *ImagesClient) CreateImageFromMachine(input *CreateImageFromMachineInput) (*Image, error) {
|
||||
|
@ -178,7 +179,7 @@ type UpdateImageInput struct {
|
|||
HomePage string `json:"homepage,omitempty"`
|
||||
EULA string `json:"eula,omitempty"`
|
||||
ACL []string `json:"acl,omitempty"`
|
||||
tags map[string]string `json:"tags,omitempty"`
|
||||
Tags map[string]string `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
func (client *ImagesClient) UpdateImage(input *UpdateImageInput) (*Image, error) {
|
||||
|
|
|
@ -35,7 +35,8 @@ type ListKeysInput struct{}
|
|||
// ListKeys lists all public keys we have on record for the specified
|
||||
// account.
|
||||
func (client *KeysClient) ListKeys(*ListKeysInput) ([]*Key, error) {
|
||||
respReader, err := client.executeRequest(http.MethodGet, "/my/keys", nil)
|
||||
path := fmt.Sprintf("/%s/keys")
|
||||
respReader, err := client.executeRequest(http.MethodGet, path, nil)
|
||||
if respReader != nil {
|
||||
defer respReader.Close()
|
||||
}
|
||||
|
|
|
@ -100,7 +100,7 @@ func (client *MachinesClient) GetMachine(input *GetMachineInput) (*Machine, erro
|
|||
if response != nil {
|
||||
defer response.Body.Close()
|
||||
}
|
||||
if response.StatusCode == http.StatusNotFound {
|
||||
if response.StatusCode == http.StatusNotFound || response.StatusCode == http.StatusGone {
|
||||
return nil, &TritonError{
|
||||
Code: "ResourceNotFound",
|
||||
}
|
||||
|
@ -219,7 +219,8 @@ func (input *CreateMachineInput) toAPI() map[string]interface{} {
|
|||
}
|
||||
|
||||
func (client *MachinesClient) CreateMachine(input *CreateMachineInput) (*Machine, error) {
|
||||
respReader, err := client.executeRequest(http.MethodPost, "/my/machines", input.toAPI())
|
||||
path := fmt.Sprintf("/%s/machines", client.accountName)
|
||||
respReader, err := client.executeRequest(http.MethodPost, path, input.toAPI())
|
||||
if respReader != nil {
|
||||
defer respReader.Close()
|
||||
}
|
||||
|
@ -501,7 +502,8 @@ type ListNICsInput struct {
|
|||
}
|
||||
|
||||
func (client *MachinesClient) ListNICs(input *ListNICsInput) ([]*NIC, error) {
|
||||
respReader, err := client.executeRequest(http.MethodGet, fmt.Sprintf("/my/machines/%s/nics", input.MachineID), nil)
|
||||
path := fmt.Sprintf("/%s/machines/%s/nics", client.accountName, input.MachineID)
|
||||
respReader, err := client.executeRequest(http.MethodGet, path, nil)
|
||||
if respReader != nil {
|
||||
defer respReader.Close()
|
||||
}
|
||||
|
@ -560,6 +562,48 @@ func (client *MachinesClient) RemoveNIC(input *RemoveNICInput) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type StopMachineInput struct {
|
||||
MachineID string
|
||||
}
|
||||
|
||||
func (client *MachinesClient) StopMachine(input *StopMachineInput) error {
|
||||
path := fmt.Sprintf("/%s/machines/%s", client.accountName, input.MachineID)
|
||||
|
||||
params := &url.Values{}
|
||||
params.Set("action", "stop")
|
||||
|
||||
respReader, err := client.executeRequestURIParams(http.MethodPost, path, nil, params)
|
||||
if respReader != nil {
|
||||
defer respReader.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("Error executing StopMachine request: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type StartMachineInput struct {
|
||||
MachineID string
|
||||
}
|
||||
|
||||
func (client *MachinesClient) StartMachine(input *StartMachineInput) error {
|
||||
path := fmt.Sprintf("/%s/machines/%s", client.accountName, input.MachineID)
|
||||
|
||||
params := &url.Values{}
|
||||
params.Set("action", "start")
|
||||
|
||||
respReader, err := client.executeRequestURIParams(http.MethodPost, path, nil, params)
|
||||
if respReader != nil {
|
||||
defer respReader.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("Error executing StartMachine request: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var reservedMachineCNSTags = map[string]struct{}{
|
||||
machineCNSTagDisable: {},
|
||||
machineCNSTagReversePTR: {},
|
||||
|
|
|
@ -36,7 +36,8 @@ type Network struct {
|
|||
type ListNetworksInput struct{}
|
||||
|
||||
func (client *NetworksClient) ListNetworks(*ListNetworksInput) ([]*Network, error) {
|
||||
respReader, err := client.executeRequest(http.MethodGet, "/my/networks", nil)
|
||||
path := fmt.Sprintf("/%s/networks", client.accountName)
|
||||
respReader, err := client.executeRequest(http.MethodGet, path, nil)
|
||||
if respReader != nil {
|
||||
defer respReader.Close()
|
||||
}
|
||||
|
|
|
@ -2341,16 +2341,16 @@
|
|||
"revisionTime": "2016-06-16T18:50:15Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "2HimxaJVVp2QDVQ0570L71Zd5s4=",
|
||||
"checksumSHA1": "XsjyaC6eTHUy/n0iuR46TZcgAK8=",
|
||||
"path": "github.com/joyent/triton-go",
|
||||
"revision": "5db9e2b6a4c1f7ffd2a7e7aa625f42dba956608c",
|
||||
"revisionTime": "2017-04-12T23:23:58Z"
|
||||
"revision": "c73729fd38522591909a371c8180ca7090a59ab9",
|
||||
"revisionTime": "2017-04-28T18:47:44Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "QzUqkCSn/ZHyIK346xb9V6EBw9U=",
|
||||
"path": "github.com/joyent/triton-go/authentication",
|
||||
"revision": "66b31a94af28a65e902423879a2820ea34b773fb",
|
||||
"revisionTime": "2017-03-31T18:12:29Z"
|
||||
"revision": "c73729fd38522591909a371c8180ca7090a59ab9",
|
||||
"revisionTime": "2017-04-28T18:47:44Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "YhQcOsGx8r2S/jkJ0Qt4cZ5BLCU=",
|
||||
|
|
|
@ -9,7 +9,7 @@ description: |-
|
|||
# Command: get
|
||||
|
||||
The `terraform get` command is used to download and update
|
||||
[modules](/docs/modules/index.html).
|
||||
[modules](/docs/modules/index.html) mentioned in the root module.
|
||||
|
||||
## Usage
|
||||
|
||||
|
@ -28,3 +28,4 @@ The command-line flags are all optional. The list of available flags are:
|
|||
|
||||
* `-update` - If specified, modules that are already downloaded will be
|
||||
checked for updates and the updates will be downloaded if present.
|
||||
* `dir` - Sets the path of the [root module](/docs/modules/index.html#definitions).
|
||||
|
|
|
@ -8,7 +8,7 @@ description: |-
|
|||
|
||||
# Collaborating on Terraform Remote State
|
||||
|
||||
Terraform Enterprise is one of a few options to store [remote state](/docs/enterprise/state).
|
||||
Terraform Enterprise is one of a few options to store [remote state](/docs/state/remote.html).
|
||||
|
||||
Remote state gives you the ability to version and collaborate on Terraform
|
||||
changes. It stores information about the changes Terraform makes based on
|
||||
|
@ -18,6 +18,5 @@ In order to collaborate safely on remote state, we recommend
|
|||
[creating an organization](/docs/enterprise/organizations/create.html) to
|
||||
manage teams of users.
|
||||
|
||||
Then, following a [remote state push](/docs/enterprise/state) you can view state
|
||||
versions in the changes tab of the environment created under the same name as
|
||||
the remote state.
|
||||
Then, following a [Terraform Enterprise Run](/docs/enterprise/runs) or [`apply`](/docs/commands/apply.html)
|
||||
you can view state versions in the `States` list of the environment.
|
||||
|
|
|
@ -8,17 +8,17 @@ description: |-
|
|||
|
||||
# State
|
||||
|
||||
Terraform stores the state of your managed infrastructure from the last time
|
||||
Terraform was run. By default this state is stored in a local file named
|
||||
`terraform.tfstate`, but it can also be stored remotely, which works better in a
|
||||
team environment.
|
||||
|
||||
Terraform Enterprise is a remote state provider, allowing you to store, version
|
||||
and collaborate on states.
|
||||
Terraform Enterprise stores the state of your managed infrastructure from the
|
||||
last time Terraform was run. The state is stored remotely, which works better in a
|
||||
team environment, allowing you to store, version and collaborate on state.
|
||||
|
||||
Remote state gives you more than just easier version control and safer storage.
|
||||
It also allows you to delegate the outputs to other teams. This allows your
|
||||
infrastructure to be more easily broken down into components that multiple teams
|
||||
can access.
|
||||
|
||||
Read [more about remote state](https://www.terraform.io/docs/state/remote.html).
|
||||
Remote state is automatically updated when you run [`apply`](/docs/commands/apply.html)
|
||||
locally. It is also updated when an `apply` is executed in a [Terraform Enterprise
|
||||
Run](/docs/enterprise/runs/index.html).
|
||||
|
||||
Read [more about remote state](/docs/state/remote.html).
|
||||
|
|
|
@ -17,7 +17,9 @@ configuration.
|
|||
To use Terraform Enterprise to store remote state, you'll first need to have the
|
||||
`ATLAS_TOKEN` environment variable set and run the following command.
|
||||
|
||||
**NOTE:** `terraform remote config` command has been deprecated in 0.9.X. Remote configuration is now managed as a [backend configuration](/docs/backends/config.html).
|
||||
|
||||
```shell
|
||||
$ terraform remote config \
|
||||
-backend-config="name=$USERNAME/product"
|
||||
```
|
||||
```
|
|
@ -35,36 +35,22 @@ operation.
|
|||
|
||||
### Using Terraform Locally
|
||||
|
||||
Another way to resolve remote state conflicts is to merge and conflicted copies
|
||||
locally by inspecting the raw state available in the path
|
||||
`.terraform/terraform.tfstate`.
|
||||
Another way to resolve remote state conflicts is by manual intervention of the
|
||||
state file.
|
||||
|
||||
When making state changes, it's important to make backup copies in order to
|
||||
avoid losing any data.
|
||||
Use the [`state pull`](/docs/commands/state/pull.html) subcommand to pull the
|
||||
remote state into a local state file.
|
||||
|
||||
Any state that is pushed with a serial that is lower than the known serial when
|
||||
the MD5 of the state does not match will be rejected.
|
||||
|
||||
The serial is embedded in the state file:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"serial": 555,
|
||||
"remote": {
|
||||
"type": "atlas",
|
||||
"config": {
|
||||
"name": "my-username/production"
|
||||
}
|
||||
}
|
||||
}
|
||||
```shell
|
||||
$ terraform state pull > example.tfstate
|
||||
```
|
||||
|
||||
Once a conflict has been resolved locally by editing the state file, the serial
|
||||
can be incremented past the current version and pushed:
|
||||
can be incremented past the current version and pushed with the
|
||||
[`state push`](/docs/commands/state/push.html) subcommand:
|
||||
|
||||
```shell
|
||||
$ terraform remote push
|
||||
$ terraform state push example.tfstate
|
||||
```
|
||||
|
||||
This will upload the manually resolved state and set it as the head version.
|
||||
|
|
|
@ -15,3 +15,8 @@ in Terraform as well as for basic code organization.
|
|||
Modules are very easy to both use and create. Depending on what you're
|
||||
looking to do first, use the navigation on the left to dive into how
|
||||
modules work.
|
||||
|
||||
## Definitions
|
||||
**Root module**
|
||||
That is the current working directory when you run [`terraform apply`](/docs/commands/apply.html) or [`get`](/docs/commands/get.html), holding the Terraform [configuration files](/docs/configuration/index.html).
|
||||
It is itself a valid module.
|
||||
|
|
|
@ -44,7 +44,6 @@ resource "azurerm_container_service" "test" {
|
|||
name = "default"
|
||||
count = 1
|
||||
dns_prefix = "acctestagent1"
|
||||
fqdn = "you.demo.com"
|
||||
vm_size = "Standard_A0"
|
||||
}
|
||||
|
||||
|
@ -89,7 +88,6 @@ resource "azurerm_container_service" "test" {
|
|||
name = "default"
|
||||
count = 1
|
||||
dns_prefix = "acctestagent1"
|
||||
fqdn = "you.demo.com"
|
||||
vm_size = "Standard_A0"
|
||||
}
|
||||
|
||||
|
@ -139,7 +137,6 @@ resource "azurerm_container_service" "test" {
|
|||
name = "default"
|
||||
count = 1
|
||||
dns_prefix = "acctestagent1"
|
||||
fqdn = "you.demo.com"
|
||||
vm_size = "Standard_A0"
|
||||
}
|
||||
|
||||
|
|
|
@ -60,6 +60,7 @@ The following arguments are supported:
|
|||
The following attributes are exported:
|
||||
|
||||
* `id` - The LoadBalancer ID.
|
||||
* `private_ip_address` - The private IP address assigned to the load balancer, if any.
|
||||
|
||||
## Import
|
||||
|
||||
|
|
|
@ -80,7 +80,9 @@ The following arguments are supported:
|
|||
* `description` - (Optional) Textual description field.
|
||||
|
||||
* `ip_address` - (Optional) The static IP. (if not set, an ephemeral IP is
|
||||
used).
|
||||
used). This should be the literal IP address to be used, not the `self_link`
|
||||
to a `google_compute_address` resource. (If using a `google_compute_address`
|
||||
resource, use the `address` property instead of the `self_link` property.)
|
||||
|
||||
* `ip_protocol` - (Optional) The IP protocol to route, one of "TCP" "UDP" "AH"
|
||||
"ESP" or "SCTP". (default "TCP").
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
layout: "heroku"
|
||||
page_title: "Heroku: heroku_app"
|
||||
sidebar_current: "docs-heroku-resource-app"
|
||||
sidebar_current: "docs-heroku-resource-app-x"
|
||||
description: |-
|
||||
Provides a Heroku App resource. This can be used to create and manage applications on Heroku.
|
||||
---
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
layout: "heroku"
|
||||
page_title: "Heroku: heroku_app_feature"
|
||||
sidebar_current: "docs-heroku-resource-app-feature"
|
||||
description: |-
|
||||
Provides a Heroku App Feature resource. This can be used to create and manage App Features on Heroku.
|
||||
---
|
||||
|
||||
# heroku\_app\_feature
|
||||
|
||||
Provides a Heroku App Feature resource. This can be used to create and manage App Features on Heroku.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```hcl
|
||||
resource "heroku_app_feature" "log_runtime_metrics" {
|
||||
app = "test-app"
|
||||
name = "log-runtime-metrics"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `app` - (Required) The Heroku app to link to.
|
||||
* `name` - (Required) The name of the App Feature to manage.
|
||||
* `enabled` - (Optional) Whether to enable or disable the App Feature. The default value is true.
|
|
@ -34,3 +34,6 @@ The following arguments are supported:
|
|||
|
||||
* `address` - (Optional) The HTTP(S) API address of the Nomad agent to use. Defaults to `http://127.0.0.1:4646`. The `NOMAD_ADDR` environment variable can also be used.
|
||||
* `region` - (Optional) The Nomad region to target. The `NOMAD_REGION` environment variable can also be used.
|
||||
* `ca_file` - (Optional) A path to a PEM-encoded certificate authority used to verify the remote agent's certificate. The `NOMAD_CACERT` environment variable can also be used.
|
||||
* `cert_file` - (Optional) A path to a PEM-encoded certificate provided to the remote agent; requires use of `key_file`. The `NOMAD_CLIENT_CERT` environment variable can also be used.
|
||||
* `key_file`- (Optional) A path to a PEM-encoded private key, required if `cert_file` is specified. The `NOMAD_CLIENT_KEY` environment variable can also be used.
|
||||
|
|
|
@ -33,3 +33,4 @@ The following arguments are supported in the `provider` block:
|
|||
* `key_material` - (Optional) This is the private key of an SSH key associated with the Triton account to be used. If this is not set, the private key corresponding to the fingerprint in `key_id` must be available via an SSH Agent.
|
||||
* `key_id` - (Required) This is the fingerprint of the public key matching the key specified in `key_path`. It can be obtained via the command `ssh-keygen -l -E md5 -f /path/to/key`
|
||||
* `url` - (Optional) This is the URL to the Triton API endpoint. It is required if using a private installation of Triton. The default is to use the Joyent public cloud us-west-1 endpoint. Valid public cloud endpoints include: `us-east-1`, `us-east-2`, `us-east-3`, `us-sw-1`, `us-west-1`, `eu-ams-1`
|
||||
* `insecure_skip_tls_verify` (Optional - defaults to false) This allows skipping TLS verification of the Triton endpoint. It is useful when connecting to a temporary Triton installation such as Cloud-On-A-Laptop which does not generally use a certificate signed by a trusted root CA.
|
||||
|
|
|
@ -77,6 +77,9 @@ The following arguments are supported:
|
|||
* `administrator_pw` - (string)
|
||||
The initial password for the Administrator user. Only used for Windows virtual machines.
|
||||
|
||||
* `cloud_config` - (string)
|
||||
Cloud-init configuration for Linux brand machines, used instead of `user_data`.
|
||||
|
||||
The nested `nic` block supports the following:
|
||||
* `network` - (string, Optional)
|
||||
The network id to attach to the network interface. It will be hex, in the format: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`.
|
||||
|
|
|
@ -10,6 +10,15 @@
|
|||
<a href="/docs/providers/do/index.html">DigitalOcean Provider</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-do-datasource") %>>
|
||||
<a href="#">Data Sources</a>
|
||||
<ul class="nav nav-visible">
|
||||
<li<%= sidebar_current("docs-do-datasource-image") %>>
|
||||
<a href="/docs/providers/do/d/image.html">digitalocean_image</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-do-resource") %>>
|
||||
<a href="#">Resources</a>
|
||||
<ul class="nav nav-visible">
|
||||
|
|
|
@ -17,10 +17,14 @@
|
|||
<a href="/docs/providers/heroku/r/addon.html">heroku_addon</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-heroku-resource-app") %>>
|
||||
<li<%= sidebar_current("docs-heroku-resource-app-x") %>>
|
||||
<a href="/docs/providers/heroku/r/app.html">heroku_app</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-heroku-resource-app-feature") %>>
|
||||
<a href="/docs/providers/heroku/r/app_feature.html">heroku_app_feature</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-heroku-resource-cert") %>>
|
||||
<a href="/docs/providers/heroku/r/cert.html">heroku_cert</a>
|
||||
</li>
|
||||
|
|
Loading…
Reference in New Issue