Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
ba91ae8
feat: validate APISIX resources in webhooks
AlinsRan Apr 27, 2026
0b74a9c
fix: use validate API in webhook checks
AlinsRan Apr 27, 2026
10d76b4
fix(e2e): add skip guards and redesign ADC validation tests
AlinsRan Apr 27, 2026
d9ba66c
fix: export kind kubeconfig in v2 e2e
AlinsRan Apr 27, 2026
07b7c4d
fix: recreate kind cluster for v2 e2e
AlinsRan Apr 27, 2026
73f3836
fix: wait for kind apiserver readiness
AlinsRan Apr 27, 2026
23fb4e7
fix: use internal kind kubeconfig in CI
AlinsRan Apr 27, 2026
7663b68
fix: support old kind kubeconfig setup
AlinsRan Apr 27, 2026
8859187
fix: harden e2e environment setup
AlinsRan Apr 27, 2026
742f11b
fix: use mirrored images in self-hosted e2e
AlinsRan Apr 27, 2026
9b1533e
fix: proxy docker hub pulls in self-hosted e2e
AlinsRan Apr 27, 2026
24f141d
fix: build local echo server image for e2e
AlinsRan Apr 27, 2026
ec74a51
fix: proxy remaining docker hub images
AlinsRan Apr 27, 2026
e90710e
fix: defer dashboard readiness checks in e2e
AlinsRan Apr 27, 2026
d401305
fix: wait for postgres readiness in v2 e2e
AlinsRan Apr 27, 2026
6618932
fix: stabilize postgres startup in v2 e2e
AlinsRan Apr 27, 2026
4141447
fix: avoid flaky docker login action downloads
AlinsRan Apr 27, 2026
d1a32ed
fix: scope postgres mirror to v2 CI
AlinsRan Apr 27, 2026
a58fd8c
fix: stabilize webhook CI coverage
AlinsRan Apr 27, 2026
f032228
fix: retry v2 postgres image preload
AlinsRan Apr 27, 2026
4dee995
fix: use legacy postgres image in v2 CI
AlinsRan Apr 27, 2026
eef1a88
test: stabilize standalone apisixconsumer webhook e2e
AlinsRan Apr 27, 2026
9a29eb2
fix: stabilize adc e2e retries
AlinsRan Apr 27, 2026
9efafc9
test: stabilize corrected consumer webhook e2e
AlinsRan Apr 27, 2026
badcb29
ci: fix misspell workflow install
AlinsRan Apr 27, 2026
f9905c1
Merge branch 'master' into feat/webhook-adc-validation
AlinsRan May 6, 2026
65b8f1d
revert: remove unnecessary CI/CD changes unrelated to webhook validation
May 6, 2026
5f3e401
test(e2e): fix TLS test retry with RequestAssert
May 6, 2026
0db12c2
fix: restore warn behavior for ApisixTls with missing secrets
May 6, 2026
4789243
fix: address PR review comments
AlinsRan May 6, 2026
b35e0f9
fix: populate global_rules and plugin_metadata in ADC validate payload
AlinsRan May 6, 2026
58ce2fb
fix: use consumerGatewayRef field index in validateDuplicateKeyAuthCr…
AlinsRan May 6, 2026
70b6a7c
fix: skip duplicate key-auth check for malformed credential JSON
AlinsRan May 6, 2026
a0b54cd
Merge remote-tracking branch 'origin/master' into feat/webhook-adc-va…
AlinsRan May 6, 2026
4ffeb5e
test(e2e): fix and expand ADC validation webhook tests
AlinsRan May 6, 2026
4c1ec6b
fix: resolve lint issues in webhook e2e tests and consumer webhook
AlinsRan May 6, 2026
f4047f6
fix(e2e): fix UPDATE path webhook tests
AlinsRan May 6, 2026
3d240f1
chore: remove unrelated framework changes from webhook validation PR
AlinsRan May 6, 2026
bf5fed5
test: remove apisix-standalone-only skip in ADC validation e2e tests
AlinsRan May 7, 2026
70137d0
fix: remove unused framework import in webhook e2e tests
AlinsRan May 7, 2026
d95462e
refactor: use ADC server /validate endpoint for all backends
AlinsRan May 7, 2026
134e77e
fix: use PUT method for ADC server /validate endpoint
AlinsRan May 7, 2026
a003a3e
test: use spec.plugins for Consumer ADC validation e2e tests
AlinsRan May 7, 2026
ce3ecb4
chore: migrate image registry from hkccr to ghcr.io
AlinsRan May 7, 2026
9d8ecb6
chore: remove legacy registry login and add packages:read permission
AlinsRan May 7, 2026
138d2d4
chore: retrigger CI
AlinsRan May 7, 2026
cfce22b
Update conformance-test.yml
AlinsRan May 7, 2026
65c6747
chore: add pull-requests write permission for add-pr-comment action
AlinsRan May 7, 2026
c3e4ec4
Merge branch 'feat/migrate-registry-to-ghcr' into feat/webhook-adc-va…
AlinsRan May 7, 2026
3bf9e3f
fix: remove duplicate failurePolicy in webhook markers; fix ApisixCon…
AlinsRan May 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 7 additions & 11 deletions .github/workflows/conformance-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ jobs:
CONFORMANCE_TEST_REPORT_OUTPUT: /tmp/api7-ingress-controller-conformance-report.yaml
timeout-minutes: 60
runs-on: ubuntu-latest
permissions:
packages: read
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -56,19 +59,12 @@ jobs:
chmod 700 get_helm.sh
./get_helm.sh

- name: Login to Registry
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ${{ secrets.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Login to Private Registry
uses: docker/login-action@v3
with:
registry: hkccr.ccs.tencentyun.com
username: ${{ secrets.PRIVATE_DOCKER_USERNAME }}
password: ${{ secrets.PRIVATE_DOCKER_PASSWORD }}
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build images
env:
Expand Down
11 changes: 7 additions & 4 deletions .github/workflows/e2e-test-k8s.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ jobs:
cases_subset:
- v2
runs-on: self-hosted
permissions:
contents: read
packages: read
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -58,12 +61,12 @@ jobs:
chmod 700 get_helm.sh
./get_helm.sh

- name: Login to Private Registry
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: hkccr.ccs.tencentyun.com
username: ${{ secrets.PRIVATE_DOCKER_USERNAME }}
password: ${{ secrets.PRIVATE_DOCKER_PASSWORD }}
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Launch Kind Cluster
env:
Expand Down
18 changes: 7 additions & 11 deletions .github/workflows/e2e-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ jobs:
- webhook
fail-fast: false
runs-on: ubuntu-latest
permissions:
contents: read
packages: read
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -65,19 +68,12 @@ jobs:
- name: Install ginkgo
run: make install-ginkgo

- name: Login to Registry
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ${{ secrets.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Login to Private Registry
uses: docker/login-action@v3
with:
registry: hkccr.ccs.tencentyun.com
username: ${{ secrets.PRIVATE_DOCKER_USERNAME }}
password: ${{ secrets.PRIVATE_DOCKER_PASSWORD }}
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build images
env:
Expand Down
18 changes: 9 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -202,22 +202,22 @@ kind-down:

.PHONY: kind-load-images
kind-load-images: pull-infra-images kind-load-ingress-image kind-load-adc-image
@kind load docker-image hkccr.ccs.tencentyun.com/api7-dev/api7-ee-3-gateway:dev --name $(KIND_NAME)
@kind load docker-image hkccr.ccs.tencentyun.com/api7-dev/api7-ee-dp-manager:$(DASHBOARD_VERSION) --name $(KIND_NAME)
@kind load docker-image hkccr.ccs.tencentyun.com/api7-dev/api7-ee-3-integrated:$(DASHBOARD_VERSION) --name $(KIND_NAME)
@kind load docker-image ghcr.io/api7/api7-ee-3-gateway:dev --name $(KIND_NAME)
@kind load docker-image ghcr.io/api7/api7-ee-dp-manager:$(DASHBOARD_VERSION) --name $(KIND_NAME)
@kind load docker-image ghcr.io/api7/api7-ee-3-integrated:$(DASHBOARD_VERSION) --name $(KIND_NAME)
@kind load docker-image kennethreitz/httpbin:latest --name $(KIND_NAME)
@kind load docker-image jmalloc/echo-server:latest --name $(KIND_NAME)
@kind load docker-image apache/apisix:dev --name $(KIND_NAME)
@kind load docker-image openresty/openresty:1.27.1.2-4-bullseye-fat --name $(KIND_NAME)

.PHONY: kind-load-gateway-image
kind-load-gateway-image:
@kind load docker-image hkccr.ccs.tencentyun.com/api7-dev/api7-ee-3-gateway:dev --name $(KIND_NAME)
@kind load docker-image ghcr.io/api7/api7-ee-3-gateway:dev --name $(KIND_NAME)

.PHONY: kind-load-dashboard-images
kind-load-dashboard-images:
@kind load docker-image hkccr.ccs.tencentyun.com/api7-dev/api7-ee-dp-manager:$(DASHBOARD_VERSION) --name $(KIND_NAME)
@kind load docker-image hkccr.ccs.tencentyun.com/api7-dev/api7-ee-3-integrated:$(DASHBOARD_VERSION) --name $(KIND_NAME)
@kind load docker-image ghcr.io/api7/api7-ee-dp-manager:$(DASHBOARD_VERSION) --name $(KIND_NAME)
@kind load docker-image ghcr.io/api7/api7-ee-3-integrated:$(DASHBOARD_VERSION) --name $(KIND_NAME)

.PHONY: kind-load-ingress-image
kind-load-ingress-image:
Expand All @@ -231,9 +231,9 @@ kind-load-adc-image:

.PHONY: pull-infra-images
pull-infra-images:
@docker pull hkccr.ccs.tencentyun.com/api7-dev/api7-ee-3-gateway:dev
@docker pull hkccr.ccs.tencentyun.com/api7-dev/api7-ee-dp-manager:$(DASHBOARD_VERSION)
@docker pull hkccr.ccs.tencentyun.com/api7-dev/api7-ee-3-integrated:$(DASHBOARD_VERSION)
@docker pull ghcr.io/api7/api7-ee-3-gateway:dev
@docker pull ghcr.io/api7/api7-ee-dp-manager:$(DASHBOARD_VERSION)
@docker pull ghcr.io/api7/api7-ee-3-integrated:$(DASHBOARD_VERSION)
@docker pull kennethreitz/httpbin:latest
@docker pull jmalloc/echo-server:latest
@docker pull ghcr.io/api7/adc:dev
Expand Down
37 changes: 37 additions & 0 deletions internal/adc/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,43 @@ func (c *Client) DeleteConfig(ctx context.Context, args Task) error {
return err
}

func (c *Client) Validate(ctx context.Context, task Task) error {
if len(task.Configs) == 0 || task.Resources == nil {
return nil
}

fileIOStart := time.Now()
syncFilePath, cleanup, err := prepareSyncFile(task.Resources)
if err != nil {
pkgmetrics.RecordFileIODuration("prepare_sync_file", "failure", time.Since(fileIOStart).Seconds())
return err
}
pkgmetrics.RecordFileIODuration("prepare_sync_file", adctypes.StatusSuccess, time.Since(fileIOStart).Seconds())
defer cleanup()

args := BuildADCExecuteArgs(syncFilePath, task.Labels, task.ResourceTypes)

var errs types.ADCValidationErrors
for _, config := range task.Configs {
if config.BackendType == "" {
config.BackendType = c.defaultMode
}
if err := c.executor.Validate(ctx, config, args); err != nil {
var validationErr types.ADCValidationError
if errors.As(err, &validationErr) {
errs.Errors = append(errs.Errors, validationErr)
Comment thread
AlinsRan marked this conversation as resolved.
continue
}
return err
}
Comment thread
AlinsRan marked this conversation as resolved.
}

if len(errs.Errors) > 0 {
return errs
}
return nil
}

func (c *Client) Sync(ctx context.Context) (map[string]types.ADCExecutionErrors, error) {
c.syncMu.Lock()
defer c.syncMu.Unlock()
Expand Down
143 changes: 139 additions & 4 deletions internal/adc/client/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const (

type ADCExecutor interface {
Execute(ctx context.Context, config adctypes.Config, args []string) error
Validate(ctx context.Context, config adctypes.Config, args []string) error
}

func BuildADCExecuteArgs(filePath string, labels map[string]string, types []string) []string {
Expand Down Expand Up @@ -81,6 +82,12 @@ type ADCServerOpts struct {
CacheKey string `json:"cacheKey"`
}

type ADCValidateResult struct {
Success *bool `json:"success,omitempty"`
ErrorMessage string `json:"message,omitempty"`
Errors []types.ADCValidationDetail `json:"errors,omitempty"`
}

// HTTPADCExecutor implements ADCExecutor interface using HTTP calls to ADC Server
type HTTPADCExecutor struct {
httpClient *http.Client
Expand Down Expand Up @@ -123,6 +130,10 @@ func (e *HTTPADCExecutor) Execute(ctx context.Context, config adctypes.Config, a
return e.runHTTPSync(ctx, config, args)
}

func (e *HTTPADCExecutor) Validate(ctx context.Context, config adctypes.Config, args []string) error {
return e.runHTTPValidate(ctx, config, args)
}

// runHTTPSync performs HTTP sync to ADC Server for each server address
func (e *HTTPADCExecutor) runHTTPSync(ctx context.Context, config adctypes.Config, args []string) error {
var execErrs = types.ADCExecutionError{
Expand Down Expand Up @@ -157,6 +168,38 @@ func (e *HTTPADCExecutor) runHTTPSync(ctx context.Context, config adctypes.Confi
return nil
}

func (e *HTTPADCExecutor) runHTTPValidate(ctx context.Context, config adctypes.Config, args []string) error {
var validationErr = types.ADCValidationError{
Name: config.Name,
}
var infraErrs []error

serverAddrs := func() []string {
return config.ServerAddrs
}()
e.log.V(1).Info("running http validate", "serverAddrs", serverAddrs)

for _, addr := range serverAddrs {
if err := e.runHTTPValidateForSingleServer(ctx, addr, config, args); err != nil {
e.log.Error(err, "failed to run http validate for server", "server", addr)
var validationServerErr types.ADCValidationServerAddrError
if errors.As(err, &validationServerErr) {
validationErr.FailedErrors = append(validationErr.FailedErrors, validationServerErr)
continue
}
infraErrs = append(infraErrs, err)
}
}

if len(validationErr.FailedErrors) > 0 {
return validationErr
}
if len(infraErrs) > 0 {
return errors.Join(infraErrs...)
}
return nil
}

// runHTTPSyncForSingleServer performs HTTP sync to a single ADC Server
func (e *HTTPADCExecutor) runHTTPSyncForSingleServer(ctx context.Context, serverAddr string, config adctypes.Config, args []string) error {
ctx, cancel := context.WithTimeout(ctx, e.httpClient.Timeout)
Expand All @@ -175,7 +218,7 @@ func (e *HTTPADCExecutor) runHTTPSyncForSingleServer(ctx context.Context, server
}

// Build HTTP request
req, err := e.buildHTTPRequest(ctx, serverAddr, config, labels, types, resources)
req, err := e.buildHTTPRequest(ctx, serverAddr, config, labels, types, resources, http.MethodPut, "/sync")
if err != nil {
return fmt.Errorf("failed to build HTTP request: %w", err)
}
Expand All @@ -195,6 +238,38 @@ func (e *HTTPADCExecutor) runHTTPSyncForSingleServer(ctx context.Context, server
return e.handleHTTPResponse(resp, serverAddr)
}

func (e *HTTPADCExecutor) runHTTPValidateForSingleServer(ctx context.Context, serverAddr string, config adctypes.Config, args []string) error {
ctx, cancel := context.WithTimeout(ctx, e.httpClient.Timeout)
defer cancel()

labels, types, filePath, err := e.parseArgs(args)
if err != nil {
return fmt.Errorf("failed to parse args: %w", err)
}

resources, err := e.loadResourcesFromFile(filePath)
if err != nil {
return fmt.Errorf("failed to load resources from file %s: %w", filePath, err)
}

req, err := e.buildHTTPRequest(ctx, serverAddr, config, labels, types, resources, http.MethodPut, "/validate")
if err != nil {
return fmt.Errorf("failed to build validate request: %w", err)
}

resp, err := e.httpClient.Do(req)
if err != nil {
return fmt.Errorf("failed to send HTTP request: %w", err)
}
defer func() {
if closeErr := resp.Body.Close(); closeErr != nil {
e.log.Error(closeErr, "failed to close response body")
}
}()

return e.handleHTTPValidateResponse(resp, serverAddr)
}

// parseArgs parses the command line arguments to extract labels, types, and file path
func (e *HTTPADCExecutor) parseArgs(args []string) (map[string]string, []string, string, error) {
labels := make(map[string]string)
Expand Down Expand Up @@ -248,7 +323,7 @@ func (e *HTTPADCExecutor) loadResourcesFromFile(filePath string) (*adctypes.Reso
}

// buildHTTPRequest builds the HTTP request for ADC Server
func (e *HTTPADCExecutor) buildHTTPRequest(ctx context.Context, serverAddr string, config adctypes.Config, labels map[string]string, types []string, resources *adctypes.Resources) (*http.Request, error) {
func (e *HTTPADCExecutor) buildHTTPRequest(ctx context.Context, serverAddr string, config adctypes.Config, labels map[string]string, types []string, resources *adctypes.Resources, method string, path string) (*http.Request, error) {
// Prepare request body
tlsVerify := config.TlsVerify
reqBody := ADCServerRequest{
Expand All @@ -274,7 +349,7 @@ func (e *HTTPADCExecutor) buildHTTPRequest(ctx context.Context, serverAddr strin
}

e.log.V(1).Info("sending HTTP request to ADC Server",
"url", e.serverURL+"/sync",
"url", e.serverURL+path,
"server", serverAddr,
"mode", config.BackendType,
"cacheKey", config.Name,
Expand All @@ -284,7 +359,7 @@ func (e *HTTPADCExecutor) buildHTTPRequest(ctx context.Context, serverAddr strin
)

// Create HTTP request
req, err := http.NewRequestWithContext(ctx, "PUT", e.serverURL+"/sync", bytes.NewBuffer(jsonData))
req, err := http.NewRequestWithContext(ctx, method, e.serverURL+path, bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("failed to create HTTP request: %w", err)
}
Expand Down Expand Up @@ -357,3 +432,63 @@ func (e *HTTPADCExecutor) handleHTTPResponse(resp *http.Response, serverAddr str
e.log.V(1).Info("ADC Server sync success", "result", result)
return nil
}

func (e *HTTPADCExecutor) handleHTTPValidateResponse(resp *http.Response, serverAddr string) error {
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response body: %w", err)
}

e.log.V(1).Info("received HTTP validate response from ADC Server",
"server", serverAddr,
"status", resp.StatusCode,
"response", string(body),
)

parseValidationResult := func() *ADCValidateResult {
if len(body) == 0 {
return nil
}
var result ADCValidateResult
if err := json.Unmarshal(body, &result); err != nil {
return nil
}
return &result
}

if resp.StatusCode == http.StatusBadRequest {
result := parseValidationResult()
errMsg := string(body)
if result != nil && result.ErrorMessage != "" {
errMsg = result.ErrorMessage
}
return types.ADCValidationServerAddrError{
ServerAddr: serverAddr,
Err: errMsg,
ValidationErrors: func() []types.ADCValidationDetail {
if result == nil {
return nil
}
return result.Errors
}(),
}
}

if resp.StatusCode/100 != 2 {
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
}

if result := parseValidationResult(); result != nil && result.Success != nil && !*result.Success {
errMsg := result.ErrorMessage
if errMsg == "" {
errMsg = "ADC validation failed"
}
return types.ADCValidationServerAddrError{
ServerAddr: serverAddr,
Err: errMsg,
ValidationErrors: result.Errors,
}
}

return nil
}
Loading
Loading