diff --git a/README.md b/README.md index f7ab4647..c0655587 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,10 @@ Also you can 'dry-run' import, to check what will happen: $ cli53 import --file zonefile.txt --replace --wait --dry-run example.com +Upsert with an imported zone (replace existing and add new records, without deleting): + + $ cli53 import --file zonefile.txt --upsert example.com + Create an A record pointed to 192.168.0.1 with TTL of 60 seconds: $ cli53 rrcreate example.com 'www 60 A 192.168.0.1' diff --git a/commands.go b/commands.go index 431b27ed..e66621e2 100644 --- a/commands.go +++ b/commands.go @@ -239,6 +239,7 @@ type importArgs struct { wait bool editauth bool replace bool + upsert bool dryrun bool } @@ -279,7 +280,7 @@ func importBind(args importArgs) { grouped := groupRecords(records) existing := map[string]*route53.ResourceRecordSet{} - if args.replace { + if args.replace || args.upsert { rrsets, err := ListAllRecordSets(r53, *zone.Id) fatalIfErr(err) for _, rrset := range rrsets { @@ -299,24 +300,34 @@ func importBind(args importArgs) { // no difference - leave it untouched delete(existing, key) } else { - // new record, add - change := route53.Change{ - Action: aws.String("CREATE"), - ResourceRecordSet: rrset, + // new record, add or upsert + if args.upsert { + change := route53.Change{ + Action: aws.String("UPSERT"), + ResourceRecordSet: rrset, + } + additions = append(additions, &change) + } else { + change := route53.Change{ + Action: aws.String("CREATE"), + ResourceRecordSet: rrset, + } + additions = append(additions, &change) } - additions = append(additions, &change) } } } // remaining records in existing should be deleted deletions := []*route53.Change{} - for _, rrset := range existing { - change := route53.Change{ - Action: aws.String("DELETE"), - ResourceRecordSet: rrset, + if !args.upsert { + for _, rrset := range existing { + change := route53.Change{ + Action: aws.String("DELETE"), + ResourceRecordSet: rrset, + } + deletions = append(deletions, &change) } - deletions = append(deletions, &change) } if args.dryrun { diff --git a/internal/features/import.feature b/internal/features/import.feature index e6ed05be..c8bbeaf4 100644 --- a/internal/features/import.feature +++ b/internal/features/import.feature @@ -72,6 +72,12 @@ Feature: import And I run "cli53 import --replace --file tests/replace2.txt $domain" Then the domain "$domain" export matches file "tests/replace2.txt" + Scenario: I can import (upsert) a zone + Given I have a domain "$domain" + When I run "cli53 import --file tests/upsert1.txt $domain" + And I run "cli53 import --upsert --file tests/upsert2.txt $domain" + Then the domain "$domain" export matches file "tests/upsert3.txt" + Scenario: I can import dry-run (with changes) Given I have a domain "$domain" When I run "cli53 import --file tests/replace1.txt $domain" diff --git a/main.go b/main.go index d8891fd7..16e64b87 100644 --- a/main.go +++ b/main.go @@ -153,6 +153,10 @@ func Main(args []string) int { Name: "replace", Usage: "replace all existing records", }, + &cli.BoolFlag{ + Name: "upsert", + Usage: "update or replace records, do not delete existing", + }, &cli.BoolFlag{ Name: "dry-run", Aliases: []string{"n"}, @@ -174,6 +178,7 @@ func Main(args []string) int { wait: c.Bool("wait"), editauth: c.Bool("editauth"), replace: c.Bool("replace"), + upsert: c.Bool("upsert"), dryrun: c.Bool("dry-run"), } importBind(args) diff --git a/tests/upsert1.txt b/tests/upsert1.txt new file mode 100644 index 00000000..2cd3af15 --- /dev/null +++ b/tests/upsert1.txt @@ -0,0 +1,14 @@ +@ 86400 IN A 10.0.0.1 +@ 86400 IN MX 10 mail.example.com. +@ 86400 IN MX 20 mail2.example.com. +@ 900 IN SOA ns1.somenameserver.com. blah.example.com. 1 7200 900 1209600 86400 +@ 172800 IN NS ns1.somenameserver.com. +@ 172800 IN NS ns2.somenameserver.com. +@ 86400 IN TXT "v=spf1 a mx a:cli53.example.com mx:mail.example.com ip4:10.0.0.0/24 ~all" +mail 86400 IN A 10.0.0.2 +mail2 86400 IN A 10.0.0.3 +test 86400 IN TXT "multivalued" " txt \"quoted\" record" +www 86400 IN A 10.0.0.1 +www2 86400 IN A 10.0.0.1 +unchanged 86400 IN A 10.1.0.1 +alias 86400 AWS ALIAS A www $self false diff --git a/tests/upsert2.txt b/tests/upsert2.txt new file mode 100644 index 00000000..c1984a65 --- /dev/null +++ b/tests/upsert2.txt @@ -0,0 +1,5 @@ +test 86400 IN TXT "multivalued" " txt \"quoted\" record" +www2 86400 IN A 10.0.0.5 +www3 86400 IN A 10.0.0.5 +unchanged 86400 IN A 10.1.0.1 +alias 86400 AWS ALIAS A www $self false diff --git a/tests/upsert3.txt b/tests/upsert3.txt new file mode 100644 index 00000000..59fcef4f --- /dev/null +++ b/tests/upsert3.txt @@ -0,0 +1,15 @@ +@ 86400 IN A 10.0.0.1 +@ 86400 IN MX 10 mail.example.com. +@ 86400 IN MX 20 mail2.example.com. +@ 900 IN SOA ns1.somenameserver.com. blah.example.com. 1 7200 900 1209600 86400 +@ 172800 IN NS ns1.somenameserver.com. +@ 172800 IN NS ns2.somenameserver.com. +@ 86400 IN TXT "v=spf1 a mx a:cli53.example.com mx:mail.example.com ip4:10.0.0.0/24 ~all" +mail 86400 IN A 10.0.0.2 +mail2 86400 IN A 10.0.0.3 +test 86400 IN TXT "multivalued" " txt \"quoted\" record" +www 86400 IN A 10.0.0.1 +www2 86400 IN A 10.0.0.5 +www3 86400 IN A 10.0.0.5 +unchanged 86400 IN A 10.1.0.1 +alias 86400 AWS ALIAS A www $self false