Merge branch 'master' into bs-heroku-app-space

This commit is contained in:
Bernerd Schaefer 2017-04-25 11:04:13 -07:00 committed by GitHub
commit c55a4b3a51
169 changed files with 16461 additions and 343 deletions

View File

@ -1,10 +1,21 @@
## 0.9.4 (Unreleased)
BACKWARDS INCOMPATIBILITIES / NOTES:
* provider/template: Fix invalid MIME formatting in `template_cloudinit_config`.
While the change itself is not breaking the data source it may be referenced
e.g. in `aws_launch_configuration` and similar resources which are immutable
and the formatting change will therefore trigger recreation [GH-13752]
FEATURES:
* **New Provider:** `opc` - Oracle Public Cloud [GH-13468]
* **New Provider:** `oneandone` [GH-13633]
* **New Data Source:** `aws_ami_ids` [GH-13844]
* **New Data Source:** `aws_ebs_snapshot_ids` [GH-13844]
* **New Data Source:** `aws_kms_alias` [GH-13669]
* **New Data Source:** `aws_kinesis_stream` [GH-13562]
* **New Data Source:** `digitalocean_image` [GH-13787]
* **New Data Source:** `google_compute_network` [GH-12442]
* **New Data Source:** `google_compute_subnetwork` [GH-12442]
* **New Resource:** `local_file` for creating local files (please see the docs for caveats) [GH-12757]
@ -14,46 +25,77 @@ FEATURES:
* **New Resource:** `alicloud_ess_schedule` [GH-13731]
* **New Resource:** `alicloud_snat_entry` [GH-13731]
* **New Resource:** `alicloud_forward_entry` [GH-13731]
* **New Resource:** `aws_cognito_identity_pool` [GH-13783]
* **New Resource:**  `aws_network_interface_attachment` [GH-13861]
* **New Resource:** `github_branch_protection` [GH-10476]
* **New Resource:** `google_bigquery_dataset` [GH-13436]
* **New Interpolation Function:** `coalescelist()` [GH-12537]
IMPROVEMENTS:
* core: Add a `-reconfigure` flag to the `init` command, to configure a backend while ignoring any saved configuration [GH-13825]
* helper/schema: Disallow validation+diff suppression on computed fields [GH-13878]
* config: The interpolation function `cidrhost` now accepts a negative host number to count backwards from the end of the range [GH-13765]
* config: New interpolation function `matchkeys` for using values from one list to filter corresponding values from another list using a matching set. [GH-13847]
* state/remote/swift: Support Openstack request logging [GH-13583]
* provider/aws: Add an option to skip getting the supported EC2 platforms [GH-13672]
* provider/aws: Add `name_prefix` support to `aws_cloudwatch_log_group` [GH-13273]
* provider/aws: Add `bucket_prefix` to `aws_s3_bucket` [GH-13274]
* provider/aws: Add replica_source_db to the aws_db_instance datasource [GH-13842]
* provider/aws: Add IPv6 outputs to aws_subnet datasource [GH-13841]
* provider/aws: Exercise SecondaryPrivateIpAddressCount for network interface [GH-10590]
* provider/aws: Expose execution ARN + invoke URL for APIG deployment [GH-13889]
* provider/aws: Expose invoke ARN from Lambda function (for API Gateway) [GH-13890]
* provider/aws: Add tagging support to the 'aws_lambda_function' resource [GH-13873]
* provider/aws: Validate WAF metric names [GH-13885]
* provider/aws: Allow AWS Subnet to change IPv6 CIDR Block without ForceNew [GH-13909]
* provider/azurerm: VM Scale Sets - import support [GH-13464]
* provider/azurerm: Allow Azure China region support [GH-13767]
* provider/digitalocean: Export droplet prices [GH-13720]
* provider/fastly: Add support for GCS logging [GH-13553]
* provider/google: `google_compute_address` and `google_compute_global_address` are now importable [GH-13270]
* provider/google: `google_compute_network` is now importable [GH-13834]
* provider/heroku: Set App buildpacks from config [GH-13910]
* provider/vault: `vault_generic_secret` resource can now optionally detect drift if it has appropriate access [GH-11776]
BUG FIXES:
* core: Prevent resource.Retry from adding untracked resources after the timeout: [GH-13778]
* core: Allow a schema.TypeList to be ForceNew and computed [GH-13863]
* core: Fix crash when refresh or apply build an invalid graph [GH-13665]
* core: Add the close provider/provisioner transformers back [GH-13102]
* core: Fix a crash condition by improving the flatmap.Expand() logic [GH-13541]
* provider/alicloud: Fix create PrePaid instance [GH-13662]
* provider/alicloud: Fix allocate public ip error [GH-13268]
* provider/alicloud: alicloud_security_group_rule: check ptr before use it [GH-13731)
* provider/alicloud: alicloud_instance: fix ecs internet_max_bandwidth_out cannot set zero bug [GH-13731]
* provider/aws: Allow force-destroying `aws_route53_zone` which has trailing dot [GH-12421]
* provider/aws: Allow GovCloud KMS ARNs to pass validation in `kms_key_id` attributes [GH-13699]
* provider/aws: Changing aws_opsworks_instance should ForceNew [GH-13839]
* provider/aws: Fix DB Parameter Group Name [GH-13279]
* provider/aws: Fix issue importing some Security Groups and Rules based on rule structure [GH-13630]
* provider/aws: Fix issue for cross account IAM role with `aws_lambda_permission` [GH-13865]
* provider/aws: Fix WAF IPSet descriptors removal on update [GH-13766]
* provider/aws: Increase default number of retries from 11 to 25 [GH-13673]
* provider/aws: Use mutex & retry for WAF change operations [GH-13656]
* provider/aws: Remove aws_vpc_dhcp_options if not found [GH-13610]
* provider/aws: Remove aws_network_acl_rule if not found [GH-13608]
* provider/aws: Allow GovCloud KMS ARNs to pass validation in `kms_key_id` attributes [GH-13699]
* provider/aws: Use mutex & retry for WAF change operations [GH-13656]
* provider/aws: Adding support for ipv6 to aws_subnets needs migration [GH-13876]
* provider/azurerm: azurerm_redis_cache resource missing hostname [GH-13650]
* provider/azurerm: Locking around Network Security Group / Subnets [GH-13637]
* provider/azurerm: Locking route table on subnet create/delete [GH-13791]
* provider/azurerm: VM's - fixes a bug where ssh_keys could contain a null entry [GH-13755]
* provider/azurerm: VM's - ignoring the case on the `create_option` field during Diff's [GH-13933]
* provider/azurerm: fixing a bug refreshing the `azurerm_redis_cache` [GH-13899]
* provider/fastly: Fix issue with using 0 for `default_ttl` [GH-13648]
* provider/fastly: Add ability to associate a healthcheck to a backend [GH-13539]
* provider/google: Stop setting the id when project creation fails [GH-13644]
* provider/google: Make ports in resource_compute_forwarding_rule ForceNew [GH-13833]
* provider/logentries: Refresh from state when resources not found [GH-13810]
* provider/newrelic: newrelic_alert_condition - `condition_scope` must be `application` or `instance` [GH-12972]
* provider/opc: fixed issue with unqualifying nats [GH-13826]
* provider/opc: Fix instance label if unset [GH-13846]
* provider/openstack: Fix updating Ports [GH-13604]
* provider/rabbitmq: Allow users without tags [GH-13798]

View File

@ -170,9 +170,30 @@ func (b *Local) DeleteState(name string) error {
}
func (b *Local) State(name string) (state.State, error) {
statePath, stateOutPath, backupPath := b.StatePaths(name)
// If we have a backend handling state, defer to that.
if b.Backend != nil {
return b.Backend.State(name)
s, err := b.Backend.State(name)
if err != nil {
return nil, err
}
// make sure we always have a backup state, unless it disabled
if backupPath == "" {
return s, nil
}
// see if the delegated backend returned a BackupState of its own
if s, ok := s.(*state.BackupState); ok {
return s, nil
}
s = &state.BackupState{
Real: s,
Path: backupPath,
}
return s, nil
}
if s, ok := b.states[name]; ok {
@ -183,8 +204,6 @@ func (b *Local) State(name string) (state.State, error) {
return nil, err
}
statePath, stateOutPath, backupPath := b.StatePaths(name)
// Otherwise, we need to load the state.
var s state.State = &state.LocalState{
Path: statePath,

View File

@ -169,6 +169,11 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
// verify it's being called.
type testDelegateBackend struct {
*Local
// return a sentinel error on these calls
stateErr bool
statesErr bool
deleteErr bool
}
var errTestDelegateState = errors.New("State called")
@ -176,22 +181,39 @@ var errTestDelegateStates = errors.New("States called")
var errTestDelegateDeleteState = errors.New("Delete called")
func (b *testDelegateBackend) State(name string) (state.State, error) {
return nil, errTestDelegateState
if b.stateErr {
return nil, errTestDelegateState
}
s := &state.LocalState{
Path: "terraform.tfstate",
PathOut: "terraform.tfstate",
}
return s, nil
}
func (b *testDelegateBackend) States() ([]string, error) {
return nil, errTestDelegateStates
if b.statesErr {
return nil, errTestDelegateStates
}
return []string{"default"}, nil
}
func (b *testDelegateBackend) DeleteState(name string) error {
return errTestDelegateDeleteState
if b.deleteErr {
return errTestDelegateDeleteState
}
return nil
}
// verify that the MultiState methods are dispatched to the correct Backend.
func TestLocal_multiStateBackend(t *testing.T) {
// assign a separate backend where we can read the state
b := &Local{
Backend: &testDelegateBackend{},
Backend: &testDelegateBackend{
stateErr: true,
statesErr: true,
deleteErr: true,
},
}
if _, err := b.State("test"); err != errTestDelegateState {
@ -205,7 +227,43 @@ func TestLocal_multiStateBackend(t *testing.T) {
if err := b.DeleteState("test"); err != errTestDelegateDeleteState {
t.Fatal("expected errTestDelegateDeleteState, got:", err)
}
}
// verify that a remote state backend is always wrapped in a BackupState
func TestLocal_remoteStateBackup(t *testing.T) {
// assign a separate backend to mock a remote state backend
b := &Local{
Backend: &testDelegateBackend{},
}
s, err := b.State("default")
if err != nil {
t.Fatal(err)
}
bs, ok := s.(*state.BackupState)
if !ok {
t.Fatal("remote state is not backed up")
}
if bs.Path != DefaultStateFilename+DefaultBackupExtension {
t.Fatal("bad backup location:", bs.Path)
}
// do the same with a named state, which should use the local env directories
s, err = b.State("test")
if err != nil {
t.Fatal(err)
}
bs, ok = s.(*state.BackupState)
if !ok {
t.Fatal("remote state is not backed up")
}
if bs.Path != filepath.Join(DefaultEnvDir, "test", DefaultStateFilename+DefaultBackupExtension) {
t.Fatal("bad backup location:", bs.Path)
}
}
// change into a tmp dir and return a deferable func to change back and cleanup

View File

@ -1,12 +1,12 @@
package main
import (
"github.com/hashicorp/terraform/builtin/providers/localfile"
"github.com/hashicorp/terraform/builtin/providers/local"
"github.com/hashicorp/terraform/plugin"
)
func main() {
plugin.Serve(&plugin.ServeOpts{
ProviderFunc: localfile.Provider,
ProviderFunc: local.Provider,
})
}

View File

@ -28,6 +28,7 @@ import (
"github.com/aws/aws-sdk-go/service/codecommit"
"github.com/aws/aws-sdk-go/service/codedeploy"
"github.com/aws/aws-sdk-go/service/codepipeline"
"github.com/aws/aws-sdk-go/service/cognitoidentity"
"github.com/aws/aws-sdk-go/service/configservice"
"github.com/aws/aws-sdk-go/service/databasemigrationservice"
"github.com/aws/aws-sdk-go/service/directoryservice"
@ -111,6 +112,7 @@ type AWSClient struct {
cloudwatchconn *cloudwatch.CloudWatch
cloudwatchlogsconn *cloudwatchlogs.CloudWatchLogs
cloudwatcheventsconn *cloudwatchevents.CloudWatchEvents
cognitoconn *cognitoidentity.CognitoIdentity
configconn *configservice.ConfigService
dmsconn *databasemigrationservice.DatabaseMigrationService
dsconn *directoryservice.DirectoryService
@ -306,6 +308,7 @@ func (c *Config) Client() (interface{}, error) {
client.codebuildconn = codebuild.New(sess)
client.codedeployconn = codedeploy.New(sess)
client.configconn = configservice.New(sess)
client.cognitoconn = cognitoidentity.New(sess)
client.dmsconn = databasemigrationservice.New(sess)
client.codepipelineconn = codepipeline.New(sess)
client.dsconn = directoryservice.New(sess)

View File

@ -0,0 +1,112 @@
package aws
import (
"fmt"
"log"
"regexp"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema"
)
func dataSourceAwsAmiIds() *schema.Resource {
return &schema.Resource{
Read: dataSourceAwsAmiIdsRead,
Schema: map[string]*schema.Schema{
"filter": dataSourceFiltersSchema(),
"executable_users": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"name_regex": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validateNameRegex,
},
"owners": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"tags": dataSourceTagsSchema(),
"ids": &schema.Schema{
Type: schema.TypeSet,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
},
}
}
func dataSourceAwsAmiIdsRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ec2conn
executableUsers, executableUsersOk := d.GetOk("executable_users")
filters, filtersOk := d.GetOk("filter")
nameRegex, nameRegexOk := d.GetOk("name_regex")
owners, ownersOk := d.GetOk("owners")
if executableUsersOk == false && filtersOk == false && nameRegexOk == false && ownersOk == false {
return fmt.Errorf("One of executable_users, filters, name_regex, or owners must be assigned")
}
params := &ec2.DescribeImagesInput{}
if executableUsersOk {
params.ExecutableUsers = expandStringList(executableUsers.([]interface{}))
}
if filtersOk {
params.Filters = buildAwsDataSourceFilters(filters.(*schema.Set))
}
if ownersOk {
o := expandStringList(owners.([]interface{}))
if len(o) > 0 {
params.Owners = o
}
}
resp, err := conn.DescribeImages(params)
if err != nil {
return err
}
var filteredImages []*ec2.Image
imageIds := make([]string, 0)
if nameRegexOk {
r := regexp.MustCompile(nameRegex.(string))
for _, image := range resp.Images {
// Check for a very rare case where the response would include no
// image name. No name means nothing to attempt a match against,
// therefore we are skipping such image.
if image.Name == nil || *image.Name == "" {
log.Printf("[WARN] Unable to find AMI name to match against "+
"for image ID %q owned by %q, nothing to do.",
*image.ImageId, *image.OwnerId)
continue
}
if r.MatchString(*image.Name) {
filteredImages = append(filteredImages, image)
}
}
} else {
filteredImages = resp.Images[:]
}
for _, image := range filteredImages {
imageIds = append(imageIds, *image.ImageId)
}
d.SetId(fmt.Sprintf("%d", hashcode.String(params.String())))
d.Set("ids", imageIds)
return nil
}

View File

@ -0,0 +1,58 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccDataSourceAwsAmiIds_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccDataSourceAwsAmiIdsConfig_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsAmiDataSourceID("data.aws_ami_ids.ubuntu"),
),
},
},
})
}
func TestAccDataSourceAwsAmiIds_empty(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccDataSourceAwsAmiIdsConfig_empty,
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsAmiDataSourceID("data.aws_ami_ids.empty"),
resource.TestCheckResourceAttr("data.aws_ami_ids.empty", "ids.#", "0"),
),
},
},
})
}
const testAccDataSourceAwsAmiIdsConfig_basic = `
data "aws_ami_ids" "ubuntu" {
owners = ["099720109477"]
filter {
name = "name"
values = ["ubuntu/images/ubuntu-*-*-amd64-server-*"]
}
}
`
const testAccDataSourceAwsAmiIdsConfig_empty = `
data "aws_ami_ids" "empty" {
filter {
name = "name"
values = []
}
}
`

View File

@ -188,6 +188,11 @@ func dataSourceAwsDbInstance() *schema.Resource {
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"replicate_source_db": {
Type: schema.TypeString,
Computed: true,
},
},
}
}
@ -271,6 +276,7 @@ func dataSourceAwsDbInstanceRead(d *schema.ResourceData, meta interface{}) error
d.Set("storage_encrypted", dbInstance.StorageEncrypted)
d.Set("storage_type", dbInstance.StorageType)
d.Set("timezone", dbInstance.Timezone)
d.Set("replicate_source_db", dbInstance.ReadReplicaSourceDBInstanceIdentifier)
var vpcSecurityGroups []string
for _, v := range dbInstance.VpcSecurityGroups {

View File

@ -0,0 +1,78 @@
package aws
import (
"fmt"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema"
)
func dataSourceAwsEbsSnapshotIds() *schema.Resource {
return &schema.Resource{
Read: dataSourceAwsEbsSnapshotIdsRead,
Schema: map[string]*schema.Schema{
"filter": dataSourceFiltersSchema(),
"owners": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"restorable_by_user_ids": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"tags": dataSourceTagsSchema(),
"ids": &schema.Schema{
Type: schema.TypeSet,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
},
}
}
func dataSourceAwsEbsSnapshotIdsRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ec2conn
restorableUsers, restorableUsersOk := d.GetOk("restorable_by_user_ids")
filters, filtersOk := d.GetOk("filter")
owners, ownersOk := d.GetOk("owners")
if restorableUsers == false && filtersOk == false && ownersOk == false {
return fmt.Errorf("One of filters, restorable_by_user_ids, or owners must be assigned")
}
params := &ec2.DescribeSnapshotsInput{}
if restorableUsersOk {
params.RestorableByUserIds = expandStringList(restorableUsers.([]interface{}))
}
if filtersOk {
params.Filters = buildAwsDataSourceFilters(filters.(*schema.Set))
}
if ownersOk {
params.OwnerIds = expandStringList(owners.([]interface{}))
}
resp, err := conn.DescribeSnapshots(params)
if err != nil {
return err
}
snapshotIds := make([]string, 0)
for _, snapshot := range resp.Snapshots {
snapshotIds = append(snapshotIds, *snapshot.SnapshotId)
}
d.SetId(fmt.Sprintf("%d", hashcode.String(params.String())))
d.Set("ids", snapshotIds)
return nil
}

View File

@ -0,0 +1,59 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccDataSourceAwsEbsSnapshotIds_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccDataSourceAwsEbsSnapshotIdsConfig_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsEbsSnapshotDataSourceID("data.aws_ebs_snapshot_ids.test"),
),
},
},
})
}
func TestAccDataSourceAwsEbsSnapshotIds_empty(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccDataSourceAwsEbsSnapshotIdsConfig_empty,
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsEbsSnapshotDataSourceID("data.aws_ebs_snapshot_ids.empty"),
resource.TestCheckResourceAttr("data.aws_ebs_snapshot_ids.empty", "ids.#", "0"),
),
},
},
})
}
const testAccDataSourceAwsEbsSnapshotIdsConfig_basic = `
resource "aws_ebs_volume" "test" {
availability_zone = "us-west-2a"
size = 40
}
resource "aws_ebs_snapshot" "test" {
volume_id = "${aws_ebs_volume.test.id}"
}
data "aws_ebs_snapshot_ids" "test" {
owners = ["self"]
}
`
const testAccDataSourceAwsEbsSnapshotIdsConfig_empty = `
data "aws_ebs_snapshot_ids" "empty" {
owners = ["000000000000"]
}
`

View File

@ -44,7 +44,7 @@ func testAccCheckAwsEbsSnapshotDataSourceID(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Can't find Volume data source: %s", n)
return fmt.Errorf("Can't find snapshot data source: %s", n)
}
if rs.Primary.ID == "" {

View File

@ -14,19 +14,25 @@ func dataSourceAwsSubnet() *schema.Resource {
Read: dataSourceAwsSubnetRead,
Schema: map[string]*schema.Schema{
"availability_zone": &schema.Schema{
"availability_zone": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"cidr_block": &schema.Schema{
"cidr_block": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"default_for_az": &schema.Schema{
"ipv6_cidr_block": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"default_for_az": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
@ -34,13 +40,13 @@ func dataSourceAwsSubnet() *schema.Resource {
"filter": ec2CustomFiltersSchema(),
"id": &schema.Schema{
"id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"state": &schema.Schema{
"state": {
Type: schema.TypeString,
Optional: true,
Computed: true,
@ -48,11 +54,26 @@ func dataSourceAwsSubnet() *schema.Resource {
"tags": tagsSchemaComputed(),
"vpc_id": &schema.Schema{
"vpc_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"assign_ipv6_address_on_creation": {
Type: schema.TypeBool,
Computed: true,
},
"map_public_ip_on_launch": {
Type: schema.TypeBool,
Computed: true,
},
"ipv6_cidr_block_association_id": {
Type: schema.TypeString,
Computed: true,
},
},
}
}
@ -76,15 +97,22 @@ func dataSourceAwsSubnetRead(d *schema.ResourceData, meta interface{}) error {
defaultForAzStr = "true"
}
req.Filters = buildEC2AttributeFilterList(
map[string]string{
"availabilityZone": d.Get("availability_zone").(string),
"cidrBlock": d.Get("cidr_block").(string),
"defaultForAz": defaultForAzStr,
"state": d.Get("state").(string),
"vpc-id": d.Get("vpc_id").(string),
},
)
filters := map[string]string{
"availabilityZone": d.Get("availability_zone").(string),
"defaultForAz": defaultForAzStr,
"state": d.Get("state").(string),
"vpc-id": d.Get("vpc_id").(string),
}
if v, ok := d.GetOk("cidr_block"); ok {
filters["cidrBlock"] = v.(string)
}
if v, ok := d.GetOk("ipv6_cidr_block"); ok {
filters["ipv6-cidr-block-association.ipv6-cidr-block"] = v.(string)
}
req.Filters = buildEC2AttributeFilterList(filters)
req.Filters = append(req.Filters, buildEC2TagFilterList(
tagsFromMap(d.Get("tags").(map[string]interface{})),
)...)
@ -118,6 +146,15 @@ func dataSourceAwsSubnetRead(d *schema.ResourceData, meta interface{}) error {
d.Set("default_for_az", subnet.DefaultForAz)
d.Set("state", subnet.State)
d.Set("tags", tagsToMap(subnet.Tags))
d.Set("assign_ipv6_address_on_creation", subnet.AssignIpv6AddressOnCreation)
d.Set("map_public_ip_on_launch", subnet.MapPublicIpOnLaunch)
for _, a := range subnet.Ipv6CidrBlockAssociationSet {
if *a.Ipv6CidrBlockState.State == "associated" { //we can only ever have 1 IPv6 block associated at once
d.Set("ipv6_cidr_block_association_id", a.AssociationId)
d.Set("ipv6_cidr_block", a.Ipv6CidrBlock)
}
}
return nil
}

View File

@ -9,7 +9,7 @@ import (
"github.com/hashicorp/terraform/terraform"
)
func TestAccDataSourceAwsSubnet_basic(t *testing.T) {
func TestAccDataSourceAwsSubnet(t *testing.T) {
rInt := acctest.RandIntRange(0, 256)
resource.Test(t, resource.TestCase{
@ -17,7 +17,7 @@ func TestAccDataSourceAwsSubnet_basic(t *testing.T) {
Providers: testAccProviders,
CheckDestroy: testAccCheckVpcDestroy,
Steps: []resource.TestStep{
resource.TestStep{
{
Config: testAccDataSourceAwsSubnetConfig(rInt),
Check: resource.ComposeTestCheckFunc(
testAccDataSourceAwsSubnetCheck("data.aws_subnet.by_id", rInt),
@ -31,6 +31,48 @@ func TestAccDataSourceAwsSubnet_basic(t *testing.T) {
})
}
func TestAccDataSourceAwsSubnetIpv6ByIpv6Filter(t *testing.T) {
rInt := acctest.RandIntRange(0, 256)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccDataSourceAwsSubnetConfigIpv6(rInt),
},
{
Config: testAccDataSourceAwsSubnetConfigIpv6WithDataSourceFilter(rInt),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet(
"data.aws_subnet.by_ipv6_cidr", "ipv6_cidr_block_association_id"),
resource.TestCheckResourceAttrSet(
"data.aws_subnet.by_ipv6_cidr", "ipv6_cidr_block"),
),
},
},
})
}
func TestAccDataSourceAwsSubnetIpv6ByIpv6CidrBlock(t *testing.T) {
rInt := acctest.RandIntRange(0, 256)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccDataSourceAwsSubnetConfigIpv6(rInt),
},
{
Config: testAccDataSourceAwsSubnetConfigIpv6WithDataSourceIpv6CidrBlock(rInt),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet(
"data.aws_subnet.by_ipv6_cidr", "ipv6_cidr_block_association_id"),
),
},
},
})
}
func testAccDataSourceAwsSubnetCheck(name string, rInt int) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
@ -103,6 +145,7 @@ func testAccDataSourceAwsSubnetConfig(rInt int) string {
}
}
data "aws_subnet" "by_id" {
id = "${aws_subnet.test.id}"
}
@ -129,3 +172,86 @@ func testAccDataSourceAwsSubnetConfig(rInt int) string {
}
`, rInt, rInt, rInt)
}
func testAccDataSourceAwsSubnetConfigIpv6(rInt int) string {
return fmt.Sprintf(`
resource "aws_vpc" "test" {
cidr_block = "172.%d.0.0/16"
assign_generated_ipv6_cidr_block = true
tags {
Name = "terraform-testacc-subnet-data-source-ipv6"
}
}
resource "aws_subnet" "test" {
vpc_id = "${aws_vpc.test.id}"
cidr_block = "172.%d.123.0/24"
availability_zone = "us-west-2a"
ipv6_cidr_block = "${cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 1)}"
tags {
Name = "terraform-testacc-subnet-data-sourceipv6-%d"
}
}
`, rInt, rInt, rInt)
}
func testAccDataSourceAwsSubnetConfigIpv6WithDataSourceFilter(rInt int) string {
return fmt.Sprintf(`
resource "aws_vpc" "test" {
cidr_block = "172.%d.0.0/16"
assign_generated_ipv6_cidr_block = true
tags {
Name = "terraform-testacc-subnet-data-source-ipv6"
}
}
resource "aws_subnet" "test" {
vpc_id = "${aws_vpc.test.id}"
cidr_block = "172.%d.123.0/24"
availability_zone = "us-west-2a"
ipv6_cidr_block = "${cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 1)}"
tags {
Name = "terraform-testacc-subnet-data-sourceipv6-%d"
}
}
data "aws_subnet" "by_ipv6_cidr" {
filter {
name = "ipv6-cidr-block-association.ipv6-cidr-block"
values = ["${aws_subnet.test.ipv6_cidr_block}"]
}
}
`, rInt, rInt, rInt)
}
func testAccDataSourceAwsSubnetConfigIpv6WithDataSourceIpv6CidrBlock(rInt int) string {
return fmt.Sprintf(`
resource "aws_vpc" "test" {
cidr_block = "172.%d.0.0/16"
assign_generated_ipv6_cidr_block = true
tags {
Name = "terraform-testacc-subnet-data-source-ipv6"
}
}
resource "aws_subnet" "test" {
vpc_id = "${aws_vpc.test.id}"
cidr_block = "172.%d.123.0/24"
availability_zone = "us-west-2a"
ipv6_cidr_block = "${cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 1)}"
tags {
Name = "terraform-testacc-subnet-data-sourceipv6-%d"
}
}
data "aws_subnet" "by_ipv6_cidr" {
ipv6_cidr_block = "${aws_subnet.test.ipv6_cidr_block}"
}
`, rInt, rInt, rInt)
}

View File

@ -111,11 +111,11 @@ func ec2CustomFiltersSchema() *schema.Schema {
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},
"values": &schema.Schema{
"values": {
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{

View File

@ -0,0 +1,30 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSCognitoIdentityPool_importBasic(t *testing.T) {
resourceName := "aws_cognito_identity_pool.main"
rName := acctest.RandString(10)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSAPIGatewayAccountDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSCognitoIdentityPoolConfig_basic(rName),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -50,36 +50,67 @@ func resourceAwsSecurityGroupImportState(
}
func resourceAwsSecurityGroupImportStatePerm(sg *ec2.SecurityGroup, ruleType string, perm *ec2.IpPermission) ([]*schema.ResourceData, error) {
/*
Create a seperate Security Group Rule for:
* The collection of IpRanges (cidr_blocks)
* The collection of Ipv6Ranges (ipv6_cidr_blocks)
* Each individual UserIdGroupPair (source_security_group_id)
If, for example, a security group has rules for:
* 2 IpRanges
* 2 Ipv6Ranges
* 2 UserIdGroupPairs
This would generate 4 security group rules:
* 1 for the collection of IpRanges
* 1 for the collection of Ipv6Ranges
* 1 for the first UserIdGroupPair
* 1 for the second UserIdGroupPair
*/
var result []*schema.ResourceData
if len(perm.UserIdGroupPairs) == 0 {
r, err := resourceAwsSecurityGroupImportStatePermPair(sg, ruleType, perm)
if perm.IpRanges != nil {
p := &ec2.IpPermission{
FromPort: perm.FromPort,
IpProtocol: perm.IpProtocol,
PrefixListIds: perm.PrefixListIds,
ToPort: perm.ToPort,
IpRanges: perm.IpRanges,
}
r, err := resourceAwsSecurityGroupImportStatePermPair(sg, ruleType, p)
if err != nil {
return nil, err
}
result = append(result, r)
} else {
// If the rule contained more than one source security group, this
// will iterate over them and create one rule for each
// source security group.
}
if perm.Ipv6Ranges != nil {
p := &ec2.IpPermission{
FromPort: perm.FromPort,
IpProtocol: perm.IpProtocol,
PrefixListIds: perm.PrefixListIds,
ToPort: perm.ToPort,
Ipv6Ranges: perm.Ipv6Ranges,
}
r, err := resourceAwsSecurityGroupImportStatePermPair(sg, ruleType, p)
if err != nil {
return nil, err
}
result = append(result, r)
}
if len(perm.UserIdGroupPairs) > 0 {
for _, pair := range perm.UserIdGroupPairs {
p := &ec2.IpPermission{
FromPort: perm.FromPort,
IpProtocol: perm.IpProtocol,
PrefixListIds: perm.PrefixListIds,
ToPort: perm.ToPort,
FromPort: perm.FromPort,
IpProtocol: perm.IpProtocol,
PrefixListIds: perm.PrefixListIds,
ToPort: perm.ToPort,
UserIdGroupPairs: []*ec2.UserIdGroupPair{pair},
}
if perm.Ipv6Ranges != nil {
p.Ipv6Ranges = perm.Ipv6Ranges
}
if perm.IpRanges != nil {
p.IpRanges = perm.IpRanges
}
r, err := resourceAwsSecurityGroupImportStatePermPair(sg, ruleType, p)
if err != nil {
return nil, err

View File

@ -101,3 +101,59 @@ func TestAccAWSSecurityGroup_importSourceSecurityGroup(t *testing.T) {
},
})
}
func TestAccAWSSecurityGroup_importIPRangeAndSecurityGroupWithSameRules(t *testing.T) {
checkFn := func(s []*terraform.InstanceState) error {
// Expect 4: group, 3 rules
if len(s) != 4 {
return fmt.Errorf("expected 4 states: %#v", s)
}
return nil
}
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSecurityGroupDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSSecurityGroupConfig_importIPRangeAndSecurityGroupWithSameRules,
},
{
ResourceName: "aws_security_group.test_group_1",
ImportState: true,
ImportStateCheck: checkFn,
},
},
})
}
func TestAccAWSSecurityGroup_importIPRangesWithSameRules(t *testing.T) {
checkFn := func(s []*terraform.InstanceState) error {
// Expect 4: group, 2 rules
if len(s) != 3 {
return fmt.Errorf("expected 3 states: %#v", s)
}
return nil
}
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSecurityGroupDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSSecurityGroupConfig_importIPRangesWithSameRules,
},
{
ResourceName: "aws_security_group.test_group_1",
ImportState: true,
ImportStateCheck: checkFn,
},
},
})
}

View File

@ -163,6 +163,7 @@ func Provider() terraform.ResourceProvider {
"aws_alb": dataSourceAwsAlb(),
"aws_alb_listener": dataSourceAwsAlbListener(),
"aws_ami": dataSourceAwsAmi(),
"aws_ami_ids": dataSourceAwsAmiIds(),
"aws_autoscaling_groups": dataSourceAwsAutoscalingGroups(),
"aws_availability_zone": dataSourceAwsAvailabilityZone(),
"aws_availability_zones": dataSourceAwsAvailabilityZones(),
@ -172,6 +173,7 @@ func Provider() terraform.ResourceProvider {
"aws_cloudformation_stack": dataSourceAwsCloudFormationStack(),
"aws_db_instance": dataSourceAwsDbInstance(),
"aws_ebs_snapshot": dataSourceAwsEbsSnapshot(),
"aws_ebs_snapshot_ids": dataSourceAwsEbsSnapshotIds(),
"aws_ebs_volume": dataSourceAwsEbsVolume(),
"aws_ecs_cluster": dataSourceAwsEcsCluster(),
"aws_ecs_container_definition": dataSourceAwsEcsContainerDefinition(),
@ -258,6 +260,7 @@ func Provider() terraform.ResourceProvider {
"aws_config_configuration_recorder": resourceAwsConfigConfigurationRecorder(),
"aws_config_configuration_recorder_status": resourceAwsConfigConfigurationRecorderStatus(),
"aws_config_delivery_channel": resourceAwsConfigDeliveryChannel(),
"aws_cognito_identity_pool": resourceAwsCognitoIdentityPool(),
"aws_autoscaling_lifecycle_hook": resourceAwsAutoscalingLifecycleHook(),
"aws_cloudwatch_metric_alarm": resourceAwsCloudWatchMetricAlarm(),
"aws_codedeploy_app": resourceAwsCodeDeployApp(),
@ -365,6 +368,7 @@ func Provider() terraform.ResourceProvider {
"aws_default_route_table": resourceAwsDefaultRouteTable(),
"aws_network_acl_rule": resourceAwsNetworkAclRule(),
"aws_network_interface": resourceAwsNetworkInterface(),
"aws_network_interface_attachment": resourceAwsNetworkInterfaceAttachment(),
"aws_opsworks_application": resourceAwsOpsworksApplication(),
"aws_opsworks_stack": resourceAwsOpsworksStack(),
"aws_opsworks_java_app_layer": resourceAwsOpsworksJavaAppLayer(),

View File

@ -54,6 +54,16 @@ func resourceAwsApiGatewayDeployment() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"invoke_url": {
Type: schema.TypeString,
Computed: true,
},
"execution_arn": {
Type: schema.TypeString,
Computed: true,
},
},
}
}
@ -90,8 +100,9 @@ func resourceAwsApiGatewayDeploymentRead(d *schema.ResourceData, meta interface{
conn := meta.(*AWSClient).apigateway
log.Printf("[DEBUG] Reading API Gateway Deployment %s", d.Id())
restApiId := d.Get("rest_api_id").(string)
out, err := conn.GetDeployment(&apigateway.GetDeploymentInput{
RestApiId: aws.String(d.Get("rest_api_id").(string)),
RestApiId: aws.String(restApiId),
DeploymentId: aws.String(d.Id()),
})
if err != nil {
@ -104,6 +115,18 @@ func resourceAwsApiGatewayDeploymentRead(d *schema.ResourceData, meta interface{
log.Printf("[DEBUG] Received API Gateway Deployment: %s", out)
d.Set("description", out.Description)
region := meta.(*AWSClient).region
stageName := d.Get("stage_name").(string)
d.Set("invoke_url", buildApiGatewayInvokeURL(restApiId, region, stageName))
accountId := meta.(*AWSClient).accountid
arn, err := buildApiGatewayExecutionARN(restApiId, region, accountId)
if err != nil {
return err
}
d.Set("execution_arn", arn+"/"+stageName)
if err := d.Set("created_date", out.CreatedDate.Format(time.RFC3339)); err != nil {
log.Printf("[DEBUG] Error setting created_date: %s", err)
}

View File

@ -0,0 +1,238 @@
package aws
import (
"fmt"
"log"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/cognitoidentity"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsCognitoIdentityPool() *schema.Resource {
return &schema.Resource{
Create: resourceAwsCognitoIdentityPoolCreate,
Read: resourceAwsCognitoIdentityPoolRead,
Update: resourceAwsCognitoIdentityPoolUpdate,
Delete: resourceAwsCognitoIdentityPoolDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"identity_pool_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateCognitoIdentityPoolName,
},
"cognito_identity_providers": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"client_id": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateCognitoIdentityProvidersClientId,
},
"provider_name": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateCognitoIdentityProvidersProviderName,
},
"server_side_token_check": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
},
},
"developer_provider_name": {
Type: schema.TypeString,
Optional: true,
ForceNew: true, // Forcing a new resource since it cannot be edited afterwards
ValidateFunc: validateCognitoProviderDeveloperName,
},
"allow_unauthenticated_identities": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"openid_connect_provider_arns": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validateArn,
},
},
"saml_provider_arns": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validateArn,
},
},
"supported_login_providers": {
Type: schema.TypeMap,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validateCognitoSupportedLoginProviders,
},
},
},
}
}
func resourceAwsCognitoIdentityPoolCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).cognitoconn
log.Print("[DEBUG] Creating Cognito Identity Pool")
params := &cognitoidentity.CreateIdentityPoolInput{
IdentityPoolName: aws.String(d.Get("identity_pool_name").(string)),
AllowUnauthenticatedIdentities: aws.Bool(d.Get("allow_unauthenticated_identities").(bool)),
}
if v, ok := d.GetOk("developer_provider_name"); ok {
params.DeveloperProviderName = aws.String(v.(string))
}
if v, ok := d.GetOk("supported_login_providers"); ok {
params.SupportedLoginProviders = expandCognitoSupportedLoginProviders(v.(map[string]interface{}))
}
if v, ok := d.GetOk("cognito_identity_providers"); ok {
params.CognitoIdentityProviders = expandCognitoIdentityProviders(v.(*schema.Set))
}
if v, ok := d.GetOk("saml_provider_arns"); ok {
params.SamlProviderARNs = expandStringList(v.([]interface{}))
}
if v, ok := d.GetOk("openid_connect_provider_arns"); ok {
params.OpenIdConnectProviderARNs = expandStringList(v.([]interface{}))
}
entity, err := conn.CreateIdentityPool(params)
if err != nil {
return fmt.Errorf("Error creating Cognito Identity Pool: %s", err)
}
d.SetId(*entity.IdentityPoolId)
return resourceAwsCognitoIdentityPoolRead(d, meta)
}
func resourceAwsCognitoIdentityPoolRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).cognitoconn
log.Printf("[DEBUG] Reading Cognito Identity Pool: %s", d.Id())
ip, err := conn.DescribeIdentityPool(&cognitoidentity.DescribeIdentityPoolInput{
IdentityPoolId: aws.String(d.Id()),
})
if err != nil {
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "ResourceNotFoundException" {
d.SetId("")
return nil
}
return err
}
d.Set("identity_pool_name", ip.IdentityPoolName)
d.Set("allow_unauthenticated_identities", ip.AllowUnauthenticatedIdentities)
d.Set("developer_provider_name", ip.DeveloperProviderName)
if ip.CognitoIdentityProviders != nil {
if err := d.Set("cognito_identity_providers", flattenCognitoIdentityProviders(ip.CognitoIdentityProviders)); err != nil {
return fmt.Errorf("[DEBUG] Error setting cognito_identity_providers error: %#v", err)
}
}
if ip.OpenIdConnectProviderARNs != nil {
if err := d.Set("openid_connect_provider_arns", flattenStringList(ip.OpenIdConnectProviderARNs)); err != nil {
return fmt.Errorf("[DEBUG] Error setting openid_connect_provider_arns error: %#v", err)
}
}
if ip.SamlProviderARNs != nil {
if err := d.Set("saml_provider_arns", flattenStringList(ip.SamlProviderARNs)); err != nil {
return fmt.Errorf("[DEBUG] Error setting saml_provider_arns error: %#v", err)
}
}
if ip.SupportedLoginProviders != nil {
if err := d.Set("supported_login_providers", flattenCognitoSupportedLoginProviders(ip.SupportedLoginProviders)); err != nil {
return fmt.Errorf("[DEBUG] Error setting supported_login_providers error: %#v", err)
}
}
return nil
}
func resourceAwsCognitoIdentityPoolUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).cognitoconn
log.Print("[DEBUG] Updating Cognito Identity Pool")
params := &cognitoidentity.IdentityPool{
IdentityPoolId: aws.String(d.Id()),
AllowUnauthenticatedIdentities: aws.Bool(d.Get("allow_unauthenticated_identities").(bool)),
IdentityPoolName: aws.String(d.Get("identity_pool_name").(string)),
}
if d.HasChange("developer_provider_name") {
params.DeveloperProviderName = aws.String(d.Get("developer_provider_name").(string))
}
if d.HasChange("cognito_identity_providers") {
params.CognitoIdentityProviders = expandCognitoIdentityProviders(d.Get("cognito_identity_providers").(*schema.Set))
}
if d.HasChange("supported_login_providers") {
params.SupportedLoginProviders = expandCognitoSupportedLoginProviders(d.Get("supported_login_providers").(map[string]interface{}))
}
if d.HasChange("openid_connect_provider_arns") {
params.OpenIdConnectProviderARNs = expandStringList(d.Get("openid_connect_provider_arns").([]interface{}))
}
if d.HasChange("saml_provider_arns") {
params.SamlProviderARNs = expandStringList(d.Get("saml_provider_arns").([]interface{}))
}
_, err := conn.UpdateIdentityPool(params)
if err != nil {
return fmt.Errorf("Error creating Cognito Identity Pool: %s", err)
}
return resourceAwsCognitoIdentityPoolRead(d, meta)
}
func resourceAwsCognitoIdentityPoolDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).cognitoconn
log.Printf("[DEBUG] Deleting Cognito Identity Pool: %s", d.Id())
return resource.Retry(5*time.Minute, func() *resource.RetryError {
_, err := conn.DeleteIdentityPool(&cognitoidentity.DeleteIdentityPoolInput{
IdentityPoolId: aws.String(d.Id()),
})
if err == nil {
return nil
}
return resource.NonRetryableError(err)
})
}

View File

@ -0,0 +1,371 @@
package aws
import (
"errors"
"fmt"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/cognitoidentity"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAWSCognitoIdentityPool_basic(t *testing.T) {
name := fmt.Sprintf("%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))
updatedName := fmt.Sprintf("%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCognitoIdentityPoolDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSCognitoIdentityPoolConfig_basic(name),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", name)),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "allow_unauthenticated_identities", "false"),
),
},
{
Config: testAccAWSCognitoIdentityPoolConfig_basic(updatedName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", updatedName)),
),
},
},
})
}
func TestAccAWSCognitoIdentityPool_supportedLoginProviders(t *testing.T) {
name := fmt.Sprintf("%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCognitoIdentityPoolDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSCognitoIdentityPoolConfig_supportedLoginProviders(name),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", name)),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "supported_login_providers.graph.facebook.com", "7346241598935555"),
),
},
{
Config: testAccAWSCognitoIdentityPoolConfig_supportedLoginProvidersModified(name),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", name)),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "supported_login_providers.graph.facebook.com", "7346241598935552"),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "supported_login_providers.accounts.google.com", "123456789012.apps.googleusercontent.com"),
),
},
{
Config: testAccAWSCognitoIdentityPoolConfig_basic(name),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", name)),
),
},
},
})
}
func TestAccAWSCognitoIdentityPool_openidConnectProviderArns(t *testing.T) {
name := fmt.Sprintf("%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCognitoIdentityPoolDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSCognitoIdentityPoolConfig_openidConnectProviderArns(name),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", name)),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "openid_connect_provider_arns.#", "1"),
),
},
{
Config: testAccAWSCognitoIdentityPoolConfig_openidConnectProviderArnsModified(name),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", name)),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "openid_connect_provider_arns.#", "2"),
),
},
{
Config: testAccAWSCognitoIdentityPoolConfig_basic(name),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", name)),
),
},
},
})
}
func TestAccAWSCognitoIdentityPool_samlProviderArns(t *testing.T) {
name := fmt.Sprintf("%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCognitoIdentityPoolDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSCognitoIdentityPoolConfig_samlProviderArns(name),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", name)),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "saml_provider_arns.#", "1"),
),
},
{
Config: testAccAWSCognitoIdentityPoolConfig_samlProviderArnsModified(name),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", name)),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "saml_provider_arns.#", "1"),
),
},
{
Config: testAccAWSCognitoIdentityPoolConfig_basic(name),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", name)),
resource.TestCheckNoResourceAttr("aws_cognito_identity_pool.main", "saml_provider_arns.#"),
),
},
},
})
}
func TestAccAWSCognitoIdentityPool_cognitoIdentityProviders(t *testing.T) {
name := fmt.Sprintf("%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCognitoIdentityPoolDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSCognitoIdentityPoolConfig_cognitoIdentityProviders(name),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", name)),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "cognito_identity_providers.66456389.client_id", "7lhlkkfbfb4q5kpp90urffao"),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "cognito_identity_providers.66456389.provider_name", "cognito-idp.us-east-1.amazonaws.com/us-east-1_Zr231apJu"),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "cognito_identity_providers.66456389.server_side_token_check", "false"),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "cognito_identity_providers.3571192419.client_id", "7lhlkkfbfb4q5kpp90urffao"),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "cognito_identity_providers.3571192419.provider_name", "cognito-idp.us-east-1.amazonaws.com/us-east-1_Ab129faBb"),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "cognito_identity_providers.3571192419.server_side_token_check", "false"),
),
},
{
Config: testAccAWSCognitoIdentityPoolConfig_cognitoIdentityProvidersModified(name),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", name)),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "cognito_identity_providers.3661724441.client_id", "6lhlkkfbfb4q5kpp90urffae"),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "cognito_identity_providers.3661724441.provider_name", "cognito-idp.us-east-1.amazonaws.com/us-east-1_Zr231apJu"),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "cognito_identity_providers.3661724441.server_side_token_check", "false"),
),
},
{
Config: testAccAWSCognitoIdentityPoolConfig_basic(name),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSCognitoIdentityPoolExists("aws_cognito_identity_pool.main"),
resource.TestCheckResourceAttr("aws_cognito_identity_pool.main", "identity_pool_name", fmt.Sprintf("identity pool %s", name)),
),
},
},
})
}
func testAccCheckAWSCognitoIdentityPoolExists(n 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 errors.New("No Cognito Identity Pool ID is set")
}
conn := testAccProvider.Meta().(*AWSClient).cognitoconn
_, err := conn.DescribeIdentityPool(&cognitoidentity.DescribeIdentityPoolInput{
IdentityPoolId: aws.String(rs.Primary.ID),
})
if err != nil {
return err
}
return nil
}
}
func testAccCheckAWSCognitoIdentityPoolDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).cognitoconn
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_cognito_identity_pool" {
continue
}
_, err := conn.DescribeIdentityPool(&cognitoidentity.DescribeIdentityPoolInput{
IdentityPoolId: aws.String(rs.Primary.ID),
})
if err != nil {
if wserr, ok := err.(awserr.Error); ok && wserr.Code() == "ResourceNotFoundException" {
return nil
}
return err
}
}
return nil
}
func testAccAWSCognitoIdentityPoolConfig_basic(name string) string {
return fmt.Sprintf(`
resource "aws_cognito_identity_pool" "main" {
identity_pool_name = "identity pool %s"
allow_unauthenticated_identities = false
developer_provider_name = "my.developer"
}
`, name)
}
func testAccAWSCognitoIdentityPoolConfig_supportedLoginProviders(name string) string {
return fmt.Sprintf(`
resource "aws_cognito_identity_pool" "main" {
identity_pool_name = "identity pool %s"
allow_unauthenticated_identities = false
supported_login_providers {
"graph.facebook.com" = "7346241598935555"
}
}
`, name)
}
func testAccAWSCognitoIdentityPoolConfig_supportedLoginProvidersModified(name string) string {
return fmt.Sprintf(`
resource "aws_cognito_identity_pool" "main" {
identity_pool_name = "identity pool %s"
allow_unauthenticated_identities = false
supported_login_providers {
"graph.facebook.com" = "7346241598935552"
"accounts.google.com" = "123456789012.apps.googleusercontent.com"
}
}
`, name)
}
func testAccAWSCognitoIdentityPoolConfig_openidConnectProviderArns(name string) string {
return fmt.Sprintf(`
resource "aws_cognito_identity_pool" "main" {
identity_pool_name = "identity pool %s"
allow_unauthenticated_identities = false
openid_connect_provider_arns = ["arn:aws:iam::123456789012:oidc-provider/server.example.com"]
}
`, name)
}
func testAccAWSCognitoIdentityPoolConfig_openidConnectProviderArnsModified(name string) string {
return fmt.Sprintf(`
resource "aws_cognito_identity_pool" "main" {
identity_pool_name = "identity pool %s"
allow_unauthenticated_identities = false
openid_connect_provider_arns = ["arn:aws:iam::123456789012:oidc-provider/foo.example.com", "arn:aws:iam::123456789012:oidc-provider/bar.example.com"]
}
`, name)
}
func testAccAWSCognitoIdentityPoolConfig_samlProviderArns(name string) string {
return fmt.Sprintf(`
resource "aws_iam_saml_provider" "default" {
name = "myprovider-%s"
saml_metadata_document = "${file("./test-fixtures/saml-metadata.xml")}"
}
resource "aws_cognito_identity_pool" "main" {
identity_pool_name = "identity pool %s"
allow_unauthenticated_identities = false
saml_provider_arns = ["${aws_iam_saml_provider.default.arn}"]
}
`, name, name)
}
func testAccAWSCognitoIdentityPoolConfig_samlProviderArnsModified(name string) string {
return fmt.Sprintf(`
resource "aws_iam_saml_provider" "default" {
name = "default-%s"
saml_metadata_document = "${file("./test-fixtures/saml-metadata.xml")}"
}
resource "aws_iam_saml_provider" "secondary" {
name = "secondary-%s"
saml_metadata_document = "${file("./test-fixtures/saml-metadata.xml")}"
}
resource "aws_cognito_identity_pool" "main" {
identity_pool_name = "identity pool %s"
allow_unauthenticated_identities = false
saml_provider_arns = ["${aws_iam_saml_provider.secondary.arn}"]
}
`, name, name, name)
}
func testAccAWSCognitoIdentityPoolConfig_cognitoIdentityProviders(name string) string {
return fmt.Sprintf(`
resource "aws_cognito_identity_pool" "main" {
identity_pool_name = "identity pool %s"
allow_unauthenticated_identities = false
cognito_identity_providers {
client_id = "7lhlkkfbfb4q5kpp90urffao"
provider_name = "cognito-idp.us-east-1.amazonaws.com/us-east-1_Ab129faBb"
server_side_token_check = false
}
cognito_identity_providers {
client_id = "7lhlkkfbfb4q5kpp90urffao"
provider_name = "cognito-idp.us-east-1.amazonaws.com/us-east-1_Zr231apJu"
server_side_token_check = false
}
}
`, name)
}
func testAccAWSCognitoIdentityPoolConfig_cognitoIdentityProvidersModified(name string) string {
return fmt.Sprintf(`
resource "aws_cognito_identity_pool" "main" {
identity_pool_name = "identity pool %s"
allow_unauthenticated_identities = false
cognito_identity_providers {
client_id = "6lhlkkfbfb4q5kpp90urffae"
provider_name = "cognito-idp.us-east-1.amazonaws.com/us-east-1_Zr231apJu"
server_side_token_check = false
}
}
`, name)
}

View File

@ -85,20 +85,21 @@ func TestParseTaskDefinition(t *testing.T) {
}
func TestAccAWSEcsServiceWithARN(t *testing.T) {
rInt := acctest.RandInt()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSEcsServiceDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSEcsService,
Config: testAccAWSEcsService(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSEcsServiceExists("aws_ecs_service.mongo"),
),
},
{
Config: testAccAWSEcsServiceModified,
Config: testAccAWSEcsServiceModified(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSEcsServiceExists("aws_ecs_service.mongo"),
),
@ -181,13 +182,14 @@ func TestAccAWSEcsService_withIamRole(t *testing.T) {
}
func TestAccAWSEcsService_withDeploymentValues(t *testing.T) {
rInt := acctest.RandInt()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSEcsServiceDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSEcsServiceWithDeploymentValues,
Config: testAccAWSEcsServiceWithDeploymentValues(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSEcsServiceExists("aws_ecs_service.mongo"),
resource.TestCheckResourceAttr(
@ -262,20 +264,21 @@ func TestAccAWSEcsService_withAlb(t *testing.T) {
}
func TestAccAWSEcsServiceWithPlacementStrategy(t *testing.T) {
rInt := acctest.RandInt()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSEcsServiceDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSEcsService,
Config: testAccAWSEcsService(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSEcsServiceExists("aws_ecs_service.mongo"),
resource.TestCheckResourceAttr("aws_ecs_service.mongo", "placement_strategy.#", "0"),
),
},
{
Config: testAccAWSEcsServiceWithPlacementStrategy,
Config: testAccAWSEcsServiceWithPlacementStrategy(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSEcsServiceExists("aws_ecs_service.mongo"),
resource.TestCheckResourceAttr("aws_ecs_service.mongo", "placement_strategy.#", "1"),
@ -286,13 +289,14 @@ func TestAccAWSEcsServiceWithPlacementStrategy(t *testing.T) {
}
func TestAccAWSEcsServiceWithPlacementConstraints(t *testing.T) {
rInt := acctest.RandInt()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSEcsServiceDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSEcsServiceWithPlacementConstraint,
Config: testAccAWSEcsServiceWithPlacementConstraint(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSEcsServiceExists("aws_ecs_service.mongo"),
resource.TestCheckResourceAttr("aws_ecs_service.mongo", "placement_constraints.#", "1"),
@ -303,13 +307,14 @@ func TestAccAWSEcsServiceWithPlacementConstraints(t *testing.T) {
}
func TestAccAWSEcsServiceWithPlacementConstraints_emptyExpression(t *testing.T) {
rInt := acctest.RandInt()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSEcsServiceDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSEcsServiceWithPlacementConstraintEmptyExpression,
Config: testAccAWSEcsServiceWithPlacementConstraintEmptyExpression(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSEcsServiceExists("aws_ecs_service.mongo"),
resource.TestCheckResourceAttr("aws_ecs_service.mongo", "placement_constraints.#", "1"),
@ -366,9 +371,10 @@ func testAccCheckAWSEcsServiceExists(name string) resource.TestCheckFunc {
}
}
var testAccAWSEcsService = `
func testAccAWSEcsService(rInt int) string {
return fmt.Sprintf(`
resource "aws_ecs_cluster" "default" {
name = "terraformecstest1"
name = "terraformecstest%d"
}
resource "aws_ecs_task_definition" "mongo" {
@ -387,16 +393,18 @@ DEFINITION
}
resource "aws_ecs_service" "mongo" {
name = "mongodb"
name = "mongodb-%d"
cluster = "${aws_ecs_cluster.default.id}"
task_definition = "${aws_ecs_task_definition.mongo.arn}"
desired_count = 1
}
`
`, rInt, rInt)
}
var testAccAWSEcsServiceModified = `
func testAccAWSEcsServiceModified(rInt int) string {
return fmt.Sprintf(`
resource "aws_ecs_cluster" "default" {
name = "terraformecstest1"
name = "terraformecstest%d"
}
resource "aws_ecs_task_definition" "mongo" {
@ -415,16 +423,18 @@ DEFINITION
}
resource "aws_ecs_service" "mongo" {
name = "mongodb"
name = "mongodb-%d"
cluster = "${aws_ecs_cluster.default.id}"
task_definition = "${aws_ecs_task_definition.mongo.arn}"
desired_count = 2
}
`
`, rInt, rInt)
}
var testAccAWSEcsServiceWithPlacementStrategy = `
func testAccAWSEcsServiceWithPlacementStrategy(rInt int) string {
return fmt.Sprintf(`
resource "aws_ecs_cluster" "default" {
name = "terraformecstest1"
name = "terraformecstest%d"
}
resource "aws_ecs_task_definition" "mongo" {
@ -443,7 +453,7 @@ DEFINITION
}
resource "aws_ecs_service" "mongo" {
name = "mongodb"
name = "mongodb-%d"
cluster = "${aws_ecs_cluster.default.id}"
task_definition = "${aws_ecs_task_definition.mongo.arn}"
desired_count = 1
@ -452,43 +462,47 @@ resource "aws_ecs_service" "mongo" {
field = "memory"
}
}
`
`, rInt, rInt)
}
var testAccAWSEcsServiceWithPlacementConstraint = `
func testAccAWSEcsServiceWithPlacementConstraint(rInt int) string {
return fmt.Sprintf(`
resource "aws_ecs_cluster" "default" {
name = "terraformecstest%d"
}
resource "aws_ecs_task_definition" "mongo" {
family = "mongodb"
container_definitions = <<DEFINITION
[
{
"cpu": 128,
"essential": true,
"image": "mongo:latest",
"memory": 128,
"name": "mongodb"
}
]
DEFINITION
}
resource "aws_ecs_service" "mongo" {
name = "mongodb-%d"
cluster = "${aws_ecs_cluster.default.id}"
task_definition = "${aws_ecs_task_definition.mongo.arn}"
desired_count = 1
placement_constraints {
type = "memberOf"
expression = "attribute:ecs.availability-zone in [us-west-2a, us-west-2b]"
}
}
`, rInt, rInt)
}
func testAccAWSEcsServiceWithPlacementConstraintEmptyExpression(rInt int) string {
return fmt.Sprintf(`
resource "aws_ecs_cluster" "default" {
name = "terraformecstest21"
}
resource "aws_ecs_task_definition" "mongo" {
family = "mongodb"
container_definitions = <<DEFINITION
[
{
"cpu": 128,
"essential": true,
"image": "mongo:latest",
"memory": 128,
"name": "mongodb"
}
]
DEFINITION
}
resource "aws_ecs_service" "mongo" {
name = "mongodb"
cluster = "${aws_ecs_cluster.default.id}"
task_definition = "${aws_ecs_task_definition.mongo.arn}"
desired_count = 1
placement_constraints {
type = "memberOf"
expression = "attribute:ecs.availability-zone in [us-west-2a, us-west-2b]"
}
}
`
var testAccAWSEcsServiceWithPlacementConstraintEmptyExpression = `
resource "aws_ecs_cluster" "default" {
name = "terraformecstest212"
name = "terraformecstest%d"
}
resource "aws_ecs_task_definition" "mongo" {
family = "mongodb"
@ -505,7 +519,7 @@ resource "aws_ecs_task_definition" "mongo" {
DEFINITION
}
resource "aws_ecs_service" "mongo" {
name = "mongodb"
name = "mongodb-%d"
cluster = "${aws_ecs_cluster.default.id}"
task_definition = "${aws_ecs_task_definition.mongo.arn}"
desired_count = 1
@ -513,7 +527,8 @@ resource "aws_ecs_service" "mongo" {
type = "distinctInstance"
}
}
`
`, rInt, rInt)
}
var testAccAWSEcsService_withIamRole = `
resource "aws_ecs_cluster" "main" {
@ -609,9 +624,10 @@ resource "aws_ecs_service" "ghost" {
}
`
var testAccAWSEcsServiceWithDeploymentValues = `
func testAccAWSEcsServiceWithDeploymentValues(rInt int) string {
return fmt.Sprintf(`
resource "aws_ecs_cluster" "default" {
name = "terraformecstest1"
name = "terraformecstest-%d"
}
resource "aws_ecs_task_definition" "mongo" {
@ -630,12 +646,13 @@ DEFINITION
}
resource "aws_ecs_service" "mongo" {
name = "mongodb"
name = "mongodb-%d"
cluster = "${aws_ecs_cluster.default.id}"
task_definition = "${aws_ecs_task_definition.mongo.arn}"
desired_count = 1
}
`
`, rInt, rInt)
}
var tpl_testAccAWSEcsService_withLbChanges = `
resource "aws_ecs_cluster" "main" {

View File

@ -81,7 +81,7 @@ func TestAccAWSIAMInstanceProfile_missingRoleThrowsError(t *testing.T) {
Steps: []resource.TestStep{
{
Config: testAccAwsIamInstanceProfileConfigMissingRole(rName),
ExpectError: regexp.MustCompile("Either `roles` or `role` must be specified when creating an IAM Instance Profile"),
ExpectError: regexp.MustCompile(regexp.QuoteMeta("Either `role` or `roles` (deprecated) must be specified when creating an IAM Instance Profile")),
},
},
})

View File

@ -146,6 +146,10 @@ func resourceAwsLambdaFunction() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"invoke_arn": {
Type: schema.TypeString,
Computed: true,
},
"last_modified": {
Type: schema.TypeString,
Computed: true,
@ -175,6 +179,8 @@ func resourceAwsLambdaFunction() *schema.Resource {
Optional: true,
ValidateFunc: validateArn,
},
"tags": tagsSchema(),
},
}
}
@ -291,6 +297,10 @@ func resourceAwsLambdaFunctionCreate(d *schema.ResourceData, meta interface{}) e
params.KMSKeyArn = aws.String(v.(string))
}
if v, exists := d.GetOk("tags"); exists {
params.Tags = tagsFromMapGeneric(v.(map[string]interface{}))
}
// IAM profiles can take ~10 seconds to propagate in AWS:
// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#launch-instance-with-role-console
// Error creating Lambda function: InvalidParameterValueException: The role defined for the task cannot be assumed by Lambda.
@ -353,6 +363,7 @@ func resourceAwsLambdaFunctionRead(d *schema.ResourceData, meta interface{}) err
d.Set("runtime", function.Runtime)
d.Set("timeout", function.Timeout)
d.Set("kms_key_arn", function.KMSKeyArn)
d.Set("tags", tagsToMapGeneric(getFunctionOutput.Tags))
config := flattenLambdaVpcConfigResponse(function.VpcConfig)
log.Printf("[INFO] Setting Lambda %s VPC config %#v from API", d.Id(), config)
@ -399,6 +410,8 @@ func resourceAwsLambdaFunctionRead(d *schema.ResourceData, meta interface{}) err
d.Set("version", lastVersion)
d.Set("qualified_arn", lastQualifiedArn)
d.Set("invoke_arn", buildLambdaInvokeArn(*function.FunctionArn, meta.(*AWSClient).region))
return nil
}
@ -448,6 +461,12 @@ func resourceAwsLambdaFunctionUpdate(d *schema.ResourceData, meta interface{}) e
d.Partial(true)
arn := d.Get("arn").(string)
if tagErr := setTagsLambda(conn, d, arn); tagErr != nil {
return tagErr
}
d.SetPartial("tags")
if d.HasChange("filename") || d.HasChange("source_code_hash") || d.HasChange("s3_bucket") || d.HasChange("s3_key") || d.HasChange("s3_object_version") {
codeReq := &lambda.UpdateFunctionCodeInput{
FunctionName: aws.String(d.Id()),

View File

@ -582,6 +582,74 @@ func TestAccAWSLambdaFunction_runtimeValidation_java8(t *testing.T) {
})
}
func TestAccAWSLambdaFunction_tags(t *testing.T) {
var conf lambda.GetFunctionOutput
rSt := acctest.RandString(5)
rName := fmt.Sprintf("tf_test_%s", rSt)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckLambdaFunctionDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSLambdaConfigBasic(rName, rSt),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsLambdaFunctionExists("aws_lambda_function.lambda_function_test", rName, &conf),
testAccCheckAwsLambdaFunctionName(&conf, rName),
testAccCheckAwsLambdaFunctionArnHasSuffix(&conf, ":"+rName),
resource.TestCheckNoResourceAttr("aws_lambda_function.lambda_function_test", "tags"),
),
},
{
Config: testAccAWSLambdaConfigTags(rName, rSt),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsLambdaFunctionExists("aws_lambda_function.lambda_function_test", rName, &conf),
testAccCheckAwsLambdaFunctionName(&conf, rName),
testAccCheckAwsLambdaFunctionArnHasSuffix(&conf, ":"+rName),
resource.TestCheckResourceAttr("aws_lambda_function.lambda_function_test", "tags.%", "2"),
resource.TestCheckResourceAttr("aws_lambda_function.lambda_function_test", "tags.Key1", "Value One"),
resource.TestCheckResourceAttr("aws_lambda_function.lambda_function_test", "tags.Description", "Very interesting"),
),
},
{
Config: testAccAWSLambdaConfigTagsModified(rName, rSt),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsLambdaFunctionExists("aws_lambda_function.lambda_function_test", rName, &conf),
testAccCheckAwsLambdaFunctionName(&conf, rName),
testAccCheckAwsLambdaFunctionArnHasSuffix(&conf, ":"+rName),
resource.TestCheckResourceAttr("aws_lambda_function.lambda_function_test", "tags.%", "3"),
resource.TestCheckResourceAttr("aws_lambda_function.lambda_function_test", "tags.Key1", "Value One Changed"),
resource.TestCheckResourceAttr("aws_lambda_function.lambda_function_test", "tags.Key2", "Value Two"),
resource.TestCheckResourceAttr("aws_lambda_function.lambda_function_test", "tags.Key3", "Value Three"),
),
},
},
})
}
func TestAccAWSLambdaFunction_runtimeValidation_python36(t *testing.T) {
var conf lambda.GetFunctionOutput
rSt := acctest.RandString(5)
rName := fmt.Sprintf("tf_test_%s", rSt)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckLambdaFunctionDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSLambdaConfigPython36Runtime(rName, rSt),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsLambdaFunctionExists("aws_lambda_function.lambda_function_test", rName, &conf),
resource.TestCheckResourceAttr("aws_lambda_function.lambda_function_test", "runtime", lambda.RuntimePython36),
),
},
},
})
}
func testAccCheckLambdaFunctionDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).lambdaconn
@ -1106,6 +1174,51 @@ resource "aws_lambda_function" "lambda_function_test" {
`, rName)
}
func testAccAWSLambdaConfigTags(rName, rSt string) string {
return fmt.Sprintf(baseAccAWSLambdaConfig(rSt)+`
resource "aws_lambda_function" "lambda_function_test" {
filename = "test-fixtures/lambdatest.zip"
function_name = "%s"
role = "${aws_iam_role.iam_for_lambda.arn}"
handler = "exports.example"
runtime = "nodejs4.3"
tags {
Key1 = "Value One"
Description = "Very interesting"
}
}
`, rName)
}
func testAccAWSLambdaConfigTagsModified(rName, rSt string) string {
return fmt.Sprintf(baseAccAWSLambdaConfig(rSt)+`
resource "aws_lambda_function" "lambda_function_test" {
filename = "test-fixtures/lambdatest.zip"
function_name = "%s"
role = "${aws_iam_role.iam_for_lambda.arn}"
handler = "exports.example"
runtime = "nodejs4.3"
tags {
Key1 = "Value One Changed"
Key2 = "Value Two"
Key3 = "Value Three"
}
}
`, rName)
}
func testAccAWSLambdaConfigPython36Runtime(rName, rSt string) string {
return fmt.Sprintf(baseAccAWSLambdaConfig(rSt)+`
resource "aws_lambda_function" "lambda_function_test" {
filename = "test-fixtures/lambdatest.zip"
function_name = "%s"
role = "${aws_iam_role.iam_for_lambda.arn}"
handler = "exports.example"
runtime = "python3.6"
}
`, rName)
}
const testAccAWSLambdaFunctionConfig_local_tpl = `
resource "aws_iam_role" "iam_for_lambda" {
name = "iam_for_lambda_%d"

View File

@ -230,7 +230,12 @@ func resourceAwsLambdaPermissionRead(d *schema.ResourceData, meta interface{}) e
}
d.Set("action", statement.Action)
d.Set("principal", statement.Principal["Service"])
// Check if the pricipal is a cross-account IAM role
if _, ok := statement.Principal["AWS"]; ok {
d.Set("principal", statement.Principal["AWS"])
} else {
d.Set("principal", statement.Principal["Service"])
}
if stringEquals, ok := statement.Condition["StringEquals"]; ok {
d.Set("source_account", stringEquals["AWS:SourceAccount"])

View File

@ -332,6 +332,30 @@ func TestAccAWSLambdaPermission_withSNS(t *testing.T) {
})
}
func TestAccAWSLambdaPermission_withIAMRole(t *testing.T) {
var statement LambdaPolicyStatement
endsWithFuncName := regexp.MustCompile(":function:lambda_function_name_perm_iamrole$")
endsWithRoleName := regexp.MustCompile("/iam_for_lambda_perm_iamrole$")
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSLambdaPermissionDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSLambdaPermissionConfig_withIAMRole,
Check: resource.ComposeTestCheckFunc(
testAccCheckLambdaPermissionExists("aws_lambda_permission.iam_role", &statement),
resource.TestCheckResourceAttr("aws_lambda_permission.iam_role", "action", "lambda:InvokeFunction"),
resource.TestMatchResourceAttr("aws_lambda_permission.iam_role", "principal", endsWithRoleName),
resource.TestCheckResourceAttr("aws_lambda_permission.iam_role", "statement_id", "AllowExecutionFromIAMRole"),
resource.TestMatchResourceAttr("aws_lambda_permission.iam_role", "function_name", endsWithFuncName),
),
},
},
})
}
func testAccCheckLambdaPermissionExists(n string, statement *LambdaPolicyStatement) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
@ -724,6 +748,42 @@ EOF
}
`
var testAccAWSLambdaPermissionConfig_withIAMRole = `
resource "aws_lambda_permission" "iam_role" {
statement_id = "AllowExecutionFromIAMRole"
action = "lambda:InvokeFunction"
function_name = "${aws_lambda_function.my-func.arn}"
principal = "${aws_iam_role.police.arn}"
}
resource "aws_lambda_function" "my-func" {
filename = "test-fixtures/lambdatest.zip"
function_name = "lambda_function_name_perm_iamrole"
role = "${aws_iam_role.police.arn}"
handler = "exports.handler"
runtime = "nodejs4.3"
}
resource "aws_iam_role" "police" {
name = "iam_for_lambda_perm_iamrole"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
`
var testLambdaPolicy = []byte(`{
"Version": "2012-10-17",
"Statement": [

View File

@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
"log"
"math"
"strconv"
"time"
@ -33,6 +34,12 @@ func resourceAwsNetworkInterface() *schema.Resource {
ForceNew: true,
},
"private_ip": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"private_ips": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
@ -41,6 +48,12 @@ func resourceAwsNetworkInterface() *schema.Resource {
Set: schema.HashString,
},
"private_ips_count": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"security_groups": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
@ -110,6 +123,10 @@ func resourceAwsNetworkInterfaceCreate(d *schema.ResourceData, meta interface{})
request.Description = aws.String(v.(string))
}
if v, ok := d.GetOk("private_ips_count"); ok {
request.SecondaryPrivateIpAddressCount = aws.Int64(int64(v.(int)))
}
log.Printf("[DEBUG] Creating network interface")
resp, err := conn.CreateNetworkInterface(request)
if err != nil {
@ -144,6 +161,7 @@ func resourceAwsNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) e
eni := describeResp.NetworkInterfaces[0]
d.Set("subnet_id", eni.SubnetId)
d.Set("private_ip", eni.PrivateIpAddress)
d.Set("private_ips", flattenNetworkInterfacesPrivateIPAddresses(eni.PrivateIpAddresses))
d.Set("security_groups", flattenGroupIdentifiers(eni.Groups))
d.Set("source_dest_check", eni.SourceDestCheck)
@ -300,6 +318,49 @@ func resourceAwsNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{})
d.SetPartial("source_dest_check")
if d.HasChange("private_ips_count") {
o, n := d.GetChange("private_ips_count")
private_ips := d.Get("private_ips").(*schema.Set).List()
private_ips_filtered := private_ips[:0]
primary_ip := d.Get("private_ip")
for _, ip := range private_ips {
if ip != primary_ip {
private_ips_filtered = append(private_ips_filtered, ip)
}
}
if o != nil && o != 0 && n != nil && n != len(private_ips_filtered) {
diff := n.(int) - o.(int)
// Surplus of IPs, add the diff
if diff > 0 {
input := &ec2.AssignPrivateIpAddressesInput{
NetworkInterfaceId: aws.String(d.Id()),
SecondaryPrivateIpAddressCount: aws.Int64(int64(diff)),
}
_, err := conn.AssignPrivateIpAddresses(input)
if err != nil {
return fmt.Errorf("Failure to assign Private IPs: %s", err)
}
}
if diff < 0 {
input := &ec2.UnassignPrivateIpAddressesInput{
NetworkInterfaceId: aws.String(d.Id()),
PrivateIpAddresses: expandStringList(private_ips_filtered[0:int(math.Abs(float64(diff)))]),
}
_, err := conn.UnassignPrivateIpAddresses(input)
if err != nil {
return fmt.Errorf("Failure to unassign Private IPs: %s", err)
}
}
d.SetPartial("private_ips_count")
}
}
if d.HasChange("security_groups") {
request := &ec2.ModifyNetworkInterfaceAttributeInput{
NetworkInterfaceId: aws.String(d.Id()),

View File

@ -0,0 +1,166 @@
package aws
import (
"fmt"
"log"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsNetworkInterfaceAttachment() *schema.Resource {
return &schema.Resource{
Create: resourceAwsNetworkInterfaceAttachmentCreate,
Read: resourceAwsNetworkInterfaceAttachmentRead,
Delete: resourceAwsNetworkInterfaceAttachmentDelete,
Schema: map[string]*schema.Schema{
"device_index": {
Type: schema.TypeInt,
Required: true,
ForceNew: true,
},
"instance_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"network_interface_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"attachment_id": {
Type: schema.TypeString,
Computed: true,
},
"status": {
Type: schema.TypeString,
Computed: true,
},
},
}
}
func resourceAwsNetworkInterfaceAttachmentCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ec2conn
device_index := d.Get("device_index").(int)
instance_id := d.Get("instance_id").(string)
network_interface_id := d.Get("network_interface_id").(string)
opts := &ec2.AttachNetworkInterfaceInput{
DeviceIndex: aws.Int64(int64(device_index)),
InstanceId: aws.String(instance_id),
NetworkInterfaceId: aws.String(network_interface_id),
}
log.Printf("[DEBUG] Attaching network interface (%s) to instance (%s)", network_interface_id, instance_id)
resp, err := conn.AttachNetworkInterface(opts)
if err != nil {
if awsErr, ok := err.(awserr.Error); ok {
return fmt.Errorf("Error attaching network interface (%s) to instance (%s), message: \"%s\", code: \"%s\"",
network_interface_id, instance_id, awsErr.Message(), awsErr.Code())
}
return err
}
stateConf := &resource.StateChangeConf{
Pending: []string{"false"},
Target: []string{"true"},
Refresh: networkInterfaceAttachmentRefreshFunc(conn, network_interface_id),
Timeout: 5 * time.Minute,
Delay: 10 * time.Second,
MinTimeout: 3 * time.Second,
}
_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf(
"Error waiting for Volume (%s) to attach to Instance: %s, error: %s", network_interface_id, instance_id, err)
}
d.SetId(*resp.AttachmentId)
return resourceAwsNetworkInterfaceAttachmentRead(d, meta)
}
func resourceAwsNetworkInterfaceAttachmentRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ec2conn
interfaceId := d.Get("network_interface_id").(string)
req := &ec2.DescribeNetworkInterfacesInput{
NetworkInterfaceIds: []*string{aws.String(interfaceId)},
}
resp, err := conn.DescribeNetworkInterfaces(req)
if err != nil {
if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidNetworkInterfaceID.NotFound" {
// The ENI is gone now, so just remove the attachment from the state
d.SetId("")
return nil
}
return fmt.Errorf("Error retrieving ENI: %s", err)
}
if len(resp.NetworkInterfaces) != 1 {
return fmt.Errorf("Unable to find ENI (%s): %#v", interfaceId, resp.NetworkInterfaces)
}
eni := resp.NetworkInterfaces[0]
if eni.Attachment == nil {
// Interface is no longer attached, remove from state
d.SetId("")
return nil
}
d.Set("attachment_id", eni.Attachment.AttachmentId)
d.Set("device_index", eni.Attachment.DeviceIndex)
d.Set("instance_id", eni.Attachment.InstanceId)
d.Set("network_interface_id", eni.NetworkInterfaceId)
d.Set("status", eni.Attachment.Status)
return nil
}
func resourceAwsNetworkInterfaceAttachmentDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ec2conn
interfaceId := d.Get("network_interface_id").(string)
detach_request := &ec2.DetachNetworkInterfaceInput{
AttachmentId: aws.String(d.Id()),
Force: aws.Bool(true),
}
_, detach_err := conn.DetachNetworkInterface(detach_request)
if detach_err != nil {
if awsErr, _ := detach_err.(awserr.Error); awsErr.Code() != "InvalidAttachmentID.NotFound" {
return fmt.Errorf("Error detaching ENI: %s", detach_err)
}
}
log.Printf("[DEBUG] Waiting for ENI (%s) to become dettached", interfaceId)
stateConf := &resource.StateChangeConf{
Pending: []string{"true"},
Target: []string{"false"},
Refresh: networkInterfaceAttachmentRefreshFunc(conn, interfaceId),
Timeout: 10 * time.Minute,
}
if _, err := stateConf.WaitForState(); err != nil {
return fmt.Errorf(
"Error waiting for ENI (%s) to become dettached: %s", interfaceId, err)
}
return nil
}

View File

@ -0,0 +1,92 @@
package aws
import (
"fmt"
"testing"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSNetworkInterfaceAttachment_basic(t *testing.T) {
var conf ec2.NetworkInterface
rInt := acctest.RandInt()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "aws_network_interface.bar",
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSENIDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSNetworkInterfaceAttachmentConfig_basic(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSENIExists("aws_network_interface.bar", &conf),
resource.TestCheckResourceAttr(
"aws_network_interface_attachment.test", "device_index", "1"),
resource.TestCheckResourceAttrSet(
"aws_network_interface_attachment.test", "instance_id"),
resource.TestCheckResourceAttrSet(
"aws_network_interface_attachment.test", "network_interface_id"),
resource.TestCheckResourceAttrSet(
"aws_network_interface_attachment.test", "attachment_id"),
resource.TestCheckResourceAttrSet(
"aws_network_interface_attachment.test", "status"),
),
},
},
})
}
func testAccAWSNetworkInterfaceAttachmentConfig_basic(rInt int) string {
return fmt.Sprintf(`
resource "aws_vpc" "foo" {
cidr_block = "172.16.0.0/16"
}
resource "aws_subnet" "foo" {
vpc_id = "${aws_vpc.foo.id}"
cidr_block = "172.16.10.0/24"
availability_zone = "us-west-2a"
}
resource "aws_security_group" "foo" {
vpc_id = "${aws_vpc.foo.id}"
description = "foo"
name = "foo-%d"
egress {
from_port = 0
to_port = 0
protocol = "tcp"
cidr_blocks = ["10.0.0.0/16"]
}
}
resource "aws_network_interface" "bar" {
subnet_id = "${aws_subnet.foo.id}"
private_ips = ["172.16.10.100"]
security_groups = ["${aws_security_group.foo.id}"]
description = "Managed by Terraform"
tags {
Name = "bar_interface"
}
}
resource "aws_instance" "foo" {
ami = "ami-c5eabbf5"
instance_type = "t2.micro"
subnet_id = "${aws_subnet.foo.id}"
tags {
Name = "foo-%d"
}
}
resource "aws_network_interface_attachment" "test" {
device_index = 1
instance_id = "${aws_instance.foo.id}"
network_interface_id = "${aws_network_interface.bar.id}"
}
`, rInt, rInt)
}

View File

@ -111,6 +111,7 @@ func resourceAwsOpsworksInstance() *schema.Resource {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"infrastructure_class": {

View File

@ -108,6 +108,44 @@ func TestAccAWSOpsworksInstance(t *testing.T) {
})
}
func TestAccAWSOpsworksInstance_UpdateHostNameForceNew(t *testing.T) {
stackName := fmt.Sprintf("tf-%d", acctest.RandInt())
var before, after opsworks.Instance
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAwsOpsworksInstanceDestroy,
Steps: []resource.TestStep{
{
Config: testAccAwsOpsworksInstanceConfigCreate(stackName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSOpsworksInstanceExists("aws_opsworks_instance.tf-acc", &before),
resource.TestCheckResourceAttr("aws_opsworks_instance.tf-acc", "hostname", "tf-acc1"),
),
},
{
Config: testAccAwsOpsworksInstanceConfigUpdateHostName(stackName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSOpsworksInstanceExists("aws_opsworks_instance.tf-acc", &after),
resource.TestCheckResourceAttr("aws_opsworks_instance.tf-acc", "hostname", "tf-acc2"),
testAccCheckAwsOpsworksInstanceRecreated(t, &before, &after),
),
},
},
})
}
func testAccCheckAwsOpsworksInstanceRecreated(t *testing.T,
before, after *opsworks.Instance) resource.TestCheckFunc {
return func(s *terraform.State) error {
if *before.InstanceId == *after.InstanceId {
t.Fatalf("Expected change of OpsWorks Instance IDs, but both were %s", *before.InstanceId)
}
return nil
}
}
func testAccCheckAWSOpsworksInstanceExists(
n string, opsinst *opsworks.Instance) resource.TestCheckFunc {
return func(s *terraform.State) error {
@ -197,6 +235,59 @@ func testAccCheckAwsOpsworksInstanceDestroy(s *terraform.State) error {
return fmt.Errorf("Fall through error on OpsWorks instance test")
}
func testAccAwsOpsworksInstanceConfigUpdateHostName(name string) string {
return fmt.Sprintf(`
resource "aws_security_group" "tf-ops-acc-web" {
name = "%s-web"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_security_group" "tf-ops-acc-php" {
name = "%s-php"
ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_opsworks_static_web_layer" "tf-acc" {
stack_id = "${aws_opsworks_stack.tf-acc.id}"
custom_security_group_ids = [
"${aws_security_group.tf-ops-acc-web.id}",
]
}
resource "aws_opsworks_php_app_layer" "tf-acc" {
stack_id = "${aws_opsworks_stack.tf-acc.id}"
custom_security_group_ids = [
"${aws_security_group.tf-ops-acc-php.id}",
]
}
resource "aws_opsworks_instance" "tf-acc" {
stack_id = "${aws_opsworks_stack.tf-acc.id}"
layer_ids = [
"${aws_opsworks_static_web_layer.tf-acc.id}",
]
instance_type = "t2.micro"
state = "stopped"
hostname = "tf-acc2"
}
%s
`, name, name, testAccAwsOpsworksStackConfigVpcCreate(name))
}
func testAccAwsOpsworksInstanceConfigCreate(name string) string {
return fmt.Sprintf(`
resource "aws_security_group" "tf-ops-acc-web" {

View File

@ -300,7 +300,7 @@ func deleteAllRecordsInHostedZoneId(hostedZoneId, hostedZoneName string, conn *r
changes := make([]*route53.Change, 0)
// 100 items per page returned by default
for _, set := range sets {
if *set.Name == hostedZoneName+"." && (*set.Type == "NS" || *set.Type == "SOA") {
if strings.TrimSuffix(*set.Name, ".") == strings.TrimSuffix(hostedZoneName, ".") && (*set.Type == "NS" || *set.Type == "SOA") {
// Zone NS & SOA records cannot be deleted
continue
}

View File

@ -89,7 +89,7 @@ func TestAccAWSRoute53Zone_basic(t *testing.T) {
}
func TestAccAWSRoute53Zone_forceDestroy(t *testing.T) {
var zone route53.GetHostedZoneOutput
var zone, zoneWithDot route53.GetHostedZoneOutput
// record the initialized providers so that we can use them to
// check for the instances in each region
@ -115,6 +115,11 @@ func TestAccAWSRoute53Zone_forceDestroy(t *testing.T) {
// Add >100 records to verify pagination works ok
testAccCreateRandomRoute53RecordsInZoneIdWithProviders(&providers, &zone, 100),
testAccCreateRandomRoute53RecordsInZoneIdWithProviders(&providers, &zone, 5),
testAccCheckRoute53ZoneExistsWithProviders("aws_route53_zone.with_trailing_dot", &zoneWithDot, &providers),
// Add >100 records to verify pagination works ok
testAccCreateRandomRoute53RecordsInZoneIdWithProviders(&providers, &zoneWithDot, 100),
testAccCreateRandomRoute53RecordsInZoneIdWithProviders(&providers, &zoneWithDot, 5),
),
},
},
@ -417,6 +422,11 @@ resource "aws_route53_zone" "destroyable" {
name = "terraform.io"
force_destroy = true
}
resource "aws_route53_zone" "with_trailing_dot" {
name = "hashicorptest.io."
force_destroy = true
}
`
const testAccRoute53ZoneConfigUpdateComment = `

View File

@ -1995,6 +1995,91 @@ resource "aws_security_group_rule" "allow_test_group_3" {
}
`
const testAccAWSSecurityGroupConfig_importIPRangeAndSecurityGroupWithSameRules = `
resource "aws_vpc" "foo" {
cidr_block = "10.1.0.0/16"
tags {
Name = "tf_sg_import_test"
}
}
resource "aws_security_group" "test_group_1" {
name = "test group 1"
vpc_id = "${aws_vpc.foo.id}"
}
resource "aws_security_group" "test_group_2" {
name = "test group 2"
vpc_id = "${aws_vpc.foo.id}"
}
resource "aws_security_group_rule" "allow_security_group" {
type = "ingress"
from_port = 0
to_port = 0
protocol = "tcp"
source_security_group_id = "${aws_security_group.test_group_2.id}"
security_group_id = "${aws_security_group.test_group_1.id}"
}
resource "aws_security_group_rule" "allow_cidr_block" {
type = "ingress"
from_port = 0
to_port = 0
protocol = "tcp"
cidr_blocks = ["10.0.0.0/32"]
security_group_id = "${aws_security_group.test_group_1.id}"
}
resource "aws_security_group_rule" "allow_ipv6_cidr_block" {
type = "ingress"
from_port = 0
to_port = 0
protocol = "tcp"
ipv6_cidr_blocks = ["::/0"]
security_group_id = "${aws_security_group.test_group_1.id}"
}
`
const testAccAWSSecurityGroupConfig_importIPRangesWithSameRules = `
resource "aws_vpc" "foo" {
cidr_block = "10.1.0.0/16"
tags {
Name = "tf_sg_import_test"
}
}
resource "aws_security_group" "test_group_1" {
name = "test group 1"
vpc_id = "${aws_vpc.foo.id}"
}
resource "aws_security_group_rule" "allow_cidr_block" {
type = "ingress"
from_port = 0
to_port = 0
protocol = "tcp"
cidr_blocks = ["10.0.0.0/32"]
security_group_id = "${aws_security_group.test_group_1.id}"
}
resource "aws_security_group_rule" "allow_ipv6_cidr_block" {
type = "ingress"
from_port = 0
to_port = 0
protocol = "tcp"
ipv6_cidr_blocks = ["::/0"]
security_group_id = "${aws_security_group.test_group_1.id}"
}
`
const testAccAWSSecurityGroupConfigPrefixListEgress = `
resource "aws_vpc" "tf_sg_prefix_list_egress_test" {
cidr_block = "10.0.0.0/16"

View File

@ -22,6 +22,9 @@ func resourceAwsSubnet() *schema.Resource {
State: schema.ImportStatePassthrough,
},
SchemaVersion: 1,
MigrateState: resourceAwsSubnetMigrateState,
Schema: map[string]*schema.Schema{
"vpc_id": {
Type: schema.TypeString,
@ -38,7 +41,6 @@ func resourceAwsSubnet() *schema.Resource {
"ipv6_cidr_block": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"availability_zone": {
@ -141,9 +143,15 @@ func resourceAwsSubnetRead(d *schema.ResourceData, meta interface{}) error {
d.Set("cidr_block", subnet.CidrBlock)
d.Set("map_public_ip_on_launch", subnet.MapPublicIpOnLaunch)
d.Set("assign_ipv6_address_on_creation", subnet.AssignIpv6AddressOnCreation)
if subnet.Ipv6CidrBlockAssociationSet != nil {
d.Set("ipv6_cidr_block", subnet.Ipv6CidrBlockAssociationSet[0].Ipv6CidrBlock)
d.Set("ipv6_cidr_block_association_id", subnet.Ipv6CidrBlockAssociationSet[0].AssociationId)
for _, a := range subnet.Ipv6CidrBlockAssociationSet {
if *a.Ipv6CidrBlockState.State == "associated" { //we can only ever have 1 IPv6 block associated at once
d.Set("ipv6_cidr_block_association_id", a.AssociationId)
d.Set("ipv6_cidr_block", a.Ipv6CidrBlock)
break
} else {
d.Set("ipv6_cidr_block_association_id", "") // we blank these out to remove old entries
d.Set("ipv6_cidr_block", "")
}
}
d.Set("tags", tagsToMap(subnet.Tags))
@ -199,6 +207,73 @@ func resourceAwsSubnetUpdate(d *schema.ResourceData, meta interface{}) error {
}
}
// We have to be careful here to not go through a change of association if this is a new resource
// A New resource here would denote that the Update func is called by the Create func
if d.HasChange("ipv6_cidr_block") && !d.IsNewResource() {
// We need to handle that we disassociate the IPv6 CIDR block before we try and associate the new one
// This could be an issue as, we could error out when we try and add the new one
// We may need to roll back the state and reattach the old one if this is the case
_, new := d.GetChange("ipv6_cidr_block")
//Firstly we have to disassociate the old IPv6 CIDR Block
disassociateOps := &ec2.DisassociateSubnetCidrBlockInput{
AssociationId: aws.String(d.Get("ipv6_cidr_block_association_id").(string)),
}
_, err := conn.DisassociateSubnetCidrBlock(disassociateOps)
if err != nil {
return err
}
// Wait for the CIDR to become disassociated
log.Printf(
"[DEBUG] Waiting for IPv6 CIDR (%s) to become disassociated",
d.Id())
stateConf := &resource.StateChangeConf{
Pending: []string{"disassociating", "associated"},
Target: []string{"disassociated"},
Refresh: SubnetIpv6CidrStateRefreshFunc(conn, d.Id(), d.Get("ipv6_cidr_block_association_id").(string)),
Timeout: 1 * time.Minute,
}
if _, err := stateConf.WaitForState(); err != nil {
return fmt.Errorf(
"Error waiting for IPv6 CIDR (%s) to become disassociated: %s",
d.Id(), err)
}
//Now we need to try and associate the new CIDR block
associatesOpts := &ec2.AssociateSubnetCidrBlockInput{
SubnetId: aws.String(d.Id()),
Ipv6CidrBlock: aws.String(new.(string)),
}
resp, err := conn.AssociateSubnetCidrBlock(associatesOpts)
if err != nil {
//The big question here is, do we want to try and reassociate the old one??
//If we have a failure here, then we may be in a situation that we have nothing associated
return err
}
// Wait for the CIDR to become associated
log.Printf(
"[DEBUG] Waiting for IPv6 CIDR (%s) to become associated",
d.Id())
stateConf = &resource.StateChangeConf{
Pending: []string{"associating", "disassociated"},
Target: []string{"associated"},
Refresh: SubnetIpv6CidrStateRefreshFunc(conn, d.Id(), *resp.Ipv6CidrBlockAssociation.AssociationId),
Timeout: 1 * time.Minute,
}
if _, err := stateConf.WaitForState(); err != nil {
return fmt.Errorf(
"Error waiting for IPv6 CIDR (%s) to become associated: %s",
d.Id(), err)
}
d.SetPartial("ipv6_cidr_block")
}
d.Partial(false)
return resourceAwsSubnetRead(d, meta)
@ -271,3 +346,38 @@ func SubnetStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc
return subnet, *subnet.State, nil
}
}
func SubnetIpv6CidrStateRefreshFunc(conn *ec2.EC2, id string, associationId string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
opts := &ec2.DescribeSubnetsInput{
SubnetIds: []*string{aws.String(id)},
}
resp, err := conn.DescribeSubnets(opts)
if err != nil {
if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidSubnetID.NotFound" {
resp = nil
} else {
log.Printf("Error on SubnetIpv6CidrStateRefreshFunc: %s", err)
return nil, "", err
}
}
if resp == nil {
// Sometimes AWS just has consistency issues and doesn't see
// our instance yet. Return an empty state.
return nil, "", nil
}
if resp.Subnets[0].Ipv6CidrBlockAssociationSet == nil {
return nil, "", nil
}
for _, association := range resp.Subnets[0].Ipv6CidrBlockAssociationSet {
if *association.AssociationId == associationId {
return association, *association.Ipv6CidrBlockState.State, nil
}
}
return nil, "", nil
}
}

View File

@ -0,0 +1,33 @@
package aws
import (
"fmt"
"log"
"github.com/hashicorp/terraform/terraform"
)
func resourceAwsSubnetMigrateState(
v int, is *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) {
switch v {
case 0:
log.Println("[INFO] Found AWS Subnet State v0; migrating to v1")
return migrateSubnetStateV0toV1(is)
default:
return is, fmt.Errorf("Unexpected schema version: %d", v)
}
}
func migrateSubnetStateV0toV1(is *terraform.InstanceState) (*terraform.InstanceState, error) {
if is.Empty() || is.Attributes == nil {
log.Println("[DEBUG] Empty Subnet State; nothing to migrate.")
return is, nil
}
log.Printf("[DEBUG] Attributes before migration: %#v", is.Attributes)
is.Attributes["assign_ipv6_address_on_creation"] = "false"
log.Printf("[DEBUG] Attributes after migration: %#v", is.Attributes)
return is, nil
}

View File

@ -0,0 +1,41 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/terraform"
)
func TestAWSSubnetMigrateState(t *testing.T) {
cases := map[string]struct {
StateVersion int
ID string
Attributes map[string]string
Expected string
Meta interface{}
}{
"v0_1_without_value": {
StateVersion: 0,
ID: "some_id",
Attributes: map[string]string{},
Expected: "false",
},
}
for tn, tc := range cases {
is := &terraform.InstanceState{
ID: tc.ID,
Attributes: tc.Attributes,
}
is, err := resourceAwsSubnetMigrateState(
tc.StateVersion, is, tc.Meta)
if err != nil {
t.Fatalf("bad: %s, err: %#v", tn, err)
}
if is.Attributes["assign_ipv6_address_on_creation"] != tc.Expected {
t.Fatalf("bad Subnet Migrate: %s\n\n expected: %s", is.Attributes["assign_ipv6_address_on_creation"], tc.Expected)
}
}
}

View File

@ -45,27 +45,7 @@ func TestAccAWSSubnet_basic(t *testing.T) {
}
func TestAccAWSSubnet_ipv6(t *testing.T) {
var v ec2.Subnet
testCheck := func(*terraform.State) error {
if v.Ipv6CidrBlockAssociationSet == nil {
return fmt.Errorf("Expected IPV6 CIDR Block Association")
}
if *v.AssignIpv6AddressOnCreation != true {
return fmt.Errorf("bad AssignIpv6AddressOnCreation: %t", *v.AssignIpv6AddressOnCreation)
}
return nil
}
testCheckUpdated := func(*terraform.State) error {
if *v.AssignIpv6AddressOnCreation != false {
return fmt.Errorf("bad AssignIpv6AddressOnCreation: %t", *v.AssignIpv6AddressOnCreation)
}
return nil
}
var before, after ec2.Subnet
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -77,22 +57,65 @@ func TestAccAWSSubnet_ipv6(t *testing.T) {
Config: testAccSubnetConfigIpv6,
Check: resource.ComposeTestCheckFunc(
testAccCheckSubnetExists(
"aws_subnet.foo", &v),
testCheck,
"aws_subnet.foo", &before),
testAccCheckAwsSubnetIpv6BeforeUpdate(t, &before),
),
},
{
Config: testAccSubnetConfigIpv6Updated,
Config: testAccSubnetConfigIpv6UpdateAssignIpv6OnCreation,
Check: resource.ComposeTestCheckFunc(
testAccCheckSubnetExists(
"aws_subnet.foo", &v),
testCheckUpdated,
"aws_subnet.foo", &after),
testAccCheckAwsSubnetIpv6AfterUpdate(t, &after),
),
},
{
Config: testAccSubnetConfigIpv6UpdateIpv6Cidr,
Check: resource.ComposeTestCheckFunc(
testAccCheckSubnetExists(
"aws_subnet.foo", &after),
testAccCheckAwsSubnetNotRecreated(t, &before, &after),
),
},
},
})
}
func testAccCheckAwsSubnetIpv6BeforeUpdate(t *testing.T, subnet *ec2.Subnet) resource.TestCheckFunc {
return func(s *terraform.State) error {
if subnet.Ipv6CidrBlockAssociationSet == nil {
return fmt.Errorf("Expected IPV6 CIDR Block Association")
}
if *subnet.AssignIpv6AddressOnCreation != true {
return fmt.Errorf("bad AssignIpv6AddressOnCreation: %t", *subnet.AssignIpv6AddressOnCreation)
}
return nil
}
}
func testAccCheckAwsSubnetIpv6AfterUpdate(t *testing.T, subnet *ec2.Subnet) resource.TestCheckFunc {
return func(s *terraform.State) error {
if *subnet.AssignIpv6AddressOnCreation != false {
return fmt.Errorf("bad AssignIpv6AddressOnCreation: %t", *subnet.AssignIpv6AddressOnCreation)
}
return nil
}
}
func testAccCheckAwsSubnetNotRecreated(t *testing.T,
before, after *ec2.Subnet) resource.TestCheckFunc {
return func(s *terraform.State) error {
if *before.SubnetId != *after.SubnetId {
t.Fatalf("Expected SubnetIDs not to change, but both got before: %s and after: %s", *before.SubnetId, *after.SubnetId)
}
return nil
}
}
func testAccCheckSubnetDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn
@ -187,7 +210,25 @@ resource "aws_subnet" "foo" {
}
`
const testAccSubnetConfigIpv6Updated = `
const testAccSubnetConfigIpv6UpdateAssignIpv6OnCreation = `
resource "aws_vpc" "foo" {
cidr_block = "10.10.0.0/16"
assign_generated_ipv6_cidr_block = true
}
resource "aws_subnet" "foo" {
cidr_block = "10.10.1.0/24"
vpc_id = "${aws_vpc.foo.id}"
ipv6_cidr_block = "${cidrsubnet(aws_vpc.foo.ipv6_cidr_block, 8, 1)}"
map_public_ip_on_launch = true
assign_ipv6_address_on_creation = false
tags {
Name = "tf-subnet-acc-test"
}
}
`
const testAccSubnetConfigIpv6UpdateIpv6Cidr = `
resource "aws_vpc" "foo" {
cidr_block = "10.10.0.0/16"
assign_generated_ipv6_cidr_block = true

View File

@ -80,17 +80,17 @@ func resourceAwsWafIPSetRead(d *schema.ResourceData, meta interface{}) error {
return err
}
var IPSetDescriptors []map[string]interface{}
var descriptors []map[string]interface{}
for _, IPSetDescriptor := range resp.IPSet.IPSetDescriptors {
IPSet := map[string]interface{}{
"type": *IPSetDescriptor.Type,
"value": *IPSetDescriptor.Value,
for _, descriptor := range resp.IPSet.IPSetDescriptors {
d := map[string]interface{}{
"type": *descriptor.Type,
"value": *descriptor.Value,
}
IPSetDescriptors = append(IPSetDescriptors, IPSet)
descriptors = append(descriptors, d)
}
d.Set("ip_set_descriptors", IPSetDescriptors)
d.Set("ip_set_descriptors", descriptors)
d.Set("name", resp.IPSet.Name)
@ -98,22 +98,36 @@ func resourceAwsWafIPSetRead(d *schema.ResourceData, meta interface{}) error {
}
func resourceAwsWafIPSetUpdate(d *schema.ResourceData, meta interface{}) error {
err := updateIPSetResource(d, meta, waf.ChangeActionInsert)
if err != nil {
return fmt.Errorf("Error Updating WAF IPSet: %s", err)
conn := meta.(*AWSClient).wafconn
if d.HasChange("ip_set_descriptors") {
o, n := d.GetChange("ip_set_descriptors")
oldD, newD := o.(*schema.Set).List(), n.(*schema.Set).List()
err := updateWafIpSetDescriptors(d.Id(), oldD, newD, conn)
if err != nil {
return fmt.Errorf("Error Updating WAF IPSet: %s", err)
}
}
return resourceAwsWafIPSetRead(d, meta)
}
func resourceAwsWafIPSetDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).wafconn
err := updateIPSetResource(d, meta, waf.ChangeActionDelete)
if err != nil {
return fmt.Errorf("Error Removing IPSetDescriptors: %s", err)
oldDescriptors := d.Get("ip_set_descriptors").(*schema.Set).List()
if len(oldDescriptors) > 0 {
noDescriptors := []interface{}{}
err := updateWafIpSetDescriptors(d.Id(), oldDescriptors, noDescriptors, conn)
if err != nil {
return fmt.Errorf("Error updating IPSetDescriptors: %s", err)
}
}
wr := newWafRetryer(conn, "global")
_, err = wr.RetryWithToken(func(token *string) (interface{}, error) {
_, err := wr.RetryWithToken(func(token *string) (interface{}, error) {
req := &waf.DeleteIPSetInput{
ChangeToken: token,
IPSetId: aws.String(d.Id()),
@ -128,29 +142,15 @@ func resourceAwsWafIPSetDelete(d *schema.ResourceData, meta interface{}) error {
return nil
}
func updateIPSetResource(d *schema.ResourceData, meta interface{}, ChangeAction string) error {
conn := meta.(*AWSClient).wafconn
func updateWafIpSetDescriptors(id string, oldD, newD []interface{}, conn *waf.WAF) error {
wr := newWafRetryer(conn, "global")
_, err := wr.RetryWithToken(func(token *string) (interface{}, error) {
req := &waf.UpdateIPSetInput{
ChangeToken: token,
IPSetId: aws.String(d.Id()),
IPSetId: aws.String(id),
Updates: diffWafIpSetDescriptors(oldD, newD),
}
IPSetDescriptors := d.Get("ip_set_descriptors").(*schema.Set)
for _, IPSetDescriptor := range IPSetDescriptors.List() {
IPSet := IPSetDescriptor.(map[string]interface{})
IPSetUpdate := &waf.IPSetUpdate{
Action: aws.String(ChangeAction),
IPSetDescriptor: &waf.IPSetDescriptor{
Type: aws.String(IPSet["type"].(string)),
Value: aws.String(IPSet["value"].(string)),
},
}
req.Updates = append(req.Updates, IPSetUpdate)
}
log.Printf("[INFO] Updating IPSet descriptors: %s", req)
return conn.UpdateIPSet(req)
})
if err != nil {
@ -159,3 +159,37 @@ func updateIPSetResource(d *schema.ResourceData, meta interface{}, ChangeAction
return nil
}
func diffWafIpSetDescriptors(oldD, newD []interface{}) []*waf.IPSetUpdate {
updates := make([]*waf.IPSetUpdate, 0)
for _, od := range oldD {
descriptor := od.(map[string]interface{})
if idx, contains := sliceContainsMap(newD, descriptor); contains {
newD = append(newD[:idx], newD[idx+1:]...)
continue
}
updates = append(updates, &waf.IPSetUpdate{
Action: aws.String(waf.ChangeActionDelete),
IPSetDescriptor: &waf.IPSetDescriptor{
Type: aws.String(descriptor["type"].(string)),
Value: aws.String(descriptor["value"].(string)),
},
})
}
for _, nd := range newD {
descriptor := nd.(map[string]interface{})
updates = append(updates, &waf.IPSetUpdate{
Action: aws.String(waf.ChangeActionInsert),
IPSetDescriptor: &waf.IPSetDescriptor{
Type: aws.String(descriptor["type"].(string)),
Value: aws.String(descriptor["value"].(string)),
},
})
}
return updates
}

View File

@ -2,6 +2,7 @@ package aws
import (
"fmt"
"reflect"
"testing"
"github.com/hashicorp/terraform/helper/resource"
@ -96,6 +97,169 @@ func TestAccAWSWafIPSet_changeNameForceNew(t *testing.T) {
})
}
func TestAccAWSWafIPSet_changeDescriptors(t *testing.T) {
var before, after waf.IPSet
ipsetName := fmt.Sprintf("ip-set-%s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSWafIPSetDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSWafIPSetConfig(ipsetName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSWafIPSetExists("aws_waf_ipset.ipset", &before),
resource.TestCheckResourceAttr(
"aws_waf_ipset.ipset", "name", ipsetName),
resource.TestCheckResourceAttr(
"aws_waf_ipset.ipset", "ip_set_descriptors.#", "1"),
resource.TestCheckResourceAttr(
"aws_waf_ipset.ipset", "ip_set_descriptors.4037960608.type", "IPV4"),
resource.TestCheckResourceAttr(
"aws_waf_ipset.ipset", "ip_set_descriptors.4037960608.value", "192.0.7.0/24"),
),
},
{
Config: testAccAWSWafIPSetConfigChangeIPSetDescriptors(ipsetName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSWafIPSetExists("aws_waf_ipset.ipset", &after),
resource.TestCheckResourceAttr(
"aws_waf_ipset.ipset", "name", ipsetName),
resource.TestCheckResourceAttr(
"aws_waf_ipset.ipset", "ip_set_descriptors.#", "1"),
resource.TestCheckResourceAttr(
"aws_waf_ipset.ipset", "ip_set_descriptors.115741513.type", "IPV4"),
resource.TestCheckResourceAttr(
"aws_waf_ipset.ipset", "ip_set_descriptors.115741513.value", "192.0.8.0/24"),
),
},
},
})
}
func TestAccAWSWafIPSet_noDescriptors(t *testing.T) {
var ipset waf.IPSet
ipsetName := fmt.Sprintf("ip-set-%s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSWafIPSetDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSWafIPSetConfig_noDescriptors(ipsetName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSWafIPSetExists("aws_waf_ipset.ipset", &ipset),
resource.TestCheckResourceAttr(
"aws_waf_ipset.ipset", "name", ipsetName),
resource.TestCheckResourceAttr(
"aws_waf_ipset.ipset", "ip_set_descriptors.#", "0"),
),
},
},
})
}
func TestDiffWafIpSetDescriptors(t *testing.T) {
testCases := []struct {
Old []interface{}
New []interface{}
ExpectedUpdates []*waf.IPSetUpdate
}{
{
// Change
Old: []interface{}{
map[string]interface{}{"type": "IPV4", "value": "192.0.7.0/24"},
},
New: []interface{}{
map[string]interface{}{"type": "IPV4", "value": "192.0.8.0/24"},
},
ExpectedUpdates: []*waf.IPSetUpdate{
&waf.IPSetUpdate{
Action: aws.String(waf.ChangeActionDelete),
IPSetDescriptor: &waf.IPSetDescriptor{
Type: aws.String("IPV4"),
Value: aws.String("192.0.7.0/24"),
},
},
&waf.IPSetUpdate{
Action: aws.String(waf.ChangeActionInsert),
IPSetDescriptor: &waf.IPSetDescriptor{
Type: aws.String("IPV4"),
Value: aws.String("192.0.8.0/24"),
},
},
},
},
{
// Fresh IPSet
Old: []interface{}{},
New: []interface{}{
map[string]interface{}{"type": "IPV4", "value": "10.0.1.0/24"},
map[string]interface{}{"type": "IPV4", "value": "10.0.2.0/24"},
map[string]interface{}{"type": "IPV4", "value": "10.0.3.0/24"},
},
ExpectedUpdates: []*waf.IPSetUpdate{
&waf.IPSetUpdate{
Action: aws.String(waf.ChangeActionInsert),
IPSetDescriptor: &waf.IPSetDescriptor{
Type: aws.String("IPV4"),
Value: aws.String("10.0.1.0/24"),
},
},
&waf.IPSetUpdate{
Action: aws.String(waf.ChangeActionInsert),
IPSetDescriptor: &waf.IPSetDescriptor{
Type: aws.String("IPV4"),
Value: aws.String("10.0.2.0/24"),
},
},
&waf.IPSetUpdate{
Action: aws.String(waf.ChangeActionInsert),
IPSetDescriptor: &waf.IPSetDescriptor{
Type: aws.String("IPV4"),
Value: aws.String("10.0.3.0/24"),
},
},
},
},
{
// Deletion
Old: []interface{}{
map[string]interface{}{"type": "IPV4", "value": "192.0.7.0/24"},
map[string]interface{}{"type": "IPV4", "value": "192.0.8.0/24"},
},
New: []interface{}{},
ExpectedUpdates: []*waf.IPSetUpdate{
&waf.IPSetUpdate{
Action: aws.String(waf.ChangeActionDelete),
IPSetDescriptor: &waf.IPSetDescriptor{
Type: aws.String("IPV4"),
Value: aws.String("192.0.7.0/24"),
},
},
&waf.IPSetUpdate{
Action: aws.String(waf.ChangeActionDelete),
IPSetDescriptor: &waf.IPSetDescriptor{
Type: aws.String("IPV4"),
Value: aws.String("192.0.8.0/24"),
},
},
},
},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
updates := diffWafIpSetDescriptors(tc.Old, tc.New)
if !reflect.DeepEqual(updates, tc.ExpectedUpdates) {
t.Fatalf("IPSet updates don't match.\nGiven: %s\nExpected: %s",
updates, tc.ExpectedUpdates)
}
})
}
}
func testAccCheckAWSWafIPSetDisappears(v *waf.IPSet) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).wafconn
@ -228,3 +392,9 @@ func testAccAWSWafIPSetConfigChangeIPSetDescriptors(name string) string {
}
}`, name)
}
func testAccAWSWafIPSetConfig_noDescriptors(name string) string {
return fmt.Sprintf(`resource "aws_waf_ipset" "ipset" {
name = "%s"
}`, name)
}

View File

@ -24,9 +24,10 @@ func resourceAwsWafRule() *schema.Resource {
ForceNew: true,
},
"metric_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateWafMetricName,
},
"predicates": &schema.Schema{
Type: schema.TypeSet,

View File

@ -37,9 +37,10 @@ func resourceAwsWafWebAcl() *schema.Resource {
},
},
"metric_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateWafMetricName,
},
"rules": &schema.Schema{
Type: schema.TypeSet,

View File

@ -14,6 +14,7 @@ import (
"github.com/aws/aws-sdk-go/service/autoscaling"
"github.com/aws/aws-sdk-go/service/cloudformation"
"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
"github.com/aws/aws-sdk-go/service/cognitoidentity"
"github.com/aws/aws-sdk-go/service/configservice"
"github.com/aws/aws-sdk-go/service/directoryservice"
"github.com/aws/aws-sdk-go/service/ec2"
@ -1925,3 +1926,104 @@ func flattenApiGatewayUsagePlanQuota(s *apigateway.QuotaSettings) []map[string]i
return []map[string]interface{}{settings}
}
func buildApiGatewayInvokeURL(restApiId, region, stageName string) string {
return fmt.Sprintf("https://%s.execute-api.%s.amazonaws.com/%s",
restApiId, region, stageName)
}
func buildApiGatewayExecutionARN(restApiId, region, accountId string) (string, error) {
if accountId == "" {
return "", fmt.Errorf("Unable to build execution ARN for %s as account ID is missing",
restApiId)
}
return fmt.Sprintf("arn:aws:execute-api:%s:%s:%s",
region, accountId, restApiId), nil
}
func expandCognitoSupportedLoginProviders(config map[string]interface{}) map[string]*string {
m := map[string]*string{}
for k, v := range config {
s := v.(string)
m[k] = &s
}
return m
}
func flattenCognitoSupportedLoginProviders(config map[string]*string) map[string]string {
m := map[string]string{}
for k, v := range config {
m[k] = *v
}
return m
}
func expandCognitoIdentityProviders(s *schema.Set) []*cognitoidentity.Provider {
ips := make([]*cognitoidentity.Provider, 0)
for _, v := range s.List() {
s := v.(map[string]interface{})
ip := &cognitoidentity.Provider{}
if sv, ok := s["client_id"].(string); ok {
ip.ClientId = aws.String(sv)
}
if sv, ok := s["provider_name"].(string); ok {
ip.ProviderName = aws.String(sv)
}
if sv, ok := s["server_side_token_check"].(bool); ok {
ip.ServerSideTokenCheck = aws.Bool(sv)
}
ips = append(ips, ip)
}
return ips
}
func flattenCognitoIdentityProviders(ips []*cognitoidentity.Provider) []map[string]interface{} {
values := make([]map[string]interface{}, 0)
for _, v := range ips {
ip := make(map[string]interface{})
if v == nil {
return nil
}
if v.ClientId != nil {
ip["client_id"] = *v.ClientId
}
if v.ProviderName != nil {
ip["provider_name"] = *v.ProviderName
}
if v.ServerSideTokenCheck != nil {
ip["server_side_token_check"] = *v.ServerSideTokenCheck
}
values = append(values, ip)
}
return values
}
func buildLambdaInvokeArn(lambdaArn, region string) string {
apiVersion := "2015-03-31"
return fmt.Sprintf("arn:aws:apigateway:%s:lambda:path/%s/functions/%s/invocations",
region, apiVersion, lambdaArn)
}
func sliceContainsMap(l []interface{}, m map[string]interface{}) (int, bool) {
for i, t := range l {
if reflect.DeepEqual(m, t.(map[string]interface{})) {
return i, true
}
}
return -1, false
}

View File

@ -0,0 +1,69 @@
package aws
import (
"log"
"regexp"
"github.com/aws/aws-sdk-go/aws"
)
// diffTags takes our tags locally and the ones remotely and returns
// the set of tags that must be created, and the set of tags that must
// be destroyed.
func diffTagsGeneric(oldTags, newTags map[string]interface{}) (map[string]*string, map[string]*string) {
// First, we're creating everything we have
create := make(map[string]*string)
for k, v := range newTags {
create[k] = aws.String(v.(string))
}
// Build the map of what to remove
remove := make(map[string]*string)
for k, v := range oldTags {
old, ok := create[k]
if !ok || old != aws.String(v.(string)) {
// Delete it!
remove[k] = aws.String(v.(string))
}
}
return create, remove
}
// tagsFromMap returns the tags for the given map of data.
func tagsFromMapGeneric(m map[string]interface{}) map[string]*string {
result := make(map[string]*string)
for k, v := range m {
if !tagIgnoredGeneric(k) {
result[k] = aws.String(v.(string))
}
}
return result
}
// tagsToMap turns the tags into a map.
func tagsToMapGeneric(ts map[string]*string) map[string]string {
result := make(map[string]string)
for k, v := range ts {
if !tagIgnoredGeneric(k) {
result[k] = aws.StringValue(v)
}
}
return result
}
// compare a tag against a list of strings and checks if it should
// be ignored or not
func tagIgnoredGeneric(k string) bool {
filter := []string{"^aws:*"}
for _, v := range filter {
log.Printf("[DEBUG] Matching %v with %v\n", v, k)
if r, _ := regexp.MatchString(v, k); r == true {
log.Printf("[DEBUG] Found AWS specific tag %s, ignoring.\n", k)
return true
}
}
return false
}

View File

@ -0,0 +1,73 @@
package aws
import (
"reflect"
"testing"
"github.com/aws/aws-sdk-go/aws"
)
// go test -v -run="TestDiffGenericTags"
func TestDiffGenericTags(t *testing.T) {
cases := []struct {
Old, New map[string]interface{}
Create, Remove map[string]string
}{
// Basic add/remove
{
Old: map[string]interface{}{
"foo": "bar",
},
New: map[string]interface{}{
"bar": "baz",
},
Create: map[string]string{
"bar": "baz",
},
Remove: map[string]string{
"foo": "bar",
},
},
// Modify
{
Old: map[string]interface{}{
"foo": "bar",
},
New: map[string]interface{}{
"foo": "baz",
},
Create: map[string]string{
"foo": "baz",
},
Remove: map[string]string{
"foo": "bar",
},
},
}
for i, tc := range cases {
c, r := diffTagsGeneric(tc.Old, tc.New)
cm := tagsToMapGeneric(c)
rm := tagsToMapGeneric(r)
if !reflect.DeepEqual(cm, tc.Create) {
t.Fatalf("%d: bad create: %#v", i, cm)
}
if !reflect.DeepEqual(rm, tc.Remove) {
t.Fatalf("%d: bad remove: %#v", i, rm)
}
}
}
// go test -v -run="TestIgnoringTagsGeneric"
func TestIgnoringTagsGeneric(t *testing.T) {
ignoredTags := map[string]*string{
"aws:cloudformation:logical-id": aws.String("foo"),
"aws:foo:bar": aws.String("baz"),
}
for k, v := range ignoredTags {
if !tagIgnoredGeneric(k) {
t.Fatalf("Tag %v with value %v not ignored, but should be!", k, *v)
}
}
}

View File

@ -0,0 +1,50 @@
package aws
import (
"log"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/lambda"
"github.com/hashicorp/terraform/helper/schema"
)
// setTags is a helper to set the tags for a resource. It expects the
// tags field to be named "tags"
func setTagsLambda(conn *lambda.Lambda, d *schema.ResourceData, arn string) error {
if d.HasChange("tags") {
oraw, nraw := d.GetChange("tags")
o := oraw.(map[string]interface{})
n := nraw.(map[string]interface{})
create, remove := diffTagsGeneric(o, n)
// Set tags
if len(remove) > 0 {
log.Printf("[DEBUG] Removing tags: %#v", remove)
keys := make([]*string, 0, len(remove))
for k := range remove {
keys = append(keys, aws.String(k))
}
_, err := conn.UntagResource(&lambda.UntagResourceInput{
Resource: aws.String(arn),
TagKeys: keys,
})
if err != nil {
return err
}
}
if len(create) > 0 {
log.Printf("[DEBUG] Creating tags: %#v", create)
_, err := conn.TagResource(&lambda.TagResourceInput{
Resource: aws.String(arn),
Tags: create,
})
if err != nil {
return err
}
}
}
return nil
}

View File

@ -1218,3 +1218,86 @@ func validateAwsKmsName(v interface{}, k string) (ws []string, es []error) {
}
return
}
func validateCognitoIdentityPoolName(v interface{}, k string) (ws []string, errors []error) {
val := v.(string)
if !regexp.MustCompile("^[\\w _]+$").MatchString(val) {
errors = append(errors, fmt.Errorf("%q must contain only alphanumeric caracters and spaces", k))
}
return
}
func validateCognitoProviderDeveloperName(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if len(value) > 100 {
errors = append(errors, fmt.Errorf("%q cannot be longer than 100 caracters", k))
}
if !regexp.MustCompile("^[\\w._-]+$").MatchString(value) {
errors = append(errors, fmt.Errorf("%q must contain only alphanumeric caracters, dots, underscores and hyphens", k))
}
return
}
func validateCognitoSupportedLoginProviders(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if len(value) < 1 {
errors = append(errors, fmt.Errorf("%q cannot be less than 1 character", k))
}
if len(value) > 128 {
errors = append(errors, fmt.Errorf("%q cannot be longer than 128 caracters", k))
}
if !regexp.MustCompile("^[\\w.;_/-]+$").MatchString(value) {
errors = append(errors, fmt.Errorf("%q must contain only alphanumeric caracters, dots, semicolons, underscores, slashes and hyphens", k))
}
return
}
func validateCognitoIdentityProvidersClientId(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if len(value) < 1 {
errors = append(errors, fmt.Errorf("%q cannot be less than 1 character", k))
}
if len(value) > 128 {
errors = append(errors, fmt.Errorf("%q cannot be longer than 128 caracters", k))
}
if !regexp.MustCompile("^[\\w_]+$").MatchString(value) {
errors = append(errors, fmt.Errorf("%q must contain only alphanumeric caracters and underscores", k))
}
return
}
func validateCognitoIdentityProvidersProviderName(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if len(value) < 1 {
errors = append(errors, fmt.Errorf("%q cannot be less than 1 character", k))
}
if len(value) > 128 {
errors = append(errors, fmt.Errorf("%q cannot be longer than 128 caracters", k))
}
if !regexp.MustCompile("^[\\w._:/-]+$").MatchString(value) {
errors = append(errors, fmt.Errorf("%q must contain only alphanumeric caracters, dots, underscores, colons, slashes and hyphens", k))
}
return
}
func validateWafMetricName(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if !regexp.MustCompile(`^[0-9A-Za-z]+$`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"Only alphanumeric characters allowed in %q: %q",
k, value))
}
return
}

View File

@ -2011,5 +2011,201 @@ func TestValidateAwsKmsName(t *testing.T) {
t.Fatalf("AWS KMS Alias Name validation failed: %v", errors)
}
}
}
func TestValidateCognitoIdentityPoolName(t *testing.T) {
validValues := []string{
"123",
"1 2 3",
"foo",
"foo bar",
"foo_bar",
"1foo 2bar 3",
}
for _, s := range validValues {
_, errors := validateCognitoIdentityPoolName(s, "identity_pool_name")
if len(errors) > 0 {
t.Fatalf("%q should be a valid Cognito Identity Pool Name: %v", s, errors)
}
}
invalidValues := []string{
"1-2-3",
"foo!",
"foo-bar",
"foo-bar",
"foo1-bar2",
}
for _, s := range invalidValues {
_, errors := validateCognitoIdentityPoolName(s, "identity_pool_name")
if len(errors) == 0 {
t.Fatalf("%q should not be a valid Cognito Identity Pool Name: %v", s, errors)
}
}
}
func TestValidateCognitoProviderDeveloperName(t *testing.T) {
validValues := []string{
"1",
"foo",
"1.2",
"foo1-bar2-baz3",
"foo_bar",
}
for _, s := range validValues {
_, errors := validateCognitoProviderDeveloperName(s, "developer_provider_name")
if len(errors) > 0 {
t.Fatalf("%q should be a valid Cognito Provider Developer Name: %v", s, errors)
}
}
invalidValues := []string{
"foo!",
"foo:bar",
"foo/bar",
"foo;bar",
}
for _, s := range invalidValues {
_, errors := validateCognitoProviderDeveloperName(s, "developer_provider_name")
if len(errors) == 0 {
t.Fatalf("%q should not be a valid Cognito Provider Developer Name: %v", s, errors)
}
}
}
func TestValidateCognitoSupportedLoginProviders(t *testing.T) {
validValues := []string{
"foo",
"7346241598935552",
"123456789012.apps.googleusercontent.com",
"foo_bar",
"foo;bar",
"foo/bar",
"foo-bar",
"xvz1evFS4wEEPTGEFPHBog;kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw",
strings.Repeat("W", 128),
}
for _, s := range validValues {
_, errors := validateCognitoSupportedLoginProviders(s, "supported_login_providers")
if len(errors) > 0 {
t.Fatalf("%q should be a valid Cognito Supported Login Providers: %v", s, errors)
}
}
invalidValues := []string{
"",
strings.Repeat("W", 129), // > 128
"foo:bar_baz",
"foobar,foobaz",
"foobar=foobaz",
}
for _, s := range invalidValues {
_, errors := validateCognitoSupportedLoginProviders(s, "supported_login_providers")
if len(errors) == 0 {
t.Fatalf("%q should not be a valid Cognito Supported Login Providers: %v", s, errors)
}
}
}
func TestValidateCognitoIdentityProvidersClientId(t *testing.T) {
validValues := []string{
"7lhlkkfbfb4q5kpp90urffao",
"12345678",
"foo_123",
strings.Repeat("W", 128),
}
for _, s := range validValues {
_, errors := validateCognitoIdentityProvidersClientId(s, "client_id")
if len(errors) > 0 {
t.Fatalf("%q should be a valid Cognito Identity Provider Client ID: %v", s, errors)
}
}
invalidValues := []string{
"",
strings.Repeat("W", 129), // > 128
"foo-bar",
"foo:bar",
"foo;bar",
}
for _, s := range invalidValues {
_, errors := validateCognitoIdentityProvidersClientId(s, "client_id")
if len(errors) == 0 {
t.Fatalf("%q should not be a valid Cognito Identity Provider Client ID: %v", s, errors)
}
}
}
func TestValidateCognitoIdentityProvidersProviderName(t *testing.T) {
validValues := []string{
"foo",
"7346241598935552",
"foo_bar",
"foo:bar",
"foo/bar",
"foo-bar",
"cognito-idp.us-east-1.amazonaws.com/us-east-1_Zr231apJu",
strings.Repeat("W", 128),
}
for _, s := range validValues {
_, errors := validateCognitoIdentityProvidersProviderName(s, "provider_name")
if len(errors) > 0 {
t.Fatalf("%q should be a valid Cognito Identity Provider Name: %v", s, errors)
}
}
invalidValues := []string{
"",
strings.Repeat("W", 129), // > 128
"foo;bar_baz",
"foobar,foobaz",
"foobar=foobaz",
}
for _, s := range invalidValues {
_, errors := validateCognitoIdentityProvidersProviderName(s, "provider_name")
if len(errors) == 0 {
t.Fatalf("%q should not be a valid Cognito Identity Provider Name: %v", s, errors)
}
}
}
func TestValidateWafMetricName(t *testing.T) {
validNames := []string{
"testrule",
"testRule",
"testRule123",
}
for _, v := range validNames {
_, errors := validateWafMetricName(v, "name")
if len(errors) != 0 {
t.Fatalf("%q should be a valid WAF metric name: %q", v, errors)
}
}
invalidNames := []string{
"!",
"/",
" ",
":",
";",
"white space",
"/slash-at-the-beginning",
"slash-at-the-end/",
}
for _, v := range invalidNames {
_, errors := validateWafMetricName(v, "name")
if len(errors) == 0 {
t.Fatalf("%q should be an invalid WAF metric name", v)
}
}
}

View File

@ -281,14 +281,17 @@ func resourceArmRedisCacheRead(d *schema.ResourceData, meta interface{}) error {
name := id.Path["Redis"]
resp, err := client.Get(resGroup, name)
if err != nil {
return fmt.Errorf("Error making Read request on Azure Redis Cache %s: %s", name, err)
}
// covers if the resource has been deleted outside of TF, but is still in the state
if resp.StatusCode == http.StatusNotFound {
d.SetId("")
return nil
}
if err != nil {
return fmt.Errorf("Error making Read request on Azure Redis Cache %s: %s", name, err)
}
keysResp, err := client.ListKeys(resGroup, name)
if err != nil {
return fmt.Errorf("Error making ListKeys request on Azure Redis Cache %s: %s", name, err)

View File

@ -177,8 +177,9 @@ func resourceArmVirtualMachine() *schema.Resource {
},
"create_option": {
Type: schema.TypeString,
Required: true,
Type: schema.TypeString,
Required: true,
DiffSuppressFunc: ignoreCaseDiffSuppressFunc,
},
"disk_size_gb": {
@ -232,8 +233,9 @@ func resourceArmVirtualMachine() *schema.Resource {
},
"create_option": {
Type: schema.TypeString,
Required: true,
Type: schema.TypeString,
Required: true,
DiffSuppressFunc: ignoreCaseDiffSuppressFunc,
},
"caching": {

View File

@ -6,7 +6,6 @@ import (
"strings"
"github.com/circonus-labs/circonus-gometrics/api"
"github.com/circonus-labs/circonus-gometrics/api/config"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema"
@ -85,9 +84,8 @@ func resourceMetricCluster() *schema.Resource {
// Out parameters
metricClusterIDAttr: &schema.Schema{
Computed: true,
Type: schema.TypeString,
ValidateFunc: validateRegexp(metricClusterIDAttr, config.MetricClusterCIDRegex),
Computed: true,
Type: schema.TypeString,
},
}),
}

View File

@ -181,9 +181,6 @@ func dataSourceConsulAgentSelf() *schema.Resource {
agentSelfACLDisabledTTL: {
Computed: true,
Type: schema.TypeString,
ValidateFunc: makeValidationFunc(agentSelfACLDisabledTTL, validatorInputs{
validateDurationMin("0ns"),
}),
},
agentSelfACLDownPolicy: {
Computed: true,
@ -196,9 +193,6 @@ func dataSourceConsulAgentSelf() *schema.Resource {
agentSelfACLTTL: {
Computed: true,
Type: schema.TypeString,
ValidateFunc: makeValidationFunc(agentSelfACLTTL, validatorInputs{
validateDurationMin("0ns"),
}),
},
agentSelfAddresses: {
Computed: true,
@ -275,23 +269,14 @@ func dataSourceConsulAgentSelf() *schema.Resource {
agentSelfCheckDeregisterIntervalMin: {
Computed: true,
Type: schema.TypeString,
ValidateFunc: makeValidationFunc(agentSelfCheckDeregisterIntervalMin, validatorInputs{
validateDurationMin("0ns"),
}),
},
agentSelfCheckReapInterval: {
Computed: true,
Type: schema.TypeString,
ValidateFunc: makeValidationFunc(agentSelfCheckReapInterval, validatorInputs{
validateDurationMin("0ns"),
}),
},
agentSelfCheckUpdateInterval: {
Computed: true,
Type: schema.TypeString,
ValidateFunc: makeValidationFunc(agentSelfCheckUpdateInterval, validatorInputs{
validateDurationMin("0ns"),
}),
},
agentSelfClientAddr: {
Computed: true,
@ -317,16 +302,10 @@ func dataSourceConsulAgentSelf() *schema.Resource {
agentSelfDNSMaxStale: {
Computed: true,
Type: schema.TypeString,
ValidateFunc: makeValidationFunc(agentSelfDNSMaxStale, validatorInputs{
validateDurationMin("0ns"),
}),
},
agentSelfDNSNodeTTL: {
Computed: true,
Type: schema.TypeString,
ValidateFunc: makeValidationFunc(agentSelfDNSNodeTTL, validatorInputs{
validateDurationMin("0ns"),
}),
},
agentSelfDNSOnlyPassing: {
Computed: true,
@ -335,16 +314,10 @@ func dataSourceConsulAgentSelf() *schema.Resource {
agentSelfDNSRecursorTimeout: {
Computed: true,
Type: schema.TypeString,
ValidateFunc: makeValidationFunc(agentSelfDNSRecursorTimeout, validatorInputs{
validateDurationMin("0ns"),
}),
},
agentSelfDNSServiceTTL: {
Computed: true,
Type: schema.TypeString,
ValidateFunc: makeValidationFunc(agentSelfDNSServiceTTL, validatorInputs{
validateDurationMin("0ns"),
}),
},
agentSelfDNSUDPAnswerLimit: {
Computed: true,
@ -406,9 +379,6 @@ func dataSourceConsulAgentSelf() *schema.Resource {
agentSelfID: {
Computed: true,
Type: schema.TypeString,
ValidateFunc: makeValidationFunc(agentSelfID, validatorInputs{
validateRegexp(`(?i)^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$`),
}),
},
agentSelfLeaveOnInt: {
Computed: true,
@ -434,9 +404,6 @@ func dataSourceConsulAgentSelf() *schema.Resource {
agentSelfPerformanceRaftMultiplier: {
Computed: true,
Type: schema.TypeString, // FIXME(sean@): should be schema.TypeInt
ValidateFunc: makeValidationFunc(agentSelfPerformanceRaftMultiplier, validatorInputs{
validateIntMin(0),
}),
},
},
},
@ -453,58 +420,30 @@ func dataSourceConsulAgentSelf() *schema.Resource {
agentSelfSchemaPortsDNS: {
Computed: true,
Type: schema.TypeInt,
ValidateFunc: makeValidationFunc(agentSelfSchemaPortsDNS, validatorInputs{
validateIntMin(1),
validateIntMax(65535),
}),
},
agentSelfSchemaPortsHTTP: {
Computed: true,
Type: schema.TypeInt,
ValidateFunc: makeValidationFunc(agentSelfSchemaPortsHTTP, validatorInputs{
validateIntMin(1),
validateIntMax(65535),
}),
},
agentSelfSchemaPortsHTTPS: {
Computed: true,
Type: schema.TypeInt,
ValidateFunc: makeValidationFunc(agentSelfSchemaPortsHTTPS, validatorInputs{
validateIntMin(1),
validateIntMax(65535),
}),
},
agentSelfSchemaPortsRPC: {
Computed: true,
Type: schema.TypeInt,
ValidateFunc: makeValidationFunc(agentSelfSchemaPortsRPC, validatorInputs{
validateIntMin(1),
validateIntMax(65535),
}),
},
agentSelfSchemaPortsSerfLAN: {
Computed: true,
Type: schema.TypeInt,
ValidateFunc: makeValidationFunc(agentSelfSchemaPortsSerfLAN, validatorInputs{
validateIntMin(1),
validateIntMax(65535),
}),
},
agentSelfSchemaPortsSerfWAN: {
Computed: true,
Type: schema.TypeInt,
ValidateFunc: makeValidationFunc(agentSelfSchemaPortsSerfWAN, validatorInputs{
validateIntMin(1),
validateIntMax(65535),
}),
},
agentSelfSchemaPortsServer: {
Computed: true,
Type: schema.TypeInt,
ValidateFunc: makeValidationFunc(agentSelfSchemaPortsServer, validatorInputs{
validateIntMin(1),
validateIntMax(65535),
}),
},
},
},
@ -516,16 +455,10 @@ func dataSourceConsulAgentSelf() *schema.Resource {
agentSelfReconnectTimeoutLAN: {
Computed: true,
Type: schema.TypeString,
ValidateFunc: makeValidationFunc(agentSelfReconnectTimeoutLAN, validatorInputs{
validateDurationMin("0ns"),
}),
},
agentSelfReconnectTimeoutWAN: {
Computed: true,
Type: schema.TypeString,
ValidateFunc: makeValidationFunc(agentSelfReconnectTimeoutWAN, validatorInputs{
validateDurationMin("0ns"),
}),
},
agentSelfRejoinAfterLeave: {
Computed: true,
@ -612,9 +545,6 @@ func dataSourceConsulAgentSelf() *schema.Resource {
agentSelfSessionTTLMin: {
Computed: true,
Type: schema.TypeString,
ValidateFunc: makeValidationFunc(agentSelfSessionTTLMin, validatorInputs{
validateDurationMin("0ns"),
}),
},
agentSelfStartJoin: {
Computed: true,
@ -702,9 +632,6 @@ func dataSourceConsulAgentSelf() *schema.Resource {
agentSelfTelemetryCirconusSubmissionInterval: &schema.Schema{
Type: schema.TypeString,
Computed: true,
ValidateFunc: makeValidationFunc(agentSelfTelemetryCirconusSubmissionInterval, validatorInputs{
validateDurationMin("0ns"),
}),
},
agentSelfTelemetryEnableHostname: &schema.Schema{
Type: schema.TypeString,

View File

@ -56,14 +56,12 @@ func dataSourceConsulCatalogNodes() *schema.Resource {
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
catalogNodesNodeID: &schema.Schema{
Type: schema.TypeString,
Computed: true,
ValidateFunc: makeValidationFunc(catalogNodesNodeID, []interface{}{validateRegexp(`^[\S]+$`)}),
Type: schema.TypeString,
Computed: true,
},
catalogNodesNodeName: &schema.Schema{
Type: schema.TypeString,
Computed: true,
ValidateFunc: makeValidationFunc(catalogNodesNodeName, []interface{}{validateRegexp(`^[\S]+$`)}),
Type: schema.TypeString,
Computed: true,
},
catalogNodesNodeAddress: &schema.Schema{
Type: schema.TypeString,

View File

@ -0,0 +1,93 @@
package digitalocean
import (
"fmt"
"strconv"
"github.com/digitalocean/godo"
"github.com/hashicorp/terraform/helper/schema"
)
func dataSourceDigitalOceanImage() *schema.Resource {
return &schema.Resource{
Read: dataSourceDigitalOceanImageRead,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: "name of the image",
},
// computed attributes
"image": &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: "slug or id of the image",
},
"min_disk_size": &schema.Schema{
Type: schema.TypeInt,
Computed: true,
Description: "minimum disk size required by the image",
},
"private": &schema.Schema{
Type: schema.TypeBool,
Computed: true,
Description: "Is the image private or non-private",
},
"regions": &schema.Schema{
Type: schema.TypeList,
Computed: true,
Description: "list of the regions that the image is available in",
Elem: &schema.Schema{Type: schema.TypeString},
},
"type": &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: "type of the image",
},
},
}
}
func dataSourceDigitalOceanImageRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*godo.Client)
opts := &godo.ListOptions{}
images, _, err := client.Images.ListUser(opts)
if err != nil {
d.SetId("")
return err
}
image, err := findImageByName(images, d.Get("name").(string))
if err != nil {
return err
}
d.SetId(image.Name)
d.Set("name", image.Name)
d.Set("image", strconv.Itoa(image.ID))
d.Set("min_disk_size", image.MinDiskSize)
d.Set("private", !image.Public)
d.Set("regions", image.Regions)
d.Set("type", image.Type)
return nil
}
func findImageByName(images []godo.Image, name string) (*godo.Image, error) {
results := make([]godo.Image, 0)
for _, v := range images {
if v.Name == name {
results = append(results, v)
}
}
if len(results) == 1 {
return &results[0], nil
}
if len(results) == 0 {
return nil, fmt.Errorf("no user image found with name %s", name)
}
return nil, fmt.Errorf("too many user images found with name %s (found %d, expected 1)", name, len(results))
}

View File

@ -0,0 +1,122 @@
package digitalocean
import (
"fmt"
"log"
"regexp"
"testing"
"github.com/digitalocean/godo"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccDigitalOceanImage_Basic(t *testing.T) {
var droplet godo.Droplet
var snapshotsId []int
rInt := acctest.RandInt()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDigitalOceanDropletDestroy,
Steps: []resource.TestStep{
{
Config: testAccCheckDigitalOceanDropletConfig_basic(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanDropletExists("digitalocean_droplet.foobar", &droplet),
takeSnapshotsOfDroplet(rInt, &droplet, &snapshotsId),
),
},
{
Config: testAccCheckDigitalOceanImageConfig_basic(rInt, 1),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"data.digitalocean_image.foobar", "name", fmt.Sprintf("snap-%d-1", rInt)),
resource.TestCheckResourceAttr(
"data.digitalocean_image.foobar", "min_disk_size", "20"),
resource.TestCheckResourceAttr(
"data.digitalocean_image.foobar", "private", "true"),
resource.TestCheckResourceAttr(
"data.digitalocean_image.foobar", "type", "snapshot"),
),
},
{
Config: testAccCheckDigitalOceanImageConfig_basic(rInt, 0),
ExpectError: regexp.MustCompile(`.*too many user images found with name snap-.*\ .found 2, expected 1.`),
},
{
Config: testAccCheckDigitalOceanImageConfig_nonexisting(rInt),
Destroy: false,
ExpectError: regexp.MustCompile(`.*no user image found with name snap-.*-nonexisting`),
},
{
Config: " ",
Check: resource.ComposeTestCheckFunc(
deleteSnapshots(&snapshotsId),
),
},
},
})
}
func takeSnapshotsOfDroplet(rInt int, droplet *godo.Droplet, snapshotsId *[]int) resource.TestCheckFunc {
return func(s *terraform.State) error {
client := testAccProvider.Meta().(*godo.Client)
for i := 0; i < 3; i++ {
err := takeSnapshotOfDroplet(rInt, i%2, droplet)
if err != nil {
return err
}
}
retrieveDroplet, _, err := client.Droplets.Get((*droplet).ID)
if err != nil {
return err
}
*snapshotsId = retrieveDroplet.SnapshotIDs
return nil
}
}
func takeSnapshotOfDroplet(rInt, sInt int, droplet *godo.Droplet) error {
client := testAccProvider.Meta().(*godo.Client)
action, _, err := client.DropletActions.Snapshot((*droplet).ID, fmt.Sprintf("snap-%d-%d", rInt, sInt))
if err != nil {
return err
}
waitForAction(client, action)
return nil
}
func deleteSnapshots(snapshotsId *[]int) resource.TestCheckFunc {
return func(s *terraform.State) error {
log.Printf("XXX Deleting snaps")
client := testAccProvider.Meta().(*godo.Client)
snapshots := *snapshotsId
for _, value := range snapshots {
log.Printf("XXX Deleting %d", value)
_, err := client.Images.Delete(value)
if err != nil {
return err
}
}
return nil
}
}
func testAccCheckDigitalOceanImageConfig_basic(rInt, sInt int) string {
return fmt.Sprintf(`
data "digitalocean_image" "foobar" {
name = "snap-%d-%d"
}
`, rInt, sInt)
}
func testAccCheckDigitalOceanImageConfig_nonexisting(rInt int) string {
return fmt.Sprintf(`
data "digitalocean_image" "foobar" {
name = "snap-%d-nonexisting"
}
`, rInt)
}

View File

@ -17,6 +17,10 @@ func Provider() terraform.ResourceProvider {
},
},
DataSourcesMap: map[string]*schema.Resource{
"digitalocean_image": dataSourceDigitalOceanImage(),
},
ResourcesMap: map[string]*schema.Resource{
"digitalocean_domain": resourceDigitalOceanDomain(),
"digitalocean_droplet": resourceDigitalOceanDroplet(),

View File

@ -30,6 +30,10 @@ func TestAccDigitalOceanDroplet_Basic(t *testing.T) {
"digitalocean_droplet.foobar", "name", fmt.Sprintf("foo-%d", rInt)),
resource.TestCheckResourceAttr(
"digitalocean_droplet.foobar", "size", "512mb"),
resource.TestCheckResourceAttr(
"digitalocean_droplet.foobar", "price_hourly", "0.00744"),
resource.TestCheckResourceAttr(
"digitalocean_droplet.foobar", "price_monthly", "5"),
resource.TestCheckResourceAttr(
"digitalocean_droplet.foobar", "image", "centos-7-x64"),
resource.TestCheckResourceAttr(
@ -37,6 +41,11 @@ func TestAccDigitalOceanDroplet_Basic(t *testing.T) {
resource.TestCheckResourceAttr(
"digitalocean_droplet.foobar", "user_data", "foobar"),
),
Destroy: false,
},
{
Config: testAccCheckDigitalOceanDropletConfig_basic(rInt),
PlanOnly: true,
},
},
})

View File

@ -647,6 +647,72 @@ func resourceServiceV1() *schema.Resource {
},
},
"gcslogging": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
// Required fields
"name": {
Type: schema.TypeString,
Required: true,
Description: "Unique name to refer to this logging setup",
},
"email": {
Type: schema.TypeString,
Required: true,
Description: "The email address associated with the target GCS bucket on your account.",
},
"bucket_name": {
Type: schema.TypeString,
Required: true,
Description: "The name of the bucket in which to store the logs.",
},
"secret_key": {
Type: schema.TypeString,
Required: true,
Description: "The secret key associated with the target gcs bucket on your account.",
},
// Optional fields
"path": {
Type: schema.TypeString,
Optional: true,
Description: "Path to store the files. Must end with a trailing slash",
},
"gzip_level": {
Type: schema.TypeInt,
Optional: true,
Default: 0,
Description: "Gzip Compression level",
},
"period": {
Type: schema.TypeInt,
Optional: true,
Default: 3600,
Description: "How frequently the logs should be transferred, in seconds (Default 3600)",
},
"format": {
Type: schema.TypeString,
Optional: true,
Default: "%h %l %u %t %r %>s",
Description: "Apache-style string or VCL variables to use for log formatting",
},
"timestamp_format": {
Type: schema.TypeString,
Optional: true,
Default: "%Y-%m-%dT%H:%M:%S.000",
Description: "specified timestamp formatting (default `%Y-%m-%dT%H:%M:%S.000`)",
},
"response_condition": {
Type: schema.TypeString,
Optional: true,
Default: "",
Description: "Name of a condition to apply this logging.",
},
},
},
},
"response_object": {
Type: schema.TypeSet,
Optional: true,
@ -1450,6 +1516,59 @@ func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error {
}
}
// find difference in gcslogging
if d.HasChange("gcslogging") {
os, ns := d.GetChange("gcslogging")
if os == nil {
os = new(schema.Set)
}
if ns == nil {
ns = new(schema.Set)
}
oss := os.(*schema.Set)
nss := ns.(*schema.Set)
removeGcslogging := oss.Difference(nss).List()
addGcslogging := nss.Difference(oss).List()
// DELETE old gcslogging configurations
for _, pRaw := range removeGcslogging {
sf := pRaw.(map[string]interface{})
opts := gofastly.DeleteGCSInput{
Service: d.Id(),
Version: latestVersion,
Name: sf["name"].(string),
}
log.Printf("[DEBUG] Fastly gcslogging removal opts: %#v", opts)
err := conn.DeleteGCS(&opts)
if err != nil {
return err
}
}
// POST new/updated gcslogging
for _, pRaw := range addGcslogging {
sf := pRaw.(map[string]interface{})
opts := gofastly.CreateGCSInput{
Service: d.Id(),
Version: latestVersion,
Name: sf["name"].(string),
User: sf["email"].(string),
Bucket: sf["bucket_name"].(string),
SecretKey: sf["secret_key"].(string),
Format: sf["format"].(string),
ResponseCondition: sf["response_condition"].(string),
}
log.Printf("[DEBUG] Create GCS Opts: %#v", opts)
_, err := conn.CreateGCS(&opts)
if err != nil {
return err
}
}
}
// find difference in Response Object
if d.HasChange("response_object") {
or, nr := d.GetChange("response_object")
@ -1883,6 +2002,22 @@ func resourceServiceV1Read(d *schema.ResourceData, meta interface{}) error {
log.Printf("[WARN] Error setting Sumologic for (%s): %s", d.Id(), err)
}
// refresh GCS Logging
log.Printf("[DEBUG] Refreshing GCS for (%s)", d.Id())
GCSList, err := conn.ListGCSs(&gofastly.ListGCSsInput{
Service: d.Id(),
Version: s.ActiveVersion.Number,
})
if err != nil {
return fmt.Errorf("[ERR] Error looking up GCS for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err)
}
gcsl := flattenGCS(GCSList)
if err := d.Set("gcs", gcsl); err != nil {
log.Printf("[WARN] Error setting gcs for (%s): %s", d.Id(), err)
}
// refresh Response Objects
log.Printf("[DEBUG] Refreshing Response Object for (%s)", d.Id())
responseObjectList, err := conn.ListResponseObjects(&gofastly.ListResponseObjectsInput{
@ -2350,6 +2485,35 @@ func flattenSumologics(sumologicList []*gofastly.Sumologic) []map[string]interfa
return l
}
func flattenGCS(gcsList []*gofastly.GCS) []map[string]interface{} {
var GCSList []map[string]interface{}
for _, currentGCS := range gcsList {
// Convert gcs to a map for saving to state.
GCSMapString := map[string]interface{}{
"name": currentGCS.Name,
"email": currentGCS.User,
"bucket_name": currentGCS.Bucket,
"secret_key": currentGCS.SecretKey,
"path": currentGCS.Path,
"period": int(currentGCS.Period),
"gzip_level": int(currentGCS.GzipLevel),
"response_condition": currentGCS.ResponseCondition,
"format": currentGCS.Format,
}
// prune any empty values that come from the default string value in structs
for k, v := range GCSMapString {
if v == "" {
delete(GCSMapString, k)
}
}
GCSList = append(GCSList, GCSMapString)
}
return GCSList
}
func flattenResponseObjects(responseObjectList []*gofastly.ResponseObject) []map[string]interface{} {
var rol []map[string]interface{}
for _, ro := range responseObjectList {

View File

@ -0,0 +1,131 @@
package fastly
import (
"fmt"
"reflect"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
gofastly "github.com/sethvargo/go-fastly"
)
func TestResourceFastlyFlattenGCS(t *testing.T) {
cases := []struct {
remote []*gofastly.GCS
local []map[string]interface{}
}{
{
remote: []*gofastly.GCS{
&gofastly.GCS{
Name: "GCS collector",
User: "email@example.com",
Bucket: "bucketName",
SecretKey: "secretKey",
Format: "log format",
Period: 3600,
GzipLevel: 0,
},
},
local: []map[string]interface{}{
map[string]interface{}{
"name": "GCS collector",
"email": "email@example.com",
"bucket_name": "bucketName",
"secret_key": "secretKey",
"format": "log format",
"period": 3600,
"gzip_level": 0,
},
},
},
}
for _, c := range cases {
out := flattenGCS(c.remote)
if !reflect.DeepEqual(out, c.local) {
t.Fatalf("Error matching:\nexpected: %#v\ngot: %#v", c.local, out)
}
}
}
func TestAccFastlyServiceV1_gcslogging(t *testing.T) {
var service gofastly.ServiceDetail
name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
gcsName := fmt.Sprintf("gcs %s", acctest.RandString(10))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckServiceV1Destroy,
Steps: []resource.TestStep{
{
Config: testAccServiceV1Config_gcs(name, gcsName),
Check: resource.ComposeTestCheckFunc(
testAccCheckServiceV1Exists("fastly_service_v1.foo", &service),
testAccCheckFastlyServiceV1Attributes_gcs(&service, name, gcsName),
),
},
},
})
}
func testAccCheckFastlyServiceV1Attributes_gcs(service *gofastly.ServiceDetail, name, gcsName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
if service.Name != name {
return fmt.Errorf("Bad name, expected (%s), got (%s)", name, service.Name)
}
conn := testAccProvider.Meta().(*FastlyClient).conn
gcsList, err := conn.ListGCSs(&gofastly.ListGCSsInput{
Service: service.ID,
Version: service.ActiveVersion.Number,
})
if err != nil {
return fmt.Errorf("[ERR] Error looking up GCSs for (%s), version (%s): %s", service.Name, service.ActiveVersion.Number, err)
}
if len(gcsList) != 1 {
return fmt.Errorf("GCS missing, expected: 1, got: %d", len(gcsList))
}
if gcsList[0].Name != gcsName {
return fmt.Errorf("GCS name mismatch, expected: %s, got: %#v", gcsName, gcsList[0].Name)
}
return nil
}
}
func testAccServiceV1Config_gcs(name, gcsName string) string {
backendName := fmt.Sprintf("%s.aws.amazon.com", acctest.RandString(3))
return fmt.Sprintf(`
resource "fastly_service_v1" "foo" {
name = "%s"
domain {
name = "test.notadomain.com"
comment = "tf-testing-domain"
}
backend {
address = "%s"
name = "tf -test backend"
}
gcslogging {
name = "%s"
email = "email@example.com",
bucket_name = "bucketName",
secret_key = "secretKey",
format = "log format",
response_condition = "",
}
force_destroy = true
}`, name, backendName, gcsName)
}

View File

@ -3,6 +3,7 @@ package github
import (
"context"
"errors"
"net/http"
"github.com/google/go-github/github"
"github.com/hashicorp/terraform/helper/schema"
@ -117,8 +118,12 @@ func resourceGithubBranchProtectionRead(d *schema.ResourceData, meta interface{}
githubProtection, _, err := client.Repositories.GetBranchProtection(context.TODO(), meta.(*Organization).name, r, b)
if err != nil {
d.SetId("")
return nil
if err, ok := err.(*github.ErrorResponse); ok && err.Response.StatusCode == http.StatusNotFound {
d.SetId("")
return nil
}
return err
}
d.Set("repository", r)

View File

@ -0,0 +1,65 @@
package google
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccComputeNetwork_importBasic(t *testing.T) {
resourceName := "google_compute_network.foobar"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckComputeNetworkDestroy,
Steps: []resource.TestStep{
{
Config: testAccComputeNetwork_basic,
}, {
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
//ImportStateVerifyIgnore: []string{"ipv4_range", "name"},
},
},
})
}
func TestAccComputeNetwork_importAuto_subnet(t *testing.T) {
resourceName := "google_compute_network.bar"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckComputeNetworkDestroy,
Steps: []resource.TestStep{
{
Config: testAccComputeNetwork_auto_subnet,
}, {
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}
func TestAccComputeNetwork_importCustom_subnet(t *testing.T) {
resourceName := "google_compute_network.baz"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckComputeNetworkDestroy,
Steps: []resource.TestStep{
{
Config: testAccComputeNetwork_custom_subnet,
}, {
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -88,6 +88,7 @@ func resourceComputeForwardingRule() *schema.Resource {
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
ForceNew: true,
Set: schema.HashString,
},

View File

@ -14,6 +14,9 @@ func resourceComputeNetwork() *schema.Resource {
Create: resourceComputeNetworkCreate,
Read: resourceComputeNetworkRead,
Delete: resourceComputeNetworkDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
@ -142,6 +145,9 @@ func resourceComputeNetworkRead(d *schema.ResourceData, meta interface{}) error
d.Set("gateway_ipv4", network.GatewayIPv4)
d.Set("self_link", network.SelfLink)
d.Set("ipv4_range", network.IPv4Range)
d.Set("name", network.Name)
d.Set("auto_create_subnetworks", network.AutoCreateSubnetworks)
return nil
}

View File

@ -31,6 +31,7 @@ type application struct {
App *herokuApplication // The heroku application
Client *heroku.Service // Client to interact with the heroku API
Vars map[string]string // The vars on the application
Buildpacks []string // The application's buildpack names or URLs
Organization bool // is the application organization app
}
@ -75,6 +76,11 @@ func (a *application) Update() error {
}
}
a.Buildpacks, err = retrieveBuildpacks(a.Id, a.Client)
if err != nil {
errs = append(errs, err)
}
a.Vars, err = retrieveConfigVars(a.Id, a.Client)
if err != nil {
errs = append(errs, err)
@ -119,6 +125,14 @@ func resourceHerokuApp() *schema.Resource {
ForceNew: true,
},
"buildpacks": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"config_vars": {
Type: schema.TypeList,
Optional: true,
@ -225,6 +239,10 @@ func resourceHerokuAppCreate(d *schema.ResourceData, meta interface{}) error {
}
}
if v, ok := d.GetOk("buildpacks"); ok {
err = updateBuildpacks(d.Id(), client, v.([]interface{}))
}
return resourceHerokuAppRead(d, meta)
}
@ -308,6 +326,9 @@ func resourceHerokuAppRead(d *schema.ResourceData, meta interface{}) error {
}
}
// Only track buildpacks when set in the configuration.
_, buildpacksConfigured := d.GetOk("buildpacks")
organizationApp := isOrganizationApp(d)
// Only set the config_vars that we have set in the configuration.
@ -332,6 +353,9 @@ func resourceHerokuAppRead(d *schema.ResourceData, meta interface{}) error {
d.Set("region", app.App.Region)
d.Set("git_url", app.App.GitURL)
d.Set("web_url", app.App.WebURL)
if buildpacksConfigured {
d.Set("buildpacks", app.Buildpacks)
}
d.Set("config_vars", configVarsValue)
d.Set("all_config_vars", app.Vars)
if organizationApp {
@ -391,6 +415,13 @@ func resourceHerokuAppUpdate(d *schema.ResourceData, meta interface{}) error {
}
}
if d.HasChange("buildpacks") {
err := updateBuildpacks(d.Id(), client, d.Get("buildpacks").([]interface{}))
if err != nil {
return err
}
}
return resourceHerokuAppRead(d, meta)
}
@ -419,6 +450,21 @@ func resourceHerokuAppRetrieve(id string, organization bool, client *heroku.Serv
return &app, nil
}
func retrieveBuildpacks(id string, client *heroku.Service) ([]string, error) {
results, err := client.BuildpackInstallationList(context.TODO(), id, nil)
if err != nil {
return nil, err
}
buildpacks := []string{}
for _, installation := range results {
buildpacks = append(buildpacks, installation.Buildpack.Name)
}
return buildpacks, nil
}
func retrieveConfigVars(id string, client *heroku.Service) (map[string]string, error) {
vars, err := client.ConfigVarInfoForApp(context.TODO(), id)
@ -467,3 +513,24 @@ func updateConfigVars(
return nil
}
func updateBuildpacks(id string, client *heroku.Service, v []interface{}) error {
opts := heroku.BuildpackInstallationUpdateOpts{
Updates: []struct {
Buildpack string `json:"buildpack" url:"buildpack,key"`
}{}}
for _, buildpack := range v {
opts.Updates = append(opts.Updates, struct {
Buildpack string `json:"buildpack" url:"buildpack,key"`
}{
Buildpack: buildpack.(string),
})
}
if _, err := client.BuildpackInstallationUpdate(context.TODO(), id, opts); err != nil {
return fmt.Errorf("Error updating buildpacks: %s", err)
}
return nil
}

View File

@ -109,6 +109,75 @@ func TestAccHerokuApp_NukeVars(t *testing.T) {
})
}
func TestAccHerokuApp_Buildpacks(t *testing.T) {
var app heroku.AppInfoResult
appName := fmt.Sprintf("tftest-%s", acctest.RandString(10))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckHerokuAppDestroy,
Steps: []resource.TestStep{
{
Config: testAccCheckHerokuAppConfig_go(appName),
Check: resource.ComposeTestCheckFunc(
testAccCheckHerokuAppExists("heroku_app.foobar", &app),
testAccCheckHerokuAppBuildpacks(appName, false),
resource.TestCheckResourceAttr("heroku_app.foobar", "buildpacks.0", "heroku/go"),
),
},
{
Config: testAccCheckHerokuAppConfig_multi(appName),
Check: resource.ComposeTestCheckFunc(
testAccCheckHerokuAppExists("heroku_app.foobar", &app),
testAccCheckHerokuAppBuildpacks(appName, true),
resource.TestCheckResourceAttr(
"heroku_app.foobar", "buildpacks.0", "https://github.com/heroku/heroku-buildpack-multi-procfile"),
resource.TestCheckResourceAttr("heroku_app.foobar", "buildpacks.1", "heroku/go"),
),
},
{
Config: testAccCheckHerokuAppConfig_no_vars(appName),
Check: resource.ComposeTestCheckFunc(
testAccCheckHerokuAppExists("heroku_app.foobar", &app),
testAccCheckHerokuAppNoBuildpacks(appName),
resource.TestCheckNoResourceAttr("heroku_app.foobar", "buildpacks.0"),
),
},
},
})
}
func TestAccHerokuApp_ExternallySetBuildpacks(t *testing.T) {
var app heroku.AppInfoResult
appName := fmt.Sprintf("tftest-%s", acctest.RandString(10))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckHerokuAppDestroy,
Steps: []resource.TestStep{
{
Config: testAccCheckHerokuAppConfig_no_vars(appName),
Check: resource.ComposeTestCheckFunc(
testAccCheckHerokuAppExists("heroku_app.foobar", &app),
testAccCheckHerokuAppNoBuildpacks(appName),
resource.TestCheckNoResourceAttr("heroku_app.foobar", "buildpacks.0"),
),
},
{
PreConfig: testAccInstallUnconfiguredBuildpack(t, appName),
Config: testAccCheckHerokuAppConfig_no_vars(appName),
Check: resource.ComposeTestCheckFunc(
testAccCheckHerokuAppExists("heroku_app.foobar", &app),
testAccCheckHerokuAppBuildpacks(appName, false),
resource.TestCheckNoResourceAttr("heroku_app.foobar", "buildpacks.0"),
),
},
},
})
}
func TestAccHerokuApp_Organization(t *testing.T) {
var app heroku.OrganizationApp
appName := fmt.Sprintf("tftest-%s", acctest.RandString(10))
@ -260,6 +329,59 @@ func testAccCheckHerokuAppAttributesNoVars(app *heroku.AppInfoResult, appName st
}
}
func testAccCheckHerokuAppBuildpacks(appName string, multi bool) resource.TestCheckFunc {
return func(s *terraform.State) error {
client := testAccProvider.Meta().(*heroku.Service)
results, err := client.BuildpackInstallationList(context.TODO(), appName, nil)
if err != nil {
return err
}
buildpacks := []string{}
for _, installation := range results {
buildpacks = append(buildpacks, installation.Buildpack.Name)
}
if multi {
herokuMulti := "https://github.com/heroku/heroku-buildpack-multi-procfile"
if len(buildpacks) != 2 || buildpacks[0] != herokuMulti || buildpacks[1] != "heroku/go" {
return fmt.Errorf("Bad buildpacks: %v", buildpacks)
}
return nil
}
if len(buildpacks) != 1 || buildpacks[0] != "heroku/go" {
return fmt.Errorf("Bad buildpacks: %v", buildpacks)
}
return nil
}
}
func testAccCheckHerokuAppNoBuildpacks(appName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
client := testAccProvider.Meta().(*heroku.Service)
results, err := client.BuildpackInstallationList(context.TODO(), appName, nil)
if err != nil {
return err
}
buildpacks := []string{}
for _, installation := range results {
buildpacks = append(buildpacks, installation.Buildpack.Name)
}
if len(buildpacks) != 0 {
return fmt.Errorf("Bad buildpacks: %v", buildpacks)
}
return nil
}
}
func testAccCheckHerokuAppAttributesOrg(app *heroku.OrganizationApp, appName, space, org string) resource.TestCheckFunc {
return func(s *terraform.State) error {
client := testAccProvider.Meta().(*heroku.Service)
@ -362,6 +484,25 @@ func testAccCheckHerokuAppExistsOrg(n string, app *heroku.OrganizationApp) resou
}
}
func testAccInstallUnconfiguredBuildpack(t *testing.T, appName string) func() {
return func() {
client := testAccProvider.Meta().(*heroku.Service)
opts := heroku.BuildpackInstallationUpdateOpts{
Updates: []struct {
Buildpack string `json:"buildpack" url:"buildpack,key"`
}{
{Buildpack: "heroku/go"},
},
}
_, err := client.BuildpackInstallationUpdate(context.TODO(), appName, opts)
if err != nil {
t.Fatalf("Error updating buildpacks: %s", err)
}
}
}
func testAccCheckHerokuAppConfig_basic(appName string) string {
return fmt.Sprintf(`
resource "heroku_app" "foobar" {
@ -374,6 +515,29 @@ resource "heroku_app" "foobar" {
}`, appName)
}
func testAccCheckHerokuAppConfig_go(appName string) string {
return fmt.Sprintf(`
resource "heroku_app" "foobar" {
name = "%s"
region = "us"
buildpacks = ["heroku/go"]
}`, appName)
}
func testAccCheckHerokuAppConfig_multi(appName string) string {
return fmt.Sprintf(`
resource "heroku_app" "foobar" {
name = "%s"
region = "us"
buildpacks = [
"https://github.com/heroku/heroku-buildpack-multi-procfile",
"heroku/go"
]
}`, appName)
}
func testAccCheckHerokuAppConfig_updated(appName string) string {
return fmt.Sprintf(`
resource "heroku_app" "foobar" {

View File

@ -29,7 +29,7 @@ func TestAccRecord_basic(t *testing.T) {
testAccCheckRecordUseClientSubnet(&record, true),
testAccCheckRecordRegionName(&record, []string{"cal"}),
// testAccCheckRecordAnswerMetaWeight(&record, 10),
testAccCheckRecordAnswerRdata(&record, "test1.terraform-record-test.io"),
testAccCheckRecordAnswerRdata(&record, 0, "test1.terraform-record-test.io"),
),
},
},
@ -52,7 +52,7 @@ func TestAccRecord_updated(t *testing.T) {
testAccCheckRecordUseClientSubnet(&record, true),
testAccCheckRecordRegionName(&record, []string{"cal"}),
// testAccCheckRecordAnswerMetaWeight(&record, 10),
testAccCheckRecordAnswerRdata(&record, "test1.terraform-record-test.io"),
testAccCheckRecordAnswerRdata(&record, 0, "test1.terraform-record-test.io"),
),
},
resource.TestStep{
@ -64,7 +64,7 @@ func TestAccRecord_updated(t *testing.T) {
testAccCheckRecordUseClientSubnet(&record, false),
testAccCheckRecordRegionName(&record, []string{"ny", "wa"}),
// testAccCheckRecordAnswerMetaWeight(&record, 5),
testAccCheckRecordAnswerRdata(&record, "test2.terraform-record-test.io"),
testAccCheckRecordAnswerRdata(&record, 0, "test2.terraform-record-test.io"),
),
},
},
@ -85,7 +85,31 @@ func TestAccRecord_SPF(t *testing.T) {
testAccCheckRecordDomain(&record, "terraform-record-test.io"),
testAccCheckRecordTTL(&record, 86400),
testAccCheckRecordUseClientSubnet(&record, true),
testAccCheckRecordAnswerRdata(&record, "v=DKIM1; k=rsa; p=XXXXXXXX"),
testAccCheckRecordAnswerRdata(&record, 0, "v=DKIM1; k=rsa; p=XXXXXXXX"),
),
},
},
})
}
func TestAccRecord_SRV(t *testing.T) {
var record dns.Record
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckRecordDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccRecordSRV,
Check: resource.ComposeTestCheckFunc(
testAccCheckRecordExists("ns1_record.srv", &record),
testAccCheckRecordDomain(&record, "_some-server._tcp.terraform-record-test.io"),
testAccCheckRecordTTL(&record, 86400),
testAccCheckRecordUseClientSubnet(&record, true),
testAccCheckRecordAnswerRdata(&record, 0, "10"),
testAccCheckRecordAnswerRdata(&record, 1, "0"),
testAccCheckRecordAnswerRdata(&record, 2, "2380"),
testAccCheckRecordAnswerRdata(&record, 3, "node-1.terraform-record-test.io"),
),
},
},
@ -206,12 +230,12 @@ func testAccCheckRecordAnswerMetaWeight(r *dns.Record, expected float64) resourc
}
}
func testAccCheckRecordAnswerRdata(r *dns.Record, expected string) resource.TestCheckFunc {
func testAccCheckRecordAnswerRdata(r *dns.Record, idx int, expected string) resource.TestCheckFunc {
return func(s *terraform.State) error {
recordAnswer := r.Answers[0]
recordAnswerString := recordAnswer.Rdata[0]
recordAnswerString := recordAnswer.Rdata[idx]
if recordAnswerString != expected {
return fmt.Errorf("Answers[0].Rdata[0]: got: %#v want: %#v", recordAnswerString, expected)
return fmt.Errorf("Answers[0].Rdata[%d]: got: %#v want: %#v", idx, recordAnswerString, expected)
}
return nil
}
@ -335,3 +359,20 @@ resource "ns1_zone" "test" {
zone = "terraform-record-test.io"
}
`
const testAccRecordSRV = `
resource "ns1_record" "srv" {
zone = "${ns1_zone.test.zone}"
domain = "_some-server._tcp.${ns1_zone.test.zone}"
type = "SRV"
ttl = 86400
use_client_subnet = "true"
answers {
answer = "10 0 2380 node-1.${ns1_zone.test.zone}"
}
}
resource "ns1_zone" "test" {
zone = "terraform-record-test.io"
}
`

View File

@ -0,0 +1,24 @@
package oneandone
import (
"github.com/1and1/oneandone-cloudserver-sdk-go"
)
type Config struct {
Token string
Retries int
Endpoint string
API *oneandone.API
}
func (c *Config) Client() (*Config, error) {
token := oneandone.SetToken(c.Token)
if len(c.Endpoint) > 0 {
c.API = oneandone.New(token, c.Endpoint)
} else {
c.API = oneandone.New(token, oneandone.BaseUrl)
}
return c, nil
}

View File

@ -0,0 +1,56 @@
package oneandone
import (
"github.com/1and1/oneandone-cloudserver-sdk-go"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)
func Provider() terraform.ResourceProvider {
return &schema.Provider{
Schema: map[string]*schema.Schema{
"token": {
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("ONEANDONE_TOKEN", nil),
Description: "1&1 token for API operations.",
},
"retries": {
Type: schema.TypeInt,
Optional: true,
Default: 50,
DefaultFunc: schema.EnvDefaultFunc("ONEANDONE_RETRIES", nil),
},
"endpoint": {
Type: schema.TypeString,
Optional: true,
Default: oneandone.BaseUrl,
DefaultFunc: schema.EnvDefaultFunc("ONEANDONE_ENDPOINT", nil),
},
},
ResourcesMap: map[string]*schema.Resource{
"oneandone_server": resourceOneandOneServer(),
"oneandone_firewall_policy": resourceOneandOneFirewallPolicy(),
"oneandone_private_network": resourceOneandOnePrivateNetwork(),
"oneandone_public_ip": resourceOneandOnePublicIp(),
"oneandone_shared_storage": resourceOneandOneSharedStorage(),
"oneandone_monitoring_policy": resourceOneandOneMonitoringPolicy(),
"oneandone_loadbalancer": resourceOneandOneLoadbalancer(),
"oneandone_vpn": resourceOneandOneVPN(),
},
ConfigureFunc: providerConfigure,
}
}
func providerConfigure(d *schema.ResourceData) (interface{}, error) {
var endpoint string
if d.Get("endpoint").(string) != oneandone.BaseUrl {
endpoint = d.Get("endpoint").(string)
}
config := Config{
Token: d.Get("token").(string),
Retries: d.Get("retries").(int),
Endpoint: endpoint,
}
return config.Client()
}

View File

@ -0,0 +1,36 @@
package oneandone
import (
"os"
"testing"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)
//
var testAccProviders map[string]terraform.ResourceProvider
var testAccProvider *schema.Provider
func init() {
testAccProvider = Provider().(*schema.Provider)
testAccProviders = map[string]terraform.ResourceProvider{
"oneandone": testAccProvider,
}
}
func TestProvider(t *testing.T) {
if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
t.Fatalf("err: %s", err)
}
}
func TestProvider_impl(t *testing.T) {
var _ terraform.ResourceProvider = Provider()
}
func testAccPreCheck(t *testing.T) {
if v := os.Getenv("ONEANDONE_TOKEN"); v == "" {
t.Fatal("ONEANDONE_TOKEN must be set for acceptance tests")
}
}

View File

@ -0,0 +1,274 @@
package oneandone
import (
"github.com/1and1/oneandone-cloudserver-sdk-go"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
"strings"
)
func resourceOneandOneFirewallPolicy() *schema.Resource {
return &schema.Resource{
Create: resourceOneandOneFirewallCreate,
Read: resourceOneandOneFirewallRead,
Update: resourceOneandOneFirewallUpdate,
Delete: resourceOneandOneFirewallDelete,
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},
"description": {
Type: schema.TypeString,
Optional: true,
},
"rules": {
Type: schema.TypeList,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"protocol": {
Type: schema.TypeString,
Required: true,
},
"port_from": {
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(1, 65535),
},
"port_to": {
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(1, 65535),
},
"source_ip": {
Type: schema.TypeString,
Optional: true,
},
"id": {
Type: schema.TypeString,
Computed: true,
},
},
},
Required: true,
},
},
}
}
func resourceOneandOneFirewallCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
req := oneandone.FirewallPolicyRequest{
Name: d.Get("name").(string),
}
if desc, ok := d.GetOk("description"); ok {
req.Description = desc.(string)
}
req.Rules = getRules(d)
fw_id, fw, err := config.API.CreateFirewallPolicy(&req)
if err != nil {
return err
}
err = config.API.WaitForState(fw, "ACTIVE", 10, config.Retries)
if err != nil {
return err
}
d.SetId(fw_id)
if err != nil {
return err
}
return resourceOneandOneFirewallRead(d, meta)
}
func resourceOneandOneFirewallUpdate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
if d.HasChange("name") || d.HasChange("description") {
fw, err := config.API.UpdateFirewallPolicy(d.Id(), d.Get("name").(string), d.Get("description").(string))
if err != nil {
return err
}
err = config.API.WaitForState(fw, "ACTIVE", 10, config.Retries)
if err != nil {
return err
}
}
if d.HasChange("rules") {
oldR, newR := d.GetChange("rules")
oldValues := oldR.([]interface{})
newValues := newR.([]interface{})
if len(oldValues) > len(newValues) {
diff := difference(oldValues, newValues)
for _, old := range diff {
o := old.(map[string]interface{})
if o["id"] != nil {
old_id := o["id"].(string)
fw, err := config.API.DeleteFirewallPolicyRule(d.Id(), old_id)
if err != nil {
return err
}
err = config.API.WaitForState(fw, "ACTIVE", 10, config.Retries)
if err != nil {
return err
}
}
}
} else {
var rules []oneandone.FirewallPolicyRule
for _, raw := range newValues {
rl := raw.(map[string]interface{})
if rl["id"].(string) == "" {
rule := oneandone.FirewallPolicyRule{
Protocol: rl["protocol"].(string),
}
if rl["port_from"] != nil {
rule.PortFrom = oneandone.Int2Pointer(rl["port_from"].(int))
}
if rl["port_to"] != nil {
rule.PortTo = oneandone.Int2Pointer(rl["port_to"].(int))
}
if rl["source_ip"] != nil {
rule.SourceIp = rl["source_ip"].(string)
}
rules = append(rules, rule)
}
}
if len(rules) > 0 {
fw, err := config.API.AddFirewallPolicyRules(d.Id(), rules)
if err != nil {
return err
}
err = config.API.WaitForState(fw, "ACTIVE", 10, config.Retries)
}
}
}
return resourceOneandOneFirewallRead(d, meta)
}
func resourceOneandOneFirewallRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
fw, err := config.API.GetFirewallPolicy(d.Id())
if err != nil {
if strings.Contains(err.Error(), "404") {
d.SetId("")
return nil
}
return err
}
d.Set("rules", readRules(d, fw.Rules))
d.Set("description", fw.Description)
return nil
}
func resourceOneandOneFirewallDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
fp, err := config.API.DeleteFirewallPolicy(d.Id())
if err != nil {
return err
}
err = config.API.WaitUntilDeleted(fp)
if err != nil {
return err
}
return nil
}
func readRules(d *schema.ResourceData, rules []oneandone.FirewallPolicyRule) interface{} {
rawRules := d.Get("rules").([]interface{})
counter := 0
for _, rR := range rawRules {
if len(rules) > counter {
rawMap := rR.(map[string]interface{})
rawMap["id"] = rules[counter].Id
if rules[counter].SourceIp != "0.0.0.0" {
rawMap["source_ip"] = rules[counter].SourceIp
}
}
counter++
}
return rawRules
}
func getRules(d *schema.ResourceData) []oneandone.FirewallPolicyRule {
var rules []oneandone.FirewallPolicyRule
if raw, ok := d.GetOk("rules"); ok {
rawRules := raw.([]interface{})
for _, raw := range rawRules {
rl := raw.(map[string]interface{})
rule := oneandone.FirewallPolicyRule{
Protocol: rl["protocol"].(string),
}
if rl["port_from"] != nil {
rule.PortFrom = oneandone.Int2Pointer(rl["port_from"].(int))
}
if rl["port_to"] != nil {
rule.PortTo = oneandone.Int2Pointer(rl["port_to"].(int))
}
if rl["source_ip"] != nil {
rule.SourceIp = rl["source_ip"].(string)
}
rules = append(rules, rule)
}
}
return rules
}
func difference(oldV, newV []interface{}) (toreturn []interface{}) {
var (
lenMin int
longest []interface{}
)
// Determine the shortest length and the longest slice
if len(oldV) < len(newV) {
lenMin = len(oldV)
longest = newV
} else {
lenMin = len(newV)
longest = oldV
}
// compare common indeces
for i := 0; i < lenMin; i++ {
if oldV[i] == nil || newV[i] == nil {
continue
}
if oldV[i].(map[string]interface{})["id"] != newV[i].(map[string]interface{})["id"] {
toreturn = append(toreturn, newV) //out += fmt.Sprintf("=>\t%s\t%s\n", oldV[i], newV[i])
}
}
// add indeces not in common
for _, v := range longest[lenMin:] {
//out += fmt.Sprintf("=>\t%s\n", v)
toreturn = append(toreturn, v)
}
return toreturn
}

View File

@ -0,0 +1,178 @@
package oneandone
import (
"fmt"
"testing"
"github.com/1and1/oneandone-cloudserver-sdk-go"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"os"
"time"
)
func TestAccOneandoneFirewall_Basic(t *testing.T) {
var firewall oneandone.FirewallPolicy
name := "test"
name_updated := "test1"
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Providers: testAccProviders,
CheckDestroy: testAccCheckDOneandoneFirewallDestroyCheck,
Steps: []resource.TestStep{
resource.TestStep{
Config: fmt.Sprintf(testAccCheckOneandoneFirewall_basic, name),
Check: resource.ComposeTestCheckFunc(
func(*terraform.State) error {
time.Sleep(10 * time.Second)
return nil
},
testAccCheckOneandoneFirewallExists("oneandone_firewall_policy.fw", &firewall),
testAccCheckOneandoneFirewallAttributes("oneandone_firewall_policy.fw", name),
resource.TestCheckResourceAttr("oneandone_firewall_policy.fw", "name", name),
),
},
resource.TestStep{
Config: fmt.Sprintf(testAccCheckOneandoneFirewall_update, name_updated),
Check: resource.ComposeTestCheckFunc(
func(*terraform.State) error {
time.Sleep(10 * time.Second)
return nil
},
testAccCheckOneandoneFirewallExists("oneandone_firewall_policy.fw", &firewall),
testAccCheckOneandoneFirewallAttributes("oneandone_firewall_policy.fw", name_updated),
resource.TestCheckResourceAttr("oneandone_firewall_policy.fw", "name", name_updated),
),
},
},
})
}
func testAccCheckDOneandoneFirewallDestroyCheck(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "oneandone_firewall_policy.fw" {
continue
}
api := oneandone.New(os.Getenv("ONEANDONE_TOKEN"), oneandone.BaseUrl)
_, err := api.GetFirewallPolicy(rs.Primary.ID)
if err == nil {
return fmt.Errorf("Firewall Policy still exists %s %s", rs.Primary.ID, err.Error())
}
}
return nil
}
func testAccCheckOneandoneFirewallAttributes(n string, reverse_dns 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.Attributes["name"] != reverse_dns {
return fmt.Errorf("Bad name: expected %s : found %s ", reverse_dns, rs.Primary.Attributes["name"])
}
return nil
}
}
func testAccCheckOneandoneFirewallExists(n string, fw_p *oneandone.FirewallPolicy) 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 Record ID is set")
}
api := oneandone.New(os.Getenv("ONEANDONE_TOKEN"), oneandone.BaseUrl)
found_fw, err := api.GetFirewallPolicy(rs.Primary.ID)
if err != nil {
return fmt.Errorf("Error occured while fetching Firewall Policy: %s", rs.Primary.ID)
}
if found_fw.Id != rs.Primary.ID {
return fmt.Errorf("Record not found")
}
fw_p = found_fw
return nil
}
}
const testAccCheckOneandoneFirewall_basic = `
resource "oneandone_firewall_policy" "fw" {
name = "%s"
rules = [
{
"protocol" = "TCP"
"port_from" = 80
"port_to" = 80
"source_ip" = "0.0.0.0"
},
{
"protocol" = "ICMP"
"source_ip" = "0.0.0.0"
},
{
"protocol" = "TCP"
"port_from" = 43
"port_to" = 43
"source_ip" = "0.0.0.0"
},
{
"protocol" = "TCP"
"port_from" = 22
"port_to" = 22
"source_ip" = "0.0.0.0"
}
]
}`
const testAccCheckOneandoneFirewall_update = `
resource "oneandone_firewall_policy" "fw" {
name = "%s"
rules = [
{
"protocol" = "TCP"
"port_from" = 80
"port_to" = 80
"source_ip" = "0.0.0.0"
},
{
"protocol" = "ICMP"
"source_ip" = "0.0.0.0"
},
{
"protocol" = "TCP"
"port_from" = 43
"port_to" = 43
"source_ip" = "0.0.0.0"
},
{
"protocol" = "TCP"
"port_from" = 22
"port_to" = 22
"source_ip" = "0.0.0.0"
},
{
"protocol" = "TCP"
"port_from" = 88
"port_to" = 88
"source_ip" = "0.0.0.0"
},
]
}`

View File

@ -0,0 +1,370 @@
package oneandone
import (
"fmt"
"github.com/1and1/oneandone-cloudserver-sdk-go"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
"log"
"strings"
)
func resourceOneandOneLoadbalancer() *schema.Resource {
return &schema.Resource{
Create: resourceOneandOneLoadbalancerCreate,
Read: resourceOneandOneLoadbalancerRead,
Update: resourceOneandOneLoadbalancerUpdate,
Delete: resourceOneandOneLoadbalancerDelete,
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},
"description": {
Type: schema.TypeString,
Optional: true,
},
"method": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validateMethod,
},
"datacenter": {
Type: schema.TypeString,
Optional: true,
},
"persistence": {
Type: schema.TypeBool,
Optional: true,
},
"persistence_time": {
Type: schema.TypeInt,
Optional: true,
},
"health_check_test": {
Type: schema.TypeString,
Optional: true,
},
"health_check_interval": {
Type: schema.TypeInt,
Optional: true,
},
"health_check_path": {
Type: schema.TypeString,
Optional: true,
},
"health_check_path_parser": {
Type: schema.TypeString,
Optional: true,
},
"rules": {
Type: schema.TypeList,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"protocol": {
Type: schema.TypeString,
Required: true,
},
"port_balancer": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntBetween(1, 65535),
},
"port_server": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntBetween(1, 65535),
},
"source_ip": {
Type: schema.TypeString,
Required: true,
},
"id": {
Type: schema.TypeString,
Computed: true,
},
},
},
Required: true,
},
},
}
}
func resourceOneandOneLoadbalancerCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
req := oneandone.LoadBalancerRequest{
Name: d.Get("name").(string),
Rules: getLBRules(d),
}
if raw, ok := d.GetOk("description"); ok {
req.Description = raw.(string)
}
if raw, ok := d.GetOk("datacenter"); ok {
dcs, err := config.API.ListDatacenters()
if err != nil {
return fmt.Errorf("An error occured while fetching list of datacenters %s", err)
}
decenter := raw.(string)
for _, dc := range dcs {
if strings.ToLower(dc.CountryCode) == strings.ToLower(decenter) {
req.DatacenterId = dc.Id
break
}
}
}
if raw, ok := d.GetOk("method"); ok {
req.Method = raw.(string)
}
if raw, ok := d.GetOk("persistence"); ok {
req.Persistence = oneandone.Bool2Pointer(raw.(bool))
}
if raw, ok := d.GetOk("persistence_time"); ok {
req.PersistenceTime = oneandone.Int2Pointer(raw.(int))
}
if raw, ok := d.GetOk("health_check_test"); ok {
req.HealthCheckTest = raw.(string)
}
if raw, ok := d.GetOk("health_check_interval"); ok {
req.HealthCheckInterval = oneandone.Int2Pointer(raw.(int))
}
if raw, ok := d.GetOk("health_check_path"); ok {
req.HealthCheckPath = raw.(string)
}
if raw, ok := d.GetOk("health_check_path_parser"); ok {
req.HealthCheckPathParser = raw.(string)
}
lb_id, lb, err := config.API.CreateLoadBalancer(&req)
if err != nil {
return err
}
err = config.API.WaitForState(lb, "ACTIVE", 10, config.Retries)
if err != nil {
return err
}
d.SetId(lb_id)
return resourceOneandOneLoadbalancerRead(d, meta)
}
func getLBRules(d *schema.ResourceData) []oneandone.LoadBalancerRule {
var rules []oneandone.LoadBalancerRule
if raw, ok := d.GetOk("rules"); ok {
rawRules := raw.([]interface{})
log.Println("[DEBUG] raw rules:", raw)
for _, raw := range rawRules {
rl := raw.(map[string]interface{})
rule := oneandone.LoadBalancerRule{
Protocol: rl["protocol"].(string),
}
if rl["port_balancer"] != nil {
rule.PortBalancer = uint16(rl["port_balancer"].(int))
}
if rl["port_server"] != nil {
rule.PortServer = uint16(rl["port_server"].(int))
}
if rl["source_ip"] != nil {
rule.Source = rl["source_ip"].(string)
}
rules = append(rules, rule)
}
}
return rules
}
func resourceOneandOneLoadbalancerUpdate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
if d.HasChange("name") || d.HasChange("description") || d.HasChange("method") || d.HasChange("persistence") || d.HasChange("persistence_time") || d.HasChange("health_check_test") || d.HasChange("health_check_interval") {
lb := oneandone.LoadBalancerRequest{}
if d.HasChange("name") {
_, n := d.GetChange("name")
lb.Name = n.(string)
}
if d.HasChange("description") {
_, n := d.GetChange("description")
lb.Description = n.(string)
}
if d.HasChange("method") {
_, n := d.GetChange("method")
lb.Method = (n.(string))
}
if d.HasChange("persistence") {
_, n := d.GetChange("persistence")
lb.Persistence = oneandone.Bool2Pointer(n.(bool))
}
if d.HasChange("persistence_time") {
_, n := d.GetChange("persistence_time")
lb.PersistenceTime = oneandone.Int2Pointer(n.(int))
}
if d.HasChange("health_check_test") {
_, n := d.GetChange("health_check_test")
lb.HealthCheckTest = n.(string)
}
if d.HasChange("health_check_path") {
_, n := d.GetChange("health_check_path")
lb.HealthCheckPath = n.(string)
}
if d.HasChange("health_check_path_parser") {
_, n := d.GetChange("health_check_path_parser")
lb.HealthCheckPathParser = n.(string)
}
ss, err := config.API.UpdateLoadBalancer(d.Id(), &lb)
if err != nil {
return err
}
err = config.API.WaitForState(ss, "ACTIVE", 10, 30)
if err != nil {
return err
}
}
if d.HasChange("rules") {
oldR, newR := d.GetChange("rules")
oldValues := oldR.([]interface{})
newValues := newR.([]interface{})
if len(oldValues) > len(newValues) {
diff := difference(oldValues, newValues)
for _, old := range diff {
o := old.(map[string]interface{})
if o["id"] != nil {
old_id := o["id"].(string)
fw, err := config.API.DeleteLoadBalancerRule(d.Id(), old_id)
if err != nil {
return err
}
err = config.API.WaitForState(fw, "ACTIVE", 10, config.Retries)
if err != nil {
return err
}
}
}
} else {
var rules []oneandone.LoadBalancerRule
log.Println("[DEBUG] new values:", newValues)
for _, raw := range newValues {
rl := raw.(map[string]interface{})
log.Println("[DEBUG] rl:", rl)
if rl["id"].(string) == "" {
rule := oneandone.LoadBalancerRule{
Protocol: rl["protocol"].(string),
}
rule.PortServer = uint16(rl["port_server"].(int))
rule.PortBalancer = uint16(rl["port_balancer"].(int))
rule.Source = rl["source_ip"].(string)
log.Println("[DEBUG] adding to list", rl["protocol"], rl["source_ip"], rl["port_balancer"], rl["port_server"])
log.Println("[DEBUG] adding to list", rule)
rules = append(rules, rule)
}
}
log.Println("[DEBUG] new rules:", rules)
if len(rules) > 0 {
fw, err := config.API.AddLoadBalancerRules(d.Id(), rules)
if err != nil {
return err
}
err = config.API.WaitForState(fw, "ACTIVE", 10, config.Retries)
}
}
}
return resourceOneandOneLoadbalancerRead(d, meta)
}
func resourceOneandOneLoadbalancerRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
ss, err := config.API.GetLoadBalancer(d.Id())
if err != nil {
if strings.Contains(err.Error(), "404") {
d.SetId("")
return nil
}
return err
}
d.Set("name", ss.Name)
d.Set("description", ss.Description)
d.Set("datacenter", ss.Datacenter.CountryCode)
d.Set("method", ss.Method)
d.Set("persistence", ss.Persistence)
d.Set("persistence_time", ss.PersistenceTime)
d.Set("health_check_test", ss.HealthCheckTest)
d.Set("health_check_interval", ss.HealthCheckInterval)
d.Set("rules", getLoadbalancerRules(ss.Rules))
return nil
}
func getLoadbalancerRules(rules []oneandone.LoadBalancerRule) []map[string]interface{} {
raw := make([]map[string]interface{}, 0, len(rules))
for _, rule := range rules {
toadd := map[string]interface{}{
"id": rule.Id,
"port_balancer": rule.PortBalancer,
"port_server": rule.PortServer,
"protocol": rule.Protocol,
"source_ip": rule.Source,
}
raw = append(raw, toadd)
}
return raw
}
func resourceOneandOneLoadbalancerDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
lb, err := config.API.DeleteLoadBalancer(d.Id())
if err != nil {
return err
}
err = config.API.WaitUntilDeleted(lb)
if err != nil {
return err
}
return nil
}
func validateMethod(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if value != "ROUND_ROBIN" && value != "LEAST_CONNECTIONS" {
errors = append(errors, fmt.Errorf("%q value sholud be either 'ROUND_ROBIN' or 'LEAST_CONNECTIONS' not %q", k, value))
}
return
}

View File

@ -0,0 +1,156 @@
package oneandone
import (
"fmt"
"testing"
"github.com/1and1/oneandone-cloudserver-sdk-go"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"os"
"time"
)
func TestAccOneandoneLoadbalancer_Basic(t *testing.T) {
var lb oneandone.LoadBalancer
name := "test_loadbalancer"
name_updated := "test_loadbalancer_renamed"
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Providers: testAccProviders,
CheckDestroy: testAccCheckDOneandoneLoadbalancerDestroyCheck,
Steps: []resource.TestStep{
resource.TestStep{
Config: fmt.Sprintf(testAccCheckOneandoneLoadbalancer_basic, name),
Check: resource.ComposeTestCheckFunc(
func(*terraform.State) error {
time.Sleep(10 * time.Second)
return nil
},
testAccCheckOneandoneLoadbalancerExists("oneandone_loadbalancer.lb", &lb),
testAccCheckOneandoneLoadbalancerAttributes("oneandone_loadbalancer.lb", name),
resource.TestCheckResourceAttr("oneandone_loadbalancer.lb", "name", name),
),
},
resource.TestStep{
Config: fmt.Sprintf(testAccCheckOneandoneLoadbalancer_update, name_updated),
Check: resource.ComposeTestCheckFunc(
func(*terraform.State) error {
time.Sleep(10 * time.Second)
return nil
},
testAccCheckOneandoneLoadbalancerExists("oneandone_loadbalancer.lb", &lb),
testAccCheckOneandoneLoadbalancerAttributes("oneandone_loadbalancer.lb", name_updated),
resource.TestCheckResourceAttr("oneandone_loadbalancer.lb", "name", name_updated),
),
},
},
})
}
func testAccCheckDOneandoneLoadbalancerDestroyCheck(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "oneandone_loadbalancer.lb" {
continue
}
api := oneandone.New(os.Getenv("ONEANDONE_TOKEN"), oneandone.BaseUrl)
_, err := api.GetLoadBalancer(rs.Primary.ID)
if err == nil {
return fmt.Errorf("Loadbalancer still exists %s %s", rs.Primary.ID, err.Error())
}
}
return nil
}
func testAccCheckOneandoneLoadbalancerAttributes(n string, reverse_dns 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.Attributes["name"] != reverse_dns {
return fmt.Errorf("Bad name: expected %s : found %s ", reverse_dns, rs.Primary.Attributes["name"])
}
return nil
}
}
func testAccCheckOneandoneLoadbalancerExists(n string, fw_p *oneandone.LoadBalancer) 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 Record ID is set")
}
api := oneandone.New(os.Getenv("ONEANDONE_TOKEN"), oneandone.BaseUrl)
found_fw, err := api.GetLoadBalancer(rs.Primary.ID)
if err != nil {
return fmt.Errorf("Error occured while fetching Loadbalancer: %s", rs.Primary.ID)
}
if found_fw.Id != rs.Primary.ID {
return fmt.Errorf("Record not found")
}
fw_p = found_fw
return nil
}
}
const testAccCheckOneandoneLoadbalancer_basic = `
resource "oneandone_loadbalancer" "lb" {
name = "%s"
method = "ROUND_ROBIN"
persistence = true
persistence_time = 60
health_check_test = "TCP"
health_check_interval = 300
datacenter = "US"
rules = [
{
protocol = "TCP"
port_balancer = 8080
port_server = 8089
source_ip = "0.0.0.0"
},
{
protocol = "TCP"
port_balancer = 9090
port_server = 9099
source_ip = "0.0.0.0"
}
]
}`
const testAccCheckOneandoneLoadbalancer_update = `
resource "oneandone_loadbalancer" "lb" {
name = "%s"
method = "ROUND_ROBIN"
persistence = true
persistence_time = 60
health_check_test = "TCP"
health_check_interval = 300
datacenter = "US"
rules = [
{
protocol = "TCP"
port_balancer = 8080
port_server = 8089
source_ip = "0.0.0.0"
}
]
}`

View File

@ -0,0 +1,706 @@
package oneandone
import (
"github.com/1and1/oneandone-cloudserver-sdk-go"
"github.com/hashicorp/terraform/helper/schema"
"strings"
)
func resourceOneandOneMonitoringPolicy() *schema.Resource {
return &schema.Resource{
Create: resourceOneandOneMonitoringPolicyCreate,
Read: resourceOneandOneMonitoringPolicyRead,
Update: resourceOneandOneMonitoringPolicyUpdate,
Delete: resourceOneandOneMonitoringPolicyDelete,
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},
"description": {
Type: schema.TypeString,
Optional: true,
},
"email": {
Type: schema.TypeString,
Optional: true,
},
"agent": {
Type: schema.TypeBool,
Required: true,
},
"thresholds": {
Type: schema.TypeSet,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"cpu": {
Type: schema.TypeSet,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"warning": {
Type: schema.TypeSet,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"value": {
Type: schema.TypeInt,
Required: true,
},
"alert": {
Type: schema.TypeBool,
Required: true,
},
},
},
Required: true,
},
"critical": {
Type: schema.TypeSet,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"value": {
Type: schema.TypeInt,
Required: true,
},
"alert": {
Type: schema.TypeBool,
Required: true,
},
},
},
Required: true,
},
},
},
Required: true,
},
"ram": {
Type: schema.TypeSet,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"warning": {
Type: schema.TypeSet,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"value": {
Type: schema.TypeInt,
Required: true,
},
"alert": {
Type: schema.TypeBool,
Required: true,
},
},
},
Required: true,
},
"critical": {
Type: schema.TypeSet,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"value": {
Type: schema.TypeInt,
Required: true,
},
"alert": {
Type: schema.TypeBool,
Required: true,
},
},
},
Required: true,
},
},
},
Required: true,
},
"disk": {
Type: schema.TypeSet,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"warning": {
Type: schema.TypeSet,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"value": {
Type: schema.TypeInt,
Required: true,
},
"alert": {
Type: schema.TypeBool,
Required: true,
},
},
},
Required: true,
},
"critical": {
Type: schema.TypeSet,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"value": {
Type: schema.TypeInt,
Required: true,
},
"alert": {
Type: schema.TypeBool,
Required: true,
},
},
},
Required: true,
},
},
},
Required: true,
},
"transfer": {
Type: schema.TypeSet,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"warning": {
Type: schema.TypeSet,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"value": {
Type: schema.TypeInt,
Required: true,
},
"alert": {
Type: schema.TypeBool,
Required: true,
},
},
},
Required: true,
},
"critical": {
Type: schema.TypeSet,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"value": {
Type: schema.TypeInt,
Required: true,
},
"alert": {
Type: schema.TypeBool,
Required: true,
},
},
},
Required: true,
},
},
},
Required: true,
},
"internal_ping": {
Type: schema.TypeSet,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"warning": {
Type: schema.TypeSet,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"value": {
Type: schema.TypeInt,
Required: true,
},
"alert": {
Type: schema.TypeBool,
Required: true,
},
},
},
Required: true,
},
"critical": {
Type: schema.TypeSet,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"value": {
Type: schema.TypeInt,
Required: true,
},
"alert": {
Type: schema.TypeBool,
Required: true,
},
},
},
Required: true,
},
},
},
Required: true,
},
},
},
Required: true,
},
"ports": {
Type: schema.TypeList,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"email_notification": {
Type: schema.TypeBool,
Required: true,
},
"port": {
Type: schema.TypeInt,
Required: true,
},
"protocol": {
Type: schema.TypeString,
Optional: true,
},
"alert_if": {
Type: schema.TypeString,
Optional: true,
},
"id": {
Type: schema.TypeString,
Computed: true,
},
},
},
Optional: true,
},
"processes": {
Type: schema.TypeList,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"email_notification": {
Type: schema.TypeBool,
Required: true,
},
"process": {
Type: schema.TypeString,
Required: true,
},
"alert_if": {
Type: schema.TypeString,
Optional: true,
},
"id": {
Type: schema.TypeString,
Computed: true,
},
},
},
Optional: true,
},
},
}
}
func resourceOneandOneMonitoringPolicyCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
mp_request := oneandone.MonitoringPolicy{
Name: d.Get("name").(string),
Agent: d.Get("agent").(bool),
Thresholds: getThresholds(d.Get("thresholds")),
}
if raw, ok := d.GetOk("ports"); ok {
mp_request.Ports = getPorts(raw)
}
if raw, ok := d.GetOk("processes"); ok {
mp_request.Processes = getProcesses(raw)
}
mp_id, mp, err := config.API.CreateMonitoringPolicy(&mp_request)
if err != nil {
return err
}
err = config.API.WaitForState(mp, "ACTIVE", 30, config.Retries)
if err != nil {
return err
}
d.SetId(mp_id)
return resourceOneandOneMonitoringPolicyRead(d, meta)
}
func resourceOneandOneMonitoringPolicyUpdate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
req := oneandone.MonitoringPolicy{}
if d.HasChange("name") {
_, n := d.GetChange("name")
req.Name = n.(string)
}
if d.HasChange("description") {
_, n := d.GetChange("description")
req.Description = n.(string)
}
if d.HasChange("email") {
_, n := d.GetChange("email")
req.Email = n.(string)
}
if d.HasChange("agent") {
_, n := d.GetChange("agent")
req.Agent = n.(bool)
}
if d.HasChange("thresholds") {
_, n := d.GetChange("thresholds")
req.Thresholds = getThresholds(n)
}
mp, err := config.API.UpdateMonitoringPolicy(d.Id(), &req)
if err != nil {
return err
}
err = config.API.WaitForState(mp, "ACTIVE", 30, config.Retries)
if err != nil {
return err
}
if d.HasChange("ports") {
o, n := d.GetChange("ports")
oldValues := o.([]interface{})
newValues := n.([]interface{})
if len(newValues) > len(oldValues) {
ports := getPorts(newValues)
newports := []oneandone.MonitoringPort{}
for _, p := range ports {
if p.Id == "" {
newports = append(newports, p)
}
}
mp, err := config.API.AddMonitoringPolicyPorts(d.Id(), newports)
if err != nil {
return err
}
err = config.API.WaitForState(mp, "ACTIVE", 30, config.Retries)
if err != nil {
return err
}
} else if len(oldValues) > len(newValues) {
diff := difference(oldValues, newValues)
ports := getPorts(diff)
for _, port := range ports {
if port.Id == "" {
continue
}
mp, err := config.API.DeleteMonitoringPolicyPort(d.Id(), port.Id)
if err != nil {
return err
}
err = config.API.WaitForState(mp, "ACTIVE", 30, config.Retries)
if err != nil {
return err
}
}
} else if len(oldValues) == len(newValues) {
ports := getPorts(newValues)
for _, port := range ports {
mp, err := config.API.ModifyMonitoringPolicyPort(d.Id(), port.Id, &port)
if err != nil {
return err
}
err = config.API.WaitForState(mp, "ACTIVE", 30, config.Retries)
if err != nil {
return err
}
}
}
}
if d.HasChange("processes") {
o, n := d.GetChange("processes")
oldValues := o.([]interface{})
newValues := n.([]interface{})
if len(newValues) > len(oldValues) {
processes := getProcesses(newValues)
newprocesses := []oneandone.MonitoringProcess{}
for _, p := range processes {
if p.Id == "" {
newprocesses = append(newprocesses, p)
}
}
mp, err := config.API.AddMonitoringPolicyProcesses(d.Id(), newprocesses)
if err != nil {
return err
}
err = config.API.WaitForState(mp, "ACTIVE", 30, config.Retries)
if err != nil {
return err
}
} else if len(oldValues) > len(newValues) {
diff := difference(oldValues, newValues)
processes := getProcesses(diff)
for _, process := range processes {
if process.Id == "" {
continue
}
mp, err := config.API.DeleteMonitoringPolicyProcess(d.Id(), process.Id)
if err != nil {
return err
}
err = config.API.WaitForState(mp, "ACTIVE", 30, config.Retries)
if err != nil {
return err
}
}
} else if len(oldValues) == len(newValues) {
processes := getProcesses(newValues)
for _, process := range processes {
mp, err := config.API.ModifyMonitoringPolicyProcess(d.Id(), process.Id, &process)
if err != nil {
return err
}
err = config.API.WaitForState(mp, "ACTIVE", 30, config.Retries)
if err != nil {
return err
}
}
}
}
return resourceOneandOneMonitoringPolicyRead(d, meta)
}
func resourceOneandOneMonitoringPolicyRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
mp, err := config.API.GetMonitoringPolicy(d.Id())
if err != nil {
if strings.Contains(err.Error(), "404") {
d.SetId("")
return nil
}
return err
}
if len(mp.Servers) > 0 {
}
if len(mp.Ports) > 0 {
pports := d.Get("ports").([]interface{})
for i, raw_ports := range pports {
port := raw_ports.(map[string]interface{})
port["id"] = mp.Ports[i].Id
}
d.Set("ports", pports)
}
if len(mp.Processes) > 0 {
pprocesses := d.Get("processes").([]interface{})
for i, raw_processes := range pprocesses {
process := raw_processes.(map[string]interface{})
process["id"] = mp.Processes[i].Id
}
d.Set("processes", pprocesses)
}
return nil
}
func resourceOneandOneMonitoringPolicyDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
mp, err := config.API.DeleteMonitoringPolicy(d.Id())
if err != nil {
return err
}
err = config.API.WaitUntilDeleted(mp)
if err != nil {
return err
}
return nil
}
func getThresholds(d interface{}) *oneandone.MonitoringThreshold {
raw_thresholds := d.(*schema.Set).List()
toReturn := &oneandone.MonitoringThreshold{}
for _, thresholds := range raw_thresholds {
th_set := thresholds.(map[string]interface{})
//CPU
cpu_raw := th_set["cpu"].(*schema.Set)
toReturn.Cpu = &oneandone.MonitoringLevel{}
for _, c := range cpu_raw.List() {
int_k := c.(map[string]interface{})
for _, w := range int_k["warning"].(*schema.Set).List() {
toReturn.Cpu.Warning = &oneandone.MonitoringValue{
Value: w.(map[string]interface{})["value"].(int),
Alert: w.(map[string]interface{})["alert"].(bool),
}
}
for _, c := range int_k["critical"].(*schema.Set).List() {
toReturn.Cpu.Critical = &oneandone.MonitoringValue{
Value: c.(map[string]interface{})["value"].(int),
Alert: c.(map[string]interface{})["alert"].(bool),
}
}
}
//RAM
ram_raw := th_set["ram"].(*schema.Set)
toReturn.Ram = &oneandone.MonitoringLevel{}
for _, c := range ram_raw.List() {
int_k := c.(map[string]interface{})
for _, w := range int_k["warning"].(*schema.Set).List() {
toReturn.Ram.Warning = &oneandone.MonitoringValue{
Value: w.(map[string]interface{})["value"].(int),
Alert: w.(map[string]interface{})["alert"].(bool),
}
}
for _, c := range int_k["critical"].(*schema.Set).List() {
toReturn.Ram.Critical = &oneandone.MonitoringValue{
Value: c.(map[string]interface{})["value"].(int),
Alert: c.(map[string]interface{})["alert"].(bool),
}
}
}
//DISK
disk_raw := th_set["disk"].(*schema.Set)
toReturn.Disk = &oneandone.MonitoringLevel{}
for _, c := range disk_raw.List() {
int_k := c.(map[string]interface{})
for _, w := range int_k["warning"].(*schema.Set).List() {
toReturn.Disk.Warning = &oneandone.MonitoringValue{
Value: w.(map[string]interface{})["value"].(int),
Alert: w.(map[string]interface{})["alert"].(bool),
}
}
for _, c := range int_k["critical"].(*schema.Set).List() {
toReturn.Disk.Critical = &oneandone.MonitoringValue{
Value: c.(map[string]interface{})["value"].(int),
Alert: c.(map[string]interface{})["alert"].(bool),
}
}
}
//TRANSFER
transfer_raw := th_set["transfer"].(*schema.Set)
toReturn.Transfer = &oneandone.MonitoringLevel{}
for _, c := range transfer_raw.List() {
int_k := c.(map[string]interface{})
for _, w := range int_k["warning"].(*schema.Set).List() {
toReturn.Transfer.Warning = &oneandone.MonitoringValue{
Value: w.(map[string]interface{})["value"].(int),
Alert: w.(map[string]interface{})["alert"].(bool),
}
}
for _, c := range int_k["critical"].(*schema.Set).List() {
toReturn.Transfer.Critical = &oneandone.MonitoringValue{
Value: c.(map[string]interface{})["value"].(int),
Alert: c.(map[string]interface{})["alert"].(bool),
}
}
}
//internal ping
ping_raw := th_set["internal_ping"].(*schema.Set)
toReturn.InternalPing = &oneandone.MonitoringLevel{}
for _, c := range ping_raw.List() {
int_k := c.(map[string]interface{})
for _, w := range int_k["warning"].(*schema.Set).List() {
toReturn.InternalPing.Warning = &oneandone.MonitoringValue{
Value: w.(map[string]interface{})["value"].(int),
Alert: w.(map[string]interface{})["alert"].(bool),
}
}
for _, c := range int_k["critical"].(*schema.Set).List() {
toReturn.InternalPing.Critical = &oneandone.MonitoringValue{
Value: c.(map[string]interface{})["value"].(int),
Alert: c.(map[string]interface{})["alert"].(bool),
}
}
}
}
return toReturn
}
func getProcesses(d interface{}) []oneandone.MonitoringProcess {
toReturn := []oneandone.MonitoringProcess{}
for _, raw := range d.([]interface{}) {
port := raw.(map[string]interface{})
m_port := oneandone.MonitoringProcess{
EmailNotification: port["email_notification"].(bool),
}
if port["id"] != nil {
m_port.Id = port["id"].(string)
}
if port["process"] != nil {
m_port.Process = port["process"].(string)
}
if port["alert_if"] != nil {
m_port.AlertIf = port["alert_if"].(string)
}
toReturn = append(toReturn, m_port)
}
return toReturn
}
func getPorts(d interface{}) []oneandone.MonitoringPort {
toReturn := []oneandone.MonitoringPort{}
for _, raw := range d.([]interface{}) {
port := raw.(map[string]interface{})
m_port := oneandone.MonitoringPort{
EmailNotification: port["email_notification"].(bool),
Port: port["port"].(int),
}
if port["id"] != nil {
m_port.Id = port["id"].(string)
}
if port["protocol"] != nil {
m_port.Protocol = port["protocol"].(string)
}
if port["alert_if"] != nil {
m_port.AlertIf = port["alert_if"].(string)
}
toReturn = append(toReturn, m_port)
}
return toReturn
}

View File

@ -0,0 +1,212 @@
package oneandone
import (
"fmt"
"testing"
"github.com/1and1/oneandone-cloudserver-sdk-go"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"os"
"time"
)
func TestAccOneandoneMonitoringPolicy_Basic(t *testing.T) {
var mp oneandone.MonitoringPolicy
name := "test"
name_updated := "test1"
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Providers: testAccProviders,
CheckDestroy: testAccCheckDOneandoneMonitoringPolicyDestroyCheck,
Steps: []resource.TestStep{
resource.TestStep{
Config: fmt.Sprintf(testAccCheckOneandoneMonitoringPolicy_basic, name),
Check: resource.ComposeTestCheckFunc(
func(*terraform.State) error {
time.Sleep(10 * time.Second)
return nil
},
testAccCheckOneandoneMonitoringPolicyExists("oneandone_monitoring_policy.mp", &mp),
testAccCheckOneandoneMonitoringPolicyAttributes("oneandone_monitoring_policy.mp", name),
resource.TestCheckResourceAttr("oneandone_monitoring_policy.mp", "name", name),
),
},
resource.TestStep{
Config: fmt.Sprintf(testAccCheckOneandoneMonitoringPolicy_basic, name_updated),
Check: resource.ComposeTestCheckFunc(
func(*terraform.State) error {
time.Sleep(10 * time.Second)
return nil
},
testAccCheckOneandoneMonitoringPolicyExists("oneandone_monitoring_policy.mp", &mp),
testAccCheckOneandoneMonitoringPolicyAttributes("oneandone_monitoring_policy.mp", name_updated),
resource.TestCheckResourceAttr("oneandone_monitoring_policy.mp", "name", name_updated),
),
},
},
})
}
func testAccCheckDOneandoneMonitoringPolicyDestroyCheck(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "oneandone_monitoring_policy.mp" {
continue
}
api := oneandone.New(os.Getenv("ONEANDONE_TOKEN"), oneandone.BaseUrl)
_, err := api.GetMonitoringPolicy(rs.Primary.ID)
if err == nil {
return fmt.Errorf("MonitoringPolicy still exists %s %s", rs.Primary.ID, err.Error())
}
}
return nil
}
func testAccCheckOneandoneMonitoringPolicyAttributes(n string, reverse_dns 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.Attributes["name"] != reverse_dns {
return fmt.Errorf("Bad name: expected %s : found %s ", reverse_dns, rs.Primary.Attributes["name"])
}
return nil
}
}
func testAccCheckOneandoneMonitoringPolicyExists(n string, fw_p *oneandone.MonitoringPolicy) 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 Record ID is set")
}
api := oneandone.New(os.Getenv("ONEANDONE_TOKEN"), oneandone.BaseUrl)
found_fw, err := api.GetMonitoringPolicy(rs.Primary.ID)
if err != nil {
return fmt.Errorf("Error occured while fetching MonitoringPolicy: %s", rs.Primary.ID)
}
if found_fw.Id != rs.Primary.ID {
return fmt.Errorf("Record not found")
}
fw_p = found_fw
return nil
}
}
const testAccCheckOneandoneMonitoringPolicy_basic = `
resource "oneandone_monitoring_policy" "mp" {
name = "%s"
agent = true
email = "email@address.com"
thresholds = {
cpu = {
warning = {
value = 50,
alert = false
}
critical = {
value = 66,
alert = false
}
}
ram = {
warning = {
value = 70,
alert = true
}
critical = {
value = 80,
alert = true
}
},
ram = {
warning = {
value = 85,
alert = true
}
critical = {
value = 95,
alert = true
}
},
disk = {
warning = {
value = 84,
alert = true
}
critical = {
value = 94,
alert = true
}
},
transfer = {
warning = {
value = 1000,
alert = true
}
critical = {
value = 2000,
alert = true
}
},
internal_ping = {
warning = {
value = 3000,
alert = true
}
critical = {
value = 4000,
alert = true
}
}
}
ports = [
{
email_notification = true
port = 443
protocol = "TCP"
alert_if = "NOT_RESPONDING"
},
{
email_notification = false
port = 80
protocol = "TCP"
alert_if = "NOT_RESPONDING"
},
{
email_notification = true
port = 21
protocol = "TCP"
alert_if = "NOT_RESPONDING"
}
]
processes = [
{
email_notification = false
process = "httpdeamon"
alert_if = "RUNNING"
},
{
process = "iexplorer",
alert_if = "NOT_RUNNING"
email_notification = true
}]
}`

View File

@ -0,0 +1,291 @@
package oneandone
import (
"fmt"
"github.com/1and1/oneandone-cloudserver-sdk-go"
"github.com/hashicorp/terraform/helper/schema"
"strings"
)
func resourceOneandOnePrivateNetwork() *schema.Resource {
return &schema.Resource{
Create: resourceOneandOnePrivateNetworkCreate,
Read: resourceOneandOnePrivateNetworkRead,
Update: resourceOneandOnePrivateNetworkUpdate,
Delete: resourceOneandOnePrivateNetworkDelete,
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},
"description": {
Type: schema.TypeString,
Optional: true,
},
"datacenter": {
Type: schema.TypeString,
Optional: true,
},
"network_address": {
Type: schema.TypeString,
Optional: true,
},
"subnet_mask": {
Type: schema.TypeString,
Optional: true,
},
"server_ids": {
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
},
}
}
func resourceOneandOnePrivateNetworkCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
req := oneandone.PrivateNetworkRequest{
Name: d.Get("name").(string),
}
if raw, ok := d.GetOk("description"); ok {
req.Description = raw.(string)
}
if raw, ok := d.GetOk("network_address"); ok {
req.NetworkAddress = raw.(string)
}
if raw, ok := d.GetOk("subnet_mask"); ok {
req.SubnetMask = raw.(string)
}
if raw, ok := d.GetOk("datacenter"); ok {
dcs, err := config.API.ListDatacenters()
if err != nil {
return fmt.Errorf("An error occured while fetching list of datacenters %s", err)
}
decenter := raw.(string)
for _, dc := range dcs {
if strings.ToLower(dc.CountryCode) == strings.ToLower(decenter) {
req.DatacenterId = dc.Id
break
}
}
}
prn_id, prn, err := config.API.CreatePrivateNetwork(&req)
if err != nil {
return err
}
err = config.API.WaitForState(prn, "ACTIVE", 30, config.Retries)
if err != nil {
return err
}
d.SetId(prn_id)
var ids []string
if raw, ok := d.GetOk("server_ids"); ok {
rawIps := raw.(*schema.Set).List()
for _, raw := range rawIps {
ids = append(ids, raw.(string))
server, err := config.API.ShutdownServer(raw.(string), false)
if err != nil {
return err
}
err = config.API.WaitForState(server, "POWERED_OFF", 10, config.Retries)
if err != nil {
return err
}
}
}
prn, err = config.API.AttachPrivateNetworkServers(d.Id(), ids)
if err != nil {
return err
}
err = config.API.WaitForState(prn, "ACTIVE", 30, config.Retries)
if err != nil {
return err
}
for _, id := range ids {
server, err := config.API.StartServer(id)
if err != nil {
return err
}
err = config.API.WaitForState(server, "POWERED_ON", 10, config.Retries)
if err != nil {
return err
}
}
return resourceOneandOnePrivateNetworkRead(d, meta)
}
func resourceOneandOnePrivateNetworkUpdate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
if d.HasChange("name") || d.HasChange("description") || d.HasChange("network_address") || d.HasChange("subnet_mask") {
pnset := oneandone.PrivateNetworkRequest{}
pnset.Name = d.Get("name").(string)
pnset.Description = d.Get("description").(string)
pnset.NetworkAddress = d.Get("network_address").(string)
pnset.SubnetMask = d.Get("subnet_mask").(string)
prn, err := config.API.UpdatePrivateNetwork(d.Id(), &pnset)
if err != nil {
return err
}
err = config.API.WaitForState(prn, "ACTIVE", 30, config.Retries)
if err != nil {
return err
}
}
if d.HasChange("server_ids") {
o, n := d.GetChange("server_ids")
newValues := n.(*schema.Set).List()
oldValues := o.(*schema.Set).List()
var ids []string
for _, newV := range oldValues {
ids = append(ids, newV.(string))
}
for _, id := range ids {
server, err := config.API.ShutdownServer(id, false)
if err != nil {
return err
}
err = config.API.WaitForState(server, "POWERED_OFF", 10, config.Retries)
if err != nil {
return err
}
_, err = config.API.RemoveServerPrivateNetwork(id, d.Id())
if err != nil {
return err
}
prn, _ := config.API.GetPrivateNetwork(d.Id())
err = config.API.WaitForState(prn, "ACTIVE", 10, config.Retries)
if err != nil {
return err
}
}
var newids []string
for _, newV := range newValues {
newids = append(newids, newV.(string))
}
pn, err := config.API.AttachPrivateNetworkServers(d.Id(), newids)
if err != nil {
return err
}
err = config.API.WaitForState(pn, "ACTIVE", 30, config.Retries)
if err != nil {
return err
}
for _, id := range newids {
server, err := config.API.StartServer(id)
if err != nil {
return err
}
err = config.API.WaitForState(server, "POWERED_ON", 10, config.Retries)
if err != nil {
return err
}
}
}
return resourceOneandOnePrivateNetworkRead(d, meta)
}
func resourceOneandOnePrivateNetworkRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
pn, err := config.API.GetPrivateNetwork(d.Id())
if err != nil {
if strings.Contains(err.Error(), "404") {
d.SetId("")
return nil
}
return err
}
d.Set("name", pn.Name)
d.Set("description", pn.Description)
d.Set("network_address", pn.NetworkAddress)
d.Set("subnet_mask", pn.SubnetMask)
d.Set("datacenter", pn.Datacenter.CountryCode)
var toAdd []string
for _, s := range pn.Servers {
toAdd = append(toAdd, s.Id)
}
d.Set("server_ids", toAdd)
return nil
}
func resourceOneandOnePrivateNetworkDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
pn, err := config.API.GetPrivateNetwork(d.Id())
for _, server := range pn.Servers {
srv, err := config.API.ShutdownServer(server.Id, false)
if err != nil {
return err
}
err = config.API.WaitForState(srv, "POWERED_OFF", 10, config.Retries)
if err != nil {
return err
}
}
pn, err = config.API.DeletePrivateNetwork(d.Id())
if err != nil {
return err
}
err = config.API.WaitUntilDeleted(pn)
if err != nil {
return err
}
for _, server := range pn.Servers {
srv, err := config.API.StartServer(server.Id)
if err != nil {
return err
}
err = config.API.WaitForState(srv, "POWERED_ON", 10, config.Retries)
if err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,160 @@
package oneandone
import (
"fmt"
"testing"
"github.com/1and1/oneandone-cloudserver-sdk-go"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"os"
"time"
)
func TestAccOneandonePrivateNetwork_Basic(t *testing.T) {
var net oneandone.PrivateNetwork
name := "test"
name_updated := "test1"
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Providers: testAccProviders,
CheckDestroy: testAccCheckOneandonePrivateNetworkDestroyCheck,
Steps: []resource.TestStep{
resource.TestStep{
Config: fmt.Sprintf(testAccCheckOneandonePrivateNetwork_basic, name),
Check: resource.ComposeTestCheckFunc(
func(*terraform.State) error {
time.Sleep(10 * time.Second)
return nil
},
testAccCheckOneandonePrivateNetworkExists("oneandone_private_network.pn", &net),
testAccCheckOneandonePrivateNetworkAttributes("oneandone_private_network.pn", name),
resource.TestCheckResourceAttr("oneandone_private_network.pn", "name", name),
),
},
resource.TestStep{
Config: fmt.Sprintf(testAccCheckOneandonePrivateNetwork_basic, name_updated),
Check: resource.ComposeTestCheckFunc(
func(*terraform.State) error {
time.Sleep(10 * time.Second)
return nil
},
testAccCheckOneandonePrivateNetworkExists("oneandone_private_network.pn", &net),
testAccCheckOneandonePrivateNetworkAttributes("oneandone_private_network.pn", name_updated),
resource.TestCheckResourceAttr("oneandone_private_network.pn", "name", name_updated),
),
},
},
})
}
func testAccCheckOneandonePrivateNetworkDestroyCheck(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "oneandone_private_network" {
continue
}
api := oneandone.New(os.Getenv("ONEANDONE_TOKEN"), oneandone.BaseUrl)
_, err := api.GetPrivateNetwork(rs.Primary.ID)
if err == nil {
return fmt.Errorf("PrivateNetwork still exists %s %s", rs.Primary.ID, err.Error())
}
}
return nil
}
func testAccCheckOneandonePrivateNetworkAttributes(n string, reverse_dns 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.Attributes["name"] != reverse_dns {
return fmt.Errorf("Bad name: expected %s : found %s ", reverse_dns, rs.Primary.Attributes["name"])
}
return nil
}
}
func testAccCheckOneandonePrivateNetworkExists(n string, server *oneandone.PrivateNetwork) 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 Record ID is set")
}
api := oneandone.New(os.Getenv("ONEANDONE_TOKEN"), oneandone.BaseUrl)
found_server, err := api.GetPrivateNetwork(rs.Primary.ID)
if err != nil {
return fmt.Errorf("Error occured while fetching PrivateNetwork: %s", rs.Primary.ID)
}
if found_server.Id != rs.Primary.ID {
return fmt.Errorf("Record not found")
}
server = found_server
return nil
}
}
const testAccCheckOneandonePrivateNetwork_basic = `
resource "oneandone_server" "server1" {
name = "server_private_net_01"
description = "ttt"
image = "CoreOS_Stable_64std"
datacenter = "US"
vcores = 1
cores_per_processor = 1
ram = 2
password = "Kv40kd8PQb"
hdds = [
{
disk_size = 60
is_main = true
}
]
}
resource "oneandone_server" "server2" {
name = "server_private_net_02"
description = "ttt"
image = "CoreOS_Stable_64std"
datacenter = "US"
vcores = 1
cores_per_processor = 1
ram = 2
password = "${oneandone_server.server1.password}"
hdds = [
{
disk_size = 60
is_main = true
}
]
}
resource "oneandone_private_network" "pn" {
name = "%s",
description = "new private net"
datacenter = "US"
network_address = "192.168.7.0"
subnet_mask = "255.255.255.0"
server_ids = [
"${oneandone_server.server1.id}",
"${oneandone_server.server2.id}"
]
}
`

View File

@ -0,0 +1,133 @@
package oneandone
import (
"fmt"
"github.com/hashicorp/terraform/helper/schema"
"strings"
)
func resourceOneandOnePublicIp() *schema.Resource {
return &schema.Resource{
Create: resourceOneandOnePublicIpCreate,
Read: resourceOneandOnePublicIpRead,
Update: resourceOneandOnePublicIpUpdate,
Delete: resourceOneandOnePublicIpDelete,
Schema: map[string]*schema.Schema{
"ip_type": { //IPV4 or IPV6
Type: schema.TypeString,
Required: true,
},
"reverse_dns": {
Type: schema.TypeString,
Optional: true,
},
"datacenter": {
Type: schema.TypeString,
Optional: true,
},
"ip_address": {
Type: schema.TypeString,
Computed: true,
},
},
}
}
func resourceOneandOnePublicIpCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
var reverse_dns string
var datacenter_id string
if raw, ok := d.GetOk("reverse_dns"); ok {
reverse_dns = raw.(string)
}
if raw, ok := d.GetOk("datacenter"); ok {
dcs, err := config.API.ListDatacenters()
if err != nil {
return fmt.Errorf("An error occured while fetching list of datacenters %s", err)
}
decenter := raw.(string)
for _, dc := range dcs {
if strings.ToLower(dc.CountryCode) == strings.ToLower(decenter) {
datacenter_id = dc.Id
break
}
}
}
ip_id, ip, err := config.API.CreatePublicIp(d.Get("ip_type").(string), reverse_dns, datacenter_id)
if err != nil {
return err
}
err = config.API.WaitForState(ip, "ACTIVE", 10, config.Retries)
if err != nil {
return err
}
d.SetId(ip_id)
return resourceOneandOnePublicIpRead(d, meta)
}
func resourceOneandOnePublicIpRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
ip, err := config.API.GetPublicIp(d.Id())
if err != nil {
if strings.Contains(err.Error(), "404") {
d.SetId("")
return nil
}
return err
}
d.Set("ip_address", ip.IpAddress)
d.Set("revers_dns", ip.ReverseDns)
d.Set("datacenter", ip.Datacenter.CountryCode)
d.Set("ip_type", ip.Type)
return nil
}
func resourceOneandOnePublicIpUpdate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
if d.HasChange("reverse_dns") {
_, n := d.GetChange("reverse_dns")
ip, err := config.API.UpdatePublicIp(d.Id(), n.(string))
if err != nil {
return err
}
err = config.API.WaitForState(ip, "ACTIVE", 10, config.Retries)
if err != nil {
return err
}
}
return resourceOneandOnePublicIpRead(d, meta)
}
func resourceOneandOnePublicIpDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
ip, err := config.API.DeletePublicIp(d.Id())
if err != nil {
return err
}
err = config.API.WaitUntilDeleted(ip)
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,119 @@
package oneandone
import (
"fmt"
"testing"
"github.com/1and1/oneandone-cloudserver-sdk-go"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"os"
"time"
)
func TestAccOneandonePublicIp_Basic(t *testing.T) {
var public_ip oneandone.PublicIp
reverse_dns := "example.de"
reverse_dns_updated := "example.ba"
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Providers: testAccProviders,
CheckDestroy: testAccCheckDOneandonePublicIpDestroyCheck,
Steps: []resource.TestStep{
resource.TestStep{
Config: fmt.Sprintf(testAccCheckOneandonePublicIp_basic, reverse_dns),
Check: resource.ComposeTestCheckFunc(
func(*terraform.State) error {
time.Sleep(10 * time.Second)
return nil
},
testAccCheckOneandonePublicIpExists("oneandone_public_ip.ip", &public_ip),
testAccCheckOneandonePublicIpAttributes("oneandone_public_ip.ip", reverse_dns),
resource.TestCheckResourceAttr("oneandone_public_ip.ip", "reverse_dns", reverse_dns),
),
},
resource.TestStep{
Config: fmt.Sprintf(testAccCheckOneandonePublicIp_basic, reverse_dns_updated),
Check: resource.ComposeTestCheckFunc(
func(*terraform.State) error {
time.Sleep(10 * time.Second)
return nil
},
testAccCheckOneandonePublicIpExists("oneandone_public_ip.ip", &public_ip),
testAccCheckOneandonePublicIpAttributes("oneandone_public_ip.ip", reverse_dns_updated),
resource.TestCheckResourceAttr("oneandone_public_ip.ip", "reverse_dns", reverse_dns_updated),
),
},
},
})
}
func testAccCheckDOneandonePublicIpDestroyCheck(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "oneandone_public_ip" {
continue
}
api := oneandone.New(os.Getenv("ONEANDONE_TOKEN"), oneandone.BaseUrl)
_, err := api.GetPublicIp(rs.Primary.ID)
if err == nil {
return fmt.Errorf("Public IP still exists %s %s", rs.Primary.ID, err.Error())
}
}
return nil
}
func testAccCheckOneandonePublicIpAttributes(n string, reverse_dns 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.Attributes["reverse_dns"] != reverse_dns {
return fmt.Errorf("Bad name: expected %s : found %s ", reverse_dns, rs.Primary.Attributes["name"])
}
return nil
}
}
func testAccCheckOneandonePublicIpExists(n string, public_ip *oneandone.PublicIp) 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 Record ID is set")
}
api := oneandone.New(os.Getenv("ONEANDONE_TOKEN"), oneandone.BaseUrl)
found_public_ip, err := api.GetPublicIp(rs.Primary.ID)
if err != nil {
return fmt.Errorf("Error occured while fetching public IP: %s", rs.Primary.ID)
}
if found_public_ip.Id != rs.Primary.ID {
return fmt.Errorf("Record not found")
}
public_ip = found_public_ip
return nil
}
}
const testAccCheckOneandonePublicIp_basic = `
resource "oneandone_public_ip" "ip" {
"ip_type" = "IPV4"
"reverse_dns" = "%s"
"datacenter" = "GB"
}`

View File

@ -0,0 +1,562 @@
package oneandone
import (
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/1and1/oneandone-cloudserver-sdk-go"
"github.com/hashicorp/terraform/helper/schema"
"golang.org/x/crypto/ssh"
"io/ioutil"
"log"
"strings"
"errors"
)
func resourceOneandOneServer() *schema.Resource {
return &schema.Resource{
Create: resourceOneandOneServerCreate,
Read: resourceOneandOneServerRead,
Update: resourceOneandOneServerUpdate,
Delete: resourceOneandOneServerDelete,
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},
"description": {
Type: schema.TypeString,
Optional: true,
},
"image": {
Type: schema.TypeString,
Required: true,
},
"vcores": {
Type: schema.TypeInt,
Required: true,
},
"cores_per_processor": {
Type: schema.TypeInt,
Required: true,
},
"ram": {
Type: schema.TypeFloat,
Required: true,
},
"ssh_key_path": {
Type: schema.TypeString,
Optional: true,
},
"password": {
Type: schema.TypeString,
Optional: true,
Sensitive: true,
},
"datacenter": {
Type: schema.TypeString,
Optional: true,
},
"ip": {
Type: schema.TypeString,
Optional: true,
},
"ips": {
Type: schema.TypeList,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeString,
Computed: true,
},
"ip": {
Type: schema.TypeString,
Computed: true,
},
"firewall_policy_id": {
Type: schema.TypeString,
Optional: true,
},
},
},
Computed: true,
},
"hdds": {
Type: schema.TypeList,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeString,
Computed: true,
},
"disk_size": {
Type: schema.TypeInt,
Required: true,
},
"is_main": {
Type: schema.TypeBool,
Optional: true,
},
},
},
Required: true,
},
"firewall_policy_id": {
Type: schema.TypeString,
Optional: true,
},
"monitoring_policy_id": {
Type: schema.TypeString,
Optional: true,
},
"loadbalancer_id": {
Type: schema.TypeString,
Optional: true,
},
},
}
}
func resourceOneandOneServerCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
saps, _ := config.API.ListServerAppliances()
var sa oneandone.ServerAppliance
for _, a := range saps {
if a.Type == "IMAGE" && strings.Contains(strings.ToLower(a.Name), strings.ToLower(d.Get("image").(string))) {
sa = a
break
}
}
var hdds []oneandone.Hdd
if raw, ok := d.GetOk("hdds"); ok {
rawhdds := raw.([]interface{})
var istheremain bool
for _, raw := range rawhdds {
hd := raw.(map[string]interface{})
hdd := oneandone.Hdd{
Size: hd["disk_size"].(int),
IsMain: hd["is_main"].(bool),
}
if hdd.IsMain {
if hdd.Size < sa.MinHddSize {
return fmt.Errorf(fmt.Sprintf("Minimum required disk size %d", sa.MinHddSize))
}
istheremain = true
}
hdds = append(hdds, hdd)
}
if !istheremain {
return fmt.Errorf("At least one HDD has to be %s", "`is_main`")
}
}
req := oneandone.ServerRequest{
Name: d.Get("name").(string),
Description: d.Get("description").(string),
ApplianceId: sa.Id,
PowerOn: true,
Hardware: oneandone.Hardware{
Vcores: d.Get("vcores").(int),
CoresPerProcessor: d.Get("cores_per_processor").(int),
Ram: float32(d.Get("ram").(float64)),
Hdds: hdds,
},
}
if raw, ok := d.GetOk("ip"); ok {
new_ip := raw.(string)
ips, err := config.API.ListPublicIps()
if err != nil {
return err
}
for _, ip := range ips {
if ip.IpAddress == new_ip {
req.IpId = ip.Id
break
}
}
log.Println("[DEBUG] req.IP", req.IpId)
}
if raw, ok := d.GetOk("datacenter"); ok {
dcs, err := config.API.ListDatacenters()
if err != nil {
return fmt.Errorf("An error occured while fetching list of datacenters %s", err)
}
decenter := raw.(string)
for _, dc := range dcs {
if strings.ToLower(dc.CountryCode) == strings.ToLower(decenter) {
req.DatacenterId = dc.Id
break
}
}
}
if fwp_id, ok := d.GetOk("firewall_policy_id"); ok {
req.FirewallPolicyId = fwp_id.(string)
}
if mp_id, ok := d.GetOk("monitoring_policy_id"); ok {
req.MonitoringPolicyId = mp_id.(string)
}
if mp_id, ok := d.GetOk("loadbalancer_id"); ok {
req.LoadBalancerId = mp_id.(string)
}
var privateKey string
if raw, ok := d.GetOk("ssh_key_path"); ok {
rawpath := raw.(string)
priv, publicKey, err := getSshKey(rawpath)
privateKey = priv
if err != nil {
return err
}
req.SSHKey = publicKey
}
var password string
if raw, ok := d.GetOk("password"); ok {
req.Password = raw.(string)
password = req.Password
}
server_id, server, err := config.API.CreateServer(&req)
if err != nil {
return err
}
err = config.API.WaitForState(server, "POWERED_ON", 10, config.Retries)
d.SetId(server_id)
server, err = config.API.GetServer(d.Id())
if err != nil {
return err
}
if password == "" {
password = server.FirstPassword
}
d.SetConnInfo(map[string]string{
"type": "ssh",
"host": server.Ips[0].Ip,
"password": password,
"private_key": privateKey,
})
return resourceOneandOneServerRead(d, meta)
}
func resourceOneandOneServerRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
server, err := config.API.GetServer(d.Id())
if err != nil {
if strings.Contains(err.Error(), "404") {
d.SetId("")
return nil
}
return err
}
d.Set("name", server.Name)
d.Set("datacenter", server.Datacenter.CountryCode)
d.Set("hdds", readHdds(server.Hardware))
d.Set("ips", readIps(server.Ips))
if len(server.FirstPassword) > 0 {
d.Set("password", server.FirstPassword)
}
return nil
}
func resourceOneandOneServerUpdate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
if d.HasChange("name") || d.HasChange("description") {
_, name := d.GetChange("name")
_, description := d.GetChange("description")
server, err := config.API.RenameServer(d.Id(), name.(string), description.(string))
if err != nil {
return err
}
err = config.API.WaitForState(server, "POWERED_ON", 10, config.Retries)
}
if d.HasChange("hdds") {
oldV, newV := d.GetChange("hdds")
newValues := newV.([]interface{})
oldValues := oldV.([]interface{})
if len(oldValues) > len(newValues) {
diff := difference(oldValues, newValues)
for _, old := range diff {
o := old.(map[string]interface{})
old_id := o["id"].(string)
server, err := config.API.DeleteServerHdd(d.Id(), old_id)
if err != nil {
return err
}
err = config.API.WaitForState(server, "POWERED_ON", 10, config.Retries)
if err != nil {
return err
}
}
} else {
for _, newHdd := range newValues {
n := newHdd.(map[string]interface{})
//old := oldHdd.(map[string]interface{})
if n["id"].(string) == "" {
hdds := oneandone.ServerHdds{
Hdds: []oneandone.Hdd{
{
Size: n["disk_size"].(int),
IsMain: n["is_main"].(bool),
},
},
}
server, err := config.API.AddServerHdds(d.Id(), &hdds)
if err != nil {
return err
}
err = config.API.WaitForState(server, "POWERED_ON", 10, config.Retries)
if err != nil {
return err
}
} else {
id := n["id"].(string)
isMain := n["is_main"].(bool)
if id != "" && !isMain {
log.Println("[DEBUG] Resizing existing HDD")
config.API.ResizeServerHdd(d.Id(), id, n["disk_size"].(int))
}
}
}
}
}
if d.HasChange("monitoring_policy_id") {
o, n := d.GetChange("monitoring_policy_id")
if n == nil {
mp, err := config.API.RemoveMonitoringPolicyServer(o.(string), d.Id())
if err != nil {
return err
}
err = config.API.WaitForState(mp, "ACTIVE", 30, config.Retries)
if err != nil {
return err
}
} else {
mp, err := config.API.AttachMonitoringPolicyServers(n.(string), []string{d.Id()})
if err != nil {
return err
}
err = config.API.WaitForState(mp, "ACTIVE", 30, config.Retries)
if err != nil {
return err
}
}
}
if d.HasChange("loadbalancer_id") {
o, n := d.GetChange("loadbalancer_id")
server, err := config.API.GetServer(d.Id())
if err != nil {
return err
}
if n == nil || n.(string) == "" {
log.Println("[DEBUG] Removing")
log.Println("[DEBUG] IPS:", server.Ips)
for _, ip := range server.Ips {
mp, err := config.API.DeleteLoadBalancerServerIp(o.(string), ip.Id)
if err != nil {
return err
}
err = config.API.WaitForState(mp, "ACTIVE", 30, config.Retries)
if err != nil {
return err
}
}
} else {
log.Println("[DEBUG] Adding")
ip_ids := []string{}
for _, ip := range server.Ips {
ip_ids = append(ip_ids, ip.Id)
}
mp, err := config.API.AddLoadBalancerServerIps(n.(string), ip_ids)
if err != nil {
return err
}
err = config.API.WaitForState(mp, "ACTIVE", 30, config.Retries)
if err != nil {
return err
}
}
}
if d.HasChange("firewall_policy_id") {
server, err := config.API.GetServer(d.Id())
if err != nil {
return err
}
o, n := d.GetChange("firewall_policy_id")
if n == nil {
for _, ip := range server.Ips {
mp, err := config.API.DeleteFirewallPolicyServerIp(o.(string), ip.Id)
if err != nil {
return err
}
err = config.API.WaitForState(mp, "ACTIVE", 30, config.Retries)
if err != nil {
return err
}
}
} else {
ip_ids := []string{}
for _, ip := range server.Ips {
ip_ids = append(ip_ids, ip.Id)
}
mp, err := config.API.AddFirewallPolicyServerIps(n.(string), ip_ids)
if err != nil {
return err
}
err = config.API.WaitForState(mp, "ACTIVE", 30, config.Retries)
if err != nil {
return err
}
}
}
return resourceOneandOneServerRead(d, meta)
}
func resourceOneandOneServerDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
_, ok := d.GetOk("ip")
server, err := config.API.DeleteServer(d.Id(), ok)
if err != nil {
return err
}
err = config.API.WaitUntilDeleted(server)
if err != nil {
log.Println("[DEBUG] ************ ERROR While waiting ************")
return err
}
return nil
}
func readHdds(hardware *oneandone.Hardware) []map[string]interface{} {
hdds := make([]map[string]interface{}, 0, len(hardware.Hdds))
for _, hd := range hardware.Hdds {
hdds = append(hdds, map[string]interface{}{
"id": hd.Id,
"disk_size": hd.Size,
"is_main": hd.IsMain,
})
}
return hdds
}
func readIps(ips []oneandone.ServerIp) []map[string]interface{} {
raw := make([]map[string]interface{}, 0, len(ips))
for _, ip := range ips {
toadd := map[string]interface{}{
"ip": ip.Ip,
"id": ip.Id,
}
if ip.Firewall != nil {
toadd["firewall_policy_id"] = ip.Firewall.Id
}
raw = append(raw, toadd)
}
return raw
}
func getSshKey(path string) (privatekey string, publickey string, err error) {
pemBytes, err := ioutil.ReadFile(path)
if err != nil {
return "", "", err
}
block, _ := pem.Decode(pemBytes)
if block == nil {
return "", "", errors.New("File " + path + " contains nothing")
}
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return "", "", err
}
priv_blk := pem.Block{
Type: "RSA PRIVATE KEY",
Headers: nil,
Bytes: x509.MarshalPKCS1PrivateKey(priv),
}
pub, err := ssh.NewPublicKey(&priv.PublicKey)
if err != nil {
return "", "", err
}
publickey = string(ssh.MarshalAuthorizedKey(pub))
privatekey = string(pem.EncodeToMemory(&priv_blk))
return privatekey, publickey, nil
}

View File

@ -0,0 +1,130 @@
package oneandone
import (
"fmt"
"testing"
"github.com/1and1/oneandone-cloudserver-sdk-go"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"os"
"time"
)
func TestAccOneandoneServer_Basic(t *testing.T) {
var server oneandone.Server
name := "test_server"
name_updated := "test_server_renamed"
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Providers: testAccProviders,
CheckDestroy: testAccCheckDOneandoneServerDestroyCheck,
Steps: []resource.TestStep{
resource.TestStep{
Config: fmt.Sprintf(testAccCheckOneandoneServer_basic, name, name),
Check: resource.ComposeTestCheckFunc(
func(*terraform.State) error {
time.Sleep(10 * time.Second)
return nil
},
testAccCheckOneandoneServerExists("oneandone_server.server", &server),
testAccCheckOneandoneServerAttributes("oneandone_server.server", name),
resource.TestCheckResourceAttr("oneandone_server.server", "name", name),
),
},
resource.TestStep{
Config: fmt.Sprintf(testAccCheckOneandoneServer_basic, name_updated, name_updated),
Check: resource.ComposeTestCheckFunc(
func(*terraform.State) error {
time.Sleep(10 * time.Second)
return nil
},
testAccCheckOneandoneServerExists("oneandone_server.server", &server),
testAccCheckOneandoneServerAttributes("oneandone_server.server", name_updated),
resource.TestCheckResourceAttr("oneandone_server.server", "name", name_updated),
),
},
},
})
}
func testAccCheckDOneandoneServerDestroyCheck(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "oneandone_server" {
continue
}
api := oneandone.New(os.Getenv("ONEANDONE_TOKEN"), oneandone.BaseUrl)
_, err := api.GetServer(rs.Primary.ID)
if err == nil {
return fmt.Errorf("Server still exists %s %s", rs.Primary.ID, err.Error())
}
}
return nil
}
func testAccCheckOneandoneServerAttributes(n string, reverse_dns 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.Attributes["name"] != reverse_dns {
return fmt.Errorf("Bad name: expected %s : found %s ", reverse_dns, rs.Primary.Attributes["name"])
}
return nil
}
}
func testAccCheckOneandoneServerExists(n string, server *oneandone.Server) 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 Record ID is set")
}
api := oneandone.New(os.Getenv("ONEANDONE_TOKEN"), oneandone.BaseUrl)
found_server, err := api.GetServer(rs.Primary.ID)
if err != nil {
return fmt.Errorf("Error occured while fetching Server: %s", rs.Primary.ID)
}
if found_server.Id != rs.Primary.ID {
return fmt.Errorf("Record not found")
}
server = found_server
return nil
}
}
const testAccCheckOneandoneServer_basic = `
resource "oneandone_server" "server" {
name = "%s"
description = "%s"
image = "ubuntu"
datacenter = "GB"
vcores = 1
cores_per_processor = 1
ram = 2
password = "Kv40kd8PQb"
hdds = [
{
disk_size = 20
is_main = true
}
]
}`

View File

@ -0,0 +1,217 @@
package oneandone
import (
"crypto/md5"
"encoding/base64"
"fmt"
"github.com/1and1/oneandone-cloudserver-sdk-go"
"github.com/hashicorp/terraform/helper/schema"
"io"
"os"
fp "path/filepath"
"strings"
)
func resourceOneandOneVPN() *schema.Resource {
return &schema.Resource{
Create: resourceOneandOneVPNCreate,
Read: resourceOneandOneVPNRead,
Update: resourceOneandOneVPNUpdate,
Delete: resourceOneandOneVPNDelete,
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},
"description": {
Type: schema.TypeString,
Optional: true,
},
"download_path": {
Type: schema.TypeString,
Computed: true,
},
"datacenter": {
Type: schema.TypeString,
Optional: true,
},
"file_name": {
Type: schema.TypeString,
Computed: true,
},
},
}
}
func resourceOneandOneVPNCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
var datacenter string
if raw, ok := d.GetOk("datacenter"); ok {
dcs, err := config.API.ListDatacenters()
if err != nil {
return fmt.Errorf("An error occured while fetching list of datacenters %s", err)
}
decenter := raw.(string)
for _, dc := range dcs {
if strings.ToLower(dc.CountryCode) == strings.ToLower(decenter) {
datacenter = dc.Id
break
}
}
}
var description string
if raw, ok := d.GetOk("description"); ok {
description = raw.(string)
}
vpn_id, vpn, err := config.API.CreateVPN(d.Get("name").(string), description, datacenter)
if err != nil {
return err
}
err = config.API.WaitForState(vpn, "ACTIVE", 10, config.Retries)
if err != nil {
return err
}
d.SetId(vpn_id)
return resourceOneandOneVPNRead(d, meta)
}
func resourceOneandOneVPNUpdate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
if d.HasChange("name") || d.HasChange("description") {
vpn, err := config.API.ModifyVPN(d.Id(), d.Get("name").(string), d.Get("description").(string))
if err != nil {
return err
}
err = config.API.WaitForState(vpn, "ACTIVE", 10, config.Retries)
if err != nil {
return err
}
}
return resourceOneandOneVPNRead(d, meta)
}
func resourceOneandOneVPNRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
vpn, err := config.API.GetVPN(d.Id())
base64_str, err := config.API.GetVPNConfigFile(d.Id())
if err != nil {
if strings.Contains(err.Error(), "404") {
d.SetId("")
return nil
}
return err
}
var download_path string
if raw, ok := d.GetOk("download_path"); ok {
download_path = raw.(string)
}
path, fileName, err := writeCofnig(vpn, download_path, base64_str)
if err != nil {
return err
}
d.Set("name", vpn.Name)
d.Set("description", vpn.Description)
d.Set("download_path", path)
d.Set("file_name", fileName)
d.Set("datacenter", vpn.Datacenter.CountryCode)
return nil
}
func writeCofnig(vpn *oneandone.VPN, path, base64config string) (string, string, error) {
data, err := base64.StdEncoding.DecodeString(base64config)
if err != nil {
return "", "", err
}
var fileName string
if vpn.CloudPanelId != "" {
fileName = vpn.CloudPanelId + ".zip"
} else {
fileName = "vpn_" + fmt.Sprintf("%x", md5.Sum(data)) + ".zip"
}
if path == "" {
path, err = os.Getwd()
if err != nil {
return "", "", err
}
}
if !fp.IsAbs(path) {
path, err = fp.Abs(path)
if err != nil {
return "", "", err
}
}
_, err = os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
// make all dirs
os.MkdirAll(path, 0666)
} else {
return "", "", err
}
}
fpath := fp.Join(path, fileName)
f, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY, 0666)
defer f.Close()
if err != nil {
return "", "", err
}
n, err := f.Write(data)
if err == nil && n < len(data) {
err = io.ErrShortWrite
}
if err != nil {
return "", "", err
}
return path, fileName, nil
}
func resourceOneandOneVPNDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
vpn, err := config.API.DeleteVPN(d.Id())
if err != nil {
return err
}
err = config.API.WaitUntilDeleted(vpn)
if err != nil {
return err
}
fullPath := fp.Join(d.Get("download_path").(string), d.Get("file_name").(string))
if _, err := os.Stat(fullPath); !os.IsNotExist(err) {
os.Remove(fullPath)
}
return nil
}

View File

@ -0,0 +1,119 @@
package oneandone
import (
"fmt"
"testing"
"github.com/1and1/oneandone-cloudserver-sdk-go"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"os"
"time"
)
func TestAccOneandoneVpn_Basic(t *testing.T) {
var server oneandone.VPN
name := "test"
name_updated := "test1"
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Providers: testAccProviders,
CheckDestroy: testAccCheckDOneandoneVPNDestroyCheck,
Steps: []resource.TestStep{
resource.TestStep{
Config: fmt.Sprintf(testAccCheckOneandoneVPN_basic, name),
Check: resource.ComposeTestCheckFunc(
func(*terraform.State) error {
time.Sleep(10 * time.Second)
return nil
},
testAccCheckOneandoneVPNExists("oneandone_vpn.vpn", &server),
testAccCheckOneandoneVPNAttributes("oneandone_vpn.vpn", name),
resource.TestCheckResourceAttr("oneandone_vpn.vpn", "name", name),
),
},
resource.TestStep{
Config: fmt.Sprintf(testAccCheckOneandoneVPN_basic, name_updated),
Check: resource.ComposeTestCheckFunc(
func(*terraform.State) error {
time.Sleep(10 * time.Second)
return nil
},
testAccCheckOneandoneVPNExists("oneandone_vpn.vpn", &server),
testAccCheckOneandoneVPNAttributes("oneandone_vpn.vpn", name_updated),
resource.TestCheckResourceAttr("oneandone_vpn.vpn", "name", name_updated),
),
},
},
})
}
func testAccCheckDOneandoneVPNDestroyCheck(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "oneandone_server" {
continue
}
api := oneandone.New(os.Getenv("ONEANDONE_TOKEN"), oneandone.BaseUrl)
_, err := api.GetVPN(rs.Primary.ID)
if err == nil {
return fmt.Errorf("VPN still exists %s %s", rs.Primary.ID, err.Error())
}
}
return nil
}
func testAccCheckOneandoneVPNAttributes(n string, reverse_dns 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.Attributes["name"] != reverse_dns {
return fmt.Errorf("Bad name: expected %s : found %s ", reverse_dns, rs.Primary.Attributes["name"])
}
return nil
}
}
func testAccCheckOneandoneVPNExists(n string, server *oneandone.VPN) 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 Record ID is set")
}
api := oneandone.New(os.Getenv("ONEANDONE_TOKEN"), oneandone.BaseUrl)
found_server, err := api.GetVPN(rs.Primary.ID)
if err != nil {
return fmt.Errorf("Error occured while fetching VPN: %s", rs.Primary.ID)
}
if found_server.Id != rs.Primary.ID {
return fmt.Errorf("Record not found")
}
server = found_server
return nil
}
}
const testAccCheckOneandoneVPN_basic = `
resource "oneandone_vpn" "vpn" {
datacenter = "GB"
name = "%s"
description = "ttest descr"
}`

View File

@ -0,0 +1,256 @@
package oneandone
import (
"fmt"
"github.com/1and1/oneandone-cloudserver-sdk-go"
"github.com/hashicorp/terraform/helper/schema"
"strings"
)
func resourceOneandOneSharedStorage() *schema.Resource {
return &schema.Resource{
Create: resourceOneandOneSharedStorageCreate,
Read: resourceOneandOneSharedStorageRead,
Update: resourceOneandOneSharedStorageUpdate,
Delete: resourceOneandOneSharedStorageDelete,
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},
"description": {
Type: schema.TypeString,
Optional: true,
},
"size": {
Type: schema.TypeInt,
Required: true,
},
"datacenter": {
Type: schema.TypeString,
Required: true,
},
"storage_servers": {
Type: schema.TypeList,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeString,
Required: true,
},
"rights": {
Type: schema.TypeString,
Required: true,
},
},
},
Optional: true,
},
},
}
}
func resourceOneandOneSharedStorageCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
req := oneandone.SharedStorageRequest{
Name: d.Get("name").(string),
Size: oneandone.Int2Pointer(d.Get("size").(int)),
}
if raw, ok := d.GetOk("description"); ok {
req.Description = raw.(string)
}
if raw, ok := d.GetOk("datacenter"); ok {
dcs, err := config.API.ListDatacenters()
if err != nil {
return fmt.Errorf("An error occured while fetching list of datacenters %s", err)
}
decenter := raw.(string)
for _, dc := range dcs {
if strings.ToLower(dc.CountryCode) == strings.ToLower(decenter) {
req.DatacenterId = dc.Id
break
}
}
}
ss_id, ss, err := config.API.CreateSharedStorage(&req)
if err != nil {
return err
}
err = config.API.WaitForState(ss, "ACTIVE", 10, config.Retries)
if err != nil {
return err
}
d.SetId(ss_id)
if raw, ok := d.GetOk("storage_servers"); ok {
storage_servers := []oneandone.SharedStorageServer{}
rawRights := raw.([]interface{})
for _, raws_ss := range rawRights {
ss := raws_ss.(map[string]interface{})
storage_server := oneandone.SharedStorageServer{
Id: ss["id"].(string),
Rights: ss["rights"].(string),
}
storage_servers = append(storage_servers, storage_server)
}
ss, err := config.API.AddSharedStorageServers(ss_id, storage_servers)
if err != nil {
return err
}
err = config.API.WaitForState(ss, "ACTIVE", 10, 30)
if err != nil {
return err
}
}
return resourceOneandOneSharedStorageRead(d, meta)
}
func resourceOneandOneSharedStorageUpdate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
if d.HasChange("name") || d.HasChange("description") || d.HasChange("size") {
ssu := oneandone.SharedStorageRequest{}
if d.HasChange("name") {
_, n := d.GetChange("name")
ssu.Name = n.(string)
}
if d.HasChange("description") {
_, n := d.GetChange("description")
ssu.Description = n.(string)
}
if d.HasChange("size") {
_, n := d.GetChange("size")
ssu.Size = oneandone.Int2Pointer(n.(int))
}
ss, err := config.API.UpdateSharedStorage(d.Id(), &ssu)
if err != nil {
return err
}
err = config.API.WaitForState(ss, "ACTIVE", 10, 30)
if err != nil {
return err
}
}
if d.HasChange("storage_servers") {
o, n := d.GetChange("storage_servers")
oldV := o.([]interface{})
for _, old := range oldV {
ol := old.(map[string]interface{})
ss, err := config.API.DeleteSharedStorageServer(d.Id(), ol["id"].(string))
if err != nil {
return err
}
err = config.API.WaitForState(ss, "ACTIVE", 10, config.Retries)
if err != nil {
return err
}
}
newV := n.([]interface{})
ids := []oneandone.SharedStorageServer{}
for _, newValue := range newV {
nn := newValue.(map[string]interface{})
ids = append(ids, oneandone.SharedStorageServer{
Id: nn["id"].(string),
Rights: nn["rights"].(string),
})
}
if len(ids) > 0 {
ss, err := config.API.AddSharedStorageServers(d.Id(), ids)
if err != nil {
return err
}
err = config.API.WaitForState(ss, "ACTIVE", 10, config.Retries)
if err != nil {
return err
}
}
//DeleteSharedStorageServer
}
return resourceOneandOneSharedStorageRead(d, meta)
}
func resourceOneandOneSharedStorageRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
ss, err := config.API.GetSharedStorage(d.Id())
if err != nil {
if strings.Contains(err.Error(), "404") {
d.SetId("")
return nil
}
return err
}
d.Set("name", ss.Name)
d.Set("description", ss.Description)
d.Set("size", ss.Size)
d.Set("datacenter", ss.Datacenter.CountryCode)
d.Set("storage_servers", getStorageServers(ss.Servers))
return nil
}
func getStorageServers(servers []oneandone.SharedStorageServer) []map[string]interface{} {
raw := make([]map[string]interface{}, 0, len(servers))
for _, server := range servers {
toadd := map[string]interface{}{
"id": server.Id,
"rights": server.Rights,
}
raw = append(raw, toadd)
}
return raw
}
func resourceOneandOneSharedStorageDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
ss, err := config.API.DeleteSharedStorage(d.Id())
if err != nil {
return err
}
err = config.API.WaitUntilDeleted(ss)
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,120 @@
package oneandone
import (
"fmt"
"testing"
"github.com/1and1/oneandone-cloudserver-sdk-go"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"os"
"time"
)
func TestAccOneandoneSharedStorage_Basic(t *testing.T) {
var storage oneandone.SharedStorage
name := "test_storage"
name_updated := "test1"
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Providers: testAccProviders,
CheckDestroy: testAccCheckDOneandoneSharedStorageDestroyCheck,
Steps: []resource.TestStep{
resource.TestStep{
Config: fmt.Sprintf(testAccCheckOneandoneSharedStorage_basic, name),
Check: resource.ComposeTestCheckFunc(
func(*terraform.State) error {
time.Sleep(10 * time.Second)
return nil
},
testAccCheckOneandoneSharedStorageExists("oneandone_shared_storage.storage", &storage),
testAccCheckOneandoneSharedStorageAttributes("oneandone_shared_storage.storage", name),
resource.TestCheckResourceAttr("oneandone_shared_storage.storage", "name", name),
),
},
resource.TestStep{
Config: fmt.Sprintf(testAccCheckOneandoneSharedStorage_basic, name_updated),
Check: resource.ComposeTestCheckFunc(
func(*terraform.State) error {
time.Sleep(10 * time.Second)
return nil
},
testAccCheckOneandoneSharedStorageExists("oneandone_shared_storage.storage", &storage),
testAccCheckOneandoneSharedStorageAttributes("oneandone_shared_storage.storage", name_updated),
resource.TestCheckResourceAttr("oneandone_shared_storage.storage", "name", name_updated),
),
},
},
})
}
func testAccCheckDOneandoneSharedStorageDestroyCheck(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "oneandone_shared_storage" {
continue
}
api := oneandone.New(os.Getenv("ONEANDONE_TOKEN"), oneandone.BaseUrl)
_, err := api.GetVPN(rs.Primary.ID)
if err == nil {
return fmt.Errorf("VPN still exists %s %s", rs.Primary.ID, err.Error())
}
}
return nil
}
func testAccCheckOneandoneSharedStorageAttributes(n string, reverse_dns 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.Attributes["name"] != reverse_dns {
return fmt.Errorf("Bad name: expected %s : found %s ", reverse_dns, rs.Primary.Attributes["name"])
}
return nil
}
}
func testAccCheckOneandoneSharedStorageExists(n string, storage *oneandone.SharedStorage) 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 Record ID is set")
}
api := oneandone.New(os.Getenv("ONEANDONE_TOKEN"), oneandone.BaseUrl)
found_storage, err := api.GetSharedStorage(rs.Primary.ID)
if err != nil {
return fmt.Errorf("Error occured while fetching SharedStorage: %s", rs.Primary.ID)
}
if found_storage.Id != rs.Primary.ID {
return fmt.Errorf("Record not found")
}
storage = found_storage
return nil
}
}
const testAccCheckOneandoneSharedStorage_basic = `
resource "oneandone_shared_storage" "storage" {
name = "%s"
description = "ttt"
size = 50
datacenter = "GB"
}`

View File

@ -80,6 +80,7 @@ func resourceInstance() *schema.Resource {
"label": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},

View File

@ -136,6 +136,27 @@ func TestAccOPCInstance_storage(t *testing.T) {
})
}
func TestAccOPCInstance_emptyLabel(t *testing.T) {
resName := "opc_compute_instance.test"
rInt := acctest.RandInt()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccOPCCheckInstanceDestroy,
Steps: []resource.TestStep{
{
Config: testAccInstanceEmptyLabel(rInt),
Check: resource.ComposeTestCheckFunc(
testAccOPCCheckInstanceExists,
resource.TestCheckResourceAttr(resName, "name", fmt.Sprintf("acc-test-instance-%d", rInt)),
resource.TestCheckResourceAttrSet(resName, "label"),
),
},
},
})
}
func testAccOPCCheckInstanceExists(s *terraform.State) error {
client := testAccProvider.Meta().(*compute.Client).Instances()
@ -271,3 +292,17 @@ resource "opc_compute_instance" "test" {
}
}`, rInt, rInt, rInt)
}
func testAccInstanceEmptyLabel(rInt int) string {
return fmt.Sprintf(`
resource "opc_compute_instance" "test" {
name = "acc-test-instance-%d"
shape = "oc3"
image_list = "/oracle/public/oel_6.7_apaas_16.4.5_1610211300"
instance_attributes = <<JSON
{
"foo": "bar"
}
JSON
}`, rInt)
}

View File

@ -11,10 +11,10 @@ sudo rabbitmq-plugins enable rabbitmq_management
sudo wget -O /usr/local/bin/gimme https://raw.githubusercontent.com/travis-ci/gimme/master/gimme
sudo chmod +x /usr/local/bin/gimme
gimme 1.6 >> .bashrc
gimme 1.8 >> .bashrc
mkdir ~/go
eval "$(/usr/local/bin/gimme 1.6)"
eval "$(/usr/local/bin/gimme 1.8)"
echo 'export GOPATH=$HOME/go' >> .bashrc
export GOPATH=$HOME/go
@ -24,3 +24,9 @@ source .bashrc
go get -u github.com/kardianos/govendor
go get github.com/hashicorp/terraform
cat <<EOF > ~/rabbitmqrc
export RABBITMQ_ENDPOINT="http://127.0.0.1:15672"
export RABBITMQ_USERNAME="guest"
export RABBITMQ_PASSWORD="guest"
EOF

View File

@ -11,7 +11,7 @@ import (
"github.com/hashicorp/terraform/terraform"
)
func TestAccBinding(t *testing.T) {
func TestAccBinding_basic(t *testing.T) {
var bindingInfo rabbithole.BindingInfo
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -28,6 +28,23 @@ func TestAccBinding(t *testing.T) {
})
}
func TestAccBinding_propertiesKey(t *testing.T) {
var bindingInfo rabbithole.BindingInfo
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccBindingCheckDestroy(bindingInfo),
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccBindingConfig_propertiesKey,
Check: testAccBindingCheck(
"rabbitmq_binding.test", &bindingInfo,
),
},
},
})
}
func testAccBindingCheck(rn string, bindingInfo *rabbithole.BindingInfo) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[rn]
@ -119,3 +136,47 @@ resource "rabbitmq_binding" "test" {
routing_key = "#"
properties_key = "%23"
}`
const testAccBindingConfig_propertiesKey = `
resource "rabbitmq_vhost" "test" {
name = "test"
}
resource "rabbitmq_permissions" "guest" {
user = "guest"
vhost = "${rabbitmq_vhost.test.name}"
permissions {
configure = ".*"
write = ".*"
read = ".*"
}
}
resource "rabbitmq_exchange" "test" {
name = "Test"
vhost = "${rabbitmq_permissions.guest.vhost}"
settings {
type = "topic"
durable = true
auto_delete = false
}
}
resource "rabbitmq_queue" "test" {
name = "Test.Queue"
vhost = "${rabbitmq_permissions.guest.vhost}"
settings {
durable = true
auto_delete = false
}
}
resource "rabbitmq_binding" "test" {
source = "${rabbitmq_exchange.test.name}"
vhost = "${rabbitmq_vhost.test.name}"
destination = "${rabbitmq_queue.test.name}"
destination_type = "queue"
routing_key = "ANYTHING.#"
properties_key = "ANYTHING.%23"
}
`

View File

@ -76,14 +76,12 @@ func resourceRancherStack() *schema.Resource {
Optional: true,
},
"rendered_docker_compose": {
Type: schema.TypeString,
Computed: true,
DiffSuppressFunc: suppressComposeDiff,
Type: schema.TypeString,
Computed: true,
},
"rendered_rancher_compose": {
Type: schema.TypeString,
Computed: true,
DiffSuppressFunc: suppressComposeDiff,
Type: schema.TypeString,
Computed: true,
},
},
}

View File

@ -140,7 +140,7 @@ func renderPartsToWriter(parts cloudInitParts, writer io.Writer) error {
}
writer.Write([]byte(fmt.Sprintf("Content-Type: multipart/mixed; boundary=\"%s\"\n", mimeWriter.Boundary())))
writer.Write([]byte("MIME-Version: 1.0\r\n"))
writer.Write([]byte("MIME-Version: 1.0\r\n\r\n"))
for _, part := range parts {
header := textproto.MIMEHeader{}

View File

@ -22,7 +22,7 @@ func TestRender(t *testing.T) {
content = "baz"
}
}`,
"Content-Type: multipart/mixed; boundary=\"MIMEBOUNDARY\"\nMIME-Version: 1.0\r\n--MIMEBOUNDARY\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nbaz\r\n--MIMEBOUNDARY--\r\n",
"Content-Type: multipart/mixed; boundary=\"MIMEBOUNDARY\"\nMIME-Version: 1.0\r\n\r\n--MIMEBOUNDARY\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nbaz\r\n--MIMEBOUNDARY--\r\n",
},
{
`data "template_cloudinit_config" "foo" {
@ -35,7 +35,7 @@ func TestRender(t *testing.T) {
filename = "foobar.sh"
}
}`,
"Content-Type: multipart/mixed; boundary=\"MIMEBOUNDARY\"\nMIME-Version: 1.0\r\n--MIMEBOUNDARY\r\nContent-Disposition: attachment; filename=\"foobar.sh\"\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nbaz\r\n--MIMEBOUNDARY--\r\n",
"Content-Type: multipart/mixed; boundary=\"MIMEBOUNDARY\"\nMIME-Version: 1.0\r\n\r\n--MIMEBOUNDARY\r\nContent-Disposition: attachment; filename=\"foobar.sh\"\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nbaz\r\n--MIMEBOUNDARY--\r\n",
},
{
`data "template_cloudinit_config" "foo" {
@ -51,7 +51,7 @@ func TestRender(t *testing.T) {
content = "ffbaz"
}
}`,
"Content-Type: multipart/mixed; boundary=\"MIMEBOUNDARY\"\nMIME-Version: 1.0\r\n--MIMEBOUNDARY\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nbaz\r\n--MIMEBOUNDARY\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nffbaz\r\n--MIMEBOUNDARY--\r\n",
"Content-Type: multipart/mixed; boundary=\"MIMEBOUNDARY\"\nMIME-Version: 1.0\r\n\r\n--MIMEBOUNDARY\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nbaz\r\n--MIMEBOUNDARY\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/x-shellscript\r\nMime-Version: 1.0\r\n\r\nffbaz\r\n--MIMEBOUNDARY--\r\n",
},
}

View File

@ -30,6 +30,7 @@ func (c *InitCommand) Run(args []string) int {
cmdFlags.BoolVar(&c.forceInitCopy, "force-copy", false, "suppress prompts about copying state data")
cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
cmdFlags.BoolVar(&c.reconfigure, "reconfigure", false, "reconfigure")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil {
@ -223,6 +224,10 @@ Options:
times. The backend type must be in the configuration
itself.
-force-copy Suppress prompts about copying state data. This is
equivalent to providing a "yes" to all confirmation
prompts.
-get=true Download any modules for this configuration.
-input=true Ask for input if necessary. If false, will error if
@ -234,9 +239,7 @@ Options:
-no-color If specified, output won't contain any color.
-force-copy Suppress prompts about copying state data. This is
equivalent to providing a "yes" to all confirmation
prompts.
-reconfigure Reconfigure the backend, ignoring any saved configuration.
`
return strings.TrimSpace(helpText)
}

View File

@ -47,6 +47,7 @@ import (
nomadprovider "github.com/hashicorp/terraform/builtin/providers/nomad"
ns1provider "github.com/hashicorp/terraform/builtin/providers/ns1"
nullprovider "github.com/hashicorp/terraform/builtin/providers/null"
oneandoneprovider "github.com/hashicorp/terraform/builtin/providers/oneandone"
opcprovider "github.com/hashicorp/terraform/builtin/providers/opc"
openstackprovider "github.com/hashicorp/terraform/builtin/providers/openstack"
opsgenieprovider "github.com/hashicorp/terraform/builtin/providers/opsgenie"
@ -125,6 +126,7 @@ var InternalProviders = map[string]plugin.ProviderFunc{
"nomad": nomadprovider.Provider,
"ns1": ns1provider.Provider,
"null": nullprovider.Provider,
"oneandone": oneandoneprovider.Provider,
"opc": opcprovider.Provider,
"openstack": openstackprovider.Provider,
"opsgenie": opsgenieprovider.Provider,

View File

@ -95,6 +95,8 @@ type Meta struct {
//
// forceInitCopy suppresses confirmation for copying state data during
// init.
//
// reconfigure forces init to ignore any stored configuration.
statePath string
stateOutPath string
backupPath string
@ -104,6 +106,7 @@ type Meta struct {
stateLock bool
stateLockTimeout time.Duration
forceInitCopy bool
reconfigure bool
}
// initStatePaths is used to initialize the default values for

View File

@ -352,6 +352,13 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, error) {
s = terraform.NewState()
}
// if we want to force reconfiguration of the backend, we set the backend
// state to nil on this copy. This will direct us through the correct
// configuration path in the switch statement below.
if m.reconfigure {
s.Backend = nil
}
// Upon return, we want to set the state we're using in-memory so that
// we can access it for commands.
m.backendState = nil

View File

@ -983,6 +983,59 @@ func TestMetaBackend_configuredChange(t *testing.T) {
}
}
// Reconfiguring with an already configured backend.
// This should ignore the existing backend config, and configure the new
// backend is if this is the first time.
func TestMetaBackend_reconfigureChange(t *testing.T) {
// Create a temporary working directory that is empty
td := tempDir(t)
copy.CopyDir(testFixturePath("backend-change-single-to-single"), td)
defer os.RemoveAll(td)
defer testChdir(t, td)()
// Register the single-state backend
backendinit.Set("local-single", backendlocal.TestNewLocalSingle)
defer backendinit.Set("local-single", nil)
// Setup the meta
m := testMetaBackend(t, nil)
// this should not ask for input
m.input = false
// cli flag -reconfigure
m.reconfigure = true
// Get the backend
b, err := m.Backend(&BackendOpts{Init: true})
if err != nil {
t.Fatalf("bad: %s", err)
}
// Check the state
s, err := b.State(backend.DefaultStateName)
if err != nil {
t.Fatalf("bad: %s", err)
}
if err := s.RefreshState(); err != nil {
t.Fatalf("bad: %s", err)
}
newState := s.State()
if newState != nil || !newState.Empty() {
t.Fatal("state should be nil/empty after forced reconfiguration")
}
// verify that the old state is still there
s = (&state.LocalState{Path: "local-state.tfstate"})
if err := s.RefreshState(); err != nil {
t.Fatal(err)
}
oldState := s.State()
if oldState == nil || oldState.Empty() {
t.Fatal("original state should be untouched")
}
}
// Changing a configured backend, copying state
func TestMetaBackend_configuredChangeCopy(t *testing.T) {
// Create a temporary working directory that is empty

View File

@ -63,12 +63,14 @@ func Funcs() map[string]ast.Function {
"cidrnetmask": interpolationFuncCidrNetmask(),
"cidrsubnet": interpolationFuncCidrSubnet(),
"coalesce": interpolationFuncCoalesce(),
"coalescelist": interpolationFuncCoalesceList(),
"compact": interpolationFuncCompact(),
"concat": interpolationFuncConcat(),
"dirname": interpolationFuncDirname(),
"distinct": interpolationFuncDistinct(),
"element": interpolationFuncElement(),
"file": interpolationFuncFile(),
"matchkeys": interpolationFuncMatchKeys(),
"floor": interpolationFuncFloor(),
"format": interpolationFuncFormat(),
"formatlist": interpolationFuncFormatList(),
@ -323,6 +325,30 @@ func interpolationFuncCoalesce() ast.Function {
}
}
// interpolationFuncCoalesceList implements the "coalescelist" function that
// returns the first non empty list from the provided input
func interpolationFuncCoalesceList() ast.Function {
return ast.Function{
ArgTypes: []ast.Type{ast.TypeList},
ReturnType: ast.TypeList,
Variadic: true,
VariadicType: ast.TypeList,
Callback: func(args []interface{}) (interface{}, error) {
if len(args) < 2 {
return nil, fmt.Errorf("must provide at least two arguments")
}
for _, arg := range args {
argument := arg.([]ast.Variable)
if len(argument) > 0 {
return argument, nil
}
}
return make([]ast.Variable, 0), nil
},
}
}
// interpolationFuncConcat implements the "concat" function that concatenates
// multiple lists.
func interpolationFuncConcat() ast.Function {
@ -668,6 +694,57 @@ func appendIfMissing(slice []string, element string) []string {
return append(slice, element)
}
// for two lists `keys` and `values` of equal length, returns all elements
// from `values` where the corresponding element from `keys` is in `searchset`.
func interpolationFuncMatchKeys() ast.Function {
return ast.Function{
ArgTypes: []ast.Type{ast.TypeList, ast.TypeList, ast.TypeList},
ReturnType: ast.TypeList,
Callback: func(args []interface{}) (interface{}, error) {
output := make([]ast.Variable, 0)
values, _ := args[0].([]ast.Variable)
keys, _ := args[1].([]ast.Variable)
searchset, _ := args[2].([]ast.Variable)
if len(keys) != len(values) {
return nil, fmt.Errorf("length of keys and values should be equal")
}
for i, key := range keys {
for _, search := range searchset {
if res, err := compareSimpleVariables(key, search); err != nil {
return nil, err
} else if res == true {
output = append(output, values[i])
break
}
}
}
// if searchset is empty, then output is an empty list as well.
// if we haven't matched any key, then output is an empty list.
return output, nil
},
}
}
// compare two variables of the same type, i.e. non complex one, such as TypeList or TypeMap
func compareSimpleVariables(a, b ast.Variable) (bool, error) {
if a.Type != b.Type {
return false, fmt.Errorf(
"won't compare items of different types %s and %s",
a.Type.Printable(), b.Type.Printable())
}
switch a.Type {
case ast.TypeString:
return a.Value.(string) == b.Value.(string), nil
default:
return false, fmt.Errorf(
"can't compare items of type %s",
a.Type.Printable())
}
}
// interpolationFuncJoin implements the "join" function that allows
// multi-variable values to be joined by some character.
func interpolationFuncJoin() ast.Function {

View File

@ -684,6 +684,33 @@ func TestInterpolateFuncCoalesce(t *testing.T) {
})
}
func TestInterpolateFuncCoalesceList(t *testing.T) {
testFunction(t, testFunctionConfig{
Cases: []testFunctionCase{
{
`${coalescelist(list("first"), list("second"), list("third"))}`,
[]interface{}{"first"},
false,
},
{
`${coalescelist(list(), list("second"), list("third"))}`,
[]interface{}{"second"},
false,
},
{
`${coalescelist(list(), list(), list())}`,
[]interface{}{},
false,
},
{
`${coalescelist(list("foo"))}`,
nil,
true,
},
},
})
}
func TestInterpolateFuncConcat(t *testing.T) {
testFunction(t, testFunctionConfig{
Cases: []testFunctionCase{
@ -964,6 +991,74 @@ func TestInterpolateFuncDistinct(t *testing.T) {
})
}
func TestInterpolateFuncMatchKeys(t *testing.T) {
testFunction(t, testFunctionConfig{
Cases: []testFunctionCase{
// normal usage
{
`${matchkeys(list("a", "b", "c"), list("ref1", "ref2", "ref3"), list("ref2"))}`,
[]interface{}{"b"},
false,
},
// normal usage 2, check the order
{
`${matchkeys(list("a", "b", "c"), list("ref1", "ref2", "ref3"), list("ref2", "ref1"))}`,
[]interface{}{"a", "b"},
false,
},
// duplicate item in searchset
{
`${matchkeys(list("a", "b", "c"), list("ref1", "ref2", "ref3"), list("ref2", "ref2"))}`,
[]interface{}{"b"},
false,
},
// no matches
{
`${matchkeys(list("a", "b", "c"), list("ref1", "ref2", "ref3"), list("ref4"))}`,
[]interface{}{},
false,
},
// no matches 2
{
`${matchkeys(list("a", "b", "c"), list("ref1", "ref2", "ref3"), list())}`,
[]interface{}{},
false,
},
// zero case
{
`${matchkeys(list(), list(), list("nope"))}`,
[]interface{}{},
false,
},
// complex values
{
`${matchkeys(list(list("a", "a")), list("a"), list("a"))}`,
[]interface{}{[]interface{}{"a", "a"}},
false,
},
// errors
// different types
{
`${matchkeys(list("a"), list(1), list("a"))}`,
nil,
true,
},
// different types
{
`${matchkeys(list("a"), list(list("a"), list("a")), list("a"))}`,
nil,
true,
},
// lists of different length is an error
{
`${matchkeys(list("a"), list("a", "b"), list("a"))}`,
nil,
true,
},
},
})
}
func TestInterpolateFuncFile(t *testing.T) {
tf, err := ioutil.TempFile("", "tf")
if err != nil {

Some files were not shown because too many files have changed in this diff Show More