Skip to content

Commit

Permalink
adding LastTaskLog api feature
Browse files Browse the repository at this point in the history
  • Loading branch information
trichardsonjr78 committed Sep 1, 2023
1 parent 3fe8c9c commit 8f064b5
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 21 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ require (
github.com/spf13/afero v1.9.2 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/pflag v1.0.5
github.com/subosito/gotenv v1.4.1 // indirect
github.com/ucarion/saml v0.1.2
github.com/ugorji/go/codec v1.2.7 // indirect
Expand Down
3 changes: 2 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,8 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
Expand Down Expand Up @@ -861,6 +861,7 @@ gorm.io/driver/postgres v1.3.9/go.mod h1:qw/FeqjxmYqW5dBcYNBsnhQULIApQdk7YuuDPkt
gorm.io/gorm v1.23.7/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.23.8 h1:h8sGJ+biDgBA1AD1Ha9gFCx7h8npU7AsLdlkX0n2TpE=
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
Expand Down
19 changes: 0 additions & 19 deletions internal/qworker/qworker.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
k8sjson "k8s.io/apimachinery/pkg/runtime/serializer/json"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
Expand Down Expand Up @@ -55,7 +54,6 @@ func worker(queue *deque.Deque[tfv1beta1.Terraform]) {
ctx := context.TODO()
kubeconfig := os.Getenv("KUBECONFIG")
config := kubernetesConfig(kubeconfig)
dynamicClient := dynamic.NewForConfigOrDie(config)
k8sclient := kubernetes.NewForConfigOrDie(config)

for {
Expand All @@ -75,23 +73,6 @@ func worker(queue *deque.Deque[tfv1beta1.Terraform]) {
continue
}

// Get the host cluster's tf resources to check if we accidentally queued it up
if unstructedTerraformList, err := dynamicClient.Resource(terraformResource).List(ctx, metav1.ListOptions{}); err == nil {
terraformList := convertTo[tfv1beta1.TerraformList](unstructedTerraformList)
isUUIDBelongToThisCluster := false
for _, terraform := range terraformList.Items {
if terraform.UID == tf.UID {
isUUIDBelongToThisCluster = true
break
}
}
if isUUIDBelongToThisCluster {
// The UUID matches another UUID which only happens for resources creatd for this cluster
log.Println("This resource is not managed by the API")
continue
}
}

// With the clusterName, check out the vcluster config
vclusterNamespace := tenantId + "-" + clusterName
secret, err := k8sclient.CoreV1().Secrets(vclusterNamespace).Get(ctx, "vc-tfo-virtual-cluster", metav1.GetOptions{})
Expand Down
1 change: 1 addition & 0 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ func (h APIHandler) RegisterRoutes() {
cluster.GET("/:cluster_name/debug/:namespace/:name", h.Debugger) // Alias
cluster.GET("/:cluster_name/resource/:namespace/:name/status", h.ResourceStatusCheck)
cluster.GET("/:cluster_name/status/:namespace/:name", h.ResourceStatusCheck) // Alias
cluster.GET("/:cluster_name/resource/:namespace/:name/last-task-log", h.LastTaskLog)

// DEPRECATED usage of clusterid is being removed. todo ensure galleybytes projects aren't using this
clusterid := routes.Group("/cluster-id")
Expand Down
103 changes: 103 additions & 0 deletions pkg/api/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import (
"net/http"
"os"
"regexp"
"sort"
"strconv"
"time"

"github.com/galleybytes/terraform-operator-api/pkg/common/models"
"github.com/galleybytes/terraform-operator-api/pkg/util"
Expand Down Expand Up @@ -43,6 +45,14 @@ type resource struct {
// TFOResource models.TFOResource `json:"tfo_resource"`
}

type PodInfo struct {
Name string
CreatedAt time.Time
}

// ByCreatedAt implements sort.Interface for []PodInfo based on the CreatedAt field
type ByCreatedAt []PodInfo

func (r resource) validate() error {

if r.ObjectMeta.UID == "" {
Expand Down Expand Up @@ -326,6 +336,99 @@ func (h APIHandler) ResourceStatusCheck(c *gin.Context) {

}

func (a ByCreatedAt) Len() int { return len(a) }
func (a ByCreatedAt) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByCreatedAt) Less(i, j int) bool { return a[i].CreatedAt.Before(a[j].CreatedAt) }

func (h APIHandler) LastTaskLog(c *gin.Context) {
clusterName := c.Param("cluster_name")
clusterID := h.getClusterID(clusterName)

if clusterID == 0 {
c.JSON(http.StatusUnprocessableEntity, response(http.StatusUnprocessableEntity, fmt.Sprintf("cluster_name '%s' not found", clusterName), nil))
return
}

resourceName := c.Param("name")
namespace := c.Param("namespace")

config, err := getVclusterConfig(h.clientset, "internal", clusterName)
if err != nil {
c.JSON(http.StatusUnprocessableEntity, response(http.StatusUnprocessableEntity, err.Error(), nil))
return
}

clientset := kubernetes.NewForConfigOrDie(config)

// get the pods with a certain label in the default namespace
labelSelector := "terraforms.tf.galleybytes.com/resourceName=" + resourceName // change this to your label selector
pods, err := clientset.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{LabelSelector: labelSelector})
if err != nil {
log.Fatal(err)
}

// check if pods were found with matching labels
if len(pods.Items) == 0 {
c.JSON(http.StatusUnprocessableEntity, response(http.StatusNotFound, fmt.Sprintf("terraform pods not found on cluster '%s' for tf resource '%s'/%s'", clusterName, namespace, resourceName), nil))
}

// create a slice of PodInfo from the pods
podInfos := make([]PodInfo, 0, len(pods.Items))

for _, pod := range pods.Items {
podInfos = append(podInfos, PodInfo{Name: pod.Name, CreatedAt: pod.CreationTimestamp.Time})
}

// sort the podInfos by creation timestamp in ascending order
sort.Sort(ByCreatedAt(podInfos))

// get the name of the newest pod
newestPod := podInfos[len(podInfos)-1].Name

pod, err := clientset.CoreV1().Pods(namespace).Get(context.Background(), newestPod, metav1.GetOptions{})
if err != nil {
log.Panic()
}

currentTask := ""
// find the environment variable
for _, container := range pod.Spec.Containers {
for _, envVar := range container.Env {
if envVar.Name == "TFO_TASK" {
currentTask = envVar.Value
}
}
}

// get the logs of the newest pod
logs, err := clientset.CoreV1().Pods(namespace).GetLogs(newestPod, &corev1.PodLogOptions{}).DoRaw(context.Background())
if err != nil {
log.Fatal(err)
}

ansiColorRegex := regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]`)
cleanString := ansiColorRegex.ReplaceAllString(string(logs), "")

responseJSONData := []struct {
ClusterName string `json:"cluster_name"`
Namespace string `json:"namespace"`
TFOResource string `json:"tfo_resource"`
CurrentTask string `json:"current_task"`
LastTaskLog string `json:"last_task_log"`
}{
{
ClusterName: string(clusterName),
Namespace: string(namespace),
TFOResource: string(resourceName),
CurrentTask: string(currentTask),
LastTaskLog: string(cleanString),
},
}

c.JSON(http.StatusOK, response(http.StatusOK, "", responseJSONData))

}

// Take into account pod phases as well as task state to determine if the workflow is still running.
func IsWorkflowRunning(status tfv1beta1.TerraformStatus) bool {
switch status.Stage.State {
Expand Down

0 comments on commit 8f064b5

Please sign in to comment.