diff --git a/Makefile b/Makefile index d41d5536f8..b226924d6b 100644 --- a/Makefile +++ b/Makefile @@ -171,9 +171,17 @@ live-test-auth: live-test-connect: @$(MAKE) live-test CLI_LIVE_TEST_GROUPS="connect" +.PHONY: live-test-billing +live-test-billing: + @$(MAKE) live-test CLI_LIVE_TEST_GROUPS="billing" + +.PHONY: live-test-flink +live-test-flink: + @$(MAKE) live-test CLI_LIVE_TEST_GROUPS="flink" + .PHONY: live-test-essential live-test-essential: - @$(MAKE) live-test CLI_LIVE_TEST_GROUPS="core,kafka,schema_registry,auth" + @$(MAKE) live-test CLI_LIVE_TEST_GROUPS="core,kafka,schema_registry,auth,billing" .PHONY: live-test-multicloud live-test-multicloud: @@ -198,6 +206,23 @@ live-test-resource: build-for-live-test printf " %-25s %-20s %s\n" "iam_rbac" "iam" "TestRBACRoleBindingCRUDLive"; \ printf " %-25s %-20s %s\n" "login" "auth" "TestLoginLogoutLive"; \ printf " %-25s %-20s %s\n" "connect" "connect" "TestConnectClusterCRUDLive"; \ + printf " %-25s %-20s %s\n" "organization" "core" "TestOrganizationLive"; \ + printf " %-25s %-20s %s\n" "audit_log" "core" "TestAuditLogLive"; \ + printf " %-25s %-20s %s\n" "service_quota" "core" "TestServiceQuotaLive"; \ + printf " %-25s %-20s %s\n" "context" "core" "TestContextAndConfigurationLive"; \ + printf " %-25s %-20s %s\n" "billing" "billing" "TestBillingLive"; \ + printf " %-25s %-20s %s\n" "kafka_region" "kafka" "TestKafkaRegionListLive"; \ + printf " %-25s %-20s %s\n" "kafka_quota" "kafka" "TestKafkaQuotaCRUDLive"; \ + printf " %-25s %-20s %s\n" "kafka_produce_consume" "kafka" "TestKafkaProduceConsumeLive"; \ + printf " %-25s %-20s %s\n" "iam_user" "iam" "TestIAMUserLive"; \ + printf " %-25s %-20s %s\n" "iam_ip_group_filter" "iam" "TestIAMIpGroupFilterCRUDLive"; \ + printf " %-25s %-20s %s\n" "iam_identity_provider" "iam" "TestIAMIdentityProviderCRUDLive"; \ + printf " %-25s %-20s %s\n" "iam_certificate" "iam" "TestIAMCertificateAuthorityCRUDLive"; \ + printf " %-25s %-20s %s\n" "connect_custom_plugin" "connect" "TestConnectCustomPluginCRUDLive"; \ + printf " %-25s %-20s %s\n" "schema_registry_ext" "schema_registry" "TestSchemaRegistryExtendedLive"; \ + printf " %-25s %-20s %s\n" "flink_region" "flink" "TestFlinkRegionListLive"; \ + printf " %-25s %-20s %s\n" "plugin" "core" "TestPluginListLive"; \ + printf " %-25s %-20s %s\n" "kafka_share_group" "kafka" "TestKafkaShareGroupListLive"; \ echo ""; \ echo "Usage: make live-test-resource RESOURCE="; \ else \ @@ -213,6 +238,23 @@ live-test-resource: build-for-live-test iam_rbac) GROUP=iam; FUNC=TestRBACRoleBindingCRUDLive;; \ login) GROUP=auth; FUNC=TestLoginLogoutLive;; \ connect) GROUP=connect; FUNC=TestConnectClusterCRUDLive;; \ + organization) GROUP=core; FUNC=TestOrganizationLive;; \ + audit_log) GROUP=core; FUNC=TestAuditLogLive;; \ + service_quota) GROUP=core; FUNC=TestServiceQuotaLive;; \ + context) GROUP=core; FUNC=TestContextAndConfigurationLive;; \ + billing) GROUP=billing; FUNC=TestBillingLive;; \ + kafka_region) GROUP=kafka; FUNC=TestKafkaRegionListLive;; \ + kafka_quota) GROUP=kafka; FUNC=TestKafkaQuotaCRUDLive;; \ + kafka_produce_consume) GROUP=kafka; FUNC=TestKafkaProduceConsumeLive;; \ + iam_user) GROUP=iam; FUNC=TestIAMUserLive;; \ + iam_ip_group_filter) GROUP=iam; FUNC=TestIAMIpGroupFilterCRUDLive;; \ + iam_identity_provider) GROUP=iam; FUNC=TestIAMIdentityProviderCRUDLive;; \ + iam_certificate) GROUP=iam; FUNC=TestIAMCertificateAuthorityCRUDLive;; \ + connect_custom_plugin) GROUP=connect; FUNC=TestConnectCustomPluginCRUDLive;; \ + schema_registry_ext) GROUP=schema_registry; FUNC=TestSchemaRegistryExtendedLive;; \ + flink_region) GROUP=flink; FUNC=TestFlinkRegionListLive;; \ + plugin) GROUP=core; FUNC=TestPluginListLive;; \ + kafka_share_group) GROUP=kafka; FUNC=TestKafkaShareGroupListLive;; \ *) echo "Unknown resource: $(RESOURCE)"; echo "Run 'make live-test-resource' to see available resources."; exit 1;; \ esac; \ echo "Running $$FUNC (group: $$GROUP)..."; \ diff --git a/debian/patches/standard_build_layout.patch b/debian/patches/standard_build_layout.patch index 47879dc223..844876bed6 100644 --- a/debian/patches/standard_build_layout.patch +++ b/debian/patches/standard_build_layout.patch @@ -1,6 +1,6 @@ ---- cli/Makefile 2026-03-10 12:57:52.531367502 -0500 +--- cli/Makefile 2026-03-13 15:10:31.362690984 -0500 +++ debian/Makefile 2025-09-22 13:01:33.829653470 -0500 -@@ -1,234 +1,163 @@ +@@ -1,276 +1,163 @@ -SHELL := /bin/bash -GORELEASER_VERSION := v2.13.3 +SHELL=/bin/bash @@ -19,8 +19,10 @@ - endif -else # darwin - $(MAKE) cli-builder --endif -- ++ifndef VERSION ++ VERSION=$(CLI_VERSION) + endif + -# Cross-compile from darwin to any of the OS/Arch pairs below -.PHONY: cross-build -cross-build: @@ -60,10 +62,7 @@ - rm -rf go go-openssl go$$(cat .go-version).src.tar.gz -else - TAGS=$(TAGS) CC=$(CC) CXX=$(CXX) CGO_LDFLAGS=$(CGO_LDFLAGS) goreleaser build --clean --single-target --snapshot -+ifndef VERSION -+ VERSION=$(CLI_VERSION) - endif - +-endif +export PACKAGE_TITLE=cli +export FULL_PACKAGE_TITLE=confluent-$(PACKAGE_TITLE) +export PACKAGE_NAME=$(FULL_PACKAGE_TITLE)-$(VERSION) @@ -142,7 +141,7 @@ + filepath=windows_amd64/confluent.exe; \ + curl -fs https://$${baseurl}/confluent-cli/binaries/$(CLI_VERSION)/confluent$${version}_windows_amd64.exe -o $${filepath}; \ + chmod 755 $${filepath} -+ + + cp LICENSE $(DESTDIR)$(DOCPATH)/LICENSE + $(DESTDIR)$(BINPATH)/confluent --version | awk -F' ' '{ print $3 }' > $(DESTDIR)$(DOCPATH)/version.txt @@ -254,8 +253,7 @@ -else - go build -cover -ldflags="-s -w -X main.commit="00000000" -X main.date="1970-01-01T00:00:00Z" -X main.isTest=true" -o test/bin/confluent ./cmd/confluent -endif -+rpm-build-area: RPM_BUILDING/BUILD RPM_BUILDING/RPMS RPM_BUILDING/SOURCES RPM_BUILDING/SPECS RPM_BUILDING/SRPMS - +- -.PHONY: build-for-integration-test-windows -build-for-integration-test-windows: -ifdef CI @@ -263,9 +261,7 @@ -else - go build -cover -ldflags="-s -w -X main.commit="00000000" -X main.date="1970-01-01T00:00:00Z" -X main.isTest=true" -o test/bin/confluent.exe ./cmd/confluent -endif -+RPM_BUILDING/%: -+ mkdir -p $@ - +- -.PHONY: integration-test -integration-test: -ifdef CI @@ -280,10 +276,13 @@ - go test -timeout 0 -v $$(go list ./... | grep github.com/confluentinc/cli/v4/test) $(INTEGRATION_TEST_ARGS) && \ - go tool covdata textfmt -i $${GOCOVERDIR} -o coverage.integration.out -endif -- ++rpm-build-area: RPM_BUILDING/BUILD RPM_BUILDING/RPMS RPM_BUILDING/SOURCES RPM_BUILDING/SPECS RPM_BUILDING/SRPMS + -.PHONY: test -test: unit-test integration-test -- ++RPM_BUILDING/%: ++ mkdir -p $@ + -.PHONY: build-for-live-test -build-for-live-test: - go build -ldflags="-s -w -X main.disableUpdates=true" -o test/live/bin/confluent ./cmd/confluent @@ -326,9 +325,17 @@ -live-test-connect: - @$(MAKE) live-test CLI_LIVE_TEST_GROUPS="connect" - +-.PHONY: live-test-billing +-live-test-billing: +- @$(MAKE) live-test CLI_LIVE_TEST_GROUPS="billing" +- +-.PHONY: live-test-flink +-live-test-flink: +- @$(MAKE) live-test CLI_LIVE_TEST_GROUPS="flink" +- -.PHONY: live-test-essential -live-test-essential: -- @$(MAKE) live-test CLI_LIVE_TEST_GROUPS="core,kafka,schema_registry,auth" +- @$(MAKE) live-test CLI_LIVE_TEST_GROUPS="core,kafka,schema_registry,auth,billing" - -.PHONY: live-test-multicloud -live-test-multicloud: @@ -353,6 +360,23 @@ - printf " %-25s %-20s %s\n" "iam_rbac" "iam" "TestRBACRoleBindingCRUDLive"; \ - printf " %-25s %-20s %s\n" "login" "auth" "TestLoginLogoutLive"; \ - printf " %-25s %-20s %s\n" "connect" "connect" "TestConnectClusterCRUDLive"; \ +- printf " %-25s %-20s %s\n" "organization" "core" "TestOrganizationLive"; \ +- printf " %-25s %-20s %s\n" "audit_log" "core" "TestAuditLogLive"; \ +- printf " %-25s %-20s %s\n" "service_quota" "core" "TestServiceQuotaLive"; \ +- printf " %-25s %-20s %s\n" "context" "core" "TestContextAndConfigurationLive"; \ +- printf " %-25s %-20s %s\n" "billing" "billing" "TestBillingLive"; \ +- printf " %-25s %-20s %s\n" "kafka_region" "kafka" "TestKafkaRegionListLive"; \ +- printf " %-25s %-20s %s\n" "kafka_quota" "kafka" "TestKafkaQuotaCRUDLive"; \ +- printf " %-25s %-20s %s\n" "kafka_produce_consume" "kafka" "TestKafkaProduceConsumeLive"; \ +- printf " %-25s %-20s %s\n" "iam_user" "iam" "TestIAMUserLive"; \ +- printf " %-25s %-20s %s\n" "iam_ip_group_filter" "iam" "TestIAMIpGroupFilterCRUDLive"; \ +- printf " %-25s %-20s %s\n" "iam_identity_provider" "iam" "TestIAMIdentityProviderCRUDLive"; \ +- printf " %-25s %-20s %s\n" "iam_certificate" "iam" "TestIAMCertificateAuthorityCRUDLive"; \ +- printf " %-25s %-20s %s\n" "connect_custom_plugin" "connect" "TestConnectCustomPluginCRUDLive"; \ +- printf " %-25s %-20s %s\n" "schema_registry_ext" "schema_registry" "TestSchemaRegistryExtendedLive"; \ +- printf " %-25s %-20s %s\n" "flink_region" "flink" "TestFlinkRegionListLive"; \ +- printf " %-25s %-20s %s\n" "plugin" "core" "TestPluginListLive"; \ +- printf " %-25s %-20s %s\n" "kafka_share_group" "kafka" "TestKafkaShareGroupListLive"; \ - echo ""; \ - echo "Usage: make live-test-resource RESOURCE="; \ - else \ @@ -368,6 +392,23 @@ - iam_rbac) GROUP=iam; FUNC=TestRBACRoleBindingCRUDLive;; \ - login) GROUP=auth; FUNC=TestLoginLogoutLive;; \ - connect) GROUP=connect; FUNC=TestConnectClusterCRUDLive;; \ +- organization) GROUP=core; FUNC=TestOrganizationLive;; \ +- audit_log) GROUP=core; FUNC=TestAuditLogLive;; \ +- service_quota) GROUP=core; FUNC=TestServiceQuotaLive;; \ +- context) GROUP=core; FUNC=TestContextAndConfigurationLive;; \ +- billing) GROUP=billing; FUNC=TestBillingLive;; \ +- kafka_region) GROUP=kafka; FUNC=TestKafkaRegionListLive;; \ +- kafka_quota) GROUP=kafka; FUNC=TestKafkaQuotaCRUDLive;; \ +- kafka_produce_consume) GROUP=kafka; FUNC=TestKafkaProduceConsumeLive;; \ +- iam_user) GROUP=iam; FUNC=TestIAMUserLive;; \ +- iam_ip_group_filter) GROUP=iam; FUNC=TestIAMIpGroupFilterCRUDLive;; \ +- iam_identity_provider) GROUP=iam; FUNC=TestIAMIdentityProviderCRUDLive;; \ +- iam_certificate) GROUP=iam; FUNC=TestIAMCertificateAuthorityCRUDLive;; \ +- connect_custom_plugin) GROUP=connect; FUNC=TestConnectCustomPluginCRUDLive;; \ +- schema_registry_ext) GROUP=schema_registry; FUNC=TestSchemaRegistryExtendedLive;; \ +- flink_region) GROUP=flink; FUNC=TestFlinkRegionListLive;; \ +- plugin) GROUP=core; FUNC=TestPluginListLive;; \ +- kafka_share_group) GROUP=kafka; FUNC=TestKafkaShareGroupListLive;; \ - *) echo "Unknown resource: $(RESOURCE)"; echo "Run 'make live-test-resource' to see available resources."; exit 1;; \ - esac; \ - echo "Running $$FUNC (group: $$GROUP)..."; \ diff --git a/test/live/audit_log_live_test.go b/test/live/audit_log_live_test.go new file mode 100644 index 0000000000..7a73403b53 --- /dev/null +++ b/test/live/audit_log_live_test.go @@ -0,0 +1,31 @@ +//go:build live_test && (all || core) + +package live + +import ( + "testing" +) + +func (s *CLILiveTestSuite) TestAuditLogLive() { + t := s.T() + t.Parallel() + state := s.setupTestContext(t) + + steps := []CLILiveTest{ + { + Name: "Describe audit log config", + Args: "audit-log describe -o json", + JSONFieldsExist: []string{"cluster_id"}, + }, + { + Name: "List audit log routes", + Args: "audit-log route list", + }, + } + + for _, step := range steps { + t.Run(step.Name, func(t *testing.T) { + s.runLiveCommand(t, step, state) + }) + } +} diff --git a/test/live/billing_live_test.go b/test/live/billing_live_test.go new file mode 100644 index 0000000000..19a177006f --- /dev/null +++ b/test/live/billing_live_test.go @@ -0,0 +1,38 @@ +//go:build live_test && (all || billing) + +package live + +import ( + "testing" +) + +func (s *CLILiveTestSuite) TestBillingLive() { + t := s.T() + t.Parallel() + state := s.setupTestContext(t) + + steps := []CLILiveTest{ + { + Name: "List billing prices", + Args: "billing price list", + }, + { + Name: "List billing costs", + Args: "billing cost list", + }, + { + Name: "List billing promos", + Args: "billing promo list", + }, + { + Name: "Describe billing payment", + Args: "billing payment describe -o json", + }, + } + + for _, step := range steps { + t.Run(step.Name, func(t *testing.T) { + s.runLiveCommand(t, step, state) + }) + } +} diff --git a/test/live/connect_extended_live_test.go b/test/live/connect_extended_live_test.go new file mode 100644 index 0000000000..61cf3d5a57 --- /dev/null +++ b/test/live/connect_extended_live_test.go @@ -0,0 +1,44 @@ +//go:build live_test && (all || connect) + +package live + +import ( + "os" + "testing" +) + +func (s *CLILiveTestSuite) TestConnectCustomPluginCRUDLive() { + t := s.T() + t.Parallel() + + envID := os.Getenv("LIVE_TEST_ENVIRONMENT_ID") + if envID == "" { + t.Skip("Skipping: LIVE_TEST_ENVIRONMENT_ID must be set") + } + + state := s.setupTestContext(t) + + // Register cleanup + s.registerCleanup(t, "connect custom-plugin delete {{.plugin_id}} --force", state) + + steps := []CLILiveTest{ + { + Name: "Use environment", + Args: "environment use " + envID, + }, + { + Name: "List custom plugins", + Args: "connect custom-plugin list", + }, + { + Name: "List custom runtimes", + Args: "connect custom-runtime list --environment " + envID, + }, + } + + for _, step := range steps { + t.Run(step.Name, func(t *testing.T) { + s.runLiveCommand(t, step, state) + }) + } +} diff --git a/test/live/connect_live_test.go b/test/live/connect_live_test.go index a4c03c4f63..c326695203 100644 --- a/test/live/connect_live_test.go +++ b/test/live/connect_live_test.go @@ -117,6 +117,18 @@ func (s *CLILiveTestSuite) TestConnectClusterCRUDLive() { UseStateVars: true, Contains: []string{connectorName}, }, + { + Name: "Describe connector offsets", + Args: "connect offset describe {{.connector_id}} --cluster " + clusterID + " --environment " + envID, + UseStateVars: true, + Retries: 3, + }, + { + Name: "View connector logs", + Args: "connect event describe {{.connector_id}} --cluster " + clusterID + " --environment " + envID, + UseStateVars: true, + Retries: 3, + }, { Name: "Pause connector", Args: "connect cluster pause {{.connector_id}}", diff --git a/test/live/context_live_test.go b/test/live/context_live_test.go new file mode 100644 index 0000000000..2f79363816 --- /dev/null +++ b/test/live/context_live_test.go @@ -0,0 +1,52 @@ +//go:build live_test && (all || core) + +package live + +import ( + "testing" +) + +func (s *CLILiveTestSuite) TestContextAndConfigurationLive() { + t := s.T() + t.Parallel() + state := s.setupTestContext(t) + + steps := []CLILiveTest{ + { + Name: "List contexts", + Args: "context list", + Contains: []string{"login-"}, + }, + { + Name: "Describe current context", + Args: "context describe -o json", + JSONFieldsExist: []string{"name", "platform"}, + }, + { + Name: "List configuration", + Args: "configuration list", + }, + { + Name: "Describe disable_updates config", + Args: "configuration describe disable_updates -o json", + JSONFieldsExist: []string{"name", "value"}, + }, + { + Name: "Update disable_update_check config", + Args: "configuration update disable_update_check true", + }, + { + Name: "Verify config updated", + Args: "configuration describe disable_update_check -o json", + JSONFields: map[string]string{ + "value": "true", + }, + }, + } + + for _, step := range steps { + t.Run(step.Name, func(t *testing.T) { + s.runLiveCommand(t, step, state) + }) + } +} diff --git a/test/live/flink_live_test.go b/test/live/flink_live_test.go new file mode 100644 index 0000000000..1443795127 --- /dev/null +++ b/test/live/flink_live_test.go @@ -0,0 +1,37 @@ +//go:build live_test && (all || flink) + +package live + +import ( + "testing" +) + +func (s *CLILiveTestSuite) TestFlinkRegionListLive() { + t := s.T() + t.Parallel() + state := s.setupTestContext(t) + + steps := []CLILiveTest{ + { + Name: "List flink regions for aws", + Args: "flink region list --cloud aws", + Contains: []string{"us-east-1"}, + }, + { + Name: "List flink regions for gcp", + Args: "flink region list --cloud gcp", + Contains: []string{"us-east1"}, + }, + { + Name: "List flink regions for azure", + Args: "flink region list --cloud azure", + Contains: []string{"eastus"}, + }, + } + + for _, step := range steps { + t.Run(step.Name, func(t *testing.T) { + s.runLiveCommand(t, step, state) + }) + } +} diff --git a/test/live/iam_certificate_live_test.go b/test/live/iam_certificate_live_test.go new file mode 100644 index 0000000000..cc3e764234 --- /dev/null +++ b/test/live/iam_certificate_live_test.go @@ -0,0 +1,133 @@ +//go:build live_test && (all || iam) + +package live + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/base64" + "encoding/pem" + "math/big" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +// generateSelfSignedCert creates a self-signed CA certificate and returns it as a base64 encoded PEM string. +func generateSelfSignedCert(t *testing.T) string { + t.Helper() + + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + + template := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Organization: []string{"CLI Live Test"}, + CommonName: "cli-live-test-ca", + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(24 * time.Hour), + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + BasicConstraintsValid: true, + IsCA: true, + } + + certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key) + require.NoError(t, err) + + certPEM := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: certDER, + }) + + return base64.StdEncoding.EncodeToString(certPEM) +} + +func (s *CLILiveTestSuite) TestIAMCertificateAuthorityCRUDLive() { + t := s.T() + t.Parallel() + state := s.setupTestContext(t) + + certChain := generateSelfSignedCert(t) + + // Register cleanups in LIFO order: pool first, then authority + s.registerCleanup(t, "iam certificate-authority delete {{.cert_authority_id}} --force", state) + s.registerCleanup(t, "iam certificate-pool delete {{.cert_pool_id}} --provider {{.cert_authority_id}} --force", state) + + steps := []CLILiveTest{ + // Certificate Authority CRUD + { + Name: "Create certificate authority", + Args: `iam certificate-authority create --description "Live test CA" --certificate-chain "` + certChain + `" --certificate-chain-filename live-test-ca.pem -o json`, + CaptureID: "cert_authority_id", + JSONFieldsExist: []string{"id"}, + }, + { + Name: "Describe certificate authority", + Args: "iam certificate-authority describe {{.cert_authority_id}} -o json", + UseStateVars: true, + JSONFieldsExist: []string{"id", "description"}, + }, + { + Name: "List certificate authorities", + Args: "iam certificate-authority list", + }, + { + Name: "Update certificate authority", + Args: `iam certificate-authority update {{.cert_authority_id}} --description "Updated live test CA"`, + UseStateVars: true, + }, + // Certificate Pool CRUD (depends on authority) + { + Name: "Create certificate pool", + Args: `iam certificate-pool create --provider {{.cert_authority_id}} --display-name "live-test-pool" --description "Live test certificate pool" --external-identifier "OU=Engineering" --filter "certificate.subject == \"OU=Engineering\"" -o json`, + UseStateVars: true, + CaptureID: "cert_pool_id", + JSONFieldsExist: []string{"id"}, + }, + { + Name: "Describe certificate pool", + Args: "iam certificate-pool describe {{.cert_pool_id}} --provider {{.cert_authority_id}} -o json", + UseStateVars: true, + JSONFieldsExist: []string{"id"}, + }, + { + Name: "List certificate pools", + Args: "iam certificate-pool list --provider {{.cert_authority_id}}", + UseStateVars: true, + }, + { + Name: "Update certificate pool", + Args: `iam certificate-pool update {{.cert_pool_id}} --provider {{.cert_authority_id}} --description "Updated live test pool"`, + UseStateVars: true, + }, + { + Name: "Delete certificate pool", + Args: "iam certificate-pool delete {{.cert_pool_id}} --provider {{.cert_authority_id}} --force", + UseStateVars: true, + }, + // Clean up certificate authority + { + Name: "Delete certificate authority", + Args: "iam certificate-authority delete {{.cert_authority_id}} --force", + UseStateVars: true, + }, + { + Name: "Verify certificate authority deleted", + Args: "iam certificate-authority describe {{.cert_authority_id}}", + UseStateVars: true, + ExitCode: 1, + }, + } + + for _, step := range steps { + t.Run(step.Name, func(t *testing.T) { + s.runLiveCommand(t, step, state) + }) + } +} diff --git a/test/live/iam_identity_provider_live_test.go b/test/live/iam_identity_provider_live_test.go new file mode 100644 index 0000000000..2646667244 --- /dev/null +++ b/test/live/iam_identity_provider_live_test.go @@ -0,0 +1,145 @@ +//go:build live_test && (all || iam) + +package live + +import ( + "testing" +) + +func (s *CLILiveTestSuite) TestIAMIdentityProviderCRUDLive() { + t := s.T() + t.Parallel() + state := s.setupTestContext(t) + + providerName := uniqueName("idp") + updatedProviderName := providerName + "-updated" + poolName := uniqueName("pool") + groupMappingName := uniqueName("grpmap") + + // Register cleanups in LIFO order: group-mapping, pool, then provider + s.registerCleanup(t, "iam provider delete {{.provider_id}} --force", state) + s.registerCleanup(t, "iam pool delete {{.pool_id}} --provider {{.provider_id}} --force", state) + s.registerCleanup(t, "iam group-mapping delete {{.group_mapping_id}} --force", state) + + steps := []CLILiveTest{ + // Identity Provider CRUD + { + Name: "Create identity provider", + Args: "iam provider create " + providerName + ` --description "Live test provider" --issuer-uri https://example.com/issuer --jwks-uri https://example.com/.well-known/jwks.json -o json`, + CaptureID: "provider_id", + JSONFields: map[string]string{ + "name": providerName, + }, + }, + { + Name: "Describe identity provider", + Args: "iam provider describe {{.provider_id}} -o json", + UseStateVars: true, + JSONFields: map[string]string{ + "name": providerName, + }, + }, + { + Name: "List identity providers", + Args: "iam provider list", + Contains: []string{providerName}, + }, + { + Name: "Update identity provider", + Args: "iam provider update {{.provider_id}} --name " + updatedProviderName + ` --description "Updated live test provider"`, + UseStateVars: true, + }, + { + Name: "Verify provider updated", + Args: "iam provider describe {{.provider_id}} -o json", + UseStateVars: true, + JSONFields: map[string]string{ + "name": updatedProviderName, + }, + }, + // Identity Pool CRUD (depends on provider) + { + Name: "Create identity pool", + Args: `iam pool create ` + poolName + ` --provider {{.provider_id}} --identity-claim sub --description "Live test pool" -o json`, + UseStateVars: true, + CaptureID: "pool_id", + JSONFields: map[string]string{ + "display_name": poolName, + }, + }, + { + Name: "Describe identity pool", + Args: "iam pool describe {{.pool_id}} --provider {{.provider_id}} -o json", + UseStateVars: true, + JSONFields: map[string]string{ + "display_name": poolName, + }, + }, + { + Name: "List identity pools", + Args: "iam pool list --provider {{.provider_id}}", + UseStateVars: true, + Contains: []string{poolName}, + }, + { + Name: "Update identity pool", + Args: `iam pool update {{.pool_id}} --provider {{.provider_id}} --description "Updated live test pool"`, + UseStateVars: true, + }, + { + Name: "Delete identity pool", + Args: "iam pool delete {{.pool_id}} --provider {{.provider_id}} --force", + UseStateVars: true, + }, + // Group Mapping CRUD + { + Name: "Create group mapping", + Args: `iam group-mapping create ` + groupMappingName + ` --description "Live test group mapping" --filter "\"engineering\" in groups" -o json`, + CaptureID: "group_mapping_id", + JSONFields: map[string]string{ + "display_name": groupMappingName, + }, + }, + { + Name: "Describe group mapping", + Args: "iam group-mapping describe {{.group_mapping_id}} -o json", + UseStateVars: true, + JSONFields: map[string]string{ + "display_name": groupMappingName, + }, + }, + { + Name: "List group mappings", + Args: "iam group-mapping list", + Contains: []string{groupMappingName}, + }, + { + Name: "Update group mapping", + Args: `iam group-mapping update {{.group_mapping_id}} --description "Updated live test group mapping"`, + UseStateVars: true, + }, + { + Name: "Delete group mapping", + Args: "iam group-mapping delete {{.group_mapping_id}} --force", + UseStateVars: true, + }, + // Clean up provider (after pool is deleted) + { + Name: "Delete identity provider", + Args: "iam provider delete {{.provider_id}} --force", + UseStateVars: true, + }, + { + Name: "Verify provider deleted", + Args: "iam provider describe {{.provider_id}}", + UseStateVars: true, + ExitCode: 1, + }, + } + + for _, step := range steps { + t.Run(step.Name, func(t *testing.T) { + s.runLiveCommand(t, step, state) + }) + } +} diff --git a/test/live/iam_ip_group_filter_live_test.go b/test/live/iam_ip_group_filter_live_test.go new file mode 100644 index 0000000000..01a992714c --- /dev/null +++ b/test/live/iam_ip_group_filter_live_test.go @@ -0,0 +1,105 @@ +//go:build live_test && (all || iam) + +package live + +import ( + "testing" +) + +func (s *CLILiveTestSuite) TestIAMIpGroupFilterCRUDLive() { + t := s.T() + t.Parallel() + state := s.setupTestContext(t) + + ipGroupName := uniqueName("ipgrp") + updatedIpGroupName := ipGroupName + "-updated" + ipFilterName := uniqueName("ipflt") + + // Register cleanups in LIFO order: delete filter first, then group + s.registerCleanup(t, "iam ip-group delete {{.ip_group_id}} --force", state) + s.registerCleanup(t, "iam ip-filter delete {{.ip_filter_id}} --force", state) + + steps := []CLILiveTest{ + // IP Group CRUD + { + Name: "Create IP group", + Args: "iam ip-group create " + ipGroupName + " --cidr-blocks 10.0.0.0/24 -o json", + CaptureID: "ip_group_id", + JSONFields: map[string]string{ + "group_name": ipGroupName, + }, + }, + { + Name: "Describe IP group", + Args: "iam ip-group describe {{.ip_group_id}} -o json", + UseStateVars: true, + JSONFields: map[string]string{ + "group_name": ipGroupName, + }, + }, + { + Name: "List IP groups", + Args: "iam ip-group list", + Contains: []string{ipGroupName}, + }, + { + Name: "Update IP group", + Args: "iam ip-group update {{.ip_group_id}} --name " + updatedIpGroupName + " --cidr-blocks 10.0.0.0/24,10.1.0.0/24", + UseStateVars: true, + }, + { + Name: "Verify IP group updated", + Args: "iam ip-group describe {{.ip_group_id}} -o json", + UseStateVars: true, + JSONFields: map[string]string{ + "group_name": updatedIpGroupName, + }, + }, + // IP Filter CRUD (depends on the IP group) + { + Name: "Create IP filter", + Args: "iam ip-filter create " + ipFilterName + " --ip-groups {{.ip_group_id}} --operations MANAGEMENT -o json", + UseStateVars: true, + CaptureID: "ip_filter_id", + JSONFields: map[string]string{ + "filter_name": ipFilterName, + }, + }, + { + Name: "Describe IP filter", + Args: "iam ip-filter describe {{.ip_filter_id}} -o json", + UseStateVars: true, + JSONFields: map[string]string{ + "filter_name": ipFilterName, + }, + }, + { + Name: "List IP filters", + Args: "iam ip-filter list", + Contains: []string{ipFilterName}, + }, + { + Name: "Delete IP filter", + Args: "iam ip-filter delete {{.ip_filter_id}} --force", + UseStateVars: true, + }, + // Clean up IP group + { + Name: "Delete IP group", + Args: "iam ip-group delete {{.ip_group_id}} --force", + UseStateVars: true, + }, + { + Name: "Verify IP group deleted", + Args: "iam ip-group describe {{.ip_group_id}}", + UseStateVars: true, + ExitCode: 1, + }, + } + + for _, step := range steps { + t.Run(step.Name, func(t *testing.T) { + s.runLiveCommand(t, step, state) + }) + } +} diff --git a/test/live/iam_user_live_test.go b/test/live/iam_user_live_test.go new file mode 100644 index 0000000000..cf412d9f71 --- /dev/null +++ b/test/live/iam_user_live_test.go @@ -0,0 +1,31 @@ +//go:build live_test && (all || iam) + +package live + +import ( + "testing" +) + +func (s *CLILiveTestSuite) TestIAMUserLive() { + t := s.T() + t.Parallel() + state := s.setupTestContext(t) + + steps := []CLILiveTest{ + { + Name: "List users", + Args: "iam user list -o json", + JSONFieldsExist: []string{"id"}, + }, + { + Name: "List user invitations", + Args: "iam user invitation list", + }, + } + + for _, step := range steps { + t.Run(step.Name, func(t *testing.T) { + s.runLiveCommand(t, step, state) + }) + } +} diff --git a/test/live/kafka_produce_consume_live_test.go b/test/live/kafka_produce_consume_live_test.go new file mode 100644 index 0000000000..55241f6989 --- /dev/null +++ b/test/live/kafka_produce_consume_live_test.go @@ -0,0 +1,81 @@ +//go:build live_test && (all || kafka) + +package live + +import ( + "os" + "testing" +) + +func (s *CLILiveTestSuite) TestKafkaProduceConsumeLive() { + t := s.T() + t.Parallel() + + envID := os.Getenv("LIVE_TEST_ENVIRONMENT_ID") + clusterID := os.Getenv("KAFKA_STANDARD_AWS_CLUSTER_ID") + apiKey := os.Getenv("KAFKA_STANDARD_AWS_API_KEY") + apiSecret := os.Getenv("KAFKA_STANDARD_AWS_API_SECRET") + if envID == "" || clusterID == "" || apiKey == "" || apiSecret == "" { + t.Skip("Skipping: LIVE_TEST_ENVIRONMENT_ID, KAFKA_STANDARD_AWS_CLUSTER_ID, KAFKA_STANDARD_AWS_API_KEY, and KAFKA_STANDARD_AWS_API_SECRET must be set") + } + + state := s.setupTestContext(t) + + topicName := uniqueName("produce-consume") + state.Set("topic_name", topicName) + + s.registerCleanup(t, "kafka topic delete "+topicName+" --cluster "+clusterID+" --environment "+envID+" --force", state) + + steps := []CLILiveTest{ + { + Name: "Use environment", + Args: "environment use " + envID, + }, + { + Name: "Use kafka cluster", + Args: "kafka cluster use " + clusterID, + }, + { + Name: "Store API key", + Args: "api-key store " + apiKey + " " + apiSecret + " --resource " + clusterID + " --force", + }, + { + Name: "Use API key", + Args: "api-key use " + apiKey + " --resource " + clusterID, + }, + { + Name: "Create topic for produce/consume", + Args: "kafka topic create " + topicName + " --partitions 1", + }, + { + Name: "Produce message to topic", + Args: "kafka topic produce " + topicName, + Input: "test-key:test-value\n", + }, + { + Name: "Consume message from topic", + Args: "kafka topic consume " + topicName + " --from-beginning --exit", + Contains: []string{"test-value"}, + Retries: 5, + }, + { + Name: "List consumer groups", + Args: "kafka consumer group list", + }, + { + Name: "Create client config", + Args: "kafka client-config create java --environment " + envID + " --cluster " + clusterID, + Contains: []string{"bootstrap.servers"}, + }, + { + Name: "Delete topic", + Args: "kafka topic delete " + topicName + " --force", + }, + } + + for _, step := range steps { + t.Run(step.Name, func(t *testing.T) { + s.runLiveCommand(t, step, state) + }) + } +} diff --git a/test/live/kafka_quota_live_test.go b/test/live/kafka_quota_live_test.go new file mode 100644 index 0000000000..76e582058e --- /dev/null +++ b/test/live/kafka_quota_live_test.go @@ -0,0 +1,82 @@ +//go:build live_test && (all || kafka) + +package live + +import ( + "os" + "testing" +) + +func (s *CLILiveTestSuite) TestKafkaQuotaCRUDLive() { + t := s.T() + t.Parallel() + + envID := os.Getenv("LIVE_TEST_ENVIRONMENT_ID") + clusterID := os.Getenv("KAFKA_STANDARD_AWS_CLUSTER_ID") + if envID == "" || clusterID == "" { + t.Skip("Skipping: LIVE_TEST_ENVIRONMENT_ID and KAFKA_STANDARD_AWS_CLUSTER_ID must be set") + } + + state := s.setupTestContext(t) + + // Register cleanup + s.registerCleanup(t, "kafka quota delete {{.quota_id}} --cluster "+clusterID+" --environment "+envID+" --force", state) + + saName := uniqueName("quota-sa") + s.registerCleanup(t, "iam service-account delete {{.quota_sa_id}} --force", state) + + steps := []CLILiveTest{ + { + Name: "Use environment", + Args: "environment use " + envID, + }, + { + Name: "Use kafka cluster", + Args: "kafka cluster use " + clusterID, + }, + // Create a service account to use as the quota principal + { + Name: "Create service account for quota", + Args: `iam service-account create ` + saName + ` --description "SA for quota live test" -o json`, + CaptureID: "quota_sa_id", + }, + { + Name: "Create client quota", + Args: "kafka quota create --ingress 1048576 --egress 1048576 --principals sa:{{.quota_sa_id}} --cluster " + clusterID + " --environment " + envID + ` --description "Live test quota" -o json`, + UseStateVars: true, + CaptureID: "quota_id", + JSONFieldsExist: []string{"id"}, + }, + { + Name: "Describe client quota", + Args: "kafka quota describe {{.quota_id}} --cluster " + clusterID + " --environment " + envID + " -o json", + UseStateVars: true, + JSONFieldsExist: []string{"id"}, + }, + { + Name: "List client quotas", + Args: "kafka quota list --cluster " + clusterID + " --environment " + envID, + }, + { + Name: "Update client quota", + Args: "kafka quota update {{.quota_id}} --ingress 2097152 --egress 2097152 --cluster " + clusterID + " --environment " + envID + ` --description "Updated live test quota"`, + UseStateVars: true, + }, + { + Name: "Delete client quota", + Args: "kafka quota delete {{.quota_id}} --cluster " + clusterID + " --environment " + envID + " --force", + UseStateVars: true, + }, + { + Name: "Delete service account", + Args: "iam service-account delete {{.quota_sa_id}} --force", + UseStateVars: true, + }, + } + + for _, step := range steps { + t.Run(step.Name, func(t *testing.T) { + s.runLiveCommand(t, step, state) + }) + } +} diff --git a/test/live/kafka_region_live_test.go b/test/live/kafka_region_live_test.go new file mode 100644 index 0000000000..966b9f71ba --- /dev/null +++ b/test/live/kafka_region_live_test.go @@ -0,0 +1,37 @@ +//go:build live_test && (all || kafka) + +package live + +import ( + "testing" +) + +func (s *CLILiveTestSuite) TestKafkaRegionListLive() { + t := s.T() + t.Parallel() + state := s.setupTestContext(t) + + steps := []CLILiveTest{ + { + Name: "List kafka regions for aws", + Args: "kafka region list --cloud aws", + Contains: []string{"us-east-1"}, + }, + { + Name: "List kafka regions for gcp", + Args: "kafka region list --cloud gcp", + Contains: []string{"us-east1"}, + }, + { + Name: "List kafka regions for azure", + Args: "kafka region list --cloud azure", + Contains: []string{"eastus"}, + }, + } + + for _, step := range steps { + t.Run(step.Name, func(t *testing.T) { + s.runLiveCommand(t, step, state) + }) + } +} diff --git a/test/live/kafka_share_group_live_test.go b/test/live/kafka_share_group_live_test.go new file mode 100644 index 0000000000..a49de7f32b --- /dev/null +++ b/test/live/kafka_share_group_live_test.go @@ -0,0 +1,42 @@ +//go:build live_test && (all || kafka) + +package live + +import ( + "os" + "testing" +) + +func (s *CLILiveTestSuite) TestKafkaShareGroupListLive() { + t := s.T() + t.Parallel() + + envID := os.Getenv("LIVE_TEST_ENVIRONMENT_ID") + clusterID := os.Getenv("KAFKA_STANDARD_AWS_CLUSTER_ID") + if envID == "" || clusterID == "" { + t.Skip("Skipping: LIVE_TEST_ENVIRONMENT_ID and KAFKA_STANDARD_AWS_CLUSTER_ID must be set") + } + + state := s.setupTestContext(t) + + steps := []CLILiveTest{ + { + Name: "Use environment", + Args: "environment use " + envID, + }, + { + Name: "Use kafka cluster", + Args: "kafka cluster use " + clusterID, + }, + { + Name: "List share groups", + Args: "kafka share-group list --cluster " + clusterID + " --environment " + envID, + }, + } + + for _, step := range steps { + t.Run(step.Name, func(t *testing.T) { + s.runLiveCommand(t, step, state) + }) + } +} diff --git a/test/live/organization_live_test.go b/test/live/organization_live_test.go new file mode 100644 index 0000000000..1a993383ef --- /dev/null +++ b/test/live/organization_live_test.go @@ -0,0 +1,32 @@ +//go:build live_test && (all || core) + +package live + +import ( + "testing" +) + +func (s *CLILiveTestSuite) TestOrganizationLive() { + t := s.T() + t.Parallel() + state := s.setupTestContext(t) + + steps := []CLILiveTest{ + { + Name: "List organizations", + Args: "organization list -o json", + JSONFieldsExist: []string{"id"}, + }, + { + Name: "Describe organization", + Args: "organization describe -o json", + JSONFieldsExist: []string{"id", "name"}, + }, + } + + for _, step := range steps { + t.Run(step.Name, func(t *testing.T) { + s.runLiveCommand(t, step, state) + }) + } +} diff --git a/test/live/plugin_live_test.go b/test/live/plugin_live_test.go new file mode 100644 index 0000000000..5a406509d2 --- /dev/null +++ b/test/live/plugin_live_test.go @@ -0,0 +1,26 @@ +//go:build live_test && (all || core) + +package live + +import ( + "testing" +) + +func (s *CLILiveTestSuite) TestPluginListLive() { + t := s.T() + t.Parallel() + state := s.setupTestContext(t) + + steps := []CLILiveTest{ + { + Name: "List plugins", + Args: "plugin list", + }, + } + + for _, step := range steps { + t.Run(step.Name, func(t *testing.T) { + s.runLiveCommand(t, step, state) + }) + } +} diff --git a/test/live/schema_registry_extended_live_test.go b/test/live/schema_registry_extended_live_test.go new file mode 100644 index 0000000000..1fe2b5d6b2 --- /dev/null +++ b/test/live/schema_registry_extended_live_test.go @@ -0,0 +1,86 @@ +//go:build live_test && (all || schema_registry) + +package live + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func (s *CLILiveTestSuite) TestSchemaRegistryExtendedLive() { + t := s.T() + t.Parallel() + + envID := os.Getenv("LIVE_TEST_ENVIRONMENT_ID") + if envID == "" { + t.Skip("Skipping: LIVE_TEST_ENVIRONMENT_ID must be set") + } + + state := s.setupTestContext(t) + + subjectName := uniqueName("sr-ext") + "-value" + + // Create temp schema files for compatibility validation + schemaContent := `{"type":"record","name":"ExtTestRecord","namespace":"io.confluent.test","fields":[{"name":"id","type":"int"},{"name":"name","type":"string"}]}` + schemaV2Content := `{"type":"record","name":"ExtTestRecord","namespace":"io.confluent.test","fields":[{"name":"id","type":"int"},{"name":"name","type":"string"},{"name":"email","type":["null","string"],"default":null}]}` + + schemaDir, err := os.MkdirTemp("", "cli-live-sr-ext-*") + require.NoError(t, err) + t.Cleanup(func() { os.RemoveAll(schemaDir) }) + + schemaFile := filepath.Join(schemaDir, "test.avsc") + require.NoError(t, os.WriteFile(schemaFile, []byte(schemaContent), 0644)) + schemaFileV2 := filepath.Join(schemaDir, "test_v2.avsc") + require.NoError(t, os.WriteFile(schemaFileV2, []byte(schemaV2Content), 0644)) + + // Cleanup: delete schema versions + s.registerCleanup(t, "schema-registry schema delete --subject "+subjectName+" --version all --force --environment "+envID, state) + + steps := []CLILiveTest{ + { + Name: "Use environment", + Args: "environment use " + envID, + }, + { + Name: "Describe SR cluster", + Args: "schema-registry cluster describe --environment " + envID + " -o json", + JSONFieldsExist: []string{"cluster_id"}, + }, + { + Name: "Register schema for subject", + Args: "schema-registry schema create --subject " + subjectName + " --schema " + schemaFile + " --type avro --environment " + envID + " -o json", + JSONFieldsExist: []string{"id"}, + }, + { + Name: "Validate schema compatibility", + Args: "schema-registry schema compatibility validate --subject " + subjectName + " --schema " + schemaFileV2 + " --type avro --environment " + envID, + Contains: []string{"compatible"}, + }, + { + Name: "Update subject compatibility", + Args: "schema-registry subject update " + subjectName + " --compatibility FULL --environment " + envID, + }, + { + Name: "Describe SR global config", + Args: "schema-registry configuration describe --environment " + envID + " -o json", + JSONFieldsExist: []string{"compatibility_level"}, + }, + { + Name: "List schema-registry endpoints", + Args: "schema-registry endpoint list --environment " + envID, + }, + { + Name: "Delete schema versions", + Args: "schema-registry schema delete --subject " + subjectName + " --version all --force --environment " + envID, + }, + } + + for _, step := range steps { + t.Run(step.Name, func(t *testing.T) { + s.runLiveCommand(t, step, state) + }) + } +} diff --git a/test/live/service_quota_live_test.go b/test/live/service_quota_live_test.go new file mode 100644 index 0000000000..88b9c8a998 --- /dev/null +++ b/test/live/service_quota_live_test.go @@ -0,0 +1,26 @@ +//go:build live_test && (all || core) + +package live + +import ( + "testing" +) + +func (s *CLILiveTestSuite) TestServiceQuotaLive() { + t := s.T() + t.Parallel() + state := s.setupTestContext(t) + + steps := []CLILiveTest{ + { + Name: "List service quotas", + Args: "service-quota list", + }, + } + + for _, step := range steps { + t.Run(step.Name, func(t *testing.T) { + s.runLiveCommand(t, step, state) + }) + } +}