From 4c4bc88a5b9f5b8a5565fe4dc84a1d8a97d121ab Mon Sep 17 00:00:00 2001 From: John Long Date: Wed, 24 Jan 2024 13:24:04 -0500 Subject: [PATCH] vault cleanup, added type to release policy --- .../kubefox.xigxog.io_appdeployments.yaml | 2 +- api/crds/kubefox.xigxog.io_environments.yaml | 45 ++- .../kubefox.xigxog.io_releasemanifests.yaml | 34 +- ...kubefox.xigxog.io_virtualenvironments.yaml | 33 +- api/data_types.go | 16 +- api/kubernetes/types.go | 14 +- .../v1alpha1/app_deployment_types.go | 16 +- api/kubernetes/v1alpha1/environment_types.go | 52 ++-- api/kubernetes/v1alpha1/groupversion_info.go | 14 +- api/kubernetes/v1alpha1/http_adapter_types.go | 14 +- api/kubernetes/v1alpha1/platform_types.go | 14 +- .../v1alpha1/release_manifest_types.go | 49 ++- api/kubernetes/v1alpha1/virtual_env_types.go | 108 ++++--- .../v1alpha1/zz_generated.deepcopy.go | 119 +++---- api/types.go | 14 +- api/vars.go | 21 +- components/bootstrap/main.go | 78 ++--- .../controller/app_deployment_controller.go | 14 +- .../operator/controller/component_mgr_test.go | 14 +- .../controller/environment_controller.go | 14 +- .../controller/platform_controller.go | 292 ++++++++---------- components/operator/controller/vars.go | 14 +- .../controller/virtual_env_controller.go | 132 ++++---- components/operator/main.go | 33 +- components/operator/templates/helpers.tpl | 5 +- .../instance/validating-webhook.yaml | 1 + .../templates/platform/configmap-env.yaml | 1 - components/operator/templates/renderer.go | 2 - components/operator/templates/types.go | 12 - components/operator/vault/client.go | 111 +------ .../webhook/app_deployment_webhook.go | 22 +- .../operator/webhook/immutable_webhook.go | 33 +- components/operator/webhook/index_webhook.go | 24 +- .../operator/webhook/platform_webhook.go | 22 +- .../operator/webhook/secrets_webhook.go | 35 +-- docs/quickstart.md | 2 +- docs/reference/conditions.md | 8 +- docs/reference/kubernetes-crds.md | 68 ++-- .../kubefox/hack/environments/prod.yaml | 4 +- .../kubefox/hack/environments/qa.yaml | 5 +- vault/client.go | 242 +++++++++++++++ 41 files changed, 906 insertions(+), 847 deletions(-) create mode 100644 vault/client.go diff --git a/api/crds/kubefox.xigxog.io_appdeployments.yaml b/api/crds/kubefox.xigxog.io_appdeployments.yaml index 1884691..8317737 100644 --- a/api/crds/kubefox.xigxog.io_appdeployments.yaml +++ b/api/crds/kubefox.xigxog.io_appdeployments.yaml @@ -29,7 +29,7 @@ spec: - jsonPath: .spec.appName name: App type: string - - jsonPath: .spec.Version + - jsonPath: .spec.version name: Version type: string - jsonPath: .status.conditions[?(@.type=='Available')].status diff --git a/api/crds/kubefox.xigxog.io_environments.yaml b/api/crds/kubefox.xigxog.io_environments.yaml index 6774043..c3bf402 100644 --- a/api/crds/kubefox.xigxog.io_environments.yaml +++ b/api/crds/kubefox.xigxog.io_environments.yaml @@ -24,14 +24,7 @@ spec: singular: environment scope: Cluster versions: - - additionalPrinterColumns: - - jsonPath: .spec.releasePolicy.versionRequired - name: Version Required - type: boolean - - jsonPath: .spec.releasePolicy.pendingDeadlineSeconds - name: Pending Deadline - type: integer - name: v1alpha1 + - name: v1alpha1 schema: openAPIV3Schema: properties: @@ -85,40 +78,42 @@ spec: properties: releasePolicy: properties: + activationDeadlineSeconds: + default: 300 + description: If the pending Release cannot be activated before + the activation deadline it will be considered failed and the + Release will automatically rolled back to the current active + Release. Pointer is used to distinguish between not set and + false. + minimum: 3 + type: integer historyLimits: properties: ageDays: description: Maximum age of the Release to keep in history. Once the limit is reached the oldest Release in history will be deleted. Age is based on archiveTime. Set to 0 to - disable. + disable. Pointer is used to distinguish between not set + and false. minimum: 0 type: integer count: default: 10 description: Maximum number of Releases to keep in history. Once the limit is reached the oldest Release in history - will be deleted. Age is based on archiveTime. + will be deleted. Age is based on archiveTime. Pointer is + used to distinguish between not set and false. minimum: 0 type: integer type: object - pendingDeadlineSeconds: - default: 300 - description: If the pending Request cannot be activated before - the deadline it will be considered failed. If the Release becomes - available for activation after the deadline has been exceeded, - it will not be activated. - minimum: 3 - type: integer - versionRequired: - default: true - description: If true '.spec.release.appDeployment.version' is - required. Pointer is used to distinguish between not set and - false. - type: boolean + type: + default: Stable + enum: + - Stable + - Testing + type: string type: object type: object type: object served: true storage: true - subresources: {} diff --git a/api/crds/kubefox.xigxog.io_releasemanifests.yaml b/api/crds/kubefox.xigxog.io_releasemanifests.yaml index a316c1f..2c2f51e 100644 --- a/api/crds/kubefox.xigxog.io_releasemanifests.yaml +++ b/api/crds/kubefox.xigxog.io_releasemanifests.yaml @@ -26,13 +26,13 @@ spec: scope: Namespaced versions: - additionalPrinterColumns: - - jsonPath: .spec.id + - jsonPath: .spec.releaseId name: Id type: string - - jsonPath: .spec.virtualEnvironment.name + - jsonPath: .spec.environment.name name: Environment type: string - - jsonPath: .spec.virtualEnvironment.environment + - jsonPath: .spec.virtualEnvironment.name name: VirtualEnvironment type: string name: v1alpha1 @@ -277,14 +277,30 @@ spec: type: object minProperties: 1 type: object - id: + environment: + properties: + name: + minLength: 1 + type: string + resourceVersion: + minLength: 1 + type: string + uid: + description: UID is a type that holds unique ID values, including + UUIDs. Because we don't ONLY use UUIDs, this is an alias to + string. Being a type captures intent and helps make sure that + UIDs and names do not get conflated. + type: string + required: + - name + - resourceVersion + - uid + type: object + releaseId: minLength: 1 type: string virtualEnvironment: properties: - environment: - minLength: 1 - type: string name: minLength: 1 type: string @@ -298,14 +314,14 @@ spec: UIDs and names do not get conflated. type: string required: - - environment - name - resourceVersion - uid type: object required: - apps - - id + - environment + - releaseId - virtualEnvironment type: object required: diff --git a/api/crds/kubefox.xigxog.io_virtualenvironments.yaml b/api/crds/kubefox.xigxog.io_virtualenvironments.yaml index 461d746..6368ad7 100644 --- a/api/crds/kubefox.xigxog.io_virtualenvironments.yaml +++ b/api/crds/kubefox.xigxog.io_virtualenvironments.yaml @@ -26,6 +26,9 @@ spec: scope: Namespaced versions: - additionalPrinterColumns: + - jsonPath: .spec.environment + name: Environment + type: string - jsonPath: .status.activeRelease.releaseManifest name: Manifest type: string @@ -117,15 +120,19 @@ spec: type: object minProperties: 1 type: object - id: - minLength: 1 - type: string required: - apps - - id type: object releasePolicy: properties: + activationDeadlineSeconds: + description: If the pending Release cannot be activated before + the activation deadline it will be considered failed and the + Release will automatically rolled back to the current active + Release. Pointer is used to distinguish between not set and + false. + minimum: 3 + type: integer historyLimits: properties: ageDays: @@ -144,19 +151,11 @@ spec: minimum: 0 type: integer type: object - pendingDeadlineSeconds: - description: If the pending Request cannot be activated before - the deadline it will be considered failed. If the Release becomes - available for activation after the deadline has been exceeded, - it will not be activated. Pointer is used to distinguish between - not set and false. - minimum: 3 - type: integer - versionRequired: - description: If true '.spec.release.appDeployment.version' is - required. Pointer is used to distinguish between not set and - false. - type: boolean + type: + enum: + - Stable + - Testing + type: string type: object required: - environment diff --git a/api/data_types.go b/api/data_types.go index 6ea137c..6fa0415 100644 --- a/api/data_types.go +++ b/api/data_types.go @@ -8,8 +8,6 @@ package api -import "fmt" - // +kubebuilder:object:generate=false type DataProvider interface { Object @@ -19,9 +17,10 @@ type DataProvider interface { } type DataKey struct { + Instance string + Namespace string Kind string Name string - Namespace string } type Data struct { @@ -63,12 +62,5 @@ func (lhs Data) MergeInto(rhs *Data) *Data { return rhs } -func (k DataKey) Path(instance string) string { - if k.Namespace == "" { - return fmt.Sprintf("kubefox/instance/%s/cluster/data/%s/%s", - instance, k.Kind, k.Name) - } else { - return fmt.Sprintf("kubefox/instance/%s/namespace/%s/data/%s/%s", - instance, k.Namespace, k.Kind, k.Name) - } -} +// POST /:secret-mount-path/data/:path +// DELETE /:secret-mount-path/metadata/:path diff --git a/api/kubernetes/types.go b/api/kubernetes/types.go index 4569d86..800eafa 100644 --- a/api/kubernetes/types.go +++ b/api/kubernetes/types.go @@ -1,10 +1,10 @@ -/* -Copyright © 2023 XigXog - -This Source Code Form is subject to the terms of the Mozilla Public License, -v2.0. If a copy of the MPL was not distributed with this file, You can obtain -one at https://mozilla.org/MPL/2.0/. -*/ +// Copyright 2023 XigXog +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 // +kubebuilder:object:generate=true package kubernetes diff --git a/api/kubernetes/v1alpha1/app_deployment_types.go b/api/kubernetes/v1alpha1/app_deployment_types.go index 25d7816..7f73505 100644 --- a/api/kubernetes/v1alpha1/app_deployment_types.go +++ b/api/kubernetes/v1alpha1/app_deployment_types.go @@ -1,10 +1,10 @@ -/* -Copyright © 2023 XigXog - -This Source Code Form is subject to the terms of the Mozilla Public License, -v2.0. If a copy of the MPL was not distributed with this file, You can obtain -one at https://mozilla.org/MPL/2.0/. -*/ +// Copyright 2023 XigXog +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 package v1alpha1 @@ -78,7 +78,7 @@ type AppDeploymentDetails struct { // +kubebuilder:subresource:status // +kubebuilder:resource:path=appdeployments,shortName=appdep;app // +kubebuilder:printcolumn:name="App",type=string,JSONPath=`.spec.appName` -// +kubebuilder:printcolumn:name="Version",type=string,JSONPath=`.spec.Version` +// +kubebuilder:printcolumn:name="Version",type=string,JSONPath=`.spec.version` // +kubebuilder:printcolumn:name="Available",type=string,JSONPath=`.status.conditions[?(@.type=='Available')].status` // +kubebuilder:printcolumn:name="Reason",type=string,JSONPath=`.status.conditions[?(@.type=='Available')].reason` // +kubebuilder:printcolumn:name="Progressing",type=string,JSONPath=`.status.conditions[?(@.type=='Progressing')].status` diff --git a/api/kubernetes/v1alpha1/environment_types.go b/api/kubernetes/v1alpha1/environment_types.go index c312775..fa47fa7 100644 --- a/api/kubernetes/v1alpha1/environment_types.go +++ b/api/kubernetes/v1alpha1/environment_types.go @@ -1,10 +1,10 @@ -/* -Copyright © 2023 XigXog - -This Source Code Form is subject to the terms of the Mozilla Public License, -v2.0. If a copy of the MPL was not distributed with this file, You can obtain -one at https://mozilla.org/MPL/2.0/. -*/ +// Copyright 2023 XigXog +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 package v1alpha1 @@ -18,47 +18,43 @@ type EnvironmentSpec struct { } type EnvReleasePolicy struct { - // +kubebuilder:validation:Minimum=3 - // +kubebuilder:default=300 + // +kubebuilder:validation:Enum=Stable;Testing + // +kubebuilder:default=Stable - // If the pending Request cannot be activated before the deadline it will be - // considered failed. If the Release becomes available for activation after - // the deadline has been exceeded, it will not be activated. - PendingDeadlineSeconds uint `json:"pendingDeadlineSeconds,omitempty"` + Type api.ReleaseType `json:"type,omitempty"` - // +kubebuilder:validation:Optional - // +kubebuilder:default=true - - // If true '.spec.release.appDeployment.version' is required. Pointer is - // used to distinguish between not set and false. - VersionRequired *bool `json:"versionRequired"` + // +kubebuilder:validation:Minimum=3 + // +kubebuilder:default=300 - // +kubebuilder:validation:Optional + // If the pending Release cannot be activated before the activation deadline + // it will be considered failed and the Release will automatically rolled + // back to the current active Release. Pointer is used to distinguish + // between not set and false. + ActivationDeadlineSeconds *uint `json:"activationDeadlineSeconds,omitempty"` - HistoryLimits EnvReleaseHistoryLimits `json:"historyLimits"` + HistoryLimits EnvHistoryLimits `json:"historyLimits,omitempty"` } -type EnvReleaseHistoryLimits struct { +type EnvHistoryLimits struct { // +kubebuilder:validation:Minimum=0 // +kubebuilder:default=10 // Maximum number of Releases to keep in history. Once the limit is reached // the oldest Release in history will be deleted. Age is based on - // archiveTime. - Count uint `json:"count,omitempty"` + // archiveTime. Pointer is used to distinguish between not set and false. + Count *uint `json:"count,omitempty"` // +kubebuilder:validation:Minimum=0 // Maximum age of the Release to keep in history. Once the limit is reached // the oldest Release in history will be deleted. Age is based on - // archiveTime. Set to 0 to disable. - AgeDays uint `json:"ageDays,omitempty"` + // archiveTime. Set to 0 to disable. Pointer is used to distinguish between + // not set and false. + AgeDays *uint `json:"ageDays,omitempty"` } // +kubebuilder:object:root=true // +kubebuilder:resource:path=environments,scope=Cluster,shortName=env -// +kubebuilder:printcolumn:name="Version Required",type=boolean,JSONPath=`.spec.releasePolicy.versionRequired` -// +kubebuilder:printcolumn:name="Pending Deadline",type=integer,JSONPath=`.spec.releasePolicy.pendingDeadlineSeconds` type Environment struct { metav1.TypeMeta `json:",inline"` diff --git a/api/kubernetes/v1alpha1/groupversion_info.go b/api/kubernetes/v1alpha1/groupversion_info.go index 8c7b6de..2553ca7 100644 --- a/api/kubernetes/v1alpha1/groupversion_info.go +++ b/api/kubernetes/v1alpha1/groupversion_info.go @@ -1,10 +1,10 @@ -/* -Copyright © 2023 XigXog - -This Source Code Form is subject to the terms of the Mozilla Public License, -v2.0. If a copy of the MPL was not distributed with this file, You can obtain -one at https://mozilla.org/MPL/2.0/. -*/ +// Copyright 2023 XigXog +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 // Package v1alpha1 contains API Schema definitions for the k8s v1alpha1 API group // +kubebuilder:object:generate=true diff --git a/api/kubernetes/v1alpha1/http_adapter_types.go b/api/kubernetes/v1alpha1/http_adapter_types.go index 0129849..f3e5e5e 100644 --- a/api/kubernetes/v1alpha1/http_adapter_types.go +++ b/api/kubernetes/v1alpha1/http_adapter_types.go @@ -1,10 +1,10 @@ -/* -Copyright © 2023 XigXog - -This Source Code Form is subject to the terms of the Mozilla Public License, -v2.0. If a copy of the MPL was not distributed with this file, You can obtain -one at https://mozilla.org/MPL/2.0/. -*/ +// Copyright 2023 XigXog +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 package v1alpha1 diff --git a/api/kubernetes/v1alpha1/platform_types.go b/api/kubernetes/v1alpha1/platform_types.go index 84ddb9e..b534bb7 100644 --- a/api/kubernetes/v1alpha1/platform_types.go +++ b/api/kubernetes/v1alpha1/platform_types.go @@ -1,10 +1,10 @@ -/* -Copyright © 2023 XigXog - -This Source Code Form is subject to the terms of the Mozilla Public License, -v2.0. If a copy of the MPL was not distributed with this file, You can obtain -one at https://mozilla.org/MPL/2.0/. -*/ +// Copyright 2023 XigXog +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 package v1alpha1 diff --git a/api/kubernetes/v1alpha1/release_manifest_types.go b/api/kubernetes/v1alpha1/release_manifest_types.go index f9ee351..c6c2edd 100644 --- a/api/kubernetes/v1alpha1/release_manifest_types.go +++ b/api/kubernetes/v1alpha1/release_manifest_types.go @@ -1,10 +1,10 @@ -/* -Copyright © 2023 XigXog - -This Source Code Form is subject to the terms of the Mozilla Public License, -v2.0. If a copy of the MPL was not distributed with this file, You can obtain -one at https://mozilla.org/MPL/2.0/. -*/ +// Copyright 2023 XigXog +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 package v1alpha1 @@ -18,11 +18,15 @@ type ReleaseManifestSpec struct { // +kubebuilder:validation:Required // +kubebuilder:validation:MinLength=1 - Id string `json:"id"` + ReleaseId string `json:"releaseId"` + + // +kubebuilder:validation:Required + + Environment ReleaseManifestRef `json:"environment"` // +kubebuilder:validation:Required - VirtualEnvironment ReleaseManifestEnv `json:"virtualEnvironment"` + VirtualEnvironment ReleaseManifestRef `json:"virtualEnvironment"` // +kubebuilder:validation:Required // +kubebuilder:validation:MinProperties=1 @@ -30,7 +34,7 @@ type ReleaseManifestSpec struct { Apps map[string]*ReleaseManifestApp `json:"apps"` } -type ReleaseManifestEnv struct { +type ReleaseManifestRef struct { // +kubebuilder:validation:Required UID types.UID `json:"uid"` @@ -44,11 +48,6 @@ type ReleaseManifestEnv struct { // +kubebuilder:validation:MinLength=1 ResourceVersion string `json:"resourceVersion"` - - // +kubebuilder:validation:Required - // +kubebuilder:validation:MinLength=1 - - Environment string `json:"environment"` } type ReleaseManifestApp struct { @@ -62,19 +61,7 @@ type ReleaseManifestApp struct { } type ReleaseManifestAppDep struct { - // +kubebuilder:validation:Required - - UID types.UID `json:"uid"` - - // +kubebuilder:validation:Required - // +kubebuilder:validation:MinLength=1 - - Name string `json:"name"` - - // +kubebuilder:validation:Required - // +kubebuilder:validation:MinLength=1 - - ResourceVersion string `json:"resourceVersion"` + ReleaseManifestRef `json:",inline"` // +kubebuilder:validation:Required @@ -84,9 +71,9 @@ type ReleaseManifestAppDep struct { // +kubebuilder:object:root=true // +kubebuilder:subresource:status // +kubebuilder:resource:path=releasemanifests,shortName=manifest;rm -// +kubebuilder:printcolumn:name="Id",type=string,JSONPath=`.spec.id` -// +kubebuilder:printcolumn:name="Environment",type=string,JSONPath=`.spec.virtualEnvironment.name` -// +kubebuilder:printcolumn:name="VirtualEnvironment",type=string,JSONPath=`.spec.virtualEnvironment.environment` +// +kubebuilder:printcolumn:name="Id",type=string,JSONPath=`.spec.releaseId` +// +kubebuilder:printcolumn:name="Environment",type=string,JSONPath=`.spec.environment.name` +// +kubebuilder:printcolumn:name="VirtualEnvironment",type=string,JSONPath=`.spec.virtualEnvironment.name` type ReleaseManifest struct { metav1.TypeMeta `json:",inline"` diff --git a/api/kubernetes/v1alpha1/virtual_env_types.go b/api/kubernetes/v1alpha1/virtual_env_types.go index 526e1b0..a8cabc9 100644 --- a/api/kubernetes/v1alpha1/virtual_env_types.go +++ b/api/kubernetes/v1alpha1/virtual_env_types.go @@ -1,10 +1,10 @@ -/* -Copyright © 2023 XigXog - -This Source Code Form is subject to the terms of the Mozilla Public License, -v2.0. If a copy of the MPL was not distributed with this file, You can obtain -one at https://mozilla.org/MPL/2.0/. -*/ +// Copyright 2023 XigXog +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 package v1alpha1 @@ -28,11 +28,6 @@ type VirtualEnvironmentSpec struct { } type Release struct { - // +kubebuilder:validation:Required - // +kubebuilder:validation:MinLength=1 - - Id string `json:"id"` - // +kubebuilder:validation:Required // +kubebuilder:validation:MinProperties=1 @@ -52,22 +47,22 @@ type ReleaseApp struct { } type ReleasePolicy struct { - // +kubebuilder:validation:Minimum=3 + // +kubebuilder:validation:Enum=Stable;Testing + + Type api.ReleaseType `json:"type,omitempty"` - // If the pending Request cannot be activated before the deadline it will be - // considered failed. If the Release becomes available for activation after - // the deadline has been exceeded, it will not be activated. Pointer is used - // to distinguish between not set and false. - PendingDeadlineSeconds *uint `json:"pendingDeadlineSeconds,omitempty"` + // +kubebuilder:validation:Minimum=3 - // If true '.spec.release.appDeployment.version' is required. Pointer is - // used to distinguish between not set and false. - VersionRequired *bool `json:"versionRequired,omitempty"` + // If the pending Release cannot be activated before the activation deadline + // it will be considered failed and the Release will automatically rolled + // back to the current active Release. Pointer is used to distinguish + // between not set and false. + ActivationDeadlineSeconds *uint `json:"activationDeadlineSeconds,omitempty"` - HistoryLimits *VirtEnvHistoryLimits `json:"historyLimits,omitempty"` + HistoryLimits *HistoryLimits `json:"historyLimits,omitempty"` } -type VirtEnvHistoryLimits struct { +type HistoryLimits struct { // +kubebuilder:validation:Minimum=0 // Maximum number of Releases to keep in history. Once the limit is reached @@ -87,6 +82,11 @@ type VirtEnvHistoryLimits struct { type ReleaseStatus struct { Release `json:",inline"` + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinLength=1 + + Id string `json:"id"` + ReleaseManifest string `json:"releaseManifest,omitempty"` // Time at which the VirtualEnvironment was updated to use the Release. RequestTime metav1.Time `json:"requestTime,omitempty"` @@ -130,6 +130,7 @@ type VirtualEnvironmentStatus struct { // +kubebuilder:object:root=true // +kubebuilder:subresource:status // +kubebuilder:resource:path=virtualenvironments,shortName=virtenv;ve +// +kubebuilder:printcolumn:name="Environment",type=string,JSONPath=`.spec.environment` // +kubebuilder:printcolumn:name="Manifest",type=string,JSONPath=`.status.activeRelease.releaseManifest` // +kubebuilder:printcolumn:name="Available",type=string,JSONPath=`.status.conditions[?(@.type=='ActiveReleaseAvailable')].status` // +kubebuilder:printcolumn:name="Reason",type=string,JSONPath=`.status.conditions[?(@.type=='ActiveReleaseAvailable')].reason` @@ -169,12 +170,12 @@ func (d *VirtualEnvironment) GetDataKey() api.DataKey { func (p *ReleasePolicy) GetPendingDeadline() time.Duration { if p == nil { - return api.DefaultReleasePendingDeadlineSeconds * time.Second + return api.DefaultReleaseActivationDeadlineSeconds * time.Second } - secs := p.PendingDeadlineSeconds + secs := p.ActivationDeadlineSeconds if secs == nil || *secs == 0 { - return api.DefaultReleasePendingDeadlineSeconds * time.Second + return api.DefaultReleaseActivationDeadlineSeconds * time.Second } return time.Duration(*secs * uint(time.Second)) @@ -191,37 +192,52 @@ func (ve *VirtualEnvironment) GetReleasePendingDuration() time.Duration { } func (ve *VirtualEnvironment) GetReleasePolicy(env *Environment) *ReleasePolicy { - p := ve.Spec.ReleasePolicy.DeepCopy() - if p == nil { - p = &ReleasePolicy{} + envPol := env.Spec.ReleasePolicy + vePol := ve.Spec.ReleasePolicy.DeepCopy() + if vePol == nil { + vePol = &ReleasePolicy{} } - if p.PendingDeadlineSeconds == nil { - p.PendingDeadlineSeconds = - &env.Spec.ReleasePolicy.PendingDeadlineSeconds + if vePol.ActivationDeadlineSeconds == nil { + if envPol.ActivationDeadlineSeconds == nil || + *envPol.ActivationDeadlineSeconds == 0 { + i := uint(api.DefaultReleaseActivationDeadlineSeconds) + vePol.ActivationDeadlineSeconds = &i + } else { + vePol.ActivationDeadlineSeconds = envPol.ActivationDeadlineSeconds + } } - if p.VersionRequired == nil { - if env.Spec.ReleasePolicy.VersionRequired == nil { - p.VersionRequired = api.True + if vePol.Type == "" { + if envPol.Type == "" { + vePol.Type = api.ReleaseTypeStable } else { - p.VersionRequired = - env.Spec.ReleasePolicy.VersionRequired + vePol.Type = envPol.Type } } - if p.HistoryLimits == nil { - p.HistoryLimits = &VirtEnvHistoryLimits{} + if vePol.HistoryLimits == nil { + vePol.HistoryLimits = &HistoryLimits{} } - if p.HistoryLimits.Count == nil { - p.HistoryLimits.Count = - &env.Spec.ReleasePolicy.HistoryLimits.Count + if vePol.HistoryLimits.Count == nil { + if envPol.HistoryLimits.Count == nil || + *envPol.HistoryLimits.Count == 0 { + i := uint(api.DefaultReleaseHistoryCountLimit) + vePol.HistoryLimits.Count = &i + } else { + vePol.HistoryLimits.Count = envPol.HistoryLimits.Count + } } - if p.HistoryLimits.AgeDays == nil { - p.HistoryLimits.AgeDays = - &env.Spec.ReleasePolicy.HistoryLimits.AgeDays + if vePol.HistoryLimits.AgeDays == nil { + if envPol.HistoryLimits.AgeDays == nil || + *envPol.HistoryLimits.AgeDays == 0 { + i := uint(api.DefaultReleaseHistoryAgeLimit) + vePol.HistoryLimits.AgeDays = &i + } else { + vePol.HistoryLimits.AgeDays = envPol.HistoryLimits.AgeDays + } } - return p + return vePol } func init() { diff --git a/api/kubernetes/v1alpha1/zz_generated.deepcopy.go b/api/kubernetes/v1alpha1/zz_generated.deepcopy.go index 80c2469..29eba30 100644 --- a/api/kubernetes/v1alpha1/zz_generated.deepcopy.go +++ b/api/kubernetes/v1alpha1/zz_generated.deepcopy.go @@ -215,16 +215,26 @@ func (in *ComponentStatus) DeepCopy() *ComponentStatus { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *EnvReleaseHistoryLimits) DeepCopyInto(out *EnvReleaseHistoryLimits) { +func (in *EnvHistoryLimits) DeepCopyInto(out *EnvHistoryLimits) { *out = *in + if in.Count != nil { + in, out := &in.Count, &out.Count + *out = new(uint) + **out = **in + } + if in.AgeDays != nil { + in, out := &in.AgeDays, &out.AgeDays + *out = new(uint) + **out = **in + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvReleaseHistoryLimits. -func (in *EnvReleaseHistoryLimits) DeepCopy() *EnvReleaseHistoryLimits { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvHistoryLimits. +func (in *EnvHistoryLimits) DeepCopy() *EnvHistoryLimits { if in == nil { return nil } - out := new(EnvReleaseHistoryLimits) + out := new(EnvHistoryLimits) in.DeepCopyInto(out) return out } @@ -232,12 +242,12 @@ func (in *EnvReleaseHistoryLimits) DeepCopy() *EnvReleaseHistoryLimits { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EnvReleasePolicy) DeepCopyInto(out *EnvReleasePolicy) { *out = *in - if in.VersionRequired != nil { - in, out := &in.VersionRequired, &out.VersionRequired - *out = new(bool) + if in.ActivationDeadlineSeconds != nil { + in, out := &in.ActivationDeadlineSeconds, &out.ActivationDeadlineSeconds + *out = new(uint) **out = **in } - out.HistoryLimits = in.HistoryLimits + in.HistoryLimits.DeepCopyInto(&out.HistoryLimits) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvReleasePolicy. @@ -486,6 +496,31 @@ func (in *HTTPSrvSpec) DeepCopy() *HTTPSrvSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HistoryLimits) DeepCopyInto(out *HistoryLimits) { + *out = *in + if in.Count != nil { + in, out := &in.Count, &out.Count + *out = new(uint) + **out = **in + } + if in.AgeDays != nil { + in, out := &in.AgeDays, &out.AgeDays + *out = new(uint) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HistoryLimits. +func (in *HistoryLimits) DeepCopy() *HistoryLimits { + if in == nil { + return nil + } + out := new(HistoryLimits) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NATSSpec) DeepCopyInto(out *NATSSpec) { *out = *in @@ -710,6 +745,7 @@ func (in *ReleaseManifestApp) DeepCopy() *ReleaseManifestApp { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ReleaseManifestAppDep) DeepCopyInto(out *ReleaseManifestAppDep) { *out = *in + out.ReleaseManifestRef = in.ReleaseManifestRef in.Spec.DeepCopyInto(&out.Spec) } @@ -723,21 +759,6 @@ func (in *ReleaseManifestAppDep) DeepCopy() *ReleaseManifestAppDep { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ReleaseManifestEnv) DeepCopyInto(out *ReleaseManifestEnv) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReleaseManifestEnv. -func (in *ReleaseManifestEnv) DeepCopy() *ReleaseManifestEnv { - if in == nil { - return nil - } - out := new(ReleaseManifestEnv) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ReleaseManifestList) DeepCopyInto(out *ReleaseManifestList) { *out = *in @@ -770,9 +791,25 @@ func (in *ReleaseManifestList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReleaseManifestRef) DeepCopyInto(out *ReleaseManifestRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReleaseManifestRef. +func (in *ReleaseManifestRef) DeepCopy() *ReleaseManifestRef { + if in == nil { + return nil + } + out := new(ReleaseManifestRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ReleaseManifestSpec) DeepCopyInto(out *ReleaseManifestSpec) { *out = *in + out.Environment = in.Environment out.VirtualEnvironment = in.VirtualEnvironment if in.Apps != nil { in, out := &in.Apps, &out.Apps @@ -805,19 +842,14 @@ func (in *ReleaseManifestSpec) DeepCopy() *ReleaseManifestSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ReleasePolicy) DeepCopyInto(out *ReleasePolicy) { *out = *in - if in.PendingDeadlineSeconds != nil { - in, out := &in.PendingDeadlineSeconds, &out.PendingDeadlineSeconds + if in.ActivationDeadlineSeconds != nil { + in, out := &in.ActivationDeadlineSeconds, &out.ActivationDeadlineSeconds *out = new(uint) **out = **in } - if in.VersionRequired != nil { - in, out := &in.VersionRequired, &out.VersionRequired - *out = new(bool) - **out = **in - } if in.HistoryLimits != nil { in, out := &in.HistoryLimits, &out.HistoryLimits - *out = new(VirtEnvHistoryLimits) + *out = new(HistoryLimits) (*in).DeepCopyInto(*out) } } @@ -864,31 +896,6 @@ func (in *ReleaseStatus) DeepCopy() *ReleaseStatus { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VirtEnvHistoryLimits) DeepCopyInto(out *VirtEnvHistoryLimits) { - *out = *in - if in.Count != nil { - in, out := &in.Count, &out.Count - *out = new(uint) - **out = **in - } - if in.AgeDays != nil { - in, out := &in.AgeDays, &out.AgeDays - *out = new(uint) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtEnvHistoryLimits. -func (in *VirtEnvHistoryLimits) DeepCopy() *VirtEnvHistoryLimits { - if in == nil { - return nil - } - out := new(VirtEnvHistoryLimits) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VirtualEnvironment) DeepCopyInto(out *VirtualEnvironment) { *out = *in diff --git a/api/types.go b/api/types.go index d9616ab..1dbbeb9 100644 --- a/api/types.go +++ b/api/types.go @@ -1,10 +1,10 @@ -/* -Copyright © 2023 XigXog - -This Source Code Form is subject to the terms of the Mozilla Public License, -v2.0. If a copy of the MPL was not distributed with this file, You can obtain -one at https://mozilla.org/MPL/2.0/. -*/ +// Copyright 2023 XigXog +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 // +kubebuilder:object:generate=true package api diff --git a/api/vars.go b/api/vars.go index aa605bd..3e7808c 100644 --- a/api/vars.go +++ b/api/vars.go @@ -30,13 +30,13 @@ var ( // Defaults const ( - DefaultLogFormat = "json" - DefaultLogLevel = "info" - DefaultMaxEventSizeBytes = 5242880 // 5 MiB - DefaultReleaseHistoryAgeLimit = 0 - DefaultReleaseHistoryCountLimit = 10 - DefaultReleasePendingDeadlineSeconds = 300 // 5 mins - DefaultTimeoutSeconds = 30 + DefaultLogFormat = "json" + DefaultLogLevel = "info" + DefaultMaxEventSizeBytes = 5242880 // 5 MiB + DefaultReleaseActivationDeadlineSeconds = 300 // 5 mins + DefaultReleaseHistoryAgeLimit = 0 + DefaultReleaseHistoryCountLimit = 10 + DefaultTimeoutSeconds = 30 ) // Kubernetes Labels @@ -206,6 +206,13 @@ func (c ComponentType) IsAdapter() bool { } } +type ReleaseType string + +const ( + ReleaseTypeStable ReleaseType = "Stable" + ReleaseTypeTesting ReleaseType = "Testing" +) + type FollowRedirects string const ( diff --git a/components/bootstrap/main.go b/components/bootstrap/main.go index 2f97d0e..2a785bf 100644 --- a/components/bootstrap/main.go +++ b/components/bootstrap/main.go @@ -16,11 +16,10 @@ import ( "os" "time" - vapi "github.com/hashicorp/vault/api" - vauth "github.com/hashicorp/vault/api/auth/kubernetes" "github.com/xigxog/kubefox/api" "github.com/xigxog/kubefox/logkf" "github.com/xigxog/kubefox/utils" + "github.com/xigxog/kubefox/vault" ) const ( @@ -28,8 +27,9 @@ const ( ) type Flags struct { - platformVaultName string - compVaultName string + instance string + platformNamespace string + compName string compSvcName string compIP string vaultURL string @@ -42,8 +42,9 @@ var ( ) func main() { - flag.StringVar(&f.platformVaultName, "platform-vault-name", "", "Vault name of Platform. (required)") - flag.StringVar(&f.compVaultName, "component-vault-name", "", "Vault name of Component to bootstrap. (required)") + flag.StringVar(&f.instance, "instance", "", "Name of KubeFox Instance. (required)") + flag.StringVar(&f.platformNamespace, "platform-namespace", "", "Kubernetes Namespace of Platform. (required)") + flag.StringVar(&f.compName, "component", "", "Name of Component to bootstrap. (required)") flag.StringVar(&f.compSvcName, "component-service-name", "", "Service name of Component to bootstrap. (required)") flag.StringVar(&f.compIP, "component-ip", "", "IP address of Component to bootstrap. (required)") flag.StringVar(&f.vaultURL, "vault-url", "", "URL of Vault server. (required)") @@ -51,8 +52,9 @@ func main() { flag.StringVar(&f.logLevel, "log-level", "debug", `Log level; one of ["debug", "info", "warn", "error"].`) flag.Parse() - utils.CheckRequiredFlag("platform-vault-name", f.platformVaultName) - utils.CheckRequiredFlag("component-vault-name", f.compVaultName) + utils.CheckRequiredFlag("instance", f.instance) + utils.CheckRequiredFlag("platform-namespace", f.platformNamespace) + utils.CheckRequiredFlag("component", f.compName) utils.CheckRequiredFlag("component-service-name", f.compSvcName) utils.CheckRequiredFlag("component-ip", f.compIP) utils.CheckRequiredFlag("vault-url", f.vaultURL) @@ -66,7 +68,7 @@ func main() { log.DebugInterface("flags:", f) for retry := 0; retry < 3; retry++ { - log.Infof("generating certificates for component %s, attempt %d of 3", f.compVaultName, retry+1) + log.Infof("generating certificates for component %s, attempt %d of 3", f.compName, retry+1) if err := generateCerts(log); err != nil { log.Errorf("error generating certificates: %v", err) time.Sleep(time.Second * time.Duration(rand.Intn(2)+1)) @@ -83,16 +85,26 @@ func generateCerts(log *logkf.Logger) error { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - c, err := vaultClient(ctx) + key := vault.Key{ + Instance: f.instance, + Namespace: f.platformNamespace, + Component: f.compName, + } + + vaultCli, err := vault.New(vault.ClientOptions{ + Instance: f.instance, + Role: vault.RoleName(key), + URL: f.vaultURL, + CACert: api.PathCACert, + }) if err != nil { return err } - path := fmt.Sprintf("pki/int/platform/%s/issue/%s", f.platformVaultName, f.compVaultName) - - s, err := c.Logical().WriteWithContext(ctx, path, map[string]interface{}{ + path := fmt.Sprintf("%s/%s", vault.PKISubPath(key, "issue"), vault.RoleName(key)) + s, err := vaultCli.Logical().WriteWithContext(ctx, path, map[string]interface{}{ "common_name": fmt.Sprintf("%s.svc", f.compSvcName), - "alt_names": fmt.Sprintf("%s@%s,%s,localhost", f.compVaultName, f.compSvcName, f.compSvcName), + "alt_names": fmt.Sprintf("%s@%s,%s,localhost", f.compName, f.compSvcName, f.compSvcName), "ip_sans": fmt.Sprintf("%s,127.0.0.1", f.compIP), "ttl": TenYears, }) @@ -101,51 +113,17 @@ func generateCerts(log *logkf.Logger) error { } cert := fmt.Sprintf("%s\n%s", s.Data["certificate"], s.Data["issuing_ca"]) - key := fmt.Sprintf("%s", s.Data["private_key"]) + privKey := fmt.Sprintf("%s", s.Data["private_key"]) if err := os.WriteFile(api.PathTLSCert, []byte(cert), 0600); err != nil { return err } log.Infof("wrote tls certificate to %s", api.PathTLSCert) - if err := os.WriteFile(api.PathTLSKey, []byte(key), 0600); err != nil { + if err := os.WriteFile(api.PathTLSKey, []byte(privKey), 0600); err != nil { return err } log.Infof("wrote tls private key to %s", api.PathTLSKey) return nil } - -func vaultClient(ctx context.Context) (*vapi.Client, error) { - cfg := vapi.DefaultConfig() - cfg.Address = f.vaultURL - cfg.MaxRetries = 3 - cfg.HttpClient.Timeout = time.Second * 15 - cfg.ConfigureTLS(&vapi.TLSConfig{ - CACert: api.PathCACert, - }) - - vault, err := vapi.NewClient(cfg) - if err != nil { - return nil, err - } - - b, err := os.ReadFile(api.PathSvcAccToken) - if err != nil { - return nil, err - } - token := vauth.WithServiceAccountToken(string(b)) - auth, err := vauth.NewKubernetesAuth(f.compVaultName, token) - if err != nil { - return nil, err - } - authInfo, err := vault.Auth().Login(ctx, auth) - if err != nil { - return nil, err - } - if authInfo == nil { - return nil, fmt.Errorf("error logging in with kubernetes auth: no auth info was returned") - } - - return vault, nil -} diff --git a/components/operator/controller/app_deployment_controller.go b/components/operator/controller/app_deployment_controller.go index 28c1ffb..b23827e 100644 --- a/components/operator/controller/app_deployment_controller.go +++ b/components/operator/controller/app_deployment_controller.go @@ -1,10 +1,10 @@ -/* -Copyright © 2023 XigXog - -This Source Code Form is subject to the terms of the Mozilla Public License, -v2.0. If a copy of the MPL was not distributed with this file, You can obtain -one at https://mozilla.org/MPL/2.0/. -*/ +// Copyright 2023 XigXog +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 package controller diff --git a/components/operator/controller/component_mgr_test.go b/components/operator/controller/component_mgr_test.go index 6b08c0e..40df563 100644 --- a/components/operator/controller/component_mgr_test.go +++ b/components/operator/controller/component_mgr_test.go @@ -1,10 +1,10 @@ -/* -Copyright © 2023 XigXog - -This Source Code Form is subject to the terms of the Mozilla Public License, -v2.0. If a copy of the MPL was not distributed with this file, You can obtain -one at https://mozilla.org/MPL/2.0/. -*/ +// Copyright 2023 XigXog +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 package controller diff --git a/components/operator/controller/environment_controller.go b/components/operator/controller/environment_controller.go index b0b7819..949c185 100644 --- a/components/operator/controller/environment_controller.go +++ b/components/operator/controller/environment_controller.go @@ -1,10 +1,10 @@ -/* -Copyright © 2023 XigXog - -This Source Code Form is subject to the terms of the Mozilla Public License, -v2.0. If a copy of the MPL was not distributed with this file, You can obtain -one at https://mozilla.org/MPL/2.0/. -*/ +// Copyright 2023 XigXog +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 package controller diff --git a/components/operator/controller/platform_controller.go b/components/operator/controller/platform_controller.go index ffd13fe..3f2be09 100644 --- a/components/operator/controller/platform_controller.go +++ b/components/operator/controller/platform_controller.go @@ -1,22 +1,21 @@ -/* -Copyright © 2023 XigXog - -This Source Code Form is subject to the terms of the Mozilla Public License, -v2.0. If a copy of the MPL was not distributed with this file, You can obtain -one at https://mozilla.org/MPL/2.0/. -*/ +// Copyright 2023 XigXog +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 package controller import ( "context" "fmt" - "os" + "strings" "sync" "time" vapi "github.com/hashicorp/vault/api" - vauth "github.com/hashicorp/vault/api/auth/kubernetes" appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -30,9 +29,11 @@ import ( "github.com/xigxog/kubefox/build" "github.com/xigxog/kubefox/components/operator/defaults" "github.com/xigxog/kubefox/components/operator/templates" + opvault "github.com/xigxog/kubefox/components/operator/vault" "github.com/xigxog/kubefox/core" "github.com/xigxog/kubefox/k8s" "github.com/xigxog/kubefox/logkf" + "github.com/xigxog/kubefox/vault" ) const ( @@ -55,8 +56,7 @@ type PlatformReconciler struct { setupMap map[string]bool - vClient *vapi.Client - log *logkf.Logger + log *logkf.Logger mutex sync.Mutex } @@ -145,7 +145,7 @@ func (r *PlatformReconciler) reconcile(ctx context.Context, platform *v1alpha1.P if platform.Spec.Events.MaxSize.IsZero() { maxEventSize = api.DefaultMaxEventSizeBytes } - baseTD := &TemplateData{ + platformTD := &TemplateData{ Data: templates.Data{ Instance: templates.Instance{ Name: r.Instance, @@ -169,8 +169,7 @@ func (r *PlatformReconciler) reconcile(ctx context.Context, platform *v1alpha1.P }, } - setup := r.isSetup(pKey) - if setup { + if r.isSetup(pKey) { r.log.Debugf("Platform '%s' already setup ", pKey) } else { @@ -180,17 +179,17 @@ func (r *PlatformReconciler) reconcile(ctx context.Context, platform *v1alpha1.P log.Error("broker or httpsrv commit from build info is invalid") return nil } - if err := r.setupVault(ctx, baseTD); err != nil { + if err := r.setupVaultPlatform(ctx, platform); err != nil { return log.ErrorN("problem setting up vault: %w", err) } - if err := r.ApplyTemplate(ctx, "platform", &baseTD.Data, log); err != nil { + if err := r.ApplyTemplate(ctx, "platform", &platformTD.Data, log); err != nil { return log.ErrorN("problem setting up Platform: %w", err) } r.setSetup(pKey, true) } - td := baseTD.ForComponent(api.PlatformComponentNATS, &appsv1.StatefulSet{}, &defaults.NATS, templates.Component{ + td := platformTD.ForComponent(api.PlatformComponentNATS, &appsv1.StatefulSet{}, &defaults.NATS, templates.Component{ Component: &core.Component{ Type: string(api.ComponentTypeNATS), Name: api.PlatformComponentNATS, @@ -200,7 +199,7 @@ func (r *PlatformReconciler) reconcile(ctx context.Context, platform *v1alpha1.P ContainerSpec: platform.Spec.NATS.ContainerSpec, IsPlatformComponent: true, }) - if err := r.setupPKI(ctx, td, ""); err != nil { + if err := r.setupVaultComponent(ctx, platform, api.PlatformComponentNATS, false); err != nil { return err } if rdy, err := r.CompMgr.SetupComponent(ctx, td); !rdy || err != nil { @@ -214,7 +213,7 @@ func (r *PlatformReconciler) reconcile(ctx context.Context, platform *v1alpha1.P return chill(err) } - td = baseTD.ForComponent(api.PlatformComponentBroker, &appsv1.DaemonSet{}, &defaults.Broker, templates.Component{ + td = platformTD.ForComponent(api.PlatformComponentBroker, &appsv1.DaemonSet{}, &defaults.Broker, templates.Component{ Component: &core.Component{ Type: string(api.ComponentTypeBroker), Name: api.PlatformComponentBroker, @@ -225,7 +224,7 @@ func (r *PlatformReconciler) reconcile(ctx context.Context, platform *v1alpha1.P ContainerSpec: platform.Spec.Broker.ContainerSpec, IsPlatformComponent: true, }) - if err := r.setupPKI(ctx, td, ""); err != nil { + if err := r.setupVaultComponent(ctx, platform, api.PlatformComponentBroker, true); err != nil { return err } if rdy, err := r.CompMgr.SetupComponent(ctx, td); !rdy || err != nil { @@ -239,7 +238,7 @@ func (r *PlatformReconciler) reconcile(ctx context.Context, platform *v1alpha1.P return chill(err) } - td = baseTD.ForComponent(api.PlatformComponentHTTPSrv, &appsv1.Deployment{}, &defaults.HTTPSrv, templates.Component{ + td = platformTD.ForComponent(api.PlatformComponentHTTPSrv, &appsv1.Deployment{}, &defaults.HTTPSrv, templates.Component{ Component: &core.Component{ Type: string(api.ComponentTypeHTTPAdapter), Name: api.PlatformComponentHTTPSrv, @@ -255,7 +254,7 @@ func (r *PlatformReconciler) reconcile(ctx context.Context, platform *v1alpha1.P td.Values["serviceType"] = platform.Spec.HTTPSrv.Service.Type td.Values["httpPort"] = platform.Spec.HTTPSrv.Service.Ports.HTTP td.Values["httpsPort"] = platform.Spec.HTTPSrv.Service.Ports.HTTPS - if err := r.setupPKI(ctx, td, ""); err != nil { + if err := r.setupVaultComponent(ctx, platform, api.PlatformComponentHTTPSrv, false); err != nil { return err } if rdy, err := r.CompMgr.SetupComponent(ctx, td); !rdy || err != nil { @@ -314,206 +313,157 @@ func (r *PlatformReconciler) updateComponentsStatus(ctx context.Context, p *v1al return nil } -func (r *PlatformReconciler) setupVault(ctx context.Context, td *TemplateData) error { - r.log.Debugf("setting up vault for Platform '%s'", td.PlatformFullName()) +func (r *PlatformReconciler) setupVaultPlatform(ctx context.Context, platform *v1alpha1.Platform) error { + r.log.Debugf("setting up Vault for Platform '%s'", platform.Name) - vault, err := r.vaultClient(ctx, r.VaultURL, []byte(td.Instance.RootCA)) + vaultCli, err := opvault.GetClient(ctx) if err != nil { return err } - // Setup KVs for secrets. - secretPath := fmt.Sprintf("kubefox/instance/%s/namespace/%s", - td.Instance.Name, td.Platform.Namespace) - if cfg, _ := vault.Sys().MountConfig(secretPath); cfg != nil { - r.log.Debugf("namespace kv store at path '%s' exists", secretPath) - - } else { - r.log.Infof("creating namespace kv store at path '%s'", secretPath) - err = vault.Sys().Mount(secretPath, &vapi.MountInput{ - Type: "kv", - Description: fmt.Sprintf("Namespace scoped KubeFox secret data store; instance: %s, namespace: %s", - td.Instance.Name, td.Platform.Namespace), - Options: map[string]string{ - "version": "2", // Supports versioning and optimistic locking. - }, - }) - if err != nil { - return fmt.Errorf("error creating namespace kv store at path '%s': %w", secretPath, err) - } + rootKey := vault.Key{Instance: r.Instance} + key := vault.Key{ + Instance: r.Instance, + Namespace: platform.Namespace, } - secretPath = fmt.Sprintf("kubefox/instance/%s/cluster", td.Instance.Name) - if cfg, _ := vault.Sys().MountConfig(secretPath); cfg != nil { - r.log.Debugf("cluster kv store at path '%s' exists", secretPath) - - } else { - r.log.Infof("creating cluster kv store at path '%s'", secretPath) - err = vault.Sys().Mount(secretPath, &vapi.MountInput{ - Type: "kv", - Description: fmt.Sprintf("Cluster scoped KubeFox secret data store; instance: %s", td.Instance.Name), - Options: map[string]string{ - "version": "2", // Supports versioning and optimistic locking. - }, - }) - if err != nil { - return fmt.Errorf("error creating cluster kv store at path '%s': %w", secretPath, err) - } + // Setup Env/VE Data stores. + if err := vaultCli.CreateDataStore(ctx, ""); err != nil { + return fmt.Errorf("error creating cluster data store: %w", err) + } + if err := vaultCli.CreateDataStore(ctx, platform.Namespace); err != nil { + return fmt.Errorf("error creating namespace data store: %w", err) } // Setup PKI. - pkiPath := fmt.Sprintf("pki/int/platform/%s", td.PlatformVaultName()) - if cfg, _ := vault.Sys().MountConfig(pkiPath); cfg == nil { - err = vault.Sys().Mount(pkiPath, &vapi.MountInput{ - Type: "pki", + // If cfg is non-nil then the mount already exists. + if cfg, _ := vaultCli.Sys().MountConfig(vault.PKIPath(key)); cfg == nil { + if err := vaultCli.Sys().Mount(vault.PKIPath(key), &vapi.MountInput{ + Type: "pki", + Description: "KubeFox Platform Intermediate CA", Config: vapi.MountConfigInput{ MaxLeaseTTL: HundredYears, }, - }) - if err != nil { + }); err != nil { return err } - _, err := vault.Logical().Write(pkiPath+"/config/urls", map[string]interface{}{ - "issuing_certificates": r.VaultURL + "/v1/" + pkiPath + "/ca", - "crl_distribution_points": r.VaultURL + "/v1/" + pkiPath + "/crl", - }) - if err != nil { + if _, err := vaultCli.Logical().Write(vault.PKISubPath(key, "config/urls"), + map[string]interface{}{ + "issuing_certificates": fmt.Sprintf("%s/v1/%s", r.VaultURL, vault.PKISubPath(key, "ca")), + "crl_distribution_points": fmt.Sprintf("%s/v1/%s", r.VaultURL, vault.PKISubPath(key, "crl")), + }, + ); err != nil { return err } - s, err := vault.Logical().Write(pkiPath+"/intermediate/generate/internal", map[string]interface{}{ - "common_name": "KubeFox Platform " + td.PlatformFullName() + " Intermediate CA", - "issuer_name": td.PlatformFullName() + "-intermediate", - }) + + // Generate intermediate cert and use root certificate to sign. + intCert, err := vaultCli.Logical().Write(vault.PKISubPath(key, "intermediate/generate/internal"), + map[string]interface{}{ + "common_name": "KubeFox Platform Intermediate CA", + "issuer_name": "kubefox-platform-intermediate", + }, + ) if err != nil { return err } - s, err = vault.Logical().Write("pki/root/root/sign-intermediate", map[string]interface{}{ - "csr": s.Data["csr"], - "format": "pem_bundle", - "ttl": HundredYears, - }) + signedIntCert, err := vaultCli.Logical().Write(vault.PKISubPath(rootKey, "root/sign-intermediate"), + map[string]interface{}{ + "csr": intCert.Data["csr"], + "format": "pem_bundle", + "ttl": HundredYears, + }, + ) if err != nil { return err } - _, err = vault.Logical().Write(pkiPath+"/intermediate/set-signed", map[string]interface{}{ - "certificate": s.Data["certificate"], - }) - if err != nil { + if _, err := vaultCli.Logical().Write(vault.PKISubPath(key, "intermediate/set-signed"), + map[string]interface{}{ + "certificate": signedIntCert.Data["certificate"], + }, + ); err != nil { return err } } - r.log.Debugf("vault successfully setup for Platform '%s'", td.Platform.Name) + r.log.Debugf("Vault successfully setup for Platform '%s'", platform.Name) return nil } -func (r *PlatformReconciler) setupPKI(ctx context.Context, td *TemplateData, additionalPolicies string) error { - setup := r.isSetup(td.ComponentFullName()) - if setup { - r.log.Debugf("pki already setup for component '%s'", td.ComponentFullName()) +func (r *PlatformReconciler) setupVaultComponent(ctx context.Context, + platform *v1alpha1.Platform, component string, grantReadData bool) error { + + if r.isSetup(component) { + r.log.Debugf("Vault already setup for Component '%s'", component) return nil } - r.log.Debugf("setting up pki for component '%s'", td.ComponentFullName()) + r.log.Debugf("setting up Vault for Component '%s'", component) - vault, err := r.vaultClient(ctx, r.VaultURL, []byte(td.Instance.RootCA)) + vaultCli, err := opvault.GetClient(ctx) if err != nil { return err } - pkiPath := fmt.Sprintf("pki/int/platform/%s", td.PlatformVaultName()) - - path := fmt.Sprintf("%s/roles/%s", pkiPath, td.ComponentVaultName()) - svcName := fmt.Sprintf("%s.%s", td.ComponentFullName(), td.Platform.Namespace) - _, err = vault.Logical().Write(path, map[string]interface{}{ - "issuer_ref": "default", - "allowed_domains": fmt.Sprintf("%s,%s.svc", svcName, svcName), - "allow_localhost": true, - "allow_bare_domains": true, - "max_ttl": TenYears, - }) - if err != nil { - return err + key := vault.Key{ + Instance: r.Instance, + Namespace: platform.Namespace, + Component: component, } - err = vault.Sys().PutPolicyWithContext(ctx, td.ComponentVaultName(), ` - // issue certs - path "`+pkiPath+`/issue/`+td.ComponentVaultName()+`" { - capabilities = ["create", "update"] - } - `) - if err != nil { + svcName := fmt.Sprintf("%s.%s", component, platform.Namespace) + if _, err := vaultCli.Logical().Write(vault.PKISubPath(key, "roles/"+vault.RoleName(key)), + map[string]interface{}{ + "issuer_ref": "default", + "allowed_domains": fmt.Sprintf("%s,%s.svc", svcName, svcName), + "allow_localhost": true, + "allow_bare_domains": true, + "max_ttl": TenYears, + }, + ); err != nil { return err } - path = fmt.Sprintf("auth/kubernetes/role/%s", td.ComponentVaultName()) - policy := td.ComponentVaultName() - if additionalPolicies != "" { - policy = fmt.Sprintf("%s,%s", policy, additionalPolicies) - } + var policies []string - r.log.Debugf("writing role; path: %s, sa: %s, policy: %s", path, td.ComponentFullName(), policy) - _, err = vault.Logical().Write(path, map[string]interface{}{ - "bound_service_account_names": td.ComponentFullName(), - "bound_service_account_namespaces": td.Platform.Namespace, - "token_policies": policy, - }) - if err != nil { + issueCertsPolicy := vault.PolicyName(key, "issue-certs") + if err := vaultCli.Sys().PutPolicyWithContext(ctx, issueCertsPolicy, ` + path "`+vault.PKISubPath(key, "issue/"+vault.RoleName(key))+`" { + capabilities = ["create", "update"] + } + `); err != nil { return err } - - r.setSetup(td.ComponentFullName(), true) - r.log.Debugf("pki successfully setup for component '%s'", td.ComponentFullName()) - - return nil -} - -func (r *PlatformReconciler) vaultClient(ctx context.Context, url string, caCert []byte) (*vapi.Client, error) { - r.mutex.Lock() - defer r.mutex.Unlock() - - if r.vClient != nil { - return r.vClient, nil - } - - cfg := vapi.DefaultConfig() - cfg.Address = url - cfg.MaxRetries = 3 - cfg.HttpClient.Timeout = time.Second * 5 - cfg.ConfigureTLS(&vapi.TLSConfig{ - CACertBytes: caCert, - }) - - vault, err := vapi.NewClient(cfg) - if err != nil { - return nil, err + policies = append(policies, issueCertsPolicy) + + if grantReadData { + readDataPolicy := vault.PolicyName(key, "read-data") + if err = vaultCli.Sys().PutPolicyWithContext(ctx, readDataPolicy, ` + path "`+vault.DataPath(api.DataKey{Instance: r.Instance})+`" { + capabilities = ["read", "list"] + } + path "`+vault.DataPath(api.DataKey{Instance: r.Instance, Namespace: platform.Namespace})+`" { + capabilities = ["read", "list"] + } + `); err != nil { + return err + } + policies = append(policies, readDataPolicy) } - b, err := os.ReadFile(api.PathSvcAccToken) - if err != nil { - return nil, err - } - token := vauth.WithServiceAccountToken(string(b)) - auth, err := vauth.NewKubernetesAuth("kubefox-operator", token) - if err != nil { - return nil, err - } - authInfo, err := vault.Auth().Login(ctx, auth) - if err != nil { - return nil, err - } - if authInfo == nil { - return nil, fmt.Errorf("error logging in with kubernetes auth: no auth info was returned") + if _, err = vaultCli.Logical().Write(vault.KubernetesRolePath(key), + map[string]interface{}{ + "bound_service_account_names": component, + "bound_service_account_namespaces": platform.Namespace, + "token_policies": strings.Join(policies, ","), + }, + ); err != nil { + return err } - r.vClient = vault - watcher, err := vault.NewLifetimeWatcher(&vapi.LifetimeWatcherInput{Secret: authInfo}) - if err != nil { - return nil, fmt.Errorf("error starting Vault token renewer: %w", err) - } - go watcher.Start() + r.setSetup(component, true) + r.log.Debugf("Vault successfully setup for Component '%s'", component) - return vault, nil + return nil } func (r *PlatformReconciler) setSetup(key string, val bool) { diff --git a/components/operator/controller/vars.go b/components/operator/controller/vars.go index 5112321..36299c3 100644 --- a/components/operator/controller/vars.go +++ b/components/operator/controller/vars.go @@ -1,10 +1,10 @@ -/* -Copyright © 2023 XigXog - -This Source Code Form is subject to the terms of the Mozilla Public License, -v2.0. If a copy of the MPL was not distributed with this file, You can obtain -one at https://mozilla.org/MPL/2.0/. -*/ +// Copyright 2023 XigXog +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 package controller diff --git a/components/operator/controller/virtual_env_controller.go b/components/operator/controller/virtual_env_controller.go index 4128412..e84e993 100644 --- a/components/operator/controller/virtual_env_controller.go +++ b/components/operator/controller/virtual_env_controller.go @@ -1,10 +1,10 @@ -/* -Copyright © 2023 XigXog - -This Source Code Form is subject to the terms of the Mozilla Public License, -v2.0. If a copy of the MPL was not distributed with this file, You can obtain -one at https://mozilla.org/MPL/2.0/. -*/ +// Copyright 2023 XigXog +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 package controller @@ -14,9 +14,11 @@ import ( "sort" "time" + "github.com/google/uuid" "github.com/xigxog/kubefox/api" common "github.com/xigxog/kubefox/api/kubernetes" "github.com/xigxog/kubefox/api/kubernetes/v1alpha1" + "github.com/xigxog/kubefox/components/operator/vault" "github.com/xigxog/kubefox/core" "github.com/xigxog/kubefox/k8s" "github.com/xigxog/kubefox/logkf" @@ -41,14 +43,19 @@ type VirtualEnvContext struct { Original *v1alpha1.VirtualEnvironment - Data *api.Data - Policy *v1alpha1.ReleasePolicy + Environment *v1alpha1.Environment + Data *api.Data + Policy *v1alpha1.ReleasePolicy EnvFound bool Now metav1.Time } +var ( + failedWebhookRequeueDuration = time.Second * 15 +) + // SetupWithManager sets up the controller with the Manager. func (r *VirtualEnvReconciler) SetupWithManager(mgr ctrl.Manager) error { r.log = logkf.Global.With(logkf.KeyController, "VirtualEnvironment") @@ -89,6 +96,7 @@ func (r *VirtualEnvReconciler) Reconcile(ctx context.Context, req ctrl.Request) Context: ctx, VirtualEnvironment: ve, Original: ve.DeepCopy(), + Environment: env, Data: ve.Data.MergeInto(&env.Data), Policy: ve.GetReleasePolicy(env), EnvFound: env.UID != "", @@ -99,7 +107,7 @@ func (r *VirtualEnvReconciler) Reconcile(ctx context.Context, req ctrl.Request) if err := r.reconcile(veCtx, log); err != nil { if IsFailedWebhookErr(err) { log.Debug("reconcile failed because of webhook, retrying in 15 seconds") - return reconcile.Result{RequeueAfter: time.Second * 15}, nil + return reconcile.Result{RequeueAfter: failedWebhookRequeueDuration}, nil } return ctrl.Result{}, err @@ -155,7 +163,7 @@ func (r *VirtualEnvReconciler) Reconcile(ctx context.Context, req ctrl.Request) err := r.MergeStatus(ctx, ve, veCtx.Original) if IsFailedWebhookErr(err) { log.Debug("reconcile failed because of webhook, retrying in 15 seconds") - return reconcile.Result{RequeueAfter: time.Second * 15}, nil + return reconcile.Result{RequeueAfter: failedWebhookRequeueDuration}, nil } if k8s.IgnoreNotFound(err) != nil { return ctrl.Result{}, err @@ -163,22 +171,21 @@ func (r *VirtualEnvReconciler) Reconcile(ctx context.Context, req ctrl.Request) } // Delete any unused ReleaseManifests. - err := r.cleanupManifests(ctx, ve) - if IsFailedWebhookErr(err) { - log.Debug("reconcile failed because of webhook, retrying in 15 seconds") - return reconcile.Result{RequeueAfter: time.Second * 15}, nil - } - if err != nil { + if err := r.cleanupManifests(ctx, ve); IgnoreFailedWebhookErr(err) != nil { return ctrl.Result{}, err + + } else if IsFailedWebhookErr(err) { + log.Debug("reconcile failed because of webhook, retrying in 15 seconds") + return reconcile.Result{RequeueAfter: failedWebhookRequeueDuration}, nil } + var requeueDuration time.Duration if ve.Status.PendingRelease != nil { - deadline := veCtx.Policy.GetPendingDeadline() - ve.GetReleasePendingDuration() - log.Debugf("Release pending, requeuing after %s to check deadline", deadline) - return ctrl.Result{RequeueAfter: deadline}, nil + requeueDuration = veCtx.Policy.GetPendingDeadline() - ve.GetReleasePendingDuration() + log.Debugf("Release pending, requeuing after %s to check deadline", requeueDuration) } - return ctrl.Result{}, nil + return ctrl.Result{RequeueAfter: requeueDuration}, nil } func (r *VirtualEnvReconciler) reconcile(ctx *VirtualEnvContext, log *logkf.Logger) error { @@ -191,21 +198,7 @@ func (r *VirtualEnvReconciler) reconcile(ctx *VirtualEnvContext, log *logkf.Logg ctx.Status.PendingReleaseFailed && !releaseEqual(ctx.Status.ActiveRelease, ctx.Spec.Release) { if active := ctx.Status.ActiveRelease; active != nil { - manifest := &v1alpha1.ReleaseManifest{} - if err := r.Get(ctx, k8s.Key(ctx.Namespace, active.ReleaseManifest), manifest); err != nil { - return err - } - ctx.Spec.Release = &v1alpha1.Release{ - Id: manifest.Spec.Id, - Apps: map[string]v1alpha1.ReleaseApp{}, - } - for appName, app := range manifest.Spec.Apps { - ctx.Spec.Release.Apps[appName] = v1alpha1.ReleaseApp{ - AppDeployment: app.AppDeployment.Name, - Version: app.Version, - } - } - + ctx.Spec.Release = &active.Release } else { ctx.Spec.Release = nil } @@ -264,6 +257,7 @@ func (r *VirtualEnvReconciler) reconcile(ctx *VirtualEnvContext, log *logkf.Logg } else if !releaseEqual(ctx.Status.PendingRelease, ctx.Spec.Release) { // Current release updated, set it to pending. ctx.Status.PendingRelease = &v1alpha1.ReleaseStatus{ + Id: uuid.NewString(), Release: *ctx.Spec.Release, RequestTime: ctx.Now, } @@ -309,15 +303,12 @@ func (r *VirtualEnvReconciler) reconcile(ctx *VirtualEnvContext, log *logkf.Logg ctx.Status.ActiveRelease.ActivationTime = &ctx.Now ctx.Status.PendingRelease = nil - // Create ReleaseManifest for VEs that require versions. - if *ctx.Policy.VersionRequired { - manifest, err := r.generateManifest(ctx) + // Create ReleaseManifest for stable releases. + if ctx.Policy.Type == api.ReleaseTypeStable { + manifest, err := r.createManifest(ctx) if err != nil { return err } - if err := r.Create(ctx, manifest); err != nil { - return err - } ctx.Status.ActiveRelease.ReleaseManifest = manifest.Name } @@ -335,7 +326,7 @@ func (r *VirtualEnvReconciler) reconcile(ctx *VirtualEnvContext, log *logkf.Logg case len(ctx.Status.PendingRelease.Problems) > 0: pendingStatus = metav1.ConditionTrue reason = api.ConditionReasonProblemsFound - msg = "One or more problems exist with Release preventing it from being activated, see `status.pendingRelease` for details." + msg = "One or more problems found with Release preventing it from being activated, see `status.pendingRelease` for details." } } ctx.Status.Conditions = k8s.UpdateConditions(ctx.Now, ctx.Status.Conditions, &metav1.Condition{ @@ -367,7 +358,7 @@ func (r *VirtualEnvReconciler) reconcile(ctx *VirtualEnvContext, log *logkf.Logg } else { activeStatus = metav1.ConditionFalse reason = api.ConditionReasonProblemsFound - msg = "One or more problems exist with the active Release causing it to be unavailable, see `status.activeRelease` for details." + msg = "One or more problems found with the active Release causing it to be unavailable, see `status.activeRelease` for details." } } ctx.Status.Conditions = k8s.UpdateConditions(ctx.Now, ctx.Status.Conditions, &metav1.Condition{ @@ -412,7 +403,22 @@ func (r *VirtualEnvReconciler) reconcile(ctx *VirtualEnvContext, log *logkf.Logg return nil } -func (r *VirtualEnvReconciler) generateManifest(ctx *VirtualEnvContext) (*v1alpha1.ReleaseManifest, error) { +func (r *VirtualEnvReconciler) createManifest(ctx *VirtualEnvContext) (*v1alpha1.ReleaseManifest, error) { + vaultCli, err := vault.GetClient(ctx) + if err != nil { + return nil, err + } + + envVaultData := &api.Data{} + if err := vaultCli.GetData(ctx, ctx.Environment.GetDataKey(), envVaultData); err != nil { + return nil, err + } + veVaultData := &api.Data{} + if err := vaultCli.GetData(ctx, ctx.GetDataKey(), veVaultData); err != nil { + return nil, err + } + data := veVaultData.MergeInto(envVaultData) + manifest := &v1alpha1.ReleaseManifest{ TypeMeta: metav1.TypeMeta{ APIVersion: v1alpha1.GroupVersion.Identifier(), @@ -433,16 +439,20 @@ func (r *VirtualEnvReconciler) generateManifest(ctx *VirtualEnvContext) (*v1alph Finalizers: []string{api.FinalizerReleaseProtection}, }, Spec: v1alpha1.ReleaseManifestSpec{ - Id: ctx.Spec.Release.Id, - VirtualEnvironment: v1alpha1.ReleaseManifestEnv{ + ReleaseId: ctx.Status.ActiveRelease.Id, + Environment: v1alpha1.ReleaseManifestRef{ + UID: ctx.Environment.UID, + Name: ctx.Environment.Name, + ResourceVersion: ctx.Environment.ResourceVersion, + }, + VirtualEnvironment: v1alpha1.ReleaseManifestRef{ UID: ctx.UID, Name: ctx.Name, ResourceVersion: ctx.ResourceVersion, - Environment: ctx.Spec.Environment, }, Apps: map[string]*v1alpha1.ReleaseManifestApp{}, }, - Data: *ctx.Data, + Data: *data, Details: ctx.Details, } @@ -455,15 +465,17 @@ func (r *VirtualEnvReconciler) generateManifest(ctx *VirtualEnvContext) (*v1alph manifest.Spec.Apps[appName] = &v1alpha1.ReleaseManifestApp{ Version: app.Version, AppDeployment: v1alpha1.ReleaseManifestAppDep{ - UID: appDep.UID, - Name: appDep.Name, - ResourceVersion: appDep.ResourceVersion, - Spec: appDep.Spec, + ReleaseManifestRef: v1alpha1.ReleaseManifestRef{ + UID: appDep.UID, + Name: appDep.Name, + ResourceVersion: appDep.ResourceVersion, + }, + Spec: appDep.Spec, }, } } - return manifest, nil + return manifest, r.Create(ctx, manifest) } func (r *VirtualEnvReconciler) updateProblems(ctx *VirtualEnvContext, rel *v1alpha1.ReleaseStatus) error { @@ -589,9 +601,9 @@ func (r *VirtualEnvReconciler) updateProblems(ctx *VirtualEnvContext, rel *v1alp }) } - if *ctx.Policy.VersionRequired && app.Version == "" { + if ctx.Policy.Type == api.ReleaseTypeStable && app.Version == "" { msg := fmt.Sprintf(`Version is required but not set for App "%s".`, appName) - value := fmt.Sprint(*ctx.Policy.VersionRequired) + value := string(ctx.Policy.Type) rel.Problems = append(rel.Problems, common.Problem{ ObservedTime: ctx.Now, Problem: api.Problem{ @@ -677,6 +689,11 @@ func (r *VirtualEnvReconciler) updateProblems(ctx *VirtualEnvContext, rel *v1alp } func (r *VirtualEnvReconciler) cleanupManifests(ctx context.Context, ve *v1alpha1.VirtualEnvironment) error { + vaultCli, err := vault.GetClient(ctx) + if err != nil { + return err + } + list := &v1alpha1.ReleaseManifestList{} if err := r.List(ctx, list, client.InNamespace(ve.Namespace), client.MatchingLabels{ api.LabelK8sVirtualEnvironment: ve.Name, @@ -711,6 +728,9 @@ func (r *VirtualEnvReconciler) cleanupManifests(ctx context.Context, ve *v1alpha if err := r.Delete(ctx, &manifest); k8s.IgnoreNotFound(err) != nil { return err } + if err := vaultCli.DeleteData(ctx, manifest.GetDataKey()); err != nil { + return err + } } } diff --git a/components/operator/main.go b/components/operator/main.go index 7b021fb..35e1ce6 100644 --- a/components/operator/main.go +++ b/components/operator/main.go @@ -1,10 +1,10 @@ -/* -Copyright © 2023 XigXog - -This Source Code Form is subject to the terms of the Mozilla Public License, -v2.0. If a copy of the MPL was not distributed with this file, You can obtain -one at https://mozilla.org/MPL/2.0/. -*/ +// Copyright 2023 XigXog +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 package main @@ -41,11 +41,12 @@ import ( "github.com/xigxog/kubefox/build" "github.com/xigxog/kubefox/components/operator/controller" "github.com/xigxog/kubefox/components/operator/templates" - "github.com/xigxog/kubefox/components/operator/vault" + opvault "github.com/xigxog/kubefox/components/operator/vault" "github.com/xigxog/kubefox/components/operator/webhook" "github.com/xigxog/kubefox/k8s" "github.com/xigxog/kubefox/logkf" "github.com/xigxog/kubefox/utils" + "github.com/xigxog/kubefox/vault" ) var ( @@ -102,8 +103,8 @@ func main() { LeaderElectionNamespace: namespace, // In the default scaffold provided, the program ends immediately after // the manager stops, so would be fine to enable this option. However, - // if you are doing or is intended to do any operation such as perform cleanups - // after the manager stops then its usage might be unsafe. + // if you are doing or is intended to do any operation such as perform + // cleanups after the manager stops then its usage might be unsafe. LeaderElectionReleaseOnCancel: true, }) if err != nil { @@ -127,10 +128,14 @@ func main() { } cancel() - vault.Instance = instance - vault.Namespace = namespace - vault.URL = vaultURL - vault.K8sClient = ctrlClient.Client + opvault.Opts = vault.ClientOptions{ + Instance: instance, + Role: fmt.Sprintf("%s-operator", instance), + URL: vaultURL, + AutoRenew: true, + } + opvault.Namespace = namespace + opvault.K8sClient = ctrlClient.Client // Register Controllers. if err = (&controller.PlatformReconciler{ diff --git a/components/operator/templates/helpers.tpl b/components/operator/templates/helpers.tpl index 7e620ad..d3550c2 100644 --- a/components/operator/templates/helpers.tpl +++ b/components/operator/templates/helpers.tpl @@ -199,8 +199,9 @@ image: {{ .Instance.BootstrapImage }} imagePullPolicy: {{ .Component.ImagePullPolicy | default "IfNotPresent" }} {{ include "securityContext" . }} args: - - -platform-vault-name={{ platformVaultName }} - - -component-vault-name={{ componentVaultName }} + - -instance={{ .Instance.Name }} + - -platform-namespace={{ .Platform.Namespace }} + - -component={{ componentFullName }} - -component-service-name={{ printf "%s.%s" componentFullName namespace }} - -component-ip=$(KUBEFOX_COMPONENT_IP) - -vault-url={{ .Values.vaultURL }} diff --git a/components/operator/templates/instance/validating-webhook.yaml b/components/operator/templates/instance/validating-webhook.yaml index 3d0a146..a564838 100644 --- a/components/operator/templates/instance/validating-webhook.yaml +++ b/components/operator/templates/instance/validating-webhook.yaml @@ -41,3 +41,4 @@ webhooks: resources: - appdeployments - releasemanifests + - virtualenvironments diff --git a/components/operator/templates/platform/configmap-env.yaml b/components/operator/templates/platform/configmap-env.yaml index 27a84e5..82927ff 100644 --- a/components/operator/templates/platform/configmap-env.yaml +++ b/components/operator/templates/platform/configmap-env.yaml @@ -26,7 +26,6 @@ data: KUBEFOX_NAMESPACE: {{ namespace | quote }} KUBEFOX_PLATFORM_FULL_NAME: {{ platformFullName | quote }} KUBEFOX_PLATFORM_NAMESPACE: {{ .Platform.Namespace | quote }} - KUBEFOX_PLATFORM_VAULT_NAME: {{ platformVaultName | quote }} KUBEFOX_PLATFORM: {{ .Platform.Name | quote }} {{- with .Logger.Format }} KUBEFOX_LOG_FORMAT: {{ . | quote }} diff --git a/components/operator/templates/renderer.go b/components/operator/templates/renderer.go index f5214be..aa0d3ad 100644 --- a/components/operator/templates/renderer.go +++ b/components/operator/templates/renderer.go @@ -106,9 +106,7 @@ func initFuncs(tpl *template.Template, data *Data) { funcMap["cleanLabel"] = utils.CleanLabel funcMap["namespace"] = data.Namespace funcMap["platformFullName"] = data.PlatformFullName - funcMap["platformVaultName"] = data.PlatformVaultName funcMap["componentFullName"] = data.ComponentFullName - funcMap["componentVaultName"] = data.ComponentVaultName funcMap["homePath"] = data.HomePath tpl.Funcs(funcMap) diff --git a/components/operator/templates/types.go b/components/operator/templates/types.go index 1383fbd..875efc3 100644 --- a/components/operator/templates/types.go +++ b/components/operator/templates/types.go @@ -79,14 +79,6 @@ func (d Data) PlatformFullName() string { return fmt.Sprintf("%s-%s", d.Instance.Name, d.Platform.Name) } -func (d Data) PlatformVaultName() string { - name := fmt.Sprintf("%s-%s", d.Platform.Namespace, d.Platform.Name) - if !strings.HasPrefix(name, "kubefox") { - name = "kubefox-" + name - } - return name -} - func (d Data) ComponentFullName() string { if d.Component.Name == "" { return "" @@ -100,10 +92,6 @@ func (d Data) ComponentFullName() string { return name } -func (d Data) ComponentVaultName() string { - return fmt.Sprintf("%s-%s", d.PlatformVaultName(), d.Component.Name) -} - func (d Data) HomePath() string { return "/tmp/kubefox" } diff --git a/components/operator/vault/client.go b/components/operator/vault/client.go index e16d4e7..50f7a61 100644 --- a/components/operator/vault/client.go +++ b/components/operator/vault/client.go @@ -10,39 +10,23 @@ package vault import ( "context" - "encoding/json" - "fmt" - "net/http" - "os" "sync" - "time" - vapi "github.com/hashicorp/vault/api" - vauth "github.com/hashicorp/vault/api/auth/kubernetes" - "github.com/xigxog/kubefox/api" "github.com/xigxog/kubefox/k8s" + "github.com/xigxog/kubefox/vault" v1 "k8s.io/api/core/v1" ) -type client struct { - *vapi.Client -} - -type vaultSecret struct { - Data *api.Data `json:"data"` -} - var ( - Instance string + Opts vault.ClientOptions Namespace string - URL string K8sClient k8s.Client - globalClient *client + globalClient *vault.Client mutex sync.Mutex ) -func Client() (*client, error) { +func GetClient(ctx context.Context) (*vault.Client, error) { mutex.Lock() defer mutex.Unlock() @@ -50,91 +34,14 @@ func Client() (*client, error) { return globalClient, nil } - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - cm := &v1.ConfigMap{} - if err := K8sClient.Get(ctx, k8s.Key(Namespace, Instance+"-root-ca"), cm); err != nil { - return nil, err - } - - cfg := vapi.DefaultConfig() - cfg.Address = URL - cfg.MaxRetries = 3 - cfg.HttpClient.Timeout = time.Second * 5 - cfg.ConfigureTLS(&vapi.TLSConfig{ - CACertBytes: []byte(cm.Data["ca.crt"]), - }) - - c, err := vapi.NewClient(cfg) - if err != nil { - return nil, err - } - - b, err := os.ReadFile(api.PathSvcAccToken) - if err != nil { - return nil, err - } - token := vauth.WithServiceAccountToken(string(b)) - auth, err := vauth.NewKubernetesAuth("kubefox-operator", token) - if err != nil { - return nil, err - } - authInfo, err := c.Auth().Login(ctx, auth) - if err != nil { - return nil, err - } - if authInfo == nil { - return nil, fmt.Errorf("error logging in with kubernetes auth: no auth info was returned") - } - - watcher, err := c.NewLifetimeWatcher(&vapi.LifetimeWatcherInput{Secret: authInfo}) - if err != nil { - return nil, fmt.Errorf("error starting Vault token renewer: %w", err) - } - go watcher.Start() - - globalClient = &client{ - Client: c, - } - - return globalClient, nil - -} - -func (c *client) GetData(ctx context.Context, key api.DataKey) (*api.Data, error) { - secret := &vaultSecret{Data: &api.Data{ - Vars: map[string]*api.Val{}, - Secrets: map[string]*api.Val{}, - }} - - resp, err := c.Logical().ReadRawWithDataWithContext(ctx, key.Path(Instance), nil) - if resp != nil { - defer resp.Body.Close() - } - - if resp.StatusCode == http.StatusNotFound { - return secret.Data, nil - } - if err != nil { - return nil, err - } - if err := resp.DecodeJSON(secret); err != nil { + if err := K8sClient.Get(ctx, k8s.Key(Namespace, Opts.Instance+"-root-ca"), cm); err != nil { return nil, err } + Opts.CACertBytes = []byte(cm.Data["ca.crt"]) - return secret.Data, nil -} - -func (c *client) PutData(ctx context.Context, key api.DataKey, data *api.Data) error { - b, err := json.Marshal(&vaultSecret{Data: data}) - if err != nil { - return err - } - - if _, err := c.Logical().WriteBytesWithContext(ctx, key.Path(Instance), b); err != nil { - return err - } + var err error + globalClient, err = vault.New(Opts) - return nil + return globalClient, err } diff --git a/components/operator/webhook/app_deployment_webhook.go b/components/operator/webhook/app_deployment_webhook.go index ba299ac..ea10b17 100644 --- a/components/operator/webhook/app_deployment_webhook.go +++ b/components/operator/webhook/app_deployment_webhook.go @@ -1,18 +1,10 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +// Copyright 2023 XigXog +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 package webhook diff --git a/components/operator/webhook/immutable_webhook.go b/components/operator/webhook/immutable_webhook.go index cb57437..0c3a078 100644 --- a/components/operator/webhook/immutable_webhook.go +++ b/components/operator/webhook/immutable_webhook.go @@ -1,18 +1,10 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +// Copyright 2023 XigXog +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 package webhook @@ -53,7 +45,7 @@ func (r *ImmutableWebhook) Handle(ctx context.Context, req admission.Request) ad if oldObj.Spec.Version != "" { if !k8s.DeepEqual(&obj.Spec, &oldObj.Spec) { - return admission.Denied(fmt.Sprintf(notAllowedMsg, req.Kind.Kind)) + return admission.Denied(fmt.Sprintf(notAllowedMsg, "AppDeployment with version")) } } @@ -68,7 +60,7 @@ func (r *ImmutableWebhook) Handle(ctx context.Context, req admission.Request) ad } if !k8s.DeepEqual(&obj.Spec, &oldObj.Spec) || !k8s.DeepEqual(&obj.Data, &oldObj.Data) { - return admission.Denied(fmt.Sprintf(notAllowedMsg, req.Kind.Kind)) + return admission.Denied(fmt.Sprintf(notAllowedMsg, "ReleaseManifest")) } case "kubefox.xigxog.io/v1alpha1, Kind=VirtualEnvironment": @@ -84,13 +76,6 @@ func (r *ImmutableWebhook) Handle(ctx context.Context, req admission.Request) ad if obj.Spec.Environment != oldObj.Spec.Environment { return admission.Denied(fmt.Sprintf(notAllowedMsg, ".spec.environment")) } - - if obj.Spec.Release != nil && oldObj.Spec.Release != nil && - obj.Spec.Release.Id == oldObj.Spec.Release.Id && - !k8s.DeepEqual(obj.Spec.Release.Apps, oldObj.Spec.Release.Apps) { - - return admission.Denied(".spec.release.Id but be updated if changes are made to .spec.release.apps") - } } return admission.Allowed("🦊") diff --git a/components/operator/webhook/index_webhook.go b/components/operator/webhook/index_webhook.go index 7f946fe..132b100 100644 --- a/components/operator/webhook/index_webhook.go +++ b/components/operator/webhook/index_webhook.go @@ -1,18 +1,10 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +// Copyright 2023 XigXog +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 package webhook @@ -69,7 +61,7 @@ func (r *IndexWebhook) Handle(ctx context.Context, req admission.Request) admiss } obj = manifest - k8s.UpdateLabel(manifest, api.LabelK8sEnvironment, manifest.Spec.VirtualEnvironment.Environment) + k8s.UpdateLabel(manifest, api.LabelK8sEnvironment, manifest.Spec.Environment.Name) k8s.UpdateLabel(manifest, api.LabelK8sVirtualEnvironment, string(manifest.Spec.VirtualEnvironment.Name)) case "kubefox.xigxog.io/v1alpha1, Kind=VirtualEnvironment": diff --git a/components/operator/webhook/platform_webhook.go b/components/operator/webhook/platform_webhook.go index 72e92f2..6ad741f 100644 --- a/components/operator/webhook/platform_webhook.go +++ b/components/operator/webhook/platform_webhook.go @@ -1,18 +1,10 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +// Copyright 2023 XigXog +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 package webhook diff --git a/components/operator/webhook/secrets_webhook.go b/components/operator/webhook/secrets_webhook.go index 9cacf19..c8aad1f 100644 --- a/components/operator/webhook/secrets_webhook.go +++ b/components/operator/webhook/secrets_webhook.go @@ -1,18 +1,10 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +// Copyright 2023 XigXog +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 package webhook @@ -24,6 +16,7 @@ import ( "github.com/xigxog/kubefox/api" "github.com/xigxog/kubefox/api/kubernetes/v1alpha1" "github.com/xigxog/kubefox/components/operator/vault" + "github.com/xigxog/kubefox/k8s" "github.com/xigxog/kubefox/logkf" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" @@ -36,7 +29,7 @@ type SecretsWebhook struct { } func (r *SecretsWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { - c, err := vault.Client() + vaultCli, err := vault.GetClient(ctx) if err != nil { logkf.Global.Error(err) return admission.Errored(http.StatusInternalServerError, err) @@ -58,8 +51,11 @@ func (r *SecretsWebhook) Handle(ctx context.Context, req admission.Request) admi return admission.Errored(http.StatusBadRequest, err) } - vaultData, err := c.GetData(ctx, dataProvider.GetDataKey()) - if err != nil { + vaultData := &api.Data{ + Vars: map[string]*api.Val{}, + Secrets: map[string]*api.Val{}, + } + if err := vaultCli.GetData(ctx, dataProvider.GetDataKey(), vaultData); k8s.IgnoreNotFound(err) != nil { logkf.Global.Error(err) return admission.Errored(http.StatusInternalServerError, err) } @@ -80,8 +76,7 @@ func (r *SecretsWebhook) Handle(ctx context.Context, req admission.Request) admi data.Secrets[k] = api.ValString(api.SecretMask) } } - - if err := c.PutData(ctx, dataProvider.GetDataKey(), vaultData); err != nil { + if err := vaultCli.PutData(ctx, dataProvider.GetDataKey(), vaultData); err != nil { logkf.Global.Error(err) return admission.Errored(http.StatusInternalServerError, err) } diff --git a/docs/quickstart.md b/docs/quickstart.md index 81efbc4..5127af8 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -481,7 +481,7 @@ func sayHello(k kit.Kontext) error { return err } - msg := fmt.Sprintf("👋 Hello %s!", r.Str()) + msg := fmt.Sprintf("👋 Hello %s!", r.Str()) //(1) json := map[string]any{"msg": msg} html := fmt.Sprintf(htmlTmpl, msg) k.Log().Debug(msg) diff --git a/docs/reference/conditions.md b/docs/reference/conditions.md index 95c8186..c04fbc0 100644 --- a/docs/reference/conditions.md +++ b/docs/reference/conditions.md @@ -19,10 +19,10 @@ detailed information about the observed status of an object. | ----------- | ------ | ------------------------------ | --------------------------------------------------------------------------------- | | Available | True | ComponentsAvailable | Components have minimum required Pods available. | | | False | ComponentUnavailable | One or more Components do not have minimum required Pods available. | -| | | ProblemsFound | One or more problems exist with AppDeployment, see `status.problems` for details. | +| | | ProblemsFound | One or more problems found with AppDeployment, see `status.problems` for details. | | Progressing | False | ComponentsDeployed | Component Deployments completed successfully. | | | | ComponentDeploymentFailed | One or more Component Deployments failed. | -| | | ProblemsFound | One or more problems exist with AppDeployment, see `status.problems` for details. | +| | | ProblemsFound | One or more problems found with AppDeployment, see `status.problems` for details. | | | True | ComponentDeploymentProgressing | One or more Component Deployments are starting, scaling, or rolling out updates. | ## VirtualEnvironment @@ -32,10 +32,10 @@ detailed information about the observed status of an object. | ActiveReleaseAvailable | True | ContextAvailable | Release AppDeployment and Environment are available, Routes and Adapters are valid and compatible with the VirtualEnvironment. | | | False | NoRelease | No Release made for VirtualEnvironment. | | | | ReleasePending | No active Release, Release is pending activation. | -| | | ProblemsFound | One or more problems exist with the active Release causing it to be unavailable, see `status.activeRelease` for details. | +| | | ProblemsFound | One or more problems found with the active Release causing it to be unavailable, see `status.activeRelease` for details. | | | | EnvironmentNotFound | Environment does not exist. | | ReleasePending | False | ReleaseActivated | Release was activated. | | | | NoRelease | Release for the VirtualEnvironment is not set. | | | | PendingDeadlineExceeded | Release was not activated within pending deadline. | -| | True | ProblemsFound | One or more problems exist with Release preventing it from being activated, see `status.pendingRelease` for details. | +| | True | ProblemsFound | One or more problems found with Release preventing it from being activated, see `status.pendingRelease` for details. | | | | EnvironmentNotFound | Environment does not exist. | diff --git a/docs/reference/kubernetes-crds.md b/docs/reference/kubernetes-crds.md index f675bb9..5d9eeca 100644 --- a/docs/reference/kubernetes-crds.md +++ b/docs/reference/kubernetes-crds.md @@ -91,6 +91,7 @@ AppDeployment is the Schema for the AppDeployments API + ### Platform Platform is the Schema for the Platforms API @@ -426,7 +427,7 @@ Used by:
-### EnvReleaseHistoryLimits +### EnvHistoryLimits @@ -438,8 +439,8 @@ Used by:
| Field | Type | Description | Validation | | ----- | ---- | ----------- | ---------- | -| `count` |
integer
|
Maximum number of Releases to keep in history. Once the limit is reached the oldest Release in history will be deleted. Age is based on archiveTime.
|
min: 0, default: 10
| -| `ageDays` |
integer
|
Maximum age of the Release to keep in history. Once the limit is reached the oldest Release in history will be deleted. Age is based on archiveTime. Set to 0 to disable.
|
min: 0
| +| `count` |
integer
|
Maximum number of Releases to keep in history. Once the limit is reached the oldest Release in history will be deleted. Age is based on archiveTime. Pointer is used to distinguish between not set and false.
|
min: 0, default: 10
| +| `ageDays` |
integer
|
Maximum age of the Release to keep in history. Once the limit is reached the oldest Release in history will be deleted. Age is based on archiveTime. Set to 0 to disable. Pointer is used to distinguish between not set and false.
|
min: 0
| @@ -455,9 +456,9 @@ Used by:
| Field | Type | Description | Validation | | ----- | ---- | ----------- | ---------- | -| `pendingDeadlineSeconds` |
integer
|
If the pending Request cannot be activated before the deadline it will be considered failed. If the Release becomes available for activation after the deadline has been exceeded, it will not be activated.
|
min: 3, default: 300
| -| `versionRequired` |
boolean
|
If true '.spec.release.appDeployment.version' is required. Pointer is used to distinguish between not set and false.
|
default: true
| -| `historyLimits` |
[EnvReleaseHistoryLimits](#envreleasehistorylimits)
|
|
| +| `type` |
enum[`Stable`, `Testing`]
|
|
default: Stable
| +| `activationDeadlineSeconds` |
integer
|
If the pending Release cannot be activated before the activation deadline it will be considered failed and the Release will automatically rolled back to the current active Release. Pointer is used to distinguish between not set and false.
|
min: 3, default: 300
| +| `historyLimits` |
[EnvHistoryLimits](#envhistorylimits)
|
|
| @@ -608,6 +609,23 @@ Used by:
+### HistoryLimits + + + +

+Used by:
+ +- ReleasePolicy
+

+ +| Field | Type | Description | Validation | +| ----- | ---- | ----------- | ---------- | +| `count` |
integer
|
Maximum number of Releases to keep in history. Once the limit is reached the oldest Release in history will be deleted. Age is based on archiveTime. Pointer is used to distinguish between not set and false.
|
min: 0
| +| `ageDays` |
integer
|
Maximum age of the Release to keep in history. Once the limit is reached the oldest Release in history will be deleted. Age is based on archiveTime. Set to 0 to disable. Pointer is used to distinguish between not set and false.
|
min: 0
| + + + ### LoggerSpec @@ -785,7 +803,6 @@ Used by:
| Field | Type | Description | Validation | | ----- | ---- | ----------- | ---------- | -| `id` |
string
|
|
required, minLength: 1
| | `apps` |
map{string, [ReleaseApp](#releaseapp)}
|
|
required
| @@ -845,13 +862,14 @@ Used by:
-### ReleaseManifestEnv +### ReleaseManifestRef

Used by:
+- ReleaseManifestAppDep
- ReleaseManifestSpec

@@ -860,7 +878,6 @@ Used by:
| `uid` |
[UID](#uid)
|
|
required
| | `name` |
string
|
|
required, minLength: 1
| | `resourceVersion` |
string
|
|
required, minLength: 1
| -| `environment` |
string
|
|
required, minLength: 1
| @@ -876,8 +893,9 @@ Used by:
| Field | Type | Description | Validation | | ----- | ---- | ----------- | ---------- | -| `id` |
string
|
|
required, minLength: 1
| -| `virtualEnvironment` |
[ReleaseManifestEnv](#releasemanifestenv)
|
|
required
| +| `releaseId` |
string
|
|
required, minLength: 1
| +| `environment` |
[ReleaseManifestRef](#releasemanifestref)
|
|
required
| +| `virtualEnvironment` |
[ReleaseManifestRef](#releasemanifestref)
|
|
required
| | `apps` |
map{string, [ReleaseManifestApp](#releasemanifestapp)}
|
|
required
| @@ -894,9 +912,9 @@ Used by:
| Field | Type | Description | Validation | | ----- | ---- | ----------- | ---------- | -| `pendingDeadlineSeconds` |
integer
|
If the pending Request cannot be activated before the deadline it will be considered failed. If the Release becomes available for activation after the deadline has been exceeded, it will not be activated. Pointer is used to distinguish between not set and false.
|
min: 3
| -| `versionRequired` |
boolean
|
If true '.spec.release.appDeployment.version' is required. Pointer is used to distinguish between not set and false.
|
| -| `historyLimits` |
[VirtEnvHistoryLimits](#virtenvhistorylimits)
|
|
| +| `type` |
enum[`Stable`, `Testing`]
|
|
| +| `activationDeadlineSeconds` |
integer
|
If the pending Release cannot be activated before the activation deadline it will be considered failed and the Release will automatically rolled back to the current active Release. Pointer is used to distinguish between not set and false.
|
min: 3
| +| `historyLimits` |
[HistoryLimits](#historylimits)
|
|
| @@ -912,8 +930,8 @@ Used by:
| Field | Type | Description | Validation | | ----- | ---- | ----------- | ---------- | -| `id` |
string
|
|
required, minLength: 1
| | `apps` |
map{string, [ReleaseApp](#releaseapp)}
|
|
required
| +| `id` |
string
|
|
required, minLength: 1
| | `releaseManifest` |
string
|
|
| | `requestTime` |
[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#time-v1-meta)
|
Time at which the VirtualEnvironment was updated to use the Release.
|
| | `activationTime` |
[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#time-v1-meta)
|
Time at which the Release became active. If not set the Release was never activated.
|
| @@ -923,6 +941,9 @@ Used by:
+ + + ### RouteSpec @@ -958,23 +979,6 @@ Used by:
-### VirtEnvHistoryLimits - - - -

-Used by:
- -- ReleasePolicy
-

- -| Field | Type | Description | Validation | -| ----- | ---- | ----------- | ---------- | -| `count` |
integer
|
Maximum number of Releases to keep in history. Once the limit is reached the oldest Release in history will be deleted. Age is based on archiveTime. Pointer is used to distinguish between not set and false.
|
min: 0
| -| `ageDays` |
integer
|
Maximum age of the Release to keep in history. Once the limit is reached the oldest Release in history will be deleted. Age is based on archiveTime. Set to 0 to disable. Pointer is used to distinguish between not set and false.
|
min: 0
| - - - ### VirtualEnvironmentSpec diff --git a/examples/go/hello-world/kubefox/hack/environments/prod.yaml b/examples/go/hello-world/kubefox/hack/environments/prod.yaml index 21d2ed3..affd774 100644 --- a/examples/go/hello-world/kubefox/hack/environments/prod.yaml +++ b/examples/go/hello-world/kubefox/hack/environments/prod.yaml @@ -4,7 +4,7 @@ metadata: name: prod spec: releasePolicy: - versionRequired: true + type: Stable data: vars: subPath: prod @@ -15,6 +15,4 @@ kind: VirtualEnvironment metadata: name: prod spec: - releasePolicy: - pendingDeadlineSeconds: 30 environment: prod diff --git a/examples/go/hello-world/kubefox/hack/environments/qa.yaml b/examples/go/hello-world/kubefox/hack/environments/qa.yaml index f9e4b5c..3c69ce1 100644 --- a/examples/go/hello-world/kubefox/hack/environments/qa.yaml +++ b/examples/go/hello-world/kubefox/hack/environments/qa.yaml @@ -4,7 +4,7 @@ metadata: name: qa spec: releasePolicy: - versionRequired: false + type: Testing data: vars: who: World @@ -16,6 +16,3 @@ metadata: name: qa spec: environment: qa -data: - secrets: - this: thisisatest diff --git a/vault/client.go b/vault/client.go new file mode 100644 index 0000000..e4cbd7e --- /dev/null +++ b/vault/client.go @@ -0,0 +1,242 @@ +// Copyright 2023 XigXog +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 + +package vault + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "os" + "path/filepath" + "strings" + "time" + + vapi "github.com/hashicorp/vault/api" + vauth "github.com/hashicorp/vault/api/auth/kubernetes" + "github.com/xigxog/kubefox/api" + "github.com/xigxog/kubefox/core" +) + +type Client struct { + *vapi.Client + ClientOptions +} + +type ClientOptions struct { + Instance string + Role string + URL string + // CACert is the path to a PEM-encoded CA cert file to use to verify the + // Vault server SSL certificate. It takes precedence over CACertBytes. + CACert string + // CACertBytes is a PEM-encoded certificate or bundle. + CACertBytes []byte + // AutoRenew indicates that the Vault token should automatically be renewed + // to ensure it does not expire. + AutoRenew bool +} + +type Key struct { + Instance string + Namespace string + Component string +} + +type VaultSecret struct { + Data *VaultData `json:"data"` +} + +type VaultData struct { + Data any `json:"data"` +} + +func New(opts ClientOptions) (*Client, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + cfg := vapi.DefaultConfig() + cfg.Address = opts.URL + cfg.MaxRetries = 3 + cfg.HttpClient.Timeout = time.Second * 5 + cfg.ConfigureTLS(&vapi.TLSConfig{ + CACert: opts.CACert, + CACertBytes: opts.CACertBytes, + }) + + vaultCli, err := vapi.NewClient(cfg) + if err != nil { + return nil, err + } + + b, err := os.ReadFile(api.PathSvcAccToken) + if err != nil { + return nil, err + } + token := vauth.WithServiceAccountToken(string(b)) + auth, err := vauth.NewKubernetesAuth(opts.Role, token) + if err != nil { + return nil, err + } + authInfo, err := vaultCli.Auth().Login(ctx, auth) + if err != nil { + return nil, err + } + if authInfo == nil { + return nil, fmt.Errorf("error logging in with Kubernetes auth: no auth info was returned") + } + + if opts.AutoRenew { + watcher, err := vaultCli.NewLifetimeWatcher(&vapi.LifetimeWatcherInput{Secret: authInfo}) + if err != nil { + return nil, fmt.Errorf("error starting Vault token renewer: %w", err) + } + go watcher.Start() + } + + return &Client{ + Client: vaultCli, + ClientOptions: opts, + }, nil +} + +func (c *Client) CreateDataStore(ctx context.Context, namespace string) error { + path := DataPath(api.DataKey{ + Instance: c.Instance, + Namespace: namespace, + }) + scope := "Namespace" + if namespace == "" { + scope = "Cluster" + } + + // Check if store already exists. + if cfg, _ := c.Sys().MountConfigWithContext(ctx, path); cfg != nil { + return nil + } + + return c.Sys().MountWithContext(ctx, path, &vapi.MountInput{ + Type: "kv", + Description: scope + " scoped KubeFox Environment Data store.", + Options: map[string]string{ + "version": "2", // Supports versioning and optimistic locking. + }, + }) +} + +func (c *Client) GetData(ctx context.Context, key api.DataKey, data *api.Data) error { + if key.Instance == "" { + key.Instance = c.Instance + } + + resp, err := c.Logical().ReadRawWithDataWithContext(ctx, DataSubPath(key, "data"), nil) + if resp != nil { + defer resp.Body.Close() + } + if resp.StatusCode == http.StatusNotFound { + return core.ErrNotFound() + } + if err != nil { + return err + } + + secret := &VaultSecret{ + Data: &VaultData{ + Data: data, + }, + } + if err := resp.DecodeJSON(&secret); err != nil { + return err + } + + return nil +} + +func (c *Client) PutData(ctx context.Context, key api.DataKey, data *api.Data) error { + if key.Instance == "" { + key.Instance = c.Instance + } + + b, err := json.Marshal(&VaultData{Data: data}) + if err != nil { + return err + } + + if _, err := c.Logical().WriteBytesWithContext(ctx, DataSubPath(key, "data"), b); err != nil { + return err + } + + return nil +} + +func (c *Client) DeleteData(ctx context.Context, key api.DataKey) error { + if key.Instance == "" { + key.Instance = c.Instance + } + + if _, err := c.Logical().DeleteWithContext(ctx, DataSubPath(key, "metadata")); err != nil { + return err + } + + return nil +} + +func RoleName(key Key) string { + return PolicyName(key, "") +} + +func PolicyName(key Key, policy string) string { + key.Namespace = strings.TrimPrefix(key.Namespace, key.Instance) + key.Namespace = strings.TrimPrefix(key.Namespace, "-") + if !strings.HasPrefix(key.Instance, "kubefox") { + key.Instance = "kubefox-" + key.Instance + } + + var parts []string + parts = append(parts, key.Instance) + if key.Namespace != "" { + parts = append(parts, key.Namespace) + } + if key.Component != "" { + parts = append(parts, key.Component) + } + if policy != "" { + parts = append(parts, policy) + } + + return strings.Join(parts, "-") +} + +func KubernetesRolePath(key Key) string { + return fmt.Sprintf("auth/kubernetes/role/%s", RoleName(key)) +} + +func PKIPath(key Key) string { + if key.Namespace == "" { + return fmt.Sprintf("kubefox/pki/instance/%s/root", key.Instance) + } else { + return fmt.Sprintf("kubefox/pki/instance/%s/namespace/%s", key.Instance, key.Namespace) + } +} + +func PKISubPath(key Key, subPath string) string { + return filepath.Join(PKIPath(key), subPath) +} + +func DataPath(key api.DataKey) string { + if key.Namespace == "" { + return fmt.Sprintf("kubefox/kv/instance/%s/cluster", key.Instance) + } else { + return fmt.Sprintf("kubefox/kv/instance/%s/namespace/%s", key.Instance, key.Namespace) + } +} + +func DataSubPath(key api.DataKey, subPath string) string { + return filepath.Join(DataPath(key), subPath, key.Kind, key.Name) +}