Skip to content

Use reflection to overlay profile onto config#872

Merged
nkubala merged 5 commits intoGoogleContainerTools:masterfrom
nkubala:reflect_profiles
Aug 15, 2018
Merged

Use reflection to overlay profile onto config#872
nkubala merged 5 commits intoGoogleContainerTools:masterfrom
nkubala:reflect_profiles

Conversation

@nkubala
Copy link
Copy Markdown
Contributor

@nkubala nkubala commented Aug 3, 2018

This changes the profile code to overlay the profile onto the original config using reflection, rather than relying on yaml unmarshalling. Two reasons why we should do this:

  1. Will allow us to use yaml:omitEmpty tags on the config fields more liberally, which makes printing out the marshalled yaml to the user nicer.
  2. Gives us more control over the actual processing of the profile. If there are fields we want to explicitly persist or ignore from the original config, we can add exceptions for those here.

@nkubala nkubala requested a review from dlorenc August 3, 2018 20:59
APIVersion: config.APIVersion,
Kind: config.Kind,
Build: BuildConfig{
Artifacts: overlayProfileField(config.Build.Artifacts, profile.Build.Artifacts).([]*Artifact),
Copy link
Copy Markdown
Contributor

@balopat balopat Aug 3, 2018

Choose a reason for hiding this comment

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

this is interesting - if I understand correctly you are avoiding to have to do a recursion/iteration on the profile structure by manually picking fields, right? The problem with this is that when we change the config structure, we will have to touch this code too because it introduces a dependency on the structure ... if we'd add a new field, e.g. config.Build.Foo, tests wouldn't fail, right, so we have to remember to add it here too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The issue here is that most of the values we're overlaying here are themselves structs (not primitive values), so we can't know which ones to treat as "values" and which ones to treat as "holders of values" (and recurse over them). This solution implicitly chooses the first non-nil value from the profile and uses it as the new value in the final config.

if fieldValue != nil && !reflect.ValueOf(fieldValue).IsNil() {
    // use this value and return
}

For example, the BuildConfig struct (top level in the SkaffoldConfig) contains the TagPolicy struct, which itself is composed of several pointers to structs:

type TagPolicy struct {
	GitTagger         *GitTagger         `yaml:"gitCommit"`
	ShaTagger         *ShaTagger         `yaml:"sha256"`
	EnvTemplateTagger *EnvTemplateTagger `yaml:"envTemplate"`
	DateTimeTagger    *DateTimeTagger    `yaml:"dateTime"`
}

GitTagger is technically of type struct, but we treat it as an actual value. Since we say it's invalid to have multiple values set here, this code just picks the first non-nil one and uses it. This was handled by yaml marshalling before because any value that wasn't set would be set to nil by marshalling the yaml, but if we add yaml:omitempty tags to the config this will break.

If we add something to the top level fields in the SkaffoldConfig, then it's true that tests could still pass and we could potentially miss a silent failure. I'm not sure of a good way to address this apart from either 1) reworking the implementation of the config to not use pointers as a way to implement "one-of" values (see #388), or 2) being more rigorous with our testing when we make changes to the config. WDYT?

Copy link
Copy Markdown
Contributor

@balopat balopat Aug 3, 2018

Choose a reason for hiding this comment

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

Thanks for the detailed explanation!

  • Can we somehow mark these top level elements as "one-of" and recognize that in the switch v.Kind()? it would be cool :) (Add yamltags #388 does it on the "option" level not on the "struct" level, maybe we could sync on this @dlorenc ? )
  • I would split the overlayProfileField to overlayOneOfField and overlaySpliceField methods (and maybe later do an overlayStructField if necessary) because the fact that the struct fields currently in the profile are "one-of" types has nothing to do inherently with the profile, it is confusing.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we somehow mark these top level elements as "one-of" and recognize that in the switch v.Kind()? it would be cool :) (#388 does it on the "option" level not on the "struct" level, maybe we could sync on this @dlorenc ? )

Marking them is doable with YAML tags, it should be relatively easy to add in here. I'll chat with Nick.

Copy link
Copy Markdown
Contributor

@r2d4 r2d4 left a comment

Choose a reason for hiding this comment

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

I'm not sure we actually want 2) as a feature. I think its better left as a task for kustomize or other tools.

@nkubala
Copy link
Copy Markdown
Contributor Author

nkubala commented Aug 3, 2018

I'm not sure we actually want 2) as a feature. I think its better left as a task for kustomize or other tools.

@r2d4 sure, that's just a side effect of this change. We don't have to change anything here if we don't want to

@codecov-io
Copy link
Copy Markdown

codecov-io commented Aug 13, 2018

Codecov Report

Merging #872 into master will not change coverage.
The diff coverage is 0%.

Impacted file tree graph

@@           Coverage Diff           @@
##           master     #872   +/-   ##
=======================================
  Coverage   38.27%   38.27%           
=======================================
  Files          56       56           
  Lines        2576     2576           
=======================================
  Hits          986      986           
  Misses       1476     1476           
  Partials      114      114
Impacted Files Coverage Δ
pkg/skaffold/watch/changes.go 76% <0%> (ø) ⬆️
pkg/skaffold/build/gcb/cloud_build.go 13.66% <0%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3f11067...b0d8bc4. Read the comment docs.

Copy link
Copy Markdown
Contributor

@balopat balopat left a comment

Choose a reason for hiding this comment

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

only one nit otherwise lgtm

return byName
}

func isOneOf(field reflect.StructField) bool {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: I would pull this out into the config package to encourage reuse of the "oneOf" logic instead of having it in v1alpha2

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

@nkubala nkubala merged commit ad07228 into GoogleContainerTools:master Aug 15, 2018
@nkubala nkubala deleted the reflect_profiles branch August 15, 2018 20:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants