Skip to content

Commit

Permalink
Automate the results installation process
Browse files Browse the repository at this point in the history
- Results will be installed by default and it can be
  enable or disable the result from tektonconfig
- For kubernetes platform user need to create the tls secret
  before installing the result
Todo:
- Statefulset of DB should be deleted on change
- Recreate API server if config map changes
  • Loading branch information
pratap0007 committed Nov 7, 2024
1 parent 92b763e commit 171c5b1
Show file tree
Hide file tree
Showing 10 changed files with 422 additions and 9 deletions.
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

0 comments on commit 171c5b1

Please sign in to comment.