-
Notifications
You must be signed in to change notification settings - Fork 210
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
Segment-free cookbook data (RFC 67) #1034
Conversation
@thommay Any way the maintainers can help out here? Maybe we could set up some time to pair on this. |
OK, here's the state of the game. The work in chef and chef-zero is complete and works. This now needs pedant updates to:
once that's done, this PR needs to get the work completed to down convert from v2 to v0 manifests. |
002d3a7
to
6743773
Compare
@thommay I'm going through old PRs and this feels like something that we still definitely want. (I'm assuming here, given that we haven't needed it so far, I'm guessing we just have client-side code that deals with the old format still). Any chance this is something that could end up on communities board this year? |
I would very much like to get this on our board this year but the habitat work is (rightly!) taking cycles right now |
85b3e60
to
0986590
Compare
c609793
to
670849c
Compare
Data). | ||
|
||
populate_segments(Data, Metadata) -> | ||
Md1 = [ ej:set({Segment}, Metadata, []) || Segment <- ?COOKBOOK_SEGMENTS ], |
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 clearly wrong but I can't figure out how to do what I want, which is to create each segment as an empty list before populating them. Is there a better way?
@@ -290,10 +331,11 @@ file_list_spec() -> | |||
|
|||
-spec extract_checksums(ejson_term()) -> [binary()]. | |||
extract_checksums(CBJson) -> | |||
Segments = [ <<"all_files">> | ?COOKBOOK_SEGMENTS ], |
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 only add all_files
to the list here because we don't want to have it treated as a segment generally (because it's not one!)
0b065c4
to
5239a91
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.
Looked at the Erlang code, left some comments/questions 😃
@@ -206,10 +217,18 @@ set_default_values(Cookbook) -> | |||
|
|||
-spec validate_cookbook(Cookbook :: ej:json_object(), | |||
{UrlName :: binary(), | |||
UrlVersion :: binary()}) -> {ok, ej:json_object()}. | |||
validate_cookbook(Cookbook, {UrlName, UrlVersion}) -> | |||
UrlVersion :: binary()}, true | false) -> {ok, ej:json_object()}. |
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.
[nit] I think I'd prefer
-spec validate_cookbook(Cookbook :: ej:json_object(),
{UrlName :: binary(), UrlVersion :: binary()},
AllFiles :: boolean()) -> {ok, ej:json_object()}.
...where I find including some "name" more important than replacing true | false
with boolean()
😃 -- I was wondering first what the lest argument might be. I mean, the next line tells me, too, so, this is just nitpicking.
@@ -186,15 +195,17 @@ compress_maybe(Data, Type) -> | |||
%% @doc Convert a binary JSON string representing a Chef Cookbook Version into an | |||
%% EJson-encoded Erlang data structure. | |||
%% @end | |||
-spec parse_binary_json(binary(), {binary(), binary()}) -> { ok, ejson_term() }. % or throw | |||
parse_binary_json(Bin, {UrlName, UrlVersion}) -> | |||
-spec parse_binary_json(binary(), {binary(), binary()}, true | false) -> { ok, ejson_term() }. % or throw |
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.
[nit] boolean()
?
%% so for data that is v0 on disk we have to prepend the segment to the name | ||
add_segment_to_filename(Segment, File) -> | ||
Fn = ej:get({<<"name">>}, File), | ||
Fn1 = iolist_to_binary([Segment, "/", Fn]), |
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.
Do we know/care that what we get in the "name"
field is good enough to be fed into iolist_to_binary
?
Weird things could happen:
2> iolist_to_binary(["foo", "/", 10]).
<<"foo/\n">>
3> iolist_to_binary(["foo", "/", -1]).
** exception error: bad argument
in function iolist_to_binary/1
called as iolist_to_binary(["foo","/",-1])
4> iolist_to_binary(["foo", "/", false]).
** exception error: bad argument
in function iolist_to_binary/1
called as iolist_to_binary(["foo","/",false])
Update: found it, we've validated that it's a string
✅
@@ -219,7 +225,7 @@ assemble_cookbook_ejson_test_() -> | |||
AuthzId, | |||
CBEJson), | |||
|
|||
MinCb = chef_cookbook_version:minimal_cookbook_ejson(Record, VHostUrl), | |||
MinCb = chef_cookbook_version:minimal_cookbook_ejson(Record, VHostUrl, 1), | |||
?assertEqual(undefined, ej:get({"metadata", "attributes"}, MinCb)), | |||
?assertEqual(undefined, ej:get({"metadata", "long_description"}, MinCb)), | |||
?assertEqual({[{<<"ruby">>, []}]}, ej:get({"metadata", "dependencies"}, MinCb)) |
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.
❓ Looks like we've updated all existing tests, and added a minimal test for the new version. Are there perhaps more interesting test cases to include?
(Unrelated thought: what happens in this case?
"all_files": [
{
"name": "metadata.rb",
"checksum": "d18c630c8a68ffa4852d13214d0525a6",
"path": ".////metadata.rb", # same path, mostly
"specificity": "default"
},
{
"name": "metadata.rb",
"checksum": "967087a09f48f234028d3aa27a094882",
"path": "metadata.rb",
"specificity": "default"
}
{
"name": "metadata.rb",
"checksum": "967087a09f48f234028d3aa27a094882",
"path": "foobar.rb", # inconsistent path/name?
"specificity": "default"
},,
)
Hmm unless it's a copy-paste mishap, the "inconsistent" part seems to be expected in the RFC (templates/default/unicorn.rb.erb
vs templates/unicorn.rb.erb
)... 🤔
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 generally looks good. I've not gone through the pedant tests in great detail yet, mostly focussing on the erlang side; I'll make another pass later today with that in mind.
UrlVersion :: binary()}) -> {ok, ej:json_object()}. | ||
validate_cookbook(Cookbook, {UrlName, UrlVersion}) -> | ||
UrlVersion :: binary()}, true | false) -> {ok, ej:json_object()}. | ||
validate_cookbook(Cookbook, {UrlName, UrlVersion}, AllFiles) when AllFiles =:= false -> |
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.
validate_cookbook(Cookbook, {UrlName, UrlVersion}, false)
might be more idiomatic.
{{opt, <<"platforms">>}, constraint_map_spec(cookbook_name)}, | ||
|
||
{{opt, <<"dependencies">>}, constraint_map_spec(cookbook_name)} | ||
]}}, |
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 note the v0 spec has recommendations, suggestions, conflicting, etc. but they seem to be missing here. The RFC seems to thing the fields are still valid, but they aren't being checked here. Are they supposed to be different? If not, we could DRY the thing up by having a common core and appending the appropriate 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.
RFC 85 removes those fields, and no v2 client will send them.
|
||
lists:foldl(fun(File, CB) -> | ||
{ Segment, Record } = remove_segment_from_filename(File), | ||
case lists:member(Segment, ?COOKBOOK_SEGMENTS) of |
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.
What happens to files that are uploaded via a V2 call that don't fit in the V0 segment pattern? The RFC doesn't mention that, but I see @stevendanna commented about it (chef-boneyard/chef-rfc#161 (comment)). What was the decision? It looks like we silently drop them; I wonder if we might want to error instead, or otherwise we could silently be breaking cookbooks.
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 only reasonable thing we can do is drop them; our choice is to drop them - and have cookbooks that only encompass the functionality that chef 12 can provide, whilst upgrading to 13 will handle everything; or we error, at which point cookbooks uploaded on 13 will almost always entirely stop working on 12, because inevitably we'll upload something (like a test directory) that 12 doesn't understand but that doesn't affect running the cookbook.
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.
Understand the reasoning, but it strikes me as creating a really painful debug experience where those files are needed in the cookbook, but silently dropped on an older version of chef.
This is where something like a 'required_chef_version' cookbook metadata field would be nice; you could allow uploading non-standard paths only if the required_chef_version was set for a version that supported , and error on downloading cookbooks to chef versions older than supported.
end. | ||
|
||
populate_all_files(Segment, Data, Metadata) -> | ||
lists:foldl(fun(File, CB) -> |
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.
Since Segment is a constant, It might be easier to invert the order; e.g.
ExpandedFiles = case Segment of
<<"root_files">> -> Data;
_ ->
[ add_segment_to_filename(Segment, File) || File <- Data ]
end,
ej:set({<<"all_files">>}, Metadata, ExpandedFiles)
lists:foldl(fun(File, CB) -> | ||
{ Segment, Record } = remove_segment_from_filename(File), | ||
case lists:member(Segment, ?COOKBOOK_SEGMENTS) of | ||
true -> ej:set_p({Segment, new}, CB, Record); |
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 tend to do much of my building of JSON structures outside of EJSON, then insert in the end. IMHO it can lead to clearer code. In particular, the ej:set_p pattern with new caught me by surprise, (it's not used anywhere else in chef-server or a1 that I can see.
I might have done this by folding over a map of segments to arrays, inserting into that, and then for each recognized COOKBOOK_SEGMENT extracting the built array from the map and ej:set-ting the field.
That's a matter of taste though.
As a secondary thing, I do like splitting things into smaller functions when possible; mostly because redbug doesn't really let you trace an anonymous function easily.
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.
So you'd break out the function the second foldl
takes (assuming the structure remains the same) into a real function?
|
opts[:payload].each do |part, files| | ||
all_files += files.each_with_object([]) do |f, acc| | ||
file = f.dup | ||
file["name"] = "#{part}/#{file["name"]}" |
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.
Looking at the usage: https://github.com/chef/chef-server/pull/1034/files#diff-d5911d79b4d2796916287911d6a428fcR69 it seems like we'd need to do something different for root_files?
case ej:get({Segment}, CB) of | ||
undefined -> CB; | ||
Data -> | ||
CB1 = populate_all_files(Segment, Data, CB), |
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.
It looks like we replace all_files in populate_all_files above, so this fold would only keep the last segment.
I pushed a branch ma/kill_segment_tweaks with some ideas on refactoring the code. |
.travis.yml
Outdated
@@ -83,7 +83,7 @@ matrix: | |||
script: bundle exec rake chef_zero_spec | |||
env: | |||
- "PEDANT_OPTS=\"--skip-oc_id\"" | |||
- "GEMFILE_MOD=\"gem 'rake'; gem 'chef-zero', github: 'chef/chef-zero', tag: 'v14.0.5' \"" | |||
- "GEMFILE_MOD=\"gem 'rake'; gem 'chef-zero', github: 'chef/chef-zero'; gem 'chef', github: 'chef/chef' \"" |
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 pin to 14.0.5 was due to the latest chef-zero having issues with chef 13; it might be better to wait on this unpinning until we no longer pin the rest of chef server to chef 13.
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.
it actually does no good anyway, so yeah, i'll revert it.
I think this is really close to merging. |
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 ready to go except for a few minor tweaks.
b87c21f
to
b96bd84
Compare
Implement RFC 67, which causes us to bump our max API version to 2. * Accept segmentless and segmented downloads and uploads of cookbooks * Add pedant tests Signed-off-by: Thom May <[email protected]>
b96bd84
to
95b5318
Compare
https://chef.github.io/chef-rfc/rfc067-cookbook-segment-deprecation.html
This bumps API version for cookbook requests to 2.0, and transforms
cookbook metadata to provide all_files.