Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Converts ResultType from string to int enum #4186

Merged
merged 1 commit into from
Sep 15, 2021

Conversation

sm43
Copy link
Member

@sm43 sm43 commented Aug 23, 2021

This converts the ResultType in PipelineResourceResult from string
to int enum to curtail the size of json object emittted by steps.
ResultTypes are converted because they made JSON messages bigger, which in
turn limited the amount of space in termination messages for task results. String
support is maintained for backwards compatibility.

Closes #4150

Signed-off-by: Shivam Mukhade [email protected]

Submitter Checklist

As the author of this PR, please check off the items in this checklist:

  • Docs included if any changes are user facing
  • Tests included if any functionality added or changed
  • Follows the commit message standard
  • Meets the Tekton contributor standards (including
    functionality, content, code)
  • Release notes block below has been filled in or deleted (only if no user facing changes)

Release Notes

ResultType in JSON blob of termination message is changed from String to Int. So, for ex previouly we used to have "type": "TaskRunResultType" in the terminaton message which now would appear as "type": 1 to curtail the size of json.

@tekton-robot tekton-robot added the release-note Denotes a PR that will be considered when it comes time to generate release notes. label Aug 23, 2021
@tekton-robot tekton-robot requested review from imjasonh and a user August 23, 2021 18:33
@tekton-robot tekton-robot added the size/M Denotes a PR that changes 30-99 lines, ignoring generated files. label Aug 23, 2021
@sm43
Copy link
Member Author

sm43 commented Aug 23, 2021

@sbwsg Could you take a look and let me know if I am right track? 😅

@ghost
Copy link

ghost commented Aug 23, 2021

One potential risk here is if a controller is upgraded while a TaskRun is mid-execution. If that would happen then the taskrun's pod could return "TaskRunResult" but the controller could be expecting 1. Is there a way we could work around this by implementing the Unmarshaller interface for ResultType? So if our custom UnmarshalJSON() detects a string then we convert it to its equivalent int?

Copy link

@ghost ghost left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me so far!

pkg/apis/pipeline/v1beta1/task_types.go Outdated Show resolved Hide resolved
@sm43 sm43 force-pushed the result-type-to-int-enum branch from cca4451 to 15fe177 Compare August 25, 2021 19:03
@tekton-robot tekton-robot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/M Denotes a PR that changes 30-99 lines, ignoring generated files. labels Aug 25, 2021
@sm43
Copy link
Member Author

sm43 commented Aug 25, 2021

/kind cleanup

@tekton-robot tekton-robot added the kind/cleanup Categorizes issue or PR as related to cleaning up code, process, or technical debt. label Aug 25, 2021
@tekton-robot
Copy link
Collaborator

The following is the coverage report on the affected files.
Say /test pull-tekton-pipeline-go-coverage to re-run this coverage report

File Old Coverage New Coverage Delta
pkg/apis/pipeline/v1beta1/resource_types.go 89.3% 84.1% -5.2

@sm43 sm43 force-pushed the result-type-to-int-enum branch from 15fe177 to e38dd57 Compare August 25, 2021 19:19
@sm43 sm43 requested a review from a user August 25, 2021 19:19
@tekton-robot
Copy link
Collaborator

The following is the coverage report on the affected files.
Say /test pull-tekton-pipeline-go-coverage to re-run this coverage report

File Old Coverage New Coverage Delta
pkg/apis/pipeline/v1beta1/resource_types.go 89.3% 84.1% -5.2

@sm43
Copy link
Member Author

sm43 commented Aug 25, 2021

@sbwsg updated the pr, ptal :)

@sm43
Copy link
Member Author

sm43 commented Aug 26, 2021

/retest

@ghost
Copy link

ghost commented Aug 26, 2021

Hey @sm43 thanks a lot for this!

I think we might be better off removing the field name change ("type" -> "t") and only changing the value's type. The reason for that is then we wouldn't need the new pipelineResourceResult type which now appears in our openapi generated spec. Here's how this would look:

// PipelineResourceResult used to export the image name and digest as json
type PipelineResourceResult struct {
        // ... 
        ResultType  ResultType           `json:"type,omitempty"`
}

// ResultType used to find out whether a PipelineResourceResult is from a task result or not
type ResultType int

// UnmarshalJSON unmarshals either an int or a string into a ResultType. String
// ResultTypes were removed because they made JSON messages bigger, which in
// turn limited the amount of space in termination messages for task results. String
// support is maintained for backwards compatibility - the Pipelines controller could
// be stopped midway through TaskRun execution, updated with support for int in place
// of string, and then fail the running TaskRun because it doesn't know how to interpret
// the string value that the TaskRun's entrypoint will emit when it completes.
func (r *ResultType) UnmarshalJSON(data []byte) error {
        var asInt int
        var intErr error

        if err := json.Unmarshal(data, &asInt); err != nil {
                intErr = err
        } else {
                *r = ResultType(asInt)
                return nil
        }

        var asString string

        if err := json.Unmarshal(data, &asString); err != nil {
                return fmt.Errorf("unsupported value type, neither int nor string: %v", multierror.Append(intErr, err).ErrorOrNil())
        }

        switch asString {
        case "TaskRunResult":
                *r = TaskRunResultType
        case "PipelineResourceResult":
                *r = PipelineResourceResultType
        case "InternalTektonResult":
                *r = InternalTektonResultType
        default:
                *r = UnknownResultType
        }

        return nil
}

We still lose a few bytes because "type" remains but I think it's better not to introduce any new intermediary types to our openapi or swagger. What do you think?

I tested this using the unit tests you wrote, here's the diff for those:

diff --git a/pkg/apis/pipeline/v1beta1/resource_types_test.go b/pkg/apis/pipeline/v1beta1/resource_types_test.go
index 6cafbbe7d..3108c2ce6 100644
--- a/pkg/apis/pipeline/v1beta1/resource_types_test.go
+++ b/pkg/apis/pipeline/v1beta1/resource_types_test.go
@@ -15,7 +15,6 @@ package v1beta1_test

 import (
        "encoding/json"
-       "reflect"
        "testing"

        "github.com/google/go-cmp/cmp"
@@ -147,7 +146,6 @@ func TestApplyTaskModifier_AlreadyAdded(t *testing.T) {
 }

 func TestPipelineResourceResult_UnmarshalJSON(t *testing.T) {
-
        testcases := []struct {
                name string
                data string
@@ -163,7 +161,7 @@ func TestPipelineResourceResult_UnmarshalJSON(t *testing.T) {
                        pr:   v1beta1.PipelineResourceResult{Key: "resultName", Value: "", ResultType: v1beta1.InternalTektonResultType},
                }, {
                        name: "already defined as int",
-                       data: "{\"key\":\"resultName\",\"value\":\"\", \"t\": 1}",
+                       data: "{\"key\":\"resultName\",\"value\":\"\", \"type\": 1}",
                        pr:   v1beta1.PipelineResourceResult{Key: "resultName", Value: "", ResultType: v1beta1.TaskRunResultType},
                }}

@@ -173,8 +171,8 @@ func TestPipelineResourceResult_UnmarshalJSON(t *testing.T) {
                        if err := json.Unmarshal([]byte(tc.data), &pipRes); err != nil {
                                t.Errorf("Unexpected error when unmarshalling the json into PipelineResourceResult")
                        }
-                       if !reflect.DeepEqual(pipRes, tc.pr) {
-                               t.Errorf("Unexpected error: both objects are different")
+                       if d := cmp.Diff(tc.pr, pipRes); d != "" {
+                               t.Errorf(diff.PrintWantGot(d))
                        }
                })
        }

and

diff --git a/pkg/pod/status_test.go b/pkg/pod/status_test.go
index c451608a9..d18aa823a 100644
--- a/pkg/pod/status_test.go
+++ b/pkg/pod/status_test.go
@@ -549,7 +549,7 @@ func TestMakeTaskRunStatus(t *testing.T) {
 				Name: "step-bar",
 				State: corev1.ContainerState{
 					Terminated: &corev1.ContainerStateTerminated{
-						Message: `[{"key":"resultName","value":"resultValue", "t":1}, {"key":"digest","value":"sha256:1234","resourceRef":{"name":"source-image"}}]`,
+						Message: `[{"key":"resultName","value":"resultValue", "type":1}, {"key":"digest","value":"sha256:1234","resourceRef":{"name":"source-image"}}]`,
 					},
 				},
 			}},
@@ -560,7 +560,7 @@ func TestMakeTaskRunStatus(t *testing.T) {
 				Steps: []v1beta1.StepState{{
 					ContainerState: corev1.ContainerState{
 						Terminated: &corev1.ContainerStateTerminated{
-							Message: `[{"key":"digest","value":"sha256:1234","resourceRef":{"name":"source-image"}},{"key":"resultName","value":"resultValue","t":1}]`,
+							Message: `[{"key":"digest","value":"sha256:1234","resourceRef":{"name":"source-image"}},{"key":"resultName","value":"resultValue","type":1}]`,
 						}},
 					Name:          "bar",
 					ContainerName: "step-bar",
@@ -587,7 +587,7 @@ func TestMakeTaskRunStatus(t *testing.T) {
 				Name: "step-banana",
 				State: corev1.ContainerState{
 					Terminated: &corev1.ContainerStateTerminated{
-						Message: `[{"key":"resultName","value":"resultValue", "t":1}, {"key":"digest","value":"sha256:1234","resourceRef":{"name":"source-image"}}]`,
+						Message: `[{"key":"resultName","value":"resultValue", "type":1}, {"key":"digest","value":"sha256:1234","resourceRef":{"name":"source-image"}}]`,
 					},
 				},
 			}},
@@ -598,7 +598,7 @@ func TestMakeTaskRunStatus(t *testing.T) {
 				Steps: []v1beta1.StepState{{
 					ContainerState: corev1.ContainerState{
 						Terminated: &corev1.ContainerStateTerminated{
-							Message: `[{"key":"digest","value":"sha256:1234","resourceRef":{"name":"source-image"}},{"key":"resultName","value":"resultValue","t":1}]`,
+							Message: `[{"key":"digest","value":"sha256:1234","resourceRef":{"name":"source-image"}},{"key":"resultName","value":"resultValue","type":1}]`,
 						}},
 					Name:          "banana",
 					ContainerName: "step-banana",
@@ -625,14 +625,14 @@ func TestMakeTaskRunStatus(t *testing.T) {
 				Name: "step-one",
 				State: corev1.ContainerState{
 					Terminated: &corev1.ContainerStateTerminated{
-						Message: `[{"key":"resultNameOne","value":"resultValueOne", "t":1}, {"key":"resultNameTwo","value":"resultValueTwo", "t":1}]`,
+						Message: `[{"key":"resultNameOne","value":"resultValueOne", "type":1}, {"key":"resultNameTwo","value":"resultValueTwo", "type":1}]`,
 					},
 				},
 			}, {
 				Name: "step-two",
 				State: corev1.ContainerState{
 					Terminated: &corev1.ContainerStateTerminated{
-						Message: `[{"key":"resultNameOne","value":"resultValueThree","t":1},{"key":"resultNameTwo","value":"resultValueTwo","t":1}]`,
+						Message: `[{"key":"resultNameOne","value":"resultValueThree","type":1},{"key":"resultNameTwo","value":"resultValueTwo","type":1}]`,
 					},
 				},
 			}},
@@ -643,14 +643,14 @@ func TestMakeTaskRunStatus(t *testing.T) {
 				Steps: []v1beta1.StepState{{
 					ContainerState: corev1.ContainerState{
 						Terminated: &corev1.ContainerStateTerminated{
-							Message: `[{"key":"resultNameOne","value":"resultValueOne","t":1},{"key":"resultNameTwo","value":"resultValueTwo","t":1}]`,
+							Message: `[{"key":"resultNameOne","value":"resultValueOne","type":1},{"key":"resultNameTwo","value":"resultValueTwo","type":1}]`,
 						}},
 					Name:          "one",
 					ContainerName: "step-one",
 				}, {
 					ContainerState: corev1.ContainerState{
 						Terminated: &corev1.ContainerStateTerminated{
-							Message: `[{"key":"resultNameOne","value":"resultValueThree","t":1},{"key":"resultNameTwo","value":"resultValueTwo","t":1}]`,
+							Message: `[{"key":"resultNameOne","value":"resultValueThree","type":1},{"key":"resultNameTwo","value":"resultValueTwo","type":1}]`,
 						}},
 					Name:          "two",
 					ContainerName: "step-two",
@@ -727,7 +727,7 @@ func TestMakeTaskRunStatus(t *testing.T) {
 				Name: "step-pear",
 				State: corev1.ContainerState{
 					Terminated: &corev1.ContainerStateTerminated{
-						Message: `[{"key":"resultNameOne","value":"","t":2}, {"key":"resultNameTwo","value":"","t":3}, {"key":"resultNameThree","value":"","t":1}]`},
+						Message: `[{"key":"resultNameOne","value":"","type":2}, {"key":"resultNameTwo","value":"","type":3}, {"key":"resultNameThree","value":"","type":1}]`},
 				},
 			}},
 		},
@@ -737,7 +737,7 @@ func TestMakeTaskRunStatus(t *testing.T) {
 				Steps: []v1beta1.StepState{{
 					ContainerState: corev1.ContainerState{
 						Terminated: &corev1.ContainerStateTerminated{
-							Message: `[{"key":"resultNameOne","value":"","t":2},{"key":"resultNameThree","value":"","t":1}]`,
+							Message: `[{"key":"resultNameOne","value":"","type":2},{"key":"resultNameThree","value":"","type":1}]`,
 						}},
 					Name:          "pear",
 					ContainerName: "step-pear",
@@ -775,7 +775,7 @@ func TestMakeTaskRunStatus(t *testing.T) {
 				Steps: []v1beta1.StepState{{
 					ContainerState: corev1.ContainerState{
 						Terminated: &corev1.ContainerStateTerminated{
-							Message: `[{"key":"resultNameOne","value":"","t":2},{"key":"resultNameThree","value":"","t":1}]`,
+							Message: `[{"key":"resultNameOne","value":"","type":2},{"key":"resultNameThree","value":"","type":1}]`,
 						}},
 					Name:          "pear",
 					ContainerName: "step-pear",

if err := json.Unmarshal([]byte(tc.data), &pipRes); err != nil {
t.Errorf("Unexpected error when unmarshalling the json into PipelineResourceResult")
}
if !reflect.DeepEqual(pipRes, tc.pr) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest using cmp.Diff here so that devs can see which fields are incorrect. Here's example usage:

if d := cmp.Diff(tc.pr, pipRes); d != "" {
    t.Errorf(diff.PrintWantGot(d))
}

// UnknownResultType default unknown result type value
UnknownResultType ResultType = ""
UnknownResultType = 4
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: Can we set "UnknownResultType" to a higher value, e.g. 10 or 100? So that, in future more types can be added.

@sm43 sm43 force-pushed the result-type-to-int-enum branch from e38dd57 to 14818ab Compare August 29, 2021 16:17
@tekton-robot
Copy link
Collaborator

The following is the coverage report on the affected files.
Say /test pull-tekton-pipeline-go-coverage to re-run this coverage report

File Old Coverage New Coverage Delta
pkg/apis/pipeline/v1beta1/resource_types.go 89.3% 86.0% -3.2

This converts the ResultType in PipelineResourceResult from string
to int enum to curtail the size of json object emittted by steps.
ResultTypes were removed because they made JSON messages bigger, which in
turn limited the amount of space in termination messages for task results. String
support is maintained for backwards compatibility.

Closes tektoncd#4150

Signed-off-by: Shivam Mukhade <[email protected]>
@sm43 sm43 force-pushed the result-type-to-int-enum branch from 14818ab to d21e087 Compare August 29, 2021 16:20
@tekton-robot
Copy link
Collaborator

The following is the coverage report on the affected files.
Say /test pull-tekton-pipeline-go-coverage to re-run this coverage report

File Old Coverage New Coverage Delta
pkg/apis/pipeline/v1beta1/resource_types.go 89.3% 86.0% -3.2

@sm43
Copy link
Member Author

sm43 commented Aug 29, 2021

@sbwsg yeah that make sense to not change type tot. Thank you adding the code snippet.
Updated the pr with changes.
@ScrapCodes Updated UnknownResultType to have value 10 let me know if that is good enough
ptal :)

Copy link

@ghost ghost left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fantastic, thanks a lot @sm43 !

// UnknownResultType default unknown result type value
UnknownResultType ResultType = ""
UnknownResultType = 10
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@tekton-robot
Copy link
Collaborator

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: sbwsg

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@tekton-robot tekton-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Aug 31, 2021
Copy link
Member

@vdemeester vdemeester left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A very naive question, isn't this kind-of breaking the API ? Even though pipeline doesn't consume it, there might be tools that are consuming this and used to see strings (like TaskRunResult) and will see an int now, isn't it ?

@ghost
Copy link

ghost commented Sep 13, 2021

@vdemeester the way we pass messages from Tasks to the controller is an internal implementation detail isn't it?

@vdemeester
Copy link
Member

@vdemeester the way we pass messages from Tasks to the controller is an internal implementation detail isn't it?

Well, isn't this "exposed" as part of the status field of a TaskRun or PipelineRun ? If yes, then this means a user that was looking at those (for whatever reason) will have a "breakage" as the type changed (using to be a string, and is a number now). This is, I think, also affecting the tkn cli 🙃 (cc @piyush-garg) as we display results.

@ghost
Copy link

ghost commented Sep 13, 2021

It's exposed at the following location in taskruns:

status:
  steps:
  - container: step-print-date-human-readable
    terminated:
      message: '[{"key":"current-date-human-readable","value":"Mon Sep 13 14:28:59
        UTC 2021\n","type":"TaskRunResult"},{"key":"current-date-unix-timestamp","value":"1631543338\n","type":"TaskRunResult"}]'

To be super explicit: we're talking about the stringified JSON blob in the termination message field. The "type" field in that stringified JSON blob will change from a string ("TaskRunResult") to an int.

You're saying that consumers of our API are parsing the stringified JSON blob for this data rather than looking in the status.taskResults list for the value?

@vdemeester
Copy link
Member

You're saying that consumers of our API are parsing the stringified JSON blob for this data rather than looking in the status.taskResults list for the value?

They might but yes, if it's only exposed there, then I think we are safe 😛

@pritidesai
Copy link
Member

I kind of agree with @vdemeester, whoever is looking at the terminated JSON blob will have a message with int instead of TaskRunResult. But I don't think it was designed to consume it in that way anyways so I am all good with these changes.

I would like to see the changes in the release notes to be on the safer side. @sm43 will you be able to please add the release notes with the status JSON blob change.

Copy link
Member

@afrittoli afrittoli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this!
I agree with @pritidesai, we need to call this out in the release notes of this PR and of next release. We might want to document that the termination message is not meant as a stable API, even if it's exposed within the status.
Once the release notes are updated this lgtm.

@ghost
Copy link

ghost commented Sep 14, 2021

Nice idea @afrittoli I'll make a PR to document the lack of guarantees around the termination message.

@ghost
Copy link

ghost commented Sep 14, 2021

Created #4226 to capture termination message note.

@sm43
Copy link
Member Author

sm43 commented Sep 15, 2021

@sbwsg @pritidesai added release notes, ptal if it looks okay.

@pritidesai
Copy link
Member

thanks @sm43

/lgtm

@tekton-robot tekton-robot added the lgtm Indicates that a PR is ready to be merged. label Sep 15, 2021
@tekton-robot tekton-robot merged commit db62a61 into tektoncd:main Sep 15, 2021
@sm43 sm43 deleted the result-type-to-int-enum branch September 15, 2021 17:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. kind/cleanup Categorizes issue or PR as related to cleaning up code, process, or technical debt. lgtm Indicates that a PR is ready to be merged. release-note Denotes a PR that will be considered when it comes time to generate release notes. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Convert "InternalTektonResult" from string to int enum
6 participants