Add support for Bindman DNS provider (#869)

This commit is contained in:
Werberson 2019-04-27 13:50:40 -03:00 committed by Ludovic Fernandez
parent 62e3f4fc01
commit a72639ef51
20 changed files with 1322 additions and 13 deletions

28
Gopkg.lock generated
View file

@ -215,6 +215,14 @@
revision = "4966fc68f5b7593aafa6cbbba2d65ec6e1416047"
version = "v1.1.0"
[[projects]]
digest = "1:ea1d5bfdb4ec5c2ee48c97865e6de1a28fa8c4849a3f56b27d521aa619038e06"
name = "github.com/go-errors/errors"
packages = ["."]
pruneopts = "NUT"
revision = "a6af135bd4e28680facf08a3d206b454abc877a4"
version = "v1.0.1"
[[projects]]
digest = "1:74d9b0a7b4107b41e0ade759fac64502876f82d29fb23d77b3dd24b194ee3dd5"
name = "github.com/go-ini/ini"
@ -329,6 +337,25 @@
pruneopts = "NUT"
revision = "16bdd962781df9696f40cc2bab924f1a855a7f89"
[[projects]]
digest = "1:1082aeb059ff66b4fb6da53f9e7591726c6a81901f05ce48a470091784b23914"
name = "github.com/labbsr0x/bindman-dns-webhook"
packages = [
"src/client",
"src/types",
]
pruneopts = "NUT"
revision = "234ca2a50eebc2095f42a884709a6e9013366d86"
version = "v1.0.0"
[[projects]]
branch = "master"
digest = "1:ad2a63b2d6dfe7d66bf14c01f1171a3951abef6e0fb136170359c3f7c4f51615"
name = "github.com/labbsr0x/goh"
packages = ["gohclient"]
pruneopts = "NUT"
revision = "8b16b4848295edda07b9a828e5a3b285c25c2b9c"
[[projects]]
digest = "1:111ff5a09a32895248270bfaef9b8b6ac163a8cde9cdd603fed64b3e4b59e8ab"
name = "github.com/linode/linodego"
@ -805,6 +832,7 @@
"github.com/gophercloud/gophercloud/openstack/dns/v2/zones",
"github.com/iij/doapi",
"github.com/iij/doapi/protocol",
"github.com/labbsr0x/bindman-dns-webhook/src/client",
"github.com/linode/linodego",
"github.com/miekg/dns",
"github.com/namedotcom/go/namecom",

View file

@ -105,3 +105,7 @@
version = "4.0.0"
name = "github.com/oracle/oci-go-sdk"
[[constraint]]
name = "github.com/labbsr0x/bindman-dns-webhook"
version = "1.0.0"

View file

@ -44,16 +44,16 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns).
| | | | |
|---------------------------------------------------------------------------------|---------------------------------------------------------------------------------|---------------------------------------------------------------------------------|---------------------------------------------------------------------------------|
| [Alibaba Cloud DNS](https://go-acme.github.io/lego/dns/alidns/) | [Amazon Lightsail](https://go-acme.github.io/lego/dns/lightsail/) | [Amazon Route 53](https://go-acme.github.io/lego/dns/route53/) | [Aurora DNS](https://go-acme.github.io/lego/dns/auroradns/) |
| [Azure](https://go-acme.github.io/lego/dns/azure/) | [Bluecat](https://go-acme.github.io/lego/dns/bluecat/) | [Cloudflare](https://go-acme.github.io/lego/dns/cloudflare/) | [ClouDNS](https://go-acme.github.io/lego/dns/cloudns/) |
| [CloudXNS](https://go-acme.github.io/lego/dns/cloudxns/) | [ConoHa](https://go-acme.github.io/lego/dns/conoha/) | [Designate DNSaaS for Openstack](https://go-acme.github.io/lego/dns/designate/) | [Digital Ocean](https://go-acme.github.io/lego/dns/digitalocean/) |
| [DNS Made Easy](https://go-acme.github.io/lego/dns/dnsmadeeasy/) | [DNSimple](https://go-acme.github.io/lego/dns/dnsimple/) | [DNSPod](https://go-acme.github.io/lego/dns/dnspod/) | [Domain Offensive (do.de)](https://go-acme.github.io/lego/dns/dode/) |
| [DreamHost](https://go-acme.github.io/lego/dns/dreamhost/) | [Duck DNS](https://go-acme.github.io/lego/dns/duckdns/) | [Dyn](https://go-acme.github.io/lego/dns/dyn/) | [Exoscale](https://go-acme.github.io/lego/dns/exoscale/) |
| [External program](https://go-acme.github.io/lego/dns/exec/) | [FastDNS](https://go-acme.github.io/lego/dns/fastdns/) | [Gandi Live DNS (v5)](https://go-acme.github.io/lego/dns/gandiv5/) | [Gandi](https://go-acme.github.io/lego/dns/gandi/) |
| [Glesys](https://go-acme.github.io/lego/dns/glesys/) | [Go Daddy](https://go-acme.github.io/lego/dns/godaddy/) | [Google Cloud](https://go-acme.github.io/lego/dns/gcloud/) | [Hosting.de](https://go-acme.github.io/lego/dns/hostingde/) |
| [HTTP request](https://go-acme.github.io/lego/dns/httpreq/) | [Internet Initiative Japan](https://go-acme.github.io/lego/dns/iij/) | [INWX](https://go-acme.github.io/lego/dns/inwx/) | [Joohoi's ACME-DNS](https://go-acme.github.io/lego/dns/acme-dns) |
| [Linode (deprecated)](https://go-acme.github.io/lego/dns/linode/) | [Linode (v4)](https://go-acme.github.io/lego/dns/linodev4/) | [Manual](https://go-acme.github.io/lego/dns/manual/) | [MyDNS.jp](https://go-acme.github.io/lego/dns/mydnsjp/) |
| [Name.com](https://go-acme.github.io/lego/dns/namedotcom/) | [Namecheap](https://go-acme.github.io/lego/dns/namecheap/) | [Netcup](https://go-acme.github.io/lego/dns/netcup/) | [NIFCloud](https://go-acme.github.io/lego/dns/nifcloud/) |
| [NS1](https://go-acme.github.io/lego/dns/ns1/) | [Open Telekom Cloud](https://go-acme.github.io/lego/dns/otc/) | [Oracle Cloud](https://go-acme.github.io/lego/dns/oraclecloud/) | [OVH](https://go-acme.github.io/lego/dns/ovh/) |
| [PowerDNS](https://go-acme.github.io/lego/dns/pdns/) | [Rackspace](https://go-acme.github.io/lego/dns/rackspace/) | [RFC2136](https://go-acme.github.io/lego/dns/rfc2136/) | [Sakura Cloud](https://go-acme.github.io/lego/dns/sakuracloud/) |
| [Selectel](https://go-acme.github.io/lego/dns/selectel/) | [Stackpath](https://go-acme.github.io/lego/dns/stackpath/) | [TransIP](https://go-acme.github.io/lego/dns/transip/) | [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/) |
| [Vscale](https://go-acme.github.io/lego/dns/vscale/) | [Vultr](https://go-acme.github.io/lego/dns/vultr/) | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/) | |
| [Azure](https://go-acme.github.io/lego/dns/azure/) | [Bindman](https://go-acme.github.io/lego/dns/bindman/) | [Bluecat](https://go-acme.github.io/lego/dns/bluecat/) | [Cloudflare](https://go-acme.github.io/lego/dns/cloudflare/) |
| [ClouDNS](https://go-acme.github.io/lego/dns/cloudns/) | [CloudXNS](https://go-acme.github.io/lego/dns/cloudxns/) | [ConoHa](https://go-acme.github.io/lego/dns/conoha/) | [Designate DNSaaS for Openstack](https://go-acme.github.io/lego/dns/designate/) |
| [Digital Ocean](https://go-acme.github.io/lego/dns/digitalocean/) | [DNS Made Easy](https://go-acme.github.io/lego/dns/dnsmadeeasy/) | [DNSimple](https://go-acme.github.io/lego/dns/dnsimple/) | [DNSPod](https://go-acme.github.io/lego/dns/dnspod/) |
| [Domain Offensive (do.de)](https://go-acme.github.io/lego/dns/dode/) | [DreamHost](https://go-acme.github.io/lego/dns/dreamhost/) | [Duck DNS](https://go-acme.github.io/lego/dns/duckdns/) | [Dyn](https://go-acme.github.io/lego/dns/dyn/) |
| [Exoscale](https://go-acme.github.io/lego/dns/exoscale/) | [External program](https://go-acme.github.io/lego/dns/exec/) | [FastDNS](https://go-acme.github.io/lego/dns/fastdns/) | [Gandi Live DNS (v5)](https://go-acme.github.io/lego/dns/gandiv5/) |
| [Gandi](https://go-acme.github.io/lego/dns/gandi/) | [Glesys](https://go-acme.github.io/lego/dns/glesys/) | [Go Daddy](https://go-acme.github.io/lego/dns/godaddy/) | [Google Cloud](https://go-acme.github.io/lego/dns/gcloud/) |
| [Hosting.de](https://go-acme.github.io/lego/dns/hostingde/) | [HTTP request](https://go-acme.github.io/lego/dns/httpreq/) | [Internet Initiative Japan](https://go-acme.github.io/lego/dns/iij/) | [INWX](https://go-acme.github.io/lego/dns/inwx/) |
| [Joohoi's ACME-DNS](https://go-acme.github.io/lego/dns/acme-dns) | [Linode (deprecated)](https://go-acme.github.io/lego/dns/linode/) | [Linode (v4)](https://go-acme.github.io/lego/dns/linodev4/) | [Manual](https://go-acme.github.io/lego/dns/manual/) |
| [MyDNS.jp](https://go-acme.github.io/lego/dns/mydnsjp/) | [Name.com](https://go-acme.github.io/lego/dns/namedotcom/) | [Namecheap](https://go-acme.github.io/lego/dns/namecheap/) | [Netcup](https://go-acme.github.io/lego/dns/netcup/) |
| [NIFCloud](https://go-acme.github.io/lego/dns/nifcloud/) | [NS1](https://go-acme.github.io/lego/dns/ns1/) | [Open Telekom Cloud](https://go-acme.github.io/lego/dns/otc/) | [Oracle Cloud](https://go-acme.github.io/lego/dns/oraclecloud/) |
| [OVH](https://go-acme.github.io/lego/dns/ovh/) | [PowerDNS](https://go-acme.github.io/lego/dns/pdns/) | [Rackspace](https://go-acme.github.io/lego/dns/rackspace/) | [RFC2136](https://go-acme.github.io/lego/dns/rfc2136/) |
| [Sakura Cloud](https://go-acme.github.io/lego/dns/sakuracloud/) | [Selectel](https://go-acme.github.io/lego/dns/selectel/) | [Stackpath](https://go-acme.github.io/lego/dns/stackpath/) | [TransIP](https://go-acme.github.io/lego/dns/transip/) |
| [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/) | [Vscale](https://go-acme.github.io/lego/dns/vscale/) | [Vultr](https://go-acme.github.io/lego/dns/vultr/) | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/) |

View file

@ -20,6 +20,7 @@ func allDNSCodes() string {
"alidns",
"auroradns",
"azure",
"bindman",
"bluecat",
"cloudflare",
"cloudns",
@ -161,6 +162,25 @@ func displayDNSHelp(name string) {
fmt.Fprintln(w)
fmt.Fprintln(w, `More information: https://go-acme.github.io/lego/dns/azure`)
case "bindman":
// generated from: providers/dns/bindman/bindman.toml
fmt.Fprintln(w, `Configuration for Bindman.`)
fmt.Fprintln(w, `Code: 'bindman'`)
fmt.Fprintln(w, `Since: 'v2.6.0'`)
fmt.Fprintln(w)
fmt.Fprintln(w, `Credentials:`)
fmt.Fprintln(w, ` - "BINDMAN_MANAGER_ADDRESS": The server URL, should have scheme, hostname, and port (if required) of the Bindman-DNS Manager server`)
fmt.Fprintln(w)
fmt.Fprintln(w, `Additional Configuration:`)
fmt.Fprintln(w, ` - "BINDMAN_HTTP_TIMEOUT": API request timeout`)
fmt.Fprintln(w, ` - "BINDMAN_POLLING_INTERVAL": Time between DNS propagation check`)
fmt.Fprintln(w, ` - "BINDMAN_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
fmt.Fprintln(w)
fmt.Fprintln(w, `More information: https://go-acme.github.io/lego/dns/bindman`)
case "bluecat":
// generated from: providers/dns/bluecat/bluecat.toml
fmt.Fprintln(w, `Configuration for Bluecat.`)

View file

@ -0,0 +1,62 @@
---
title: "Bindman"
date: 2019-03-03T16:39:46+01:00
draft: false
slug: bindman
---
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/bindman/bindman.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
Since: v2.6.0
Configuration for [Bindman](https://github.com/labbsr0x/bindman-dns-webhook).
<!--more-->
- Code: `bindman`
Here is an example bash command using the Bindman provider:
```bash
BINDMAN_MANAGER_ADDRESS=<your bindman manager address> \
lego --dns bindman --domains my.domain.com --email my@email.com run
```
## Credentials
| Environment Variable Name | Description |
|-----------------------|-------------|
| `BINDMAN_MANAGER_ADDRESS` | The server URL, should have scheme, hostname, and port (if required) of the Bindman-DNS Manager server |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here](/lego/dns/#configuration-and-credentials).
## Additional Configuration
| Environment Variable Name | Description |
|--------------------------------|-------------|
| `BINDMAN_HTTP_TIMEOUT` | API request timeout |
| `BINDMAN_POLLING_INTERVAL` | Time between DNS propagation check |
| `BINDMAN_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here](/lego/dns/#configuration-and-credentials).
## More information
- [API documentation](https://gitlab.isc.org/isc-projects/bind9)
- [Go client](https://github.com/labbsr0x/bindman-dns-webhook)
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/bindman/bindman.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->

View file

@ -0,0 +1,99 @@
// Package bindman implements a DNS provider for solving the DNS-01 challenge.
package bindman
import (
"errors"
"fmt"
"net/http"
"time"
"github.com/go-acme/lego/challenge/dns01"
"github.com/go-acme/lego/platform/config/env"
"github.com/labbsr0x/bindman-dns-webhook/src/client"
)
// Config is used to configure the creation of the DNSProvider
type Config struct {
PropagationTimeout time.Duration
PollingInterval time.Duration
BaseURL string
HTTPClient *http.Client
}
// NewDefaultConfig returns a default configuration for the DNSProvider
func NewDefaultConfig() *Config {
return &Config{
PropagationTimeout: env.GetOrDefaultSecond("BINDMAN_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
PollingInterval: env.GetOrDefaultSecond("BINDMAN_POLLING_INTERVAL", dns01.DefaultPollingInterval),
HTTPClient: &http.Client{
Timeout: env.GetOrDefaultSecond("BINDMAN_HTTP_TIMEOUT", time.Minute),
},
}
}
// DNSProvider is an implementation of the acme.ChallengeProvider interface that uses
// Bindman's Address Manager REST API to manage TXT records for a domain.
type DNSProvider struct {
config *Config
client *client.DNSWebhookClient
}
// NewDNSProvider returns a DNSProvider instance configured for Bindman.
// BINDMAN_MANAGER_ADDRESS should have the scheme, hostname, and port (if required) of the authoritative Bindman Manager server.
func NewDNSProvider() (*DNSProvider, error) {
values, err := env.Get("BINDMAN_MANAGER_ADDRESS")
if err != nil {
return nil, fmt.Errorf("bindman: %v", err)
}
config := NewDefaultConfig()
config.BaseURL = values["BINDMAN_MANAGER_ADDRESS"]
return NewDNSProviderConfig(config)
}
// NewDNSProviderConfig return a DNSProvider instance configured for Bindman.
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
if config == nil {
return nil, errors.New("bindman: the configuration of the DNS provider is nil")
}
if config.BaseURL == "" {
return nil, fmt.Errorf("bindman: bindman manager address missing")
}
bClient, err := client.New(config.BaseURL, config.HTTPClient)
if err != nil {
return nil, fmt.Errorf("bindman: %v", err)
}
return &DNSProvider{config: config, client: bClient}, nil
}
// Present creates a TXT record using the specified parameters.
// This will *not* create a subzone to contain the TXT record,
// so make sure the FQDN specified is within an extant zone.
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value := dns01.GetRecord(domain, keyAuth)
if err := d.client.AddRecord(fqdn, "TXT", value); err != nil {
return fmt.Errorf("bindman: %v", err)
}
return nil
}
// CleanUp removes the TXT record matching the specified parameters.
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _ := dns01.GetRecord(domain, keyAuth)
if err := d.client.RemoveRecord(fqdn, "TXT"); err != nil {
return fmt.Errorf("bindman: %v", err)
}
return nil
}
// Timeout returns the timeout and interval to use when checking for DNS propagation.
// Adjusting here to cope with spikes in propagation times.
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.config.PropagationTimeout, d.config.PollingInterval
}

View file

@ -0,0 +1,22 @@
Name = "Bindman"
Description = ''''''
URL = "https://github.com/labbsr0x/bindman-dns-webhook"
Code = "bindman"
Since = "v2.6.0"
Example = '''
BINDMAN_MANAGER_ADDRESS=<your bindman manager address> \
lego --dns bindman --domains my.domain.com --email my@email.com run
'''
[Configuration]
[Configuration.Credentials]
BINDMAN_MANAGER_ADDRESS = "The server URL, should have scheme, hostname, and port (if required) of the Bindman-DNS Manager server"
[Configuration.Additional]
BINDMAN_POLLING_INTERVAL = "Time between DNS propagation check"
BINDMAN_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
BINDMAN_HTTP_TIMEOUT = "API request timeout"
[Links]
API = "https://gitlab.isc.org/isc-projects/bind9"
GoClient = "https://github.com/labbsr0x/bindman-dns-webhook"

View file

@ -0,0 +1,238 @@
// Package bindman implements a DNS provider for solving the DNS-01 challenge.
package bindman
import (
"errors"
"net/http"
"testing"
"time"
"github.com/go-acme/lego/platform/tester"
bindmanClient "github.com/labbsr0x/bindman-dns-webhook/src/client"
"github.com/stretchr/testify/require"
)
var envTest = tester.NewEnvTest("BINDMAN_MANAGER_ADDRESS").WithDomain("BINDMAN_DOMAIN")
func TestNewDNSProvider(t *testing.T) {
testCases := []struct {
desc string
envVars map[string]string
expected string
}{
{
desc: "success",
envVars: map[string]string{
"BINDMAN_MANAGER_ADDRESS": "http://localhost",
},
},
{
desc: "missing bindman manager address",
envVars: map[string]string{
"BINDMAN_MANAGER_ADDRESS": "",
},
expected: "bindman: some credentials information are missing: BINDMAN_MANAGER_ADDRESS",
},
{
desc: "empty bindman manager address",
envVars: map[string]string{
"BINDMAN_MANAGER_ADDRESS": " ",
},
expected: "bindman: managerAddress parameter must be a non-empty string",
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
defer envTest.RestoreEnv()
envTest.ClearEnv()
envTest.Apply(test.envVars)
p, err := NewDNSProvider()
if len(test.expected) == 0 {
require.NoError(t, err)
require.NotNil(t, p)
require.NotNil(t, p.config)
require.NotNil(t, p.client)
} else {
require.EqualError(t, err, test.expected)
}
})
}
}
func TestNewDNSProviderConfig(t *testing.T) {
testCases := []struct {
desc string
config *Config
expected string
}{
{
desc: "success",
config: &Config{BaseURL: "http://localhost"},
},
{
desc: "missing base URL",
config: &Config{BaseURL: ""},
expected: "bindman: bindman manager address missing",
},
{
desc: "missing base URL",
config: &Config{BaseURL: " "},
expected: "bindman: managerAddress parameter must be a non-empty string",
},
{
desc: "missing config",
expected: "bindman: the configuration of the DNS provider is nil",
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
p, err := NewDNSProviderConfig(test.config)
if len(test.expected) == 0 {
require.NoError(t, err)
require.NotNil(t, p)
require.NotNil(t, p.config)
} else {
require.EqualError(t, err, test.expected)
}
})
}
}
func TestDNSProvider_Present(t *testing.T) {
testCases := []struct {
name string
client *bindmanClient.DNSWebhookClient
domain string
token string
keyAuth string
expectError bool
}{
{
name: "success when add record function return no error",
client: &bindmanClient.DNSWebhookClient{
ClientAPI: &MockHTTPClientAPI{Status: http.StatusNoContent},
},
domain: "hello.test.com",
keyAuth: "szDTG4zmM0GsKG91QAGO2M4UYOJMwU8oFpWOP7eTjCw",
expectError: false,
},
{
name: "error when add record function return an error",
client: &bindmanClient.DNSWebhookClient{
ClientAPI: &MockHTTPClientAPI{Error: errors.New("error adding record")},
},
domain: "hello.test.com",
keyAuth: "szDTG4zmM0GsKG91QAGO2M4UYOJMwU8oFpWOP7eTjCw",
expectError: true,
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
d := &DNSProvider{client: test.client}
err := d.Present(test.domain, test.token, test.keyAuth)
if test.expectError {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}
func TestDNSProvider_CleanUp(t *testing.T) {
testCases := []struct {
name string
client *bindmanClient.DNSWebhookClient
domain string
token string
keyAuth string
expectError bool
}{
{
name: "success when remove record function return no error",
client: &bindmanClient.DNSWebhookClient{
ClientAPI: &MockHTTPClientAPI{Status: http.StatusNoContent},
},
domain: "hello.test.com",
keyAuth: "szDTG4zmM0GsKG91QAGO2M4UYOJMwU8oFpWOP7eTjCw",
expectError: false,
},
{
name: "error when remove record function return an error",
client: &bindmanClient.DNSWebhookClient{
ClientAPI: &MockHTTPClientAPI{Error: errors.New("error adding record")},
},
domain: "hello.test.com",
keyAuth: "szDTG4zmM0GsKG91QAGO2M4UYOJMwU8oFpWOP7eTjCw",
expectError: true,
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
d := &DNSProvider{client: test.client}
err := d.CleanUp(test.domain, test.token, test.keyAuth)
if test.expectError {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}
func TestLivePresent(t *testing.T) {
if !envTest.IsLiveTest() {
t.Skip("skipping live test")
}
envTest.RestoreEnv()
provider, err := NewDNSProvider()
require.NoError(t, err)
err = provider.Present(envTest.GetDomain(), "", "123d==")
require.NoError(t, err)
}
func TestLiveCleanUp(t *testing.T) {
if !envTest.IsLiveTest() {
t.Skip("skipping live test")
}
envTest.RestoreEnv()
provider, err := NewDNSProvider()
require.NoError(t, err)
time.Sleep(1 * time.Second)
err = provider.CleanUp(envTest.GetDomain(), "", "123d==")
require.NoError(t, err)
}
type MockHTTPClientAPI struct {
Data []byte
Status int
Error error
}
func (m *MockHTTPClientAPI) Put(url string, data []byte) (*http.Response, []byte, error) {
return &http.Response{StatusCode: m.Status}, m.Data, m.Error
}
func (m *MockHTTPClientAPI) Post(url string, data []byte) (*http.Response, []byte, error) {
return &http.Response{StatusCode: m.Status}, m.Data, m.Error
}
func (m *MockHTTPClientAPI) Get(url string) (*http.Response, []byte, error) {
return &http.Response{StatusCode: m.Status}, m.Data, m.Error
}
func (m *MockHTTPClientAPI) Delete(url string) (*http.Response, []byte, error) {
return &http.Response{StatusCode: m.Status}, m.Data, m.Error
}

View file

@ -9,6 +9,7 @@ import (
"github.com/go-acme/lego/providers/dns/alidns"
"github.com/go-acme/lego/providers/dns/auroradns"
"github.com/go-acme/lego/providers/dns/azure"
"github.com/go-acme/lego/providers/dns/bindman"
"github.com/go-acme/lego/providers/dns/bluecat"
"github.com/go-acme/lego/providers/dns/cloudflare"
"github.com/go-acme/lego/providers/dns/cloudns"
@ -72,6 +73,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) {
return azure.NewDNSProvider()
case "auroradns":
return auroradns.NewDNSProvider()
case "bindman":
return bindman.NewDNSProvider()
case "bluecat":
return bluecat.NewDNSProvider()
case "cloudflare":

7
vendor/github.com/go-errors/errors/LICENSE.MIT generated vendored Normal file
View file

@ -0,0 +1,7 @@
Copyright (c) 2015 Conrad Irwin <conrad@bugsnag.com>
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.

217
vendor/github.com/go-errors/errors/error.go generated vendored Normal file
View file

@ -0,0 +1,217 @@
// Package errors provides errors that have stack-traces.
//
// This is particularly useful when you want to understand the
// state of execution when an error was returned unexpectedly.
//
// It provides the type *Error which implements the standard
// golang error interface, so you can use this library interchangably
// with code that is expecting a normal error return.
//
// For example:
//
// package crashy
//
// import "github.com/go-errors/errors"
//
// var Crashed = errors.Errorf("oh dear")
//
// func Crash() error {
// return errors.New(Crashed)
// }
//
// This can be called as follows:
//
// package main
//
// import (
// "crashy"
// "fmt"
// "github.com/go-errors/errors"
// )
//
// func main() {
// err := crashy.Crash()
// if err != nil {
// if errors.Is(err, crashy.Crashed) {
// fmt.Println(err.(*errors.Error).ErrorStack())
// } else {
// panic(err)
// }
// }
// }
//
// This package was original written to allow reporting to Bugsnag,
// but after I found similar packages by Facebook and Dropbox, it
// was moved to one canonical location so everyone can benefit.
package errors
import (
"bytes"
"fmt"
"reflect"
"runtime"
)
// The maximum number of stackframes on any error.
var MaxStackDepth = 50
// Error is an error with an attached stacktrace. It can be used
// wherever the builtin error interface is expected.
type Error struct {
Err error
stack []uintptr
frames []StackFrame
prefix string
}
// New makes an Error from the given value. If that value is already an
// error then it will be used directly, if not, it will be passed to
// fmt.Errorf("%v"). The stacktrace will point to the line of code that
// called New.
func New(e interface{}) *Error {
var err error
switch e := e.(type) {
case error:
err = e
default:
err = fmt.Errorf("%v", e)
}
stack := make([]uintptr, MaxStackDepth)
length := runtime.Callers(2, stack[:])
return &Error{
Err: err,
stack: stack[:length],
}
}
// Wrap makes an Error from the given value. If that value is already an
// error then it will be used directly, if not, it will be passed to
// fmt.Errorf("%v"). The skip parameter indicates how far up the stack
// to start the stacktrace. 0 is from the current call, 1 from its caller, etc.
func Wrap(e interface{}, skip int) *Error {
var err error
switch e := e.(type) {
case *Error:
return e
case error:
err = e
default:
err = fmt.Errorf("%v", e)
}
stack := make([]uintptr, MaxStackDepth)
length := runtime.Callers(2+skip, stack[:])
return &Error{
Err: err,
stack: stack[:length],
}
}
// WrapPrefix makes an Error from the given value. If that value is already an
// error then it will be used directly, if not, it will be passed to
// fmt.Errorf("%v"). The prefix parameter is used to add a prefix to the
// error message when calling Error(). The skip parameter indicates how far
// up the stack to start the stacktrace. 0 is from the current call,
// 1 from its caller, etc.
func WrapPrefix(e interface{}, prefix string, skip int) *Error {
err := Wrap(e, 1+skip)
if err.prefix != "" {
prefix = fmt.Sprintf("%s: %s", prefix, err.prefix)
}
return &Error{
Err: err.Err,
stack: err.stack,
prefix: prefix,
}
}
// Is detects whether the error is equal to a given error. Errors
// are considered equal by this function if they are the same object,
// or if they both contain the same error inside an errors.Error.
func Is(e error, original error) bool {
if e == original {
return true
}
if e, ok := e.(*Error); ok {
return Is(e.Err, original)
}
if original, ok := original.(*Error); ok {
return Is(e, original.Err)
}
return false
}
// Errorf creates a new error with the given message. You can use it
// as a drop-in replacement for fmt.Errorf() to provide descriptive
// errors in return values.
func Errorf(format string, a ...interface{}) *Error {
return Wrap(fmt.Errorf(format, a...), 1)
}
// Error returns the underlying error's message.
func (err *Error) Error() string {
msg := err.Err.Error()
if err.prefix != "" {
msg = fmt.Sprintf("%s: %s", err.prefix, msg)
}
return msg
}
// Stack returns the callstack formatted the same way that go does
// in runtime/debug.Stack()
func (err *Error) Stack() []byte {
buf := bytes.Buffer{}
for _, frame := range err.StackFrames() {
buf.WriteString(frame.String())
}
return buf.Bytes()
}
// Callers satisfies the bugsnag ErrorWithCallerS() interface
// so that the stack can be read out.
func (err *Error) Callers() []uintptr {
return err.stack
}
// ErrorStack returns a string that contains both the
// error message and the callstack.
func (err *Error) ErrorStack() string {
return err.TypeName() + " " + err.Error() + "\n" + string(err.Stack())
}
// StackFrames returns an array of frames containing information about the
// stack.
func (err *Error) StackFrames() []StackFrame {
if err.frames == nil {
err.frames = make([]StackFrame, len(err.stack))
for i, pc := range err.stack {
err.frames[i] = NewStackFrame(pc)
}
}
return err.frames
}
// TypeName returns the type this error. e.g. *errors.stringError.
func (err *Error) TypeName() string {
if _, ok := err.Err.(uncaughtPanic); ok {
return "panic"
}
return reflect.TypeOf(err.Err).String()
}

127
vendor/github.com/go-errors/errors/parse_panic.go generated vendored Normal file
View file

@ -0,0 +1,127 @@
package errors
import (
"strconv"
"strings"
)
type uncaughtPanic struct{ message string }
func (p uncaughtPanic) Error() string {
return p.message
}
// ParsePanic allows you to get an error object from the output of a go program
// that panicked. This is particularly useful with https://github.com/mitchellh/panicwrap.
func ParsePanic(text string) (*Error, error) {
lines := strings.Split(text, "\n")
state := "start"
var message string
var stack []StackFrame
for i := 0; i < len(lines); i++ {
line := lines[i]
if state == "start" {
if strings.HasPrefix(line, "panic: ") {
message = strings.TrimPrefix(line, "panic: ")
state = "seek"
} else {
return nil, Errorf("bugsnag.panicParser: Invalid line (no prefix): %s", line)
}
} else if state == "seek" {
if strings.HasPrefix(line, "goroutine ") && strings.HasSuffix(line, "[running]:") {
state = "parsing"
}
} else if state == "parsing" {
if line == "" {
state = "done"
break
}
createdBy := false
if strings.HasPrefix(line, "created by ") {
line = strings.TrimPrefix(line, "created by ")
createdBy = true
}
i++
if i >= len(lines) {
return nil, Errorf("bugsnag.panicParser: Invalid line (unpaired): %s", line)
}
frame, err := parsePanicFrame(line, lines[i], createdBy)
if err != nil {
return nil, err
}
stack = append(stack, *frame)
if createdBy {
state = "done"
break
}
}
}
if state == "done" || state == "parsing" {
return &Error{Err: uncaughtPanic{message}, frames: stack}, nil
}
return nil, Errorf("could not parse panic: %v", text)
}
// The lines we're passing look like this:
//
// main.(*foo).destruct(0xc208067e98)
// /0/go/src/github.com/bugsnag/bugsnag-go/pan/main.go:22 +0x151
func parsePanicFrame(name string, line string, createdBy bool) (*StackFrame, error) {
idx := strings.LastIndex(name, "(")
if idx == -1 && !createdBy {
return nil, Errorf("bugsnag.panicParser: Invalid line (no call): %s", name)
}
if idx != -1 {
name = name[:idx]
}
pkg := ""
if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
pkg += name[:lastslash] + "/"
name = name[lastslash+1:]
}
if period := strings.Index(name, "."); period >= 0 {
pkg += name[:period]
name = name[period+1:]
}
name = strings.Replace(name, "·", ".", -1)
if !strings.HasPrefix(line, "\t") {
return nil, Errorf("bugsnag.panicParser: Invalid line (no tab): %s", line)
}
idx = strings.LastIndex(line, ":")
if idx == -1 {
return nil, Errorf("bugsnag.panicParser: Invalid line (no line number): %s", line)
}
file := line[1:idx]
number := line[idx+1:]
if idx = strings.Index(number, " +"); idx > -1 {
number = number[:idx]
}
lno, err := strconv.ParseInt(number, 10, 32)
if err != nil {
return nil, Errorf("bugsnag.panicParser: Invalid line (bad line number): %s", line)
}
return &StackFrame{
File: file,
LineNumber: int(lno),
Package: pkg,
Name: name,
}, nil
}

102
vendor/github.com/go-errors/errors/stackframe.go generated vendored Normal file
View file

@ -0,0 +1,102 @@
package errors
import (
"bytes"
"fmt"
"io/ioutil"
"runtime"
"strings"
)
// A StackFrame contains all necessary information about to generate a line
// in a callstack.
type StackFrame struct {
// The path to the file containing this ProgramCounter
File string
// The LineNumber in that file
LineNumber int
// The Name of the function that contains this ProgramCounter
Name string
// The Package that contains this function
Package string
// The underlying ProgramCounter
ProgramCounter uintptr
}
// NewStackFrame popoulates a stack frame object from the program counter.
func NewStackFrame(pc uintptr) (frame StackFrame) {
frame = StackFrame{ProgramCounter: pc}
if frame.Func() == nil {
return
}
frame.Package, frame.Name = packageAndName(frame.Func())
// pc -1 because the program counters we use are usually return addresses,
// and we want to show the line that corresponds to the function call
frame.File, frame.LineNumber = frame.Func().FileLine(pc - 1)
return
}
// Func returns the function that contained this frame.
func (frame *StackFrame) Func() *runtime.Func {
if frame.ProgramCounter == 0 {
return nil
}
return runtime.FuncForPC(frame.ProgramCounter)
}
// String returns the stackframe formatted in the same way as go does
// in runtime/debug.Stack()
func (frame *StackFrame) String() string {
str := fmt.Sprintf("%s:%d (0x%x)\n", frame.File, frame.LineNumber, frame.ProgramCounter)
source, err := frame.SourceLine()
if err != nil {
return str
}
return str + fmt.Sprintf("\t%s: %s\n", frame.Name, source)
}
// SourceLine gets the line of code (from File and Line) of the original source if possible.
func (frame *StackFrame) SourceLine() (string, error) {
data, err := ioutil.ReadFile(frame.File)
if err != nil {
return "", New(err)
}
lines := bytes.Split(data, []byte{'\n'})
if frame.LineNumber <= 0 || frame.LineNumber >= len(lines) {
return "???", nil
}
// -1 because line-numbers are 1 based, but our array is 0 based
return string(bytes.Trim(lines[frame.LineNumber-1], " \t")), nil
}
func packageAndName(fn *runtime.Func) (string, string) {
name := fn.Name()
pkg := ""
// The name includes the path name to the package, which is unnecessary
// since the file name is already included. Plus, it has center dots.
// That is, we see
// runtime/debug.*T·ptrmethod
// and want
// *T.ptrmethod
// Since the package path might contains dots (e.g. code.google.com/...),
// we first remove the path prefix if there is one.
if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
pkg += name[:lastslash] + "/"
name = name[lastslash+1:]
}
if period := strings.Index(name, "."); period >= 0 {
pkg += name[:period]
name = name[period+1:]
}
name = strings.Replace(name, "·", ".", -1)
return pkg, name
}

21
vendor/github.com/labbsr0x/bindman-dns-webhook/LICENSE generated vendored Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Labbs
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.

View file

@ -0,0 +1,113 @@
package client
import (
"encoding/json"
"errors"
"fmt"
"github.com/labbsr0x/bindman-dns-webhook/src/types"
"github.com/labbsr0x/goh/gohclient"
"net/http"
"strings"
)
const recordsPath = "/records"
// DNSWebhookClient defines the basic structure of a DNS Listener
type DNSWebhookClient struct {
ClientAPI gohclient.API
}
// New builds the client to communicate with the dns manager
func New(managerAddress string, httpClient *http.Client) (*DNSWebhookClient, error) {
if strings.TrimSpace(managerAddress) == "" {
return nil, errors.New("managerAddress parameter must be a non-empty string")
}
client, err := gohclient.New(httpClient, managerAddress)
if err != nil {
return nil, err
}
client.Accept = "application/json"
client.ContentType = "application/json"
client.UserAgent = "bindman-dns-webhook-client"
return &DNSWebhookClient{
ClientAPI: client,
}, nil
}
// GetRecords communicates with the dns manager and gets the DNS Records
func (l *DNSWebhookClient) GetRecords() (result []types.DNSRecord, err error) {
resp, data, err := l.ClientAPI.Get(recordsPath)
if err != nil {
return
}
if resp.StatusCode == http.StatusOK {
err = json.Unmarshal(data, &result)
} else {
err = parseResponseBodyToError(data)
}
return
}
// GetRecord communicates with the dns manager and gets a DNS Record
func (l *DNSWebhookClient) GetRecord(name, recordType string) (result types.DNSRecord, err error) {
resp, data, err := l.ClientAPI.Get(fmt.Sprintf(recordsPath+"/%s/%s", name, recordType))
if err != nil {
return
}
if resp.StatusCode == http.StatusOK {
err = json.Unmarshal(data, &result)
} else {
err = parseResponseBodyToError(data)
}
return
}
// AddRecord adds a DNS record
func (l *DNSWebhookClient) AddRecord(name string, recordType string, value string) error {
return l.addOrUpdateRecord(&types.DNSRecord{Value: value, Name: name, Type: recordType}, l.ClientAPI.Post)
}
// UpdateRecord is a function that calls the defined webhook to update a specific dns record
func (l *DNSWebhookClient) UpdateRecord(record *types.DNSRecord) error {
return l.addOrUpdateRecord(record, l.ClientAPI.Put)
}
// addOrUpdateRecord .
func (l *DNSWebhookClient) addOrUpdateRecord(record *types.DNSRecord, action func(url string, body []byte) (*http.Response, []byte, error)) error {
if errs := record.Check(); errs != nil {
return fmt.Errorf("invalid DNS Record: %v", strings.Join(errs, ", "))
}
mr, err := json.Marshal(record)
if err != nil {
return err
}
resp, data, err := action(recordsPath, mr)
if err != nil {
return err
}
if resp.StatusCode != http.StatusNoContent {
return parseResponseBodyToError(data)
}
return nil
}
// RemoveRecord is a function that calls the defined webhook to remove a specific dns record
func (l *DNSWebhookClient) RemoveRecord(name, recordType string) error {
resp, data, err := l.ClientAPI.Delete(fmt.Sprintf(recordsPath+"/%s/%s", name, recordType))
if err != nil {
return err
}
if resp.StatusCode != http.StatusNoContent {
return parseResponseBodyToError(data)
}
return err
}
func parseResponseBodyToError(data []byte) error {
var err types.Error
if errUnmarshal := json.Unmarshal(data, &err); errUnmarshal != nil {
return errUnmarshal
}
return &err
}

View file

@ -0,0 +1,20 @@
package types
// DNSManager defines the operations a DNS Manager provider should implement
type DNSManager interface {
// GetDNSRecords retrieves all the dns records being managed
GetDNSRecords() ([]DNSRecord, error)
// GetDNSRecord retrieves the dns record identified by name
GetDNSRecord(name, recordType string) (*DNSRecord, error)
// RemoveDNSRecord removes a DNS record
RemoveDNSRecord(name, recordType string) error
// AddDNSRecord adds a new DNS record
AddDNSRecord(record DNSRecord) error
// UpdateDNSRecord updates an existing DNS record
UpdateDNSRecord(record DNSRecord) error
}

View file

@ -0,0 +1,40 @@
package types
import (
"fmt"
"strings"
"github.com/sirupsen/logrus"
)
// DNSRecord defines what we understand as a DNSRecord
type DNSRecord struct {
// Name the DNS host name
Name string `json:"name"`
// Value the value of this record
Value string `json:"value"`
// Type the record type
Type string `json:"type"`
}
// Check verifies if the DNS record satisfies certain conditions
func (record *DNSRecord) Check() []string {
logrus.Infof("Record to check: '%v'", record)
emptyValueErrorMessage := "the value of field '%s' cannot be empty"
var errs []string
if strings.TrimSpace(record.Name) == "" {
errs = append(errs, fmt.Sprintf(emptyValueErrorMessage, "name"))
}
if strings.TrimSpace(record.Value) == "" {
errs = append(errs, fmt.Sprintf(emptyValueErrorMessage, "value"))
}
if strings.TrimSpace(record.Type) == "" {
errs = append(errs, fmt.Sprintf(emptyValueErrorMessage, "type"))
}
return errs
}

View file

@ -0,0 +1,51 @@
package types
import (
"fmt"
"github.com/sirupsen/logrus"
"net/http"
)
// Error groups together information that defines an error. Should always be used to
type Error struct {
Message string `json:"message"`
Code int `json:"code"`
Details []string `json:"details,omitempty"`
Err error `json:"-"`
}
// Error() gives a string representing the error; also, forces the Error type to comply with the error interface
func (e *Error) Error() string {
msg := fmt.Sprintf("ERROR (%v): %s; \n Inner error: %s", e.Code, e.Message, e.Err)
logrus.Debug(msg)
return msg
}
// BadRequestError create an Error instance with http.StatusBadRequest code
func BadRequestError(message string, err error, details ...string) *Error {
return &Error{Message: message, Err: err, Code: http.StatusBadRequest, Details: details}
}
// BadRequestError create an Error instance with http.StatusNotFound code
func NotFoundError(message string, err error, details ...string) *Error {
return &Error{Message: message, Err: err, Code: http.StatusNotFound, Details: details}
}
// BadRequestError create an Error instance with http.StatusInternalServerError code
func InternalServerError(message string, err error, details ...string) *Error {
return &Error{Message: message, Err: err, Code: http.StatusInternalServerError, Details: details}
}
// PanicIfError is just a wrapper to a panic call that propagates error when it's not nil
func PanicIfError(e error) {
if e != nil {
logrus.Errorf(e.Error())
panic(e)
}
}
// Panic wraps a panic call propagating the given error parameter
func Panic(e Error) {
logrus.Errorf(e.Error())
panic(e)
}

21
vendor/github.com/labbsr0x/goh/LICENSE generated vendored Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Abilio Esteves
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.

114
vendor/github.com/labbsr0x/goh/gohclient/gohclient.go generated vendored Normal file
View file

@ -0,0 +1,114 @@
package gohclient
import (
"bytes"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
"github.com/go-errors/errors"
"github.com/sirupsen/logrus"
)
// API defines an interface for helper methods that encapsulates http requests complexities
type API interface {
Put(url string, data []byte) (*http.Response, []byte, error)
Post(url string, data []byte) (*http.Response, []byte, error)
Get(url string) (*http.Response, []byte, error)
Delete(url string) (*http.Response, []byte, error)
}
// Default defines a struct that handles with HTTP requests for a bindman webhook client
type Default struct {
// User agent used when communicating with the API
UserAgent string
// Request content type used when communicating with the API
ContentType string
Accept string
BaseURL *url.URL
HTTPClient *http.Client
}
// New instantiates a default goh client
// If a nil httpClient is provided, http.DefaultClient will be used.
func New(httpClient *http.Client, baseURL string) (*Default, error) {
if httpClient == nil {
httpClient = http.DefaultClient
}
if strings.TrimSpace(baseURL) == "" {
return nil, errors.New("base URL cannot be an empty string")
}
parsedURL, err := url.Parse(baseURL)
if err != nil {
return nil, err
}
return &Default{
BaseURL: parsedURL,
HTTPClient: httpClient,
}, nil
}
// Put wraps the call to http.NewRequest apis and properly submits a new HTTP POST request
func (c *Default) Put(path string, data []byte) (*http.Response, []byte, error) {
return c.request(path, "PUT", data)
}
// Post wraps the call to http.NewRequest apis and properly submits a new HTTP POST request
func (c *Default) Post(path string, data []byte) (*http.Response, []byte, error) {
return c.request(path, "POST", data)
}
// Get wraps the call to http.NewRequest apis and properly submits a new HTTP GET request
func (c *Default) Get(path string) (*http.Response, []byte, error) {
return c.request(path, "GET", nil)
}
// Delete wraps the call to http.NewRequest apis and properly submits a new HTTP DELETE request
func (c *Default) Delete(path string) (*http.Response, []byte, error) {
return c.request(path, "DELETE", nil)
}
// request defines a generic method to execute http requests
func (c *Default) request(path, method string, body []byte) (resp *http.Response, data []byte, err error) {
u, err := c.BaseURL.Parse(path)
if err != nil {
return
}
req, err := http.NewRequest(method, u.String(), bytes.NewBuffer(body))
if err != nil {
logrus.Errorf("HTTP request creation failed. err=%v", err)
return
}
if body != nil && strings.TrimSpace(c.ContentType) != "" {
req.Header.Set("Content-Type", c.ContentType)
}
if strings.TrimSpace(c.Accept) != "" {
req.Header.Set("Accept", c.Accept)
}
if strings.TrimSpace(c.UserAgent) != "" {
req.Header.Set("User-Agent", c.UserAgent)
}
logrus.Debugf("%v request=%v", method, req)
resp, err = c.HTTPClient.Do(req)
if err != nil {
logrus.Errorf("HTTP %v request invocation failed. err=%v", method, err)
return
}
defer dClose(resp.Body)
logrus.Debugf("Response: %v", resp)
data, err = ioutil.ReadAll(resp.Body)
logrus.Debugf("Response body: %v", data)
return
}
func dClose(c io.Closer) {
if err := c.Close(); err != nil {
logrus.Errorf("HTTP response body close invocation failed. err=%v", err)
}
}