From cd7a52453f6be68ca0f90d8697692ce5ddf7b861 Mon Sep 17 00:00:00 2001 From: Joseph Hanson Date: Fri, 2 Jun 2023 07:12:49 -0500 Subject: [PATCH] Updating webhook from source repo. --- go.mod | 15 ++-- go.sum | 16 +++++ main.go | 214 +++++++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 212 insertions(+), 33 deletions(-) diff --git a/go.mod b/go.mod index 46fc085..8c08131 100644 --- a/go.mod +++ b/go.mod @@ -8,10 +8,13 @@ replace github.com/cert-manager/cert-manager => github.com/cert-manager/cert-man require ( github.com/cert-manager/cert-manager v1.11.0 + github.com/dnsimple/dnsimple-go v1.2.0 github.com/miekg/dns v1.1.50 - github.com/stretchr/testify v1.8.1 + github.com/stretchr/testify v1.8.2 k8s.io/apiextensions-apiserver v0.26.0 + k8s.io/apimachinery v0.26.0 k8s.io/client-go v0.26.0 + k8s.io/klog v0.2.0 ) require ( @@ -40,6 +43,7 @@ require ( github.com/google/cel-go v0.12.5 // indirect github.com/google/gnostic v0.6.9 // indirect github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect @@ -80,12 +84,12 @@ require ( go.uber.org/zap v1.24.0 // indirect golang.org/x/crypto v0.5.0 // indirect golang.org/x/mod v0.7.0 // indirect - golang.org/x/net v0.5.0 // indirect + golang.org/x/net v0.7.0 // indirect golang.org/x/oauth2 v0.4.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.4.0 // indirect - golang.org/x/term v0.4.0 // indirect - golang.org/x/text v0.6.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/term v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.4.0 // indirect google.golang.org/appengine v1.6.7 // indirect @@ -97,7 +101,6 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.26.0 // indirect - k8s.io/apimachinery v0.26.0 // indirect k8s.io/apiserver v0.26.0 // indirect k8s.io/component-base v0.26.0 // indirect k8s.io/klog/v2 v2.80.1 // indirect diff --git a/go.sum b/go.sum index 897093a..6d823f8 100644 --- a/go.sum +++ b/go.sum @@ -85,6 +85,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dnsimple/dnsimple-go v1.2.0 h1:ddTGyLVKly5HKb5L65AkLqFqwZlWo3WnR0BlFZlIddM= +github.com/dnsimple/dnsimple-go v1.2.0/go.mod h1:z/cs26v/eiRvUyXsHQBLd8lWF8+cD6GbmkPH84plM4U= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -184,11 +186,14 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -331,6 +336,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= @@ -466,6 +472,8 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -535,10 +543,14 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/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.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -549,6 +561,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -743,6 +757,8 @@ k8s.io/client-go v0.26.0 h1:lT1D3OfO+wIi9UFolCrifbjUUgu7CpLca0AD8ghRLI8= k8s.io/client-go v0.26.0/go.mod h1:I2Sh57A79EQsDmn7F7ASpmru1cceh3ocVT9KlX2jEZg= k8s.io/component-base v0.26.0 h1:0IkChOCohtDHttmKuz+EP3j3+qKmV55rM9gIFTXA7Vs= k8s.io/component-base v0.26.0/go.mod h1:lqHwlfV1/haa14F/Z5Zizk5QmzaVf23nQzCwVOQpfC8= +k8s.io/klog v0.2.0 h1:0ElL0OHzF3N+OhoJTL0uca20SxtYt4X4+bzHeqrB83c= +k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kms v0.26.0 h1:5+GOQLvUajSd0z5ODF52RzB2rHo1HJUSYsVC3Ri3VgI= diff --git a/main.go b/main.go index 969e6d2..cfc6b77 100644 --- a/main.go +++ b/main.go @@ -1,15 +1,24 @@ package main import ( + "context" "encoding/json" "fmt" "os" + "strconv" + "strings" extapi "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/klog" + + "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" + "github.com/dnsimple/dnsimple-go/dnsimple" "github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1" "github.com/cert-manager/cert-manager/pkg/acme/webhook/cmd" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) var GroupName = os.Getenv("GROUP_NAME") @@ -25,25 +34,25 @@ func main() { // webhook, where the Name() method will be used to disambiguate between // the different implementations. cmd.RunWebhookServer(GroupName, - &customDNSProviderSolver{}, + &dnsimpleDNSProviderSolver{}, ) } -// customDNSProviderSolver implements the provider-specific logic needed to +// dnsimpleDNSProviderSolver implements the provider-specific logic needed to // 'present' an ACME challenge TXT record for your own DNS provider. // To do so, it must implement the `github.com/cert-manager/cert-manager/pkg/acme/webhook.Solver` // interface. -type customDNSProviderSolver struct { +type dnsimpleDNSProviderSolver struct { // If a Kubernetes 'clientset' is needed, you must: // 1. uncomment the additional `client` field in this structure below // 2. uncomment the "k8s.io/client-go/kubernetes" import at the top of the file // 3. uncomment the relevant code in the Initialize method below // 4. ensure your webhook's service account has the required RBAC role // assigned to it for interacting with the Kubernetes APIs you need. - //client kubernetes.Clientset + client *kubernetes.Clientset } -// customDNSProviderConfig is a structure that is used to decode into when +// dnsimpleDNSProviderConfig is a structure that is used to decode into when // solving a DNS01 challenge. // This information is provided by cert-manager, and may be a reference to // additional configuration that's needed to solve the challenge for this @@ -57,7 +66,7 @@ type customDNSProviderSolver struct { // You should not include sensitive information here. If credentials need to // be used by your provider here, you should reference a Kubernetes Secret // resource and fetch these credentials using a Kubernetes clientset. -type customDNSProviderConfig struct { +type dnsimpleDNSProviderConfig struct { // Change the two fields below according to the format of the configuration // to be decoded. // These fields will be set by users in the @@ -65,6 +74,7 @@ type customDNSProviderConfig struct { //Email string `json:"email"` //APIKeySecretRef v1alpha1.SecretKeySelector `json:"apiKeySecretRef"` + TokenSecretRef cmmeta.SecretKeySelector `json:"tokenSecretRef"` } // Name is used as the name for this DNS solver when referencing it on the ACME @@ -73,8 +83,105 @@ type customDNSProviderConfig struct { // solvers configured with the same Name() **so long as they do not co-exist // within a single webhook deployment**. // For example, `cloudflare` may be used as the name of a solver. -func (c *customDNSProviderSolver) Name() string { - return "my-custom-solver" +func (c *dnsimpleDNSProviderSolver) Name() string { + return "dnsimple" +} + +func (c *dnsimpleDNSProviderSolver) getClient(cfg *dnsimpleDNSProviderConfig, namespace string) (*dnsimple.Client, error) { + secretName := cfg.TokenSecretRef.LocalObjectReference.Name + klog.V(6).Infof("Try to load secret `%s` with key `%s`", secretName, cfg.TokenSecretRef.Key) + sec, err := c.client.CoreV1().Secrets(namespace).Get(context.Background(), secretName, metav1.GetOptions{}) + + if err != nil { + return nil, fmt.Errorf("unable to get secret `%s`; %v", secretName, err) + } + + secBytes, ok := sec.Data[cfg.TokenSecretRef.Key] + + if !ok { + return nil, fmt.Errorf("Key %q not found in secret \"%s/%s\"", cfg.TokenSecretRef.Key, cfg.TokenSecretRef.LocalObjectReference.Name, namespace) + } + + apiKey := string(secBytes) + + tc := dnsimple.StaticTokenHTTPClient(context.Background(), apiKey) + + // new client + client := dnsimple.NewClient(tc) + client.SetUserAgent("cert-manager-webhook-dnsimple") + return client, nil +} + +func (c *dnsimpleDNSProviderSolver) getDomainAndEntry(ch *v1alpha1.ChallengeRequest) (string, string) { + // Both ch.ResolvedZone and ch.ResolvedFQDN end with a dot: '.' + entry := strings.TrimSuffix(ch.ResolvedFQDN, ch.ResolvedZone) + entry = strings.TrimSuffix(entry, ".") + domain := strings.TrimSuffix(ch.ResolvedZone, ".") + return entry, domain +} + +func (c *dnsimpleDNSProviderSolver) getExistingRecord(cfg *dnsimpleDNSProviderConfig, client *dnsimple.Client, accountID string, zoneName string, entry string, key string) (*dnsimple.ZoneRecord, error) { + zone, err := client.Zones.GetZone(context.Background(), accountID, zoneName) + + if err != nil { + return nil, fmt.Errorf("unable to get zone: %s", err) + } + + // Look for existing TXT records. + records, err := client.Zones.ListRecords(context.Background(), accountID, zone.Data.Name, &dnsimple.ZoneRecordListOptions{Type: dnsimple.String("TXT"), Name: dnsimple.String(entry)}) + + if err != nil { + return nil, fmt.Errorf("unable to get resource records: %s", err) + } + + for _, record := range records.Data { + if record.Content == key { + return &record, nil + } + } + + return nil, nil +} + +func (c *dnsimpleDNSProviderSolver) updateRecord(cfg *dnsimpleDNSProviderConfig, client *dnsimple.Client, accountID string, record *dnsimple.ZoneRecord, key string) (*dnsimple.ZoneRecord, error) { + attributes := dnsimple.ZoneRecordAttributes{Content: key} + updatedRecord, err := client.Zones.UpdateRecord(context.Background(), accountID, record.ZoneID, record.ID, attributes) + + if err != nil { + return nil, fmt.Errorf("unable to update record: %s", err) + } + + return updatedRecord.Data, nil +} + +func (c *dnsimpleDNSProviderSolver) createRecord(cfg *dnsimpleDNSProviderConfig, client *dnsimple.Client, accountID string, entry *string, zoneName string, key string) (*dnsimple.ZoneRecord, error) { + attributes := dnsimple.ZoneRecordAttributes{Name: entry, Type: "TXT", Content: key, TTL: 60} + createdRecord, err := client.Zones.CreateRecord(context.Background(), accountID, zoneName, attributes) + + if err != nil { + return nil, fmt.Errorf("unable to create record: %s", err) + } + + return createdRecord.Data, nil +} + +func (c *dnsimpleDNSProviderSolver) deleteRecord(cfg *dnsimpleDNSProviderConfig, client *dnsimple.Client, accountID string, zoneName string, record *dnsimple.ZoneRecord) (*dnsimple.ZoneRecord, error) { + createdRecord, err := client.Zones.DeleteRecord(context.Background(), accountID, zoneName, record.ID) + + if err != nil { + return nil, fmt.Errorf("unable to delete record: %s", err) + } + + return createdRecord.Data, nil +} + +func Whoami(client *dnsimple.Client) (string, error) { + whoamiResponse, err := client.Identity.Whoami(context.Background()) + if err != nil { + return "", err + } + + return strconv.FormatInt(whoamiResponse.Data.Account.ID, 10), nil } // Present is responsible for actually presenting the DNS record with the @@ -82,16 +189,46 @@ func (c *customDNSProviderSolver) Name() string { // This method should tolerate being called multiple times with the same value. // 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 { +func (c *dnsimpleDNSProviderSolver) Present(ch *v1alpha1.ChallengeRequest) error { cfg, err := loadConfig(ch.Config) if err != nil { return err } - // TODO: do something more useful with the decoded configuration - fmt.Printf("Decoded configuration %v", cfg) + client, err := c.getClient(&cfg, ch.ResourceNamespace) + + if err != nil { + return fmt.Errorf("unable to get client: %s", err) + } + + accountID, err := Whoami(client) + if err != nil { + return fmt.Errorf("unable to fetch account ID: %s", err) + } + + entry, domain := c.getDomainAndEntry(ch) + klog.V(6).Infof("present for entry=%s, domain=%s", entry, domain) + + existingRecord, err := c.getExistingRecord(&cfg, client, accountID, domain, entry, ch.Key) + + if err != nil { + return fmt.Errorf("unable to find txt records: %s", err) + } + + if existingRecord != nil { + _, err = c.updateRecord(&cfg, client, accountID, existingRecord, ch.Key) + + if err != nil { + return fmt.Errorf("unable to update record: %s", err) + } + } else { + _, err = c.createRecord(&cfg, client, accountID, &entry, domain, ch.Key) + + if err != nil { + return fmt.Errorf("unable to create record: %s", err) + } + } - // TODO: add code that sets a record in the DNS provider's console return nil } @@ -101,8 +238,36 @@ func (c *customDNSProviderSolver) Present(ch *v1alpha1.ChallengeRequest) error { // value provided on the ChallengeRequest should be cleaned up. // This is in order to facilitate multiple DNS validations for the same domain // concurrently. -func (c *customDNSProviderSolver) CleanUp(ch *v1alpha1.ChallengeRequest) error { - // TODO: add code that deletes a record from the DNS provider's console +func (c *dnsimpleDNSProviderSolver) CleanUp(ch *v1alpha1.ChallengeRequest) error { + cfg, err := loadConfig(ch.Config) + if err != nil { + return err + } + + client, err := c.getClient(&cfg, ch.ResourceNamespace) + + if err != nil { + return fmt.Errorf("unable to get client: %s", err) + } + + accountID, err := Whoami(client) + if err != nil { + return fmt.Errorf("unable to fetch account ID: %s", err) + } + + entry, domain := c.getDomainAndEntry(ch) + klog.V(6).Infof("present for entry=%s, domain=%s", entry, domain) + + existingRecord, err := c.getExistingRecord(&cfg, client, accountID, domain, entry, ch.Key) + + if existingRecord != nil { + _, err = c.deleteRecord(&cfg, client, accountID, domain, existingRecord) + + if err != nil { + return fmt.Errorf("unable to delete record: %s", err) + } + } + return nil } @@ -115,25 +280,20 @@ func (c *customDNSProviderSolver) CleanUp(ch *v1alpha1.ChallengeRequest) error { // provider accounts. // The stopCh can be used to handle early termination of the webhook, in cases // where a SIGTERM or similar signal is sent to the webhook process. -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 +func (c *dnsimpleDNSProviderSolver) Initialize(kubeClientConfig *rest.Config, stopCh <-chan struct{}) error { + cl, err := kubernetes.NewForConfig(kubeClientConfig) + if err != nil { + return err + } - //cl, err := kubernetes.NewForConfig(kubeClientConfig) - //if err != nil { - // return err - //} - // - //c.client = cl - - ///// END OF CODE TO MAKE KUBERNETES CLIENTSET AVAILABLE + c.client = cl return nil } // loadConfig is a small helper function that decodes JSON configuration into // the typed config struct. -func loadConfig(cfgJSON *extapi.JSON) (customDNSProviderConfig, error) { - cfg := customDNSProviderConfig{} +func loadConfig(cfgJSON *extapi.JSON) (dnsimpleDNSProviderConfig, error) { + cfg := dnsimpleDNSProviderConfig{} // handle the 'base case' where no configuration has been provided if cfgJSON == nil { return cfg, nil