Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Automate the results installation process #2398

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/apis/operator/v1alpha1/tektonconfig_defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func (tc *TektonConfig) SetDefaults(ctx context.Context) {
tc.Spec.Pipeline.setDefaults()
tc.Spec.Trigger.setDefaults()
tc.Spec.Chain.setDefaults()
tc.Spec.Result.setDefaults()

if IsOpenShiftPlatform() {
if tc.Spec.Platforms.OpenShift.PipelinesAsCode == nil {
Expand Down
3 changes: 3 additions & 0 deletions pkg/apis/operator/v1alpha1/tektonconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ type TektonConfigSpec struct {
// Chain holds the customizable option for chains component
// +optional
Chain Chain `json:"chain,omitempty"`
// Result holds the customize option for results component
// +optional
Result Result `json:"result,omitempty"`
// Dashboard holds the customizable options for dashboards component
// +optional
Dashboard Dashboard `json:"dashboard,omitempty"`
Expand Down
1 change: 1 addition & 0 deletions pkg/apis/operator/v1alpha1/tektonconfig_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ func (tc *TektonConfig) Validate(ctx context.Context) (errs *apis.FieldError) {
errs = errs.Also(tc.Spec.Dashboard.Options.validate("spec.dashboard.options"))
errs = errs.Also(tc.Spec.Chain.Options.validate("spec.chain.options"))
errs = errs.Also(tc.Spec.Trigger.Options.validate("spec.trigger.options"))
errs = errs.Also(tc.Spec.Result.Options.validate("spec.result.options"))

return errs.Also(tc.Spec.Trigger.TriggersProperties.validate("spec.trigger"))
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/operator/v1alpha1/tektonresult_defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,8 @@ func (tp *TektonResult) SetDefaults(ctx context.Context) {
tp.Spec.TLSHostnameOverride = ""
}
}

// Sets default values of Result
func (c *Result) setDefaults() {
// TODO: Set the other default values for Result
}
9 changes: 9 additions & 0 deletions pkg/apis/operator/v1alpha1/tektonresult_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ type LokiStackProperties struct {
LokiStackNamespace string `json:"loki_stack_namespace,omitempty"`
}

// Result defines the field to customize Result component
type Result struct {
// enable or disable Result Component
Disabled bool `json:"disabled"`
TektonResultSpec `json:",inline"`
// Options holds additions fields and these fields will be updated on the manifests
Options AdditionalOptions `json:"options"`
}

// ResultsAPIProperties defines the fields which are configurable for
// Results API server config
type ResultsAPIProperties struct {
Expand Down
1 change: 0 additions & 1 deletion pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go

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

83 changes: 75 additions & 8 deletions pkg/reconciler/kubernetes/tektonresult/tektonresult.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package tektonresult

import (
"context"
"crypto/rand"
"encoding/base64"
"errors"
"fmt"

Expand Down Expand Up @@ -145,12 +147,20 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, tr *v1alpha1.TektonResul
return errors.New(errMsg)
}

// check if the secrets are created
// TODO: Create secret automatically if they don't exist
// TODO: And remove this check in future release.
if err := r.validateSecretsAreCreated(ctx, tr); err != nil {
return err
// If external database is not set then create default DB
if !tr.Spec.IsExternalDB {
if err := r.createDBSecret(ctx, tr); err != nil {
return err
}
}

// Validated TLS Secret for kubernetes platform
if !v1alpha1.IsOpenShiftPlatform() {
if err := r.validateTLSSecretsAreCreated(ctx, tr); err != nil {
return err
}
}

tr.Status.MarkDependenciesInstalled()

if err := r.extension.PreReconcile(ctx, tr); err != nil {
Expand Down Expand Up @@ -314,17 +324,74 @@ func (r *Reconciler) updateTektonResultsStatus(ctx context.Context, tr *v1alpha1
}

// TektonResults expects secrets to be created before installing
func (r *Reconciler) validateSecretsAreCreated(ctx context.Context, tr *v1alpha1.TektonResult) error {
func (r *Reconciler) validateTLSSecretsAreCreated(ctx context.Context, tr *v1alpha1.TektonResult) error {
logger := logging.FromContext(ctx)
_, err := r.kubeClientSet.CoreV1().Secrets(tr.Spec.TargetNamespace).Get(ctx, DbSecretName, metav1.GetOptions{})
_, err := r.kubeClientSet.CoreV1().Secrets(tr.Spec.TargetNamespace).Get(ctx, TlsSecretName, metav1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
logger.Error(err)
tr.Status.MarkDependencyMissing(fmt.Sprintf("%s secret is missing", DbSecretName))
tr.Status.MarkDependencyMissing(fmt.Sprintf("%s secret is missing", TlsSecretName))
return err
}
logger.Error(err)
return err
}
return nil
}

// Generate the DB secret
func (r *Reconciler) getDBSecret(name string, namespace string, tr *v1alpha1.TektonResult) *corev1.Secret {
s := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
OwnerReferences: []metav1.OwnerReference{getOwnerRef(tr)},
},
Type: corev1.SecretTypeOpaque,
StringData: map[string]string{},
}
password, _ := generateRandomBaseString(20)
s.StringData["POSTGRES_PASSWORD"] = password
s.StringData["POSTGRES_USER"] = "result"
return s
}

// Create Result default database
func (r *Reconciler) createDBSecret(ctx context.Context, tr *v1alpha1.TektonResult) error {
logger := logging.FromContext(ctx)

// Get the DB secret, if not found then create the DB secret
_, err := r.kubeClientSet.CoreV1().Secrets(tr.Spec.TargetNamespace).Get(ctx, DbSecretName, metav1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
// If not found then create DB secret with default data
newDBSecret := r.getDBSecret(DbSecretName, tr.Spec.TargetNamespace, tr)
_, err := r.kubeClientSet.CoreV1().Secrets(tr.Spec.TargetNamespace).Create(ctx, newDBSecret, metav1.CreateOptions{})
if err != nil {
logger.Error(err)
tr.Status.MarkDependencyMissing(fmt.Sprintf("Default db %s creation is failing", DbSecretName))
return err
}
}
}
return nil
}

// Get an owner reference of Tekton Result
func getOwnerRef(tr *v1alpha1.TektonResult) metav1.OwnerReference {
return *metav1.NewControllerRef(tr, tr.GroupVersionKind())
}

func generateRandomBaseString(size int) (string, error) {
bytes := make([]byte, size)

// Generate random bytes
_, err := rand.Read(bytes)
if err != nil {
return "", err
}
// Encode the random bytes into a Base64 string
base64String := base64.StdEncoding.EncodeToString(bytes)

return base64String, nil
}
172 changes: 172 additions & 0 deletions pkg/reconciler/shared/tektonconfig/result/result.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
Copyright 2024 The Tekton 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.
*/

package result

import (
"context"
"fmt"
"reflect"
"strings"

"github.com/tektoncd/operator/pkg/apis/operator/v1alpha1"
op "github.com/tektoncd/operator/pkg/client/clientset/versioned/typed/operator/v1alpha1"
apierrs "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"knative.dev/pkg/apis"
)

func EnsureTektonResultExists(ctx context.Context, clients op.TektonResultInterface, tr *v1alpha1.TektonResult) (*v1alpha1.TektonResult, error) {
trCR, err := GetResult(ctx, clients, v1alpha1.ResultResourceName)
if err != nil {
if !apierrs.IsNotFound(err) {
return nil, err
}
if err := CreateResult(ctx, clients, tr); err != nil {
return nil, err
}
return nil, v1alpha1.RECONCILE_AGAIN_ERR
}

trCR, err = UpdateResult(ctx, trCR, tr, clients)
if err != nil {
return nil, err
}

ready, err := isTektonResultReady(trCR)
if err != nil {
return nil, err
}
if !ready {
return nil, v1alpha1.RECONCILE_AGAIN_ERR
}

return trCR, err
}

func EnsureTektonResultCRNotExists(ctx context.Context, clients op.TektonResultInterface) error {
if _, err := GetResult(ctx, clients, v1alpha1.ResultResourceName); err != nil {
if apierrs.IsNotFound(err) {
// TektonResult CR is gone, hence return nil
return nil
}
return err
}
// if the Get was successful, try deleting the CR
if err := clients.Delete(ctx, v1alpha1.ResultResourceName, metav1.DeleteOptions{}); err != nil {
if apierrs.IsNotFound(err) {
// TektonResult CR is gone, hence return nil
return nil
}
return fmt.Errorf("TektonResult %q failed to delete: %v", v1alpha1.ResultResourceName, err)
}
// if the Delete API call was success,
// then return requeue_event
// so that in a subsequent reconcile call the absence of the CR is verified by one of the 2 checks above
return v1alpha1.RECONCILE_AGAIN_ERR
}

// Get the result
func GetResult(ctx context.Context, clients op.TektonResultInterface, name string) (*v1alpha1.TektonResult, error) {
return clients.Get(ctx, name, metav1.GetOptions{})
}

// Create the Result

func CreateResult(ctx context.Context, clients op.TektonResultInterface, tr *v1alpha1.TektonResult) error {
_, err := clients.Create(ctx, tr, metav1.CreateOptions{})
return err
}

func isTektonResultReady(s *v1alpha1.TektonResult) (bool, error) {
if s.GetStatus() != nil && s.GetStatus().GetCondition(apis.ConditionReady) != nil {
if strings.Contains(s.GetStatus().GetCondition(apis.ConditionReady).Message, v1alpha1.UpgradePending) {
return false, v1alpha1.DEPENDENCY_UPGRADE_PENDING_ERR
}
}
return s.Status.IsReady(), nil
}

func UpdateResult(ctx context.Context, old *v1alpha1.TektonResult, new *v1alpha1.TektonResult, clients op.TektonResultInterface) (*v1alpha1.TektonResult, error) {
// if the result spec is changed then update the instance
updated := false

// initialize labels(map) object
if old.ObjectMeta.Labels == nil {
old.ObjectMeta.Labels = map[string]string{}
}

if new.Spec.TargetNamespace != old.Spec.TargetNamespace {
old.Spec.TargetNamespace = new.Spec.TargetNamespace
updated = true
}

if !reflect.DeepEqual(old.Spec.ResultsAPIProperties, new.Spec.ResultsAPIProperties) {
old.Spec.ResultsAPIProperties = new.Spec.ResultsAPIProperties
updated = true
}

if !reflect.DeepEqual(old.Spec.LokiStackProperties, new.Spec.LokiStackProperties) {
old.Spec.LokiStackProperties = new.Spec.LokiStackProperties
updated = true
}

if !reflect.DeepEqual(old.Spec.ResultsAPIProperties.Options, new.Spec.ResultsAPIProperties.Options) {
old.Spec.ResultsAPIProperties.Options = new.Spec.ResultsAPIProperties.Options
updated = true
}

if old.ObjectMeta.OwnerReferences == nil {
old.ObjectMeta.OwnerReferences = new.ObjectMeta.OwnerReferences
updated = true
}

oldLabels, oldHasLabels := old.ObjectMeta.Labels[v1alpha1.ReleaseVersionKey]
newLabels, newHasLabels := new.ObjectMeta.Labels[v1alpha1.ReleaseVersionKey]
if !oldHasLabels || (newHasLabels && oldLabels != newLabels) {
old.ObjectMeta.Labels[v1alpha1.ReleaseVersionKey] = newLabels
updated = true
}

if updated {
_, err := clients.Update(ctx, old, metav1.UpdateOptions{})
if err != nil {
return nil, err
}
return nil, v1alpha1.RECONCILE_AGAIN_ERR
}
return old, nil
}

func GetTektonResultCR(config *v1alpha1.TektonConfig, operatorVersion string) *v1alpha1.TektonResult {
ownerRef := *metav1.NewControllerRef(config, config.GroupVersionKind())
return &v1alpha1.TektonResult{
ObjectMeta: metav1.ObjectMeta{
Name: v1alpha1.ResultResourceName,
OwnerReferences: []metav1.OwnerReference{ownerRef},
Labels: map[string]string{
v1alpha1.ReleaseVersionKey: operatorVersion,
},
},
Spec: v1alpha1.TektonResultSpec{
CommonSpec: v1alpha1.CommonSpec{
TargetNamespace: config.Spec.TargetNamespace,
},
ResultsAPIProperties: config.Spec.Result.ResultsAPIProperties,
LokiStackProperties: config.Spec.Result.LokiStackProperties,
},
}
}
Loading