From 044b636a57ca46c4bfe222df1bf230a3ef1068b3 Mon Sep 17 00:00:00 2001
From: bijayasharma <vetbijaya@gmail.com>
Date: Thu, 4 Feb 2021 15:25:07 -0500
Subject: [PATCH] Updated executor definition to introduce a status interface

Change-Id: Ib95cb2f6464b2d15ba7378e9b40a13899d273ba3
Signed-off-by: bijayasharma <vetbijaya@gmail.com>
Relates-To: #409
Closes: #409
---
 pkg/phase/client.go                      | 15 ++++++++
 pkg/phase/client_test.go                 |  4 +++
 pkg/phase/executors/baremetal_manager.go |  6 ++++
 pkg/phase/executors/clusterctl.go        |  6 ++++
 pkg/phase/executors/container.go         |  5 +++
 pkg/phase/executors/ephemeral.go         |  5 +++
 pkg/phase/executors/k8s_applier.go       | 44 ++++++++++++++++++++++++
 pkg/phase/ifc/executor.go                |  4 +++
 pkg/phase/ifc/phase.go                   |  6 ++++
 9 files changed, 95 insertions(+)

diff --git a/pkg/phase/client.go b/pkg/phase/client.go
index 79e329a43..a0ef7f6bb 100644
--- a/pkg/phase/client.go
+++ b/pkg/phase/client.go
@@ -178,6 +178,21 @@ func (p *phase) Render(w io.Writer, executorRender bool, options ifc.RenderOptio
 	return rendered.Write(w)
 }
 
+// Status returns the status of the given phase
+func (p *phase) Status() (ifc.PhaseStatus, error) {
+	executor, err := p.Executor()
+	if err != nil {
+		return ifc.PhaseStatus{}, err
+	}
+
+	sts, err := executor.Status()
+	if err != nil {
+		return ifc.PhaseStatus{}, err
+	}
+
+	return ifc.PhaseStatus{ExecutorStatus: sts}, err
+}
+
 // DocumentRoot root that holds all the documents associated with the phase
 func (p *phase) DocumentRoot() (string, error) {
 	relativePath := p.apiObj.Config.DocumentEntryPoint
diff --git a/pkg/phase/client_test.go b/pkg/phase/client_test.go
index 41bdd0e7a..394219318 100644
--- a/pkg/phase/client_test.go
+++ b/pkg/phase/client_test.go
@@ -208,6 +208,10 @@ func TestPhaseValidate(t *testing.T) {
 	}
 }
 
+func (e fakeExecutor) Status() (ifc.ExecutorStatus, error) {
+	return ifc.ExecutorStatus{}, nil
+}
+
 // TODO develop tests, when we add phase object validation
 func TestClientByAPIObj(t *testing.T) {
 	helper, err := phase.NewHelper(testConfig(t))
diff --git a/pkg/phase/executors/baremetal_manager.go b/pkg/phase/executors/baremetal_manager.go
index deca9d8ac..5305daef3 100644
--- a/pkg/phase/executors/baremetal_manager.go
+++ b/pkg/phase/executors/baremetal_manager.go
@@ -21,6 +21,7 @@ import (
 
 	"opendev.org/airship/airshipctl/pkg/api/v1alpha1"
 	airshipv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1"
+	"opendev.org/airship/airshipctl/pkg/errors"
 	"opendev.org/airship/airshipctl/pkg/events"
 	"opendev.org/airship/airshipctl/pkg/inventory"
 	inventoryifc "opendev.org/airship/airshipctl/pkg/inventory/ifc"
@@ -140,3 +141,8 @@ func toCommandOptions(i inventoryifc.Inventory,
 		Timeout:   timeout,
 	}
 }
+
+// Status returns the status of the given phase
+func (e *BaremetalManagerExecutor) Status() (ifc.ExecutorStatus, error) {
+	return ifc.ExecutorStatus{}, errors.ErrNotImplemented{What: BMHManager}
+}
diff --git a/pkg/phase/executors/clusterctl.go b/pkg/phase/executors/clusterctl.go
index 2eb1806c1..7e5639afe 100755
--- a/pkg/phase/executors/clusterctl.go
+++ b/pkg/phase/executors/clusterctl.go
@@ -24,6 +24,7 @@ import (
 	"opendev.org/airship/airshipctl/pkg/cluster/clustermap"
 	"opendev.org/airship/airshipctl/pkg/clusterctl/client"
 	"opendev.org/airship/airshipctl/pkg/document"
+	"opendev.org/airship/airshipctl/pkg/errors"
 	airerrors "opendev.org/airship/airshipctl/pkg/errors"
 	"opendev.org/airship/airshipctl/pkg/events"
 	"opendev.org/airship/airshipctl/pkg/k8s/kubeconfig"
@@ -231,3 +232,8 @@ func (c *ClusterctlExecutor) Render(w io.Writer, ro ifc.RenderOptions) error {
 	}
 	return filtered.Write(w)
 }
+
+// Status returns the status of the given phase
+func (c *ClusterctlExecutor) Status() (ifc.ExecutorStatus, error) {
+	return ifc.ExecutorStatus{}, errors.ErrNotImplemented{What: Clusterctl}
+}
diff --git a/pkg/phase/executors/container.go b/pkg/phase/executors/container.go
index 1979f5396..838613022 100644
--- a/pkg/phase/executors/container.go
+++ b/pkg/phase/executors/container.go
@@ -199,3 +199,8 @@ func (c *ContainerExecutor) setConfig() error {
 	}
 	return nil
 }
+
+// Status returns the status of the given phase
+func (c *ContainerExecutor) Status() (ifc.ExecutorStatus, error) {
+	return ifc.ExecutorStatus{}, errors.ErrNotImplemented{What: GenericContainer}
+}
diff --git a/pkg/phase/executors/ephemeral.go b/pkg/phase/executors/ephemeral.go
index 17c4c9ffe..06667fd8d 100644
--- a/pkg/phase/executors/ephemeral.go
+++ b/pkg/phase/executors/ephemeral.go
@@ -140,3 +140,8 @@ func (c *EphemeralExecutor) Render(w io.Writer, _ ifc.RenderOptions) error {
 	log.Print("Ephemeral Executor Render() will be implemented later.")
 	return nil
 }
+
+// Status returns the status of the given phase
+func (c *EphemeralExecutor) Status() (ifc.ExecutorStatus, error) {
+	return ifc.ExecutorStatus{}, errors.ErrNotImplemented{What: Ephemeral}
+}
diff --git a/pkg/phase/executors/k8s_applier.go b/pkg/phase/executors/k8s_applier.go
index 53083e739..957fab23e 100644
--- a/pkg/phase/executors/k8s_applier.go
+++ b/pkg/phase/executors/k8s_applier.go
@@ -19,6 +19,10 @@ import (
 	"time"
 
 	"sigs.k8s.io/cli-utils/pkg/common"
+	"sigs.k8s.io/cli-utils/pkg/kstatus/polling/aggregator"
+	"sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
+	"sigs.k8s.io/cli-utils/pkg/kstatus/status"
+	"sigs.k8s.io/cli-utils/pkg/provider"
 
 	airshipv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1"
 	"opendev.org/airship/airshipctl/pkg/cluster/clustermap"
@@ -146,3 +150,43 @@ func (e *KubeApplierExecutor) Render(w io.Writer, o ifc.RenderOptions) error {
 	}
 	return bundle.Write(w)
 }
+
+// Status returns the status of the given phase
+func (e *KubeApplierExecutor) Status() (sts ifc.ExecutorStatus, err error) {
+	var ctx string
+	ctx, err = e.clusterMap.ClusterKubeconfigContext(e.clusterName)
+	if err != nil {
+		return sts, err
+	}
+	log.Debug("Getting kubeconfig file information from kubeconfig provider")
+	path, _, err := e.kubeconfig.GetFile()
+	if err != nil {
+		return sts, err
+	}
+
+	cf := provider.NewProvider(utils.FactoryFromKubeConfig(path, ctx))
+	rm, err := cf.Factory().ToRESTMapper()
+	if err != nil {
+		return
+	}
+	r := utils.DefaultManifestReaderFactory(false, e.ExecutorBundle, rm)
+	infos, err := r.Read()
+	if err != nil {
+		return
+	}
+
+	var resSts event.ResourceStatuses
+
+	for _, info := range infos {
+		s, sErr := status.Compute(info)
+		if sErr != nil {
+			return
+		}
+		st := &event.ResourceStatus{
+			Status: s.Status,
+		}
+		resSts = append(resSts, st)
+	}
+	_ = aggregator.AggregateStatus(resSts, status.CurrentStatus)
+	return ifc.ExecutorStatus{}, err
+}
diff --git a/pkg/phase/ifc/executor.go b/pkg/phase/ifc/executor.go
index 6308e1d6b..5df7cf603 100644
--- a/pkg/phase/ifc/executor.go
+++ b/pkg/phase/ifc/executor.go
@@ -30,8 +30,12 @@ type Executor interface {
 	Run(chan events.Event, RunOptions)
 	Render(io.Writer, RenderOptions) error
 	Validate() error
+	Status() (ExecutorStatus, error)
 }
 
+// ExecutorStatus is a struct which defines the status
+type ExecutorStatus struct{}
+
 // RunOptions holds options for run method
 type RunOptions struct {
 	DryRun   bool
diff --git a/pkg/phase/ifc/phase.go b/pkg/phase/ifc/phase.go
index 5df30c668..618689292 100644
--- a/pkg/phase/ifc/phase.go
+++ b/pkg/phase/ifc/phase.go
@@ -29,6 +29,12 @@ type Phase interface {
 	Details() (string, error)
 	Executor() (Executor, error)
 	Render(io.Writer, bool, RenderOptions) error
+	Status() (PhaseStatus, error)
+}
+
+// PhaseStatus is a struct which defines status of phase
+type PhaseStatus struct {
+	ExecutorStatus ExecutorStatus
 }
 
 // Plan provides a way to interact with phase plans