diff --git a/README.markdown b/README.markdown
index 5bd2dfe4..a0c05814 100644
--- a/README.markdown
+++ b/README.markdown
@@ -82,7 +82,7 @@ _(Click to expand the following items.)_
```bash
docker run \
--network host \
- -e CF_API_TOKEN=YOUR-CLOUDFLARE-API-TOKEN \
+ -e CLOUDFLARE_API_TOKEN=YOUR-CLOUDFLARE-API-TOKEN \
-e DOMAINS=example.org,www.example.org,example.io \
-e PROXIED=true \
favonia/cloudflare-ddns:latest
@@ -95,7 +95,7 @@ docker run \
You need the [Go tool](https://golang.org/doc/install) to run the updater from its source.
```bash
-CF_API_TOKEN=YOUR-CLOUDFLARE-API-TOKEN \
+CLOUDFLARE_API_TOKEN=YOUR-CLOUDFLARE-API-TOKEN \
DOMAINS=example.org,www.example.org,example.io \
PROXIED=true \
go run github.com/favonia/cloudflare-ddns/cmd/ddns@latest
@@ -132,7 +132,7 @@ services:
security_opt: [no-new-privileges:true]
# Another protection to restrict superuser privileges (optional but recommended)
environment:
- - CF_API_TOKEN=YOUR-CLOUDFLARE-API-TOKEN
+ - CLOUDFLARE_API_TOKEN=YOUR-CLOUDFLARE-API-TOKEN
# Your Cloudflare API token
- DOMAINS=example.org,www.example.org,example.io
# Your domains (separated by commas)
@@ -143,15 +143,14 @@ services:
_(Click to expand the following important tips.)_
-🔑 CF_API_TOKEN
is your Cloudflare API token
+🔑 CLOUDFLARE_API_TOKEN
is your Cloudflare API token
-The value of `CF_API_TOKEN` should be an API **token** (_not_ an API key), which can be obtained from the [API Tokens page](https://dash.cloudflare.com/profile/api-tokens). (The less secure API key authentication is deliberately _not_ supported.)
+The value of `CLOUDFLARE_API_TOKEN` should be an API **token** (_not_ an API key), which can be obtained from the [API Tokens page](https://dash.cloudflare.com/profile/api-tokens). The less secure API key authentication is deliberately _not_ supported.
- To update only DNS records, use the **Edit zone DNS** template to create a token.
- To update only WAF lists, choose **Create Custom Token** and then add the **Account - Account Filter Lists - Edit** permission to create a token.
-- To update DNS records _and_ WAF lists, use the **Edit zone DNS** template and then add the **Account - Account Filter Lists - Edit** permission when creating the token.
-
-You can also adjust the permissions of existing tokens at any time!
+- To update _both_ DNS records _and_ WAF lists, use the **Edit zone DNS** template and then add the **Account - Account Filter Lists - Edit** permission when creating the token.
+- You can adjust the permissions of existing tokens at any time!
@@ -259,15 +258,20 @@ _(Click to expand the following items.)_
🔑 The Cloudflare API token
-> Exactly one of the following variables should be set.
+> Starting with version 1.15.0, the updater supports environment variables that begin with `CLOUDFLARE_*`. Multiple environment variables can be used at the same time, provided they all specify the same token.
-| Name | Meaning |
-| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
-| `CF_API_TOKEN` | The [Cloudflare API token](https://dash.cloudflare.com/profile/api-tokens) to access the Cloudflare API |
-| `CF_API_TOKEN_FILE` | A path to a file that contains the [Cloudflare API token](https://dash.cloudflare.com/profile/api-tokens) to access the Cloudflare API |
+| Name | Meaning |
+| ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
+| `CLOUDFLARE_API_TOKEN` | The [Cloudflare API token](https://dash.cloudflare.com/profile/api-tokens) to access the Cloudflare API |
+| `CLOUDFLARE_API_TOKEN_FILE` | A path to a file that contains the [Cloudflare API token](https://dash.cloudflare.com/profile/api-tokens) to access the Cloudflare API |
+| `CF_API_TOKEN` (will be deprecated in version 2.0) | Same as `CLOUDFLARE_API_TOKEN` |
+| `CF_API_TOKEN_FILE` (will be deprecated version in 2.0) | Same as `CLOUDFLARE_API_TOKEN_FILE` |
-- 🔑 To update DNS records, the updater needs the **Account - Account Filter Lists - Edit** permission.
-- 🔑 To manipulate WAF lists, the updater needs the **Zone - DNS - Edit** permission.
+> 🚂 Cloudflare is updating its tools to use environment variables starting with `CLOUDFLARE_*` instead of `CF_*`. It is recommended to align your setting to align with this new convention. However, the updater will fully support both `CLOUDFLARE_*` and `CF_*` environment variables until version 2.0.
+>
+> 🔑 To update DNS records, the updater needs the **Account - Account Filter Lists - Edit** permission.
+>
+> 🔑 To manipulate WAF lists, the updater needs the **Zone - DNS - Edit** permission.
@@ -423,8 +427,8 @@ _(Click to expand the following items.)_
| Old Parameter | | Note |
| -------------------------------------- | --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `API_KEY=key` | ✔️ | Use `CF_API_TOKEN=key` |
-| `API_KEY_FILE=file` | ✔️ | Use `CF_API_TOKEN_FILE=file` |
+| `API_KEY=key` | ✔️ | Use `CLOUDFLARE_API_TOKEN=key` |
+| `API_KEY_FILE=file` | ✔️ | Use `CLOUDFLARE_API_TOKEN_FILE=file` |
| `ZONE=example.org` and `SUBDOMAIN=sub` | ✔️ | Use `DOMAINS=sub.example.org` directly |
| `PROXIED=true` | ✔️ | Same (`PROXIED=true`) |
| `RRTYPE=A` | ✔️ | Both IPv4 and IPv6 are enabled by default; use `IP6_PROVIDER=none` to disable IPv6 |
@@ -441,7 +445,7 @@ _(Click to expand the following items.)_
| Old JSON Key | | Note |
| ------------------------------------- | --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `cloudflare.authentication.api_token` | ✔️ | Use `CF_API_TOKEN=key` |
+| `cloudflare.authentication.api_token` | ✔️ | Use `CLOUDFLARE_API_TOKEN=key` |
| `cloudflare.authentication.api_key` | ❌ | Please use the newer, more secure [API tokens](https://dash.cloudflare.com/profile/api-tokens) |
| `cloudflare.zone_id` | ✔️ | Not needed; automatically retrieved from the server |
| `cloudflare.subdomains[].name` | ✔️ | Use `DOMAINS` with [**fully qualified domain names (FQDNs)**](https://en.wikipedia.org/wiki/Fully_qualified_domain_name) directly; for example, if your zone is `example.org` and your subdomain is `sub`, use `DOMAINS=sub.example.org` |
diff --git a/internal/config/config_read_test.go b/internal/config/config_read_test.go
index 8ee6e446..45d71e2f 100644
--- a/internal/config/config_read_test.go
+++ b/internal/config/config_read_test.go
@@ -19,6 +19,7 @@ import (
func unsetAll(t *testing.T) {
t.Helper()
unset(t,
+ "CLOUDFLARE_API_TOKEN", "CLOUDFLARE_API_TOKEN_FILE",
"CF_API_TOKEN", "CF_API_TOKEN_FILE", "CF_ACCOUNT_ID",
"IP4_PROVIDER", "IP6_PROVIDER",
"DOMAINS", "IP4_DOMAINS", "IP6_DOMAINS", "WAF_LISTS",
@@ -43,7 +44,7 @@ func TestReadEnvWithOnlyToken(t *testing.T) {
mockCtrl := gomock.NewController(t)
unsetAll(t)
- store(t, "CF_API_TOKEN", "deadbeaf")
+ store(t, "CLOUDFLARE_API_TOKEN", "deadbeaf")
var cfg config.Config
mockPP := mocks.NewMockPP(mockCtrl)
@@ -79,7 +80,8 @@ func TestReadEnvEmpty(t *testing.T) {
mockPP.EXPECT().IsShowing(pp.Info).Return(true),
mockPP.EXPECT().Infof(pp.EmojiEnvVars, "Reading settings . . ."),
mockPP.EXPECT().Indent().Return(innerMockPP),
- innerMockPP.EXPECT().Noticef(pp.EmojiUserError, "Needs either CF_API_TOKEN or CF_API_TOKEN_FILE"),
+ innerMockPP.EXPECT().Noticef(pp.EmojiUserError,
+ "Needs either %s or %s", "CLOUDFLARE_API_TOKEN", "CLOUDFLARE_API_TOKEN_FILE"),
)
ok := cfg.ReadEnv(mockPP)
require.False(t, ok)
diff --git a/internal/config/env_auth.go b/internal/config/env_auth.go
index 864d1908..b0e4e330 100644
--- a/internal/config/env_auth.go
+++ b/internal/config/env_auth.go
@@ -10,48 +10,128 @@ import (
var oauthBearerRegex = regexp.MustCompile(`^[-a-zA-Z0-9._~+/]+=*$`)
-func readAuthToken(ppfmt pp.PP) (string, bool) {
- var (
- token = Getenv("CF_API_TOKEN")
- tokenFile = Getenv("CF_API_TOKEN_FILE")
- )
- var ok bool
+// Keys of environment variables.
+const (
+ TokenKey1 string = "CLOUDFLARE_API_TOKEN" //nolint:gosec
+ TokenKey2 string = "CF_API_TOKEN" //nolint:gosec
+ TokenFileKey1 string = "CLOUDFLARE_API_TOKEN_FILE" //nolint:gosec
+ TokenFileKey2 string = "CF_API_TOKEN_FILE" //nolint:gosec
+)
+
+// HintAuthTokenNewPrefix contains the hint about the transition from
+// CF_* to CLOUDFLARE_*.
+const HintAuthTokenNewPrefix string = "Cloudflare is transitioning its tools to use the prefix CLOUDFLARE instead of CF. To align with this change, it is recommended to use CLOUDFLARE_API_TOKEN (or CLOUDFLARE_API_TOKEN_FILE) instead of CF_API_TOKEN (or CF_API_TOKEN_FILE) moving forward. All options will be fully supported until version 2.0." //nolint:lll,gosec
+
+func readPlainAuthTokens(ppfmt pp.PP) (string, string, bool) {
+ token1 := Getenv(TokenKey1)
+ token2 := Getenv(TokenKey2)
+
+ var token, tokenKey string
+ switch {
+ case token1 == "" && token2 == "":
+ return "", "", true
+ case token1 != "" && token2 != "" && token1 != token2:
+ ppfmt.Noticef(pp.EmojiUserError,
+ "The values of %s and %s do not match; they must specify the same token", TokenKey1, TokenKey2)
+ return "", "", false
+ case token1 != "":
+ token, tokenKey = token1, TokenKey1
+ case token2 != "":
+ ppfmt.Hintf(pp.HintAuthTokenNewPrefix, HintAuthTokenNewPrefix)
+ token, tokenKey = token2, TokenKey2
+ }
- // foolproof checks
+ // foolproof check: the sample value in README
if token == "YOUR-CLOUDFLARE-API-TOKEN" {
- ppfmt.Noticef(pp.EmojiUserError, "You need to provide a real API token as CF_API_TOKEN")
+ ppfmt.Noticef(pp.EmojiUserError, "You need to provide a real API token as %s", tokenKey)
+ return "", "", false
+ }
+
+ return token, tokenKey, true
+}
+
+func readAuthTokenFile(ppfmt pp.PP, key string) (string, bool) {
+ tokenFile := Getenv(key)
+ if tokenFile == "" {
+ return "", true
+ }
+
+ token, ok := file.ReadString(ppfmt, tokenFile)
+ if !ok {
+ return "", false
+ }
+
+ if token == "" {
+ ppfmt.Noticef(pp.EmojiUserError, "The file specified by %s does not contain an API token", key)
+ return "", false
+ }
+
+ return token, true
+}
+
+func readAuthTokenFiles(ppfmt pp.PP) (string, string, bool) {
+ token1, ok := readAuthTokenFile(ppfmt, TokenFileKey1)
+ if !ok {
+ return "", "", false
+ }
+
+ token2, ok := readAuthTokenFile(ppfmt, TokenFileKey2)
+ if !ok {
+ return "", "", false
+ }
+
+ switch {
+ case token1 != "" && token2 != "" && token1 != token2:
+ ppfmt.Noticef(pp.EmojiUserError,
+ "The files specified by %s and %s have conflicting tokens; their content must match", TokenFileKey1, TokenFileKey2)
+ return "", "", false
+ case token1 != "":
+ return token1, TokenFileKey1, true
+ case token2 != "":
+ ppfmt.Hintf(pp.HintAuthTokenNewPrefix, HintAuthTokenNewPrefix)
+ return token2, TokenFileKey2, true
+ default:
+ return "", "", true
+ }
+}
+
+func readAuthToken(ppfmt pp.PP) (string, bool) {
+ tokenPlain, tokenPlainKey, ok := readPlainAuthTokens(ppfmt)
+ if !ok {
+ return "", false
+ }
+
+ tokenFile, tokenFileKey, ok := readAuthTokenFiles(ppfmt)
+ if !ok {
return "", false
}
+ var token string
switch {
- case token != "" && tokenFile != "":
- ppfmt.Noticef(pp.EmojiUserError, "Cannot have both CF_API_TOKEN and CF_API_TOKEN_FILE set")
+ case tokenPlain != "" && tokenFile != "" && tokenPlain != tokenFile:
+ ppfmt.Noticef(pp.EmojiUserError,
+ "The value of %s does not match the token found in the file specified by %s; they must specify the same token",
+ tokenPlainKey, tokenFileKey)
return "", false
- case token != "":
+ case tokenPlain != "":
+ token = tokenPlain
case tokenFile != "":
- token, ok = file.ReadString(ppfmt, tokenFile)
- if !ok {
- return "", false
- }
-
- if token == "" {
- ppfmt.Noticef(pp.EmojiUserError, "The token in the file specified by CF_API_TOKEN_FILE is empty")
- return "", false
- }
+ token = tokenFile
default:
- ppfmt.Noticef(pp.EmojiUserError, "Needs either CF_API_TOKEN or CF_API_TOKEN_FILE")
+ ppfmt.Noticef(pp.EmojiUserError, "Needs either %s or %s", TokenKey1, TokenFileKey1)
return "", false
}
if !oauthBearerRegex.MatchString(token) {
- ppfmt.Noticef(pp.EmojiUserWarning, "The API token does not look like a valid OAuth2 bearer token")
+ ppfmt.Noticef(pp.EmojiUserWarning,
+ "The API token appears to be invalid; it does not follow the OAuth2 bearer token format")
}
return token, true
}
-// ReadAuth reads environment variables CF_API_TOKEN, CF_API_TOKEN_FILE, and CF_ACCOUNT_ID
-// and creates an [api.CloudflareAuth].
+// ReadAuth reads environment variables CLOUDFLARE_API_TOKEN, CLOUDFLARE_API_TOKEN_FILE,
+// CF_API_TOKEN, CF_API_TOKEN_FILE, and CF_ACCOUNT_ID and creates an [api.CloudflareAuth].
func ReadAuth(ppfmt pp.PP, field *api.Auth) bool {
token, ok := readAuthToken(ppfmt)
if !ok {
diff --git a/internal/config/env_auth_test.go b/internal/config/env_auth_test.go
index 5bc357f2..a469cd24 100644
--- a/internal/config/env_auth_test.go
+++ b/internal/config/env_auth_test.go
@@ -15,103 +15,161 @@ import (
"github.com/favonia/cloudflare-ddns/internal/pp"
)
-//nolint:paralleltest // environment vars are global
-func TestReadAuth(t *testing.T) {
- unset(t, "CF_API_TOKEN", "CF_API_TOKEN_FILE", "CF_ACCOUNT_ID")
+func useMemFS(memfs fstest.MapFS) {
+ file.FS = memfs
+}
+//nolint:paralleltest // environment vars and file system are global
+func TestReadAuth(t *testing.T) {
for name, tc := range map[string]struct {
- token string
- account string
- ok bool
- prepareMockPP func(*mocks.MockPP)
+ mapFS map[string]string
+ token1 string
+ token2 string
+ fileToken1Path string
+ fileToken2Path string
+ account string
+ ok bool
+ expected string
+ prepareMockPP func(*mocks.MockPP)
}{
- "success": {"123456789", "", true, nil},
- "empty-token": {
- "", "account", false,
+ "success": {
+ map[string]string{"token.txt": "hello"},
+ "123456789", "", "", "", "",
+ true, "123456789", nil,
+ },
+ "empty": {
+ map[string]string{},
+ "", "", "", "", "",
+ false, "",
func(m *mocks.MockPP) {
- m.EXPECT().Noticef(pp.EmojiUserError, "Needs either CF_API_TOKEN or CF_API_TOKEN_FILE")
+ m.EXPECT().Noticef(pp.EmojiUserError,
+ "Needs either %s or %s", "CLOUDFLARE_API_TOKEN", "CLOUDFLARE_API_TOKEN_FILE")
},
},
+ "conflicting": {
+ map[string]string{},
+ "token1", "token2", "", "", "",
+ false, "",
+ func(m *mocks.MockPP) {
+ m.EXPECT().Noticef(pp.EmojiUserError,
+ "The values of %s and %s do not match; they must specify the same token",
+ "CLOUDFLARE_API_TOKEN", "CF_API_TOKEN")
+ },
+ },
+ "old": {
+ map[string]string{},
+ "", "token2", "", "", "",
+ true, "token2",
+ func(m *mocks.MockPP) {
+ m.EXPECT().Hintf(pp.HintAuthTokenNewPrefix, config.HintAuthTokenNewPrefix)
+ },
+ },
+ "old/same": {
+ map[string]string{},
+ "token", "token", "", "", "",
+ true, "token", nil,
+ },
"invalid": {
- "!!!", "", true,
+ map[string]string{},
+ "!!!", "", "", "", "",
+ true, "!!!",
func(m *mocks.MockPP) {
- m.EXPECT().Noticef(pp.EmojiUserWarning, "The API token does not look like a valid OAuth2 bearer token")
+ m.EXPECT().Noticef(pp.EmojiUserWarning,
+ "The API token appears to be invalid; it does not follow the OAuth2 bearer token format")
},
},
"account": {
- "123456789", "secret account", true,
+ map[string]string{},
+ "123456789", "", "", "", "secret account",
+ true, "123456789",
func(m *mocks.MockPP) {
m.EXPECT().Noticef(pp.EmojiUserWarning, "CF_ACCOUNT_ID is ignored since 1.14.0")
},
},
"copycat": {
- "YOUR-CLOUDFLARE-API-TOKEN", "", false,
+ map[string]string{},
+ "YOUR-CLOUDFLARE-API-TOKEN", "", "", "", "",
+ false, "",
func(m *mocks.MockPP) {
- m.EXPECT().Noticef(pp.EmojiUserError, "You need to provide a real API token as CF_API_TOKEN")
+ m.EXPECT().Noticef(pp.EmojiUserError,
+ "You need to provide a real API token as %s", "CLOUDFLARE_API_TOKEN")
},
},
- } {
- t.Run(name, func(t *testing.T) {
- mockCtrl := gomock.NewController(t)
-
- store(t, "CF_API_TOKEN", tc.token)
- store(t, "CF_ACCOUNT_ID", tc.account)
-
- mockPP := mocks.NewMockPP(mockCtrl)
- if tc.prepareMockPP != nil {
- tc.prepareMockPP(mockPP)
- }
-
- var field api.Auth
- ok := config.ReadAuth(mockPP, &field)
- require.Equal(t, tc.ok, ok)
- if tc.ok {
- require.Equal(t, &api.CloudflareAuth{Token: tc.token, BaseURL: ""}, field)
- } else {
- require.Nil(t, field)
- }
- })
- }
-}
-
-func useMemFS(memfs fstest.MapFS) {
- file.FS = memfs
-}
-
-//nolint:paralleltest // environment vars and file system are global
-func TestReadAuthWithFile(t *testing.T) {
- unset(t, "CF_API_TOKEN", "CF_API_TOKEN_FILE", "CF_ACCOUNT_ID")
-
- for name, tc := range map[string]struct {
- token string
- tokenFile string
- actualPath string
- actualContent string
- expected string
- ok bool
- prepareMockPP func(*mocks.MockPP)
- }{
- "ok": {"", "test.txt", "test.txt", "hello", "hello", true, nil},
- "both": {
- "123456789", "test.txt", "test.txt", "hello", "", false,
+ "file/success": {
+ map[string]string{"token.txt": "hello"},
+ "", "", "token.txt", "", "",
+ true, "hello", nil,
+ },
+ "file/empty": {
+ map[string]string{"empty.txt": ""},
+ "", "", "empty.txt", "", "",
+ false, "",
func(m *mocks.MockPP) {
- m.EXPECT().Noticef(pp.EmojiUserError, "Cannot have both CF_API_TOKEN and CF_API_TOKEN_FILE set")
+ m.EXPECT().Noticef(pp.EmojiUserError,
+ "The file specified by %s does not contain an API token",
+ "CLOUDFLARE_API_TOKEN_FILE")
},
},
- "wrong.path": {
- "", "wrong.txt", "actual.txt", "hello", "", false,
+ "file/conflicting": {
+ map[string]string{"token1.txt": "hello1", "token2.txt": "hello2"},
+ "", "", "token1.txt", "token2.txt", "",
+ false, "",
+ func(m *mocks.MockPP) {
+ m.EXPECT().Noticef(pp.EmojiUserError,
+ "The files specified by %s and %s have conflicting tokens; their content must match",
+ "CLOUDFLARE_API_TOKEN_FILE", "CF_API_TOKEN_FILE")
+ },
+ },
+ "file/conflicting/non-file": {
+ map[string]string{"token.txt": "file"},
+ "plain", "", "token.txt", "", "",
+ false, "",
+ func(m *mocks.MockPP) {
+ m.EXPECT().Noticef(pp.EmojiUserError,
+ "The value of %s does not match the token found in the file specified by %s; "+
+ "they must specify the same token",
+ "CLOUDFLARE_API_TOKEN", "CLOUDFLARE_API_TOKEN_FILE",
+ )
+ },
+ },
+ "file/same/non-file": {
+ map[string]string{"token.txt": "token"},
+ "token", "", "token.txt", "", "",
+ true, "token", nil,
+ },
+ "file/old": {
+ map[string]string{"token.txt": "hello"},
+ "", "", "", "token.txt", "",
+ true, "hello",
+ func(m *mocks.MockPP) {
+ m.EXPECT().Hintf(pp.HintAuthTokenNewPrefix, config.HintAuthTokenNewPrefix)
+ },
+ },
+ "file/old/same": {
+ map[string]string{"token1.txt": "hello", "token2.txt": "hello"},
+ "", "", "token1.txt", "token2.txt", "",
+ true, "hello", nil,
+ },
+ "file/wrong.path": {
+ map[string]string{},
+ "", "", "wrong.txt", "", "",
+ false, "",
func(m *mocks.MockPP) {
m.EXPECT().Noticef(pp.EmojiUserError, "Failed to read %q: %v", "wrong.txt", gomock.Any())
},
},
- "empty": {
- "", "test.txt", "test.txt", "", "", false,
+ "file/wrong.path/2": {
+ map[string]string{},
+ "", "", "", "wrong.txt", "",
+ false, "",
func(m *mocks.MockPP) {
- m.EXPECT().Noticef(pp.EmojiUserError, "The token in the file specified by CF_API_TOKEN_FILE is empty")
+ m.EXPECT().Noticef(pp.EmojiUserError, "Failed to read %q: %v", "wrong.txt", gomock.Any())
},
},
- "invalid path": {
- "", "dir", "dir/test.txt", "hello", "", false,
+ "file/invalid-directory": {
+ map[string]string{"dir/file.txt": ""},
+ "", "", "dir", "", "",
+ false, "",
func(m *mocks.MockPP) {
m.EXPECT().Noticef(pp.EmojiUserError, "Failed to read %q: %v", "dir", gomock.Any())
},
@@ -120,17 +178,22 @@ func TestReadAuthWithFile(t *testing.T) {
t.Run(name, func(t *testing.T) {
mockCtrl := gomock.NewController(t)
- store(t, "CF_API_TOKEN", tc.token)
- store(t, "CF_API_TOKEN_FILE", tc.tokenFile)
+ store(t, "CLOUDFLARE_API_TOKEN", tc.token1)
+ store(t, "CLOUDFLARE_API_TOKEN_FILE", tc.fileToken1Path)
+ store(t, "CF_API_TOKEN", tc.token2)
+ store(t, "CF_API_TOKEN_FILE", tc.fileToken2Path)
+ store(t, "CF_ACCOUNT_ID", tc.account)
- useMemFS(fstest.MapFS{
- tc.actualPath: &fstest.MapFile{
- Data: []byte(tc.actualContent),
+ mapFS := fstest.MapFS{}
+ for path, content := range tc.mapFS {
+ mapFS[path] = &fstest.MapFile{
+ Data: []byte(content),
Mode: 0o644,
ModTime: time.Unix(1234, 5678),
Sys: nil,
- },
- })
+ }
+ }
+ useMemFS(mapFS)
var field api.Auth
mockPP := mocks.NewMockPP(mockCtrl)
diff --git a/internal/pp/hint.go b/internal/pp/hint.go
index bd533b3a..cf9368cc 100644
--- a/internal/pp/hint.go
+++ b/internal/pp/hint.go
@@ -5,18 +5,19 @@ type Hint int
// All the registered hints.
const (
- HintUpdateDockerTemplate Hint = iota
- HintIP4DetectionFails
- HintIP6DetectionFails
- HintIP4MappedIP6Address
- HintDetectionTimeouts
- HintUpdateTimeouts
- HintRecordPermission
- HintWAFListPermission
- HintMismatchedRecordAttributes
- HintMismatchedWAFListAttributes
- Hint1111Blockage
- HintExperimentalShoutrrr // introduced in 1.12.0
- HintExperimentalWAF // introduced in 1.14.0
- HintExperimentalLocalWithInterface // introduced in 1.15.0
+ HintUpdateDockerTemplate Hint = iota // PUID or PGID was used
+ HintAuthTokenNewPrefix // "CF_*" to "CLOUDFLARE_*"
+ HintIP4DetectionFails // How to turn off IPv4
+ HintIP6DetectionFails // How to set up IPv6 or turn it off
+ HintIP4MappedIP6Address // IPv4-mapped IPv6 addresses are bad for AAAA records
+ HintDetectionTimeouts // Longer detection timeout
+ HintUpdateTimeouts // Longer update timeout
+ HintRecordPermission // Permissions to update DNS tokens
+ HintWAFListPermission // Permissions to update WAF lists
+ HintMismatchedRecordAttributes // Attributes of DNS records have been changed
+ HintMismatchedWAFListAttributes // Attributes of WAF lists have been changed
+ Hint1111Blockage // 1.1.1.1 is blocked
+ HintExperimentalShoutrrr // New feature introduced in 1.12.0 on 2024/6/28
+ HintExperimentalWAF // New feature introduced in 1.14.0 on 2024/8/25
+ HintExperimentalLocalWithInterface // New feature introduced in 1.15.0
)