You've already forked cloudflare-dns-cli
Add vendor files, allowing compilation without internet
Added with `go mod vendor`
This commit is contained in:
21
vendor/github.com/libdns/cloudflare/LICENSE
generated
vendored
Normal file
21
vendor/github.com/libdns/cloudflare/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Matthew Holt
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
25
vendor/github.com/libdns/cloudflare/README.md
generated
vendored
Normal file
25
vendor/github.com/libdns/cloudflare/README.md
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
Cloudflare for `libdns`
|
||||
=======================
|
||||
|
||||
[](https://pkg.go.dev/github.com/libdns/cloudflare)
|
||||
|
||||
This package implements the [libdns interfaces](https://github.com/libdns/libdns) for [Cloudflare](https://www.cloudflare.com).
|
||||
|
||||
## Authenticating
|
||||
|
||||
This package supports API **token** authentication.
|
||||
|
||||
You will need to create a token with the following permissions:
|
||||
|
||||
- Zone / Zone / Read
|
||||
- Zone / DNS / Edit
|
||||
|
||||
The first permission is needed to get the zone ID, and the second permission is obviously necessary to edit the DNS records. If you're only using the `GetRecords()` method, you can change the second permission to Read to guarantee no changes will be made.
|
||||
|
||||
To clarify, do NOT use API keys, which are globally-scoped:
|
||||
|
||||

|
||||
|
||||
DO use scoped API tokens:
|
||||
|
||||

|
150
vendor/github.com/libdns/cloudflare/client.go
generated
vendored
Normal file
150
vendor/github.com/libdns/cloudflare/client.go
generated
vendored
Normal file
@ -0,0 +1,150 @@
|
||||
package cloudflare
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/libdns/libdns"
|
||||
)
|
||||
|
||||
func (p *Provider) createRecord(ctx context.Context, zoneInfo cfZone, record libdns.Record) (cfDNSRecord, error) {
|
||||
jsonBytes, err := json.Marshal(cloudflareRecord(record))
|
||||
if err != nil {
|
||||
return cfDNSRecord{}, err
|
||||
}
|
||||
|
||||
reqURL := fmt.Sprintf("%s/zones/%s/dns_records", baseURL, zoneInfo.ID)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, reqURL, bytes.NewReader(jsonBytes))
|
||||
if err != nil {
|
||||
return cfDNSRecord{}, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
var result cfDNSRecord
|
||||
_, err = p.doAPIRequest(req, &result)
|
||||
if err != nil {
|
||||
return cfDNSRecord{}, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// updateRecord updates a DNS record. oldRec must have both an ID and zone ID.
|
||||
// Only the non-empty fields in newRec will be changed.
|
||||
func (p *Provider) updateRecord(ctx context.Context, oldRec, newRec cfDNSRecord) (cfDNSRecord, error) {
|
||||
reqURL := fmt.Sprintf("%s/zones/%s/dns_records/%s", baseURL, oldRec.ZoneID, oldRec.ID)
|
||||
jsonBytes, err := json.Marshal(newRec)
|
||||
if err != nil {
|
||||
return cfDNSRecord{}, err
|
||||
}
|
||||
|
||||
// PATCH changes only the populated fields; PUT resets Type, Name, Content, and TTL even if empty
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPatch, reqURL, bytes.NewReader(jsonBytes))
|
||||
if err != nil {
|
||||
return cfDNSRecord{}, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
var result cfDNSRecord
|
||||
_, err = p.doAPIRequest(req, &result)
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (p *Provider) getDNSRecords(ctx context.Context, zoneInfo cfZone, rec libdns.Record, matchContent bool) ([]cfDNSRecord, error) {
|
||||
qs := make(url.Values)
|
||||
qs.Set("type", rec.Type)
|
||||
qs.Set("name", libdns.AbsoluteName(rec.Name, zoneInfo.Name))
|
||||
if matchContent {
|
||||
qs.Set("content", rec.Value)
|
||||
}
|
||||
|
||||
reqURL := fmt.Sprintf("%s/zones/%s/dns_records?%s", baseURL, zoneInfo.ID, qs.Encode())
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var results []cfDNSRecord
|
||||
_, err = p.doAPIRequest(req, &results)
|
||||
return results, err
|
||||
}
|
||||
|
||||
func (p *Provider) getZoneInfo(ctx context.Context, zoneName string) (cfZone, error) {
|
||||
p.zonesMu.Lock()
|
||||
defer p.zonesMu.Unlock()
|
||||
|
||||
// if we already got the zone info, reuse it
|
||||
if p.zones == nil {
|
||||
p.zones = make(map[string]cfZone)
|
||||
}
|
||||
if zone, ok := p.zones[zoneName]; ok {
|
||||
return zone, nil
|
||||
}
|
||||
|
||||
qs := make(url.Values)
|
||||
qs.Set("name", zoneName)
|
||||
reqURL := fmt.Sprintf("%s/zones?%s", baseURL, qs.Encode())
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil)
|
||||
if err != nil {
|
||||
return cfZone{}, err
|
||||
}
|
||||
|
||||
var zones []cfZone
|
||||
_, err = p.doAPIRequest(req, &zones)
|
||||
if err != nil {
|
||||
return cfZone{}, err
|
||||
}
|
||||
if len(zones) != 1 {
|
||||
return cfZone{}, fmt.Errorf("expected 1 zone, got %d for %s", len(zones), zoneName)
|
||||
}
|
||||
|
||||
// cache this zone for possible reuse
|
||||
p.zones[zoneName] = zones[0]
|
||||
|
||||
return zones[0], nil
|
||||
}
|
||||
|
||||
// doAPIRequest authenticates the request req and does the round trip. It returns
|
||||
// the decoded response from Cloudflare if successful; otherwise it returns an
|
||||
// error including error information from the API if applicable. If result is a
|
||||
// non-nil pointer, the result field from the API response will be decoded into
|
||||
// it for convenience.
|
||||
func (p *Provider) doAPIRequest(req *http.Request, result interface{}) (cfResponse, error) {
|
||||
req.Header.Set("Authorization", "Bearer "+p.APIToken)
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return cfResponse{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var respData cfResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&respData)
|
||||
if err != nil {
|
||||
return cfResponse{}, err
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return cfResponse{}, fmt.Errorf("got error status: HTTP %d: %+v", resp.StatusCode, respData.Errors)
|
||||
}
|
||||
if len(respData.Errors) > 0 {
|
||||
return cfResponse{}, fmt.Errorf("got errors: HTTP %d: %+v", resp.StatusCode, respData.Errors)
|
||||
}
|
||||
|
||||
if len(respData.Result) > 0 && result != nil {
|
||||
err = json.Unmarshal(respData.Result, result)
|
||||
if err != nil {
|
||||
return cfResponse{}, err
|
||||
}
|
||||
respData.Result = nil
|
||||
}
|
||||
|
||||
return respData, err
|
||||
}
|
||||
|
||||
const baseURL = "https://api.cloudflare.com/client/v4"
|
148
vendor/github.com/libdns/cloudflare/models.go
generated
vendored
Normal file
148
vendor/github.com/libdns/cloudflare/models.go
generated
vendored
Normal file
@ -0,0 +1,148 @@
|
||||
package cloudflare
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/libdns/libdns"
|
||||
)
|
||||
|
||||
type cfZone struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
DevelopmentMode int `json:"development_mode"`
|
||||
OriginalNameServers []string `json:"original_name_servers"`
|
||||
OriginalRegistrar string `json:"original_registrar"`
|
||||
OriginalDnshost string `json:"original_dnshost"`
|
||||
CreatedOn time.Time `json:"created_on"`
|
||||
ModifiedOn time.Time `json:"modified_on"`
|
||||
ActivatedOn time.Time `json:"activated_on"`
|
||||
Account struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
} `json:"account"`
|
||||
Permissions []string `json:"permissions"`
|
||||
Plan struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Price int `json:"price"`
|
||||
Currency string `json:"currency"`
|
||||
Frequency string `json:"frequency"`
|
||||
LegacyID string `json:"legacy_id"`
|
||||
IsSubscribed bool `json:"is_subscribed"`
|
||||
CanSubscribe bool `json:"can_subscribe"`
|
||||
} `json:"plan"`
|
||||
PlanPending struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Price int `json:"price"`
|
||||
Currency string `json:"currency"`
|
||||
Frequency string `json:"frequency"`
|
||||
LegacyID string `json:"legacy_id"`
|
||||
IsSubscribed bool `json:"is_subscribed"`
|
||||
CanSubscribe bool `json:"can_subscribe"`
|
||||
} `json:"plan_pending"`
|
||||
Status string `json:"status"`
|
||||
Paused bool `json:"paused"`
|
||||
Type string `json:"type"`
|
||||
NameServers []string `json:"name_servers"`
|
||||
}
|
||||
|
||||
type cfDNSRecord struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Proxiable bool `json:"proxiable,omitempty"`
|
||||
Proxied bool `json:"proxied,omitempty"`
|
||||
TTL int `json:"ttl,omitempty"` // seconds
|
||||
Locked bool `json:"locked,omitempty"`
|
||||
ZoneID string `json:"zone_id,omitempty"`
|
||||
ZoneName string `json:"zone_name,omitempty"`
|
||||
CreatedOn time.Time `json:"created_on,omitempty"`
|
||||
ModifiedOn time.Time `json:"modified_on,omitempty"`
|
||||
Data *struct {
|
||||
// LOC
|
||||
LatDegrees int `json:"lat_degrees,omitempty"`
|
||||
LatMinutes int `json:"lat_minutes,omitempty"`
|
||||
LatSeconds int `json:"lat_seconds,omitempty"`
|
||||
LatDirection string `json:"lat_direction,omitempty"`
|
||||
LongDegrees int `json:"long_degrees,omitempty"`
|
||||
LongMinutes int `json:"long_minutes,omitempty"`
|
||||
LongSeconds int `json:"long_seconds,omitempty"`
|
||||
LongDirection string `json:"long_direction,omitempty"`
|
||||
Altitude int `json:"altitude,omitempty"`
|
||||
Size int `json:"size,omitempty"`
|
||||
PrecisionHorz int `json:"precision_horz,omitempty"`
|
||||
PrecisionVert int `json:"precision_vert,omitempty"`
|
||||
|
||||
// SRV
|
||||
Service string `json:"service,omitempty"`
|
||||
Proto string `json:"proto,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Priority int `json:"priority,omitempty"`
|
||||
Weight int `json:"weight,omitempty"`
|
||||
Port int `json:"port,omitempty"`
|
||||
Target string `json:"target,omitempty"`
|
||||
|
||||
// DNSKEY
|
||||
Flags int `json:"flags,omitempty"`
|
||||
Protocol int `json:"protocol,omitempty"`
|
||||
Algorithm int `json:"algorithm,omitempty"`
|
||||
|
||||
// DS
|
||||
KeyTag int `json:"key_tag,omitempty"`
|
||||
DigestType int `json:"digest_type,omitempty"`
|
||||
|
||||
// TLSA
|
||||
Usage int `json:"usage,omitempty"`
|
||||
Selector int `json:"selector,omitempty"`
|
||||
MatchingType int `json:"matching_type,omitempty"`
|
||||
|
||||
// URI
|
||||
Content string `json:"content,omitempty"`
|
||||
} `json:"data,omitempty"`
|
||||
Meta *struct {
|
||||
AutoAdded bool `json:"auto_added,omitempty"`
|
||||
Source string `json:"source,omitempty"`
|
||||
} `json:"meta,omitempty"`
|
||||
}
|
||||
|
||||
func (r cfDNSRecord) libdnsRecord(zone string) libdns.Record {
|
||||
return libdns.Record{
|
||||
Type: r.Type,
|
||||
Name: libdns.RelativeName(r.Name, zone),
|
||||
Value: r.Content,
|
||||
TTL: time.Duration(r.TTL) * time.Second,
|
||||
ID: r.ID,
|
||||
}
|
||||
}
|
||||
|
||||
func cloudflareRecord(r libdns.Record) cfDNSRecord {
|
||||
return cfDNSRecord{
|
||||
ID: r.ID,
|
||||
Type: r.Type,
|
||||
Name: r.Name,
|
||||
Content: r.Value,
|
||||
TTL: int(r.TTL.Seconds()),
|
||||
}
|
||||
}
|
||||
|
||||
// All API responses have this structure.
|
||||
type cfResponse struct {
|
||||
Result json.RawMessage `json:"result,omitempty"`
|
||||
Success bool `json:"success"`
|
||||
Errors []struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
} `json:"errors,omitempty"`
|
||||
Messages []interface{} `json:"messages,omitempty"`
|
||||
ResultInfo *cfResultInfo `json:"result_info,omitempty"`
|
||||
}
|
||||
|
||||
type cfResultInfo struct {
|
||||
Page int `json:"page"`
|
||||
PerPage int `json:"per_page"`
|
||||
Count int `json:"count"`
|
||||
TotalCount int `json:"total_count"`
|
||||
}
|
174
vendor/github.com/libdns/cloudflare/provider.go
generated
vendored
Normal file
174
vendor/github.com/libdns/cloudflare/provider.go
generated
vendored
Normal file
@ -0,0 +1,174 @@
|
||||
package cloudflare
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/libdns/libdns"
|
||||
)
|
||||
|
||||
// Provider implements the libdns interfaces for Cloudflare.
|
||||
// TODO: Support pagination and retries, handle rate limits.
|
||||
type Provider struct {
|
||||
// API token is used for authentication. Make sure to use a
|
||||
// scoped API **token**, NOT a global API **key**. It will
|
||||
// need two permissions: Zone-Zone-Read and Zone-DNS-Edit,
|
||||
// unless you are only using `GetRecords()`, in which case
|
||||
// the second can be changed to Read.
|
||||
APIToken string `json:"api_token,omitempty"`
|
||||
|
||||
zones map[string]cfZone
|
||||
zonesMu sync.Mutex
|
||||
}
|
||||
|
||||
// GetRecords lists all the records in the zone.
|
||||
func (p *Provider) GetRecords(ctx context.Context, zone string) ([]libdns.Record, error) {
|
||||
zoneInfo, err := p.getZoneInfo(ctx, zone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqURL := fmt.Sprintf("%s/zones/%s/dns_records", baseURL, zoneInfo.ID)
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", reqURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result []cfDNSRecord
|
||||
_, err = p.doAPIRequest(req, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
recs := make([]libdns.Record, 0, len(result))
|
||||
for _, rec := range result {
|
||||
recs = append(recs, rec.libdnsRecord(zone))
|
||||
}
|
||||
|
||||
return recs, nil
|
||||
}
|
||||
|
||||
// AppendRecords adds records to the zone. It returns the records that were added.
|
||||
func (p *Provider) AppendRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) {
|
||||
zoneInfo, err := p.getZoneInfo(ctx, zone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var created []libdns.Record
|
||||
for _, rec := range records {
|
||||
result, err := p.createRecord(ctx, zoneInfo, rec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
created = append(created, result.libdnsRecord(zone))
|
||||
}
|
||||
|
||||
return created, nil
|
||||
}
|
||||
|
||||
// DeleteRecords deletes the records from the zone. If a record does not have an ID,
|
||||
// it will be looked up. It returns the records that were deleted.
|
||||
func (p *Provider) DeleteRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) {
|
||||
zoneInfo, err := p.getZoneInfo(ctx, zone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var recs []libdns.Record
|
||||
for _, rec := range records {
|
||||
// we create a "delete queue" for each record
|
||||
// requested for deletion; if the record ID
|
||||
// is known, that is the only one to fill the
|
||||
// queue, but if it's not known, we try to find
|
||||
// a match theoretically there could be more
|
||||
// than one
|
||||
var deleteQueue []libdns.Record
|
||||
|
||||
if rec.ID == "" {
|
||||
// record ID is required; try to find it with what was provided
|
||||
exactMatches, err := p.getDNSRecords(ctx, zoneInfo, rec, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, rec := range exactMatches {
|
||||
deleteQueue = append(deleteQueue, rec.libdnsRecord(zone))
|
||||
}
|
||||
} else {
|
||||
deleteQueue = []libdns.Record{rec}
|
||||
}
|
||||
|
||||
for _, delRec := range deleteQueue {
|
||||
reqURL := fmt.Sprintf("%s/zones/%s/dns_records/%s", baseURL, zoneInfo.ID, delRec.ID)
|
||||
req, err := http.NewRequestWithContext(ctx, "DELETE", reqURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result cfDNSRecord
|
||||
_, err = p.doAPIRequest(req, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
recs = append(recs, result.libdnsRecord(zone))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return recs, nil
|
||||
}
|
||||
|
||||
// SetRecords sets the records in the zone, either by updating existing records
|
||||
// or creating new ones. It returns the updated records.
|
||||
func (p *Provider) SetRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) {
|
||||
zoneInfo, err := p.getZoneInfo(ctx, zone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var results []libdns.Record
|
||||
for _, rec := range records {
|
||||
oldRec := cloudflareRecord(rec)
|
||||
oldRec.ZoneID = zoneInfo.ID
|
||||
if rec.ID == "" {
|
||||
// the record might already exist, even if we don't know the ID yet
|
||||
matches, err := p.getDNSRecords(ctx, zoneInfo, rec, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(matches) == 0 {
|
||||
// record doesn't exist; create it
|
||||
result, err := p.createRecord(ctx, zoneInfo, rec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, result.libdnsRecord(zone))
|
||||
continue
|
||||
}
|
||||
if len(matches) > 1 {
|
||||
return nil, fmt.Errorf("unexpectedly found more than 1 record for %v", rec)
|
||||
}
|
||||
// record does exist, fill in the ID so that we can update it
|
||||
oldRec.ID = matches[0].ID
|
||||
}
|
||||
// record exists; update it
|
||||
result, err := p.updateRecord(ctx, oldRec, cloudflareRecord(rec))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, result.libdnsRecord(zone))
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ libdns.RecordGetter = (*Provider)(nil)
|
||||
_ libdns.RecordAppender = (*Provider)(nil)
|
||||
_ libdns.RecordSetter = (*Provider)(nil)
|
||||
_ libdns.RecordDeleter = (*Provider)(nil)
|
||||
)
|
Reference in New Issue
Block a user