Skip to content

Commit

Permalink
feat: replace domains by auth config
Browse files Browse the repository at this point in the history
  • Loading branch information
jspdown authored Apr 15, 2024
1 parent af43704 commit 0e3f0e9
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 203 deletions.
39 changes: 3 additions & 36 deletions pkg/apis/hub/v1alpha1/api_portal.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
package v1alpha1

import (
netv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

Expand Down Expand Up @@ -50,48 +49,23 @@ type APIPortalSpec struct {
// +optional
Description string `json:"description,omitempty"`

// Domains are the domains under which the portal will be exposed.
// +optional
// TrustedDomains are the domains that are trusted by the OAuth 2.0 authorization server.
// +kubebuilder:validation:MinItems=1
// +kubebuilder:validation:MaxItems=20
// +kubebuilder:validation:XValidation:message="duplicate domains",rule="self.all(x, self.exists_one(y, y == x))"
Domains []Domain `json:"domains,omitempty"`
TrustedDomains []string `json:"trustedDomains"`

// UI holds the UI customization options.
// +optional
UI *UISpec `json:"ui,omitempty"`
}

// Domain is the domain name under which an APIPortal will be exposed.
// +kubebuilder:validation:MaxLength=253
// +kubebuilder:validation:XValidation:message="domain must be a valid domain name",rule="self.matches(r\"\"\"([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]\"\"\")"
type Domain string

// UISpec configures the UI customization.
type UISpec struct {
// Service defines a Kubernetes Service exposing a custom UI.
// +optional
Service *UIService `json:"service,omitempty"`

// LogoURL is the public URL of the logo.
// +optional
LogoURL string `json:"logoUrl,omitempty"`
}

// UIService configures the service to expose on the edge.
type UIService struct {
// Name of the Kubernetes Service resource.
Name string `json:"name"`

// Namespace of the Kubernetes Service resource.
// +optional
Namespace string `json:"namespace,omitempty"`

// Port of the referenced service.
// A port name or port number is required.
// +kubebuilder:validation:XValidation:message="name or number must be defined",rule="has(self.name) || has(self.number)"
Port netv1.ServiceBackendPort `json:"port"`
}

// OIDCConfigStatus is the OIDC configuration status.
type OIDCConfigStatus struct {
// Issuer is the OIDC issuer for accessing the exposed APIPortal WebUI.
Expand All @@ -112,13 +86,6 @@ type APIPortalStatus struct {
Version string `json:"version,omitempty"`
SyncedAt *metav1.Time `json:"syncedAt,omitempty"`

// URLs are the URLs for accessing the APIPortal WebUI.
URLs string `json:"urls"`

// Domains are the domains for accessing the exposed APIPortal.
// +optional
Domains []string `json:"domains,omitempty"`

// OIDC is the OIDC configuration for accessing the exposed APIPortal WebUI.
// +optional
OIDC *OIDCConfigStatus `json:"oidc,omitempty"`
Expand Down
66 changes: 9 additions & 57 deletions pkg/apis/hub/v1alpha1/crd/hub.traefik.io_apiportals.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,73 +42,30 @@ spec:
description:
description: Description of the APIPortal.
type: string
domains:
description: Domains are the domains under which the portal will be
exposed.
title:
description: Title is the public facing name of the APIPortal.
type: string
trustedDomains:
description: TrustedDomains are the domains that are trusted by the
OAuth 2.0 authorization server.
items:
description: Domain is the domain name under which an APIPortal
will be exposed.
maxLength: 253
type: string
x-kubernetes-validations:
- message: domain must be a valid domain name
rule: self.matches(r"""([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]""")
maxItems: 20
minItems: 1
type: array
x-kubernetes-validations:
- message: duplicate domains
rule: self.all(x, self.exists_one(y, y == x))
title:
description: Title is the public facing name of the APIPortal.
type: string
ui:
description: UI holds the UI customization options.
properties:
logoUrl:
description: LogoURL is the public URL of the logo.
type: string
service:
description: Service defines a Kubernetes Service exposing a custom
UI.
properties:
name:
description: Name of the Kubernetes Service resource.
type: string
namespace:
description: Namespace of the Kubernetes Service resource.
type: string
port:
description: Port of the referenced service. A port name or
port number is required.
properties:
name:
description: Name is the name of the port on the Service.
This is a mutually exclusive setting with "Number".
type: string
number:
description: Number is the numerical port number (e.g.
80) on the Service. This is a mutually exclusive setting
with "Name".
format: int32
type: integer
type: object
x-kubernetes-validations:
- message: name or number must be defined
rule: has(self.name) || has(self.number)
required:
- name
- port
type: object
type: object
required:
- trustedDomains
type: object
status:
description: The current status of this APIPortal.
properties:
domains:
description: Domains are the domains for accessing the exposed APIPortal.
items:
type: string
type: array
hash:
description: Hash is a hash representing the APIPortal.
type: string
Expand All @@ -132,13 +89,8 @@ spec:
syncedAt:
format: date-time
type: string
urls:
description: URLs are the URLs for accessing the APIPortal WebUI.
type: string
version:
type: string
required:
- urls
type: object
type: object
served: true
Expand Down
35 changes: 4 additions & 31 deletions pkg/apis/hub/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

97 changes: 18 additions & 79 deletions pkg/validation/v1alpha1/portal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ kind: APIPortal
metadata:
name: my-portal
namespace: default
spec: {}`),
spec:
trustedDomains: ["example.com"]`),
},
{
desc: "valid: full",
Expand All @@ -48,15 +49,9 @@ metadata:
spec:
title: title
description: description
domains:
- example.com
trustedDomains: ["example.com"]
ui:
logoUrl: https://example.com/logo.png
service:
name: my-service
namespace: default
port:
number: 8080
`),
},
{
Expand All @@ -65,7 +60,9 @@ spec:
apiVersion: hub.traefik.io/v1alpha1
kind: APIPortal
metadata:
name: my-portal`),
name: my-portal
spec:
trustedDomains: ["example.com"]`),
wantErrs: field.ErrorList{{Type: field.ErrorTypeRequired, Field: "metadata.namespace", BadValue: ""}},
},
{
Expand All @@ -76,7 +73,8 @@ kind: APIPortal
metadata:
name: .non-dns-compliant-portal
namespace: default
spec: {}`),
spec:
trustedDomains: ["example.com"]`),
wantErrs: field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "metadata.name", BadValue: ".non-dns-compliant-portal", Detail: "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"}},
},
{
Expand All @@ -87,7 +85,8 @@ kind: APIPortal
metadata:
name: ""
namespace: default
spec: {}`),
spec:
trustedDomains: ["example.com"]`),
wantErrs: field.ErrorList{{Type: field.ErrorTypeRequired, Field: "metadata.name", BadValue: "", Detail: "name or generateName is required"}},
},
{
Expand All @@ -98,92 +97,32 @@ kind: APIPortal
metadata:
name: portal-with-a-way-toooooooooooooooooooooooooooooooooooooo-long-name
namespace: default
spec: {}`),
wantErrs: field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "metadata.name", BadValue: "portal-with-a-way-toooooooooooooooooooooooooooooooooooooo-long-name", Detail: "must be no more than 63 characters"}},
},
{
desc: "empty custom domain",
manifest: []byte(`
apiVersion: hub.traefik.io/v1alpha1
kind: APIPortal
metadata:
name: my-portal
namespace: default
spec:
domains:
- ""`),
wantErrs: field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "spec.domains[0]", BadValue: "string", Detail: "domain must be a valid domain name"}},
},
{
desc: "invalid custom domain",
manifest: []byte(`
apiVersion: hub.traefik.io/v1alpha1
kind: APIPortal
metadata:
name: my-portal
namespace: default
spec:
domains:
- example..com`),
wantErrs: field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "spec.domains[0]", BadValue: "string", Detail: "domain must be a valid domain name"}},
},
{
desc: "duplicated custom domain",
manifest: []byte(`
apiVersion: hub.traefik.io/v1alpha1
kind: APIPortal
metadata:
name: my-portal
namespace: default
spec:
domains:
- example.com
- example.com`),
wantErrs: field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "spec.domains", BadValue: "array", Detail: "duplicate domains"}},
},
{
desc: "missing custom ui service name",
manifest: []byte(`
apiVersion: hub.traefik.io/v1alpha1
kind: APIPortal
metadata:
name: my-portal
namespace: default
spec:
ui:
service:
port:
number: 8080`),
wantErrs: field.ErrorList{{Type: field.ErrorTypeRequired, Field: "spec.ui.service.name", BadValue: ""}},
trustedDomains: ["example.com"]`),
wantErrs: field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "metadata.name", BadValue: "portal-with-a-way-toooooooooooooooooooooooooooooooooooooo-long-name", Detail: "must be no more than 63 characters"}},
},
{
desc: "missing custom ui service port",
desc: "missing trustedDomains",
manifest: []byte(`
apiVersion: hub.traefik.io/v1alpha1
kind: APIPortal
metadata:
name: my-portal
namespace: default
spec:
ui:
service:
name: my-service`),
wantErrs: field.ErrorList{{Type: field.ErrorTypeRequired, Field: "spec.ui.service.port", BadValue: ""}},
spec: {}`),
wantErrs: field.ErrorList{{Type: field.ErrorTypeRequired, Field: "spec.trustedDomains", BadValue: ""}},
},
{
desc: "custom ui service port must have a name or number",
desc: "empty trustedDomains",
manifest: []byte(`
apiVersion: hub.traefik.io/v1alpha1
kind: APIPortal
metadata:
name: my-portal
namespace: default
spec:
ui:
service:
name: my-service
port: {}`),
wantErrs: field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "spec.ui.service.port", BadValue: "object", Detail: "name or number must be defined"}},
trustedDomains: []`),
wantErrs: field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "spec.trustedDomains", BadValue: int64(0), Detail: "spec.trustedDomains in body should have at least 1 items"}},
},
}

Expand Down

0 comments on commit 0e3f0e9

Please sign in to comment.