WIP - tests working independently

Refactored to be able to use credentials from yaml file
Updated DynuClient
updated customDNSProviderConfig
This commit is contained in:
Gun Store 2020-11-26 00:23:05 +00:00
parent 23bbe9b3e7
commit 99d2af76be
9 changed files with 108 additions and 79 deletions

View file

@ -18,14 +18,14 @@ var httpClient *http.Client
// CreateDNSRecord ... Create a DNS Record and return it's ID
// POST https://api.dynu.com/v2/dns/{DNSID}/record
func (c *DynuClient) CreateDNSRecord(records DNSRecord) (int, error) {
dnsURL := fmt.Sprintf("%s/dns/%d/record", dynuAPI, c.DNSID)
dnsURL := fmt.Sprintf("%s/dns/%s/record", dynuAPI, c.DNSID)
body, err := json.Marshal(records)
if err != nil {
return -1, err
}
var resp *http.Response
resp, err = c.MakeRequest(dnsURL, "POST", bytes.NewReader(body))
resp, err = c.makeRequest(dnsURL, "POST", bytes.NewReader(body))
if err != nil {
return -1, err
}
@ -52,10 +52,10 @@ func (c *DynuClient) CreateDNSRecord(records DNSRecord) (int, error) {
// RemoveDNSRecord ... Removes a DNS record based on dnsRecordID
// DELETE https://api.dynu.com/v2/dns/{DNSID}/record/{DNSRecordID}
func (c *DynuClient) RemoveDNSRecord(DNSRecordID int) error {
dnsURL := fmt.Sprintf("%s/dns/%d/record/%d", dynuAPI, c.DNSID, DNSRecordID)
dnsURL := fmt.Sprintf("%s/dns/%s/record/%d", dynuAPI, c.DNSID, DNSRecordID)
var resp *http.Response
resp, err := c.MakeRequest(dnsURL, "DELETE", nil)
resp, err := c.makeRequest(dnsURL, "DELETE", nil)
if err != nil {
return err
}
@ -67,29 +67,27 @@ func (c *DynuClient) RemoveDNSRecord(DNSRecordID int) error {
return nil
}
// MakeRequest ...
func (c *DynuClient) MakeRequest(URL string, method string, body io.Reader) (*http.Response, error) {
func (c *DynuClient) makeRequest(URL string, method string, body io.Reader) (*http.Response, error) {
req, err := http.NewRequest(method, URL, body)
if err != nil {
return nil, err
}
req.Header["accept"] = []string{"application/json"}
req.Header["User-Agent"] = []string{"Mozilla/5.0 (X11; Linux x86_64; rv:82.0) Gecko/20100101 Firefox/82.0"} //c.UserAgent)
req.Header["User-Agent"] = []string{c.UserAgent}
req.Header["Content-Type"] = []string{"application/json"}
req.Header["API-Key"] = []string{c.APISecret}
req.Header["API-Key"] = []string{c.APIKey}
if c.HTTPClient == nil {
c.HTTPClient = &http.Client{}
}
c.getHTTPClient().Timeout = 30 * time.Second
c.HTTPClient.Timeout = 30 * time.Second
return c.getHTTPClient().Do(req)
return c.HTTPClient.Do(req)
}
// DecodeBytes ...
func (c *DynuClient) DecodeBytes(input []byte) (string, error) {
func (c *DynuClient) decodeBytes(input []byte) (string, error) {
buf := new(strings.Builder)
_, err := io.Copy(buf, bytes.NewBuffer(input))
@ -99,15 +97,3 @@ func (c *DynuClient) DecodeBytes(input []byte) (string, error) {
return buf.String(), nil
}
func (c *DynuClient) getHTTPClient() *http.Client {
if httpClient != nil {
return httpClient
}
if c.HTTPClient != nil {
httpClient = c.HTTPClient
} else {
httpClient = &http.Client{}
}
return httpClient
}

View file

@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"testing"
guntest "github.com/gstore/cert-manager-webhook-dynu/test"
@ -30,8 +31,8 @@ var i int
// }
func TestRemoveDNSRecord(t *testing.T) {
expectedMethod := "DELETE"
dnsID := 1
expectedURL := fmt.Sprintf("/v2/dns/%d/record/12345", dnsID)
dnsID := "1"
expectedURL := fmt.Sprintf("/v2/dns/%s/record/12345", dnsID)
testHandlerFunc := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
assert.Equal(t, expectedURL, req.URL.String(), "Should call %s but called %s", expectedURL, req.URL.String())
@ -85,8 +86,7 @@ func TestCreateDNSRecord(t *testing.T) {
client := &guntest.Testclient{}
httpClient, teardown := client.TestingHTTPClient(testHandlerFunc)
defer teardown()
dynu := DynuClient{HTTPClient: httpClient, DNSID: domainID}
dynu := DynuClient{HTTPClient: httpClient, DNSID: strconv.Itoa(domainID)}
recordID, err := dynu.CreateDNSRecord(rec)
if err != nil {
fmt.Println("an error occured: ", err.Error())
@ -109,7 +109,7 @@ func TestAddAndRemoveRecord(t *testing.T) {
State: true,
}
d := &DynuClient{DNSID: dnsID, APISecret: apiKey}
d := &DynuClient{DNSID: strconv.Itoa(dnsID), APIKey: apiKey}
dnsrecordid, err := d.CreateDNSRecord(rec)
assert.NoError(t, err)

View file

@ -31,8 +31,14 @@ type DNSResponse struct {
// DynuClient ... options for DynuClient
type DynuClient struct {
HTTPClient *http.Client
DNSID int
DNSID string
DNSRecordID int
UserAgent string
APISecret string
APIKey string
}
// DynuCreds - Details required to access API
type DynuCreds struct {
APIKey string
DNSID string
}

2
go.mod
View file

@ -6,6 +6,8 @@ require (
github.com/jetstack/cert-manager v1.0.4
github.com/stretchr/testify v1.6.1
gitlab.com/smueller18/cert-manager-webhook-inwx v0.3.0
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect
k8s.io/apiextensions-apiserver v0.19.0
k8s.io/apimachinery v0.19.0
k8s.io/client-go v0.19.0
)

9
go.sum
View file

@ -65,6 +65,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/blang/semver v3.5.0+incompatible h1:CGxCgetQ64DKk7rdZ++Vfnb1+ogGNnB17OJKJXD2Cfs=
github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/bobappleyard/readline v0.0.0-20150707195538-7e300e02d38e h1:4G8AYOOwZdDWOiJR6D6JXaFmj5BDS7c5D5PyqsG/+Hg=
github.com/bobappleyard/readline v0.0.0-20150707195538-7e300e02d38e/go.mod h1:fmqtV+Wqx0uFYLN1F4VhjZdtT56Dr8c3yA7nALFsw/Q=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
@ -352,6 +354,8 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGi
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/koyachi/go-term-ansicolor v0.0.0-20130114081603-6f81280f9360 h1:RAav20p/CzLXPs8DxPry4eM4YuTooJ1d6ctCmXjpaA8=
github.com/koyachi/go-term-ansicolor v0.0.0-20130114081603-6f81280f9360/go.mod h1:zUllqwUpvIS0pyapVJDiHwwEkmaV9qeCR9+sh5MPdRs=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@ -407,6 +411,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nathany/looper v0.3.3 h1:WRMKUgJt4jeU44DrMkeuyM/ueBrt/PfXuxx3lAGp7h8=
github.com/nathany/looper v0.3.3/go.mod h1:lAOncmeiijTmAX17hI41yptNBp880ShD1nObDNEaUm0=
github.com/nrdcg/goinwx v0.6.1/go.mod h1:XPiut7enlbEdntAqalBIqcYcTEVhpv/dKWgDCX2SwKQ=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
@ -677,6 +683,7 @@ golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -689,6 +696,8 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4 h1:5/PjkGUjvEU5Gl6BxmvKRPpqo2uNMv4rcHBMwzk/st8=
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

95
main.go
View file

@ -1,6 +1,7 @@
package main
import (
"context"
"encoding/json"
"fmt"
"net/http"
@ -16,8 +17,12 @@ import (
// "github.com/jetstack/cert-manager/pkg/issuer/acme/dns/util"
"github.com/gstore/cert-manager-webhook-dynu/dynuclient"
certmgrv1 "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const acmeNode = "ACME node"
var (
dnsRecordID int
)
@ -77,12 +82,11 @@ type customDNSProviderConfig struct {
//Email string `json:"email"`
// APIKeySecretRef v1alpha1.SecretKeySelector `json:"apiKeySecretRef"`
APIKey string `json:"apiKey"`
DomainID string `json:"domainId"`
BaseURL string `json:"baseURL"`
EndPoint string `json:"endPoint"`
Production bool `json:"production"`
TTL int `json:"ttl"`
APIKey string `json:"apiKey"`
DomainID string `json:"domainId"`
TTL int `json:"ttl"`
APIKeySecretKeyRef certmgrv1.SecretKeySelector `json:"apikeySecretKeyRef"`
DomainIDKeyRef certmgrv1.SecretKeySelector `json:"domainidSecretKeyRef"`
}
// Name is used as the name for this DNS solver when referencing it on the ACME
@ -101,27 +105,23 @@ func (c *customDNSProviderSolver) Name() string {
// cert-manager itself will later perform a self check to ensure that the
// solver has correctly configured the DNS provider.
func (c *customDNSProviderSolver) Present(ch *v1alpha1.ChallengeRequest) error {
fmt.Println("before config: ", ch)
cfg, err := loadConfig(ch.Config)
dynu, cfg, err := c.NewDynuClient(ch)
if err != nil {
return err
}
fmt.Println("cfg: ", cfg)
rec := dynuclient.DNSRecord{
NodeName: "asgard",
NodeName: acmeNode,
RecordType: "TXT",
TextData: "some text",
TTL: "90",
TextData: ch.Key,
TTL: strconv.Itoa(cfg.TTL),
}
domainID, err := strconv.Atoi(cfg.DomainID)
dynu := &dynuclient.DynuClient{DNSID: domainID, APISecret: cfg.APIKey, HTTPClient: c.httpClient}
// &dynuclient.DynuClient{DNSID: cfg.DomainID, APIKey: cfg.APIKey, HTTPClient: c.httpClient}
dnsRecordID, err = dynu.CreateDNSRecord(rec)
if err != nil {
return err
}
return nil
}
@ -132,11 +132,10 @@ func (c *customDNSProviderSolver) Present(ch *v1alpha1.ChallengeRequest) error {
// This is in order to facilitate multiple DNS validations for the same domain
// concurrently.
func (c *customDNSProviderSolver) CleanUp(ch *v1alpha1.ChallengeRequest) error {
fmt.Println("before config: ", ch)
cfg, err := loadConfig(ch.Config)
fmt.Println("cfg: ", cfg)
domainID, err := strconv.Atoi(cfg.DomainID)
dynu := &dynuclient.DynuClient{DNSID: domainID, APISecret: cfg.APIKey, HTTPClient: c.httpClient}
dynu, _, err := c.NewDynuClient(ch)
if err != nil {
return err
}
err = dynu.RemoveDNSRecord(dnsRecordID)
if err != nil {
return err
@ -156,12 +155,10 @@ func (c *customDNSProviderSolver) CleanUp(ch *v1alpha1.ChallengeRequest) error {
func (c *customDNSProviderSolver) Initialize(kubeClientConfig *rest.Config, stopCh <-chan struct{}) error {
///// UNCOMMENT THE BELOW CODE TO MAKE A KUBERNETES CLIENTSET AVAILABLE TO
///// YOUR CUSTOM DNS PROVIDER
fmt.Println("init")
cl, err := kubernetes.NewForConfig(kubeClientConfig)
if err != nil {
return err
}
fmt.Println("init:", cl)
c.client = *cl
///// END OF CODE TO MAKE KUBERNETES CLIENTSET AVAILABLEuri := cfg.BaseURL + cfg.DomainId + "/" + cfg.EndPoint
@ -182,3 +179,55 @@ func loadConfig(cfgJSON *extapi.JSON) (customDNSProviderConfig, error) {
return cfg, nil
}
func (c *customDNSProviderSolver) getCredentials(config *customDNSProviderConfig, ns string) (*dynuclient.DynuCreds, error) {
creds := dynuclient.DynuCreds{}
if config.APIKey != "" {
creds.APIKey = config.APIKey
} else {
secret, err := c.client.CoreV1().Secrets(ns).Get(context.Background(), config.APIKeySecretKeyRef.Name, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("failed to load secret %q", ns+"/"+config.APIKeySecretKeyRef.Name)
}
if apikey, ok := secret.Data[config.APIKeySecretKeyRef.Key]; ok {
creds.APIKey = string(apikey)
} else {
return nil, fmt.Errorf("no key %q in secret %q", config.APIKeySecretKeyRef, ns+"/"+config.APIKeySecretKeyRef.Name)
}
}
if config.DomainID != "" {
creds.DNSID = config.DomainID
} else {
secret, err := c.client.CoreV1().Secrets(ns).Get(context.Background(), config.DomainIDKeyRef.Name, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("failed to load secret %q", ns+"/"+config.DomainIDKeyRef.Name)
}
if password, ok := secret.Data[config.DomainIDKeyRef.Key]; ok {
creds.DNSID = string(password)
} else {
return nil, fmt.Errorf("no key %q in secret %q", config.DomainIDKeyRef, ns+"/"+config.DomainIDKeyRef.Name)
}
}
return &creds, nil
}
// NewDynuClient - Create a new DynuClient
func (c *customDNSProviderSolver) NewDynuClient(ch *v1alpha1.ChallengeRequest) (*dynuclient.DynuClient, *customDNSProviderConfig, error) {
cfg, err := loadConfig(ch.Config)
if err != nil {
return nil, &cfg, err
}
creds, err := c.getCredentials(&cfg, ch.ResourceNamespace)
if err != nil {
return nil, &cfg, fmt.Errorf("error getting credentials: %v", err)
}
client := *&dynuclient.DynuClient{DNSID: creds.DNSID, APIKey: creds.APIKey, HTTPClient: c.httpClient}
return &client, &cfg, nil
}

View file

@ -28,23 +28,7 @@ var (
fqdn string
)
// RoundTripFunc .
type RoundTripFunc func(req *http.Request) *http.Response
// RoundTrip .
func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return f(req), nil
}
//NewTestClient returns *http.Client with Transport replaced to avoid making real calls
func NewTestClient(fn RoundTripFunc) *http.Client {
return &http.Client{
Transport: RoundTripFunc(fn),
}
}
func TestRunsSuite(t *testing.T) {
t.Skip()
// The manifest path should contain a file named config.json that is a
// snippet of valid configuration that should be included on the
// ChallengeRequest passed as part of the test cases.

View file

@ -1,8 +1,5 @@
{
"APIKey": "secretkey",
"DomainID": "123456",
"BaseURL": "https://api.dynu.com/v2/dns/",
"Endpoint": "record",
"Production": false,
"TTL": 600
}

View file

@ -3,9 +3,5 @@ kind: Secret
metadata:
name: dynu-credentials
data:
APIKey: "secretkey"
DomainID: "123456"
BaseURL: "https://api.dynu.com/v2/dns/"
Endpoint: "record"
Production: "false"
TTL: "600"
APIKeySecretKeyRef: ""
DomainIDKeyRef: ""