-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Sergiy Kulanov <sergiy_kulanov@epam.com>
- Loading branch information
Showing
5 changed files
with
190 additions
and
100 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package main | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"strings" | ||
|
||
v1pipeline "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" | ||
) | ||
|
||
type TaskGraph struct { | ||
PipelineName string | ||
Nodes map[string]*TaskNode | ||
} | ||
|
||
type TaskNode struct { | ||
Name string | ||
Dependencies []*TaskNode | ||
} | ||
|
||
type DOT struct { | ||
Name string | ||
Edges []string | ||
Format string | ||
} | ||
|
||
func BuildTaskGraph(tasks []v1pipeline.PipelineTask) *TaskGraph { | ||
graph := &TaskGraph{ | ||
Nodes: make(map[string]*TaskNode), | ||
} | ||
|
||
// Create a node for each task | ||
for _, task := range tasks { | ||
node := &TaskNode{ | ||
Name: task.Name, | ||
} | ||
graph.Nodes[task.Name] = node | ||
} | ||
|
||
// Add dependencies to each node | ||
for _, task := range tasks { | ||
node := graph.Nodes[task.Name] | ||
for _, dep := range task.RunAfter { | ||
depNode := graph.Nodes[dep] | ||
node.Dependencies = append(node.Dependencies, depNode) | ||
} | ||
} | ||
|
||
return graph | ||
} | ||
|
||
func (g *TaskGraph) ToDOT() *DOT { | ||
dot := &DOT{ | ||
Name: g.PipelineName, | ||
Format: "digraph", | ||
} | ||
|
||
for _, node := range g.Nodes { | ||
for _, dep := range node.Dependencies { | ||
dot.Edges = append(dot.Edges, fmt.Sprintf(" \"%s\" -> \"%s\"", dep.Name, node.Name)) | ||
} | ||
} | ||
|
||
return dot | ||
} | ||
|
||
func (d *DOT) String() string { | ||
var buf bytes.Buffer | ||
buf.WriteString(fmt.Sprintf("%s \"%s\" {\n", d.Format, d.Name)) | ||
for _, edge := range d.Edges { | ||
buf.WriteString(fmt.Sprintf("%s\n", edge)) | ||
} | ||
buf.WriteString("}\n") | ||
return buf.String() | ||
} | ||
|
||
func (g *TaskGraph) ToPlantUML() string { | ||
plantuml := "@startuml\n\n" | ||
for _, node := range g.Nodes { | ||
nodeName := strings.ReplaceAll(node.Name, "-", "_") | ||
if len(node.Dependencies) == 0 { | ||
plantuml += fmt.Sprintf("[*] --> %s\n", nodeName) | ||
} | ||
for _, dep := range node.Dependencies { | ||
depName := strings.ReplaceAll(dep.Name, "-", "_") | ||
plantuml += fmt.Sprintf("%s <-down- %s\n", nodeName, depName) | ||
} | ||
plantuml += "\n" | ||
} | ||
plantuml += "@enduml\n" | ||
return plantuml | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,121 +1,110 @@ | ||
package main | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"fmt" | ||
"log" | ||
"os" | ||
|
||
v1pipeline "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" | ||
"github.com/spf13/cobra" | ||
"github.com/tektoncd/pipeline/pkg/client/clientset/versioned" | ||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/client-go/rest" | ||
"k8s.io/client-go/tools/clientcmd" | ||
) | ||
|
||
const ( | ||
namespace = "edp-delivery-tekton-dev" | ||
) | ||
|
||
type TaskGraph struct { | ||
PipelineName string | ||
Nodes map[string]*TaskNode | ||
} | ||
|
||
type TaskNode struct { | ||
Name string | ||
Dependencies []*TaskNode | ||
type Options struct { | ||
Namespace string | ||
ObjectKind string | ||
} | ||
|
||
func main() { | ||
// Get the Kubernetes configuration | ||
config, err := rest.InClusterConfig() | ||
if err != nil { | ||
kubeconfig := os.Getenv("KUBECONFIG") | ||
if kubeconfig == "" { | ||
kubeconfig = clientcmd.RecommendedHomeFile | ||
} | ||
config, err = clientcmd.BuildConfigFromFlags("", kubeconfig) | ||
if err != nil { | ||
log.Fatalf("Failed to get Kubernetes configuration: %v", err) | ||
} | ||
} | ||
|
||
// Create a Kubernetes clientset for the Tekton API group | ||
tektonClient, err := versioned.NewForConfig(config) | ||
if err != nil { | ||
log.Fatalf("Failed to create Tekton clientset: %v", err) | ||
} | ||
|
||
// Get all Tekton Pipelines | ||
pipelines, err := tektonClient.TektonV1().Pipelines(namespace).List(context.TODO(), v1.ListOptions{}) | ||
if err != nil { | ||
log.Fatalf("Failed to get Pipelines: %v", err) | ||
} | ||
for _, pipeline := range pipelines.Items { | ||
// Build the task graph | ||
graph := BuildTaskGraph(pipeline.Spec.Tasks) | ||
graph.PipelineName = pipeline.Name | ||
|
||
// Generate the DOT format string | ||
dot := graph.ToDOT() | ||
|
||
// Print the DOT format string | ||
fmt.Println(dot) | ||
} | ||
|
||
// Get all Tekton PipelineRuns | ||
pipelineRuns, err := tektonClient.TektonV1().PipelineRuns(namespace).List(context.TODO(), v1.ListOptions{}) | ||
if err != nil { | ||
log.Fatalf("Failed to get PipelineRuns: %v", err) | ||
} | ||
for _, pipelineRun := range pipelineRuns.Items { | ||
// Build the task graph | ||
graph := BuildTaskGraph(pipelineRun.Status.PipelineSpec.Tasks) | ||
graph.PipelineName = pipelineRun.Name | ||
// Generate the DOT format string | ||
dot := graph.ToDOT() | ||
|
||
// Print the DOT format string | ||
fmt.Println(dot) | ||
} | ||
|
||
} | ||
|
||
func BuildTaskGraph(tasks []v1pipeline.PipelineTask) *TaskGraph { | ||
graph := &TaskGraph{ | ||
Nodes: make(map[string]*TaskNode), | ||
} | ||
|
||
// Create a node for each task | ||
for _, task := range tasks { | ||
node := &TaskNode{ | ||
Name: task.Name, | ||
} | ||
graph.Nodes[task.Name] = node | ||
var options Options | ||
|
||
// Define the root command | ||
rootCmd := &cobra.Command{ | ||
Use: "graph", | ||
Run: func(cmd *cobra.Command, args []string) { | ||
// Create the Kubernetes client | ||
config, err := rest.InClusterConfig() | ||
if err != nil { | ||
kubeconfig := os.Getenv("KUBECONFIG") | ||
if kubeconfig == "" { | ||
kubeconfig = clientcmd.RecommendedHomeFile | ||
} | ||
config, err = clientcmd.BuildConfigFromFlags("", kubeconfig) | ||
if err != nil { | ||
log.Fatalf("Failed to get Kubernetes configuration: %v", err) | ||
} | ||
} | ||
tektonClient, err := versioned.NewForConfig(config) | ||
if err != nil { | ||
log.Fatalf("Failed to create Tekton client: %v", err) | ||
} | ||
|
||
// Get the namespace to use | ||
if options.Namespace == "" { | ||
namespace, _, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( | ||
clientcmd.NewDefaultClientConfigLoadingRules(), | ||
&clientcmd.ConfigOverrides{}, | ||
).Namespace() | ||
if err != nil { | ||
log.Fatalf("Failed to get namespace from kubeconfig: %v", err) | ||
} | ||
if namespace == "" { | ||
namespace = "default" | ||
} | ||
options.Namespace = namespace | ||
} | ||
|
||
switch options.ObjectKind { | ||
case "Pipeline": | ||
pipelines, err := tektonClient.TektonV1().Pipelines(options.Namespace).List(context.TODO(), v1.ListOptions{}) | ||
if err != nil { | ||
log.Fatalf("Failed to get Pipelines: %v", err) | ||
} | ||
for _, pipeline := range pipelines.Items { | ||
// Build the task graph | ||
graph := BuildTaskGraph(pipeline.Spec.Tasks) | ||
graph.PipelineName = pipeline.Name | ||
|
||
// Generate the DOT format string | ||
dot := graph.ToDOT() | ||
|
||
// Print the DOT format string | ||
fmt.Println(dot) | ||
} | ||
case "PipelineRun": | ||
pipelineRuns, err := tektonClient.TektonV1().PipelineRuns(options.Namespace).List(context.TODO(), v1.ListOptions{}) | ||
if err != nil { | ||
log.Fatalf("Failed to get PipelineRuns: %v", err) | ||
} | ||
for _, pipelineRun := range pipelineRuns.Items { | ||
// Build the task graph | ||
graph := BuildTaskGraph(pipelineRun.Status.PipelineSpec.Tasks) | ||
plantuml := graph.ToPlantUML() | ||
fmt.Println(plantuml) | ||
// graph.PipelineName = pipelineRun.Name | ||
|
||
// Generate the DOT format string | ||
// dot := graph.ToDOT() | ||
|
||
// Print the DOT format string | ||
// fmt.Println(dot) | ||
} | ||
default: | ||
log.Fatalf("Invalid kind type: %s", options.ObjectKind) | ||
} | ||
}, | ||
} | ||
|
||
// Add dependencies to each node | ||
for _, task := range tasks { | ||
node := graph.Nodes[task.Name] | ||
for _, dep := range task.RunAfter { | ||
depNode := graph.Nodes[dep] | ||
node.Dependencies = append(node.Dependencies, depNode) | ||
} | ||
} | ||
|
||
return graph | ||
} | ||
// Define the command-line options | ||
rootCmd.Flags().StringVar(&options.Namespace, "namespace", "", "the Kubernetes namespace to use") | ||
rootCmd.Flags().StringVar(&options.ObjectKind, "kind", "Pipeline", "the kind of the Tekton object to parse (Pipeline or PipelineRun)") | ||
|
||
func (g *TaskGraph) ToDOT() string { | ||
var buf bytes.Buffer | ||
buf.WriteString(fmt.Sprintf("digraph \"%s\" {\n", g.PipelineName)) | ||
for _, node := range g.Nodes { | ||
for _, dep := range node.Dependencies { | ||
buf.WriteString(fmt.Sprintf(" \"%s\" -> \"%s\"\n", dep.Name, node.Name)) | ||
} | ||
// Parse the command-line options | ||
if err := rootCmd.Execute(); err != nil { | ||
fmt.Println(err) | ||
os.Exit(1) | ||
} | ||
buf.WriteString("}\n") | ||
return buf.String() | ||
} |