From 4642c5b5fe2b7d8a14a7a4abef60988f539bd3dd Mon Sep 17 00:00:00 2001 From: Harsha Dixit Date: Wed, 10 Dec 2025 12:43:38 +0530 Subject: [PATCH 01/24] feat: event gateway CP, implementation in progress --- .../planner/egw_control_plane_planner.go | 39 +++++++ .../resources/event_gateway_control_plane.go | 100 ++++++++++++++++++ internal/declarative/resources/types.go | 66 ++++++------ internal/declarative/state/client.go | 30 ++++++ .../helpers/event_gateway_control_plane.go | 59 +++++++++++ 5 files changed, 262 insertions(+), 32 deletions(-) create mode 100644 internal/declarative/planner/egw_control_plane_planner.go create mode 100644 internal/declarative/resources/event_gateway_control_plane.go create mode 100644 internal/konnect/helpers/event_gateway_control_plane.go diff --git a/internal/declarative/planner/egw_control_plane_planner.go b/internal/declarative/planner/egw_control_plane_planner.go new file mode 100644 index 00000000..72b1e254 --- /dev/null +++ b/internal/declarative/planner/egw_control_plane_planner.go @@ -0,0 +1,39 @@ +package planner + +import ( + "context" + + "github.com/Kong/kongctl/internal/declarative/resources" +) + +type EGWControlPlanePlannerImpl struct { + planner *Planner + resources *resources.ResourceSet +} + +func newEGWControlPlanePlanner(planner *Planner, resources *resources.ResourceSet) *EGWControlPlanePlannerImpl { + return &EGWControlPlanePlannerImpl{ + planner: planner, + resources: resources, + } +} + +func (p *EGWControlPlanePlannerImpl) GetDesiredEGWControlPlanes(namespace string) []resources.EventGatewayControlPlaneResource { + var result []resources.EventGatewayControlPlaneResource + + return result +} + +func (p *EGWControlPlanePlannerImpl) PlanChanges(ctx context.Context, plannerCtx *Config, plan *Plan) error { + namespace := plannerCtx.Namespace + err := p.planner.planEGWControlPlaneChanges(ctx, plannerCtx, p.GetDesiredEGWControlPlanes(namespace), plan) + if err != nil { + return err + } + + return nil +} + +func (p *Planner) planEGWControlPlaneChanges(ctx context.Context, plannerCtx *Config, desired []resources.EventGatewayControlPlaneResource, plan *Plan) error { + return nil +} diff --git a/internal/declarative/resources/event_gateway_control_plane.go b/internal/declarative/resources/event_gateway_control_plane.go new file mode 100644 index 00000000..fd43b5bc --- /dev/null +++ b/internal/declarative/resources/event_gateway_control_plane.go @@ -0,0 +1,100 @@ +package resources + +import ( + "fmt" + "reflect" + + kkComps "github.com/Kong/sdk-konnect-go/models/components" +) + +type EventGatewayControlPlaneResource struct { + kkComps.CreateGatewayRequest + Ref string `json:"ref" yaml:"ref"` + Kongctl *KongctlMeta `json:"kongctl,omitempty" yaml:"kongctl,omitempty"` + + // Resolved Konnect ID (not serialized) + konnectID string `json:"-" yaml:"-"` +} + +func (e EventGatewayControlPlaneResource) GetType() ResourceType { + return ResourceTypeEventGatewayControlPlane +} + +func (e EventGatewayControlPlaneResource) GetRef() string { + return e.Ref +} + +func (e EventGatewayControlPlaneResource) GetMoniker() string { + return e.Name +} + +func (e EventGatewayControlPlaneResource) GetKonnectID() string { + return e.konnectID +} + +func (e EventGatewayControlPlaneResource) GetDependencies() []ResourceRef { + return []ResourceRef{} +} + +func (e EventGatewayControlPlaneResource) GetLabels() map[string]string { + return e.Labels +} + +func (e *EventGatewayControlPlaneResource) SetLabels(labels map[string]string) { + // Convert map to SDK format + e.Labels = labels +} + +func (e EventGatewayControlPlaneResource) Validate() error { + if err := ValidateRef(e.Ref); err != nil { + return fmt.Errorf("invalid Event Gateway Control Plane ref: %w", err) + } + return nil +} + +func (e *EventGatewayControlPlaneResource) SetDefaults() { + if e.Name == "" { + e.Name = e.Ref + } +} + +func (e EventGatewayControlPlaneResource) GetKonnectMonikerFilter() string { + if e.Name == "" { + return "" + } + return fmt.Sprintf("name[eq]=%s", e.Name) +} + +func (e *EventGatewayControlPlaneResource) TryMatchKonnectResource(konnectResource any) bool { + v := reflect.ValueOf(konnectResource) + + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + + if v.Kind() != reflect.Struct { + return false + } + + nameField := v.FieldByName("Name") + idField := v.FieldByName("ID") + + if !nameField.IsValid() || !idField.IsValid() { + eventGatewayField := v.FieldByName("EventGatewayInfo") + if eventGatewayField.IsValid() && eventGatewayField.Kind() == reflect.Struct { + nameField = eventGatewayField.FieldByName("Name") + idField = eventGatewayField.FieldByName("ID") + } + } + + // Extract values if fields are valid + if nameField.IsValid() && idField.IsValid() && + nameField.Kind() == reflect.String && idField.Kind() == reflect.String { + if nameField.String() == e.Name { + e.konnectID = idField.String() + return true + } + } + + return false +} diff --git a/internal/declarative/resources/types.go b/internal/declarative/resources/types.go index d38ba4ce..a87ff58c 100644 --- a/internal/declarative/resources/types.go +++ b/internal/declarative/resources/types.go @@ -5,27 +5,28 @@ type ResourceType string // Resource type constants const ( - ResourceTypePortal ResourceType = "portal" - ResourceTypeApplicationAuthStrategy ResourceType = "application_auth_strategy" - ResourceTypeControlPlane ResourceType = "control_plane" - ResourceTypeAPI ResourceType = "api" - ResourceTypeAPIVersion ResourceType = "api_version" - ResourceTypeAPIPublication ResourceType = "api_publication" - ResourceTypeAPIImplementation ResourceType = "api_implementation" - ResourceTypeAPIDocument ResourceType = "api_document" - ResourceTypeGatewayService ResourceType = "gateway_service" - ResourceTypePortalCustomization ResourceType = "portal_customization" - ResourceTypePortalCustomDomain ResourceType = "portal_custom_domain" - ResourceTypePortalAuthSettings ResourceType = "portal_auth_settings" - ResourceTypePortalPage ResourceType = "portal_page" - ResourceTypePortalSnippet ResourceType = "portal_snippet" - ResourceTypePortalTeam ResourceType = "portal_team" - ResourceTypePortalTeamRole ResourceType = "portal_team_role" - ResourceTypePortalAssetLogo ResourceType = "portal_asset_logo" - ResourceTypePortalAssetFavicon ResourceType = "portal_asset_favicon" - ResourceTypePortalEmailConfig ResourceType = "portal_email_config" - ResourceTypePortalEmailTemplate ResourceType = "portal_email_template" - ResourceTypeCatalogService ResourceType = "catalog_service" + ResourceTypePortal ResourceType = "portal" + ResourceTypeApplicationAuthStrategy ResourceType = "application_auth_strategy" + ResourceTypeControlPlane ResourceType = "control_plane" + ResourceTypeAPI ResourceType = "api" + ResourceTypeAPIVersion ResourceType = "api_version" + ResourceTypeAPIPublication ResourceType = "api_publication" + ResourceTypeAPIImplementation ResourceType = "api_implementation" + ResourceTypeAPIDocument ResourceType = "api_document" + ResourceTypeGatewayService ResourceType = "gateway_service" + ResourceTypePortalCustomization ResourceType = "portal_customization" + ResourceTypePortalCustomDomain ResourceType = "portal_custom_domain" + ResourceTypePortalAuthSettings ResourceType = "portal_auth_settings" + ResourceTypePortalPage ResourceType = "portal_page" + ResourceTypePortalSnippet ResourceType = "portal_snippet" + ResourceTypePortalTeam ResourceType = "portal_team" + ResourceTypePortalTeamRole ResourceType = "portal_team_role" + ResourceTypePortalAssetLogo ResourceType = "portal_asset_logo" + ResourceTypePortalAssetFavicon ResourceType = "portal_asset_favicon" + ResourceTypePortalEmailConfig ResourceType = "portal_email_config" + ResourceTypePortalEmailTemplate ResourceType = "portal_email_template" + ResourceTypeCatalogService ResourceType = "catalog_service" + ResourceTypeEventGatewayControlPlane ResourceType = "event_gateway_control_plane" ) const ( @@ -55,17 +56,18 @@ type ResourceSet struct { APIImplementations []APIImplementationResource `yaml:"api_implementations,omitempty" json:"api_implementations,omitempty"` //nolint:lll APIDocuments []APIDocumentResource `yaml:"api_documents,omitempty" json:"api_documents,omitempty"` //nolint:lll // Portal child resources can be defined at root level (with parent reference) or nested under Portals - PortalCustomizations []PortalCustomizationResource `yaml:"portal_customizations,omitempty" json:"portal_customizations,omitempty"` //nolint:lll - PortalAuthSettings []PortalAuthSettingsResource `yaml:"portal_auth_settings,omitempty" json:"portal_auth_settings,omitempty"` //nolint:lll - PortalCustomDomains []PortalCustomDomainResource `yaml:"portal_custom_domains,omitempty" json:"portal_custom_domains,omitempty"` //nolint:lll - PortalPages []PortalPageResource `yaml:"portal_pages,omitempty" json:"portal_pages,omitempty"` //nolint:lll - PortalSnippets []PortalSnippetResource `yaml:"portal_snippets,omitempty" json:"portal_snippets,omitempty"` //nolint:lll - PortalTeams []PortalTeamResource `yaml:"portal_teams,omitempty" json:"portal_teams,omitempty"` //nolint:lll - PortalTeamRoles []PortalTeamRoleResource `yaml:"portal_team_roles,omitempty" json:"portal_team_roles,omitempty"` //nolint:lll - PortalAssetLogos []PortalAssetLogoResource `yaml:"portal_asset_logos,omitempty" json:"portal_asset_logos,omitempty"` //nolint:lll - PortalAssetFavicons []PortalAssetFaviconResource `yaml:"portal_asset_favicons,omitempty" json:"portal_asset_favicons,omitempty"` //nolint:lll - PortalEmailConfigs []PortalEmailConfigResource `yaml:"portal_email_configs,omitempty" json:"portal_email_configs,omitempty"` //nolint:lll - PortalEmailTemplates []PortalEmailTemplateResource `yaml:"portal_email_templates,omitempty" json:"portal_email_templates,omitempty"` //nolint:lll + PortalCustomizations []PortalCustomizationResource `yaml:"portal_customizations,omitempty" json:"portal_customizations,omitempty"` //nolint:lll + PortalAuthSettings []PortalAuthSettingsResource `yaml:"portal_auth_settings,omitempty" json:"portal_auth_settings,omitempty"` //nolint:lll + PortalCustomDomains []PortalCustomDomainResource `yaml:"portal_custom_domains,omitempty" json:"portal_custom_domains,omitempty"` //nolint:lll + PortalPages []PortalPageResource `yaml:"portal_pages,omitempty" json:"portal_pages,omitempty"` //nolint:lll + PortalSnippets []PortalSnippetResource `yaml:"portal_snippets,omitempty" json:"portal_snippets,omitempty"` //nolint:lll + PortalTeams []PortalTeamResource `yaml:"portal_teams,omitempty" json:"portal_teams,omitempty"` //nolint:lll + PortalTeamRoles []PortalTeamRoleResource `yaml:"portal_team_roles,omitempty" json:"portal_team_roles,omitempty"` //nolint:lll + PortalAssetLogos []PortalAssetLogoResource `yaml:"portal_asset_logos,omitempty" json:"portal_asset_logos,omitempty"` //nolint:lll + PortalAssetFavicons []PortalAssetFaviconResource `yaml:"portal_asset_favicons,omitempty" json:"portal_asset_favicons,omitempty"` //nolint:lll + PortalEmailConfigs []PortalEmailConfigResource `yaml:"portal_email_configs,omitempty" json:"portal_email_configs,omitempty"` //nolint:lll + PortalEmailTemplates []PortalEmailTemplateResource `yaml:"portal_email_templates,omitempty" json:"portal_email_templates,omitempty"` //nolint:lll + EventGatewayControlPlanes []EventGatewayControlPlaneResource `yaml:"event_gateway_control_planes,omitempty" json:"event_gateway_control_planes,omitempty"` //nolint:lll // DefaultNamespace tracks namespace from _defaults when no resources are present // This is used by the planner to determine which namespace to check for deletions diff --git a/internal/declarative/state/client.go b/internal/declarative/state/client.go index 709bdd82..4a84fbd0 100644 --- a/internal/declarative/state/client.go +++ b/internal/declarative/state/client.go @@ -44,6 +44,9 @@ type ClientConfig struct { APIPublicationAPI helpers.APIPublicationAPI APIImplementationAPI helpers.APIImplementationAPI APIDocumentAPI helpers.APIDocumentAPI + + // Event Gateway Control Plane API + EGWControlPlaneAPI helpers.EGWControlPlaneAPI } // Client wraps Konnect SDK for state management @@ -73,6 +76,9 @@ type Client struct { apiPublicationAPI helpers.APIPublicationAPI apiImplementationAPI helpers.APIImplementationAPI apiDocumentAPI helpers.APIDocumentAPI + + // Event Gateway Control Plane API + egwControlPlaneAPI helpers.EGWControlPlaneAPI } // NewClient creates a new state client with the provided configuration @@ -103,6 +109,9 @@ func NewClient(config ClientConfig) *Client { apiPublicationAPI: config.APIPublicationAPI, apiImplementationAPI: config.APIImplementationAPI, apiDocumentAPI: config.APIDocumentAPI, + + // Event Gateway Control Plane APIs + egwControlPlaneAPI: config.EGWControlPlaneAPI, } } @@ -219,6 +228,11 @@ type ApplicationAuthStrategy struct { NormalizedLabels map[string]string // Non-pointer labels } +type EGWControlPlane struct { + kkComps.EventGatewayInfo + NormalizedLabels map[string]string // Non-pointer labels +} + // ListManagedPortals returns all KONGCTL-managed portals in the specified namespaces // If namespaces is empty, no resources are returned (breaking change from previous behavior) // To get all managed resources across all namespaces, pass []string{"*"} @@ -3184,6 +3198,22 @@ func (c *Client) RemovePortalTeamRole(ctx context.Context, portalID string, team return nil } +func ListManagedEGWControlPlanes(ctx context.Context, namespaces []string) { + // Placeholder for future implementation +} + +func CreateEGWControlPlane(ctx context.Context, req kkComps.CreateGatewayRequest, namespace string) { + // Placeholder for future implementation +} + +func UpdateEGWControlPlane(ctx context.Context, id string, req kkComps.UpdateGatewayRequest, namespace string) { + // Placeholder for future implementation +} + +func DeleteEGWControlPlane(ctx context.Context, id string) { + // Placeholder for future implementation +} + func getString(value *string) string { if value == nil { return "" diff --git a/internal/konnect/helpers/event_gateway_control_plane.go b/internal/konnect/helpers/event_gateway_control_plane.go new file mode 100644 index 00000000..0e9bb053 --- /dev/null +++ b/internal/konnect/helpers/event_gateway_control_plane.go @@ -0,0 +1,59 @@ +package helpers + +import ( + "context" + + kkSDK "github.com/Kong/sdk-konnect-go" + kkComps "github.com/Kong/sdk-konnect-go/models/components" + kkOps "github.com/Kong/sdk-konnect-go/models/operations" +) + +type EGWControlPlaneAPI interface { + // Event Gateway Control Plane operations + ListEGWControlPlanes(ctx context.Context, request kkOps.ListEventGatewaysRequest, + opts ...kkOps.Option) (*kkOps.ListEventGatewaysResponse, error) + FetchEGWControlPlane(ctx context.Context, gatewayID string, + opts ...kkOps.Option) (*kkOps.GetEventGatewayResponse, error) + CreateEGWControlPlane(ctx context.Context, request kkComps.CreateGatewayRequest, + opts ...kkOps.Option) (*kkOps.CreateEventGatewayResponse, error) + UpdateEGWControlPlane(ctx context.Context, gatewayID string, request kkComps.UpdateGatewayRequest, + opts ...kkOps.Option) (*kkOps.UpdateEventGatewayResponse, error) + DeleteEGWControlPlane(ctx context.Context, gatewayID string, + opts ...kkOps.Option) (*kkOps.DeleteEventGatewayResponse, error) +} + +// APIAPIImpl provides an implementation of the APIFullAPI interface +// It implements both APIAPI and all child resource operations for backward compatibility +type EGWControlPlaneAPIImpl struct { + SDK *kkSDK.SDK +} + +func (a *EGWControlPlaneAPIImpl) ListEGWControlPlanes(ctx context.Context, request kkOps.ListEventGatewaysRequest, + opts ...kkOps.Option, +) (*kkOps.ListEventGatewaysResponse, error) { + return a.SDK.EventGateways.ListEventGateways(ctx, request, opts...) +} + +func (a *EGWControlPlaneAPIImpl) FetchEGWControlPlane(ctx context.Context, gatewayID string, + opts ...kkOps.Option, +) (*kkOps.GetEventGatewayResponse, error) { + return a.SDK.EventGateways.GetEventGateway(ctx, gatewayID, opts...) +} + +func (a *EGWControlPlaneAPIImpl) CreateEGWControlPlane(ctx context.Context, request kkComps.CreateGatewayRequest, + opts ...kkOps.Option, +) (*kkOps.CreateEventGatewayResponse, error) { + return a.SDK.EventGateways.CreateEventGateway(ctx, request, opts...) +} + +func (a *EGWControlPlaneAPIImpl) UpdateEGWControlPlane(ctx context.Context, gatewayID string, request kkComps.UpdateGatewayRequest, + opts ...kkOps.Option, +) (*kkOps.UpdateEventGatewayResponse, error) { + return a.SDK.EventGateways.UpdateEventGateway(ctx, gatewayID, request, opts...) +} + +func (a *EGWControlPlaneAPIImpl) DeleteEGWControlPlane(ctx context.Context, gatewayID string, + opts ...kkOps.Option, +) (*kkOps.DeleteEventGatewayResponse, error) { + return a.SDK.EventGateways.DeleteEventGateway(ctx, gatewayID, opts...) +} From e2147a6d0253ddd2ed381c353f25212060e0e886 Mon Sep 17 00:00:00 2001 From: Harsha Dixit Date: Wed, 17 Dec 2025 11:07:57 +0530 Subject: [PATCH 02/24] feat: bootstrap event gateway control plane --- .../event_gateway_control_plane_adapter.go | 140 ++++++++++++++++ internal/declarative/executor/executor.go | 22 ++- internal/declarative/labels/labels.go | 14 ++ .../planner/egw_control_plane_planner.go | 149 +++++++++++++++++- internal/declarative/planner/interfaces.go | 7 + internal/declarative/planner/planner.go | 16 +- internal/declarative/state/client.go | 66 +++++++- 7 files changed, 391 insertions(+), 23 deletions(-) create mode 100644 internal/declarative/executor/event_gateway_control_plane_adapter.go diff --git a/internal/declarative/executor/event_gateway_control_plane_adapter.go b/internal/declarative/executor/event_gateway_control_plane_adapter.go new file mode 100644 index 00000000..190e0a93 --- /dev/null +++ b/internal/declarative/executor/event_gateway_control_plane_adapter.go @@ -0,0 +1,140 @@ +package executor + +import ( + "context" + + "github.com/Kong/sdk-konnect-go/models/components" + "github.com/kong/kongctl/internal/declarative/common" + "github.com/kong/kongctl/internal/declarative/labels" + "github.com/kong/kongctl/internal/declarative/planner" + "github.com/kong/kongctl/internal/declarative/state" +) + +type EventGatewayControlPlaneControlPlaneAdapter struct { + client *state.Client +} + +func NewEventGatewayControlPlaneControlPlaneAdapter(client *state.Client) *EventGatewayControlPlaneControlPlaneAdapter { + return &EventGatewayControlPlaneControlPlaneAdapter{ + client: client, + } +} + +func (a *EventGatewayControlPlaneControlPlaneAdapter) MapCreateFields(_ context.Context, + execCtx *ExecutionContext, + fields map[string]any, create *components.CreateGatewayRequest, +) error { + namespace := execCtx.Namespace + protection := execCtx.Protection + + // Map required fields + create.Name = common.ExtractResourceName(fields) + + // Map optional fields + common.MapOptionalStringFieldToPtr(&create.Description, fields, "description") + + // Handle labels + userLabels := labels.ExtractLabelsFromField(fields["labels"]) + labelsMap := labels.BuildCreateLabels(userLabels, namespace, protection) + + // Convert to SDK format + if len(labelsMap) > 0 { + create.Labels = labelsMap + } + + return nil +} + +func (a *EventGatewayControlPlaneControlPlaneAdapter) MapUpdateFields( + _ context.Context, execCtx *ExecutionContext, + fields map[string]any, update *components.UpdateGatewayRequest, + currentLabels map[string]string, +) error { + namespace := execCtx.Namespace + protection := execCtx.Protection + + // Only include changed fields + for field, value := range fields { + switch field { + case "name": + if name, ok := value.(string); ok { + update.Name = &name + } + case "description": + if desc, ok := value.(string); ok { + update.Description = &desc + } + } + } + + // Handle labels + desiredLabels := labels.ExtractLabelsFromField(fields["labels"]) + if desiredLabels != nil { + plannerCurrentLabels := labels.ExtractLabelsFromField(fields[planner.FieldCurrentLabels]) + if plannerCurrentLabels != nil { + currentLabels = plannerCurrentLabels + } + + labelsMap := labels.BuildUpdateLabels(desiredLabels, currentLabels, namespace, protection) + + // Convert to SDK format + update.Labels = labels.ConvertPointerMapsToStringMap(labelsMap) + // OR: update.Labels = labelsMap + } else if currentLabels != nil { + // No label changes, preserve with updated protection + labelsMap := labels.BuildUpdateLabels(currentLabels, currentLabels, namespace, protection) + update.Labels = labels.ConvertPointerMapsToStringMap(labelsMap) + } + + return nil +} + +func (a *EventGatewayControlPlaneControlPlaneAdapter) Create( + ctx context.Context, req components.CreateGatewayRequest, + namespace string, _ *ExecutionContext, +) (string, error) { + resp, err := a.client.CreateEventGatewayControlPlane(ctx, req, namespace) + if err != nil { + return "", err + } + return resp, nil +} + +func (a *EventGatewayControlPlaneControlPlaneAdapter) Update( + ctx context.Context, id string, req components.UpdateGatewayRequest, + namespace string, _ *ExecutionContext, +) (string, error) { + resp, err := a.client.UpdateEventGatewayControlPlane(ctx, id, req, namespace) + if err != nil { + return "", err + } + return resp, nil +} + +// GetByID gets a event_gateway_control_plane by ID +func (a *EventGatewayControlPlaneControlPlaneAdapter) GetByID(_ context.Context, _ string, _ *ExecutionContext) (ResourceInfo, error) { + // todo - why and fix + return nil, nil +} + +// GetByName gets a event_gateway_control_plane by name +func (a *EventGatewayControlPlaneControlPlaneAdapter) GetByName(ctx context.Context, name string) (ResourceInfo, error) { + // todo - why and fix + return nil, nil +} + +func (a *EventGatewayControlPlaneControlPlaneAdapter) Delete(ctx context.Context, id string, _ *ExecutionContext) error { + return a.client.DeleteEventGatewayControlPlane(ctx, id) +} + +func (a *EventGatewayControlPlaneControlPlaneAdapter) ResourceType() string { + return "event_gateway_control_plane" +} + +func (a *EventGatewayControlPlaneControlPlaneAdapter) RequiredFields() []string { + return []string{"name"} +} + +func (a *EventGatewayControlPlaneControlPlaneAdapter) SupportsUpdate() bool { + return true +} diff --git a/internal/declarative/executor/executor.go b/internal/declarative/executor/executor.go index f32bb36e..b2fedd59 100644 --- a/internal/declarative/executor/executor.go +++ b/internal/declarative/executor/executor.go @@ -31,11 +31,12 @@ type Executor struct { stateCache *state.Cache // Resource executors - portalExecutor *BaseExecutor[kkComps.CreatePortal, kkComps.UpdatePortal] - controlPlaneExecutor *BaseExecutor[kkComps.CreateControlPlaneRequest, kkComps.UpdateControlPlaneRequest] - apiExecutor *BaseExecutor[kkComps.CreateAPIRequest, kkComps.UpdateAPIRequest] - authStrategyExecutor *BaseExecutor[kkComps.CreateAppAuthStrategyRequest, kkComps.UpdateAppAuthStrategyRequest] - catalogServiceExecutor *BaseExecutor[kkComps.CreateCatalogService, kkComps.UpdateCatalogService] + portalExecutor *BaseExecutor[kkComps.CreatePortal, kkComps.UpdatePortal] + controlPlaneExecutor *BaseExecutor[kkComps.CreateControlPlaneRequest, kkComps.UpdateControlPlaneRequest] + apiExecutor *BaseExecutor[kkComps.CreateAPIRequest, kkComps.UpdateAPIRequest] + authStrategyExecutor *BaseExecutor[kkComps.CreateAppAuthStrategyRequest, kkComps.UpdateAppAuthStrategyRequest] + catalogServiceExecutor *BaseExecutor[kkComps.CreateCatalogService, kkComps.UpdateCatalogService] + eventGatewayControlPlaneExecutor *BaseExecutor[kkComps.CreateGatewayRequest, kkComps.UpdateGatewayRequest] // Portal child resource executors portalCustomizationExecutor *BaseSingletonExecutor[kkComps.PortalCustomization] @@ -97,6 +98,11 @@ func New(client *state.Client, reporter ProgressReporter, dryRun bool) *Executor client, dryRun, ) + e.eventGatewayControlPlaneExecutor = NewBaseExecutor[kkComps.CreateGatewayRequest, kkComps.UpdateGatewayRequest]( + NewEventGatewayControlPlaneControlPlaneAdapter(client), + client, + dryRun, + ) // Initialize portal child resource executors e.portalCustomizationExecutor = NewBaseSingletonExecutor[kkComps.PortalCustomization]( @@ -1414,6 +1420,8 @@ func (e *Executor) createResource(ctx context.Context, change *planner.PlannedCh change.References["portal_id"] = portalRef } return e.portalEmailTemplateExecutor.Create(ctx, *change) + case "event_gateway_control_plane": + return e.eventGatewayControlPlaneExecutor.Create(ctx, *change) default: return "", fmt.Errorf("create operation not yet implemented for %s", change.ResourceType) } @@ -1625,6 +1633,8 @@ func (e *Executor) updateResource(ctx context.Context, change *planner.PlannedCh } return e.apiVersionExecutor.Update(ctx, *change) // Note: api_publication and api_implementation don't support update + case "event_gateway_control_plane": + return e.eventGatewayControlPlaneExecutor.Update(ctx, *change) default: return "", fmt.Errorf("update operation not yet implemented for %s", change.ResourceType) } @@ -1753,6 +1763,8 @@ func (e *Executor) deleteResource(ctx context.Context, change *planner.PlannedCh } return e.portalEmailTemplateExecutor.Delete(ctx, *change) // Note: portal_customization is a singleton resource and cannot be deleted + case "event_gateway_control_plane": + return e.eventGatewayControlPlaneExecutor.Delete(ctx, *change) default: return fmt.Errorf("delete operation not yet implemented for %s", change.ResourceType) } diff --git a/internal/declarative/labels/labels.go b/internal/declarative/labels/labels.go index 8cc03424..d7a82222 100644 --- a/internal/declarative/labels/labels.go +++ b/internal/declarative/labels/labels.go @@ -381,3 +381,17 @@ func ConvertStringMapToPointerMap(labels map[string]string) map[string]*string { } return result } + +// ConvertPointerMapsToStringMap converts map[string]*string to map[string]string +func ConvertPointerMapsToStringMap(labels map[string]*string) map[string]string { + if len(labels) == 0 { + return nil + } + + result := make(map[string]string) + for k, v := range labels { + val := v + result[k] = *val + } + return result +} diff --git a/internal/declarative/planner/egw_control_plane_planner.go b/internal/declarative/planner/egw_control_plane_planner.go index 72b1e254..1fe729bc 100644 --- a/internal/declarative/planner/egw_control_plane_planner.go +++ b/internal/declarative/planner/egw_control_plane_planner.go @@ -3,18 +3,20 @@ package planner import ( "context" - "github.com/Kong/kongctl/internal/declarative/resources" + "github.com/kong/kongctl/internal/declarative/labels" + "github.com/kong/kongctl/internal/declarative/resources" + "github.com/kong/kongctl/internal/declarative/state" ) type EGWControlPlanePlannerImpl struct { - planner *Planner + *BasePlanner resources *resources.ResourceSet } -func newEGWControlPlanePlanner(planner *Planner, resources *resources.ResourceSet) *EGWControlPlanePlannerImpl { +func NewEGWControlPlanePlanner(planner *BasePlanner, resources *resources.ResourceSet) *EGWControlPlanePlannerImpl { return &EGWControlPlanePlannerImpl{ - planner: planner, - resources: resources, + BasePlanner: planner, + resources: resources, } } @@ -37,3 +39,140 @@ func (p *EGWControlPlanePlannerImpl) PlanChanges(ctx context.Context, plannerCtx func (p *Planner) planEGWControlPlaneChanges(ctx context.Context, plannerCtx *Config, desired []resources.EventGatewayControlPlaneResource, plan *Plan) error { return nil } + +func (p *Planner) shouldUpdateEGWControlPlaneResource(current state.EventGatewayControlPlane, desired resources.EventGatewayControlPlaneResource) (bool, map[string]any) { + updates := make(map[string]any) + + if desired.Name != current.Name { + currentName := current.Name + if currentName != desired.Name { + updates["name"] = desired.Name + } + } + + if desired.Description != current.Description { + currentDesc := getString(current.Description) + if currentDesc != *desired.Description { + updates["description"] = *desired.Description + } + } + + if desired.Labels != nil { + if labels.CompareUserLabels(current.NormalizedLabels, desired.GetLabels()) { + updates["labels"] = desired.GetLabels() + } + } + + // Add other field comparisons + + return len(updates) > 0, updates +} + +func (p *Planner) planEGWControlPlaneCreate(egwControlPlane resources.EventGatewayControlPlaneResource, plan *Plan) string { + var protection any + if egwControlPlane.Kongctl != nil && egwControlPlane.Kongctl.Protected != nil { + protection = *egwControlPlane.Kongctl.Protected + } + + // Extract namespace + namespace := DefaultNamespace + if egwControlPlane.Kongctl != nil && egwControlPlane.Kongctl.Namespace != nil { + namespace = *egwControlPlane.Kongctl.Namespace + } + + config := CreateConfig{ + ResourceType: string(egwControlPlane.GetType()), + ResourceName: egwControlPlane.Name, + ResourceRef: egwControlPlane.Ref, + RequiredFields: []string{"name"}, + FieldExtractor: func(_ any) map[string]any { + return extractEGWControlPlaneFields(egwControlPlane) + }, + Namespace: namespace, + DependsOn: []string{}, + } + + change, err := p.genericPlanner.PlanCreate(context.Background(), config) + if err != nil { + return "" + } + change.Protection = protection + plan.AddChange(change) + return change.ID +} + +func extractEGWControlPlaneFields(resource any) map[string]any { + fields := make(map[string]any) + egwControlPlane, ok := resource.(resources.EventGatewayControlPlaneResource) + if !ok { + return fields + } + + fields["name"] = egwControlPlane.Name + + if egwControlPlane.Description != nil { + fields["description"] = *egwControlPlane.Description + } + + if len(egwControlPlane.GetLabels()) > 0 { + fields["labels"] = egwControlPlane.GetLabels() + } + return fields +} + +func (p *Planner) planEGWControlPlaneUpdateWithFields( + current state.EventGatewayControlPlane, + desired resources.EventGatewayControlPlaneResource, + updateFields map[string]any, + plan *Plan) { + var protection any + if desired.Kongctl != nil && desired.Kongctl.Protected != nil { + protection = *desired.Kongctl.Protected + } + + // Extract namespace + namespace := DefaultNamespace + if desired.Kongctl != nil && desired.Kongctl.Namespace != nil { + namespace = *desired.Kongctl.Namespace + } + + updateFields[FieldCurrentLabels] = current.NormalizedLabels + config := UpdateConfig{ + ResourceType: string(desired.GetType()), + ResourceName: desired.Name, + ResourceRef: desired.Ref, + RequiredFields: []string{"name"}, + FieldComparator: func(current, desired map[string]any) bool { + // todo + return false + }, + Namespace: namespace, + } + + change, err := p.genericPlanner.PlanUpdate(context.Background(), config) + if err != nil { + // Handle error appropriately - this is example code + // In real implementation, return the error + return + } + change.Protection = protection + + plan.AddChange(change) +} + +func (p *Planner) planEGWControlPlaneDelete(egwControlPlane state.EventGatewayControlPlane, plan *Plan) { + namespace := DefaultNamespace + if ns, ok := egwControlPlane.NormalizedLabels[labels.NamespaceKey]; ok { + namespace = ns + } + + config := DeleteConfig{ + ResourceType: string(resources.ResourceTypeEventGatewayControlPlane), + ResourceName: egwControlPlane.Name, + ResourceID: egwControlPlane.ID, + Namespace: namespace, + } + + change := p.genericPlanner.PlanDelete(context.Background(), config) + plan.AddChange(change) +} diff --git a/internal/declarative/planner/interfaces.go b/internal/declarative/planner/interfaces.go index 252e8911..65758c7a 100644 --- a/internal/declarative/planner/interfaces.go +++ b/internal/declarative/planner/interfaces.go @@ -40,3 +40,10 @@ type APIPlanner interface { type CatalogServicePlanner interface { ResourcePlanner } + +// EGWControlPlanePlanner handles planning for Event Gateway Control Plane resources +type EGWControlPlanePlanner interface { + ResourcePlanner + + // Additional Event Gateway Control Plane-specific methods if needed +} diff --git a/internal/declarative/planner/planner.go b/internal/declarative/planner/planner.go index b78e26ee..c541abc2 100644 --- a/internal/declarative/planner/planner.go +++ b/internal/declarative/planner/planner.go @@ -33,11 +33,12 @@ type Planner struct { genericPlanner *GenericPlanner // Resource-specific planners - portalPlanner PortalPlanner - controlPlanePlanner ControlPlanePlanner - authStrategyPlanner AuthStrategyPlanner - apiPlanner APIPlanner - catalogServicePlanner CatalogServicePlanner + portalPlanner PortalPlanner + controlPlanePlanner ControlPlanePlanner + authStrategyPlanner AuthStrategyPlanner + apiPlanner APIPlanner + catalogServicePlanner CatalogServicePlanner + eventGatewayControlPlanePlanner EGWControlPlanePlanner // ResourceSet containing all desired resources resources *resources.ResourceSet @@ -73,6 +74,7 @@ func NewPlanner(client *state.Client, logger *slog.Logger) *Planner { // Initialize resource-specific planners base := NewBasePlanner(p) p.portalPlanner = NewPortalPlanner(base) + p.eventGatewayControlPlanePlanner = NewEGWControlPlanePlanner(base, p.resources) p.controlPlanePlanner = NewControlPlanePlanner(base) p.authStrategyPlanner = NewAuthStrategyPlanner(base) p.catalogServicePlanner = NewCatalogServicePlanner(base) @@ -208,6 +210,10 @@ func (p *Planner) GeneratePlan(ctx context.Context, rs *resources.ResourceSet, o return nil, fmt.Errorf("failed to plan API changes for namespace %s: %w", namespace, err) } + if err := namespacePlanner.eventGatewayControlPlanePlanner.PlanChanges(ctx, plannerCtx, namespacePlan); err != nil { + return nil, fmt.Errorf("failed to plan Event Gateway Control Plane changes for namespace %s: %w", namespace, err) + } + // Merge namespace plan into base plan basePlan.Changes = append(basePlan.Changes, namespacePlan.Changes...) basePlan.Warnings = append(basePlan.Warnings, namespacePlan.Warnings...) diff --git a/internal/declarative/state/client.go b/internal/declarative/state/client.go index 4a84fbd0..44e3be2f 100644 --- a/internal/declarative/state/client.go +++ b/internal/declarative/state/client.go @@ -228,7 +228,7 @@ type ApplicationAuthStrategy struct { NormalizedLabels map[string]string // Non-pointer labels } -type EGWControlPlane struct { +type EventGatewayControlPlane struct { kkComps.EventGatewayInfo NormalizedLabels map[string]string // Non-pointer labels } @@ -3198,20 +3198,70 @@ func (c *Client) RemovePortalTeamRole(ctx context.Context, portalID string, team return nil } -func ListManagedEGWControlPlanes(ctx context.Context, namespaces []string) { - // Placeholder for future implementation +func (c *Client) ListManagedEventGatewayControlPlanes(ctx context.Context, namespaces []string) ([]EventGatewayControlPlane, error) { + req := kkOps.ListEventGatewaysRequest{} + + resp, err := c.egwControlPlaneAPI.ListEGWControlPlanes(ctx, req) + if err != nil { + return nil, WrapAPIError(err, "list event gateway control planes", nil) + } + + var filteredEGWControlPlanes []EventGatewayControlPlane + for _, f := range resp.ListEventGatewaysResponse.Data { + + // Filter by managed status and namespace + if labels.IsManagedResource(f.Labels) { + if shouldIncludeNamespace(f.Labels[labels.NamespaceKey], namespaces) { + eventGatewayControlPlane := EventGatewayControlPlane{ + f, + f.Labels, + } + filteredEGWControlPlanes = append(filteredEGWControlPlanes, eventGatewayControlPlane) + } + } + } + return filteredEGWControlPlanes, nil } -func CreateEGWControlPlane(ctx context.Context, req kkComps.CreateGatewayRequest, namespace string) { - // Placeholder for future implementation +func (c *Client) CreateEventGatewayControlPlane(ctx context.Context, req kkComps.CreateGatewayRequest, namespace string) (string, error) { + resp, err := c.egwControlPlaneAPI.CreateEGWControlPlane(ctx, req) + if err != nil { + return "", WrapAPIError(err, "create event gateway control plane", &ErrorWrapperOptions{ + ResourceType: "event_gateway_control_plane", + ResourceName: "", // Adjust based on SDK + Namespace: namespace, + UseEnhanced: true, + }) + } + + if err := ValidateResponse(resp.EventGatewayInfo, "create event gateway control plane"); err != nil { + return "", err + } + + return resp.EventGatewayInfo.ID, nil } -func UpdateEGWControlPlane(ctx context.Context, id string, req kkComps.UpdateGatewayRequest, namespace string) { - // Placeholder for future implementation +func (c *Client) UpdateEventGatewayControlPlane(ctx context.Context, id string, req kkComps.UpdateGatewayRequest, namespace string) (string, error) { + resp, err := c.egwControlPlaneAPI.UpdateEGWControlPlane(ctx, id, req) + if err != nil { + return "", WrapAPIError(err, "update event gateway control plane", &ErrorWrapperOptions{ + ResourceType: "event_gateway_control_plane", + ResourceName: "", // Adjust based on SDK + Namespace: namespace, + UseEnhanced: true, + }) + } + + return resp.EventGatewayInfo.ID, nil } -func DeleteEGWControlPlane(ctx context.Context, id string) { +func (c *Client) DeleteEventGatewayControlPlane(ctx context.Context, id string) error { // Placeholder for future implementation + _, err := c.egwControlPlaneAPI.DeleteEGWControlPlane(ctx, id) + if err != nil { + return WrapAPIError(err, "delete event gateway control plane", nil) + } + return nil } func getString(value *string) string { From e60c20a73765596838f3a9d5bb820c3c06920871 Mon Sep 17 00:00:00 2001 From: Harsha Dixit Date: Wed, 17 Dec 2025 20:06:03 +0530 Subject: [PATCH 03/24] feat: diff and apply for event gateway control plane --- .../konnect/declarative/declarative.go | 7 +- internal/declarative/loader/loader.go | 25 ++++++ internal/declarative/planner/base_planner.go | 5 ++ .../planner/egw_control_plane_planner.go | 84 ++++++++++++++++++- internal/declarative/planner/planner.go | 6 ++ internal/declarative/resources/types.go | 11 +++ internal/konnect/helpers/sdk.go | 10 +++ internal/konnect/helpers/sdk_mock.go | 11 +++ 8 files changed, 154 insertions(+), 5 deletions(-) diff --git a/internal/cmd/root/products/konnect/declarative/declarative.go b/internal/cmd/root/products/konnect/declarative/declarative.go index 2a85ac56..99ec935d 100644 --- a/internal/cmd/root/products/konnect/declarative/declarative.go +++ b/internal/cmd/root/products/konnect/declarative/declarative.go @@ -441,7 +441,7 @@ func runDiff(command *cobra.Command, args []string) error { } totalResources := len(resourceSet.Portals) + len(resourceSet.ApplicationAuthStrategies) + - len(resourceSet.ControlPlanes) + len(resourceSet.APIs) + len(resourceSet.CatalogServices) + len(resourceSet.ControlPlanes) + len(resourceSet.APIs) + len(resourceSet.CatalogServices) + len(resourceSet.EventGatewayControlPlanes) if totalResources == 0 { if len(filenames) == 0 { return fmt.Errorf("no configuration files found. Use -f to specify files or --plan to use existing plan") @@ -907,7 +907,7 @@ func runApply(command *cobra.Command, args []string) error { // Check if configuration is empty totalResources := len(resourceSet.Portals) + len(resourceSet.ApplicationAuthStrategies) + - len(resourceSet.ControlPlanes) + len(resourceSet.APIs) + len(resourceSet.CatalogServices) + len(resourceSet.ControlPlanes) + len(resourceSet.APIs) + len(resourceSet.EventGatewayControlPlanes) + len(resourceSet.CatalogServices) if totalResources == 0 { // Check if we're using default directory (no explicit sources) @@ -1482,5 +1482,8 @@ func createStateClient(kkClient helpers.SDKAPI) *state.Client { APIPublicationAPI: kkClient.GetAPIPublicationAPI(), APIImplementationAPI: kkClient.GetAPIImplementationAPI(), APIDocumentAPI: kkClient.GetAPIDocumentAPI(), + + // Event Gateway APIs + EGWControlPlaneAPI: kkClient.GetEventGatewayControlPlaneAPI(), }) } diff --git a/internal/declarative/loader/loader.go b/internal/declarative/loader/loader.go index c1acdf24..2217a6cb 100644 --- a/internal/declarative/loader/loader.go +++ b/internal/declarative/loader/loader.go @@ -498,6 +498,15 @@ func (l *Loader) appendResourcesWithDuplicateCheck( accumulated.PortalTeamRoles = append(accumulated.PortalTeamRoles, role) } + for _, egwControlPlane := range source.EventGatewayControlPlanes { + if accumulated.HasRef(egwControlPlane.Ref) { + existing, _ := accumulated.GetResourceByRef(egwControlPlane.Ref) + return fmt.Errorf("duplicate ref '%s' found in %s (already defined as %s)", + egwControlPlane.Ref, sourcePath, existing.GetType()) + } + accumulated.EventGatewayControlPlanes = append(accumulated.EventGatewayControlPlanes, egwControlPlane) + } + // If this source defines a namespace default without parent resources, // propagate it so sync mode can inspect the correct namespace. if source.DefaultNamespace != "" { @@ -653,6 +662,22 @@ func (l *Loader) applyNamespaceDefaults(rs *resources.ResourceSet, fileDefaults } } + // Apply defaults to ControlPlanes (parent resources) + for i := range rs.EventGatewayControlPlanes { + if err := assignNamespace(&rs.EventGatewayControlPlanes[i].Kongctl, "control_plane", rs.EventGatewayControlPlanes[i].Ref); err != nil { + return err + } + // Apply protected default if not set + if rs.EventGatewayControlPlanes[i].Kongctl.Protected == nil && protectedDefault != nil { + rs.EventGatewayControlPlanes[i].Kongctl.Protected = protectedDefault + } + // Ensure protected has a value (false if still nil) + if rs.EventGatewayControlPlanes[i].Kongctl.Protected == nil { + falseVal := false + rs.EventGatewayControlPlanes[i].Kongctl.Protected = &falseVal + } + } + // Note: Child resources (API versions, publications, etc.) do not get kongctl metadata // as Konnect doesn't support labels on child resources return nil diff --git a/internal/declarative/planner/base_planner.go b/internal/declarative/planner/base_planner.go index b15d8690..dc896bf8 100644 --- a/internal/declarative/planner/base_planner.go +++ b/internal/declarative/planner/base_planner.go @@ -129,6 +129,11 @@ func (b *BasePlanner) GetDesiredPortalSnippets(namespace string) []resources.Por return b.planner.resources.GetPortalSnippetsByNamespace(namespace) } +// GetDesiredEventGatewayControlPlanes returns desired EGW CP resources from the specified namespace +func (b *BasePlanner) GetDesiredEventGatewayControlPlanes(namespace string) []resources.EventGatewayControlPlaneResource { + return b.planner.resources.GetEventGatewayControlPlanesByNamespace(namespace) +} + // GetGenericPlanner returns the generic planner instance func (b *BasePlanner) GetGenericPlanner() *GenericPlanner { if b == nil || b.planner == nil { diff --git a/internal/declarative/planner/egw_control_plane_planner.go b/internal/declarative/planner/egw_control_plane_planner.go index 1fe729bc..36d05d8d 100644 --- a/internal/declarative/planner/egw_control_plane_planner.go +++ b/internal/declarative/planner/egw_control_plane_planner.go @@ -2,6 +2,7 @@ package planner import ( "context" + "fmt" "github.com/kong/kongctl/internal/declarative/labels" "github.com/kong/kongctl/internal/declarative/resources" @@ -21,9 +22,7 @@ func NewEGWControlPlanePlanner(planner *BasePlanner, resources *resources.Resour } func (p *EGWControlPlanePlannerImpl) GetDesiredEGWControlPlanes(namespace string) []resources.EventGatewayControlPlaneResource { - var result []resources.EventGatewayControlPlaneResource - - return result + return p.BasePlanner.GetDesiredEventGatewayControlPlanes(namespace) } func (p *EGWControlPlanePlannerImpl) PlanChanges(ctx context.Context, plannerCtx *Config, plan *Plan) error { @@ -37,6 +36,85 @@ func (p *EGWControlPlanePlannerImpl) PlanChanges(ctx context.Context, plannerCtx } func (p *Planner) planEGWControlPlaneChanges(ctx context.Context, plannerCtx *Config, desired []resources.EventGatewayControlPlaneResource, plan *Plan) error { + // Skip if no API resources to plan and not in sync mode + if len(desired) == 0 && plan.Metadata.Mode != PlanModeSync { + p.logger.Debug("Skipping API planning - no desired APIs") + return nil + } + + // Get namespace from planner context + namespace := plannerCtx.Namespace + + // Fetch current managed APIs from the specific namespace + namespaceFilter := []string{namespace} + currentEGWControlPlanes, err := p.client.ListManagedEventGatewayControlPlanes(ctx, namespaceFilter) + if err != nil { + // If API client is not configured, skip API planning + if err.Error() == "API client not configured" { + return nil + } + return fmt.Errorf("failed to list current Event Gateway Control Planes: %w", err) + } + + // Index current APIs by name + currentByName := make(map[string]state.EventGatewayControlPlane) + for _, cp := range currentEGWControlPlanes { + currentByName[cp.Name] = cp + } + + // Collect protection validation errors + var protectionErrors []error + + // Compare each desired API + for _, desiredEGWCP := range desired { + current, exists := currentByName[desiredEGWCP.Name] + + if !exists { + // CREATE action + _ = p.planEGWControlPlaneCreate(desiredEGWCP, plan) + } else { + // Check if update needed + isProtected := labels.IsProtectedResource(current.NormalizedLabels) + + // Get protection status from desired configuration + shouldProtect := false + if desiredEGWCP.Kongctl != nil && desiredEGWCP.Kongctl.Protected != nil && *desiredEGWCP.Kongctl.Protected { + shouldProtect = true + } + + // Handle protection changes + if isProtected != shouldProtect { + // When changing protection status, include any other field updates too + needsUpdate, _ := p.shouldUpdateEGWControlPlaneResource(current, desiredEGWCP) + + // Create protection change object + protectionChange := &ProtectionChange{ + Old: isProtected, + New: shouldProtect, + } + + // Validate protection change + err := p.validateProtectionWithChange("api", desiredEGWCP.Name, isProtected, ActionUpdate, + protectionChange, needsUpdate) + if err != nil { + protectionErrors = append(protectionErrors, err) + } else { + //p.planEGWControlPlaneProtectionChangeWithFields(current, desiredEGWCP, isProtected, shouldProtect, updateFields, plan) + } + } else { + // Check if update needed based on configuration + needsUpdate, updateFields := p.shouldUpdateEGWControlPlaneResource(current, desiredEGWCP) + if needsUpdate { + // Regular update - check protection + if err := p.validateProtection("event-gateway-control-plane", desiredEGWCP.Name, isProtected, ActionUpdate); err != nil { + protectionErrors = append(protectionErrors, err) + } else { + p.planEGWControlPlaneUpdateWithFields(current, desiredEGWCP, updateFields, plan) + } + } + } + } + } return nil } diff --git a/internal/declarative/planner/planner.go b/internal/declarative/planner/planner.go index c541abc2..08f157b5 100644 --- a/internal/declarative/planner/planner.go +++ b/internal/declarative/planner/planner.go @@ -157,6 +157,7 @@ func (p *Planner) GeneratePlan(ctx context.Context, rs *resources.ResourceSet, o namespacePlanner.authStrategyPlanner = NewAuthStrategyPlanner(base) namespacePlanner.catalogServicePlanner = NewCatalogServicePlanner(base) namespacePlanner.apiPlanner = NewAPIPlanner(base) + namespacePlanner.eventGatewayControlPlanePlanner = NewEGWControlPlanePlanner(base, rs) // Store full ResourceSet for access by planners (enables both filtered views and global lookups) namespacePlanner.resources = rs @@ -1283,6 +1284,11 @@ func (p *Planner) getResourceNamespaces(rs *resources.ResourceSet) []string { namespaceSet[ns] = true } + for _, cp := range rs.EventGatewayControlPlanes { + ns := resources.GetNamespace(cp.Kongctl) + namespaceSet[ns] = true + } + // Convert set to sorted slice for consistent ordering namespaces := make([]string, 0, len(namespaceSet)) for ns := range namespaceSet { diff --git a/internal/declarative/resources/types.go b/internal/declarative/resources/types.go index a87ff58c..f5bf2a6a 100644 --- a/internal/declarative/resources/types.go +++ b/internal/declarative/resources/types.go @@ -602,6 +602,17 @@ func (rs *ResourceSet) GetPortalTeamRolesByNamespace(namespace string) []PortalT return filtered } +// GetEventGatewayControlPlanesByNamespace returns all EGW CP resources from the specified namespace +func (rs *ResourceSet) GetEventGatewayControlPlanesByNamespace(namespace string) []EventGatewayControlPlaneResource { + var filtered []EventGatewayControlPlaneResource + for _, cp := range rs.EventGatewayControlPlanes { + if GetNamespace(cp.Kongctl) == namespace { + filtered = append(filtered, cp) + } + } + return filtered +} + // GetNamespace safely extracts namespace from kongctl metadata func GetNamespace(kongctl *KongctlMeta) string { if kongctl == nil || kongctl.Namespace == nil { diff --git a/internal/konnect/helpers/sdk.go b/internal/konnect/helpers/sdk.go index 479d11fc..0e3dbad3 100644 --- a/internal/konnect/helpers/sdk.go +++ b/internal/konnect/helpers/sdk.go @@ -43,6 +43,7 @@ type SDKAPI interface { GetPortalTeamMembershipAPI() PortalTeamMembershipAPI GetAssetsAPI() AssetsAPI GetPortalEmailsAPI() PortalEmailsAPI + GetEventGatewayControlPlaneAPI() EGWControlPlaneAPI } // This is the real implementation of the SDKAPI @@ -275,6 +276,15 @@ func (k *KonnectSDK) GetPortalEmailsAPI() PortalEmailsAPI { return &PortalEmailsAPIImpl{SDK: k.SDK} } +// Returns the implementation of the APIAPI interface +func (k *KonnectSDK) GetEventGatewayControlPlaneAPI() EGWControlPlaneAPI { + if k.SDK == nil { + return nil + } + + return &EGWControlPlaneAPIImpl{SDK: k.SDK} +} + // A function that can build an SDKAPI with a given configuration type SDKAPIFactory func(cfg config.Hook, logger *slog.Logger) (SDKAPI, error) diff --git a/internal/konnect/helpers/sdk_mock.go b/internal/konnect/helpers/sdk_mock.go index b62d3911..1287ced1 100644 --- a/internal/konnect/helpers/sdk_mock.go +++ b/internal/konnect/helpers/sdk_mock.go @@ -32,6 +32,9 @@ type MockKonnectSDK struct { PortalTeamMembershipFactory func() PortalTeamMembershipAPI AssetsFactory func() AssetsAPI PortalEmailsFactory func() PortalEmailsAPI + + // Event Gateway Control Plane factory + EventGatewayControlPlaneFactory func() EGWControlPlaneAPI } // Returns a mock instance of the ControlPlaneAPI @@ -231,6 +234,14 @@ func (m *MockKonnectSDK) GetPortalEmailsAPI() PortalEmailsAPI { return nil } +// Returns the implementation of the APIAPI interface +func (m *MockKonnectSDK) GetEventGatewayControlPlaneAPI() EGWControlPlaneAPI { + if m.EventGatewayControlPlaneFactory != nil { + return m.EventGatewayControlPlaneFactory() + } + return nil +} + // This is a mock implementation of the SDKFactory interface // which can associate a Testing.T instance with any MockKonnectSDK // instances Built by it From 9c186226dc4b231ab2370bd34218f35c89ebad32 Mon Sep 17 00:00:00 2001 From: Harsha Dixit Date: Wed, 31 Dec 2025 18:44:47 +0530 Subject: [PATCH 04/24] checkpoint: declarative CRUD works for event gateway control plane --- .../event_gateway_control_plane_adapter.go | 34 +++++++++++-- internal/declarative/labels/labels.go | 5 +- .../planner/egw_control_plane_planner.go | 50 ++++++++++++++++--- internal/declarative/state/client.go | 28 +++++++++++ 4 files changed, 105 insertions(+), 12 deletions(-) diff --git a/internal/declarative/executor/event_gateway_control_plane_adapter.go b/internal/declarative/executor/event_gateway_control_plane_adapter.go index 190e0a93..adb9c184 100644 --- a/internal/declarative/executor/event_gateway_control_plane_adapter.go +++ b/internal/declarative/executor/event_gateway_control_plane_adapter.go @@ -112,9 +112,15 @@ func (a *EventGatewayControlPlaneControlPlaneAdapter) Update( } // GetByID gets a event_gateway_control_plane by ID -func (a *EventGatewayControlPlaneControlPlaneAdapter) GetByID(_ context.Context, _ string, _ *ExecutionContext) (ResourceInfo, error) { - // todo - why and fix - return nil, nil +func (a *EventGatewayControlPlaneControlPlaneAdapter) GetByID(ctx context.Context, id string, _ *ExecutionContext) (ResourceInfo, error) { + eventGateway, err := a.client.GetEventGatewayControlPlaneByID(ctx, id) + if err != nil { + return nil, err + } + if eventGateway == nil { + return nil, nil + } + return &EventGatewayControlPlaneResourceInfo{eventGatewayControlPlane: eventGateway}, nil } // GetByName gets a event_gateway_control_plane by name @@ -138,3 +144,25 @@ func (a *EventGatewayControlPlaneControlPlaneAdapter) RequiredFields() []string func (a *EventGatewayControlPlaneControlPlaneAdapter) SupportsUpdate() bool { return true } + +// APIResourceInfo wraps an API to implement ResourceInfo +type EventGatewayControlPlaneResourceInfo struct { + eventGatewayControlPlane *state.EventGatewayControlPlane +} + +func (e *EventGatewayControlPlaneResourceInfo) GetID() string { + return e.eventGatewayControlPlane.ID +} + +func (e *EventGatewayControlPlaneResourceInfo) GetName() string { + return e.eventGatewayControlPlane.Name +} + +func (e *EventGatewayControlPlaneResourceInfo) GetLabels() map[string]string { + // API.Labels is already map[string]string + return e.eventGatewayControlPlane.Labels +} + +func (e *EventGatewayControlPlaneResourceInfo) GetNormalizedLabels() map[string]string { + return e.eventGatewayControlPlane.NormalizedLabels +} diff --git a/internal/declarative/labels/labels.go b/internal/declarative/labels/labels.go index d7a82222..e6954cb5 100644 --- a/internal/declarative/labels/labels.go +++ b/internal/declarative/labels/labels.go @@ -391,7 +391,10 @@ func ConvertPointerMapsToStringMap(labels map[string]*string) map[string]string result := make(map[string]string) for k, v := range labels { val := v - result[k] = *val + + if val != nil { + result[k] = *val + } } return result } diff --git a/internal/declarative/planner/egw_control_plane_planner.go b/internal/declarative/planner/egw_control_plane_planner.go index 36d05d8d..44eefdb5 100644 --- a/internal/declarative/planner/egw_control_plane_planner.go +++ b/internal/declarative/planner/egw_control_plane_planner.go @@ -115,6 +115,39 @@ func (p *Planner) planEGWControlPlaneChanges(ctx context.Context, plannerCtx *Co } } } + + // Check for managed resources to delete (sync mode only) + if plan.Metadata.Mode == PlanModeSync { + // Build set of desired Event gateway names + desiredNames := make(map[string]bool) + for _, eventGateway := range desired { + desiredNames[eventGateway.Name] = true + } + + // Find managed Event Gateway Control Planes not in desired state + for name, current := range currentByName { + if !desiredNames[name] { + // Validate protection before adding DELETE + isProtected := labels.IsProtectedResource(current.NormalizedLabels) + if err := p.validateProtection("event-gateway-control-plane", name, isProtected, ActionDelete); err != nil { + protectionErrors = append(protectionErrors, err) + } else { + p.planEGWControlPlaneDelete(current, plan) + } + } + } + } + + // Fail fast if any protected resources would be modified + if len(protectionErrors) > 0 { + errMsg := "Cannot generate plan due to protected resources:\n" + for _, err := range protectionErrors { + errMsg += fmt.Sprintf("- %s\n", err.Error()) + } + errMsg += "\nTo proceed, first update these resources to set protected: false" + return fmt.Errorf("%s", errMsg) + } + return nil } @@ -129,9 +162,8 @@ func (p *Planner) shouldUpdateEGWControlPlaneResource(current state.EventGateway } if desired.Description != current.Description { - currentDesc := getString(current.Description) - if currentDesc != *desired.Description { - updates["description"] = *desired.Description + if getString(current.Description) != getString(desired.Description) { + updates["description"] = getString(desired.Description) } } @@ -214,17 +246,19 @@ func (p *Planner) planEGWControlPlaneUpdateWithFields( namespace = *desired.Kongctl.Namespace } + // Always include name for identification + updateFields["name"] = current.Name + updateFields[FieldCurrentLabels] = current.NormalizedLabels config := UpdateConfig{ ResourceType: string(desired.GetType()), ResourceName: desired.Name, ResourceRef: desired.Ref, + ResourceID: current.ID, + CurrentFields: nil, // Not needed for direct update + DesiredFields: updateFields, RequiredFields: []string{"name"}, - FieldComparator: func(current, desired map[string]any) bool { - // todo - return false - }, - Namespace: namespace, + Namespace: namespace, } change, err := p.genericPlanner.PlanUpdate(context.Background(), config) diff --git a/internal/declarative/state/client.go b/internal/declarative/state/client.go index 44e3be2f..0a1fd394 100644 --- a/internal/declarative/state/client.go +++ b/internal/declarative/state/client.go @@ -3255,6 +3255,34 @@ func (c *Client) UpdateEventGatewayControlPlane(ctx context.Context, id string, return resp.EventGatewayInfo.ID, nil } +func (c *Client) GetEventGatewayControlPlaneByID(ctx context.Context, id string) (*EventGatewayControlPlane, error) { + resp, err := c.egwControlPlaneAPI.FetchEGWControlPlane(ctx, id) + if err != nil { + return nil, WrapAPIError(err, "get event gateway control plane by ID", &ErrorWrapperOptions{ + ResourceType: "event_gateway_control_plane", + ResourceName: "", // Adjust based on SDK + UseEnhanced: true, + }) + } + + if resp.EventGatewayInfo == nil { + return nil, nil + } + + // Labels are already map[string]string in the SDK + normalized := resp.EventGatewayInfo.Labels + if normalized == nil { + normalized = make(map[string]string) + } + + eventGateway := &EventGatewayControlPlane{ + EventGatewayInfo: *resp.EventGatewayInfo, + NormalizedLabels: normalized, + } + + return eventGateway, nil +} + func (c *Client) DeleteEventGatewayControlPlane(ctx context.Context, id string) error { // Placeholder for future implementation _, err := c.egwControlPlaneAPI.DeleteEGWControlPlane(ctx, id) From 0879bafe9135b8ca14fe1dbf578ce88c0b872add Mon Sep 17 00:00:00 2001 From: Harsha Dixit Date: Mon, 5 Jan 2026 16:37:56 +0530 Subject: [PATCH 05/24] feat: add imperative get command to support listing, get by ID and Name for Event Gateway control planes --- .../eventgateway/eventgatewaycontrolplane.go | 60 +++ .../getEventGatewayControlPlane.go | 445 ++++++++++++++++++ internal/cmd/root/products/konnect/konnect.go | 8 + .../verbs/get/eventgatewaycontrolplane.go | 126 +++++ internal/cmd/root/verbs/get/get.go | 8 + internal/declarative/state/client.go | 35 +- 6 files changed, 677 insertions(+), 5 deletions(-) create mode 100644 internal/cmd/root/products/konnect/eventgateway/eventgatewaycontrolplane.go create mode 100644 internal/cmd/root/products/konnect/eventgateway/getEventGatewayControlPlane.go create mode 100644 internal/cmd/root/verbs/get/eventgatewaycontrolplane.go diff --git a/internal/cmd/root/products/konnect/eventgateway/eventgatewaycontrolplane.go b/internal/cmd/root/products/konnect/eventgateway/eventgatewaycontrolplane.go new file mode 100644 index 00000000..7a5fe6e0 --- /dev/null +++ b/internal/cmd/root/products/konnect/eventgateway/eventgatewaycontrolplane.go @@ -0,0 +1,60 @@ +package eventgateway + +import ( + "fmt" + + "github.com/kong/kongctl/internal/cmd/root/verbs" + "github.com/kong/kongctl/internal/meta" + "github.com/kong/kongctl/internal/util/i18n" + "github.com/kong/kongctl/internal/util/normalizers" + "github.com/spf13/cobra" +) + +const ( + CommandName = "eventgatewaycontrolplane" +) + +var ( + eventGatewayControlPlaneUse = CommandName + eventGatewayControlPlaneShort = i18n.T("root.products.konnect.eventgateway.eventGatewayControlPlaneShort", + "Manage Konnect event gateway control plane resources") + eventGatewayControlPlaneLong = normalizers.LongDesc(i18n.T("root.products.konnect.eventgateway.eventGatewayControlPlaneLong", + `The event gateway control plane command allows you to work with Konnect event gateway control plane resources.`)) + eventGatewayControlPlaneExample = normalizers.Examples( + i18n.T("root.products.konnect.eventgateway.eventGatewayControlPlaneExamples", + fmt.Sprintf(` +# List all the Konnect event gateway control planes for the organization +%[1]s get eventgatewaycontrolplanes +# Get a specific Konnect event gateway control plane +%[1]s get eventgatewaycontrolplane +# List portal pages +%[1]s get portal pages --portal-id +# List portal applications +%[1]s get portal applications --portal-id +# List portals using explicit konnect product +%[1]s get konnect portals +`, meta.CLIName))) +) + +func NewEventGatewayControlPlaneCmd(verb verbs.VerbValue, + addParentFlags func(verbs.VerbValue, *cobra.Command), + parentPreRun func(*cobra.Command, []string) error, +) (*cobra.Command, error) { + baseCmd := cobra.Command{ + Use: eventGatewayControlPlaneUse, + Short: eventGatewayControlPlaneShort, + Long: eventGatewayControlPlaneLong, + Example: eventGatewayControlPlaneExample, + Aliases: []string{"eventgatewaycontrolplanes", "p", "ps", "P", "PS"}, + } + + switch verb { + case verbs.Get: + return newGetEventGatewayControlPlaneCmd(verb, &baseCmd, addParentFlags, parentPreRun).Command, nil + case verbs.Create, verbs.Add, verbs.Apply, verbs.Dump, verbs.Update, verbs.Help, verbs.Login, + verbs.Plan, verbs.Sync, verbs.Diff, verbs.Export, verbs.Adopt, verbs.API, verbs.Kai, verbs.View, verbs.Logout: + return &baseCmd, nil + } + + return &baseCmd, nil +} diff --git a/internal/cmd/root/products/konnect/eventgateway/getEventGatewayControlPlane.go b/internal/cmd/root/products/konnect/eventgateway/getEventGatewayControlPlane.go new file mode 100644 index 00000000..d4ec655f --- /dev/null +++ b/internal/cmd/root/products/konnect/eventgateway/getEventGatewayControlPlane.go @@ -0,0 +1,445 @@ +package eventgateway + +import ( + "fmt" + "net/url" + "strings" + "time" + + kk "github.com/Kong/sdk-konnect-go" // kk = Kong Konnect + kkComps "github.com/Kong/sdk-konnect-go/models/components" + kkOps "github.com/Kong/sdk-konnect-go/models/operations" + "github.com/kong/kongctl/internal/cmd" + cmdCommon "github.com/kong/kongctl/internal/cmd/common" + "github.com/kong/kongctl/internal/cmd/output/tableview" + "github.com/kong/kongctl/internal/cmd/root/products/konnect/common" + "github.com/kong/kongctl/internal/cmd/root/products/konnect/navigator" + "github.com/kong/kongctl/internal/cmd/root/verbs" + "github.com/kong/kongctl/internal/config" + "github.com/kong/kongctl/internal/konnect/helpers" + "github.com/kong/kongctl/internal/meta" + "github.com/kong/kongctl/internal/util" + "github.com/kong/kongctl/internal/util/i18n" + "github.com/kong/kongctl/internal/util/normalizers" + "github.com/segmentio/cli" + "github.com/spf13/cobra" +) + +var ( + getEventGatewayControlPlanesShort = i18n.T("root.products.konnect.api.getEventGatewayControlPlanesShort", + "List or get Konnect APIs") + getEventGatewayControlPlanesLong = i18n.T("root.products.konnect.api.getEventGatewayControlPlanesLong", + `Use the get verb with the api command to query Konnect APIs.`) + getEventGatewayControlPlanesExample = normalizers.Examples( + i18n.T("root.products.konnect.api.getEventGatewayControlPlanesExamples", + fmt.Sprintf(` + # List all the Event gateway control planes for the organization + %[1]s get eventgatewaycontrolplanes + # Get details for an Event gateway control plane with a specific ID + %[1]s get eventgatewaycontrolplane 22cd8a0b-72e7-4212-9099-0764f8e9c5ac + # Get details for an Event gateway control plane with a specific name + %[1]s get eventgatewaycontrolplane my-eventgatewaycontrolplane + # Get all the Event gateway control planes using command aliases + %[1]s get eventgatewaycontrolplanes + `, meta.CLIName))) +) + +// Represents a text display record for an Event gateway control plane +type textDisplayRecord struct { + ID string + Name string + Description string + LocalCreatedTime string + LocalUpdatedTime string +} + +func eventGatewayControlPlaneToDisplayRecord(e *kkComps.EventGatewayInfo) textDisplayRecord { + missing := "n/a" + + var id, name string + if e.ID != "" { + id = util.AbbreviateUUID(e.ID) + } else { + id = missing + } + + if e.Name != "" { + name = e.Name + } else { + name = missing + } + + description := missing + if e.Description != nil && *e.Description != "" { + description = *e.Description + } + + createdAt := e.CreatedAt.In(time.Local).Format("2006-01-02 15:04:05") + updatedAt := e.UpdatedAt.In(time.Local).Format("2006-01-02 15:04:05") + return textDisplayRecord{ + ID: id, + Name: name, + Description: description, + LocalCreatedTime: createdAt, + LocalUpdatedTime: updatedAt, + } +} + +/* +func eventGatewayControlPlaneDetailView(eventGateway *kkComps.EventGatewayInfo) string { + if eventGateway == nil { + return "" + } + + const missing = "n/a" + id := strings.TrimSpace(eventGateway.ID) + if id == "" { + id = missing + } + name := strings.TrimSpace(eventGateway.Name) + if name == "" { + name = missing + } + + type detailField struct { + label string + value string + multiline bool + } + + var fields []detailField + + addField := func(label, value string) { + value = strings.TrimSpace(value) + if value == "" { + return + } + fields = append(fields, detailField{ + label: label, + value: value, + }) + } + + addMultiline := func(label, value string) { + value = strings.TrimRight(value, "\n") + if strings.TrimSpace(value) == "" { + return + } + fields = append(fields, detailField{ + label: label, + value: value, + multiline: true, + }) + } + + if eventGateway.Description != nil && *eventGateway.Description != "" { + description := strings.TrimSpace(*eventGateway.Description) + if description != "" { + const wrapWidth = 80 + addMultiline("description", wordwrap.String(description, wrapWidth)) + } + } + + if labels := eventGateway.GetLabels(); len(labels) > 0 { + keys := make([]string, 0, len(labels)) + for k := range labels { + keys = append(keys, k) + } + sort.Strings(keys) + var sb strings.Builder + for _, k := range keys { + fmt.Fprintf(&sb, " %s: %s\n", k, labels[k]) + } + addMultiline("labels", sb.String()) + } + + addField("created_at", eventGateway.CreatedAt.In(time.Local).Format("2006-01-02 15:04:05")) + addField("updated_at", eventGateway.UpdatedAt.In(time.Local).Format("2006-01-02 15:04:05")) + + sort.Slice(fields, func(i, j int) bool { + li := strings.ToLower(fields[i].label) + lj := strings.ToLower(fields[j].label) + if li == lj { + return fields[i].label < fields[j].label + } + return li < lj + }) + + var b strings.Builder + fmt.Fprintf(&b, "id: %s\n", id) + fmt.Fprintf(&b, "name: %s\n", name) + for _, field := range fields { + if field.multiline { + value := strings.TrimRight(field.value, "\n") + fmt.Fprintf(&b, "%s:\n%s\n", field.label, value) + continue + } + fmt.Fprintf(&b, "%s: %s\n", field.label, field.value) + } + + return b.String() +} +*/ + +type getEventGatewayControlPlaneCmd struct { + *cobra.Command +} + +func runListByName(name string, kkClient helpers.EGWControlPlaneAPI, helper cmd.Helper, + cfg config.Hook, +) (*kkComps.EventGatewayInfo, error) { + allEventGateways, err := runList(kkClient, helper, cfg) + if err != nil { + return nil, err + } + + for _, eventGateway := range allEventGateways { + if eventGateway.Name == name { + return &eventGateway, nil + } + } + + return nil, cmd.PrepareExecutionErrorMsg(helper, + fmt.Sprintf("Event Gateway Control Plane with name %s not found", name)) +} + +func runList(kkClient helpers.EGWControlPlaneAPI, helper cmd.Helper, + cfg config.Hook, +) ([]kkComps.EventGatewayInfo, error) { + requestPageSize := int64(cfg.GetInt(common.RequestPageSizeConfigPath)) + + var allData []kkComps.EventGatewayInfo + var pageAfter *string + + for { + req := kkOps.ListEventGatewaysRequest{ + PageSize: kk.Int64(requestPageSize), + } + + if pageAfter != nil { + req.PageAfter = pageAfter + } + + res, err := kkClient.ListEGWControlPlanes(helper.GetContext(), req) + if err != nil { + attrs := cmd.TryConvertErrorToAttrs(err) + return nil, cmd.PrepareExecutionError("Failed to list Event Gateways", err, helper.GetCmd(), attrs...) + } + + allData = append(allData, res.ListEventGatewaysResponse.Data...) + + if res.ListEventGatewaysResponse.Meta.Page.Next == nil { + break + } else { + u, err := url.Parse(*res.ListEventGatewaysResponse.Meta.Page.Next) + if err != nil { + return nil, cmd.PrepareExecutionError("Failed to list Event Gateways: invalid cursor", err, helper.GetCmd()) + } + + values := u.Query() + pageAfter = kk.String(values.Get("page[after]")) + } + } + + return allData, nil +} + +func runGet(id string, kkClient helpers.EGWControlPlaneAPI, helper cmd.Helper, +) (*kkComps.EventGatewayInfo, error) { + // Note: FetchAPI doesn't support include parameters + // Version and publication information would require separate API calls + res, err := kkClient.FetchEGWControlPlane(helper.GetContext(), id) + if err != nil { + attrs := cmd.TryConvertErrorToAttrs(err) + return nil, cmd.PrepareExecutionError("Failed to get API", err, helper.GetCmd(), attrs...) + } + + return res.GetEventGatewayInfo(), nil +} + +func (c *getEventGatewayControlPlaneCmd) validate(helper cmd.Helper) error { + if len(helper.GetArgs()) > 1 { + return &cmd.ConfigurationError{ + Err: fmt.Errorf("too many arguments. Listing Event Gateways requires 0 or 1 arguments (name or ID)"), + } + } + + config, err := helper.GetConfig() + if err != nil { + return err + } + + pageSize := config.GetInt(common.RequestPageSizeConfigPath) + if pageSize < 1 { + return &cmd.ConfigurationError{ + Err: fmt.Errorf("%s must be greater than 0", common.RequestPageSizeFlagName), + } + } + return nil +} + +func (c *getEventGatewayControlPlaneCmd) runE(cobraCmd *cobra.Command, args []string) error { + var e error + helper := cmd.BuildHelper(cobraCmd, args) + if e = c.validate(helper); e != nil { + return e + } + + logger, e := helper.GetLogger() + if e != nil { + return e + } + + outType, e := helper.GetOutputFormat() + if e != nil { + return e + } + + interactive, e := helper.IsInteractive() + if e != nil { + return e + } + + var printer cli.PrintFlusher + if !interactive { + printer, e = cli.Format(outType.String(), helper.GetStreams().Out) + if e != nil { + return e + } + defer printer.Flush() + } + + cfg, e := helper.GetConfig() + if e != nil { + return e + } + + sdk, e := helper.GetKonnectSDK(cfg, logger) + if e != nil { + return e + } + + // 'get eventgateways' can be run in various ways: + // > get eventgateways # Get by UUID + // > get eventgateways # Get by name + // > get eventgateways # List all + if len(helper.GetArgs()) == 1 { // validate above checks that args is 0 or 1 + id := strings.TrimSpace(helper.GetArgs()[0]) + + isUUID := util.IsValidUUID(id) + + if !isUUID { + // If the ID is not a UUID, then it is a name + eventGatewayControlPlane, e := runListByName(id, sdk.GetEventGatewayControlPlaneAPI(), helper, cfg) + if e != nil { + return e + } + return tableview.RenderForFormat( + interactive, + outType, + printer, + helper.GetStreams(), + eventGatewayControlPlaneToDisplayRecord(eventGatewayControlPlane), + eventGatewayControlPlane, + "", + tableview.WithRootLabel(helper.GetCmd().Name()), + tableview.WithDetailHelper(helper), + tableview.WithDetailContext("eventGatewayControlPlane", func(index int) any { + if index != 0 { + return nil + } + return eventGatewayControlPlane + }), + ) + } + + eventGatewayControlPlane, e := runGet(id, sdk.GetEventGatewayControlPlaneAPI(), helper) + if e != nil { + return e + } + + return tableview.RenderForFormat( + interactive, + outType, + printer, + helper.GetStreams(), + eventGatewayControlPlaneToDisplayRecord(eventGatewayControlPlane), + eventGatewayControlPlane, + "", + tableview.WithRootLabel(helper.GetCmd().Name()), + tableview.WithDetailHelper(helper), + tableview.WithDetailContext("eventGatewayControlPlane", func(index int) any { + if index != 0 { + return nil + } + return eventGatewayControlPlane + }), + ) + } + + if interactive { + return navigator.Run(helper, navigator.Options{InitialResource: "apis"}) + } + + eventGatewayControlPlanes, e := runList(sdk.GetEventGatewayControlPlaneAPI(), helper, cfg) + if e != nil { + return e + } + + return renderEventGatewayControlPlaneList(helper, helper.GetCmd().Name(), interactive, outType, printer, eventGatewayControlPlanes) +} + +func renderEventGatewayControlPlaneList( + helper cmd.Helper, + rootLabel string, + interactive bool, + outType cmdCommon.OutputFormat, + printer cli.PrintFlusher, + eventGatewayControlPlanes []kkComps.EventGatewayInfo, +) error { + displayRecords := make([]textDisplayRecord, 0, len(eventGatewayControlPlanes)) + for i := range eventGatewayControlPlanes { + displayRecords = append(displayRecords, eventGatewayControlPlaneToDisplayRecord(&eventGatewayControlPlanes[i])) + } + + options := []tableview.Option{ + tableview.WithRootLabel(rootLabel), + tableview.WithDetailHelper(helper), + } + + return tableview.RenderForFormat( + interactive, + outType, + printer, + helper.GetStreams(), + displayRecords, + eventGatewayControlPlanes, + "", + options..., + ) +} + +func newGetEventGatewayControlPlaneCmd(verb verbs.VerbValue, + baseCmd *cobra.Command, + addParentFlags func(verbs.VerbValue, *cobra.Command), + parentPreRun func(*cobra.Command, []string) error, +) *getEventGatewayControlPlaneCmd { + rv := getEventGatewayControlPlaneCmd{ + Command: baseCmd, + } + + rv.Short = getEventGatewayControlPlanesShort + rv.Long = getEventGatewayControlPlanesLong + rv.Example = getEventGatewayControlPlanesExample + if parentPreRun != nil { + rv.PreRunE = parentPreRun + } + rv.RunE = rv.runE + + // Ensure parent-level flags are available on this command + if addParentFlags != nil { + addParentFlags(verb, rv.Command) + } + + return &rv +} diff --git a/internal/cmd/root/products/konnect/konnect.go b/internal/cmd/root/products/konnect/konnect.go index ce23fc1e..df861f5e 100644 --- a/internal/cmd/root/products/konnect/konnect.go +++ b/internal/cmd/root/products/konnect/konnect.go @@ -11,6 +11,7 @@ import ( "github.com/kong/kongctl/internal/cmd/root/products/konnect/authstrategy" "github.com/kong/kongctl/internal/cmd/root/products/konnect/common" "github.com/kong/kongctl/internal/cmd/root/products/konnect/declarative" + "github.com/kong/kongctl/internal/cmd/root/products/konnect/eventgateway" "github.com/kong/kongctl/internal/cmd/root/products/konnect/gateway" "github.com/kong/kongctl/internal/cmd/root/products/konnect/me" "github.com/kong/kongctl/internal/cmd/root/products/konnect/organization" @@ -242,6 +243,13 @@ func NewKonnectCmd(verb verbs.VerbValue) (*cobra.Command, error) { } cmd.AddCommand(rc) + // Add eventGatewayControlPlane command + egcpc, e := eventgateway.NewEventGatewayControlPlaneCmd(verb, addFlags, preRunE) + if e != nil { + return nil, e + } + cmd.AddCommand(egcpc) + if verb == verbs.Get { cmd.RunE = func(c *cobra.Command, args []string) error { helper := cmdpkg.BuildHelper(c, args) diff --git a/internal/cmd/root/verbs/get/eventgatewaycontrolplane.go b/internal/cmd/root/verbs/get/eventgatewaycontrolplane.go new file mode 100644 index 00000000..c6b7a4be --- /dev/null +++ b/internal/cmd/root/verbs/get/eventgatewaycontrolplane.go @@ -0,0 +1,126 @@ +package get + +import ( + "context" + "fmt" + + "github.com/kong/kongctl/internal/cmd" + "github.com/kong/kongctl/internal/cmd/root/products" + "github.com/kong/kongctl/internal/cmd/root/products/konnect" + "github.com/kong/kongctl/internal/cmd/root/products/konnect/common" + "github.com/kong/kongctl/internal/cmd/root/products/konnect/eventgateway" + "github.com/kong/kongctl/internal/cmd/root/verbs" + "github.com/kong/kongctl/internal/konnect/helpers" + "github.com/spf13/cobra" +) + +// NewDirectEventGatewayControlPlaneCmd creates an Event Gateway Control Plane command that works at the root level (Konnect-first) +func NewDirectEventGatewayControlPlaneCmd() (*cobra.Command, error) { + // Define the addFlags function to add Konnect-specific flags + addFlags := func(verb verbs.VerbValue, cmd *cobra.Command) { + cmd.Flags().String(common.BaseURLFlagName, "", + fmt.Sprintf(`Base URL for Konnect API requests. +- Config path: [ %s ] +- Default : [ %s ]`, + common.BaseURLConfigPath, common.BaseURLDefault)) + + cmd.Flags().String(common.RegionFlagName, "", + fmt.Sprintf(`Konnect region identifier (for example "eu"). Used to construct the base URL when --%s is not provided. +- Config path: [ %s ]`, + common.BaseURLFlagName, common.RegionConfigPath)) + + cmd.Flags().String(common.PATFlagName, "", + fmt.Sprintf(`Konnect Personal Access Token (PAT) used to authenticate the CLI. +Setting this value overrides tokens obtained from the login command. +- Config path: [ %s ]`, + common.PATConfigPath)) + + if verb == verbs.Get || verb == verbs.List { + cmd.Flags().Int( + common.RequestPageSizeFlagName, + common.DefaultRequestPageSize, + fmt.Sprintf(`Max number of results to include per response page for get and list operations. +- Config path: [ %s ]`, + common.RequestPageSizeConfigPath)) + } + } + + // Define the preRunE function to set up Konnect context + preRunE := func(c *cobra.Command, args []string) error { + ctx := c.Context() + if ctx == nil { + ctx = context.Background() + } + ctx = context.WithValue(ctx, products.Product, konnect.Product) + ctx = context.WithValue(ctx, helpers.SDKAPIFactoryKey, helpers.SDKAPIFactory(common.KonnectSDKFactory)) + c.SetContext(ctx) + + // Bind flags + return bindEventGatewayControlPlaneFlags(c, args) + } + + // Create the Event Gateway Control Plane command using the existing api package + eventGatewayCmd, err := eventgateway.NewEventGatewayControlPlaneCmd(verbs.Get, addFlags, preRunE) + if err != nil { + return nil, err + } + + // Override the example to show direct usage without "konnect" + eventGatewayCmd.Example = ` # List all the Event Gateways for the organization + kongctl get eventgatewaycontrolplanes + # Get a specific Event Gateway + kongctl get eventgatewaycontrolplane + # List documents for a specific Event Gateway + kongctl get eventgatewaycontrolplane documents --eventgatewaycontrolplane-id + # List versions for a specific API + kongctl get api versions --api-id + # List publications for a specific API + kongctl get api publications --api-id + # List APIs using aliases + kongctl get apis` + + return eventGatewayCmd, nil +} + +// bindEventGatewayControlPlaneFlags binds Konnect-specific flags to configuration +func bindEventGatewayControlPlaneFlags(c *cobra.Command, args []string) error { + helper := cmd.BuildHelper(c, args) + cfg, err := helper.GetConfig() + if err != nil { + return err + } + + f := c.Flags().Lookup(common.BaseURLFlagName) + if f != nil { + err = cfg.BindFlag(common.BaseURLConfigPath, f) + if err != nil { + return err + } + } + + f = c.Flags().Lookup(common.RegionFlagName) + if f != nil { + err = cfg.BindFlag(common.RegionConfigPath, f) + if err != nil { + return err + } + } + + f = c.Flags().Lookup(common.PATFlagName) + if f != nil { + err = cfg.BindFlag(common.PATConfigPath, f) + if err != nil { + return err + } + } + + f = c.Flags().Lookup(common.RequestPageSizeFlagName) + if f != nil { + err = cfg.BindFlag(common.RequestPageSizeConfigPath, f) + if err != nil { + return err + } + } + + return nil +} diff --git a/internal/cmd/root/verbs/get/get.go b/internal/cmd/root/verbs/get/get.go index 8dc63aef..2393c30e 100644 --- a/internal/cmd/root/verbs/get/get.go +++ b/internal/cmd/root/verbs/get/get.go @@ -43,6 +43,8 @@ Output can be formatted in multiple ways to aid in further processing.`)) %[1]s get gateway control-planes # Retrieve Konnect control planes (explicit) %[1]s get konnect gateway control-planes + # Retrieve Konnect Event Gateway control planes + %[1]s get eventgatewaycontrolplanes `, meta.CLIName))) ) @@ -154,6 +156,12 @@ Setting this value overrides tokens obtained from the login command. } cmd.AddCommand(regionsCmd) + eventGatewayControlPlaneCmd, err := NewDirectEventGatewayControlPlaneCmd() + if err != nil { + return nil, err + } + cmd.AddCommand(eventGatewayControlPlaneCmd) + return cmd, nil } diff --git a/internal/declarative/state/client.go b/internal/declarative/state/client.go index 0a1fd394..5e37144e 100644 --- a/internal/declarative/state/client.go +++ b/internal/declarative/state/client.go @@ -5,8 +5,10 @@ import ( "errors" "fmt" "log/slog" + "net/url" "strings" + kk "github.com/Kong/sdk-konnect-go" // kk = Kong Konnect kkComps "github.com/Kong/sdk-konnect-go/models/components" kkOps "github.com/Kong/sdk-konnect-go/models/operations" kkErrors "github.com/Kong/sdk-konnect-go/models/sdkerrors" @@ -3199,15 +3201,38 @@ func (c *Client) RemovePortalTeamRole(ctx context.Context, portalID string, team } func (c *Client) ListManagedEventGatewayControlPlanes(ctx context.Context, namespaces []string) ([]EventGatewayControlPlane, error) { - req := kkOps.ListEventGatewaysRequest{} + var allData []kkComps.EventGatewayInfo + var pageAfter *string - resp, err := c.egwControlPlaneAPI.ListEGWControlPlanes(ctx, req) - if err != nil { - return nil, WrapAPIError(err, "list event gateway control planes", nil) + for { + req := kkOps.ListEventGatewaysRequest{} + + if pageAfter != nil { + req.PageAfter = pageAfter + } + + res, err := c.egwControlPlaneAPI.ListEGWControlPlanes(ctx, req) + if err != nil { + return nil, WrapAPIError(err, "list event gateway control planes", nil) + } + + allData = append(allData, res.ListEventGatewaysResponse.Data...) + + if res.ListEventGatewaysResponse.Meta.Page.Next == nil { + break + } else { + u, err := url.Parse(*res.ListEventGatewaysResponse.Meta.Page.Next) + if err != nil { + return nil, WrapAPIError(err, "list event gateway control planes: invalid cursor", nil) + } + + values := u.Query() + pageAfter = kk.String(values.Get("page[after]")) + } } var filteredEGWControlPlanes []EventGatewayControlPlane - for _, f := range resp.ListEventGatewaysResponse.Data { + for _, f := range allData { // Filter by managed status and namespace if labels.IsManagedResource(f.Labels) { From 46a5d1f6ed6b092e38773e269c7ffa57543dfdad Mon Sep 17 00:00:00 2001 From: Harsha Dixit Date: Tue, 6 Jan 2026 18:21:01 +0530 Subject: [PATCH 06/24] fix: protection updates --- internal/declarative/loader/loader.go | 4 ++ .../planner/egw_control_plane_planner.go | 72 ++++++++++++++++++- 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/internal/declarative/loader/loader.go b/internal/declarative/loader/loader.go index 2217a6cb..ccc32fbc 100644 --- a/internal/declarative/loader/loader.go +++ b/internal/declarative/loader/loader.go @@ -761,6 +761,10 @@ func (l *Loader) applyDefaults(rs *resources.ResourceSet) { for i := range rs.PortalEmailTemplates { rs.PortalEmailTemplates[i].SetDefaults() } + + for i := range rs.EventGatewayControlPlanes { + rs.EventGatewayControlPlanes[i].SetDefaults() + } } // extractPortalPages recursively extracts and flattens nested portal pages diff --git a/internal/declarative/planner/egw_control_plane_planner.go b/internal/declarative/planner/egw_control_plane_planner.go index 44eefdb5..a7dca799 100644 --- a/internal/declarative/planner/egw_control_plane_planner.go +++ b/internal/declarative/planner/egw_control_plane_planner.go @@ -3,6 +3,7 @@ package planner import ( "context" "fmt" + "strings" "github.com/kong/kongctl/internal/declarative/labels" "github.com/kong/kongctl/internal/declarative/resources" @@ -85,7 +86,7 @@ func (p *Planner) planEGWControlPlaneChanges(ctx context.Context, plannerCtx *Co // Handle protection changes if isProtected != shouldProtect { // When changing protection status, include any other field updates too - needsUpdate, _ := p.shouldUpdateEGWControlPlaneResource(current, desiredEGWCP) + needsUpdate, updateFields := p.shouldUpdateEGWControlPlaneResource(current, desiredEGWCP) // Create protection change object protectionChange := &ProtectionChange{ @@ -94,12 +95,12 @@ func (p *Planner) planEGWControlPlaneChanges(ctx context.Context, plannerCtx *Co } // Validate protection change - err := p.validateProtectionWithChange("api", desiredEGWCP.Name, isProtected, ActionUpdate, + err := p.validateProtectionWithChange("event_gateway_control_plane", desiredEGWCP.Name, isProtected, ActionUpdate, protectionChange, needsUpdate) if err != nil { protectionErrors = append(protectionErrors, err) } else { - //p.planEGWControlPlaneProtectionChangeWithFields(current, desiredEGWCP, isProtected, shouldProtect, updateFields, plan) + p.planEGWControlPlaneProtectionChangeWithFields(current, desiredEGWCP, isProtected, shouldProtect, updateFields, plan) } } else { // Check if update needed based on configuration @@ -151,6 +152,70 @@ func (p *Planner) planEGWControlPlaneChanges(ctx context.Context, plannerCtx *Co return nil } +func (p *Planner) planEGWControlPlaneProtectionChangeWithFields( + current state.EventGatewayControlPlane, + desired resources.EventGatewayControlPlaneResource, + wasProtected, shouldProtect bool, + updateFields map[string]any, + plan *Plan, +) { + // Extract namespace + namespace := DefaultNamespace + if desired.Kongctl != nil && desired.Kongctl.Namespace != nil { + namespace = *desired.Kongctl.Namespace + } + + // Use generic protection change planner + config := ProtectionChangeConfig{ + ResourceType: "event_gateway_control_plane", + ResourceName: desired.Name, + ResourceRef: desired.GetRef(), + ResourceID: current.ID, + OldProtected: wasProtected, + NewProtected: shouldProtect, + Namespace: namespace, + } + + change := p.genericPlanner.PlanProtectionChange(context.Background(), config) + + // Always include essential fields for protection changes + fields := make(map[string]any) + + // Include any field updates if present + for field, newValue := range updateFields { + fields[field] = newValue + } + + // ALWAYS include essential identification fields for protection changes + fields["name"] = current.Name + fields["id"] = current.ID + + // Preserve namespace context for execution phase + if current.Labels != nil { + if namespace, exists := current.Labels[labels.NamespaceKey]; exists { + fields["namespace"] = namespace + } + } + + // Preserve other critical labels that identify managed resources + if current.Labels != nil { + preservedLabels := make(map[string]string) + for key, value := range current.Labels { + // Preserve all KONGCTL- prefixed labels except protected (which will be updated) + if strings.HasPrefix(key, "KONGCTL-") && key != labels.ProtectedKey { + preservedLabels[key] = value + } + } + if len(preservedLabels) > 0 { + fields["preserved_labels"] = preservedLabels + } + } + + change.Fields = fields + + plan.AddChange(change) +} + func (p *Planner) shouldUpdateEGWControlPlaneResource(current state.EventGatewayControlPlane, desired resources.EventGatewayControlPlaneResource) (bool, map[string]any) { updates := make(map[string]any) @@ -281,6 +346,7 @@ func (p *Planner) planEGWControlPlaneDelete(egwControlPlane state.EventGatewayCo config := DeleteConfig{ ResourceType: string(resources.ResourceTypeEventGatewayControlPlane), ResourceName: egwControlPlane.Name, + ResourceRef: egwControlPlane.Name, ResourceID: egwControlPlane.ID, Namespace: namespace, } From 6b399f84f00b6d5d7e01912ca11b1a6a5bb9ce49 Mon Sep 17 00:00:00 2001 From: Harsha Dixit Date: Tue, 6 Jan 2026 18:22:03 +0530 Subject: [PATCH 07/24] tests: add e2e scenario for event gateway control plane CRUD and protection changes --- .../002-update-fields/event-gateways.yaml | 10 + .../003-sync-delete/event-gateways.yaml | 5 + .../control-planes/scenario.yaml | 130 +++++++++++++ .../002-attempt-delete/event_gateways.yaml | 9 + .../003-unprotect/event_gateways.yaml | 17 ++ .../event_gateways.yaml | 8 + .../event-gateways/scenario.yaml | 173 ++++++++++++++++++ .../comprehensive-fields/event-gateways.yaml | 10 + .../declarative/protected/event-gateways.yaml | 10 + 9 files changed, 372 insertions(+) create mode 100644 test/e2e/scenarios/event-gateway/control-planes/overlays/002-update-fields/event-gateways.yaml create mode 100644 test/e2e/scenarios/event-gateway/control-planes/overlays/003-sync-delete/event-gateways.yaml create mode 100644 test/e2e/scenarios/event-gateway/control-planes/scenario.yaml create mode 100644 test/e2e/scenarios/protected-resources/event-gateways/overlays/002-attempt-delete/event_gateways.yaml create mode 100644 test/e2e/scenarios/protected-resources/event-gateways/overlays/003-unprotect/event_gateways.yaml create mode 100644 test/e2e/scenarios/protected-resources/event-gateways/overlays/004-delete-unprotected/event_gateways.yaml create mode 100644 test/e2e/scenarios/protected-resources/event-gateways/scenario.yaml create mode 100644 test/e2e/testdata/declarative/event-gateways/comprehensive-fields/event-gateways.yaml create mode 100644 test/e2e/testdata/declarative/protected/event-gateways.yaml diff --git a/test/e2e/scenarios/event-gateway/control-planes/overlays/002-update-fields/event-gateways.yaml b/test/e2e/scenarios/event-gateway/control-planes/overlays/002-update-fields/event-gateways.yaml new file mode 100644 index 00000000..c0202f10 --- /dev/null +++ b/test/e2e/scenarios/event-gateway/control-planes/overlays/002-update-fields/event-gateways.yaml @@ -0,0 +1,10 @@ +event_gateway_control_planes: + - ref: egw-top-level + name: "My Event Gateway CP" + description: "Updated description for My Event Gateway CP" + labels: + owner: "platform-eng" + lifecycle: "production" + kongctl: + namespace: "event-gateways-comprehensive" + protected: false diff --git a/test/e2e/scenarios/event-gateway/control-planes/overlays/003-sync-delete/event-gateways.yaml b/test/e2e/scenarios/event-gateway/control-planes/overlays/003-sync-delete/event-gateways.yaml new file mode 100644 index 00000000..0421d255 --- /dev/null +++ b/test/e2e/scenarios/event-gateway/control-planes/overlays/003-sync-delete/event-gateways.yaml @@ -0,0 +1,5 @@ +_defaults: + kongctl: + namespace: "event-gateways-comprehensive" + +event_gateway_control_planes: [] diff --git a/test/e2e/scenarios/event-gateway/control-planes/scenario.yaml b/test/e2e/scenarios/event-gateway/control-planes/scenario.yaml new file mode 100644 index 00000000..2e2b5c05 --- /dev/null +++ b/test/e2e/scenarios/event-gateway/control-planes/scenario.yaml @@ -0,0 +1,130 @@ +baseInputsPath: ../../../testdata/declarative/event-gateways/comprehensive-fields + +env: + KONGCTL_LOG_LEVEL: info + +defaults: + mask: + dropKeys: + - id + - created_at + - updated_at + +steps: + - name: 000-reset-org + skipInputs: true + commands: + - resetOrg: true + + - name: 001-apply-create + commands: + - name: 000-apply-create + run: + - apply + - -f + - "{{ .workdir }}/event-gateways.yaml" + - --auto-approve + assertions: + - select: plan.summary + expect: + fields: + total_changes: 1 + by_action.CREATE: 1 + - select: summary + expect: + fields: + applied: 1 + failed: 0 + status: success + - select: "plan.changes[?resource_type=='event_gateway_control_plane' && resource_ref=='egw-top-level'] | [0]" + expect: + fields: + action: CREATE + fields.name: "My Event Gateway CP" + fields.description: "Initial description for My Event Gateway CP" + fields.labels.owner: "platform" + fields.labels.lifecycle: "dev" + - name: 001-get-event-gateways + run: ["get", "eventgatewaycontrolplane", "-o", "json"] + assertions: + - select: "[?name=='My Event Gateway CP'] | [0]" + expect: + fields: + name: "My Event Gateway CP" + description: "Initial description for My Event Gateway CP" + labels.owner: "platform" + labels.lifecycle: "dev" + labels."KONGCTL-namespace": "event-gateways-comprehensive" + - name: 002-apply-update + inputOverlayDirs: + - overlays/002-update-fields + commands: + - name: 000-apply-update + run: + - apply + - -f + - "{{ .workdir }}/event-gateways.yaml" + - --auto-approve + assertions: + - select: plan.summary + expect: + fields: + total_changes: 1 + by_action.UPDATE: 1 + - select: summary + expect: + fields: + applied: 1 + failed: 0 + status: success + - select: "plan.changes[?resource_type=='event_gateway_control_plane' && resource_ref=='egw-top-level'] | [0]" + expect: + fields: + action: UPDATE + fields.description: "Updated description for My Event Gateway CP" + fields.labels.owner: "platform-eng" + fields.labels.lifecycle: "production" + - name: 001-get-event-gateways + run: ["get", "eventgatewaycontrolplane", "-o", "json"] + assertions: + - select: "[?name=='My Event Gateway CP'] | [0]" + expect: + fields: + name: "My Event Gateway CP" + description: "Updated description for My Event Gateway CP" + labels.owner: "platform-eng" + labels.lifecycle: "production" + labels."KONGCTL-namespace": "event-gateways-comprehensive" + - name: 003-sync-delete + inputOverlayDirs: + - overlays/003-sync-delete + commands: + - name: 000-sync-delete + run: + - sync + - -f + - "{{ .workdir }}/event-gateways.yaml" + - --auto-approve + assertions: + - select: plan.summary + expect: + fields: + total_changes: 1 + by_action.DELETE: 1 + - select: summary + expect: + fields: + applied: 1 + failed: 0 + status: success + - select: "plan.changes[?resource_type=='event_gateway_control_plane' && resource_ref=='My Event Gateway CP'] | [0]" + expect: + fields: + action: DELETE + - name: 001-get-event-gateways + run: ["get", "eventgatewaycontrolplane", "-o", "json"] + assertions: + - select: "[?name=='My Event Gateway CP']" + expect: + fields: + "length(@)": 0 diff --git a/test/e2e/scenarios/protected-resources/event-gateways/overlays/002-attempt-delete/event_gateways.yaml b/test/e2e/scenarios/protected-resources/event-gateways/overlays/002-attempt-delete/event_gateways.yaml new file mode 100644 index 00000000..4af3dfbf --- /dev/null +++ b/test/e2e/scenarios/protected-resources/event-gateways/overlays/002-attempt-delete/event_gateways.yaml @@ -0,0 +1,9 @@ +# Overlay: Remove all Event Gateway Control Planes from config +# This should trigger DELETE plans in sync mode +# Protected Event Gateway Control Planes should cause plan generation to fail + +_defaults: + kongctl: + namespace: default + +event_gateway_control_planes: [] diff --git a/test/e2e/scenarios/protected-resources/event-gateways/overlays/003-unprotect/event_gateways.yaml b/test/e2e/scenarios/protected-resources/event-gateways/overlays/003-unprotect/event_gateways.yaml new file mode 100644 index 00000000..af730b4f --- /dev/null +++ b/test/e2e/scenarios/protected-resources/event-gateways/overlays/003-unprotect/event_gateways.yaml @@ -0,0 +1,17 @@ +# Overlay: Set protected: false on Customer Event Gateway Control Plane +# This should allow the protection label to be removed + +_defaults: + kongctl: + namespace: default + +event_gateway_control_planes: + - ref: egw-protected-top-level + name: "My Protected Event Gateway CP" + description: "Initial description for My Protected Event Gateway CP" + labels: + owner: "platform" + lifecycle: "dev" + kongctl: + namespace: "default" + protected: false \ No newline at end of file diff --git a/test/e2e/scenarios/protected-resources/event-gateways/overlays/004-delete-unprotected/event_gateways.yaml b/test/e2e/scenarios/protected-resources/event-gateways/overlays/004-delete-unprotected/event_gateways.yaml new file mode 100644 index 00000000..e0651448 --- /dev/null +++ b/test/e2e/scenarios/protected-resources/event-gateways/overlays/004-delete-unprotected/event_gateways.yaml @@ -0,0 +1,8 @@ +# Overlay: Empty config to delete all Event Gateway Control Planes +# Now that Customer Event Gateway Control Plane is unprotected, this should succeed + +_defaults: + kongctl: + namespace: default + +event_gateway_control_planes: [] diff --git a/test/e2e/scenarios/protected-resources/event-gateways/scenario.yaml b/test/e2e/scenarios/protected-resources/event-gateways/scenario.yaml new file mode 100644 index 00000000..213044c2 --- /dev/null +++ b/test/e2e/scenarios/protected-resources/event-gateways/scenario.yaml @@ -0,0 +1,173 @@ +baseInputsPath: ../../../testdata/declarative/protected + +env: + KONGCTL_LOG_LEVEL: info + +vars: + protectedEventGatewayName: "My Protected Event Gateway CP" + +defaults: + mask: + dropKeys: + - id + - created_at + - updated_at + +steps: + # Step 0: Clean environment + - name: 000-reset-org + skipInputs: true + commands: + - resetOrg: true + + # Step 1: Initial sync - create protected and unprotected resources + - name: 001-initial-sync + commands: + - name: 000-sync + run: + - sync + - -f + - "{{ .workdir }}/event-gateways.yaml" + - --auto-approve + assertions: + # Verify sync mode + - select: "plan.metadata" + expect: + fields: + mode: sync + + # Verify protected Event Gateway created + - select: >- + plan.changes[?resource_type=='event_gateway_control_plane' && + resource_ref=='egw-protected-top-level'] | [0] + expect: + fields: + action: CREATE + + # Verify protection changes + - select: "plan.summary.protection_changes" + expect: + fields: + protecting: 1 + unprotecting: 0 + + # Verify resources exist with proper labels + - name: 001-get-event-gateway-control-planes + run: ["get", "eventgatewaycontrolplanes", "-o", "json"] + assertions: + # Verify Protected Event Gateway CP has protected label + - select: "[?name=='{{ .vars.protectedEventGatewayName }}'] | [0]" + expect: + fields: + name: "My Protected Event Gateway CP" + labels."KONGCTL-protected": "true" + labels."KONGCTL-namespace": "default" + + # Step 2: Attempt to delete protected resource (should fail) + - name: 002-attempt-delete-protected + inputOverlayDirs: + - overlays/002-attempt-delete + commands: + - name: 000-sync-should-fail + run: + - sync + - -f + - "{{ .workdir }}/event_gateways.yaml" + - --auto-approve + expectFailure: + exitCode: 1 + contains: "protected" + + # Verify protected resource still exists + - name: 001-verify-protected-exists + run: ["get", "eventgatewaycontrolplane", "-o", "json"] + assertions: + - select: "[?name=='{{ .vars.protectedEventGatewayName }}']" + expect: + fields: + "length(@)": 1 + + # Verify it still has protected label + - select: "[?name=='{{ .vars.protectedEventGatewayName }}'] | [0]" + expect: + fields: + labels."KONGCTL-protected": "true" + + # Step 3: Unprotect the resource + - name: 003-unprotect-resource + inputOverlayDirs: + - overlays/003-unprotect + commands: + - name: 000-sync-unprotect + run: + - sync + - -f + - "{{ .workdir }}/event_gateways.yaml" + - --auto-approve + assertions: + # Should see UPDATE action for protection change + - select: >- + plan.changes[?resource_type=='event_gateway_control_plane' && + resource_ref=='egw-protected-top-level'] | [0] + expect: + fields: + action: UPDATE + + # Verify execution succeeded + - select: "summary" + expect: + fields: + applied: 1 + failed: 0 + + # Verify protection changes + - select: "plan.summary.protection_changes" + expect: + fields: + protecting: 0 + unprotecting: 1 + + # Verify protected label removed + - name: 001-verify-unprotected + run: ["get", "eventgatewaycontrolplane", "-o", "json"] + assertions: + - select: "[?name=='{{ .vars.protectedEventGatewayName }}'] | [0]" + expect: + fields: + labels."KONGCTL-namespace": "default" + + # Step 4: Delete now-unprotected resource (should succeed) + - name: 004-delete-unprotected + inputOverlayDirs: + - overlays/004-delete-unprotected + commands: + - name: 000-sync-delete + run: + - sync + - -f + - "{{ .workdir }}/event_gateways.yaml" + - --auto-approve + assertions: + # Verify DELETE action planned + - select: >- + plan.changes[?resource_type=='event_gateway_control_plane' && + resource_ref=='My Protected Event Gateway CP'] | [0] + expect: + fields: + action: DELETE + + # Verify execution succeeded + - select: "summary" + expect: + fields: + applied: 1 + failed: 0 + + # Verify resource deleted + - name: 001-verify-deleted + run: ["get", "eventgatewaycontrolplane", "-o", "json"] + assertions: + - select: "[?name=='{{ .vars.protectedEventGatewayName }}']" + expect: + fields: + "length(@)": 0 diff --git a/test/e2e/testdata/declarative/event-gateways/comprehensive-fields/event-gateways.yaml b/test/e2e/testdata/declarative/event-gateways/comprehensive-fields/event-gateways.yaml new file mode 100644 index 00000000..8c89cdb0 --- /dev/null +++ b/test/e2e/testdata/declarative/event-gateways/comprehensive-fields/event-gateways.yaml @@ -0,0 +1,10 @@ +event_gateway_control_planes: + - ref: egw-top-level + name: "My Event Gateway CP" + description: "Initial description for My Event Gateway CP" + labels: + owner: "platform" + lifecycle: "dev" + kongctl: + namespace: "event-gateways-comprehensive" + protected: false diff --git a/test/e2e/testdata/declarative/protected/event-gateways.yaml b/test/e2e/testdata/declarative/protected/event-gateways.yaml new file mode 100644 index 00000000..84dc55cf --- /dev/null +++ b/test/e2e/testdata/declarative/protected/event-gateways.yaml @@ -0,0 +1,10 @@ +event_gateway_control_planes: + - ref: egw-protected-top-level + name: "My Protected Event Gateway CP" + description: "Initial description for My Protected Event Gateway CP" + labels: + owner: "platform" + lifecycle: "dev" + kongctl: + namespace: "default" + protected: true From 98a7a2fbaef2def8394adef79b8000cc9deaea23 Mon Sep 17 00:00:00 2001 From: Harsha Dixit Date: Wed, 7 Jan 2026 17:54:25 +0530 Subject: [PATCH 08/24] test: update protection test namespace --- .../overlays/002-attempt-delete/event_gateways.yaml | 2 +- .../event-gateways/overlays/003-unprotect/event_gateways.yaml | 4 ++-- .../overlays/004-delete-unprotected/event_gateways.yaml | 2 +- .../protected-resources/event-gateways/scenario.yaml | 4 ++-- test/e2e/testdata/declarative/protected/event-gateways.yaml | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/e2e/scenarios/protected-resources/event-gateways/overlays/002-attempt-delete/event_gateways.yaml b/test/e2e/scenarios/protected-resources/event-gateways/overlays/002-attempt-delete/event_gateways.yaml index 4af3dfbf..27d2a61b 100644 --- a/test/e2e/scenarios/protected-resources/event-gateways/overlays/002-attempt-delete/event_gateways.yaml +++ b/test/e2e/scenarios/protected-resources/event-gateways/overlays/002-attempt-delete/event_gateways.yaml @@ -4,6 +4,6 @@ _defaults: kongctl: - namespace: default + namespace: "egw-protection" event_gateway_control_planes: [] diff --git a/test/e2e/scenarios/protected-resources/event-gateways/overlays/003-unprotect/event_gateways.yaml b/test/e2e/scenarios/protected-resources/event-gateways/overlays/003-unprotect/event_gateways.yaml index af730b4f..e4e9634b 100644 --- a/test/e2e/scenarios/protected-resources/event-gateways/overlays/003-unprotect/event_gateways.yaml +++ b/test/e2e/scenarios/protected-resources/event-gateways/overlays/003-unprotect/event_gateways.yaml @@ -3,7 +3,7 @@ _defaults: kongctl: - namespace: default + namespace: "egw-protection" event_gateway_control_planes: - ref: egw-protected-top-level @@ -13,5 +13,5 @@ event_gateway_control_planes: owner: "platform" lifecycle: "dev" kongctl: - namespace: "default" + namespace: "egw-protection" protected: false \ No newline at end of file diff --git a/test/e2e/scenarios/protected-resources/event-gateways/overlays/004-delete-unprotected/event_gateways.yaml b/test/e2e/scenarios/protected-resources/event-gateways/overlays/004-delete-unprotected/event_gateways.yaml index e0651448..a48c83d5 100644 --- a/test/e2e/scenarios/protected-resources/event-gateways/overlays/004-delete-unprotected/event_gateways.yaml +++ b/test/e2e/scenarios/protected-resources/event-gateways/overlays/004-delete-unprotected/event_gateways.yaml @@ -3,6 +3,6 @@ _defaults: kongctl: - namespace: default + namespace: "egw-protection" event_gateway_control_planes: [] diff --git a/test/e2e/scenarios/protected-resources/event-gateways/scenario.yaml b/test/e2e/scenarios/protected-resources/event-gateways/scenario.yaml index 213044c2..148a2a82 100644 --- a/test/e2e/scenarios/protected-resources/event-gateways/scenario.yaml +++ b/test/e2e/scenarios/protected-resources/event-gateways/scenario.yaml @@ -61,7 +61,7 @@ steps: fields: name: "My Protected Event Gateway CP" labels."KONGCTL-protected": "true" - labels."KONGCTL-namespace": "default" + labels."KONGCTL-namespace": "egw-protection" # Step 2: Attempt to delete protected resource (should fail) - name: 002-attempt-delete-protected @@ -134,7 +134,7 @@ steps: - select: "[?name=='{{ .vars.protectedEventGatewayName }}'] | [0]" expect: fields: - labels."KONGCTL-namespace": "default" + labels."KONGCTL-namespace": "egw-protection" # Step 4: Delete now-unprotected resource (should succeed) - name: 004-delete-unprotected diff --git a/test/e2e/testdata/declarative/protected/event-gateways.yaml b/test/e2e/testdata/declarative/protected/event-gateways.yaml index 84dc55cf..49d1dcc5 100644 --- a/test/e2e/testdata/declarative/protected/event-gateways.yaml +++ b/test/e2e/testdata/declarative/protected/event-gateways.yaml @@ -6,5 +6,5 @@ event_gateway_control_planes: owner: "platform" lifecycle: "dev" kongctl: - namespace: "default" + namespace: "egw-protection" protected: true From 05afccf6364e8dcb8d3baf8ec3865291e5ea2601 Mon Sep 17 00:00:00 2001 From: Harsha Dixit Date: Wed, 7 Jan 2026 18:27:58 +0530 Subject: [PATCH 09/24] fix: UX for imperative get --- .../controlplane.go} | 22 ++- .../getControlPlane.go} | 135 +++--------------- .../konnect/eventgateway/event-gateway.go | 37 +++++ internal/cmd/root/products/konnect/konnect.go | 4 +- ...gatewaycontrolplane.go => eventgateway.go} | 25 ++-- internal/cmd/root/verbs/get/get.go | 2 +- .../control-planes/scenario.yaml | 6 +- .../event-gateways/scenario.yaml | 8 +- 8 files changed, 81 insertions(+), 158 deletions(-) rename internal/cmd/root/products/konnect/eventgateway/{eventgatewaycontrolplane.go => control-plane/controlplane.go} (72%) rename internal/cmd/root/products/konnect/eventgateway/{getEventGatewayControlPlane.go => control-plane/getControlPlane.go} (72%) create mode 100644 internal/cmd/root/products/konnect/eventgateway/event-gateway.go rename internal/cmd/root/verbs/get/{eventgatewaycontrolplane.go => eventgateway.go} (75%) diff --git a/internal/cmd/root/products/konnect/eventgateway/eventgatewaycontrolplane.go b/internal/cmd/root/products/konnect/eventgateway/control-plane/controlplane.go similarity index 72% rename from internal/cmd/root/products/konnect/eventgateway/eventgatewaycontrolplane.go rename to internal/cmd/root/products/konnect/eventgateway/control-plane/controlplane.go index 7a5fe6e0..db72ebae 100644 --- a/internal/cmd/root/products/konnect/eventgateway/eventgatewaycontrolplane.go +++ b/internal/cmd/root/products/konnect/eventgateway/control-plane/controlplane.go @@ -1,4 +1,4 @@ -package eventgateway +package controlplane import ( "fmt" @@ -11,28 +11,22 @@ import ( ) const ( - CommandName = "eventgatewaycontrolplane" + CommandName = "control-plane" ) var ( eventGatewayControlPlaneUse = CommandName - eventGatewayControlPlaneShort = i18n.T("root.products.konnect.eventgateway.eventGatewayControlPlaneShort", + eventGatewayControlPlaneShort = i18n.T("root.products.konnect.event-gateway.controlPlaneShort", "Manage Konnect event gateway control plane resources") - eventGatewayControlPlaneLong = normalizers.LongDesc(i18n.T("root.products.konnect.eventgateway.eventGatewayControlPlaneLong", + eventGatewayControlPlaneLong = normalizers.LongDesc(i18n.T("root.products.konnect.event-gateway.controlPlaneLong", `The event gateway control plane command allows you to work with Konnect event gateway control plane resources.`)) eventGatewayControlPlaneExample = normalizers.Examples( - i18n.T("root.products.konnect.eventgateway.eventGatewayControlPlaneExamples", + i18n.T("root.products.konnect.event-gateway.controlPlaneExamples", fmt.Sprintf(` # List all the Konnect event gateway control planes for the organization -%[1]s get eventgatewaycontrolplanes +%[1]s get event-gateway control-planes # Get a specific Konnect event gateway control plane -%[1]s get eventgatewaycontrolplane -# List portal pages -%[1]s get portal pages --portal-id -# List portal applications -%[1]s get portal applications --portal-id -# List portals using explicit konnect product -%[1]s get konnect portals +%[1]s get event-gateway control-plane `, meta.CLIName))) ) @@ -45,7 +39,7 @@ func NewEventGatewayControlPlaneCmd(verb verbs.VerbValue, Short: eventGatewayControlPlaneShort, Long: eventGatewayControlPlaneLong, Example: eventGatewayControlPlaneExample, - Aliases: []string{"eventgatewaycontrolplanes", "p", "ps", "P", "PS"}, + Aliases: []string{"control-planes", "cp", "cps", "CP", "CPS"}, } switch verb { diff --git a/internal/cmd/root/products/konnect/eventgateway/getEventGatewayControlPlane.go b/internal/cmd/root/products/konnect/eventgateway/control-plane/getControlPlane.go similarity index 72% rename from internal/cmd/root/products/konnect/eventgateway/getEventGatewayControlPlane.go rename to internal/cmd/root/products/konnect/eventgateway/control-plane/getControlPlane.go index d4ec655f..c503485b 100644 --- a/internal/cmd/root/products/konnect/eventgateway/getEventGatewayControlPlane.go +++ b/internal/cmd/root/products/konnect/eventgateway/control-plane/getControlPlane.go @@ -1,4 +1,4 @@ -package eventgateway +package controlplane import ( "fmt" @@ -13,7 +13,6 @@ import ( cmdCommon "github.com/kong/kongctl/internal/cmd/common" "github.com/kong/kongctl/internal/cmd/output/tableview" "github.com/kong/kongctl/internal/cmd/root/products/konnect/common" - "github.com/kong/kongctl/internal/cmd/root/products/konnect/navigator" "github.com/kong/kongctl/internal/cmd/root/verbs" "github.com/kong/kongctl/internal/config" "github.com/kong/kongctl/internal/konnect/helpers" @@ -26,21 +25,21 @@ import ( ) var ( - getEventGatewayControlPlanesShort = i18n.T("root.products.konnect.api.getEventGatewayControlPlanesShort", - "List or get Konnect APIs") - getEventGatewayControlPlanesLong = i18n.T("root.products.konnect.api.getEventGatewayControlPlanesLong", - `Use the get verb with the api command to query Konnect APIs.`) + getEventGatewayControlPlanesShort = i18n.T("root.products.konnect.event-gateway.control-plane.getEventGatewayControlPlanesShort", + "List or get Konnect Event Gateway Control Planes") + getEventGatewayControlPlanesLong = i18n.T("root.products.konnect.event-gateway.control-plane.getEventGatewayControlPlanesLong", + `Use the get verb with the event-gateway control-plane command to query Konnect Event Gateway Control Planes.`) getEventGatewayControlPlanesExample = normalizers.Examples( - i18n.T("root.products.konnect.api.getEventGatewayControlPlanesExamples", + i18n.T("root.products.konnect.event-gateway.control-plane.getEventGatewayControlPlanesExamples", fmt.Sprintf(` # List all the Event gateway control planes for the organization - %[1]s get eventgatewaycontrolplanes + %[1]s get event-gateway control-planes # Get details for an Event gateway control plane with a specific ID - %[1]s get eventgatewaycontrolplane 22cd8a0b-72e7-4212-9099-0764f8e9c5ac + %[1]s get event-gateway control-plane 22cd8a0b-72e7-4212-9099-0764f8e9c5ac # Get details for an Event gateway control plane with a specific name - %[1]s get eventgatewaycontrolplane my-eventgatewaycontrolplane + %[1]s get event-gateway control-plane my-eventgatewaycontrolplane # Get all the Event gateway control planes using command aliases - %[1]s get eventgatewaycontrolplanes + %[1]s get egw cp `, meta.CLIName))) ) @@ -85,106 +84,12 @@ func eventGatewayControlPlaneToDisplayRecord(e *kkComps.EventGatewayInfo) textDi } } -/* -func eventGatewayControlPlaneDetailView(eventGateway *kkComps.EventGatewayInfo) string { - if eventGateway == nil { - return "" - } - - const missing = "n/a" - id := strings.TrimSpace(eventGateway.ID) - if id == "" { - id = missing - } - name := strings.TrimSpace(eventGateway.Name) - if name == "" { - name = missing - } - - type detailField struct { - label string - value string - multiline bool - } - - var fields []detailField - - addField := func(label, value string) { - value = strings.TrimSpace(value) - if value == "" { - return - } - fields = append(fields, detailField{ - label: label, - value: value, - }) - } - - addMultiline := func(label, value string) { - value = strings.TrimRight(value, "\n") - if strings.TrimSpace(value) == "" { - return - } - fields = append(fields, detailField{ - label: label, - value: value, - multiline: true, - }) - } - - if eventGateway.Description != nil && *eventGateway.Description != "" { - description := strings.TrimSpace(*eventGateway.Description) - if description != "" { - const wrapWidth = 80 - addMultiline("description", wordwrap.String(description, wrapWidth)) - } - } - - if labels := eventGateway.GetLabels(); len(labels) > 0 { - keys := make([]string, 0, len(labels)) - for k := range labels { - keys = append(keys, k) - } - sort.Strings(keys) - var sb strings.Builder - for _, k := range keys { - fmt.Fprintf(&sb, " %s: %s\n", k, labels[k]) - } - addMultiline("labels", sb.String()) - } - - addField("created_at", eventGateway.CreatedAt.In(time.Local).Format("2006-01-02 15:04:05")) - addField("updated_at", eventGateway.UpdatedAt.In(time.Local).Format("2006-01-02 15:04:05")) - - sort.Slice(fields, func(i, j int) bool { - li := strings.ToLower(fields[i].label) - lj := strings.ToLower(fields[j].label) - if li == lj { - return fields[i].label < fields[j].label - } - return li < lj - }) - - var b strings.Builder - fmt.Fprintf(&b, "id: %s\n", id) - fmt.Fprintf(&b, "name: %s\n", name) - for _, field := range fields { - if field.multiline { - value := strings.TrimRight(field.value, "\n") - fmt.Fprintf(&b, "%s:\n%s\n", field.label, value) - continue - } - fmt.Fprintf(&b, "%s: %s\n", field.label, field.value) - } - - return b.String() -} -*/ - type getEventGatewayControlPlaneCmd struct { *cobra.Command } +// runListByName retrieves an Event Gateway Control Plane by its name +// Todo: Since the API does not support filtering by name, we fetch all and filter locally func runListByName(name string, kkClient helpers.EGWControlPlaneAPI, helper cmd.Helper, cfg config.Hook, ) (*kkComps.EventGatewayInfo, error) { @@ -246,12 +151,10 @@ func runList(kkClient helpers.EGWControlPlaneAPI, helper cmd.Helper, func runGet(id string, kkClient helpers.EGWControlPlaneAPI, helper cmd.Helper, ) (*kkComps.EventGatewayInfo, error) { - // Note: FetchAPI doesn't support include parameters - // Version and publication information would require separate API calls res, err := kkClient.FetchEGWControlPlane(helper.GetContext(), id) if err != nil { attrs := cmd.TryConvertErrorToAttrs(err) - return nil, cmd.PrepareExecutionError("Failed to get API", err, helper.GetCmd(), attrs...) + return nil, cmd.PrepareExecutionError("Failed to get Event Gateway Control Plane", err, helper.GetCmd(), attrs...) } return res.GetEventGatewayInfo(), nil @@ -319,10 +222,10 @@ func (c *getEventGatewayControlPlaneCmd) runE(cobraCmd *cobra.Command, args []st return e } - // 'get eventgateways' can be run in various ways: - // > get eventgateways # Get by UUID - // > get eventgateways # Get by name - // > get eventgateways # List all + // 'get event-gateway control-planes' can be run in various ways: + // > get event-gateway control-planes # Get by UUID + // > get event-gateway control-planes # Get by name + // > get event-gateway control-planes # List all if len(helper.GetArgs()) == 1 { // validate above checks that args is 0 or 1 id := strings.TrimSpace(helper.GetArgs()[0]) @@ -377,10 +280,6 @@ func (c *getEventGatewayControlPlaneCmd) runE(cobraCmd *cobra.Command, args []st ) } - if interactive { - return navigator.Run(helper, navigator.Options{InitialResource: "apis"}) - } - eventGatewayControlPlanes, e := runList(sdk.GetEventGatewayControlPlaneAPI(), helper, cfg) if e != nil { return e diff --git a/internal/cmd/root/products/konnect/eventgateway/event-gateway.go b/internal/cmd/root/products/konnect/eventgateway/event-gateway.go new file mode 100644 index 00000000..1db4605d --- /dev/null +++ b/internal/cmd/root/products/konnect/eventgateway/event-gateway.go @@ -0,0 +1,37 @@ +package eventgateway + +import ( + controlplane "github.com/kong/kongctl/internal/cmd/root/products/konnect/eventgateway/control-plane" + + "github.com/kong/kongctl/internal/cmd/root/verbs" + "github.com/kong/kongctl/internal/util/i18n" + "github.com/kong/kongctl/internal/util/normalizers" + "github.com/spf13/cobra" +) + +var ( + eventGatewayUse = "event-gateway" + eventGatewayShort = i18n.T("root.konnect.event-gateway.gatewayShort", "Manage Konnect Event Gateway resources") + eventGatewayLong = normalizers.LongDesc(i18n.T("root.konnect.event-gateway.gatewayLong", + `The event-gateway command allows you to manage Konnect Event Gateway resources.`)) +) + +func NewEventGatewayCmd(verb verbs.VerbValue, + addParentFlags func(verbs.VerbValue, *cobra.Command), + parentPreRun func(*cobra.Command, []string) error, +) (*cobra.Command, error) { + cmd := &cobra.Command{ + Use: eventGatewayUse, + Short: eventGatewayShort, + Long: eventGatewayLong, + Aliases: []string{"egw", "EGW"}, + } + + c, e := controlplane.NewEventGatewayControlPlaneCmd(verb, addParentFlags, parentPreRun) + if e != nil { + return nil, e + } + cmd.AddCommand(c) + + return cmd, nil +} diff --git a/internal/cmd/root/products/konnect/konnect.go b/internal/cmd/root/products/konnect/konnect.go index df861f5e..640da090 100644 --- a/internal/cmd/root/products/konnect/konnect.go +++ b/internal/cmd/root/products/konnect/konnect.go @@ -243,8 +243,8 @@ func NewKonnectCmd(verb verbs.VerbValue) (*cobra.Command, error) { } cmd.AddCommand(rc) - // Add eventGatewayControlPlane command - egcpc, e := eventgateway.NewEventGatewayControlPlaneCmd(verb, addFlags, preRunE) + // Add EventGateway command + egcpc, e := eventgateway.NewEventGatewayCmd(verb, addFlags, preRunE) if e != nil { return nil, e } diff --git a/internal/cmd/root/verbs/get/eventgatewaycontrolplane.go b/internal/cmd/root/verbs/get/eventgateway.go similarity index 75% rename from internal/cmd/root/verbs/get/eventgatewaycontrolplane.go rename to internal/cmd/root/verbs/get/eventgateway.go index c6b7a4be..f133d119 100644 --- a/internal/cmd/root/verbs/get/eventgatewaycontrolplane.go +++ b/internal/cmd/root/verbs/get/eventgateway.go @@ -14,8 +14,8 @@ import ( "github.com/spf13/cobra" ) -// NewDirectEventGatewayControlPlaneCmd creates an Event Gateway Control Plane command that works at the root level (Konnect-first) -func NewDirectEventGatewayControlPlaneCmd() (*cobra.Command, error) { +// NewDirectEventGatewayCmd creates an Event Gateway Control Plane command that works at the root level (Konnect-first) +func NewDirectEventGatewayCmd() (*cobra.Command, error) { // Define the addFlags function to add Konnect-specific flags addFlags := func(verb verbs.VerbValue, cmd *cobra.Command) { cmd.Flags().String(common.BaseURLFlagName, "", @@ -56,34 +56,27 @@ Setting this value overrides tokens obtained from the login command. c.SetContext(ctx) // Bind flags - return bindEventGatewayControlPlaneFlags(c, args) + return bindEventGatewayFlags(c, args) } // Create the Event Gateway Control Plane command using the existing api package - eventGatewayCmd, err := eventgateway.NewEventGatewayControlPlaneCmd(verbs.Get, addFlags, preRunE) + eventGatewayCmd, err := eventgateway.NewEventGatewayCmd(verbs.Get, addFlags, preRunE) if err != nil { return nil, err } // Override the example to show direct usage without "konnect" eventGatewayCmd.Example = ` # List all the Event Gateways for the organization - kongctl get eventgatewaycontrolplanes + kongctl get event-gateway control-planes # Get a specific Event Gateway - kongctl get eventgatewaycontrolplane - # List documents for a specific Event Gateway - kongctl get eventgatewaycontrolplane documents --eventgatewaycontrolplane-id - # List versions for a specific API - kongctl get api versions --api-id - # List publications for a specific API - kongctl get api publications --api-id - # List APIs using aliases - kongctl get apis` + kongctl get event-gateway control-plane + ` return eventGatewayCmd, nil } -// bindEventGatewayControlPlaneFlags binds Konnect-specific flags to configuration -func bindEventGatewayControlPlaneFlags(c *cobra.Command, args []string) error { +// bindEventGatewayFlags binds Konnect-specific flags to configuration +func bindEventGatewayFlags(c *cobra.Command, args []string) error { helper := cmd.BuildHelper(c, args) cfg, err := helper.GetConfig() if err != nil { diff --git a/internal/cmd/root/verbs/get/get.go b/internal/cmd/root/verbs/get/get.go index 2393c30e..c9f2a9d3 100644 --- a/internal/cmd/root/verbs/get/get.go +++ b/internal/cmd/root/verbs/get/get.go @@ -156,7 +156,7 @@ Setting this value overrides tokens obtained from the login command. } cmd.AddCommand(regionsCmd) - eventGatewayControlPlaneCmd, err := NewDirectEventGatewayControlPlaneCmd() + eventGatewayControlPlaneCmd, err := NewDirectEventGatewayCmd() if err != nil { return nil, err } diff --git a/test/e2e/scenarios/event-gateway/control-planes/scenario.yaml b/test/e2e/scenarios/event-gateway/control-planes/scenario.yaml index 2e2b5c05..b2171c79 100644 --- a/test/e2e/scenarios/event-gateway/control-planes/scenario.yaml +++ b/test/e2e/scenarios/event-gateway/control-planes/scenario.yaml @@ -45,7 +45,7 @@ steps: fields.labels.owner: "platform" fields.labels.lifecycle: "dev" - name: 001-get-event-gateways - run: ["get", "eventgatewaycontrolplane", "-o", "json"] + run: ["get", "event-gateway", "control-plane", "-o", "json"] assertions: - select: "[?name=='My Event Gateway CP'] | [0]" expect: @@ -85,7 +85,7 @@ steps: fields.labels.owner: "platform-eng" fields.labels.lifecycle: "production" - name: 001-get-event-gateways - run: ["get", "eventgatewaycontrolplane", "-o", "json"] + run: ["get", "event-gateway", "control-plane", "-o", "json"] assertions: - select: "[?name=='My Event Gateway CP'] | [0]" expect: @@ -122,7 +122,7 @@ steps: fields: action: DELETE - name: 001-get-event-gateways - run: ["get", "eventgatewaycontrolplane", "-o", "json"] + run: ["get", "event-gateway", "control-plane", "-o", "json"] assertions: - select: "[?name=='My Event Gateway CP']" expect: diff --git a/test/e2e/scenarios/protected-resources/event-gateways/scenario.yaml b/test/e2e/scenarios/protected-resources/event-gateways/scenario.yaml index 148a2a82..812370e2 100644 --- a/test/e2e/scenarios/protected-resources/event-gateways/scenario.yaml +++ b/test/e2e/scenarios/protected-resources/event-gateways/scenario.yaml @@ -53,7 +53,7 @@ steps: # Verify resources exist with proper labels - name: 001-get-event-gateway-control-planes - run: ["get", "eventgatewaycontrolplanes", "-o", "json"] + run: ["get", "event-gateway", "control-planes", "-o", "json"] assertions: # Verify Protected Event Gateway CP has protected label - select: "[?name=='{{ .vars.protectedEventGatewayName }}'] | [0]" @@ -80,7 +80,7 @@ steps: # Verify protected resource still exists - name: 001-verify-protected-exists - run: ["get", "eventgatewaycontrolplane", "-o", "json"] + run: ["get", "event-gateway", "control-plane", "-o", "json"] assertions: - select: "[?name=='{{ .vars.protectedEventGatewayName }}']" expect: @@ -129,7 +129,7 @@ steps: # Verify protected label removed - name: 001-verify-unprotected - run: ["get", "eventgatewaycontrolplane", "-o", "json"] + run: ["get", "event-gateway", "control-plane", "-o", "json"] assertions: - select: "[?name=='{{ .vars.protectedEventGatewayName }}'] | [0]" expect: @@ -165,7 +165,7 @@ steps: # Verify resource deleted - name: 001-verify-deleted - run: ["get", "eventgatewaycontrolplane", "-o", "json"] + run: ["get", "event-gateway", "control-plane", "-o", "json"] assertions: - select: "[?name=='{{ .vars.protectedEventGatewayName }}']" expect: From 82c2c53bc8f19bb42fbbb2a6238e223d70bfa351 Mon Sep 17 00:00:00 2001 From: Harsha Dixit Date: Wed, 7 Jan 2026 18:34:44 +0530 Subject: [PATCH 10/24] style: fix linting in imperative get --- .../control-plane/getControlPlane.go | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/internal/cmd/root/products/konnect/eventgateway/control-plane/getControlPlane.go b/internal/cmd/root/products/konnect/eventgateway/control-plane/getControlPlane.go index c503485b..de3d1429 100644 --- a/internal/cmd/root/products/konnect/eventgateway/control-plane/getControlPlane.go +++ b/internal/cmd/root/products/konnect/eventgateway/control-plane/getControlPlane.go @@ -25,9 +25,11 @@ import ( ) var ( - getEventGatewayControlPlanesShort = i18n.T("root.products.konnect.event-gateway.control-plane.getEventGatewayControlPlanesShort", + getEventGatewayControlPlanesShort = i18n.T( + "root.products.konnect.event-gateway.control-plane.getEventGatewayControlPlanesShort", "List or get Konnect Event Gateway Control Planes") - getEventGatewayControlPlanesLong = i18n.T("root.products.konnect.event-gateway.control-plane.getEventGatewayControlPlanesLong", + getEventGatewayControlPlanesLong = i18n.T( + "root.products.konnect.event-gateway.control-plane.getEventGatewayControlPlanesLong", `Use the get verb with the event-gateway control-plane command to query Konnect Event Gateway Control Planes.`) getEventGatewayControlPlanesExample = normalizers.Examples( i18n.T("root.products.konnect.event-gateway.control-plane.getEventGatewayControlPlanesExamples", @@ -43,7 +45,7 @@ var ( `, meta.CLIName))) ) -// Represents a text display record for an Event gateway control plane +// textDisplayRecord represents a text display record for an Event gateway control plane type textDisplayRecord struct { ID string Name string @@ -89,7 +91,7 @@ type getEventGatewayControlPlaneCmd struct { } // runListByName retrieves an Event Gateway Control Plane by its name -// Todo: Since the API does not support filtering by name, we fetch all and filter locally +// TODO: Since the API does not support filtering by name, we fetch all and filter locally func runListByName(name string, kkClient helpers.EGWControlPlaneAPI, helper cmd.Helper, cfg config.Hook, ) (*kkComps.EventGatewayInfo, error) { @@ -285,7 +287,14 @@ func (c *getEventGatewayControlPlaneCmd) runE(cobraCmd *cobra.Command, args []st return e } - return renderEventGatewayControlPlaneList(helper, helper.GetCmd().Name(), interactive, outType, printer, eventGatewayControlPlanes) + return renderEventGatewayControlPlaneList( + helper, + helper.GetCmd().Name(), + interactive, + outType, + printer, + eventGatewayControlPlanes, + ) } func renderEventGatewayControlPlaneList( From b219e14baa626cf1ec3d1f44e81b892585d22bd8 Mon Sep 17 00:00:00 2001 From: Harsha Dixit Date: Wed, 7 Jan 2026 19:06:45 +0530 Subject: [PATCH 11/24] style: fix remaining linting issues --- .../konnect/declarative/declarative.go | 6 ++-- .../control-plane/controlplane.go | 6 ++-- .../control-plane/getControlPlane.go | 20 ++++++----- .../event_gateway_control_plane_adapter.go | 14 +++++--- internal/declarative/executor/executor.go | 11 +++--- internal/declarative/loader/loader.go | 11 ++++-- internal/declarative/planner/base_planner.go | 4 ++- .../planner/egw_control_plane_planner.go | 30 ++++++++++++---- internal/declarative/state/client.go | 35 +++++++++++++------ .../helpers/event_gateway_control_plane.go | 5 ++- 10 files changed, 99 insertions(+), 43 deletions(-) diff --git a/internal/cmd/root/products/konnect/declarative/declarative.go b/internal/cmd/root/products/konnect/declarative/declarative.go index 99ec935d..5623a8a2 100644 --- a/internal/cmd/root/products/konnect/declarative/declarative.go +++ b/internal/cmd/root/products/konnect/declarative/declarative.go @@ -441,7 +441,8 @@ func runDiff(command *cobra.Command, args []string) error { } totalResources := len(resourceSet.Portals) + len(resourceSet.ApplicationAuthStrategies) + - len(resourceSet.ControlPlanes) + len(resourceSet.APIs) + len(resourceSet.CatalogServices) + len(resourceSet.EventGatewayControlPlanes) + len(resourceSet.ControlPlanes) + len(resourceSet.APIs) + len(resourceSet.CatalogServices) + + len(resourceSet.EventGatewayControlPlanes) if totalResources == 0 { if len(filenames) == 0 { return fmt.Errorf("no configuration files found. Use -f to specify files or --plan to use existing plan") @@ -907,7 +908,8 @@ func runApply(command *cobra.Command, args []string) error { // Check if configuration is empty totalResources := len(resourceSet.Portals) + len(resourceSet.ApplicationAuthStrategies) + - len(resourceSet.ControlPlanes) + len(resourceSet.APIs) + len(resourceSet.EventGatewayControlPlanes) + len(resourceSet.CatalogServices) + len(resourceSet.ControlPlanes) + len(resourceSet.APIs) + + len(resourceSet.EventGatewayControlPlanes) + len(resourceSet.CatalogServices) if totalResources == 0 { // Check if we're using default directory (no explicit sources) diff --git a/internal/cmd/root/products/konnect/eventgateway/control-plane/controlplane.go b/internal/cmd/root/products/konnect/eventgateway/control-plane/controlplane.go index db72ebae..2fa32796 100644 --- a/internal/cmd/root/products/konnect/eventgateway/control-plane/controlplane.go +++ b/internal/cmd/root/products/konnect/eventgateway/control-plane/controlplane.go @@ -45,10 +45,10 @@ func NewEventGatewayControlPlaneCmd(verb verbs.VerbValue, switch verb { case verbs.Get: return newGetEventGatewayControlPlaneCmd(verb, &baseCmd, addParentFlags, parentPreRun).Command, nil - case verbs.Create, verbs.Add, verbs.Apply, verbs.Dump, verbs.Update, verbs.Help, verbs.Login, + case verbs.Create, verbs.Add, verbs.Apply, verbs.Dump, verbs.Update, verbs.Delete, verbs.List, verbs.Help, verbs.Login, verbs.Plan, verbs.Sync, verbs.Diff, verbs.Export, verbs.Adopt, verbs.API, verbs.Kai, verbs.View, verbs.Logout: return &baseCmd, nil + default: + return &baseCmd, nil } - - return &baseCmd, nil } diff --git a/internal/cmd/root/products/konnect/eventgateway/control-plane/getControlPlane.go b/internal/cmd/root/products/konnect/eventgateway/control-plane/getControlPlane.go index de3d1429..ec969f51 100644 --- a/internal/cmd/root/products/konnect/eventgateway/control-plane/getControlPlane.go +++ b/internal/cmd/root/products/konnect/eventgateway/control-plane/getControlPlane.go @@ -137,15 +137,15 @@ func runList(kkClient helpers.EGWControlPlaneAPI, helper cmd.Helper, if res.ListEventGatewaysResponse.Meta.Page.Next == nil { break - } else { - u, err := url.Parse(*res.ListEventGatewaysResponse.Meta.Page.Next) - if err != nil { - return nil, cmd.PrepareExecutionError("Failed to list Event Gateways: invalid cursor", err, helper.GetCmd()) - } + } - values := u.Query() - pageAfter = kk.String(values.Get("page[after]")) + u, err := url.Parse(*res.ListEventGatewaysResponse.Meta.Page.Next) + if err != nil { + return nil, cmd.PrepareExecutionError("Failed to list Event Gateways: invalid cursor", err, helper.GetCmd()) } + + values := u.Query() + pageAfter = kk.String(values.Get("page[after]")) } return allData, nil @@ -156,7 +156,11 @@ func runGet(id string, kkClient helpers.EGWControlPlaneAPI, helper cmd.Helper, res, err := kkClient.FetchEGWControlPlane(helper.GetContext(), id) if err != nil { attrs := cmd.TryConvertErrorToAttrs(err) - return nil, cmd.PrepareExecutionError("Failed to get Event Gateway Control Plane", err, helper.GetCmd(), attrs...) + return nil, cmd.PrepareExecutionError( + "Failed to get Event Gateway Control Plane", + err, + helper.GetCmd(), + attrs...) } return res.GetEventGatewayInfo(), nil diff --git a/internal/declarative/executor/event_gateway_control_plane_adapter.go b/internal/declarative/executor/event_gateway_control_plane_adapter.go index adb9c184..43a1582e 100644 --- a/internal/declarative/executor/event_gateway_control_plane_adapter.go +++ b/internal/declarative/executor/event_gateway_control_plane_adapter.go @@ -112,7 +112,9 @@ func (a *EventGatewayControlPlaneControlPlaneAdapter) Update( } // GetByID gets a event_gateway_control_plane by ID -func (a *EventGatewayControlPlaneControlPlaneAdapter) GetByID(ctx context.Context, id string, _ *ExecutionContext) (ResourceInfo, error) { +func (a *EventGatewayControlPlaneControlPlaneAdapter) GetByID( + ctx context.Context, id string, _ *ExecutionContext, +) (ResourceInfo, error) { eventGateway, err := a.client.GetEventGatewayControlPlaneByID(ctx, id) if err != nil { return nil, err @@ -124,12 +126,16 @@ func (a *EventGatewayControlPlaneControlPlaneAdapter) GetByID(ctx context.Contex } // GetByName gets a event_gateway_control_plane by name -func (a *EventGatewayControlPlaneControlPlaneAdapter) GetByName(ctx context.Context, name string) (ResourceInfo, error) { - // todo - why and fix +func (a *EventGatewayControlPlaneControlPlaneAdapter) GetByName( + _ context.Context, _ string, +) (ResourceInfo, error) { + // TODO - why and fix return nil, nil } -func (a *EventGatewayControlPlaneControlPlaneAdapter) Delete(ctx context.Context, id string, _ *ExecutionContext) error { +func (a *EventGatewayControlPlaneControlPlaneAdapter) Delete( + ctx context.Context, id string, _ *ExecutionContext, +) error { return a.client.DeleteEventGatewayControlPlane(ctx, id) } diff --git a/internal/declarative/executor/executor.go b/internal/declarative/executor/executor.go index b2fedd59..1f3136f5 100644 --- a/internal/declarative/executor/executor.go +++ b/internal/declarative/executor/executor.go @@ -31,10 +31,13 @@ type Executor struct { stateCache *state.Cache // Resource executors - portalExecutor *BaseExecutor[kkComps.CreatePortal, kkComps.UpdatePortal] - controlPlaneExecutor *BaseExecutor[kkComps.CreateControlPlaneRequest, kkComps.UpdateControlPlaneRequest] - apiExecutor *BaseExecutor[kkComps.CreateAPIRequest, kkComps.UpdateAPIRequest] - authStrategyExecutor *BaseExecutor[kkComps.CreateAppAuthStrategyRequest, kkComps.UpdateAppAuthStrategyRequest] + portalExecutor *BaseExecutor[kkComps.CreatePortal, kkComps.UpdatePortal] + controlPlaneExecutor *BaseExecutor[kkComps.CreateControlPlaneRequest, kkComps.UpdateControlPlaneRequest] + apiExecutor *BaseExecutor[kkComps.CreateAPIRequest, kkComps.UpdateAPIRequest] + authStrategyExecutor *BaseExecutor[ + kkComps.CreateAppAuthStrategyRequest, + kkComps.UpdateAppAuthStrategyRequest, + ] catalogServiceExecutor *BaseExecutor[kkComps.CreateCatalogService, kkComps.UpdateCatalogService] eventGatewayControlPlaneExecutor *BaseExecutor[kkComps.CreateGatewayRequest, kkComps.UpdateGatewayRequest] diff --git a/internal/declarative/loader/loader.go b/internal/declarative/loader/loader.go index ccc32fbc..9a369121 100644 --- a/internal/declarative/loader/loader.go +++ b/internal/declarative/loader/loader.go @@ -574,7 +574,10 @@ func (l *Loader) applyNamespaceDefaults(rs *resources.ResourceSet, fileDefaults for i := range rs.Portals { if rs.Portals[i].IsExternal() { if rs.Portals[i].Kongctl != nil { - return fmt.Errorf("portal '%s' is marked as external and cannot use kongctl metadata", rs.Portals[i].Ref) + return fmt.Errorf( + "portal '%s' is marked as external and cannot use kongctl metadata", + rs.Portals[i].Ref, + ) } continue } @@ -664,7 +667,11 @@ func (l *Loader) applyNamespaceDefaults(rs *resources.ResourceSet, fileDefaults // Apply defaults to ControlPlanes (parent resources) for i := range rs.EventGatewayControlPlanes { - if err := assignNamespace(&rs.EventGatewayControlPlanes[i].Kongctl, "control_plane", rs.EventGatewayControlPlanes[i].Ref); err != nil { + if err := assignNamespace( + &rs.EventGatewayControlPlanes[i].Kongctl, + "control_plane", + rs.EventGatewayControlPlanes[i].Ref, + ); err != nil { return err } // Apply protected default if not set diff --git a/internal/declarative/planner/base_planner.go b/internal/declarative/planner/base_planner.go index dc896bf8..03399e90 100644 --- a/internal/declarative/planner/base_planner.go +++ b/internal/declarative/planner/base_planner.go @@ -130,7 +130,9 @@ func (b *BasePlanner) GetDesiredPortalSnippets(namespace string) []resources.Por } // GetDesiredEventGatewayControlPlanes returns desired EGW CP resources from the specified namespace -func (b *BasePlanner) GetDesiredEventGatewayControlPlanes(namespace string) []resources.EventGatewayControlPlaneResource { +func (b *BasePlanner) GetDesiredEventGatewayControlPlanes( + namespace string, +) []resources.EventGatewayControlPlaneResource { return b.planner.resources.GetEventGatewayControlPlanesByNamespace(namespace) } diff --git a/internal/declarative/planner/egw_control_plane_planner.go b/internal/declarative/planner/egw_control_plane_planner.go index a7dca799..99d01c77 100644 --- a/internal/declarative/planner/egw_control_plane_planner.go +++ b/internal/declarative/planner/egw_control_plane_planner.go @@ -22,8 +22,10 @@ func NewEGWControlPlanePlanner(planner *BasePlanner, resources *resources.Resour } } -func (p *EGWControlPlanePlannerImpl) GetDesiredEGWControlPlanes(namespace string) []resources.EventGatewayControlPlaneResource { - return p.BasePlanner.GetDesiredEventGatewayControlPlanes(namespace) +func (p *EGWControlPlanePlannerImpl) GetDesiredEGWControlPlanes( + namespace string, +) []resources.EventGatewayControlPlaneResource { + return p.GetDesiredEventGatewayControlPlanes(namespace) } func (p *EGWControlPlanePlannerImpl) PlanChanges(ctx context.Context, plannerCtx *Config, plan *Plan) error { @@ -36,7 +38,12 @@ func (p *EGWControlPlanePlannerImpl) PlanChanges(ctx context.Context, plannerCtx return nil } -func (p *Planner) planEGWControlPlaneChanges(ctx context.Context, plannerCtx *Config, desired []resources.EventGatewayControlPlaneResource, plan *Plan) error { +func (p *Planner) planEGWControlPlaneChanges( + ctx context.Context, + plannerCtx *Config, + desired []resources.EventGatewayControlPlaneResource, + plan *Plan, +) error { // Skip if no API resources to plan and not in sync mode if len(desired) == 0 && plan.Metadata.Mode != PlanModeSync { p.logger.Debug("Skipping API planning - no desired APIs") @@ -100,14 +107,17 @@ func (p *Planner) planEGWControlPlaneChanges(ctx context.Context, plannerCtx *Co if err != nil { protectionErrors = append(protectionErrors, err) } else { - p.planEGWControlPlaneProtectionChangeWithFields(current, desiredEGWCP, isProtected, shouldProtect, updateFields, plan) + p.planEGWControlPlaneProtectionChangeWithFields( + current, desiredEGWCP, isProtected, shouldProtect, updateFields, plan) } } else { // Check if update needed based on configuration needsUpdate, updateFields := p.shouldUpdateEGWControlPlaneResource(current, desiredEGWCP) if needsUpdate { // Regular update - check protection - if err := p.validateProtection("event-gateway-control-plane", desiredEGWCP.Name, isProtected, ActionUpdate); err != nil { + if err := p.validateProtection( + "event-gateway-control-plane", desiredEGWCP.Name, isProtected, ActionUpdate, + ); err != nil { protectionErrors = append(protectionErrors, err) } else { p.planEGWControlPlaneUpdateWithFields(current, desiredEGWCP, updateFields, plan) @@ -216,7 +226,10 @@ func (p *Planner) planEGWControlPlaneProtectionChangeWithFields( plan.AddChange(change) } -func (p *Planner) shouldUpdateEGWControlPlaneResource(current state.EventGatewayControlPlane, desired resources.EventGatewayControlPlaneResource) (bool, map[string]any) { +func (p *Planner) shouldUpdateEGWControlPlaneResource( + current state.EventGatewayControlPlane, + desired resources.EventGatewayControlPlaneResource, +) (bool, map[string]any) { updates := make(map[string]any) if desired.Name != current.Name { @@ -243,7 +256,10 @@ func (p *Planner) shouldUpdateEGWControlPlaneResource(current state.EventGateway return len(updates) > 0, updates } -func (p *Planner) planEGWControlPlaneCreate(egwControlPlane resources.EventGatewayControlPlaneResource, plan *Plan) string { +func (p *Planner) planEGWControlPlaneCreate( + egwControlPlane resources.EventGatewayControlPlaneResource, + plan *Plan, +) string { var protection any if egwControlPlane.Kongctl != nil && egwControlPlane.Kongctl.Protected != nil { protection = *egwControlPlane.Kongctl.Protected diff --git a/internal/declarative/state/client.go b/internal/declarative/state/client.go index 5e37144e..833db31b 100644 --- a/internal/declarative/state/client.go +++ b/internal/declarative/state/client.go @@ -3112,7 +3112,8 @@ func (c *Client) ListPortalTeamRoles(ctx context.Context, portalID string, teamI }) } - if resp.AssignedPortalRoleCollectionResponse == nil || len(resp.AssignedPortalRoleCollectionResponse.Data) == 0 { + if resp.AssignedPortalRoleCollectionResponse == nil || + len(resp.AssignedPortalRoleCollectionResponse.Data) == 0 { break } @@ -3200,7 +3201,10 @@ func (c *Client) RemovePortalTeamRole(ctx context.Context, portalID string, team return nil } -func (c *Client) ListManagedEventGatewayControlPlanes(ctx context.Context, namespaces []string) ([]EventGatewayControlPlane, error) { +func (c *Client) ListManagedEventGatewayControlPlanes( + ctx context.Context, + namespaces []string, +) ([]EventGatewayControlPlane, error) { var allData []kkComps.EventGatewayInfo var pageAfter *string @@ -3220,15 +3224,15 @@ func (c *Client) ListManagedEventGatewayControlPlanes(ctx context.Context, names if res.ListEventGatewaysResponse.Meta.Page.Next == nil { break - } else { - u, err := url.Parse(*res.ListEventGatewaysResponse.Meta.Page.Next) - if err != nil { - return nil, WrapAPIError(err, "list event gateway control planes: invalid cursor", nil) - } + } - values := u.Query() - pageAfter = kk.String(values.Get("page[after]")) + u, err := url.Parse(*res.ListEventGatewaysResponse.Meta.Page.Next) + if err != nil { + return nil, WrapAPIError(err, "list event gateway control planes: invalid cursor", nil) } + + values := u.Query() + pageAfter = kk.String(values.Get("page[after]")) } var filteredEGWControlPlanes []EventGatewayControlPlane @@ -3248,7 +3252,11 @@ func (c *Client) ListManagedEventGatewayControlPlanes(ctx context.Context, names return filteredEGWControlPlanes, nil } -func (c *Client) CreateEventGatewayControlPlane(ctx context.Context, req kkComps.CreateGatewayRequest, namespace string) (string, error) { +func (c *Client) CreateEventGatewayControlPlane( + ctx context.Context, + req kkComps.CreateGatewayRequest, + namespace string, +) (string, error) { resp, err := c.egwControlPlaneAPI.CreateEGWControlPlane(ctx, req) if err != nil { return "", WrapAPIError(err, "create event gateway control plane", &ErrorWrapperOptions{ @@ -3266,7 +3274,12 @@ func (c *Client) CreateEventGatewayControlPlane(ctx context.Context, req kkComps return resp.EventGatewayInfo.ID, nil } -func (c *Client) UpdateEventGatewayControlPlane(ctx context.Context, id string, req kkComps.UpdateGatewayRequest, namespace string) (string, error) { +func (c *Client) UpdateEventGatewayControlPlane( + ctx context.Context, + id string, + req kkComps.UpdateGatewayRequest, + namespace string, +) (string, error) { resp, err := c.egwControlPlaneAPI.UpdateEGWControlPlane(ctx, id, req) if err != nil { return "", WrapAPIError(err, "update event gateway control plane", &ErrorWrapperOptions{ diff --git a/internal/konnect/helpers/event_gateway_control_plane.go b/internal/konnect/helpers/event_gateway_control_plane.go index 0e9bb053..98509343 100644 --- a/internal/konnect/helpers/event_gateway_control_plane.go +++ b/internal/konnect/helpers/event_gateway_control_plane.go @@ -46,7 +46,10 @@ func (a *EGWControlPlaneAPIImpl) CreateEGWControlPlane(ctx context.Context, requ return a.SDK.EventGateways.CreateEventGateway(ctx, request, opts...) } -func (a *EGWControlPlaneAPIImpl) UpdateEGWControlPlane(ctx context.Context, gatewayID string, request kkComps.UpdateGatewayRequest, +func (a *EGWControlPlaneAPIImpl) UpdateEGWControlPlane( + ctx context.Context, + gatewayID string, + request kkComps.UpdateGatewayRequest, opts ...kkOps.Option, ) (*kkOps.UpdateEventGatewayResponse, error) { return a.SDK.EventGateways.UpdateEventGateway(ctx, gatewayID, request, opts...) From 5f24687412583a0c1ebe843d89eb4679deb3ce6a Mon Sep 17 00:00:00 2001 From: Harsha Dixit Date: Wed, 7 Jan 2026 19:38:00 +0530 Subject: [PATCH 12/24] test: fix test failures due to missing API client initialisation --- .../planner/egw_control_plane_planner.go | 14 +++++++++++--- internal/declarative/state/client.go | 10 ++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/internal/declarative/planner/egw_control_plane_planner.go b/internal/declarative/planner/egw_control_plane_planner.go index 99d01c77..cd029b74 100644 --- a/internal/declarative/planner/egw_control_plane_planner.go +++ b/internal/declarative/planner/egw_control_plane_planner.go @@ -30,7 +30,15 @@ func (p *EGWControlPlanePlannerImpl) GetDesiredEGWControlPlanes( func (p *EGWControlPlanePlannerImpl) PlanChanges(ctx context.Context, plannerCtx *Config, plan *Plan) error { namespace := plannerCtx.Namespace - err := p.planner.planEGWControlPlaneChanges(ctx, plannerCtx, p.GetDesiredEGWControlPlanes(namespace), plan) + desired := p.GetDesiredEGWControlPlanes(namespace) + + // Skip if no desired Event Gateway Control Planes and not in sync mode + if len(desired) == 0 && plan.Metadata.Mode != PlanModeSync { + return nil + } + + err := p.planner.planEGWControlPlaneChanges(ctx, plannerCtx, desired, plan) + if err != nil { return err } @@ -57,8 +65,8 @@ func (p *Planner) planEGWControlPlaneChanges( namespaceFilter := []string{namespace} currentEGWControlPlanes, err := p.client.ListManagedEventGatewayControlPlanes(ctx, namespaceFilter) if err != nil { - // If API client is not configured, skip API planning - if err.Error() == "API client not configured" { + // If API client is not configured, skip planning + if state.IsAPIClientError(err) { return nil } return fmt.Errorf("failed to list current Event Gateway Control Planes: %w", err) diff --git a/internal/declarative/state/client.go b/internal/declarative/state/client.go index 833db31b..e18e7b7a 100644 --- a/internal/declarative/state/client.go +++ b/internal/declarative/state/client.go @@ -3205,6 +3205,11 @@ func (c *Client) ListManagedEventGatewayControlPlanes( ctx context.Context, namespaces []string, ) ([]EventGatewayControlPlane, error) { + // Validate API client is initialized + if err := ValidateAPIClient(c.egwControlPlaneAPI, "event gateway control plane API"); err != nil { + return nil, err + } + var allData []kkComps.EventGatewayInfo var pageAfter *string @@ -3220,6 +3225,11 @@ func (c *Client) ListManagedEventGatewayControlPlanes( return nil, WrapAPIError(err, "list event gateway control planes", nil) } + // If response is nil, break the loop + if res.ListEventGatewaysResponse == nil { + return []EventGatewayControlPlane{}, nil + } + allData = append(allData, res.ListEventGatewaysResponse.Data...) if res.ListEventGatewaysResponse.Meta.Page.Next == nil { From 225b8528ef91aae188f319858f131a9c65976247 Mon Sep 17 00:00:00 2001 From: Harsha Dixit Date: Thu, 8 Jan 2026 16:40:53 +0530 Subject: [PATCH 13/24] fix: rename to event_gateways --- internal/declarative/resources/types.go | 24 +++++++++---------- .../002-update-fields/event-gateways.yaml | 2 +- .../003-sync-delete/event-gateways.yaml | 2 +- .../002-attempt-delete/event_gateways.yaml | 2 +- .../003-unprotect/event_gateways.yaml | 2 +- .../event_gateways.yaml | 2 +- .../comprehensive-fields/event-gateways.yaml | 2 +- .../declarative/protected/event-gateways.yaml | 2 +- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/internal/declarative/resources/types.go b/internal/declarative/resources/types.go index f5bf2a6a..5bb185ca 100644 --- a/internal/declarative/resources/types.go +++ b/internal/declarative/resources/types.go @@ -56,18 +56,18 @@ type ResourceSet struct { APIImplementations []APIImplementationResource `yaml:"api_implementations,omitempty" json:"api_implementations,omitempty"` //nolint:lll APIDocuments []APIDocumentResource `yaml:"api_documents,omitempty" json:"api_documents,omitempty"` //nolint:lll // Portal child resources can be defined at root level (with parent reference) or nested under Portals - PortalCustomizations []PortalCustomizationResource `yaml:"portal_customizations,omitempty" json:"portal_customizations,omitempty"` //nolint:lll - PortalAuthSettings []PortalAuthSettingsResource `yaml:"portal_auth_settings,omitempty" json:"portal_auth_settings,omitempty"` //nolint:lll - PortalCustomDomains []PortalCustomDomainResource `yaml:"portal_custom_domains,omitempty" json:"portal_custom_domains,omitempty"` //nolint:lll - PortalPages []PortalPageResource `yaml:"portal_pages,omitempty" json:"portal_pages,omitempty"` //nolint:lll - PortalSnippets []PortalSnippetResource `yaml:"portal_snippets,omitempty" json:"portal_snippets,omitempty"` //nolint:lll - PortalTeams []PortalTeamResource `yaml:"portal_teams,omitempty" json:"portal_teams,omitempty"` //nolint:lll - PortalTeamRoles []PortalTeamRoleResource `yaml:"portal_team_roles,omitempty" json:"portal_team_roles,omitempty"` //nolint:lll - PortalAssetLogos []PortalAssetLogoResource `yaml:"portal_asset_logos,omitempty" json:"portal_asset_logos,omitempty"` //nolint:lll - PortalAssetFavicons []PortalAssetFaviconResource `yaml:"portal_asset_favicons,omitempty" json:"portal_asset_favicons,omitempty"` //nolint:lll - PortalEmailConfigs []PortalEmailConfigResource `yaml:"portal_email_configs,omitempty" json:"portal_email_configs,omitempty"` //nolint:lll - PortalEmailTemplates []PortalEmailTemplateResource `yaml:"portal_email_templates,omitempty" json:"portal_email_templates,omitempty"` //nolint:lll - EventGatewayControlPlanes []EventGatewayControlPlaneResource `yaml:"event_gateway_control_planes,omitempty" json:"event_gateway_control_planes,omitempty"` //nolint:lll + PortalCustomizations []PortalCustomizationResource `yaml:"portal_customizations,omitempty" json:"portal_customizations,omitempty"` //nolint:lll + PortalAuthSettings []PortalAuthSettingsResource `yaml:"portal_auth_settings,omitempty" json:"portal_auth_settings,omitempty"` //nolint:lll + PortalCustomDomains []PortalCustomDomainResource `yaml:"portal_custom_domains,omitempty" json:"portal_custom_domains,omitempty"` //nolint:lll + PortalPages []PortalPageResource `yaml:"portal_pages,omitempty" json:"portal_pages,omitempty"` //nolint:lll + PortalSnippets []PortalSnippetResource `yaml:"portal_snippets,omitempty" json:"portal_snippets,omitempty"` //nolint:lll + PortalTeams []PortalTeamResource `yaml:"portal_teams,omitempty" json:"portal_teams,omitempty"` //nolint:lll + PortalTeamRoles []PortalTeamRoleResource `yaml:"portal_team_roles,omitempty" json:"portal_team_roles,omitempty"` //nolint:lll + PortalAssetLogos []PortalAssetLogoResource `yaml:"portal_asset_logos,omitempty" json:"portal_asset_logos,omitempty"` //nolint:lll + PortalAssetFavicons []PortalAssetFaviconResource `yaml:"portal_asset_favicons,omitempty" json:"portal_asset_favicons,omitempty"` //nolint:lll + PortalEmailConfigs []PortalEmailConfigResource `yaml:"portal_email_configs,omitempty" json:"portal_email_configs,omitempty"` //nolint:lll + PortalEmailTemplates []PortalEmailTemplateResource `yaml:"portal_email_templates,omitempty" json:"portal_email_templates,omitempty"` //nolint:lll + EventGatewayControlPlanes []EventGatewayControlPlaneResource `yaml:"event_gateways,omitempty" json:"event_gateways,omitempty"` //nolint:lll // DefaultNamespace tracks namespace from _defaults when no resources are present // This is used by the planner to determine which namespace to check for deletions diff --git a/test/e2e/scenarios/event-gateway/control-planes/overlays/002-update-fields/event-gateways.yaml b/test/e2e/scenarios/event-gateway/control-planes/overlays/002-update-fields/event-gateways.yaml index c0202f10..628ba6a9 100644 --- a/test/e2e/scenarios/event-gateway/control-planes/overlays/002-update-fields/event-gateways.yaml +++ b/test/e2e/scenarios/event-gateway/control-planes/overlays/002-update-fields/event-gateways.yaml @@ -1,4 +1,4 @@ -event_gateway_control_planes: +event_gateways: - ref: egw-top-level name: "My Event Gateway CP" description: "Updated description for My Event Gateway CP" diff --git a/test/e2e/scenarios/event-gateway/control-planes/overlays/003-sync-delete/event-gateways.yaml b/test/e2e/scenarios/event-gateway/control-planes/overlays/003-sync-delete/event-gateways.yaml index 0421d255..8d75aa05 100644 --- a/test/e2e/scenarios/event-gateway/control-planes/overlays/003-sync-delete/event-gateways.yaml +++ b/test/e2e/scenarios/event-gateway/control-planes/overlays/003-sync-delete/event-gateways.yaml @@ -2,4 +2,4 @@ _defaults: kongctl: namespace: "event-gateways-comprehensive" -event_gateway_control_planes: [] +event_gateways: [] diff --git a/test/e2e/scenarios/protected-resources/event-gateways/overlays/002-attempt-delete/event_gateways.yaml b/test/e2e/scenarios/protected-resources/event-gateways/overlays/002-attempt-delete/event_gateways.yaml index 27d2a61b..b8c7c713 100644 --- a/test/e2e/scenarios/protected-resources/event-gateways/overlays/002-attempt-delete/event_gateways.yaml +++ b/test/e2e/scenarios/protected-resources/event-gateways/overlays/002-attempt-delete/event_gateways.yaml @@ -6,4 +6,4 @@ _defaults: kongctl: namespace: "egw-protection" -event_gateway_control_planes: [] +event_gateways: [] diff --git a/test/e2e/scenarios/protected-resources/event-gateways/overlays/003-unprotect/event_gateways.yaml b/test/e2e/scenarios/protected-resources/event-gateways/overlays/003-unprotect/event_gateways.yaml index e4e9634b..2dcbd96c 100644 --- a/test/e2e/scenarios/protected-resources/event-gateways/overlays/003-unprotect/event_gateways.yaml +++ b/test/e2e/scenarios/protected-resources/event-gateways/overlays/003-unprotect/event_gateways.yaml @@ -5,7 +5,7 @@ _defaults: kongctl: namespace: "egw-protection" -event_gateway_control_planes: +event_gateways: - ref: egw-protected-top-level name: "My Protected Event Gateway CP" description: "Initial description for My Protected Event Gateway CP" diff --git a/test/e2e/scenarios/protected-resources/event-gateways/overlays/004-delete-unprotected/event_gateways.yaml b/test/e2e/scenarios/protected-resources/event-gateways/overlays/004-delete-unprotected/event_gateways.yaml index a48c83d5..90ea44a2 100644 --- a/test/e2e/scenarios/protected-resources/event-gateways/overlays/004-delete-unprotected/event_gateways.yaml +++ b/test/e2e/scenarios/protected-resources/event-gateways/overlays/004-delete-unprotected/event_gateways.yaml @@ -5,4 +5,4 @@ _defaults: kongctl: namespace: "egw-protection" -event_gateway_control_planes: [] +event_gateways: [] diff --git a/test/e2e/testdata/declarative/event-gateways/comprehensive-fields/event-gateways.yaml b/test/e2e/testdata/declarative/event-gateways/comprehensive-fields/event-gateways.yaml index 8c89cdb0..02077da2 100644 --- a/test/e2e/testdata/declarative/event-gateways/comprehensive-fields/event-gateways.yaml +++ b/test/e2e/testdata/declarative/event-gateways/comprehensive-fields/event-gateways.yaml @@ -1,4 +1,4 @@ -event_gateway_control_planes: +event_gateways: - ref: egw-top-level name: "My Event Gateway CP" description: "Initial description for My Event Gateway CP" diff --git a/test/e2e/testdata/declarative/protected/event-gateways.yaml b/test/e2e/testdata/declarative/protected/event-gateways.yaml index 49d1dcc5..645785b7 100644 --- a/test/e2e/testdata/declarative/protected/event-gateways.yaml +++ b/test/e2e/testdata/declarative/protected/event-gateways.yaml @@ -1,4 +1,4 @@ -event_gateway_control_planes: +event_gateways: - ref: egw-protected-top-level name: "My Protected Event Gateway CP" description: "Initial description for My Protected Event Gateway CP" From 4642dda29658e81cad12431dcea37c3ce0e34775 Mon Sep 17 00:00:00 2001 From: Harsha Dixit Date: Thu, 8 Jan 2026 16:50:02 +0530 Subject: [PATCH 14/24] style: update comments and implement copilot feedback --- .../executor/event_gateway_control_plane_adapter.go | 6 +++--- .../declarative/planner/egw_control_plane_planner.go | 12 ++++++------ .../konnect/helpers/event_gateway_control_plane.go | 4 ++-- internal/konnect/helpers/sdk.go | 2 +- internal/konnect/helpers/sdk_mock.go | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/internal/declarative/executor/event_gateway_control_plane_adapter.go b/internal/declarative/executor/event_gateway_control_plane_adapter.go index 43a1582e..b0ae71e9 100644 --- a/internal/declarative/executor/event_gateway_control_plane_adapter.go +++ b/internal/declarative/executor/event_gateway_control_plane_adapter.go @@ -129,7 +129,7 @@ func (a *EventGatewayControlPlaneControlPlaneAdapter) GetByID( func (a *EventGatewayControlPlaneControlPlaneAdapter) GetByName( _ context.Context, _ string, ) (ResourceInfo, error) { - // TODO - why and fix + // TODO - find why this is required and fix return nil, nil } @@ -151,7 +151,7 @@ func (a *EventGatewayControlPlaneControlPlaneAdapter) SupportsUpdate() bool { return true } -// APIResourceInfo wraps an API to implement ResourceInfo +// EventGatewayControlPlaneResourceInfo wraps an Event Gateway Control Plane to implement ResourceInfo type EventGatewayControlPlaneResourceInfo struct { eventGatewayControlPlane *state.EventGatewayControlPlane } @@ -165,7 +165,7 @@ func (e *EventGatewayControlPlaneResourceInfo) GetName() string { } func (e *EventGatewayControlPlaneResourceInfo) GetLabels() map[string]string { - // API.Labels is already map[string]string + // EventGatewayControlPlane.Labels is already map[string]string return e.eventGatewayControlPlane.Labels } diff --git a/internal/declarative/planner/egw_control_plane_planner.go b/internal/declarative/planner/egw_control_plane_planner.go index cd029b74..1cb4ecd5 100644 --- a/internal/declarative/planner/egw_control_plane_planner.go +++ b/internal/declarative/planner/egw_control_plane_planner.go @@ -52,27 +52,27 @@ func (p *Planner) planEGWControlPlaneChanges( desired []resources.EventGatewayControlPlaneResource, plan *Plan, ) error { - // Skip if no API resources to plan and not in sync mode + // Skip if no Event Gateway Control Plane resources to plan and not in sync mode if len(desired) == 0 && plan.Metadata.Mode != PlanModeSync { - p.logger.Debug("Skipping API planning - no desired APIs") + p.logger.Debug("Skipping Event Gateway Control Plane planning - no desired Event Gateway Control Planes") return nil } // Get namespace from planner context namespace := plannerCtx.Namespace - // Fetch current managed APIs from the specific namespace + // Fetch current managed Event Gateway Control Planes from the specific namespace namespaceFilter := []string{namespace} currentEGWControlPlanes, err := p.client.ListManagedEventGatewayControlPlanes(ctx, namespaceFilter) if err != nil { - // If API client is not configured, skip planning + // If API client is not configured, skip Event Gateway Control Plane planning if state.IsAPIClientError(err) { return nil } return fmt.Errorf("failed to list current Event Gateway Control Planes: %w", err) } - // Index current APIs by name + // Index current Event Gateway Control Planes by name currentByName := make(map[string]state.EventGatewayControlPlane) for _, cp := range currentEGWControlPlanes { currentByName[cp.Name] = cp @@ -81,7 +81,7 @@ func (p *Planner) planEGWControlPlaneChanges( // Collect protection validation errors var protectionErrors []error - // Compare each desired API + // Compare each desired Event Gateway Control Plane for _, desiredEGWCP := range desired { current, exists := currentByName[desiredEGWCP.Name] diff --git a/internal/konnect/helpers/event_gateway_control_plane.go b/internal/konnect/helpers/event_gateway_control_plane.go index 98509343..09cb1a74 100644 --- a/internal/konnect/helpers/event_gateway_control_plane.go +++ b/internal/konnect/helpers/event_gateway_control_plane.go @@ -22,8 +22,8 @@ type EGWControlPlaneAPI interface { opts ...kkOps.Option) (*kkOps.DeleteEventGatewayResponse, error) } -// APIAPIImpl provides an implementation of the APIFullAPI interface -// It implements both APIAPI and all child resource operations for backward compatibility +// EGWControlPlaneAPIImpl provides an implementation of the EGWControlPlaneAPI interface. +// It implements all Event Gateway Control Plane operations defined by EGWControlPlaneAPI. type EGWControlPlaneAPIImpl struct { SDK *kkSDK.SDK } diff --git a/internal/konnect/helpers/sdk.go b/internal/konnect/helpers/sdk.go index 0e3dbad3..4e4aeda1 100644 --- a/internal/konnect/helpers/sdk.go +++ b/internal/konnect/helpers/sdk.go @@ -276,7 +276,7 @@ func (k *KonnectSDK) GetPortalEmailsAPI() PortalEmailsAPI { return &PortalEmailsAPIImpl{SDK: k.SDK} } -// Returns the implementation of the APIAPI interface +// Returns the implementation of the EGWControlPlaneAPI interface func (k *KonnectSDK) GetEventGatewayControlPlaneAPI() EGWControlPlaneAPI { if k.SDK == nil { return nil diff --git a/internal/konnect/helpers/sdk_mock.go b/internal/konnect/helpers/sdk_mock.go index 1287ced1..0347dff1 100644 --- a/internal/konnect/helpers/sdk_mock.go +++ b/internal/konnect/helpers/sdk_mock.go @@ -234,7 +234,7 @@ func (m *MockKonnectSDK) GetPortalEmailsAPI() PortalEmailsAPI { return nil } -// Returns the implementation of the APIAPI interface +// Returns a mock instance of the EGWControlPlaneAPI func (m *MockKonnectSDK) GetEventGatewayControlPlaneAPI() EGWControlPlaneAPI { if m.EventGatewayControlPlaneFactory != nil { return m.EventGatewayControlPlaneFactory() From f061671f029a07fe307bcef4a14b46861055605c Mon Sep 17 00:00:00 2001 From: Harsha Dixit Date: Thu, 8 Jan 2026 16:53:25 +0530 Subject: [PATCH 15/24] docs: add example for event gateways --- docs/declarative.md | 1 + docs/examples/declarative/event-gateway/event-gateway.yaml | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 docs/examples/declarative/event-gateway/event-gateway.yaml diff --git a/docs/declarative.md b/docs/declarative.md index 03bdebf7..632435de 100644 --- a/docs/declarative.md +++ b/docs/declarative.md @@ -178,6 +178,7 @@ lists the currently supported resources and their relationships. - Portals - Application Auth Strategies - Control Planes (including Control Plane Groups) +- Event Gateways **Child Resources** (do NOT support kongctl metadata): diff --git a/docs/examples/declarative/event-gateway/event-gateway.yaml b/docs/examples/declarative/event-gateway/event-gateway.yaml new file mode 100644 index 00000000..1451db29 --- /dev/null +++ b/docs/examples/declarative/event-gateway/event-gateway.yaml @@ -0,0 +1,6 @@ +event_gateways: + - ref: getting-started-event-gateway + name: "My First Event Gateway" + description: "My first event gateway control plane for managing event-driven architectures" + kongctl: + namespace: "default" \ No newline at end of file From add897d1eb01565acfceb191a553955ff903f7f1 Mon Sep 17 00:00:00 2001 From: Rick Spurgeon <10521262+rspurgeon@users.noreply.github.com> Date: Thu, 8 Jan 2026 09:45:30 -0600 Subject: [PATCH 16/24] Tidy: Adds event gateway to reset org command --- docs/examples/declarative/event-gateway/event-gateway.yaml | 2 +- test/e2e/harness/reset.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/examples/declarative/event-gateway/event-gateway.yaml b/docs/examples/declarative/event-gateway/event-gateway.yaml index 1451db29..18fd3313 100644 --- a/docs/examples/declarative/event-gateway/event-gateway.yaml +++ b/docs/examples/declarative/event-gateway/event-gateway.yaml @@ -3,4 +3,4 @@ event_gateways: name: "My First Event Gateway" description: "My first event gateway control plane for managing event-driven architectures" kongctl: - namespace: "default" \ No newline at end of file + namespace: "default" diff --git a/test/e2e/harness/reset.go b/test/e2e/harness/reset.go index a1216a78..789f85b4 100644 --- a/test/e2e/harness/reset.go +++ b/test/e2e/harness/reset.go @@ -180,6 +180,7 @@ var resetSequence = []struct { {"v2", "application-auth-strategies"}, {"v2", "control-planes"}, {"v1", "catalog-services"}, + {"v1", "event-gateways"}, } func executeReset(client *http.Client, baseURL, token string) (resetResult, error) { From 169d4bcfa7caddf6137945e6014fae278f0e1a39 Mon Sep 17 00:00:00 2001 From: Harsha Dixit Date: Fri, 9 Jan 2026 16:04:12 +0530 Subject: [PATCH 17/24] fix: plan command in apply mode for event gateways --- .../konnect/declarative/declarative.go | 10 ++++- .../overlays/002-plan-update/config.yaml | 5 +++ .../plan/apply-workflow/scenario.yaml | 41 ++++++++++++++----- .../overlays/002-plan-sync/config.yaml | 5 +++ .../plan/sync-workflow/scenario.yaml | 39 ++++++++++++++---- .../declarative/plan/apply/config.yaml | 5 +++ .../declarative/plan/sync/config.yaml | 5 +++ 7 files changed, 90 insertions(+), 20 deletions(-) diff --git a/internal/cmd/root/products/konnect/declarative/declarative.go b/internal/cmd/root/products/konnect/declarative/declarative.go index 5623a8a2..d282b77e 100644 --- a/internal/cmd/root/products/konnect/declarative/declarative.go +++ b/internal/cmd/root/products/konnect/declarative/declarative.go @@ -262,7 +262,8 @@ func runPlan(command *cobra.Command, args []string) error { // Check if configuration is empty totalResources := len(resourceSet.Portals) + len(resourceSet.ApplicationAuthStrategies) + - len(resourceSet.ControlPlanes) + len(resourceSet.APIs) + len(resourceSet.CatalogServices) + len(resourceSet.ControlPlanes) + len(resourceSet.APIs) + len(resourceSet.CatalogServices) + + len(resourceSet.EventGatewayControlPlanes) if err := nsValidator.ValidateNamespaceRequirement(resourceSet, requirement); err != nil { return err @@ -338,6 +339,13 @@ func runPlan(command *cobra.Command, args []string) error { } namespaces[ns] = true } + for _, egwControlPlane := range resourceSet.EventGatewayControlPlanes { + ns := "default" + if egwControlPlane.Kongctl != nil && egwControlPlane.Kongctl.Namespace != nil { + ns = *egwControlPlane.Kongctl.Namespace + } + namespaces[ns] = true + } if len(namespaces) > 1 { fmt.Fprintf(command.OutOrStderr(), "Processing %d namespaces...\n", len(namespaces)) diff --git a/test/e2e/scenarios/plan/apply-workflow/overlays/002-plan-update/config.yaml b/test/e2e/scenarios/plan/apply-workflow/overlays/002-plan-update/config.yaml index 6bb22eeb..b5958838 100644 --- a/test/e2e/scenarios/plan/apply-workflow/overlays/002-plan-update/config.yaml +++ b/test/e2e/scenarios/plan/apply-workflow/overlays/002-plan-update/config.yaml @@ -24,3 +24,8 @@ apis: labels: domain: insights lifecycle: beta + +event_gateways: + - ref: plan-apply-egw + name: plan-apply-egw + description: "Updated description for plan apply workflow" \ No newline at end of file diff --git a/test/e2e/scenarios/plan/apply-workflow/scenario.yaml b/test/e2e/scenarios/plan/apply-workflow/scenario.yaml index 4ed1539d..d1509574 100644 --- a/test/e2e/scenarios/plan/apply-workflow/scenario.yaml +++ b/test/e2e/scenarios/plan/apply-workflow/scenario.yaml @@ -10,6 +10,10 @@ vars: namespace: plan-apply ordersAPIRef: plan-apply-orders analyticsAPIRef: plan-apply-analytics + egwRef: plan-apply-egw + egwName: "plan-apply-egw" + egwInitialDesc: "Initial description for plan apply workflow" + egwUpdatedDesc: "Updated description for plan apply workflow" defaults: mask: @@ -46,8 +50,8 @@ steps: - select: summary expect: fields: - total_changes: 2 - by_action.CREATE: 2 + total_changes: 3 + by_action.CREATE: 3 - name: 001-diff-plan-text outputFormat: text parseAs: raw @@ -59,9 +63,10 @@ steps: - select: stdout expect: fields: - "contains(@, 'Plan: 2 to add')": true + "contains(@, 'Plan: 3 to add')": true "contains(@, 'portal \"plan-apply-portal\" will be created')": true "contains(@, 'api \"plan-apply-orders\" will be created')": true + "contains(@, 'event_gateway \"plan-apply-egw\" will be created')": true - name: 002-apply-plan outputFormat: json run: @@ -77,7 +82,7 @@ steps: - select: summary expect: fields: - applied: 2 + applied: 3 failed: 0 - name: 003-get-portals run: ["get", "portals", "-o", "json"] @@ -95,7 +100,14 @@ steps: fields: description: "Orders API for plan apply workflow" labels.domain: commerce - + - name: 005-get-event-gateways + run: ["get", "event-gateway", "control-plane", "-o", "json"] + assertions: + - select: "[?name=='{{ .vars.egwRef }}'] | [0]" + expect: + fields: + name: "{{ .vars.egwName }}" + description: "{{ .vars.egwInitialDesc }}" - name: 002-plan-apply-update inputOverlayDirs: - overlays/002-plan-update @@ -117,9 +129,9 @@ steps: - select: summary expect: fields: - total_changes: 3 + total_changes: 4 by_action.CREATE: 1 - by_action.UPDATE: 2 + by_action.UPDATE: 3 - name: 001-diff-plan-text outputFormat: text parseAs: raw @@ -131,9 +143,10 @@ steps: - select: stdout expect: fields: - "contains(@, 'Plan: 1 to add, 2 to change')": true + "contains(@, 'Plan: 1 to add, 3 to change')": true "contains(@, 'portal \"plan-apply-portal\" will be updated')": true "contains(@, 'api \"plan-apply-analytics\" will be created')": true + "contains(@, 'event_gateway \"plan-apply-egw\" will be updated')": true - name: 002-apply-plan outputFormat: json run: @@ -145,12 +158,12 @@ steps: - select: summary expect: fields: - applied: 3 + applied: 4 failed: 0 - select: plan.summary expect: fields: - by_action.UPDATE: 2 + by_action.UPDATE: 3 by_action.CREATE: 1 - name: 003-get-portals run: ["get", "portals", "-o", "json"] @@ -171,3 +184,11 @@ steps: fields: description: "Orders API with updated details" labels.lifecycle: production + - name: 005-get-event-gateways + run: ["get", "event-gateway", "control-plane", "-o", "json"] + assertions: + - select: "[?name=='{{ .vars.egwRef }}'] | [0]" + expect: + fields: + name: "{{ .vars.egwName }}" + description: "{{ .vars.egwUpdatedDesc }}" diff --git a/test/e2e/scenarios/plan/sync-workflow/overlays/002-plan-sync/config.yaml b/test/e2e/scenarios/plan/sync-workflow/overlays/002-plan-sync/config.yaml index f7c8c20c..44462c0b 100644 --- a/test/e2e/scenarios/plan/sync-workflow/overlays/002-plan-sync/config.yaml +++ b/test/e2e/scenarios/plan/sync-workflow/overlays/002-plan-sync/config.yaml @@ -24,3 +24,8 @@ apis: labels: domain: supply lifecycle: beta + +event_gateways: + - ref: plan-sync-egw + name: plan-sync-egw + description: "Updated description for plan sync workflow" \ No newline at end of file diff --git a/test/e2e/scenarios/plan/sync-workflow/scenario.yaml b/test/e2e/scenarios/plan/sync-workflow/scenario.yaml index 1a6f1e9b..c75df5db 100644 --- a/test/e2e/scenarios/plan/sync-workflow/scenario.yaml +++ b/test/e2e/scenarios/plan/sync-workflow/scenario.yaml @@ -11,6 +11,10 @@ vars: ordersAPIRef: plan-sync-orders legacyAPIRef: plan-sync-legacy inventoryAPIRef: plan-sync-inventory + egwRef: plan-sync-egw + egwName: "plan-sync-egw" + egwInitialDesc: "Initial description for plan sync workflow" + egwUpdatedDesc: "Updated description for plan sync workflow" defaults: mask: @@ -44,12 +48,12 @@ steps: - select: summary expect: fields: - applied: 3 + applied: 4 failed: 0 - select: execution.applied_changes expect: fields: - "length(@)": 3 + "length(@)": 4 - name: 001-get-portal run: ["get", "portals", "-o", "json"] assertions: @@ -69,6 +73,14 @@ steps: expect: fields: labels.lifecycle: sunset + - name: 003-get-event-gateways + run: ["get", "event-gateway", "control-plane", "-o", "json"] + assertions: + - select: "[?name=='{{ .vars.egwRef }}'] | [0]" + expect: + fields: + name: "{{ .vars.egwName }}" + description: "{{ .vars.egwInitialDesc }}" - name: 002-plan-sync-update inputOverlayDirs: @@ -91,9 +103,9 @@ steps: - select: summary expect: fields: - total_changes: 4 + total_changes: 5 by_action.CREATE: 1 - by_action.UPDATE: 2 + by_action.UPDATE: 3 by_action.DELETE: 1 - name: 001-diff-plan-text outputFormat: text @@ -106,12 +118,13 @@ steps: - select: stdout expect: fields: - "contains(@, 'Plan: 1 to add, 2 to change, 1 to destroy')": true + "contains(@, 'Plan: 1 to add, 3 to change, 1 to destroy')": true "contains(@, 'portal \"plan-sync-portal\" will be updated')": true "contains(@, 'authentication_enabled: false')": true "contains(@, 'api \"plan-sync-inventory\" will be created')": true "contains(@, 'api \"plan-sync-orders\" will be updated')": true "contains(@, 'api \"plan-sync-legacy\" will be deleted')": true + "contains(@, 'event_gateway \"plan-sync-egw\" will be updated')": true - name: 002-sync-plan run: - sync @@ -123,12 +136,12 @@ steps: expect: fields: by_action.CREATE: 1 - by_action.UPDATE: 2 + by_action.UPDATE: 3 by_action.DELETE: 1 - select: summary expect: fields: - applied: 4 + applied: 5 failed: 0 - name: 003-get-portal run: ["get", "portals", "-o", "json"] @@ -154,7 +167,15 @@ steps: fields: description: "Orders API with updated configuration" labels.lifecycle: stable - - name: 005-diff-no-changes + - name: 005-get-event-gateways-updated + run: ["get", "event-gateway", "control-plane", "-o", "json"] + assertions: + - select: "[?name=='{{ .vars.egwRef }}'] | [0]" + expect: + fields: + name: "{{ .vars.egwName }}" + description: "{{ .vars.egwUpdatedDesc }}" + - name: 006-diff-no-changes outputFormat: text parseAs: raw run: @@ -166,7 +187,7 @@ steps: expect: fields: "contains(@, 'No changes detected. Konnect is up to date.')": true - - name: 006-plan-post-sync + - name: 007-plan-post-sync outputFormat: disable run: - plan diff --git a/test/e2e/testdata/declarative/plan/apply/config.yaml b/test/e2e/testdata/declarative/plan/apply/config.yaml index 4b3b4060..c68fafc4 100644 --- a/test/e2e/testdata/declarative/plan/apply/config.yaml +++ b/test/e2e/testdata/declarative/plan/apply/config.yaml @@ -18,3 +18,8 @@ apis: labels: domain: commerce lifecycle: pilot + +event_gateways: + - ref: plan-apply-egw + name: plan-apply-egw + description: "Initial description for plan apply workflow" diff --git a/test/e2e/testdata/declarative/plan/sync/config.yaml b/test/e2e/testdata/declarative/plan/sync/config.yaml index ba290c84..372562bd 100644 --- a/test/e2e/testdata/declarative/plan/sync/config.yaml +++ b/test/e2e/testdata/declarative/plan/sync/config.yaml @@ -24,3 +24,8 @@ apis: labels: domain: legacy lifecycle: sunset + +event_gateways: + - ref: plan-sync-egw + name: plan-sync-egw + description: "Initial description for plan sync workflow" \ No newline at end of file From c33ebb6bf5bb5fd406e7972607456bb36041a3d0 Mon Sep 17 00:00:00 2001 From: Harsha Dixit Date: Fri, 9 Jan 2026 16:07:53 +0530 Subject: [PATCH 18/24] style: changes resource type to event_gateway in code --- apis.yaml | 4 +++ egw.yaml | 10 ++++++ .../event_gateway_control_plane_adapter.go | 7 ++-- internal/declarative/executor/executor.go | 6 ++-- .../planner/egw_control_plane_planner.go | 4 +-- internal/declarative/resources/types.go | 2 +- internal/declarative/state/client.go | 6 ++-- portals.yaml | 35 +++++++++++++++++++ .../control-planes/scenario.yaml | 6 ++-- .../event-gateways/scenario.yaml | 6 ++-- 10 files changed, 68 insertions(+), 18 deletions(-) create mode 100644 apis.yaml create mode 100644 egw.yaml create mode 100644 portals.yaml diff --git a/apis.yaml b/apis.yaml new file mode 100644 index 00000000..4f2cfaaf --- /dev/null +++ b/apis.yaml @@ -0,0 +1,4 @@ +_defaults: + kongctl: + namespace: "apis-comprehensive" +apis: diff --git a/egw.yaml b/egw.yaml new file mode 100644 index 00000000..fc81702b --- /dev/null +++ b/egw.yaml @@ -0,0 +1,10 @@ +event_gateways: +- name: egw-control-plane-2 + kongctl: + protected: true + namespace: poppycorp +- ref: getting-started-event-gateway + name: "My First Event Gateway" + description: "My first event gateway control plane for managing event-driven architectures" + kongctl: + namespace: "default" \ No newline at end of file diff --git a/internal/declarative/executor/event_gateway_control_plane_adapter.go b/internal/declarative/executor/event_gateway_control_plane_adapter.go index b0ae71e9..202fd7e7 100644 --- a/internal/declarative/executor/event_gateway_control_plane_adapter.go +++ b/internal/declarative/executor/event_gateway_control_plane_adapter.go @@ -7,6 +7,7 @@ import ( "github.com/kong/kongctl/internal/declarative/common" "github.com/kong/kongctl/internal/declarative/labels" "github.com/kong/kongctl/internal/declarative/planner" + "github.com/kong/kongctl/internal/declarative/resources" "github.com/kong/kongctl/internal/declarative/state" ) @@ -111,7 +112,7 @@ func (a *EventGatewayControlPlaneControlPlaneAdapter) Update( return resp, nil } -// GetByID gets a event_gateway_control_plane by ID +// GetByID gets a event_gateway by ID func (a *EventGatewayControlPlaneControlPlaneAdapter) GetByID( ctx context.Context, id string, _ *ExecutionContext, ) (ResourceInfo, error) { @@ -125,7 +126,7 @@ func (a *EventGatewayControlPlaneControlPlaneAdapter) GetByID( return &EventGatewayControlPlaneResourceInfo{eventGatewayControlPlane: eventGateway}, nil } -// GetByName gets a event_gateway_control_plane by name +// GetByName gets a event_gateway by name func (a *EventGatewayControlPlaneControlPlaneAdapter) GetByName( _ context.Context, _ string, ) (ResourceInfo, error) { @@ -140,7 +141,7 @@ func (a *EventGatewayControlPlaneControlPlaneAdapter) Delete( } func (a *EventGatewayControlPlaneControlPlaneAdapter) ResourceType() string { - return "event_gateway_control_plane" + return string(resources.ResourceTypeEventGatewayControlPlane) } func (a *EventGatewayControlPlaneControlPlaneAdapter) RequiredFields() []string { diff --git a/internal/declarative/executor/executor.go b/internal/declarative/executor/executor.go index 1f3136f5..316d54a0 100644 --- a/internal/declarative/executor/executor.go +++ b/internal/declarative/executor/executor.go @@ -1423,7 +1423,7 @@ func (e *Executor) createResource(ctx context.Context, change *planner.PlannedCh change.References["portal_id"] = portalRef } return e.portalEmailTemplateExecutor.Create(ctx, *change) - case "event_gateway_control_plane": + case "event_gateway": return e.eventGatewayControlPlaneExecutor.Create(ctx, *change) default: return "", fmt.Errorf("create operation not yet implemented for %s", change.ResourceType) @@ -1636,7 +1636,7 @@ func (e *Executor) updateResource(ctx context.Context, change *planner.PlannedCh } return e.apiVersionExecutor.Update(ctx, *change) // Note: api_publication and api_implementation don't support update - case "event_gateway_control_plane": + case "event_gateway": return e.eventGatewayControlPlaneExecutor.Update(ctx, *change) default: return "", fmt.Errorf("update operation not yet implemented for %s", change.ResourceType) @@ -1766,7 +1766,7 @@ func (e *Executor) deleteResource(ctx context.Context, change *planner.PlannedCh } return e.portalEmailTemplateExecutor.Delete(ctx, *change) // Note: portal_customization is a singleton resource and cannot be deleted - case "event_gateway_control_plane": + case "event_gateway": return e.eventGatewayControlPlaneExecutor.Delete(ctx, *change) default: return fmt.Errorf("delete operation not yet implemented for %s", change.ResourceType) diff --git a/internal/declarative/planner/egw_control_plane_planner.go b/internal/declarative/planner/egw_control_plane_planner.go index 1cb4ecd5..fec63581 100644 --- a/internal/declarative/planner/egw_control_plane_planner.go +++ b/internal/declarative/planner/egw_control_plane_planner.go @@ -110,7 +110,7 @@ func (p *Planner) planEGWControlPlaneChanges( } // Validate protection change - err := p.validateProtectionWithChange("event_gateway_control_plane", desiredEGWCP.Name, isProtected, ActionUpdate, + err := p.validateProtectionWithChange(string(resources.ResourceTypeEventGatewayControlPlane), desiredEGWCP.Name, isProtected, ActionUpdate, protectionChange, needsUpdate) if err != nil { protectionErrors = append(protectionErrors, err) @@ -185,7 +185,7 @@ func (p *Planner) planEGWControlPlaneProtectionChangeWithFields( // Use generic protection change planner config := ProtectionChangeConfig{ - ResourceType: "event_gateway_control_plane", + ResourceType: string(resources.ResourceTypeEventGatewayControlPlane), ResourceName: desired.Name, ResourceRef: desired.GetRef(), ResourceID: current.ID, diff --git a/internal/declarative/resources/types.go b/internal/declarative/resources/types.go index 5bb185ca..bb16b25d 100644 --- a/internal/declarative/resources/types.go +++ b/internal/declarative/resources/types.go @@ -26,7 +26,7 @@ const ( ResourceTypePortalEmailConfig ResourceType = "portal_email_config" ResourceTypePortalEmailTemplate ResourceType = "portal_email_template" ResourceTypeCatalogService ResourceType = "catalog_service" - ResourceTypeEventGatewayControlPlane ResourceType = "event_gateway_control_plane" + ResourceTypeEventGatewayControlPlane ResourceType = "event_gateway" ) const ( diff --git a/internal/declarative/state/client.go b/internal/declarative/state/client.go index e18e7b7a..aa75d507 100644 --- a/internal/declarative/state/client.go +++ b/internal/declarative/state/client.go @@ -3270,7 +3270,7 @@ func (c *Client) CreateEventGatewayControlPlane( resp, err := c.egwControlPlaneAPI.CreateEGWControlPlane(ctx, req) if err != nil { return "", WrapAPIError(err, "create event gateway control plane", &ErrorWrapperOptions{ - ResourceType: "event_gateway_control_plane", + ResourceType: "event_gateway", ResourceName: "", // Adjust based on SDK Namespace: namespace, UseEnhanced: true, @@ -3293,7 +3293,7 @@ func (c *Client) UpdateEventGatewayControlPlane( resp, err := c.egwControlPlaneAPI.UpdateEGWControlPlane(ctx, id, req) if err != nil { return "", WrapAPIError(err, "update event gateway control plane", &ErrorWrapperOptions{ - ResourceType: "event_gateway_control_plane", + ResourceType: "event_gateway", ResourceName: "", // Adjust based on SDK Namespace: namespace, UseEnhanced: true, @@ -3307,7 +3307,7 @@ func (c *Client) GetEventGatewayControlPlaneByID(ctx context.Context, id string) resp, err := c.egwControlPlaneAPI.FetchEGWControlPlane(ctx, id) if err != nil { return nil, WrapAPIError(err, "get event gateway control plane by ID", &ErrorWrapperOptions{ - ResourceType: "event_gateway_control_plane", + ResourceType: "event_gateway", ResourceName: "", // Adjust based on SDK UseEnhanced: true, }) diff --git a/portals.yaml b/portals.yaml new file mode 100644 index 00000000..a11d3a78 --- /dev/null +++ b/portals.yaml @@ -0,0 +1,35 @@ +_defaults: + kongctl: + namespace: poppycorp +application_auth_strategies: +- configs: + key-auth: + key_names: + - apikey + display_name: API Key Auth (dev)2 + labels: + env: development + name: api-key-auth-dev-2 + ref: b6acd7df-f326-42a3-839a-f87e3fe4beba + strategy_type: key_auth +portals: +- authentication_enabled: true + auto_approve_applications: false + auto_approve_developers: false + default_api_visibility: private + default_application_auth_strategy_id: b6acd7df-f326-42a3-839a-f87e3fe4beba + default_page_visibility: private + display_name: Developer Portal + name: kctlportal1 + rbac_enabled: false + ref: 3ef26f1f-98e5-4473-bb06-5f631f1c43fb +- authentication_enabled: true + auto_approve_applications: false + auto_approve_developers: false + default_api_visibility: private + default_application_auth_strategy_id: b6acd7df-f326-42a3-839a-f87e3fe4beba + default_page_visibility: private + display_name: Developer Portal + name: yoloportal2 + rbac_enabled: false + ref: aaaaa diff --git a/test/e2e/scenarios/event-gateway/control-planes/scenario.yaml b/test/e2e/scenarios/event-gateway/control-planes/scenario.yaml index b2171c79..e218ce49 100644 --- a/test/e2e/scenarios/event-gateway/control-planes/scenario.yaml +++ b/test/e2e/scenarios/event-gateway/control-planes/scenario.yaml @@ -36,7 +36,7 @@ steps: applied: 1 failed: 0 status: success - - select: "plan.changes[?resource_type=='event_gateway_control_plane' && resource_ref=='egw-top-level'] | [0]" + - select: "plan.changes[?resource_type=='event_gateway' && resource_ref=='egw-top-level'] | [0]" expect: fields: action: CREATE @@ -77,7 +77,7 @@ steps: applied: 1 failed: 0 status: success - - select: "plan.changes[?resource_type=='event_gateway_control_plane' && resource_ref=='egw-top-level'] | [0]" + - select: "plan.changes[?resource_type=='event_gateway' && resource_ref=='egw-top-level'] | [0]" expect: fields: action: UPDATE @@ -117,7 +117,7 @@ steps: applied: 1 failed: 0 status: success - - select: "plan.changes[?resource_type=='event_gateway_control_plane' && resource_ref=='My Event Gateway CP'] | [0]" + - select: "plan.changes[?resource_type=='event_gateway' && resource_ref=='My Event Gateway CP'] | [0]" expect: fields: action: DELETE diff --git a/test/e2e/scenarios/protected-resources/event-gateways/scenario.yaml b/test/e2e/scenarios/protected-resources/event-gateways/scenario.yaml index 812370e2..8efeb0cc 100644 --- a/test/e2e/scenarios/protected-resources/event-gateways/scenario.yaml +++ b/test/e2e/scenarios/protected-resources/event-gateways/scenario.yaml @@ -38,7 +38,7 @@ steps: # Verify protected Event Gateway created - select: >- - plan.changes[?resource_type=='event_gateway_control_plane' && + plan.changes[?resource_type=='event_gateway' && resource_ref=='egw-protected-top-level'] | [0] expect: fields: @@ -107,7 +107,7 @@ steps: assertions: # Should see UPDATE action for protection change - select: >- - plan.changes[?resource_type=='event_gateway_control_plane' && + plan.changes[?resource_type=='event_gateway' && resource_ref=='egw-protected-top-level'] | [0] expect: fields: @@ -150,7 +150,7 @@ steps: assertions: # Verify DELETE action planned - select: >- - plan.changes[?resource_type=='event_gateway_control_plane' && + plan.changes[?resource_type=='event_gateway' && resource_ref=='My Protected Event Gateway CP'] | [0] expect: fields: From 87c906094646a3c044c1d6b63b36dfe5639b9bb7 Mon Sep 17 00:00:00 2001 From: Harsha Dixit Date: Fri, 9 Jan 2026 16:38:13 +0530 Subject: [PATCH 19/24] feat: support dumping event gateways --- internal/cmd/root/verbs/dump/declarative.go | 78 +++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/internal/cmd/root/verbs/dump/declarative.go b/internal/cmd/root/verbs/dump/declarative.go index 92b5d348..ab492231 100644 --- a/internal/cmd/root/verbs/dump/declarative.go +++ b/internal/cmd/root/verbs/dump/declarative.go @@ -3,6 +3,7 @@ package dump import ( "context" "fmt" + "net/url" "reflect" "sort" "strings" @@ -35,6 +36,7 @@ var declarativeAllowedResources = map[string]struct{}{ "apis": {}, "application_auth_strategies": {}, "control_planes": {}, + "event_gateways": {}, } func newDeclarativeCmd() *cobra.Command { @@ -194,6 +196,16 @@ func runDeclarativeDump(helper cmdpkg.Helper, opts declarativeOptions) error { return err } resourceSet.ControlPlanes = append(resourceSet.ControlPlanes, controlPlanes...) + case "event_gateways": + eventGateways, err := collectDeclarativeEventGateways( + ctx, + sdk.GetEventGatewayControlPlaneAPI(), + requestPageSize, + ) + if err != nil { + return err + } + resourceSet.EventGatewayControlPlanes = append(resourceSet.EventGatewayControlPlanes, eventGateways...) } } @@ -329,6 +341,56 @@ func collectDeclarativeAPIs( return results, nil } +func collectDeclarativeEventGateways( + ctx context.Context, + eventGatewayClient helpers.EGWControlPlaneAPI, + requestPageSize int64, +) ([]declresources.EventGatewayControlPlaneResource, error) { + if eventGatewayClient == nil { + return nil, fmt.Errorf("Event Gateway client is not configured") + } + + var allData []declresources.EventGatewayControlPlaneResource + var pageAfter *string + + for { + req := kkOps.ListEventGatewaysRequest{ + PageSize: Int64(requestPageSize), + } + + if pageAfter != nil { + req.PageAfter = pageAfter + } + + res, err := eventGatewayClient.ListEGWControlPlanes(ctx, req) + if err != nil { + return nil, err + } + + for _, egw := range res.ListEventGatewaysResponse.Data { + allData = append(allData, mapEventGatewayToDeclarativeResource(egw)) + } + + if res.ListEventGatewaysResponse.Meta.Page.Next == nil { + break + } + + u, err := url.Parse(*res.ListEventGatewaysResponse.Meta.Page.Next) + if err != nil { + return nil, err + } + + values := u.Query() + pageAfter = stringPointer(values.Get("page[after]")) + } + + sort.Slice(allData, func(i, j int) bool { + return allData[i].Name < allData[j].Name + }) + + return allData, nil +} + func mapPortalToDeclarativeResource(portal kkComps.ListPortalsResponsePortal) declresources.PortalResource { result := declresources.PortalResource{ CreatePortal: kkComps.CreatePortal{ @@ -399,6 +461,22 @@ func mapAPIToDeclarativeResource(api kkComps.APIResponseSchema) declresources.AP return result } +func mapEventGatewayToDeclarativeResource(egw kkComps.EventGatewayInfo) declresources.EventGatewayControlPlaneResource { + result := declresources.EventGatewayControlPlaneResource{ + CreateGatewayRequest: kkComps.CreateGatewayRequest{ + Name: egw.Name, + Description: egw.Description, + }, + Ref: egw.ID, + } + + if labels := decllabels.GetUserLabels(egw.Labels); len(labels) > 0 { + result.Labels = labels + } + + return result +} + func normalizeAPIResource(api *declresources.APIResource) { if api.Attributes == nil { return From 99de1b0692f8193263f0a7b199c1eba1a666a70a Mon Sep 17 00:00:00 2001 From: Harsha Dixit Date: Fri, 9 Jan 2026 18:55:19 +0530 Subject: [PATCH 20/24] feat: support adopting existing event gateways --- .../products/konnect/adopt/event_gateway.go | 255 ++++++++++++++++++ .../adopt/event_gateway_control_plane_test.go | 184 +++++++++++++ .../konnect/eventgateway/event-gateway.go | 2 +- internal/cmd/root/products/konnect/konnect.go | 6 + internal/cmd/root/verbs/adopt/adopt.go | 8 + internal/cmd/root/verbs/adopt/direct.go | 41 +++ internal/cmd/root/verbs/get/get.go | 2 +- 7 files changed, 496 insertions(+), 2 deletions(-) create mode 100644 internal/cmd/root/products/konnect/adopt/event_gateway.go create mode 100644 internal/cmd/root/products/konnect/adopt/event_gateway_control_plane_test.go diff --git a/internal/cmd/root/products/konnect/adopt/event_gateway.go b/internal/cmd/root/products/konnect/adopt/event_gateway.go new file mode 100644 index 00000000..91d7509e --- /dev/null +++ b/internal/cmd/root/products/konnect/adopt/event_gateway.go @@ -0,0 +1,255 @@ +package adopt + +import ( + "fmt" + "net/url" + "strings" + + kk "github.com/Kong/sdk-konnect-go" + kkComps "github.com/Kong/sdk-konnect-go/models/components" + kkOps "github.com/Kong/sdk-konnect-go/models/operations" + cmdpkg "github.com/kong/kongctl/internal/cmd" + cmdCommon "github.com/kong/kongctl/internal/cmd/common" + "github.com/kong/kongctl/internal/cmd/root/products/konnect/common" + "github.com/kong/kongctl/internal/cmd/root/verbs" + "github.com/kong/kongctl/internal/config" + "github.com/kong/kongctl/internal/declarative/labels" + "github.com/kong/kongctl/internal/declarative/validator" + "github.com/kong/kongctl/internal/konnect/helpers" + "github.com/kong/kongctl/internal/util" + "github.com/segmentio/cli" + "github.com/spf13/cobra" +) + +func NewEventGatewayControlPlaneCmd( + verb verbs.VerbValue, + baseCmd *cobra.Command, + addParentFlags func(verbs.VerbValue, *cobra.Command), + parentPreRun func(*cobra.Command, []string) error, +) (*cobra.Command, error) { + cmd := baseCmd + if cmd == nil { + cmd = &cobra.Command{} + } + + cmd.Use = "event-gateway " + cmd.Short = "Adopt an existing Konnect Event Gateway Control Plane into namespace management" + cmd.Long = "Apply the KONGCTL-namespace label to an existing Konnect Event Gateway Control Plane " + + "that is not currently managed by kongctl." + cmd.Args = func(_ *cobra.Command, args []string) error { + if len(args) != 1 { + return fmt.Errorf("exactly one Event Gateway Control Plane identifier (name or ID) is required") + } + if trimmed := strings.TrimSpace(args[0]); trimmed == "" { + return fmt.Errorf("Event Gateway Control Plane identifier cannot be empty") + } + return nil + } + + if addParentFlags != nil { + addParentFlags(verb, cmd) + } + + if parentPreRun != nil { + cmd.PreRunE = parentPreRun + } + + cmd.Flags().String(NamespaceFlagName, "", "Namespace label to apply to the resource") + if err := cmd.MarkFlagRequired(NamespaceFlagName); err != nil { + return nil, err + } + + cmd.RunE = func(cobraCmd *cobra.Command, args []string) error { + helper := cmdpkg.BuildHelper(cobraCmd, args) + + namespace, err := cobraCmd.Flags().GetString(NamespaceFlagName) + if err != nil { + return err + } + + nsValidator := validator.NewNamespaceValidator() + if err := nsValidator.ValidateNamespace(namespace); err != nil { + return &cmdpkg.ConfigurationError{Err: err} + } + + outType, err := helper.GetOutputFormat() + if err != nil { + return err + } + + cfg, err := helper.GetConfig() + if err != nil { + return err + } + + logger, err := helper.GetLogger() + if err != nil { + return err + } + + sdk, err := helper.GetKonnectSDK(cfg, logger) + if err != nil { + return err + } + + result, err := adoptEventGatewayControlPlane(helper, sdk.GetEventGatewayControlPlaneAPI(), cfg, namespace, strings.TrimSpace(args[0])) + if err != nil { + return err + } + + streams := helper.GetStreams() + if outType == cmdCommon.TEXT { + name := result.Name + if name == "" { + name = result.ID + } + fmt.Fprintf(streams.Out, "Adopted Event Gateway Control Plane %q (%s) into namespace %q\n", name, result.ID, result.Namespace) + return nil + } + + printer, err := cli.Format(outType.String(), streams.Out) + if err != nil { + return err + } + defer printer.Flush() + printer.Print(result) + return nil + } + + return cmd, nil +} + +func adoptEventGatewayControlPlane( + helper cmdpkg.Helper, + egwClient helpers.EGWControlPlaneAPI, + cfg config.Hook, + namespace string, + identifier string, +) (*adoptResult, error) { + egw, err := resolveEventGatewayControlPlane(helper, egwClient, cfg, identifier) + if err != nil { + return nil, err + } + + if existing := egw.Labels; existing != nil { + if currentNamespace, ok := existing[labels.NamespaceKey]; ok && currentNamespace != "" { + return nil, &cmdpkg.ConfigurationError{ + Err: fmt.Errorf("Event Gateway Control Plane %q already has namespace label %q", egw.Name, currentNamespace), + } + } + } + + updateReq := kkComps.UpdateGatewayRequest{ + Name: &egw.Name, + Description: egw.Description, + Labels: stringLabelMap(egw.Labels, namespace), + } + + ctx := ensureContext(helper.GetContext()) + + resp, err := egwClient.UpdateEGWControlPlane(ctx, egw.ID, updateReq) + if err != nil { + attrs := cmdpkg.TryConvertErrorToAttrs(err) + return nil, cmdpkg.PrepareExecutionError("failed to update Event Gateway Control Plane", err, helper.GetCmd(), attrs...) + } + + updated := resp.EventGatewayInfo + if updated == nil { + return nil, cmdpkg.PrepareExecutionErrorMsg(helper, "Update Event Gateway Control Plane failed.") + } + + ns := namespace + if updated.Labels != nil { + if v, ok := updated.Labels[labels.NamespaceKey]; ok && v != "" { + ns = v + } + } + + return &adoptResult{ + ResourceType: "event_gateway", + ID: updated.ID, + Name: updated.Name, + Namespace: ns, + }, nil +} + +func resolveEventGatewayControlPlane( + helper cmdpkg.Helper, + egwClient helpers.EGWControlPlaneAPI, + cfg config.Hook, + identifier string, +) (*kkComps.EventGatewayInfo, error) { + ctx := ensureContext(helper.GetContext()) + + if util.IsValidUUID(identifier) { + res, err := egwClient.FetchEGWControlPlane(ctx, identifier) + if err != nil { + attrs := cmdpkg.TryConvertErrorToAttrs(err) + return nil, cmdpkg.PrepareExecutionError("failed to retrieve Event Gateway Control Plane", err, helper.GetCmd(), attrs...) + } + egw := res.EventGatewayInfo + if egw == nil { + return nil, cmdpkg.PrepareExecutionErrorMsg(helper, fmt.Sprintf("Event Gateway Control Plane %s not found", identifier)) + } + return egw, nil + } + + pageSize := cfg.GetInt(common.RequestPageSizeConfigPath) + if pageSize < 1 { + pageSize = common.DefaultRequestPageSize + } + + var pageAfter *string + for { + req := kkOps.ListEventGatewaysRequest{ + PageSize: kk.Int64(int64(pageSize)), + } + + if pageAfter != nil { + req.PageAfter = pageAfter + } + + res, err := egwClient.ListEGWControlPlanes(ctx, req) + if err != nil { + attrs := cmdpkg.TryConvertErrorToAttrs(err) + return nil, cmdpkg.PrepareExecutionError("failed to list Event Gateway Control Planes", err, helper.GetCmd(), attrs...) + } + + list := res.ListEventGatewaysResponse + if list == nil || len(list.Data) == 0 { + break + } + + for _, egw := range list.Data { + if egw.Name == identifier { + egwCopy := egw + return &egwCopy, nil + } + } + + if list.Meta.Page.Next == nil { + break + } + + // Page.Next contains a full URL; parse it and extract the cursor from + // the `page[after]` query parameter so we can pass it to the next request. + u, err := url.Parse(*list.Meta.Page.Next) + if err != nil { + attrs := cmdpkg.TryConvertErrorToAttrs(err) + return nil, cmdpkg.PrepareExecutionError("failed to parse pagination URL", err, helper.GetCmd(), attrs...) + } + + values := u.Query() + after := values.Get("page[after]") + if after == "" { + break + } + // allocate a new string so the pointer remains valid across iterations + tmp := after + pageAfter = &tmp + } + + return nil, &cmdpkg.ConfigurationError{ + Err: fmt.Errorf("Event Gateway Control Plane %q not found", identifier), + } +} diff --git a/internal/cmd/root/products/konnect/adopt/event_gateway_control_plane_test.go b/internal/cmd/root/products/konnect/adopt/event_gateway_control_plane_test.go new file mode 100644 index 00000000..cfccc9a4 --- /dev/null +++ b/internal/cmd/root/products/konnect/adopt/event_gateway_control_plane_test.go @@ -0,0 +1,184 @@ +package adopt + +import ( + "context" + "testing" + + kkComps "github.com/Kong/sdk-konnect-go/models/components" + kkOps "github.com/Kong/sdk-konnect-go/models/operations" + "github.com/kong/kongctl/internal/cmd" + "github.com/kong/kongctl/internal/config" + "github.com/kong/kongctl/internal/declarative/labels" + helpers "github.com/kong/kongctl/internal/konnect/helpers" + "github.com/stretchr/testify/assert" +) + +type egwControlPlaneAPIStub struct { + t *testing.T + fetchResponse *kkComps.EventGatewayInfo + listResponse []kkComps.EventGatewayInfo + lastUpdate kkComps.UpdateGatewayRequest + updateCalls int +} + +func (e *egwControlPlaneAPIStub) ListEGWControlPlanes( + context.Context, + kkOps.ListEventGatewaysRequest, + ...kkOps.Option, +) (*kkOps.ListEventGatewaysResponse, error) { + resp := &kkOps.ListEventGatewaysResponse{ + ListEventGatewaysResponse: &kkComps.ListEventGatewaysResponse{ + Data: e.listResponse, + Meta: kkComps.CursorMeta{}, + }, + } + return resp, nil +} + +func (e *egwControlPlaneAPIStub) FetchEGWControlPlane(_ context.Context, id string, _ ...kkOps.Option) (*kkOps.GetEventGatewayResponse, error) { + if id != e.fetchResponse.ID { + e.t.Fatalf("unexpected Event Gateway Control Plane id: %s", id) + } + return &kkOps.GetEventGatewayResponse{EventGatewayInfo: e.fetchResponse}, nil +} + +func (e *egwControlPlaneAPIStub) CreateEGWControlPlane( + context.Context, + kkComps.CreateGatewayRequest, + ...kkOps.Option, +) (*kkOps.CreateEventGatewayResponse, error) { + e.t.Fatalf("unexpected CreateEGWControlPlane call") + return nil, nil +} + +func (e *egwControlPlaneAPIStub) UpdateEGWControlPlane( + _ context.Context, + id string, + req kkComps.UpdateGatewayRequest, + _ ...kkOps.Option, +) (*kkOps.UpdateEventGatewayResponse, error) { + if id != e.fetchResponse.ID { + e.t.Fatalf("unexpected Event Gateway Control Plane id: %s", id) + } + e.updateCalls++ + e.lastUpdate = req + + labels := make(map[string]string) + if req.Labels != nil { + for k, v := range req.Labels { + labels[k] = v + } + } + + resp := &kkOps.UpdateEventGatewayResponse{ + EventGatewayInfo: &kkComps.EventGatewayInfo{ + ID: e.fetchResponse.ID, + Name: e.fetchResponse.Name, + Labels: labels, + }, + } + return resp, nil +} + +func (e *egwControlPlaneAPIStub) DeleteEGWControlPlane(context.Context, string, ...kkOps.Option) (*kkOps.DeleteEventGatewayResponse, error) { + e.t.Fatalf("unexpected DeleteEGWControlPlane call") + return nil, nil +} + +func TestAdoptEventGatewayControlPlaneByName(t *testing.T) { + helper := new(cmd.MockHelper) + helper.EXPECT().GetContext().Return(context.Background()) + + egw := &egwControlPlaneAPIStub{ + t: t, + fetchResponse: &kkComps.EventGatewayInfo{ + ID: "f3b8c0d1-9a2e-4f12-8d3c-1e4a5b6c7d8e", + Name: "production-egw", + Labels: map[string]string{"env": "prod"}, + }, + listResponse: []kkComps.EventGatewayInfo{ + { + ID: "f3b8c0d1-9a2e-4f12-8d3c-1e4a5b6c7d8e", + Name: "production-egw", + Labels: map[string]string{"env": "prod"}, + }, + }, + } + + cfg := stubConfig{pageSize: 50} + + result, err := adoptEventGatewayControlPlane(helper, egw, cfg, "team-events", "production-egw") + assert.NoError(t, err) + assert.NotNil(t, result) + assert.Equal(t, "event_gateway", result.ResourceType) + assert.Equal(t, "f3b8c0d1-9a2e-4f12-8d3c-1e4a5b6c7d8e", result.ID) + assert.Equal(t, "production-egw", result.Name) + assert.Equal(t, "team-events", result.Namespace) + + assert.Equal(t, 1, egw.updateCalls) + assert.Equal(t, "prod", egw.lastUpdate.Labels["env"]) + assert.Equal(t, "team-events", egw.lastUpdate.Labels[labels.NamespaceKey]) + + helper.AssertExpectations(t) +} + +func TestAdoptEventGatewayControlPlaneById(t *testing.T) { + helper := new(cmd.MockHelper) + helper.EXPECT().GetContext().Return(context.Background()) + + egw := &egwControlPlaneAPIStub{ + t: t, + fetchResponse: &kkComps.EventGatewayInfo{ + ID: "f3b8c0d1-9a2e-4f12-8d3c-1e4a5b6c7d8e", + Name: "production-egw", + Labels: map[string]string{"env": "prod"}, + }, + } + + cfg := stubConfig{pageSize: 50} + + result, err := adoptEventGatewayControlPlane(helper, egw, cfg, "team-events", "f3b8c0d1-9a2e-4f12-8d3c-1e4a5b6c7d8e") + assert.NoError(t, err) + assert.NotNil(t, result) + assert.Equal(t, "event_gateway", result.ResourceType) + assert.Equal(t, "f3b8c0d1-9a2e-4f12-8d3c-1e4a5b6c7d8e", result.ID) + assert.Equal(t, "production-egw", result.Name) + assert.Equal(t, "team-events", result.Namespace) + + assert.Equal(t, 1, egw.updateCalls) + assert.Equal(t, "prod", egw.lastUpdate.Labels["env"]) + assert.Equal(t, "team-events", egw.lastUpdate.Labels[labels.NamespaceKey]) + + helper.AssertExpectations(t) +} + +func TestAdoptEventGatewayControlPlaneRejectsExistingNamespace(t *testing.T) { + helper := new(cmd.MockHelper) + helper.EXPECT().GetContext().Return(context.Background()) + + egw := &egwControlPlaneAPIStub{ + t: t, + listResponse: []kkComps.EventGatewayInfo{ + { + ID: "f3b8c0d1-9a2e-4f12-8d3c-1e4a5b6c7d8e", + Name: "production-egw", + Labels: map[string]string{labels.NamespaceKey: "existing-team"}, + }, + }, + } + + cfg := stubConfig{pageSize: 50} + + _, err := adoptEventGatewayControlPlane(helper, egw, cfg, "team-events", "production-egw") + assert.Error(t, err) + var cfgErr *cmd.ConfigurationError + assert.ErrorAs(t, err, &cfgErr) + assert.Equal(t, 0, egw.updateCalls) + + helper.AssertExpectations(t) +} + +var ( + _ helpers.EGWControlPlaneAPI = (*egwControlPlaneAPIStub)(nil) + _ config.Hook = stubConfig{} +) diff --git a/internal/cmd/root/products/konnect/eventgateway/event-gateway.go b/internal/cmd/root/products/konnect/eventgateway/event-gateway.go index 1db4605d..edc9a323 100644 --- a/internal/cmd/root/products/konnect/eventgateway/event-gateway.go +++ b/internal/cmd/root/products/konnect/eventgateway/event-gateway.go @@ -24,7 +24,7 @@ func NewEventGatewayCmd(verb verbs.VerbValue, Use: eventGatewayUse, Short: eventGatewayShort, Long: eventGatewayLong, - Aliases: []string{"egw", "EGW"}, + Aliases: []string{"egw", "EGW", "event-gateways"}, } c, e := controlplane.NewEventGatewayControlPlaneCmd(verb, addParentFlags, parentPreRun) diff --git a/internal/cmd/root/products/konnect/konnect.go b/internal/cmd/root/products/konnect/konnect.go index 640da090..25916142 100644 --- a/internal/cmd/root/products/konnect/konnect.go +++ b/internal/cmd/root/products/konnect/konnect.go @@ -187,6 +187,12 @@ func NewKonnectCmd(verb verbs.VerbValue) (*cobra.Command, error) { } cmd.AddCommand(authStrategyCmd) + eventGatewayCmd, err := adopt.NewEventGatewayControlPlaneCmd(verb, &cobra.Command{}, addFlags, preRunE) + if err != nil { + return nil, err + } + cmd.AddCommand(eventGatewayCmd) + addFlags(verb, cmd) return cmd, nil case verbs.Add, verbs.Get, verbs.Create, verbs.Dump, verbs.Update, diff --git a/internal/cmd/root/verbs/adopt/adopt.go b/internal/cmd/root/verbs/adopt/adopt.go index a24c618b..bfe0f37e 100644 --- a/internal/cmd/root/verbs/adopt/adopt.go +++ b/internal/cmd/root/verbs/adopt/adopt.go @@ -32,6 +32,8 @@ var ( %[1]s adopt control-plane 22cd8a0b-72e7-4212-9099-0764f8e9c5ac --namespace platform # Adopt an API explicitly via the konnect product %[1]s adopt konnect api my-api --namespace team-alpha + # Adopt an Event Gateway explicitly via the konnect product + %[1]s adopt konnect event-gateway my-egw --namespace team-alpha `, meta.CLIName))) ) @@ -95,5 +97,11 @@ Setting this value overrides tokens obtained from the login command. } cmd.AddCommand(authStrategyCmd) + eventGatewayCmd, err := NewDirectEventGatewayCmd() + if err != nil { + return nil, err + } + cmd.AddCommand(eventGatewayCmd) + return cmd, nil } diff --git a/internal/cmd/root/verbs/adopt/direct.go b/internal/cmd/root/verbs/adopt/direct.go index af0280d6..a224f3bf 100644 --- a/internal/cmd/root/verbs/adopt/direct.go +++ b/internal/cmd/root/verbs/adopt/direct.go @@ -139,6 +139,47 @@ Setting this value overrides tokens obtained from the login command. return cmd, nil } +func NewDirectEventGatewayCmd() (*cobra.Command, error) { + addFlags := func(_ verbs.VerbValue, cmd *cobra.Command) { + cmd.Flags().String(common.BaseURLFlagName, "", + fmt.Sprintf(`Base URL for Konnect API requests. +- Config path: [ %s ] +- Default : [ %s ]`, + common.BaseURLConfigPath, common.BaseURLDefault)) + + cmd.Flags().String(common.RegionFlagName, "", + fmt.Sprintf(`Konnect region identifier (for example "eu"). Used to construct the base URL when --%s is not provided. +- Config path: [ %s ]`, + common.BaseURLFlagName, common.RegionConfigPath)) + + cmd.Flags().String(common.PATFlagName, "", + fmt.Sprintf(`Konnect Personal Access Token (PAT) used to authenticate the CLI. +Setting this value overrides tokens obtained from the login command. +- Config path: [ %s ]`, + common.PATConfigPath)) + } + + preRunE := func(c *cobra.Command, args []string) error { + ctx := c.Context() + if ctx == nil { + ctx = context.Background() + } + ctx = context.WithValue(ctx, products.Product, konnect.Product) + ctx = context.WithValue(ctx, helpers.SDKAPIFactoryKey, common.GetSDKFactory()) + c.SetContext(ctx) + return bindKonnectFlags(c, args) + } + + cmd, err := konnectadopt.NewEventGatewayControlPlaneCmd(Verb, &cobra.Command{}, addFlags, preRunE) + if err != nil { + return nil, err + } + + cmd.Example = ` # Adopt an Event Gateway by name + kongctl adopt event-gateway my-egw --namespace team-alpha` + + return cmd, nil +} func NewDirectAuthStrategyCmd() (*cobra.Command, error) { addFlags := func(_ verbs.VerbValue, cmd *cobra.Command) { diff --git a/internal/cmd/root/verbs/get/get.go b/internal/cmd/root/verbs/get/get.go index c9f2a9d3..003cb328 100644 --- a/internal/cmd/root/verbs/get/get.go +++ b/internal/cmd/root/verbs/get/get.go @@ -44,7 +44,7 @@ Output can be formatted in multiple ways to aid in further processing.`)) # Retrieve Konnect control planes (explicit) %[1]s get konnect gateway control-planes # Retrieve Konnect Event Gateway control planes - %[1]s get eventgatewaycontrolplanes + %[1]s get event-gateway control-planes `, meta.CLIName))) ) From 3f762d5e022872613c1f1b30659ecc61e6b96312 Mon Sep 17 00:00:00 2001 From: Harsha Dixit Date: Fri, 9 Jan 2026 19:19:15 +0530 Subject: [PATCH 21/24] style: remove unused files and fix lint --- apis.yaml | 4 -- egw.yaml | 10 ---- .../products/konnect/adopt/event_gateway.go | 52 +++++++++++++++---- .../adopt/event_gateway_control_plane_test.go | 12 ++++- .../control-plane/getControlPlane.go | 20 +++---- internal/cmd/root/verbs/dump/declarative.go | 2 +- .../planner/egw_control_plane_planner.go | 10 +++- portals.yaml | 35 ------------- 8 files changed, 67 insertions(+), 78 deletions(-) delete mode 100644 apis.yaml delete mode 100644 egw.yaml delete mode 100644 portals.yaml diff --git a/apis.yaml b/apis.yaml deleted file mode 100644 index 4f2cfaaf..00000000 --- a/apis.yaml +++ /dev/null @@ -1,4 +0,0 @@ -_defaults: - kongctl: - namespace: "apis-comprehensive" -apis: diff --git a/egw.yaml b/egw.yaml deleted file mode 100644 index fc81702b..00000000 --- a/egw.yaml +++ /dev/null @@ -1,10 +0,0 @@ -event_gateways: -- name: egw-control-plane-2 - kongctl: - protected: true - namespace: poppycorp -- ref: getting-started-event-gateway - name: "My First Event Gateway" - description: "My first event gateway control plane for managing event-driven architectures" - kongctl: - namespace: "default" \ No newline at end of file diff --git a/internal/cmd/root/products/konnect/adopt/event_gateway.go b/internal/cmd/root/products/konnect/adopt/event_gateway.go index 91d7509e..95d7f548 100644 --- a/internal/cmd/root/products/konnect/adopt/event_gateway.go +++ b/internal/cmd/root/products/konnect/adopt/event_gateway.go @@ -38,10 +38,10 @@ func NewEventGatewayControlPlaneCmd( "that is not currently managed by kongctl." cmd.Args = func(_ *cobra.Command, args []string) error { if len(args) != 1 { - return fmt.Errorf("exactly one Event Gateway Control Plane identifier (name or ID) is required") + return fmt.Errorf("exactly one event gateway control plane identifier (name or ID) is required") } if trimmed := strings.TrimSpace(args[0]); trimmed == "" { - return fmt.Errorf("Event Gateway Control Plane identifier cannot be empty") + return fmt.Errorf("event gateway control plane identifier cannot be empty") } return nil } @@ -92,7 +92,13 @@ func NewEventGatewayControlPlaneCmd( return err } - result, err := adoptEventGatewayControlPlane(helper, sdk.GetEventGatewayControlPlaneAPI(), cfg, namespace, strings.TrimSpace(args[0])) + result, err := adoptEventGatewayControlPlane( + helper, + sdk.GetEventGatewayControlPlaneAPI(), + cfg, + namespace, + strings.TrimSpace(args[0]), + ) if err != nil { return err } @@ -103,7 +109,13 @@ func NewEventGatewayControlPlaneCmd( if name == "" { name = result.ID } - fmt.Fprintf(streams.Out, "Adopted Event Gateway Control Plane %q (%s) into namespace %q\n", name, result.ID, result.Namespace) + fmt.Fprintf( + streams.Out, + "Adopted Event Gateway Control Plane %q (%s) into namespace %q\n", + name, + result.ID, + result.Namespace, + ) return nil } @@ -134,7 +146,7 @@ func adoptEventGatewayControlPlane( if existing := egw.Labels; existing != nil { if currentNamespace, ok := existing[labels.NamespaceKey]; ok && currentNamespace != "" { return nil, &cmdpkg.ConfigurationError{ - Err: fmt.Errorf("Event Gateway Control Plane %q already has namespace label %q", egw.Name, currentNamespace), + Err: fmt.Errorf("event gateway control plane %q already has namespace label %q", egw.Name, currentNamespace), } } } @@ -150,12 +162,17 @@ func adoptEventGatewayControlPlane( resp, err := egwClient.UpdateEGWControlPlane(ctx, egw.ID, updateReq) if err != nil { attrs := cmdpkg.TryConvertErrorToAttrs(err) - return nil, cmdpkg.PrepareExecutionError("failed to update Event Gateway Control Plane", err, helper.GetCmd(), attrs...) + return nil, cmdpkg.PrepareExecutionError( + "failed to update Event Gateway Control Plane", + err, + helper.GetCmd(), + attrs..., + ) } updated := resp.EventGatewayInfo if updated == nil { - return nil, cmdpkg.PrepareExecutionErrorMsg(helper, "Update Event Gateway Control Plane failed.") + return nil, cmdpkg.PrepareExecutionErrorMsg(helper, "update Event Gateway Control Plane failed") } ns := namespace @@ -185,11 +202,19 @@ func resolveEventGatewayControlPlane( res, err := egwClient.FetchEGWControlPlane(ctx, identifier) if err != nil { attrs := cmdpkg.TryConvertErrorToAttrs(err) - return nil, cmdpkg.PrepareExecutionError("failed to retrieve Event Gateway Control Plane", err, helper.GetCmd(), attrs...) + return nil, cmdpkg.PrepareExecutionError( + "failed to retrieve Event Gateway Control Plane", + err, + helper.GetCmd(), + attrs..., + ) } egw := res.EventGatewayInfo if egw == nil { - return nil, cmdpkg.PrepareExecutionErrorMsg(helper, fmt.Sprintf("Event Gateway Control Plane %s not found", identifier)) + return nil, cmdpkg.PrepareExecutionErrorMsg( + helper, + fmt.Sprintf("event gateway control plane %s not found", identifier), + ) } return egw, nil } @@ -212,7 +237,12 @@ func resolveEventGatewayControlPlane( res, err := egwClient.ListEGWControlPlanes(ctx, req) if err != nil { attrs := cmdpkg.TryConvertErrorToAttrs(err) - return nil, cmdpkg.PrepareExecutionError("failed to list Event Gateway Control Planes", err, helper.GetCmd(), attrs...) + return nil, cmdpkg.PrepareExecutionError( + "failed to list Event Gateway Control Planes", + err, + helper.GetCmd(), + attrs..., + ) } list := res.ListEventGatewaysResponse @@ -250,6 +280,6 @@ func resolveEventGatewayControlPlane( } return nil, &cmdpkg.ConfigurationError{ - Err: fmt.Errorf("Event Gateway Control Plane %q not found", identifier), + Err: fmt.Errorf("event gateway control plane %q not found", identifier), } } diff --git a/internal/cmd/root/products/konnect/adopt/event_gateway_control_plane_test.go b/internal/cmd/root/products/konnect/adopt/event_gateway_control_plane_test.go index cfccc9a4..20df25e1 100644 --- a/internal/cmd/root/products/konnect/adopt/event_gateway_control_plane_test.go +++ b/internal/cmd/root/products/konnect/adopt/event_gateway_control_plane_test.go @@ -35,7 +35,11 @@ func (e *egwControlPlaneAPIStub) ListEGWControlPlanes( return resp, nil } -func (e *egwControlPlaneAPIStub) FetchEGWControlPlane(_ context.Context, id string, _ ...kkOps.Option) (*kkOps.GetEventGatewayResponse, error) { +func (e *egwControlPlaneAPIStub) FetchEGWControlPlane( + _ context.Context, + id string, + _ ...kkOps.Option, +) (*kkOps.GetEventGatewayResponse, error) { if id != e.fetchResponse.ID { e.t.Fatalf("unexpected Event Gateway Control Plane id: %s", id) } @@ -80,7 +84,11 @@ func (e *egwControlPlaneAPIStub) UpdateEGWControlPlane( return resp, nil } -func (e *egwControlPlaneAPIStub) DeleteEGWControlPlane(context.Context, string, ...kkOps.Option) (*kkOps.DeleteEventGatewayResponse, error) { +func (e *egwControlPlaneAPIStub) DeleteEGWControlPlane( + context.Context, + string, + ...kkOps.Option, +) (*kkOps.DeleteEventGatewayResponse, error) { e.t.Fatalf("unexpected DeleteEGWControlPlane call") return nil, nil } diff --git a/internal/cmd/root/products/konnect/eventgateway/control-plane/getControlPlane.go b/internal/cmd/root/products/konnect/eventgateway/control-plane/getControlPlane.go index ec969f51..a689965a 100644 --- a/internal/cmd/root/products/konnect/eventgateway/control-plane/getControlPlane.go +++ b/internal/cmd/root/products/konnect/eventgateway/control-plane/getControlPlane.go @@ -204,19 +204,13 @@ func (c *getEventGatewayControlPlaneCmd) runE(cobraCmd *cobra.Command, args []st return e } - interactive, e := helper.IsInteractive() + var printer cli.PrintFlusher + + printer, e = cli.Format(outType.String(), helper.GetStreams().Out) if e != nil { return e } - - var printer cli.PrintFlusher - if !interactive { - printer, e = cli.Format(outType.String(), helper.GetStreams().Out) - if e != nil { - return e - } - defer printer.Flush() - } + defer printer.Flush() cfg, e := helper.GetConfig() if e != nil { @@ -244,7 +238,7 @@ func (c *getEventGatewayControlPlaneCmd) runE(cobraCmd *cobra.Command, args []st return e } return tableview.RenderForFormat( - interactive, + false, outType, printer, helper.GetStreams(), @@ -268,7 +262,7 @@ func (c *getEventGatewayControlPlaneCmd) runE(cobraCmd *cobra.Command, args []st } return tableview.RenderForFormat( - interactive, + false, outType, printer, helper.GetStreams(), @@ -294,7 +288,7 @@ func (c *getEventGatewayControlPlaneCmd) runE(cobraCmd *cobra.Command, args []st return renderEventGatewayControlPlaneList( helper, helper.GetCmd().Name(), - interactive, + false, outType, printer, eventGatewayControlPlanes, diff --git a/internal/cmd/root/verbs/dump/declarative.go b/internal/cmd/root/verbs/dump/declarative.go index ab492231..d48b5ef1 100644 --- a/internal/cmd/root/verbs/dump/declarative.go +++ b/internal/cmd/root/verbs/dump/declarative.go @@ -347,7 +347,7 @@ func collectDeclarativeEventGateways( requestPageSize int64, ) ([]declresources.EventGatewayControlPlaneResource, error) { if eventGatewayClient == nil { - return nil, fmt.Errorf("Event Gateway client is not configured") + return nil, fmt.Errorf("event gateway client is not configured") } var allData []declresources.EventGatewayControlPlaneResource diff --git a/internal/declarative/planner/egw_control_plane_planner.go b/internal/declarative/planner/egw_control_plane_planner.go index fec63581..703f9766 100644 --- a/internal/declarative/planner/egw_control_plane_planner.go +++ b/internal/declarative/planner/egw_control_plane_planner.go @@ -110,8 +110,14 @@ func (p *Planner) planEGWControlPlaneChanges( } // Validate protection change - err := p.validateProtectionWithChange(string(resources.ResourceTypeEventGatewayControlPlane), desiredEGWCP.Name, isProtected, ActionUpdate, - protectionChange, needsUpdate) + err := p.validateProtectionWithChange( + string(resources.ResourceTypeEventGatewayControlPlane), + desiredEGWCP.Name, + isProtected, + ActionUpdate, + protectionChange, + needsUpdate, + ) if err != nil { protectionErrors = append(protectionErrors, err) } else { diff --git a/portals.yaml b/portals.yaml deleted file mode 100644 index a11d3a78..00000000 --- a/portals.yaml +++ /dev/null @@ -1,35 +0,0 @@ -_defaults: - kongctl: - namespace: poppycorp -application_auth_strategies: -- configs: - key-auth: - key_names: - - apikey - display_name: API Key Auth (dev)2 - labels: - env: development - name: api-key-auth-dev-2 - ref: b6acd7df-f326-42a3-839a-f87e3fe4beba - strategy_type: key_auth -portals: -- authentication_enabled: true - auto_approve_applications: false - auto_approve_developers: false - default_api_visibility: private - default_application_auth_strategy_id: b6acd7df-f326-42a3-839a-f87e3fe4beba - default_page_visibility: private - display_name: Developer Portal - name: kctlportal1 - rbac_enabled: false - ref: 3ef26f1f-98e5-4473-bb06-5f631f1c43fb -- authentication_enabled: true - auto_approve_applications: false - auto_approve_developers: false - default_api_visibility: private - default_application_auth_strategy_id: b6acd7df-f326-42a3-839a-f87e3fe4beba - default_page_visibility: private - display_name: Developer Portal - name: yoloportal2 - rbac_enabled: false - ref: aaaaa From 1515fce233e05ec76a5fe4500dcf88eb03948cf9 Mon Sep 17 00:00:00 2001 From: Harsha Dixit Date: Mon, 12 Jan 2026 22:47:17 +0530 Subject: [PATCH 22/24] refactor: change imperative get command to drop `control-planes` for event-gateway --- .../control-plane/controlplane.go | 54 ------------------- .../konnect/eventgateway/event-gateway.go | 20 +++---- .../getControlPlane.go => getEventGateway.go} | 34 ++++++------ internal/cmd/root/verbs/get/eventgateway.go | 4 +- internal/cmd/root/verbs/get/get.go | 4 +- .../control-planes/scenario.yaml | 6 +-- .../plan/apply-workflow/scenario.yaml | 4 +- .../plan/sync-workflow/scenario.yaml | 4 +- .../event-gateways/scenario.yaml | 8 +-- 9 files changed, 42 insertions(+), 96 deletions(-) delete mode 100644 internal/cmd/root/products/konnect/eventgateway/control-plane/controlplane.go rename internal/cmd/root/products/konnect/eventgateway/{control-plane/getControlPlane.go => getEventGateway.go} (89%) diff --git a/internal/cmd/root/products/konnect/eventgateway/control-plane/controlplane.go b/internal/cmd/root/products/konnect/eventgateway/control-plane/controlplane.go deleted file mode 100644 index 2fa32796..00000000 --- a/internal/cmd/root/products/konnect/eventgateway/control-plane/controlplane.go +++ /dev/null @@ -1,54 +0,0 @@ -package controlplane - -import ( - "fmt" - - "github.com/kong/kongctl/internal/cmd/root/verbs" - "github.com/kong/kongctl/internal/meta" - "github.com/kong/kongctl/internal/util/i18n" - "github.com/kong/kongctl/internal/util/normalizers" - "github.com/spf13/cobra" -) - -const ( - CommandName = "control-plane" -) - -var ( - eventGatewayControlPlaneUse = CommandName - eventGatewayControlPlaneShort = i18n.T("root.products.konnect.event-gateway.controlPlaneShort", - "Manage Konnect event gateway control plane resources") - eventGatewayControlPlaneLong = normalizers.LongDesc(i18n.T("root.products.konnect.event-gateway.controlPlaneLong", - `The event gateway control plane command allows you to work with Konnect event gateway control plane resources.`)) - eventGatewayControlPlaneExample = normalizers.Examples( - i18n.T("root.products.konnect.event-gateway.controlPlaneExamples", - fmt.Sprintf(` -# List all the Konnect event gateway control planes for the organization -%[1]s get event-gateway control-planes -# Get a specific Konnect event gateway control plane -%[1]s get event-gateway control-plane -`, meta.CLIName))) -) - -func NewEventGatewayControlPlaneCmd(verb verbs.VerbValue, - addParentFlags func(verbs.VerbValue, *cobra.Command), - parentPreRun func(*cobra.Command, []string) error, -) (*cobra.Command, error) { - baseCmd := cobra.Command{ - Use: eventGatewayControlPlaneUse, - Short: eventGatewayControlPlaneShort, - Long: eventGatewayControlPlaneLong, - Example: eventGatewayControlPlaneExample, - Aliases: []string{"control-planes", "cp", "cps", "CP", "CPS"}, - } - - switch verb { - case verbs.Get: - return newGetEventGatewayControlPlaneCmd(verb, &baseCmd, addParentFlags, parentPreRun).Command, nil - case verbs.Create, verbs.Add, verbs.Apply, verbs.Dump, verbs.Update, verbs.Delete, verbs.List, verbs.Help, verbs.Login, - verbs.Plan, verbs.Sync, verbs.Diff, verbs.Export, verbs.Adopt, verbs.API, verbs.Kai, verbs.View, verbs.Logout: - return &baseCmd, nil - default: - return &baseCmd, nil - } -} diff --git a/internal/cmd/root/products/konnect/eventgateway/event-gateway.go b/internal/cmd/root/products/konnect/eventgateway/event-gateway.go index edc9a323..64a83aed 100644 --- a/internal/cmd/root/products/konnect/eventgateway/event-gateway.go +++ b/internal/cmd/root/products/konnect/eventgateway/event-gateway.go @@ -1,8 +1,6 @@ package eventgateway import ( - controlplane "github.com/kong/kongctl/internal/cmd/root/products/konnect/eventgateway/control-plane" - "github.com/kong/kongctl/internal/cmd/root/verbs" "github.com/kong/kongctl/internal/util/i18n" "github.com/kong/kongctl/internal/util/normalizers" @@ -20,18 +18,22 @@ func NewEventGatewayCmd(verb verbs.VerbValue, addParentFlags func(verbs.VerbValue, *cobra.Command), parentPreRun func(*cobra.Command, []string) error, ) (*cobra.Command, error) { - cmd := &cobra.Command{ + baseCmd := cobra.Command{ Use: eventGatewayUse, Short: eventGatewayShort, Long: eventGatewayLong, Aliases: []string{"egw", "EGW", "event-gateways"}, } - c, e := controlplane.NewEventGatewayControlPlaneCmd(verb, addParentFlags, parentPreRun) - if e != nil { - return nil, e + switch verb { + case verbs.Get: + return newGetEventGatewayControlPlaneCmd(verb, &baseCmd, addParentFlags, parentPreRun).Command, nil + case verbs.List: + return newGetEventGatewayControlPlaneCmd(verb, &baseCmd, addParentFlags, parentPreRun).Command, nil + case verbs.Create, verbs.Add, verbs.Apply, verbs.Dump, verbs.Update, verbs.Delete, verbs.Help, verbs.Login, + verbs.Plan, verbs.Sync, verbs.Diff, verbs.Export, verbs.Adopt, verbs.API, verbs.Kai, verbs.View, verbs.Logout: + return &baseCmd, nil + default: + return &baseCmd, nil } - cmd.AddCommand(c) - - return cmd, nil } diff --git a/internal/cmd/root/products/konnect/eventgateway/control-plane/getControlPlane.go b/internal/cmd/root/products/konnect/eventgateway/getEventGateway.go similarity index 89% rename from internal/cmd/root/products/konnect/eventgateway/control-plane/getControlPlane.go rename to internal/cmd/root/products/konnect/eventgateway/getEventGateway.go index a689965a..49662c69 100644 --- a/internal/cmd/root/products/konnect/eventgateway/control-plane/getControlPlane.go +++ b/internal/cmd/root/products/konnect/eventgateway/getEventGateway.go @@ -1,4 +1,4 @@ -package controlplane +package eventgateway import ( "fmt" @@ -27,25 +27,23 @@ import ( var ( getEventGatewayControlPlanesShort = i18n.T( "root.products.konnect.event-gateway.control-plane.getEventGatewayControlPlanesShort", - "List or get Konnect Event Gateway Control Planes") + "List or get Konnect Event Gateways") getEventGatewayControlPlanesLong = i18n.T( "root.products.konnect.event-gateway.control-plane.getEventGatewayControlPlanesLong", - `Use the get verb with the event-gateway control-plane command to query Konnect Event Gateway Control Planes.`) + `Use the get verb with the event-gateway command to query Konnect Event Gateways.`) getEventGatewayControlPlanesExample = normalizers.Examples( i18n.T("root.products.konnect.event-gateway.control-plane.getEventGatewayControlPlanesExamples", - fmt.Sprintf(` - # List all the Event gateway control planes for the organization - %[1]s get event-gateway control-planes - # Get details for an Event gateway control plane with a specific ID - %[1]s get event-gateway control-plane 22cd8a0b-72e7-4212-9099-0764f8e9c5ac - # Get details for an Event gateway control plane with a specific name - %[1]s get event-gateway control-plane my-eventgatewaycontrolplane - # Get all the Event gateway control planes using command aliases - %[1]s get egw cp - `, meta.CLIName))) + fmt.Sprintf(`# List all the Event Gateways for the organization +%[1]s get event-gateway +# Get details for an Event Gateway with a specific ID +%[1]s get event-gateway 22cd8a0b-72e7-4212-9099-0764f8e9c5ac +# Get details for an Event Gateway with a specific name +%[1]s get event-gateway my-eventgateway +# Get all the Event Gateways using command aliases +%[1]s get egw +`, meta.CLIName))) ) -// textDisplayRecord represents a text display record for an Event gateway control plane type textDisplayRecord struct { ID string Name string @@ -222,10 +220,10 @@ func (c *getEventGatewayControlPlaneCmd) runE(cobraCmd *cobra.Command, args []st return e } - // 'get event-gateway control-planes' can be run in various ways: - // > get event-gateway control-planes # Get by UUID - // > get event-gateway control-planes # Get by name - // > get event-gateway control-planes # List all + // 'get event-gateway' can be run in various ways: + // > get event-gateway # Get by UUID + // > get event-gateway # Get by name + // > get event-gateway # List all if len(helper.GetArgs()) == 1 { // validate above checks that args is 0 or 1 id := strings.TrimSpace(helper.GetArgs()[0]) diff --git a/internal/cmd/root/verbs/get/eventgateway.go b/internal/cmd/root/verbs/get/eventgateway.go index f133d119..3ccca364 100644 --- a/internal/cmd/root/verbs/get/eventgateway.go +++ b/internal/cmd/root/verbs/get/eventgateway.go @@ -67,9 +67,9 @@ Setting this value overrides tokens obtained from the login command. // Override the example to show direct usage without "konnect" eventGatewayCmd.Example = ` # List all the Event Gateways for the organization - kongctl get event-gateway control-planes + kongctl get event-gateways # Get a specific Event Gateway - kongctl get event-gateway control-plane + kongctl get event-gateways ` return eventGatewayCmd, nil diff --git a/internal/cmd/root/verbs/get/get.go b/internal/cmd/root/verbs/get/get.go index 003cb328..4356396e 100644 --- a/internal/cmd/root/verbs/get/get.go +++ b/internal/cmd/root/verbs/get/get.go @@ -43,8 +43,8 @@ Output can be formatted in multiple ways to aid in further processing.`)) %[1]s get gateway control-planes # Retrieve Konnect control planes (explicit) %[1]s get konnect gateway control-planes - # Retrieve Konnect Event Gateway control planes - %[1]s get event-gateway control-planes + # Retrieve Konnect Event Gateways + %[1]s get event-gateways `, meta.CLIName))) ) diff --git a/test/e2e/scenarios/event-gateway/control-planes/scenario.yaml b/test/e2e/scenarios/event-gateway/control-planes/scenario.yaml index e218ce49..e07b0300 100644 --- a/test/e2e/scenarios/event-gateway/control-planes/scenario.yaml +++ b/test/e2e/scenarios/event-gateway/control-planes/scenario.yaml @@ -45,7 +45,7 @@ steps: fields.labels.owner: "platform" fields.labels.lifecycle: "dev" - name: 001-get-event-gateways - run: ["get", "event-gateway", "control-plane", "-o", "json"] + run: ["get", "event-gateways", "-o", "json"] assertions: - select: "[?name=='My Event Gateway CP'] | [0]" expect: @@ -85,7 +85,7 @@ steps: fields.labels.owner: "platform-eng" fields.labels.lifecycle: "production" - name: 001-get-event-gateways - run: ["get", "event-gateway", "control-plane", "-o", "json"] + run: ["get", "event-gateways", "-o", "json"] assertions: - select: "[?name=='My Event Gateway CP'] | [0]" expect: @@ -122,7 +122,7 @@ steps: fields: action: DELETE - name: 001-get-event-gateways - run: ["get", "event-gateway", "control-plane", "-o", "json"] + run: ["get", "event-gateways", "-o", "json"] assertions: - select: "[?name=='My Event Gateway CP']" expect: diff --git a/test/e2e/scenarios/plan/apply-workflow/scenario.yaml b/test/e2e/scenarios/plan/apply-workflow/scenario.yaml index d1509574..5ac356c7 100644 --- a/test/e2e/scenarios/plan/apply-workflow/scenario.yaml +++ b/test/e2e/scenarios/plan/apply-workflow/scenario.yaml @@ -101,7 +101,7 @@ steps: description: "Orders API for plan apply workflow" labels.domain: commerce - name: 005-get-event-gateways - run: ["get", "event-gateway", "control-plane", "-o", "json"] + run: ["get", "event-gateways", "-o", "json"] assertions: - select: "[?name=='{{ .vars.egwRef }}'] | [0]" expect: @@ -185,7 +185,7 @@ steps: description: "Orders API with updated details" labels.lifecycle: production - name: 005-get-event-gateways - run: ["get", "event-gateway", "control-plane", "-o", "json"] + run: ["get", "event-gateways", "-o", "json"] assertions: - select: "[?name=='{{ .vars.egwRef }}'] | [0]" expect: diff --git a/test/e2e/scenarios/plan/sync-workflow/scenario.yaml b/test/e2e/scenarios/plan/sync-workflow/scenario.yaml index c75df5db..ba5d2264 100644 --- a/test/e2e/scenarios/plan/sync-workflow/scenario.yaml +++ b/test/e2e/scenarios/plan/sync-workflow/scenario.yaml @@ -74,7 +74,7 @@ steps: fields: labels.lifecycle: sunset - name: 003-get-event-gateways - run: ["get", "event-gateway", "control-plane", "-o", "json"] + run: ["get", "event-gateways", "-o", "json"] assertions: - select: "[?name=='{{ .vars.egwRef }}'] | [0]" expect: @@ -168,7 +168,7 @@ steps: description: "Orders API with updated configuration" labels.lifecycle: stable - name: 005-get-event-gateways-updated - run: ["get", "event-gateway", "control-plane", "-o", "json"] + run: ["get", "event-gateways", "-o", "json"] assertions: - select: "[?name=='{{ .vars.egwRef }}'] | [0]" expect: diff --git a/test/e2e/scenarios/protected-resources/event-gateways/scenario.yaml b/test/e2e/scenarios/protected-resources/event-gateways/scenario.yaml index 8efeb0cc..97bd9771 100644 --- a/test/e2e/scenarios/protected-resources/event-gateways/scenario.yaml +++ b/test/e2e/scenarios/protected-resources/event-gateways/scenario.yaml @@ -53,7 +53,7 @@ steps: # Verify resources exist with proper labels - name: 001-get-event-gateway-control-planes - run: ["get", "event-gateway", "control-planes", "-o", "json"] + run: ["get", "event-gateways", "-o", "json"] assertions: # Verify Protected Event Gateway CP has protected label - select: "[?name=='{{ .vars.protectedEventGatewayName }}'] | [0]" @@ -80,7 +80,7 @@ steps: # Verify protected resource still exists - name: 001-verify-protected-exists - run: ["get", "event-gateway", "control-plane", "-o", "json"] + run: ["get", "event-gateways", "-o", "json"] assertions: - select: "[?name=='{{ .vars.protectedEventGatewayName }}']" expect: @@ -129,7 +129,7 @@ steps: # Verify protected label removed - name: 001-verify-unprotected - run: ["get", "event-gateway", "control-plane", "-o", "json"] + run: ["get", "event-gateways", "-o", "json"] assertions: - select: "[?name=='{{ .vars.protectedEventGatewayName }}'] | [0]" expect: @@ -165,7 +165,7 @@ steps: # Verify resource deleted - name: 001-verify-deleted - run: ["get", "event-gateway", "control-plane", "-o", "json"] + run: ["get", "event-gateways", "-o", "json"] assertions: - select: "[?name=='{{ .vars.protectedEventGatewayName }}']" expect: From cabd50c933ae8be901f2cd067cad87d6407efd38 Mon Sep 17 00:00:00 2001 From: Harsha Dixit Date: Wed, 14 Jan 2026 18:38:40 +0530 Subject: [PATCH 23/24] tests: add separate event gateway planning scenarios --- .../overlays/001-update/config.yaml | 8 + .../plan/apply-workflow/scenario.yaml | 143 ++++++++++++++++ .../overlays/001-update/config.yaml | 8 + .../plan/sync-workflow/scenario.yaml | 155 ++++++++++++++++++ .../event-gateways/plan/apply/config.yaml | 8 + .../event-gateways/plan/sync/config.yaml | 8 + 6 files changed, 330 insertions(+) create mode 100644 test/e2e/scenarios/event-gateway/plan/apply-workflow/overlays/001-update/config.yaml create mode 100644 test/e2e/scenarios/event-gateway/plan/apply-workflow/scenario.yaml create mode 100644 test/e2e/scenarios/event-gateway/plan/sync-workflow/overlays/001-update/config.yaml create mode 100644 test/e2e/scenarios/event-gateway/plan/sync-workflow/scenario.yaml create mode 100644 test/e2e/testdata/declarative/event-gateways/plan/apply/config.yaml create mode 100644 test/e2e/testdata/declarative/event-gateways/plan/sync/config.yaml diff --git a/test/e2e/scenarios/event-gateway/plan/apply-workflow/overlays/001-update/config.yaml b/test/e2e/scenarios/event-gateway/plan/apply-workflow/overlays/001-update/config.yaml new file mode 100644 index 00000000..19e51947 --- /dev/null +++ b/test/e2e/scenarios/event-gateway/plan/apply-workflow/overlays/001-update/config.yaml @@ -0,0 +1,8 @@ +_defaults: + kongctl: + namespace: egw-plan-apply + +event_gateways: + - ref: plan-apply-egw + name: plan-apply-egw + description: "Updated description for plan apply workflow" diff --git a/test/e2e/scenarios/event-gateway/plan/apply-workflow/scenario.yaml b/test/e2e/scenarios/event-gateway/plan/apply-workflow/scenario.yaml new file mode 100644 index 00000000..42cc5a12 --- /dev/null +++ b/test/e2e/scenarios/event-gateway/plan/apply-workflow/scenario.yaml @@ -0,0 +1,143 @@ +baseInputsPath: ../../../../testdata/declarative/event-gateways/plan/apply + +env: + KONGCTL_LOG_LEVEL: info + +vars: + egwRef: plan-apply-egw + egwName: plan-apply-egw + egwInitialDesc: "Initial description for plan apply workflow" + egwUpdatedDesc: "Updated description for plan apply workflow" + +defaults: + mask: + dropKeys: + - id + - created_at + - updated_at + +steps: + - name: 000-reset-org + skipInputs: true + commands: + - resetOrg: true + + - name: 001-plan-apply-create + commands: + - name: 000-plan-create + outputFormat: disable + stdoutFile: "{{ .workdir }}/plan-create.json" + run: + - plan + - -f + - "{{ .workdir }}/config.yaml" + - --mode + - apply + assertions: + - select: metadata + expect: + fields: + mode: apply + - select: summary + expect: + fields: + total_changes: 1 + by_action.CREATE: 1 + - name: 001-diff-plan-text + outputFormat: text + parseAs: raw + run: + - diff + - --plan + - "{{ .workdir }}/plan-create.json" + assertions: + - select: stdout + expect: + fields: + "contains(@, 'Plan: 1 to add')": true + "contains(@, 'event_gateway \"plan-apply-egw\" will be created')": true + - name: 002-apply-plan + outputFormat: json + run: + - apply + - --plan + - "{{ .workdir }}/plan-create.json" + - --auto-approve + assertions: + - select: plan.metadata + expect: + fields: + mode: apply + - select: summary + expect: + fields: + applied: 1 + failed: 0 + - name: 003-get-event-gateways + run: ["get", "event-gateways", "-o", "json"] + assertions: + - select: "[?name=='{{ .vars.egwRef }}'] | [0]" + expect: + fields: + name: "{{ .vars.egwName }}" + description: "{{ .vars.egwInitialDesc }}" + - name: 002-plan-apply-update + inputOverlayDirs: + - overlays/001-update + commands: + - name: 000-plan-update + outputFormat: disable + stdoutFile: "{{ .workdir }}/plan-update.json" + run: + - plan + - -f + - "{{ .workdir }}/config.yaml" + - --mode + - apply + assertions: + - select: metadata + expect: + fields: + mode: apply + - select: summary + expect: + fields: + total_changes: 1 + by_action.UPDATE: 1 + - name: 001-diff-plan-text + outputFormat: text + parseAs: raw + run: + - diff + - --plan + - "{{ .workdir }}/plan-update.json" + assertions: + - select: stdout + expect: + fields: + "contains(@, 'event_gateway \"plan-apply-egw\" will be updated')": true + - name: 002-apply-plan + outputFormat: json + run: + - apply + - --plan + - "{{ .workdir }}/plan-update.json" + - --auto-approve + assertions: + - select: summary + expect: + fields: + applied: 1 + failed: 0 + - select: plan.summary + expect: + fields: + by_action.UPDATE: 1 + - name: 003-get-event-gateways + run: ["get", "event-gateways", "-o", "json"] + assertions: + - select: "[?name=='{{ .vars.egwRef }}'] | [0]" + expect: + fields: + name: "{{ .vars.egwName }}" + description: "{{ .vars.egwUpdatedDesc }}" diff --git a/test/e2e/scenarios/event-gateway/plan/sync-workflow/overlays/001-update/config.yaml b/test/e2e/scenarios/event-gateway/plan/sync-workflow/overlays/001-update/config.yaml new file mode 100644 index 00000000..cd00f30f --- /dev/null +++ b/test/e2e/scenarios/event-gateway/plan/sync-workflow/overlays/001-update/config.yaml @@ -0,0 +1,8 @@ +_defaults: + kongctl: + namespace: egw-plan-sync + +event_gateways: + - ref: plan-sync-egw + name: plan-sync-egw + description: "Updated description for plan sync workflow" diff --git a/test/e2e/scenarios/event-gateway/plan/sync-workflow/scenario.yaml b/test/e2e/scenarios/event-gateway/plan/sync-workflow/scenario.yaml new file mode 100644 index 00000000..d8e6b6a3 --- /dev/null +++ b/test/e2e/scenarios/event-gateway/plan/sync-workflow/scenario.yaml @@ -0,0 +1,155 @@ +baseInputsPath: ../../../../testdata/declarative/event-gateways/plan/sync + +env: + KONGCTL_LOG_LEVEL: info + +vars: + egwRef: plan-sync-egw + egwName: plan-sync-egw + egwInitialDesc: "Initial description for plan sync workflow" + egwUpdatedDesc: "Updated description for plan sync workflow" + +defaults: + mask: + dropKeys: + - id + - created_at + - updated_at + +steps: + - name: 000-reset-org + skipInputs: true + commands: + - resetOrg: true + + - name: 001-plan-sync-create + commands: + - name: 000-plan-create + outputFormat: disable + stdoutFile: "{{ .workdir }}/plan-create.json" + run: + - plan + - -f + - "{{ .workdir }}/config.yaml" + - --mode + - sync + assertions: + - select: metadata + expect: + fields: + mode: sync + - select: summary + expect: + fields: + total_changes: 1 + by_action.CREATE: 1 + - name: 001-diff-plan-text + outputFormat: text + parseAs: raw + run: + - diff + - --plan + - "{{ .workdir }}/plan-create.json" + assertions: + - select: stdout + expect: + fields: + "contains(@, 'Plan: 1 to add')": true + "contains(@, 'event_gateway \"plan-sync-egw\" will be created')": true + - name: 002-sync-plan + outputFormat: json + run: + - sync + - --plan + - "{{ .workdir }}/plan-create.json" + - --auto-approve + assertions: + - select: plan.metadata + expect: + fields: + mode: sync + - select: summary + expect: + fields: + applied: 1 + failed: 0 + - name: 003-get-event-gateways + run: ["get", "event-gateways", "-o", "json"] + assertions: + - select: "[?name=='{{ .vars.egwRef }}'] | [0]" + expect: + fields: + name: "{{ .vars.egwName }}" + description: "{{ .vars.egwInitialDesc }}" + - name: 002-plan-sync-update + inputOverlayDirs: + - overlays/001-update + commands: + - name: 000-plan-update + outputFormat: disable + stdoutFile: "{{ .workdir }}/plan-update.json" + run: + - plan + - -f + - "{{ .workdir }}/config.yaml" + - --mode + - sync + assertions: + - select: metadata + expect: + fields: + mode: sync + - select: summary + expect: + fields: + total_changes: 1 + by_action.UPDATE: 1 + - name: 001-diff-plan-text + outputFormat: text + parseAs: raw + run: + - diff + - --plan + - "{{ .workdir }}/plan-update.json" + assertions: + - select: stdout + expect: + fields: + "contains(@, 'event_gateway \"plan-sync-egw\" will be updated')": true + - name: 002-sync-plan + outputFormat: json + run: + - sync + - --plan + - "{{ .workdir }}/plan-update.json" + - --auto-approve + assertions: + - select: summary + expect: + fields: + applied: 1 + failed: 0 + - select: plan.summary + expect: + fields: + by_action.UPDATE: 1 + - name: 003-get-event-gateways + run: ["get", "event-gateways", "-o", "json"] + assertions: + - select: "[?name=='{{ .vars.egwRef }}'] | [0]" + expect: + fields: + name: "{{ .vars.egwName }}" + description: "{{ .vars.egwUpdatedDesc }}" + - name: 004-diff-no-changes + outputFormat: text + parseAs: raw + run: + - diff + - -f + - "{{ .workdir }}/config.yaml" + assertions: + - select: stdout + expect: + fields: + "contains(@, 'No changes detected. Konnect is up to date.')": true diff --git a/test/e2e/testdata/declarative/event-gateways/plan/apply/config.yaml b/test/e2e/testdata/declarative/event-gateways/plan/apply/config.yaml new file mode 100644 index 00000000..2f7620a9 --- /dev/null +++ b/test/e2e/testdata/declarative/event-gateways/plan/apply/config.yaml @@ -0,0 +1,8 @@ +_defaults: + kongctl: + namespace: egw-plan-apply + +event_gateways: + - ref: plan-apply-egw + name: plan-apply-egw + description: "Initial description for plan apply workflow" diff --git a/test/e2e/testdata/declarative/event-gateways/plan/sync/config.yaml b/test/e2e/testdata/declarative/event-gateways/plan/sync/config.yaml new file mode 100644 index 00000000..15313837 --- /dev/null +++ b/test/e2e/testdata/declarative/event-gateways/plan/sync/config.yaml @@ -0,0 +1,8 @@ +_defaults: + kongctl: + namespace: egw-plan-sync + +event_gateways: + - ref: plan-sync-egw + name: plan-sync-egw + description: "Initial description for plan sync workflow" \ No newline at end of file From d6d1a83406f26f32fe7aa44cb0873dd74f12dbdf Mon Sep 17 00:00:00 2001 From: Harsha Dixit Date: Wed, 14 Jan 2026 18:39:11 +0530 Subject: [PATCH 24/24] tests: revert changes to generic plan workflow --- .../overlays/002-plan-update/config.yaml | 5 --- .../plan/apply-workflow/scenario.yaml | 41 +++++-------------- .../overlays/002-plan-sync/config.yaml | 5 --- .../plan/sync-workflow/scenario.yaml | 39 ++++-------------- .../declarative/plan/apply/config.yaml | 5 --- .../declarative/plan/sync/config.yaml | 5 --- 6 files changed, 19 insertions(+), 81 deletions(-) diff --git a/test/e2e/scenarios/plan/apply-workflow/overlays/002-plan-update/config.yaml b/test/e2e/scenarios/plan/apply-workflow/overlays/002-plan-update/config.yaml index b5958838..6bb22eeb 100644 --- a/test/e2e/scenarios/plan/apply-workflow/overlays/002-plan-update/config.yaml +++ b/test/e2e/scenarios/plan/apply-workflow/overlays/002-plan-update/config.yaml @@ -24,8 +24,3 @@ apis: labels: domain: insights lifecycle: beta - -event_gateways: - - ref: plan-apply-egw - name: plan-apply-egw - description: "Updated description for plan apply workflow" \ No newline at end of file diff --git a/test/e2e/scenarios/plan/apply-workflow/scenario.yaml b/test/e2e/scenarios/plan/apply-workflow/scenario.yaml index 5ac356c7..4ed1539d 100644 --- a/test/e2e/scenarios/plan/apply-workflow/scenario.yaml +++ b/test/e2e/scenarios/plan/apply-workflow/scenario.yaml @@ -10,10 +10,6 @@ vars: namespace: plan-apply ordersAPIRef: plan-apply-orders analyticsAPIRef: plan-apply-analytics - egwRef: plan-apply-egw - egwName: "plan-apply-egw" - egwInitialDesc: "Initial description for plan apply workflow" - egwUpdatedDesc: "Updated description for plan apply workflow" defaults: mask: @@ -50,8 +46,8 @@ steps: - select: summary expect: fields: - total_changes: 3 - by_action.CREATE: 3 + total_changes: 2 + by_action.CREATE: 2 - name: 001-diff-plan-text outputFormat: text parseAs: raw @@ -63,10 +59,9 @@ steps: - select: stdout expect: fields: - "contains(@, 'Plan: 3 to add')": true + "contains(@, 'Plan: 2 to add')": true "contains(@, 'portal \"plan-apply-portal\" will be created')": true "contains(@, 'api \"plan-apply-orders\" will be created')": true - "contains(@, 'event_gateway \"plan-apply-egw\" will be created')": true - name: 002-apply-plan outputFormat: json run: @@ -82,7 +77,7 @@ steps: - select: summary expect: fields: - applied: 3 + applied: 2 failed: 0 - name: 003-get-portals run: ["get", "portals", "-o", "json"] @@ -100,14 +95,7 @@ steps: fields: description: "Orders API for plan apply workflow" labels.domain: commerce - - name: 005-get-event-gateways - run: ["get", "event-gateways", "-o", "json"] - assertions: - - select: "[?name=='{{ .vars.egwRef }}'] | [0]" - expect: - fields: - name: "{{ .vars.egwName }}" - description: "{{ .vars.egwInitialDesc }}" + - name: 002-plan-apply-update inputOverlayDirs: - overlays/002-plan-update @@ -129,9 +117,9 @@ steps: - select: summary expect: fields: - total_changes: 4 + total_changes: 3 by_action.CREATE: 1 - by_action.UPDATE: 3 + by_action.UPDATE: 2 - name: 001-diff-plan-text outputFormat: text parseAs: raw @@ -143,10 +131,9 @@ steps: - select: stdout expect: fields: - "contains(@, 'Plan: 1 to add, 3 to change')": true + "contains(@, 'Plan: 1 to add, 2 to change')": true "contains(@, 'portal \"plan-apply-portal\" will be updated')": true "contains(@, 'api \"plan-apply-analytics\" will be created')": true - "contains(@, 'event_gateway \"plan-apply-egw\" will be updated')": true - name: 002-apply-plan outputFormat: json run: @@ -158,12 +145,12 @@ steps: - select: summary expect: fields: - applied: 4 + applied: 3 failed: 0 - select: plan.summary expect: fields: - by_action.UPDATE: 3 + by_action.UPDATE: 2 by_action.CREATE: 1 - name: 003-get-portals run: ["get", "portals", "-o", "json"] @@ -184,11 +171,3 @@ steps: fields: description: "Orders API with updated details" labels.lifecycle: production - - name: 005-get-event-gateways - run: ["get", "event-gateways", "-o", "json"] - assertions: - - select: "[?name=='{{ .vars.egwRef }}'] | [0]" - expect: - fields: - name: "{{ .vars.egwName }}" - description: "{{ .vars.egwUpdatedDesc }}" diff --git a/test/e2e/scenarios/plan/sync-workflow/overlays/002-plan-sync/config.yaml b/test/e2e/scenarios/plan/sync-workflow/overlays/002-plan-sync/config.yaml index 44462c0b..f7c8c20c 100644 --- a/test/e2e/scenarios/plan/sync-workflow/overlays/002-plan-sync/config.yaml +++ b/test/e2e/scenarios/plan/sync-workflow/overlays/002-plan-sync/config.yaml @@ -24,8 +24,3 @@ apis: labels: domain: supply lifecycle: beta - -event_gateways: - - ref: plan-sync-egw - name: plan-sync-egw - description: "Updated description for plan sync workflow" \ No newline at end of file diff --git a/test/e2e/scenarios/plan/sync-workflow/scenario.yaml b/test/e2e/scenarios/plan/sync-workflow/scenario.yaml index ba5d2264..1a6f1e9b 100644 --- a/test/e2e/scenarios/plan/sync-workflow/scenario.yaml +++ b/test/e2e/scenarios/plan/sync-workflow/scenario.yaml @@ -11,10 +11,6 @@ vars: ordersAPIRef: plan-sync-orders legacyAPIRef: plan-sync-legacy inventoryAPIRef: plan-sync-inventory - egwRef: plan-sync-egw - egwName: "plan-sync-egw" - egwInitialDesc: "Initial description for plan sync workflow" - egwUpdatedDesc: "Updated description for plan sync workflow" defaults: mask: @@ -48,12 +44,12 @@ steps: - select: summary expect: fields: - applied: 4 + applied: 3 failed: 0 - select: execution.applied_changes expect: fields: - "length(@)": 4 + "length(@)": 3 - name: 001-get-portal run: ["get", "portals", "-o", "json"] assertions: @@ -73,14 +69,6 @@ steps: expect: fields: labels.lifecycle: sunset - - name: 003-get-event-gateways - run: ["get", "event-gateways", "-o", "json"] - assertions: - - select: "[?name=='{{ .vars.egwRef }}'] | [0]" - expect: - fields: - name: "{{ .vars.egwName }}" - description: "{{ .vars.egwInitialDesc }}" - name: 002-plan-sync-update inputOverlayDirs: @@ -103,9 +91,9 @@ steps: - select: summary expect: fields: - total_changes: 5 + total_changes: 4 by_action.CREATE: 1 - by_action.UPDATE: 3 + by_action.UPDATE: 2 by_action.DELETE: 1 - name: 001-diff-plan-text outputFormat: text @@ -118,13 +106,12 @@ steps: - select: stdout expect: fields: - "contains(@, 'Plan: 1 to add, 3 to change, 1 to destroy')": true + "contains(@, 'Plan: 1 to add, 2 to change, 1 to destroy')": true "contains(@, 'portal \"plan-sync-portal\" will be updated')": true "contains(@, 'authentication_enabled: false')": true "contains(@, 'api \"plan-sync-inventory\" will be created')": true "contains(@, 'api \"plan-sync-orders\" will be updated')": true "contains(@, 'api \"plan-sync-legacy\" will be deleted')": true - "contains(@, 'event_gateway \"plan-sync-egw\" will be updated')": true - name: 002-sync-plan run: - sync @@ -136,12 +123,12 @@ steps: expect: fields: by_action.CREATE: 1 - by_action.UPDATE: 3 + by_action.UPDATE: 2 by_action.DELETE: 1 - select: summary expect: fields: - applied: 5 + applied: 4 failed: 0 - name: 003-get-portal run: ["get", "portals", "-o", "json"] @@ -167,15 +154,7 @@ steps: fields: description: "Orders API with updated configuration" labels.lifecycle: stable - - name: 005-get-event-gateways-updated - run: ["get", "event-gateways", "-o", "json"] - assertions: - - select: "[?name=='{{ .vars.egwRef }}'] | [0]" - expect: - fields: - name: "{{ .vars.egwName }}" - description: "{{ .vars.egwUpdatedDesc }}" - - name: 006-diff-no-changes + - name: 005-diff-no-changes outputFormat: text parseAs: raw run: @@ -187,7 +166,7 @@ steps: expect: fields: "contains(@, 'No changes detected. Konnect is up to date.')": true - - name: 007-plan-post-sync + - name: 006-plan-post-sync outputFormat: disable run: - plan diff --git a/test/e2e/testdata/declarative/plan/apply/config.yaml b/test/e2e/testdata/declarative/plan/apply/config.yaml index c68fafc4..4b3b4060 100644 --- a/test/e2e/testdata/declarative/plan/apply/config.yaml +++ b/test/e2e/testdata/declarative/plan/apply/config.yaml @@ -18,8 +18,3 @@ apis: labels: domain: commerce lifecycle: pilot - -event_gateways: - - ref: plan-apply-egw - name: plan-apply-egw - description: "Initial description for plan apply workflow" diff --git a/test/e2e/testdata/declarative/plan/sync/config.yaml b/test/e2e/testdata/declarative/plan/sync/config.yaml index 372562bd..ba290c84 100644 --- a/test/e2e/testdata/declarative/plan/sync/config.yaml +++ b/test/e2e/testdata/declarative/plan/sync/config.yaml @@ -24,8 +24,3 @@ apis: labels: domain: legacy lifecycle: sunset - -event_gateways: - - ref: plan-sync-egw - name: plan-sync-egw - description: "Initial description for plan sync workflow" \ No newline at end of file