Browse Source

add jobs endpoints

Alexander Belanger 4 years ago
parent
commit
0f9695241a

+ 75 - 0
api/server/handlers/release/get_job_status.go

@@ -0,0 +1,75 @@
+package release
+
+import (
+	"net/http"
+
+	"github.com/porter-dev/porter/api/server/authz"
+	"github.com/porter-dev/porter/api/server/handlers"
+	"github.com/porter-dev/porter/api/server/shared"
+	"github.com/porter-dev/porter/api/server/shared/apierrors"
+	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/models"
+	"helm.sh/helm/v3/pkg/release"
+)
+
+type GetJobsStatusHandler struct {
+	handlers.PorterHandlerReadWriter
+	authz.KubernetesAgentGetter
+}
+
+func NewGetJobsStatusHandler(
+	config *config.Config,
+	writer shared.ResultWriter,
+) *GetJobsStatusHandler {
+	return &GetJobsStatusHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, nil, writer),
+		KubernetesAgentGetter:   authz.NewOutOfClusterAgentGetter(config),
+	}
+}
+
+func (c *GetJobsStatusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	helmRelease, _ := r.Context().Value(types.ReleaseScope).(*release.Release)
+	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+	agent, err := c.GetAgent(r, cluster)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	jobs, err := agent.ListJobsByLabel(helmRelease.Namespace, getJobLabels(helmRelease)...)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	res := &types.GetJobsStatusResponse{}
+
+	// get the most recent job
+	if len(jobs) > 0 {
+		mostRecentJob := jobs[0]
+
+		for _, job := range jobs {
+			createdAt := job.ObjectMeta.CreationTimestamp
+
+			if mostRecentJob.CreationTimestamp.Before(&createdAt) {
+				mostRecentJob = job
+			}
+		}
+
+		res.StartTime = mostRecentJob.Status.StartTime
+
+		// get the status of the most recent job
+		if mostRecentJob.Status.Succeeded >= 1 {
+			res.Status = "succeeded"
+		} else if mostRecentJob.Status.Active >= 1 {
+			res.Status = "running"
+		} else if mostRecentJob.Status.Failed >= 1 {
+			res.Status = "failed"
+		}
+	}
+
+	c.WriteResult(w, r, res)
+}

+ 64 - 0
api/server/handlers/release/get_jobs.go

@@ -0,0 +1,64 @@
+package release
+
+import (
+	"fmt"
+	"net/http"
+
+	"github.com/porter-dev/porter/api/server/authz"
+	"github.com/porter-dev/porter/api/server/handlers"
+	"github.com/porter-dev/porter/api/server/shared"
+	"github.com/porter-dev/porter/api/server/shared/apierrors"
+	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/kubernetes"
+	"github.com/porter-dev/porter/internal/models"
+	"helm.sh/helm/v3/pkg/release"
+)
+
+type GetJobsHandler struct {
+	handlers.PorterHandlerReadWriter
+	authz.KubernetesAgentGetter
+}
+
+func NewGetJobsHandler(
+	config *config.Config,
+	writer shared.ResultWriter,
+) *GetJobsHandler {
+	return &GetJobsHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, nil, writer),
+		KubernetesAgentGetter:   authz.NewOutOfClusterAgentGetter(config),
+	}
+}
+
+func (c *GetJobsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	helmRelease, _ := r.Context().Value(types.ReleaseScope).(*release.Release)
+	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+	agent, err := c.GetAgent(r, cluster)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	jobs, err := agent.ListJobsByLabel(helmRelease.Namespace, getJobLabels(helmRelease)...)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	c.WriteResult(w, r, jobs)
+}
+
+func getJobLabels(helmRelease *release.Release) []kubernetes.Label {
+	return []kubernetes.Label{
+		{
+			Key: "helm.sh/chart",
+			Val: fmt.Sprintf("%s-%s", helmRelease.Chart.Metadata.Name, helmRelease.Chart.Metadata.Version),
+		},
+		{
+			Key: "meta.helm.sh/release-name",
+			Val: helmRelease.Name,
+		},
+	}
+}

+ 62 - 0
api/server/router/release.go

@@ -475,5 +475,67 @@ func getReleaseRoutes(
 		Router:   r,
 	})
 
+	// GET /api/projects/{project_id}/clusters/{cluster_id}/namespaces/{namespace}/releases/{name}/{version}/jobs ->
+	// release.NewGetJobsHandler
+	getJobsEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbGet,
+			Method: types.HTTPVerbGet,
+			Path: &types.Path{
+				Parent:       basePath,
+				RelativePath: relPath + "/jobs",
+			},
+			Scopes: []types.PermissionScope{
+				types.UserScope,
+				types.ProjectScope,
+				types.ClusterScope,
+				types.NamespaceScope,
+				types.ReleaseScope,
+			},
+		},
+	)
+
+	getJobsHandler := release.NewGetJobsHandler(
+		config,
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &Route{
+		Endpoint: getJobsEndpoint,
+		Handler:  getJobsHandler,
+		Router:   r,
+	})
+
+	// GET /api/projects/{project_id}/clusters/{cluster_id}/namespaces/{namespace}/releases/{name}/{version}/jobs/status ->
+	// release.NewGetJobsHandler
+	getJobsStatusEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbGet,
+			Method: types.HTTPVerbGet,
+			Path: &types.Path{
+				Parent:       basePath,
+				RelativePath: relPath + "/jobs/status",
+			},
+			Scopes: []types.PermissionScope{
+				types.UserScope,
+				types.ProjectScope,
+				types.ClusterScope,
+				types.NamespaceScope,
+				types.ReleaseScope,
+			},
+		},
+	)
+
+	getJobsStatusHandler := release.NewGetJobsStatusHandler(
+		config,
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &Route{
+		Endpoint: getJobsStatusEndpoint,
+		Handler:  getJobsStatusHandler,
+		Router:   r,
+	})
+
 	return routes, newPath
 }

+ 9 - 1
api/types/release.go

@@ -1,6 +1,9 @@
 package types
 
-import "helm.sh/helm/v3/pkg/release"
+import (
+	"helm.sh/helm/v3/pkg/release"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
 
 // Release is a helm release with a form attached
 type Release struct {
@@ -58,3 +61,8 @@ type UpdateImageBatchRequest struct {
 	ImageRepoURI string `json:"image_repo_uri" form:"required"`
 	Tag          string `json:"tag" form:"required"`
 }
+
+type GetJobsStatusResponse struct {
+	Status    string       `json:"status,omitempty"`
+	StartTime *metav1.Time `json:"start_time,omitempty"`
+}

+ 2 - 2
dashboard/src/main/home/cluster-dashboard/chart/Chart.tsx

@@ -109,11 +109,11 @@ const Chart: React.FunctionComponent<Props> = ({
       .getJobStatus(
         "<token>",
         {
-          cluster_id: currentCluster.id,
         },
         {
           id: currentProject.id,
-          name: chart.name,
+          release_name: chart.name,
+          cluster_id: currentCluster.id,
           namespace: chart.namespace,
         }
       )

+ 2 - 4
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedJobChart.tsx

@@ -154,7 +154,7 @@ class ExpandedJobChart extends Component<PropsType, StateType> {
     let { currentCluster, currentProject } = this.context;
     let protocol = window.location.protocol == "https:" ? "wss" : "ws";
     let ws = new WebSocket(
-      `${protocol}://${window.location.host}/api/projects/${currentProject.id}/k8s/job/status?cluster_id=${currentCluster.id}`
+      `${protocol}://${window.location.host}/api/projects/${currentProject.id}/clusters/${currentCluster.id}/job/status`
     );
     ws.onopen = () => {
       console.log("connected to websocket");
@@ -344,7 +344,6 @@ class ExpandedJobChart extends Component<PropsType, StateType> {
       .upgradeChartValues(
         "<token>",
         {
-          storage: StorageType.Secret,
           values: conf,
         },
         {
@@ -387,11 +386,10 @@ class ExpandedJobChart extends Component<PropsType, StateType> {
       .getJobs(
         "<token>",
         {
-          cluster_id: currentCluster.id,
         },
         {
           id: currentProject.id,
-          chart: `${chart.chart.metadata.name}-${chart.chart.metadata.version}`,
+          cluster_id: currentCluster.id,
           namespace: chart.namespace,
           release_name: chart.name,
         }

+ 8 - 6
dashboard/src/shared/api.tsx

@@ -551,20 +551,22 @@ const getInvites = baseApi<{}, { id: number }>("GET", (pathParams) => {
 
 const getJobs = baseApi<
   {
-    cluster_id: number;
   },
-  { chart: string; namespace: string; release_name: string; id: number }
+  { namespace: string; cluster_id: number; release_name: string; id: number }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.id}/k8s/${pathParams.namespace}/${pathParams.chart}/${pathParams.release_name}/jobs`;
+  let { id, release_name, cluster_id, namespace } = pathParams
+
+  return `/api/projects/${id}/clusters/${cluster_id}/namespaces/${namespace}/releases/${release_name}/0/jobs`;
 });
 
 const getJobStatus = baseApi<
   {
-    cluster_id: number;
   },
-  { name: string; namespace: string; id: number }
+  { namespace: string; cluster_id: number; release_name: string; id: number }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.id}/k8s/${pathParams.namespace}/${pathParams.name}/jobs/status`;
+  let { id, release_name, cluster_id, namespace } = pathParams
+
+  return `/api/projects/${id}/clusters/${cluster_id}/namespaces/${namespace}/releases/${release_name}/0/jobs/status`;
 });
 
 const getJobPods = baseApi<