Skip to content

Commit c045497

Browse files
authored
Add support for updating RDS tags (#5)
* change license * update buffalo version * add support for updating RDS tags * fixup * add tests for DetermineArn * fixup * fixup * fixup
1 parent 11a589f commit c045497

File tree

4 files changed

+179
-1
lines changed

4 files changed

+179
-1
lines changed

README.md

+16
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,22 @@ PUT http://127.0.0.1:3000/v1/rds/{account}/myaurora
124124
}
125125
```
126126

127+
### Updating tags for a database
128+
129+
You can pass a list of tags (Key/Value pairs) to add or updated on the given database. If there is an RDS cluster and instance with the same name, the tags for both will be updated.
130+
131+
```
132+
PUT http://127.0.0.1:3000/v1/rds/{account}/myaurora
133+
{
134+
"Tags": [
135+
{
136+
"Key": "NewTag",
137+
"Value": "new"
138+
}
139+
]
140+
}
141+
```
142+
127143
### Deleting a database
128144

129145
By default, a final snapshot is _not_ created when deleting a database instance. You can override that by adding `snapshot=true` query parameter.

actions/databases.go

+33-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ type DatabaseModifyInput struct {
2727
Cluster *rds.ModifyDBClusterInput
2828
// https://docs.aws.amazon.com/sdk-for-go/api/service/rds/#ModifyDBInstanceInput
2929
Instance *rds.ModifyDBInstanceInput
30+
Tags []*rds.Tag
3031
}
3132

3233
// DatabasesList gets a list of databases for a given account
@@ -251,16 +252,21 @@ func DatabasesPost(c buffalo.Context) error {
251252

252253
// DatabasesPut modifies a database in a given account
253254
// Either Cluster or Instance input parameters can be specified for a request
255+
// Tags list can be given with any key/value tags to add/update
254256
func DatabasesPut(c buffalo.Context) error {
255257
input := DatabaseModifyInput{}
256258
if err := c.Bind(&input); err != nil {
257259
log.Println(err)
258260
return c.Error(400, err)
259261
}
260-
if (input.Cluster == nil && input.Instance == nil) || (input.Cluster != nil && input.Instance != nil) {
262+
if input.Cluster == nil && input.Instance == nil && input.Tags == nil {
261263
return c.Error(400, errors.New("Bad request"))
262264
}
263265

266+
if input.Cluster != nil && input.Instance != nil {
267+
return c.Error(400, errors.New("Bad request: cannot specify both Cluster and Instance"))
268+
}
269+
264270
rdsClient, ok := RDS[c.Param("account")]
265271
if !ok {
266272
return c.Error(400, errors.New("Bad request: unknown account "+c.Param("account")))
@@ -294,6 +300,32 @@ func DatabasesPut(c buffalo.Context) error {
294300
log.Println("Modified RDS instance", instanceOutput)
295301
}
296302

303+
if input.Tags != nil {
304+
log.Println("Updating tags for "+c.Param("db"), input.Tags)
305+
tags := &rds.AddTagsToResourceInput{}
306+
tags.Tags = input.Tags
307+
308+
// determine ARN(s) for this RDS resource
309+
arns, err := rdsClient.DetermineArn(c.Param("db"))
310+
if err != nil {
311+
log.Println(err)
312+
return c.Error(400, err)
313+
}
314+
315+
// update tags for all RDS resources with matching ARNs
316+
for _, arn := range arns {
317+
tags.ResourceName = aws.String(arn)
318+
if _, err = rdsClient.Service.AddTagsToResourceWithContext(c, tags); err != nil {
319+
log.Println(err.Error())
320+
if aerr, ok := err.(awserr.Error); ok {
321+
return c.Error(400, aerr)
322+
}
323+
return err
324+
}
325+
log.Println("Updated tags for RDS resource", arn)
326+
}
327+
}
328+
297329
output := struct {
298330
*rds.ModifyDBClusterOutput
299331
*rds.ModifyDBInstanceOutput

pkg/rds/tags.go

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package rds
2+
3+
import (
4+
"errors"
5+
"log"
6+
7+
"github.com/aws/aws-sdk-go/aws"
8+
"github.com/aws/aws-sdk-go/service/rds"
9+
)
10+
11+
// DetermineArn returns the ARN for an RDS instance or cluster given the database name
12+
// It could return 2 ARNs if a cluster and instance with the same name exist
13+
func (cl Client) DetermineArn(dbName string) ([]string, error) {
14+
arns := []string{}
15+
16+
log.Println("Trying to determine ARN for", dbName)
17+
db := aws.String(dbName)
18+
19+
// search clusters for the given db name
20+
clustersOutput, _ := cl.Service.DescribeDBClusters(&rds.DescribeDBClustersInput{
21+
DBClusterIdentifier: db,
22+
})
23+
24+
// search instances for the given db name
25+
instancesOutput, _ := cl.Service.DescribeDBInstances(&rds.DescribeDBInstancesInput{
26+
DBInstanceIdentifier: db,
27+
})
28+
29+
if clustersOutput == nil && instancesOutput == nil {
30+
return nil, errors.New("Unable to determine ARN for database " + dbName)
31+
}
32+
33+
for _, cluster := range clustersOutput.DBClusters {
34+
arns = append(arns, aws.StringValue(cluster.DBClusterArn))
35+
}
36+
37+
for _, instance := range instancesOutput.DBInstances {
38+
arns = append(arns, aws.StringValue(instance.DBInstanceArn))
39+
}
40+
41+
if len(arns) == 0 {
42+
return nil, errors.New("Unable to determine ARN for database " + dbName)
43+
}
44+
45+
return arns, nil
46+
}

pkg/rds/tags_test.go

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package rds
2+
3+
import (
4+
"testing"
5+
6+
"github.com/aws/aws-sdk-go/aws"
7+
"github.com/aws/aws-sdk-go/service/rds"
8+
"github.com/aws/aws-sdk-go/service/rds/rdsiface"
9+
)
10+
11+
type mockRdsService struct {
12+
rdsiface.RDSAPI
13+
}
14+
15+
func (m *mockRdsService) DescribeDBClusters(input *rds.DescribeDBClustersInput) (*rds.DescribeDBClustersOutput, error) {
16+
dbName := *input.DBClusterIdentifier
17+
18+
if dbName == "unknown" || dbName == "instance" {
19+
return &rds.DescribeDBClustersOutput{}, nil
20+
}
21+
22+
return &rds.DescribeDBClustersOutput{
23+
DBClusters: []*rds.DBCluster{
24+
&rds.DBCluster{
25+
DBClusterArn: aws.String("arn:aws:rds:us-east-1:123456789012:db:" + dbName),
26+
},
27+
},
28+
}, nil
29+
}
30+
31+
func (m *mockRdsService) DescribeDBInstances(input *rds.DescribeDBInstancesInput) (*rds.DescribeDBInstancesOutput, error) {
32+
dbName := *input.DBInstanceIdentifier
33+
34+
if dbName == "unknown" || dbName == "cluster" {
35+
return &rds.DescribeDBInstancesOutput{}, nil
36+
}
37+
38+
return &rds.DescribeDBInstancesOutput{
39+
DBInstances: []*rds.DBInstance{
40+
&rds.DBInstance{
41+
DBInstanceArn: aws.String("arn:aws:rds:us-east-1:123456789012:db:" + dbName),
42+
},
43+
},
44+
}, nil
45+
}
46+
47+
func TestDetermineArn(t *testing.T) {
48+
mc := Client{
49+
Service: &mockRdsService{},
50+
}
51+
52+
got, err := mc.DetermineArn("cluster")
53+
t.Log(got)
54+
if err != nil {
55+
t.Fatalf("Expected error nil, got: %v", err)
56+
}
57+
if len(got) != 1 {
58+
t.Fatalf("Expected single ARN, got: %v", len(got))
59+
}
60+
61+
got, err = mc.DetermineArn("instance")
62+
t.Log(got)
63+
if err != nil {
64+
t.Fatalf("Expected error nil, got: %v", err)
65+
}
66+
if len(got) != 1 {
67+
t.Fatalf("Expected single ARN, got: %v", len(got))
68+
}
69+
70+
got, err = mc.DetermineArn("both")
71+
t.Log(got)
72+
if err != nil {
73+
t.Fatalf("Expected error nil, got: %v", err)
74+
}
75+
if len(got) != 2 {
76+
t.Fatalf("Expected two ARNs, got: %v", len(got))
77+
}
78+
79+
got, err = mc.DetermineArn("unknown")
80+
t.Log(got)
81+
if err == nil {
82+
t.Fatalf("Expected error, got: nil")
83+
}
84+
}

0 commit comments

Comments
 (0)