DynamoDB UpdateItem failed - go

I am using DynamoDB Go SDK for CRUD operations. I verified PutItem and GetItem calls are working fine. However, when I use UpdateItem which updates some attributes, it fails. I narrowed it down to specific to an attribute that stores the current timestamp in epoch format.
updateItem := &dynamodb.UpdateItemInput{
TableName:aws.String(tableName),
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
":r": {
S:aws.String("Renamed"),
},
":rp": {
S: aws.String("RenamingParty"),
},
":rr": {
S: aws.String("RenameReason"),
},
"rt": {
N: aws.String(strconv.FormatInt(time.Now().Unix(), 10)),
},
},
Key: map[string]*dynamodb.AttributeValue{
"pKey": {
S: aws.String(pKey),
},
"rKey": {
S:aws.String(rKey),
},
},
ReturnValues:aws.String("ALL_NEW"),
UpdateExpression:aws.String("set RenameStatus = :r, RenamingParty = :rp, RenameReason = :rr RenameTime = :rt"),
}
_, err := svc.UpdateItem(updateItem)
Error returned:
failed to update item: %v ValidationException: ExpressionAttributeValues contains invalid key: Syntax error; key: "rt"
status code: 400, request id:

It seems like your attribute rt is missing a : -> :rt :)

Related

How to write a custom terraform provider schema for a map of maps values? [duplicate]

I have a golang function that returns roles of type map[string]map[string]string
eg:
map[foo:map[name:abc env:dev id:465 project:e-1] boo:map[name:def env:prd id:82 project:e-1] :doo[name:ght env:stg id:353 project:e-3]]
and I created a schema for it like the following...
func dataSourceAwsAccountHelper() *schema.Resource {
return &schema.Resource{
Read: accountHelperRead,
Schema: map[string]*schema.Schema{
"roles": {
Type: schema.TypeMap,
Elem: &schema.Schema{
Type: schema.TypeMap,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
Computed: true,
},
"id": &schema.Schema{
Computed: true,
Type: schema.TypeString,
},
},
}
}
And the create method to pass the role values to the schema
func rolesRead(d *schema.ResourceData, m interface{}) error {
filteredRoles := filterAccounts("john") // returns `map[string]map[string]string`
if err := d.Set("account_map", filteredRoles); err != nil {
return err
}
//accountMaps := make(map[string]interface{})
d.SetId("-")
return nil
}
but the Terraform output is an empty map, how do I fix it please help :)
Outputs:
output = {
"roles" = tomap(null) /* of map of string */
"id" = tostring(null)
}
expecting output like
Outputs:
output = {
"roles" = { foo = {name = "abc" env = "dev" id= 465 project = "e-1"}
boo = {name = "efg" env = "prd" id= 82 project = "e-2"}
},
"id" = "-"
}
What you are trying to do here is not possbile with the legacy Terraform SDK that you are using. Maps can only be of primitive types: TypeString, TypeInt, TypeBool.
To create this structure you'll need to migrate to the new framework, which is built for the type system of modern Terraform rather than (as is the case for SDKv2) the type system of classic Terraform v0.11 and earlier.
In the Terraform Plugin Framework, the equivalent structure to what you tried to describe here is MapNestedAttribute, with the following describing the schema structure you showed in your question:
schema.MapNestedAttribute{
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
// ...
},
"env": schema.StringAttribute{
// ...
},
"id": schema.NumberAttribute{
// ...
},
"project": schema.StringAttribute{
// ...
},
},
},
}
This represents a map of objects with the given attributes, and so the above schema type is equivalent to the following type constraint as might be written in a Terraform module using the Terraform language's type constraint syntax:
map(
object({
name = string
env = string
id = number
project = string
})
)

How to create a custom terraform datasource provider schema for a map of maps values?

I have a golang function that returns roles of type map[string]map[string]string
eg:
map[foo:map[name:abc env:dev id:465 project:e-1] boo:map[name:def env:prd id:82 project:e-1] :doo[name:ght env:stg id:353 project:e-3]]
and I created a schema for it like the following...
func dataSourceAwsAccountHelper() *schema.Resource {
return &schema.Resource{
Read: accountHelperRead,
Schema: map[string]*schema.Schema{
"roles": {
Type: schema.TypeMap,
Elem: &schema.Schema{
Type: schema.TypeMap,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
Computed: true,
},
"id": &schema.Schema{
Computed: true,
Type: schema.TypeString,
},
},
}
}
And the create method to pass the role values to the schema
func rolesRead(d *schema.ResourceData, m interface{}) error {
filteredRoles := filterAccounts("john") // returns `map[string]map[string]string`
if err := d.Set("account_map", filteredRoles); err != nil {
return err
}
//accountMaps := make(map[string]interface{})
d.SetId("-")
return nil
}
but the Terraform output is an empty map, how do I fix it please help :)
Outputs:
output = {
"roles" = tomap(null) /* of map of string */
"id" = tostring(null)
}
expecting output like
Outputs:
output = {
"roles" = { foo = {name = "abc" env = "dev" id= 465 project = "e-1"}
boo = {name = "efg" env = "prd" id= 82 project = "e-2"}
},
"id" = "-"
}
What you are trying to do here is not possbile with the legacy Terraform SDK that you are using. Maps can only be of primitive types: TypeString, TypeInt, TypeBool.
To create this structure you'll need to migrate to the new framework, which is built for the type system of modern Terraform rather than (as is the case for SDKv2) the type system of classic Terraform v0.11 and earlier.
In the Terraform Plugin Framework, the equivalent structure to what you tried to describe here is MapNestedAttribute, with the following describing the schema structure you showed in your question:
schema.MapNestedAttribute{
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
// ...
},
"env": schema.StringAttribute{
// ...
},
"id": schema.NumberAttribute{
// ...
},
"project": schema.StringAttribute{
// ...
},
},
},
}
This represents a map of objects with the given attributes, and so the above schema type is equivalent to the following type constraint as might be written in a Terraform module using the Terraform language's type constraint syntax:
map(
object({
name = string
env = string
id = number
project = string
})
)

Accessing typeset in Golang from the terraform schema and iterate over the Map

I am trying to access a key element in Golang with the following schema via terraform config file:
"vehicles": {
Type: schema.TypeSet,
Optional: true,
MaxItems: 5,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"car": {
Type: schema.TypeList,
Optional: true,
MaxItems: 2,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"make": {
Type: schema.TypeString,
Optional: true,
},
"model": {
Type: schema.TypeString,
Optional: true,
},
},
},
},
},
},
}
In config file,
resource "type_test" "type_name" {
vehicles {
car {
make = "Toyota"
model = "Camry"
}
car {
make = "Nissan"
model = "Rogue"
}
}
}
I want to iterate over the list and access the vehicles map via Golang.
The terraform crashes with the below code:
vehicles_map, ok = d.getOK("vehicles")
if ok {
vehicleSet := vehicles_d.(*schema.Set)List()
for i, vehicle := range vehicleSet {
mdi, ok = vehicle.(map[string]interface{})
if ok {
log.Printf("%v", mdi["vehicles"].(map[string]interface{})["car"])
}
}
Crash Log:
2019-12-25T21 [DEBUG] plugin.terraform-provider: panic: interface conversion: interface {} is nil, not map[string]interface {}
for line "log.Printf("%v", mdi["vehicles"].(map[string]interface{})["car"])"
I want to print and access the each vehicles element in the config file, any help would be appreciated.
d.getOK("vehicles") already performs the indexing with "vehicles" key, which results in a *schema.Set. Calling its Set.List() method, you get a slice (of type []interface{}). Iterating over its elements will give you values that represent a car, modeled with type map[string]interface{}. So inside the loop you just have to type assert to this type, and not index again with "vehicles" nor with "car".
Something like this:
for i, vehicle := range vehicleSet {
car, ok := vehicle.(map[string]interface{})
if ok {
log.Printf("model: %v, make: %v\n", car["model"], car["make"])
}
}

Writing a Terraform provider with nested map

I am writing a custom provider with 2 level deeply nested map. I am able to expand the schema when calling create function. However, I am having issues when I try to set this value from read function after this resource has been created. I tried to follow Terraform documentation steps, "Complex read" section but I am getting error Invalid address to set: []string{"docker_info", "0", "port_mapping"}.
Schema looks like this:
"docker_info": {
Type: schema.TypeList,
Required: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"image": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"force_pull_image": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: "false",
},
"network": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "BRIDGE",
ValidateFunc: validateDockerNetwork,
},
// We use typeSet because this parameter can be unordered list and must be unique.
"port_mapping": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"host_port": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
"container_port": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
"container_port_type": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateSingularityPortMappingType,
},
"host_port_type": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateSingularityPortMappingType,
},
"protocol": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateSingularityPortProtocol,
Default: "tcp",
},
},
},
},
},
},
},
in my read function, I have:
d.Set("docker_info", flattenDockerInfo(ContainerInfo.DockerInfo))
func flattenDockerInfo(in singularity.DockerInfo) []interface{} {
var out = make([]interface{}, 0, 0)
m := make(map[string]interface{})
m["network"] = in.Network
m["image"] = in.Image
m["force_pull_image"] = in.ForcePullImage
if len(in.PortMappings) > 0 {
m["port_mapping"] = flattenDockerPortMappings(in.PortMappings)
}
out = append(out, m)
return out
}
func flattenDockerPortMappings(in []singularity.DockerPortMapping []map[string]interface{} {
var out = make([]map[string]interface{}, len(in), len(in))
for i := range in {
m := make(map[string]interface{})
m["container_port"] = v.ContainerPort
m["container_port_type"] = v.ContainerPortType
m["host_port"] = v.HostPort
m["host_port_type"] = v.HostPortType
m["protocol"] = v.Protocol
out[i] = m
}
return out
}
singularityDocker struct:
type DockerInfo struct {
Parameters map[string]string
`json:"parameters,omitempty"`
ForcePullImage bool
`json:"forcePullImage,omitempty"`
SingularityDockerParameters []SingularityDockerParameter
`json:"dockerParameters,omitEmpty"`
Privileged bool
`json:"privileged,omitEmpty"`
Network string
`json:"network,omitEmpty"` //Value can be BRIDGE, HOST, or NONE
Image string
`json:"image"`
PortMappings []DockerPortMapping
`json:"portMappings,omitempty"`
}
type DockerPortMapping struct {
ContainerPort int64 `json:"containerPort"`
ContainerPortType string `json:"containerPortType,omitempty"`
HostPort int64 `json:"hostPort"`
HostPortType string `json:"hostPortType,omitempty"`
Protocol string `json:"protocol,omitempty"`
}
I expect to see something like
"docker_info.0.port_mapping.3218452487.container_port": "8888",
"docker_info.0.port_mapping.3218452487.container_port_type": "LITERAL",
"docker_info.0.port_mapping.3218452487.host_port": "0",
"docker_info.0.port_mapping.3218452487.host_port_type": "FROM_OFFER",
"docker_info.0.port_mapping.3218452487.protocol": "tcp",
I found out that by adding below code to create a typeSet
m["port_mapping"] = schema.NewSet(portMappingHash, []interface{}{flattenDockerPortMappings(in.PortMappings)})
func portMappingHash(v interface{}) int {
var buf bytes.Buffer
x := v.([]map[string]interface{})
for i := range x {
buf.WriteString(fmt.Sprintf("%s-%d", "test", i))
}
return hashcode.String(buf.String())
}
I now get docker_info.0.port_mapping.2384314926: '' expected a map, got 'slice'

Parsing dynamodb.GetItemOutput type in Golang

So basically I an kinda new to golang and need to parse the output of dynamodb.GetItemOutput type. Here is a sample:
{
Item: {
share: {
L: [{
S: "Hello"
},{
S: "Brave"
}]
},
userid: {
S: "43"
},
amount: {
S: "1000"
},
library: {
L: [{
S: "Demons"
},{
S: "HUMBLE"
}]
}
}
}
Any help would be wonderful.
Thanks a lot.
I would recommend to use ODM (Object Document Mapper) for Go
We have used the following and would recommend it.
https://github.com/guregu/dynamo
You can use with regular json objects and ODM will convert it to dynamodb form and manage mapping automatically.
db := dynamo.New(session.New(), &aws.Config{Region: aws.String("us-west-2")})
table := db.Table("Widgets")
// put item
w := widget{UserID: 613, Time: time.Now(), Msg: "hello"}
err := table.Put(w).Run()
// get the same item
var result widget
err = table.Get("UserID", w.UserID).
Range("Time", dynamo.Equal, w.Time).
Filter("'Count' = ? AND $ = ?", w.Count, "Message", w.Msg). // placeholders in expressions
One(&result)
// get all items
var results []widget
err = table.Scan().All(&results)
In the above example the code just deals only with javascript objects, mapping to dynamodb is automatically taken care.
Hope it helps.

Resources