-
Notifications
You must be signed in to change notification settings - Fork 65
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
Add Global Attributes, Global Variable & Substitution and Deprecate Metadata.Attributes #352
Conversation
2ed5bcd
to
c92ac68
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looks good, with a few comments/questions. One more general confusion I could foresee is that we reuse the term attributes
despite this attributes field being special, and others being just plain. Is this confusing?
schemaVersion: 2.1.0
metadata:
name: test-devfile
attributes:
go-version: "1.14.7"
components:
name: my-component
attributes:
go-version: "1.15.6"
image: my-sidecar-go:{{go-version}}
// +optional | ||
// +patchStrategy=merge | ||
// +devfile:overrides:include:omitInPlugin=true,description=Overrides of attributes encapsulated in a parent devfile. | ||
Attributes attributes.Attributes `json:"attributes,omitempty" patchStrategy:"merge"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While I'm generally in favor of the changes (especially as it enables attributes in DevWorkspaces in addition to devfiles), we're reaching a point where attributes is becoming pretty confusing:
schemaVersion: 2.1.0
metadata:
name: my-devfile
attributes:
# free-form yaml
attributes:
# free-form yaml
Should we remove metadata attributes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@amisevsk metadata attributes can store information about data that is not required to be used and substituted in other devfile fields. FWIW, I had the same question as you regd removal of the metadata attribute.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With that in mind then, are the global attributes intended to be only used for substitution? I was kind of imagining using the global attributes for both, at which point .metadata.attributes
is redundant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that is the intended usage. If we want to store info about properties like Dockerfile location etc, then it would be in the metadata section for example.. and like you said the global attributes would be used for substitution but I guess nothing is preventing users from using the global attributes to store other info. Whether they intend to use it for substitution is up to them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless we're explicitly forbidding the global attributes introduced here from storing non-replacement attributes, then I think it doesn't make sense to have both -- it'll just be a source of confusion. If global attributes to be used just for replacement, then why isn't their type map[string]string
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@amisevsk I've talked with Elson. We're definitely interested in keeping metadata attributes as they will be used for information pertaining to devfiles. And they've been used in the past by both odo and Console devfiles.
With that being said, as I see Global Attributes at this point to be used for replacement, I am changing the type to map[string]string
as you suggested to avoid confusion and this also probably makes sense since we're targetting string type fields.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the intended usage of the global attributes is just for replacement, what about just calling them such? E.g.:
schemaVersion: 2.1.0
metadata:
name: my-devfile
attributes:
# free-form yaml
substitutions:
# string: string
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed type in new commit :)
return nil | ||
} | ||
|
||
var globalAttributeRegex = regexp.MustCompile(`\{{2}(.*?)\}{2}`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick (feel free to ignore): I feel like using the repetition regexp obscures the meaning here since the char being checked is {
. Perhaps \{\{(.*?)\}\}
is clearer? We may also want to exclude {
and }
from the inner match, to avoid weirdness around using e.g. {{my{attribute}}
, but that's probably disallowed elsewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my{attribute
is a valid string for an attribute key, if we exclude them, are we restricting it too much? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're fully in the weeds of edge cases now :)
How would we parse {{my{{attribute}}test}}
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first occurrence of {{
& }}
ie, my{{attribute
is the match and remaining test}}
is considered a string, to keep it fairly simply simple and consistent 🤔
|
||
if workspaceTemplateSpec != nil { | ||
// Validate the components | ||
if err = ValidateComponents(workspaceTemplateSpec.Attributes, &workspaceTemplateSpec.Components); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not clear on why we're passing references to slices throughout this code -- if the concern is being able to modify the underlying data, passing the slices is sufficient (and cleaner). Is there a reason we need to pass pointers to structs everywhere?
For context, passing workspaceTemplateSpec.Components
will copy the slice struct, but the slice struct is just a bunch of references to memory locations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed
attrValue := attributes.GetString(match[1], &err) | ||
if err != nil { | ||
return "", err | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we sure we want to consider this an error case? It basically forbids using {{text}}
anywhere in a Devfile. I think a safer choice is to leave it unmodified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm interesting thought. So if we go by the suggested convention, it either replaces the valid {{key}}
to its value or leaves the invalid key {{invalidKey}}
as it is? Then there would be no error cases at all..
What happens when someone commits an unintentional typo or it maybe confusing if an error is not returned and someone forgets a global attribute since we are allowing the global attributes to overriden and merged 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, not requiring replacement does leave the ugliness of trying to execute something like go{{go-version}}
(though ideally one would notice that pretty quickly). My main worry is that there might be a use case somewhere that requires {{text}}
and this approach would effectively make us incompatible (e.g. I know helm charts are littered with {{}}
).
Good point about parent attributes though -- I think the most confusing outcome would be an attribute from parent replacing {{my-var}}
in your code despite you never seeing the my-var
attribute defined. I'm pretty split here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@amisevsk regd the parent case, I think if a user is intending to use a parent devfile, they should be aware of the possible consequences of parent data being merged in the main devfile content, in this case a parent global attribute my-var
replacing all occurances of {{my-var}}
in the final devfile.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we just need to define a way how {{}}
can be escaped.
We may use a common \{
but then just slash needs to be escaped as \\
.
Maybe it would be good enough if we replace {{{var}}}
with {{var}}
without resolving var.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sleshchenko not sure I follow, are we saying convention should be one pair {}
ie, {var}
?
@amisevsk I thought about a bit more, I am not sure how devfile data will give way to incompatibility with a Helm chart. {{my-var}}
in a devfile is either resolved or not. If its not resolved, we can err out(also depends upon our other conversation whether we should err out) or it will remain as {{my-var}}
. If this data is being used in Helm, it will most likely not follow the Helm convention which is something like {{.IMAGE}}
(notice the period) so in those cases will likely result in a Helm failure 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've thought about it a bit more and I'm going towards "this shouldn't be an error". Failing here causes subtle complexity that's hard to resolve, e.g. the devfiles:
metadata:
name: parent-devfile
attributes:
parent-attribute: "test"
components:
- name: parent-component
container:
image: quay.io/test/image
---
metadata:
name: main-devfile
attributes:
parent-devfile-id: "parent-devfile"
parent:
id: "{{parent-devfile-id}}"
components:
- name: child-component
image: quay.io/{{parent-attribute}}/image
become impossible to reconcile: doing the replacement in main-devfile
will fail because {{parent-attribute}}
does not exist, and we can't flatten in the parent attribute becuase its id is hidden behind {{parent-devfile-id}}
, which requires attribute replacement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@amisevsk I've explained the steps in this comment here #352 (comment) regd this case, pls do share your thoughts :)
switch { | ||
case len(events.PreStart) > 0: | ||
for i := range events.PreStart { | ||
if events.PreStart[i], err = validateAndReplaceDataWithAttribute(events.PreStart[i], attributes); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious on the design here -- events.Prestart
is a slice of strings referring to command IDs, and command IDs cannot have {
and }
in them, which makes it seem impossible to cleanly use this functionality. I'm not sure we should support attributes inside events
unless we relax the regexes for validation (though that poses further problems)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a valid point 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a few issues that I would like to see resolved before merging these changes (as much as I want to use them in the DevWorkspace Operator 😉 )
- We should figure out what to do with
.metadata.attributes
and.attributes
. It doesn't make sense to have both, so I think we should get rid ofmetadata.attributes
-- otherwise it'll just be confusing. - There's some subtlety around how replacement + validation + flattening has to happen once global attributes (as implemented here) are added (see my review comments below)
- We're still replacing attributes in a few cross-reference fields (e.g. volumeMounts in container components that have to refer to volume component ids), which we need to be very careful about. I'm not sure it's a valid usecase, and so I think any field that refers to another field in the devfile should not support attributes replacement.
newObjectAttributeField: | ||
objectAttributeSubField: 11 | ||
newObjectAttributeSubField: newObjectAttributeFieldValue | ||
newField: true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing newline at end of file
newField: true | |
newField: true | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in new commit :)
|
||
// Validate composite commands | ||
for i := range composite.Commands { | ||
if composite.Commands[i], err = validateAndReplaceDataWithAttribute(composite.Commands[i], attributes); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to be another case of using attribute replacement in a place where the value you're replacing is an ID (and so cannot be an attribute), since composite.Commands
refers to command IDs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makes sense, this was an oversight after your comment regd events in the prev feedback
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in new commit :)
if exec.Component, err = validateAndReplaceDataWithAttribute(exec.Component, attributes); err != nil { | ||
return err | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would this work? exec.Component
has to be one of the component IDs in the devfile, and so cannot have any {{}}
chars. This would mean that you could have a global attribute doing something like
commands:
- id: test
exec:
component: {{my-component}}
but the value of the my-component
attribute is now bound by the same rules as the value here (must refer to a component key). This sort of thing would also make validation more difficult, as we would have to replace attributes before validating.
Is there a use case where we would want to configure the target component in this way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makes sense, this was an oversight after your comment regd events in the prev feedback
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in new commit :)
} | ||
|
||
// Validate apply component | ||
if apply.Component, err = validateAndReplaceDataWithAttribute(apply.Component, attributes); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar comment as above, for exec.Component
. I'm not sure there's a use-case for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makes sense, this was an oversight after your comment regd events in the prev feedback
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in new commit :)
|
||
// Validate container volume mounts | ||
for i := range container.VolumeMounts { | ||
if container.VolumeMounts[i].Name, err = validateAndReplaceDataWithAttribute(container.VolumeMounts[i].Name, attributes); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has the same problem as command components; volumeMount.Name
has to refer to a volume component in the DevWorkspace, so any attribute here would have a fixed value (or else the DevWorkspace is invalid).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makes sense, this was an oversight after your comment regd events in the prev feedback
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in new commit :)
// Note: No need to substitute parent overrides at this point. Call global attribute validation/substitution | ||
// after merging the flattened parent devfile to the main devfile. Parent's global attribute key can | ||
// be overridden in parent overrides or the alternative is to mention the attribute as a main devfile | ||
// global attribute if parent devfile does not have a global attribute |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is kind of subtle and results in a fairly complicated replacement flow:
- Do attribute replacement to fill placeholders in
parent
section- This might fail if a component in the current devfile expects to use an attribute from its parent
- Flatten parent into Devfile to get a devfile without parent elements
- Do attribute replacement again, to handle placeholders in parent
- Validate devfile only at this point
Global attributes + replacement means that there are various places where devfiles are invalid until some other steps happen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@amisevsk So I've thought about this when drafting this PR and the way I see it is (copying devfile snipppet from your comment below),
metadata:
name: parent-devfile
attributes:
parent-attribute: "test"
components:
- name: parent-component
container:
image: quay.io/test/image
---
metadata:
name: main-devfile
attributes:
parent-devfile-id: "parent-devfile"
parent:
id: "{{parent-devfile-id}}"
components:
- name: child-component
image: quay.io/{{parent-attribute}}/image
To resolve this flow, in order
- resolve the parent in the main-devfile, so in this case the id
{{parent-devfile-id}}
(uri/kubernetes/registryURL) needs to be resolved to fetch the parent (cannot reference parent-devfile attribute because that is just not possible as parent hasnt been fetched) - overwrite the parent-devfile with main-devfile's parent overrides (teh regular override convention)
- merge parent-devfile to main-devfile (the regular merging convention)
- now validate the flatten devfile for global attributes
So we definitely need to resolve a parent source in the main-devfile to fetch the parent and is the only requirement here, unless we say parent source cannot have a global attribute as well.. WDYT? (EDIT: it probably makes sense to remove global attribute from parent id/uri/kubernetes, so we can get rid of the confusion from step 1 and will probably be consistent with us restricting it for other object's name/id)
As for your comment regd various places in devfile maybe invalid until some steps happen - isn't that true for other devfile objects? For example, my main-devfile command can be the following and is invalid by itself in the main-devfile but is valid in a flattened devfile(in devfile/library and as a result odo, we validate only the flattened devfile before using it for consumption).. 🤔
metadata:
name: parent-devfile
components:
- name: parent-component
container:
image: quay.io/test/image
---
metadata:
name: main-devfile
parent:
id: "parent-devfile-id"
command:
- id: child-command
exec:
component: parent-component
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sense to me (and good point about unflattened devfiles in general). I'm on the side of "no attributes in parent" except maybe in overrides to make step 1 simpler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@amisevsk Ack! Will remove global attributes from parent source but the overrides can have them(following the agreed upon observation that override object's id/name cannot have global attributes) and they will be resolved in the flattened devfile which is the step 4 mentioned above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in new commit :)
attrValue := attributes.GetString(match[1], &err) | ||
if err != nil { | ||
return "", err | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've thought about it a bit more and I'm going towards "this shouldn't be an error". Failing here causes subtle complexity that's hard to resolve, e.g. the devfiles:
metadata:
name: parent-devfile
attributes:
parent-attribute: "test"
components:
- name: parent-component
container:
image: quay.io/test/image
---
metadata:
name: main-devfile
attributes:
parent-devfile-id: "parent-devfile"
parent:
id: "{{parent-devfile-id}}"
components:
- name: child-component
image: quay.io/{{parent-attribute}}/image
become impossible to reconcile: doing the replacement in main-devfile
will fail because {{parent-attribute}}
does not exist, and we can't flatten in the parent attribute becuase its id is hidden behind {{parent-devfile-id}}
, which requires attribute replacement.
// +optional | ||
// +patchStrategy=merge | ||
// +devfile:overrides:include:omitInPlugin=true,description=Overrides of attributes encapsulated in a parent devfile. | ||
Attributes attributes.Attributes `json:"attributes,omitempty" patchStrategy:"merge"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless we're explicitly forbidding the global attributes introduced here from storing non-replacement attributes, then I think it doesn't make sense to have both -- it'll just be a source of confusion. If global attributes to be used just for replacement, then why isn't their type map[string]string
?
@@ -0,0 +1,3 @@ | |||
# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml | |||
label: "{{version}}" | |||
component: "{{foo}}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trailing newlines missing in various files here, may as well fix it now in case we get a linter one day.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in new commit :)
// +optional | ||
// +patchStrategy=merge | ||
// +devfile:overrides:include:omitInPlugin=true,description=Overrides of attributes encapsulated in a parent devfile. | ||
Attributes map[string]string `json:"attributes,omitempty" patchStrategy:"merge"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moving to map[string]string
complicates the issue further IMO. Now we have adjacent attributes fields that take different types of data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@amisevsk so do you recommend a rename of this field or stay as Attributes and move back to free-form yaml? Btw I'm not sure if you have read my prev comment here #352 (comment), but we're definitely interested in keeping metadata.Attributes.
If we want to keep it as map[string]string
, then it might be a good idea to rename it? 🤔
I just realized someting. (Hope it is not too late) ping @elsony @davidfestal |
@kadel yes this PR is on hold, and that is one of the topics up for discussion in the next cabal call to see what everyone prefers to call it. EDIT - here is the PR conversation about it #352 (comment) |
@maysunfaisal @kadel in the Che/DevWorkspace context we need attributes that are not in |
@l0rd there was one odo stack devfile that used metadata.attributes information previously, though I am not sure if they consume it at the moment - https://github.com/odo-devfiles/registry/blob/master/devfiles/java-openliberty/devfile.yaml#L6-L7. The console devfile dev preview also uses this metadata.attributes currently - https://github.com/redhat-developer/devfile-sample/blob/master/devfile.yaml#L5-L7 |
I do not see
Who's the DevConsole point of contact to verify if we can still change that or if it's too late (i.e. it's used in the wild)? |
@l0rd
That would be Andrew Ballantyne. I think the Console dev preview is waiting for the Devfile 2.2 outerloop support being discussed on the cabal call. I've already put up a topic for this on the next cabal call, we can discuss this then perhaps, with the wider team (I noticed that you also put up a topic on the next cabal call). |
@maysunfaisal I added the same topic in the agenda, I didn't notice yours :-) Let remove mine. To clarify: we figured out today that we require top level attributes, NOT in metadata. That's necessary for devfile v2 support in Che (hence v2.1 of the spec). Those top level attributes have to be free form On the other hand what you are introducing with this PR is something distinct that I would rather call Let's discuss that on this week cabal. |
As discussed on the cabal call, we'll:
|
I am planning on addressing all the points mentioned in Elson's comment here #352 (comment) with this PR since this PR started out as global attributes free form yaml. So the code for it can be adjusted from my prev commits on this PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apart from logging and a few minor comments, generally LGTM.
Signed-off-by: Maysun J Faisal <[email protected]>
Signed-off-by: Maysun J Faisal <[email protected]>
Signed-off-by: Maysun J Faisal <[email protected]>
Signed-off-by: Maysun J Faisal <[email protected]>
Signed-off-by: Maysun J Faisal <[email protected]>
Signed-off-by: Maysun J Faisal <[email protected]>
Signed-off-by: Maysun J Faisal <[email protected]>
pkg/utils/overriding/merging_test.go
Outdated
t.Error(err) | ||
} | ||
|
||
assert.Equal(t, string(expectedYaml), string(resultYaml), "The two values should be the same.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why bother getting json if it's not used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this was written prev, i just cut-paste them to a new file, but i'll update it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the original intention of the prev author was to have the result.yaml file contents sorted when comparing, the yaml file bytes is converted to JSON and then re-converted to YAML to maintain the order:
expectedJson, err := yamlMachinery.ToJSON(expected)
if err != nil {
t.Error(err)
}
expectedYaml, err := yaml.JSONToYAML(expectedJson)
if err != nil {
t.Error(err)
}
assert.Equal(t, string(expectedYaml), string(resultYaml), "The two values should be the same.")
I noticed that it results in a different order otherwise:
FAIL: TestMerging/no-parent (0.00s)
/Users/maysun/dev/redhat/api/pkg/utils/overriding/merging_test.go:40:
Error Trace: merging_test.go:40
Error: Not equal:
expected: "components:\n - name: test-plugin-component\n container:\n image: test-plugin-image\n - name: test-component\n container:\n image: test-image\n"
actual : "components:\n- container:\n image: test-plugin-image\n name: test-plugin-component\n- container:\n image: test-image\n name: test-component\n"
Diff:
--- Expected
+++ Actual
@@ -1,8 +1,8 @@
components:
- - name: test-plugin-component
- container:
- image: test-plugin-image
- - name: test-component
- container:
- image: test-image
+- container:
+ image: test-plugin-image
+ name: test-plugin-component
+- container:
+ image: test-image
+ name: test-component
Test: TestMerging/no-parent
Messages: The two values should be the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh, strange. I'm fine with keeping it as is but that should definitely have a doc explaining why it's necessary (otherwise someone like me might remove it when I see it in a year ;))
Another option is to compare structs directly, something along the lines of
var expectedTemplate *dw.DevWorkspaceTemplateSpec
err = yaml.Unmarshal(expected, expectedTemplate)
if err != nil {
t.Error(err)
}
assert.Equal(t, expectedTemplate, result, "The two values should be the same")
this makes some sense, since we're already serializing the result -- why not deserialize the expected instead?
If we want to keep the nice diff showing the error, this can be done using go-cmp
as well -- replace the assert.Equal
with
assert.True(t, cmp.Equal(expectedTemplate, result), fmt.Sprintf("Output doesn't match expected\n%s", cmp.Diff(expectedTemplate, result)))
(I don't remember if assert.Equal
prints nice diffs by default)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thats a good idea, I've just done what you've suggested and used the template struct to compare. Updated for both merging and overriding tests
Not sure why I havent thought this approach since most of the variable tests follow this way 😅
} | ||
dirPath := filepath.Dir(path) | ||
parent := []byte{} | ||
parentFile := filepath.Join(dirPath, "parent.yaml") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While you're touching this code, why not reuse readStructToFile()
? This could be a lot shorter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this was written prev, i just cut-paste them to a new file, but i'll update it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the prev author wanted to test the wrapper func for merging(and the same for overriding_test) which takes bytes[] instead of structs.
func MergeDevWorkspaceTemplateSpecBytes(originalBytes []byte, flattenedParentBytes []byte, flattenPluginsBytes ...[]byte)
calls
func MergeDevWorkspaceTemplateSpec( mainContent *dw.DevWorkspaceTemplateSpecContent, parentFlattenedContent *dw.DevWorkspaceTemplateSpecContent, pluginFlattenedContents ...*dw.DevWorkspaceTemplateSpecContent) (*dw.DevWorkspaceTemplateSpecContent, error)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sense, but is kind of ugly IMO. We've already had one panic bug due to testing the bytes
version over the default: #295
I'm fine with leaving it out of this PR as it's probably out-of-scope though.
Signed-off-by: Maysun J Faisal <[email protected]>
Signed-off-by: Maysun J Faisal <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only one more request 🎉
Thank you for your patience!
Edit: Looks like github changed how reviews are submitted -- the PR comments appear as comments above on the last review (I don't see the usual comments this review anymore).
Comments:
Signed-off-by: Maysun J Faisal <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉 🎉 🎉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/lgtm
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: amisevsk, elsony, maysunfaisal The full list of commands accepted by this bot can be found here.
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test review
Signed-off-by: Maysun J Faisal [email protected]
What does this PR do?
{{attributeKey}}
whereattributeKey
is the key entry in the global attributeschemaVersion
&metadata
{{}}
as value in the devfile📌 EDIT POST DEVFILE CABAL AND PR REVIEW DISCUSSION:
Post Devfile cabal call & PR review discussion, the above criteria has been modified, Please refer #352 (comment) for the PR acceptance criteria
What issues does this PR fix or reference?
Fixes #239
Is your PR tested? Consider putting some instruction how to test your changes
Yes, updated current tests for overriding & merging and added new go tests.
Docs PR