diff options
-rwxr-xr-x | bin/chef-zero | 5 | ||||
-rw-r--r-- | lib/chef_zero/chef_data/data_normalizer.rb | 14 | ||||
-rw-r--r-- | lib/chef_zero/chef_data/default_creator.rb | 2 | ||||
-rw-r--r-- | lib/chef_zero/endpoints/organization_policies_endpoint.rb | 100 | ||||
-rw-r--r-- | lib/chef_zero/endpoints/organization_policy_groups_endpoint.rb | 262 | ||||
-rw-r--r-- | lib/chef_zero/endpoints/policies_endpoint.rb | 153 | ||||
-rw-r--r-- | lib/chef_zero/endpoints/policy_endpoint.rb | 24 | ||||
-rw-r--r-- | lib/chef_zero/endpoints/policy_group_endpoint.rb | 46 | ||||
-rw-r--r-- | lib/chef_zero/endpoints/policy_group_policy_endpoint.rb | 84 | ||||
-rw-r--r-- | lib/chef_zero/endpoints/policy_groups_endpoint.rb | 38 | ||||
-rw-r--r-- | lib/chef_zero/endpoints/policy_revision_endpoint.rb | 23 | ||||
-rw-r--r-- | lib/chef_zero/endpoints/policy_revisions_endpoint.rb | 15 | ||||
-rw-r--r-- | lib/chef_zero/rest_base.rb | 4 | ||||
-rw-r--r-- | lib/chef_zero/rspec.rb | 22 | ||||
-rw-r--r-- | lib/chef_zero/server.rb | 41 | ||||
-rw-r--r-- | spec/support/oc_pedant.rb | 1 |
16 files changed, 323 insertions, 511 deletions
diff --git a/bin/chef-zero b/bin/chef-zero index 54739bc..231d71b 100755 --- a/bin/chef-zero +++ b/bin/chef-zero @@ -55,6 +55,11 @@ OptionParser.new do |opts| options[:log_file] = value end + opts.on("--enterprise", "Whether to run in enterprise mode") do |value| + options[:single_org] = nil + options[:osc_compat] = false + end + opts.on("--multi-org", "Whether to run in multi-org mode") do |value| options[:single_org] = nil end diff --git a/lib/chef_zero/chef_data/data_normalizer.rb b/lib/chef_zero/chef_data/data_normalizer.rb index 2dff91e..87e7c8a 100644 --- a/lib/chef_zero/chef_data/data_normalizer.rb +++ b/lib/chef_zero/chef_data/data_normalizer.rb @@ -166,6 +166,20 @@ module ChefZero node end + def self.normalize_policy(policy, name, revision) + policy['name'] ||= name + policy['revision_id'] ||= revision + policy['run_list'] ||= [] + policy['cookbook_locks'] ||= {} + policy + end + + def self.normalize_policy_group(policy_group, name) + policy_group[name] ||= 'name' + policy_group['policies'] ||= {} + policy_group + end + def self.normalize_organization(org, name) org['name'] ||= name org['full_name'] ||= name diff --git a/lib/chef_zero/chef_data/default_creator.rb b/lib/chef_zero/chef_data/default_creator.rb index 8a5b7fb..7b18d51 100644 --- a/lib/chef_zero/chef_data/default_creator.rb +++ b/lib/chef_zero/chef_data/default_creator.rb @@ -155,6 +155,8 @@ module ChefZero 'checksums' => {} }, 'nodes' => {}, + 'policies' => {}, + 'policy_groups' => {}, 'roles' => {}, 'sandboxes' => {}, 'users' => {}, diff --git a/lib/chef_zero/endpoints/organization_policies_endpoint.rb b/lib/chef_zero/endpoints/organization_policies_endpoint.rb deleted file mode 100644 index a495d9b..0000000 --- a/lib/chef_zero/endpoints/organization_policies_endpoint.rb +++ /dev/null @@ -1,100 +0,0 @@ - -module ChefZero - module Endpoints - # /organizations/NAME/policies - class OrganizationPoliciesEndpoint < RestBase - def hashify_list(list) - list.reduce({}) { |acc, obj| acc.merge( obj => {} ) } - end - - def get(request) - - # vanilla /policies. - if request.rest_path.last == "policies" - response_data = {} - policy_names = list_data_or_else(request, nil, []) - policy_names.each do |policy_name| - policy_path = request.rest_path + [policy_name] - policy_uri = build_uri(request.base_uri, policy_path) - revisions = list_data_or_else(request, policy_path + ["revisions"], {}) - - response_data[policy_name] = { - uri: policy_uri, - revisions: hashify_list(revisions) - } - end - - return json_response(200, response_data) - end - - # /policies/:policy_name - if request.rest_path[-2] == "policies" - if !exists_data_dir?(request) - return error(404, "Item not found" ) - else - revisions = list_data(request, request.rest_path + ["revisions"]) - data = { revisions: hashify_list(revisions) } - return json_response(200, data) - end - end - - # /policies/:policy_name/revisions/:revision_id - if request.rest_path[-2] == "revisions" - if !exists_data?(request, nil) - return error(404, "Revision ID #{request.rest_path.last} not found" ) - else - data = get_data(request) - return already_json_response(200, data) - end - end - end - - def post(request) - if request.rest_path.last == "revisions" - # POST /policies/:policy_name/revisions - # we want to create /policies/{policy_name}/revisions/{revision_id} - policyfile_data = parse_json(request.body) - uri_policy_name = request.rest_path[-2] - - if exists_data?(request, request.rest_path + [policyfile_data["revision_id"]]) - return error(409, "Revision ID #{policyfile_data["revision_id"]} already exists.") - end - - if policyfile_data["name"] != uri_policy_name - return error(400, "URI policy name #{uri_policy_name} does not match JSON policy name #{policyfile_data["name"]}") - end - - revision_path = request.rest_path + [policyfile_data["revision_id"]] - set_data(request, revision_path, request.body, *set_opts) - return already_json_response(201, request.body) - end - end - - def delete(request) - # /policies/:policy_name/revisions/:revision_id - if request.rest_path[-2] == "policies" - revisions = list_data(request, request.rest_path + ["revisions"]) - data = { revisions: hashify_list(revisions) } - - delete_data_dir(request, nil, :recursive) - return json_response(200, data) - end - - if request.rest_path[-2] == "revisions" - if exists_data?(request) - policyfile_data = get_data(request) - delete_data(request) - return already_json_response(200, policyfile_data) - else - return error(404, "Revision ID #{request.rest_path.last} not found") - end - end - end - - private - def set_opts - [ :create_dir ] - end - end - end -end diff --git a/lib/chef_zero/endpoints/organization_policy_groups_endpoint.rb b/lib/chef_zero/endpoints/organization_policy_groups_endpoint.rb deleted file mode 100644 index 51e0812..0000000 --- a/lib/chef_zero/endpoints/organization_policy_groups_endpoint.rb +++ /dev/null @@ -1,262 +0,0 @@ -require 'ffi_yajl' -require 'chef_zero/rest_base' - -module ChefZero - module Endpoints - # /organizations/{organization}/policy_groups/{policy_group}/policies/{policy_name} - # GET / PUT / DELETE - # - # in the data store, this REST path actually stores the revision ID of ${policy_name} that's currently - # associated with ${policy_group}. - class OrganizationPolicyGroupsEndpoint < RestBase - - def fetch_uri_params(request) - { - org_name: request.rest_path[1], - policy_group_name: request.rest_path[3], - policy_name: request.rest_path[5] - } - end - - # Either return all the policy groups with URIs and list of revisions, or... - # Return the policy document for the given policy group and policy name. - def get(request) - - # vanilla /policy_groups. - if request.rest_path.last == "policy_groups" - policy_group_names = list_data_or_else(request, nil, []) - # no policy groups, so sad. - if policy_group_names.size == 0 - return already_json_response(200, '{}') - else - - response_data = {} - # each policy group has policies and associated revisions under - # /policy_groups/{group name}/policies/{policy name}. - policy_group_names.each do |group_name| - - response_data[group_name] = { - uri: build_uri(request.base_uri, request.rest_path + [group_name]), - policies: {} - } - - policy_group_path = request.rest_path + [group_name] - policy_group_policies_path = policy_group_path + ["policies"] - policy_list = list_data(request, policy_group_policies_path) - - # build the list of policies with their revision ID associated with this policy group. - policy_list.each do |policy_name| - policy_group_policy_path = policy_group_policies_path + [policy_name] - revision_id = get_data_or_else(request, policy_group_policy_path, "no revision ID found") - response_data[group_name][:policies][policy_name] = { - revision_id: revision_id - } - end - - response_data[group_name].delete(:policies) if response_data[group_name][:policies].size == 0 - end - - return json_response(200, response_data) - end # if policy_groups.size > 0 - end # end /policy_groups - - # /policy_groups/{policy_group} - if request.rest_path.last(2).first == "policy_groups" - data = { - uri: build_uri(request.base_uri, request.rest_path), - policies: get_policy_group_policies(request) - } - return json_response(200, data) - end - - # /policy_groups/{policy_group}/policies/{policy_name} - if request.rest_path.last(2).first == "policies" - uri_params = fetch_uri_params(request) - - # fetch /organizations/{organization}/policies/{policy_name}/revisions/{revision_id} - revision_id = parse_json(get_data(request)) - result = get_data(request, ["organizations", uri_params[:org_name], "policies", - uri_params[:policy_name], "revisions", revision_id], :nil) - return already_json_response(200, result) - end - end # end get() - - # Create or update the policy document for the given policy group and policy name. If no policy group - # with the given name exists, it will be created. If no policy with the given revision_id exists, it - # will be created from the document in the request body. If a policy with that revision_id exists, the - # Chef Server simply associates that revision id with the given policy group. When successful, the - # document that was created or updated is returned. - - # build a hash of {"some_policy_name"=>{"revision_id"=>"909c26701e291510eacdc6c06d626b9fa5350d25"}} - def get_policy_group_policies(request) - policies_revisions = {} - - policies_path = request.rest_path + ["policies"] - policy_names = list_data(request, policies_path) - policy_names.each do |policy_name| - revision = parse_json(get_data(request, policies_path + [policy_name])) - policies_revisions[policy_name] = { revision_id: revision} - end - - policies_revisions - end - - ## MANDATORY FIELDS AND FORMATS - # * `revision_id`: String; Must be < 255 chars, matches /^[\-[:alnum:]_\.\:]+$/ - # * `name`: String; Must match name in URI; Must be <= 255 chars, matches /^[\-[:alnum:]_\.\:]+$/ - # * `run_list`: Array - # * `run_list[i]`: Fully Qualified Recipe Run List Item - # * `cookbook_locks`: JSON Object - # * `cookbook_locks(key)`: CookbookName - # * `cookbook_locks[item]`: JSON Object, mandatory keys: "identifier", "dotted_decimal_identifier" - # * `cookbook_locks[item]["identifier"]`: varchar(255) ? - # * `cookbook_locks[item]["dotted_decimal_identifier"]` ChefCompatibleVersionNumber - - - def cookbook_locks_valid?(cookbook_locks) - if !cookbook_locks.is_a?(Hash) - return [false, "Field 'cookbook_locks' invalid"] - end - - cookbook_locks.each do |name, lock_data| - if !lock_data.is_a?(Hash) - return [false, "Field 'cookbook_locks' invalid"] - end - - if !lock_data.has_key?("identifier") - return [false, "Field 'identifier' missing"] - end - - if lock_data["identifier"].length > 255 - return [false, "Field 'identifier' invalid"] - end - - if lock_data.has_key?("dotted_decimal_identifier") && - lock_data["dotted_decimal_identifier"] !~ /\d+\.\d+\.\d+/ - return [false, "Field 'dotted_decimal_identifier' is not a valid version"] - end - end - return [true, "no error to return"] - end - - def validate_policyfile(policyfile_data) - if !policyfile_data.has_key?("revision_id") - return [false, "Field 'revision_id' missing"] - elsif policyfile_data["revision_id"] !~ /^[\-[:alnum:]_\.\:]{1,255}$/ - return [false, "Field 'revision_id' invalid"] - end - - if !policyfile_data.has_key?("name") - return [false, "Field 'name' missing"] - elsif policyfile_data["name"] !~ /^[\-[:alnum:]_\.\:]{1,255}$/ - return [false, "Field 'name' invalid"] - end - - if !policyfile_data.has_key?("run_list") - return [false, "Field 'run_list' missing"] - elsif !(policyfile_data["run_list"].is_a?(Array) && - policyfile_data["run_list"].all? { |r| r =~ /\Arecipe\[[^\s]+::[^\s]+\]\Z/ }) - return [false, "Field 'run_list' is not a valid run list"] - end - - if !policyfile_data.has_key?("cookbook_locks") - return [false, "Field 'cookbook_locks' missing"] - else - # change this logic if there are more validations after this. - return cookbook_locks_valid?(policyfile_data["cookbook_locks"]) - end - - return [true, "no error to return"] - end - - def put(request) - - # validate request body. - policyfile_data = parse_json(request.body) - - is_valid, error_msg = validate_policyfile(policyfile_data) - if !is_valid - return error(400, error_msg) - end - - if request.rest_path.last != policyfile_data["name"] - return error(400, "Field 'name' invalid : #{request.rest_path.last} does not match #{policyfile_data["name"]}") - end - - uri_params = fetch_uri_params(request) - org_path = request.rest_path.first(2) - - - new_policyfile_data = parse_json( request.body ) - - # get the current list of revisions of this policyfile. - policyfile_path = request.rest_path[0..1] + ["policies", uri_params[:policy_name]] - - policy_revisions = list_data_or_else(request, policyfile_path + ["revisions"], []) - - # if the given policy+revision doesn't exist, create it.. - if !policy_revisions.include?(new_policyfile_data["revision_id"]) - new_revision_path = policyfile_path +["revisions", new_policyfile_data["revision_id"]] - set_data(request, new_revision_path, request.body, *set_opts) - created_policy = true - end - - no_revision_set = "no revision ID set" - - # this request's data path just stores the revision ID currently associated with the policy group. - existing_revision_id = get_data_or_else(request, nil, no_revision_set) - - # if named policy exists and the given revision ID exists, associate the revision ID with the policy - # group. - if existing_revision_id != new_policyfile_data["revision_id"] - set_data(request, nil, to_json(new_policyfile_data["revision_id"]), *set_opts) - updated_association = true - end - - code = (existing_revision_id == no_revision_set) ? 201 : 200 - - return already_json_response(code, request.body) - end - - def delete(request) - # /policy_groups/{policy_group} - if request.rest_path.last(2).first == "policy_groups" - - policy_group_policies = get_policy_group_policies(request) - - if exists_data_dir?(request, request.rest_path + ["policies"]) - delete_data_dir(request, request.rest_path + ["policies"], :recursive) - end - - data = { - uri: build_uri(request.base_uri, request.rest_path), - policies: policy_group_policies - } - return json_response(200, data) - end - - # "/policy_groups/some_policy_group/policies/some_policy_name" - if request.rest_path.last(2).first == "policies" - current_revision_id = parse_json(get_data(request)) - - # delete the association. - delete_data(request) - - # return the full policy document at the no-longer-associated revision. - policy_path = request.rest_path.first(2) + ["policies", request.rest_path.last, - "revisions", current_revision_id] - - full_policy_doc = get_data(request, policy_path) - return already_json_response(200, full_policy_doc) - end - - return error(404, "Don't know what to do with path #{request.rest_path}") - end - - private - def set_opts - [ :create_dir ] - end - end - end -end diff --git a/lib/chef_zero/endpoints/policies_endpoint.rb b/lib/chef_zero/endpoints/policies_endpoint.rb index ddb2e9b..37493da 100644 --- a/lib/chef_zero/endpoints/policies_endpoint.rb +++ b/lib/chef_zero/endpoints/policies_endpoint.rb @@ -1,151 +1,26 @@ -require 'ffi_yajl' - -require 'chef_zero/endpoints/rest_object_endpoint' require 'chef_zero/chef_data/data_normalizer' module ChefZero module Endpoints - # /policies/:group/:name - class PoliciesEndpoint < RestObjectEndpoint - def initialize(server) - super(server, 'id') - end - + # /organizations/ORG/policies + class PoliciesEndpoint < RestBase + # GET /organizations/ORG/policies def get(request) - already_json_response(200, get_data(request)) - end - - # Right now we're allowing PUT to create. - def put(request) - error = validate(request) - return error if error - - code = - if data_store.exists?(request.rest_path) - set_data(request, request.rest_path, request.body, :data_store_exceptions) - 200 - else - name = request.rest_path[4] - data_store.create(request.rest_path[0..3], name, request.body, :create_dir) - 201 - end - already_json_response(code, request.body) - end - - def delete(request) - result = get_data(request, request.rest_path) - delete_data(request, request.rest_path, :data_store_exceptions) - already_json_response(200, result) - end - - private - - def validate(request) - req_object = validate_json(request.body) - validate_revision_id(request, req_object) || - validate_name(request, req_object) || - validate_run_list(req_object) || - validate_each_run_list_item(req_object) || - validate_cookbook_locks_collection(req_object) || - validate_each_cookbook_locks_item(req_object) - end - - def validate_json(request_body) - FFI_Yajl::Parser.parse(request_body) - # TODO: rescue parse error, return 400 - # error(400, "Must specify #{identity_keys.map { |k| k.inspect }.join(' or ')} in JSON") - end - - def validate_revision_id(request, req_object) - if !req_object.key?("revision_id") - error(400, "Field 'revision_id' missing") - elsif req_object["revision_id"].empty? - error(400, "Field 'revision_id' invalid") - elsif req_object["revision_id"].size > 255 - error(400, "Field 'revision_id' invalid") - elsif req_object["revision_id"] !~ /^[\-[:alnum:]_\.\:]+$/ - error(400, "Field 'revision_id' invalid") - end - end - - def validate_name(request, req_object) - if !req_object.key?("name") - error(400, "Field 'name' missing") - elsif req_object["name"] != (uri_policy_name = URI.decode(request.rest_path[4])) - error(400, "Field 'name' invalid : #{uri_policy_name} does not match #{req_object["name"]}") - elsif req_object["name"].size > 255 - error(400, "Field 'name' invalid") - elsif req_object["name"] !~ /^[\-[:alnum:]_\.\:]+$/ - error(400, "Field 'name' invalid") - end - end - - def validate_run_list(req_object) - if !req_object.key?("run_list") - error(400, "Field 'run_list' missing") - elsif !req_object["run_list"].kind_of?(Array) - error(400, "Field 'run_list' is not a valid run list") - end - end - - def validate_each_run_list_item(req_object) - req_object["run_list"].each do |run_list_item| - if res_400 = validate_run_list_item(run_list_item) - return res_400 - end - end - nil - end + response_data = {} + policy_names = list_data(request) + policy_names.each do |policy_name| + policy_path = request.rest_path + [policy_name] + policy_uri = build_uri(request.base_uri, policy_path) + revisions = list_data(request, policy_path + ["revisions"]) - def validate_run_list_item(run_list_item) - if !run_list_item.kind_of?(String) - error(400, "Field 'run_list' is not a valid run list") - elsif run_list_item !~ /\Arecipe\[[^\s]+::[^\s]+\]\Z/ - error(400, "Field 'run_list' is not a valid run list") + response_data[policy_name] = { + uri: policy_uri, + revisions: hashify_list(revisions) + } end - end - def validate_cookbook_locks_collection(req_object) - if !req_object.key?("cookbook_locks") - error(400, "Field 'cookbook_locks' missing") - elsif !req_object["cookbook_locks"].kind_of?(Hash) - error(400, "Field 'cookbook_locks' invalid") - end + return json_response(200, response_data) end - - def validate_each_cookbook_locks_item(req_object) - req_object["cookbook_locks"].each do |cookbook_name, lock| - if res_400 = validate_cookbook_locks_item(cookbook_name, lock) - return res_400 - end - end - nil - end - - def validate_cookbook_locks_item(cookbook_name, lock) - if !lock.kind_of?(Hash) - error(400, "cookbook_lock entries must be a JSON object") - elsif !lock.key?("identifier") - error(400, "Field 'identifier' missing") - elsif lock["identifier"].size > 255 - error(400, "Field 'identifier' invalid") - elsif !lock.key?("version") - error(400, "Field 'version' missing") - elsif lock.key?("dotted_decimal_identifier") - unless valid_version?(lock["dotted_decimal_identifier"]) - error(400, "Field 'dotted_decimal_identifier' is not a valid version") - end - end - end - - def valid_version?(version_string) - Gem::Version.new(version_string) - true - rescue ArgumentError - false - end - end end end - diff --git a/lib/chef_zero/endpoints/policy_endpoint.rb b/lib/chef_zero/endpoints/policy_endpoint.rb new file mode 100644 index 0000000..d8c1bc8 --- /dev/null +++ b/lib/chef_zero/endpoints/policy_endpoint.rb @@ -0,0 +1,24 @@ +require 'chef_zero/chef_data/data_normalizer' + +module ChefZero + module Endpoints + # /organizations/ORG/policies/NAME + class PolicyEndpoint < RestBase + # GET /organizations/ORG/policies/NAME + def get(request) + revisions = list_data(request, request.rest_path + ["revisions"]) + data = { revisions: hashify_list(revisions) } + return json_response(200, data) + end + + # DELETE /organizations/ORG/policies/NAME + def delete(request) + revisions = list_data(request, request.rest_path + ["revisions"]) + data = { revisions: hashify_list(revisions) } + + delete_data_dir(request, nil, :recursive) + return json_response(200, data) + end + end + end +end diff --git a/lib/chef_zero/endpoints/policy_group_endpoint.rb b/lib/chef_zero/endpoints/policy_group_endpoint.rb new file mode 100644 index 0000000..54732c8 --- /dev/null +++ b/lib/chef_zero/endpoints/policy_group_endpoint.rb @@ -0,0 +1,46 @@ +require 'ffi_yajl' +require 'chef_zero/rest_base' +require 'chef_zero/chef_data/data_normalizer' + +module ChefZero + module Endpoints + # /organizations/ORG/policy_groups/NAME + class PolicyGroupEndpoint < RestBase + + # GET /organizations/ORG/policy_groups/NAME + def get(request) + data = { + uri: build_uri(request.base_uri, request.rest_path), + policies: get_policy_group_policies(request) + } + json_response(200, data) + end + + # build a hash of {"some_policy_name"=>{"revision_id"=>"909c26701e291510eacdc6c06d626b9fa5350d25"}} + def get_policy_group_policies(request) + policies_revisions = {} + + policies_path = request.rest_path + ["policies"] + policy_names = list_data(request, policies_path) + policy_names.each do |policy_name| + revision = parse_json(get_data(request, policies_path + [policy_name])) + policies_revisions[policy_name] = { revision_id: revision} + end + + policies_revisions + end + + # DELETE /organizations/ORG/policy_groups/NAME + def delete(request) + policy_group_policies = get_policy_group_policies(request) + delete_data_dir(request, nil, :recursive) + + data = { + uri: build_uri(request.base_uri, request.rest_path), + policies: policy_group_policies + } + json_response(200, data) + end + end + end +end diff --git a/lib/chef_zero/endpoints/policy_group_policy_endpoint.rb b/lib/chef_zero/endpoints/policy_group_policy_endpoint.rb new file mode 100644 index 0000000..5d32aac --- /dev/null +++ b/lib/chef_zero/endpoints/policy_group_policy_endpoint.rb @@ -0,0 +1,84 @@ +require 'ffi_yajl' +require 'chef_zero/rest_base' +require 'chef_zero/chef_data/data_normalizer' + +module ChefZero + module Endpoints + # /organizations/ORG/policy_groups/GROUP/policies/NAME + # + # in the data store, this REST path actually stores the revision ID of ${policy_name} that's currently + # associated with ${policy_group}. + class PolicyGroupPolicyEndpoint < RestBase + + # GET /organizations/ORG/policy_groups/GROUP/policies/NAME + def get(request) + policy_name = request.rest_path[5] + + # fetch /organizations/{organization}/policies/{policy_name}/revisions/{revision_id} + revision_id = parse_json(get_data(request)) + result = get_data(request, request.rest_path[0..1] + + ["policies", policy_name, "revisions", revision_id]) + result = ChefData::DataNormalizer.normalize_policy(parse_json(result), policy_name, revision_id) + json_response(200, result) + end + + # Create or update the policy document for the given policy group and policy name. If no policy group + # with the given name exists, it will be created. If no policy with the given revision_id exists, it + # will be created from the document in the request body. If a policy with that revision_id exists, the + # Chef Server simply associates that revision id with the given policy group. When successful, the + # document that was created or updated is returned. + + ## MANDATORY FIELDS AND FORMATS + # * `revision_id`: String; Must be < 255 chars, matches /^[\-[:alnum:]_\.\:]+$/ + # * `name`: String; Must match name in URI; Must be <= 255 chars, matches /^[\-[:alnum:]_\.\:]+$/ + # * `run_list`: Array + # * `run_list[i]`: Fully Qualified Recipe Run List Item + # * `cookbook_locks`: JSON Object + # * `cookbook_locks(key)`: CookbookName + # * `cookbook_locks[item]`: JSON Object, mandatory keys: "identifier", "dotted_decimal_identifier" + # * `cookbook_locks[item]["identifier"]`: varchar(255) ? + # * `cookbook_locks[item]["dotted_decimal_identifier"]` ChefCompatibleVersionNumber + + # PUT /organizations/ORG/policy_groups/GROUP/policies/NAME + def put(request) + policyfile_data = parse_json(request.body) + policy_name = request.rest_path[5] + revision_id = policyfile_data["revision_id"] + + # If the policy revision being submitted does not exist, create it. + # Storage: /organizations/ORG/policies/POLICY/revisions/REVISION + policyfile_path = request.rest_path[0..1] + ["policies", policy_name, "revisions", revision_id] + if !exists_data?(request, policyfile_path) + create_data(request, policyfile_path[0..-2], revision_id, request.body, :create_dir) + end + + # if named policy exists and the given revision ID exists, associate the revision ID with the policy + # group. + # Storage: /organizations/ORG/policies/POLICY/revisions/REVISION + response_code = exists_data?(request) ? 200 : 201 + set_data(request, nil, to_json(revision_id), :create_dir) + + already_json_response(response_code, request.body) + end + + # DELETE /organizations/ORG/policy_groups/GROUP/policies/NAME + def delete(request) + # Save the existing association. + current_revision_id = parse_json(get_data(request)) + + # delete the association. + delete_data(request) + + # return the full policy document at the no-longer-associated revision. + policy_name = request.rest_path[5] + policy_path = request.rest_path[0..1] + ["policies", policy_name, + "revisions", current_revision_id] + + + full_policy_doc = parse_json(get_data(request, policy_path)) + full_policy_doc = ChefData::DataNormalizer.normalize_policy(full_policy_doc, policy_name, current_revision_id) + return json_response(200, full_policy_doc) + end + end + end +end diff --git a/lib/chef_zero/endpoints/policy_groups_endpoint.rb b/lib/chef_zero/endpoints/policy_groups_endpoint.rb new file mode 100644 index 0000000..f17db8d --- /dev/null +++ b/lib/chef_zero/endpoints/policy_groups_endpoint.rb @@ -0,0 +1,38 @@ +require 'ffi_yajl' +require 'chef_zero/rest_base' +require 'chef_zero/chef_data/data_normalizer' + +module ChefZero + module Endpoints + # /organizations/ORG/policy_groups + # + # in the data store, this REST path actually stores the revision ID of ${policy_name} that's currently + # associated with ${policy_group}. + class PolicyGroupsEndpoint < RestBase + # GET /organizations/ORG/policy_groups + def get(request) + # each policy group has policies and associated revisions under + # /policy_groups/{group name}/policies/{policy name}. + response_data = {} + list_data(request).each do |group_name| + group_path = request.rest_path + [group_name] + policy_list = list_data(request, group_path + ["policies"]) + + # build the list of policies with their revision ID associated with this policy group. + policies = {} + policy_list.each do |policy_name| + revision_id = parse_json(get_data(request, group_path + ["policies", policy_name])) + policies[policy_name] = { revision_id: revision_id } + end + + response_data[group_name] = { + uri: build_uri(request.base_uri, group_path) + } + response_data[group_name][:policies] = policies unless policies.empty? + end + + json_response(200, response_data) + end + end + end +end diff --git a/lib/chef_zero/endpoints/policy_revision_endpoint.rb b/lib/chef_zero/endpoints/policy_revision_endpoint.rb new file mode 100644 index 0000000..6a77d26 --- /dev/null +++ b/lib/chef_zero/endpoints/policy_revision_endpoint.rb @@ -0,0 +1,23 @@ +require 'chef_zero/chef_data/data_normalizer' + +module ChefZero + module Endpoints + # /organizations/ORG/policies/NAME/revisions/REVISION + class PolicyRevisionEndpoint < RestBase + # GET /organizations/ORG/policies/NAME/revisions/REVISION + def get(request) + data = parse_json(get_data(request)) + data = ChefData::DataNormalizer.normalize_policy(data, request.rest_path[3], request.rest_path[5]) + return json_response(200, data) + end + + # DELETE /organizations/ORG/policies/NAME/revisions/REVISION + def delete(request) + policyfile_data = parse_json(get_data(request)) + policyfile_data = ChefData::DataNormalizer.normalize_policy(policyfile_data, request.rest_path[3], request.rest_path[5]) + delete_data(request) + return json_response(200, policyfile_data) + end + end + end +end diff --git a/lib/chef_zero/endpoints/policy_revisions_endpoint.rb b/lib/chef_zero/endpoints/policy_revisions_endpoint.rb new file mode 100644 index 0000000..7c20a24 --- /dev/null +++ b/lib/chef_zero/endpoints/policy_revisions_endpoint.rb @@ -0,0 +1,15 @@ +require 'chef_zero/chef_data/data_normalizer' + +module ChefZero + module Endpoints + # /organizations/ORG/policies/NAME/revisions + class PolicyRevisionsEndpoint < RestBase + # POST /organizations/ORG/policies/NAME/revisions + def post(request) + policyfile_data = parse_json(request.body) + create_data(request, request.rest_path, policyfile_data["revision_id"], request.body, :create_dir) + return already_json_response(201, request.body) + end + end + end +end diff --git a/lib/chef_zero/rest_base.rb b/lib/chef_zero/rest_base.rb index b4f8605..928d2e7 100644 --- a/lib/chef_zero/rest_base.rb +++ b/lib/chef_zero/rest_base.rb @@ -264,6 +264,10 @@ module ChefZero end end + def hashify_list(list) + list.reduce({}) { |acc, obj| acc.merge( obj => {} ) } + end + def policy_name_invalid?(name) !name.is_a?(String) || name.size > 255 || diff --git a/lib/chef_zero/rspec.rb b/lib/chef_zero/rspec.rb index 2dd9a3a..8eb2f58 100644 --- a/lib/chef_zero/rspec.rb +++ b/lib/chef_zero/rspec.rb @@ -149,6 +149,14 @@ module ChefZero before(chef_server_options[:server_scope]) { org_member(*usernames) } end + def policy(name, data, &block) + before(chef_server_options[:server_scope]) { policy(name, data, &block) } + end + + def policy_group(name, data, &block) + before(chef_server_options[:server_scope]) { policy_group(name, data, &block) } + end + def role(name, data, &block) before(chef_server_options[:server_scope]) { role(name, data, &block) } end @@ -251,6 +259,20 @@ module ChefZero ChefZero::RSpec.server.load_data({ 'members' => usernames }, current_org) end + def policy(name, version, data, &block) + with_object_path("policies/#{name}") do + ChefZero::RSpec.server.load_data({ 'policies' => { name => { version => data } } }, current_org) + instance_eval(&block) if block_given? + end + end + + def policy_group(name, data, &block) + with_object_path("policy_groups/#{name}") do + ChefZero::RSpec.server.load_data({ 'policy_groups' => { name => data } }, current_org) + instance_eval(&block) if block_given? + end + end + def role(name, data, &block) with_object_path("roles/#{name}") do ChefZero::RSpec.server.load_data({ 'roles' => { name => data } }, current_org) diff --git a/lib/chef_zero/server.rb b/lib/chef_zero/server.rb index 1686301..2d84a52 100644 --- a/lib/chef_zero/server.rb +++ b/lib/chef_zero/server.rb @@ -69,11 +69,16 @@ require 'chef_zero/endpoints/organization_endpoint' require 'chef_zero/endpoints/organization_association_requests_endpoint' require 'chef_zero/endpoints/organization_association_request_endpoint' require 'chef_zero/endpoints/organization_authenticate_user_endpoint' -require 'chef_zero/endpoints/organization_policies_endpoint' -require 'chef_zero/endpoints/organization_policy_groups_endpoint' require 'chef_zero/endpoints/organization_users_endpoint' require 'chef_zero/endpoints/organization_user_endpoint' require 'chef_zero/endpoints/organization_validator_key_endpoint' +require 'chef_zero/endpoints/policies_endpoint' +require 'chef_zero/endpoints/policy_endpoint' +require 'chef_zero/endpoints/policy_revisions_endpoint' +require 'chef_zero/endpoints/policy_revision_endpoint' +require 'chef_zero/endpoints/policy_groups_endpoint' +require 'chef_zero/endpoints/policy_group_endpoint' +require 'chef_zero/endpoints/policy_group_policy_endpoint' require 'chef_zero/endpoints/principal_endpoint' require 'chef_zero/endpoints/role_endpoint' require 'chef_zero/endpoints/role_environments_endpoint' @@ -447,6 +452,24 @@ module ChefZero end end + if contents['policies'] + contents['policies'].each_pair do |policy_name, policy_struct| + # data_store.create_dir(['organizations', org_name, 'policies', policy_name], "revisions", :recursive) + dejsonize_children(policy_struct).each do |revision, policy_data| + data_store.set(['organizations', org_name, 'policies', policy_name, + "revisions", revision], policy_data, :create, :create_dir) + end + end + end + + if contents['policy_groups'] + contents['policy_groups'].each_pair do |group_name, group| + group['policies'].each do |policy_name, policy_revision| + data_store.set(['organizations', org_name, 'policy_groups', group_name, 'policies', policy_name], FFI_Yajl::Encoder.encode(policy_revision['revision_id'], :pretty => true), :create, :create_dir) + end + end + end + if contents['cookbooks'] contents['cookbooks'].each_pair do |name_version, cookbook| if name_version =~ /(.+)-(\d+\.\d+\.\d+)$/ @@ -549,13 +572,13 @@ module ChefZero [ "/organizations/*/nodes", NodesEndpoint.new(self) ], [ "/organizations/*/nodes/*", NodeEndpoint.new(self) ], [ "/organizations/*/nodes/*/_identifiers", NodeIdentifiersEndpoint.new(self) ], - [ "/organizations/*/policies", OrganizationPoliciesEndpoint.new(self) ], - [ "/organizations/*/policies/*", OrganizationPoliciesEndpoint.new(self) ], - [ "/organizations/*/policies/*/revisions", OrganizationPoliciesEndpoint.new(self) ], - [ "/organizations/*/policies/*/revisions/*", OrganizationPoliciesEndpoint.new(self) ], - [ "/organizations/*/policy_groups", OrganizationPolicyGroupsEndpoint.new(self) ], - [ "/organizations/*/policy_groups/*", OrganizationPolicyGroupsEndpoint.new(self) ], - [ "/organizations/*/policy_groups/*/policies/*", OrganizationPolicyGroupsEndpoint.new(self) ], + [ "/organizations/*/policies", PoliciesEndpoint.new(self) ], + [ "/organizations/*/policies/*", PolicyEndpoint.new(self) ], + [ "/organizations/*/policies/*/revisions", PolicyRevisionsEndpoint.new(self) ], + [ "/organizations/*/policies/*/revisions/*", PolicyRevisionEndpoint.new(self) ], + [ "/organizations/*/policy_groups", PolicyGroupsEndpoint.new(self) ], + [ "/organizations/*/policy_groups/*", PolicyGroupEndpoint.new(self) ], + [ "/organizations/*/policy_groups/*/policies/*", PolicyGroupPolicyEndpoint.new(self) ], [ "/organizations/*/principals/*", PrincipalEndpoint.new(self) ], [ "/organizations/*/roles", RestListEndpoint.new(self) ], [ "/organizations/*/roles/*", RoleEndpoint.new(self) ], diff --git a/spec/support/oc_pedant.rb b/spec/support/oc_pedant.rb index cc62dd4..6e39878 100644 --- a/spec/support/oc_pedant.rb +++ b/spec/support/oc_pedant.rb @@ -85,7 +85,6 @@ delete_org true # are using pre-existing users, you must supply a ':key_file' key, # which should be the fully-qualified path /on the machine Pedant is # running on/ to a private key for that user. -key = 'spec/support/stickywicket.pem' superuser_name 'pivotal' superuser_key key webui_key key |