Repository: tektoncd/results
Pull request #382 (2023-03-07 03:15:30)

Bump golang.org/x/crypto from 0.0.0-20210920023735-84f357641f63 to 0.1.0 in /tools/tkn-results
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.0.0-20210920023735-84f357641f63 to 0.1.0.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a href="https://github.com/golang/crypto/commits/v0.1.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golang.org/x/crypto&package-manager=go_modules&previous-version=0.0.0-20210920023735-84f357641f63&new-version=0.1.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/tektoncd/results/network/alerts).

</details>

tekton-robot: @dependabot[bot]: Adding the "do-not-merge/release-note-label-needed" label because no release-note block was detected, please follow our [release note process](https://git.k8s.io/community/contributors/guide/release-notes.md) to remove it.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
tekton-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by:
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **wlynch** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @wlynch` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://prow.tekton.dev/command-help?repo=tektoncd%2Fresults).

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

- **[OWNERS](https://github.com/tektoncd/results/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":["wlynch"]} -->
tekton-robot: Hi @dependabot[bot]. Thanks for your PR.

I'm waiting for a [tektoncd](https://github.com/orgs/tektoncd/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://github.com/orgs/tektoncd/people) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=tektoncd%2Fresults).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

khrm: 
/release-note-none


tekton-robot: @dependabot: PR needs rebase.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
khrm: @dependabot rebase
dependabot[bot]: Sorry, only users with push access can use that command.
khrm: /kind misc
adambkaplan: @dependabot rebase

Patch:
diff --git a/config/base/100-watcher-serviceaccount.yaml b/config/base/100-watcher-serviceaccount.yaml
index be4dd821..9e2445fb 100644
--- a/config/base/100-watcher-serviceaccount.yaml
+++ b/config/base/100-watcher-serviceaccount.yaml
@@ -25,7 +25,7 @@ metadata:
 rules:
   # Watcher needs to be able to create new and update existing results.
   - apiGroups: ["results.tekton.dev"]
-    resources: ["results", "records"]
+    resources: ["logs", "results", "records"]
     verbs: ["create", "get", "update"]
   # Needed to read results and update annotations with Result ID.
   - apiGroups: ["tekton.dev"]
diff --git a/docs/install.md b/docs/install.md
index 9f367170..235a4d73 100644
--- a/docs/install.md
+++ b/docs/install.md
@@ -49,7 +49,7 @@
 ## Installing latest release
 
 ```sh
-$ kubectl apply -f https://storage.googleapis.com/tekton-releases/results/previous/v0.3.0/release.yaml
+$ kubectl apply -f https://storage.googleapis.com/tekton-releases/results/latest/release.yaml
 ```
 
 ## Installing specific release
diff --git a/go.mod b/go.mod
index caac74f3..2c163ab6 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.18
 
 require (
 	github.com/aws/aws-sdk-go-v2 v1.17.6
-	github.com/aws/aws-sdk-go-v2/config v1.18.6
+	github.com/aws/aws-sdk-go-v2/config v1.18.17
 	github.com/aws/aws-sdk-go-v2/credentials v1.13.17
 	github.com/aws/aws-sdk-go-v2/service/s3 v1.29.6
 	github.com/fatih/color v1.13.0
@@ -35,8 +35,8 @@ require (
 	gorm.io/driver/postgres v1.4.6
 	gorm.io/driver/sqlite v1.4.4
 	gorm.io/gorm v1.24.3
-	k8s.io/api v0.25.5
-	k8s.io/apimachinery v0.25.5
+	k8s.io/api v0.25.8
+	k8s.io/apimachinery v0.25.8
 	k8s.io/client-go v0.25.4
 	knative.dev/pkg v0.0.0-20221221230956-4fd6eb8652b7
 	sigs.k8s.io/yaml v1.3.0
@@ -62,7 +62,7 @@ require (
 	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.0 // indirect
 	github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.30 // indirect
 	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.24 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/ini v1.3.31 // indirect
 	github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.18 // indirect
 	github.com/aws/aws-sdk-go-v2/service/ecr v1.17.18 // indirect
 	github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.13.17 // indirect
diff --git a/go.sum b/go.sum
index aad0eec5..16c28a36 100644
--- a/go.sum
+++ b/go.sum
@@ -96,14 +96,12 @@ github.com/aws/aws-sdk-go-v2 v1.17.6/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3eP
 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 h1:dK82zF6kkPeCo8J1e+tGx4JdvDIQzj7ygIoLg8WMuGs=
 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10/go.mod h1:VeTZetY5KRJLuD/7fkQXMU6Mw7H5m/KP2J5Iy9osMno=
 github.com/aws/aws-sdk-go-v2/config v1.17.8/go.mod h1:UkCI3kb0sCdvtjiXYiU4Zx5h07BOpgBTtkPu/49r+kA=
-github.com/aws/aws-sdk-go-v2/config v1.18.6 h1:iSuEAeervBWMHA7Aaq5hCNfwuN2m7x2VuQCnEbbQg68=
-github.com/aws/aws-sdk-go-v2/config v1.18.6/go.mod h1:qyjgnyqpKnNGT+C62zMsrZ/Mn2OodYqwIH0DpXiW8f8=
+github.com/aws/aws-sdk-go-v2/config v1.18.17 h1:jwTkhULSrbr/SQA8tfdYqZxpG8YsRycmIXxJcbrqY5E=
+github.com/aws/aws-sdk-go-v2/config v1.18.17/go.mod h1:Lj3E7XcxJnxMa+AYo89YiL68s1cFJRGduChynYU67VA=
 github.com/aws/aws-sdk-go-v2/credentials v1.12.21/go.mod h1:O+4XyAt4e+oBAoIwNUYkRg3CVMscaIJdmZBOcPgJ8D8=
-github.com/aws/aws-sdk-go-v2/credentials v1.13.6/go.mod h1:VbnUvhw31DUu6aiubViixQwWCBNO/st84dhPeOkmdls=
 github.com/aws/aws-sdk-go-v2/credentials v1.13.17 h1:IubQO/RNeIVKF5Jy77w/LfUvmmCxTnk2TP1UZZIMiF4=
 github.com/aws/aws-sdk-go-v2/credentials v1.13.17/go.mod h1:K9xeFo1g/YPMguMUD69YpwB4Nyi6W/5wn706xIInJFg=
 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.17/go.mod h1:yIkQcCDYNsZfXpd5UX2Cy+sWA1jPgIhGTw9cOBzfVnQ=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21/go.mod h1:ugwW57Z5Z48bpvUyZuaPy4Kv+vEfJWnIrky7RmkBvJg=
 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.0 h1:/2Cb3SK3xVOQA7Xfr5nCWCo5H3UiNINtsVvVdk8sQqA=
 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.0/go.mod h1:neYVaeKr5eT7BzwULuG2YbLhzWZ22lpjKdCybR7AXrQ=
 github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23/go.mod h1:2DFxAQ9pfIRy0imBCJv+vZ2X6RKxves6fbnEuSry6b4=
@@ -115,8 +113,8 @@ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21/go.mod h1:+Gxn8jYn5k9
 github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.24 h1:r+Kv+SEJquhAZXaJ7G4u44cIwXV3f8K+N482NNAzJZA=
 github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.24/go.mod h1:gAuCezX/gob6BSMbItsSlMb6WZGV7K2+fWOvk8xBSto=
 github.com/aws/aws-sdk-go-v2/internal/ini v1.3.24/go.mod h1:jULHjqqjDlbyTa7pfM7WICATnOv+iOhjletM3N0Xbu8=
-github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28 h1:KeTxcGdNnQudb46oOl4d90f2I33DF/c6q3RnZAmvQdQ=
-github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28/go.mod h1:yRZVr/iT0AqyHeep00SZ4YfBAKojXz08w3XMBscdi0c=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.3.31 h1:hf+Vhp5WtTdcSdE+yEcUz8L73sAzN0R+0jQv+Z51/mI=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.3.31/go.mod h1:5zUjguZfG5qjhG9/wqmuyHRyUftl2B5Cp6NNxNC6kRA=
 github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.18 h1:H/mF2LNWwX00lD6FlYfKpLLZgUW7oIzCBkig78x4Xok=
 github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.18/go.mod h1:T2Ku+STrYQ1zIkL1wMvj8P3wWQaaCMKNdz70MT2FLfE=
 github.com/aws/aws-sdk-go-v2/service/ecr v1.17.18 h1:uiF/RI+Up8H2xdgT2GWa20YzxiKEalHieqNjm6HC3Xk=
@@ -136,15 +134,12 @@ github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.21/go.mod h1:WZvNXT
 github.com/aws/aws-sdk-go-v2/service/s3 v1.29.6 h1:W8pLcSn6Uy0eXgDBUUl8M8Kxv7JCoP68ZKTD04OXLEA=
 github.com/aws/aws-sdk-go-v2/service/s3 v1.29.6/go.mod h1:L2l2/q76teehcW7YEsgsDjqdsDTERJeX3nOMIFlgGUE=
 github.com/aws/aws-sdk-go-v2/service/sso v1.11.23/go.mod h1:/w0eg9IhFGjGyyncHIQrXtU8wvNsTJOP0R6PPj0wf80=
-github.com/aws/aws-sdk-go-v2/service/sso v1.11.27/go.mod h1:wo/B7uUm/7zw/dWhBJ4FXuw1sySU5lyIhVg1Bu2yL9A=
 github.com/aws/aws-sdk-go-v2/service/sso v1.12.5 h1:bdKIX6SVF3nc3xJFw6Nf0igzS6Ff/louGq8Z6VP/3Hs=
 github.com/aws/aws-sdk-go-v2/service/sso v1.12.5/go.mod h1:vuWiaDB30M/QTC+lI3Wj6S/zb7tpUK2MSYgy3Guh2L0=
 github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.6/go.mod h1:csZuQY65DAdFBt1oIjO5hhBR49kQqop4+lcuCjf2arA=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.11/go.mod h1:TZSH7xLO7+phDtViY/KUp9WGCJMQkLJ/VpgkTFd5gh8=
 github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.5 h1:xLPZMyuZ4GuqRCIec/zWuIhRFPXh2UOJdLXBSi64ZWQ=
 github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.5/go.mod h1:QjxpHmCwAg0ESGtPQnLIVp7SedTOBMYy+Slr3IfMKeI=
 github.com/aws/aws-sdk-go-v2/service/sts v1.16.19/go.mod h1:h4J3oPZQbxLhzGnk+j9dfYHi5qIOVJ5kczZd658/ydM=
-github.com/aws/aws-sdk-go-v2/service/sts v1.17.7/go.mod h1:+lGbb3+1ugwKrNTWcf2RT05Xmp543B06zDFTwiTLp7I=
 github.com/aws/aws-sdk-go-v2/service/sts v1.18.6 h1:rIFn5J3yDoeuKCE9sESXqM5POTAhOP1du3bv/qTL+tE=
 github.com/aws/aws-sdk-go-v2/service/sts v1.18.6/go.mod h1:48WJ9l3dwP0GSHWGc5sFGGlCkuA82Mc2xnw+T6Q8aDw=
 github.com/aws/smithy-go v1.13.3/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
@@ -1220,12 +1215,12 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-k8s.io/api v0.25.5 h1:mqyHf7aoaYMpdvO87mqpol+Qnsmo+y09S0PMIXwiZKo=
-k8s.io/api v0.25.5/go.mod h1:RzplZX0Z8rV/WhSTfEvnyd91bBhBQTRWo85qBQwRmb8=
+k8s.io/api v0.25.8 h1:pcbnWkCcmjNhp6OEKqR+ojO0CJydpOOw7WiWedjLJAU=
+k8s.io/api v0.25.8/go.mod h1:FaJqAtI13XOERtpLOQTkW3SiSf0lqsUohYqaxCyHI18=
 k8s.io/apiextensions-apiserver v0.25.4 h1:7hu9pF+xikxQuQZ7/30z/qxIPZc2J1lFElPtr7f+B6U=
 k8s.io/apiextensions-apiserver v0.25.4/go.mod h1:bkSGki5YBoZWdn5pWtNIdGvDrrsRWlmnvl9a+tAw5vQ=
-k8s.io/apimachinery v0.25.5 h1:SQomYHvv+aO43qdu3QKRf9YuI0oI8w3RrOQ1qPbAUGY=
-k8s.io/apimachinery v0.25.5/go.mod h1:1S2i1QHkmxc8+EZCIxe/fX5hpldVXk4gvnJInMEb8D4=
+k8s.io/apimachinery v0.25.8 h1:c4kI9xm0U5nid8sBpBvM+2VHlv4Af8KnbhZIodZF/54=
+k8s.io/apimachinery v0.25.8/go.mod h1:ZTl0drTQaFi5gMM3snYI5tWV1XJmRH1gfnDx2QCLsxk=
 k8s.io/cli-runtime v0.25.4 h1:GTSBN7aKBrc2LqpdO30CmHQqJtRmotxV7XsMSP+QZIk=
 k8s.io/cli-runtime v0.25.4/go.mod h1:JGOw1CR8v4Mcz6cEKA7bFQe0bPrNn1l5sGAX1/Ke4Eg=
 k8s.io/client-go v0.25.4 h1:3RNRDffAkNU56M/a7gUfXaEzdhZlYhoW8dgViGy5fn8=
diff --git a/pkg/watcher/reconciler/annotation/annotation.go b/pkg/watcher/reconciler/annotation/annotation.go
index 5624bc9a..f61f0a31 100644
--- a/pkg/watcher/reconciler/annotation/annotation.go
+++ b/pkg/watcher/reconciler/annotation/annotation.go
@@ -14,12 +14,26 @@
 
 package annotation
 
-import "encoding/json"
+import (
+	"encoding/json"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
 
 const (
+
+	// Result identifier.
 	Result = "results.tekton.dev/result"
+
+	// Record identifier.
 	Record = "results.tekton.dev/record"
-	Log    = "results.tekton.dev/log"
+
+	Log = "results.tekton.dev/log"
+
+	// Annotation that signals to the controller that a given child object
+	// (e.g. TaskRun owned by a PipelineRun) is done and up to date in the
+	// API server and therefore, ready to be garbage collected.
+	ChildReadyForDeletion = "results.tekton.dev/childReadyForDeletion"
 )
 
 type Annotation struct {
@@ -27,19 +41,65 @@ type Annotation struct {
 	Value string
 }
 
-// Add creates a jsonpatch path used for adding result / record identifiers
-// an object's annotations field.
-func Add(annotation ...Annotation) ([]byte, error) {
-	annotations := make(map[string]string)
-	for _, annotation := range annotation {
-		if len(annotation.Value) > 0 {
-			annotations[annotation.Name] = annotation.Value
+type mergePatch struct {
+	Metadata metadata `json:"metadata"`
+}
+
+type metadata struct {
+	Annotations map[string]string `json:"annotations"`
+}
+
+// Patch creates a jsonpatch path used for adding result / record identifiers as
+// well as other internal annotations to an object's annotations field.
+func Patch(object metav1.Object, annotations ...Annotation) ([]byte, error) {
+	data := mergePatch{
+		Metadata: metadata{
+			Annotations: map[string]string{},
+		},
+	}
+
+	for _, annotation := range annotations {
+		if len(annotation.Value) != 0 {
+			data.Metadata.Annotations[annotation.Name] = annotation.Value
 		}
 	}
-	data := map[string]interface{}{
-		"metadata": map[string]interface{}{
-			"annotations": annotations,
-		},
+
+	if isChildAndDone(object) {
+		data.Metadata.Annotations[ChildReadyForDeletion] = "true"
 	}
 	return json.Marshal(data)
 }
+
+// isChildAndDone returns true if the object in question is a child resource
+// (i.e. has owner references) and it's done, therefore eligible to be patched
+// with the results.tekton.dev/childReadyForDeletion annotation.
+func isChildAndDone(objecct metav1.Object) bool {
+	if len(objecct.GetOwnerReferences()) == 0 {
+		return false
+	}
+
+	doneObj, ok := objecct.(interface{ IsDone() bool })
+	if !ok {
+		return false
+	}
+	return doneObj.IsDone()
+}
+
+// IsPatched returns true if the object in question contains all relevant
+// annotations or false otherwise.
+func IsPatched(object metav1.Object, annotations ...Annotation) bool {
+	objAnnotations := object.GetAnnotations()
+	if isChildAndDone(object) {
+		if _, found := objAnnotations[ChildReadyForDeletion]; !found {
+			return false
+		}
+	}
+
+	for _, annotation := range annotations {
+		if objAnnotations[annotation.Name] != annotation.Value {
+			return false
+		}
+	}
+
+	return true
+}
diff --git a/pkg/watcher/reconciler/dynamic/dynamic.go b/pkg/watcher/reconciler/dynamic/dynamic.go
index 4cadfb7c..5b03b294 100644
--- a/pkg/watcher/reconciler/dynamic/dynamic.go
+++ b/pkg/watcher/reconciler/dynamic/dynamic.go
@@ -51,9 +51,10 @@ var (
 // Reconciler implements common reconciler behavior across different Tekton Run
 // Object types.
 type Reconciler struct {
-	resultsClient *results.Client
-	objectClient  ObjectClient
-	cfg           *reconciler.Config
+	resultsClient          *results.Client
+	objectClient           ObjectClient
+	cfg                    *reconciler.Config
+	IsReadyForDeletionFunc IsReadyForDeletion
 }
 
 func init() {
@@ -61,12 +62,25 @@ func init() {
 	color.NoColor = true
 }
 
+// IsReadyForDeletion is a predicate function which indicates whether the object
+// being reconciled is ready to be garbage collected. Besides the reqirements
+// that are already enforced by this reconciler, callers may define more
+// specific constraints by providing a function that has the below signature to
+// the Reconciler instance. For instance, the controller that reconciles
+// PipelineRuns can verify whether all dependent TaskRuns are up to date in the
+// API server before deleting all objects in cascade.
+type IsReadyForDeletion func(ctx context.Context, object results.Object) (bool, error)
+
 // NewDynamicReconciler creates a new dynamic Reconciler.
 func NewDynamicReconciler(rc pb.ResultsClient, lc pb.LogsClient, oc ObjectClient, cfg *reconciler.Config) *Reconciler {
 	return &Reconciler{
 		resultsClient: results.NewClient(rc, lc),
 		objectClient:  oc,
 		cfg:           cfg,
+		// Always true predicate.
+		IsReadyForDeletionFunc: func(ctx context.Context, object results.Object) (bool, error) {
+			return true, nil
+		},
 	}
 }
 
@@ -125,26 +139,18 @@ func (r *Reconciler) Reconcile(ctx context.Context, o results.Object) error {
 func (r *Reconciler) addResultsAnnotations(ctx context.Context, o results.Object, annotations ...annotation.Annotation) error {
 	logger := logging.FromContext(ctx)
 	if r.cfg.GetDisableAnnotationUpdate() {
-		logger.Debugf("Skipping CRD patch - annotation patching disabled [%t]", r.cfg.GetDisableAnnotationUpdate())
-		return nil
-	}
-	var shouldBeUpdated bool
-	for _, a := range annotations {
-		if o.GetAnnotations()[a.Name] != a.Value {
-			shouldBeUpdated = true
-			break
-		}
-	}
-	if shouldBeUpdated {
-		patch, err := annotation.Add(annotations...)
+		logger.Debug("Skipping CRD annotation patch: annotation update is disabled")
+	} else if annotation.IsPatched(o, annotations...) {
+		logger.Debug("Skipping CRD annotation patch: Result annotations are already set")
+	} else {
+		// Update object with Result Annotations.
+		patch, err := annotation.Patch(o, annotations...)
 		if err != nil {
 			return fmt.Errorf("error adding Result annotations: %v", err)
 		}
 		if err := r.objectClient.Patch(ctx, o.GetName(), types.MergePatchType, patch, metav1.PatchOptions{}); err != nil {
 			return fmt.Errorf("error patching object: %v", err)
 		}
-	} else {
-		logger.Debugf("Skipping CRD patch - annotations already match [%t]", r.cfg.GetDisableAnnotationUpdate())
 	}
 	return nil
 }
@@ -156,6 +162,7 @@ func (r *Reconciler) addResultsAnnotations(ctx context.Context, o results.Object
 // * The object is done, and it isn't owned by other object.
 // * The configured grace period has elapsed since the object's completion.
 // * The object satisfies all label requirements defined in the supplied config.
+// * The assigned IsReadyForDeletionFunc returns true.
 func (r *Reconciler) deleteUponCompletion(ctx context.Context, o results.Object) error {
 	logger := logging.FromContext(ctx)
 
@@ -200,8 +207,15 @@ func (r *Reconciler) deleteUponCompletion(ctx context.Context, o results.Object)
 		return controller.NewRequeueAfter(r.cfg.RequeueInterval)
 	}
 
+	if isReady, err := r.IsReadyForDeletionFunc(ctx, o); err != nil {
+		return err
+	} else if !isReady {
+		return controller.NewRequeueAfter(r.cfg.RequeueInterval)
+	}
+
 	logger.Infow("Deleting object", zap.String("results.tekton.dev/uid", string(o.GetUID())),
 		zap.Int64("results.tekton.dev/time-taken-seconds", int64(time.Since(*completionTime).Seconds())))
+
 	if err := r.objectClient.Delete(ctx, o.GetName(), metav1.DeleteOptions{
 		Preconditions: metav1.NewUIDPreconditions(string(o.GetUID())),
 	}); err != nil && !errors.IsNotFound(err) {
diff --git a/pkg/watcher/reconciler/pipelinerun/controller.go b/pkg/watcher/reconciler/pipelinerun/controller.go
index a04c4de6..38db78bd 100644
--- a/pkg/watcher/reconciler/pipelinerun/controller.go
+++ b/pkg/watcher/reconciler/pipelinerun/controller.go
@@ -16,8 +16,10 @@ package pipelinerun
 
 import (
 	"context"
+
 	pipelineclient "github.com/tektoncd/pipeline/pkg/client/injection/client"
 	pipelineruninformer "github.com/tektoncd/pipeline/pkg/client/injection/informers/pipeline/v1beta1/pipelinerun"
+	taskruninformer "github.com/tektoncd/pipeline/pkg/client/injection/informers/pipeline/v1beta1/taskrun"
 	"github.com/tektoncd/results/pkg/watcher/logs"
 	"github.com/tektoncd/results/pkg/watcher/reconciler"
 	"github.com/tektoncd/results/pkg/watcher/reconciler/leaderelection"
@@ -33,16 +35,16 @@ func NewController(ctx context.Context, resultsClient pb.ResultsClient) *control
 }
 
 func NewControllerWithConfig(ctx context.Context, resultsClient pb.ResultsClient, cfg *reconciler.Config) *controller.Impl {
-	informer := pipelineruninformer.Get(ctx)
-	lister := informer.Lister()
-
+	pipelineRunInformer := pipelineruninformer.Get(ctx)
+	pipelineRunLister := pipelineRunInformer.Lister()
 	c := &Reconciler{
-		LeaderAwareFuncs: leaderelection.NewLeaderAwareFuncs(lister.List),
-		resultsClient:    resultsClient,
-		logsClient:       logs.Get(ctx),
-		lister:           lister,
-		pipelineClient:   pipelineclient.Get(ctx),
-		cfg:              cfg,
+		LeaderAwareFuncs:  leaderelection.NewLeaderAwareFuncs(pipelineRunLister.List),
+		resultsClient:     resultsClient,
+		logsClient:        logs.Get(ctx),
+		pipelineRunLister: pipelineRunLister,
+		taskRunLister:     taskruninformer.Get(ctx).Lister(),
+		pipelineClient:    pipelineclient.Get(ctx),
+		cfg:               cfg,
 	}
 
 	impl := controller.NewContext(ctx, c, controller.ControllerOptions{
@@ -50,7 +52,7 @@ func NewControllerWithConfig(ctx context.Context, resultsClient pb.ResultsClient
 		WorkQueueName: "PipelineRuns",
 	})
 
-	informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
+	pipelineRunInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
 		AddFunc:    impl.Enqueue,
 		UpdateFunc: controller.PassNew(impl.Enqueue),
 	})
diff --git a/pkg/watcher/reconciler/pipelinerun/reconciler.go b/pkg/watcher/reconciler/pipelinerun/reconciler.go
index b7df9694..1b62998a 100644
--- a/pkg/watcher/reconciler/pipelinerun/reconciler.go
+++ b/pkg/watcher/reconciler/pipelinerun/reconciler.go
@@ -1,13 +1,30 @@
+// Copyright 2020 The Tekton 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 pipelinerun
 
 import (
 	"context"
 	"fmt"
 
+	pipelinev1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
 	"github.com/tektoncd/pipeline/pkg/client/clientset/versioned"
-	"github.com/tektoncd/pipeline/pkg/client/listers/pipeline/v1beta1"
+	pipelinev1beta1listers "github.com/tektoncd/pipeline/pkg/client/listers/pipeline/v1beta1"
 	"github.com/tektoncd/results/pkg/watcher/reconciler"
+	resultsannotation "github.com/tektoncd/results/pkg/watcher/reconciler/annotation"
 	"github.com/tektoncd/results/pkg/watcher/reconciler/dynamic"
+	"github.com/tektoncd/results/pkg/watcher/results"
 	pb "github.com/tektoncd/results/proto/v1alpha2/results_go_proto"
 	"go.uber.org/zap"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -22,11 +39,12 @@ type Reconciler struct {
 	// Inline LeaderAwareFuncs to support leader election.
 	knativereconciler.LeaderAwareFuncs
 
-	resultsClient  pb.ResultsClient
-	logsClient     pb.LogsClient
-	lister         v1beta1.PipelineRunLister
-	pipelineClient versioned.Interface
-	cfg            *reconciler.Config
+	resultsClient     pb.ResultsClient
+	logsClient        pb.LogsClient
+	pipelineRunLister pipelinev1beta1listers.PipelineRunLister
+	taskRunLister     pipelinev1beta1listers.TaskRunLister
+	pipelineClient    versioned.Interface
+	cfg               *reconciler.Config
 }
 
 // Check that our Reconciler is LeaderAware.
@@ -34,6 +52,7 @@ var _ knativereconciler.LeaderAware = (*Reconciler)(nil)
 
 func (r *Reconciler) Reconcile(ctx context.Context, key string) error {
 	logger := logging.FromContext(ctx).With(zap.String("results.tekton.dev/kind", "PipelineRun"))
+	logger.Info("Reconciling PipelineRun")
 
 	namespace, name, err := cache.SplitMetaNamespaceKey(key)
 	if err != nil {
@@ -46,9 +65,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, key string) error {
 		return controller.NewSkipKey(key)
 	}
 
-	logger.Info("Reconciling PipelineRun")
-
-	pr, err := r.lister.PipelineRuns(namespace).Get(name)
+	pr, err := r.pipelineRunLister.PipelineRuns(namespace).Get(name)
 	if err != nil {
 		if apierrors.IsNotFound(err) {
 			logger.Debug("Skipping key: object is no longer available")
@@ -62,9 +79,79 @@ func (r *Reconciler) Reconcile(ctx context.Context, key string) error {
 	}
 
 	dyn := dynamic.NewDynamicReconciler(r.resultsClient, r.logsClient, pipelineRunClient, r.cfg)
+	// Tell the dynamic reconciler to wait until all underlying TaskRuns are
+	// ready for deletion before deleting the PipelineRun. This guarantees
+	// that the TaskRuns will not be deleted before their final state being
+	// properly archived into the API server.
+	dyn.IsReadyForDeletionFunc = r.areAllUnderlyingTaskRunsReadyForDeletion
+
 	if err := dyn.Reconcile(logging.WithLogger(ctx, logger), pr); err != nil {
 		return err
 	}
 
 	return nil
 }
+
+func (r *Reconciler) areAllUnderlyingTaskRunsReadyForDeletion(ctx context.Context, object results.Object) (bool, error) {
+	pipelineRun, ok := object.(*pipelinev1beta1.PipelineRun)
+	if !ok {
+		return false, fmt.Errorf("unexpected object (must not happen): want %T, but got %T", &pipelinev1beta1.PipelineRun{}, object)
+	}
+
+	logger := logging.FromContext(ctx)
+
+	// Support both minimal and full embedded status (see the TODO comment
+	// below).
+	if len(pipelineRun.Status.ChildReferences) > 0 {
+		for _, reference := range pipelineRun.Status.ChildReferences {
+			taskRun, err := r.taskRunLister.TaskRuns(pipelineRun.Namespace).Get(reference.Name)
+			if err != nil {
+				if apierrors.IsNotFound(err) {
+					// Let's assume that the TaskRun in
+					// question is gone and therefore, we
+					// can safely ignore it.
+					logger.Debugf("TaskRun %s/%s is no longer available - ignoring", pipelineRun.Namespace, reference.Name)
+					continue
+				}
+				return false, fmt.Errorf("error reading TaskRun from the indexer: %w", err)
+			}
+			if !isMarkedAsReadyForDeletion(taskRun) {
+				logger.Debugf("TaskRun %s/%s isn't yet ready to be deleted - the annotation %s is missing", taskRun.Namespace, taskRun.Name, resultsannotation.ChildReadyForDeletion)
+				return false, nil
+			}
+		}
+	} else {
+		// TODO(alan-ghelardi): remove this else once we support only
+		// Tekton v1 API since the full embedded status will no longer
+		// be supported.
+		for taskRunName := range pipelineRun.Status.TaskRuns {
+			taskRun, err := r.taskRunLister.TaskRuns(pipelineRun.Namespace).Get(taskRunName)
+			if err != nil {
+				if apierrors.IsNotFound(err) {
+					// Let's assume that the TaskRun in
+					// question is gone and therefore, we
+					// can safely ignore it.
+					logger.Debugf("TaskRun %s/%s is no longer available - ignoring", pipelineRun.Namespace, taskRunName)
+					continue
+				}
+				return false, fmt.Errorf("error reading TaskRun from the indexer: %w", err)
+			}
+			if !isMarkedAsReadyForDeletion(taskRun) {
+				logger.Debugf("TaskRun %s/%s isn't yet ready to be deleted - the annotation %s is missing", taskRun.Namespace, taskRun.Name, resultsannotation.ChildReadyForDeletion)
+				return false, nil
+			}
+		}
+	}
+
+	return true, nil
+}
+
+func isMarkedAsReadyForDeletion(taskRun *pipelinev1beta1.TaskRun) bool {
+	if taskRun.Annotations == nil {
+		return false
+	}
+	if _, found := taskRun.Annotations[resultsannotation.ChildReadyForDeletion]; found {
+		return true
+	}
+	return false
+}
diff --git a/tools/tkn-results/go.mod b/tools/tkn-results/go.mod
index 3cdab6fd..95c49841 100644
--- a/tools/tkn-results/go.mod
+++ b/tools/tkn-results/go.mod
@@ -49,11 +49,11 @@ require (
 	github.com/spf13/cast v1.4.1 // indirect
 	github.com/spf13/jwalterweatherman v1.1.0 // indirect
 	github.com/subosito/gotenv v1.2.0 // indirect
-	golang.org/x/crypto v0.0.0-20210920023735-84f357641f63 // indirect
-	golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 // indirect
-	golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
-	golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect
-	golang.org/x/text v0.3.8 // indirect
+	golang.org/x/crypto v0.1.0 // indirect
+	golang.org/x/net v0.1.0 // indirect
+	golang.org/x/sys v0.1.0 // indirect
+	golang.org/x/term v0.1.0 // indirect
+	golang.org/x/text v0.4.0 // indirect
 	golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
diff --git a/tools/tkn-results/go.sum b/tools/tkn-results/go.sum
index 590b988e..b29b5727 100644
--- a/tools/tkn-results/go.sum
+++ b/tools/tkn-results/go.sum
@@ -1154,10 +1154,10 @@ golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20210920023735-84f357641f63 h1:kETrAMYZq6WVGPa8IIixL0CaEcIUNi+1WX7grUoi3y8=
 golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
+golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1257,10 +1257,10 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 h1:VrJZAjbekhoRn7n5FBujY31gboH+iB3pdLxn3gE9FjU=
 golang.org/x/net v0.0.0-20211101193420-4a448f8816b3/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
+golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1402,14 +1402,15 @@ golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
 golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
+golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1419,8 +1420,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
-golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
+golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
diff --git a/vendor/github.com/aws/aws-sdk-go-v2/config/CHANGELOG.md b/vendor/github.com/aws/aws-sdk-go-v2/config/CHANGELOG.md
index 54795af1..3ef0bfd1 100644
--- a/vendor/github.com/aws/aws-sdk-go-v2/config/CHANGELOG.md
+++ b/vendor/github.com/aws/aws-sdk-go-v2/config/CHANGELOG.md
@@ -1,3 +1,47 @@
+# v1.18.17 (2023-03-14)
+
+* **Dependency Update**: Updated to the latest SDK module versions
+
+# v1.18.16 (2023-03-10)
+
+* **Dependency Update**: Updated to the latest SDK module versions
+
+# v1.18.15 (2023-02-22)
+
+* **Dependency Update**: Updated to the latest SDK module versions
+
+# v1.18.14 (2023-02-20)
+
+* **Dependency Update**: Updated to the latest SDK module versions
+
+# v1.18.13 (2023-02-15)
+
+* **Dependency Update**: Updated to the latest SDK module versions
+
+# v1.18.12 (2023-02-03)
+
+* **Dependency Update**: Updated to the latest SDK module versions
+
+# v1.18.11 (2023-02-01)
+
+* **Dependency Update**: Updated to the latest SDK module versions
+
+# v1.18.10 (2023-01-25)
+
+* **Dependency Update**: Updated to the latest SDK module versions
+
+# v1.18.9 (2023-01-23)
+
+* **Dependency Update**: Updated to the latest SDK module versions
+
+# v1.18.8 (2023-01-05)
+
+* **Dependency Update**: Updated to the latest SDK module versions
+
+# v1.18.7 (2022-12-20)
+
+* **Dependency Update**: Updated to the latest SDK module versions
+
 # v1.18.6 (2022-12-19)
 
 * **Dependency Update**: Updated to the latest SDK module versions
diff --git a/vendor/github.com/aws/aws-sdk-go-v2/config/go_module_metadata.go b/vendor/github.com/aws/aws-sdk-go-v2/config/go_module_metadata.go
index f2e2acfe..95b0e713 100644
--- a/vendor/github.com/aws/aws-sdk-go-v2/config/go_module_metadata.go
+++ b/vendor/github.com/aws/aws-sdk-go-v2/config/go_module_metadata.go
@@ -3,4 +3,4 @@
 package config
 
 // goModuleVersion is the tagged release for this module
-const goModuleVersion = "1.18.6"
+const goModuleVersion = "1.18.17"
diff --git a/vendor/github.com/aws/aws-sdk-go-v2/internal/ini/CHANGELOG.md b/vendor/github.com/aws/aws-sdk-go-v2/internal/ini/CHANGELOG.md
index efd865d6..4e38aff9 100644
--- a/vendor/github.com/aws/aws-sdk-go-v2/internal/ini/CHANGELOG.md
+++ b/vendor/github.com/aws/aws-sdk-go-v2/internal/ini/CHANGELOG.md
@@ -1,3 +1,15 @@
+# v1.3.31 (2023-03-10)
+
+* **Dependency Update**: Updated to the latest SDK module versions
+
+# v1.3.30 (2023-02-20)
+
+* **Dependency Update**: Updated to the latest SDK module versions
+
+# v1.3.29 (2023-02-03)
+
+* **Dependency Update**: Updated to the latest SDK module versions
+
 # v1.3.28 (2022-12-15)
 
 * **Dependency Update**: Updated to the latest SDK module versions
diff --git a/vendor/github.com/aws/aws-sdk-go-v2/internal/ini/go_module_metadata.go b/vendor/github.com/aws/aws-sdk-go-v2/internal/ini/go_module_metadata.go
index 96ac9fbe..b0c62754 100644
--- a/vendor/github.com/aws/aws-sdk-go-v2/internal/ini/go_module_metadata.go
+++ b/vendor/github.com/aws/aws-sdk-go-v2/internal/ini/go_module_metadata.go
@@ -3,4 +3,4 @@
 package ini
 
 // goModuleVersion is the tagged release for this module
-const goModuleVersion = "1.3.28"
+const goModuleVersion = "1.3.31"
diff --git a/vendor/k8s.io/client-go/dynamic/fake/simple.go b/vendor/k8s.io/client-go/dynamic/fake/simple.go
deleted file mode 100644
index 02a797ad..00000000
--- a/vendor/k8s.io/client-go/dynamic/fake/simple.go
+++ /dev/null
@@ -1,537 +0,0 @@
-/*
-Copyright 2018 The Kubernetes 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 fake
-
-import (
-	"context"
-	"fmt"
-	"strings"
-
-	"k8s.io/apimachinery/pkg/api/meta"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"k8s.io/apimachinery/pkg/labels"
-	"k8s.io/apimachinery/pkg/runtime"
-	"k8s.io/apimachinery/pkg/runtime/schema"
-	"k8s.io/apimachinery/pkg/runtime/serializer"
-	"k8s.io/apimachinery/pkg/types"
-	"k8s.io/apimachinery/pkg/watch"
-	"k8s.io/client-go/dynamic"
-	"k8s.io/client-go/testing"
-)
-
-func NewSimpleDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) *FakeDynamicClient {
-	unstructuredScheme := runtime.NewScheme()
-	for gvk := range scheme.AllKnownTypes() {
-		if unstructuredScheme.Recognizes(gvk) {
-			continue
-		}
-		if strings.HasSuffix(gvk.Kind, "List") {
-			unstructuredScheme.AddKnownTypeWithName(gvk, &unstructured.UnstructuredList{})
-			continue
-		}
-		unstructuredScheme.AddKnownTypeWithName(gvk, &unstructured.Unstructured{})
-	}
-
-	objects, err := convertObjectsToUnstructured(scheme, objects)
-	if err != nil {
-		panic(err)
-	}
-
-	for _, obj := range objects {
-		gvk := obj.GetObjectKind().GroupVersionKind()
-		if !unstructuredScheme.Recognizes(gvk) {
-			unstructuredScheme.AddKnownTypeWithName(gvk, &unstructured.Unstructured{})
-		}
-		gvk.Kind += "List"
-		if !unstructuredScheme.Recognizes(gvk) {
-			unstructuredScheme.AddKnownTypeWithName(gvk, &unstructured.UnstructuredList{})
-		}
-	}
-
-	return NewSimpleDynamicClientWithCustomListKinds(unstructuredScheme, nil, objects...)
-}
-
-// NewSimpleDynamicClientWithCustomListKinds try not to use this.  In general you want to have the scheme have the List types registered
-// and allow the default guessing for resources match.  Sometimes that doesn't work, so you can specify a custom mapping here.
-func NewSimpleDynamicClientWithCustomListKinds(scheme *runtime.Scheme, gvrToListKind map[schema.GroupVersionResource]string, objects ...runtime.Object) *FakeDynamicClient {
-	// In order to use List with this client, you have to have your lists registered so that the object tracker will find them
-	// in the scheme to support the t.scheme.New(listGVK) call when it's building the return value.
-	// Since the base fake client needs the listGVK passed through the action (in cases where there are no instances, it
-	// cannot look up the actual hits), we need to know a mapping of GVR to listGVK here.  For GETs and other types of calls,
-	// there is no return value that contains a GVK, so it doesn't have to know the mapping in advance.
-
-	// first we attempt to invert known List types from the scheme to auto guess the resource with unsafe guesses
-	// this covers common usage of registering types in scheme and passing them
-	completeGVRToListKind := map[schema.GroupVersionResource]string{}
-	for listGVK := range scheme.AllKnownTypes() {
-		if !strings.HasSuffix(listGVK.Kind, "List") {
-			continue
-		}
-		nonListGVK := listGVK.GroupVersion().WithKind(listGVK.Kind[:len(listGVK.Kind)-4])
-		plural, _ := meta.UnsafeGuessKindToResource(nonListGVK)
-		completeGVRToListKind[plural] = listGVK.Kind
-	}
-
-	for gvr, listKind := range gvrToListKind {
-		if !strings.HasSuffix(listKind, "List") {
-			panic("coding error, listGVK must end in List or this fake client doesn't work right")
-		}
-		listGVK := gvr.GroupVersion().WithKind(listKind)
-
-		// if we already have this type registered, just skip it
-		if _, err := scheme.New(listGVK); err == nil {
-			completeGVRToListKind[gvr] = listKind
-			continue
-		}
-
-		scheme.AddKnownTypeWithName(listGVK, &unstructured.UnstructuredList{})
-		completeGVRToListKind[gvr] = listKind
-	}
-
-	codecs := serializer.NewCodecFactory(scheme)
-	o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder())
-	for _, obj := range objects {
-		if err := o.Add(obj); err != nil {
-			panic(err)
-		}
-	}
-
-	cs := &FakeDynamicClient{scheme: scheme, gvrToListKind: completeGVRToListKind, tracker: o}
-	cs.AddReactor("*", "*", testing.ObjectReaction(o))
-	cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
-		gvr := action.GetResource()
-		ns := action.GetNamespace()
-		watch, err := o.Watch(gvr, ns)
-		if err != nil {
-			return false, nil, err
-		}
-		return true, watch, nil
-	})
-
-	return cs
-}
-
-// Clientset implements clientset.Interface. Meant to be embedded into a
-// struct to get a default implementation. This makes faking out just the method
-// you want to test easier.
-type FakeDynamicClient struct {
-	testing.Fake
-	scheme        *runtime.Scheme
-	gvrToListKind map[schema.GroupVersionResource]string
-	tracker       testing.ObjectTracker
-}
-
-type dynamicResourceClient struct {
-	client    *FakeDynamicClient
-	namespace string
-	resource  schema.GroupVersionResource
-	listKind  string
-}
-
-var (
-	_ dynamic.Interface  = &FakeDynamicClient{}
-	_ testing.FakeClient = &FakeDynamicClient{}
-)
-
-func (c *FakeDynamicClient) Tracker() testing.ObjectTracker {
-	return c.tracker
-}
-
-func (c *FakeDynamicClient) Resource(resource schema.GroupVersionResource) dynamic.NamespaceableResourceInterface {
-	return &dynamicResourceClient{client: c, resource: resource, listKind: c.gvrToListKind[resource]}
-}
-
-func (c *dynamicResourceClient) Namespace(ns string) dynamic.ResourceInterface {
-	ret := *c
-	ret.namespace = ns
-	return &ret
-}
-
-func (c *dynamicResourceClient) Create(ctx context.Context, obj *unstructured.Unstructured, opts metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error) {
-	var uncastRet runtime.Object
-	var err error
-	switch {
-	case len(c.namespace) == 0 && len(subresources) == 0:
-		uncastRet, err = c.client.Fake.
-			Invokes(testing.NewRootCreateAction(c.resource, obj), obj)
-
-	case len(c.namespace) == 0 && len(subresources) > 0:
-		var accessor metav1.Object // avoid shadowing err
-		accessor, err = meta.Accessor(obj)
-		if err != nil {
-			return nil, err
-		}
-		name := accessor.GetName()
-		uncastRet, err = c.client.Fake.
-			Invokes(testing.NewRootCreateSubresourceAction(c.resource, name, strings.Join(subresources, "/"), obj), obj)
-
-	case len(c.namespace) > 0 && len(subresources) == 0:
-		uncastRet, err = c.client.Fake.
-			Invokes(testing.NewCreateAction(c.resource, c.namespace, obj), obj)
-
-	case len(c.namespace) > 0 && len(subresources) > 0:
-		var accessor metav1.Object // avoid shadowing err
-		accessor, err = meta.Accessor(obj)
-		if err != nil {
-			return nil, err
-		}
-		name := accessor.GetName()
-		uncastRet, err = c.client.Fake.
-			Invokes(testing.NewCreateSubresourceAction(c.resource, name, strings.Join(subresources, "/"), c.namespace, obj), obj)
-
-	}
-
-	if err != nil {
-		return nil, err
-	}
-	if uncastRet == nil {
-		return nil, err
-	}
-
-	ret := &unstructured.Unstructured{}
-	if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil {
-		return nil, err
-	}
-	return ret, err
-}
-
-func (c *dynamicResourceClient) Update(ctx context.Context, obj *unstructured.Unstructured, opts metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error) {
-	var uncastRet runtime.Object
-	var err error
-	switch {
-	case len(c.namespace) == 0 && len(subresources) == 0:
-		uncastRet, err = c.client.Fake.
-			Invokes(testing.NewRootUpdateAction(c.resource, obj), obj)
-
-	case len(c.namespace) == 0 && len(subresources) > 0:
-		uncastRet, err = c.client.Fake.
-			Invokes(testing.NewRootUpdateSubresourceAction(c.resource, strings.Join(subresources, "/"), obj), obj)
-
-	case len(c.namespace) > 0 && len(subresources) == 0:
-		uncastRet, err = c.client.Fake.
-			Invokes(testing.NewUpdateAction(c.resource, c.namespace, obj), obj)
-
-	case len(c.namespace) > 0 && len(subresources) > 0:
-		uncastRet, err = c.client.Fake.
-			Invokes(testing.NewUpdateSubresourceAction(c.resource, strings.Join(subresources, "/"), c.namespace, obj), obj)
-
-	}
-
-	if err != nil {
-		return nil, err
-	}
-	if uncastRet == nil {
-		return nil, err
-	}
-
-	ret := &unstructured.Unstructured{}
-	if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil {
-		return nil, err
-	}
-	return ret, err
-}
-
-func (c *dynamicResourceClient) UpdateStatus(ctx context.Context, obj *unstructured.Unstructured, opts metav1.UpdateOptions) (*unstructured.Unstructured, error) {
-	var uncastRet runtime.Object
-	var err error
-	switch {
-	case len(c.namespace) == 0:
-		uncastRet, err = c.client.Fake.
-			Invokes(testing.NewRootUpdateSubresourceAction(c.resource, "status", obj), obj)
-
-	case len(c.namespace) > 0:
-		uncastRet, err = c.client.Fake.
-			Invokes(testing.NewUpdateSubresourceAction(c.resource, "status", c.namespace, obj), obj)
-
-	}
-
-	if err != nil {
-		return nil, err
-	}
-	if uncastRet == nil {
-		return nil, err
-	}
-
-	ret := &unstructured.Unstructured{}
-	if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil {
-		return nil, err
-	}
-	return ret, err
-}
-
-func (c *dynamicResourceClient) Delete(ctx context.Context, name string, opts metav1.DeleteOptions, subresources ...string) error {
-	var err error
-	switch {
-	case len(c.namespace) == 0 && len(subresources) == 0:
-		_, err = c.client.Fake.
-			Invokes(testing.NewRootDeleteAction(c.resource, name), &metav1.Status{Status: "dynamic delete fail"})
-
-	case len(c.namespace) == 0 && len(subresources) > 0:
-		_, err = c.client.Fake.
-			Invokes(testing.NewRootDeleteSubresourceAction(c.resource, strings.Join(subresources, "/"), name), &metav1.Status{Status: "dynamic delete fail"})
-
-	case len(c.namespace) > 0 && len(subresources) == 0:
-		_, err = c.client.Fake.
-			Invokes(testing.NewDeleteAction(c.resource, c.namespace, name), &metav1.Status{Status: "dynamic delete fail"})
-
-	case len(c.namespace) > 0 && len(subresources) > 0:
-		_, err = c.client.Fake.
-			Invokes(testing.NewDeleteSubresourceAction(c.resource, strings.Join(subresources, "/"), c.namespace, name), &metav1.Status{Status: "dynamic delete fail"})
-	}
-
-	return err
-}
-
-func (c *dynamicResourceClient) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOptions metav1.ListOptions) error {
-	var err error
-	switch {
-	case len(c.namespace) == 0:
-		action := testing.NewRootDeleteCollectionAction(c.resource, listOptions)
-		_, err = c.client.Fake.Invokes(action, &metav1.Status{Status: "dynamic deletecollection fail"})
-
-	case len(c.namespace) > 0:
-		action := testing.NewDeleteCollectionAction(c.resource, c.namespace, listOptions)
-		_, err = c.client.Fake.Invokes(action, &metav1.Status{Status: "dynamic deletecollection fail"})
-
-	}
-
-	return err
-}
-
-func (c *dynamicResourceClient) Get(ctx context.Context, name string, opts metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) {
-	var uncastRet runtime.Object
-	var err error
-	switch {
-	case len(c.namespace) == 0 && len(subresources) == 0:
-		uncastRet, err = c.client.Fake.
-			Invokes(testing.NewRootGetAction(c.resource, name), &metav1.Status{Status: "dynamic get fail"})
-
-	case len(c.namespace) == 0 && len(subresources) > 0:
-		uncastRet, err = c.client.Fake.
-			Invokes(testing.NewRootGetSubresourceAction(c.resource, strings.Join(subresources, "/"), name), &metav1.Status{Status: "dynamic get fail"})
-
-	case len(c.namespace) > 0 && len(subresources) == 0:
-		uncastRet, err = c.client.Fake.
-			Invokes(testing.NewGetAction(c.resource, c.namespace, name), &metav1.Status{Status: "dynamic get fail"})
-
-	case len(c.namespace) > 0 && len(subresources) > 0:
-		uncastRet, err = c.client.Fake.
-			Invokes(testing.NewGetSubresourceAction(c.resource, c.namespace, strings.Join(subresources, "/"), name), &metav1.Status{Status: "dynamic get fail"})
-	}
-
-	if err != nil {
-		return nil, err
-	}
-	if uncastRet == nil {
-		return nil, err
-	}
-
-	ret := &unstructured.Unstructured{}
-	if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil {
-		return nil, err
-	}
-	return ret, err
-}
-
-func (c *dynamicResourceClient) List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) {
-	if len(c.listKind) == 0 {
-		panic(fmt.Sprintf("coding error: you must register resource to list kind for every resource you're going to LIST when creating the client.  See NewSimpleDynamicClientWithCustomListKinds or register the list into the scheme: %v out of %v", c.resource, c.client.gvrToListKind))
-	}
-	listGVK := c.resource.GroupVersion().WithKind(c.listKind)
-	listForFakeClientGVK := c.resource.GroupVersion().WithKind(c.listKind[:len(c.listKind)-4]) /*base library appends List*/
-
-	var obj runtime.Object
-	var err error
-	switch {
-	case len(c.namespace) == 0:
-		obj, err = c.client.Fake.
-			Invokes(testing.NewRootListAction(c.resource, listForFakeClientGVK, opts), &metav1.Status{Status: "dynamic list fail"})
-
-	case len(c.namespace) > 0:
-		obj, err = c.client.Fake.
-			Invokes(testing.NewListAction(c.resource, listForFakeClientGVK, c.namespace, opts), &metav1.Status{Status: "dynamic list fail"})
-
-	}
-
-	if obj == nil {
-		return nil, err
-	}
-
-	label, _, _ := testing.ExtractFromListOptions(opts)
-	if label == nil {
-		label = labels.Everything()
-	}
-
-	retUnstructured := &unstructured.Unstructured{}
-	if err := c.client.scheme.Convert(obj, retUnstructured, nil); err != nil {
-		return nil, err
-	}
-	entireList, err := retUnstructured.ToList()
-	if err != nil {
-		return nil, err
-	}
-
-	list := &unstructured.UnstructuredList{}
-	list.SetResourceVersion(entireList.GetResourceVersion())
-	list.GetObjectKind().SetGroupVersionKind(listGVK)
-	for i := range entireList.Items {
-		item := &entireList.Items[i]
-		metadata, err := meta.Accessor(item)
-		if err != nil {
-			return nil, err
-		}
-		if label.Matches(labels.Set(metadata.GetLabels())) {
-			list.Items = append(list.Items, *item)
-		}
-	}
-	return list, nil
-}
-
-func (c *dynamicResourceClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
-	switch {
-	case len(c.namespace) == 0:
-		return c.client.Fake.
-			InvokesWatch(testing.NewRootWatchAction(c.resource, opts))
-
-	case len(c.namespace) > 0:
-		return c.client.Fake.
-			InvokesWatch(testing.NewWatchAction(c.resource, c.namespace, opts))
-
-	}
-
-	panic("math broke")
-}
-
-// TODO: opts are currently ignored.
-func (c *dynamicResourceClient) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*unstructured.Unstructured, error) {
-	var uncastRet runtime.Object
-	var err error
-	switch {
-	case len(c.namespace) == 0 && len(subresources) == 0:
-		uncastRet, err = c.client.Fake.
-			Invokes(testing.NewRootPatchAction(c.resource, name, pt, data), &metav1.Status{Status: "dynamic patch fail"})
-
-	case len(c.namespace) == 0 && len(subresources) > 0:
-		uncastRet, err = c.client.Fake.
-			Invokes(testing.NewRootPatchSubresourceAction(c.resource, name, pt, data, subresources...), &metav1.Status{Status: "dynamic patch fail"})
-
-	case len(c.namespace) > 0 && len(subresources) == 0:
-		uncastRet, err = c.client.Fake.
-			Invokes(testing.NewPatchAction(c.resource, c.namespace, name, pt, data), &metav1.Status{Status: "dynamic patch fail"})
-
-	case len(c.namespace) > 0 && len(subresources) > 0:
-		uncastRet, err = c.client.Fake.
-			Invokes(testing.NewPatchSubresourceAction(c.resource, c.namespace, name, pt, data, subresources...), &metav1.Status{Status: "dynamic patch fail"})
-
-	}
-
-	if err != nil {
-		return nil, err
-	}
-	if uncastRet == nil {
-		return nil, err
-	}
-
-	ret := &unstructured.Unstructured{}
-	if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil {
-		return nil, err
-	}
-	return ret, err
-}
-
-// TODO: opts are currently ignored.
-func (c *dynamicResourceClient) Apply(ctx context.Context, name string, obj *unstructured.Unstructured, options metav1.ApplyOptions, subresources ...string) (*unstructured.Unstructured, error) {
-	outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
-	if err != nil {
-		return nil, err
-	}
-	var uncastRet runtime.Object
-	switch {
-	case len(c.namespace) == 0 && len(subresources) == 0:
-		uncastRet, err = c.client.Fake.
-			Invokes(testing.NewRootPatchAction(c.resource, name, types.ApplyPatchType, outBytes), &metav1.Status{Status: "dynamic patch fail"})
-
-	case len(c.namespace) == 0 && len(subresources) > 0:
-		uncastRet, err = c.client.Fake.
-			Invokes(testing.NewRootPatchSubresourceAction(c.resource, name, types.ApplyPatchType, outBytes, subresources...), &metav1.Status{Status: "dynamic patch fail"})
-
-	case len(c.namespace) > 0 && len(subresources) == 0:
-		uncastRet, err = c.client.Fake.
-			Invokes(testing.NewPatchAction(c.resource, c.namespace, name, types.ApplyPatchType, outBytes), &metav1.Status{Status: "dynamic patch fail"})
-
-	case len(c.namespace) > 0 && len(subresources) > 0:
-		uncastRet, err = c.client.Fake.
-			Invokes(testing.NewPatchSubresourceAction(c.resource, c.namespace, name, types.ApplyPatchType, outBytes, subresources...), &metav1.Status{Status: "dynamic patch fail"})
-
-	}
-
-	if err != nil {
-		return nil, err
-	}
-	if uncastRet == nil {
-		return nil, err
-	}
-
-	ret := &unstructured.Unstructured{}
-	if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil {
-		return nil, err
-	}
-	return ret, nil
-}
-
-func (c *dynamicResourceClient) ApplyStatus(ctx context.Context, name string, obj *unstructured.Unstructured, options metav1.ApplyOptions) (*unstructured.Unstructured, error) {
-	return c.Apply(ctx, name, obj, options, "status")
-}
-
-func convertObjectsToUnstructured(s *runtime.Scheme, objs []runtime.Object) ([]runtime.Object, error) {
-	ul := make([]runtime.Object, 0, len(objs))
-
-	for _, obj := range objs {
-		u, err := convertToUnstructured(s, obj)
-		if err != nil {
-			return nil, err
-		}
-
-		ul = append(ul, u)
-	}
-	return ul, nil
-}
-
-func convertToUnstructured(s *runtime.Scheme, obj runtime.Object) (runtime.Object, error) {
-	var (
-		err error
-		u   unstructured.Unstructured
-	)
-
-	u.Object, err = runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
-	if err != nil {
-		return nil, fmt.Errorf("failed to convert to unstructured: %w", err)
-	}
-
-	gvk := u.GroupVersionKind()
-	if gvk.Group == "" || gvk.Kind == "" {
-		gvks, _, err := s.ObjectKinds(obj)
-		if err != nil {
-			return nil, fmt.Errorf("failed to convert to unstructured - unable to get GVK %w", err)
-		}
-		apiv, k := gvks[0].ToAPIVersionAndKind()
-		u.SetAPIVersion(apiv)
-		u.SetKind(k)
-	}
-	return &u, nil
-}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index d0e59adf..0243241b 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -73,7 +73,7 @@ github.com/aws/aws-sdk-go-v2/internal/timeconv
 ## explicit; go 1.15
 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream
 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/eventstreamapi
-# github.com/aws/aws-sdk-go-v2/config v1.18.6
+# github.com/aws/aws-sdk-go-v2/config v1.18.17
 ## explicit; go 1.15
 github.com/aws/aws-sdk-go-v2/config
 # github.com/aws/aws-sdk-go-v2/credentials v1.13.17
@@ -95,7 +95,7 @@ github.com/aws/aws-sdk-go-v2/internal/configsources
 # github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.24
 ## explicit; go 1.15
 github.com/aws/aws-sdk-go-v2/internal/endpoints/v2
-# github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28
+# github.com/aws/aws-sdk-go-v2/internal/ini v1.3.31
 ## explicit; go 1.15
 github.com/aws/aws-sdk-go-v2/internal/ini
 # github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.18
@@ -1168,7 +1168,7 @@ gorm.io/gorm/logger
 gorm.io/gorm/migrator
 gorm.io/gorm/schema
 gorm.io/gorm/utils
-# k8s.io/api v0.25.5
+# k8s.io/api v0.25.8
 ## explicit; go 1.19
 k8s.io/api/admission/v1
 k8s.io/api/admissionregistration/v1
@@ -1221,7 +1221,7 @@ k8s.io/api/storage/v1beta1
 ## explicit; go 1.19
 k8s.io/apiextensions-apiserver/pkg/apis/apiextensions
 k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1
-# k8s.io/apimachinery v0.25.5
+# k8s.io/apimachinery v0.25.8
 ## explicit; go 1.19
 k8s.io/apimachinery/pkg/api/equality
 k8s.io/apimachinery/pkg/api/errors
@@ -1327,7 +1327,6 @@ k8s.io/client-go/discovery
 k8s.io/client-go/discovery/cached/disk
 k8s.io/client-go/discovery/fake
 k8s.io/client-go/dynamic
-k8s.io/client-go/dynamic/fake
 k8s.io/client-go/informers
 k8s.io/client-go/informers/admissionregistration
 k8s.io/client-go/informers/admissionregistration/v1


Test patch:
diff --git a/pkg/watcher/reconciler/annotation/annotation_test.go b/pkg/watcher/reconciler/annotation/annotation_test.go
new file mode 100644
index 00000000..91169cde
--- /dev/null
+++ b/pkg/watcher/reconciler/annotation/annotation_test.go
@@ -0,0 +1,249 @@
+// Copyright 2023 The Tekton 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 annotation
+
+import (
+	"encoding/json"
+	"errors"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	pipelinev1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+)
+
+func TestPatch(t *testing.T) {
+	const (
+		fakeResultID = "foo/results/bar"
+		fakeRecordID = "foo/results/bar/records/baz"
+	)
+
+	annotations := []Annotation{{
+		Name:  Result,
+		Value: fakeResultID,
+	},
+		{
+			Name:  Record,
+			Value: fakeRecordID,
+		},
+	}
+
+	tests := []struct {
+		name string
+		in   func() metav1.Object
+		want mergePatch
+	}{{
+		name: "create a patch containing only the result and record identifiers since the object is a PipelineRun",
+		in: func() metav1.Object {
+			return &pipelinev1beta1.PipelineRun{}
+		},
+		want: mergePatch{
+			Metadata: metadata{
+				Annotations: map[string]string{
+					Result: fakeResultID,
+					Record: fakeRecordID,
+				},
+			},
+		},
+	},
+		{
+			name: "create a patch containing only the result and record identifiers since the TaskRun isn't owned by a PipelineRun",
+			in: func() metav1.Object {
+				return &pipelinev1beta1.TaskRun{}
+			},
+			want: mergePatch{
+				Metadata: metadata{
+					Annotations: map[string]string{
+						Result: fakeResultID,
+						Record: fakeRecordID,
+					},
+				},
+			},
+		},
+		{
+			name: "create a patch containing only the result and record identifiers since the TaskRun isn't done yet",
+			in: func() metav1.Object {
+				return &pipelinev1beta1.TaskRun{
+					ObjectMeta: metav1.ObjectMeta{
+						OwnerReferences: []metav1.OwnerReference{{
+							UID: types.UID("UID"),
+						},
+						},
+					},
+				}
+			},
+			want: mergePatch{
+				Metadata: metadata{
+					Annotations: map[string]string{
+						Result: fakeResultID,
+						Record: fakeRecordID,
+					},
+				},
+			},
+		},
+		{
+			name: "mark the TaskRun as ready for deletion since it's owned by a PipelineRun and is done",
+			in: func() metav1.Object {
+				taskRun := &pipelinev1beta1.TaskRun{
+					ObjectMeta: metav1.ObjectMeta{
+						OwnerReferences: []metav1.OwnerReference{{
+							UID: types.UID("UID"),
+						},
+						},
+					},
+				}
+				taskRun.Status.MarkResourceFailed(pipelinev1beta1.TaskRunReasonFailed, errors.New("Failed"))
+				return taskRun
+			},
+			want: mergePatch{
+				Metadata: metadata{
+					Annotations: map[string]string{
+						Result:                fakeResultID,
+						Record:                fakeRecordID,
+						ChildReadyForDeletion: "true",
+					},
+				},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			object := test.in()
+			patch, err := Patch(object, annotations...)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			var got mergePatch
+			if err := json.Unmarshal(patch, &got); err != nil {
+				t.Fatal(err)
+			}
+
+			if diff := cmp.Diff(test.want, got); diff != "" {
+				t.Errorf("Mismatch (-want +got):\n%s", diff)
+			}
+		})
+	}
+}
+
+func TestIsPatched(t *testing.T) {
+	const (
+		fakeResultID = "foo/results/bar"
+		fakeRecordID = "foo/results/bar/records/baz"
+	)
+
+	annotations := []Annotation{{
+		Name:  Result,
+		Value: fakeResultID,
+	},
+		{
+			Name:  Record,
+			Value: fakeRecordID,
+		},
+	}
+
+	tests := []struct {
+		name string
+		in   func() metav1.Object
+		want bool
+	}{{
+		name: "result and record identifiers are missing in the PipelineRun",
+		in: func() metav1.Object {
+			return &pipelinev1beta1.PipelineRun{}
+		},
+		want: false,
+	},
+		{
+			name: "the record identifier is missing in the PipelineRun",
+			in: func() metav1.Object {
+				return &pipelinev1beta1.PipelineRun{
+					ObjectMeta: metav1.ObjectMeta{
+						Annotations: map[string]string{
+							Result: fakeResultID,
+						},
+					},
+				}
+			},
+			want: false,
+		},
+		{
+			name: "the PipelineRun contains all relevant annotations",
+			in: func() metav1.Object {
+				return &pipelinev1beta1.PipelineRun{
+					ObjectMeta: metav1.ObjectMeta{
+						Annotations: map[string]string{
+							Result: fakeResultID,
+							Record: fakeRecordID,
+						},
+					},
+				}
+			},
+			want: true,
+		},
+		{
+			name: "the TaskRun contains all relevant annotations",
+			in: func() metav1.Object {
+				taskRun := &pipelinev1beta1.TaskRun{
+					ObjectMeta: metav1.ObjectMeta{
+						Annotations: map[string]string{
+							Result:                fakeResultID,
+							Record:                fakeRecordID,
+							ChildReadyForDeletion: "true",
+						},
+						OwnerReferences: []metav1.OwnerReference{{
+							UID: types.UID("UID"),
+						},
+						},
+					},
+				}
+				taskRun.Status.MarkResourceFailed(pipelinev1beta1.TaskRunReasonFailed, errors.New("Failed"))
+				return taskRun
+			},
+			want: true,
+		},
+		{
+			name: "the TaskRun should be marked as ready to be deleted",
+			in: func() metav1.Object {
+				taskRun := &pipelinev1beta1.TaskRun{
+					ObjectMeta: metav1.ObjectMeta{
+						Annotations: map[string]string{
+							Result: fakeResultID,
+							Record: fakeRecordID,
+						},
+						OwnerReferences: []metav1.OwnerReference{{
+							UID: types.UID("UID"),
+						},
+						},
+					},
+				}
+				taskRun.Status.MarkResourceFailed(pipelinev1beta1.TaskRunReasonFailed, errors.New("Failed"))
+				return taskRun
+			},
+			want: false,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			object := test.in()
+			got := IsPatched(object, annotations...)
+			if test.want != got {
+				t.Errorf("Want %t, but got %t", test.want, got)
+			}
+		})
+	}
+}
diff --git a/pkg/watcher/reconciler/dynamic/dynamic_test.go b/pkg/watcher/reconciler/dynamic/dynamic_test.go
index a0fb0acc..2546d5fe 100644
--- a/pkg/watcher/reconciler/dynamic/dynamic_test.go
+++ b/pkg/watcher/reconciler/dynamic/dynamic_test.go
@@ -17,11 +17,14 @@ package dynamic
 import (
 	"context"
 	"errors"
-	"github.com/google/uuid"
-	"github.com/tektoncd/results/pkg/api/server/v1alpha2/log"
 	"testing"
 	"time"
 
+	watcherresults "github.com/tektoncd/results/pkg/watcher/results"
+
+	"github.com/google/uuid"
+	"github.com/tektoncd/results/pkg/api/server/v1alpha2/log"
+
 	"github.com/jonboulle/clockwork"
 	"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
 	pipelineclient "github.com/tektoncd/pipeline/pkg/client/injection/client"
@@ -36,7 +39,6 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	dynamicclient "k8s.io/client-go/dynamic/fake"
 	"knative.dev/pkg/apis"
 	duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1"
 	"knative.dev/pkg/controller"
@@ -45,13 +47,6 @@ import (
 	_ "github.com/tektoncd/pipeline/test"
 )
 
-type env struct {
-	ctx     context.Context
-	ctrl    *controller.Impl
-	results pb.ResultsClient
-	dynamic *dynamicclient.FakeDynamicClient
-}
-
 var (
 	taskrun = &v1beta1.TaskRun{
 		TypeMeta: metav1.TypeMeta{
@@ -287,6 +282,50 @@ func TestReconcile_TaskRun(t *testing.T) {
 			t.Fatalf("Want NotFound, but got %v", err)
 		}
 	})
+
+	t.Run("wait until the IsReadyForDeletion function returns true to delete the object", func(t *testing.T) {
+		// Recreate the object to retest the deletion
+		if _, err := trclient.Create(ctx, taskrun, metav1.CreateOptions{}); err != nil {
+			t.Fatal(err)
+		}
+
+		// Pretend that the IsReadyForDeletion function returns an
+		// error.
+		errSomethingBad := errors.New("Something really bad happened")
+		r.IsReadyForDeletionFunc = func(_ context.Context, object watcherresults.Object) (bool, error) {
+			return false, errSomethingBad
+		}
+
+		// Then the controller should return the same error
+		if err := r.Reconcile(ctx, taskrun); !errors.Is(err, errSomethingBad) {
+			t.Fatalf("Want %v, but got %v", errSomethingBad, err)
+		}
+
+		// Assign a predicate that checks if a given annotation exists.
+		r.IsReadyForDeletionFunc = func(_ context.Context, object watcherresults.Object) (bool, error) {
+			_, found := object.GetAnnotations()["x"]
+			return found, nil
+		}
+
+		// The controller must return a RequeueKeyError because the
+		// TaskRun doesn't have this annotation
+		if err := r.Reconcile(ctx, taskrun); !isRequeueKey(err) {
+			t.Fatalf("Want a controller.RequeueKey error, but got %v", err)
+		}
+
+		// Set the expected annotation and reconcile.
+		taskrun.Annotations = map[string]string{
+			"x": "foo",
+		}
+		if err := r.Reconcile(ctx, taskrun); err != nil {
+			t.Fatal(err)
+		}
+
+		// Make sure that the resource no longer exists
+		if _, err := trclient.Get(ctx, taskrun.GetName(), metav1.GetOptions{}); !apierrors.IsNotFound(err) {
+			t.Fatalf("Want NotFound, but got %v", err)
+		}
+	})
 }
 
 // This is a simpler test than TaskRun, since most of this behavior is
diff --git a/pkg/watcher/reconciler/pipelinerun/reconciler_test.go b/pkg/watcher/reconciler/pipelinerun/reconciler_test.go
new file mode 100644
index 00000000..e9768902
--- /dev/null
+++ b/pkg/watcher/reconciler/pipelinerun/reconciler_test.go
@@ -0,0 +1,143 @@
+// Copyright 2023 The Tekton 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 pipelinerun
+
+import (
+	"context"
+	"testing"
+
+	pipelinev1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
+	pipelinev1beta1listers "github.com/tektoncd/pipeline/pkg/client/listers/pipeline/v1beta1"
+	resultsannotation "github.com/tektoncd/results/pkg/watcher/reconciler/annotation"
+	"go.uber.org/zap/zaptest"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/tools/cache"
+	"knative.dev/pkg/logging"
+)
+
+func TestAreAllUnderlyingTaskRunsReadyForDeletion(t *testing.T) {
+	tests := []struct {
+		name string
+		in   *pipelinev1beta1.PipelineRun
+		want bool
+	}{{
+		name: "all underlying TaskRuns are ready to be deleted",
+		in: &pipelinev1beta1.PipelineRun{
+			Status: pipelinev1beta1.PipelineRunStatus{
+				PipelineRunStatusFields: pipelinev1beta1.PipelineRunStatusFields{
+					ChildReferences: []pipelinev1beta1.ChildStatusReference{{
+						Name: "foo",
+					},
+					},
+				},
+			},
+		},
+		want: true,
+	},
+		{
+			name: "one TaskRun is ready to be deleted whereas the other is not",
+			in: &pipelinev1beta1.PipelineRun{
+				Status: pipelinev1beta1.PipelineRunStatus{
+					PipelineRunStatusFields: pipelinev1beta1.PipelineRunStatusFields{
+						ChildReferences: []pipelinev1beta1.ChildStatusReference{{
+							Name: "foo",
+						},
+							{
+								Name: "bar",
+							},
+						},
+					},
+				},
+			},
+			want: false,
+		},
+		{
+			name: "consider that missing TaskRuns can be deleted",
+			in: &pipelinev1beta1.PipelineRun{
+				Status: pipelinev1beta1.PipelineRunStatus{
+					PipelineRunStatusFields: pipelinev1beta1.PipelineRunStatusFields{
+						ChildReferences: []pipelinev1beta1.ChildStatusReference{{
+							Name: "foo",
+						},
+							{
+								Name: "baz",
+							},
+						},
+					},
+				},
+			},
+			want: true,
+		},
+		{
+			name: "support full embedded status",
+			in: &pipelinev1beta1.PipelineRun{
+				Status: pipelinev1beta1.PipelineRunStatus{
+					PipelineRunStatusFields: pipelinev1beta1.PipelineRunStatusFields{
+						TaskRuns: map[string]*pipelinev1beta1.PipelineRunTaskRunStatus{
+							"bar": &pipelinev1beta1.PipelineRunTaskRunStatus{},
+						},
+					},
+				},
+			},
+			want: false,
+		},
+	}
+
+	indexer := cache.NewIndexer(cache.DeletionHandlingMetaNamespaceKeyFunc, cache.Indexers{})
+
+	// Put a few objects into the indexer.
+	if err := indexer.Add(&pipelinev1beta1.TaskRun{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "foo",
+			Namespace: corev1.NamespaceDefault,
+			Annotations: map[string]string{
+				resultsannotation.ChildReadyForDeletion: "true",
+			},
+		},
+	}); err != nil {
+		t.Fatal(err)
+	}
+
+	if err := indexer.Add(&pipelinev1beta1.TaskRun{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "bar",
+			Namespace: corev1.NamespaceDefault,
+		},
+	}); err != nil {
+		t.Fatal(err)
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			reconciler := &Reconciler{
+				taskRunLister: pipelinev1beta1listers.NewTaskRunLister(indexer),
+			}
+
+			test.in.Namespace = corev1.NamespaceDefault
+
+			ctx := context.Background()
+			ctx = logging.WithLogger(ctx, zaptest.NewLogger(t).Sugar())
+			got, err := reconciler.areAllUnderlyingTaskRunsReadyForDeletion(ctx, test.in)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			if test.want != got {
+				t.Fatalf("Want %t, but got %t", test.want, got)
+			}
+		})
+	}
+}
diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go
index e411bab8..05a00bce 100644
--- a/test/e2e/e2e_test.go
+++ b/test/e2e/e2e_test.go
@@ -21,11 +21,8 @@ import (
 	"context"
 	"errors"
 	"io/ioutil"
-	"os"
-	"path"
 	"strings"
 	"testing"
-	"time"
 
 	"github.com/google/go-cmp/cmp"
 	resultsv1alpha2 "github.com/tektoncd/results/proto/v1alpha2/results_go_proto"
@@ -33,6 +30,11 @@ import (
 	"google.golang.org/grpc/status"
 	"google.golang.org/protobuf/testing/protocmp"
 
+	"time"
+
+	"os"
+	"path"
+
 	"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
 	clientset "github.com/tektoncd/pipeline/pkg/client/clientset/versioned/typed/pipeline/v1beta1"
 	corev1 "k8s.io/api/core/v1"
@@ -106,7 +108,7 @@ func TestTaskRun(t *testing.T) {
 		}
 	})
 
-	t.Run("Run Cleanup", func(t *testing.T) {
+	t.Run("TaskRun Cleanup", func(t *testing.T) {
 		if err := wait.PollImmediate(1*time.Second, 1*time.Minute, func() (done bool, err error) {
 			tr, err := c.TaskRuns(ns).Get(ctx, tr.GetName(), metav1.GetOptions{})
 			if k8serrors.IsNotFound(err) {
@@ -140,21 +142,36 @@ func TestPipelineRun(t *testing.T) {
 		t.Fatalf("Create: %v", err)
 	}
 
-	// Wait for Result ID to show up.
-	if err := wait.PollImmediate(1*time.Second, 1*time.Minute, func() (done bool, err error) {
-		pr, err := c.PipelineRuns(ns).Get(ctx, pr.GetName(), metav1.GetOptions{})
-		if err != nil {
-			t.Logf("Get: %v", err)
+	t.Run("result annotations", func(t *testing.T) {
+		// Wait for Result ID to show up.
+		if err := wait.PollImmediate(1*time.Second, 1*time.Minute, func() (done bool, err error) {
+			pr, err := c.PipelineRuns(ns).Get(ctx, pr.GetName(), metav1.GetOptions{})
+			if err != nil {
+				t.Logf("Get: %v", err)
+				return false, nil
+			}
+			if r, ok := pr.GetAnnotations()["results.tekton.dev/result"]; ok {
+				t.Logf("Found Result: %s", r)
+				return true, nil
+			}
 			return false, nil
+		}); err != nil {
+			t.Fatalf("error waiting for Result ID: %v", err)
 		}
-		if r, ok := pr.GetAnnotations()["results.tekton.dev/result"]; ok {
-			t.Logf("Found Result: %s", r)
-			return true, nil
+	})
+
+	t.Run("PipelineRun cleanup", func(t *testing.T) {
+		if err := wait.PollImmediate(1*time.Second, 1*time.Minute, func() (done bool, err error) {
+			pr, err := c.PipelineRuns(ns).Get(ctx, pr.GetName(), metav1.GetOptions{})
+			if k8serrors.IsNotFound(err) {
+				return true, nil
+			}
+			t.Logf("Get: %+v, %v", pr.GetName(), err)
+			return false, nil
+		}); err != nil {
+			t.Fatalf("error waiting PipelineRun to be deleted: %v", err)
 		}
-		return false, nil
-	}); err != nil {
-		t.Fatalf("error waiting for Result ID: %v", err)
-	}
+	})
 }
 
 func clientConfig(t *testing.T) *rest.Config {
@@ -174,6 +191,93 @@ func clientTekton(t *testing.T) *clientset.TektonV1beta1Client {
 	return clientset.NewForConfigOrDie(clientConfig(t))
 }
 
+func TestGRPCLogging(t *testing.T) {
+	ctx := context.Background()
+
+	// ignore old logs
+	sinceTime := metav1.Now()
+	podLogOptions := corev1.PodLogOptions{
+		SinceTime: &sinceTime,
+	}
+
+	matcher := "\"grpc.method\":\"ListResults\""
+
+	client := newResultsClient(t, allNamespacesReadAccessPath)
+
+	t.Run("Log entry is found when not expected", func(t *testing.T) {
+		resultsApiLogs, err := getResultsApiLogs(ctx, &podLogOptions, t)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if !strings.Contains(resultsApiLogs, matcher) {
+			t.Logf("No log match %s when there should not be\n", matcher)
+		} else {
+			t.Errorf("Found log match for %s in logs %s when there should not be", matcher, resultsApiLogs)
+		}
+	})
+
+	t.Run("Log entry is found when expected", func(t *testing.T) {
+
+		_, err := client.ListResults(ctx, &resultsv1alpha2.ListResultsRequest{Parent: "default"})
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		resultsApiLogs, err := getResultsApiLogs(ctx, &podLogOptions, t)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if strings.Contains(resultsApiLogs, matcher) {
+			t.Logf("Found log match %s\n", matcher)
+		} else {
+			t.Errorf("No match for %s in logs %s", matcher, resultsApiLogs)
+		}
+	})
+}
+
+// Returns a string of api pods logs concatenated
+func getResultsApiLogs(ctx context.Context, podLogOptions *corev1.PodLogOptions, t *testing.T) (string, error) {
+	t.Helper()
+	const apiPodBasename = "tekton-results-api"
+	const nsResults = "tekton-pipelines"
+
+	clientset := kubernetes.NewForConfigOrDie(clientConfig(t))
+
+	pods, err := clientset.CoreV1().Pods(nsResults).List(ctx, metav1.ListOptions{})
+	if err != nil {
+		return "", err
+	}
+
+	numApiPods := 0
+	var apiPodsLogs []string
+	for _, pod := range pods.Items {
+		// find api pods
+		if strings.HasPrefix(pod.Name, apiPodBasename) {
+			numApiPods++
+			// read pod logs
+			podLogRequest := clientset.CoreV1().Pods(nsResults).GetLogs(pod.Name, podLogOptions)
+			stream, err := podLogRequest.Stream(ctx)
+			if err != nil {
+				return "", err
+			}
+			defer stream.Close()
+			podLogBytes, err := ioutil.ReadAll(stream)
+			if err != nil {
+				return "", err
+			}
+			apiPodsLogs = append(apiPodsLogs, string(podLogBytes))
+		}
+	}
+
+	if numApiPods == 0 {
+		return "", errors.New("No " + apiPodBasename + "pod found")
+	}
+
+	return strings.Join(apiPodsLogs, ""), nil
+}
+
 func TestListResults(t *testing.T) {
 	ctx := context.Background()
 
@@ -330,90 +434,3 @@ func recordNames(t *testing.T, records []*resultsv1alpha2.Record) []string {
 	}
 	return ret
 }
-
-func TestGRPCLogging(t *testing.T) {
-	ctx := context.Background()
-
-	// ignore old logs
-	sinceTime := metav1.Now()
-	podLogOptions := corev1.PodLogOptions{
-		SinceTime: &sinceTime,
-	}
-
-	matcher := "\"grpc.method\":\"ListResults\""
-
-	client := newResultsClient(t, allNamespacesReadAccessPath)
-
-	t.Run("Log entry is found when not expected", func(t *testing.T) {
-		resultsApiLogs, err := getResultsApiLogs(ctx, &podLogOptions, t)
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		if !strings.Contains(resultsApiLogs, matcher) {
-			t.Logf("No log match %s when there should not be\n", matcher)
-		} else {
-			t.Errorf("Found log match for %s in logs %s when there should not be", matcher, resultsApiLogs)
-		}
-	})
-
-	t.Run("Log entry is found when expected", func(t *testing.T) {
-
-		_, err := client.ListResults(ctx, &resultsv1alpha2.ListResultsRequest{Parent: "default"})
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		resultsApiLogs, err := getResultsApiLogs(ctx, &podLogOptions, t)
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		if strings.Contains(resultsApiLogs, matcher) {
-			t.Logf("Found log match %s\n", matcher)
-		} else {
-			t.Errorf("No match for %s in logs %s", matcher, resultsApiLogs)
-		}
-	})
-}
-
-// Returns a string of api pods logs concatenated
-func getResultsApiLogs(ctx context.Context, podLogOptions *corev1.PodLogOptions, t *testing.T) (string, error) {
-	t.Helper()
-	const apiPodBasename = "tekton-results-api"
-	const nsResults = "tekton-pipelines"
-
-	clientset := kubernetes.NewForConfigOrDie(clientConfig(t))
-
-	pods, err := clientset.CoreV1().Pods(nsResults).List(ctx, metav1.ListOptions{})
-	if err != nil {
-		return "", err
-	}
-
-	numApiPods := 0
-	var apiPodsLogs []string
-	for _, pod := range pods.Items {
-		// find api pods
-		if strings.HasPrefix(pod.Name, apiPodBasename) {
-			numApiPods++
-			// read pod logs
-			podLogRequest := clientset.CoreV1().Pods(nsResults).GetLogs(pod.Name, podLogOptions)
-			stream, err := podLogRequest.Stream(ctx)
-			if err != nil {
-				return "", err
-			}
-			defer stream.Close()
-			podLogBytes, err := ioutil.ReadAll(stream)
-			if err != nil {
-				return "", err
-			}
-			apiPodsLogs = append(apiPodsLogs, string(podLogBytes))
-		}
-	}
-
-	if numApiPods == 0 {
-		return "", errors.New("No " + apiPodBasename + "pod found")
-	}
-
-	return strings.Join(apiPodsLogs, ""), nil
-}