From 8a945b3e0bbd81eb3d1a52f846d40ed72c43c84d Mon Sep 17 00:00:00 2001 From: Moiz Ibrar Date: Tue, 9 Dec 2025 00:36:04 +0500 Subject: [PATCH 1/5] Validate host ID uniqueness in get-join-options endpoint - Add validateHostIDUniqueness function in validate.go to check if host ID already exists - Add ErrHostAlreadyExists error definition in errors.go - Update GetJoinOptions handler to validate host ID before creating credentials - Add invalid_input error support to get-join-options endpoint in API design - Update generated error encoder to handle invalid_input with HTTP 400 Fixes PLAT-205: get-join-options endpoint should validate incoming host ID uniqueness --- api/apiv1/design/api.go | 1 + .../control_plane/server/encode_decode.go | 13 ++++++++++++ .../gen/http/control_plane/server/types.go | 20 +++++++++++++++++++ server/internal/api/apiv1/errors.go | 1 + .../internal/api/apiv1/post_init_handlers.go | 5 +++++ server/internal/api/apiv1/validate.go | 19 ++++++++++++++++++ 6 files changed, 59 insertions(+) diff --git a/api/apiv1/design/api.go b/api/apiv1/design/api.go index 19dd1e2e..5c2062c8 100644 --- a/api/apiv1/design/api.go +++ b/api/apiv1/design/api.go @@ -117,6 +117,7 @@ var _ = g.Service("control-plane", func() { g.Payload(ClusterJoinRequest) g.Error("cluster_not_initialized") g.Error("invalid_join_token") + g.Error("invalid_input") g.HTTP(func() { g.POST("/v1/internal/cluster/join-options") diff --git a/api/apiv1/gen/http/control_plane/server/encode_decode.go b/api/apiv1/gen/http/control_plane/server/encode_decode.go index d603649b..31275b11 100644 --- a/api/apiv1/gen/http/control_plane/server/encode_decode.go +++ b/api/apiv1/gen/http/control_plane/server/encode_decode.go @@ -343,6 +343,19 @@ func EncodeGetJoinOptionsError(encoder func(context.Context, http.ResponseWriter w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusUnauthorized) return enc.Encode(body) + case "invalid_input": + var res *controlplane.APIError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewGetJoinOptionsInvalidInputResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusBadRequest) + return enc.Encode(body) case "server_error": var res *controlplane.APIError errors.As(v, &res) diff --git a/api/apiv1/gen/http/control_plane/server/types.go b/api/apiv1/gen/http/control_plane/server/types.go index 630edba7..4a53f139 100644 --- a/api/apiv1/gen/http/control_plane/server/types.go +++ b/api/apiv1/gen/http/control_plane/server/types.go @@ -472,6 +472,16 @@ type GetJoinOptionsInvalidJoinTokenResponseBody struct { Message string `form:"message" json:"message" xml:"message"` } +// GetJoinOptionsInvalidInputResponseBody is the type of the "control-plane" +// service "get-join-options" endpoint HTTP response body for the +// "invalid_input" error. +type GetJoinOptionsInvalidInputResponseBody struct { + // The name of the error. + Name string `form:"name" json:"name" xml:"name"` + // The error message. + Message string `form:"message" json:"message" xml:"message"` +} + // GetJoinOptionsServerErrorResponseBody is the type of the "control-plane" // service "get-join-options" endpoint HTTP response body for the // "server_error" error. @@ -2879,6 +2889,16 @@ func NewGetJoinOptionsInvalidJoinTokenResponseBody(res *controlplane.APIError) * return body } +// NewGetJoinOptionsInvalidInputResponseBody builds the HTTP response body from +// the result of the "get-join-options" endpoint of the "control-plane" service. +func NewGetJoinOptionsInvalidInputResponseBody(res *controlplane.APIError) *GetJoinOptionsInvalidInputResponseBody { + body := &GetJoinOptionsInvalidInputResponseBody{ + Name: res.Name, + Message: res.Message, + } + return body +} + // NewGetJoinOptionsServerErrorResponseBody builds the HTTP response body from // the result of the "get-join-options" endpoint of the "control-plane" service. func NewGetJoinOptionsServerErrorResponseBody(res *controlplane.APIError) *GetJoinOptionsServerErrorResponseBody { diff --git a/server/internal/api/apiv1/errors.go b/server/internal/api/apiv1/errors.go index 8c64a2ce..c075c2f2 100644 --- a/server/internal/api/apiv1/errors.go +++ b/server/internal/api/apiv1/errors.go @@ -38,6 +38,7 @@ var ( ErrInvalidJoinToken = newAPIError(errInvalidJoinToken, "the given join token is invalid") ErrDatabaseAlreadyExists = newAPIError(errDatabaseAlreadyExists, "a database already exists with the given ID") ErrHostNotFound = newAPIError(errNotFound, "no host found with the given ID") + ErrHostAlreadyExists = newAPIError(errInvalidInput, "a host with the given ID already exists in the cluster") ErrNoPrimaryInstance = newAPIError(errNotFound, "no primary instance found for the given node") ErrOperationNotSupported = newAPIError(errOperationNotSupported, "operation not supported by this control plane server") ) diff --git a/server/internal/api/apiv1/post_init_handlers.go b/server/internal/api/apiv1/post_init_handlers.go index 85be148c..417ea425 100644 --- a/server/internal/api/apiv1/post_init_handlers.go +++ b/server/internal/api/apiv1/post_init_handlers.go @@ -95,6 +95,11 @@ func (s *PostInitHandlers) GetJoinOptions(ctx context.Context, req *api.ClusterJ return nil, apiErr(err) } + // Validate that the host ID is unique in the cluster + if err := validateHostIDUniqueness(ctx, s.hostSvc, hostID); err != nil { + return nil, makeInvalidInputErr(err) + } + creds, err := s.etcd.AddHost(ctx, etcd.HostCredentialOptions{ HostID: hostID, Hostname: req.Hostname, diff --git a/server/internal/api/apiv1/validate.go b/server/internal/api/apiv1/validate.go index 65610b75..91846501 100644 --- a/server/internal/api/apiv1/validate.go +++ b/server/internal/api/apiv1/validate.go @@ -1,6 +1,7 @@ package apiv1 import ( + "context" "errors" "fmt" "path/filepath" @@ -11,7 +12,9 @@ import ( api "github.com/pgEdge/control-plane/api/apiv1/gen/control_plane" "github.com/pgEdge/control-plane/server/internal/database" "github.com/pgEdge/control-plane/server/internal/ds" + "github.com/pgEdge/control-plane/server/internal/host" "github.com/pgEdge/control-plane/server/internal/pgbackrest" + "github.com/pgEdge/control-plane/server/internal/storage" "github.com/pgEdge/control-plane/server/internal/utils" ) @@ -428,3 +431,19 @@ func validateIdentifier(ident string, path []string) error { return nil } + +// validateHostIDUniqueness checks that the given host ID does not already exist in the cluster. +// Returns an error if the host ID already exists, or if there's an error checking for the host. +func validateHostIDUniqueness(ctx context.Context, hostSvc *host.Service, hostID string) error { + _, err := hostSvc.GetHost(ctx, hostID) + if err == nil { + // Host already exists - this is a duplicate + return fmt.Errorf("host with ID %q already exists in the cluster", hostID) + } + if !errors.Is(err, storage.ErrNotFound) { + // Some error other than "not found" occurred + return fmt.Errorf("failed to check host ID uniqueness: %w", err) + } + // Host doesn't exist (storage.ErrNotFound) - good, host ID is unique + return nil +} From 06eabc9b97e67d736e8b40b22f38dec5142dac31 Mon Sep 17 00:00:00 2001 From: Moiz Ibrar Date: Wed, 10 Dec 2025 19:03:40 +0500 Subject: [PATCH 2/5] Simplify validateHostIDUniqueness error handling Remove explicit error handling for non-NotFound errors. The function now only checks if host exists (err == nil) and returns error for duplicates. Any other error (including NotFound) is treated as host not existing, which is valid for uniqueness check. --- server/internal/api/apiv1/validate.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/server/internal/api/apiv1/validate.go b/server/internal/api/apiv1/validate.go index 91846501..bfa92c79 100644 --- a/server/internal/api/apiv1/validate.go +++ b/server/internal/api/apiv1/validate.go @@ -14,7 +14,6 @@ import ( "github.com/pgEdge/control-plane/server/internal/ds" "github.com/pgEdge/control-plane/server/internal/host" "github.com/pgEdge/control-plane/server/internal/pgbackrest" - "github.com/pgEdge/control-plane/server/internal/storage" "github.com/pgEdge/control-plane/server/internal/utils" ) @@ -433,17 +432,13 @@ func validateIdentifier(ident string, path []string) error { } // validateHostIDUniqueness checks that the given host ID does not already exist in the cluster. -// Returns an error if the host ID already exists, or if there's an error checking for the host. +// Returns an error if the host ID already exists. func validateHostIDUniqueness(ctx context.Context, hostSvc *host.Service, hostID string) error { _, err := hostSvc.GetHost(ctx, hostID) if err == nil { // Host already exists - this is a duplicate return fmt.Errorf("host with ID %q already exists in the cluster", hostID) } - if !errors.Is(err, storage.ErrNotFound) { - // Some error other than "not found" occurred - return fmt.Errorf("failed to check host ID uniqueness: %w", err) - } // Host doesn't exist (storage.ErrNotFound) - good, host ID is unique return nil } From 2da12498a6065a57657d59ae2dfb18b6a36ad83d Mon Sep 17 00:00:00 2001 From: Moiz Ibrar Date: Fri, 12 Dec 2025 17:36:31 +0500 Subject: [PATCH 3/5] Add host ID uniqueness validation to join-cluster endpoint - Add invalid_input error to join-cluster endpoint in API design - Create ErrHostAlreadyExistsWithID function in errors.go to return error with specific host ID - Update validateHostIDUniqueness to use new error function and properly handle storage errors - Error message now includes the specific host ID for better client feedback --- api/apiv1/design/api.go | 1 + server/internal/api/apiv1/errors.go | 6 ++++++ server/internal/api/apiv1/validate.go | 14 ++++++++++---- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/api/apiv1/design/api.go b/api/apiv1/design/api.go index 5c2062c8..e811ae6e 100644 --- a/api/apiv1/design/api.go +++ b/api/apiv1/design/api.go @@ -89,6 +89,7 @@ var _ = g.Service("control-plane", func() { g.Payload(ClusterJoinToken) g.Error("cluster_already_initialized") g.Error("invalid_join_token") + g.Error("invalid_input") g.HTTP(func() { g.POST("/v1/cluster/join") diff --git a/server/internal/api/apiv1/errors.go b/server/internal/api/apiv1/errors.go index c075c2f2..5320178d 100644 --- a/server/internal/api/apiv1/errors.go +++ b/server/internal/api/apiv1/errors.go @@ -2,6 +2,7 @@ package apiv1 import ( "errors" + "fmt" goa "goa.design/goa/v3/pkg" @@ -43,6 +44,11 @@ var ( ErrOperationNotSupported = newAPIError(errOperationNotSupported, "operation not supported by this control plane server") ) +// ErrHostAlreadyExistsWithID returns an error indicating that a host with the given ID already exists. +func ErrHostAlreadyExistsWithID(hostID string) error { + return fmt.Errorf("host with ID %q already exists in the cluster", hostID) +} + func apiErr(err error) error { var goaErr *goa.ServiceError var apiErr *api.APIError diff --git a/server/internal/api/apiv1/validate.go b/server/internal/api/apiv1/validate.go index bfa92c79..b4474715 100644 --- a/server/internal/api/apiv1/validate.go +++ b/server/internal/api/apiv1/validate.go @@ -14,6 +14,7 @@ import ( "github.com/pgEdge/control-plane/server/internal/ds" "github.com/pgEdge/control-plane/server/internal/host" "github.com/pgEdge/control-plane/server/internal/pgbackrest" + "github.com/pgEdge/control-plane/server/internal/storage" "github.com/pgEdge/control-plane/server/internal/utils" ) @@ -435,10 +436,15 @@ func validateIdentifier(ident string, path []string) error { // Returns an error if the host ID already exists. func validateHostIDUniqueness(ctx context.Context, hostSvc *host.Service, hostID string) error { _, err := hostSvc.GetHost(ctx, hostID) - if err == nil { + switch { + case err == nil: // Host already exists - this is a duplicate - return fmt.Errorf("host with ID %q already exists in the cluster", hostID) + return ErrHostAlreadyExistsWithID(hostID) + case errors.Is(err, storage.ErrNotFound): + // Host doesn't exist - good, host ID is unique + return nil + default: + // Other errors (connection failures, permission errors, etc.) should be propagated + return fmt.Errorf("failed to check for existing host: %w", err) } - // Host doesn't exist (storage.ErrNotFound) - good, host ID is unique - return nil } From e5e983366df886944a73b7b751d1c365c9babcb7 Mon Sep 17 00:00:00 2001 From: Moiz Ibrar Date: Mon, 15 Dec 2025 22:26:25 +0500 Subject: [PATCH 4/5] Fix error handling in host ID uniqueness validation - Remove unused ErrHostAlreadyExists constant - Change ErrHostAlreadyExistsWithID to return *api.APIError instead of generic error - Use apiErr() instead of makeInvalidInputErr() in GetJoinOptions handler - Improve error message format (remove escaped quotes for better readability) This ensures proper error routing: - Duplicate host ID errors return HTTP 400 (invalid_input) - Connection failures return HTTP 500 (server_error) - Error messages are user-friendly and consistent with API patterns --- server/internal/api/apiv1/errors.go | 5 ++--- server/internal/api/apiv1/post_init_handlers.go | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/server/internal/api/apiv1/errors.go b/server/internal/api/apiv1/errors.go index 5320178d..1f4d6243 100644 --- a/server/internal/api/apiv1/errors.go +++ b/server/internal/api/apiv1/errors.go @@ -39,14 +39,13 @@ var ( ErrInvalidJoinToken = newAPIError(errInvalidJoinToken, "the given join token is invalid") ErrDatabaseAlreadyExists = newAPIError(errDatabaseAlreadyExists, "a database already exists with the given ID") ErrHostNotFound = newAPIError(errNotFound, "no host found with the given ID") - ErrHostAlreadyExists = newAPIError(errInvalidInput, "a host with the given ID already exists in the cluster") ErrNoPrimaryInstance = newAPIError(errNotFound, "no primary instance found for the given node") ErrOperationNotSupported = newAPIError(errOperationNotSupported, "operation not supported by this control plane server") ) // ErrHostAlreadyExistsWithID returns an error indicating that a host with the given ID already exists. -func ErrHostAlreadyExistsWithID(hostID string) error { - return fmt.Errorf("host with ID %q already exists in the cluster", hostID) +func ErrHostAlreadyExistsWithID(hostID string) *api.APIError { + return newAPIError(errInvalidInput, fmt.Sprintf("a host with ID %s already exists in the cluster", hostID)) } func apiErr(err error) error { diff --git a/server/internal/api/apiv1/post_init_handlers.go b/server/internal/api/apiv1/post_init_handlers.go index 417ea425..831d8294 100644 --- a/server/internal/api/apiv1/post_init_handlers.go +++ b/server/internal/api/apiv1/post_init_handlers.go @@ -97,7 +97,7 @@ func (s *PostInitHandlers) GetJoinOptions(ctx context.Context, req *api.ClusterJ // Validate that the host ID is unique in the cluster if err := validateHostIDUniqueness(ctx, s.hostSvc, hostID); err != nil { - return nil, makeInvalidInputErr(err) + return nil, apiErr(err) } creds, err := s.etcd.AddHost(ctx, etcd.HostCredentialOptions{ From 64cecf91d22d91b8f1025c5641641b1ce1a7fa75 Mon Sep 17 00:00:00 2001 From: Moiz Ibrar Date: Mon, 15 Dec 2025 23:45:11 +0500 Subject: [PATCH 5/5] Regenerate API files with latest design changes --- api/apiv1/gen/control_plane/client.go | 2 + api/apiv1/gen/control_plane/service.go | 10 +-- .../control_plane/client/encode_decode.go | 30 +++++++++ .../gen/http/control_plane/client/types.go | 66 +++++++++++++++++++ .../control_plane/server/encode_decode.go | 13 ++++ .../gen/http/control_plane/server/types.go | 20 ++++++ api/apiv1/gen/http/openapi.json | 10 +++ api/apiv1/gen/http/openapi.yaml | 7 ++ api/apiv1/gen/http/openapi3.json | 14 ++++ api/apiv1/gen/http/openapi3.yaml | 9 +++ 10 files changed, 176 insertions(+), 5 deletions(-) diff --git a/api/apiv1/gen/control_plane/client.go b/api/apiv1/gen/control_plane/client.go index 12894d74..40f90abd 100644 --- a/api/apiv1/gen/control_plane/client.go +++ b/api/apiv1/gen/control_plane/client.go @@ -92,6 +92,7 @@ func (c *Client) InitCluster(ctx context.Context, p *InitClusterRequest) (res *C // JoinCluster may return the following errors: // - "cluster_already_initialized" (type *goa.ServiceError) // - "invalid_join_token" (type *goa.ServiceError) +// - "invalid_input" (type *goa.ServiceError) // - "server_error" (type *goa.ServiceError) // - error: internal error func (c *Client) JoinCluster(ctx context.Context, p *ClusterJoinToken) (err error) { @@ -119,6 +120,7 @@ func (c *Client) GetJoinToken(ctx context.Context) (res *ClusterJoinToken, err e // GetJoinOptions may return the following errors: // - "cluster_not_initialized" (type *goa.ServiceError) // - "invalid_join_token" (type *goa.ServiceError) +// - "invalid_input" (type *goa.ServiceError) // - "server_error" (type *goa.ServiceError) // - error: internal error func (c *Client) GetJoinOptions(ctx context.Context, p *ClusterJoinRequest) (res *ClusterJoinOptions, err error) { diff --git a/api/apiv1/gen/control_plane/service.go b/api/apiv1/gen/control_plane/service.go index 42f2a09e..f316a1ae 100644 --- a/api/apiv1/gen/control_plane/service.go +++ b/api/apiv1/gen/control_plane/service.go @@ -992,16 +992,16 @@ func MakeInvalidJoinToken(err error) *goa.ServiceError { return goa.NewServiceError(err, "invalid_join_token", false, false, false) } -// MakeClusterNotInitialized builds a goa.ServiceError from an error. -func MakeClusterNotInitialized(err error) *goa.ServiceError { - return goa.NewServiceError(err, "cluster_not_initialized", false, false, false) -} - // MakeInvalidInput builds a goa.ServiceError from an error. func MakeInvalidInput(err error) *goa.ServiceError { return goa.NewServiceError(err, "invalid_input", false, false, false) } +// MakeClusterNotInitialized builds a goa.ServiceError from an error. +func MakeClusterNotInitialized(err error) *goa.ServiceError { + return goa.NewServiceError(err, "cluster_not_initialized", false, false, false) +} + // MakeNotFound builds a goa.ServiceError from an error. func MakeNotFound(err error) *goa.ServiceError { return goa.NewServiceError(err, "not_found", false, false, false) diff --git a/api/apiv1/gen/http/control_plane/client/encode_decode.go b/api/apiv1/gen/http/control_plane/client/encode_decode.go index e9ba78db..3675b7ea 100644 --- a/api/apiv1/gen/http/control_plane/client/encode_decode.go +++ b/api/apiv1/gen/http/control_plane/client/encode_decode.go @@ -176,6 +176,7 @@ func EncodeJoinClusterRequest(encoder func(*http.Request) goahttp.Encoder) func( // DecodeJoinClusterResponse may return the following errors: // - "cluster_already_initialized" (type *controlplane.APIError): http.StatusConflict // - "invalid_join_token" (type *controlplane.APIError): http.StatusUnauthorized +// - "invalid_input" (type *controlplane.APIError): http.StatusBadRequest // - "server_error" (type *controlplane.APIError): http.StatusInternalServerError // - error: internal error func DecodeJoinClusterResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { @@ -223,6 +224,20 @@ func DecodeJoinClusterResponse(decoder func(*http.Response) goahttp.Decoder, res return nil, goahttp.ErrValidationError("control-plane", "join-cluster", err) } return nil, NewJoinClusterInvalidJoinToken(&body) + case http.StatusBadRequest: + var ( + body JoinClusterInvalidInputResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("control-plane", "join-cluster", err) + } + err = ValidateJoinClusterInvalidInputResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("control-plane", "join-cluster", err) + } + return nil, NewJoinClusterInvalidInput(&body) case http.StatusInternalServerError: var ( body JoinClusterServerErrorResponseBody @@ -368,6 +383,7 @@ func EncodeGetJoinOptionsRequest(encoder func(*http.Request) goahttp.Encoder) fu // DecodeGetJoinOptionsResponse may return the following errors: // - "cluster_not_initialized" (type *controlplane.APIError): http.StatusConflict // - "invalid_join_token" (type *controlplane.APIError): http.StatusUnauthorized +// - "invalid_input" (type *controlplane.APIError): http.StatusBadRequest // - "server_error" (type *controlplane.APIError): http.StatusInternalServerError // - error: internal error func DecodeGetJoinOptionsResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { @@ -428,6 +444,20 @@ func DecodeGetJoinOptionsResponse(decoder func(*http.Response) goahttp.Decoder, return nil, goahttp.ErrValidationError("control-plane", "get-join-options", err) } return nil, NewGetJoinOptionsInvalidJoinToken(&body) + case http.StatusBadRequest: + var ( + body GetJoinOptionsInvalidInputResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("control-plane", "get-join-options", err) + } + err = ValidateGetJoinOptionsInvalidInputResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("control-plane", "get-join-options", err) + } + return nil, NewGetJoinOptionsInvalidInput(&body) case http.StatusInternalServerError: var ( body GetJoinOptionsServerErrorResponseBody diff --git a/api/apiv1/gen/http/control_plane/client/types.go b/api/apiv1/gen/http/control_plane/client/types.go index e8d2f55e..7643fa3f 100644 --- a/api/apiv1/gen/http/control_plane/client/types.go +++ b/api/apiv1/gen/http/control_plane/client/types.go @@ -422,6 +422,16 @@ type JoinClusterInvalidJoinTokenResponseBody struct { Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } +// JoinClusterInvalidInputResponseBody is the type of the "control-plane" +// service "join-cluster" endpoint HTTP response body for the "invalid_input" +// error. +type JoinClusterInvalidInputResponseBody struct { + // The name of the error. + Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` + // The error message. + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + // JoinClusterServerErrorResponseBody is the type of the "control-plane" // service "join-cluster" endpoint HTTP response body for the "server_error" // error. @@ -472,6 +482,16 @@ type GetJoinOptionsInvalidJoinTokenResponseBody struct { Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } +// GetJoinOptionsInvalidInputResponseBody is the type of the "control-plane" +// service "get-join-options" endpoint HTTP response body for the +// "invalid_input" error. +type GetJoinOptionsInvalidInputResponseBody struct { + // The name of the error. + Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` + // The error message. + Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` +} + // GetJoinOptionsServerErrorResponseBody is the type of the "control-plane" // service "get-join-options" endpoint HTTP response body for the // "server_error" error. @@ -2575,6 +2595,17 @@ func NewJoinClusterInvalidJoinToken(body *JoinClusterInvalidJoinTokenResponseBod return v } +// NewJoinClusterInvalidInput builds a control-plane service join-cluster +// endpoint invalid_input error. +func NewJoinClusterInvalidInput(body *JoinClusterInvalidInputResponseBody) *controlplane.APIError { + v := &controlplane.APIError{ + Name: *body.Name, + Message: *body.Message, + } + + return v +} + // NewJoinClusterServerError builds a control-plane service join-cluster // endpoint server_error error. func NewJoinClusterServerError(body *JoinClusterServerErrorResponseBody) *controlplane.APIError { @@ -2651,6 +2682,17 @@ func NewGetJoinOptionsInvalidJoinToken(body *GetJoinOptionsInvalidJoinTokenRespo return v } +// NewGetJoinOptionsInvalidInput builds a control-plane service +// get-join-options endpoint invalid_input error. +func NewGetJoinOptionsInvalidInput(body *GetJoinOptionsInvalidInputResponseBody) *controlplane.APIError { + v := &controlplane.APIError{ + Name: *body.Name, + Message: *body.Message, + } + + return v +} + // NewGetJoinOptionsServerError builds a control-plane service get-join-options // endpoint server_error error. func NewGetJoinOptionsServerError(body *GetJoinOptionsServerErrorResponseBody) *controlplane.APIError { @@ -4478,6 +4520,18 @@ func ValidateJoinClusterInvalidJoinTokenResponseBody(body *JoinClusterInvalidJoi return } +// ValidateJoinClusterInvalidInputResponseBody runs the validations defined on +// join-cluster_invalid_input_response_body +func ValidateJoinClusterInvalidInputResponseBody(body *JoinClusterInvalidInputResponseBody) (err error) { + if body.Name == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("name", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + // ValidateJoinClusterServerErrorResponseBody runs the validations defined on // join-cluster_server_error_response_body func ValidateJoinClusterServerErrorResponseBody(body *JoinClusterServerErrorResponseBody) (err error) { @@ -4538,6 +4592,18 @@ func ValidateGetJoinOptionsInvalidJoinTokenResponseBody(body *GetJoinOptionsInva return } +// ValidateGetJoinOptionsInvalidInputResponseBody runs the validations defined +// on get-join-options_invalid_input_response_body +func ValidateGetJoinOptionsInvalidInputResponseBody(body *GetJoinOptionsInvalidInputResponseBody) (err error) { + if body.Name == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("name", "body")) + } + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) + } + return +} + // ValidateGetJoinOptionsServerErrorResponseBody runs the validations defined // on get-join-options_server_error_response_body func ValidateGetJoinOptionsServerErrorResponseBody(body *GetJoinOptionsServerErrorResponseBody) (err error) { diff --git a/api/apiv1/gen/http/control_plane/server/encode_decode.go b/api/apiv1/gen/http/control_plane/server/encode_decode.go index 31275b11..7cb30152 100644 --- a/api/apiv1/gen/http/control_plane/server/encode_decode.go +++ b/api/apiv1/gen/http/control_plane/server/encode_decode.go @@ -193,6 +193,19 @@ func EncodeJoinClusterError(encoder func(context.Context, http.ResponseWriter) g w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusUnauthorized) return enc.Encode(body) + case "invalid_input": + var res *controlplane.APIError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewJoinClusterInvalidInputResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusBadRequest) + return enc.Encode(body) case "server_error": var res *controlplane.APIError errors.As(v, &res) diff --git a/api/apiv1/gen/http/control_plane/server/types.go b/api/apiv1/gen/http/control_plane/server/types.go index 4a53f139..bb70c8da 100644 --- a/api/apiv1/gen/http/control_plane/server/types.go +++ b/api/apiv1/gen/http/control_plane/server/types.go @@ -422,6 +422,16 @@ type JoinClusterInvalidJoinTokenResponseBody struct { Message string `form:"message" json:"message" xml:"message"` } +// JoinClusterInvalidInputResponseBody is the type of the "control-plane" +// service "join-cluster" endpoint HTTP response body for the "invalid_input" +// error. +type JoinClusterInvalidInputResponseBody struct { + // The name of the error. + Name string `form:"name" json:"name" xml:"name"` + // The error message. + Message string `form:"message" json:"message" xml:"message"` +} + // JoinClusterServerErrorResponseBody is the type of the "control-plane" // service "join-cluster" endpoint HTTP response body for the "server_error" // error. @@ -2836,6 +2846,16 @@ func NewJoinClusterInvalidJoinTokenResponseBody(res *controlplane.APIError) *Joi return body } +// NewJoinClusterInvalidInputResponseBody builds the HTTP response body from +// the result of the "join-cluster" endpoint of the "control-plane" service. +func NewJoinClusterInvalidInputResponseBody(res *controlplane.APIError) *JoinClusterInvalidInputResponseBody { + body := &JoinClusterInvalidInputResponseBody{ + Name: res.Name, + Message: res.Message, + } + return body +} + // NewJoinClusterServerErrorResponseBody builds the HTTP response body from the // result of the "join-cluster" endpoint of the "control-plane" service. func NewJoinClusterServerErrorResponseBody(res *controlplane.APIError) *JoinClusterServerErrorResponseBody { diff --git a/api/apiv1/gen/http/openapi.json b/api/apiv1/gen/http/openapi.json index 39fb43d2..46f698bc 100644 --- a/api/apiv1/gen/http/openapi.json +++ b/api/apiv1/gen/http/openapi.json @@ -154,6 +154,16 @@ "204": { "description": "No Content response." }, + "400": { + "description": "Bad Request response.", + "schema": { + "$ref": "#/definitions/APIError", + "required": [ + "name", + "message" + ] + } + }, "401": { "description": "Unauthorized response.", "schema": { diff --git a/api/apiv1/gen/http/openapi.yaml b/api/apiv1/gen/http/openapi.yaml index fb7e6a78..c142a679 100644 --- a/api/apiv1/gen/http/openapi.yaml +++ b/api/apiv1/gen/http/openapi.yaml @@ -109,6 +109,13 @@ paths: responses: "204": description: No Content response. + "400": + description: Bad Request response. + schema: + $ref: '#/definitions/APIError' + required: + - name + - message "401": description: Unauthorized response. schema: diff --git a/api/apiv1/gen/http/openapi3.json b/api/apiv1/gen/http/openapi3.json index 06c41324..f19024a7 100644 --- a/api/apiv1/gen/http/openapi3.json +++ b/api/apiv1/gen/http/openapi3.json @@ -305,6 +305,20 @@ "204": { "description": "No Content response." }, + "400": { + "description": "invalid_input: Bad Request response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/APIError" + }, + "example": { + "message": "A longer description of the error.", + "name": "error_name" + } + } + } + }, "401": { "description": "invalid_join_token: Unauthorized response.", "content": { diff --git a/api/apiv1/gen/http/openapi3.yaml b/api/apiv1/gen/http/openapi3.yaml index bbcf4e82..156b2f93 100644 --- a/api/apiv1/gen/http/openapi3.yaml +++ b/api/apiv1/gen/http/openapi3.yaml @@ -205,6 +205,15 @@ paths: responses: "204": description: No Content response. + "400": + description: 'invalid_input: Bad Request response.' + content: + application/json: + schema: + $ref: '#/components/schemas/APIError' + example: + message: A longer description of the error. + name: error_name "401": description: 'invalid_join_token: Unauthorized response.' content: