Skip to content

Containerize baton-cloudflare connector#39

Open
laurenleach wants to merge 2 commits intomainfrom
containerize-cloudflare
Open

Containerize baton-cloudflare connector#39
laurenleach wants to merge 2 commits intomainfrom
containerize-cloudflare

Conversation

@laurenleach
Copy link
Contributor

@laurenleach laurenleach commented Jan 29, 2026

Summary

This PR containerizes the baton-cloudflare connector following the patterns from baton-databricks#35 and baton-contentful#48.

Changes

  • Created pkg/config package with field-based configuration schema
  • Generated configuration struct using field.NewConfiguration
  • Simplified main.go to use config.RunConnector() pattern
  • Updated connector to implement ConnectorBuilderV2 interface
  • Updated resource syncers to use ResourceSyncerV2 (SyncOpAttrs/SyncOpResults)
  • Updated Makefile with config generation and BATON_LAMBDA_SUPPORT
  • Updated GitHub workflows for config schema generation

Testing

  • Build succeeds
  • Generated config schema is valid
  • Connector works in containerized environment

Co-Authored-By: Claude Sonnet 4.5 noreply@anthropic.com

Summary by CodeRabbit

  • New Features

    • Configuration interface now displays grouped authentication options: separate field groups for API Token authentication and Email + API Key authentication for improved clarity.
  • Chores

    • Build system enhanced with Windows executable support and conditional feature flag handling.
    • Refactored authentication configuration structure and connector initialization for improved code organization.
    • Consolidated configuration logic and removed redundant test files.

@laurenleach laurenleach requested a review from a team January 29, 2026 23:15
@coderabbitai
Copy link

coderabbitai bot commented Jan 29, 2026

📝 Walkthrough

Walkthrough

This pull request refactors the Cloudflare connector to align with an updated SDK pattern. Configuration field definitions transition from public exports to a centralized structure with field grouping support. The connector's New() function signature changes to return ConnectorBuilderV2 and options instead of a concrete type. Resource syncing methods across multiple resource types adopt a unified SyncOpAttrs/SyncOpResults signature pattern, replacing pagination tokens and annotations. The build system gains conditional compilation support and generation stages.

Changes

Cohort / File(s) Summary
Configuration Structure
pkg/config/config.go
Field definitions renamed from exported to unexported (ApiKeyField → apiKeyField, etc.), ValidateConfig function removed, configuration now uses preassembled configurationFields slice with field grouping via WithFieldGroups, adds display name, help URL, and icon URL.
Connector Initialization
pkg/connector/connector.go
New() method signature updated to return (ConnectorBuilderV2, []Opt, error) instead of (*Cloudflare, error); ResourceSyncers() returns ResourceSyncerV2 instead of ResourceSyncer; all error handling paths updated to 3-tuple returns.
Data Models
pkg/connector/model.go
Removes exported Config struct; adds unexported emailId field to Cloudflare struct.
Resource Sync Methods
pkg/connector/role.go, pkg/connector/user.go
List, Entitlements, and Grants methods updated to accept rs.SyncOpAttrs instead of pagination.Token; return type changed from ([]Resource, string, annotations.Annotations, error) to ([]Resource, *rs.SyncOpResults, error); pagination handling moved into SyncOpResults.
Build Configuration
Makefile
Adds GENERATED_CONF and OUTPUT_PATH variables with OS-specific handling; introduces conditional BUILD_TAGS based on BATON_LAMBDA_SUPPORT; adds explicit generate target; renames add-dep target to add-deps.
Entry Point & Legacy Config
cmd/baton-cloudflare/main.go, cmd/baton-cloudflare/config.go, cmd/baton-cloudflare/config_test.go
main.go simplifies to use RunConnector instead of DefineConfiguration; removes legacy imports and scaffolding; switches to WithDefaultCapabilitiesConnectorBuilderV2. config.go and config_test.go files removed entirely (consolidating config definitions into pkg/config/).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 The config hops to a grouping way,
With SyncOp patterns guiding the way,
ConnectorBuilderV2 takes the lead,
From tokens and strings, we're freed indeed,
A cleaner path for credentials to play! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'Containerize baton-cloudflare connector' accurately describes the main objective—containerizing the Cloudflare connector—matching the PR objectives which detail the containerization effort following established patterns from other connectors.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch containerize-cloudflare

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/connector/role.go (1)

394-401: Typo: "couldflare" should be "cloudflare".

The error messages on lines 396 and 400 have a typo:

-		"couldflare-connector: only users can have role membership revoked",
+		"cloudflare-connector: only users can have role membership revoked",
...
-	return nil, fmt.Errorf("couldflare-connector: only users can have role membership revoked")
+	return nil, fmt.Errorf("cloudflare-connector: only users can have role membership revoked")
🤖 Fix all issues with AI agents
In `@go.mod`:
- Around line 111-114: The go.mod lists vulnerable indirect dependencies
golang.org/x/crypto v0.34.0 and golang.org/x/oauth2 v0.26.0; update these
transitive modules to fixed releases by running: go get
golang.org/x/crypto@v0.45.0 golang.org/x/oauth2@v0.27.0 and then go mod tidy so
the go.mod entries for golang.org/x/crypto and golang.org/x/oauth2 are bumped to
the patched versions and the dependency graph is cleaned.

In `@pkg/config/config.go`:
- Around line 39-42: The FieldsDependentOn call currently inverts the
dependency: change the argument order so emailIdField depends on apiKeyField by
calling field.FieldsDependentOn([]field.SchemaField{emailIdField},
[]field.SchemaField{apiKeyField}) instead of
field.FieldsDependentOn([]field.SchemaField{apiKeyField},
[]field.SchemaField{emailIdField}); this ensures the required relationship is
correct per the FieldsDependentOn(fields, required) signature.

In `@pkg/connector/connector.go`:
- Around line 33-51: The New function can return a nil client when apiKey is set
but emailId is empty; update New() to validate inputs early: if apiToken is
empty and apiKey provided without emailId, return a clear error (e.g., "apiKey
requires emailId") instead of constructing a Cloudflare with nil client, or more
generally after attempting both cloudflare.NewWithAPIToken and cloudflare.New
check if client == nil and return an error; reference the New() function, the
Cloudflare struct, and the apiToken/apiKey/emailId variables and ensure callers
relying on Validate() get a deterministic failure.
🧹 Nitpick comments (3)
pkg/connector/user.go (2)

81-87: Consider returning &rs.SyncOpResults{} instead of nil for consistency.

The Entitlements and Grants methods return nil for the SyncOpResults pointer, while similar methods in role.go return &rs.SyncOpResults{}. For API consistency, consider returning an empty struct:

 func (o *UserResourceType) Entitlements(_ context.Context, _ *v2.Resource, _ rs.SyncOpAttrs) ([]*v2.Entitlement, *rs.SyncOpResults, error) {
-	return nil, nil, nil
+	return nil, &rs.SyncOpResults{}, nil
 }

 func (o *UserResourceType) Grants(_ context.Context, _ *v2.Resource, _ rs.SyncOpAttrs) ([]*v2.Grant, *rs.SyncOpResults, error) {
-	return nil, nil, nil
+	return nil, &rs.SyncOpResults{}, nil
 }

59-65: Inconsistent error message casing.

The error messages use inconsistent casing for "Cloudflare":

  • Line 59: "Cloudflare: invalid page token error"
  • Line 65: "cloudflare: could not retrieve users: %w"

Consider standardizing to one style (preferably lowercase cloudflare: to match conventional Go error formatting).

pkg/connector/connector.go (1)

33-45: Clarify authentication method precedence.

Both authentication methods can execute if the user provides both apiToken and apiKey+emailId. The current flow allows the API key method to overwrite the token-based client on line 41.

If this precedence is intentional, consider adding an else or a comment. Otherwise, consider using else if to prevent accidental overwrites:

 	if apiToken != "" {
 		client, err = cloudflare.NewWithAPIToken(apiToken, cloudflare.HTTPClient(httpClient))
 		if err != nil {
 			return nil, nil, err
 		}
-	}
-
-	if apiKey != "" && emailId != "" {
+	} else if apiKey != "" && emailId != "" {
 		client, err = cloudflare.New(apiKey, emailId, cloudflare.HTTPClient(httpClient))

go.mod Outdated
Comment on lines +111 to +114
golang.org/x/crypto v0.34.0 // indirect
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/oauth2 v0.26.0 // indirect
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if crypto or oauth2 packages are directly imported anywhere in the codebase
echo "=== Checking for direct usage of x/crypto ==="
rg -l 'golang.org/x/crypto' --type go

echo "=== Checking for direct usage of x/oauth2 ==="
rg -l 'golang.org/x/oauth2' --type go

echo "=== Check go.sum for actual versions ==="
cat go.sum | grep -E 'golang.org/x/(crypto|oauth2)' | head -20

Repository: ConductorOne/baton-cloudflare

Length of output: 3203


🏁 Script executed:

#!/bin/bash
# Check the go.mod file directly to see the full dependency list
echo "=== Full go.mod file showing direct dependencies ==="
cat go.mod

echo -e "\n=== Check if crypto or oauth2 are direct dependencies ==="
grep -E "golang.org/x/(crypto|oauth2)" go.mod

echo -e "\n=== Look for what actually depends on these packages ==="
cat go.sum | grep "golang.org/x/crypto\|golang.org/x/oauth2" | wc -l

Repository: ConductorOne/baton-cloudflare

Length of output: 6792


🌐 Web query:

GO-2025-3487 GO-2025-4116 GO-2025-4134 GO-2025-4135 golang.org/x/crypto vulnerability

💡 Result:

These Go vulnerability IDs all affect golang.org/x/crypto, specifically the ssh and/or ssh/agent packages, and are primarily denial-of-service (panic / memory exhaustion) issues:

Go vuln ID Alias Affected area Impact Fixed in golang.org/x/crypto
GO-2025-3487 CVE-2025-22869 golang.org/x/crypto/ssh DoS: slow/incomplete key exchange can cause pending content to be read into memory but never transmitted (notably for servers implementing file transfer protocols). v0.35.0 (upgrade if < v0.35.0) [1]
GO-2025-4116 CVE-2025-47913 golang.org/x/crypto/ssh/agent DoS: SSH client can panic if it receives SSH_AGENT_SUCCESS when it expects a typed response. v0.43.0 (upgrade if < v0.43.0) [2][3]
GO-2025-4134 CVE-2025-58181 golang.org/x/crypto/ssh DoS: unbounded memory consumption when parsing GSSAPI auth requests due to missing validation of mechanism count. v0.45.0 (upgrade if < v0.45.0) [4]
GO-2025-4135 CVE-2025-47914 golang.org/x/crypto/ssh/agent DoS: agent servers may panic on malformed “new identity” requests due to an out-of-bounds read (message size not validated). v0.45.0 (upgrade if < v0.45.0) [5]

Recommended remediation

To cover all four issues at once, upgrade to golang.org/x/crypto >= v0.45.0 (this is the highest “fixed in” version among the listed vulns) [4][5].

Example:

go get golang.org/x/crypto@v0.45.0
go mod tidy

Sources: [1] [2] [3] [4] [5]


🌐 Web query:

GO-2025-3488 golang.org/x/oauth2 vulnerability

💡 Result:

GO-2025-3488 is a Denial-of-Service (DoS) vulnerability in golang.org/x/oauth2 where a malformed, attacker-controlled token can trigger unexpected/unbounded memory consumption during token parsing. It specifically impacts golang.org/x/oauth2/jws.Verify. [1][2]

  • Aliases: CVE-2025-22868, GHSA-6v2p-p543-phr9 [1][2]
  • Affected: golang.org/x/oauth2 < v0.27.0 (notably golang.org/x/oauth2/jws) [1][2]
  • Fixed in: v0.27.0 [1][2]
  • Severity (commonly reported): CVSS 3.1 7.5 (HIGH) (availability impact) [3]

What to do

  1. Upgrade:

    go get golang.org/x/oauth2@v0.27.0
    go mod tidy

    (Or upgrade to any newer version.) [1][2]

  2. If you can’t upgrade immediately, reduce exposure by ensuring untrusted tokens aren’t fed into jws.Verify (e.g., gate by size/structure before verification), since the issue is triggered by malformed token parsing. [1][2]

Sources: [1] OSV entry for GO-2025-3488, [2] Go Vulnerability Database report, [3] GitLab advisory (CVE-2025-22868).


Upgrade golang.org/x/crypto and golang.org/x/oauth2 to patched versions.

Multiple confirmed HIGH severity vulnerabilities affect these indirect dependencies:

golang.org/x/crypto v0.34.0 (4 DoS vulnerabilities in ssh/agent):

  • GO-2025-3487 (CVE-2025-22869): Memory exhaustion from incomplete key exchange
  • GO-2025-4116 (CVE-2025-47913): Client panic on unexpected SSH_AGENT_SUCCESS
  • GO-2025-4134 (CVE-2025-58181): Unbounded memory consumption in GSSAPI auth parsing
  • GO-2025-4135 (CVE-2025-47914): Agent server panic on malformed "new identity" requests
  • Fixed in v0.45.0+

golang.org/x/oauth2 v0.26.0 (DoS via token parsing):

  • GO-2025-3488 (CVE-2025-22868): Unexpected memory consumption during malformed token parsing
  • Fixed in v0.27.0+

Upgrade to golang.org/x/crypto >= v0.45.0 and golang.org/x/oauth2 >= v0.27.0 via:

go get golang.org/x/crypto@v0.45.0 golang.org/x/oauth2@v0.27.0
go mod tidy

These are transitive dependencies pulled through baton-sdk and other packages. While no direct SSH or OAuth2 code is present in this repository, upgrading eliminates exposure if these packages are exercised through the dependency chain.

🧰 Tools
🪛 OSV Scanner (2.3.2)

[HIGH] 111-111: golang.org/x/crypto 0.34.0: Potential denial of service in golang.org/x/crypto

(GO-2025-3487)


[HIGH] 111-111: golang.org/x/crypto 0.34.0: Potential denial of service in golang.org/x/crypto/ssh/agent

(GO-2025-4116)


[HIGH] 111-111: golang.org/x/crypto 0.34.0: Unbounded memory consumption in golang.org/x/crypto/ssh

(GO-2025-4134)


[HIGH] 111-111: golang.org/x/crypto 0.34.0: Malformed constraint may cause denial of service in golang.org/x/crypto/ssh/agent

(GO-2025-4135)


[HIGH] 111-111: golang.org/x/crypto 0.34.0: golang.org/x/crypto/ssh/agent vulnerable to panic if message is malformed due to out of bounds read

(GHSA-f6x5-jh6r-wrfv)


[HIGH] 111-111: golang.org/x/crypto 0.34.0: golang.org/x/crypto Vulnerable to Denial of Service (DoS) via Slow or Incomplete Key Exchange

(GHSA-hcg3-q754-cr77)


[HIGH] 111-111: golang.org/x/crypto 0.34.0: golang.org/x/crypto/ssh allows an attacker to cause unbounded memory consumption

(GHSA-j5w8-q4qc-rx2x)


[HIGH] 114-114: golang.org/x/oauth2 0.26.0: Unexpected memory consumption during token parsing in golang.org/x/oauth2

(GO-2025-3488)


[HIGH] 114-114: golang.org/x/oauth2 0.26.0: golang.org/x/oauth2 Improper Validation of Syntactic Correctness of Input vulnerability

(GHSA-6v2p-p543-phr9)

🤖 Prompt for AI Agents
In `@go.mod` around lines 111 - 114, The go.mod lists vulnerable indirect
dependencies golang.org/x/crypto v0.34.0 and golang.org/x/oauth2 v0.26.0; update
these transitive modules to fixed releases by running: go get
golang.org/x/crypto@v0.45.0 golang.org/x/oauth2@v0.27.0 and then go mod tidy so
the go.mod entries for golang.org/x/crypto and golang.org/x/oauth2 are bumped to
the patched versions and the dependency graph is cleaned.

Comment on lines +39 to +42
field.FieldsDependentOn(
[]field.SchemaField{apiKeyField},
[]field.SchemaField{emailIdField},
),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for FieldsDependentOn usage patterns
rg -n "FieldsDependentOn" --type go -C 3

# Also look for the function definition in baton-sdk imports/modules
rg -n "func.*FieldsDependentOn" --type go

# Check the config.go file itself
echo "=== Full config.go content ==="
cat pkg/config/config.go

Repository: ConductorOne/baton-cloudflare

Length of output: 4620


Reverse the argument order in FieldsDependentOn.

The function signature shows FieldsDependentOn(fields []SchemaField, required []SchemaField) with documentation stating: "the provided fields are valid if and only if every field in required are also present."

The current code has FieldsDependentOn([apiKeyField], [emailIdField]), which means "apiKeyField is valid only if emailIdField is present." This is backwards. When using API key authentication with Cloudflare, the email address is required alongside the API key. The correct relationship is: emailIdField depends on apiKeyField.

Swap the arguments:

Diff
 	field.FieldsDependentOn(
-		[]field.SchemaField{apiKeyField},
-		[]field.SchemaField{emailIdField},
+		[]field.SchemaField{emailIdField},
+		[]field.SchemaField{apiKeyField},
 	),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
field.FieldsDependentOn(
[]field.SchemaField{apiKeyField},
[]field.SchemaField{emailIdField},
),
field.FieldsDependentOn(
[]field.SchemaField{emailIdField},
[]field.SchemaField{apiKeyField},
),
🤖 Prompt for AI Agents
In `@pkg/config/config.go` around lines 39 - 42, The FieldsDependentOn call
currently inverts the dependency: change the argument order so emailIdField
depends on apiKeyField by calling
field.FieldsDependentOn([]field.SchemaField{emailIdField},
[]field.SchemaField{apiKeyField}) instead of
field.FieldsDependentOn([]field.SchemaField{apiKeyField},
[]field.SchemaField{emailIdField}); this ensures the required relationship is
correct per the FieldsDependentOn(fields, required) signature.

Comment on lines 33 to +51
if apiToken != "" {
client, err = cloudflare.NewWithAPIToken(apiToken, cloudflare.HTTPClient(httpClient))
if err != nil {
return nil, err
return nil, nil, err
}
}

if apiKey != "" && emailId != "" {
client, err = cloudflare.New(apiKey, emailId, cloudflare.HTTPClient(httpClient))
if err != nil {
return nil, err
return nil, nil, err
}
}

return &Cloudflare{
client: client,
accountId: accountId,
emailId: emailId,
}, nil
}, nil, nil
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential nil client when API key is provided without email.

The configuration requires at least one of apiToken or apiKey via FieldsAtLeastOneUsed, but the emailIdField dependency may be configured incorrectly (see config.go comment). If a user provides apiKey without emailId:

  1. Line 33: apiToken != "" is false, client remains nil
  2. Line 40: apiKey != "" && emailId != "" is false (emailId is empty), client remains nil
  3. A nil client is returned, causing a delayed failure in Validate()

Consider adding early validation in New():

Proposed fix
+	if apiToken == "" && (apiKey == "" || emailId == "") {
+		return nil, nil, fmt.Errorf("cloudflare: either api-token or both api-key and email-id must be provided")
+	}
+
 	if apiToken != "" {
🤖 Prompt for AI Agents
In `@pkg/connector/connector.go` around lines 33 - 51, The New function can return
a nil client when apiKey is set but emailId is empty; update New() to validate
inputs early: if apiToken is empty and apiKey provided without emailId, return a
clear error (e.g., "apiKey requires emailId") instead of constructing a
Cloudflare with nil client, or more generally after attempting both
cloudflare.NewWithAPIToken and cloudflare.New check if client == nil and return
an error; reference the New() function, the Cloudflare struct, and the
apiToken/apiKey/emailId variables and ensure callers relying on Validate() get a
deterministic failure.

cfg.Config,
connectorrunner.WithDefaultCapabilitiesConnectorBuilder(&connector.Cloudflare{}),
connector.New,
connectorrunner.WithSessionStoreEnabled(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@laurenleach laurenleach marked this pull request as draft February 3, 2026 20:08
@laurenleach laurenleach force-pushed the containerize-cloudflare branch from 6e8930a to c2ace46 Compare March 2, 2026 02:01
@github-actions
Copy link
Contributor

github-actions bot commented Mar 2, 2026

Claude finished @laurenleach's task in 5m 43s —— View job


PR Review: Containerize baton-cloudflare connector

Issues: 7 (2 introduced by PR, 5 pre-existing)

Breaking Changes

None detected (no resource type ID changes, entitlement slug changes, or resource ID derivation changes).

Issues

Introduced by this PR:

  1. Config: All fields marked required (pkg/config/config.go:8-39) — apiKeyField, apiTokenField, and emailIdField all now have WithRequired(true). This means users must provide all four fields, breaking the previous behavior where they could authenticate with either api-token alone or api-key + email-id. The old FieldsAtLeastOneUsed and FieldsDependentOn constraints were removed and replaced with field groups, but field groups are UI metadata only — they don't enforce validation. Fix: Re-add field.WithConstraints(...) or remove WithRequired(true) from the auth fields and use constraints instead. See inline comment →

  2. Nil client possible in New() (pkg/connector/connector.go:33-45) — Both auth if blocks can execute sequentially (should be else if). If neither condition matches, client remains nil, causing panics during sync. See inline comment →

Pre-existing (not regressions, but worth fixing):

  1. Nil pointer panic in GetAccountMember (pkg/connector/role.go:156-158) — resp.Body is accessed unconditionally in the error format string, but resp can be nil on network errors. This will panic. Fix this →

  2. Copy-paste artifact (pkg/connector/helpers.go:65) — Variable named bitbucketErr should be cloudflareErr. Also uses %s instead of %w for error wrapping. Fix this →

  3. Typo "couldflare" (pkg/connector/role.go:398,402) — Should be "cloudflare" in the Revoke method error messages. (Also flagged by CodeRabbit.)

  4. ExternalLink.Url set to account ID (pkg/connector/connector.go:62) — c.accountId is not a valid URL. Should be something like fmt.Sprintf("https://dash.cloudflare.com/%s", c.accountId).

  5. Validate() skips when accountId is empty (pkg/connector/connector.go:72) — Returns nil, nil if accountId == "", silently accepting an unusable config. Less of a concern since account-id is required in config, but still a gap.

Reviewer Feedback Check

  • @btipling's comment about removing the session store — appears already addressed in the current revision (no session store in main.go).
  • CodeRabbit's FieldsDependentOn argument order — moot since both the FieldsDependentOn and FieldsAtLeastOneUsed constraints were removed entirely (see issue Cloudflare Connector V0 #1 above).

Documentation

Up to date — no resource types, entitlements, or auth methods were changed.

Comment on lines +8 to 39
apiKeyField = field.StringField(
"api-key",
field.WithIsSecret(true),
field.WithDisplayName("API Key"),
field.WithDescription("The api key for the Cloudflare account."),
field.WithIsSecret(true),
field.WithRequired(true),
)
ApiTokenField = field.StringField(
apiTokenField = field.StringField(
"api-token",
field.WithIsSecret(true),
field.WithDisplayName("API Token"),
field.WithDescription("The api token for the Cloudflare account."),
field.WithIsSecret(true),
field.WithRequired(true),
)
AccountIdField = field.StringField(
accountIdField = field.StringField(
"account-id",
field.WithRequired(true),
field.WithDisplayName("Account ID"),
field.WithDescription("The account id for the Cloudflare account."),
field.WithRequired(true),
)
EmailIdField = field.StringField(
emailIdField = field.StringField(
"email-id",
field.WithDisplayName("Email ID"),
field.WithDescription("The email id for the Cloudflare account."),
field.WithRequired(true),
)

FieldRelationships = []field.SchemaFieldRelationship{
field.FieldsAtLeastOneUsed(ApiTokenField, ApiKeyField),
field.FieldsDependentOn(
[]field.SchemaField{ApiKeyField},
[]field.SchemaField{EmailIdField},
),
configurationFields = []field.SchemaField{
apiKeyField,
apiTokenField,
accountIdField,
emailIdField,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All four fields (api-key, api-token, account-id, email-id) are now marked WithRequired(true). This breaks the previous behavior where users could authenticate with either api-token alone or api-key + email-id.

The old code used field.FieldsAtLeastOneUsed(apiTokenField, apiKeyField) and field.FieldsDependentOn(...) to enforce this correctly. These constraints have been removed and replaced with field groups, but field groups are UI-only metadata — they don't enforce validation.

Recommended fix: Either re-add field.WithConstraints(...) alongside the field groups, or remove WithRequired(true) from apiKeyField, apiTokenField, and emailIdField and rely on the constraint relationships instead.

Comment on lines 33 to 45
if apiToken != "" {
client, err = cloudflare.NewWithAPIToken(apiToken, cloudflare.HTTPClient(httpClient))
if err != nil {
return nil, err
return nil, nil, err
}
}

if apiKey != "" && emailId != "" {
client, err = cloudflare.New(apiKey, emailId, cloudflare.HTTPClient(httpClient))
if err != nil {
return nil, err
return nil, nil, err
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both if blocks can execute sequentially — if the user provides both apiToken and apiKey+emailId, the second block silently overwrites the token-based client. Use else if to make these mutually exclusive.

Also, if neither auth method matches (e.g., apiKey provided without emailId), client remains nil and will cause nil pointer panics during sync. Consider adding a guard after the auth blocks:

if client == nil {
    return nil, nil, fmt.Errorf("baton-cloudflare: either api-token or both api-key and email-id must be provided")
}

@github-actions
Copy link
Contributor

github-actions bot commented Mar 2, 2026

Claude finished @laurenleach's task in 7m 35s —— View job


PR Review: Containerize baton-cloudflare connector

Issues: 8 (2 introduced by PR, 6 pre-existing)

Breaking Changes

None detected (no resource type ID changes, entitlement slug changes, or resource ID derivation changes).

Issues

Introduced by this PR:

  1. Config: All fields marked required, constraints removed (pkg/config/config.go:8-39) — apiKeyField, apiTokenField, accountIdField, and emailIdField all have WithRequired(true), but the FieldsAtLeastOneUsed and FieldsDependentOn constraints were removed. Field groups are UI-only metadata — they don't enforce validation. This breaks the previous either/or authentication model. The deleted config_test.go validated this behavior. See inline comment →

  2. Nil client possible in New() (pkg/connector/connector.go:33-51) — Both if blocks can execute sequentially (should be else if). If neither condition matches, client remains nil, causing panics during sync. See inline comment →

Pre-existing (not regressions, worth fixing):

  1. Nil pointer panic in GetAccountMember (pkg/connector/role.go:156-162) — resp.Body is accessed unconditionally in the error format string, but resp can be nil on network errors. Also uses %s instead of %w, breaking error chains. Fix this →

  2. Copy-paste artifact (pkg/connector/helpers.go:65-71) — Variable named bitbucketErr should be cloudflareErr. Also uses %s instead of %w for error wrapping. Fix this →

  3. Typo "couldflare" (pkg/connector/role.go:398,402) — Should be "baton-cloudflare" in the Revoke method error messages.

  4. ExternalLink.Url set to account ID (pkg/connector/connector.go:62) — c.accountId is not a valid URL. Should be fmt.Sprintf("https://dash.cloudflare.com/%s", c.accountId).

  5. Validate() skips when accountId is empty (pkg/connector/connector.go:72) — Returns nil, nil if accountId == "", silently accepting an unusable config.

  6. UpdateAccountMember resp.Body leak (pkg/connector/role.go:336-357) — On error paths where resp is non-nil, resp.Body is read (line 345) but never closed because defer resp.Body.Close() on line 357 is after the error returns.

Reviewer Feedback Check

  • @btipling's comment about removing the session store — appears already addressed (no session store in main.go).
  • Previous CodeRabbit feedback on FieldsDependentOn argument order — moot since both constraints were removed entirely (see issue Cloudflare Connector V0 #1 above).

Documentation

Docs exist at docs/connector.mdx and were not updated in this PR. The docs currently describe the either/or authentication model which aligns with the intended behavior. If the config constraints are restored per issue #1, the docs remain accurate. No documentation update needed assuming the fix is applied.

Comment on lines +8 to 39
apiKeyField = field.StringField(
"api-key",
field.WithIsSecret(true),
field.WithDisplayName("API Key"),
field.WithDescription("The api key for the Cloudflare account."),
field.WithIsSecret(true),
field.WithRequired(true),
)
ApiTokenField = field.StringField(
apiTokenField = field.StringField(
"api-token",
field.WithIsSecret(true),
field.WithDisplayName("API Token"),
field.WithDescription("The api token for the Cloudflare account."),
field.WithIsSecret(true),
field.WithRequired(true),
)
AccountIdField = field.StringField(
accountIdField = field.StringField(
"account-id",
field.WithRequired(true),
field.WithDisplayName("Account ID"),
field.WithDescription("The account id for the Cloudflare account."),
field.WithRequired(true),
)
EmailIdField = field.StringField(
emailIdField = field.StringField(
"email-id",
field.WithDisplayName("Email ID"),
field.WithDescription("The email id for the Cloudflare account."),
field.WithRequired(true),
)

FieldRelationships = []field.SchemaFieldRelationship{
field.FieldsAtLeastOneUsed(ApiTokenField, ApiKeyField),
field.FieldsDependentOn(
[]field.SchemaField{ApiKeyField},
[]field.SchemaField{EmailIdField},
),
configurationFields = []field.SchemaField{
apiKeyField,
apiTokenField,
accountIdField,
emailIdField,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All four fields now have WithRequired(true), but the field.WithConstraints(...) with FieldsAtLeastOneUsed and FieldsDependentOn have been removed. Field groups are UI-only metadata — they don't enforce validation at runtime.

This means users must provide all of api-key, api-token, account-id, and email-id, breaking the prior either/or authentication model where they could use api-token alone or api-key + email-id.

The old config_test.go (now deleted) validated this constraint behavior with test cases like "with api token" (valid), "with api key but missing email ID" (invalid), etc.

Fix: Remove WithRequired(true) from apiKeyField, apiTokenField, and emailIdField, and re-add field.WithConstraints(...):

Suggested change
apiKeyField = field.StringField(
"api-key",
field.WithIsSecret(true),
field.WithDisplayName("API Key"),
field.WithDescription("The api key for the Cloudflare account."),
field.WithIsSecret(true),
field.WithRequired(true),
)
ApiTokenField = field.StringField(
apiTokenField = field.StringField(
"api-token",
field.WithIsSecret(true),
field.WithDisplayName("API Token"),
field.WithDescription("The api token for the Cloudflare account."),
field.WithIsSecret(true),
field.WithRequired(true),
)
AccountIdField = field.StringField(
accountIdField = field.StringField(
"account-id",
field.WithRequired(true),
field.WithDisplayName("Account ID"),
field.WithDescription("The account id for the Cloudflare account."),
field.WithRequired(true),
)
EmailIdField = field.StringField(
emailIdField = field.StringField(
"email-id",
field.WithDisplayName("Email ID"),
field.WithDescription("The email id for the Cloudflare account."),
field.WithRequired(true),
)
FieldRelationships = []field.SchemaFieldRelationship{
field.FieldsAtLeastOneUsed(ApiTokenField, ApiKeyField),
field.FieldsDependentOn(
[]field.SchemaField{ApiKeyField},
[]field.SchemaField{EmailIdField},
),
configurationFields = []field.SchemaField{
apiKeyField,
apiTokenField,
accountIdField,
emailIdField,
}
apiKeyField = field.StringField(
"api-key",
field.WithDisplayName("API Key"),
field.WithDescription("The api key for the Cloudflare account."),
field.WithIsSecret(true),
)
apiTokenField = field.StringField(
"api-token",
field.WithDisplayName("API Token"),
field.WithDescription("The api token for the Cloudflare account."),
field.WithIsSecret(true),
)
accountIdField = field.StringField(
"account-id",
field.WithDisplayName("Account ID"),
field.WithDescription("The account id for the Cloudflare account."),
field.WithRequired(true),
)
emailIdField = field.StringField(
"email-id",
field.WithDisplayName("Email ID"),
field.WithDescription("The email id for the Cloudflare account."),
)
configurationFields = []field.SchemaField{
apiKeyField,
apiTokenField,
accountIdField,
emailIdField,
}

And add constraints back to the Config definition:

var Config = field.NewConfiguration(
	configurationFields,
	field.WithConnectorDisplayName("Cloudflare"),
	field.WithHelpUrl("/docs/baton/cloudflare"),
	field.WithIconUrl("/static/app-icons/cloudflare.svg"),
	field.WithConstraints(
		field.FieldsAtLeastOneUsed(apiTokenField, apiKeyField),
		field.FieldsDependentOn(
			[]field.SchemaField{emailIdField},
			[]field.SchemaField{apiKeyField},
		),
	),
	field.WithFieldGroups([]field.SchemaFieldGroup{...}),
)

Comment on lines 33 to +51
if apiToken != "" {
client, err = cloudflare.NewWithAPIToken(apiToken, cloudflare.HTTPClient(httpClient))
if err != nil {
return nil, err
return nil, nil, err
}
}

if apiKey != "" && emailId != "" {
client, err = cloudflare.New(apiKey, emailId, cloudflare.HTTPClient(httpClient))
if err != nil {
return nil, err
return nil, nil, err
}
}

return &Cloudflare{
client: client,
accountId: accountId,
emailId: emailId,
}, nil
}, nil, nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two issues here:

  1. Both if blocks can execute sequentially. If the user provides both apiToken and apiKey+emailId, the second block silently overwrites the token-based client. Use else if to make these mutually exclusive.

  2. Nil client possible. If neither auth method matches (e.g., apiKey provided without emailId due to the now-broken config validation), client remains nil and will cause nil pointer panics during sync.

Suggested change
if apiToken != "" {
client, err = cloudflare.NewWithAPIToken(apiToken, cloudflare.HTTPClient(httpClient))
if err != nil {
return nil, err
return nil, nil, err
}
}
if apiKey != "" && emailId != "" {
client, err = cloudflare.New(apiKey, emailId, cloudflare.HTTPClient(httpClient))
if err != nil {
return nil, err
return nil, nil, err
}
}
return &Cloudflare{
client: client,
accountId: accountId,
emailId: emailId,
}, nil
}, nil, nil
if apiToken != "" {
client, err = cloudflare.NewWithAPIToken(apiToken, cloudflare.HTTPClient(httpClient))
if err != nil {
return nil, nil, err
}
} else if apiKey != "" && emailId != "" {
client, err = cloudflare.New(apiKey, emailId, cloudflare.HTTPClient(httpClient))
if err != nil {
return nil, nil, err
}
}
if client == nil {
return nil, nil, fmt.Errorf("baton-cloudflare: either api-token or both api-key and email-id must be provided")
}
return &Cloudflare{
client: client,
accountId: accountId,
emailId: emailId,
}, nil, nil

@laurenleach laurenleach marked this pull request as ready for review March 2, 2026 02:27
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
Makefile (1)

27-27: Declare generate as PHONY.

The generate target should be declared .PHONY since it's not a real file. This prevents issues if a file named generate ever exists.

Proposed fix
+.PHONY: generate
 generate: $(GENERATED_CONF)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Makefile` at line 27, The Makefile's generate target is not declared phony;
add a .PHONY declaration for the target name "generate" so make won't confuse
the target with a same-named file—update the Makefile to include ".PHONY:
generate" (either in an existing .PHONY list or as its own line) to cover the
generate target that depends on the GENERATED_CONF variable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@Makefile`:
- Line 27: The Makefile's generate target is not declared phony; add a .PHONY
declaration for the target name "generate" so make won't confuse the target with
a same-named file—update the Makefile to include ".PHONY: generate" (either in
an existing .PHONY list or as its own line) to cover the generate target that
depends on the GENERATED_CONF variable.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c55e03 and d53f828.

📒 Files selected for processing (9)
  • Makefile
  • cmd/baton-cloudflare/config.go
  • cmd/baton-cloudflare/config_test.go
  • cmd/baton-cloudflare/main.go
  • pkg/config/config.go
  • pkg/connector/connector.go
  • pkg/connector/model.go
  • pkg/connector/role.go
  • pkg/connector/user.go
💤 Files with no reviewable changes (3)
  • pkg/connector/model.go
  • cmd/baton-cloudflare/config_test.go
  • cmd/baton-cloudflare/config.go

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants