<commit_before>%% -*- erlang-indent-level: 4;indent-tabs-mode: nil; fill-column: 92 -*-
%% ex: ts=4 sw=4 et
%% @author <NAME> <<EMAIL>>
%% Copyright 2014 Opscode, Inc. All Rights Reserved.

-module(oc_chef_wm_controls).

-include_lib("chef_wm/include/chef_wm.hrl").
-include_lib("oc_chef_wm.hrl").
-include_lib("eunit/include/eunit.hrl").

-mixin([{chef_wm_base, [
                        content_types_accepted/2,
                        content_types_provided/2,
                        finish_request/2,
                        malformed_request/2,
                        ping/2,
                        post_is_create/2,
                        {list_objects_json/2, to_json}
                       ]}]).

-mixin([{oc_chef_wm_base, [forbidden/2,
                           is_authorized/2,
                           service_available/2]}]).

-behaviour(chef_wm).

-export([
         auth_info/2,
         init/1,
         init_resource_state/1,
         malformed_request_message/3,
         request_type/0,
         validate_request/3
        ]).

-export([
         allowed_methods/2,
         create_path/2,
         from_json/2,
         resource_exists/2
        ]).

-define(CHEF_CONTROL_GROUP_MESSAGE_VERSION, <<"0.1.0">>).

-define(CONTROL_GROUP_SCHEMA,
        {[ {<<"control_group">>, object},
           {<<"node_name">>, string},
           {<<"run_id">>, string}
         ]}).

init(Config) ->
    chef_wm_base:init(?MODULE, Config).

init_resource_state(_Config) ->
    {ok, #control_state{}}.

request_type() ->
    "controls".

allowed_methods(Req, State) ->
    {['POST'], Req, State}.

validate_request('POST', Req, #base_state{resource_state = ControlState} = State) ->
    Body = wrq:req_body(Req),
    Data = chef_json:decode(Body),
    case ej:valid(?CONTROL_GROUP_SCHEMA, Data) of
        ok ->
            % message is valid, stash it in resource_state/control_data
            {Req, State#base_state{resource_state =
                                     ControlState#control_state{control_data = Data}}};
        #ej_invalid{} = Error ->
             lager:error(": ~p", [Error]),
             throw(Error)
    end.


auth_info(Req, State) ->
    auth_info(wrq:method(Req), Req, State).

auth_info('POST', Req, #base_state{resource_state = #control_state{control_data = ControlData},
                                   requestor=#chef_client{name = ClientName}} = State) ->
    % schema validates that node_name exist
    case ej:get({"node_name"}, ControlData) of
    ClientName ->
        {authorized, Req, State};
    _Else ->
        {{halt, 403}, Req, State}
    end;


auth_info('POST', Req, State) ->
    {{halt, 403}, Req, State}.

resource_exists(Req, State) ->
    {true, Req, State}.

create_path(Req, #base_state{resource_state = #control_state{} = ControlGroupState}
                                                            = State) ->
    %% create the ID and stash it in the state
    %% This path isn't very useful, as we don't provide a way to GET a
    %% control_group once it's posted
    Id = list_to_binary(uuid:uuid_to_string(uuid:get_v4())),
    ControlGroupState1 = ControlGroupState#control_state{control_group_id=Id},
    {binary_to_list(Id), Req, State#base_state{resource_state=ControlGroupState1}}.

from_json(Req, #base_state{resource_state = #control_state{control_data = ControlData,
                                                           control_group_id=Id}} = State) ->
    RoutingKey = routing_key(),
    Msg = construct_payload(ControlData, Id, Req, State),
    publish(RoutingKey, Msg),
    %% return an empty response so knife raw doesn't throw an exception
    Out = chef_json:encode(""),
    Req2 = wrq:set_resp_body(Out, Req),
    {true, Req2, State}.

-spec construct_payload(FullControlGroupPayload :: [{binary(), binary()},...],
                        Id   :: binary(),
                        Req :: wm_req(),
                        State :: #base_state{}) -> binary().
construct_payload(FullControlGroupPayload,
                  Id,
                  Req, #base_state{reqid = RequestId,
                                   organization_name = OrgName,
                                   requestor = _Requestor}
                                   ) ->
    Msg = {[{<<"message_type">>, <<"control_group">>},
            {<<"message_version">>, ?CHEF_CONTROL_GROUP_MESSAGE_VERSION},
            {<<"organization_name">>, OrgName},
            {<<"chef_server_fqdn">>, hostname()},
            {<<"recorded_at">>, req_header("x-ops-timestamp", Req)},
            {<<"remote_hostname">>, req_header("x-forwarded-for", Req)},
            {<<"request_id">>, RequestId},
            {<<"id">>, Id},
            {<<"control_group">>, ej:get({<<"control_group">>}, FullControlGroupPayload)}
           ]},
    Msg1 = maybe_add_remote_request_id(Msg, req_header("x-remote-request-id", Req)),
    iolist_to_binary(chef_json:encode(Msg1)).


maybe_add_remote_request_id(Msg, undefined) ->
    Msg;
maybe_add_remote_request_id(Msg, RemoteRequestId) ->
    ej:set({<<"remote_request_id">>}, Msg, RemoteRequestId).

-spec hostname() -> binary().<commit_msg>cleanup control_groups message<commit_after>%% -*- erlang-indent-level: 4;indent-tabs-mode: nil; fill-column: 92 -*-
%% ex: ts=4 sw=4 et
%% @author <NAME> <<EMAIL>>
%% Copyright 2014 Opscode, Inc. All Rights Reserved.

-module(oc_chef_wm_controls).

-include_lib("chef_wm/include/chef_wm.hrl").
-include_lib("oc_chef_wm.hrl").
-include_lib("eunit/include/eunit.hrl").

-mixin([{chef_wm_base, [
                        content_types_accepted/2,
                        content_types_provided/2,
                        finish_request/2,
                        malformed_request/2,
                        ping/2,
                        post_is_create/2,
                        {list_objects_json/2, to_json}
                       ]}]).

-mixin([{oc_chef_wm_base, [forbidden/2,
                           is_authorized/2,
                           service_available/2]}]).

-behaviour(chef_wm).

-export([
         auth_info/2,
         init/1,
         init_resource_state/1,
         malformed_request_message/3,
         request_type/0,
         validate_request/3
        ]).

-export([
         allowed_methods/2,
         create_path/2,
         from_json/2,
         resource_exists/2
        ]).

-define(CHEF_CONTROL_GROUP_MESSAGE_VERSION, <<"0.1.0">>).

-define(CONTROL_GROUP_SCHEMA,
        {[ {<<"control_groups">>, array},
           {<<"node_name">>, string},
           {<<"run_id">>, string}
         ]}).

init(Config) ->
    chef_wm_base:init(?MODULE, Config).

init_resource_state(_Config) ->
    {ok, #control_state{}}.

request_type() ->
    "controls".

allowed_methods(Req, State) ->
    {['POST'], Req, State}.

validate_request('POST', Req, #base_state{resource_state = ControlState} = State) ->
    Body = wrq:req_body(Req),
    Data = chef_json:decode(Body),
    case ej:valid(?CONTROL_GROUP_SCHEMA, Data) of
        ok ->
            % message is valid, stash it in resource_state/control_data
            {Req, State#base_state{resource_state =
                                     ControlState#control_state{control_data = Data}}};
        #ej_invalid{} = Error ->
             lager:error(": ~p", [Error]),
             throw(Error)
    end.


auth_info(Req, State) ->
    auth_info(wrq:method(Req), Req, State).

auth_info('POST', Req, #base_state{resource_state = #control_state{control_data = ControlData},
                                   requestor=#chef_client{name = ClientName}} = State) ->
    % schema validates that node_name exist
    case ej:get({"node_name"}, ControlData) of
    ClientName ->
        {authorized, Req, State};
    _Else ->
        {{halt, 403}, Req, State}
    end;


auth_info('POST', Req, State) ->
    {{halt, 403}, Req, State}.

resource_exists(Req, State) ->
    {true, Req, State}.

create_path(Req, #base_state{resource_state = #control_state{} = ControlGroupState}
                                                            = State) ->
    %% create the ID and stash it in the state
    %% This path isn't very useful, as we don't provide a way to GET a
    %% control_group once it's posted
    Id = list_to_binary(uuid:uuid_to_string(uuid:get_v4())),
    ControlGroupState1 = ControlGroupState#control_state{control_group_id=Id},
    {binary_to_list(Id), Req, State#base_state{resource_state=ControlGroupState1}}.

from_json(Req, #base_state{resource_state = #control_state{control_data = ControlData,
                                                           control_group_id=Id}} = State) ->
    RoutingKey = routing_key(),
    Msg = construct_payload(ControlData, Id, Req, State),
    publish(RoutingKey, Msg),
    %% return an empty response so knife raw doesn't throw an exception
    Out = chef_json:encode(""),
    Req2 = wrq:set_resp_body(Out, Req),
    {true, Req2, State}.

-spec construct_payload(FullControlGroupPayload :: [{binary(), binary()},...],
                        Id   :: binary(),
                        Req :: wm_req(),
                        State :: #base_state{}) -> binary().
construct_payload(FullControlGroupPayload,
                  Id,
                  Req, #base_state{reqid = RequestId,
                                   organization_name = OrgName,
                                   requestor = _Requestor}
                                   ) ->
    Msg = {[{<<"message_type">>, <<"control_groups">>},
            {<<"message_version">>, ?CHEF_CONTROL_GROUP_MESSAGE_VERSION},
            {<<"organization_name">>, OrgName},
            {<<"chef_server_fqdn">>, hostname()},
            {<<"recorded_at">>, req_header("x-ops-timestamp", Req)},
            {<<"remote_hostname">>, req_header("x-forwarded-for", Req)},
            {<<"request_id">>, RequestId},
            {<<"node_name">>, ej:get({<<"node_name">>}, FullControlGroupPayload)},
            {<<"id">>, Id},
            {<<"run_id">>, ej:get({<<"run_id">>}, FullControlGroupPayload)},
            {<<"control_groups">>, ej:get({<<"control_groups">>}, FullControlGroupPayload)}
           ]},
    Msg1 = maybe_add_remote_request_id(Msg, req_header("x-remote-request-id", Req)),
    iolist_to_binary(chef_json:encode(Msg1)).

maybe_add_remote_request_id(Msg, undefined) ->
    Msg;
maybe_add_remote_request_id(Msg, RemoteRequestId) ->
    ej:set({<<"remote_request_id">>}, Msg, RemoteRequestId).

-spec hostname() -> binary().
{'fraction_non_alphanumeric': 0.14429433745329118, 'fraction_numerical': 0.00862316757688991, 'mean_word_length': 2.2196175200493524, 'pattern_counts': {'":': 2, '<': 81, '<?xml version=': 0, '>': 121, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 18, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7220124', 'n_tokens_mistral': 3344, 'n_tokens_neox': 3212, 'n_words': 565}