Skip to content

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
...
  • 16 commits
  • 34 files changed
  • 0 commit comments
  • 3 contributors
Commits on Aug 15, 2012
@galthaus galthaus Merge pull request #144 from cloudedge/pull-req-release-rails3anddb-m…
…aster-6ec9f23c53

Updates to Unit tests and docs [3/7]
bfc2a67
@clement-jim clement-jim Added user nav texts 150b806
@clement-jim clement-jim Added navs for User menu 4b63b3b
@clement-jim clement-jim Added routes to manage users and logout. 50339b3
@galthaus galthaus Merge pull request #146 from clement-jim/pull-req-release-rails3anddb…
…-master-50339b399f

Added User navs. [1/2]
5a54472
Commits on Aug 16, 2012
@galthaus galthaus Add SuSE fix for dequeue proposal into the development tree. 49b66b8
Rob Hirschfeld Merge pull request #147 from galthaus/pull-req-release-rails3anddb-ma…
…ster-49b66b815f

Add SUSE fix for dequeue to the tree [1/5]
1fa4780
Commits on Aug 17, 2012
@clement-jim clement-jim Added labels for user management page 4758074
@clement-jim clement-jim Added routes for user logoff and manage user page. 72fc612
@clement-jim clement-jim Changed Logoff to Sign Out to be consistent with Sign In 0ef16a8
@clement-jim clement-jim Controller for User object d25970a
@clement-jim clement-jim Rough out for Manage Users page. 15653d8
@galthaus galthaus Merge pull request #148 from clement-jim/pull-req-release-rails3anddb…
…-master-15653d8a8d

Continuing work on adding users and user management." [1/2]
67bf63d
Commits on Aug 18, 2012
Rob Hirschfeld get setup & teardown working for nodes feature test. tweak to models …
…to prevent mixcase name collisions. DOCSgit status
17ede45
Rob Hirschfeld Merge remote-tracking branch 'origin/release/rails3anddb/master' into…
… release/rails3anddb/master
19c27a5
Commits on Aug 19, 2012
Rob Hirschfeld refactor webrat to use html module instead of bdd_utils bcbbb94
Showing with 530 additions and 197 deletions.
  1. +1 −1 bin/barclamp_lib.rb
  2. +1 −0 crowbar_framework/BDD/Win7.bat
  3. +3 −1 crowbar_framework/BDD/bdd.erl
  4. +13 −28 crowbar_framework/BDD/bdd_utils.erl
  5. +17 −25 crowbar_framework/BDD/bdd_webrat.erl
  6. +5 −0 crowbar_framework/BDD/digest_auth.erl
  7. +7 −0 crowbar_framework/BDD/documentation.feature
  8. +163 −0 crowbar_framework/BDD/html.erl
  9. +18 −21 crowbar_framework/BDD/nodes.erl
  10. +2 −5 crowbar_framework/BDD/nodes.feature
  11. +6 −6 crowbar_framework/BDD/scaffolds.feature
  12. +20 −10 crowbar_framework/app/controllers/barclamp_controller.rb
  13. +6 −0 crowbar_framework/app/controllers/nodes_controller.rb
  14. +12 −0 crowbar_framework/app/controllers/users_controller.rb
  15. +1 −1 crowbar_framework/app/models/barclamp.rb
  16. +1 −1 crowbar_framework/app/models/cmdb.rb
  17. +1 −1 crowbar_framework/app/models/doc.rb
  18. +1 −1 crowbar_framework/app/models/group.rb
  19. +1 −1 crowbar_framework/app/models/nav.rb
  20. +3 −2 crowbar_framework/app/models/node.rb
  21. +1 −1 crowbar_framework/app/models/os.rb
  22. +2 −2 crowbar_framework/app/models/service_object.rb
  23. +1 −1 crowbar_framework/app/models/user.rb
  24. +23 −0 crowbar_framework/app/views/users/index.html.haml
  25. +14 −0 crowbar_framework/config/locales/crowbar/en.yml
  26. +24 −8 crowbar_framework/config/routes.rb
  27. +6 −1 crowbar_framework/db/migrate/20120723233100_create_navs.rb
  28. +7 −2 crowbar_framework/doc/crowbar.yml
  29. +23 −14 crowbar_framework/doc/default/crowbar/devguide/api.md
  30. +1 −35 crowbar_framework/doc/default/crowbar/devguide/api/barclamp.md
  31. +12 −24 crowbar_framework/doc/default/crowbar/devguide/api/node.md
  32. +42 −0 crowbar_framework/doc/default/crowbar/devguide/api/status.md
  33. +84 −5 crowbar_framework/doc/default/crowbar/devguide/testing.md
  34. +8 −0 crowbar_framework/test/unit/node_model_test.rb
View
2 bin/barclamp_lib.rb
@@ -335,7 +335,7 @@ def proposal_commit(name)
def proposal_dequeue(name)
usage -1 if name.nil? or name == ""
- struct = post_json("/proposals/dequeue/#{name}", @data)
+ struct = post_delete("/proposals/dequeue/#{name}")
if struct[1] == 200
[ "Dequeued #{name}", 0 ]
View
1 crowbar_framework/BDD/Win7.bat
@@ -5,6 +5,7 @@ del *.beam
REM Compile
erlc json.erl
erlc digest_auth.erl
+erlc html.erl
erlc bdd_utils.erl
erlc sc.erl
erlc bdd_webrat.erl
View
4 crowbar_framework/BDD/bdd.erl
@@ -66,7 +66,8 @@ feature(ConfigName, FeatureName) ->
step_run(Config, [], {step_teardown, 0, []}, [list_to_atom(ConfigName)]), %teardown
application:stop(crypto),
application:stop(inets).
-
+
+% load the configuration file
getconfig(ConfigName) ->
{ok, ConfigBase} = file:consult(ConfigName++".config"),
[{config, ConfigName} | ConfigBase].
@@ -91,6 +92,7 @@ setup_scenario(Config, Scenario, Tests) ->
true -> io:format("\tSKIPPED ~p~n", [Name])
end.
+% output results information
print_fail([]) -> true;
print_fail({Pass, {_Type, N, Description}}) ->
PF = case Pass of
View
41 crowbar_framework/BDD/bdd_utils.erl
@@ -15,7 +15,7 @@
% Author: RobHirschfeld
%
-module(bdd_utils).
--export([assert/1, assert/2, assert_atoms/1, config/2, tokenize/1, clean_line/1, strip_doctype/1, uri/2]).
+-export([assert/1, assert/2, assert_atoms/1, config/2, tokenize/1, clean_line/1, uri/2]).
-export([http_get/2, http_get/3, html_peek/2, html_search/2, html_search/3]).
-export([html_find_button/2, html_find_link/2, html_find_block/4]).
-export([debug/3, debug/2, debug/1, trace/6]).
@@ -58,39 +58,19 @@ trace(Config, Name, N, Steps, Given, When) ->
file:close(S).
html_search(Match, Results, Test) ->
- F = fun(X) -> case {X, Test} of
- {true, true} -> true;
- {true, false} -> false;
- {_, false} -> true;
- {_, true} -> false end end,
- lists:any(F, ([html_peek(Match,Result) || Result <- Results, Result =/= [no_op]])).
+ debug(true, "REMAP bdd_utils html_search!"),
+ html:search(Match, Results, Test).
+
html_search(Match, Results) ->
html_search(Match, Results, true).
-strip_doctype(Input) ->
- RegEx = "<!DOCTYPE(.*)>",
- {ok, RE} = re:compile(RegEx),
- case re:run(Input, RE) of
- {match, S} -> [{Start, Length} | _] = S, % we only want the 1st expression!
- string:substr(Input, Start+1+Length);
- _ -> Input
- end.
-
+
html_peek(Match, Input) ->
- RegEx = "<(html|HTML)(.*)"++Match++"(.*)</(html|HTML)>",
- {ok, RE} = re:compile(RegEx, [caseless, multiline, dotall, {newline , anycrlf}]),
- bdd_utils:debug("html_peek compile: ~p on ~p~n", [RegEx, Input]),
- Result = re:run(Input, RE),
- bdd_utils:debug("html_peek match: ~p~n", [Result]),
- %{ match, [ {_St, _Ln} | _ ] } = Result,
- %bdd_utils:debug("html_peek substr: ~p~n", [string:substr(Input, _St, _Ln)]),
- case Result of
- {match, _} -> true;
- _ -> Result
- end.
-
+ debug(true, "REMAP bdd_utils html_peek!"),
+ html:peek(Match, Input).
html_find_button(Match, Input) ->
+ debug(true, "REMAP bdd_utils html_find_button!"),
%<form.. <input class="button" name="submit" type="submit" value="Save"></form>
%debug(puts,Match),
Form = html_find_block("<form ", "</form>", Input, "value='"++Match++"'"),
@@ -105,6 +85,7 @@ html_find_button(Match, Input) ->
% return the HREF part of an anchor tag given the content of the link
html_find_link(Match, Input) ->
+ debug(true, "REMAP bdd_utils html_find_link!"),
RegEx = "(\\<(a|A)\\b(/?[^\\>]+)\\>"++Match++"\\<\\/(a|A)\\>)",
RE = case re:compile(RegEx, [multiline, dotall, {newline , anycrlf}]) of
{ok, R} -> R;
@@ -126,6 +107,7 @@ html_find_link(Match, Input) ->
% we allow for a of open tags (nesting) but only the inner close is needed
html_find_block(OpenTag, CloseTag, Input, Match) ->
+ debug(true, "REMAP bdd_utils html_find_block!"),
{ok, RE} = re:compile([Match]),
CandidatesNotTested = re:split(Input, OpenTag, [{return, list}]),
Candidates = [ html_find_block_helper(C, RE) || C <- CandidatesNotTested ],
@@ -160,6 +142,7 @@ http_get(Config, Page, not_found) ->
http_get(Config, Page, ok) ->
http_get(Config, uri(Config,Page), 200, "OK(.*)").
http_get(Config, URL, ReturnCode, StateRegEx) ->
+ debug(true, "REMAP bdd_utils html_get!"),
{ok, {{"HTTP/1.1",ReturnCode,State}, _Head, Body}} = digest_auth:request(Config, URL),
{ok, StateMP} = re:compile(StateRegEx),
%bdd_utils:debug(true, "hppt_get has: URL ~p = ~s~n", [URL, Body]),
@@ -171,6 +154,7 @@ http_get(Config, URL, ReturnCode, StateRegEx) ->
http_post_params(ParamsIn) -> http_post_params(ParamsIn, []).
http_post_params([], Params) -> Params;
http_post_params([{K, V} | P], ParamsOrig) ->
+ debug(true, "REMAP bdd_utils html_post_params!"),
ParamsAdd = case ParamsOrig of
[] -> "?"++K++"="++V;
_ -> "&"++K++"="++V
@@ -178,6 +162,7 @@ http_post_params([{K, V} | P], ParamsOrig) ->
http_post_params(P, ParamsOrig++ParamsAdd).
http_post(Config, URL, Parameters, ReturnCode, StateRegEx) ->
+ debug(true, "REMAP bdd_utils html_post!"),
Post = URL ++ http_post_params(Parameters),
{ok, {{"HTTP/1.1",ReturnCode, State}, _Head, Body}} = digest_auth:request(Config, post, {Post, "application/json", "application/json", "body"}, [{timeout, 10000}], []),
{ok, StateMP} = re:compile(StateRegEx),
View
42 crowbar_framework/BDD/bdd_webrat.erl
@@ -16,69 +16,61 @@
%
-module(bdd_webrat).
-export([step/3]).
--import(bdd_util).
% helper routine
click_link(Config, URL, Link) ->
- _Debug = false,
- bdd_utils:debug(_Debug, "URL ~p~n",[URL]),
Result = case URL of
[] -> io:format("CANNOT FIND LINK ~s~n", [Link]), error;
- _ -> bdd_utils:http_get(Config, URL, ok)
+ _ -> http:get(Config, URL, ok)
end,
- bdd_utils:debug(_Debug, "when I click result ~p~n", [Result]),
Result.
step(_Config, _Global, {step_given, _N, ["I am on the home page"]}) ->
- bdd_utils:debug("start from home.~n"),
- bdd_utils:http_get(_Config, []);
+ http:get(_Config, []);
step(_Config, _Global, {step_given, _N, ["I am on the", Page, "page"]}) ->
step(_Config, _Global, {step_given, _N, ["I went to the", Page, "page"]});
step(_Config, _Global, {step_given, _N, ["I went to the", Page, "page"]}) ->
- bdd_utils:http_get(_Config, Page);
+ http:get(_Config, Page);
step(_Config, _Given, {step_when, _N, ["I go to the home page"]}) ->
- bdd_utils:debug("go home.~n"),
- bdd_utils:http_get(_Config, []);
+ http:get(_Config, []);
step(_Config, _Given, {step_when, _N, ["I go to the", Page, "page"]}) ->
- bdd_utils:debug("go to the ~p page~n", [Page]),
- bdd_utils:http_get(_Config, Page);
+ http:get(_Config, Page);
step(_Config, _Given, {step_when, _N, ["I try to go to the", Page, "page"]}) ->
- %bdd_utils:debug("expect FAIL when going to the ~p page~n", [Page]),
- bdd_utils:http_get(_Config, Page, not_found);
+ http:get(_Config, Page, not_found);
-step(_Config, _Given, {step_when, _N, ["AJAX requests the",Page,"page"]}) ->
- JSON = bdd_utils:http_get(_Config, Page),
+step(Config, _Given, {step_when, _N, ["AJAX requests the",Page,"page"]}) ->
+ JSON = http:get(Config, Page),
{ajax, json:parse(JSON), Page};
step(Config, Given, {step_when, _N, ["I click on the",Link,"link"]}) ->
- [URL | _] = [bdd_utils:html_find_link(Link, HTML) || HTML <- (Given), HTML =/= []],
+ [URL | _] = [html:find_link(Link, HTML) || HTML <- (Given), HTML =/= []],
click_link(Config, URL, Link);
step(Config, Given, {step_when, _N, ["I click on the", Menu, "menu item"]}) ->
- [Block] = bdd_utils:html_find_block("<li", "</li>", Given, ">"++Menu++"</a>"),
- URL = bdd_utils:html_find_link(Menu, Block),
+ [Block] = html:find_block("<li", "</li>", Given, ">"++Menu++"</a>"),
+ URL = html:find_link(Menu, Block),
click_link(Config, URL, Menu);
step(_Config, _Result, {step_then, _N, ["I should not see", Text]}) ->
bdd_utils:debug("step_then result ~p should NOT have ~p on the page~n", [_Result, Text]),
- bdd_utils:html_search(Text,_Result, false);
+ html:search(Text,_Result, false);
step(_Config, _Result, {step_then, _N, ["I should see", Text]}) ->
bdd_utils:debug("step_then result ~p should have ~p on the page~n", [_Result, Text]),
- bdd_utils:html_search(Text,_Result);
+ html:search(Text,_Result);
step(Config, _Result, {step_then, _N, ["there should be no translation errors"]}) ->
TransError = bdd_utils:config(Config, translation_error),
- bdd_utils:html_search(TransError,_Result, false);
+ html:search(TransError,_Result, false);
step(_Config, Result, {step_then, _N, ["I should see a link to", Link]}) ->
bdd_utils:assert([
- try bdd_utils:html_find_link(Link,R) of
+ try html:find_link(Link,R) of
_ -> true
catch
error: E -> io:format("FAIL: Did not find ~p in page (Error: ~p)~n", [Link, E]), false
@@ -87,7 +79,7 @@ step(_Config, Result, {step_then, _N, ["I should see a link to", Link]}) ->
);
step(_Config, Result, {step_then, _N, ["I should see a button with", Button]}) ->
- try bdd_utils:html_find_button(Button,Result) of
+ try html:find_button(Button,Result) of
_ -> true
catch
error: E -> io:format("FAIL: Did not find ~p in page (Error: ~p)~n", [Button, E]), false
@@ -144,7 +136,7 @@ step(_Config, Results, {step_then, _N, ["key",Key, "should be an empty string"]}
"" =:= json:value(JSON, Key);
step(_Config, _Result, {step_then, _N, ["I should see a menu for", Menu]}) ->
- bdd_utils:assert([bdd_utils:html_find_block("<li", "</li>", R, Menu) =/= [] || R <- _Result]);
+ bdd_utils:assert([html:find_block("<li", "</li>", R, Menu) =/= [] || R <- _Result]);
step(_Config, _Result, {_Type, _N, ["END OF WEBRAT"]}) ->
false.
View
5 crowbar_framework/BDD/digest_auth.erl
@@ -54,6 +54,9 @@ request(Config, URL) ->
request(Config, get, {URL, Header}, HTTPOptions, Options) ->
request(Config, get, {URL, Header, [], []}, HTTPOptions, Options);
+
+request(Config, delete, {URL}, HTTPOptions, Options) ->
+ request(Config, delete, {URL, [], [], []}, HTTPOptions, Options);
request(Config, Method, {URL, Header, Type, Body}, HTTPOptions, Options) ->
% prepare information that's common
@@ -72,6 +75,7 @@ request(Config, Method, {URL, Header, Type, Body}, HTTPOptions, Options) ->
% try request
{Status,{{Protocol,Code,Comment}, Fields, Message}} = case Method of
get -> http:request(Method, {URL, TrialHeader}, HTTPOptions, Options);
+ delete -> http:request(Method, {URL, TrialHeader}, HTTPOptions, Options);
_ -> http:request(Method, {URL, TrialHeader, Type, Body}, HTTPOptions, Options)
end,
% if 401, then get the auth info and retry (to save this, use the header/2 method to save the fields)
@@ -82,6 +86,7 @@ request(Config, Method, {URL, Header, Type, Body}, HTTPOptions, Options) ->
HeaderDigested = Header ++ [{"Authorization", AuthHeader}],
case Method of
get -> http:request(Method, {URL, HeaderDigested}, HTTPOptions, Options);
+ delete -> http:request(Method, {URL, HeaderDigested}, HTTPOptions, Options);
_ -> http:request(Method, {URL, HeaderDigested, Type, Body}, HTTPOptions, Options)
end;
_ -> {Status,{{Protocol,Code,Comment}, Fields, Message}}
View
7 crowbar_framework/BDD/documentation.feature
@@ -36,3 +36,10 @@ Feature: Documentation
When I click on the "Master Index" link
Then I should see "System Documentation"
And there should be no translation errors
+
+ Scenario: Doc Export
+ Given I am on the "docs/topic/crowbar+book-userguide" page
+ When I click on the "Export" link
+ Then I should see "Crowbar User Guide"
+ And I should see a link to "< Back"
+ And there should be no translation errors
View
163 crowbar_framework/BDD/html.erl
@@ -0,0 +1,163 @@
+% Copyright 2012, Dell
+%
+% Licensed under the Apache License, Version 2.0 (the "License");
+% you may not use this file except in compliance with the License.
+% You may obtain a copy of the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS,
+% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+% See the License for the specific language governing permissions and
+% limitations under the License.
+%
+% Author: RobHirschfeld
+%
+-module(html).
+-export([post/3, delete/3, post_params/1, post/5]).
+-export([get/2, get/3, peek/2, search/2, search/3]).
+-export([find_button/2, find_link/2, find_block/4]).
+
+search(Match, Results, Test) ->
+ F = fun(X) -> case {X, Test} of
+ {true, true} -> true;
+ {true, false} -> false;
+ {_, false} -> true;
+ {_, true} -> false end end,
+ lists:any(F, ([peek(Match,Result) || Result <- Results, Result =/= [no_op]])).
+search(Match, Results) ->
+ search(Match, Results, true).
+
+peek(Match, Input) ->
+ RegEx = "<(html|HTML)(.*)"++Match++"(.*)</(html|HTML)>",
+ {ok, RE} = re:compile(RegEx, [caseless, multiline, dotall, {newline , anycrlf}]),
+ bdd_utils:debug("html:peek compile: ~p on ~p~n", [RegEx, Input]),
+ Result = re:run(Input, RE),
+ bdd_utils:debug("html:peek match: ~p~n", [Result]),
+ %{ match, [ {_St, _Ln} | _ ] } = Result,
+ %bdd_utils:debug("html_peek substr: ~p~n", [string:substr(Input, _St, _Ln)]),
+ case Result of
+ {match, _} -> true;
+ _ -> Result
+ end.
+
+
+find_button(Match, Input) ->
+ %<form.. <input class="button" name="submit" type="submit" value="Save"></form>
+ %debug(puts,Match),
+ Form = find_block("<form ", "</form>", Input, "value='"++Match++"'"),
+ %debug(puts,Form),
+ Button = find_block("<input ", ">", Form, "value='"++Match++"'"),
+ %debug(puts,Button),
+ {ok, RegEx} = re:compile("type='submit'"),
+ case re:run(Button, RegEx) of
+ {match, _} -> Button;
+ _ -> io:format("ERROR: Could not find button with value '~p'. HTML could have other components encoded in a tag~n", [Match]), throw("could not find_button")
+ end.
+
+% return the HREF part of an anchor tag given the content of the link
+find_link(Match, Input) ->
+ RegEx = "(\\<(a|A)\\b(/?[^\\>]+)\\>"++Match++"\\<\\/(a|A)\\>)",
+ RE = case re:compile(RegEx, [multiline, dotall, {newline , anycrlf}]) of
+ {ok, R} -> R;
+ Error -> io:format("ERROR: Could not parse regex: '~p'.", [Error])
+ end,
+ AnchorTag = case re:run(Input, RE) of
+ {match, [{AStart, ALength} | _]} -> string:substr(Input, AStart+1,AStart+ALength);
+ {_, _} -> io:format("ERROR: Could not find Anchor tags enclosing '~p'. HTML could have other components encoded in a tag~n", [Match]), throw("could not find_link")
+ end,
+ {ok, HrefREX} = re:compile("\\bhref=(['\"])([^\\s]+?)(\\1)", [multiline, dotall, {newline , anycrlf}]),
+ Href = case re:run(AnchorTag, HrefREX) of
+ {match, [_1, _2, {HStart, HLength} | _]} -> string:substr(AnchorTag, HStart+1,HLength);
+ {_, _} -> io:format("ERROR: Could not find href= information in substring '~p'~n", [AnchorTag]), throw("could not html_find_link")
+ end,
+ bdd_utils:debug("find_link anchor ~p~n", [AnchorTag]),
+ %bdd_utils:debug(, "html_find_link href regex~p~n", [re:run(AnchorTag, HrefREX)]),
+ bdd_utils:debug("find_link found path ~p~n", [Href]),
+ Href.
+
+% we allow for a of open tags (nesting) but only the inner close is needed
+find_block(OpenTag, CloseTag, Input, Match) ->
+ {ok, RE} = re:compile([Match]),
+ CandidatesNotTested = re:split(Input, OpenTag, [{return, list}]),
+ Candidates = [ find_block_helper(C, RE) || C <- CandidatesNotTested ],
+ Block = case [ C || C <- Candidates, C =/= false ] of
+ [B] -> B;
+ [B, _] -> B;
+ _ -> []
+ end,
+ [Inside | _ ] = re:split(Block, CloseTag, [{parts, 2}, {return, list}]),
+ [Inside].
+
+find_block_helper(Test, RE) ->
+ case re:run(Test, RE) of
+ {match, _} -> Test;
+ _ -> false
+ end.
+
+uri(Config, Path) ->
+ {url, Base} = lists:keyfind(url,1,Config),
+ case {string:right(Base,1),string:left(Path,1)} of
+ {"/", "/"}-> Base ++ string:substr(Path,2);
+ {_, "/"} -> Base ++ Path;
+ {"/", _} -> Base ++ Path;
+ {_, _} -> Base ++ "/" ++ Path
+ end.
+
+% get a page from a server
+get(Config, Page) ->
+ get(Config, Page, ok).
+get(Config, Page, not_found) ->
+ get(uri(Config,Page), 404, "Not Found");
+get(Config, Page, ok) ->
+ get(Config, uri(Config,Page), 200, "OK(.*)").
+get(Config, URL, ReturnCode, StateRegEx) ->
+ {ok, {{"HTTP/1.1",ReturnCode,State}, _Head, Body}} = digest_auth:request(Config, URL),
+ {ok, StateMP} = re:compile(StateRegEx),
+ %bdd_utils:debug(true, "hppt_get has: URL ~p = ~s~n", [URL, Body]),
+ case re:run(State, StateMP) of
+ {match, _} -> Body;
+ _ -> "ERROR, return of " ++ URL ++ " result was not 200 OK"
+ end.
+
+post_params(ParamsIn) -> post_params(ParamsIn, []).
+post_params([], Params) -> Params;
+post_params([{K, V} | P], ParamsOrig) ->
+ ParamsAdd = case ParamsOrig of
+ [] -> "?"++K++"="++V;
+ _ -> "&"++K++"="++V
+ end,
+ post_params(P, ParamsOrig++ParamsAdd).
+
+% Post using Parameters to convey the values
+post(Config, URL, Parameters, ReturnCode, StateRegEx) ->
+ Post = URL ++ post_params(Parameters),
+ {ok, {{"HTTP/1.1",ReturnCode, State}, _Head, Body}} = digest_auth:request(Config, post, {Post, "application/json", "application/json", "body"}, [{timeout, 10000}], []),
+ {ok, StateMP} = re:compile(StateRegEx),
+ case re:run(State, StateMP) of
+ {match, _} -> Body;
+ _ -> "ERROR, return of " ++ URL ++ " result was not 200 OK"
+ end.
+
+% Post using JSON to convey the values
+post(Config, Path, JSON) ->
+ URL = bdd_utils:uri(Config, Path),
+ R = digest_auth:request(Config, post, {URL, [], "application/json", JSON}, [{timeout, 10000}], []),
+ {ok, {{"HTTP/1.1",_ReturnCode, State}, _Head, Body}} = R,
+ {ok, StateMP} = re:compile("OK"),
+ case re:run(State, StateMP) of
+ {match, _} -> json:parse(Body);
+ _ -> "ERROR, return of " ++ URL ++ " result was not 200 OK. " ++ Body
+ end.
+
+delete(Config, Path, Id) ->
+ URL = bdd_utils:uri(Config, Path) ++ "/" ++ Id,
+ R = digest_auth:request(Config, delete, {URL}, [{timeout, 10000}], []),
+ {ok, {{"HTTP/1.1",_ReturnCode, State}, _Head, Body}} = R,
+ {ok, StateMP} = re:compile("OK"),
+ case re:run(State, StateMP) of
+ {match, _} -> true;
+ _ -> "ERROR, return of " ++ URL ++ " result was not 200 OK. " ++ Body
+ end.
+
View
39 crowbar_framework/BDD/nodes.erl
@@ -14,32 +14,29 @@
%
%
-module(nodes).
--export([step/3, http_post/3]).
--import(bdd_utils).
+-export([step/3, node_json/3]).
node_json(Name, Description, Order) ->
json:output([{"name",Name},{"description", Description}, {"order", Order}]).
-
-http_post(Config, Path, JSON) ->
- URL = bdd_utils:uri(Config, Path),
- R = digest_auth:request(Config, post, {URL, [], "application/json", JSON}, [{timeout, 10000}], []),
- {ok, {{"HTTP/1.1",_ReturnCode, State}, _Head, Body}} = R,
- {ok, StateMP} = re:compile("OK"),
- case re:run(State, StateMP) of
- {match, _} -> json:parse(Body);
- _ -> "ERROR, return of " ++ URL ++ " result was not 200 OK. " ++ Body
- end.
step(Config, _Global, {step_setup, _N, _}) ->
- Path = "node/2.0/new",
- Node = node_json("BDD1.example.com", "Testing Only", 100),
- Result = http_post(Config, Path, Node),
+ Path = "node/2.0",
+ Node1 = "bdd1.example.com",
+ % just in case, cleanup first (ignore result)
+ http:delete(Config, Path, Node1),
+ % create node(s) for tests
+ Node = node_json(Node1, "BDD Testing Only - should be automatically removed", 100),
+ Result = http:post(Config, Path, Node),
{"id", Key} = lists:keyfind("id",1,Result),
- io:format("\tCreated Node BDD1 (id=~p) for testing.~n", [Key]),
- {"node1", Key};
+ io:format("\tCreated Node ~p (id=~p) for testing.~n", [Node1, Key]),
+ [{node1, Key} | Config];
step(Config, _Global, {step_teardown, _N, _}) ->
- io:format("\tNo Nodes Tear Down Step.~n"),
- Config;
-
-step(_Config, _Result, {step_then, _N, ["I should use my special step file"]}) -> true.
+ Path = "node/2.0",
+ % find the node from setup and remove it
+ Node = lists:keyfind(node1, 1, Config),
+ {node1, Key} = Node,
+ http:delete(Config, Path, Key),
+ io:format("\tRemoved Node ID ~p for Tear Down Step.~n", [Key]),
+ lists:delete(Node, Config).
+
View
7 crowbar_framework/BDD/nodes.feature
@@ -3,11 +3,8 @@ Feature: Nodes
The system operator, Oscar
wants to be able to check the status of nodes
- Scenario: REST Node Create
- Then I should use my special step file
-
Scenario: Status Empty
- When AJAX requests the "node/status/2.0" page
+ When AJAX requests the "status/2.0/node" page
Then key "sum" should be a number
And there should be a key "state"
And there should be a key "status"
@@ -17,7 +14,7 @@ Feature: Nodes
And key "[groups][0]" should contain "7" items
Scenario: Status Non Nodes
- When AJAX requests the "node/status/2.0/0" page
+ When AJAX requests the "status/2.0/node/0" page
Then key "sum" should be a number
And there should be a key "state"
And there should be a key "status"
View
12 crowbar_framework/BDD/scaffolds.feature
@@ -2,6 +2,10 @@ Feature: Scaffolds
In order develop the system
The devoper operator, Greg
wants to be able to quickly check the models
+
+ TODO: proposal_queue_item
+ TODO: proposal_queue
+ TODO: role_element_order
Scenario: Barclamp
When I go to the "scaffolds/barclamps" page
@@ -9,6 +13,7 @@ Feature: Scaffolds
And I should see "Name"
And I should see "Description"
And I should see "Order"
+ And I should see "transition list"
And I should see "Display"
And I should see "Layout"
And I should see "Search"
@@ -188,9 +193,4 @@ Feature: Scaffolds
And I should see "Search"
And I should see "Create New"
And there should be no translation errors
-
- TODO: proposal_queue_item
- TODO: proposal_queue
- TODO: role_element_order
-
-
+
View
30 crowbar_framework/app/controllers/barclamp_controller.rb
@@ -338,8 +338,11 @@ def proposal_update
elsif params[:submit] == t('barclamp.proposal_show.dequeue_proposal')
begin
answer = operations.dequeue_proposal(params[:name])
- flash[:notice] = t('barclamp.proposal_show.dequeue_proposal_failure') unless answer
- flash[:notice] = t('barclamp.proposal_show.dequeue_proposal_success') if answer
+ if answer[0] == 200
+ flash[:notice] = t('barclamp.proposal_show.dequeue_proposal_success')
+ else
+ flash[:notice] = t('barclamp.proposal_show.dequeue_proposal_failure') + ": " + answer[1].to_s
+ end
rescue Exception => e
flash[:notice] = e.message
end
@@ -377,7 +380,7 @@ def proposal_delete
#
# Provides the restful api call for
- # Commit Proposal Instance /crowbar/<barclamp-name>/<version>/proposals/commit/<barclamp-instance-name> POST This action will create a new instance based upon this proposal. If the instance already exists, it will be editted and replaced
+ # Commit Proposal Instance /crowbar/<barclamp-name>/<version>/proposals/commit/<barclamp-instance-name> POST This action will create a new instance based upon this proposal. If the instance already exists, it will be edited and replaced
#
add_help(:proposal_commit,[:id],[:post])
def proposal_commit
@@ -387,15 +390,22 @@ def proposal_commit
end
#
- # Currently, A UI ONLY METHOD
- # XXX: TODO: Make this a restful call defined somewhere.
+ # Provides the restful api call for
+ # Dequeue Proposal Instance /crowbar/<barclamp-name>/<version>/proposals/dequeue/<barclamp-instance-name> DELETE This action will dequeue an existing proposal.
#
- add_help(:proposal_dequeue,[:id],[:post])
+ add_help(:proposal_dequeue,[:id],[:delete])
def proposal_dequeue
- ret = operations.dequeue_proposal params[:id]
- flash[:notice] = (ret[0]==200 ? t('proposal.actions.dequeue.success') : t('proposal.actions.dequeue.fail'))
- return render :text => flash[:notice], :status => 400 unless ret
- render :json => {}, :status => 200 if ret
+ answer = operations.dequeue_proposal params[:id]
+ if answer[0] == 200
+ flash[:notice] = t('proposal.actions.dequeue.success')
+ else
+ flash[:notice] = t('proposal.actions.dequeue.fail') + ": " + answer[1].to_s
+ end
+ if answer[0] == 200
+ return render :json => {}, :status => answer[0]
+ else
+ return render :text => flash[:notice], :status => answer[0]
+ end
end
add_help(:nodes,[],[:get])
View
6 crowbar_framework/app/controllers/nodes_controller.rb
@@ -222,6 +222,12 @@ def show
end
end
+ # RESTfule delete of the node
+ def delete
+ Node.delete Node.find_key(params[:id]).id
+ render :text => "Node #{params[:id]} deleted!"
+ end
+
def new
if request.post?
@node = Node.create! params
View
12 crowbar_framework/app/controllers/users_controller.rb
@@ -0,0 +1,12 @@
+class UsersController < Devise::SessionsController
+
+ def sign_out
+ destroy
+ end
+
+ def index
+ @users = User.all
+ end
+
+end
+
View
2 crowbar_framework/app/models/barclamp.rb
@@ -24,7 +24,7 @@ class Barclamp < ActiveRecord::Base
# Validate the name should unique
# and that it starts with an alph and only contains alpha,digist,hyphen,underscore
#
- validates_uniqueness_of :name, :message => I18n.t("db.notunique", :default=>"Name item must be unique")
+ validates_uniqueness_of :name, :case_sensitive => false, :message => I18n.t("db.notunique", :default=>"Name item must be unique")
validates_format_of :name, :with=>/^[a-zA-Z][_a-zA-Z0-9]*$/, :message => I18n.t("db.lettersnumbers", :default=>"Name limited to [_a-zA-Z0-9]")
#
View
2 crowbar_framework/app/models/cmdb.rb
@@ -16,7 +16,7 @@ class Cmdb < ActiveRecord::Base
attr_accessible :name, :description, :order
- validates_uniqueness_of :name, :message => I18n.t("db.notunique", :default=>"Name item must be unique")
+ validates_uniqueness_of :name, :case_sensitive => false, :message => I18n.t("db.notunique", :default=>"Name item must be unique")
validates_format_of :name, :with=>/[a-zA-Z][_a-zA-Z0-9]/, :message => I18n.t("db.lettersnumbers", :default=>"Name limited to [_a-zA-Z0-9]")
has_many :cmdb_runs
View
2 crowbar_framework/app/models/doc.rb
@@ -22,6 +22,6 @@ class Doc < ActiveRecord::Base
belongs_to :parent, :class_name => "Doc", :foreign_key => "parent_name"
has_many :children, :class_name => "Doc", :foreign_key => "parent_name", :order => "[order]+[description] ASC"
- validates_uniqueness_of :name, :on => :create, :message => I18n.t("db.notunique", :default=>"Doc handle must be unique")
+ validates_uniqueness_of :name, :on => :create, :case_sensitive => false, :message => I18n.t("db.notunique", :default=>"Doc handle must be unique")
end
View
2 crowbar_framework/app/models/group.rb
@@ -20,7 +20,7 @@ class Group < ActiveRecord::Base
validates_format_of :name, :with=>/^[a-zA-Z][_a-zA-Z0-9]*$/, :message => I18n.t("db.lettersnumbers", :default=>"Name limited to [_a-zA-Z0-9]")
validates_format_of :category, :with=>/^[a-zA-Z][_a-zA-Z0-9]*$/, :message => I18n.t("db.lettersnumbers", :default=>"Category limited to [_a-zA-Z0-9]")
- validates_inclusion_of :category, :in => %w(ui rack), :message => "Group Model Validation Error: type is not an allowed category"
+ validates_inclusion_of :category, :in => %w(ui rack tag), :message => "Group Model Validation Error: type is not an allowed category"
has_and_belongs_to_many :nodes, :join_table => "node_groups", :foreign_key => "group_id", :order=>"[order], [name] ASC"
View
2 crowbar_framework/app/models/nav.rb
@@ -21,6 +21,6 @@ class Nav < ActiveRecord::Base
belongs_to :parent, :class_name => "Nav", :foreign_key => "parent_item"
has_many :children, :class_name => "Nav", :foreign_key => "parent_item", :order => "[order] ASC", :conditions=>(Rails.env.eql?('development') ? [] : ['development=?', false])
- validates_uniqueness_of :item, :on => :create, :message => I18n.t("db.notunique", :default=>"Name item must be unique")
+ validates_uniqueness_of :item, :case_sensitive => false, :on => :create, :message => I18n.t("db.notunique", :default=>"Name item must be unique")
end
View
5 crowbar_framework/app/models/node.rb
@@ -19,10 +19,10 @@ class Node < ActiveRecord::Base
attr_accessible :name, :description, :order, :state, :fingerprint, :admin, :allocated
#
- # Validate the name should unique
+ # Validate the name should unique (no matter the case)
# and that it starts with a valid FQDN
#
- validates_uniqueness_of :name, :message => I18n.t("db.notunique", :default=>"Name item must be unique")
+ validates_uniqueness_of :name, :message => I18n.t("db.notunique", :case_sensitive => false, :default=>"Name item must be unique")
validates_format_of :name, :with=>/^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9]))*\.([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])*\.([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$/, :message => I18n.t("db.fqdn", :default=>"Name must be a fully qualified domain name.")
has_and_belongs_to_many :groups, :join_table => "node_groups", :foreign_key => "node_id", :order=>"[order], [name] ASC"
@@ -199,6 +199,7 @@ def <=>(other)
# make sure some safe values are set for the node
def default_population
self.fingerprint = self.name.hash
+ self.name = self.name.to_lower
self.state ||= 'unknown'
if self.groups.size == 0
g = Group.find_or_create_by_name :name=>'not_set', :description=>I18n.t('not_set')
View
2 crowbar_framework/app/models/os.rb
@@ -17,7 +17,7 @@ class Os < ActiveRecord::Base
attr_accessible :name, :description, :order
- validates_uniqueness_of :name, :message => I18n.t("db.notunique", :default=>"Name item must be unique")
+ validates_uniqueness_of :name, :message => I18n.t("db.notunique", :case_sensitive => false, :default=>"Name item must be unique")
validates_format_of :name, :with=>/[a-zA-Z][_a-zA-Z0-9]/, :message => I18n.t("db.lettersnumbers", :default=>"Name limited to [_a-zA-Z0-9]")
has_many :nodes
View
4 crowbar_framework/app/models/service_object.rb
@@ -223,7 +223,7 @@ def proposal_edit(params)
#
# Function to handle the barclamp controller API request to delete proposal (proposal delete)
# Input:
- # inst - String name of a proposal to dequeue
+ # inst - String name of a proposal to delete
#
# Output:
# [ HTTP Error Code, String Message ]
@@ -244,7 +244,7 @@ def proposal_delete(inst)
#
# Function to handle the barclamp controller API request to commit proposal (proposal commit)
# Input:
- # inst - String name of a proposal to dequeue
+ # inst - String name of a proposal to commit
# in_queue - optional boolean to indicate if we are in the queue code vs. UI/API
#
# Output:
View
2 crowbar_framework/app/models/user.rb
@@ -34,7 +34,7 @@ class User < ActiveRecord::Base
attr_accessible :username, :is_admin, :email, :password, :password_confirmation, :remember_me
# attr_accessible :title, :body
- validates :username, :uniqueness => true
+ validates :username, :uniqueness => true, :case_sensitive => false
def email_required?
false
View
23 crowbar_framework/app/views/users/index.html.haml
@@ -0,0 +1,23 @@
+%h1= t('user.title')
+%table.data.box
+ %thead
+ %tr
+ %th= t('user.username')
+ %th= t('user.sign_in_count')
+ %th= t('user.failed_attempts')
+ %th= t('user.last_sign_in_at')
+ %th= t('user.last_sign_in_ip')
+ %tbody
+ - if @users.count > 0
+ - @users.each do |user|
+ %tr.node{ :class => cycle(:odd, :even) }
+ %td= user.username
+ %td= user.sign_in_count
+ %td= user.failed_attempts
+ %td= user.last_sign_in_at
+ %td= user.last_sign_in_ip
+ - else
+ %tr
+ %td{ :colspan=>5 }
+ = t 'no_items'
+
View
14 crowbar_framework/config/locales/crowbar/en.yml
@@ -52,6 +52,12 @@ en:
docs: Documentation
docs_description: Documentation
+ users: Users
+ manage_users: Manage Users
+ manage_users_description: Add, remove and edit users
+ sign_out: Sign Out
+ sign_out_description: End the current session
+
scaffold:
top: Scaffolds
top_description: Developer Tools
@@ -442,3 +448,11 @@ en:
lettersnumbers: "Cannot Save: Item can only contain letters (a..z, A..Z), numbers (0..9) and underscore (_)."
fqdn: "Item must be a fully qualified domain name (FQDN) such as 'server.example.com'."
+ # Users
+ user:
+ title: Manage User Accounts
+ username: Username
+ sign_in_count: Sign In Count
+ failed_attempts: Failed Signins
+ last_sign_in_at: Last Signed In
+ last_sign_in_ip: IP of Last Sign In
View
32 crowbar_framework/config/routes.rb
@@ -36,8 +36,6 @@
resources :role_element_orders do as_routes end
end
- devise_for :users
-
resources :nodes, :only => [:index, :new] do
get 'status', :on => :collection
end
@@ -72,12 +70,24 @@
get "dashboard", :controller => 'nodes', :action => 'index', :as => 'dashboard'
get "dashboard/:name", :controller => 'nodes', :action => 'index', :constraints => { :name => /.*/ }, :as => 'dashboard_detail'
+ # status operations
+ scope 'status' do
+ scope '2.0' do
+ constraints(:id => /.*/ ) do
+ get "node(/:id)(.:format)" => 'nodes#status', :as=>'node_status'
+ end
+ end
+ end
+
+ # node operations
scope 'node' do
- version = "2.0"
- post "#{version}/new" => "nodes#new"
- constraints(:id => /.*/ ) do
- get "status/#{version}(/:id)(.:format)" => 'nodes#status', :as => :node_status
- get "#{version}/(:id)(.:format)" =>'nodes#show', :as => :node
+ scope '2.0' do
+ post "/" => "nodes#new"
+ constraints(:id => /.*/ ) do
+ get "/:id(.:format)" => 'nodes#show', :as => :node
+ delete "/:id" => 'nodes#delete', :as => :node_delete
+ put "/:id" => 'nodes#edit'
+ end
end
end
@@ -93,7 +103,13 @@
match ':name/update' => 'nodes#update', :as => :update_node
end
end
-
+
+ devise_for :users, :controllers => { :registrations => "registrations" }
+ devise_scope :user do
+ match "users/sign_out", :controller => 'users', :action =>'sign_out'
+ match "manage_users", :controller => 'users', :action => 'index'
+ end
+
scope 'proposal' do
version = "2.0"
get "status/#{version}(/:id)(.:format)", :controller=>'proposals', :action => 'status', :constraints => { :id => /.*/ }, :as=>:proposal_status
View
7 crowbar_framework/db/migrate/20120723233100_create_navs.rb
@@ -54,9 +54,14 @@ def self.up
Nav.find_or_create_by_item :item=>'docs', :parent_item=>'help', :name=>'nav.docs', :description=>'nav.docs_description', :path=>"docs_path", :order=>100
Nav.find_or_create_by_item :item=>'crowbar_wiki', :parent_item=>'help', :name=>'nav.wiki', :description=>'nav.wiki_description', :path=>"https://github.com/dellcloudedge/crowbar/wiki/", :order=>200
+ # users
+ Nav.find_or_create_by_item :item=>'users', :parent_item=>'root', :name=>'nav.users', :description=>'nav.users_description', :path=>"manage_users_path", :order=>6000
+ Nav.find_or_create_by_item :item=>'manage_users', :parent_item=>'users', :name=>'nav.manage_users', :description=>'nav.manage_users_description', :path=>"manage_users_path", :order=>100
+ Nav.find_or_create_by_item :item=>'signout', :parent_item=>'users', :name=>'nav.sign_out', :description=>'nav.sign_out_description', :path=>"destroy_user_session_path", :order=>200
+
end
def self.down
drop_table :navs
end
-end
+end
View
9 crowbar_framework/doc/crowbar.yml
@@ -43,9 +43,14 @@ crowbar+book-developersguide:
crowbar+devguide+locatization:
crowbar+devguide+api:
order: 6000
- crowbar+devguide+api+proposal:
- crowbar+devguide+api+barclamp:
+ crowbar+devguide+api+status:
+ order: 1000
crowbar+devguide+api+node:
+ order: 2000
+ crowbar+devguide+api+barclamp:
+ order: 5000
+ crowbar+devguide+api+proposal:
+ order: 9000
crowbar+devguide+testing:
order: 8000
View
37 crowbar_framework/doc/default/crowbar/devguide/api.md
@@ -20,32 +20,43 @@ The Crowbar 2 API attempts to follow the following behavior pattern.
* JSON is the API serialization model
* Single objects will return the simple serialization (object.json) output
* Cross references will use database IDs (normalized xref) for returns, not natural keys or names
-* Some calls are shared by the UI and may require `?format=json`. It is recommended at all API calls include this parameter for safety.
+* Some calls are shared by the UI and may require `?format=json`. It is recommended that all API calls include this parameter for safety.
+
+ > Warning: Do NOT use API calls without the version # included! Calls without version numbers are tightly coupled to the UI screens and do not have any contract at all. They are expected to be used internally by the UI and not maintained for external users!
#### Common API URL Patterns:
-* Basic Contract: `[object_type]/2.0/` ...
- * object_type - (singular) such as node, barclamp, network, etc
+* UI URLs: _these are undocumented, unsupported for external use, and do not include a version number_
+ * Do not use these for API calls!
+
+* Basic Contract: `[key_word]/2.0/` ...
+ * key_word - groups the API into different categories
+