From e5f7364d8cbe2cf1643c8fc09a72e06fe43ea093 Mon Sep 17 00:00:00 2001 From: Aakash Date: Wed, 2 Oct 2024 22:28:04 -0700 Subject: [PATCH 01/37] task(fix): modify auth url --- internal/unifi/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/unifi/client.go b/internal/unifi/client.go index 6f1f934..c4c35c7 100644 --- a/internal/unifi/client.go +++ b/internal/unifi/client.go @@ -25,7 +25,7 @@ type httpClient struct { } const ( - unifiLoginPath = "%s/api/auth/login" + unifiLoginPath = "%s/api/login" unifiRecordPath = "%s/proxy/network/v2/api/site/%s/static-dns/%s" ) From 4f0254e90ef0b8e4bbc414d67ccd86e683c7f1c0 Mon Sep 17 00:00:00 2001 From: Aakash Date: Wed, 2 Oct 2024 22:31:29 -0700 Subject: [PATCH 02/37] task(fix): fix workflow --- .github/workflows/release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index ee3ab5f..9764326 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -24,7 +24,7 @@ jobs: uses: docker/metadata-action@v5 with: images: | - ghcr.io/${{ github.repository }} + ghcr.io/aki-${{ github.repository }} tags: | type=semver,pattern={{version}},prefix=v type=semver,pattern={{major}}.{{minor}},prefix=v From e560d5672097140112835d42a8ad2b48ab48c85e Mon Sep 17 00:00:00 2001 From: aki263 Date: Wed, 2 Oct 2024 22:40:36 -0700 Subject: [PATCH 03/37] Update release.yaml Signed-off-by: aki263 --- .github/workflows/release.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 9764326..9e06fbd 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -3,6 +3,7 @@ name: Release on: pull_request: + workflow_dispatch: push: branches: ["main"] release: From 2db98268b4072b1b7aa4b345a21681804eaf7b28 Mon Sep 17 00:00:00 2001 From: aki263 Date: Wed, 2 Oct 2024 23:40:14 -0700 Subject: [PATCH 04/37] Update release.yaml Signed-off-by: aki263 --- .github/workflows/release.yaml | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 9e06fbd..b95f4c8 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -3,7 +3,6 @@ name: Release on: pull_request: - workflow_dispatch: push: branches: ["main"] release: @@ -11,7 +10,6 @@ on: jobs: build-image: - if: ${{ github.event.pull_request.head.repo.full_name == github.repository || github.event_name != 'pull_request' }} runs-on: ubuntu-latest permissions: contents: read @@ -25,7 +23,7 @@ jobs: uses: docker/metadata-action@v5 with: images: | - ghcr.io/aki-${{ github.repository }} + ghcr.io/${{ github.repository }} tags: | type=semver,pattern={{version}},prefix=v type=semver,pattern={{major}}.{{minor}},prefix=v @@ -48,6 +46,14 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Restore Docker cache + uses: actions/cache@v3 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + - name: Build and Push uses: docker/build-push-action@v6 with: @@ -60,3 +66,12 @@ jobs: build-args: | VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache + + - name: Save Docker cache + if: always() + uses: actions/cache@v3 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} From 576d1c1b15f36043f6527f5bd8fd3382cda6747f Mon Sep 17 00:00:00 2001 From: Aakash Date: Thu, 3 Oct 2024 00:01:50 -0700 Subject: [PATCH 05/37] task(fix): add logging --- internal/unifi/client.go | 404 ++++++++++++++++++++------------------- 1 file changed, 205 insertions(+), 199 deletions(-) diff --git a/internal/unifi/client.go b/internal/unifi/client.go index c4c35c7..6886157 100644 --- a/internal/unifi/client.go +++ b/internal/unifi/client.go @@ -1,241 +1,247 @@ package unifi import ( - "bytes" - "crypto/tls" - "encoding/json" - "fmt" - "io" - "net/http" - "net/http/cookiejar" - "net/url" - - "github.com/kashalls/external-dns-provider-unifi/cmd/webhook/init/log" - "golang.org/x/net/publicsuffix" - "sigs.k8s.io/external-dns/endpoint" - - "go.uber.org/zap" + "bytes" + "crypto/tls" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/cookiejar" + "net/url" + + "github.com/kashalls/external-dns-provider-unifi/cmd/webhook/init/log" + "golang.org/x/net/publicsuffix" + "sigs.k8s.io/external-dns/endpoint" + + "go.uber.org/zap" ) // httpClient is the DNS provider client. type httpClient struct { - *Config - *http.Client - csrf string + *Config + *http.Client + csrf string } const ( - unifiLoginPath = "%s/api/login" - unifiRecordPath = "%s/proxy/network/v2/api/site/%s/static-dns/%s" + unifiLoginPath = "%s/api/auth/login" + unifiRecordPath = "%s/proxy/network/v2/api/site/%s/static-dns/%s" ) // newUnifiClient creates a new DNS provider client and logs in to store cookies. func newUnifiClient(config *Config) (*httpClient, error) { - jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) - if err != nil { - return nil, err - } - - // Create the HTTP client - client := &httpClient{ - Config: config, - Client: &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: config.SkipTLSVerify}, - }, - Jar: jar, - }, - } - - if err := client.login(); err != nil { - return nil, err - } - - return client, nil + jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) + if err != nil { + return nil, err + } + + // Create the HTTP client + client := &httpClient{ + Config: config, + Client: &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: config.SkipTLSVerify}, + }, + Jar: jar, + }, + } + + if err := client.login(); err != nil { + return nil, err + } + + return client, nil } // login performs a login request to the UniFi controller. func (c *httpClient) login() error { - jsonBody, err := json.Marshal(Login{ - Username: c.Config.User, - Password: c.Config.Password, - Remember: true, - }) - if err != nil { - return err - } - - // Perform the login request - resp, err := c.doRequest( - http.MethodPost, - FormatUrl(unifiLoginPath, c.Config.Host), - bytes.NewBuffer(jsonBody), - ) - if err != nil { - return err - } - - defer resp.Body.Close() - - // Check if the login was successful - if resp.StatusCode != http.StatusOK { - respBody, _ := io.ReadAll(resp.Body) - log.Error("login failed", zap.String("status", resp.Status), zap.String("response", string(respBody))) - return fmt.Errorf("login failed: %s", resp.Status) - } - - // Retrieve CSRF token from the response headers - if csrf := resp.Header.Get("x-csrf-token"); csrf != "" { - c.csrf = resp.Header.Get("x-csrf-token") - } - - return nil + jsonBody, err := json.Marshal(Login{ + Username: c.Config.User, + Password: c.Config.Password, + Remember: true, + }) + if err != nil { + return err + } + + // Print request details + log.Debug(fmt.Sprintf("Request sent to URL: %s with params: %s", FormatUrl(unifiLoginPath, c.Config.Host), string(jsonBody))) + + // Perform the login request + resp, err := c.doRequest( + http.MethodPost, + FormatUrl(unifiLoginPath, c.Config.Host), + bytes.NewBuffer(jsonBody), + ) + if err != nil { + return err + } + + defer resp.Body.Close() + + // Check if the login was successful + if resp.StatusCode != http.StatusOK { + respBody, _ := io.ReadAll(resp.Body) + log.Error("login failed", zap.String("status", resp.Status), zap.String("response", string(respBody))) + return fmt.Errorf("login failed: %s", resp.Status) + } + + // Retrieve CSRF token from the response headers + if csrf := resp.Header.Get("x-csrf-token"); csrf != "" { + c.csrf = resp.Header.Get("x-csrf-token") + } + + // Print response details + log.Debug(fmt.Sprintf("Response received with status: %s", resp.Status)) + + return nil } // doRequest makes an HTTP request to the UniFi controller. func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Response, error) { - log.Debug(fmt.Sprintf("making %s request to %s", method, path)) - - req, err := http.NewRequest(method, path, body) - if err != nil { - return nil, err - } - - c.setHeaders(req) - - resp, err := c.Client.Do(req) - if err != nil { - return nil, err - } - - if csrf := resp.Header.Get("X-CSRF-Token"); csrf != "" { - c.csrf = csrf - } - - log.Debug(fmt.Sprintf("response code from %s request to %s: %d", method, path, resp.StatusCode)) - - // If the status code is 401, re-login and retry the request - if resp.StatusCode == http.StatusUnauthorized { - log.Debug("Received 401 Unauthorized, re-login required") - if err := c.login(); err != nil { - return nil, err - } - // Update the headers with new CSRF token - c.setHeaders(req) - // Retry the request - resp, err = c.Client.Do(req) - if err != nil { - return nil, err - } - } - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("%s request to %s was not successful: %d", method, path, resp.StatusCode) - } - - return resp, nil + log.Debug(fmt.Sprintf("making %s request to %s", method, path)) + + req, err := http.NewRequest(method, path, body) + if err != nil { + return nil, err + } + + c.setHeaders(req) + + resp, err := c.Client.Do(req) + if err != nil { + return nil, err + } + + if csrf := resp.Header.Get("X-CSRF-Token"); csrf != "" { + c.csrf = csrf + } + + log.Debug(fmt.Sprintf("response code from %s request to %s: %d", method, path, resp.StatusCode)) + + // If the status code is 401, re-login and retry the request + if resp.StatusCode == http.StatusUnauthorized { + log.Debug("Received 401 Unauthorized, re-login required") + if err := c.login(); err != nil { + return nil, err + } + // Update the headers with new CSRF token + c.setHeaders(req) + // Retry the request + resp, err = c.Client.Do(req) + if err != nil { + return nil, err + } + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s request to %s was not successful: %d", method, path, resp.StatusCode) + } + + return resp, nil } // GetEndpoints retrieves the list of DNS records from the UniFi controller. func (c *httpClient) GetEndpoints() ([]DNSRecord, error) { - resp, err := c.doRequest( - http.MethodGet, - FormatUrl(unifiRecordPath, c.Config.Host, c.Config.Site), - nil, - ) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var records []DNSRecord - if err = json.NewDecoder(resp.Body).Decode(&records); err != nil { - return nil, err - } - - log.Debug(fmt.Sprintf("retrieved records: %+v", records)) - - return records, nil + resp, err := c.doRequest( + http.MethodGet, + FormatUrl(unifiRecordPath, c.Config.Host, c.Config.Site), + nil, + ) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var records []DNSRecord + if err = json.NewDecoder(resp.Body).Decode(&records); err != nil { + return nil, err + } + + log.Debug(fmt.Sprintf("retrieved records: %+v", records)) + + return records, nil } // CreateEndpoint creates a new DNS record in the UniFi controller. func (c *httpClient) CreateEndpoint(endpoint *endpoint.Endpoint) (*DNSRecord, error) { - jsonBody, err := json.Marshal(DNSRecord{ - Enabled: true, - Key: endpoint.DNSName, - RecordType: endpoint.RecordType, - TTL: endpoint.RecordTTL, - Value: endpoint.Targets[0], - }) - if err != nil { - return nil, err - } - resp, err := c.doRequest( - http.MethodPost, - FormatUrl(unifiRecordPath, c.Config.Host, c.Config.Site), - bytes.NewReader(jsonBody), - ) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var record DNSRecord - if err = json.NewDecoder(resp.Body).Decode(&record); err != nil { - return nil, err - } - - log.Debug(fmt.Sprintf("created record: %+v", record)) - - return &record, nil + jsonBody, err := json.Marshal(DNSRecord{ + Enabled: true, + Key: endpoint.DNSName, + RecordType: endpoint.RecordType, + TTL: endpoint.RecordTTL, + Value: endpoint.Targets[0], + }) + if err != nil { + return nil, err + } + resp, err := c.doRequest( + http.MethodPost, + FormatUrl(unifiRecordPath, c.Config.Host, c.Config.Site), + bytes.NewReader(jsonBody), + ) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var record DNSRecord + if err = json.NewDecoder(resp.Body).Decode(&record); err != nil { + return nil, err + } + + log.Debug(fmt.Sprintf("created record: %+v", record)) + + return &record, nil } // DeleteEndpoint deletes a DNS record from the UniFi controller. func (c *httpClient) DeleteEndpoint(endpoint *endpoint.Endpoint) error { - lookup, err := c.lookupIdentifier(endpoint.DNSName, endpoint.RecordType) - if err != nil { - return err - } - - if _, err = c.doRequest( - http.MethodDelete, - FormatUrl(unifiRecordPath, c.Config.Host, c.Config.Site, lookup.ID), - nil, - ); err != nil { - return err - } - - return nil + lookup, err := c.lookupIdentifier(endpoint.DNSName, endpoint.RecordType) + if err != nil { + return err + } + + if _, err = c.doRequest( + http.MethodDelete, + FormatUrl(unifiRecordPath, c.Config.Host, c.Config.Site, lookup.ID), + nil, + ); err != nil { + return err + } + + return nil } // lookupIdentifier finds the ID of a DNS record in the UniFi controller. func (c *httpClient) lookupIdentifier(key, recordType string) (*DNSRecord, error) { - records, err := c.GetEndpoints() - if err != nil { - return nil, err - } - - for _, r := range records { - if r.Key == key && r.RecordType == recordType { - return &r, nil - } - } - - return nil, err + records, err := c.GetEndpoints() + if err != nil { + return nil, err + } + + for _, r := range records { + if r.Key == key && r.RecordType == recordType { + return &r, nil + } + } + + return nil, err } // setHeaders sets the headers for the HTTP request. func (c *httpClient) setHeaders(req *http.Request) { - // Add the saved CSRF header. - req.Header.Set("X-CSRF-Token", c.csrf) - req.Header.Add("Accept", "application/json") - req.Header.Add("Content-Type", "application/json; charset=utf-8") - - // Log the request URL and cookies - if c.Client.Jar != nil { - parsedURL, _ := url.Parse(req.URL.String()) - log.Debug(fmt.Sprintf("Requesting %s cookies: %d", req.URL, len(c.Client.Jar.Cookies(parsedURL)))) - } else { - log.Debug(fmt.Sprintf("Requesting %s", req.URL)) - } -} + // Add the saved CSRF header. + req.Header.Set("X-CSRF-Token", c.csrf) + req.Header.Add("Accept", "application/json") + req.Header.Add("Content-Type", "application/json; charset=utf-8") + + // Log the request URL and cookies + if c.Client.Jar != nil { + parsedURL, _ := url.Parse(req.URL.String()) + log.Debug(fmt.Sprintf("Requesting %s cookies: %d", req.URL, len(c.Client.Jar.Cookies(parsedURL)))) + } else { + log.Debug(fmt.Sprintf("Requesting %s", req.URL)) + } +} \ No newline at end of file From 767e7afa18ef62fc240d003976d9fdc5ace57c91 Mon Sep 17 00:00:00 2001 From: aki263 Date: Thu, 3 Oct 2024 00:08:11 -0700 Subject: [PATCH 06/37] task(add): add debug msg --- internal/unifi/client.go | 404 ++++++++++++++++++++------------------- 1 file changed, 205 insertions(+), 199 deletions(-) diff --git a/internal/unifi/client.go b/internal/unifi/client.go index c4c35c7..6886157 100644 --- a/internal/unifi/client.go +++ b/internal/unifi/client.go @@ -1,241 +1,247 @@ package unifi import ( - "bytes" - "crypto/tls" - "encoding/json" - "fmt" - "io" - "net/http" - "net/http/cookiejar" - "net/url" - - "github.com/kashalls/external-dns-provider-unifi/cmd/webhook/init/log" - "golang.org/x/net/publicsuffix" - "sigs.k8s.io/external-dns/endpoint" - - "go.uber.org/zap" + "bytes" + "crypto/tls" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/cookiejar" + "net/url" + + "github.com/kashalls/external-dns-provider-unifi/cmd/webhook/init/log" + "golang.org/x/net/publicsuffix" + "sigs.k8s.io/external-dns/endpoint" + + "go.uber.org/zap" ) // httpClient is the DNS provider client. type httpClient struct { - *Config - *http.Client - csrf string + *Config + *http.Client + csrf string } const ( - unifiLoginPath = "%s/api/login" - unifiRecordPath = "%s/proxy/network/v2/api/site/%s/static-dns/%s" + unifiLoginPath = "%s/api/auth/login" + unifiRecordPath = "%s/proxy/network/v2/api/site/%s/static-dns/%s" ) // newUnifiClient creates a new DNS provider client and logs in to store cookies. func newUnifiClient(config *Config) (*httpClient, error) { - jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) - if err != nil { - return nil, err - } - - // Create the HTTP client - client := &httpClient{ - Config: config, - Client: &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: config.SkipTLSVerify}, - }, - Jar: jar, - }, - } - - if err := client.login(); err != nil { - return nil, err - } - - return client, nil + jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) + if err != nil { + return nil, err + } + + // Create the HTTP client + client := &httpClient{ + Config: config, + Client: &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: config.SkipTLSVerify}, + }, + Jar: jar, + }, + } + + if err := client.login(); err != nil { + return nil, err + } + + return client, nil } // login performs a login request to the UniFi controller. func (c *httpClient) login() error { - jsonBody, err := json.Marshal(Login{ - Username: c.Config.User, - Password: c.Config.Password, - Remember: true, - }) - if err != nil { - return err - } - - // Perform the login request - resp, err := c.doRequest( - http.MethodPost, - FormatUrl(unifiLoginPath, c.Config.Host), - bytes.NewBuffer(jsonBody), - ) - if err != nil { - return err - } - - defer resp.Body.Close() - - // Check if the login was successful - if resp.StatusCode != http.StatusOK { - respBody, _ := io.ReadAll(resp.Body) - log.Error("login failed", zap.String("status", resp.Status), zap.String("response", string(respBody))) - return fmt.Errorf("login failed: %s", resp.Status) - } - - // Retrieve CSRF token from the response headers - if csrf := resp.Header.Get("x-csrf-token"); csrf != "" { - c.csrf = resp.Header.Get("x-csrf-token") - } - - return nil + jsonBody, err := json.Marshal(Login{ + Username: c.Config.User, + Password: c.Config.Password, + Remember: true, + }) + if err != nil { + return err + } + + // Print request details + log.Debug(fmt.Sprintf("Request sent to URL: %s with params: %s", FormatUrl(unifiLoginPath, c.Config.Host), string(jsonBody))) + + // Perform the login request + resp, err := c.doRequest( + http.MethodPost, + FormatUrl(unifiLoginPath, c.Config.Host), + bytes.NewBuffer(jsonBody), + ) + if err != nil { + return err + } + + defer resp.Body.Close() + + // Check if the login was successful + if resp.StatusCode != http.StatusOK { + respBody, _ := io.ReadAll(resp.Body) + log.Error("login failed", zap.String("status", resp.Status), zap.String("response", string(respBody))) + return fmt.Errorf("login failed: %s", resp.Status) + } + + // Retrieve CSRF token from the response headers + if csrf := resp.Header.Get("x-csrf-token"); csrf != "" { + c.csrf = resp.Header.Get("x-csrf-token") + } + + // Print response details + log.Debug(fmt.Sprintf("Response received with status: %s", resp.Status)) + + return nil } // doRequest makes an HTTP request to the UniFi controller. func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Response, error) { - log.Debug(fmt.Sprintf("making %s request to %s", method, path)) - - req, err := http.NewRequest(method, path, body) - if err != nil { - return nil, err - } - - c.setHeaders(req) - - resp, err := c.Client.Do(req) - if err != nil { - return nil, err - } - - if csrf := resp.Header.Get("X-CSRF-Token"); csrf != "" { - c.csrf = csrf - } - - log.Debug(fmt.Sprintf("response code from %s request to %s: %d", method, path, resp.StatusCode)) - - // If the status code is 401, re-login and retry the request - if resp.StatusCode == http.StatusUnauthorized { - log.Debug("Received 401 Unauthorized, re-login required") - if err := c.login(); err != nil { - return nil, err - } - // Update the headers with new CSRF token - c.setHeaders(req) - // Retry the request - resp, err = c.Client.Do(req) - if err != nil { - return nil, err - } - } - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("%s request to %s was not successful: %d", method, path, resp.StatusCode) - } - - return resp, nil + log.Debug(fmt.Sprintf("making %s request to %s", method, path)) + + req, err := http.NewRequest(method, path, body) + if err != nil { + return nil, err + } + + c.setHeaders(req) + + resp, err := c.Client.Do(req) + if err != nil { + return nil, err + } + + if csrf := resp.Header.Get("X-CSRF-Token"); csrf != "" { + c.csrf = csrf + } + + log.Debug(fmt.Sprintf("response code from %s request to %s: %d", method, path, resp.StatusCode)) + + // If the status code is 401, re-login and retry the request + if resp.StatusCode == http.StatusUnauthorized { + log.Debug("Received 401 Unauthorized, re-login required") + if err := c.login(); err != nil { + return nil, err + } + // Update the headers with new CSRF token + c.setHeaders(req) + // Retry the request + resp, err = c.Client.Do(req) + if err != nil { + return nil, err + } + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s request to %s was not successful: %d", method, path, resp.StatusCode) + } + + return resp, nil } // GetEndpoints retrieves the list of DNS records from the UniFi controller. func (c *httpClient) GetEndpoints() ([]DNSRecord, error) { - resp, err := c.doRequest( - http.MethodGet, - FormatUrl(unifiRecordPath, c.Config.Host, c.Config.Site), - nil, - ) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var records []DNSRecord - if err = json.NewDecoder(resp.Body).Decode(&records); err != nil { - return nil, err - } - - log.Debug(fmt.Sprintf("retrieved records: %+v", records)) - - return records, nil + resp, err := c.doRequest( + http.MethodGet, + FormatUrl(unifiRecordPath, c.Config.Host, c.Config.Site), + nil, + ) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var records []DNSRecord + if err = json.NewDecoder(resp.Body).Decode(&records); err != nil { + return nil, err + } + + log.Debug(fmt.Sprintf("retrieved records: %+v", records)) + + return records, nil } // CreateEndpoint creates a new DNS record in the UniFi controller. func (c *httpClient) CreateEndpoint(endpoint *endpoint.Endpoint) (*DNSRecord, error) { - jsonBody, err := json.Marshal(DNSRecord{ - Enabled: true, - Key: endpoint.DNSName, - RecordType: endpoint.RecordType, - TTL: endpoint.RecordTTL, - Value: endpoint.Targets[0], - }) - if err != nil { - return nil, err - } - resp, err := c.doRequest( - http.MethodPost, - FormatUrl(unifiRecordPath, c.Config.Host, c.Config.Site), - bytes.NewReader(jsonBody), - ) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var record DNSRecord - if err = json.NewDecoder(resp.Body).Decode(&record); err != nil { - return nil, err - } - - log.Debug(fmt.Sprintf("created record: %+v", record)) - - return &record, nil + jsonBody, err := json.Marshal(DNSRecord{ + Enabled: true, + Key: endpoint.DNSName, + RecordType: endpoint.RecordType, + TTL: endpoint.RecordTTL, + Value: endpoint.Targets[0], + }) + if err != nil { + return nil, err + } + resp, err := c.doRequest( + http.MethodPost, + FormatUrl(unifiRecordPath, c.Config.Host, c.Config.Site), + bytes.NewReader(jsonBody), + ) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var record DNSRecord + if err = json.NewDecoder(resp.Body).Decode(&record); err != nil { + return nil, err + } + + log.Debug(fmt.Sprintf("created record: %+v", record)) + + return &record, nil } // DeleteEndpoint deletes a DNS record from the UniFi controller. func (c *httpClient) DeleteEndpoint(endpoint *endpoint.Endpoint) error { - lookup, err := c.lookupIdentifier(endpoint.DNSName, endpoint.RecordType) - if err != nil { - return err - } - - if _, err = c.doRequest( - http.MethodDelete, - FormatUrl(unifiRecordPath, c.Config.Host, c.Config.Site, lookup.ID), - nil, - ); err != nil { - return err - } - - return nil + lookup, err := c.lookupIdentifier(endpoint.DNSName, endpoint.RecordType) + if err != nil { + return err + } + + if _, err = c.doRequest( + http.MethodDelete, + FormatUrl(unifiRecordPath, c.Config.Host, c.Config.Site, lookup.ID), + nil, + ); err != nil { + return err + } + + return nil } // lookupIdentifier finds the ID of a DNS record in the UniFi controller. func (c *httpClient) lookupIdentifier(key, recordType string) (*DNSRecord, error) { - records, err := c.GetEndpoints() - if err != nil { - return nil, err - } - - for _, r := range records { - if r.Key == key && r.RecordType == recordType { - return &r, nil - } - } - - return nil, err + records, err := c.GetEndpoints() + if err != nil { + return nil, err + } + + for _, r := range records { + if r.Key == key && r.RecordType == recordType { + return &r, nil + } + } + + return nil, err } // setHeaders sets the headers for the HTTP request. func (c *httpClient) setHeaders(req *http.Request) { - // Add the saved CSRF header. - req.Header.Set("X-CSRF-Token", c.csrf) - req.Header.Add("Accept", "application/json") - req.Header.Add("Content-Type", "application/json; charset=utf-8") - - // Log the request URL and cookies - if c.Client.Jar != nil { - parsedURL, _ := url.Parse(req.URL.String()) - log.Debug(fmt.Sprintf("Requesting %s cookies: %d", req.URL, len(c.Client.Jar.Cookies(parsedURL)))) - } else { - log.Debug(fmt.Sprintf("Requesting %s", req.URL)) - } -} + // Add the saved CSRF header. + req.Header.Set("X-CSRF-Token", c.csrf) + req.Header.Add("Accept", "application/json") + req.Header.Add("Content-Type", "application/json; charset=utf-8") + + // Log the request URL and cookies + if c.Client.Jar != nil { + parsedURL, _ := url.Parse(req.URL.String()) + log.Debug(fmt.Sprintf("Requesting %s cookies: %d", req.URL, len(c.Client.Jar.Cookies(parsedURL)))) + } else { + log.Debug(fmt.Sprintf("Requesting %s", req.URL)) + } +} \ No newline at end of file From 791ca2bbca5c9b527e57b084f1f0ccfe44373531 Mon Sep 17 00:00:00 2001 From: aki263 Date: Thu, 3 Oct 2024 00:21:36 -0700 Subject: [PATCH 07/37] task(fix): add more log --- internal/unifi/client.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/internal/unifi/client.go b/internal/unifi/client.go index 6886157..091b729 100644 --- a/internal/unifi/client.go +++ b/internal/unifi/client.go @@ -98,7 +98,6 @@ func (c *httpClient) login() error { return nil } -// doRequest makes an HTTP request to the UniFi controller. func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Response, error) { log.Debug(fmt.Sprintf("making %s request to %s", method, path)) @@ -109,11 +108,25 @@ func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Respo c.setHeaders(req) + // Log all request headers + for name, values := range req.Header { + for _, value := range values { + log.Debug(fmt.Sprintf("Request Header: %s: %s", name, value)) + } + } + resp, err := c.Client.Do(req) if err != nil { return nil, err } + // Log all response headers + for name, values := range resp.Header { + for _, value := range values { + log.Debug(fmt.Sprintf("Response Header: %s: %s", name, value)) + } + } + if csrf := resp.Header.Get("X-CSRF-Token"); csrf != "" { c.csrf = csrf } From 105018802a90ad9260cab377ea446aade8e6b0ec Mon Sep 17 00:00:00 2001 From: aki263 Date: Thu, 3 Oct 2024 00:32:51 -0700 Subject: [PATCH 08/37] task(fix): fix url --- internal/unifi/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/unifi/client.go b/internal/unifi/client.go index 091b729..cda2fd7 100644 --- a/internal/unifi/client.go +++ b/internal/unifi/client.go @@ -25,7 +25,7 @@ type httpClient struct { } const ( - unifiLoginPath = "%s/api/auth/login" + unifiLoginPath = "%s/api/login" unifiRecordPath = "%s/proxy/network/v2/api/site/%s/static-dns/%s" ) From 8f04b2ee93133497b766f4e4070cce86141baf91 Mon Sep 17 00:00:00 2001 From: aki263 Date: Thu, 3 Oct 2024 00:45:55 -0700 Subject: [PATCH 09/37] task(fix): add more debug --- internal/unifi/client.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/internal/unifi/client.go b/internal/unifi/client.go index cda2fd7..433ce0c 100644 --- a/internal/unifi/client.go +++ b/internal/unifi/client.go @@ -101,6 +101,13 @@ func (c *httpClient) login() error { func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Response, error) { log.Debug(fmt.Sprintf("making %s request to %s", method, path)) + // Convert body to bytes for logging and reuse + var bodyBytes []byte + if body != nil { + bodyBytes, _ = io.ReadAll(body) + body = bytes.NewReader(bodyBytes) + } + req, err := http.NewRequest(method, path, body) if err != nil { return nil, err @@ -115,6 +122,11 @@ func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Respo } } + // Log request body + if len(bodyBytes) > 0 { + log.Debug(fmt.Sprintf("Request Body: %s", string(bodyBytes))) + } + resp, err := c.Client.Do(req) if err != nil { return nil, err @@ -127,6 +139,11 @@ func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Respo } } + // Log response body + respBody, _ := io.ReadAll(resp.Body) + log.Debug(fmt.Sprintf("Response Body: %s", string(respBody))) + resp.Body = io.NopCloser(bytes.NewBuffer(respBody)) // Restore the response body for further use + if csrf := resp.Header.Get("X-CSRF-Token"); csrf != "" { c.csrf = csrf } @@ -154,7 +171,6 @@ func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Respo return resp, nil } - // GetEndpoints retrieves the list of DNS records from the UniFi controller. func (c *httpClient) GetEndpoints() ([]DNSRecord, error) { resp, err := c.doRequest( From 1869c0dabca6eb977b4f084e2a523bc163d1c509 Mon Sep 17 00:00:00 2001 From: aki263 Date: Thu, 3 Oct 2024 01:07:46 -0700 Subject: [PATCH 10/37] task(fix): add more debug --- internal/unifi/client.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/internal/unifi/client.go b/internal/unifi/client.go index 433ce0c..f19c5d1 100644 --- a/internal/unifi/client.go +++ b/internal/unifi/client.go @@ -112,7 +112,17 @@ func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Respo if err != nil { return nil, err } - + // Set the required headers + req.Header.Set("Content-Type", "application/json; charset=utf-8") + if body != nil { + req.Header.Set("Content-Length", fmt.Sprintf("%d", len(bodyBytes))) + } + // Dynamically set the Host header + parsedURL, err := url.Parse(path) + if err != nil { + return nil, err + } + req.Host = parsedURL.Host c.setHeaders(req) // Log all request headers From 1cc866c693820e7fdc17e5271015f487c3dccb86 Mon Sep 17 00:00:00 2001 From: aki263 Date: Thu, 3 Oct 2024 01:20:11 -0700 Subject: [PATCH 11/37] task(fix): remove double --- internal/unifi/client.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/unifi/client.go b/internal/unifi/client.go index f19c5d1..1b5e241 100644 --- a/internal/unifi/client.go +++ b/internal/unifi/client.go @@ -113,7 +113,6 @@ func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Respo return nil, err } // Set the required headers - req.Header.Set("Content-Type", "application/json; charset=utf-8") if body != nil { req.Header.Set("Content-Length", fmt.Sprintf("%d", len(bodyBytes))) } @@ -122,7 +121,10 @@ func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Respo if err != nil { return nil, err } + req.Host = parsedURL.Host + log.Debug(fmt.Sprintf("Host test AKI: %s", req.Host)) + c.setHeaders(req) // Log all request headers From e8b5bc5b3c66e3e17493a6d9403d86844f60191f Mon Sep 17 00:00:00 2001 From: Aakash Date: Tue, 15 Oct 2024 21:48:01 -0700 Subject: [PATCH 12/37] before change --- internal/unifi/client.go | 456 +++++++++++++++++++++------------------ internal/unifi/types.go | 11 +- 2 files changed, 256 insertions(+), 211 deletions(-) diff --git a/internal/unifi/client.go b/internal/unifi/client.go index 6886157..443bc4e 100644 --- a/internal/unifi/client.go +++ b/internal/unifi/client.go @@ -1,247 +1,291 @@ package unifi import ( - "bytes" - "crypto/tls" - "encoding/json" - "fmt" - "io" - "net/http" - "net/http/cookiejar" - "net/url" - - "github.com/kashalls/external-dns-provider-unifi/cmd/webhook/init/log" - "golang.org/x/net/publicsuffix" - "sigs.k8s.io/external-dns/endpoint" - - "go.uber.org/zap" + "bytes" + "crypto/tls" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/cookiejar" + "net/url" + + "github.com/kashalls/external-dns-provider-unifi/cmd/webhook/init/log" + "golang.org/x/net/publicsuffix" + "sigs.k8s.io/external-dns/endpoint" + + "go.uber.org/zap" ) // httpClient is the DNS provider client. type httpClient struct { - *Config - *http.Client - csrf string + *Config + *http.Client + csrf string } const ( - unifiLoginPath = "%s/api/auth/login" - unifiRecordPath = "%s/proxy/network/v2/api/site/%s/static-dns/%s" + unifiLoginPath = "%s/api/auth/login" + unifiRecordPath = "%s/proxy/network/v2/api/site/%s/static-dns/%s" ) // newUnifiClient creates a new DNS provider client and logs in to store cookies. func newUnifiClient(config *Config) (*httpClient, error) { - jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) - if err != nil { - return nil, err - } - - // Create the HTTP client - client := &httpClient{ - Config: config, - Client: &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: config.SkipTLSVerify}, - }, - Jar: jar, - }, - } - - if err := client.login(); err != nil { - return nil, err - } - - return client, nil + jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) + if err != nil { + return nil, err + } + + // Create the HTTP client + client := &httpClient{ + Config: config, + Client: &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: config.SkipTLSVerify}, + }, + Jar: jar, + }, + } + + if err := client.login(); err != nil { + return nil, err + } + + return client, nil } // login performs a login request to the UniFi controller. func (c *httpClient) login() error { - jsonBody, err := json.Marshal(Login{ - Username: c.Config.User, - Password: c.Config.Password, - Remember: true, - }) - if err != nil { - return err - } - - // Print request details - log.Debug(fmt.Sprintf("Request sent to URL: %s with params: %s", FormatUrl(unifiLoginPath, c.Config.Host), string(jsonBody))) - - // Perform the login request - resp, err := c.doRequest( - http.MethodPost, - FormatUrl(unifiLoginPath, c.Config.Host), - bytes.NewBuffer(jsonBody), - ) - if err != nil { - return err - } - - defer resp.Body.Close() - - // Check if the login was successful - if resp.StatusCode != http.StatusOK { - respBody, _ := io.ReadAll(resp.Body) - log.Error("login failed", zap.String("status", resp.Status), zap.String("response", string(respBody))) - return fmt.Errorf("login failed: %s", resp.Status) - } - - // Retrieve CSRF token from the response headers - if csrf := resp.Header.Get("x-csrf-token"); csrf != "" { - c.csrf = resp.Header.Get("x-csrf-token") - } - - // Print response details - log.Debug(fmt.Sprintf("Response received with status: %s", resp.Status)) - - return nil + jsonBody, err := json.Marshal(Login{ + Username: c.Config.User, + Password: c.Config.Password, + Remember: true, + }) + if err != nil { + return err + } + + // Print request details + log.Debug(fmt.Sprintf("Request sent to URL: %s with params: %s", FormatUrl(unifiLoginPath, c.Config.Host), string(jsonBody))) + + // Perform the login request + resp, err := c.doRequest( + http.MethodPost, + FormatUrl(unifiLoginPath, c.Config.Host), + bytes.NewBuffer(jsonBody), + ) + if err != nil { + return err + } + + defer resp.Body.Close() + + // Check if the login was successful + if resp.StatusCode != http.StatusOK { + respBody, _ := io.ReadAll(resp.Body) + log.Error("login failed", zap.String("status", resp.Status), zap.String("response", string(respBody))) + return fmt.Errorf("login failed: %s", resp.Status) + } + + // Retrieve CSRF token from the response headers + if csrf := resp.Header.Get("x-csrf-token"); csrf != "" { + c.csrf = resp.Header.Get("x-csrf-token") + } + + // Print response details + log.Debug(fmt.Sprintf("Response received with status: %s", resp.Status)) + + return nil } -// doRequest makes an HTTP request to the UniFi controller. func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Response, error) { - log.Debug(fmt.Sprintf("making %s request to %s", method, path)) - - req, err := http.NewRequest(method, path, body) - if err != nil { - return nil, err - } - - c.setHeaders(req) - - resp, err := c.Client.Do(req) - if err != nil { - return nil, err - } - - if csrf := resp.Header.Get("X-CSRF-Token"); csrf != "" { - c.csrf = csrf - } - - log.Debug(fmt.Sprintf("response code from %s request to %s: %d", method, path, resp.StatusCode)) - - // If the status code is 401, re-login and retry the request - if resp.StatusCode == http.StatusUnauthorized { - log.Debug("Received 401 Unauthorized, re-login required") - if err := c.login(); err != nil { - return nil, err - } - // Update the headers with new CSRF token - c.setHeaders(req) - // Retry the request - resp, err = c.Client.Do(req) - if err != nil { - return nil, err - } - } - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("%s request to %s was not successful: %d", method, path, resp.StatusCode) - } - - return resp, nil + log.Debug(fmt.Sprintf("making %s request to %s", method, path)) + + // Convert body to bytes for logging and reuse + var bodyBytes []byte + if body != nil { + bodyBytes, _ = io.ReadAll(body) + body = bytes.NewReader(bodyBytes) + } + + req, err := http.NewRequest(method, path, body) + if err != nil { + return nil, err + } + // Set the required headers + if body != nil { + req.Header.Set("Content-Length", fmt.Sprintf("%d", len(bodyBytes))) + } + // Dynamically set the Host header + parsedURL, err := url.Parse(path) + if err != nil { + return nil, err + } + req.Host = fmt.Sprintf("%s", parsedURL.Host) + // // req.Header.Set("Host", fmt.Sprintf("%s", req.Host)) + // req.Host = fmt.Sprintf("%s", parsedURL.Host) + + log.Debug(fmt.Sprintf("Host test AKI: %s", req.Host)) + + c.setHeaders(req) + + // Log all request headers + for name, values := range req.Header { + for _, value := range values { + log.Debug(fmt.Sprintf("Request Header: %s: %s", name, value)) + } + } + + // Log request body + if len(bodyBytes) > 0 { + log.Debug(fmt.Sprintf("Request Body: %s", string(bodyBytes))) + } + + resp, err := c.Client.Do(req) + if err != nil { + return nil, err + } + + // Log all response headers + for name, values := range resp.Header { + for _, value := range values { + log.Debug(fmt.Sprintf("Response Header: %s: %s", name, value)) + } + } + + // Log response body + respBody, _ := io.ReadAll(resp.Body) + log.Debug(fmt.Sprintf("Response Body: %s", string(respBody))) + resp.Body = io.NopCloser(bytes.NewBuffer(respBody)) // Restore the response body for further use + + if csrf := resp.Header.Get("X-CSRF-Token"); csrf != "" { + c.csrf = csrf + } + + log.Debug(fmt.Sprintf("response code from %s request to %s: %d", method, path, resp.StatusCode)) + + // If the status code is 401, re-login and retry the request + if resp.StatusCode == http.StatusUnauthorized { + log.Debug("Received 401 Unauthorized, re-login required") + if err := c.login(); err != nil { + return nil, err + } + // Update the headers with new CSRF token + c.setHeaders(req) + // Retry the request + resp, err = c.Client.Do(req) + if err != nil { + return nil, err + } + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s request to %s was not successful: %d", method, path, resp.StatusCode) + } + + return resp, nil } // GetEndpoints retrieves the list of DNS records from the UniFi controller. func (c *httpClient) GetEndpoints() ([]DNSRecord, error) { - resp, err := c.doRequest( - http.MethodGet, - FormatUrl(unifiRecordPath, c.Config.Host, c.Config.Site), - nil, - ) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var records []DNSRecord - if err = json.NewDecoder(resp.Body).Decode(&records); err != nil { - return nil, err - } - - log.Debug(fmt.Sprintf("retrieved records: %+v", records)) - - return records, nil + resp, err := c.doRequest( + http.MethodGet, + FormatUrl(unifiRecordPath, c.Config.Host, c.Config.Site), + nil, + ) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var records []DNSRecord + if err = json.NewDecoder(resp.Body).Decode(&records); err != nil { + return nil, err + } + + log.Debug(fmt.Sprintf("retrieved records: %+v", records)) + + return records, nil } // CreateEndpoint creates a new DNS record in the UniFi controller. func (c *httpClient) CreateEndpoint(endpoint *endpoint.Endpoint) (*DNSRecord, error) { - jsonBody, err := json.Marshal(DNSRecord{ - Enabled: true, - Key: endpoint.DNSName, - RecordType: endpoint.RecordType, - TTL: endpoint.RecordTTL, - Value: endpoint.Targets[0], - }) - if err != nil { - return nil, err - } - resp, err := c.doRequest( - http.MethodPost, - FormatUrl(unifiRecordPath, c.Config.Host, c.Config.Site), - bytes.NewReader(jsonBody), - ) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var record DNSRecord - if err = json.NewDecoder(resp.Body).Decode(&record); err != nil { - return nil, err - } - - log.Debug(fmt.Sprintf("created record: %+v", record)) - - return &record, nil + jsonBody, err := json.Marshal(DNSRecord{ + Enabled: true, + Key: endpoint.DNSName, + RecordType: endpoint.RecordType, + TTL: endpoint.RecordTTL, + Value: endpoint.Targets[0], + }) + if err != nil { + return nil, err + } + resp, err := c.doRequest( + http.MethodPost, + FormatUrl(unifiRecordPath, c.Config.Host, c.Config.Site), + bytes.NewReader(jsonBody), + ) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var record DNSRecord + if err = json.NewDecoder(resp.Body).Decode(&record); err != nil { + return nil, err + } + + log.Debug(fmt.Sprintf("created record: %+v", record)) + + return &record, nil } // DeleteEndpoint deletes a DNS record from the UniFi controller. func (c *httpClient) DeleteEndpoint(endpoint *endpoint.Endpoint) error { - lookup, err := c.lookupIdentifier(endpoint.DNSName, endpoint.RecordType) - if err != nil { - return err - } - - if _, err = c.doRequest( - http.MethodDelete, - FormatUrl(unifiRecordPath, c.Config.Host, c.Config.Site, lookup.ID), - nil, - ); err != nil { - return err - } - - return nil + lookup, err := c.lookupIdentifier(endpoint.DNSName, endpoint.RecordType) + if err != nil { + return err + } + + if _, err = c.doRequest( + http.MethodDelete, + FormatUrl(unifiRecordPath, c.Config.Host, c.Config.Site, lookup.ID), + nil, + ); err != nil { + return err + } + + return nil } // lookupIdentifier finds the ID of a DNS record in the UniFi controller. func (c *httpClient) lookupIdentifier(key, recordType string) (*DNSRecord, error) { - records, err := c.GetEndpoints() - if err != nil { - return nil, err - } - - for _, r := range records { - if r.Key == key && r.RecordType == recordType { - return &r, nil - } - } - - return nil, err + records, err := c.GetEndpoints() + if err != nil { + return nil, err + } + + for _, r := range records { + if r.Key == key && r.RecordType == recordType { + return &r, nil + } + } + + return nil, err } // setHeaders sets the headers for the HTTP request. func (c *httpClient) setHeaders(req *http.Request) { - // Add the saved CSRF header. - req.Header.Set("X-CSRF-Token", c.csrf) - req.Header.Add("Accept", "application/json") - req.Header.Add("Content-Type", "application/json; charset=utf-8") - - // Log the request URL and cookies - if c.Client.Jar != nil { - parsedURL, _ := url.Parse(req.URL.String()) - log.Debug(fmt.Sprintf("Requesting %s cookies: %d", req.URL, len(c.Client.Jar.Cookies(parsedURL)))) - } else { - log.Debug(fmt.Sprintf("Requesting %s", req.URL)) - } -} \ No newline at end of file + // Add the saved CSRF header. + req.Header.Set("X-CSRF-Token", c.csrf) + req.Header.Add("Accept", "application/json") + req.Header.Add("Content-Type", "application/json; charset=utf-8") + + // Log the request URL and cookies + if c.Client.Jar != nil { + parsedURL, _ := url.Parse(req.URL.String()) + log.Debug(fmt.Sprintf("Requesting %s cookies: %d", req.URL, len(c.Client.Jar.Cookies(parsedURL)))) + } else { + log.Debug(fmt.Sprintf("Requesting %s", req.URL)) + } +} diff --git a/internal/unifi/types.go b/internal/unifi/types.go index 391bc0b..ad76ba2 100644 --- a/internal/unifi/types.go +++ b/internal/unifi/types.go @@ -4,11 +4,12 @@ import "sigs.k8s.io/external-dns/endpoint" // Config represents the configuration for the UniFi API. type Config struct { - Host string `env:"UNIFI_HOST,notEmpty"` - User string `env:"UNIFI_USER,notEmpty"` - Password string `env:"UNIFI_PASS,notEmpty"` - Site string `env:"UNIFI_SITE" envDefault:"default"` - SkipTLSVerify bool `env:"UNIFI_SKIP_TLS_VERIFY" envDefault:"true"` + Host string `env:"UNIFI_HOST,notEmpty"` + User string `env:"UNIFI_USER,notEmpty"` + Password string `env:"UNIFI_PASS,notEmpty"` + Site string `env:"UNIFI_SITE" envDefault:"default"` + ControllerType string `env:"CONTRAOLLOER_TYPE" envDefault:"default"` + SkipTLSVerify bool `env:"UNIFI_SKIP_TLS_VERIFY" envDefault:"true"` } // Login represents a login request to the UniFi API. From e8d4eb236b762008af4829c33997d165634ca5af Mon Sep 17 00:00:00 2001 From: Aakash Date: Tue, 15 Oct 2024 22:37:04 -0700 Subject: [PATCH 13/37] added new code --- cmd/webhook/init/server/server.go | 2 +- internal/unifi/client.go | 153 ++++++++++++++++++++++-------- internal/unifi/types.go | 2 +- test.yaml | 42 ++++++++ 4 files changed, 159 insertions(+), 40 deletions(-) create mode 100644 test.yaml diff --git a/cmd/webhook/init/server/server.go b/cmd/webhook/init/server/server.go index eabb986..c041ebd 100644 --- a/cmd/webhook/init/server/server.go +++ b/cmd/webhook/init/server/server.go @@ -52,7 +52,7 @@ func Init(config configuration.Config, p *webhook.Webhook) (*http.Server, *http. healthRouter.Get("/healthz", HealthCheckHandler) healthRouter.Get("/readyz", ReadinessHandler) - healthServer := createHTTPServer("0.0.0.0:8080", healthRouter, config.ServerReadTimeout, config.ServerWriteTimeout) + healthServer := createHTTPServer("0.0.0.0:8090", healthRouter, config.ServerReadTimeout, config.ServerWriteTimeout) go func() { log.Info("starting health server", zap.String("address", healthServer.Addr)) if err := healthServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { diff --git a/internal/unifi/client.go b/internal/unifi/client.go index 443bc4e..cd890bd 100644 --- a/internal/unifi/client.go +++ b/internal/unifi/client.go @@ -25,14 +25,19 @@ type httpClient struct { } const ( - unifiLoginPath = "%s/api/auth/login" - unifiRecordPath = "%s/proxy/network/v2/api/site/%s/static-dns/%s" + unifiLoginPathUDM = "%s/api/auth/login" + unifiLoginPathNetworkApp = "%s/api/login" + unifiRecordPathUDM = "%s/proxy/network/v2/api/site/%s/static-dns/%s" + unifiRecordPathNetworkApp = "%s/v2/api/site/%s/static-dns/%s" ) // newUnifiClient creates a new DNS provider client and logs in to store cookies. func newUnifiClient(config *Config) (*httpClient, error) { + log.Debug("Creating new UniFi client", zap.String("host", config.Host), zap.String("controllerType", config.ControllerType)) + jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) if err != nil { + log.Error("Failed to create cookie jar", zap.Error(err)) return nil, err } @@ -47,34 +52,47 @@ func newUnifiClient(config *Config) (*httpClient, error) { }, } + log.Debug("Attempting to login") if err := client.login(); err != nil { + log.Error("Failed to login", zap.Error(err)) return nil, err } + log.Debug("UniFi client created and logged in successfully") return client, nil } // login performs a login request to the UniFi controller. func (c *httpClient) login() error { + loginPath := unifiLoginPathUDM + if c.Config.ControllerType == "NETWORK_SERVER" { + loginPath = unifiLoginPathNetworkApp + } + log.Debug("Logging in", zap.String("loginPath", loginPath)) + jsonBody, err := json.Marshal(Login{ Username: c.Config.User, Password: c.Config.Password, Remember: true, }) if err != nil { + log.Error("Failed to marshal login JSON", zap.Error(err)) return err } // Print request details - log.Debug(fmt.Sprintf("Request sent to URL: %s with params: %s", FormatUrl(unifiLoginPath, c.Config.Host), string(jsonBody))) + log.Debug("Sending login request", + zap.String("URL", FormatUrl(loginPath, c.Config.Host)), + zap.String("body", string(jsonBody))) // Perform the login request resp, err := c.doRequest( http.MethodPost, - FormatUrl(unifiLoginPath, c.Config.Host), + FormatUrl(loginPath, c.Config.Host), bytes.NewBuffer(jsonBody), ) if err != nil { + log.Error("Login request failed", zap.Error(err)) return err } @@ -83,33 +101,38 @@ func (c *httpClient) login() error { // Check if the login was successful if resp.StatusCode != http.StatusOK { respBody, _ := io.ReadAll(resp.Body) - log.Error("login failed", zap.String("status", resp.Status), zap.String("response", string(respBody))) + log.Error("Login failed", + zap.String("status", resp.Status), + zap.String("response", string(respBody))) return fmt.Errorf("login failed: %s", resp.Status) } // Retrieve CSRF token from the response headers if csrf := resp.Header.Get("x-csrf-token"); csrf != "" { c.csrf = resp.Header.Get("x-csrf-token") + log.Debug("Retrieved CSRF token", zap.String("token", c.csrf)) + } else { + log.Debug("No CSRF token found in response headers") } - // Print response details - log.Debug(fmt.Sprintf("Response received with status: %s", resp.Status)) - + log.Debug("Login successful", zap.String("status", resp.Status)) return nil } func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Response, error) { - log.Debug(fmt.Sprintf("making %s request to %s", method, path)) + log.Debug("Making request", zap.String("method", method), zap.String("path", path)) // Convert body to bytes for logging and reuse var bodyBytes []byte if body != nil { bodyBytes, _ = io.ReadAll(body) body = bytes.NewReader(bodyBytes) + log.Debug("Request body", zap.String("body", string(bodyBytes))) } req, err := http.NewRequest(method, path, body) if err != nil { + log.Error("Failed to create request", zap.Error(err)) return nil, err } // Set the required headers @@ -119,67 +142,73 @@ func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Respo // Dynamically set the Host header parsedURL, err := url.Parse(path) if err != nil { + log.Error("Failed to parse URL", zap.Error(err)) return nil, err } - req.Host = fmt.Sprintf("%s", parsedURL.Host) - // // req.Header.Set("Host", fmt.Sprintf("%s", req.Host)) - // req.Host = fmt.Sprintf("%s", parsedURL.Host) + req.Host = parsedURL.Host - log.Debug(fmt.Sprintf("Host test AKI: %s", req.Host)) + log.Debug("Request host", zap.String("host", req.Host)) c.setHeaders(req) // Log all request headers for name, values := range req.Header { for _, value := range values { - log.Debug(fmt.Sprintf("Request Header: %s: %s", name, value)) + log.Debug("Request header", zap.String("name", name), zap.String("value", value)) } } - // Log request body - if len(bodyBytes) > 0 { - log.Debug(fmt.Sprintf("Request Body: %s", string(bodyBytes))) - } - resp, err := c.Client.Do(req) if err != nil { + log.Error("Request failed", zap.Error(err)) return nil, err } // Log all response headers for name, values := range resp.Header { for _, value := range values { - log.Debug(fmt.Sprintf("Response Header: %s: %s", name, value)) + log.Debug("Response header", zap.String("name", name), zap.String("value", value)) } } // Log response body respBody, _ := io.ReadAll(resp.Body) - log.Debug(fmt.Sprintf("Response Body: %s", string(respBody))) + log.Debug("Response body", zap.String("body", string(respBody))) resp.Body = io.NopCloser(bytes.NewBuffer(respBody)) // Restore the response body for further use if csrf := resp.Header.Get("X-CSRF-Token"); csrf != "" { c.csrf = csrf + log.Debug("Updated CSRF token", zap.String("token", c.csrf)) } - log.Debug(fmt.Sprintf("response code from %s request to %s: %d", method, path, resp.StatusCode)) + log.Debug("Response received", + zap.String("method", method), + zap.String("path", path), + zap.Int("statusCode", resp.StatusCode)) // If the status code is 401, re-login and retry the request if resp.StatusCode == http.StatusUnauthorized { - log.Debug("Received 401 Unauthorized, re-login required") + log.Debug("Received 401 Unauthorized, attempting to re-login") if err := c.login(); err != nil { + log.Error("Re-login failed", zap.Error(err)) return nil, err } // Update the headers with new CSRF token c.setHeaders(req) // Retry the request + log.Debug("Retrying request after re-login") resp, err = c.Client.Do(req) if err != nil { + log.Error("Retry request failed", zap.Error(err)) return nil, err } } if resp.StatusCode != http.StatusOK { + log.Error("Request was not successful", + zap.String("method", method), + zap.String("path", path), + zap.Int("statusCode", resp.StatusCode)) return nil, fmt.Errorf("%s request to %s was not successful: %d", method, path, resp.StatusCode) } @@ -188,94 +217,136 @@ func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Respo // GetEndpoints retrieves the list of DNS records from the UniFi controller. func (c *httpClient) GetEndpoints() ([]DNSRecord, error) { + log.Debug("Getting endpoints") + recordPath := unifiRecordPathUDM + if c.Config.ControllerType == "NETWORK_SERVER" { + recordPath = unifiRecordPathNetworkApp + } + resp, err := c.doRequest( http.MethodGet, - FormatUrl(unifiRecordPath, c.Config.Host, c.Config.Site), + FormatUrl(recordPath, c.Config.Host, c.Config.Site), nil, ) if err != nil { + log.Error("Failed to get endpoints", zap.Error(err)) return nil, err } defer resp.Body.Close() var records []DNSRecord if err = json.NewDecoder(resp.Body).Decode(&records); err != nil { + log.Error("Failed to decode response", zap.Error(err)) return nil, err } - log.Debug(fmt.Sprintf("retrieved records: %+v", records)) + log.Debug("Retrieved records", zap.Int("count", len(records))) + for _, record := range records { + log.Debug("Record", zap.Any("record", record)) + } return records, nil } // CreateEndpoint creates a new DNS record in the UniFi controller. func (c *httpClient) CreateEndpoint(endpoint *endpoint.Endpoint) (*DNSRecord, error) { - jsonBody, err := json.Marshal(DNSRecord{ + log.Debug("Creating endpoint", zap.String("dnsName", endpoint.DNSName)) + recordPath := unifiRecordPathUDM + if c.Config.ControllerType == "NETWORK_SERVER" { + recordPath = unifiRecordPathNetworkApp + } + + record := DNSRecord{ Enabled: true, Key: endpoint.DNSName, RecordType: endpoint.RecordType, TTL: endpoint.RecordTTL, Value: endpoint.Targets[0], - }) + } + + jsonBody, err := json.Marshal(record) if err != nil { + log.Error("Failed to marshal record", zap.Error(err)) return nil, err } + log.Debug("Creating record", zap.String("record", string(jsonBody))) + resp, err := c.doRequest( http.MethodPost, - FormatUrl(unifiRecordPath, c.Config.Host, c.Config.Site), + FormatUrl(recordPath, c.Config.Host, c.Config.Site), bytes.NewReader(jsonBody), ) if err != nil { + log.Error("Failed to create endpoint", zap.Error(err)) return nil, err } defer resp.Body.Close() - var record DNSRecord - if err = json.NewDecoder(resp.Body).Decode(&record); err != nil { + var createdRecord DNSRecord + if err = json.NewDecoder(resp.Body).Decode(&createdRecord); err != nil { + log.Error("Failed to decode response", zap.Error(err)) return nil, err } - log.Debug(fmt.Sprintf("created record: %+v", record)) + log.Debug("Created record", zap.Any("record", createdRecord)) - return &record, nil + return &createdRecord, nil } // DeleteEndpoint deletes a DNS record from the UniFi controller. func (c *httpClient) DeleteEndpoint(endpoint *endpoint.Endpoint) error { + log.Debug("Deleting endpoint", zap.String("dnsName", endpoint.DNSName)) lookup, err := c.lookupIdentifier(endpoint.DNSName, endpoint.RecordType) if err != nil { + log.Error("Failed to lookup identifier", zap.Error(err)) return err } - if _, err = c.doRequest( + recordPath := unifiRecordPathUDM + if c.Config.ControllerType == "NETWORK_SERVER" { + recordPath = unifiRecordPathNetworkApp + } + + deleteURL := FormatUrl(recordPath, c.Config.Host, c.Config.Site, lookup.ID) + log.Debug("Deleting record", zap.String("url", deleteURL)) + + _, err = c.doRequest( http.MethodDelete, - FormatUrl(unifiRecordPath, c.Config.Host, c.Config.Site, lookup.ID), + deleteURL, nil, - ); err != nil { + ) + if err != nil { + log.Error("Failed to delete endpoint", zap.Error(err)) return err } + log.Debug("Successfully deleted endpoint", zap.String("dnsName", endpoint.DNSName)) return nil } // lookupIdentifier finds the ID of a DNS record in the UniFi controller. func (c *httpClient) lookupIdentifier(key, recordType string) (*DNSRecord, error) { + log.Debug("Looking up identifier", zap.String("key", key), zap.String("recordType", recordType)) records, err := c.GetEndpoints() if err != nil { + log.Error("Failed to get endpoints", zap.Error(err)) return nil, err } for _, r := range records { if r.Key == key && r.RecordType == recordType { + log.Debug("Found matching record", zap.Any("record", r)) return &r, nil } } - return nil, err + log.Debug("Record not found", zap.String("key", key), zap.String("recordType", recordType)) + return nil, fmt.Errorf("record not found") } // setHeaders sets the headers for the HTTP request. func (c *httpClient) setHeaders(req *http.Request) { + log.Debug("Setting headers for request") // Add the saved CSRF header. req.Header.Set("X-CSRF-Token", c.csrf) req.Header.Add("Accept", "application/json") @@ -284,8 +355,14 @@ func (c *httpClient) setHeaders(req *http.Request) { // Log the request URL and cookies if c.Client.Jar != nil { parsedURL, _ := url.Parse(req.URL.String()) - log.Debug(fmt.Sprintf("Requesting %s cookies: %d", req.URL, len(c.Client.Jar.Cookies(parsedURL)))) + cookies := c.Client.Jar.Cookies(parsedURL) + log.Debug("Request cookies", + zap.String("url", req.URL.String()), + zap.Int("cookieCount", len(cookies))) + for _, cookie := range cookies { + log.Debug("Cookie", zap.String("name", cookie.Name), zap.String("value", cookie.Value)) + } } else { - log.Debug(fmt.Sprintf("Requesting %s", req.URL)) + log.Debug("No cookie jar available", zap.String("url", req.URL.String())) } } diff --git a/internal/unifi/types.go b/internal/unifi/types.go index ad76ba2..32f8dee 100644 --- a/internal/unifi/types.go +++ b/internal/unifi/types.go @@ -8,7 +8,7 @@ type Config struct { User string `env:"UNIFI_USER,notEmpty"` Password string `env:"UNIFI_PASS,notEmpty"` Site string `env:"UNIFI_SITE" envDefault:"default"` - ControllerType string `env:"CONTRAOLLOER_TYPE" envDefault:"default"` + ControllerType string `env:"CONTROLLER_TYPE" envDefault:"default"` SkipTLSVerify bool `env:"UNIFI_SKIP_TLS_VERIFY" envDefault:"true"` } diff --git a/test.yaml b/test.yaml new file mode 100644 index 0000000..5c2083c --- /dev/null +++ b/test.yaml @@ -0,0 +1,42 @@ +fullnameOverride: external-dns-unifi +logLevel: debug +provider: + name: webhook + webhook: + image: + repository: ghcr.io/kashalls/external-dns-unifi-webhook + tag: main # replace with a versioned release tag + env: + - name: UNIFI_HOST + value: https://192.168.1.1 # replace with the address to your UniFi router + - name: UNIFI_USER + valueFrom: + secretKeyRef: + name: external-dns-unifi-secret + key: username + - name: UNIFI_PASS + valueFrom: + secretKeyRef: + name: external-dns-unifi-secret + key: password + - name: LOG_LEVEL + value: debug + livenessProbe: + httpGet: + path: /healthz + port: http-webhook + initialDelaySeconds: 10 + timeoutSeconds: 5 + readinessProbe: + httpGet: + path: /readyz + port: http-webhook + initialDelaySeconds: 10 + timeoutSeconds: 5 +extraArgs: + - --ignore-ingress-tls-spec +policy: sync +sources: ["ingress", "service"] +txtOwnerId: default +txtPrefix: k8s. +domainFilters: ["example.com"] # replace with your domain From f645bb80bdb2cdcbd6c0b0349d72933e53212e85 Mon Sep 17 00:00:00 2001 From: Aakash Date: Tue, 15 Oct 2024 22:38:18 -0700 Subject: [PATCH 14/37] check --- .DS_Store | Bin 0 -> 6148 bytes cmd/.DS_Store | Bin 0 -> 6148 bytes cmd/webhook/.DS_Store | Bin 0 -> 6148 bytes cmd/webhook/init/.DS_Store | Bin 0 -> 6148 bytes internal/.DS_Store | Bin 0 -> 6148 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 .DS_Store create mode 100644 cmd/.DS_Store create mode 100644 cmd/webhook/.DS_Store create mode 100644 cmd/webhook/init/.DS_Store create mode 100644 internal/.DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..68b1081365f01ecd8954ef4e36f74de6b924330e GIT binary patch literal 6148 zcmeHK%}T>S5T0$TZYe?!Djow~1zUeA;w4ml0V8@)sfj5XjM>tr_7Dm=`tv^GllVN& z?5<*e^eCb;F#FBU&TN=3VK)N+qS+7Y0962xsDyV>c+FhLxg>4KuJBgA|4VtQ-ggu?>nFU#vdRUmrd2gowb_D4W`G%B2F4k%r>LxqFYufh zUaL_FPBK<~cL7RFB%5f~Z7ITBRf+9>RqDd9D#SkVP{f>=u zE#?MIItbf*2>WDVI~1Xxj{7@09E59-M`nN-7-gVnrd7KC55DgIN0WHQ3@`(KiUCpD z_x77ulD%6eile*MLA^jFp}5@OM+zGHD#lp4ikDHfpx+?_(Y2TxL=Ou82xuC3U zgUmX~{1pqD)cEj9z z31G1RuqIA{h`==`Eo-SGgIZ^>CaI3&m zEH_sFU*UiB|Fi80h5~3oFN4 cPl~)^bL`i|DbVSNI~~ZM0n>#>1^!!sZ@_64mjD0& literal 0 HcmV?d00001 diff --git a/cmd/webhook/.DS_Store b/cmd/webhook/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..e1aa38446460a5cc65551a0c8f19dac28b9e3294 GIT binary patch literal 6148 zcmeH~F%H5o3`K3|fW*>~F$)La1`&c2Z~+8ekSZ8}JxAx;@j_rm75bL!7dy4u{-LQw zM7PgjBhrh=0&bMGg~k+lCl|TGAH)4To^H3%im+LQSPJi@*^h043Qz$mKn17(6_}6$ zaf~mP6M7~-3KgIN(@?;^4+(C}!BXj;4g?fR2fxGo1vWo;LY;X~ujYEukMYrAMd0@LhZpaN8&QD9#4-p>C${M-EBv@oRtRN&7P z&~A6wZShiZwtl>x)sIoNb%BF^Il|jd01`WjS8zA%CtH9ySSnG0@khXAV4wm&Rp14v CSQ5Da literal 0 HcmV?d00001 diff --git a/cmd/webhook/init/.DS_Store b/cmd/webhook/init/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ad619eb6af516437a4beee35be2ada20fb6af065 GIT binary patch literal 6148 zcmeHK%TB{U3>-rbl`5njIqom;2T@hNfFCF{fGSl4^}#u}{x)NKfmA714rnZSCp&Ae zl{v&Q09zc}J75lAN_WJ`m$CV|`^c^;<7jcm2Cq0`hesSn(U%F&y}=qC-tdB9{>Hg> zzurD|-Imv5hL%Blyt6=u4+gh*#%>sLxrSINAO)m=6p#W^;70*(sJ)Ou%Z_#v!zpuwHa$`FUFvk`a(X6&*M2s z3LDm25qAzG-^uw&nh%hr5i~j4=xuB1ff0&|K?UF~Nvj%@GAfIu2qP)J*d? zP5A9Cma~LK?19aH|3@&6qcrRHKlw_d*;;K|ZL4eDc~7$NGC#{x*Pqyp6vBe zJ(|rr*2ebE>BaCleo5q;rjY~RO4bZk@D9q{eqOyv63gTftW`!8Nk|M31H=F^u&fN2 ziePn@l>pi|F+dFb$N-)X0ve)Yu+*rI4(RaujQ%Df3h4NjKokZYgQZ6BfN-4(s8hLl zVsM=fc46WigQZ5D&bV3`<}oW*j~A|12fI+=j5`{sCkBXtMFtvrXk-09hhL`lk-wNi zBVvFU_-738=D-`cuqjhpzikg|t$=n94F%&$R6syqxCFoe&yg+V)P8|F#5o2_jW`S1 RRXQMD1Qa3E5d*)#z!#lbN_qeQ literal 0 HcmV?d00001 From 8ea8ed5d638268e984f442310daf55d0561ce7c3 Mon Sep 17 00:00:00 2001 From: Aakash Date: Tue, 15 Oct 2024 22:40:39 -0700 Subject: [PATCH 15/37] fix port --- .gitignore | 3 ++- cmd/webhook/init/server/server.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 83c5afc..6abe6c2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .idea ### .private/ -external-dns-unifi-webhook \ No newline at end of file +external-dns-unifi-webhook +.DS_Store diff --git a/cmd/webhook/init/server/server.go b/cmd/webhook/init/server/server.go index c041ebd..eabb986 100644 --- a/cmd/webhook/init/server/server.go +++ b/cmd/webhook/init/server/server.go @@ -52,7 +52,7 @@ func Init(config configuration.Config, p *webhook.Webhook) (*http.Server, *http. healthRouter.Get("/healthz", HealthCheckHandler) healthRouter.Get("/readyz", ReadinessHandler) - healthServer := createHTTPServer("0.0.0.0:8090", healthRouter, config.ServerReadTimeout, config.ServerWriteTimeout) + healthServer := createHTTPServer("0.0.0.0:8080", healthRouter, config.ServerReadTimeout, config.ServerWriteTimeout) go func() { log.Info("starting health server", zap.String("address", healthServer.Addr)) if err := healthServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { From 20bb9021b0be3da90b63c70f464a55cef1cf5d8c Mon Sep 17 00:00:00 2001 From: Aakash Date: Tue, 15 Oct 2024 23:18:16 -0700 Subject: [PATCH 16/37] update readme --- README.md | 2 ++ internal/unifi/client.go | 3 +-- test.yaml | 42 ---------------------------------------- 3 files changed, 3 insertions(+), 44 deletions(-) delete mode 100644 test.yaml diff --git a/README.md b/README.md index 0d7f660..781e019 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,8 @@ key: password - name: LOG_LEVEL value: debug + - name: CONTROLLER_TYPE # if you have Unifi Network Server running remotely + value: NETWORK_SERVER livenessProbe: httpGet: path: /healthz diff --git a/internal/unifi/client.go b/internal/unifi/client.go index cd890bd..c77192b 100644 --- a/internal/unifi/client.go +++ b/internal/unifi/client.go @@ -82,8 +82,7 @@ func (c *httpClient) login() error { // Print request details log.Debug("Sending login request", - zap.String("URL", FormatUrl(loginPath, c.Config.Host)), - zap.String("body", string(jsonBody))) + zap.String("URL", FormatUrl(loginPath, c.Config.Host))) // Perform the login request resp, err := c.doRequest( diff --git a/test.yaml b/test.yaml deleted file mode 100644 index 5c2083c..0000000 --- a/test.yaml +++ /dev/null @@ -1,42 +0,0 @@ -fullnameOverride: external-dns-unifi -logLevel: debug -provider: - name: webhook - webhook: - image: - repository: ghcr.io/kashalls/external-dns-unifi-webhook - tag: main # replace with a versioned release tag - env: - - name: UNIFI_HOST - value: https://192.168.1.1 # replace with the address to your UniFi router - - name: UNIFI_USER - valueFrom: - secretKeyRef: - name: external-dns-unifi-secret - key: username - - name: UNIFI_PASS - valueFrom: - secretKeyRef: - name: external-dns-unifi-secret - key: password - - name: LOG_LEVEL - value: debug - livenessProbe: - httpGet: - path: /healthz - port: http-webhook - initialDelaySeconds: 10 - timeoutSeconds: 5 - readinessProbe: - httpGet: - path: /readyz - port: http-webhook - initialDelaySeconds: 10 - timeoutSeconds: 5 -extraArgs: - - --ignore-ingress-tls-spec -policy: sync -sources: ["ingress", "service"] -txtOwnerId: default -txtPrefix: k8s. -domainFilters: ["example.com"] # replace with your domain From 36985a28a1a5990651a902e9300a6601c820b614 Mon Sep 17 00:00:00 2001 From: aki263 Date: Tue, 15 Oct 2024 23:24:32 -0700 Subject: [PATCH 17/37] task(fix): restore original workflow --- .github/workflows/release.yaml | 20 ++------------------ cmd/webhook/init/.DS_Store | Bin 6148 -> 0 bytes 2 files changed, 2 insertions(+), 18 deletions(-) delete mode 100644 cmd/webhook/init/.DS_Store diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b95f4c8..2109dee 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -10,6 +10,7 @@ on: jobs: build-image: + if: ${{ github.event.pull_request.head.repo.full_name == github.repository || github.event_name != 'pull_request' }} runs-on: ubuntu-latest permissions: contents: read @@ -46,14 +47,6 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Restore Docker cache - uses: actions/cache@v3 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-buildx- - - name: Build and Push uses: docker/build-push-action@v6 with: @@ -65,13 +58,4 @@ jobs: labels: ${{ steps.meta.outputs.labels }} build-args: | VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} - REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }} - cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache - - - name: Save Docker cache - if: always() - uses: actions/cache@v3 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ github.sha }} + REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }} \ No newline at end of file diff --git a/cmd/webhook/init/.DS_Store b/cmd/webhook/init/.DS_Store deleted file mode 100644 index ad619eb6af516437a4beee35be2ada20fb6af065..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%TB{U3>-rbl`5njIqom;2T@hNfFCF{fGSl4^}#u}{x)NKfmA714rnZSCp&Ae zl{v&Q09zc}J75lAN_WJ`m$CV|`^c^;<7jcm2Cq0`hesSn(U%F&y}=qC-tdB9{>Hg> zzurD|-Imv5hL%Blyt6=u4+gh*#%>sLxrSINAO)m=6p#W^;70*(s Date: Wed, 16 Oct 2024 00:30:42 -0700 Subject: [PATCH 18/37] task(fix): address comments. --- README.md | 4 ++-- internal/unifi/client.go | 8 ++++---- internal/unifi/types.go | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 781e019..26094d7 100644 --- a/README.md +++ b/README.md @@ -60,8 +60,8 @@ key: password - name: LOG_LEVEL value: debug - - name: CONTROLLER_TYPE # if you have Unifi Network Server running remotely - value: NETWORK_SERVER + - name: CONTROLLER_TYPE # Defaults to gateway, possible values: standalone(hosting unifi controller remotely, for ex: gateway max) or gateway(unifi server is build into hardware like cloud gateway max) + value: gateway livenessProbe: httpGet: path: /healthz diff --git a/internal/unifi/client.go b/internal/unifi/client.go index c77192b..6c67cc1 100644 --- a/internal/unifi/client.go +++ b/internal/unifi/client.go @@ -65,7 +65,7 @@ func newUnifiClient(config *Config) (*httpClient, error) { // login performs a login request to the UniFi controller. func (c *httpClient) login() error { loginPath := unifiLoginPathUDM - if c.Config.ControllerType == "NETWORK_SERVER" { + if c.Config.ControllerType == "standalone" { loginPath = unifiLoginPathNetworkApp } log.Debug("Logging in", zap.String("loginPath", loginPath)) @@ -218,7 +218,7 @@ func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Respo func (c *httpClient) GetEndpoints() ([]DNSRecord, error) { log.Debug("Getting endpoints") recordPath := unifiRecordPathUDM - if c.Config.ControllerType == "NETWORK_SERVER" { + if c.Config.ControllerType == "standalone" { recordPath = unifiRecordPathNetworkApp } @@ -251,7 +251,7 @@ func (c *httpClient) GetEndpoints() ([]DNSRecord, error) { func (c *httpClient) CreateEndpoint(endpoint *endpoint.Endpoint) (*DNSRecord, error) { log.Debug("Creating endpoint", zap.String("dnsName", endpoint.DNSName)) recordPath := unifiRecordPathUDM - if c.Config.ControllerType == "NETWORK_SERVER" { + if c.Config.ControllerType == "standalone" { recordPath = unifiRecordPathNetworkApp } @@ -302,7 +302,7 @@ func (c *httpClient) DeleteEndpoint(endpoint *endpoint.Endpoint) error { } recordPath := unifiRecordPathUDM - if c.Config.ControllerType == "NETWORK_SERVER" { + if c.Config.ControllerType == "standalone" { recordPath = unifiRecordPathNetworkApp } diff --git a/internal/unifi/types.go b/internal/unifi/types.go index 32f8dee..bb54bca 100644 --- a/internal/unifi/types.go +++ b/internal/unifi/types.go @@ -8,7 +8,7 @@ type Config struct { User string `env:"UNIFI_USER,notEmpty"` Password string `env:"UNIFI_PASS,notEmpty"` Site string `env:"UNIFI_SITE" envDefault:"default"` - ControllerType string `env:"CONTROLLER_TYPE" envDefault:"default"` + ControllerType string `env:"CONTROLLER_TYPE" envDefault:"gateway"` SkipTLSVerify bool `env:"UNIFI_SKIP_TLS_VERIFY" envDefault:"true"` } From a15978151115ae855f21dd5ee2bad5880459df70 Mon Sep 17 00:00:00 2001 From: Aakash Date: Wed, 16 Oct 2024 00:33:06 -0700 Subject: [PATCH 19/37] remove extra files --- cmd/.DS_Store | Bin 6148 -> 0 bytes cmd/webhook/.DS_Store | Bin 6148 -> 0 bytes internal/.DS_Store | Bin 6148 -> 0 bytes 3 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 cmd/.DS_Store delete mode 100644 cmd/webhook/.DS_Store delete mode 100644 internal/.DS_Store diff --git a/cmd/.DS_Store b/cmd/.DS_Store deleted file mode 100644 index b4749e0331d4581b9351fb2bc5194cfa05c72616..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKJ8Hu~5S?)hHl%Usa<7mZEJ8U!E)X0U3^u4RA+0K(%SX$bPZ6_8rxZ4C#O&Lh zowq`-*wKiHcAvwG$VNoga6`G+(3 zgUmX~{1pqD)cEj9z z31G1RuqIA{h`==`Eo-SGgIZ^>CaI3&m zEH_sFU*UiB|Fi80h5~3oFN4 cPl~)^bL`i|DbVSNI~~ZM0n>#>1^!!sZ@_64mjD0& diff --git a/cmd/webhook/.DS_Store b/cmd/webhook/.DS_Store deleted file mode 100644 index e1aa38446460a5cc65551a0c8f19dac28b9e3294..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~F%H5o3`K3|fW*>~F$)La1`&c2Z~+8ekSZ8}JxAx;@j_rm75bL!7dy4u{-LQw zM7PgjBhrh=0&bMGg~k+lCl|TGAH)4To^H3%im+LQSPJi@*^h043Qz$mKn17(6_}6$ zaf~mP6M7~-3KgIN(@?;^4+(C}!BXj;4g?fR2fxGo1vWo;LY;X~ujYEukMYrAMd0@LhZpaN8&QD9#4-p>C${M-EBv@oRtRN&7P z&~A6wZShiZwtl>x)sIoNb%BF^Il|jd01`WjS8zA%CtH9ySSnG0@khXAV4wm&Rp14v CSQ5Da diff --git a/internal/.DS_Store b/internal/.DS_Store deleted file mode 100644 index 73ffa3ada1548edf26dcb7e40f5a7827c841382d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK-AcnS6i(dcI)=~-6)ywc4xImpH>J)Ou%Z_#v!zpuwHa$`FUFvk`a(X6&*M2s z3LDm25qAzG-^uw&nh%hr5i~j4=xuB1ff0&|K?UF~Nvj%@GAfIu2qP)J*d? zP5A9Cma~LK?19aH|3@&6qcrRHKlw_d*;;K|ZL4eDc~7$NGC#{x*Pqyp6vBe zJ(|rr*2ebE>BaCleo5q;rjY~RO4bZk@D9q{eqOyv63gTftW`!8Nk|M31H=F^u&fN2 ziePn@l>pi|F+dFb$N-)X0ve)Yu+*rI4(RaujQ%Df3h4NjKokZYgQZ6BfN-4(s8hLl zVsM=fc46WigQZ5D&bV3`<}oW*j~A|12fI+=j5`{sCkBXtMFtvrXk-09hhL`lk-wNi zBVvFU_-738=D-`cuqjhpzikg|t$=n94F%&$R6syqxCFoe&yg+V)P8|F#5o2_jW`S1 RRXQMD1Qa3E5d*)#z!#lbN_qeQ From 3324dcbb0c17fffab2a5358e6603d214db8eae17 Mon Sep 17 00:00:00 2001 From: Aakash Date: Wed, 16 Oct 2024 00:33:41 -0700 Subject: [PATCH 20/37] remove extra files --- .DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 68b1081365f01ecd8954ef4e36f74de6b924330e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}T>S5T0$TZYe?!Djow~1zUeA;w4ml0V8@)sfj5XjM>tr_7Dm=`tv^GllVN& z?5<*e^eCb;F#FBU&TN=3VK)N+qS+7Y0962xsDyV>c+FhLxg>4KuJBgA|4VtQ-ggu?>nFU#vdRUmrd2gowb_D4W`G%B2F4k%r>LxqFYufh zUaL_FPBK<~cL7RFB%5f~Z7ITBRf+9>RqDd9D#SkVP{f>=u zE#?MIItbf*2>WDVI~1Xxj{7@09E59-M`nN-7-gVnrd7KC55DgIN0WHQ3@`(KiUCpD z_x77ulD%6eile*MLA^jFp}5@OM+zGHD#lp4ikDHfpx+?_(Y2TxL=Ou82xuC3U Date: Wed, 16 Oct 2024 02:07:52 -0700 Subject: [PATCH 21/37] chore: test if controller type is supported when parsing config for unifi --- cmd/webhook/init/dnsprovider/dnsprovider.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/webhook/init/dnsprovider/dnsprovider.go b/cmd/webhook/init/dnsprovider/dnsprovider.go index a05de29..9273325 100644 --- a/cmd/webhook/init/dnsprovider/dnsprovider.go +++ b/cmd/webhook/init/dnsprovider/dnsprovider.go @@ -49,5 +49,9 @@ func Init(config configuration.Config) (provider.Provider, error) { return nil, fmt.Errorf("reading unifi configuration failed: %v", err) } + if unifiConfig.ControllerType != "gateway" && unifiConfig.ControllerType != "standalone" { + return nil, fmt.Errorf("unsupported controller type: %s", unifiConfig.ControllerType) + } + return unifi.NewUnifiProvider(domainFilter, &unifiConfig) } From 77712c08c823786e599365bdedd41636cd2e64f6 Mon Sep 17 00:00:00 2001 From: Jordan Jones Date: Wed, 16 Oct 2024 02:21:48 -0700 Subject: [PATCH 22/37] chore: clean up readme and add tables for configuration options Very helpful for people who want to get started using it instead of DM-ing me. --- README.md | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 26094d7..1021162 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ tag: main # replace with a versioned release tag env: - name: UNIFI_HOST - value: https://192.168.1.1 # replace with the address to your UniFi router + value: https://192.168.1.1 # replace with the address to your UniFi router/controller - name: UNIFI_USER valueFrom: secretKeyRef: @@ -58,10 +58,10 @@ secretKeyRef: name: external-dns-unifi-secret key: password - - name: LOG_LEVEL - value: debug - - name: CONTROLLER_TYPE # Defaults to gateway, possible values: standalone(hosting unifi controller remotely, for ex: gateway max) or gateway(unifi server is build into hardware like cloud gateway max) - value: gateway + # - name: LOG_LEVEL + # value: debug + + # Apply additional configurations here as needed. livenessProbe: httpGet: path: /healthz @@ -89,6 +89,33 @@ helm install external-dns-unifi external-dns/external-dns -f external-dns-unifi-values.yaml --version 1.14.3 -n external-dns ``` +## Configuration + +### Unifi Controller Configuration + +| Environment Variable | Description | Default Value | +|-------------------------|--------------------------------------------------------------|---------------| +| `UNIFI_USER` | Username for the Unifi Controller (must be provided). | N/A | +| `UNIFI_SKIP_TLS_VERIFY` | Whether to skip TLS verification (true or false). | `true` | +| `UNIFI_SITE` | Unifi Site Identifier (used in multi-site installations) | `default` | +| `UNIFI_PASS` | Password for the Unifi Controller (must be provided). | N/A | +| `UNIFI_HOST` | Host of the Unifi Controller (must be provided). | N/A | +| `CONTROLLER_TYPE` | Type of Unifi controller (e.g., gateway or standalone). | `gateway` | +| `LOG_LEVEL` | Change the verbosity of logs (used when making a bug report) | `info` | + +### Server Configuration + +| Environment Variable | Description | Default Value | +|----------------------------------|------------------------------------------------------------------|---------------| +| `SERVER_HOST` | The host address where the server listens. | `localhost` | +| `SERVER_PORT` | The port where the server listens. | `8888` | +| `SERVER_READ_TIMEOUT` | Duration the server waits before timing out on read operations. | N/A | +| `SERVER_WRITE_TIMEOUT` | Duration the server waits before timing out on write operations. | N/A | +| `DOMAIN_FILTER` | List of domains to include in the filter. | Empty | +| `EXCLUDE_DOMAIN_FILTER` | List of domains to exclude from filtering. | Empty | +| `REGEXP_DOMAIN_FILTER` | Regular expression for filtering domains. | Empty | +| `REGEXP_DOMAIN_FILTER_EXCLUSION` | Regular expression for excluding domains from the filter. | Empty | + ## ⭐ Stargazers
From a57dd7bb56cc12fdada1a9f79004fa04370d87c0 Mon Sep 17 00:00:00 2001 From: aki263 Date: Wed, 16 Oct 2024 07:52:10 -0700 Subject: [PATCH 23/37] task(fix): change env variable to UNIFI_CONTROLLER_TYPE --- README.md | 2 +- internal/unifi/types.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1021162..e9ce7ff 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ | `UNIFI_SITE` | Unifi Site Identifier (used in multi-site installations) | `default` | | `UNIFI_PASS` | Password for the Unifi Controller (must be provided). | N/A | | `UNIFI_HOST` | Host of the Unifi Controller (must be provided). | N/A | -| `CONTROLLER_TYPE` | Type of Unifi controller (e.g., gateway or standalone). | `gateway` | +| `UNIFI_CONTROLLER_TYPE` | Type of Unifi controller (e.g., gateway or standalone). | `gateway` | | `LOG_LEVEL` | Change the verbosity of logs (used when making a bug report) | `info` | ### Server Configuration diff --git a/internal/unifi/types.go b/internal/unifi/types.go index bb54bca..7e43ce2 100644 --- a/internal/unifi/types.go +++ b/internal/unifi/types.go @@ -8,7 +8,7 @@ type Config struct { User string `env:"UNIFI_USER,notEmpty"` Password string `env:"UNIFI_PASS,notEmpty"` Site string `env:"UNIFI_SITE" envDefault:"default"` - ControllerType string `env:"CONTROLLER_TYPE" envDefault:"gateway"` + ControllerType string `env:"UNIFI_CONTROLLER_TYPE" envDefault:"gateway"` SkipTLSVerify bool `env:"UNIFI_SKIP_TLS_VERIFY" envDefault:"true"` } From f8b795e44070aecb747beffad9adf049b31be93c Mon Sep 17 00:00:00 2001 From: Jordan Jones Date: Wed, 16 Oct 2024 11:53:02 -0700 Subject: [PATCH 24/37] chore: match vars to the name of the option types --- internal/unifi/client.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/unifi/client.go b/internal/unifi/client.go index 6c67cc1..4bb9d0a 100644 --- a/internal/unifi/client.go +++ b/internal/unifi/client.go @@ -25,10 +25,10 @@ type httpClient struct { } const ( - unifiLoginPathUDM = "%s/api/auth/login" - unifiLoginPathNetworkApp = "%s/api/login" - unifiRecordPathUDM = "%s/proxy/network/v2/api/site/%s/static-dns/%s" - unifiRecordPathNetworkApp = "%s/v2/api/site/%s/static-dns/%s" + unifiLoginPathGateway = "%s/api/auth/login" + unifiLoginPathStandalone = "%s/api/login" + unifiRecordPathGateway = "%s/proxy/network/v2/api/site/%s/static-dns/%s" + unifiRecordPathStandalone = "%s/v2/api/site/%s/static-dns/%s" ) // newUnifiClient creates a new DNS provider client and logs in to store cookies. @@ -64,9 +64,9 @@ func newUnifiClient(config *Config) (*httpClient, error) { // login performs a login request to the UniFi controller. func (c *httpClient) login() error { - loginPath := unifiLoginPathUDM + loginPath := unifiLoginPathGateway if c.Config.ControllerType == "standalone" { - loginPath = unifiLoginPathNetworkApp + loginPath = unifiLoginPathStandalone } log.Debug("Logging in", zap.String("loginPath", loginPath)) @@ -217,9 +217,9 @@ func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Respo // GetEndpoints retrieves the list of DNS records from the UniFi controller. func (c *httpClient) GetEndpoints() ([]DNSRecord, error) { log.Debug("Getting endpoints") - recordPath := unifiRecordPathUDM + recordPath := unifiRecordPathGateway if c.Config.ControllerType == "standalone" { - recordPath = unifiRecordPathNetworkApp + recordPath = unifiRecordPathStandalone } resp, err := c.doRequest( @@ -250,9 +250,9 @@ func (c *httpClient) GetEndpoints() ([]DNSRecord, error) { // CreateEndpoint creates a new DNS record in the UniFi controller. func (c *httpClient) CreateEndpoint(endpoint *endpoint.Endpoint) (*DNSRecord, error) { log.Debug("Creating endpoint", zap.String("dnsName", endpoint.DNSName)) - recordPath := unifiRecordPathUDM + recordPath := unifiRecordPathGateway if c.Config.ControllerType == "standalone" { - recordPath = unifiRecordPathNetworkApp + recordPath = unifiRecordPathStandalone } record := DNSRecord{ @@ -301,9 +301,9 @@ func (c *httpClient) DeleteEndpoint(endpoint *endpoint.Endpoint) error { return err } - recordPath := unifiRecordPathUDM + recordPath := unifiRecordPathGateway if c.Config.ControllerType == "standalone" { - recordPath = unifiRecordPathNetworkApp + recordPath = unifiRecordPathStandalone } deleteURL := FormatUrl(recordPath, c.Config.Host, c.Config.Site, lookup.ID) From 681d99043b1d29a2fcbd419ec4898f2420a57622 Mon Sep 17 00:00:00 2001 From: Jordan Jones Date: Wed, 16 Oct 2024 11:53:15 -0700 Subject: [PATCH 25/37] chore: fix new line on release.yaml --- .github/workflows/release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 2109dee..ee3ab5f 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -58,4 +58,4 @@ jobs: labels: ${{ steps.meta.outputs.labels }} build-args: | VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} - REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }} \ No newline at end of file + REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }} From b22073795c21939287aadfd789f647dd2768b2d1 Mon Sep 17 00:00:00 2001 From: Jordan Jones Date: Wed, 16 Oct 2024 12:04:18 -0700 Subject: [PATCH 26/37] chore: remove extra debug logs --- internal/unifi/client.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/internal/unifi/client.go b/internal/unifi/client.go index 4bb9d0a..359be21 100644 --- a/internal/unifi/client.go +++ b/internal/unifi/client.go @@ -37,7 +37,6 @@ func newUnifiClient(config *Config) (*httpClient, error) { jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) if err != nil { - log.Error("Failed to create cookie jar", zap.Error(err)) return nil, err } @@ -52,13 +51,10 @@ func newUnifiClient(config *Config) (*httpClient, error) { }, } - log.Debug("Attempting to login") if err := client.login(); err != nil { - log.Error("Failed to login", zap.Error(err)) return nil, err } - log.Debug("UniFi client created and logged in successfully") return client, nil } @@ -68,7 +64,6 @@ func (c *httpClient) login() error { if c.Config.ControllerType == "standalone" { loginPath = unifiLoginPathStandalone } - log.Debug("Logging in", zap.String("loginPath", loginPath)) jsonBody, err := json.Marshal(Login{ Username: c.Config.User, @@ -76,14 +71,9 @@ func (c *httpClient) login() error { Remember: true, }) if err != nil { - log.Error("Failed to marshal login JSON", zap.Error(err)) return err } - // Print request details - log.Debug("Sending login request", - zap.String("URL", FormatUrl(loginPath, c.Config.Host))) - // Perform the login request resp, err := c.doRequest( http.MethodPost, @@ -109,12 +99,7 @@ func (c *httpClient) login() error { // Retrieve CSRF token from the response headers if csrf := resp.Header.Get("x-csrf-token"); csrf != "" { c.csrf = resp.Header.Get("x-csrf-token") - log.Debug("Retrieved CSRF token", zap.String("token", c.csrf)) - } else { - log.Debug("No CSRF token found in response headers") } - - log.Debug("Login successful", zap.String("status", resp.Status)) return nil } From 9529a12c697aca4b0fa27ffdfdf2f6b0abf89087 Mon Sep 17 00:00:00 2001 From: Jordan Jones Date: Wed, 16 Oct 2024 12:04:31 -0700 Subject: [PATCH 27/37] chore: do not dump cookies --- internal/unifi/client.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/internal/unifi/client.go b/internal/unifi/client.go index 359be21..8d9de1f 100644 --- a/internal/unifi/client.go +++ b/internal/unifi/client.go @@ -300,7 +300,6 @@ func (c *httpClient) DeleteEndpoint(endpoint *endpoint.Endpoint) error { nil, ) if err != nil { - log.Error("Failed to delete endpoint", zap.Error(err)) return err } @@ -343,9 +342,6 @@ func (c *httpClient) setHeaders(req *http.Request) { log.Debug("Request cookies", zap.String("url", req.URL.String()), zap.Int("cookieCount", len(cookies))) - for _, cookie := range cookies { - log.Debug("Cookie", zap.String("name", cookie.Name), zap.String("value", cookie.Value)) - } } else { log.Debug("No cookie jar available", zap.String("url", req.URL.String())) } From af738bcd18c8078413f081c717e4f415ebf9f3c1 Mon Sep 17 00:00:00 2001 From: Jordan Jones Date: Wed, 16 Oct 2024 12:40:34 -0700 Subject: [PATCH 28/37] chore: placing logs a little better --- internal/unifi/client.go | 9 +-------- internal/unifi/provider.go | 8 ++++++++ pkg/webhook/webhook.go | 1 + 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/internal/unifi/client.go b/internal/unifi/client.go index 8d9de1f..93fe91a 100644 --- a/internal/unifi/client.go +++ b/internal/unifi/client.go @@ -279,10 +279,8 @@ func (c *httpClient) CreateEndpoint(endpoint *endpoint.Endpoint) (*DNSRecord, er // DeleteEndpoint deletes a DNS record from the UniFi controller. func (c *httpClient) DeleteEndpoint(endpoint *endpoint.Endpoint) error { - log.Debug("Deleting endpoint", zap.String("dnsName", endpoint.DNSName)) lookup, err := c.lookupIdentifier(endpoint.DNSName, endpoint.RecordType) if err != nil { - log.Error("Failed to lookup identifier", zap.Error(err)) return err } @@ -292,7 +290,6 @@ func (c *httpClient) DeleteEndpoint(endpoint *endpoint.Endpoint) error { } deleteURL := FormatUrl(recordPath, c.Config.Host, c.Config.Site, lookup.ID) - log.Debug("Deleting record", zap.String("url", deleteURL)) _, err = c.doRequest( http.MethodDelete, @@ -303,7 +300,6 @@ func (c *httpClient) DeleteEndpoint(endpoint *endpoint.Endpoint) error { return err } - log.Debug("Successfully deleted endpoint", zap.String("dnsName", endpoint.DNSName)) return nil } @@ -312,7 +308,6 @@ func (c *httpClient) lookupIdentifier(key, recordType string) (*DNSRecord, error log.Debug("Looking up identifier", zap.String("key", key), zap.String("recordType", recordType)) records, err := c.GetEndpoints() if err != nil { - log.Error("Failed to get endpoints", zap.Error(err)) return nil, err } @@ -323,13 +318,11 @@ func (c *httpClient) lookupIdentifier(key, recordType string) (*DNSRecord, error } } - log.Debug("Record not found", zap.String("key", key), zap.String("recordType", recordType)) - return nil, fmt.Errorf("record not found") + return nil, fmt.Errorf("record not found: %s", key) } // setHeaders sets the headers for the HTTP request. func (c *httpClient) setHeaders(req *http.Request) { - log.Debug("Setting headers for request") // Add the saved CSRF header. req.Header.Set("X-CSRF-Token", c.csrf) req.Header.Add("Accept", "application/json") diff --git a/internal/unifi/provider.go b/internal/unifi/provider.go index 33b1b65..863918e 100644 --- a/internal/unifi/provider.go +++ b/internal/unifi/provider.go @@ -4,6 +4,8 @@ import ( "context" "fmt" + "github.com/kashalls/external-dns-provider-unifi/cmd/webhook/init/log" + "go.uber.org/zap" "sigs.k8s.io/external-dns/endpoint" "sigs.k8s.io/external-dns/plan" "sigs.k8s.io/external-dns/provider" @@ -62,13 +64,19 @@ func (p *Provider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) { // ApplyChanges applies a given set of changes in the DNS provider. func (p *Provider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { for _, endpoint := range append(changes.UpdateOld, changes.Delete...) { + log.Debug("deleting endpoint", zap.String("name", endpoint.DNSName), zap.String("type", endpoint.RecordType)) + if err := p.client.DeleteEndpoint(endpoint); err != nil { + log.Error("failed to delete endpoint", zap.String("name", endpoint.DNSName), zap.String("type", endpoint.RecordType), zap.Error(err)) return err } } for _, endpoint := range append(changes.Create, changes.UpdateNew...) { + log.Debug("creating endpoint", zap.String("name", endpoint.DNSName), zap.String("type", endpoint.RecordType)) + if _, err := p.client.CreateEndpoint(endpoint); err != nil { + log.Error("failed to create endpoint", zap.String("name", endpoint.DNSName), zap.String("type", endpoint.RecordType), zap.Error(err)) return err } } diff --git a/pkg/webhook/webhook.go b/pkg/webhook/webhook.go index 36461d3..564c60a 100644 --- a/pkg/webhook/webhook.go +++ b/pkg/webhook/webhook.go @@ -145,6 +145,7 @@ func (p *Webhook) ApplyChanges(w http.ResponseWriter, r *http.Request) { zap.Int("delete", len(changes.Delete)), ).Debug("requesting apply changes") if err := p.provider.ApplyChanges(ctx, &changes); err != nil { + requestLog(r).Error("error when applying changes", zap.Error(err)) w.Header().Set(contentTypeHeader, contentTypePlaintext) w.WriteHeader(http.StatusInternalServerError) return From 9681bd545295e99cdb729138f8dfc728269b0223 Mon Sep 17 00:00:00 2001 From: Jordan Jones Date: Wed, 16 Oct 2024 12:57:51 -0700 Subject: [PATCH 29/37] fix: dry --- internal/unifi/client.go | 49 +++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/internal/unifi/client.go b/internal/unifi/client.go index 93fe91a..199de7b 100644 --- a/internal/unifi/client.go +++ b/internal/unifi/client.go @@ -17,17 +17,23 @@ import ( "go.uber.org/zap" ) +type ClientURLs struct { + Login string + Records string +} + // httpClient is the DNS provider client. type httpClient struct { *Config *http.Client - csrf string + csrf string + ClientURLs *ClientURLs } const ( - unifiLoginPathGateway = "%s/api/auth/login" + unifiLoginPathGateway = "%s/api/auth/login" unifiLoginPathStandalone = "%s/api/login" - unifiRecordPathGateway = "%s/proxy/network/v2/api/site/%s/static-dns/%s" + unifiRecordPathGateway = "%s/proxy/network/v2/api/site/%s/static-dns/%s" unifiRecordPathStandalone = "%s/v2/api/site/%s/static-dns/%s" ) @@ -49,6 +55,15 @@ func newUnifiClient(config *Config) (*httpClient, error) { }, Jar: jar, }, + ClientURLs: &ClientURLs{ + Login: unifiLoginPathGateway, + Records: unifiRecordPathGateway, + }, + } + + if config.ControllerType == "standalone" { + client.ClientURLs.Login = unifiLoginPathStandalone + client.ClientURLs.Records = unifiRecordPathStandalone } if err := client.login(); err != nil { @@ -60,11 +75,6 @@ func newUnifiClient(config *Config) (*httpClient, error) { // login performs a login request to the UniFi controller. func (c *httpClient) login() error { - loginPath := unifiLoginPathGateway - if c.Config.ControllerType == "standalone" { - loginPath = unifiLoginPathStandalone - } - jsonBody, err := json.Marshal(Login{ Username: c.Config.User, Password: c.Config.Password, @@ -77,7 +87,7 @@ func (c *httpClient) login() error { // Perform the login request resp, err := c.doRequest( http.MethodPost, - FormatUrl(loginPath, c.Config.Host), + FormatUrl(c.ClientURLs.Login, c.Config.Host), bytes.NewBuffer(jsonBody), ) if err != nil { @@ -202,14 +212,10 @@ func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Respo // GetEndpoints retrieves the list of DNS records from the UniFi controller. func (c *httpClient) GetEndpoints() ([]DNSRecord, error) { log.Debug("Getting endpoints") - recordPath := unifiRecordPathGateway - if c.Config.ControllerType == "standalone" { - recordPath = unifiRecordPathStandalone - } resp, err := c.doRequest( http.MethodGet, - FormatUrl(recordPath, c.Config.Host, c.Config.Site), + FormatUrl(c.ClientURLs.Records, c.Config.Host, c.Config.Site), nil, ) if err != nil { @@ -234,11 +240,7 @@ func (c *httpClient) GetEndpoints() ([]DNSRecord, error) { // CreateEndpoint creates a new DNS record in the UniFi controller. func (c *httpClient) CreateEndpoint(endpoint *endpoint.Endpoint) (*DNSRecord, error) { - log.Debug("Creating endpoint", zap.String("dnsName", endpoint.DNSName)) - recordPath := unifiRecordPathGateway - if c.Config.ControllerType == "standalone" { - recordPath = unifiRecordPathStandalone - } + log.Debug("Creating endpoint", zap.String("key", endpoint.DNSName)) record := DNSRecord{ Enabled: true, @@ -257,7 +259,7 @@ func (c *httpClient) CreateEndpoint(endpoint *endpoint.Endpoint) (*DNSRecord, er resp, err := c.doRequest( http.MethodPost, - FormatUrl(recordPath, c.Config.Host, c.Config.Site), + FormatUrl(c.ClientURLs.Records, c.Config.Host, c.Config.Site), bytes.NewReader(jsonBody), ) if err != nil { @@ -284,12 +286,7 @@ func (c *httpClient) DeleteEndpoint(endpoint *endpoint.Endpoint) error { return err } - recordPath := unifiRecordPathGateway - if c.Config.ControllerType == "standalone" { - recordPath = unifiRecordPathStandalone - } - - deleteURL := FormatUrl(recordPath, c.Config.Host, c.Config.Site, lookup.ID) + deleteURL := FormatUrl(c.ClientURLs.Records, c.Config.Host, c.Config.Site, lookup.ID) _, err = c.doRequest( http.MethodDelete, From f6389c2f98af093a78ac514123dd7e05451992d3 Mon Sep 17 00:00:00 2001 From: Jordan Jones Date: Wed, 16 Oct 2024 13:08:09 -0700 Subject: [PATCH 30/37] chore: move external flag to bool --- README.md | 18 +++++++++--------- cmd/webhook/init/dnsprovider/dnsprovider.go | 4 ---- internal/unifi/client.go | 4 +--- internal/unifi/types.go | 12 ++++++------ 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index e9ce7ff..b2a85ec 100644 --- a/README.md +++ b/README.md @@ -93,15 +93,15 @@ ### Unifi Controller Configuration -| Environment Variable | Description | Default Value | -|-------------------------|--------------------------------------------------------------|---------------| -| `UNIFI_USER` | Username for the Unifi Controller (must be provided). | N/A | -| `UNIFI_SKIP_TLS_VERIFY` | Whether to skip TLS verification (true or false). | `true` | -| `UNIFI_SITE` | Unifi Site Identifier (used in multi-site installations) | `default` | -| `UNIFI_PASS` | Password for the Unifi Controller (must be provided). | N/A | -| `UNIFI_HOST` | Host of the Unifi Controller (must be provided). | N/A | -| `UNIFI_CONTROLLER_TYPE` | Type of Unifi controller (e.g., gateway or standalone). | `gateway` | -| `LOG_LEVEL` | Change the verbosity of logs (used when making a bug report) | `info` | +| Environment Variable | Description | Default Value | +|-----------------------------|--------------------------------------------------------------|---------------| +| `UNIFI_USER` | Username for the Unifi Controller (must be provided). | N/A | +| `UNIFI_SKIP_TLS_VERIFY` | Whether to skip TLS verification (true or false). | `true` | +| `UNIFI_SITE` | Unifi Site Identifier (used in multi-site installations) | `default` | +| `UNIFI_PASS` | Password for the Unifi Controller (must be provided). | N/A | +| `UNIFI_HOST` | Host of the Unifi Controller (must be provided). | N/A | +| `UNIFI_EXTERNAL_CONTROLLER` | Whether your controller is running on official hardware. | `false` | +| `LOG_LEVEL` | Change the verbosity of logs (used when making a bug report) | `info` | ### Server Configuration diff --git a/cmd/webhook/init/dnsprovider/dnsprovider.go b/cmd/webhook/init/dnsprovider/dnsprovider.go index 9273325..a05de29 100644 --- a/cmd/webhook/init/dnsprovider/dnsprovider.go +++ b/cmd/webhook/init/dnsprovider/dnsprovider.go @@ -49,9 +49,5 @@ func Init(config configuration.Config) (provider.Provider, error) { return nil, fmt.Errorf("reading unifi configuration failed: %v", err) } - if unifiConfig.ControllerType != "gateway" && unifiConfig.ControllerType != "standalone" { - return nil, fmt.Errorf("unsupported controller type: %s", unifiConfig.ControllerType) - } - return unifi.NewUnifiProvider(domainFilter, &unifiConfig) } diff --git a/internal/unifi/client.go b/internal/unifi/client.go index 199de7b..f1d4637 100644 --- a/internal/unifi/client.go +++ b/internal/unifi/client.go @@ -39,8 +39,6 @@ const ( // newUnifiClient creates a new DNS provider client and logs in to store cookies. func newUnifiClient(config *Config) (*httpClient, error) { - log.Debug("Creating new UniFi client", zap.String("host", config.Host), zap.String("controllerType", config.ControllerType)) - jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) if err != nil { return nil, err @@ -61,7 +59,7 @@ func newUnifiClient(config *Config) (*httpClient, error) { }, } - if config.ControllerType == "standalone" { + if config.ExternalController { client.ClientURLs.Login = unifiLoginPathStandalone client.ClientURLs.Records = unifiRecordPathStandalone } diff --git a/internal/unifi/types.go b/internal/unifi/types.go index 7e43ce2..b4c536a 100644 --- a/internal/unifi/types.go +++ b/internal/unifi/types.go @@ -4,12 +4,12 @@ import "sigs.k8s.io/external-dns/endpoint" // Config represents the configuration for the UniFi API. type Config struct { - Host string `env:"UNIFI_HOST,notEmpty"` - User string `env:"UNIFI_USER,notEmpty"` - Password string `env:"UNIFI_PASS,notEmpty"` - Site string `env:"UNIFI_SITE" envDefault:"default"` - ControllerType string `env:"UNIFI_CONTROLLER_TYPE" envDefault:"gateway"` - SkipTLSVerify bool `env:"UNIFI_SKIP_TLS_VERIFY" envDefault:"true"` + Host string `env:"UNIFI_HOST,notEmpty"` + User string `env:"UNIFI_USER,notEmpty"` + Password string `env:"UNIFI_PASS,notEmpty"` + Site string `env:"UNIFI_SITE" envDefault:"default"` + ExternalController bool `env:"UNIFI_EXTERNAL_CONTROLLER" envDefault:"false"` + SkipTLSVerify bool `env:"UNIFI_SKIP_TLS_VERIFY" envDefault:"true"` } // Login represents a login request to the UniFi API. From 35be80768d78a23d1359f661bc060b863e3feecc Mon Sep 17 00:00:00 2001 From: Jordan Jones Date: Wed, 16 Oct 2024 13:16:01 -0700 Subject: [PATCH 31/37] chore: way too much logging --- README.md | 18 +++++++++--------- internal/unifi/client.go | 18 ++++++------------ 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index b2a85ec..b8940a6 100644 --- a/README.md +++ b/README.md @@ -93,15 +93,15 @@ ### Unifi Controller Configuration -| Environment Variable | Description | Default Value | -|-----------------------------|--------------------------------------------------------------|---------------| -| `UNIFI_USER` | Username for the Unifi Controller (must be provided). | N/A | -| `UNIFI_SKIP_TLS_VERIFY` | Whether to skip TLS verification (true or false). | `true` | -| `UNIFI_SITE` | Unifi Site Identifier (used in multi-site installations) | `default` | -| `UNIFI_PASS` | Password for the Unifi Controller (must be provided). | N/A | -| `UNIFI_HOST` | Host of the Unifi Controller (must be provided). | N/A | -| `UNIFI_EXTERNAL_CONTROLLER` | Whether your controller is running on official hardware. | `false` | -| `LOG_LEVEL` | Change the verbosity of logs (used when making a bug report) | `info` | +| Environment Variable | Description | Default Value | +|-----------------------------|---------------------------------------------------------------------|---------------| +| `UNIFI_USER` | Username for the Unifi Controller (must be provided). | N/A | +| `UNIFI_SKIP_TLS_VERIFY` | Whether to skip TLS verification (true or false). | `true` | +| `UNIFI_SITE` | Unifi Site Identifier (used in multi-site installations) | `default` | +| `UNIFI_PASS` | Password for the Unifi Controller (must be provided). | N/A | +| `UNIFI_HOST` | Host of the Unifi Controller (must be provided). | N/A | +| `UNIFI_EXTERNAL_CONTROLLER` | Whether your controller is supported by official Ubiquiti hardware. | `false` | +| `LOG_LEVEL` | Change the verbosity of logs (used when making a bug report) | `info` | ### Server Configuration diff --git a/internal/unifi/client.go b/internal/unifi/client.go index f1d4637..3eb2e04 100644 --- a/internal/unifi/client.go +++ b/internal/unifi/client.go @@ -173,22 +173,19 @@ func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Respo log.Debug("Updated CSRF token", zap.String("token", c.csrf)) } - log.Debug("Response received", - zap.String("method", method), - zap.String("path", path), - zap.Int("statusCode", resp.StatusCode)) + log.Debug("recieved response", zap.String("method", method), zap.String("path", path), zap.Int("statusCode", resp.StatusCode)) // If the status code is 401, re-login and retry the request if resp.StatusCode == http.StatusUnauthorized { - log.Debug("Received 401 Unauthorized, attempting to re-login") + log.Debug("received 401 unauthorized, attempting to re-login") if err := c.login(); err != nil { - log.Error("Re-login failed", zap.Error(err)) + log.Error("re-login failed", zap.Error(err)) return nil, err } // Update the headers with new CSRF token c.setHeaders(req) // Retry the request - log.Debug("Retrying request after re-login") + log.Debug("retrying request after re-login") resp, err = c.Client.Do(req) if err != nil { log.Error("Retry request failed", zap.Error(err)) @@ -217,7 +214,7 @@ func (c *httpClient) GetEndpoints() ([]DNSRecord, error) { nil, ) if err != nil { - log.Error("Failed to get endpoints", zap.Error(err)) + log.Error("failed to get endpoints", zap.Error(err)) return nil, err } defer resp.Body.Close() @@ -228,10 +225,7 @@ func (c *httpClient) GetEndpoints() ([]DNSRecord, error) { return nil, err } - log.Debug("Retrieved records", zap.Int("count", len(records))) - for _, record := range records { - log.Debug("Record", zap.Any("record", record)) - } + log.Debug("retrieved records", zap.Int("count", len(records))) return records, nil } From 127c9b02fa36e7e3aa352c1c1e4dd8994efcbedc Mon Sep 17 00:00:00 2001 From: Jordan Jones Date: Wed, 16 Oct 2024 13:17:02 -0700 Subject: [PATCH 32/37] chore: remove too much debug --- internal/unifi/client.go | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/internal/unifi/client.go b/internal/unifi/client.go index 3eb2e04..39a3180 100644 --- a/internal/unifi/client.go +++ b/internal/unifi/client.go @@ -143,29 +143,15 @@ func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Respo c.setHeaders(req) - // Log all request headers - for name, values := range req.Header { - for _, value := range values { - log.Debug("Request header", zap.String("name", name), zap.String("value", value)) - } - } - resp, err := c.Client.Do(req) if err != nil { log.Error("Request failed", zap.Error(err)) return nil, err } - // Log all response headers - for name, values := range resp.Header { - for _, value := range values { - log.Debug("Response header", zap.String("name", name), zap.String("value", value)) - } - } - // Log response body respBody, _ := io.ReadAll(resp.Body) - log.Debug("Response body", zap.String("body", string(respBody))) + log.Debug("response body", zap.String("body", string(respBody))) resp.Body = io.NopCloser(bytes.NewBuffer(respBody)) // Restore the response body for further use if csrf := resp.Header.Get("X-CSRF-Token"); csrf != "" { From 9aa75e896a5b33793afd47673a3792d8dd3b3018 Mon Sep 17 00:00:00 2001 From: Jordan Jones Date: Wed, 16 Oct 2024 13:37:32 -0700 Subject: [PATCH 33/37] chore: remove logs for bubble up errors --- internal/unifi/client.go | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/internal/unifi/client.go b/internal/unifi/client.go index 39a3180..185b328 100644 --- a/internal/unifi/client.go +++ b/internal/unifi/client.go @@ -89,7 +89,6 @@ func (c *httpClient) login() error { bytes.NewBuffer(jsonBody), ) if err != nil { - log.Error("Login request failed", zap.Error(err)) return err } @@ -98,9 +97,7 @@ func (c *httpClient) login() error { // Check if the login was successful if resp.StatusCode != http.StatusOK { respBody, _ := io.ReadAll(resp.Body) - log.Error("Login failed", - zap.String("status", resp.Status), - zap.String("response", string(respBody))) + log.Error("login failed", zap.String("status", resp.Status), zap.String("response", string(respBody))) return fmt.Errorf("login failed: %s", resp.Status) } @@ -112,7 +109,7 @@ func (c *httpClient) login() error { } func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Response, error) { - log.Debug("Making request", zap.String("method", method), zap.String("path", path)) + log.Debug("Do request", zap.String("method", method), zap.String("path", path)) // Convert body to bytes for logging and reuse var bodyBytes []byte @@ -124,7 +121,6 @@ func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Respo req, err := http.NewRequest(method, path, body) if err != nil { - log.Error("Failed to create request", zap.Error(err)) return nil, err } // Set the required headers @@ -134,13 +130,10 @@ func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Respo // Dynamically set the Host header parsedURL, err := url.Parse(path) if err != nil { - log.Error("Failed to parse URL", zap.Error(err)) return nil, err } req.Host = parsedURL.Host - log.Debug("Request host", zap.String("host", req.Host)) - c.setHeaders(req) resp, err := c.Client.Do(req) @@ -180,10 +173,6 @@ func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Respo } if resp.StatusCode != http.StatusOK { - log.Error("Request was not successful", - zap.String("method", method), - zap.String("path", path), - zap.Int("statusCode", resp.StatusCode)) return nil, fmt.Errorf("%s request to %s was not successful: %d", method, path, resp.StatusCode) } @@ -200,7 +189,6 @@ func (c *httpClient) GetEndpoints() ([]DNSRecord, error) { nil, ) if err != nil { - log.Error("failed to get endpoints", zap.Error(err)) return nil, err } defer resp.Body.Close() @@ -218,8 +206,6 @@ func (c *httpClient) GetEndpoints() ([]DNSRecord, error) { // CreateEndpoint creates a new DNS record in the UniFi controller. func (c *httpClient) CreateEndpoint(endpoint *endpoint.Endpoint) (*DNSRecord, error) { - log.Debug("Creating endpoint", zap.String("key", endpoint.DNSName)) - record := DNSRecord{ Enabled: true, Key: endpoint.DNSName, @@ -230,10 +216,8 @@ func (c *httpClient) CreateEndpoint(endpoint *endpoint.Endpoint) (*DNSRecord, er jsonBody, err := json.Marshal(record) if err != nil { - log.Error("Failed to marshal record", zap.Error(err)) return nil, err } - log.Debug("Creating record", zap.String("record", string(jsonBody))) resp, err := c.doRequest( http.MethodPost, @@ -241,19 +225,15 @@ func (c *httpClient) CreateEndpoint(endpoint *endpoint.Endpoint) (*DNSRecord, er bytes.NewReader(jsonBody), ) if err != nil { - log.Error("Failed to create endpoint", zap.Error(err)) return nil, err } defer resp.Body.Close() var createdRecord DNSRecord if err = json.NewDecoder(resp.Body).Decode(&createdRecord); err != nil { - log.Error("Failed to decode response", zap.Error(err)) return nil, err } - log.Debug("Created record", zap.Any("record", createdRecord)) - return &createdRecord, nil } From 2aaad6c4d2004e6f02c02af76badf5b50bf4e868 Mon Sep 17 00:00:00 2001 From: Jordan Jones Date: Wed, 16 Oct 2024 13:42:47 -0700 Subject: [PATCH 34/37] fix: formatting --- internal/unifi/client.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/unifi/client.go b/internal/unifi/client.go index 185b328..6140694 100644 --- a/internal/unifi/client.go +++ b/internal/unifi/client.go @@ -163,8 +163,10 @@ func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Respo } // Update the headers with new CSRF token c.setHeaders(req) + // Retry the request log.Debug("retrying request after re-login") + resp, err = c.Client.Do(req) if err != nil { log.Error("Retry request failed", zap.Error(err)) @@ -268,7 +270,6 @@ func (c *httpClient) lookupIdentifier(key, recordType string) (*DNSRecord, error for _, r := range records { if r.Key == key && r.RecordType == recordType { - log.Debug("Found matching record", zap.Any("record", r)) return &r, nil } } @@ -287,9 +288,8 @@ func (c *httpClient) setHeaders(req *http.Request) { if c.Client.Jar != nil { parsedURL, _ := url.Parse(req.URL.String()) cookies := c.Client.Jar.Cookies(parsedURL) - log.Debug("Request cookies", - zap.String("url", req.URL.String()), - zap.Int("cookieCount", len(cookies))) + + log.Debug("Request cookies", zap.String("url", req.URL.String()), zap.Int("cookieCount", len(cookies))) } else { log.Debug("No cookie jar available", zap.String("url", req.URL.String())) } From 09b0910919e36a8f1a3489d9e40411c26c73223a Mon Sep 17 00:00:00 2001 From: Jordan Jones Date: Wed, 16 Oct 2024 13:48:18 -0700 Subject: [PATCH 35/37] chore: update copypasta example + rename vars for understandability --- README.md | 10 +++++----- internal/unifi/client.go | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index b8940a6..1575d80 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ ```yaml fullnameOverride: external-dns-unifi - logLevel: debug + logLevel: &logLevel debug provider: name: webhook webhook: @@ -48,6 +48,8 @@ env: - name: UNIFI_HOST value: https://192.168.1.1 # replace with the address to your UniFi router/controller + - name: UNIFI_EXTERNAL_CONTROLLER + value: false - name: UNIFI_USER valueFrom: secretKeyRef: @@ -58,10 +60,8 @@ secretKeyRef: name: external-dns-unifi-secret key: password - # - name: LOG_LEVEL - # value: debug - - # Apply additional configurations here as needed. + - name: LOG_LEVEL + value: *logLevel livenessProbe: httpGet: path: /healthz diff --git a/internal/unifi/client.go b/internal/unifi/client.go index 6140694..47b125a 100644 --- a/internal/unifi/client.go +++ b/internal/unifi/client.go @@ -31,10 +31,10 @@ type httpClient struct { } const ( - unifiLoginPathGateway = "%s/api/auth/login" - unifiLoginPathStandalone = "%s/api/login" - unifiRecordPathGateway = "%s/proxy/network/v2/api/site/%s/static-dns/%s" - unifiRecordPathStandalone = "%s/v2/api/site/%s/static-dns/%s" + unifiLoginPath = "%s/api/auth/login" + unifiLoginPathExternal = "%s/api/login" + unifiRecordPath = "%s/proxy/network/v2/api/site/%s/static-dns/%s" + unifiRecordPathExternal = "%s/v2/api/site/%s/static-dns/%s" ) // newUnifiClient creates a new DNS provider client and logs in to store cookies. @@ -54,14 +54,14 @@ func newUnifiClient(config *Config) (*httpClient, error) { Jar: jar, }, ClientURLs: &ClientURLs{ - Login: unifiLoginPathGateway, - Records: unifiRecordPathGateway, + Login: unifiLoginPath, + Records: unifiRecordPath, }, } if config.ExternalController { - client.ClientURLs.Login = unifiLoginPathStandalone - client.ClientURLs.Records = unifiRecordPathStandalone + client.ClientURLs.Login = unifiLoginPathExternal + client.ClientURLs.Records = unifiRecordPathExternal } if err := client.login(); err != nil { From 85227d85755d44fa8c1c6887403594eaf6cc6751 Mon Sep 17 00:00:00 2001 From: Jordan Jones Date: Wed, 16 Oct 2024 14:04:52 -0700 Subject: [PATCH 36/37] chore: remove excessive logging --- internal/unifi/client.go | 31 ------------------------------- pkg/webhook/webhook.go | 2 -- 2 files changed, 33 deletions(-) diff --git a/internal/unifi/client.go b/internal/unifi/client.go index 47b125a..4ad03b4 100644 --- a/internal/unifi/client.go +++ b/internal/unifi/client.go @@ -109,51 +109,22 @@ func (c *httpClient) login() error { } func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Response, error) { - log.Debug("Do request", zap.String("method", method), zap.String("path", path)) - - // Convert body to bytes for logging and reuse - var bodyBytes []byte - if body != nil { - bodyBytes, _ = io.ReadAll(body) - body = bytes.NewReader(bodyBytes) - log.Debug("Request body", zap.String("body", string(bodyBytes))) - } - req, err := http.NewRequest(method, path, body) if err != nil { return nil, err } - // Set the required headers - if body != nil { - req.Header.Set("Content-Length", fmt.Sprintf("%d", len(bodyBytes))) - } - // Dynamically set the Host header - parsedURL, err := url.Parse(path) - if err != nil { - return nil, err - } - req.Host = parsedURL.Host c.setHeaders(req) resp, err := c.Client.Do(req) if err != nil { - log.Error("Request failed", zap.Error(err)) return nil, err } - // Log response body - respBody, _ := io.ReadAll(resp.Body) - log.Debug("response body", zap.String("body", string(respBody))) - resp.Body = io.NopCloser(bytes.NewBuffer(respBody)) // Restore the response body for further use - if csrf := resp.Header.Get("X-CSRF-Token"); csrf != "" { c.csrf = csrf - log.Debug("Updated CSRF token", zap.String("token", c.csrf)) } - log.Debug("recieved response", zap.String("method", method), zap.String("path", path), zap.Int("statusCode", resp.StatusCode)) - // If the status code is 401, re-login and retry the request if resp.StatusCode == http.StatusUnauthorized { log.Debug("received 401 unauthorized, attempting to re-login") @@ -183,8 +154,6 @@ func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Respo // GetEndpoints retrieves the list of DNS records from the UniFi controller. func (c *httpClient) GetEndpoints() ([]DNSRecord, error) { - log.Debug("Getting endpoints") - resp, err := c.doRequest( http.MethodGet, FormatUrl(c.ClientURLs.Records, c.Config.Host, c.Config.Site), diff --git a/pkg/webhook/webhook.go b/pkg/webhook/webhook.go index 564c60a..d61a04c 100644 --- a/pkg/webhook/webhook.go +++ b/pkg/webhook/webhook.go @@ -97,7 +97,6 @@ func (p *Webhook) Records(w http.ResponseWriter, r *http.Request) { return } - requestLog(r).Debug("requesting records") ctx := r.Context() records, err := p.provider.Records(ctx) if err != nil { @@ -106,7 +105,6 @@ func (p *Webhook) Records(w http.ResponseWriter, r *http.Request) { return } - requestLog(r).With(zap.Int("count", len(records))).Debug("returning records") w.Header().Set(contentTypeHeader, string(mediaTypeVersion1)) w.Header().Set(varyHeader, contentTypeHeader) err = json.NewEncoder(w).Encode(records) From f22589281ae9c7bd1718701c9d6fe7a77592a4c3 Mon Sep 17 00:00:00 2001 From: Jordan Jones Date: Wed, 16 Oct 2024 14:08:38 -0700 Subject: [PATCH 37/37] chore: don't log if we have cookies every request --- internal/unifi/client.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/internal/unifi/client.go b/internal/unifi/client.go index 4ad03b4..dfb943d 100644 --- a/internal/unifi/client.go +++ b/internal/unifi/client.go @@ -8,7 +8,6 @@ import ( "io" "net/http" "net/http/cookiejar" - "net/url" "github.com/kashalls/external-dns-provider-unifi/cmd/webhook/init/log" "golang.org/x/net/publicsuffix" @@ -252,14 +251,4 @@ func (c *httpClient) setHeaders(req *http.Request) { req.Header.Set("X-CSRF-Token", c.csrf) req.Header.Add("Accept", "application/json") req.Header.Add("Content-Type", "application/json; charset=utf-8") - - // Log the request URL and cookies - if c.Client.Jar != nil { - parsedURL, _ := url.Parse(req.URL.String()) - cookies := c.Client.Jar.Cookies(parsedURL) - - log.Debug("Request cookies", zap.String("url", req.URL.String()), zap.Int("cookieCount", len(cookies))) - } else { - log.Debug("No cookie jar available", zap.String("url", req.URL.String())) - } }