provider/digital_ocean.go
func (p *DigitalOceanProvider) submitChanges(changes []*DigitalOceanChange) error {
	// return early if there is nothing to change
	if len(changes) == 0 {
		return nil
	}

	zones, err := p.Zones()
	if err != nil {
		return err
	}

	// separate into per-zone change sets to be passed to the API.
	changesByZone := digitalOceanChangesByZone(zones, changes)
	for zoneName, changes := range changesByZone {
		records, err := p.fetchRecords(zoneName)
		if err != nil {
			log.Errorf("Failed to list records in the zone: %s", zoneName)
			continue
		}
		for _, change := range changes {
			logFields := log.Fields{
				"record": change.ResourceRecordSet.Name,
				"type":   change.ResourceRecordSet.Type,
				"ttl":    change.ResourceRecordSet.TTL,
				"action": change.Action,
				"zone":   zoneName,
			}

			log.WithFields(logFields).Info("Changing record.")

			if p.DryRun {
				continue
			}

			change.ResourceRecordSet.Name = strings.TrimSuffix(change.ResourceRecordSet.Name, "."+zoneName)

			// record at the root should be defined as @ instead of
			// the full domain name
			if change.ResourceRecordSet.Name == zoneName {
				change.ResourceRecordSet.Name = "@"
			}

			// for some reason the DO API requires the '.' at the end of "data" in case of CNAME request
			// Example: {"type":"CNAME","name":"hello","data":"www.example.com."}
			if change.ResourceRecordSet.Type == endpoint.RecordTypeCNAME {
				change.ResourceRecordSet.Data += "."
			}

			switch change.Action {
			case DigitalOceanCreate:
				_, _, err = p.Client.CreateRecord(context.TODO(), zoneName,
					&godo.DomainRecordEditRequest{
						Data: change.ResourceRecordSet.Data,
						Name: change.ResourceRecordSet.Name,
						Type: change.ResourceRecordSet.Type,
						TTL:  change.ResourceRecordSet.TTL,
					})
				if err != nil {
					return err
				}
			case DigitalOceanDelete:
				recordID := p.getRecordID(records, change.ResourceRecordSet)
				_, err = p.Client.DeleteRecord(context.TODO(), zoneName, recordID)
				if err != nil {
					return err
				}
			case DigitalOceanUpdate:
				recordID := p.getRecordID(records, change.ResourceRecordSet)
				_, _, err = p.Client.EditRecord(context.TODO(), zoneName, recordID,
					&godo.DomainRecordEditRequest{
						Data: change.ResourceRecordSet.Data,
						Name: change.ResourceRecordSet.Name,
						Type: change.ResourceRecordSet.Type,
						TTL:  change.ResourceRecordSet.TTL,
					})
				if err != nil {
					return err
				}
			}
		}
	}
	return nil
}
func (p *DigitalOceanProvider) submitChanges(changes []*DigitalOceanChange) error {
	// return early if there is nothing to change
	if len(changes) == 0 {
		return nil
	}

	zones, err := p.Zones()
	if err != nil {
		return err
	}

	// separate into per-zone change sets to be passed to the API.
	changesByZone := digitalOceanChangesByZone(zones, changes)
	for zoneName, changes := range changesByZone {
		records, err := p.fetchRecords(zoneName)
		if err != nil {
			log.Errorf("Failed to list records in the zone: %s", zoneName)
			continue
		}
		for _, change := range changes {
			logFields := log.Fields{
				"record": change.ResourceRecordSet.Name,
				"type":   change.ResourceRecordSet.Type,
				"ttl":    change.ResourceRecordSet.TTL,
				"action": change.Action,
				"zone":   zoneName,
			}

			log.WithFields(logFields).Info("Changing record.")

			if p.DryRun {
				continue
			}

			change.ResourceRecordSet.Name = strings.TrimSuffix(change.ResourceRecordSet.Name, "."+zoneName)

			// record at the root should be defined as @ instead of
			// the full domain name
			if change.ResourceRecordSet.Name == zoneName {
				change.ResourceRecordSet.Name = "@"
			}

			// for some reason the DO API requires the '.' at the end of "data" in case of CNAME request
			// Example: {"type":"CNAME","name":"hello","data":"www.example.com."}
			if change.ResourceRecordSet.Type == endpoint.RecordTypeCNAME {
				change.ResourceRecordSet.Data += "."
			}

			switch change.Action {
			case DigitalOceanCreate:
				_, _, err = p.Client.CreateRecord(context.TODO(), zoneName,
					&godo.DomainRecordEditRequest{
						Data: change.ResourceRecordSet.Data,
						Name: change.ResourceRecordSet.Name,
						Type: change.ResourceRecordSet.Type,
						TTL:  change.ResourceRecordSet.TTL,
					})
				if err != nil {
					return err
				}
			case DigitalOceanDelete:
				recordID := p.getRecordID(records, change.ResourceRecordSet)
				_, err = p.Client.DeleteRecord(context.TODO(), zoneName, recordID)
				if err != nil {
					return err
				}
			case DigitalOceanUpdate:
				recordID := p.getRecordID(records, change.ResourceRecordSet)
				_, _, err = p.Client.EditRecord(context.TODO(), zoneName, recordID,
					&godo.DomainRecordEditRequest{
						Data: change.ResourceRecordSet.Data,
						Name: change.ResourceRecordSet.Name,
						Type: change.ResourceRecordSet.Type,
						TTL:  change.ResourceRecordSet.TTL,
					})
				if err != nil {
					return err
				}
			}
		}
	}
	return nil
}
// submitChanges takes a zone and a collection of Changes and sends them as a single transaction.