Commit 99154ae7 authored by Martin Linkhorst's avatar Martin Linkhorst Committed by GitHub
Browse files

Merge pull request #55 from linki/route53-impl

route53: initial implementation of Route53 DNS provider
parents 7058f6c4 94907b90
Showing with 2302 additions and 24 deletions
+2302 -24
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dnsprovider
import (
"fmt"
log "github.com/Sirupsen/logrus"
"github.com/google/uuid"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/route53"
"github.com/kubernetes-incubator/external-dns/endpoint"
"github.com/kubernetes-incubator/external-dns/plan"
)
// Route53API is the subset of the AWS Route53 API that we actually use. Add methods as required. Signatures must match exactly.
// mostly taken from: https://github.com/kubernetes/kubernetes/blob/853167624edb6bc0cfdcdfb88e746e178f5db36c/federation/pkg/dnsprovider/providers/aws/route53/stubs/route53api.go
type Route53API interface {
ListResourceRecordSetsPages(input *route53.ListResourceRecordSetsInput, fn func(resp *route53.ListResourceRecordSetsOutput, lastPage bool) (shouldContinue bool)) error
ChangeResourceRecordSets(*route53.ChangeResourceRecordSetsInput) (*route53.ChangeResourceRecordSetsOutput, error)
ListHostedZonesPages(input *route53.ListHostedZonesInput, fn func(resp *route53.ListHostedZonesOutput, lastPage bool) (shouldContinue bool)) error
ListHostedZonesByName(input *route53.ListHostedZonesByNameInput) (*route53.ListHostedZonesByNameOutput, error)
CreateHostedZone(*route53.CreateHostedZoneInput) (*route53.CreateHostedZoneOutput, error)
DeleteHostedZone(*route53.DeleteHostedZoneInput) (*route53.DeleteHostedZoneOutput, error)
}
// AWSProvider is an implementation of DNSProvider for AWS Route53.
type AWSProvider struct {
Client Route53API
DryRun bool
}
// Zones returns the list of hosted zones.
func (p *AWSProvider) Zones() ([]string, error) {
zones := []string{}
f := func(resp *route53.ListHostedZonesOutput, lastPage bool) (shouldContinue bool) {
for _, zone := range resp.HostedZones {
zones = append(zones, aws.StringValue(zone.Name))
}
return true
}
err := p.Client.ListHostedZonesPages(&route53.ListHostedZonesInput{}, f)
if err != nil {
return zones, err
}
return zones, nil
}
// Zone returns a single zone given a DNS name.
func (p *AWSProvider) Zone(dnsName string) (*route53.HostedZone, error) {
params := &route53.ListHostedZonesByNameInput{
DNSName: aws.String(dnsName),
}
resp, err := p.Client.ListHostedZonesByName(params)
if err != nil {
return nil, err
}
if len(resp.HostedZones) != 1 {
return nil, fmt.Errorf("not exactly one hosted zone found by name, got %d", len(resp.HostedZones))
}
return resp.HostedZones[0], nil
}
// CreateZone creates a hosted zone given a name.
func (p *AWSProvider) CreateZone(name string) (*route53.HostedZone, error) {
params := &route53.CreateHostedZoneInput{
CallerReference: aws.String(uuid.New().String()),
Name: aws.String(name),
}
resp, err := p.Client.CreateHostedZone(params)
if err != nil {
return nil, err
}
return resp.HostedZone, nil
}
// DeleteZone deletes a hosted zone given a name.
func (p *AWSProvider) DeleteZone(name string) error {
params := &route53.DeleteHostedZoneInput{
Id: aws.String(name),
}
_, err := p.Client.DeleteHostedZone(params)
if err != nil {
return err
}
return nil
}
// Records returns the list of records in a given hosted zone.
func (p *AWSProvider) Records(zone string) ([]endpoint.Endpoint, error) {
hostedZone, err := p.Zone(zone)
if err != nil {
return nil, err
}
params := &route53.ListResourceRecordSetsInput{
HostedZoneId: hostedZone.Id,
}
endpoints := []endpoint.Endpoint{}
f := func(resp *route53.ListResourceRecordSetsOutput, lastPage bool) (shouldContinue bool) {
for _, r := range resp.ResourceRecordSets {
if aws.StringValue(r.Type) != route53.RRTypeA {
continue
}
for _, rr := range r.ResourceRecords {
endpoints = append(endpoints, endpoint.Endpoint{
DNSName: aws.StringValue(r.Name),
Target: aws.StringValue(rr.Value),
})
}
}
return true
}
err = p.Client.ListResourceRecordSetsPages(params, f)
if err != nil {
return nil, err
}
return endpoints, nil
}
// CreateRecords creates a given set of DNS records in the given hosted zone.
func (p *AWSProvider) CreateRecords(zone string, endpoints []endpoint.Endpoint) error {
return p.submitChanges(zone, newChanges(route53.ChangeActionCreate, endpoints))
}
// UpdateRecords updates a given set of old records to a new set of records in a given hosted zone.
func (p *AWSProvider) UpdateRecords(zone string, endpoints, _ []endpoint.Endpoint) error {
return p.submitChanges(zone, newChanges(route53.ChangeActionUpsert, endpoints))
}
// DeleteRecords deletes a given set of DNS records in a given zone.
func (p *AWSProvider) DeleteRecords(zone string, endpoints []endpoint.Endpoint) error {
return p.submitChanges(zone, newChanges(route53.ChangeActionDelete, endpoints))
}
// ApplyChanges applies a given set of changes in a given zone.
func (p *AWSProvider) ApplyChanges(zone string, changes *plan.Changes) error {
combinedChanges := make([]*route53.Change, 0, len(changes.Create)+len(changes.UpdateNew)+len(changes.Delete))
combinedChanges = append(combinedChanges, newChanges(route53.ChangeActionCreate, changes.Create)...)
combinedChanges = append(combinedChanges, newChanges(route53.ChangeActionUpsert, changes.UpdateNew)...)
combinedChanges = append(combinedChanges, newChanges(route53.ChangeActionDelete, changes.Delete)...)
return p.submitChanges(zone, combinedChanges)
}
// submitChanges takes a zone and a collection of Changes and sends them as a single transaction.
func (p *AWSProvider) submitChanges(zone string, changes []*route53.Change) error {
hostedZone, err := p.Zone(zone)
if err != nil {
return err
}
if p.DryRun {
for _, change := range changes {
log.Infof("Changing records: %s %s", aws.StringValue(change.Action), change.String())
}
return nil
}
params := &route53.ChangeResourceRecordSetsInput{
HostedZoneId: hostedZone.Id,
ChangeBatch: &route53.ChangeBatch{
Changes: changes,
},
}
_, err = p.Client.ChangeResourceRecordSets(params)
if err != nil {
return err
}
return nil
}
// newChanges returns a collection of Changes based on the given records and action.
func newChanges(action string, endpoints []endpoint.Endpoint) []*route53.Change {
changes := make([]*route53.Change, 0, len(endpoints))
for _, endpoint := range endpoints {
changes = append(changes, newChange(action, endpoint))
}
return changes
}
// newChange returns a Change of the given record by the given action, e.g.
// action=ChangeActionCreate returns a change for creation of the record and
// action=ChangeActionDelete returns a change for deletion of the record.
func newChange(action string, endpoint endpoint.Endpoint) *route53.Change {
change := &route53.Change{
Action: aws.String(action),
ResourceRecordSet: &route53.ResourceRecordSet{
Name: aws.String(endpoint.DNSName),
ResourceRecords: []*route53.ResourceRecord{
{
Value: aws.String(endpoint.Target),
},
},
TTL: aws.Int64(300),
Type: aws.String(route53.RRTypeA),
},
}
return change
}
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dnsprovider
import (
"fmt"
"strings"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/route53"
"github.com/kubernetes-incubator/external-dns/endpoint"
"github.com/kubernetes-incubator/external-dns/plan"
)
// Compile time check for interface conformance
var _ Route53API = &Route53APIStub{}
// Route53APIStub is a minimal implementation of Route53API, used primarily for unit testing.
// See http://http://docs.aws.amazon.com/sdk-for-go/api/service/route53.html for descriptions
// of all of its methods.
// mostly taken from: https://github.com/kubernetes/kubernetes/blob/853167624edb6bc0cfdcdfb88e746e178f5db36c/federation/pkg/dnsprovider/providers/aws/route53/stubs/route53api.go
type Route53APIStub struct {
zones map[string]*route53.HostedZone
recordSets map[string]map[string][]*route53.ResourceRecordSet
}
// NewRoute53APIStub returns an initialized Route53APIStub
func NewRoute53APIStub() *Route53APIStub {
return &Route53APIStub{
zones: make(map[string]*route53.HostedZone),
recordSets: make(map[string]map[string][]*route53.ResourceRecordSet),
}
}
func (r *Route53APIStub) ListResourceRecordSetsPages(input *route53.ListResourceRecordSetsInput, fn func(p *route53.ListResourceRecordSetsOutput, lastPage bool) (shouldContinue bool)) error {
output := route53.ListResourceRecordSetsOutput{} // TODO: Support optional input args.
if len(r.recordSets) <= 0 {
output.ResourceRecordSets = []*route53.ResourceRecordSet{}
} else if _, ok := r.recordSets[aws.StringValue(input.HostedZoneId)]; !ok {
output.ResourceRecordSets = []*route53.ResourceRecordSet{}
} else {
for _, rrsets := range r.recordSets[aws.StringValue(input.HostedZoneId)] {
for _, rrset := range rrsets {
output.ResourceRecordSets = append(output.ResourceRecordSets, rrset)
}
}
}
lastPage := true
fn(&output, lastPage)
return nil
}
func (r *Route53APIStub) ChangeResourceRecordSets(input *route53.ChangeResourceRecordSetsInput) (*route53.ChangeResourceRecordSetsOutput, error) {
_, ok := r.zones[aws.StringValue(input.HostedZoneId)]
if !ok {
return nil, fmt.Errorf("Hosted zone doesn't exist: %s", aws.StringValue(input.HostedZoneId))
}
output := &route53.ChangeResourceRecordSetsOutput{}
recordSets, ok := r.recordSets[aws.StringValue(input.HostedZoneId)]
if !ok {
recordSets = make(map[string][]*route53.ResourceRecordSet)
}
for _, change := range input.ChangeBatch.Changes {
key := aws.StringValue(change.ResourceRecordSet.Name) + "::" + aws.StringValue(change.ResourceRecordSet.Type)
switch aws.StringValue(change.Action) {
case route53.ChangeActionCreate:
if _, found := recordSets[key]; found {
return nil, fmt.Errorf("Attempt to create duplicate rrset %s", key) // TODO: Return AWS errors with codes etc
}
recordSets[key] = append(recordSets[key], change.ResourceRecordSet)
case route53.ChangeActionDelete:
if _, found := recordSets[key]; !found {
return nil, fmt.Errorf("Attempt to delete non-existent rrset %s", key) // TODO: Check other fields too
}
delete(recordSets, key)
case route53.ChangeActionUpsert:
recordSets[key] = []*route53.ResourceRecordSet{change.ResourceRecordSet}
}
}
r.recordSets[aws.StringValue(input.HostedZoneId)] = recordSets
return output, nil // TODO: We should ideally return status etc, but we don't' use that yet.
}
func (r *Route53APIStub) ListHostedZonesPages(input *route53.ListHostedZonesInput, fn func(p *route53.ListHostedZonesOutput, lastPage bool) (shouldContinue bool)) error {
output := &route53.ListHostedZonesOutput{}
for _, zone := range r.zones {
output.HostedZones = append(output.HostedZones, zone)
}
lastPage := true
fn(output, lastPage)
return nil
}
func (r *Route53APIStub) ListHostedZonesByName(input *route53.ListHostedZonesByNameInput) (*route53.ListHostedZonesByNameOutput, error) {
output := &route53.ListHostedZonesByNameOutput{}
for _, zone := range r.zones {
if strings.Contains(*input.DNSName, aws.StringValue(zone.Name)) {
output.HostedZones = append(output.HostedZones, zone)
}
}
return output, nil
}
func (r *Route53APIStub) CreateHostedZone(input *route53.CreateHostedZoneInput) (*route53.CreateHostedZoneOutput, error) {
name := aws.StringValue(input.Name)
id := "/hostedzone/" + name
if _, ok := r.zones[id]; ok {
return nil, fmt.Errorf("Error creating hosted DNS zone: %s already exists", id)
}
r.zones[id] = &route53.HostedZone{
Id: aws.String(id),
Name: aws.String(name),
}
return &route53.CreateHostedZoneOutput{HostedZone: r.zones[id]}, nil
}
func (r *Route53APIStub) DeleteHostedZone(input *route53.DeleteHostedZoneInput) (*route53.DeleteHostedZoneOutput, error) {
if _, ok := r.zones[aws.StringValue(input.Id)]; !ok {
return nil, fmt.Errorf("Error deleting hosted DNS zone: %s does not exist", aws.StringValue(input.Id))
}
if len(r.recordSets[aws.StringValue(input.Id)]) > 0 {
return nil, fmt.Errorf("Error deleting hosted DNS zone: %s has resource records", aws.StringValue(input.Id))
}
delete(r.zones, aws.StringValue(input.Id))
return &route53.DeleteHostedZoneOutput{}, nil
}
func TestAWSZones(t *testing.T) {
provider := newAWSProvider(t, false)
_, err := provider.CreateZone("ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
zones, err := provider.Zones()
if err != nil {
t.Fatal(err)
}
if len(zones) != 1 {
t.Fatalf("expected %d zones, got %d", 1, len(zones))
}
zone := zones[0]
if zone != "ext-dns-test.teapot.zalan.do." {
t.Errorf("expected %s, got %s", "ext-dns-test.teapot.zalan.do.", zone)
}
}
func TestAWSZone(t *testing.T) {
provider := newAWSProvider(t, false)
hostedZone, err := provider.CreateZone("list-ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
zone, err := provider.Zone("list-ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
if aws.StringValue(zone.Id) != aws.StringValue(hostedZone.Id) {
t.Errorf("expected %s, got %s", aws.StringValue(hostedZone.Id), aws.StringValue(zone.Id))
}
if aws.StringValue(zone.Name) != "list-ext-dns-test.teapot.zalan.do." {
t.Errorf("expected %s, got %s", "list-ext-dns-test.teapot.zalan.do.", aws.StringValue(zone.Name))
}
}
func TestAWSCreateZone(t *testing.T) {
provider := newAWSProvider(t, false)
_, err := provider.CreateZone("ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
zones, err := provider.Zones()
if err != nil {
t.Fatal(err)
}
found := false
for _, z := range zones {
if z == "ext-dns-test.teapot.zalan.do." {
found = true
}
}
if !found {
t.Fatal("ext-dns-test.teapot.zalan.do. should be there")
}
}
func TestAWSDeleteZone(t *testing.T) {
provider := newAWSProvider(t, false)
zone, err := provider.CreateZone("ext-dns-test-2.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
err = provider.DeleteZone(aws.StringValue(zone.Id))
if err != nil {
t.Fatal(err)
}
zones, err := provider.Zones()
if err != nil {
t.Fatal(err)
}
for _, z := range zones {
if z == "ext-dns-test-2.teapot.zalan.do." {
t.Fatal("ext-dns-test-2.teapot.zalan.do.")
}
}
}
func TestAWSRecords(t *testing.T) {
provider := newAWSProvider(t, false)
_, err := provider.CreateZone("list-ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
records := []endpoint.Endpoint{{DNSName: "list-test.list-ext-dns-test.teapot.zalan.do.", Target: "8.8.8.8"}}
err = provider.CreateRecords("list-ext-dns-test.teapot.zalan.do.", records)
if err != nil {
t.Fatal(err)
}
records, err = provider.Records("list-ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
if len(records) != 1 {
t.Errorf("expected %d records, got %d", 1, len(records))
}
found := false
for _, r := range records {
if r.DNSName == "list-test.list-ext-dns-test.teapot.zalan.do." {
if r.Target == "8.8.8.8" {
found = true
}
}
}
if !found {
t.Fatal("list-test.list-ext-dns-test.teapot.zalan.do. should be there")
}
}
func TestAWSCreateRecords(t *testing.T) {
provider := newAWSProvider(t, false)
_, err := provider.CreateZone("ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
records := []endpoint.Endpoint{{DNSName: "create-test.ext-dns-test.teapot.zalan.do.", Target: "8.8.8.8"}}
err = provider.CreateRecords("ext-dns-test.teapot.zalan.do.", records)
if err != nil {
t.Fatal(err)
}
records, err = provider.Records("ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
found := false
for _, r := range records {
if r.DNSName == "create-test.ext-dns-test.teapot.zalan.do." {
if r.Target == "8.8.8.8" {
found = true
}
}
}
if !found {
t.Fatal("create-test.ext-dns-test.teapot.zalan.do. should be there")
}
}
func TestAWSUpdateRecords(t *testing.T) {
provider := newAWSProvider(t, false)
_, err := provider.CreateZone("ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
oldRecords := []endpoint.Endpoint{{DNSName: "update-test.ext-dns-test.teapot.zalan.do.", Target: "8.8.8.8"}}
err = provider.CreateRecords("ext-dns-test.teapot.zalan.do.", oldRecords)
if err != nil {
t.Fatal(err)
}
newRecords := []endpoint.Endpoint{{DNSName: "update-test.ext-dns-test.teapot.zalan.do.", Target: "1.2.3.4"}}
err = provider.UpdateRecords("ext-dns-test.teapot.zalan.do.", newRecords, oldRecords)
if err != nil {
t.Fatal(err)
}
records, err := provider.Records("ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
found := false
for _, r := range records {
if r.DNSName == "update-test.ext-dns-test.teapot.zalan.do." {
if r.Target == "1.2.3.4" {
found = true
}
}
}
if !found {
t.Fatal("update-test.ext-dns-test.teapot.zalan.do. should point to 1.2.3.4")
}
}
func TestAWSDeleteRecords(t *testing.T) {
provider := newAWSProvider(t, false)
_, err := provider.CreateZone("ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
records := []endpoint.Endpoint{{DNSName: "delete-test.ext-dns-test.teapot.zalan.do.", Target: "20.153.88.175"}}
err = provider.CreateRecords("ext-dns-test.teapot.zalan.do.", records)
if err != nil {
t.Fatal(err)
}
err = provider.DeleteRecords("ext-dns-test.teapot.zalan.do.", records)
if err != nil {
t.Fatal(err)
}
records, err = provider.Records("ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
found := false
for _, r := range records {
if r.DNSName == "delete-test.ext-dns-test.teapot.zalan.do." {
found = true
}
}
if found {
t.Fatal("delete-test.ext-dns-test.teapot.zalan.do. should be gone")
}
}
func TestAWSApply(t *testing.T) {
provider := newAWSProvider(t, false)
_, err := provider.CreateZone("ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
updateRecords := []endpoint.Endpoint{{DNSName: "update-test.ext-dns-test.teapot.zalan.do.", Target: "8.8.8.8"}}
err = provider.CreateRecords("ext-dns-test.teapot.zalan.do.", updateRecords)
if err != nil {
t.Fatal(err)
}
deleteRecords := []endpoint.Endpoint{{DNSName: "delete-test.ext-dns-test.teapot.zalan.do.", Target: "20.153.88.175"}}
err = provider.CreateRecords("ext-dns-test.teapot.zalan.do.", deleteRecords)
if err != nil {
t.Fatal(err)
}
createRecords := []endpoint.Endpoint{{DNSName: "create-test.ext-dns-test.teapot.zalan.do.", Target: "8.8.8.8"}}
updateNewRecords := []endpoint.Endpoint{{DNSName: "update-test.ext-dns-test.teapot.zalan.do.", Target: "1.2.3.4"}}
changes := &plan.Changes{
Create: createRecords,
UpdateNew: updateNewRecords,
UpdateOld: updateRecords,
Delete: deleteRecords,
}
err = provider.ApplyChanges("ext-dns-test.teapot.zalan.do.", changes)
if err != nil {
t.Fatal(err)
}
// create validation
records, err := provider.Records("ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
found := false
for _, r := range records {
if r.DNSName == "create-test.ext-dns-test.teapot.zalan.do." {
if r.Target == "8.8.8.8" {
found = true
}
}
}
if !found {
t.Fatal("create-test.ext-dns-test.teapot.zalan.do. should be there")
}
// update validation
found = false
for _, r := range records {
if r.DNSName == "update-test.ext-dns-test.teapot.zalan.do." {
if r.Target == "1.2.3.4" {
found = true
}
}
}
if !found {
t.Fatal("update-test.ext-dns-test.teapot.zalan.do. should point to 1.2.3.4")
}
// delete validation
found = false
for _, r := range records {
if r.DNSName == "delete-test.ext-dns-test.teapot.zalan.do." {
found = true
}
}
if found {
t.Fatal("delete-test.ext-dns-test.teapot.zalan.do. should be gone")
}
}
func TestAWSCreateRecordDryRun(t *testing.T) {
provider := newAWSProvider(t, false)
_, err := provider.CreateZone("ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
provider.DryRun = true
records := []endpoint.Endpoint{{DNSName: "create-test.ext-dns-test.teapot.zalan.do.", Target: "8.8.8.8"}}
err = provider.CreateRecords("ext-dns-test.teapot.zalan.do.", records)
if err != nil {
t.Fatal(err)
}
records, err = provider.Records("ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
found := false
for _, r := range records {
if r.DNSName == "create-test.ext-dns-test.teapot.zalan.do." {
if r.Target == "8.8.8.8" {
found = true
}
}
}
if found {
t.Fatal("create-test.ext-dns-test.teapot.zalan.do. should not be there")
}
}
func TestAWSUpdateRecordDryRun(t *testing.T) {
provider := newAWSProvider(t, false)
_, err := provider.CreateZone("ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
oldRecords := []endpoint.Endpoint{{DNSName: "update-test.ext-dns-test.teapot.zalan.do.", Target: "8.8.8.8"}}
err = provider.CreateRecords("ext-dns-test.teapot.zalan.do.", oldRecords)
if err != nil {
t.Fatal(err)
}
provider.DryRun = true
newRecords := []endpoint.Endpoint{{DNSName: "update-test.ext-dns-test.teapot.zalan.do.", Target: "1.2.3.4"}}
err = provider.UpdateRecords("ext-dns-test.teapot.zalan.do.", newRecords, oldRecords)
if err != nil {
t.Fatal(err)
}
records, err := provider.Records("ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
found := false
for _, r := range records {
if r.DNSName == "update-test.ext-dns-test.teapot.zalan.do." {
if r.Target == "1.2.3.4" {
found = true
}
}
}
if found {
t.Fatal("update-test.ext-dns-test.teapot.zalan.do. should not point to 1.2.3.4")
}
}
func TestAWSDeleteRecordDryRun(t *testing.T) {
provider := newAWSProvider(t, false)
_, err := provider.CreateZone("ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
records := []endpoint.Endpoint{{DNSName: "delete-test.ext-dns-test.teapot.zalan.do.", Target: "20.153.88.175"}}
err = provider.CreateRecords("ext-dns-test.teapot.zalan.do.", records)
if err != nil {
t.Fatal(err)
}
provider.DryRun = true
err = provider.DeleteRecords("ext-dns-test.teapot.zalan.do.", records)
if err != nil {
t.Fatal(err)
}
records, err = provider.Records("ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
found := false
for _, r := range records {
if r.DNSName == "delete-test.ext-dns-test.teapot.zalan.do." {
found = true
}
}
if !found {
t.Fatal("delete-test.ext-dns-test.teapot.zalan.do. should not be gone")
}
}
func TestAWSApplyDryRun(t *testing.T) {
provider := newAWSProvider(t, false)
_, err := provider.CreateZone("ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
updateRecords := []endpoint.Endpoint{{DNSName: "update-test.ext-dns-test.teapot.zalan.do.", Target: "8.8.8.8"}}
err = provider.CreateRecords("ext-dns-test.teapot.zalan.do.", updateRecords)
if err != nil {
t.Fatal(err)
}
deleteRecords := []endpoint.Endpoint{{DNSName: "delete-test.ext-dns-test.teapot.zalan.do.", Target: "20.153.88.175"}}
err = provider.CreateRecords("ext-dns-test.teapot.zalan.do.", deleteRecords)
if err != nil {
t.Fatal(err)
}
provider.DryRun = true
createRecords := []endpoint.Endpoint{{DNSName: "create-test.ext-dns-test.teapot.zalan.do.", Target: "8.8.8.8"}}
updateNewRecords := []endpoint.Endpoint{{DNSName: "update-test.ext-dns-test.teapot.zalan.do.", Target: "1.2.3.4"}}
changes := &plan.Changes{
Create: createRecords,
UpdateNew: updateNewRecords,
UpdateOld: updateRecords,
Delete: deleteRecords,
}
err = provider.ApplyChanges("ext-dns-test.teapot.zalan.do.", changes)
if err != nil {
t.Fatal(err)
}
// create validation
records, err := provider.Records("ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
found := false
for _, r := range records {
if r.DNSName == "create-test.ext-dns-test.teapot.zalan.do." {
if r.Target == "8.8.8.8" {
found = true
}
}
}
if found {
t.Fatal("create-test.ext-dns-test.teapot.zalan.do. should not be there")
}
// update validation
found = false
for _, r := range records {
if r.DNSName == "update-test.ext-dns-test.teapot.zalan.do." {
if r.Target == "1.2.3.4" {
found = true
}
}
}
if found {
t.Fatal("update-test.ext-dns-test.teapot.zalan.do. should not point to 1.2.3.4")
}
// delete validation
found = false
for _, r := range records {
if r.DNSName == "delete-test.ext-dns-test.teapot.zalan.do." {
found = true
}
}
if !found {
t.Fatal("delete-test.ext-dns-test.teapot.zalan.do. should not be gone")
}
}
func newAWSProvider(t *testing.T, dryRun bool) *AWSProvider {
client := NewRoute53APIStub()
return &AWSProvider{
Client: client,
DryRun: dryRun,
}
}
hash: ab629131300d541eec768469491aa5194939640f664feb9d14ce11678b23c138
updated: 2017-02-28T22:25:07.275158056+01:00
hash: df53656c5b944ac606104fd4339ec0f31cb098426ab0b94b06eff1843fa93461
updated: 2017-03-06T15:22:13.908484696+01:00
imports:
- name: cloud.google.com/go
version: 3b1ae45394a234c385be014e9a488f2bb6eef821
subpackages:
- compute/metadata
- internal
- name: github.com/aws/aws-sdk-go
version: 6669bce73b4e3bc922ff5ea3a3983ede26e02b39
subpackages:
- aws
- aws/awserr
- aws/awsutil
- aws/client
- aws/client/metadata
- aws/credentials
- aws/endpoints
- aws/request
- aws/signer/v4
- private/protocol
- private/protocol/query
- private/protocol/query/queryutil
- private/protocol/rest
- private/protocol/restxml
- private/protocol/xml/xmlutil
- private/waiter
- service/route53
- name: github.com/blang/semver
version: 31b736133b98f26d5e078ec9eb591666edfd091f
- name: github.com/coreos/go-oidc
......@@ -38,6 +58,8 @@ imports:
- swagger
- name: github.com/ghodss/yaml
version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee
- name: github.com/go-ini/ini
version: c437d20015c2ab6454b7a66a13109ff0fb99e17a
- name: github.com/go-openapi/jsonpointer
version: 46af16f9f7b149af66e5d1bd010e3574dc06de98
- name: github.com/go-openapi/jsonreference
......@@ -59,10 +81,14 @@ imports:
- proto
- name: github.com/google/gofuzz
version: bbcb9da2d746f8bdbd6a936686a0a6067ada0ec5
- name: github.com/google/uuid
version: 064e2069ce9c359c118179501254f67d7d37ba24
- name: github.com/howeyc/gopass
version: 3ca23474a7c7203e0a0a070fd33508f6efdb9b3d
- name: github.com/imdario/mergo
version: 6633656539c1639d9d78127b7d47c622b5d7b6dc
- name: github.com/jmespath/go-jmespath
version: bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d
- name: github.com/jonboulle/clockwork
version: 72f9bd7c4e0c2a40055ab3d0f09654f730cce982
- name: github.com/juju/ratelimit
......@@ -82,7 +108,7 @@ imports:
- name: github.com/PuerkitoBio/urlesc
version: 5bd2802263f21d8788851d5305584c82a5c75d7e
- name: github.com/Sirupsen/logrus
version: c078b1e43f58d563c74cebe63c85789e76ddb627
version: 0208149b40d863d2c1a2f8fe5753096a9cf2cc8b
- name: github.com/spf13/pflag
version: 5ccb023bc27df288a957c5e994cd44fd19619465
- name: github.com/ugorji/go
......
......@@ -2,6 +2,13 @@ package: github.com/kubernetes-incubator/external-dns
import:
- package: github.com/Sirupsen/logrus
version: ~0.11.2
- package: github.com/aws/aws-sdk-go
version: ~1.7.3
subpackages:
- aws
- service/route53
- package: github.com/google/uuid
version: ~0.2.0
- package: github.com/spf13/pflag
- package: github.com/kubernetes/repo-infra
- package: golang.org/x/oauth2
......
# 0.11.4
* bug: fix undefined variable on solaris (#493)
# 0.11.3
* formatter: configure quoting of empty values (#484)
* formatter: configure quoting character (default is `"`) (#484)
* bug: fix not importing io correctly in non-linux environments (#481)
# 0.11.2
* bug: fix windows terminal detection (#476)
......
......@@ -180,6 +180,20 @@ In general, with Logrus using any of the `printf`-family functions should be
seen as a hint you should add a field, however, you can still use the
`printf`-family functions with Logrus.
#### Default Fields
Often it's helpful to have fields _always_ attached to log statements in an
application or parts of one. For example, you may want to always log the
`request_id` and `user_ip` in the context of a request. Instead of writing
`log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})` on
every line, you can create a `logrus.Entry` to pass around instead:
```go
requestLogger := log.WithFields(log.Fields{"request_id": request_id, user_ip: user_ip})
requestLogger.Info("something happened on that request") # will log request_id and user_ip
requestLogger.Warn("something not great happened")
```
#### Hooks
You can add hooks for logging levels. For example to send errors to an exception
......
......@@ -2,6 +2,8 @@
package logrus
import "io"
// IsTerminal returns true if stderr's file descriptor is a terminal.
func IsTerminal(f io.Writer) bool {
return true
......
......@@ -3,6 +3,7 @@
package logrus
import (
"io"
"os"
"golang.org/x/sys/unix"
......@@ -10,7 +11,6 @@ import (
// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(f io.Writer) bool {
var termios Termios
switch v := f.(type) {
case *os.File:
_, err := unix.IoctlGetTermios(int(v.Fd()), unix.TCGETA)
......
......@@ -8,8 +8,8 @@
package logrus
import (
"io"
"os"
"io"
"os"
"syscall"
"unsafe"
)
......
......@@ -49,9 +49,26 @@ type TextFormatter struct {
// be desired.
DisableSorting bool
// QuoteEmptyFields will wrap empty fields in quotes if true
QuoteEmptyFields bool
// QuoteCharacter can be set to the override the default quoting character "
// with something else. For example: ', or `.
QuoteCharacter string
// Whether the logger's out is to a terminal
isTerminal bool
terminalOnce sync.Once
isTerminal bool
sync.Once
}
func (f *TextFormatter) init(entry *Entry) {
if len(f.QuoteCharacter) == 0 {
f.QuoteCharacter = "\""
}
if entry.Logger != nil {
f.isTerminal = IsTerminal(entry.Logger.Out)
}
}
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
......@@ -72,11 +89,7 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
prefixFieldClashes(entry.Data)
f.terminalOnce.Do(func() {
if entry.Logger != nil {
f.isTerminal = IsTerminal(entry.Logger.Out)
}
})
f.Do(func() { f.init(entry) })
isColored := (f.ForceColors || f.isTerminal) && !f.DisableColors
......@@ -132,7 +145,10 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin
}
}
func needsQuoting(text string) bool {
func (f *TextFormatter) needsQuoting(text string) bool {
if f.QuoteEmptyFields && len(text) == 0 {
return true
}
for _, ch := range text {
if !((ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
......@@ -155,17 +171,17 @@ func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interf
func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
switch value := value.(type) {
case string:
if !needsQuoting(value) {
if !f.needsQuoting(value) {
b.WriteString(value)
} else {
fmt.Fprintf(b, "%q", value)
fmt.Fprintf(b, "%s%v%s", f.QuoteCharacter, value, f.QuoteCharacter)
}
case error:
errmsg := value.Error()
if !needsQuoting(errmsg) {
if !f.needsQuoting(errmsg) {
b.WriteString(errmsg)
} else {
fmt.Fprintf(b, "%q", errmsg)
fmt.Fprintf(b, "%s%v%s", f.QuoteCharacter, errmsg, f.QuoteCharacter)
}
default:
fmt.Fprint(b, value)
......
......@@ -3,9 +3,9 @@ package logrus
import (
"bytes"
"errors"
"strings"
"testing"
"time"
"strings"
)
func TestQuoting(t *testing.T) {
......@@ -14,7 +14,7 @@ func TestQuoting(t *testing.T) {
checkQuoting := func(q bool, value interface{}) {
b, _ := tf.Format(WithField("test", value))
idx := bytes.Index(b, ([]byte)("test="))
cont := bytes.Contains(b[idx+5:], []byte{'"'})
cont := bytes.Contains(b[idx+5:], []byte(tf.QuoteCharacter))
if cont != q {
if q {
t.Errorf("quoting expected for: %#v", value)
......@@ -24,6 +24,7 @@ func TestQuoting(t *testing.T) {
}
}
checkQuoting(false, "")
checkQuoting(false, "abcd")
checkQuoting(false, "v1.0")
checkQuoting(false, "1234567890")
......@@ -32,6 +33,24 @@ func TestQuoting(t *testing.T) {
checkQuoting(true, "x,y")
checkQuoting(false, errors.New("invalid"))
checkQuoting(true, errors.New("invalid argument"))
// Test for custom quote character.
tf.QuoteCharacter = "`"
checkQuoting(false, "")
checkQuoting(false, "abcd")
checkQuoting(true, "/foobar")
checkQuoting(true, errors.New("invalid argument"))
// Test for multi-character quotes.
tf.QuoteCharacter = "§~±"
checkQuoting(false, "abcd")
checkQuoting(true, errors.New("invalid argument"))
// Test for quoting empty fields.
tf.QuoteEmptyFields = true
checkQuoting(true, "")
checkQuoting(false, "abcd")
checkQuoting(true, errors.New("invalid argument"))
}
func TestTimestampFormat(t *testing.T) {
......@@ -40,10 +59,7 @@ func TestTimestampFormat(t *testing.T) {
customStr, _ := customFormatter.Format(WithField("test", "test"))
timeStart := bytes.Index(customStr, ([]byte)("time="))
timeEnd := bytes.Index(customStr, ([]byte)("level="))
timeStr := customStr[timeStart+5 : timeEnd-1]
if timeStr[0] == '"' && timeStr[len(timeStr)-1] == '"' {
timeStr = timeStr[1 : len(timeStr)-1]
}
timeStr := customStr[timeStart+5+len(customFormatter.QuoteCharacter) : timeEnd-1-len(customFormatter.QuoteCharacter)]
if format == "" {
format = time.RFC3339
}
......
dist
/doc
/doc-staging
.yardoc
Gemfile.lock
awstesting/integration/smoke/**/importmarker__.go
awstesting/integration/smoke/_test/
/vendor/bin/
/vendor/pkg/
/vendor/src/
/private/model/cli/gen-api/gen-api
{
"PkgHandler": {
"Pattern": "/sdk-for-go/api/",
"StripPrefix": "/sdk-for-go/api",
"Include": ["/src/github.com/aws/aws-sdk-go/aws", "/src/github.com/aws/aws-sdk-go/service"],
"Exclude": ["/src/cmd", "/src/github.com/aws/aws-sdk-go/awstesting", "/src/github.com/aws/aws-sdk-go/awsmigrate"],
"IgnoredSuffixes": ["iface"]
},
"Github": {
"Tag": "master",
"Repo": "/aws/aws-sdk-go",
"UseGithub": true
}
}
language: go
sudo: false
go:
- 1.5
- 1.6
- 1.7
- 1.8
- tip
# Use Go 1.5's vendoring experiment for 1.5 tests.
env:
- GO15VENDOREXPERIMENT=1
install:
- make get-deps
script:
- make unit-with-race-cover
matrix:
allow_failures:
- go: tip
This diff is collapsed.
Contributing to the AWS SDK for Go
We work hard to provide a high-quality and useful SDK, and we greatly value
feedback and contributions from our community. Whether it's a bug report,
new feature, correction, or additional documentation, we welcome your issues
and pull requests. Please read through this document before submitting any
issues or pull requests to ensure we have all the necessary information to
effectively respond to your bug report or contribution.
## Filing Bug Reports
You can file bug reports against the SDK on the [GitHub issues][issues] page.
If you are filing a report for a bug or regression in the SDK, it's extremely
helpful to provide as much information as possible when opening the original
issue. This helps us reproduce and investigate the possible bug without having
to wait for this extra information to be provided. Please read the following
guidelines prior to filing a bug report.
1. Search through existing [issues][] to ensure that your specific issue has
not yet been reported. If it is a common issue, it is likely there is
already a bug report for your problem.
2. Ensure that you have tested the latest version of the SDK. Although you
may have an issue against an older version of the SDK, we cannot provide
bug fixes for old versions. It's also possible that the bug may have been
fixed in the latest release.
3. Provide as much information about your environment, SDK version, and
relevant dependencies as possible. For example, let us know what version
of Go you are using, which and version of the operating system, and the
the environment your code is running in. e.g Container.
4. Provide a minimal test case that reproduces your issue or any error
information you related to your problem. We can provide feedback much
more quickly if we know what operations you are calling in the SDK. If
you cannot provide a full test case, provide as much code as you can
to help us diagnose the problem. Any relevant information should be provided
as well, like whether this is a persistent issue, or if it only occurs
some of the time.
## Submitting Pull Requests
We are always happy to receive code and documentation contributions to the SDK.
Please be aware of the following notes prior to opening a pull request:
1. The SDK is released under the [Apache license][license]. Any code you submit
will be released under that license. For substantial contributions, we may
ask you to sign a [Contributor License Agreement (CLA)][cla].
2. If you would like to implement support for a significant feature that is not
yet available in the SDK, please talk to us beforehand to avoid any
duplication of effort.
3. Wherever possible, pull requests should contain tests as appropriate.
Bugfixes should contain tests that exercise the corrected behavior (i.e., the
test should fail without the bugfix and pass with it), and new features
should be accompanied by tests exercising the feature.
4. Pull requests that contain failing tests will not be merged until the test
failures are addressed. Pull requests that cause a significant drop in the
SDK's test coverage percentage are unlikely to be merged until tests have
been added.
### Testing
To run the tests locally, running the `make unit` command will `go get` the
SDK's testing dependencies, and run vet, link and unit tests for the SDK.
```
make unit
```
Standard go testing functionality is supported as well. To test SDK code that
is tagged with `codegen` you'll need to set the build tag in the go test
command. The `make unit` command will do this automatically.
```
go test -tags codegen ./private/...
```
See the `Makefile` for additional testing tags that can be used in testing.
To test on multiple platform the SDK includes several DockerFiles under the
`awstesting/sandbox` folder, and associated make recipes to to execute
unit testing within environments configured for specific Go versions.
```
make sandbox-test-go18
```
To run all sandbox environments use the following make recipe
```
# Optionally update the Go tip that will be used during the batch testing
make update-aws-golang-tip
# Run all SDK tests for supported Go versions in sandboxes
make sandbox-test
```
In addition the sandbox environment include make recipes for interactive modes
so you can run command within the Docker container and context of the SDK.
```
make sandbox-go18
```
### Changelog
You can see all release changes in the `CHANGELOG.md` file at the root of the
repository. The release notes added to this file will contain service client
updates, and major SDK changes.
[issues]: https://github.com/aws/aws-sdk-go/issues
[pr]: https://github.com/aws/aws-sdk-go/pulls
[license]: http://aws.amazon.com/apache2.0/
[cla]: http://en.wikipedia.org/wiki/Contributor_License_Agreement
[releasenotes]: https://github.com/aws/aws-sdk-go/releases
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
LINTIGNOREDOT='awstesting/integration.+should not use dot imports'
LINTIGNOREDOC='service/[^/]+/(api|service|waiters)\.go:.+(comment on exported|should have comment or be unexported)'
LINTIGNORECONST='service/[^/]+/(api|service|waiters)\.go:.+(type|struct field|const|func) ([^ ]+) should be ([^ ]+)'
LINTIGNORESTUTTER='service/[^/]+/(api|service)\.go:.+(and that stutters)'
LINTIGNOREINFLECT='service/[^/]+/(api|errors|service)\.go:.+(method|const) .+ should be '
LINTIGNOREINFLECTS3UPLOAD='service/s3/s3manager/upload\.go:.+struct field SSEKMSKeyId should be '
LINTIGNOREDEPS='vendor/.+\.go'
UNIT_TEST_TAGS="example codegen"
SDK_WITH_VENDOR_PKGS=$(shell go list -tags ${UNIT_TEST_TAGS} ./... | grep -v "/vendor/src")
SDK_ONLY_PKGS=$(shell go list ./... | grep -v "/vendor/")
SDK_UNIT_TEST_ONLY_PKGS=$(shell go list -tags ${UNIT_TEST_TAGS} ./... | grep -v "/vendor/")
SDK_GO_1_4=$(shell go version | grep "go1.4")
SDK_GO_1_5=$(shell go version | grep "go1.5")
SDK_GO_VERSION=$(shell go version | awk '''{print $$3}''' | tr -d '''\n''')
all: get-deps generate unit
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " api_info to print a list of services and versions"
@echo " docs to build SDK documentation"
@echo " build to go build the SDK"
@echo " unit to run unit tests"
@echo " integration to run integration tests"
@echo " performance to run performance tests"
@echo " verify to verify tests"
@echo " lint to lint the SDK"
@echo " vet to vet the SDK"
@echo " generate to go generate and make services"
@echo " gen-test to generate protocol tests"
@echo " gen-services to generate services"
@echo " get-deps to go get the SDK dependencies"
@echo " get-deps-tests to get the SDK's test dependencies"
@echo " get-deps-verify to get the SDK's verification dependencies"
generate: gen-test gen-endpoints gen-services
gen-test: gen-protocol-test
gen-services:
go generate ./service
gen-protocol-test:
go generate ./private/protocol/...
gen-endpoints:
go generate ./models/endpoints/
build:
@echo "go build SDK and vendor packages"
@go build ${SDK_ONLY_PKGS}
unit: get-deps-tests build verify
@echo "go test SDK and vendor packages"
@go test -tags ${UNIT_TEST_TAGS} $(SDK_UNIT_TEST_ONLY_PKGS)
unit-with-race-cover: get-deps-tests build verify
@echo "go test SDK and vendor packages"
@go test -tags ${UNIT_TEST_TAGS} -race -cpu=1,2,4 $(SDK_UNIT_TEST_ONLY_PKGS)
integration: get-deps-tests integ-custom smoke-tests performance
integ-custom:
go test -tags "integration" ./awstesting/integration/customizations/...
smoke-tests: get-deps-tests
gucumber -go-tags "integration" ./awstesting/integration/smoke
performance: get-deps-tests
AWS_TESTING_LOG_RESULTS=${log-detailed} AWS_TESTING_REGION=$(region) AWS_TESTING_DB_TABLE=$(table) gucumber -go-tags "integration" ./awstesting/performance
sandbox-tests: sandbox-test-go15 sandbox-test-go15-novendorexp sandbox-test-go16 sandbox-test-go17 sandbox-test-go18 sandbox-test-gotip
sandbox-build-go15:
docker build -f ./awstesting/sandbox/Dockerfile.test.go1.5 -t "aws-sdk-go-1.5" .
sandbox-go15: sandbox-build-go15
docker run -i -t aws-sdk-go-1.5 bash
sandbox-test-go15: sandbox-build-go15
docker run -t aws-sdk-go-1.5
sandbox-build-go15-novendorexp:
docker build -f ./awstesting/sandbox/Dockerfile.test.go1.5-novendorexp -t "aws-sdk-go-1.5-novendorexp" .
sandbox-go15-novendorexp: sandbox-build-go15-novendorexp
docker run -i -t aws-sdk-go-1.5-novendorexp bash
sandbox-test-go15-novendorexp: sandbox-build-go15-novendorexp
docker run -t aws-sdk-go-1.5-novendorexp
sandbox-build-go16:
docker build -f ./awstesting/sandbox/Dockerfile.test.go1.6 -t "aws-sdk-go-1.6" .
sandbox-go16: sandbox-build-go16
docker run -i -t aws-sdk-go-1.6 bash
sandbox-test-go16: sandbox-build-go16
docker run -t aws-sdk-go-1.6
sandbox-build-go17:
docker build -f ./awstesting/sandbox/Dockerfile.test.go1.7 -t "aws-sdk-go-1.7" .
sandbox-go17: sandbox-build-go17
docker run -i -t aws-sdk-go-1.7 bash
sandbox-test-go17: sandbox-build-go17
docker run -t aws-sdk-go-1.7
sandbox-build-go18:
docker build -f ./awstesting/sandbox/Dockerfile.test.go1.8 -t "aws-sdk-go-1.8" .
sandbox-go18: sandbox-build-go18
docker run -i -t aws-sdk-go-1.8 bash
sandbox-test-go18: sandbox-build-go18
docker run -t aws-sdk-go-1.8
sandbox-build-gotip:
@echo "Run make update-aws-golang-tip, if this test fails because missing aws-golang:tip container"
docker build -f ./awstesting/sandbox/Dockerfile.test.gotip -t "aws-sdk-go-tip" .
sandbox-gotip: sandbox-build-gotip
docker run -i -t aws-sdk-go-tip bash
sandbox-test-gotip: sandbox-build-gotip
docker run -t aws-sdk-go-tip
update-aws-golang-tip:
docker build --no-cache=true -f ./awstesting/sandbox/Dockerfile.golang-tip -t "aws-golang:tip" .
verify: get-deps-verify lint vet
lint:
@echo "go lint SDK and vendor packages"
@lint=`if [ \( -z "${SDK_GO_1_4}" \) -a \( -z "${SDK_GO_1_5}" \) ]; then golint ./...; else echo "skipping golint"; fi`; \
lint=`echo "$$lint" | grep -E -v -e ${LINTIGNOREDOT} -e ${LINTIGNOREDOC} -e ${LINTIGNORECONST} -e ${LINTIGNORESTUTTER} -e ${LINTIGNOREINFLECT} -e ${LINTIGNOREDEPS} -e ${LINTIGNOREINFLECTS3UPLOAD}`; \
echo "$$lint"; \
if [ "$$lint" != "" ] && [ "$$lint" != "skipping golint" ]; then exit 1; fi
SDK_BASE_FOLDERS=$(shell ls -d */ | grep -v vendor | grep -v awsmigrate)
ifneq (,$(findstring go1.4, ${SDK_GO_VERSION}))
GO_VET_CMD=echo skipping go vet, ${SDK_GO_VERSION}
else ifneq (,$(findstring go1.6, ${SDK_GO_VERSION}))
GO_VET_CMD=go tool vet --all -shadow -example=false
else
GO_VET_CMD=go tool vet --all -shadow
endif
vet:
${GO_VET_CMD} ${SDK_BASE_FOLDERS}
get-deps: get-deps-tests get-deps-verify
@echo "go get SDK dependencies"
@go get -v $(SDK_ONLY_PKGS)
get-deps-tests:
@echo "go get SDK testing dependencies"
go get github.com/gucumber/gucumber/cmd/gucumber
go get github.com/stretchr/testify
go get github.com/smartystreets/goconvey
go get golang.org/x/net/html
get-deps-verify:
@echo "go get SDK verification utilities"
@if [ \( -z "${SDK_GO_1_4}" \) -a \( -z "${SDK_GO_1_5}" \) ]; then go get github.com/golang/lint/golint; else echo "skipped getting golint"; fi
bench:
@echo "go bench SDK packages"
@go test -run NONE -bench . -benchmem -tags 'bench' $(SDK_ONLY_PKGS)
bench-protocol:
@echo "go bench SDK protocol marshallers"
@go test -run NONE -bench . -benchmem -tags 'bench' ./private/protocol/...
docs:
@echo "generate SDK docs"
@# This env variable, DOCS, is for internal use
@if [ -z ${AWS_DOC_GEN_TOOL} ]; then\
rm -rf doc && bundle install && bundle exec yard;\
else\
$(AWS_DOC_GEN_TOOL) `pwd`;\
fi
api_info:
@go run private/model/cli/api-info/api-info.go
AWS SDK for Go
Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Copyright 2014-2015 Stripe, Inc.
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment