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

Add Results configuration in tekton config #2398

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
19 changes: 19 additions & 0 deletions 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.

15 changes: 15 additions & 0 deletions pkg/reconciler/kubernetes/tektoninstallerset/client/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,18 @@ func (i *InstallerSetClient) ListCustomSet(ctx context.Context, labelSelector st
}
return is, nil
}

// ListPreSet return the lists of Pre sets with the provided labelSelector
func (i *InstallerSetClient) ListPreSet(ctx context.Context, labelSelector string) (*v1alpha1.TektonInstallerSetList, error) {
logger := logging.FromContext(ctx)
logger.Debugf("%v: checking installer sets with labels: %v", i.resourceKind, labelSelector)

is, err := i.clientSet.List(ctx, v1.ListOptions{LabelSelector: labelSelector})
if err != nil {
return nil, err
}
if len(is.Items) == 0 {
logger.Debugf("%v: no installer sets found with labels: %v", i.resourceKind, labelSelector)
}
return is, nil
}
199 changes: 191 additions & 8 deletions pkg/reconciler/kubernetes/tektonresult/tektonresult.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,17 @@ package tektonresult

import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"math/big"
"time"

apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -145,12 +154,24 @@ 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 disable then create default database and tls secret
// otherwise checks default database secret is created or not also creates tls secret
if !tr.Spec.IsExternalDB {
if err := r.createDBSecret(ctx, tr); err != nil {
return err
}
if err := r.createTLSSecret(ctx, tr); err != nil {
pratap0007 marked this conversation as resolved.
Show resolved Hide resolved
return err
}
} else {
if err := r.validateSecretsAreCreated(ctx, tr, DbSecretName); err != nil {
return err
}
if err := r.createTLSSecret(ctx, tr); err != nil {
return err
}
}

tr.Status.MarkDependenciesInstalled()

if err := r.extension.PreReconcile(ctx, tr); err != nil {
Expand Down Expand Up @@ -314,17 +335,179 @@ 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) validateSecretsAreCreated(ctx context.Context, tr *v1alpha1.TektonResult, secretName string) 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, secretName, 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", secretName))
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 secret
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 {
return nil
}
if !apierrors.IsNotFound(err) {
logger.Errorf("Failed to find default TektonResult database secret %s in namespace %s: %v", DbSecretName, tr.Spec.TargetNamespace, err)
return err
}
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.Errorf("Failed to create default TektonResult database secret %s in namespace %s: %v", DbSecretName, tr.Spec.TargetNamespace, err)
tr.Status.MarkDependencyMissing(fmt.Sprintf("Default db %s creation is failing", DbSecretName))
return err
}
return nil
}

// Create default TLS certificates for the database
func (r *Reconciler) createTLSSecret(ctx context.Context, tr *v1alpha1.TektonResult) error {
logger := logging.FromContext(ctx)

_, err := r.kubeClientSet.CoreV1().Secrets(tr.Spec.TargetNamespace).Get(ctx, TlsSecretName, metav1.GetOptions{})
if err == nil {
return nil
}
if !apierrors.IsNotFound(err) {
logger.Errorf("Failed to find default TektonResult TLS secret %s in namespace %s: %v", TlsSecretName, tr.Spec.TargetNamespace, err)
return err
}
certPEM, keyPEM, err := generateTLSCertificate(tr.Spec.TargetNamespace)
if err != nil {
logger.Errorf("Failed to generate default TektonResult TLS certificate: %v", err)
return err
}
// Create Kubernetes TLS secret
err = r.createKubernetesTLSSecret(ctx, tr.Spec.TargetNamespace, TlsSecretName, certPEM, keyPEM, tr)
if err != nil {
logger.Fatalf("Failed to create TLS secret %s in namespace %s: %v", TlsSecretName, tr.Spec.TargetNamespace, 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
}

// generateTLSCertificate generates a self-signed TLS certificate and private key.
func generateTLSCertificate(targetNS string) (certPEM, keyPEM []byte, err error) {

// Define subject and DNS names
dnsName := fmt.Sprintf("tekton-results-api-service.%s.svc.cluster.local", targetNS)

priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, nil, err
}

notBefore := time.Now()
notAfter := notBefore.Add(365 * 24 * time.Hour)

serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
return nil, nil, err
}

template := x509.Certificate{
SerialNumber: serialNumber,
Issuer: pkix.Name{},
Subject: pkix.Name{
CommonName: dnsName,
},
DNSNames: []string{dnsName},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}

certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
return nil, nil, err
}

certPEM = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})

privBytes, err := x509.MarshalECPrivateKey(priv)
if err != nil {
return nil, nil, err
}
keyPEM = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: privBytes})

return certPEM, keyPEM, nil
}

// createKubernetesSecret creates a Kubernetes TLS secret with the given cert and key.
func (r *Reconciler) createKubernetesTLSSecret(ctx context.Context, namespace, secretName string, certPEM, keyPEM []byte, tr *v1alpha1.TektonResult) error {

// Define the secret
logger := logging.FromContext(ctx)
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: namespace,
},
Type: corev1.SecretTypeTLS,
Data: map[string][]byte{
corev1.TLSCertKey: certPEM,
corev1.TLSPrivateKeyKey: keyPEM,
},
}

_, err := r.kubeClientSet.CoreV1().Secrets(tr.Spec.TargetNamespace).Create(ctx, secret, metav1.CreateOptions{})
if err != nil {
logger.Errorf("Failed to create TLS secret %s in namespace %s: %v", secretName, namespace, err)
tr.Status.MarkDependencyMissing(fmt.Sprintf("Default TLS Secret %s creation is failing", secretName))
return err
}

logger.Infof("Secret '%s' created successfully in namespace '%s'\n", secretName, namespace)
return nil
}
15 changes: 11 additions & 4 deletions pkg/reconciler/openshift/tektonresult/extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func OpenShiftExtension(ctx context.Context) common.Extension {
logger.Fatalf("Failed to fetch logs RBAC manifest: %v", err)
}

ext := openshiftExtension{
ext := &openshiftExtension{
installerSetClient: client.NewInstallerSetClient(operatorclient.Get(ctx).OperatorV1alpha1().TektonInstallerSets(),
version, "results-ext", v1alpha1.KindTektonResult, nil),
internalDBManifest: internalDBManifest,
Expand All @@ -87,6 +87,7 @@ type openshiftExtension struct {
routeManifest *mf.Manifest
internalDBManifest *mf.Manifest
logsRBACManifest *mf.Manifest
removePreset bool
}

func (oe openshiftExtension) Transformers(comp v1alpha1.TektonComponent) []mf.Transformer {
Expand All @@ -102,14 +103,20 @@ func (oe openshiftExtension) Transformers(comp v1alpha1.TektonComponent) []mf.Tr
}
}

func (oe openshiftExtension) PreReconcile(ctx context.Context, tc v1alpha1.TektonComponent) error {
func (oe *openshiftExtension) PreReconcile(ctx context.Context, tc v1alpha1.TektonComponent) error {
result := tc.(*v1alpha1.TektonResult)

mf := mf.Manifest{}

if !result.Spec.IsExternalDB {
mf = *oe.internalDBManifest
oe.removePreset = true
}
if result.Spec.IsExternalDB && oe.removePreset {
if err := oe.installerSetClient.CleanupPreSet(ctx); err != nil {
return err
}
oe.removePreset = false
}

if (result.Spec.LokiStackName != "" && result.Spec.LokiStackNamespace != "") ||
strings.EqualFold(result.Spec.LogsType, "LOKI") {
mf = mf.Append(*oe.logsRBACManifest)
Expand Down
8 changes: 8 additions & 0 deletions pkg/reconciler/shared/tektonconfig/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
tektonConfiginformer "github.com/tektoncd/operator/pkg/client/injection/informers/operator/v1alpha1/tektonconfig"
tektonInstallerinformer "github.com/tektoncd/operator/pkg/client/injection/informers/operator/v1alpha1/tektoninstallerset"
tektonPipelineinformer "github.com/tektoncd/operator/pkg/client/injection/informers/operator/v1alpha1/tektonpipeline"
tektonResultinformer "github.com/tektoncd/operator/pkg/client/injection/informers/operator/v1alpha1/tektonresult"
tektonTriggerinformer "github.com/tektoncd/operator/pkg/client/injection/informers/operator/v1alpha1/tektontrigger"
tektonConfigreconciler "github.com/tektoncd/operator/pkg/client/injection/reconciler/operator/v1alpha1/tektonconfig"
"github.com/tektoncd/operator/pkg/reconciler/common"
Expand Down Expand Up @@ -105,6 +106,13 @@ func NewExtensibleController(generator common.ExtensionGenerator) injection.Cont
logger.Panicf("Couldn't register TektonChain informer event handler: %w", err)
}

if _, err := tektonResultinformer.Get(ctx).Informer().AddEventHandler(cache.FilteringResourceEventHandler{
FilterFunc: controller.FilterController(&v1alpha1.TektonConfig{}),
Handler: controller.HandleAll(impl.EnqueueControllerOf),
}); err != nil {
logger.Panicf("Couldn't register TektonResult informer event handler: %w", err)
}

if _, err := tektonInstallerinformer.Get(ctx).Informer().AddEventHandler(cache.FilteringResourceEventHandler{
FilterFunc: controller.FilterController(&v1alpha1.TektonConfig{}),
Handler: controller.HandleAll(impl.EnqueueControllerOf),
Expand Down
Loading
Loading