Skip to content

Containerize baton-cloudflare-zero-trust connector - #18

Draft
laurenleach wants to merge 6 commits into
mainfrom
containerize-cloudflare-zero-trust
Draft

Containerize baton-cloudflare-zero-trust connector#18
laurenleach wants to merge 6 commits into
mainfrom
containerize-cloudflare-zero-trust

Conversation

@laurenleach

@laurenleach laurenleach commented Jan 29, 2026

Copy link
Copy Markdown

Containerizes the connector following baton-databricks#35 and baton-contentful#48.

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

Summary by CodeRabbit

  • New Features

    • Added Cloudflare Zero Trust configuration: Account ID, API Key, API Token, and Email fields with validation and inter-field relationships; type-safe accessors for those config values.
  • Bug Fixes

    • Fixed credential selection so API Key and API Token are handled exclusively to avoid unintended overrides.
  • Chores

    • Upgraded Go toolchain and refreshed numerous dependencies for compatibility and maintenance.

✏️ Tip: You can customize this high-level summary in your review settings.

@laurenleach
laurenleach requested a review from a team January 29, 2026 23:16
@coderabbitai

coderabbitai Bot commented Jan 29, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@laurenleach has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 1 minutes and 15 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

Walkthrough

Upgrades Go toolchain and dependencies (notably Baton SDK), adds a Cloudflare Zero Trust configuration module with typed accessors and field relationships, and refactors credential-selection logic to an if/else flow preventing client override.

Changes

Cohort / File(s) Summary
Go Module Dependencies
go.mod
Bumped Go version to 1.25.2. Upgraded github.com/conductorone/baton-sdk v0.3.35→v0.7.10 and numerous indirects (e.g., gopsutil→v4, otter→v2, semver/v3, purego); adjusted transitive dependency versions.
Configuration Schema & Generated Accessors
pkg/config/config.go, pkg/config/conf.gen.go
Added Cloudflare Zero Trust config: fields (account-id, api-key, api-token, email), schema relationships (mutual exclusion, dependency, at-least-one), and generated type-safe accessors (GetString, GetStringSlice, GetInt, GetBool, GetStringMap) with reflection-based lookup. Review generated code and relationship rules for correctness.
Connector Credential Logic
pkg/connector/connector.go
Changed credential selection from two independent if blocks to an if/else chain so apiToken is only used when apiKey path is not taken, preventing client override; small formatting adjustments.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through go.mod with a grin so wide,
New fields sprouted where configs abide,
Tokens and keys now politely decide,
SDKs updated — I nibbled bugs aside,
Hooray! Cloudflare’s garden grows with pride 🌱

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title 'Containerize baton-cloudflare-zero-trust connector' does not match the actual changes, which are primarily dependency updates (Go 1.25.2, baton-sdk v0.7.10) and configuration scaffolding additions. Revise the title to reflect the actual changes, such as 'Update Go toolchain and baton-sdk, add Cloudflare Zero Trust config' or 'Upgrade dependencies and add configuration framework for Cloudflare Zero Trust'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch containerize-cloudflare-zero-trust

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

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

47-60: Guard against missing/partial credentials to avoid a nil client.

Line 52-56 only initializes the client for valid credential paths; otherwise client stays nil and err nil, so New returns a connector that will crash later. Consider returning a clear error when credentials are missing or incomplete.

🔧 Proposed fix
 import (
 	"context"
+	"errors"

 	"github.com/cloudflare/cloudflare-go"
 	v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2"
 	"github.com/conductorone/baton-sdk/pkg/annotations"
 	"github.com/conductorone/baton-sdk/pkg/connectorbuilder"
 )
@@
-	if apiKey != "" && email != "" {
-		client, err = cloudflare.New(apiKey, email)
-	} else if apiToken != "" {
-		client, err = cloudflare.NewWithAPIToken(apiToken)
-	}
+	if apiKey != "" || email != "" {
+		if apiKey == "" || email == "" {
+			return nil, errors.New("api-key and email must be provided together")
+		}
+		client, err = cloudflare.New(apiKey, email)
+	} else if apiToken != "" {
+		client, err = cloudflare.NewWithAPIToken(apiToken)
+	} else {
+		return nil, errors.New("missing credentials: provide api-token or api-key+email")
+	}
🤖 Fix all issues with AI agents
In `@pkg/config/conf.gen.go`:
- Around line 13-15: In findFieldByTag, add a nil check for the receiver c
before calling reflect.ValueOf(c).Elem(): if c == nil return nil,false to avoid
a panic when the generated accessor is called with a nil receiver; update the
generated method (CloudflareZeroTrust.findFieldByTag) to guard early and return
a safe zero value and false when c is nil.

Comment thread pkg/config/conf.gen.go
Comment on lines +13 to +15
func (c *CloudflareZeroTrust) findFieldByTag(tagValue string) (any, bool) {
v := reflect.ValueOf(c).Elem() // Dereference pointer to struct
t := v.Type()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add a nil receiver guard to avoid panics in reflection.

Line 14 will panic if c is nil. Even if callers are expected to pass non-nil, a defensive guard makes generated accessors safer.

🛡️ Proposed fix (apply in generator)
 func (c *CloudflareZeroTrust) findFieldByTag(tagValue string) (any, bool) {
+	if c == nil {
+		return nil, false
+	}
 	v := reflect.ValueOf(c).Elem() // Dereference pointer to struct
 	t := v.Type()
📝 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
func (c *CloudflareZeroTrust) findFieldByTag(tagValue string) (any, bool) {
v := reflect.ValueOf(c).Elem() // Dereference pointer to struct
t := v.Type()
func (c *CloudflareZeroTrust) findFieldByTag(tagValue string) (any, bool) {
if c == nil {
return nil, false
}
v := reflect.ValueOf(c).Elem() // Dereference pointer to struct
t := v.Type()
🤖 Prompt for AI Agents
In `@pkg/config/conf.gen.go` around lines 13 - 15, In findFieldByTag, add a nil
check for the receiver c before calling reflect.ValueOf(c).Elem(): if c == nil
return nil,false to avoid a panic when the generated accessor is called with a
nil receiver; update the generated method (CloudflareZeroTrust.findFieldByTag)
to guard early and return a safe zero value and false when c is nil.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@go.mod`:
- Line 3: The CI test job is hardcoded to Go 1.23.x causing a mismatch with
go.mod (go 1.25.2); edit the test job's Install Go step (the "test" job and its
"Install Go" step in ci.yaml) and replace the go-version: 1.23.x setting with
go-version-file: 'go.mod' so the action reads the Go version from go.mod and
aligns the pipeline versions.

Comment thread go.mod
module github.com/conductorone/baton-cloudflare-zero-trust

go 1.23.4
go 1.25.2

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for Go version references in configs
rg -n -C 2 'go 1\.25\.2|GOLANG_VERSION|golang:1\.25|1\.25\.2|go 1\.|Go version' --type=toml --type=yaml --type=dockerfile --type=sh .

Repository: ConductorOne/baton-cloudflare-zero-trust

Length of output: 118


🏁 Script executed:

#!/bin/bash
# Also look for CI workflow files
fd -e yaml -e yml . .github/workflows 2>/dev/null | head -20

Repository: ConductorOne/baton-cloudflare-zero-trust

Length of output: 187


🏁 Script executed:

#!/bin/bash
# Check for Dockerfiles
fd -i dockerfile . | head -20

Repository: ConductorOne/baton-cloudflare-zero-trust

Length of output: 193


🏁 Script executed:

#!/bin/bash
# Check for any build/CI related files
find . -maxdepth 3 -type f \( -name ".gitlab-ci.yml" -o -name "*.mk" -o -name "Makefile" -o -name "tox.ini" -o -name ".travis.yml" \) 2>/dev/null

Repository: ConductorOne/baton-cloudflare-zero-trust

Length of output: 94


🏁 Script executed:

cat .github/workflows/ci.yaml

Repository: ConductorOne/baton-cloudflare-zero-trust

Length of output: 2312


🏁 Script executed:

cat .github/workflows/release.yaml

Repository: ConductorOne/baton-cloudflare-zero-trust

Length of output: 720


🏁 Script executed:

cat .github/workflows/capabilities_and_config.yaml

Repository: ConductorOne/baton-cloudflare-zero-trust

Length of output: 1129


🏁 Script executed:

cat Makefile

Repository: ConductorOne/baton-cloudflare-zero-trust

Length of output: 963


🏁 Script executed:

# Check if there's any docker-compose or other container config
fd -i docker-compose . --maxdepth 2

Repository: ConductorOne/baton-cloudflare-zero-trust

Length of output: 66


Update test job in ci.yaml to use Go 1.25.2 from go.mod.

The test job hardcodes go-version: 1.23.x while other CI jobs and go.mod specify 1.25.2. This version mismatch can cause build inconsistencies. Update it to go-version-file: 'go.mod' to align with the rest of the pipeline.

ci.yaml test job (line ~39)
test:
  runs-on: ubuntu-latest
  steps:
    - name: Install Go
      uses: actions/setup-go@v5
      with:
        go-version: 1.23.x  # Change to: go-version-file: 'go.mod'
🤖 Prompt for AI Agents
In `@go.mod` at line 3, The CI test job is hardcoded to Go 1.23.x causing a
mismatch with go.mod (go 1.25.2); edit the test job's Install Go step (the
"test" job and its "Install Go" step in ci.yaml) and replace the go-version:
1.23.x setting with go-version-file: 'go.mod' so the action reads the Go version
from go.mod and aligns the pipeline versions.

@jugonzalez12 jugonzalez12 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CI workflow go versions seem to be in a weird state, but this otherwise looks good

@laurenleach
laurenleach marked this pull request as draft February 3, 2026 20:08
@github-actions

github-actions Bot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Claude finished @laurenleach's task in 2m 40s —— View job


PR Review: Containerize baton-cloudflare-zero-trust connector

  • Gather context and read changed source files
  • Review configuration module (pkg/config/)
  • Review connector changes (pkg/connector/)
  • Review entry point changes (cmd/baton-cloudflare-zero-trust/)
  • Review CI/build changes
  • Post review findings

Overview

This PR migrates the connector to the newer baton-sdk patterns: RunConnector, ResourceSyncerV2, generated config structs, field groups, and permission annotations. It also upgrades Go to 1.25.2 and baton-sdk to v0.8.24. The credential selection logic is improved from two independent if blocks to an if/else chain. Overall the migration looks well-done.


Issues Found

1. Critical: Field validation constraints were dropped

pkg/config/config.go — The old config in cmd/baton-cloudflare-zero-trust/config.go had three fieldRelationships:

field.FieldsAtLeastOneUsed(apiTokenField, apiKeyField),
field.FieldsMutuallyExclusive(apiTokenField, apiKeyField),
field.FieldsDependentOn([]field.SchemaField{apiKeyField}, []field.SchemaField{emailField}),

The new config uses WithFieldGroups instead of WithConstraints, which provides a UI-level grouping, but the explicit validation rules (at-least-one, mutual exclusion, api-key requires email) appear to have been removed. The field groups provide a UI hint but may not enforce the same validation at the SDK level.

This means the connector could be started with no credentials (no api-token, no api-key), or with api-key but no email, and the validation wouldn't catch it — the New() function would silently return a connector with a nil client.

Additionally, all four fields are now marked WithRequired(true), but api-key and api-token should not both be required — they're alternatives. This conflicts with the field group approach where only one group's fields should be required.

Fix this →

2. Critical: nil client when no credentials are provided

pkg/connector/connector.go:54-58 — As CodeRabbit also flagged: if neither credential path matches (both ApiKey+Email and ApiToken are empty), client stays nil and err is nil, so New() returns a Connector with a nil client. This will panic on first API call.

if c.ApiKey != "" && c.Email != "" {
    client, err = cloudflare.New(c.ApiKey, c.Email)
} else if c.ApiToken != "" {
    client, err = cloudflare.NewWithAPIToken(c.ApiToken)
}
// Falls through with client == nil if neither condition is true

Add an else branch that returns a clear error:

} else {
    return nil, nil, fmt.Errorf("baton-cloudflare-zero-trust: missing credentials: provide api-token or api-key+email")
}

Fix this →

3. Medium: CI test job still hardcodes Go 1.23.x

.github/workflows/ci.yaml:60 — The go-lint and go-test jobs correctly use go-version-file: 'go.mod', but the test job still hardcodes go-version: 1.23.x. With go.mod at 1.25.2, this version mismatch could cause build failures or inconsistent behavior.

- name: Install Go
  uses: actions/setup-go@v5
  with:
    go-version: 1.23.x  # Should be: go-version-file: 'go.mod'

Fix this →

4. Medium: Test coverage significantly reduced

cmd/baton-cloudflare-zero-trust/config_test.go — The old test had 6 test cases covering:

  • empty config, missing api-key/token, api-token only, api-key without email, api-key with email, api-key + api-token (mutual exclusion)

The new test has only 3 cases and doesn't test:

  • api-key with email (valid)
  • api-key without email (should fail)
  • api-key + api-token together (mutual exclusion, should fail)

This reduction is likely a consequence of the dropped validation constraints (issue #1). Once constraints are restored, the test cases should also be restored.

5. Low: Nil-receiver panic in generated code

pkg/config/conf.gen.go:14reflect.ValueOf(c).Elem() will panic if c is nil. While this is generated code and callers are expected to pass non-nil, a nil guard would make it safer:

func (c *CloudflareZeroTrust) findFieldByTag(tagValue string) (any, bool) {
    if c == nil {
        return nil, false
    }
    v := reflect.ValueOf(c).Elem()
    // ...

This should be fixed in the generator (pkg/config/gen/gen.go / SDK) rather than in the generated file directly.

6. Low: Connector.Metadata still has template description

pkg/connector/connector.go:33:

Description: "The template implementation of a baton connector",

This should be updated to something like "Syncs identity and access data from Cloudflare Zero Trust".


What looks good

  • The migration from ResourceSyncer to ResourceSyncerV2 is clean and correct across all builder files
  • The if/else fix for credential selection properly prevents api-token from overriding api-key
  • Permission annotations on resource types are a good addition
  • The Makefile correctly auto-generates conf.gen.go from config.go
  • Pagination logic is preserved correctly in the V2 migration (using SyncOpResults)
  • The new RunConnector pattern in main.go is much cleaner than the old boilerplate

Summary

The SDK migration and containerization work is solid. The main concerns are: (1) the dropped field validation constraints that could allow invalid credential configurations, (2) the nil-client fallthrough in New(), and (3) the Go version mismatch in CI. Issues 1 and 2 should be addressed before merge; 3 is a CI correctness fix that the reviewer @jugonzalez12 also noted.

laurenleach and others added 5 commits April 17, 2026 15:15
- Update baton-sdk to v0.7.10
- Create pkg/config package with generated configuration
- Update main.go to use config.RunConnector API
- Update connector to use V2 interface
- Update Makefile for config generation and lambda support
- Update GitHub workflows

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Updated main.go to use config.RunConnector instead of DefineConfiguration
- Updated connector.New signature to accept config and opts parameters
- Updated all resource syncers (users, groups, roles, members) to use V2 interface with SyncOpAttrs and SyncOpResults
- Ran go mod tidy and go mod vendor to update dependencies

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@laurenleach
laurenleach force-pushed the containerize-cloudflare-zero-trust branch from f18826e to a2af02f Compare April 17, 2026 22:20
@github-actions

github-actions Bot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Claude finished @laurenleach's task in 2m 1s —— View job


  • Gather context and read changed source files
  • Review configuration module (pkg/config/)
  • Review connector changes (pkg/connector/)
  • Review entry point changes (cmd/baton-cloudflare-zero-trust/)
  • Review CI/build changes
  • Post review findings

Overview

This PR migrates the connector to newer baton-sdk patterns (RunConnector, ResourceSyncerV2, generated config structs, field groups) and adds permission annotations to resource types. It upgrades Go to 1.25.2 and baton-sdk to v0.7.10. The credential selection logic is improved from two independent if blocks to an if/else chain. The overall migration is clean and well-structured.


Issues Found

1. Critical: Field validation constraints dropped

pkg/config/config.go — The old config in cmd/baton-cloudflare-zero-trust/config.go enforced three explicit validation rules:

field.FieldsAtLeastOneUsed(apiTokenField, apiKeyField),
field.FieldsMutuallyExclusive(apiTokenField, apiKeyField),
field.FieldsDependentOn([]field.SchemaField{apiKeyField}, []field.SchemaField{emailField}),

The new config uses WithFieldGroups instead of WithConstraints. Field groups provide UI-level selection between credential methods, but the explicit validation rules (at-least-one, mutual exclusion, api-key-requires-email) have been removed.

Additionally, all four fields are marked WithRequired(true) (lines 14, 20, 27, 33), which conflicts with the field group approach — api-key and api-token are alternatives, not both required. This means the SDK will require all four fields to be provided, making it impossible to use just an API token without also providing an API key and email.

Recommendation: Remove WithRequired(true) from apiKeyField, apiTokenField, and emailField (only accountIdField should be unconditionally required). Then re-add the constraint rules via field.WithConstraints(...) alongside the field groups:

var Config = field.NewConfiguration(
    configurationFields,
    field.WithConnectorDisplayName("Cloudflare Zero Trust"),
    // ...existing options...
    field.WithFieldGroups([]field.SchemaFieldGroup{...}),
    field.WithConstraints(
        field.FieldsAtLeastOneUsed(apiTokenField, apiKeyField),
        field.FieldsMutuallyExclusive(apiTokenField, apiKeyField),
        field.FieldsDependentOn([]field.SchemaField{apiKeyField}, []field.SchemaField{emailField}),
    ),
)

Fix this →

2. Critical: nil client when no credentials are provided

pkg/connector/connector.go:54-58 — If neither credential path matches (both ApiKey+Email and ApiToken are empty), client stays nil and err is nil, so New() returns a Connector with a nil client. This will panic on the first API call (e.g. in Validate).

if c.ApiKey != "" && c.Email != "" {
    client, err = cloudflare.New(c.ApiKey, c.Email)
} else if c.ApiToken != "" {
    client, err = cloudflare.NewWithAPIToken(c.ApiToken)
}
// Falls through with client == nil

Add an else branch:

} else {
    return nil, nil, fmt.Errorf("baton-cloudflare-zero-trust: missing credentials: provide api-token or api-key+email")
}

Also consider what happens when ApiKey is provided but Email is empty — the first condition fails silently and falls through to ApiToken, which may also be empty. A more robust approach (as CodeRabbit suggested):

if c.ApiKey != "" || c.Email != "" {
    if c.ApiKey == "" || c.Email == "" {
        return nil, nil, fmt.Errorf("baton-cloudflare-zero-trust: api-key and email must be provided together")
    }
    client, err = cloudflare.New(c.ApiKey, c.Email)
} else if c.ApiToken != "" {
    client, err = cloudflare.NewWithAPIToken(c.ApiToken)
} else {
    return nil, nil, fmt.Errorf("baton-cloudflare-zero-trust: missing credentials: provide api-token or api-key+email")
}

Fix this →

3. Medium: CI test job hardcodes Go 1.23.x

.github/workflows/ci.yaml:60 — The go-lint and go-test jobs correctly use go-version-file: 'go.mod', but the test job still hardcodes go-version: 1.23.x. With go.mod at 1.25.2, this creates a version mismatch that could cause build failures.

- name: Install Go
  uses: actions/setup-go@v5
  with:
    go-version: 1.23.x  # Should be: go-version-file: 'go.mod'

Reviewer @jugonzalez12 also noted this: "CI workflow go versions seem to be in a weird state"

Fix this →

4. Medium: Test coverage significantly reduced

cmd/baton-cloudflare-zero-trust/config_test.go — The old test had 6 test cases covering empty config, missing credentials, api-token only, api-key without email, api-key with email, and mutual exclusion. The new test has only 3 cases and doesn't test:

  • api-key with email (valid path)
  • api-key without email (should fail with dependency constraint)
  • api-key + api-token together (should fail with mutual exclusion)

Once the validation constraints are restored (issue #1), the dropped test cases should be restored too.

Fix this →

5. Low: Connector.Metadata still has template description

pkg/connector/connector.go:33:

Description: "The template implementation of a baton connector",

This was a leftover from the original code and should be updated to something meaningful like "Syncs identity and access data from Cloudflare Zero Trust".

Note: this pre-dates the PR (it exists on main too), so not a regression — but worth fixing while you're here.

6. Low: Nil-receiver panic in generated code

pkg/config/conf.gen.go:14reflect.ValueOf(c).Elem() will panic if c is nil. While callers are expected to pass non-nil, a nil guard would make it safer. This should be fixed in the generator (pkg/config/gen/gen.go / SDK) rather than in the generated file.


What looks good

  • The migration from ResourceSyncer to ResourceSyncerV2 is clean across all builder files — List, Entitlements, and Grants correctly return *rs.SyncOpResults instead of bare page tokens
  • The if/else fix for credential selection properly prevents apiToken from overriding apiKey (the old code had two independent if blocks)
  • Permission annotations on resource types are well-chosen and map to actual Cloudflare API permission scopes
  • The Makefile correctly auto-generates conf.gen.go from config.go with proper dependencies
  • Pagination logic is preserved correctly in the V2 migration
  • The new RunConnector pattern in main.go is much cleaner than the old boilerplate
  • The capabilities_and_config.yaml workflow is properly structured

Summary

The SDK migration and containerization work is well-executed. The two critical issues to fix before merge are: (1) restoring the field validation constraints (WithConstraints) in pkg/config/config.go and removing WithRequired(true) from the credential fields, and (2) adding a nil-client guard in pkg/connector/connector.go:New(). The CI Go version mismatch (issue #3) should also be addressed to avoid build failures.

@github-actions

github-actions Bot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Claude finished @laurenleach's task in 1m 57s —— View job


  • Gather context and read changed source files
  • Review configuration module (pkg/config/)
  • Review connector changes (pkg/connector/)
  • Review entry point and tests (cmd/baton-cloudflare-zero-trust/)
  • Review CI/build changes
  • Post review findings

Overview

This PR migrates the connector to the newer baton-sdk patterns: RunConnector, ResourceSyncerV2, generated config structs with field groups, and permission annotations. It also upgrades Go to 1.25.2 and baton-sdk to v0.7.10. The credential selection logic is improved from two independent if blocks to a proper if/else chain. The migration is well-structured overall.


Issues Found

1. Critical: Field validation constraints dropped — users can start the connector with invalid credentials

pkg/config/config.go — The old config (cmd/baton-cloudflare-zero-trust/config.go, now deleted) enforced three critical validation rules:

field.FieldsAtLeastOneUsed(apiTokenField, apiKeyField),
field.FieldsMutuallyExclusive(apiTokenField, apiKeyField),
field.FieldsDependentOn([]field.SchemaField{apiKeyField}, []field.SchemaField{emailField}),

These were replaced with WithFieldGroups, which provides UI-level grouping but not equivalent validation. Worse, all four fields are now marked WithRequired(true) (lines 14, 21, 28, 34), which means the SDK demands all four fields — making it impossible to use just an API token without also providing an API key and email.

The old config correctly had WithRequired(true) only on accountIdField, and the credential fields were optional with relationship constraints controlling their validity.

Fix: Remove WithRequired(true) from apiKeyField, apiTokenField, and emailField. Re-add WithConstraints(...) alongside the field groups. I verified that WithConstraints, FieldsAtLeastOneUsed, FieldsMutuallyExclusive, and FieldsDependentOn are all available in the vendored baton-sdk v0.7.10.

Fix this →

2. Critical: nil client when no credentials are provided — will panic at runtime

pkg/connector/connector.go:54-58 — If neither credential path matches (both ApiKey+Email and ApiToken are empty), client stays nil and err is nil, so New() returns a Connector with a nil client. The first API call (e.g., Validate at line 40) will panic with a nil pointer dereference.

if c.ApiKey != "" && c.Email != "" {
    client, err = cloudflare.New(c.ApiKey, c.Email)
} else if c.ApiToken != "" {
    client, err = cloudflare.NewWithAPIToken(c.ApiToken)
}
// Falls through with client == nil if neither condition is true

There's also a subtle issue: if ApiKey is provided without Email, the first condition fails silently and falls through to the ApiToken check. Add comprehensive validation:

if c.ApiKey != "" || c.Email != "" {
    if c.ApiKey == "" || c.Email == "" {
        return nil, nil, fmt.Errorf("baton-cloudflare-zero-trust: api-key and email must be provided together")
    }
    client, err = cloudflare.New(c.ApiKey, c.Email)
} else if c.ApiToken != "" {
    client, err = cloudflare.NewWithAPIToken(c.ApiToken)
} else {
    return nil, nil, fmt.Errorf("baton-cloudflare-zero-trust: missing credentials: provide api-token or api-key+email")
}

Fix this →

3. Medium: CI test job hardcodes Go 1.23.x

.github/workflows/ci.yaml:61 — The go-lint and go-test jobs correctly use go-version-file: 'go.mod', but the test job still hardcodes go-version: 1.23.x. With go.mod at Go 1.25.2, this version mismatch will cause build failures or inconsistent behavior. Reviewer @jugonzalez12 also flagged this.

- name: Install Go
  uses: actions/setup-go@v5
  with:
    go-version: 1.23.x  # Should be: go-version-file: 'go.mod'

Fix this →

4. Medium: Test coverage significantly reduced

cmd/baton-cloudflare-zero-trust/config_test.go — The old test had 6 test cases covering all validation paths. The new test has only 3 cases and no longer tests:

  • api-key with email (valid path)
  • api-key without email (should fail — dependency constraint)
  • api-key + api-token together (should fail — mutual exclusion)

This is a direct consequence of the dropped constraints (issue #1). Once constraints are restored, these test cases should be restored too.

Fix this →

5. Low: Connector.Metadata still has template description

pkg/connector/connector.go:33:

Description: "The template implementation of a baton connector",

This predates the PR (exists on main), but worth updating while making changes. Something like "Syncs identity and access data from Cloudflare Zero Trust" would be more accurate.

6. Low: Nil-receiver panic in generated code

pkg/config/conf.gen.go:14reflect.ValueOf(c).Elem() will panic if c is nil. A nil guard would make the generated accessor safer. This should ideally be fixed in the generator or SDK template rather than in the generated file directly.

7. Low: Missing Close() method on Connector

The Connector struct holds a *cloudflare.API client but doesn't implement a Close() method. Per the CLAUDE.md guidelines: "Connectors that create clients MUST close them." While the cloudflare-go client may not require explicit cleanup, implementing Close() follows the pattern and prevents resource leaks if the client changes.


What looks good

  • The migration from ResourceSyncer to ResourceSyncerV2 is clean across all builder files — List, Entitlements, and Grants correctly return *rs.SyncOpResults instead of bare page tokens
  • The if/else fix for credential selection properly prevents apiToken from overriding apiKey (the old code had two independent if blocks — a real bug fix)
  • Permission annotations on resource types are well-chosen and map to actual Cloudflare API permission scopes
  • The Makefile correctly auto-generates conf.gen.go from config.go with proper dependency tracking
  • Pagination logic is preserved correctly in the V2 migration
  • The new RunConnector pattern in main.go is much cleaner than the old boilerplate
  • The capabilities_and_config.yaml workflow is properly structured with go-version-file: 'go.mod'
  • The BATON_AUTH_METHOD: 'api-key-group' env var in CI correctly selects the field group for integration tests

Summary

The SDK migration and containerization work is solid. Two critical issues should be addressed before merge:

  1. Restore field validation constraints in pkg/config/config.go — remove WithRequired(true) from credential fields, add WithConstraints(...) with the three original relationship rules
  2. Add nil-client guard in pkg/connector/connector.go:New() — return an explicit error when no valid credentials are provided

Additionally, the CI Go version mismatch (issue #3) should be fixed to avoid build failures in the test job, and the dropped test cases (issue #4) should be restored once the constraints are back.

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