Make a resource schema dependant on another variable - go

I'm creating a plugin in Terraform and I want to add a field to the schema which can be called only when another field has been provided.
"host_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("host_name", nil),
Description: "Should give name in FQDN if being used for DNS puposes .",
},
"enableDns": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("host_name", nil),
Description: "Should give name in FQDN if being used for DNS puposes .",
Here I want to pass the enableDns string in the .tf file only when host_name is passed. If it is not given and I pass enableDns it should throw an error during plan.

Terraform providers don't really have a first class way to do stuff conditionally based on other parameters other than the ConflictsWith attribute.
There is a hacky way to do some cross parameter stuff using CustomizeDiff but it's only really used in a couple of places where it's really needed.
Normally the provider would simply validate the individual parameters and if the API that the provider is working against requires cross parameter validation then this is only seen at apply time when the API returns the error.
For an example of using CustomizeDiff to throw a plan time error when doing cross parameter validation see the aws_elasticache_cluster resource:
CustomizeDiff: customdiff.Sequence(
func(diff *schema.ResourceDiff, v interface{}) error {
// Plan time validation for az_mode
// InvalidParameterCombination: Must specify at least two cache nodes in order to specify AZ Mode of 'cross-az'.
if v, ok := diff.GetOk("az_mode"); !ok || v.(string) != elasticache.AZModeCrossAz {
return nil
}
if v, ok := diff.GetOk("num_cache_nodes"); !ok || v.(int) != 1 {
return nil
}
return errors.New(`az_mode "cross-az" is not supported with num_cache_nodes = 1`)
},

Related

How to return value from the Looker Golang sdk

I'm attempting to perform the run inline query api endpoint using the Looker Golang SDK. https://github.com/looker-open-source/sdk-codegen/blob/474ee9365dafe6549826a9f627ac0a79dc0e9a56/go/sdk/v4/models.go
https://developers.looker.com/api/explorer/4.0/methods/Query/run_inline_query
However when I execute this I get a blank response back not the expected data from fmt.Println(response). I also attempted to create and run, a sql query, while it looked like the API call executed I didn't receive any results in a very similar fashion. I feel like i'm missing something in getting results. I've validated these queries run in Looker.
Massive disclaimer, I'm a beginner to Golang. I'd really appreciate anyone being able to tell me what i'm doing wrong here.
func getDashboards(sdk *looker.LookerSDK) error {
fields := []string{
"dashboard.id",
"dashboard_element.id",
"dashboard_element.type",
"dashboard_element.result_source",
"query.model",
"query.view",
"query.formatted_fields",
"query.id",
"dashboard.title",
"look.id"}
limit := "5000"
response, err := sdk.RunInlineQuery(
looker.RequestRunInlineQuery{
ResultFormat: "json",
Body: looker.WriteQuery{
Model: "system__activity",
View: "dashboard",
Fields: &fields,
Limit: &limit,
},
}, nil)
if err != nil {
fmt.Println(err)
}
fmt.Println(response)
return err
}

How can I test validation functionality in a terraform provider

I have written some more sophisticated validation logic for fields I need to validate in a custom terraform provider. I can, of course, test these are unit tests, but that's insufficient; why if I forgot to actually apply the validator?
So, I need to actually use terraform config and have the provider do it's normal, natural thing.
Basically, I expect it to error. The documentation seems to indicate that I should do a regex match on the output. But this can't be right; it seems super brittle. Can someone tell me how this is done?
func TestValidation(t *testing.T) {
const userBasic = `
resource "my_user" "dude" {
name = "the.dude%d"
password = "Password1" // needs a special char to pass
email = "the.dude%d#domain.com"
groups = [ "readers" ]
}
`
rgx, _ := regexp.Compile("abc")
resource.UnitTest(t, resource.TestCase{
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: userBasic,
ExpectError: rgx,
},
},
})
}
This code obviously doesn't work. And, a lot of research isn't yielding answers.
Since sdk version 2.3.0 you can set ErrorCheck function on resource.TestCase to provide more complex validation for errors.
For example:
resource.UnitTest(t, resource.TestCase{
Providers: testAccProviders,
ErrorCheck: func(err error) error {
if err == nil {
return errors.New("expected error but got none")
}
// your validation code here
// some simple example with string matching
if strings.Contains(err.Error(), "your expected error message blah") {
return nil
}
// return original error if no match
return err
},
Steps: []resource.TestStep{
{
Config: userBasic,
},
},
})

AWS Route53 - Adding Simple Record

I am able to add "Weighted" A records for the AWS Route53 using the API, using [Weight: aws.Int64(weight)], it works great using the code below. But how to add "Simple" A record - I did not see an option for Simple?
params := &route53.ChangeResourceRecordSetsInput{
ChangeBatch: &route53.ChangeBatch{ // Required
Changes: []*route53.Change{ // Required
{ // Required
Action: aws.String("UPSERT"), // Required
ResourceRecordSet: &route53.ResourceRecordSet{ // Required
Name: aws.String(name), // Required
Type: aws.String("A"), // Required
ResourceRecords: []*route53.ResourceRecord{
{ // Required
Value: aws.String(target), // Required
},
},
TTL: aws.Int64(TTL),
//Region: aws.String("us-east-1"),
Weight: aws.Int64(weight),
SetIdentifier: aws.String("-"),
},
},
},
Comment: aws.String("Sample update."),
},
HostedZoneId: aws.String(zoneId), // Required
}
A 'Simple' record is just a phrasing inside the Web Console. Just leave the record without any Weight or Latency flag and it will be a standard DNS record inside Route53.
See the type ResourceRecordSet documentation, required fields are marked. The rest, like Weight are optional!
https://docs.aws.amazon.com/sdk-for-go/api/service/route53/#ResourceRecordSet
Pretty much the same as using the CLI (https://aws.amazon.com/premiumsupport/knowledge-center/simple-resource-record-route53-cli/), just port the same fields to the Go struct.
after a couple of hours of searching that lead nowhere, I found that using the aws-sdk-go-v2 works well for adding a simple record, trying to not add a weight or latency flag will only result in an error while using the version 1 of the SDK, so I did this and worked fine:
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
// handle error
return err
}
client := route53.NewFromConfig(cfg)
params := &route53.ChangeResourceRecordSetsInput{
ChangeBatch: &types.ChangeBatch{
Changes: []types.Change{
{
Action: types.ChangeActionUpsert,
ResourceRecordSet: &types.ResourceRecordSet{
Name: aws.String(name),
Type: types.RRTypeCname,
ResourceRecords: []types.ResourceRecord{
{
Value: aws.String(target),
},
},
TTL: aws.Int64(60),
},
},
},
},
HostedZoneId: aws.String(zoneId),
}
output, err := client.ChangeResourceRecordSets(context.TODO(), params)

Programmatically get Account Id from lambda context arn

I have access to
com.amazonaws.services.lambda.runtime.Context;
object and by extension the invoked function Arn. The arn contains the account Id where the lambda resides.
My question is simple, I want the cleanest way to extract the account Id from that.
I was taking a look
com.amazon.arn.ARN;
It has a whole bunch of stuff, but no account ID (which i presume is due to the fact that not all arns have account ids ?)
I want to cleanly extract the account Id, without resorting to parsing the string.
If your lambda is being used as an API Gateway proxy lambda, then you have access to event.requestContext.accountId (where event is the first parameter to your handler function).
Otherwise, you will have to split the ARN up.
From the AWS documentation about ARN formats, here are the valid Lambda ARN formats:
arn:aws:lambda:region:account-id:function:function-name
arn:aws:lambda:region:account-id:function:function-name:alias-name
arn:aws:lambda:region:account-id:function:function-name:version
arn:aws:lambda:region:account-id:event-source-mappings:event-source-mapping-id
In all cases, account-id is the 5th item in the ARN (treating : as a separator). Therefore, you can just do this:
String accountId = arn.split(":")[4];
You no longer need to parse the arn anymore, sts library has introduced get_caller_identity for this purpose.
Its an overkill, but works!.
Excerpts from aws docs.
python
import boto3
client = boto3.client('sts')
response = client.get_caller_identity()['Account']
js
/* This example shows a request and response made with the credentials for a user named Alice in the AWS account 123456789012. */
var params = {
};
sts.getCallerIdentity(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
/*
data = {
Account: "123456789012",
Arn: "arn:aws:iam::123456789012:user/Alice",
UserId: "AKIAI44QH8DHBEXAMPLE"
}
*/
});
More details here & here
I use this:
ACCID: { "Fn::Join" : ["", [{ "Ref" : "AWS::AccountId" }, "" ]] }
golang
import (
"github.com/aws/aws-lambda-go/lambdacontext"
)
func Handler(ctx context.Context) error {
lc, ok := lambdacontext.FromContext(ctx)
if !ok {
return errors.Errorf("could not get lambda context")
}
AwsAccountId := strings.Split(lc.InvokedFunctionArn, ":")[4]

kubernetes filter objects in Informer

Im writing custom controller for kubernetes.
Im creating shared informer
cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options meta_v1.ListOptions) (k8sruntime.Object, error) {
return client.CoreV1().ConfigMaps(nameSpace).List(options)
},
WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
return client.CoreV1().ConfigMaps(nameSpace).Watch(options)
},
},
&api_v1.ConfigMap{},
0, //Skip resyncr
cache.Indexers{},
)
I have option to add filtering function into call back functions to further decrease number of objects im working with.
Something like that
options.FieldSelector := fields.OneTermEqualSelector("metadata.name", nodeName).String()
I would like to filter out objects by regular expression. Or by some label at least. Unfortunately documentation is not helping. Could not find anything except for tests for code itself.
Ho do i apply regular expression on filtering mechanism?
Where do i get some examples on this issue?
Its not possible to filter objects by regular expression.
It is possible to filer object by lable
This is the code that will filter by label
labelSelector := labels.Set(map[string]string{"mylabel": "ourdaomain1"}).AsSelector()
informer := cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options meta_v1.ListOptions) (k8sruntime.Object, error) {
options.LabelSelector = labelSelector.String()
return client.CoreV1().ConfigMaps(nameSpace).List(options)
},
WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
options.LabelSelector = labelSelector.String()
return client.CoreV1().ConfigMaps(nameSpace).Watch(options)
},
},
&api_v1.ConfigMap{},
0, //Skip resyncr
cache.Indexers{},
)
Another thing that is important to remember is how do you add new objects to k8s
I was doing something like
kubectl --namespace==ourdomain1 create configmap config4 -f ./config1.yaml
This is not good. It overwrites all the fields in config map and puts whole file content into data of the new object.
Proper way is
kubectl create -f ./config1.yam

Resources