Repository: kurator-dev/kurator
Pull request #537 (2024-01-04 03:49:54)

pipeline: complete pipeline logs cli
**What type of PR is this?**


/kind feature


**What this PR does / why we need it**:
part of https://github.com/kurator-dev/kurator/pull/533

here shows how to use it

```
root@xql:~/go/src/kurator.dev/kurator#kurator pipeline execution logs -h
Display aggregated logs from multiple tasks within kurator pipeline execution

Usage:
  kurator pipeline execution logs [flags]

Examples:
  # Display aggregated logs from an example pipeline execution in the default namespace
  kurator pipeline execution logs example-pipeline-execution

  # Display aggregated logs from an example pipeline execution in a specific namespace (replace 'example-namespace' with your namespace)
  kurator pipeline execution logs example-pipeline-execution -n example-namespace

  # Display the last 10 lines of logs from an example pipeline execution
  kurator pipeline execution logs example-pipeline-execution --tail 10


Flags:
  -h, --help               help for logs
  -n, --namespace string   specific namespace (default "default")
      --tail int           number of lines to display from the end of the logs; must be greater than 0 to take effect

Global Flags:
      --context string           name of the kubeconfig context to use
      --dry-run                  console/log output only, make no changes.
      --home-dir string          install path, default to $HOME/.kurator (default "/root/.kurator")
  -c, --kubeconfig string        path to the kubeconfig file, default to karmada apiserver config (default "/etc/karmada/karmada-apiserver.config")
      --wait-interval duration   interval used for checking pod ready, default value is 1s. (default 1s)
      --wait-timeout duration    timeout used for checking pod ready, default value is 2m. (default 2m0s)


root@xql:~/go/src/kurator.dev/kurator# kurator pipeline execution logs quick-start-run-frb7l  -n kurator-pipeline --tail 10 --kubeconfig /root/.kube/kurator-host.config
INFO[2024-01-04 11:47:34] Fetching logs for TaskRun: quick-start-run-frb7l-git-clone 
INFO[2024-01-04 11:47:34] Fetching logs for container 'step-clone' in Pod 'quick-start-run-frb7l-git-clone-pod' 
INFO[2024-01-04 11:47:34] Logs from container 'step-clone':
+ cd /workspace/source/
+ git rev-parse HEAD
+ RESULT_SHA=1858f8e5129516d6e7d9ad993b1ec41cef922d18
+ EXIT_CODE=0
+ '[' 0 '!=' 0 ]
+ git log -1 '--pretty=%ct'
+ RESULT_COMMITTER_DATE=1703581193
+ printf '%s' 1703581193
+ printf '%s' 1858f8e5129516d6e7d9ad993b1ec41cef922d18
+ printf '%s' https://github.com/Xieql/podinfo 
INFO[2024-01-04 11:47:34] Fetching logs for TaskRun: quick-start-run-frb7l-build-and-push-image 
INFO[2024-01-04 11:47:34] Fetching logs for container 'step-build-and-push' in Pod 'quick-start-run-frb7l-build-and-push-image-pod' 
INFO[2024-01-04 11:47:34] Logs from container 'step-build-and-push':
INFO[0161] RUN chown -R app:app ./                      
INFO[0161] Cmd: /bin/sh                                 
INFO[0161] Args: [-c chown -R app:app ./]               
INFO[0161] Running: [/bin/sh -c chown -R app:app ./]    
INFO[0161] Taking snapshot of full filesystem...        
INFO[0162] USER app                                     
INFO[0162] Cmd: USER                                    
INFO[0162] CMD ["./podinfo"]                            
INFO[0162] Pushing image to ghcr.io/xieql/kurator-final:0.4.1 
INFO[0188] Pushed ghcr.io/xieql/kurator-final@sha256:73c1ad5046233adb70aae2ee5df6e00f2c521e89cc980a954dc024d12add8daf  
INFO[2024-01-04 11:47:34] Fetching logs for container 'step-write-url' in Pod 'quick-start-run-frb7l-build-and-push-image-pod' 
INFO[2024-01-04 11:47:34] Logs from container 'step-write-url':
ghcr.io/xieql/kurator-final:0.4.1 
INFO[2024-01-04 11:47:34] Fetching logs for TaskRun: quick-start-run-frb7l-cat-readme 
INFO[2024-01-04 11:47:34] Fetching logs for container 'step-cat-readme-quick-start' in Pod 'quick-start-run-frb7l-cat-readme-pod' 
INFO[2024-01-04 11:47:34] Logs from container 'step-cat-readme-quick-start':
To delete podinfo's Helm repository and release from your cluster run:

flux -n default delete source helm podinfo
flux -n default delete helmrelease podinfo

If you wish to manage the lifecycle of your applications in a **GitOps** manner, check out
this [workflow example](https://github.com/fluxcd/flux2-kustomize-helm-example)
for multi-env deployments with Flux, Kustomize and Helm. 
INFO[2024-01-04 11:47:34] Fetching logs for TaskRun: quick-start-run-frb7l-go-test 
INFO[2024-01-04 11:47:34] Fetching logs for container 'step-unit-test' in Pod 'quick-start-run-frb7l-go-test-pod' 
INFO[2024-01-04 11:47:34] Logs from container 'step-unit-test':
--- PASS: TestInfoHandler (0.00s)
=== RUN   TestStatusHandler
--- PASS: TestStatusHandler (0.00s)
=== RUN   TestTokenHandler
--- PASS: TestTokenHandler (0.00s)
=== RUN   TestVersionHandler
--- PASS: TestVersionHandler (0.00s)
PASS
coverage: 14.4% of statements
ok  	github.com/stefanprodan/podinfo/pkg/api	1.088s	coverage: 14.4% of statements 
INFO[2024-01-04 11:47:34] Fetching logs for TaskRun: quick-start-run-frb7l-go-lint 
INFO[2024-01-04 11:47:34] Fetching logs for container 'step-lint' in Pod 'quick-start-run-frb7l-go-lint-pod' 
INFO[2024-01-04 11:47:34] Logs from container 'step-lint':
level=info msg="[config_reader] Config search paths: [./ /workspace/src /workspace / /root]"
level=info msg="[lintersdb] Active 2 linters: [govet ineffassign]"
level=info msg="[loader] Go packages loading at mode 575 (deps|imports|types_sizes|compiled_files|files|name|exports_file) took 1m0.435700461s"
level=info msg="[runner/filename_unadjuster] Pre-built 0 adjustments in 4.381259ms"
level=info msg="[linters_context/goanalysis] analyzers took 1.651993451s with top 10 stages: inspect: 842.409271ms, ctrlflow: 402.369985ms, printf: 382.21663ms, ineffassign: 12.079525ms, slog: 3.20068ms, lostcancel: 1.346293ms, copylocks: 1.333706ms, directive: 1.287065ms, bools: 976.125µs, composites: 435.523µs"
level=info msg="[runner] processing took 2.828µs with stages: max_same_issues: 349ns, skip_dirs: 325ns, nolint: 302ns, cgo: 230ns, exclude-rules: 208ns, max_from_linter: 146ns, source_code: 143ns, path_prettifier: 133ns, filename_unadjuster: 133ns, autogenerated_exclude: 131ns, skip_files: 124ns, identifier_marker: 118ns, max_per_file_from_linter: 72ns, severity-rules: 59ns, path_shortener: 56ns, sort_results: 53ns, diff: 51ns, exclude: 50ns, uniq_by_line: 50ns, fixer: 50ns, path_prefixer: 45ns"
level=info msg="[runner] linters took 3.81614172s with stages: goanalysis_metalinter: 3.816076131s"
level=info msg="File cache stats: 0 entries of total size 0B"
level=info msg="Memory: 644 samples, avg is 35.5MB, max is 435.9MB"
level=info msg="Execution took 1m4.265454941s" 
```


netlify[bot]: ### <span aria-hidden="true">👷</span> Deploy Preview for *kurator-dev* processing.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 0a707782c9ccee209857ec2d1caef1b142758fdd |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/kurator-dev/deploys/65962ae4fd36460008775095 |
kurator-bot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by:
**Once this PR has been reviewed and has the lgtm label**, please assign hzxuzhonghu for approval. For more information see [the Kubernetes Code Review Process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process).

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kurator-dev%2Fkurator).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kurator-dev/kurator/blob/main/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["hzxuzhonghu"]} -->
Xieql: /label tide/merge-method-squash
Xieql: 
/label tide/merge-method-squash


Xieql: It seems that the "/label tide/merge-method-squash" does not works. @hzxuzhonghu 
hzxuzhonghu: /lgtm

Patch:
diff --git a/cmd/kurator/app/pipeline/execution/execution.go b/cmd/kurator/app/pipeline/execution/execution.go
index 5c23507..37b7b7c 100644
--- a/cmd/kurator/app/pipeline/execution/execution.go
+++ b/cmd/kurator/app/pipeline/execution/execution.go
@@ -20,6 +20,7 @@ import (
 	"github.com/spf13/cobra"
 
 	"kurator.dev/kurator/cmd/kurator/app/pipeline/execution/list"
+	"kurator.dev/kurator/cmd/kurator/app/pipeline/execution/logs"
 	"kurator.dev/kurator/pkg/generic"
 )
 
@@ -34,6 +35,7 @@ func NewCmd(opts *generic.Options) *cobra.Command {
 	}
 
 	executionCmd.AddCommand(list.NewCmd(opts))
+	executionCmd.AddCommand(logs.NewCmd(opts))
 
 	return executionCmd
 }
diff --git a/cmd/kurator/app/pipeline/execution/logs/logs.go b/cmd/kurator/app/pipeline/execution/logs/logs.go
new file mode 100644
index 0000000..9d66010
--- /dev/null
+++ b/cmd/kurator/app/pipeline/execution/logs/logs.go
@@ -0,0 +1,74 @@
+/*
+Copyright Kurator Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package logs
+
+import (
+	"fmt"
+
+	"github.com/sirupsen/logrus"
+	"github.com/spf13/cobra"
+
+	"kurator.dev/kurator/pkg/generic"
+	"kurator.dev/kurator/pkg/pipeline/execution/logs"
+)
+
+func NewCmd(opts *generic.Options) *cobra.Command {
+	var Args = logs.Args{}
+	logsCmd := &cobra.Command{
+		Use:     "logs",
+		Short:   "Display aggregated logs from multiple tasks within kurator pipeline execution",
+		Example: getExample(),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			if len(args) < 1 {
+				return fmt.Errorf("please specify the pipeline execution name")
+			}
+
+			pipelineExecutionName := args[0] // the first one in args[] is the name of pipeline execution
+
+			PipelineList, err := logs.NewPipelineLogs(opts, &Args, pipelineExecutionName)
+			if err != nil {
+				logrus.Errorf("pipeline excution logs init error: %v", err)
+				return fmt.Errorf("pipeline excution logs init error: %v", err)
+			}
+
+			logrus.Debugf("start logs pipeline execution obj, Global: %+v ", opts)
+			if err := PipelineList.LogsExecute(); err != nil {
+				logrus.Errorf("pipeline logs execute error: %v", err)
+				return fmt.Errorf("pipeline logs execute error: %v", err)
+			}
+
+			return nil
+		},
+	}
+
+	logsCmd.PersistentFlags().StringVarP(&Args.Namespace, "namespace", "n", "default", "specific namespace")
+	logsCmd.PersistentFlags().Int64Var(&Args.TailLines, "tail", 0, "number of lines to display from the end of the logs in each task pod container, must be greater than 0 to take effect")
+
+	return logsCmd
+}
+
+func getExample() string {
+	return `  # Display aggregated logs from an example pipeline execution in the default namespace
+  kurator pipeline execution logs example-pipeline-execution
+
+  # Display aggregated logs from an example pipeline execution in a specific namespace (replace 'example-namespace' with your namespace)
+  kurator pipeline execution logs example-pipeline-execution -n example-namespace
+
+  # Display the last 10 lines of logs from an example pipeline execution
+  kurator pipeline execution logs example-pipeline-execution --tail 10
+`
+}
diff --git a/pkg/apis/pipeline/v1alpha1/type.go b/pkg/apis/pipeline/v1alpha1/type.go
index ef54910..a487be7 100644
--- a/pkg/apis/pipeline/v1alpha1/type.go
+++ b/pkg/apis/pipeline/v1alpha1/type.go
@@ -132,6 +132,37 @@ const (
 	// - GOMODCACHE: value for go module caching path (default: "")
 	GoTest TaskTemplate = "go-test"
 
+	// GoLint performs linting on Go source code, checking for coding style issues and common errors.
+	// This task uses golangci-lint, a popular linter aggregator for Go, providing a comprehensive check of the code.
+	// The following parameters can be configured for the task:
+	// - package: The base package (and its children) under validation. Defaults to the current directory.
+	// - context: The directory path to use as context for the linting process. Defaults to the current directory.
+	// - flags: Additional flags to use for the lint command. Defaults to "--verbose" for detailed linting output.
+	// - version: The golangci-lint version to use for linting. Defaults to the latest available version.
+	// - GOOS: The target operating system for the running program. Defaults to "linux".
+	// - GOARCH: The target architecture for the running program. Defaults to "amd64".
+	// - GO111MODULE: Specifies the module support. Defaults to "auto".
+	// - GOCACHE: The path for Go caching. Defaults to an empty string, allowing Go to use its default path.
+	// - GOMODCACHE: The path for Go mod caching. Also defaults to an empty string.
+	// - GOLANGCI_LINT_CACHE: The path for golangci-lint caching. Defaults to an empty string.
+	GoLint TaskTemplate = "go-lint"
+
+	// BuildPushImage builds and pushes a Docker image using Kaniko.
+	// This Task builds a simple Dockerfile with Kaniko and pushes it to a registry.
+	// It stores the image name and digest as results, enabling Tekton Chains to recognize
+	// that an image was built and to sign it.
+	// The params for this Task include:
+	// - IMAGE: The name (reference) of the image to build. This parameter must be explicitly set by the user.
+	// - DOCKERFILE: The path to the Dockerfile that should be built.
+	//   Defaults to "./Dockerfile".
+	// - CONTEXT: The build context used by Kaniko.
+	//   Defaults to "./".
+	// - EXTRA_ARGS: An array of extra arguments for the Kaniko build. More details can be found in the Kaniko documentation.See https://github.com/GoogleContainerTools/kaniko?tab=readme-ov-file#additional-flags.
+	//   Default to []
+	// - BUILDER_IMAGE: The kaniko image that will be used to run the builds.
+	//   Defaults to is v1.19.2 debug version: gcr.io/kaniko-project/executor@sha256:899886a2db1c127ff1565d5c7b1e574af1810bbdad048e9850e4f40b5848d79c
+	BuildPushImage TaskTemplate = "build-and-push-image"
+
 	// TODO: add more PredefinedTask
 )
 
diff --git a/pkg/fleet-manager/pipeline/render/manifests/rbac/rbac.tpl b/pkg/fleet-manager/pipeline/render/manifests/rbac/rbac.tpl
deleted file mode 100644
index bf36061..0000000
--- a/pkg/fleet-manager/pipeline/render/manifests/rbac/rbac.tpl
+++ /dev/null
@@ -1,33 +0,0 @@
-apiVersion: v1
-kind: ServiceAccount
-metadata:
-  name: "{{ .ServiceAccountName }}"
-  namespace: "{{ .PipelineNamespace }}"
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: RoleBinding
-metadata:
-  name: "{{ .BroadResourceRoleBindingName }}"
-  namespace: "{{ .PipelineNamespace }}"
-subjects:
-- kind: ServiceAccount
-  name: "{{ .ServiceAccountName }}"
-  namespace: "{{ .PipelineNamespace }}"
-roleRef:
-  apiGroup: rbac.authorization.k8s.io
-  kind: ClusterRole
-  name: tekton-triggers-eventlistener-roles # add role for handle broad-resource, such as eventListener, triggers, configmaps and so on. `tekton-triggers-eventlistener-roles` is provided by Tekton
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: RoleBinding
-metadata:
-  name: "{{ .SecretRoleBindingName }}"
-  namespace: "{{ .PipelineNamespace }}"
-subjects:
-- kind: ServiceAccount
-  name: "{{ .ServiceAccountName }}"
-  namespace: "{{ .PipelineNamespace }}"
-roleRef:
-  apiGroup: rbac.authorization.k8s.io
-  kind: ClusterRole
-  name: tekton-triggers-eventlistener-clusterroles # add role for handle secret, clustertriggerbinding and clusterinterceptors. `tekton-triggers-eventlistener-clusterroles` is provided by Tekton
diff --git a/pkg/fleet-manager/pipeline/render/rbac.go b/pkg/fleet-manager/pipeline/render/rbac.go
index bb62eaa..bdf9bb7 100644
--- a/pkg/fleet-manager/pipeline/render/rbac.go
+++ b/pkg/fleet-manager/pipeline/render/rbac.go
@@ -18,15 +18,12 @@ package render
 
 import (
 	"fmt"
-	"io/fs"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 const (
-	// RBACTemplateFileName is the name of the RBAC template file.
-	RBACTemplateFileName = "rbac.tpl"
-	RBACTemplateName     = "pipeline rbac template"
-	SecretSuffix         = "-secret"
-	BroadResourceSuffix  = "-broad-resource"
+	RBACTemplateName = "pipeline rbac template"
 )
 
 // RBACConfig contains the configuration data required for the RBAC template.
@@ -34,27 +31,77 @@ const (
 type RBACConfig struct {
 	PipelineName      string // Name of the pipeline.
 	PipelineNamespace string // Kubernetes namespace where the pipeline is deployed.
+	OwnerReference    *metav1.OwnerReference
 }
 
-// ServiceAccountName generates the service account name using the pipeline name and namespace.
+// ServiceAccountName generates the service account name using the pipeline name.
 func (rbac RBACConfig) ServiceAccountName() string {
 	return rbac.PipelineName
 }
 
-// RoleBindingName generates the role binding name using the service account name.
-func (rbac RBACConfig) BroadResourceRoleBindingName() string {
-	return rbac.ServiceAccountName() + BroadResourceSuffix
-}
-
-// ClusterRoleBindingName generates the cluster role binding name using the service account name.
-func (rbac RBACConfig) SecretRoleBindingName() string {
-	return rbac.ServiceAccountName() + SecretSuffix
-}
-
-// renderRBAC renders the RBAC configuration using a specified template.
-func renderRBAC(fsys fs.FS, cfg RBACConfig) ([]byte, error) {
+// RenderRBAC renders the RBAC configuration using a specified template.
+func RenderRBAC(cfg RBACConfig) ([]byte, error) {
 	if cfg.PipelineName == "" || cfg.PipelineNamespace == "" {
 		return nil, fmt.Errorf("invalid RBACConfig: PipelineName and PipelineNamespace must not be empty")
 	}
-	return renderPipelineTemplate(fsys, RBACTemplateFileName, RBACTemplateName, cfg)
+	return renderTemplate(RBACTemplateContent, RBACTemplateName, cfg)
 }
+
+const RBACTemplateContent = `apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: "{{ .ServiceAccountName }}"
+  namespace: "{{ .PipelineNamespace }}"
+{{- if .OwnerReference }}
+  ownerReferences:
+  - apiVersion: "{{ .OwnerReference.APIVersion }}"
+    kind: "{{ .OwnerReference.Kind }}"
+    name: "{{ .OwnerReference.Name }}"
+    uid: "{{ .OwnerReference.UID }}"
+{{- end }}
+secrets:
+  - name: "chain-credentials"
+    namespace: "{{ .PipelineNamespace }}"
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: "{{ .PipelineName }}"
+  namespace: "{{ .PipelineNamespace }}"
+{{- if .OwnerReference }}
+  ownerReferences:
+  - apiVersion: "{{ .OwnerReference.APIVersion }}"
+    kind: "{{ .OwnerReference.Kind }}"
+    name: "{{ .OwnerReference.Name }}"
+    uid: "{{ .OwnerReference.UID }}"
+{{- end }}
+subjects:
+- kind: ServiceAccount
+  name: "{{ .ServiceAccountName }}"
+  namespace: "{{ .PipelineNamespace }}"
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: tekton-triggers-eventlistener-roles # add role for handle broad-resource, such as eventListener, triggers, configmaps and so on. tekton-triggers-eventlistener-roles is provided by Tekton
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: "{{ .PipelineName }}"
+  namespace: "{{ .PipelineNamespace }}"
+{{- if .OwnerReference }}
+  ownerReferences:
+  - apiVersion: "{{ .OwnerReference.APIVersion }}"
+    kind: "{{ .OwnerReference.Kind }}"
+    name: "{{ .OwnerReference.Name }}"
+    uid: "{{ .OwnerReference.UID }}"
+{{- end }}
+subjects:
+- kind: ServiceAccount
+  name: "{{ .ServiceAccountName }}"
+  namespace: "{{ .PipelineNamespace }}"
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: tekton-triggers-eventlistener-clusterroles # add role for handle secret, clustertriggerbinding and clusterinterceptors. tekton-triggers-eventlistener-clusterroles is provided by Tekton
+`
diff --git a/pkg/fleet-manager/pipeline/render/util.go b/pkg/fleet-manager/pipeline/render/util.go
index d5f410c..74d4b66 100644
--- a/pkg/fleet-manager/pipeline/render/util.go
+++ b/pkg/fleet-manager/pipeline/render/util.go
@@ -18,23 +18,15 @@ package render
 
 import (
 	"bytes"
-	"html/template"
-	"io/fs"
+	"text/template"
 
 	"github.com/Masterminds/sprig/v3"
 	"sigs.k8s.io/yaml"
 )
 
-// renderPipelineTemplate reads, parses, and renders a template file using the provided configuration data.
-func renderPipelineTemplate(fsys fs.FS, tplFileName, tplName string, cfg interface{}) ([]byte, error) {
-	out, err := fs.ReadFile(fsys, tplFileName)
-	if err != nil {
-		return nil, err
-	}
-
-	t := template.New(tplName)
-
-	tpl, err := t.Funcs(funMap()).Parse(string(out))
+// renderTemplate reads, parses, and renders a template file using the provided configuration data.
+func renderTemplate(tplFileString, tplName string, cfg interface{}) ([]byte, error) {
+	tpl, err := template.New(tplName).Funcs(funMap()).Parse(tplFileString)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/pipeline/execution/logs/logs.go b/pkg/pipeline/execution/logs/logs.go
new file mode 100644
index 0000000..c95e2e5
--- /dev/null
+++ b/pkg/pipeline/execution/logs/logs.go
@@ -0,0 +1,143 @@
+/*
+Copyright Kurator Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package logs
+
+import (
+	"bytes"
+	"context"
+	"io"
+
+	"github.com/sirupsen/logrus"
+	tektonapi "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/types"
+	ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
+
+	"kurator.dev/kurator/pkg/client"
+	"kurator.dev/kurator/pkg/generic"
+)
+
+// pipelineLogs is used to handle and display aggregated logs from a specific pipeline execution.
+type pipelineLogs struct {
+	*client.Client       // Embedded client for API interactions.
+	args           *Args // Arguments passed through command line flags.
+	options        *generic.Options
+	name           string // Name of the pipeline execution to fetch logs for.
+}
+
+// Args holds the command line arguments for the logs command.
+type Args struct {
+	Namespace string // Namespace from which to fetch the logs.
+	TailLines int64  // Number of lines to display from the end of the logs in each task pod container, must be greater than 0 to take effect
+}
+
+// NewPipelineLogs creates a new pipelineLogs instance.
+func NewPipelineLogs(opts *generic.Options, args *Args, pipelineExecutionName string) (*pipelineLogs, error) {
+	pList := &pipelineLogs{
+		options: opts,
+		args:    args,
+		name:    pipelineExecutionName,
+	}
+	rest := opts.RESTClientGetter()
+	c, err := client.NewClient(rest)
+	if err != nil {
+		return nil, err
+	}
+	pList.Client = c
+	return pList, nil
+}
+
+// LogsExecute fetches and displays aggregated logs from pipeline execution.
+func (p *pipelineLogs) LogsExecute() error {
+	namespace := p.args.Namespace
+
+	pipelineRun := &tektonapi.PipelineRun{}
+
+	namespacedName := types.NamespacedName{
+		Namespace: namespace,
+		Name:      p.name,
+	}
+
+	// Retrieve the PipelineRun object
+	if err := p.CtrlRuntimeClient().Get(context.Background(), namespacedName, pipelineRun); err != nil {
+		logrus.Errorf("failed to get PipelineRun '%s' in namespace '%s', %v", p.name, namespace, err)
+		return err
+	}
+
+	// Retrieve the ChildReferences of TaskRun
+	taskPodList := pipelineRun.Status.ChildReferences
+
+	// Iterate through each TaskRun to fetch their logs
+	for _, taskRef := range taskPodList {
+		if taskRef.Kind == "TaskRun" {
+			logrus.Infof("Fetching logs for TaskRun: %s", taskRef.Name)
+			if err := p.fetchAndPrintPodLogs(taskRef.Name, namespace); err != nil {
+				logrus.Errorf("failed to fetch logs for TaskRun '%s' in namespace '%s', %v", taskRef.Name, namespace, err)
+				// here just continue to attempt fetching logs for other TaskRuns, rather than directly return an error.
+			}
+		}
+	}
+
+	return nil
+}
+
+// fetchAndPrintPodLogs fetches and prints the logs of the pod associated with a given TaskRun.
+func (p *pipelineLogs) fetchAndPrintPodLogs(taskRunName, namespace string) error {
+	podName := getPodNameFromTaskRun(taskRunName)
+	pod := &corev1.Pod{}
+
+	// Get the details of the Pod
+	if err := p.Client.CtrlRuntimeClient().Get(context.Background(), ctrlclient.ObjectKey{Name: podName, Namespace: namespace}, pod); err != nil {
+		return err
+	}
+
+	// Iterate through each container in the Pod to fetch logs
+	for _, container := range pod.Spec.Containers {
+		logrus.Infof("Fetching logs for container '%s' in Pod '%s'", container.Name, podName)
+
+		podLogOpts := corev1.PodLogOptions{Container: container.Name}
+		if p.args.TailLines > 0 {
+			podLogOpts.TailLines = &p.args.TailLines
+		}
+
+		req := p.Client.KubeClient().CoreV1().Pods(namespace).GetLogs(podName, &podLogOpts)
+
+		podLogs, err := req.Stream(context.Background())
+		if err != nil {
+			logrus.Errorf("Failed to fetch logs for container '%s', %v", container.Name, err)
+			continue
+		}
+
+		// Read and display the logs
+		buf := new(bytes.Buffer)
+		_, err = io.Copy(buf, podLogs)
+		podLogs.Close()
+		if err != nil {
+			logrus.Errorf("Failed to read logs for container '%s', %v", container.Name, err)
+			continue
+		}
+
+		logrus.Infof("Logs from container '%s':\n%s", container.Name, buf.String())
+	}
+
+	return nil
+}
+
+// getPodNameFromTaskRun gets the pod name for a taskrun. The taskRunName and the name of the pod executing the task differ only by "-pod"
+func getPodNameFromTaskRun(taskRunName string) string {
+	return taskRunName + "-pod"
+}


Test patch:
diff --git a/pkg/fleet-manager/pipeline/render/rbac_test.go b/pkg/fleet-manager/pipeline/render/rbac_test.go
index b8489ae..97a17dc 100644
--- a/pkg/fleet-manager/pipeline/render/rbac_test.go
+++ b/pkg/fleet-manager/pipeline/render/rbac_test.go
@@ -21,15 +21,11 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
-
-	"kurator.dev/kurator/pkg/fleet-manager/manifests"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
-var manifestFS = manifests.BuiltinOrDir("manifests/rbac/")
-
-const expectedRBACFilePath = "testdata/rbac/"
-
 func TestRenderRBAC(t *testing.T) {
+	const expectedRBACFilePath = "testdata/rbac/"
 	// Define test cases including both valid and error scenarios.
 	cases := []struct {
 		name         string
@@ -63,30 +59,30 @@ func TestRenderRBAC(t *testing.T) {
 			expectError: true,
 		},
 		{
-			name: "invalid file system path",
+			name: "configuration with OwnerReference",
 			cfg: RBACConfig{
-				PipelineName:      "example",
+				PipelineName:      "example-with-owner",
 				PipelineNamespace: "default",
+				OwnerReference: &metav1.OwnerReference{
+					APIVersion: "v1",
+					Kind:       "Deployment",
+					Name:       "example-deployment",
+					UID:        "12345678-1234-1234-1234-123456789abc",
+				},
 			},
-			expectError: true,
+			expectError:  false,
+			expectedFile: "with-owner.yaml",
 		},
 	}
 
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
-			fs := manifestFS
-			// Use an invalid file system for the relevant test case.
-			if tc.name == "invalid file system path" {
-				fs = manifests.BuiltinOrDir("invalid-path")
-			}
-
-			result, err := renderRBAC(fs, tc.cfg)
+			result, err := RenderRBAC(tc.cfg)
 
 			if tc.expectError {
 				assert.Error(t, err)
 			} else {
 				assert.NoError(t, err)
-
 				expected, err := os.ReadFile(expectedRBACFilePath + tc.expectedFile)
 				assert.NoError(t, err)
 				assert.Equal(t, string(expected), string(result))
diff --git a/pkg/fleet-manager/pipeline/render/testdata/rbac/default-example.yaml b/pkg/fleet-manager/pipeline/render/testdata/rbac/default-example.yaml
index 97681dc..94d7122 100644
--- a/pkg/fleet-manager/pipeline/render/testdata/rbac/default-example.yaml
+++ b/pkg/fleet-manager/pipeline/render/testdata/rbac/default-example.yaml
@@ -3,11 +3,14 @@ kind: ServiceAccount
 metadata:
   name: "example"
   namespace: "default"
+secrets:
+  - name: "chain-credentials"
+    namespace: "default"
 ---
 apiVersion: rbac.authorization.k8s.io/v1
 kind: RoleBinding
 metadata:
-  name: "example-broad-resource"
+  name: "example"
   namespace: "default"
 subjects:
 - kind: ServiceAccount
@@ -16,12 +19,12 @@ subjects:
 roleRef:
   apiGroup: rbac.authorization.k8s.io
   kind: ClusterRole
-  name: tekton-triggers-eventlistener-roles # add role for handle broad-resource, such as eventListener, triggers, configmaps and so on. `tekton-triggers-eventlistener-roles` is provided by Tekton
+  name: tekton-triggers-eventlistener-roles # add role for handle broad-resource, such as eventListener, triggers, configmaps and so on. tekton-triggers-eventlistener-roles is provided by Tekton
 ---
 apiVersion: rbac.authorization.k8s.io/v1
-kind: RoleBinding
+kind: ClusterRoleBinding
 metadata:
-  name: "example-secret"
+  name: "example"
   namespace: "default"
 subjects:
 - kind: ServiceAccount
@@ -30,4 +33,4 @@ subjects:
 roleRef:
   apiGroup: rbac.authorization.k8s.io
   kind: ClusterRole
-  name: tekton-triggers-eventlistener-clusterroles # add role for handle secret, clustertriggerbinding and clusterinterceptors. `tekton-triggers-eventlistener-clusterroles` is provided by Tekton
+  name: tekton-triggers-eventlistener-clusterroles # add role for handle secret, clustertriggerbinding and clusterinterceptors. tekton-triggers-eventlistener-clusterroles is provided by Tekton
diff --git a/pkg/fleet-manager/pipeline/render/testdata/rbac/with-owner.yaml b/pkg/fleet-manager/pipeline/render/testdata/rbac/with-owner.yaml
new file mode 100644
index 0000000..9f65d41
--- /dev/null
+++ b/pkg/fleet-manager/pipeline/render/testdata/rbac/with-owner.yaml
@@ -0,0 +1,51 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: "example-with-owner"
+  namespace: "default"
+  ownerReferences:
+  - apiVersion: "v1"
+    kind: "Deployment"
+    name: "example-deployment"
+    uid: "12345678-1234-1234-1234-123456789abc"
+secrets:
+  - name: "chain-credentials"
+    namespace: "default"
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: "example-with-owner"
+  namespace: "default"
+  ownerReferences:
+  - apiVersion: "v1"
+    kind: "Deployment"
+    name: "example-deployment"
+    uid: "12345678-1234-1234-1234-123456789abc"
+subjects:
+- kind: ServiceAccount
+  name: "example-with-owner"
+  namespace: "default"
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: tekton-triggers-eventlistener-roles # add role for handle broad-resource, such as eventListener, triggers, configmaps and so on. tekton-triggers-eventlistener-roles is provided by Tekton
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: "example-with-owner"
+  namespace: "default"
+  ownerReferences:
+  - apiVersion: "v1"
+    kind: "Deployment"
+    name: "example-deployment"
+    uid: "12345678-1234-1234-1234-123456789abc"
+subjects:
+- kind: ServiceAccount
+  name: "example-with-owner"
+  namespace: "default"
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: tekton-triggers-eventlistener-clusterroles # add role for handle secret, clustertriggerbinding and clusterinterceptors. tekton-triggers-eventlistener-clusterroles is provided by Tekton