summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--Rakefile263
-rw-r--r--chef-server-api/LICENSE (renamed from chef-server-slice/LICENSE)0
-rw-r--r--chef-server-api/README.rdoc94
-rw-r--r--chef-server-api/Rakefile (renamed from chef-server-slice/Rakefile)33
-rw-r--r--chef-server-api/app/controllers/application.rb287
-rw-r--r--chef-server-api/app/controllers/cookbooks.rb123
-rw-r--r--chef-server-api/app/controllers/data.rb73
-rw-r--r--chef-server-api/app/controllers/data_item.rb81
-rw-r--r--chef-server-api/app/controllers/exceptions.rb (renamed from chef-server-slice/app/controllers/exceptions.rb)15
-rw-r--r--chef-server-api/app/controllers/main.rb18
-rw-r--r--chef-server-api/app/controllers/nodes.rb95
-rw-r--r--chef-server-api/app/controllers/roles.rb71
-rw-r--r--chef-server-api/app/controllers/search.rb56
-rw-r--r--chef-server-api/app/helpers/application_helper.rb217
-rw-r--r--chef-server-api/app/helpers/exceptions_helper.rb (renamed from chef-server-slice/app/helpers/exceptions_helper.rb)2
-rw-r--r--chef-server-api/app/helpers/global_helpers.rb30
-rw-r--r--chef-server-api/app/helpers/nodes_helper.rb26
-rw-r--r--chef-server-api/app/helpers/roles_helper.rb (renamed from chef-server-slice/app/helpers/roles_helper.rb)0
-rw-r--r--chef-server-api/app/views/exceptions/bad_request.json.erb (renamed from chef-server-slice/app/views/exceptions/bad_request.json.erb)0
-rw-r--r--chef-server-api/app/views/exceptions/internal_server_error.html.erb (renamed from chef-server-slice/app/views/exceptions/internal_server_error.html.erb)0
-rw-r--r--chef-server-api/app/views/exceptions/not_acceptable.html.haml5
-rw-r--r--chef-server-api/app/views/exceptions/not_found.html.erb (renamed from chef-server-slice/app/views/exceptions/not_found.html.erb)0
-rw-r--r--chef-server-api/app/views/exceptions/standard_error.html.erb (renamed from chef-server-slice/app/views/exceptions/standard_error.html.erb)0
-rw-r--r--chef-server-api/app/views/layout/chef_server_api.html.haml23
-rw-r--r--chef-server-api/app/views/main/index.html.haml5
-rw-r--r--chef-server-api/config/init.rb60
-rw-r--r--chef-server-api/config/router.rb (renamed from chef-server-slice/config/router.rb)0
-rw-r--r--chef-server-api/lib/chef-server-api.rb153
-rw-r--r--chef-server-api/lib/chef-server-api/merbtasks.rb103
-rw-r--r--chef-server-api/lib/chef-server-api/slicetasks.rb (renamed from chef-server-slice/lib/chef-server-slice/slicetasks.rb)0
-rw-r--r--chef-server-api/lib/chef-server-api/spectasks.rb53
-rw-r--r--chef-server-api/public/images/avatar.png (renamed from chef-server-slice/public/images/avatar.png)bin3214 -> 3214 bytes
-rw-r--r--chef-server-api/public/images/indicator.gif (renamed from chef-server-slice/public/images/indicator.gif)bin1553 -> 1553 bytes
-rw-r--r--chef-server-api/public/images/merb.jpg (renamed from chef-server-slice/public/images/merb.jpg)bin5815 -> 5815 bytes
-rw-r--r--chef-server-api/public/stylesheets/base.css (renamed from chef-server-slice/public/stylesheets/base.css)0
-rw-r--r--chef-server-api/public/stylesheets/chef.css (renamed from chef-server-slice/public/stylesheets/chef.css)0
-rw-r--r--chef-server-api/public/stylesheets/themes/bec-green/style.css (renamed from chef-server-slice/public/stylesheets/themes/bec-green/style.css)0
-rw-r--r--chef-server-api/public/stylesheets/themes/bec/style.css (renamed from chef-server-slice/public/stylesheets/themes/bec/style.css)0
-rw-r--r--chef-server-api/public/stylesheets/themes/blue/style.css (renamed from chef-server-slice/public/stylesheets/themes/blue/style.css)0
-rw-r--r--chef-server-api/public/stylesheets/themes/default/style.css (renamed from chef-server-slice/public/stylesheets/themes/default/style.css)0
-rw-r--r--chef-server-api/public/stylesheets/themes/djime-cerulean/style.css (renamed from chef-server-slice/public/stylesheets/themes/djime-cerulean/style.css)0
-rw-r--r--chef-server-api/public/stylesheets/themes/kathleene/style.css (renamed from chef-server-slice/public/stylesheets/themes/kathleene/style.css)0
-rw-r--r--chef-server-api/public/stylesheets/themes/orange/style.css (renamed from chef-server-slice/public/stylesheets/themes/orange/style.css)0
-rw-r--r--chef-server-api/public/stylesheets/themes/reidb-greenish/style.css (renamed from chef-server-slice/public/stylesheets/themes/reidb-greenish/style.css)0
-rw-r--r--chef-server-api/stubs/app/controllers/application.rb2
-rw-r--r--chef-server-api/stubs/app/controllers/main.rb2
-rw-r--r--chef-server-slice/app/controllers/cookbook_attributes.rb59
-rw-r--r--chef-server-slice/app/controllers/cookbook_definitions.rb60
-rw-r--r--chef-server-slice/app/controllers/cookbook_files.rb94
-rw-r--r--chef-server-slice/app/controllers/cookbook_libraries.rb60
-rw-r--r--chef-server-slice/app/controllers/cookbook_recipes.rb59
-rw-r--r--chef-server-slice/app/controllers/main.rb7
-rw-r--r--chef-server-slice/app/controllers/nodes.rb144
-rw-r--r--chef-server-slice/app/controllers/openid_register.rb113
-rw-r--r--chef-server-slice/app/controllers/openid_server.rb252
-rw-r--r--chef-server-slice/app/controllers/roles.rb138
-rw-r--r--chef-server-webui/LICENSE201
-rw-r--r--chef-server-webui/NOTICE (renamed from chef-server-slice/NOTICE)0
-rw-r--r--chef-server-webui/README.rdoc (renamed from chef-server-slice/README.rdoc)0
-rw-r--r--chef-server-webui/Rakefile65
-rw-r--r--chef-server-webui/app/controllers/application.rb (renamed from chef-server-slice/app/controllers/application.rb)4
-rw-r--r--chef-server-webui/app/controllers/cookbook_attributes.rb41
-rw-r--r--chef-server-webui/app/controllers/cookbook_definitions.rb41
-rw-r--r--chef-server-webui/app/controllers/cookbook_files.rb39
-rw-r--r--chef-server-webui/app/controllers/cookbook_libraries.rb41
-rw-r--r--chef-server-webui/app/controllers/cookbook_recipes.rb40
-rw-r--r--chef-server-webui/app/controllers/cookbook_templates.rb (renamed from chef-server-slice/app/controllers/cookbook_templates.rb)31
-rw-r--r--chef-server-webui/app/controllers/cookbooks.rb (renamed from chef-server-slice/app/controllers/cookbooks.rb)2
-rw-r--r--chef-server-webui/app/controllers/exceptions.rb19
-rw-r--r--chef-server-webui/app/controllers/main.rb7
-rw-r--r--chef-server-webui/app/controllers/nodes.rb114
-rw-r--r--chef-server-webui/app/controllers/openid_consumer.rb (renamed from chef-server-slice/app/controllers/openid_consumer.rb)10
-rw-r--r--chef-server-webui/app/controllers/roles.rb103
-rw-r--r--chef-server-webui/app/controllers/search.rb (renamed from chef-server-slice/app/controllers/search.rb)16
-rw-r--r--chef-server-webui/app/controllers/search_entries.rb (renamed from chef-server-slice/app/controllers/search_entries.rb)21
-rw-r--r--chef-server-webui/app/controllers/status.rb (renamed from chef-server-slice/app/controllers/status.rb)6
-rw-r--r--chef-server-webui/app/helpers/application_helper.rb (renamed from chef-server-slice/app/helpers/application_helper.rb)8
-rw-r--r--chef-server-webui/app/helpers/cookbook_attributes_helper.rb (renamed from chef-server-slice/app/helpers/cookbook_attributes_helper.rb)2
-rw-r--r--chef-server-webui/app/helpers/cookbook_definitions_helper.rb (renamed from chef-server-slice/app/helpers/cookbook_definitions_helper.rb)2
-rw-r--r--chef-server-webui/app/helpers/cookbook_files_helper.rb (renamed from chef-server-slice/app/helpers/cookbook_files_helper.rb)2
-rw-r--r--chef-server-webui/app/helpers/cookbook_libraries_helper.rb (renamed from chef-server-slice/app/helpers/cookbook_libraries_helper.rb)2
-rw-r--r--chef-server-webui/app/helpers/cookbook_recipes_helper.rb (renamed from chef-server-slice/app/helpers/cookbook_recipes_helper.rb)2
-rw-r--r--chef-server-webui/app/helpers/cookbook_templates_helper.rb (renamed from chef-server-slice/app/helpers/cookbook_templates_helper.rb)2
-rw-r--r--chef-server-webui/app/helpers/cookbooks_helper.rb (renamed from chef-server-slice/app/helpers/cookbooks_helper.rb)9
-rw-r--r--chef-server-webui/app/helpers/exceptions_helper.rb6
-rw-r--r--chef-server-webui/app/helpers/global_helpers.rb (renamed from chef-server-slice/app/helpers/global_helpers.rb)2
-rw-r--r--chef-server-webui/app/helpers/nodes_helper.rb (renamed from chef-server-slice/app/helpers/nodes_helper.rb)10
-rw-r--r--chef-server-webui/app/helpers/openid_consumer_helper.rb (renamed from chef-server-slice/app/helpers/openid_consumer_helper.rb)2
-rw-r--r--chef-server-webui/app/helpers/openid_register_helper.rb (renamed from chef-server-slice/app/helpers/openid_register_helper.rb)2
-rw-r--r--chef-server-webui/app/helpers/openid_server_helper.rb (renamed from chef-server-slice/app/helpers/openid_server_helper.rb)2
-rw-r--r--chef-server-webui/app/helpers/openid_server_helpers.rb (renamed from chef-server-slice/app/helpers/openid_server_helpers.rb)2
-rw-r--r--chef-server-webui/app/helpers/roles_helper.rb5
-rw-r--r--chef-server-webui/app/helpers/search_entries_helper.rb (renamed from chef-server-slice/app/helpers/search_entries_helper.rb)2
-rw-r--r--chef-server-webui/app/helpers/search_helper.rb (renamed from chef-server-slice/app/helpers/search_helper.rb)2
-rw-r--r--chef-server-webui/app/helpers/status_helper.rb (renamed from chef-server-slice/app/helpers/status_helper.rb)2
-rw-r--r--chef-server-webui/app/views/cookbook_templates/index.html.haml (renamed from chef-server-slice/app/views/cookbook_templates/index.html.haml)0
-rw-r--r--chef-server-webui/app/views/cookbooks/index.html.haml (renamed from chef-server-slice/app/views/cookbooks/index.html.haml)0
-rw-r--r--chef-server-webui/app/views/cookbooks/show.html.haml (renamed from chef-server-slice/app/views/cookbooks/show.html.haml)0
-rw-r--r--chef-server-webui/app/views/exceptions/bad_request.json.erb1
-rw-r--r--chef-server-webui/app/views/exceptions/internal_server_error.html.erb216
-rw-r--r--chef-server-webui/app/views/exceptions/not_acceptable.html.erb (renamed from chef-server-slice/app/views/exceptions/not_acceptable.html.erb)0
-rw-r--r--chef-server-webui/app/views/exceptions/not_found.html.erb47
-rw-r--r--chef-server-webui/app/views/exceptions/standard_error.html.erb217
-rw-r--r--chef-server-webui/app/views/layout/chef_server_webui.html.haml (renamed from chef-server-slice/app/views/layout/chef_server_slice.html.haml)0
-rw-r--r--chef-server-webui/app/views/layout/login.html.haml (renamed from chef-server-slice/app/views/layout/login.html.haml)0
-rw-r--r--chef-server-webui/app/views/main/index.html.erb (renamed from chef-server-slice/app/views/main/index.html.erb)0
-rw-r--r--chef-server-webui/app/views/nodes/_action.html.haml (renamed from chef-server-slice/app/views/nodes/_action.html.haml)0
-rw-r--r--chef-server-webui/app/views/nodes/_form.html.haml (renamed from chef-server-slice/app/views/nodes/_form.html.haml)0
-rw-r--r--chef-server-webui/app/views/nodes/_navigation.html.haml (renamed from chef-server-slice/app/views/nodes/_navigation.html.haml)0
-rw-r--r--chef-server-webui/app/views/nodes/_resource.html.haml (renamed from chef-server-slice/app/views/nodes/_resource.html.haml)0
-rw-r--r--chef-server-webui/app/views/nodes/edit.html.haml (renamed from chef-server-slice/app/views/nodes/edit.html.haml)0
-rw-r--r--chef-server-webui/app/views/nodes/index.html.haml (renamed from chef-server-slice/app/views/nodes/index.html.haml)0
-rw-r--r--chef-server-webui/app/views/nodes/new.html.haml (renamed from chef-server-slice/app/views/nodes/new.html.haml)0
-rw-r--r--chef-server-webui/app/views/nodes/show.html.haml (renamed from chef-server-slice/app/views/nodes/show.html.haml)0
-rw-r--r--chef-server-webui/app/views/openid_consumer/index.html.haml (renamed from chef-server-slice/app/views/openid_consumer/index.html.haml)0
-rw-r--r--chef-server-webui/app/views/openid_consumer/start.html.haml (renamed from chef-server-slice/app/views/openid_consumer/start.html.haml)0
-rw-r--r--chef-server-webui/app/views/openid_login/index.html.haml (renamed from chef-server-slice/app/views/openid_login/index.html.haml)0
-rw-r--r--chef-server-webui/app/views/openid_register/index.html.haml (renamed from chef-server-slice/app/views/openid_register/index.html.haml)0
-rw-r--r--chef-server-webui/app/views/openid_register/show.html.haml (renamed from chef-server-slice/app/views/openid_register/show.html.haml)0
-rw-r--r--chef-server-webui/app/views/openid_server/decide.html.haml (renamed from chef-server-slice/app/views/openid_server/decide.html.haml)0
-rw-r--r--chef-server-webui/app/views/roles/_form.html.haml (renamed from chef-server-slice/app/views/roles/_form.html.haml)0
-rw-r--r--chef-server-webui/app/views/roles/_navigation.html.haml (renamed from chef-server-slice/app/views/roles/_navigation.html.haml)0
-rw-r--r--chef-server-webui/app/views/roles/edit.html.haml (renamed from chef-server-slice/app/views/roles/edit.html.haml)0
-rw-r--r--chef-server-webui/app/views/roles/index.html.haml (renamed from chef-server-slice/app/views/roles/index.html.haml)0
-rw-r--r--chef-server-webui/app/views/roles/new.html.haml (renamed from chef-server-slice/app/views/roles/new.html.haml)0
-rw-r--r--chef-server-webui/app/views/roles/show.html.haml (renamed from chef-server-slice/app/views/roles/show.html.haml)0
-rw-r--r--chef-server-webui/app/views/search/_search_form.html.haml (renamed from chef-server-slice/app/views/search/_search_form.html.haml)0
-rw-r--r--chef-server-webui/app/views/search/index.html.haml (renamed from chef-server-slice/app/views/search/index.html.haml)0
-rw-r--r--chef-server-webui/app/views/search/show.html.haml (renamed from chef-server-slice/app/views/search/show.html.haml)0
-rw-r--r--chef-server-webui/app/views/search_entries/index.html.haml (renamed from chef-server-slice/app/views/search_entries/index.html.haml)0
-rw-r--r--chef-server-webui/app/views/search_entries/show.html.haml (renamed from chef-server-slice/app/views/search_entries/show.html.haml)0
-rw-r--r--chef-server-webui/app/views/status/index.html.haml (renamed from chef-server-slice/app/views/status/index.html.haml)0
-rw-r--r--chef-server-webui/config/init.rb (renamed from chef-server-slice/config/init.rb)4
-rw-r--r--chef-server-webui/config/router.rb6
-rw-r--r--chef-server-webui/lib/chef-server-webui.rb (renamed from chef-server-slice/lib/chef-server-slice.rb)39
-rw-r--r--chef-server-webui/lib/chef-server-webui/merbtasks.rb (renamed from chef-server-slice/lib/chef-server-slice/merbtasks.rb)34
-rw-r--r--chef-server-webui/lib/chef-server-webui/slicetasks.rb20
-rw-r--r--chef-server-webui/lib/chef-server-webui/spectasks.rb (renamed from chef-server-slice/lib/chef-server-slice/spectasks.rb)2
-rw-r--r--chef-server-webui/public/facebox/README.txt (renamed from chef-server-slice/public/facebox/README.txt)0
-rw-r--r--chef-server-webui/public/facebox/b.png (renamed from chef-server-slice/public/facebox/b.png)bin84 -> 84 bytes
-rw-r--r--chef-server-webui/public/facebox/bl.png (renamed from chef-server-slice/public/facebox/bl.png)bin124 -> 124 bytes
-rw-r--r--chef-server-webui/public/facebox/br.png (renamed from chef-server-slice/public/facebox/br.png)bin124 -> 124 bytes
-rwxr-xr-xchef-server-webui/public/facebox/closelabel.gif (renamed from chef-server-slice/public/facebox/closelabel.gif)bin979 -> 979 bytes
-rw-r--r--chef-server-webui/public/facebox/facebox.css (renamed from chef-server-slice/public/facebox/facebox.css)0
-rw-r--r--chef-server-webui/public/facebox/facebox.js (renamed from chef-server-slice/public/facebox/facebox.js)0
-rwxr-xr-xchef-server-webui/public/facebox/loading.gif (renamed from chef-server-slice/public/facebox/loading.gif)bin2767 -> 2767 bytes
-rw-r--r--chef-server-webui/public/facebox/tl.png (renamed from chef-server-slice/public/facebox/tl.png)bin132 -> 132 bytes
-rw-r--r--chef-server-webui/public/facebox/tr.png (renamed from chef-server-slice/public/facebox/tr.png)bin125 -> 125 bytes
-rw-r--r--chef-server-webui/public/images/avatar.pngbin0 -> 3214 bytes
-rw-r--r--chef-server-webui/public/images/black_big.png (renamed from chef-server-slice/public/images/black_big.png)bin2734 -> 2734 bytes
-rw-r--r--chef-server-webui/public/images/indicator.gifbin0 -> 1553 bytes
-rw-r--r--chef-server-webui/public/images/merb.jpgbin0 -> 5815 bytes
-rw-r--r--chef-server-webui/public/images/toggle-collapse-dark.png (renamed from chef-server-slice/public/images/toggle-collapse-dark.png)bin2886 -> 2886 bytes
-rw-r--r--chef-server-webui/public/images/toggle-collapse-light.png (renamed from chef-server-slice/public/images/toggle-collapse-light.png)bin2864 -> 2864 bytes
-rw-r--r--chef-server-webui/public/images/toggle-collapse.gif (renamed from chef-server-slice/public/images/toggle-collapse.gif)bin176 -> 176 bytes
-rw-r--r--chef-server-webui/public/images/toggle-expand-dark.png (renamed from chef-server-slice/public/images/toggle-expand-dark.png)bin2894 -> 2894 bytes
-rw-r--r--chef-server-webui/public/images/toggle-expand-light.png (renamed from chef-server-slice/public/images/toggle-expand-light.png)bin2863 -> 2863 bytes
-rw-r--r--chef-server-webui/public/images/toggle-expand.gif (renamed from chef-server-slice/public/images/toggle-expand.gif)bin181 -> 181 bytes
-rwxr-xr-xchef-server-webui/public/images/treeBuilderImages/Thumbs.db (renamed from chef-server-slice/public/images/treeBuilderImages/Thumbs.db)bin20480 -> 20480 bytes
-rwxr-xr-xchef-server-webui/public/images/treeBuilderImages/doc.gif (renamed from chef-server-slice/public/images/treeBuilderImages/doc.gif)bin993 -> 993 bytes
-rwxr-xr-xchef-server-webui/public/images/treeBuilderImages/docNode.gif (renamed from chef-server-slice/public/images/treeBuilderImages/docNode.gif)bin147 -> 147 bytes
-rwxr-xr-xchef-server-webui/public/images/treeBuilderImages/docNodeLast.gif (renamed from chef-server-slice/public/images/treeBuilderImages/docNodeLast.gif)bin142 -> 142 bytes
-rwxr-xr-xchef-server-webui/public/images/treeBuilderImages/docNodeLastFirst.gif (renamed from chef-server-slice/public/images/treeBuilderImages/docNodeLastFirst.gif)bin107 -> 107 bytes
-rwxr-xr-xchef-server-webui/public/images/treeBuilderImages/folder.gif (renamed from chef-server-slice/public/images/treeBuilderImages/folder.gif)bin974 -> 974 bytes
-rwxr-xr-xchef-server-webui/public/images/treeBuilderImages/folderNode.gif (renamed from chef-server-slice/public/images/treeBuilderImages/folderNode.gif)bin133 -> 133 bytes
-rwxr-xr-xchef-server-webui/public/images/treeBuilderImages/folderNodeFirst.gif (renamed from chef-server-slice/public/images/treeBuilderImages/folderNodeFirst.gif)bin878 -> 878 bytes
-rwxr-xr-xchef-server-webui/public/images/treeBuilderImages/folderNodeLast.gif (renamed from chef-server-slice/public/images/treeBuilderImages/folderNodeLast.gif)bin130 -> 130 bytes
-rwxr-xr-xchef-server-webui/public/images/treeBuilderImages/folderNodeLastFirst.gif (renamed from chef-server-slice/public/images/treeBuilderImages/folderNodeLastFirst.gif)bin872 -> 872 bytes
-rwxr-xr-xchef-server-webui/public/images/treeBuilderImages/folderNodeOpen.gif (renamed from chef-server-slice/public/images/treeBuilderImages/folderNodeOpen.gif)bin129 -> 129 bytes
-rwxr-xr-xchef-server-webui/public/images/treeBuilderImages/folderNodeOpenFirst.gif (renamed from chef-server-slice/public/images/treeBuilderImages/folderNodeOpenFirst.gif)bin868 -> 868 bytes
-rwxr-xr-xchef-server-webui/public/images/treeBuilderImages/folderNodeOpenLast.gif (renamed from chef-server-slice/public/images/treeBuilderImages/folderNodeOpenLast.gif)bin125 -> 125 bytes
-rwxr-xr-xchef-server-webui/public/images/treeBuilderImages/folderNodeOpenLastFirst.gif (renamed from chef-server-slice/public/images/treeBuilderImages/folderNodeOpenLastFirst.gif)bin863 -> 863 bytes
-rwxr-xr-xchef-server-webui/public/images/treeBuilderImages/folderOpen.gif (renamed from chef-server-slice/public/images/treeBuilderImages/folderOpen.gif)bin254 -> 254 bytes
-rwxr-xr-xchef-server-webui/public/images/treeBuilderImages/vertLine.gif (renamed from chef-server-slice/public/images/treeBuilderImages/vertLine.gif)bin140 -> 140 bytes
-rwxr-xr-xchef-server-webui/public/javascripts/JSONeditor.js (renamed from chef-server-slice/public/javascripts/JSONeditor.js)0
-rw-r--r--chef-server-webui/public/javascripts/chef.js (renamed from chef-server-slice/public/javascripts/chef.js)0
-rwxr-xr-xchef-server-webui/public/javascripts/jquery-1.3.2.min.js (renamed from chef-server-slice/public/javascripts/jquery-1.3.2.min.js)0
-rwxr-xr-xchef-server-webui/public/javascripts/jquery-ui-1.7.1.custom.min.js (renamed from chef-server-slice/public/javascripts/jquery-ui-1.7.1.custom.min.js)0
-rw-r--r--chef-server-webui/public/javascripts/jquery.editinline.js (renamed from chef-server-slice/public/javascripts/jquery.editinline.js)0
-rw-r--r--chef-server-webui/public/javascripts/jquery.jeditable.mini.js (renamed from chef-server-slice/public/javascripts/jquery.jeditable.mini.js)0
-rw-r--r--chef-server-webui/public/javascripts/jquery.livequery.js (renamed from chef-server-slice/public/javascripts/jquery.livequery.js)0
-rw-r--r--chef-server-webui/public/javascripts/jquery.localscroll.js (renamed from chef-server-slice/public/javascripts/jquery.localscroll.js)0
-rw-r--r--chef-server-webui/public/javascripts/jquery.scrollTo.js (renamed from chef-server-slice/public/javascripts/jquery.scrollTo.js)0
-rw-r--r--chef-server-webui/public/javascripts/jquery.tools.min.js (renamed from chef-server-slice/public/javascripts/jquery.tools.min.js)0
-rw-r--r--chef-server-webui/public/javascripts/jquery.treeTable.min.js (renamed from chef-server-slice/public/javascripts/jquery.treeTable.min.js)0
-rw-r--r--chef-server-webui/public/stylesheets/base.css336
-rw-r--r--chef-server-webui/public/stylesheets/chef.css157
-rwxr-xr-xchef-server-webui/public/stylesheets/images/ui-bg_diagonals-small_0_aaaaaa_40x40.png (renamed from chef-server-slice/public/stylesheets/images/ui-bg_diagonals-small_0_aaaaaa_40x40.png)bin128 -> 128 bytes
-rwxr-xr-xchef-server-webui/public/stylesheets/images/ui-bg_diagonals-thick_15_444444_40x40.png (renamed from chef-server-slice/public/stylesheets/images/ui-bg_diagonals-thick_15_444444_40x40.png)bin253 -> 253 bytes
-rwxr-xr-xchef-server-webui/public/stylesheets/images/ui-bg_glass_100_f0f0f0_1x400.png (renamed from chef-server-slice/public/stylesheets/images/ui-bg_glass_100_f0f0f0_1x400.png)bin106 -> 106 bytes
-rwxr-xr-xchef-server-webui/public/stylesheets/images/ui-bg_glass_50_99c2ff_1x400.png (renamed from chef-server-slice/public/stylesheets/images/ui-bg_glass_50_99c2ff_1x400.png)bin123 -> 123 bytes
-rwxr-xr-xchef-server-webui/public/stylesheets/images/ui-bg_glass_55_fbf5d0_1x400.png (renamed from chef-server-slice/public/stylesheets/images/ui-bg_glass_55_fbf5d0_1x400.png)bin153 -> 153 bytes
-rwxr-xr-xchef-server-webui/public/stylesheets/images/ui-bg_glass_80_e6e6e6_1x400.png (renamed from chef-server-slice/public/stylesheets/images/ui-bg_glass_80_e6e6e6_1x400.png)bin113 -> 113 bytes
-rwxr-xr-xchef-server-webui/public/stylesheets/images/ui-bg_glass_95_fef1ec_1x400.png (renamed from chef-server-slice/public/stylesheets/images/ui-bg_glass_95_fef1ec_1x400.png)bin119 -> 119 bytes
-rwxr-xr-xchef-server-webui/public/stylesheets/images/ui-bg_highlight-hard_100_f9f9f9_1x100.png (renamed from chef-server-slice/public/stylesheets/images/ui-bg_highlight-hard_100_f9f9f9_1x100.png)bin86 -> 86 bytes
-rwxr-xr-xchef-server-webui/public/stylesheets/images/ui-bg_highlight-soft_100_e7eef3_1x100.png (renamed from chef-server-slice/public/stylesheets/images/ui-bg_highlight-soft_100_e7eef3_1x100.png)bin149 -> 149 bytes
-rwxr-xr-xchef-server-webui/public/stylesheets/images/ui-icons_222222_256x240.png (renamed from chef-server-slice/public/stylesheets/images/ui-icons_222222_256x240.png)bin4379 -> 4379 bytes
-rwxr-xr-xchef-server-webui/public/stylesheets/images/ui-icons_2694e8_256x240.png (renamed from chef-server-slice/public/stylesheets/images/ui-icons_2694e8_256x240.png)bin4379 -> 4379 bytes
-rwxr-xr-xchef-server-webui/public/stylesheets/images/ui-icons_2e83ff_256x240.png (renamed from chef-server-slice/public/stylesheets/images/ui-icons_2e83ff_256x240.png)bin4379 -> 4379 bytes
-rwxr-xr-xchef-server-webui/public/stylesheets/images/ui-icons_72a7cf_256x240.png (renamed from chef-server-slice/public/stylesheets/images/ui-icons_72a7cf_256x240.png)bin4379 -> 4379 bytes
-rwxr-xr-xchef-server-webui/public/stylesheets/images/ui-icons_888888_256x240.png (renamed from chef-server-slice/public/stylesheets/images/ui-icons_888888_256x240.png)bin4379 -> 4379 bytes
-rwxr-xr-xchef-server-webui/public/stylesheets/images/ui-icons_cd0a0a_256x240.png (renamed from chef-server-slice/public/stylesheets/images/ui-icons_cd0a0a_256x240.png)bin4379 -> 4379 bytes
-rwxr-xr-xchef-server-webui/public/stylesheets/images/ui-icons_ffffff_256x240.png (renamed from chef-server-slice/public/stylesheets/images/ui-icons_ffffff_256x240.png)bin4379 -> 4379 bytes
-rwxr-xr-xchef-server-webui/public/stylesheets/jquery-ui-1.7.1.custom.css (renamed from chef-server-slice/public/stylesheets/jquery-ui-1.7.1.custom.css)0
-rw-r--r--chef-server-webui/public/stylesheets/jquery.treeTable.css (renamed from chef-server-slice/public/stylesheets/jquery.treeTable.css)0
-rw-r--r--chef-server-webui/public/stylesheets/themes/bec-green/style.css290
-rw-r--r--chef-server-webui/public/stylesheets/themes/bec/style.css301
-rw-r--r--chef-server-webui/public/stylesheets/themes/blue/style.css280
-rw-r--r--chef-server-webui/public/stylesheets/themes/default/style.css267
-rw-r--r--chef-server-webui/public/stylesheets/themes/djime-cerulean/style.css298
-rw-r--r--chef-server-webui/public/stylesheets/themes/kathleene/style.css272
-rw-r--r--chef-server-webui/public/stylesheets/themes/orange/style.css263
-rw-r--r--chef-server-webui/public/stylesheets/themes/reidb-greenish/style.css301
-rw-r--r--chef-server-webui/stubs/app/controllers/application.rb2
-rw-r--r--chef-server-webui/stubs/app/controllers/main.rb2
-rw-r--r--chef-server/Rakefile2
-rwxr-xr-xchef-server/bin/chef-server5
-rw-r--r--chef-server/config/dependencies.rb15
-rw-r--r--chef-server/config/init.rb10
-rw-r--r--chef-server/config/rack.rb2
-rw-r--r--chef-server/config/router.rb15
-rw-r--r--chef-solr/.document5
-rw-r--r--chef-solr/.gitignore9
-rw-r--r--chef-solr/README.rdoc7
-rw-r--r--chef-solr/Rakefile57
-rw-r--r--chef-solr/VERSION1
-rwxr-xr-xchef-solr/bin/chef-solr27
-rwxr-xr-xchef-solr/bin/chef-solr-indexer27
-rwxr-xr-xchef-solr/bin/chef-solr-rebuild27
-rw-r--r--chef-solr/chef-solr.gemspec37
-rw-r--r--chef-solr/lib/chef/solr.rb181
-rw-r--r--chef-solr/lib/chef/solr/application/indexer.rb140
-rw-r--r--chef-solr/lib/chef/solr/application/rebuild.rb120
-rw-r--r--chef-solr/lib/chef/solr/application/solr.rb165
-rw-r--r--chef-solr/lib/chef/solr/index.rb153
-rw-r--r--chef-solr/lib/chef/solr/index_actor.rb90
-rw-r--r--chef-solr/lib/chef/solr/query.rb87
-rw-r--r--chef-solr/solr/solr-home.tar.gzbin0 -> 12400 bytes
-rw-r--r--chef-solr/solr/solr-jetty.tar.gzbin0 -> 18435366 bytes
-rw-r--r--chef-solr/spec/chef/solr/index_spec.rb168
-rw-r--r--chef-solr/spec/chef/solr/query_spec.rb14
-rw-r--r--chef-solr/spec/chef/solr_spec.rb167
-rw-r--r--chef-solr/spec/spec_helper.rb13
-rw-r--r--chef/Rakefile2
-rw-r--r--chef/lib/chef.rb2
-rw-r--r--chef/lib/chef/application/indexer.rb141
-rw-r--r--chef/lib/chef/client.rb237
-rw-r--r--chef/lib/chef/config.rb31
-rw-r--r--chef/lib/chef/cookbook_helper.rb90
-rw-r--r--chef/lib/chef/couchdb.rb138
-rw-r--r--chef/lib/chef/data_bag.rb167
-rw-r--r--chef/lib/chef/data_bag_item.rb188
-rw-r--r--chef/lib/chef/exceptions.rb3
-rw-r--r--chef/lib/chef/mixin/generate_url.rb27
-rw-r--r--chef/lib/chef/mixin/language.rb6
-rw-r--r--chef/lib/chef/nanite.rb84
-rw-r--r--chef/lib/chef/node.rb79
-rw-r--r--chef/lib/chef/openid_registration.rb2
-rw-r--r--chef/lib/chef/platform.rb1
-rw-r--r--chef/lib/chef/provider/http_request.rb4
-rw-r--r--chef/lib/chef/provider/remote_file.rb2
-rw-r--r--chef/lib/chef/provider/template.rb3
-rw-r--r--chef/lib/chef/queue.rb145
-rw-r--r--chef/lib/chef/recipe.rb16
-rw-r--r--chef/lib/chef/rest.rb159
-rw-r--r--chef/lib/chef/role.rb26
-rw-r--r--chef/lib/chef/run_list.rb5
-rw-r--r--chef/lib/chef/search.rb88
-rw-r--r--chef/lib/chef/search/query.rb63
-rw-r--r--chef/lib/chef/search/result.rb64
-rw-r--r--chef/lib/chef/search_index.rb77
-rw-r--r--chef/lib/chef/streaming_cookbook_uploader.rb178
-rw-r--r--chef/spec/unit/application/indexer_spec.rb64
-rw-r--r--chef/spec/unit/client_spec.rb82
-rw-r--r--chef/spec/unit/couchdb_spec.rb400
-rw-r--r--chef/spec/unit/data_bag_item_spec.rb164
-rw-r--r--chef/spec/unit/data_bag_spec.rb72
-rw-r--r--chef/spec/unit/node_spec.rb15
-rw-r--r--chef/spec/unit/provider/group/groupadd_spec.rb1
-rw-r--r--chef/spec/unit/provider/http_request_spec.rb6
-rw-r--r--chef/spec/unit/queue_spec.rb105
-rw-r--r--chef/spec/unit/rest_spec.rb618
-rw-r--r--chef/spec/unit/run_list_spec.rb2
-rw-r--r--chef/spec/unit/search/query_spec.rb105
-rw-r--r--chef/spec/unit/search/result_spec.rb84
-rw-r--r--chef/tasks/rspec.rb2
-rw-r--r--cucumber.yml30
-rw-r--r--features/api/cookbooks/list_cookbooks_api.feature19
-rw-r--r--features/api/cookbooks/show_cookbook_api.feature32
-rw-r--r--features/api/cookbooks/show_cookbook_attributes_api.feature21
-rw-r--r--features/api/data/create_data_bag_api.feature26
-rw-r--r--features/api/data/create_data_bag_item_api.feature29
-rw-r--r--features/api/data/delete_data_bag_api.feature34
-rw-r--r--features/api/data/delete_data_bag_item.feature27
-rw-r--r--features/api/data/list_data_bags.feature34
-rw-r--r--features/api/data/show_data_bag_api.feature44
-rw-r--r--features/api/data/show_data_bag_item_api.feature28
-rw-r--r--features/api/nodes/cookbook_sync_api.feature25
-rw-r--r--features/api/nodes/create_node_api.feature18
-rw-r--r--features/api/nodes/delete_node_api.feature13
-rw-r--r--features/api/nodes/list_nodes_api.feature20
-rw-r--r--features/api/nodes/show_node_api.feature15
-rw-r--r--features/api/nodes/update_node_api.feature14
-rw-r--r--features/api/roles/create_role_api.feature16
-rw-r--r--features/api/roles/delete_role_api.feature15
-rw-r--r--features/api/roles/list_roles_api.feature21
-rw-r--r--features/api/roles/show_roles_api.feature16
-rw-r--r--features/api/roles/update_roles_api.feature13
-rw-r--r--features/api/search/list_search.feature29
-rw-r--r--features/api/search/show_search.feature115
-rw-r--r--features/data/config/client.rb12
-rw-r--r--features/data/config/server.rb19
-rw-r--r--features/data/cookbooks/integration_setup/recipes/default.rb2
-rw-r--r--features/data/cookbooks/node_cookbook_sync/README.rdoc8
-rw-r--r--features/data/cookbooks/node_cookbook_sync/attributes/attr_file.rb0
-rw-r--r--features/data/cookbooks/node_cookbook_sync/definitions/def_file.rb0
-rw-r--r--features/data/cookbooks/node_cookbook_sync/libraries/lib_file.rb0
-rw-r--r--features/data/cookbooks/node_cookbook_sync/metadata.rb6
-rw-r--r--features/data/cookbooks/node_cookbook_sync/recipes/default.rb18
-rw-r--r--features/data/cookbooks/search/recipes/search_data.rb15
-rw-r--r--features/data/cookbooks/search/recipes/search_data_noblock.rb32
-rw-r--r--features/data/cookbooks/show_cookbook/README.rdoc8
-rw-r--r--features/data/cookbooks/show_cookbook/attributes/attr_file.rb0
-rw-r--r--features/data/cookbooks/show_cookbook/definitions/def_file.rb0
-rw-r--r--features/data/cookbooks/show_cookbook/files/default/prime_time.txt0
-rw-r--r--features/data/cookbooks/show_cookbook/files/host-latte/prime_time.txt0
-rw-r--r--features/data/cookbooks/show_cookbook/files/mac_os_x-10.5/prime_time.txt0
-rw-r--r--features/data/cookbooks/show_cookbook/files/mac_os_x/prime_time.txt0
-rw-r--r--features/data/cookbooks/show_cookbook/libraries/lib_file.rb0
-rw-r--r--features/data/cookbooks/show_cookbook/metadata.rb6
-rw-r--r--features/data/cookbooks/show_cookbook/recipes/default.rb18
-rw-r--r--features/data/cookbooks/show_cookbook/templates/default/prime_time.txt.erb0
-rw-r--r--features/data/cookbooks/show_cookbook/templates/host-latte/prime_time.txt.erb0
-rw-r--r--features/data/cookbooks/show_cookbook/templates/mac_os_x-10.5/prime_time.txt.erb0
-rw-r--r--features/data/cookbooks/show_cookbook/templates/mac_os_x/prime_time.txt.erb0
-rw-r--r--features/language/delayed_notifications.feature1
-rw-r--r--features/provider/directory/create_directories.feature1
-rw-r--r--features/provider/directory/delete_directories.feature1
-rw-r--r--features/provider/execute/run_commands.feature1
-rw-r--r--features/provider/file/manage_files.feature1
-rw-r--r--features/search/search_data.feature20
-rw-r--r--features/steps/cookbook_steps.rb2
-rw-r--r--features/steps/couchdb_steps.rb33
-rw-r--r--features/steps/fixture_steps.rb112
-rw-r--r--features/steps/node_steps.rb4
-rw-r--r--features/steps/request_steps.rb96
-rw-r--r--features/steps/response_steps.rb61
-rw-r--r--features/steps/run_client_steps.rb9
-rw-r--r--features/steps/run_solo.rb2
-rw-r--r--features/steps/webrat_steps.rb2
-rw-r--r--features/support/env.rb110
-rwxr-xr-xopscode-start76
353 files changed, 10671 insertions, 3054 deletions
diff --git a/.gitignore b/.gitignore
index ee6b386eef..9401920165 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,7 +9,8 @@ examples/openid-cstore
examples/openid-db
chef/pkg
chef-server/pkg
-chef-server-slice/pkg
+chef-server-webui/pkg
+chef-server-api/pkg
chef/log
chef-server/log
log
@@ -18,3 +19,5 @@ couchdb.stdout
features/data/tmp/**
*.swp
features/data/cookbooks/**/metadata.json
+features/data/solr/**
+erl_crash.dump
diff --git a/Rakefile b/Rakefile
index ad5fe113ff..7d308ed0f9 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,4 +1,4 @@
-gems = %w[chef chef-server-slice chef-server]
+gems = %w[chef chef-server-api chef-server-webui chef-server chef-solr]
require 'rubygems'
require 'cucumber/rake/task'
@@ -26,71 +26,111 @@ end
desc "Run the rspec tests"
task :spec do
Dir.chdir("chef") { sh "rake spec" }
+ Dir.chdir("chef-solr") { sh "rake spec" }
end
task :default => :spec
-def start_dev_environment(type="normal")
- @couchdb_server_pid = nil
- @chef_server_pid = nil
- @chef_indexer_pid = nil
- @stompserver_pid = nil
-
- ccid = fork
- if ccid
- @couchdb_server_pid = ccid
+def start_couchdb(type="normal")
+ @couchdb_server_pid = nil
+ cid = fork
+ if cid
+ @couchdb_server_pid = cid
else
exec("couchdb")
end
+end
- scid = fork
- if scid
- @stompserver_pid = scid
+def start_rabbitmq(type="normal")
+ @rabbitmq_server_pid = nil
+ cid = fork
+ if cid
+ @rabbitmq_server_pid = cid
else
- exec("stompserver")
+ exec("rabbitmq-server")
end
+end
- mcid = fork
- if mcid # parent
- @chef_indexer_pid = mcid
- else # child
+def start_chef_solr(type="normal")
+ @chef_solr_pid = nil
+ cid = fork
+ if cid
+ @chef_solr_pid = cid
+ else
case type
when "normal"
- exec("chef-indexer -l debug")
+ exec("./chef-solr/bin/chef-solr -l debug")
when "features"
- exec("chef-indexer -c #{File.join(File.dirname(__FILE__), "features", "data", "config", "server.rb")} -l debug")
+ exec("./chef-solr/bin/chef-solr -c #{File.join(File.dirname(__FILE__), "features", "data", "config", "server.rb")} -l debug")
end
end
+end
+def start_chef_solr_indexer(type="normal")
+ @chef_solr_indexer = nil
+ cid = fork
+ if cid
+ @chef_solr_indexer_pid = cid
+ else
+ case type
+ when "normal"
+ exec("./chef-solr/bin/chef-solr-indexer -l debug")
+ when "features"
+ exec("./chef-solr/bin/chef-solr-indexer -c #{File.join(File.dirname(__FILE__), "features", "data", "config", "server.rb")} -l debug")
+ end
+ end
+end
+
+def start_chef_server(type="normal")
+ @chef_server_pid = nil
mcid = fork
if mcid # parent
@chef_server_pid = mcid
else # child
case type
when "normal"
- exec("chef-server -l debug -N -c 2")
+ exec("./chef-server/bin/chef-server -a thin -l debug -N")
when "features"
- exec("chef-server -C #{File.join(File.dirname(__FILE__), "features", "data", "config", "server.rb")} -l debug -N -c 2")
-
+ exec("./chef-server/bin/chef-server -a thin -C #{File.join(File.dirname(__FILE__), "features", "data", "config", "server.rb")} -l debug -N")
end
end
+end
- puts "Running Chef at #{@chef_server_pid}"
- puts "Running Chef Indexer at #{@chef_indexer_pid}"
+def start_dev_environment(type="normal")
+ start_couchdb(type)
+ start_rabbitmq(type)
+ start_chef_solr(type)
+ start_chef_solr_indexer(type)
+ start_chef_server(type)
puts "Running CouchDB at #{@couchdb_server_pid}"
- puts "Running Stompserver at #{@stompserver_pid}"
+ puts "Running RabbitMQ at #{@rabbitmq_server_pid}"
+ puts "Running Chef Solr at #{@chef_solr_pid}"
+ puts "Running Chef Solr Indexer at #{@chef_solr_indexer_pid}"
+ puts "Running Chef at #{@chef_server_pid}"
end
def stop_dev_environment
- puts "Stopping CouchDB"
- Process.kill("KILL", @couchdb_server_pid)
- puts "Stopping Stomp server"
- Process.kill("KILL", @stompserver_pid)
- puts "Stopping Chef Server"
- Process.kill("INT", @chef_server_pid)
- puts "Stopping Chef Indexer"
- Process.kill("INT", @chef_indexer_pid)
- puts "\nCouchDB, Stomp, Chef Server and Chef Indexer killed - have a nice day!"
+ if @chef_server_pid
+ puts "Stopping Chef"
+ Process.kill("KILL", @chef_server_pid)
+ end
+ if @chef_solr_pid
+ puts "Stopping Chef Solr"
+ Process.kill("INT", @chef_solr_pid)
+ end
+ if @chef_solr_indexer_pid
+ puts "Stopping Chef Solr Indexer"
+ Process.kill("INT", @chef_solr_indexer_pid)
+ end
+ if @couchdb_server_pid
+ puts "Stopping CouchDB"
+ Process.kill("KILL", @couchdb_server_pid)
+ end
+ if @rabbitmq_server_pid
+ puts "Stopping RabbitMQ"
+ Process.kill("KILL", @rabbitmq_server_pid)
+ end
+ puts "Have a nice day!"
end
def wait_for_ctrlc
@@ -106,7 +146,7 @@ def wait_for_ctrlc
end
desc "Run a Devel instance of Chef"
-task :dev => "dev:install" do
+task :dev do
start_dev_environment
wait_for_ctrlc
end
@@ -114,12 +154,77 @@ end
namespace :dev do
desc "Install a test instance of Chef for doing features against"
task :features do
- gems.each do |dir|
- Dir.chdir(dir) { sh "rake install" }
- end
start_dev_environment("features")
wait_for_ctrlc
end
+
+ namespace :features do
+
+ namespace :start do
+ desc "Start CouchDB for testing"
+ task :couchdb do
+ start_couchdb("features")
+ wait_for_ctrlc
+ end
+
+ desc "Start RabbitMQ for testing"
+ task :rabbitmq do
+ start_rabbitmq("features")
+ wait_for_ctrlc
+ end
+
+ desc "Start Chef Solr for testing"
+ task :chef_solr do
+ start_chef_solr("features")
+ wait_for_ctrlc
+ end
+
+ desc "Start Chef Solr Indexer for testing"
+ task :chef_solr_indexer do
+ start_chef_solr_indexer("features")
+ wait_for_ctrlc
+ end
+
+ desc "Start Chef Server for testing"
+ task :chef_server do
+ start_chef_server("features")
+ wait_for_ctrlc
+ end
+
+ end
+ end
+
+ namespace :start do
+ desc "Start CouchDB"
+ task :couchdb do
+ start_couchdb
+ wait_for_ctrlc
+ end
+
+ desc "Start RabbitMQ"
+ task :rabbitmq do
+ start_rabbitmq
+ wait_for_ctrlc
+ end
+
+ desc "Start Chef Solr"
+ task :chef_solr do
+ start_chef_solr
+ wait_for_ctrlc
+ end
+
+ desc "Start Chef Solr Indexer"
+ task :chef_solr_indexer do
+ start_chef_solr_indexer
+ wait_for_ctrlc
+ end
+
+ desc "Start Chef Server"
+ task :chef_server do
+ start_chef_server
+ wait_for_ctrlc
+ end
+ end
end
Cucumber::Rake::Task.new(:features) do |t|
@@ -127,12 +232,13 @@ Cucumber::Rake::Task.new(:features) do |t|
end
namespace :features do
+ desc "Run cucumber tests for the REST API"
Cucumber::Rake::Task.new(:api) do |t|
t.profile = "api"
end
namespace :api do
- [ :nodes, :roles].each do |api|
+ [ :nodes, :roles ].each do |api|
Cucumber::Rake::Task.new(api) do |apitask|
apitask.profile = "api_#{api.to_s}"
end
@@ -144,8 +250,52 @@ namespace :features do
end
end
end
+
+ namespace :cookbooks do
+ desc "Run cucumber tests for the cookbooks portion of the REST API"
+ Cucumber::Rake::Task.new(:cookbooks) do |t|
+ t.profile = "api_cookbooks"
+ end
+
+ Cucumber::Rake::Task.new(:cookbook_tarballs) do |t|
+ t.profile = "api_cookbooks_tarballs"
+ end
+ end
+
+ namespace :data do
+ desc "Run cucumber tests for the data portion of the REST API"
+ Cucumber::Rake::Task.new(:data) do |t|
+ t.profile = "api_data"
+ end
+
+ desc "Run cucumber tests for deleting data via the REST API"
+ Cucumber::Rake::Task.new(:delete) do |t|
+ t.profile = "api_data_delete"
+ end
+ desc "Run cucumber tests for adding items via the REST API"
+ Cucumber::Rake::Task.new(:item) do |t|
+ t.profile = "api_data_item"
+ end
+ end
+
+ namespace :search do
+ desc "Run cucumber tests for searching via the REST API"
+ Cucumber::Rake::Task.new(:search) do |t|
+ t.profile = "api_search"
+ end
+
+ desc "Run cucumber tests for listing search endpoints via the REST API"
+ Cucumber::Rake::Task.new(:list) do |t|
+ t.profile = "api_search_list"
+ end
+ desc "Run cucumber tests for searching via the REST API"
+ Cucumber::Rake::Task.new(:show) do |t|
+ t.profile = "api_search_show"
+ end
+ end
end
+ desc "Run cucumber tests for the chef client"
Cucumber::Rake::Task.new(:client) do |t|
t.profile = "client"
end
@@ -156,6 +306,17 @@ namespace :features do
end
end
+ desc "Run cucumber tests for the cookbooks"
+ Cucumber::Rake::Task.new(:cookbooks) do |t|
+ t.profile = "cookbooks"
+ end
+
+ desc "Run cucumber tests for the recipe language"
+ Cucumber::Rake::Task.new(:language) do |t|
+ t.profile = "language"
+ end
+
+ desc "Run cucumber tests for searching in recipes"
Cucumber::Rake::Task.new(:search) do |t|
t.profile = "search"
end
@@ -170,12 +331,34 @@ namespace :features do
end
end
+ desc "Run cucumber tests for providers"
+ Cucumber::Rake::Task.new(:provider) do |t|
+ t.profile = "provider"
+ end
+
namespace :provider do
+ desc "Run cucumber tests for directory resources"
+ Cucumber::Rake::Task.new(:directory) do |t|
+ t.profile = "provider_directory"
+ end
+
+ desc "Run cucumber tests for execute resources"
+ Cucumber::Rake::Task.new(:execute) do |t|
+ t.profile = "provider_execute"
+ end
+
+ desc "Run cucumber tests for file resources"
+ Cucumber::Rake::Task.new(:execute) do |t|
+ t.profile = "provider_file"
+ end
+
+ desc "Run cucumber tests for remote_file resources"
Cucumber::Rake::Task.new(:remote_file) do |t|
t.profile = "provider_remote_file"
end
namespace :package do
+ desc "Run cucumber tests for macports packages"
Cucumber::Rake::Task.new(:macports) do |t|
t.profile = "provider_package_macports"
end
diff --git a/chef-server-slice/LICENSE b/chef-server-api/LICENSE
index 11069edd79..11069edd79 100644
--- a/chef-server-slice/LICENSE
+++ b/chef-server-api/LICENSE
diff --git a/chef-server-api/README.rdoc b/chef-server-api/README.rdoc
new file mode 100644
index 0000000000..a7a4ea8dea
--- /dev/null
+++ b/chef-server-api/README.rdoc
@@ -0,0 +1,94 @@
+= chef
+
+* http://www.opscode.com/chef
+
+== DESCRIPTION:
+
+Chef is a systems management framework masquerading as a configuration management tool.
+
+I'm in ur netwerk, cookin up yer servers. :)
+
+== REQUIREMENTS:
+
+chef:
+
+* ruby-openid
+* json
+* erubis
+* extlib
+* stomp
+* ohai
+
+chef-server and the chefserverslice (merb slice), same requires as chef above, plus:
+
+* stompserver
+* ferret
+* merb-core
+* merb-haml
+* mongrel
+* haml
+* ruby-openid
+* syntax
+
+Interim Note:
+
+Once the {chef,chef-server,chefserverslice}.gem set is installed, do the following:
+
+ > cd /Library/Ruby/Gems/1.8/gems/chef-server<xxx> # or wherever your gems land
+ > sudo rake slices:chefserverslice:install
+
+This installs the chefserverslice into the chef-server merb application
+
+External Servers:
+
+* stompserver (for easy stomp mq testing)
+* CouchDB
+
+== INSTALL:
+
+Install all of the above. To fire up a develpment environment, do the following:
+
+ * Start CouchDB with 'couchdb'
+ * Start stompserver with 'stompserver'
+ * Start chef-indexer with:
+
+ chef-indexer -l debug
+
+ * Start chef-server:
+
+ chef-server -N -c 2
+
+ * Test run chef to begin node registration:
+
+ sudo ./bin/chef-client
+
+ * Validate the node registration:
+
+ Visit http://localhost:4000
+ Login, enter an openid URL (see http://openid.net/get/)
+ Registrations, click Validate
+
+ * Test run chef with:
+
+ chef-client
+
+== LICENSE:
+
+Chef - A configuration management system
+
+Author:: Adam Jacob (<adam@opscode.com>)
+Copyright:: Copyright (c) 2008 Opscode, Inc.
+License:: Apache License, Version 2.0
+
+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.
+
diff --git a/chef-server-slice/Rakefile b/chef-server-api/Rakefile
index 2457cfd0a4..3ae75760ff 100644
--- a/chef-server-slice/Rakefile
+++ b/chef-server-api/Rakefile
@@ -1,15 +1,11 @@
require 'rubygems'
require 'rake/gempackagetask'
-begin
- require 'merb-core'
- require 'merb-core/tasks/merb'
-rescue LoadError
- nil
-end
+require 'merb-core'
+require 'merb-core/tasks/merb'
-GEM_NAME = "chef-server-slice"
-CHEF_SERVER_VERSION="0.7.9"
+GEM_NAME = "chef-server-api"
+CHEF_SERVER_VERSION="0.8.0"
AUTHOR = "Opscode"
EMAIL = "chef@opscode.com"
HOMEPAGE = "http://wiki.opscode.com/display/chef"
@@ -28,13 +24,22 @@ spec = Gem::Specification.new do |s|
s.email = EMAIL
s.homepage = HOMEPAGE
- %w{stomp stompserver ferret
- merb-core merb-haml merb-assets
- merb-helpers mongrel haml
- ruby-openid json coderay}.each { |gem| s.add_dependency gem }
-
+ ["merb-slices",
+ "stomp",
+ "stompserver",
+ "ferret",
+ "merb-core",
+ "merb-haml",
+ "merb-assets",
+ "merb-helpers",
+ "mongrel",
+ "haml",
+ "ruby-openid",
+ "json",
+ "syntax",].each { |g| s.add_dependency g}
+
s.require_path = 'lib'
- s.files = %w(LICENSE README.rdoc) + Dir.glob("{app,config,lib,public}/**/*")
+ s.files = %w(LICENSE README.rdoc Rakefile) + Dir.glob("{config,lib,spec,app,public,stubs}/**/*")
end
Rake::GemPackageTask.new(spec) do |pkg|
diff --git a/chef-server-api/app/controllers/application.rb b/chef-server-api/app/controllers/application.rb
new file mode 100644
index 0000000000..b461b2098b
--- /dev/null
+++ b/chef-server-api/app/controllers/application.rb
@@ -0,0 +1,287 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Christopher Brown (<cb@opscode.com>)
+# Author:: Christopher Walters (<cw@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require 'rubygems'
+require "chef" / "mixin" / "checksum"
+require "chef" / "cookbook_loader"
+
+class ChefServerApi::Application < Merb::Controller
+ include AuthenticateEvery
+ include Chef::Mixin::Checksum
+
+ include Mixlib::Auth::AuthHelper
+ include Merb::ChefServerApi::ApplicationHelper::AuthenticateEvery
+
+ controller_for_slice
+
+ # Generate the absolute url for a slice - takes the slice's :path_prefix into account.
+ #
+ # @param slice_name<Symbol>
+ # The name of the slice - in identifier_sym format (underscored).
+ # @param *args<Array[Symbol,Hash]>
+ # There are several possibilities regarding arguments:
+ # - when passing a Hash only, the :default route of the current
+ # slice will be used
+ # - when a Symbol is passed, it's used as the route name
+ # - a Hash with additional params can optionally be passed
+ #
+ # @return <String> A uri based on the requested slice.
+ #
+ # @example absolute_slice_url(:awesome, :format => 'html')
+ # @example absolute_slice_url(:forum, :posts, :format => 'xml')
+ def absolute_slice_url(slice_name, *args)
+ options = {}
+ if args.length == 1 && args[0].respond_to?(:keys)
+ options = args[0]
+ else
+ options = extract_options_from_args!(args) || {}
+ end
+ protocol = options.delete(:protocol) || request.protocol
+ host = options.delete(:host) || request.host
+
+ protocol + "://" + host + slice_url(slice_name, *args)
+ end
+
+ def fix_up_node_id
+ if params.has_key?(:id)
+ params[:id].gsub!(/_/, '.')
+ end
+ end
+
+ def escape_node_id(arg=nil)
+ unless arg
+ arg = params[:id] if params.has_key?(:id)
+ end
+ arg.gsub(/\./, '_')
+ end
+
+ def login_required
+ if session[:openid]
+ return session[:openid]
+ else
+ self.store_location
+ throw(:halt, :access_denied)
+ end
+ end
+
+ def authorized_node
+ if session[:level] == :admin
+ Chef::Log.debug("Authorized as Administrator")
+ true
+ elsif session[:level] == :node
+ Chef::Log.debug("Authorized as node")
+ if session[:node_name] == params[:id].gsub(/\./, '_')
+ true
+ else
+ raise(
+ Unauthorized,
+ "You are not the correct node for this action: #{session[:node_name]} instead of #{params[:id]}"
+ )
+ end
+ else
+ Chef::Log.debug("Unauthorized")
+ raise Unauthorized, "You are not allowed to take this action."
+ end
+ end
+
+ # Store the URI of the current request in the session.
+ #
+ # We can return to this location by calling #redirect_back_or_default.
+ def store_location
+ session[:return_to] = request.uri
+ end
+
+ # Redirect to the URI stored by the most recent store_location call or
+ # to the passed default.
+ def redirect_back_or_default(default)
+ loc = session[:return_to] || default
+ session[:return_to] = nil
+ redirect loc
+ end
+
+ def access_denied
+ case content_type
+ when :html
+ store_location
+ redirect slice_url(:openid_consumer), :message => { :error => "You don't have access to that, please login."}
+ else
+ raise Unauthorized, "You must authenticate first!"
+ end
+ end
+
+ # Load a cookbook and return a hash with a list of all the files of a
+ # given segment (attributes, recipes, definitions, libraries)
+ #
+ # === Parameters
+ # cookbook_id<String>:: The cookbook to load
+ # segment<Symbol>:: :attributes, :recipes, :definitions, :libraries
+ #
+ # === Returns
+ # <Hash>:: A hash consisting of the short name of the file in :name, and the full path
+ # to the file in :file.
+ def load_cookbook_segment(cookbook, segment)
+ files_list = segment_files(segment, cookbook)
+
+ files = Hash.new
+ files_list.each do |f|
+ full = File.expand_path(f)
+ name = File.basename(full)
+ files[name] = {
+ :name => name,
+ :file => full,
+ }
+ end
+ files
+ end
+
+ def segment_files(segment, cookbook)
+ files_list = nil
+ case segment
+ when :attributes
+ files_list = cookbook.attribute_files
+ when :recipes
+ files_list = cookbook.recipe_files
+ when :definitions
+ files_list = cookbook.definition_files
+ when :libraries
+ files_list = cookbook.lib_files
+ when :files
+ files_list = cookbook.remote_files
+ when :templates
+ files_list = cookbook.template_files
+ else
+ raise ArgumentError, "segment must be one of :attributes, :recipes, :definitions, :remote_files, :template_files or :libraries"
+ end
+ files_list
+ end
+
+ def specific_cookbooks(node_name, cl)
+ valid_cookbooks = Hash.new
+ begin
+ node = Chef::Node.load(node_name, @couchdb)
+ recipes, default_attrs, override_attrs = node.run_list.expand('couchdb', @couchdb)
+ rescue Net::HTTPServerException
+ recipes = []
+ end
+ recipes.each do |recipe|
+ valid_cookbooks = expand_cookbook_deps(valid_cookbooks, cl, recipe)
+ end
+ valid_cookbooks
+ end
+
+ def expand_cookbook_deps(valid_cookbooks, cl, recipe)
+ cookbook = recipe
+ if recipe =~ /^(.+)::/
+ cookbook = $1
+ end
+ Chef::Log.debug("Node requires #{cookbook}")
+ valid_cookbooks[cookbook] = true
+ cl.metadata[cookbook.to_sym].dependencies.each do |dep, versions|
+ expand_cookbook_deps(valid_cookbooks, cl, dep) unless valid_cookbooks[dep]
+ end
+ valid_cookbooks
+ end
+
+ def load_cookbook_files(cookbook)
+ response = {
+ :recipes => Array.new,
+ :definitions => Array.new,
+ :libraries => Array.new,
+ :attributes => Array.new,
+ :files => Array.new,
+ :templates => Array.new
+ }
+ [ :recipes, :definitions, :libraries, :attributes, :files, :templates ].each do |segment|
+ segment_files(segment, cookbook).each do |sf|
+ next if File.directory?(sf)
+ file_name = nil
+ file_url = nil
+ file_specificity = nil
+
+ if segment == :templates || segment == :files
+ mo = sf.match("#{Merb::Config.cookbook_cache_path}/[^/]+/#{cookbook.name}/#{segment}/(.+?)/(.+)")
+ specificity = mo[1]
+ file_name = mo[2]
+ url_options = { :cookbook_id => cookbook.name.to_s, :segment => segment, :id => file_name, :organization_id => @organization_id }
+
+ case specificity
+ when "default"
+ when /^host-(.+)$/
+ url_options[:fqdn] = $1
+ when /^(.+)-(.+)$/
+ url_options[:platform] = $1
+ url_options[:version] = $2
+ when /^(.+)$/
+ url_options[:platform] = $1
+ end
+
+ file_specificity = specificity
+ file_url = absolute_slice_url(:organization_cookbook_segment, url_options)
+ else
+ mo = sf.match("#{Merb::Config.cookbook_cache_path}/[^/]+/#{cookbook.name}/#{segment}/(.+)")
+ file_name = mo[1]
+ url_options = { :cookbook_id => cookbook.name.to_s, :segment => segment, :id => file_name, :organization_id => @organization_id }
+ file_url = absolute_slice_url(:organization_cookbook_segment, url_options)
+ end
+ rs = {
+ :name => file_name,
+ :uri => file_url,
+ :checksum => checksum(sf)
+ }
+ rs[:specificity] = file_specificity if file_specificity
+ response[segment] << rs
+ end
+ end
+ response
+ end
+
+ def load_all_files(node_name=nil)
+ latest_cookbooks = get_all_latest_cookbooks
+ Merb.logger.debug "Retrieved latest cookbooks: #{latest_cookbooks.inspect}"
+ policies = latest_cookbooks.map{|doc| Chef::CookbookPolicy.new(doc['display_name'], doc['_id']) }
+ cl = Chef::CachedCookbookLoader.new(policies)
+
+ valid_cookbooks = node_name ? specific_cookbooks(node_name, cl) : {}
+ cookbook_list = Hash.new
+ cl.each do |cookbook|
+ if node_name
+ next unless valid_cookbooks[cookbook.name.to_s]
+ end
+ cookbook_list[cookbook.name.to_s] = load_cookbook_files(cookbook)
+ end
+ cookbook_list
+ end
+
+ def get_available_recipes
+ cl = Chef::CookbookLoader.new
+ available_recipes = cl.sort{ |a,b| a.name.to_s <=> b.name.to_s }.inject([]) do |result, element|
+ element.recipes.sort.each do |r|
+ if r =~ /^(.+)::default$/
+ result << $1
+ else
+ result << r
+ end
+ end
+ result
+ end
+ available_recipes
+ end
+
+end
diff --git a/chef-server-api/app/controllers/cookbooks.rb b/chef-server-api/app/controllers/cookbooks.rb
new file mode 100644
index 0000000000..d372699c05
--- /dev/null
+++ b/chef-server-api/app/controllers/cookbooks.rb
@@ -0,0 +1,123 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Christopher Brown (<cb@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require 'chef' / 'cookbook_loader'
+require 'chef' / 'cookbook' / 'metadata'
+
+class ChefServerApi::Cookbooks < ChefServerApi::Application
+
+ provides :json
+
+ before :authenticate_every
+
+ include Chef::Mixin::Checksum
+
+ def index
+ cl = Chef::CookbookLoader.new
+ cookbook_list = Hash.new
+ cl.each do |cookbook|
+ cookbook_list[cookbook.name] = absolute_slice_url(:cookbook, :id => cookbook.name.to_s)
+ end
+ display cookbook_list
+ end
+
+ def show
+ cl = Chef::CookbookLoader.new
+ cookbook = cl[params[:id]]
+ raise NotFound unless cookbook
+ results = load_cookbook_files(cookbook)
+ results[:name] = cookbook.name.to_s
+ results[:metadata] = cl.metadata[cookbook.name.to_sym]
+ display results
+ end
+
+ def show_segment
+ cl = Chef::CookbookLoader.new
+ cookbook = cl[params[:cookbook_id]]
+ raise NotFound unless cookbook
+ cookbook_files = load_cookbook_files(cookbook)
+ raise NotFound unless cookbook_files.has_key?(params[:segment].to_sym)
+
+ if params[:id]
+ case params[:segment]
+ when "templates","files"
+ serve_segment_preferred(cookbook, params[:segment], cookbook_files[params[:segment].to_sym])
+ else
+ serve_segment_file(cookbook, params[:segment], cookbook_files[params[:segment].to_sym])
+ end
+ else
+ display cookbook_files[params[:segment].to_sym]
+ end
+ end
+
+ def serve_segment_preferred(cookbook, segment, files)
+
+ to_send = nil
+
+ preferences = [
+ "host-#{params[:fqdn]}",
+ "#{params[:platform]}-#{params[:version]}",
+ "#{params[:platform]}",
+ "default"
+ ]
+
+ preferences.each do |pref|
+ unless to_send
+ to_send = files.detect { |file| Chef::Log.debug("#{pref.inspect} #{file.inspect}"); file[:name] == params[:id] && file[:specificity] == pref }
+ end
+ end
+
+ raise NotFound, "Cannot find a suitable #{segment} file!" unless to_send
+ current_checksum = to_send[:checksum]
+ Chef::Log.debug("#{to_send[:name]} Client Checksum: #{params[:checksum]}, Server Checksum: #{current_checksum}")
+ if current_checksum == params[:checksum]
+ raise NotModified, "File #{to_send[:name]} has not changed"
+ else
+ file_name = nil
+ segment_files(segment.to_sym, cookbook).each do |f|
+ if f =~ /#{to_send[:specificity]}\/#{to_send[:name]}$/
+ file_name = File.expand_path(f)
+ break
+ end
+ end
+ raise NotFound, "Cannot find the real file for #{to_send[:specificity]} #{to_send[:name]} - this is a 42 error (shouldn't ever happen)" unless file_name
+ send_file(file_name)
+ end
+ end
+
+ def serve_segment_file(cookbook, segment, files)
+ to_send = files.detect { |f| f[:name] == params[:id] }
+ raise NotFound, "Cannot find a suitable #{segment} file!" unless to_send
+ current_checksum = to_send[:checksum]
+ Chef::Log.debug("#{to_send[:name]} Client Checksum: #{params[:checksum]}, Server Checksum: #{current_checksum}")
+ if current_checksum == params[:checksum]
+ raise NotModified, "File #{to_send[:name]} has not changed"
+ else
+ file_name = nil
+ segment_files(segment.to_sym, cookbook).each do |f|
+ next unless File.basename(f) == to_send[:name]
+ file_name = File.expand_path(f)
+ end
+ raise NotFound, "Cannot find the real file for #{to_send[:name]} - this is a 42 error (shouldn't ever happen)" unless file_name
+ send_file(file_name)
+ end
+ end
+
+end
+
diff --git a/chef-server-api/app/controllers/data.rb b/chef-server-api/app/controllers/data.rb
new file mode 100644
index 0000000000..732e6d6c1d
--- /dev/null
+++ b/chef-server-api/app/controllers/data.rb
@@ -0,0 +1,73 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Christopher Brown (<cb@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require 'chef/data_bag'
+
+class ChefServerApi::Data < ChefServerApi::Application
+
+ provides :json
+
+ before :authenticate_every
+
+ def index
+ @bag_list = Chef::DataBag.list(false)
+ display(@bag_list.collect { |b| absolute_slice_url(:organization_datum, :id => b, :organization_id => @organization_id) })
+ end
+
+ def show
+ begin
+ @data_bag = Chef::DataBag.load(params[:id])
+ rescue Chef::Exceptions::CouchDBNotFound => e
+ raise NotFound, "Cannot load data bag #{params[:id]}"
+ end
+ display(@data_bag.list.collect { |i| absolute_slice_url(:organization_data_bag_item, :data_bag_id => @data_bag.name, :id => i) })
+ end
+
+ def create
+ @data_bag = nil
+ if params.has_key?("inflated_object")
+ @data_bag = params["inflated_object"]
+ else
+ @data_bag = Chef::DataBag.new
+ @data_bag.name(params["name"])
+ end
+ exists = true
+ begin
+ Chef::DataBag.load(@data_bag.name)
+ rescue Chef::Exceptions::CouchDBNotFound
+ exists = false
+ end
+ raise Forbidden, "Data bag already exists" if exists
+ self.status = 201
+ @data_bag.save
+ display({ :uri => absolute_slice_url(:organization_datum, :id => @data_bag.name, :organization_id => @organization_id) })
+ end
+
+ def destroy
+ begin
+ @data_bag = Chef::DataBag.load(params[:id])
+ rescue Chef::Exceptions::CouchDBNotFound => e
+ raise NotFound, "Cannot load data bag #{params[:id]}"
+ end
+ @data_bag.destroy
+ @data_bag.couchdb_rev = nil
+ display @data_bag
+ end
+
+end
diff --git a/chef-server-api/app/controllers/data_item.rb b/chef-server-api/app/controllers/data_item.rb
new file mode 100644
index 0000000000..7ac388b2a1
--- /dev/null
+++ b/chef-server-api/app/controllers/data_item.rb
@@ -0,0 +1,81 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Christopher Brown (<cb@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require 'chef/data_bag'
+require 'chef/data_bag_item'
+
+class ChefServerApi::DataItem < ChefServerApi::Application
+
+ provides :json
+
+ before :populate_data_bag
+ before :authenticate_every
+
+ def populate_data_bag
+ begin
+ @data_bag = Chef::DataBag.load(params[:data_bag_id])
+ rescue Chef::Exceptions::CouchDBNotFound => e
+ raise NotFound, "Cannot load data bag #{params[:data_bag_id]}"
+ end
+ end
+
+ def show
+ begin
+ @data_bag_item = Chef::DataBagItem.load(params[:data_bag_id], params[:id])
+ rescue Chef::Exceptions::CouchDBNotFound => e
+ raise NotFound, "Cannot load data bag #{params[:data_bag_id]} item #{params[:id]}"
+ end
+ display @data_bag_item.raw_data
+ end
+
+ def create
+ raw_data = nil
+ if params.has_key?("inflated_object")
+ raw_data = params["inflated_object"].raw_data
+ else
+ raw_data = params
+ raw_data.delete(:organization_id)
+ raw_data.delete(:action)
+ raw_data.delete(:controller)
+ raw_data.delete(:data_bag_id)
+ end
+ @data_bag_item = nil
+ begin
+ @data_bag_item = Chef::DataBagItem.load(@data_bag.name, params[:id])
+ rescue Chef::Exceptions::CouchDBNotFound
+ @data_bag_item = Chef::DataBagItem.new
+ @data_bag_item.data_bag(@data_bag.name)
+ end
+ @data_bag_item.raw_data = raw_data
+ @data_bag_item.save
+ display @data_bag_item.raw_data
+ end
+
+ def destroy
+ begin
+ @data_bag_item = Chef::DataBagItem.load(params[:data_bag_id], params[:id])
+ rescue Chef::Exceptions::CouchDBNotFound => e
+ raise NotFound, "Cannot load data bag #{params[:data_bag_id]} item #{params[:id]}"
+ end
+ @data_bag_item.destroy
+ @data_bag_item.couchdb_rev = nil
+ display @data_bag_item
+ end
+
+end
diff --git a/chef-server-slice/app/controllers/exceptions.rb b/chef-server-api/app/controllers/exceptions.rb
index f3f43d25da..76428895f2 100644
--- a/chef-server-slice/app/controllers/exceptions.rb
+++ b/chef-server-api/app/controllers/exceptions.rb
@@ -17,12 +17,19 @@
# limitations under the License.
#
-class ChefServerSlice::Exceptions < ChefServerSlice::Application
-
- provides :html, :json
+class Exceptions < ChefServerApi::Application
+ provides :json
+
+ def not_acceptable
+ if request.accept =~ /application\/json/
+ display({ "error" => request.exceptions })
+ else
+ render
+ end
+ end
+
def standard_error
- Merb.logger.warn(request.content_type)
if request.accept =~ /application\/json/
display({ "error" => request.exceptions })
else
diff --git a/chef-server-api/app/controllers/main.rb b/chef-server-api/app/controllers/main.rb
new file mode 100644
index 0000000000..ecd11347dc
--- /dev/null
+++ b/chef-server-api/app/controllers/main.rb
@@ -0,0 +1,18 @@
+class ChefServerApi::Main < ChefServerApi::Application
+
+ before :authenticate_every
+ provides :html, :json
+
+ def index
+ display(
+ {
+ absolute_slice_url(:nodes) => "Manage Nodes",
+ absolute_slice_url(:roles) => "Manage Roles",
+ absolute_slice_url(:cookbooks) => "Manage Cookbooks",
+ absolute_slice_url(:data) => "Manage Data Bags",
+ absolute_slice_url(:search) => "Search"
+ }
+ )
+ end
+
+end
diff --git a/chef-server-api/app/controllers/nodes.rb b/chef-server-api/app/controllers/nodes.rb
new file mode 100644
index 0000000000..eb8eb3b0c7
--- /dev/null
+++ b/chef-server-api/app/controllers/nodes.rb
@@ -0,0 +1,95 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Christopher Brown (<cb@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require 'chef' / 'node'
+
+class ChefServerApi::Nodes < ChefServerApi::Application
+
+ provides :json
+
+ before :authenticate_every
+ before :fix_up_node_id
+
+ def index
+ @node_list = Chef::Node.list
+ display(@node_list.collect { |n| absolute_slice_url(:node, escape_node_id(n)) })
+ end
+
+ def show
+ begin
+ @node = Chef::Node.load(params[:id])
+ rescue Net::HTTPServerException => e
+ raise NotFound, "Cannot load node #{params[:id]}"
+ end
+ @node.couchdb_rev = nil
+ display @node
+ end
+
+ def create
+ @node = params["inflated_object"]
+ exists = true
+ begin
+ Chef::Node.load(@node.name)
+ rescue Net::HTTPServerException
+ exists = false
+ end
+ raise Forbidden, "Node already exists" if exists
+ self.status = 201
+ @node.save
+ display({ :uri => absolute_slice_url(:node, escape_node_id(@node.name)) })
+ end
+
+ def update
+ begin
+ @node = Chef::Node.load(params[:id])
+ rescue Net::HTTPServerException => e
+ raise NotFound, "Cannot load node #{params[:id]}"
+ end
+
+ updated = params['inflated_object']
+ @node.run_list.reset(updated.run_list)
+ @node.attribute = updated.attribute
+ @node.save
+ @node.couchdb_rev = nil
+ display(@node)
+ end
+
+ def destroy
+ begin
+ @node = Chef::Node.load(params[:id])
+ rescue Net::HTTPServerException => e
+ raise NotFound, "Cannot load node #{params[:id]}"
+ end
+ @node.destroy
+ @node.couchdb_rev = nil
+ display @node
+ end
+
+ def cookbooks
+ begin
+ @node = Chef::Node.load(params[:id])
+ rescue Net::HTTPServerException => e
+ raise NotFound, "Cannot load node #{params[:id]}"
+ end
+
+ display(load_all_files(params[:id]))
+ end
+
+end
+
diff --git a/chef-server-api/app/controllers/roles.rb b/chef-server-api/app/controllers/roles.rb
new file mode 100644
index 0000000000..08661c4d23
--- /dev/null
+++ b/chef-server-api/app/controllers/roles.rb
@@ -0,0 +1,71 @@
+require 'chef/role'
+
+class ChefServerApi::Roles < ChefServerApi::Application
+ provides :json
+
+ before :authenticate_every
+
+ # GET /roles
+ def index
+ @role_list = Chef::Role.list(true)
+ display(@role_list.collect { |r| absolute_slice_url(:organization_role, :id => r.name, :organization_id => @organization_id) })
+ end
+
+ # GET /roles/:id
+ def show
+ begin
+ @role = Chef::Role.load(params[:id])
+ rescue Chef::Exceptions::CouchDBNotFound => e
+ raise NotFound, "Cannot load role #{params[:id]}"
+ end
+ @role.couchdb_rev = nil
+ display @role
+ end
+
+ # POST /roles
+ def create
+ @role = params["inflated_object"]
+ exists = true
+ begin
+ Chef::Role.load(@role.name)
+ rescue Chef::Exceptions::CouchDBNotFound
+ exists = false
+ end
+ raise Forbidden, "Role already exists" if exists
+
+ @role.save
+
+ self.status = 201
+ display({ :uri => absolute_slice_url(:organization_role, :id => @role.name, :organization_id => @organization_id) })
+ end
+
+ # PUT /roles/:id
+ def update
+ begin
+ @role = Chef::Role.load(params[:id])
+ rescue Chef::Exceptions::CouchDBNotFound => e
+ raise NotFound, "Cannot load role #{params[:id]}"
+ end
+
+ @role.description(params["inflated_object"].description)
+ @role.recipes(params["inflated_object"].recipes)
+ @role.default_attributes(params["inflated_object"].default_attributes)
+ @role.override_attributes(params["inflated_object"].override_attributes)
+ @role.save
+ self.status = 200
+ @role.couchdb_rev = nil
+ display(@role)
+ end
+
+ # DELETE /roles/:id
+ def destroy
+ begin
+ @role = Chef::Role.load(params[:id])
+ rescue Chef::Exceptions::CouchDBNotFound => e
+ raise NotFound, "Cannot load role #{params[:id]}"
+ end
+ @role.destroy
+ display @role
+ end
+
+end
diff --git a/chef-server-api/app/controllers/search.rb b/chef-server-api/app/controllers/search.rb
new file mode 100644
index 0000000000..269a326e6d
--- /dev/null
+++ b/chef-server-api/app/controllers/search.rb
@@ -0,0 +1,56 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Christopher Brown (<cb@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+
+
+require 'chef/solr/query'
+
+class ChefServerApi::Search < ChefServerApi::Application
+ provides :json
+
+ before :authenticate_every
+
+ def index
+ indexes = valid_indexes
+ display(indexes.collect { |i| absolute_slice_url(:organization_search, :id => i, :organization_id => @organization_id) })
+ end
+
+ def valid_indexes
+ indexes = Chef::DataBag.list(false)
+ indexes << "role"
+ indexes << "node"
+ end
+
+ def show
+ unless valid_indexes.include?(params[:id])
+ raise NotFound, "I don't know how to search for #{params[:id]} data objects."
+ end
+
+ query = Chef::Solr::Query.new(Chef::Config[:solr_url], Chef::Config[:couchdb_database])
+ params[:q] ||= "*:*"
+ params[:sort] ||= nil
+ params[:start] ||= 0
+ params[:rows] ||= 20
+ objects, start, total = query.search(params[:id], params[:q], params[:sort], params[:start], params[:rows])
+ display({
+ "rows" => objects,
+ "start" => start,
+ "total" => total
+ })
+ end
+
+end
diff --git a/chef-server-api/app/helpers/application_helper.rb b/chef-server-api/app/helpers/application_helper.rb
new file mode 100644
index 0000000000..e71bdbd4d0
--- /dev/null
+++ b/chef-server-api/app/helpers/application_helper.rb
@@ -0,0 +1,217 @@
+module Merb
+ module ChefServerApi
+ module ApplicationHelper
+
+ # Generate the absolute url for a slice - takes the slice's :path_prefix into account.
+ #
+ # @param slice_name<Symbol>
+ # The name of the slice - in identifier_sym format (underscored).
+ # @param *args<Array[Symbol,Hash]>
+ # There are several possibilities regarding arguments:
+ # - when passing a Hash only, the :default route of the current
+ # slice will be used
+ # - when a Symbol is passed, it's used as the route name
+ # - a Hash with additional params can optionally be passed
+ #
+ # @return <String> A uri based on the requested slice.
+ #
+ # @example absolute_slice_url(:awesome, :format => 'html')
+ # @example absolute_slice_url(:forum, :posts, :format => 'xml')
+ def absolute_slice_url(slice_name, *args)
+ options = extract_options_from_args!(args) || {}
+ protocol = options.delete(:protocol) || request.protocol
+ host = options.delete(:host) || request.host
+
+ protocol + "://" + host + slice_url(slice_name,*args)
+ end
+
+ # @param *segments<Array[#to_s]> Path segments to append.
+ #
+ # @return <String>
+ # A path relative to the public directory, with added segments.
+ def image_path(*segments)
+ public_path_for(:image, *segments)
+ end
+
+ # @param *segments<Array[#to_s]> Path segments to append.
+ #
+ # @return <String>
+ # A path relative to the public directory, with added segments.
+ def javascript_path(*segments)
+ public_path_for(:javascript, *segments)
+ end
+
+ # @param *segments<Array[#to_s]> Path segments to append.
+ #
+ # @return <String>
+ # A path relative to the public directory, with added segments.
+ def stylesheet_path(*segments)
+ public_path_for(:stylesheet, *segments)
+ end
+
+ # Construct a path relative to the public directory
+ #
+ # @param <Symbol> The type of component.
+ # @param *segments<Array[#to_s]> Path segments to append.
+ #
+ # @return <String>
+ # A path relative to the public directory, with added segments.
+ def public_path_for(type, *segments)
+ ::ChefServerApi.public_path_for(type, *segments)
+ end
+
+ # Construct an app-level path.
+ #
+ # @param <Symbol> The type of component.
+ # @param *segments<Array[#to_s]> Path segments to append.
+ #
+ # @return <String>
+ # A path within the host application, with added segments.
+ def app_path_for(type, *segments)
+ ::ChefServerApi.app_path_for(type, *segments)
+ end
+
+ # Construct a slice-level path.
+ #
+ # @param <Symbol> The type of component.
+ # @param *segments<Array[#to_s]> Path segments to append.
+ #
+ # @return <String>
+ # A path within the slice source (Gem), with added segments.
+ def slice_path_for(type, *segments)
+ ::ChefServerApi.slice_path_for(type, *segments)
+ end
+
+ def build_tree(name, node, default={}, override={})
+ node = Chef::Mixin::DeepMerge.merge(default, node)
+ node = Chef::Mixin::DeepMerge.merge(node, override)
+ html = "<table id='#{name}' class='tree table'>"
+ html << "<tr><th class='first'>Attribute</th><th class='last'>Value</th></tr>"
+ count = 0
+ parent = 0
+ append_tree(name, html, node, count, parent, override)
+ html << "</table>"
+ html
+ end
+
+ def append_tree(name, html, node, count, parent, override)
+ node.sort{ |a,b| a[0] <=> b[0] }.each do |key, value|
+ to_send = Array.new
+ count += 1
+ is_parent = false
+ local_html = ""
+ local_html << "<tr id='#{name}-#{count}' class='collapsed #{name}"
+ if parent != 0
+ local_html << " child-of-#{name}-#{parent}' style='display: none;'>"
+ else
+ local_html << "'>"
+ end
+ local_html << "<td class='table-key'><span toggle='#{name}-#{count}'/>#{key}</td>"
+ case value
+ when Hash
+ is_parent = true
+ local_html << "<td></td>"
+ p = count
+ to_send << Proc.new { append_tree(name, html, value, count, p, override) }
+ when Array
+ is_parent = true
+ local_html << "<td></td>"
+ as_hash = {}
+ value.each_index { |i| as_hash[i] = value[i] }
+ p = count
+ to_send << Proc.new { append_tree(name, html, as_hash, count, p, override) }
+ when String,Symbol
+ local_html << "<td><div class='json-attr'>#{value}</div></td>"
+ else
+ local_html << "<td>#{JSON.pretty_generate(value)}</td>"
+ end
+ local_html << "</tr>"
+ local_html.sub!(/class='collapsed/, 'class=\'collapsed parent') if is_parent
+ local_html.sub!(/<span/, "<span class='expander'") if is_parent
+ html << local_html
+ to_send.each { |s| count = s.call }
+ count += to_send.length
+ end
+ count
+ end
+
+ # Recursively build a tree of lists.
+ #def build_tree(node)
+ # list = "<dl>"
+ # list << "\n<!-- Beginning of Tree -->"
+ # walk = lambda do |key,value|
+ # case value
+ # when Hash, Array
+ # list << "\n<!-- Beginning of Enumerable obj -->"
+ # list << "\n<dt>#{key}</dt>"
+ # list << "<dd>"
+ # list << "\t<dl>\n"
+ # value.each(&walk)
+ # list << "\t</dl>\n"
+ # list << "</dd>"
+ # list << "\n<!-- End of Enumerable obj -->"
+ #
+ # else
+ # list << "\n<dt>#{key}</dt>"
+ # list << "<dd>#{value}</dd>"
+ # end
+ # end
+ # node.sort{ |a,b| a[0] <=> b[0] }.each(&walk)
+ # list << "</dl>"
+ #end
+
+
+ module AuthenticateEvery
+ require 'rubygems'
+ require 'mixlib/auth/signatureverification'
+ require 'extlib'
+
+ class << self
+ def authenticator
+ @authenticator ||= Mixlib::Auth::SignatureVerification.new
+ end
+ end
+
+
+ protected
+
+ # This is the main method to use as a before filter.
+ # It will perform the user lookup, header signing and signature comparison
+ # A failed login will result in an Unauthorized exception being raised.
+ #
+ # ====Parameters
+ #
+ def authenticate_every
+ auth = begin
+
+ Merb.logger.debug("Raw request: #{request.inspect}")
+ headers = request.env.inject({ }) { |memo, kv| memo[$2.downcase.to_sym] = kv[1] if kv[0] =~ /^(HTTP_)(.*)/; memo }
+ username = headers[:x_ops_userid].chomp
+ orgname = params[:organization_id]
+ Merb.logger.debug "I have #{headers.inspect}"
+
+ user = begin
+ User.find(username)
+ rescue ArgumentError
+ if orgname
+ cr = database_from_orgname(orgname)
+ Client.on(cr).by_clientname(:key=>username).first
+ end
+ end
+
+ actor = user_to_actor(user.id)
+ params[:requesting_actor_id] = actor.auth_object_id
+ user_key = OpenSSL::PKey::RSA.new(user.public_key)
+ Merb.logger.debug "authenticating:\n #{user.inspect}\n"
+ AuthenticateEvery::authenticator.authenticate_user_request(request, user_key)
+ rescue StandardError => se
+ Merb.logger.debug "authenticate every failed: #{se}, #{se.backtrace}"
+ nil
+ end
+ raise Merb::ControllerExceptions::Unauthorized, "Failed authorization" unless auth
+ auth
+ end
+ end
+ end
+ end
+end
diff --git a/chef-server-slice/app/helpers/exceptions_helper.rb b/chef-server-api/app/helpers/exceptions_helper.rb
index 86501d650d..18a41965b6 100644
--- a/chef-server-slice/app/helpers/exceptions_helper.rb
+++ b/chef-server-api/app/helpers/exceptions_helper.rb
@@ -1,5 +1,5 @@
module Merb
- module ChefServerSlice
+ module ChefServerApi
module ExceptionsHelper
end
end
diff --git a/chef-server-api/app/helpers/global_helpers.rb b/chef-server-api/app/helpers/global_helpers.rb
new file mode 100644
index 0000000000..b5627e92b4
--- /dev/null
+++ b/chef-server-api/app/helpers/global_helpers.rb
@@ -0,0 +1,30 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+module Merb
+ module ChefServerApi
+
+ module GlobalHelpers
+ # helpers defined here available to all views.
+ def node_escape(node)
+ node.gsub(/\./, '_')
+ end
+
+ end
+ end
+end
diff --git a/chef-server-api/app/helpers/nodes_helper.rb b/chef-server-api/app/helpers/nodes_helper.rb
new file mode 100644
index 0000000000..0204baba8e
--- /dev/null
+++ b/chef-server-api/app/helpers/nodes_helper.rb
@@ -0,0 +1,26 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: AJ Christensen (<aj@junglist.gen.nz>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+module Merb
+ module ChefServerApi
+ module NodesHelper
+
+ end
+ end
+end
diff --git a/chef-server-slice/app/helpers/roles_helper.rb b/chef-server-api/app/helpers/roles_helper.rb
index b6d46cdcd1..b6d46cdcd1 100644
--- a/chef-server-slice/app/helpers/roles_helper.rb
+++ b/chef-server-api/app/helpers/roles_helper.rb
diff --git a/chef-server-slice/app/views/exceptions/bad_request.json.erb b/chef-server-api/app/views/exceptions/bad_request.json.erb
index f266cf99b9..f266cf99b9 100644
--- a/chef-server-slice/app/views/exceptions/bad_request.json.erb
+++ b/chef-server-api/app/views/exceptions/bad_request.json.erb
diff --git a/chef-server-slice/app/views/exceptions/internal_server_error.html.erb b/chef-server-api/app/views/exceptions/internal_server_error.html.erb
index aadbfad350..aadbfad350 100644
--- a/chef-server-slice/app/views/exceptions/internal_server_error.html.erb
+++ b/chef-server-api/app/views/exceptions/internal_server_error.html.erb
diff --git a/chef-server-api/app/views/exceptions/not_acceptable.html.haml b/chef-server-api/app/views/exceptions/not_acceptable.html.haml
new file mode 100644
index 0000000000..b3e5b966c1
--- /dev/null
+++ b/chef-server-api/app/views/exceptions/not_acceptable.html.haml
@@ -0,0 +1,5 @@
+.block#block-text
+ .content
+ %h2.title Content-Type Unacceptable.
+ .inner
+ The Chef Server's REST API only responds to requests for JSON data. Set the 'Accept' header on your request to 'application/json' and try again.
diff --git a/chef-server-slice/app/views/exceptions/not_found.html.erb b/chef-server-api/app/views/exceptions/not_found.html.erb
index 388c72c31d..388c72c31d 100644
--- a/chef-server-slice/app/views/exceptions/not_found.html.erb
+++ b/chef-server-api/app/views/exceptions/not_found.html.erb
diff --git a/chef-server-slice/app/views/exceptions/standard_error.html.erb b/chef-server-api/app/views/exceptions/standard_error.html.erb
index edb45ddf90..edb45ddf90 100644
--- a/chef-server-slice/app/views/exceptions/standard_error.html.erb
+++ b/chef-server-api/app/views/exceptions/standard_error.html.erb
diff --git a/chef-server-api/app/views/layout/chef_server_api.html.haml b/chef-server-api/app/views/layout/chef_server_api.html.haml
new file mode 100644
index 0000000000..c7913fc491
--- /dev/null
+++ b/chef-server-api/app/views/layout/chef_server_api.html.haml
@@ -0,0 +1,23 @@
+!!! XML
+!!!
+%html
+ %head
+ %meta{ "http-equiv" => "content-type", :content => "text/html; charset=utf-8" }
+ %title Chef Server
+ = css_include_tag "base", "themes/djime-cerulean/style", "chef"
+ %body
+ #container
+ #header
+ %h1= link_to "Chef REST API", absolute_slice_url(:top)
+ #main-navigation
+ .clear
+ #wrapper
+ #main
+ = catch_content :for_layout
+ #footer
+ .block
+ %p Copyright &copy; 2009 Opscode, Inc.
+ #sidebar
+ .block.notice#sidebar_block_notice= catch_content :sidebar_block_notice
+ .block#sidebar_block= catch_content :sidebar_block
+ .clear
diff --git a/chef-server-api/app/views/main/index.html.haml b/chef-server-api/app/views/main/index.html.haml
new file mode 100644
index 0000000000..4697d483f0
--- /dev/null
+++ b/chef-server-api/app/views/main/index.html.haml
@@ -0,0 +1,5 @@
+.block#block-text
+ .content
+ %h2.title The REST API
+ .inner
+ This is the top of the Chef Server REST API. It's not really explorable via a web browser.
diff --git a/chef-server-api/config/init.rb b/chef-server-api/config/init.rb
new file mode 100644
index 0000000000..07d7916822
--- /dev/null
+++ b/chef-server-api/config/init.rb
@@ -0,0 +1,60 @@
+#
+# ==== Standalone Chefserver configuration
+#
+# This configuration/environment file is only loaded by bin/slice, which can be
+# used during development of the slice. It has no effect on this slice being
+# loaded in a host application. To run your slice in standalone mode, just
+# run 'slice' from its directory. The 'slice' command is very similar to
+# the 'merb' command, and takes all the same options, including -i to drop
+# into an irb session for example.
+#
+# The usual Merb configuration directives and init.rb setup methods apply,
+# including use_orm and before_app_loads/after_app_loads.
+#
+# If you need need different configurations for different environments you can
+# even create the specific environment file in config/environments/ just like
+# in a regular Merb application.
+#
+# In fact, a slice is no different from a normal # Merb application - it only
+# differs by the fact that seamlessly integrates into a so called 'host'
+# application, which in turn can override or finetune the slice implementation
+# code and views.
+#
+
+$: << File.join(File.dirname(__FILE__), "..", "..", "chef", "lib")
+require 'chef'
+
+merb_gems_version = " > 1.0"
+dependency "merb-haml", merb_gems_version
+dependency "merb-assets", merb_gems_version
+dependency "merb-helpers", merb_gems_version
+dependency "chef", :immediate=>true unless defined?(Chef)
+
+require 'rubygems'
+
+use_template_engine :haml
+
+Merb::Config.use do |c|
+ # BUGBUG [cb] For some reason, this next line
+ # causes a merb slice to vomit around openid
+ # c[:fork_for_class_load] = false
+
+ c[:couchdb_uri] = 'localhost:5984'
+ c[:guidservice_host] = 'localhost'
+ c[:guidservice_port] = 8000
+ c[:certificateservice_uri] = 'http://localhost:4000/certificates'
+ c[:couchdb_database] = 'opscode_account'
+ c[:authorizationservice_uri] = 'http://localhost:5959'
+ c[:service_private_key] = OpenSSL::PKey::RSA.new(File.read('/etc/opscode/azs.pem'))
+
+ c[:session_id_key] = '_chef_server_session_id'
+ c[:session_secret_key] = Chef::Config.manage_secret_key
+ c[:session_store] = 'cookie'
+ c[:exception_details] = true
+ c[:reload_classes] = true
+ c[:log_level] = Chef::Config[:log_level]
+ if Chef::Config[:log_location].kind_of?(String)
+ c[:log_file] = Chef::Config[:log_location]
+ end
+end
+
diff --git a/chef-server-slice/config/router.rb b/chef-server-api/config/router.rb
index 3142a1d1a9..3142a1d1a9 100644
--- a/chef-server-slice/config/router.rb
+++ b/chef-server-api/config/router.rb
diff --git a/chef-server-api/lib/chef-server-api.rb b/chef-server-api/lib/chef-server-api.rb
new file mode 100644
index 0000000000..ab05d11ad7
--- /dev/null
+++ b/chef-server-api/lib/chef-server-api.rb
@@ -0,0 +1,153 @@
+if defined?(Merb::Plugins)
+ $:.unshift File.dirname(__FILE__)
+ $:.unshift File.join(File.dirname(__FILE__), "..", "..", "chef-solr", "lib")
+ $:.unshift File.join(File.dirname(__FILE__), "..", "..", "chef", "lib")
+
+ dependency 'merb-slices', :immediate => true
+ dependency 'chef', :immediate=>true unless defined?(Chef)
+ dependency 'nanite', :immediate=>true
+
+ require 'chef/role'
+ require 'chef/data_bag'
+ require 'chef/data_bag_item'
+ require 'chef/nanite'
+
+ require 'mixlib/auth'
+
+ require 'chef/data_bag'
+ require 'chef/data_bag_item'
+ require 'ohai'
+ require 'chef/nanite'
+
+ require 'syntax/convertors/html'
+
+ Merb::Plugins.add_rakefiles "chef-server-api/merbtasks", "chef-server-api/slicetasks", "chef-server-api/spectasks"
+
+ # Register the Slice for the current host application
+ Merb::Slices::register(__FILE__)
+
+ Merb.disable :json
+
+ # Slice configuration - set this in a before_app_loads callback.
+ # By default a Slice uses its own layout, so you can switch to
+ # the main application layout or no layout at all if needed.
+ #
+ # Configuration options:
+ # :layout - the layout to use; defaults to :chefserverslice
+ # :mirror - which path component types to use on copy operations; defaults to all
+ Merb::Slices::config[:chef_server_api][:layout] ||= :chef_server_api
+
+ # All Slice code is expected to be namespaced inside a module
+ module ChefServerApi
+ # Slice metadata
+ self.description = "ChefServerApi.. serving up some piping hot infrastructure!"
+ self.version = Chef::VERSION
+ self.author = "Opscode"
+
+ # Stub classes loaded hook - runs before LoadClasses BootLoader
+ # right after a slice's classes have been loaded internally.
+ def self.loaded
+ Chef::Log.info("Compiling routes... (totally normal to see 'Cannot find resource model')")
+ end
+
+ # Initialization hook - runs before AfterAppLoads BootLoader
+ def self.init
+ end
+
+ # Activation hook - runs after AfterAppLoads BootLoader
+ def self.activate
+ Nanite::Log.logger = Mixlib::Auth::Log.logger = Ohai::Log.logger = Chef::Log.logger
+ Merb.logger.set_log(STDOUT, Chef::Config[:log_level])
+ Thread.new do
+ until EM.reactor_running?
+ sleep 1
+ end
+ Chef::Nanite.in_event { Chef::Log.info("Nanite is ready") }
+
+ # create the couch design docs for nodes, roles, and databags
+ Chef::CouchDB.new.create_id_map
+ Chef::Node.create_design_document
+ Chef::Role.create_design_document
+ Chef::DataBag.create_design_document
+
+ Chef::Log.info('Loading roles')
+ Chef::Role.sync_from_disk_to_couchdb
+ end
+ end
+
+ # Deactivation hook - triggered by Merb::Slices.deactivate(Chefserver)
+ def self.deactivate
+ end
+
+ # Setup routes inside the host application
+ #
+ # @param scope<Merb::Router::Behaviour>
+ # Routes will be added within this scope (namespace). In fact, any
+ # router behaviour is a valid namespace, so you can attach
+ # routes at any level of your router setup.
+ #
+ # @note prefix your named routes with :chefserverslice_
+ # to avoid potential conflicts with global named routes.
+ def self.setup_router(scope)
+ # Nodes
+ scope.match('/nodes/:id/cookbooks', :method => 'get').to(:controller => "nodes", :action => "cookbooks")
+ scope.resources :nodes
+
+ # Roles
+ scope.resources :roles
+
+ # Status
+ scope.match("/status").to(:controller => "status", :action => "index").name(:status)
+
+
+ # Search
+ scope.resources :search
+
+ # Cookbooks
+ scope.match('/nodes/:id/cookbooks', :method => 'get').to(:controller => "nodes", :action => "cookbooks")
+
+ scope.match("/cookbooks", :method => 'get').to(:controller => "cookbooks", :action => "index")
+ scope.match("/cookbooks", :method => 'post').to(:controller => "cookbooks", :action => "create")
+ scope.match("/cookbooks/:cookbook_id", :method => 'get', :cookbook_id => /[\w\.]+/).to(:controller => "cookbooks", :action => "show").name(:cookbook)
+ scope.match("/cookbooks/:cookbook_id", :method => 'delete', :cookbook_id => /[\w\.]+/).to(:controller => "cookbooks", :action => "destroy")
+ scope.match("/cookbooks/:cookbook_id/_content", :method => 'get', :cookbook_id => /[\w\.]+/).to(:controller => "cookbooks", :action => "get_tarball")
+ scope.match("/cookbooks/:cookbook_id/_content", :method => 'put', :cookbook_id => /[\w\.]+/).to(:controller => "cookbooks", :action => "update")
+ scope.match("/cookbooks/:cookbook_id/:segment", :cookbook_id => /[\w\.]+/).to(:controller => "cookbooks", :action => "show_segment").name(:cookbook_segment)
+
+ # Data
+ scope.match("/data/:data_bag_id/:id", :method => 'get').to(:controller => "data_item", :action => "show").name("data_bag_item")
+ scope.match("/data/:data_bag_id/:id", :method => 'put').to(:controller => "data_item", :action => "create").name("create_data_bag_item")
+ scope.match("/data/:data_bag_id/:id", :method => 'delete').to(:controller => "data_item", :action => "destroy").name("destroy_data_bag_item")
+ scope.resources :data
+
+ scope.match('/').to(:controller => 'main', :action =>'index').name(:top)
+ end
+ end
+
+ # TODO: make this read from an environment-specific file
+ Merb::Config.use do |c|
+ c[:couchdb_uri] = Chef::Config[:couchdb_url]
+ c[:couchdb_database] = Chef::Config[:couchdb_database]
+ end
+
+ COUCHDB = CouchRest.new(Merb::Config[:couchdb_uri])
+ COUCHDB.database!(Merb::Config[:couchdb_database])
+ COUCHDB.default_database = Merb::Config[:couchdb_database]
+
+ Mixlib::Auth::AuthJoin.use_database(COUCHDB.default_database)
+ Mixlib::Auth::PRIVKEY = Chef::Config[:validation_key]
+
+ # Setup the slice layout for ChefServerApi
+ #
+ # Use ChefServerApi.push_path and ChefServerApi.push_app_path
+ # to set paths to chefserver-level and app-level paths. Example:
+ #
+ # ChefServerApi.push_path(:application, ChefServerApi.root)
+ # ChefServerApi.push_app_path(:application, Merb.root / 'slices' / 'chefserverslice')
+ # ...
+ #
+ # Any component path that hasn't been set will default to ChefServerApi.root
+ #
+ # Or just call setup_default_structure! to setup a basic Merb MVC structure.
+ ChefServerApi.setup_default_structure!
+end
diff --git a/chef-server-api/lib/chef-server-api/merbtasks.rb b/chef-server-api/lib/chef-server-api/merbtasks.rb
new file mode 100644
index 0000000000..65cb589b82
--- /dev/null
+++ b/chef-server-api/lib/chef-server-api/merbtasks.rb
@@ -0,0 +1,103 @@
+namespace :slices do
+ namespace :chefserverslice do
+
+ desc "Install Chefserver"
+ task :install => [:preflight, :setup_directories, :copy_assets, :migrate]
+
+ desc "Test for any dependencies"
+ task :preflight do # see slicetasks.rb
+ end
+
+ desc "Setup directories"
+ task :setup_directories do
+ puts "Creating directories for host application"
+ ChefServerApi.mirrored_components.each do |type|
+ if File.directory?(ChefServerApi.dir_for(type))
+ if !File.directory?(dst_path = ChefServerApi.app_dir_for(type))
+ relative_path = dst_path.relative_path_from(Merb.root)
+ puts "- creating directory :#{type} #{File.basename(Merb.root) / relative_path}"
+ mkdir_p(dst_path)
+ end
+ end
+ end
+ end
+
+ # desc "Copy stub files to host application"
+ # task :stubs do
+ # puts "Copying stubs for ChefServerApi - resolves any collisions"
+ # copied, preserved = ChefServerApi.mirror_stubs!
+ # puts "- no files to copy" if copied.empty? && preserved.empty?
+ # copied.each { |f| puts "- copied #{f}" }
+ # preserved.each { |f| puts "! preserved override as #{f}" }
+ # end
+
+ # desc "Copy stub files and views to host application"
+ # task :patch => [ "stubs", "freeze:views" ]
+
+ desc "Copy public assets to host application"
+ task :copy_assets do
+ puts "Copying assets for ChefServerApi - resolves any collisions"
+ copied, preserved = ChefServerApi.mirror_public!
+ puts "- no files to copy" if copied.empty? && preserved.empty?
+ copied.each { |f| puts "- copied #{f}" }
+ preserved.each { |f| puts "! preserved override as #{f}" }
+ end
+
+ desc "Migrate the database"
+ task :migrate do # see slicetasks.rb
+ end
+
+ desc "Freeze ChefServerApi into your app (only chefserverslice/app)"
+ task :freeze => [ "freeze:app" ]
+
+ namespace :freeze do
+
+ # desc "Freezes ChefServerApi by installing the gem into application/gems"
+ # task :gem do
+ # ENV["GEM"] ||= "chefserverslice"
+ # Rake::Task['slices:install_as_gem'].invoke
+ # end
+
+ desc "Freezes ChefServerApi by copying all files from chefserverslice/app to your application"
+ task :app do
+ puts "Copying all chefserverslice/app files to your application - resolves any collisions"
+ copied, preserved = ChefServerApi.mirror_app!
+ puts "- no files to copy" if copied.empty? && preserved.empty?
+ copied.each { |f| puts "- copied #{f}" }
+ preserved.each { |f| puts "! preserved override as #{f}" }
+ end
+
+ desc "Freeze all views into your application for easy modification"
+ task :views do
+ puts "Copying all view templates to your application - resolves any collisions"
+ copied, preserved = ChefServerApi.mirror_files_for :view
+ puts "- no files to copy" if copied.empty? && preserved.empty?
+ copied.each { |f| puts "- copied #{f}" }
+ preserved.each { |f| puts "! preserved override as #{f}" }
+ end
+
+ desc "Freeze all models into your application for easy modification"
+ task :models do
+ puts "Copying all models to your application - resolves any collisions"
+ copied, preserved = ChefServerApi.mirror_files_for :model
+ puts "- no files to copy" if copied.empty? && preserved.empty?
+ copied.each { |f| puts "- copied #{f}" }
+ preserved.each { |f| puts "! preserved override as #{f}" }
+ end
+
+ desc "Freezes ChefServerApi as a gem and copies over chefserver/app"
+ task :app_with_gem => [:gem, :app]
+
+ desc "Freezes ChefServerApi by unpacking all files into your application"
+ task :unpack do
+ puts "Unpacking ChefServerApi files to your application - resolves any collisions"
+ copied, preserved = ChefServerApi.unpack_slice!
+ puts "- no files to copy" if copied.empty? && preserved.empty?
+ copied.each { |f| puts "- copied #{f}" }
+ preserved.each { |f| puts "! preserved override as #{f}" }
+ end
+
+ end
+
+ end
+end
diff --git a/chef-server-slice/lib/chef-server-slice/slicetasks.rb b/chef-server-api/lib/chef-server-api/slicetasks.rb
index 187323a299..187323a299 100644
--- a/chef-server-slice/lib/chef-server-slice/slicetasks.rb
+++ b/chef-server-api/lib/chef-server-api/slicetasks.rb
diff --git a/chef-server-api/lib/chef-server-api/spectasks.rb b/chef-server-api/lib/chef-server-api/spectasks.rb
new file mode 100644
index 0000000000..e00f1b9490
--- /dev/null
+++ b/chef-server-api/lib/chef-server-api/spectasks.rb
@@ -0,0 +1,53 @@
+namespace :slices do
+ namespace :chefserverslice do
+
+ desc "Run slice specs within the host application context"
+ task :spec => [ "spec:explain", "spec:default" ]
+
+ namespace :spec do
+
+ slice_root = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
+
+ task :explain do
+ puts "\nNote: By running ChefServerApi specs inside the application context any\n" +
+ "overrides could break existing specs. This isn't always a problem,\n" +
+ "especially in the case of views. Use these spec tasks to check how\n" +
+ "well your application conforms to the original slice implementation."
+ end
+
+ Spec::Rake::SpecTask.new('default') do |t|
+ t.spec_opts = ["--format", "specdoc", "--colour"]
+ t.spec_files = Dir["#{slice_root}/spec/**/*_spec.rb"].sort
+ end
+
+ desc "Run all model specs, run a spec for a specific Model with MODEL=MyModel"
+ Spec::Rake::SpecTask.new('model') do |t|
+ t.spec_opts = ["--format", "specdoc", "--colour"]
+ if(ENV['MODEL'])
+ t.spec_files = Dir["#{slice_root}/spec/models/**/#{ENV['MODEL']}_spec.rb"].sort
+ else
+ t.spec_files = Dir["#{slice_root}/spec/models/**/*_spec.rb"].sort
+ end
+ end
+
+ desc "Run all request specs, run a spec for a specific request with REQUEST=MyRequest"
+ Spec::Rake::SpecTask.new('request') do |t|
+ t.spec_opts = ["--format", "specdoc", "--colour"]
+ if(ENV['REQUEST'])
+ t.spec_files = Dir["#{slice_root}/spec/requests/**/#{ENV['REQUEST']}_spec.rb"].sort
+ else
+ t.spec_files = Dir["#{slice_root}/spec/requests/**/*_spec.rb"].sort
+ end
+ end
+
+ desc "Run all specs and output the result in html"
+ Spec::Rake::SpecTask.new('html') do |t|
+ t.spec_opts = ["--format", "html"]
+ t.libs = ['lib', 'server/lib' ]
+ t.spec_files = Dir["#{slice_root}/spec/**/*_spec.rb"].sort
+ end
+
+ end
+
+ end
+end
diff --git a/chef-server-slice/public/images/avatar.png b/chef-server-api/public/images/avatar.png
index 66488481ae..66488481ae 100644
--- a/chef-server-slice/public/images/avatar.png
+++ b/chef-server-api/public/images/avatar.png
Binary files differ
diff --git a/chef-server-slice/public/images/indicator.gif b/chef-server-api/public/images/indicator.gif
index 085ccaecaf..085ccaecaf 100644
--- a/chef-server-slice/public/images/indicator.gif
+++ b/chef-server-api/public/images/indicator.gif
Binary files differ
diff --git a/chef-server-slice/public/images/merb.jpg b/chef-server-api/public/images/merb.jpg
index a19dcf4048..a19dcf4048 100644
--- a/chef-server-slice/public/images/merb.jpg
+++ b/chef-server-api/public/images/merb.jpg
Binary files differ
diff --git a/chef-server-slice/public/stylesheets/base.css b/chef-server-api/public/stylesheets/base.css
index 0da62086ed..0da62086ed 100644
--- a/chef-server-slice/public/stylesheets/base.css
+++ b/chef-server-api/public/stylesheets/base.css
diff --git a/chef-server-slice/public/stylesheets/chef.css b/chef-server-api/public/stylesheets/chef.css
index 52a06d056c..52a06d056c 100644
--- a/chef-server-slice/public/stylesheets/chef.css
+++ b/chef-server-api/public/stylesheets/chef.css
diff --git a/chef-server-slice/public/stylesheets/themes/bec-green/style.css b/chef-server-api/public/stylesheets/themes/bec-green/style.css
index 225b6d4e35..225b6d4e35 100644
--- a/chef-server-slice/public/stylesheets/themes/bec-green/style.css
+++ b/chef-server-api/public/stylesheets/themes/bec-green/style.css
diff --git a/chef-server-slice/public/stylesheets/themes/bec/style.css b/chef-server-api/public/stylesheets/themes/bec/style.css
index c94474866a..c94474866a 100644
--- a/chef-server-slice/public/stylesheets/themes/bec/style.css
+++ b/chef-server-api/public/stylesheets/themes/bec/style.css
diff --git a/chef-server-slice/public/stylesheets/themes/blue/style.css b/chef-server-api/public/stylesheets/themes/blue/style.css
index cce8f4bdf0..cce8f4bdf0 100644
--- a/chef-server-slice/public/stylesheets/themes/blue/style.css
+++ b/chef-server-api/public/stylesheets/themes/blue/style.css
diff --git a/chef-server-slice/public/stylesheets/themes/default/style.css b/chef-server-api/public/stylesheets/themes/default/style.css
index e7a5ee1271..e7a5ee1271 100644
--- a/chef-server-slice/public/stylesheets/themes/default/style.css
+++ b/chef-server-api/public/stylesheets/themes/default/style.css
diff --git a/chef-server-slice/public/stylesheets/themes/djime-cerulean/style.css b/chef-server-api/public/stylesheets/themes/djime-cerulean/style.css
index 9b050c6785..9b050c6785 100644
--- a/chef-server-slice/public/stylesheets/themes/djime-cerulean/style.css
+++ b/chef-server-api/public/stylesheets/themes/djime-cerulean/style.css
diff --git a/chef-server-slice/public/stylesheets/themes/kathleene/style.css b/chef-server-api/public/stylesheets/themes/kathleene/style.css
index e68a545431..e68a545431 100644
--- a/chef-server-slice/public/stylesheets/themes/kathleene/style.css
+++ b/chef-server-api/public/stylesheets/themes/kathleene/style.css
diff --git a/chef-server-slice/public/stylesheets/themes/orange/style.css b/chef-server-api/public/stylesheets/themes/orange/style.css
index 90e7d8de58..90e7d8de58 100644
--- a/chef-server-slice/public/stylesheets/themes/orange/style.css
+++ b/chef-server-api/public/stylesheets/themes/orange/style.css
diff --git a/chef-server-slice/public/stylesheets/themes/reidb-greenish/style.css b/chef-server-api/public/stylesheets/themes/reidb-greenish/style.css
index 23e5245eb4..23e5245eb4 100644
--- a/chef-server-slice/public/stylesheets/themes/reidb-greenish/style.css
+++ b/chef-server-api/public/stylesheets/themes/reidb-greenish/style.css
diff --git a/chef-server-api/stubs/app/controllers/application.rb b/chef-server-api/stubs/app/controllers/application.rb
new file mode 100644
index 0000000000..c082fba50d
--- /dev/null
+++ b/chef-server-api/stubs/app/controllers/application.rb
@@ -0,0 +1,2 @@
+class ChefServerApi::Application < Merb::Controller
+end
diff --git a/chef-server-api/stubs/app/controllers/main.rb b/chef-server-api/stubs/app/controllers/main.rb
new file mode 100644
index 0000000000..bbb75747f6
--- /dev/null
+++ b/chef-server-api/stubs/app/controllers/main.rb
@@ -0,0 +1,2 @@
+class ChefServerApi::Main < ChefServerApi::Application
+end
diff --git a/chef-server-slice/app/controllers/cookbook_attributes.rb b/chef-server-slice/app/controllers/cookbook_attributes.rb
deleted file mode 100644
index fd888448b0..0000000000
--- a/chef-server-slice/app/controllers/cookbook_attributes.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@opscode.com>)
-# Author:: Christopher Brown (<cb@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# 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.
-#
-
-require 'chef' / 'mixin' / 'checksum'
-
-class ChefServerSlice::CookbookAttributes < ChefServerSlice::Application
-
- provides :html, :json
-
- before :login_required
-
- include Chef::Mixin::Checksum
-
- def load_cookbook_attributes()
- @attribute_files = load_cookbook_segment(params[:cookbook_id], :attributes)
- end
-
- def index
- if params[:id]
- show
- else
- load_cookbook_attributes()
- display @attribute_files
- end
- end
-
- def show
- only_provides :json
- load_cookbook_attributes
- raise NotFound, "Cannot find a suitable attribute file!" unless @attribute_files.has_key?(params[:id])
- to_send = @attribute_files[params[:id]][:file]
- current_checksum = checksum(to_send)
- Chef::Log.debug("old sum: #{params[:checksum]}, new sum: #{current_checksum}")
- if current_checksum == params[:checksum]
- display "File #{to_send} has not changed", :status => 304
- else
- send_file(to_send)
- end
- end
-
-end
-
-
diff --git a/chef-server-slice/app/controllers/cookbook_definitions.rb b/chef-server-slice/app/controllers/cookbook_definitions.rb
deleted file mode 100644
index da5654fbcc..0000000000
--- a/chef-server-slice/app/controllers/cookbook_definitions.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@opscode.com>)
-# Author:: Christopher Brown (<cb@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# 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.
-#
-
-require 'chef' / 'mixin' / 'checksum'
-
-class ChefServerSlice::CookbookDefinitions < ChefServerSlice::Application
-
- provides :html, :json
-
- before :login_required
-
- include Chef::Mixin::Checksum
-
- def load_cookbook_definitions()
- @definition_files = load_cookbook_segment(params[:cookbook_id], :definitions)
- end
-
- def index
- if params[:id]
- show
- else
- load_cookbook_definitions()
- display @definition_files
- end
- end
-
- def show
- only_provides :json
- load_cookbook_definitions
- raise NotFound, "Cannot find a suitable definition file!" unless @definition_files.has_key?(params[:id])
-
- to_send = @definition_files[params[:id]][:file]
- current_checksum = checksum(to_send)
- Chef::Log.debug("Old sum: #{params[:checksum]}, New sum: #{current_checksum}")
- if current_checksum == params[:checksum]
- display "File #{to_send} has not changed", :status => 304
- else
- send_file(to_send)
- end
- end
-
-end
-
-
diff --git a/chef-server-slice/app/controllers/cookbook_files.rb b/chef-server-slice/app/controllers/cookbook_files.rb
deleted file mode 100644
index b739366d83..0000000000
--- a/chef-server-slice/app/controllers/cookbook_files.rb
+++ /dev/null
@@ -1,94 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@opscode.com>)
-# Author:: Christopher Brown (<cb@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# 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.
-#
-
-require 'chef' / 'mixin' / 'checksum'
-require 'chef' / 'cookbook_loader'
-require 'chef' / 'mixin' / 'find_preferred_file'
-
-class ChefServerSlice::CookbookFiles < ChefServerSlice::Application
-
- provides :html, :json
- before :login_required
-
- include Chef::Mixin::Checksum
- include Chef::Mixin::FindPreferredFile
-
- layout nil
-
- def index
- if params[:id]
- if params[:recursive] == "true"
- show_directory
- else
- show
- end
- else
- @remote_files = load_cookbook_files(params[:cookbook_id], :remote_file)
- display @remote_files
- end
- end
-
- def show
- only_provides :json
- begin
- to_send = find_preferred_file(
- params[:cookbook_id],
- :remote_file,
- params[:id],
- params[:fqdn],
- params[:platform],
- params[:version]
- )
- rescue Chef::Exceptions::FileNotFound
- raise NotFound, "Cannot find a suitable file!"
- end
-
- current_checksum = checksum(to_send)
- Chef::Log.debug("old sum: #{params[:checksum]}, new sum: #{current_checksum}")
- if current_checksum == params[:checksum]
- render "File #{to_send} has not changed", :status => 304
- else
- send_file(to_send)
- end
- end
-
- def show_directory
- dir_to_send = find_preferred_file(
- params[:cookbook_id],
- :remote_file,
- params[:id],
- params[:fqdn],
- params[:platform],
- params[:version]
- )
- unless (dir_to_send && File.directory?(dir_to_send))
- raise NotFound, "Cannot find a suitable directory"
- end
-
- @directory_listing = Array.new
- Dir[::File.join(dir_to_send, '**', '*')].sort { |a,b| b <=> a }.each do |file_to_send|
- next if File.directory?(file_to_send)
- file_to_send =~ /^#{dir_to_send}\/(.+)$/
- @directory_listing << $1
- end
-
- display @directory_listing
- end
-
-end
diff --git a/chef-server-slice/app/controllers/cookbook_libraries.rb b/chef-server-slice/app/controllers/cookbook_libraries.rb
deleted file mode 100644
index 72b296f351..0000000000
--- a/chef-server-slice/app/controllers/cookbook_libraries.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@opscode.com>)
-# Author:: Christopher Brown (<cb@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# 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.
-#
-
-require 'chef' / 'mixin' / 'checksum'
-require 'chef' / 'cookbook_loader'
-
-class ChefServerSlice::CookbookLibraries < ChefServerSlice::Application
-
- provides :html, :json
- before :login_required
-
- include Chef::Mixin::Checksum
-
- def load_cookbook_libs()
- @lib_files = load_cookbook_segment(params[:cookbook_id], :libraries)
- end
-
- def index
- if params[:id]
- show
- else
- load_cookbook_libs()
- display @lib_files
- end
- end
-
- def show
- only_provides :json
- load_cookbook_libs
- raise NotFound, "Cannot find a suitable library file!" unless @lib_files.has_key?(params[:id])
-
- to_send = @lib_files[params[:id]][:file]
- current_checksum = checksum(to_send)
- Chef::Log.debug("Old sum: #{params[:checksum]}, New sum: #{current_checksum}")
- if current_checksum == params[:checksum]
- display "File #{to_send} has not changed", :status => 304
- else
- send_file(to_send)
- end
- end
-
-end
-
-
diff --git a/chef-server-slice/app/controllers/cookbook_recipes.rb b/chef-server-slice/app/controllers/cookbook_recipes.rb
deleted file mode 100644
index fbcb427eb3..0000000000
--- a/chef-server-slice/app/controllers/cookbook_recipes.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@opscode.com>)
-# Author:: Christopher Brown (<cb@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# 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.
-#
-
-require 'chef' / 'mixin' / 'checksum'
-
-class ChefServerSlice::CookbookRecipes < ChefServerSlice::Application
-
- provides :html, :json
- before :login_required
-
- include Chef::Mixin::Checksum
-
- def load_cookbook_recipes()
- @recipe_files = load_cookbook_segment(params[:cookbook_id], :recipes)
- end
-
- def index
- if params[:id]
- show
- else
- load_cookbook_recipes()
- display @recipe_files
- end
- end
-
- def show
- only_provides :json
- load_cookbook_recipes
- raise NotFound, "Cannot find a suitable recipe file!" unless @recipe_files.has_key?(params[:id])
-
- to_send = @recipe_files[params[:id]][:file]
- current_checksum = checksum(to_send)
- Chef::Log.debug("old sum: #{params[:checksum]}, new sum: #{current_checksum}")
- if current_checksum == params[:checksum]
- display "File #{to_send} has not changed", :status => 304
- else
- send_file(to_send)
- end
- end
-
-end
-
-
diff --git a/chef-server-slice/app/controllers/main.rb b/chef-server-slice/app/controllers/main.rb
deleted file mode 100644
index 871328943e..0000000000
--- a/chef-server-slice/app/controllers/main.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-class ChefServerSlice::Main < ChefServerSlice::Application
-
- def index
- render
- end
-
-end
diff --git a/chef-server-slice/app/controllers/nodes.rb b/chef-server-slice/app/controllers/nodes.rb
deleted file mode 100644
index 4faa3c727e..0000000000
--- a/chef-server-slice/app/controllers/nodes.rb
+++ /dev/null
@@ -1,144 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@opscode.com>)
-# Author:: Christopher Brown (<cb@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# 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.
-#
-
-require 'chef' / 'node'
-
-class ChefServerSlice::Nodes < ChefServerSlice::Application
-
- provides :html, :json
-
- before :fix_up_node_id
- before :login_required
- before :authorized_node, :only => [ :update, :destroy ]
-
- def index
- @node_list = Chef::Node.list
- display(@node_list.collect { |n| absolute_slice_url(:node, escape_node_id(n)) })
- end
-
- def show
- begin
- @node = Chef::Node.load(params[:id])
- rescue Net::HTTPServerException => e
- raise NotFound, "Cannot load node #{params[:id]}"
- end
- # TODO - might as well expand the run list here, too, rather than take multiple round trips.
- recipes, defaults, overrides = @node.run_list.expand("couchdb")
- @node.default = defaults
- @node.override = overrides
- display @node
- end
-
- def new
- @node = Chef::Node.new
- @available_recipes = get_available_recipes
- @available_roles = Chef::Role.list.sort
- @run_list = @node.run_list
- render
- end
-
- def edit
- begin
- @node = Chef::Node.load(params[:id])
- rescue Net::HTTPServerException => e
- raise NotFound, "Cannot load node #{params[:id]}"
- end
- @available_recipes = get_available_recipes
- @available_roles = Chef::Role.list.sort
- @run_list = @node.run_list
- render
- end
-
- def create
- if params.has_key?("inflated_object")
- @node = params["inflated_object"]
- exists = true
- begin
- Chef::Node.load(@node.name)
- rescue Net::HTTPServerException
- exists = false
- end
- raise Forbidden, "Node already exists" if exists
- self.status = 201
- @node.save
- display({ :uri => absolute_slice_url(:node, escape_node_id(@node.name)) })
- else
- begin
- @node = Chef::Node.new
- @node.name params[:name]
- @node.attribute = JSON.parse(params[:attributes])
- @node.run_list params[:for_node]
- @node.save
- redirect(slice_url(:nodes), :message => { :notice => "Created Node #{@node.name}" })
- rescue
- @node.attribute = JSON.parse(params[:attributes])
- @available_recipes = get_available_recipes
- @available_roles = Chef::Role.list.sort
- @run_list = params[:for_node]
- @_message = { :error => $! }
- render :new
- end
- end
- end
-
- def update
- begin
- @node = Chef::Node.load(params[:id])
- rescue Net::HTTPServerException => e
- raise NotFound, "Cannot load node #{params[:id]}"
- end
-
- if params.has_key?("inflated_object")
- updated = params['inflated_object']
- @node.run_list.reset(updated.run_list)
- @node.attribute = updated.attribute
- @node.save
- display(@node)
- else
- begin
- @node.run_list.reset(params[:for_node] ? params[:for_node] : [])
- @node.attribute = JSON.parse(params[:attributes])
- @node.save
- @_message = { :notice => "Updated Node" }
- render :show
- rescue
- @available_recipes = get_available_recipes
- @available_roles = Chef::Role.list.sort
- @run_list = Chef::RunList.new
- @run_list.reset(params[:for_node])
- render :edit
- end
- end
- end
-
- def destroy
- begin
- @node = Chef::Node.load(params[:id])
- rescue Net::HTTPServerException => e
- raise NotFound, "Cannot load node #{params[:id]}"
- end
- @node.destroy
- if request.accept == 'application/json'
- display @node
- else
- redirect(absolute_slice_url(:nodes), {:message => { :notice => "Node #{params[:id]} deleted successfully" }, :permanent => true})
- end
- end
-
-end
diff --git a/chef-server-slice/app/controllers/openid_register.rb b/chef-server-slice/app/controllers/openid_register.rb
deleted file mode 100644
index 1a15762447..0000000000
--- a/chef-server-slice/app/controllers/openid_register.rb
+++ /dev/null
@@ -1,113 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@opscode.com>)
-# Author:: Christopher Brown (<cb@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# 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.
-#
-
-require 'openid'
-require 'chef' / 'openid_registration'
-
-class ChefServerSlice::OpenidRegister < ChefServerSlice::Application
-
- provides :html, :json
-
- before :fix_up_node_id
- before :login_required, :only => [ :index, :update, :destroy, :validate, :admin ]
- before :authorized_node, :only => [ :update, :destroy, :validate, :admin ]
-
- def index
- @headers['X-XRDS-Location'] = Chef::Config[:openid_url] + "/openid/server/server/xrds"
- @registered_nodes = Chef::OpenIDRegistration.list(true)
- Chef::Log.debug(@registered_nodes.inspect)
- display @registered_nodes
- end
-
- def show
- begin
- @registered_node = Chef::OpenIDRegistration.load(params[:id])
- rescue Net::HTTPServerException => e
- if e.message =~ /^404/
- raise NotFound, "Cannot load node registration for #{params[:id]}"
- else
- raise e
- end
- end
- Chef::Log.debug(@registered_node.inspect)
- display @registered_node
- end
-
- def create
- params.has_key?(:id) or raise BadRequest, "You must provide an id to register"
- params.has_key?(:password) or raise BadRequest, "You must provide a password to register"
- if Chef::OpenIDRegistration.has_key?(params[:id])
- raise BadRequest, "You cannot re-register #{params[:id]}!"
- end
- @registered_node = Chef::OpenIDRegistration.new
- @registered_node.name = params[:id]
- @registered_node.set_password(params[:password])
- if Chef::Config[:validation_token]
- if params[:validation_token] == Chef::Config[:validation_token]
- @registered_node.validated = true
- else
- @registered_node.validated = false
- end
- else
- @registered_node.validated = false
- end
- @registered_node.save
- display @registered_node
- end
-
- def update
- raise BadRequest, "You cannot update your registration -- delete #{params[:id]} and re-register"
- end
-
- def destroy
- begin
- r = Chef::OpenIDRegistration.load(params[:id])
- rescue Exception => e
- raise BadRequest, "Cannot find the registration for #{params[:id]}"
- end
- r.destroy
- if content_type == :html
- redirect slice_url(:registrations)
- else
- display({ :message => "Deleted registration for #{params[:id]}"})
- end
- end
-
- def validate
- begin
- r = Chef::OpenIDRegistration.load(params[:id])
- rescue Exception => e
- raise BadRequest, "Cannot find the registration for #{params[:id]}"
- end
- r.validated = r.validated ? false : true
- r.save
- redirect slice_url(:registrations)
- end
-
- def admin
- begin
- r = Chef::OpenIDRegistration.load(params[:id])
- rescue Exception => e
- raise BadRequest, "Cannot find the registration for #{params[:id]}"
- end
- r.admin = r.admin ? false : true
- r.save
- redirect slice_url(:registrations)
- end
-end
diff --git a/chef-server-slice/app/controllers/openid_server.rb b/chef-server-slice/app/controllers/openid_server.rb
deleted file mode 100644
index 17dd564e09..0000000000
--- a/chef-server-slice/app/controllers/openid_server.rb
+++ /dev/null
@@ -1,252 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@opscode.com>)
-# Author:: Christopher Brown (<cb@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# 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.
-#
-
-require 'pathname'
-
-# load the openid library, first trying rubygems
-#begin
-# require "rubygems"
-# require_gem "ruby-openid", ">= 1.0"
-#rescue LoadError
-require "openid"
-require "openid/consumer/discovery"
-require 'json'
-require 'chef' / 'openid_registration'
-#end
-
-class ChefServerSlice::OpenidServer < ChefServerSlice::Application
-
- provides :html, :json
-
- include Merb::ChefServerSlice::OpenidServerHelper
- include OpenID::Server
-
- layout nil
-
- before :fix_up_node_id
- after :dump_cookies_and_session
-
- def index
-
- oidreq = server.decode_request(params.reject{|k,v| k == "controller" || k == "action"})
-
- # no openid.mode was given
- unless oidreq
- return "This is the Chef OpenID server endpoint."
- end
-
- oidresp = nil
-
- if oidreq.kind_of?(CheckIDRequest)
- identity = oidreq.identity
-
- if oidresp
- nil
- elsif self.is_authorized(identity, oidreq.trust_root)
- oidresp = oidreq.answer(true, nil, identity)
- elsif oidreq.immediate
- server_url = slice_url :openid_server
- oidresp = oidreq.answer(false, server_url)
- else
- if content_type == :json
- session[:last_oidreq] = oidreq
- response = { :action => slice_url(:openid_server_decision) }
- return response.to_json
- else
- return show_decision_page(oidreq)
- end
- end
- else
- oidresp = server.handle_request(oidreq)
- end
-
- self.render_response(oidresp)
- end
-
- def show_decision_page(oidreq, message="Do you trust this site with your identity?")
- session[:last_oidreq] = oidreq
- @oidreq = oidreq
-
- if message
- session[:notice] = message
- end
-
- render :template => 'openid_server/decide'
- end
-
- def node_page
- unless Chef::OpenIDRegistration.has_key?(params[:id])
- raise NotFound, "Cannot find registration for #{params[:id]}"
- end
-
- # Yadis content-negotiation: we want to return the xrds if asked for.
- accept = request.env['HTTP_ACCEPT']
-
- # This is not technically correct, and should eventually be updated
- # to do real Accept header parsing and logic. Though I expect it will work
- # 99% of the time.
- if accept and accept.include?('application/xrds+xml')
- return node_xrds
- end
-
- # content negotiation failed, so just render the user page
- xrds_url = absolute_slice_url(:openid_node_xrds, :id => params[:id])
- identity_page = <<EOS
-<html><head>
-<meta http-equiv="X-XRDS-Location" content="#{xrds_url}" />
-<link rel="openid.server" href="#{absolute_slice_url(:openid_node, :id => params[:id])}" />
-</head><body><p>OpenID identity page for registration #{params[:id]}</p>
-</body></html>
-EOS
-
- # Also add the Yadis location header, so that they don't have
- # to parse the html unless absolutely necessary.
- @headers['X-XRDS-Location'] = xrds_url
- render identity_page
- end
-
- def node_xrds
- types = [
- OpenID::OPENID_2_0_TYPE,
- OpenID::OPENID_1_0_TYPE
- ]
-
- render_xrds(types)
- end
-
- def idp_xrds
- types = [
- OpenID::OPENID_IDP_2_0_TYPE,
- ]
-
- render_xrds(types)
- end
-
- def decision
- oidreq = session[:last_oidreq]
- session[:last_oidreq] = nil
-
- if params.has_key?(:cancel)
- Chef::Log.info("Cancelling OpenID Authentication")
- return(redirect(oidreq.cancel_url))
- else
- identity = oidreq.identity
- identity =~ /node\/(.+)$/
- openid_node = Chef::OpenIDRegistration.load($1)
- unless openid_node.validated
- raise Unauthorized, "This nodes registration has not been validated"
- end
- if openid_node.password == encrypt_password(openid_node.salt, params[:password])
- if session[:approvals] and !session[:approvals].include?(oidreq.trust_root)
- session[:approvals] << oidreq.trust_root
- else
- session[:approvals] = [oidreq.trust_root]
- end
- oidresp = oidreq.answer(true, nil, identity)
- return self.render_response(oidresp)
- else
- raise Unauthorized, "Invalid credentials"
- end
- end
- end
-
- protected
-
- def encrypt_password(salt, password)
- Digest::SHA1.hexdigest("--#{salt}--#{password}--")
- end
-
- def server
- if @server.nil?
- server_url = absolute_slice_url(:openid_server)
- if Chef::Config[:openid_store_couchdb]
- require 'openid-store-couchdb'
- store = OpenID::Store::CouchDB.new(Chef::Config[:couchdb_url])
- else
- require 'openid/store/filesystem'
- dir = Chef::Config[:openid_store_path]
- store = OpenID::Store::Filesystem.new(dir)
- end
- @server = Server.new(store, server_url)
- end
- return @server
- end
-
- def approved(trust_root)
- return false if session[:approvals].nil?
- return session[:approvals].member?(trust_root)
- end
-
- def is_authorized(identity_url, trust_root)
- return (session[:username] and (identity_url == url_for_user) and self.approved(trust_root))
- end
-
- def render_xrds(types)
- type_str = ""
-
- types.each { |uri|
- type_str += "<Type>#{uri}</Type>\n "
- }
-
- yadis = <<EOS
-<?xml version="1.0" encoding="UTF-8"?>
-<xrds:XRDS
- xmlns:xrds="xri://$xrds"
- xmlns="xri://$xrd*($v*2.0)">
- <XRD>
- <Service priority="0">
- #{type_str}
- <URI>#{absolute_slice_url(:openid_server)}</URI>
- </Service>
- </XRD>
-</xrds:XRDS>
-EOS
-
- @headers['content-type'] = 'application/xrds+xml'
- render yadis
- end
-
- def render_response(oidresp)
- if oidresp.needs_signing
- signed_response = server.signatory.sign(oidresp)
- end
- web_response = server.encode_response(oidresp)
-
- case web_response.code
- when HTTP_OK
- @status = 200
- render web_response.body
- when HTTP_REDIRECT
- redirect web_response.headers['location']
- else
- @status = 400
- render web_response.body
- end
- end
-
- def dump_cookies_and_session
- unless session.empty? or request.cookies.empty?
- cookie_size = request.cookies.inject(0) {|sum,c| sum + c[1].length }
- c, s = request.cookies.inspect, session.inspect
- Chef::Log.debug("cookie dump (size: #{cookie_size}): #{c}")
- Chef::Log.debug("session dump #{s}")
- end
- end
-
-end
diff --git a/chef-server-slice/app/controllers/roles.rb b/chef-server-slice/app/controllers/roles.rb
deleted file mode 100644
index 0ca70ea01b..0000000000
--- a/chef-server-slice/app/controllers/roles.rb
+++ /dev/null
@@ -1,138 +0,0 @@
-require 'chef/role'
-
-class ChefServerSlice::Roles < ChefServerSlice::Application
-
- provides :html, :json
- before :login_required
-
- # GET /roles
- def index
- @role_list = Chef::Role.list(true)
- display(@role_list.collect { |r| absolute_slice_url(:role, r.name) })
- end
-
- # GET /roles/:id
- def show
- begin
- @role = Chef::Role.load(params[:id])
- rescue Net::HTTPServerException => e
- raise NotFound, "Cannot load role #{params[:id]}"
- end
- display @role
- end
-
- # GET /roles/new
- def new
- @available_recipes = get_available_recipes
- @role = Chef::Role.new
- @current_recipes = @role.recipes
- render
- end
-
- # GET /roles/:id/edit
- def edit
- begin
- @role = Chef::Role.load(params[:id])
- rescue Net::HTTPServerException => e
- raise NotFound, "Cannot load role #{params[:id]}"
- end
- @available_recipes = get_available_recipes
- @current_recipes = @role.recipes
- render
- end
-
- # GET /roles/:id/delete
- def delete
-
- end
-
- # POST /roles
- def create
- if params.has_key?("inflated_object")
- @role = params["inflated_object"]
- exists = true
- begin
- Chef::Role.load(@role.name)
- rescue Net::HTTPServerException
- exists = false
- end
- raise Forbidden, "Role already exists" if exists
-
- @role.save
- self.status = 201
- display({ :uri => absolute_slice_url(:role, @role.name) })
- else
- begin
- @role = Chef::Role.new
- @role.name(params[:name])
- @role.recipes(params[:for_role] ? params[:for_role] : [])
- @role.description(params[:description]) if params[:description] != ''
- @role.default_attributes(JSON.parse(params[:default_attributes])) if params[:default_attributes] != ''
- @role.override_attributes(JSON.parse(params[:override_attributes])) if params[:override_attributes] != ''
- @role.save
- redirect(slice_url(:roles), :message => { :notice => "Created Role #{@role.name}" })
- rescue ArgumentError
- @available_recipes = get_available_recipes
- @role = Chef::Role.new
- @role.default_attributes(JSON.parse(params[:default_attributes])) if params[:default_attributes] != ''
- @role.override_attributes(JSON.parse(params[:override_attributes])) if params[:override_attributes] != ''
- @current_recipes = params[:for_role] ? params[:for_role] : []
- @_message = { :error => $! }
- render :new
- end
- end
- end
-
- # PUT /roles/:id
- def update
- begin
- @role = Chef::Role.load(params[:id])
- rescue Net::HTTPServerException => e
- raise NotFound, "Cannot load role #{params[:id]}"
- end
-
- if params.has_key?("inflated_object")
- @role.description(params["inflated_object"].description)
- @role.recipes(params["inflated_object"].recipes)
- @role.default_attributes(params["inflated_object"].default_attributes)
- @role.override_attributes(params["inflated_object"].override_attributes)
- @role.save
- self.status = 200
- display(@role)
- else
- begin
- @role.recipes(params[:for_role])
- @role.description(params[:description]) if params[:description] != ''
- @role.default_attributes(JSON.parse(params[:default_attributes])) if params[:default_attributes] != ''
- @role.override_attributes(JSON.parse(params[:override_attributes])) if params[:override_attributes] != ''
- @role.save
- @_message = { :notice => "Updated Role" }
- render :show
- rescue ArgumentError
- @available_recipes = get_available_recipes
- @current_recipes = params[:for_role] ? params[:for_role] : []
- @role.default_attributes(JSON.parse(params[:default_attributes])) if params[:default_attributes] != ''
- @role.override_attributes(JSON.parse(params[:override_attributes])) if params[:override_attributes] != ''
- render :edit
- end
-
- end
- end
-
- # DELETE /roles/:id
- def destroy
- begin
- @role = Chef::Role.load(params[:id])
- rescue Net::HTTPServerException => e
- raise NotFound, "Cannot load role #{params[:id]}"
- end
- @role.destroy
-
- if request.accept == "application/json"
- display @role
- else
- redirect(absolute_slice_url(:roles), :message => { :notice => "Role #{@role.name} deleted successfully." }, :permanent => true)
- end
- end
-
-end
diff --git a/chef-server-webui/LICENSE b/chef-server-webui/LICENSE
new file mode 100644
index 0000000000..11069edd79
--- /dev/null
+++ b/chef-server-webui/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+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.
diff --git a/chef-server-slice/NOTICE b/chef-server-webui/NOTICE
index 02f03a496d..02f03a496d 100644
--- a/chef-server-slice/NOTICE
+++ b/chef-server-webui/NOTICE
diff --git a/chef-server-slice/README.rdoc b/chef-server-webui/README.rdoc
index 2c2eaa706b..2c2eaa706b 100644
--- a/chef-server-slice/README.rdoc
+++ b/chef-server-webui/README.rdoc
diff --git a/chef-server-webui/Rakefile b/chef-server-webui/Rakefile
new file mode 100644
index 0000000000..600458efe2
--- /dev/null
+++ b/chef-server-webui/Rakefile
@@ -0,0 +1,65 @@
+require 'rubygems'
+require 'rake/gempackagetask'
+
+require 'merb-core'
+require 'merb-core/tasks/merb'
+
+GEM_NAME = "chef-server-webui"
+CHEF_SERVER_VERSION="0.8.0"
+AUTHOR = "Opscode"
+EMAIL = "chef@opscode.com"
+HOMEPAGE = "http://wiki.opscode.com/display/chef"
+SUMMARY = "A systems integration framework, built to bring the benefits of configuration management to your entire infrastructure."
+
+spec = Gem::Specification.new do |s|
+ s.rubyforge_project = 'chef'
+ s.name = GEM_NAME
+ s.version = CHEF_SERVER_VERSION
+ s.platform = Gem::Platform::RUBY
+ s.has_rdoc = true
+ s.extra_rdoc_files = ["README.rdoc", "LICENSE" ]
+ s.summary = SUMMARY
+ s.description = s.summary
+ s.author = AUTHOR
+ s.email = EMAIL
+ s.homepage = HOMEPAGE
+
+ ["merb-slices",
+ "stomp",
+ "stompserver",
+ "ferret",
+ "merb-core",
+ "merb-haml",
+ "merb-assets",
+ "merb-helpers",
+ "mongrel",
+ "haml",
+ "ruby-openid",
+ "json",
+ "syntax",].each { |g| s.add_dependency g}
+
+ s.require_path = 'lib'
+ s.files = %w(LICENSE README.rdoc Rakefile) + Dir.glob("{config,lib,spec,app,public,stubs}/**/*")
+end
+
+Rake::GemPackageTask.new(spec) do |pkg|
+ pkg.gem_spec = spec
+end
+
+desc "Install the gem"
+task :install => :package do
+ sh %{sudo gem install pkg/#{GEM_NAME}-#{CHEF_SERVER_VERSION} --no-rdoc --no-ri}
+end
+
+desc "Uninstall the gem"
+task :uninstall do
+ sh %{sudo gem uninstall #{GEM_NAME} -x -v #{CHEF_SERVER_VERSION} }
+end
+
+desc "Create a gemspec file"
+task :gemspec do
+ File.open("#{GEM_NAME}.gemspec", "w") do |file|
+ file.puts spec.to_ruby
+ end
+end
+
diff --git a/chef-server-slice/app/controllers/application.rb b/chef-server-webui/app/controllers/application.rb
index 0aeda27c59..60fd6ddec3 100644
--- a/chef-server-slice/app/controllers/application.rb
+++ b/chef-server-webui/app/controllers/application.rb
@@ -20,7 +20,7 @@
require "chef" / "mixin" / "checksum"
require "chef" / "cookbook_loader"
-class ChefServerSlice::Application < Merb::Controller
+class ChefServerWebui::Application < Merb::Controller
include Chef::Mixin::Checksum
@@ -61,7 +61,7 @@ class ChefServerSlice::Application < Merb::Controller
end
arg.gsub(/\./, '_')
end
-
+
def login_required
if session[:openid]
return session[:openid]
diff --git a/chef-server-webui/app/controllers/cookbook_attributes.rb b/chef-server-webui/app/controllers/cookbook_attributes.rb
new file mode 100644
index 0000000000..f0fa51a9dc
--- /dev/null
+++ b/chef-server-webui/app/controllers/cookbook_attributes.rb
@@ -0,0 +1,41 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Christopher Brown (<cb@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require 'chef' / 'mixin' / 'checksum'
+
+class ChefServerWebui::CookbookAttributes < ChefServerWebui::Application
+
+ provides :html
+
+ before :login_required
+
+ include Chef::Mixin::Checksum
+
+ def load_cookbook_attributes()
+ @attribute_files = load_cookbook_segment(params[:cookbook_id], :attributes)
+ end
+
+ def index
+ load_cookbook_attributes()
+ render
+ end
+
+end
+
+
diff --git a/chef-server-webui/app/controllers/cookbook_definitions.rb b/chef-server-webui/app/controllers/cookbook_definitions.rb
new file mode 100644
index 0000000000..5437d41141
--- /dev/null
+++ b/chef-server-webui/app/controllers/cookbook_definitions.rb
@@ -0,0 +1,41 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Christopher Brown (<cb@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require 'chef' / 'mixin' / 'checksum'
+
+class ChefServerWebui::CookbookDefinitions < ChefServerWebui::Application
+
+ provides :html
+
+ before :login_required
+
+ include Chef::Mixin::Checksum
+
+ def load_cookbook_definitions()
+ @definition_files = load_cookbook_segment(params[:cookbook_id], :definitions)
+ end
+
+ def index
+ load_cookbook_definitions()
+ render
+ end
+
+end
+
+
diff --git a/chef-server-webui/app/controllers/cookbook_files.rb b/chef-server-webui/app/controllers/cookbook_files.rb
new file mode 100644
index 0000000000..a16662c384
--- /dev/null
+++ b/chef-server-webui/app/controllers/cookbook_files.rb
@@ -0,0 +1,39 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Christopher Brown (<cb@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require 'chef' / 'mixin' / 'checksum'
+require 'chef' / 'cookbook_loader'
+require 'chef' / 'mixin' / 'find_preferred_file'
+
+class ChefServerWebui::CookbookFiles < ChefServerWebui::Application
+
+ provides :html
+ before :login_required
+
+ include Chef::Mixin::Checksum
+ include Chef::Mixin::FindPreferredFile
+
+ layout nil
+
+ def index
+ @remote_files = load_cookbook_files(params[:cookbook_id], :remote_file)
+ render
+ end
+
+end
diff --git a/chef-server-webui/app/controllers/cookbook_libraries.rb b/chef-server-webui/app/controllers/cookbook_libraries.rb
new file mode 100644
index 0000000000..65dd23221f
--- /dev/null
+++ b/chef-server-webui/app/controllers/cookbook_libraries.rb
@@ -0,0 +1,41 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Christopher Brown (<cb@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require 'chef' / 'mixin' / 'checksum'
+require 'chef' / 'cookbook_loader'
+
+class ChefServerWebui::CookbookLibraries < ChefServerWebui::Application
+
+ provides :html
+ before :login_required
+
+ include Chef::Mixin::Checksum
+
+ def load_cookbook_libs()
+ @lib_files = load_cookbook_segment(params[:cookbook_id], :libraries)
+ end
+
+ def index
+ load_cookbook_libs()
+ render
+ end
+
+end
+
+
diff --git a/chef-server-webui/app/controllers/cookbook_recipes.rb b/chef-server-webui/app/controllers/cookbook_recipes.rb
new file mode 100644
index 0000000000..9a43abcb09
--- /dev/null
+++ b/chef-server-webui/app/controllers/cookbook_recipes.rb
@@ -0,0 +1,40 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Christopher Brown (<cb@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require 'chef' / 'mixin' / 'checksum'
+
+class ChefServerWebui::CookbookRecipes < ChefServerWebui::Application
+
+ provides :html
+ before :login_required
+
+ include Chef::Mixin::Checksum
+
+ def load_cookbook_recipes()
+ @recipe_files = load_cookbook_segment(params[:cookbook_id], :recipes)
+ end
+
+ def index
+ load_cookbook_recipes()
+ render
+ end
+
+end
+
+
diff --git a/chef-server-slice/app/controllers/cookbook_templates.rb b/chef-server-webui/app/controllers/cookbook_templates.rb
index bce79bd573..1ab991b63e 100644
--- a/chef-server-slice/app/controllers/cookbook_templates.rb
+++ b/chef-server-webui/app/controllers/cookbook_templates.rb
@@ -21,9 +21,9 @@ require 'chef' / 'mixin' / 'checksum'
require 'chef' / 'cookbook_loader'
require 'chef' / 'mixin' / 'find_preferred_file'
-class ChefServerSlice::CookbookTemplates < ChefServerSlice::Application
+class ChefServerWebui::CookbookTemplates < ChefServerWebui::Application
- provides :html, :json
+ provides :html
before :login_required
include Chef::Mixin::Checksum
@@ -50,31 +50,8 @@ class ChefServerSlice::CookbookTemplates < ChefServerSlice::Application
# end
def index
- if params[:id]
- show
- else
- @templates = load_cookbook_files(params[:cookbook_id], :template)
- display @templates
- end
- end
-
- def show
- to_send = find_preferred_file(
- params[:cookbook_id],
- :template,
- params[:id],
- params[:fqdn],
- params[:platform],
- params[:version]
- )
- raise NotFound, "Cannot find a suitable template!" unless to_send
- current_checksum = checksum(to_send)
- Chef::Log.debug("old sum: #{params[:checksum]}, new sum: #{current_checksum}")
- if current_checksum == params[:checksum]
- render "Template #{to_send} has not changed", :status => 304
- else
- send_file(to_send)
- end
+ @templates = load_cookbook_files(params[:cookbook_id], :template)
+ display @templates
end
end
diff --git a/chef-server-slice/app/controllers/cookbooks.rb b/chef-server-webui/app/controllers/cookbooks.rb
index 24c8be5f17..4460ac6cde 100644
--- a/chef-server-slice/app/controllers/cookbooks.rb
+++ b/chef-server-webui/app/controllers/cookbooks.rb
@@ -19,7 +19,7 @@
require 'chef' / 'cookbook_loader'
-class ChefServerSlice::Cookbooks < ChefServerSlice::Application
+class ChefServerWebui::Cookbooks < ChefServerWebui::Application
provides :html, :json
before :login_required
diff --git a/chef-server-webui/app/controllers/exceptions.rb b/chef-server-webui/app/controllers/exceptions.rb
new file mode 100644
index 0000000000..c1344b8fae
--- /dev/null
+++ b/chef-server-webui/app/controllers/exceptions.rb
@@ -0,0 +1,19 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Christopher Brown (<cb@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
diff --git a/chef-server-webui/app/controllers/main.rb b/chef-server-webui/app/controllers/main.rb
new file mode 100644
index 0000000000..7ee2c60182
--- /dev/null
+++ b/chef-server-webui/app/controllers/main.rb
@@ -0,0 +1,7 @@
+class ChefServerWebui::Main < ChefServerWebui::Application
+
+ def index
+ render
+ end
+
+end
diff --git a/chef-server-webui/app/controllers/nodes.rb b/chef-server-webui/app/controllers/nodes.rb
new file mode 100644
index 0000000000..d95e7fd262
--- /dev/null
+++ b/chef-server-webui/app/controllers/nodes.rb
@@ -0,0 +1,114 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Christopher Brown (<cb@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require 'chef' / 'node'
+
+class ChefServerWebui::Nodes < ChefServerWebui::Application
+
+ provides :html
+
+ before :fix_up_node_id
+ before :login_required
+ before :authorized_node, :only => [ :update, :destroy ]
+
+ def index
+ @node_list = Chef::Node.list
+ render
+ end
+
+ def show
+ begin
+ @node = Chef::Node.load(params[:id])
+ rescue Net::HTTPServerException => e
+ raise NotFound, "Cannot load node #{params[:id]}"
+ end
+ render
+ end
+
+ def new
+ @node = Chef::Node.new
+ @available_recipes = get_available_recipes
+ @available_roles = Chef::Role.list.sort
+ @run_list = @node.run_list
+ render
+ end
+
+ def edit
+ begin
+ @node = Chef::Node.load(params[:id])
+ rescue Net::HTTPServerException => e
+ raise NotFound, "Cannot load node #{params[:id]}"
+ end
+ @available_recipes = get_available_recipes
+ @available_roles = Chef::Role.list.sort
+ @run_list = @node.run_list
+ render
+ end
+
+ def create
+ begin
+ @node = Chef::Node.new
+ @node.name params[:name]
+ @node.attribute = JSON.parse(params[:attributes])
+ @node.run_list params[:for_node]
+ @node.save
+ redirect(slice_url(:nodes), :message => { :notice => "Created Node #{@node.name}" })
+ rescue
+ @node.attribute = JSON.parse(params[:attributes])
+ @available_recipes = get_available_recipes
+ @available_roles = Chef::Role.list.sort
+ @run_list = params[:for_node]
+ @_message = { :error => $! }
+ render :new
+ end
+ end
+
+ def update
+ begin
+ @node = Chef::Node.load(params[:id])
+ rescue Net::HTTPServerException => e
+ raise NotFound, "Cannot load node #{params[:id]}"
+ end
+
+ begin
+ @node.run_list.reset(params[:for_node] ? params[:for_node] : [])
+ @node.attribute = JSON.parse(params[:attributes])
+ @node.save
+ @_message = { :notice => "Updated Node" }
+ render :show
+ rescue
+ @available_recipes = get_available_recipes
+ @available_roles = Chef::Role.list.sort
+ @run_list = Chef::RunList.new
+ @run_list.reset(params[:for_node])
+ render :edit
+ end
+ end
+
+ def destroy
+ begin
+ @node = Chef::Node.load(params[:id])
+ rescue Net::HTTPServerException => e
+ raise NotFound, "Cannot load node #{params[:id]}"
+ end
+ @node.destroy
+ redirect(absolute_slice_url(:nodes), {:message => { :notice => "Node #{params[:id]} deleted successfully" }, :permanent => true})
+ end
+
+end
diff --git a/chef-server-slice/app/controllers/openid_consumer.rb b/chef-server-webui/app/controllers/openid_consumer.rb
index e42ef2bb74..e2bab98606 100644
--- a/chef-server-slice/app/controllers/openid_consumer.rb
+++ b/chef-server-webui/app/controllers/openid_consumer.rb
@@ -21,9 +21,9 @@ require 'pathname'
require 'openid'
require (Chef::Config[:openid_cstore_couchdb] ? 'openid-store-couchdb' : 'openid/store/filesystem')
-class ChefServerSlice::OpenidConsumer < ChefServerSlice::Application
+class ChefServerWebui::OpenidConsumer < ChefServerWebui::Application
- provides :html, :json
+ provides :html
def index
if request.xhr?
@@ -107,12 +107,12 @@ class ChefServerSlice::OpenidConsumer < ChefServerSlice::Application
true
end
end
-
+
def is_authorized_openid_identifier?(openid, authorized_identifiers)
Chef::Log.debug("checking for valid openid identifier: openid: #{openid}, authorized openids: #{authorized_identifiers}")
if authorized_identifiers and openid
- if authorized_identifiers.length > 0
- authorized_identifiers.detect { |p| Chef::Log.debug("openid: #{openid} (#{openid.class}), p: #{p} (#{p.class})"); openid == p }
+ if authorized_identifiers.length > 0
+ authorized_identifiers.detect { |p| Chef::Log.debug("openid: #{openid} (#{openid.class}), p: #{p} (#{p.class})"); openid == p }
else
true
end
diff --git a/chef-server-webui/app/controllers/roles.rb b/chef-server-webui/app/controllers/roles.rb
new file mode 100644
index 0000000000..10ad8b84db
--- /dev/null
+++ b/chef-server-webui/app/controllers/roles.rb
@@ -0,0 +1,103 @@
+require 'chef/role'
+
+class ChefServerWebui::Roles < ChefServerWebui::Application
+
+ provides :html
+ before :login_required
+
+ # GET /roles
+ def index
+ @role_list = Chef::Role.list(true)
+ render
+ end
+
+ # GET /roles/:id
+ def show
+ begin
+ @role = Chef::Role.load(params[:id])
+ rescue Net::HTTPServerException => e
+ raise NotFound, "Cannot load role #{params[:id]}"
+ end
+ render
+ end
+
+ # GET /roles/new
+ def new
+ @available_recipes = get_available_recipes
+ @role = Chef::Role.new
+ @current_recipes = @role.recipes
+ render
+ end
+
+ # GET /roles/:id/edit
+ def edit
+ begin
+ @role = Chef::Role.load(params[:id])
+ rescue Net::HTTPServerException => e
+ raise NotFound, "Cannot load role #{params[:id]}"
+ end
+ @available_recipes = get_available_recipes
+ @current_recipes = @role.recipes
+ render
+ end
+
+ # POST /roles
+ def create
+ begin
+ @role = Chef::Role.new
+ @role.name(params[:name])
+ @role.recipes(params[:for_role] ? params[:for_role] : [])
+ @role.description(params[:description]) if params[:description] != ''
+ @role.default_attributes(JSON.parse(params[:default_attributes])) if params[:default_attributes] != ''
+ @role.override_attributes(JSON.parse(params[:override_attributes])) if params[:override_attributes] != ''
+ @role.save
+ redirect(slice_url(:roles), :message => { :notice => "Created Role #{@role.name}" })
+ rescue ArgumentError
+ @available_recipes = get_available_recipes
+ @role = Chef::Role.new
+ @role.default_attributes(JSON.parse(params[:default_attributes])) if params[:default_attributes] != ''
+ @role.override_attributes(JSON.parse(params[:override_attributes])) if params[:override_attributes] != ''
+ @current_recipes = params[:for_role] ? params[:for_role] : []
+ @_message = { :error => $! }
+ render :new
+ end
+ end
+
+ # PUT /roles/:id
+ def update
+ begin
+ @role = Chef::Role.load(params[:id])
+ rescue Net::HTTPServerException => e
+ raise NotFound, "Cannot load role #{params[:id]}"
+ end
+
+ begin
+ @role.recipes(params[:for_role])
+ @role.description(params[:description]) if params[:description] != ''
+ @role.default_attributes(JSON.parse(params[:default_attributes])) if params[:default_attributes] != ''
+ @role.override_attributes(JSON.parse(params[:override_attributes])) if params[:override_attributes] != ''
+ @role.save
+ @_message = { :notice => "Updated Role" }
+ render :show
+ rescue ArgumentError
+ @available_recipes = get_available_recipes
+ @current_recipes = params[:for_role] ? params[:for_role] : []
+ @role.default_attributes(JSON.parse(params[:default_attributes])) if params[:default_attributes] != ''
+ @role.override_attributes(JSON.parse(params[:override_attributes])) if params[:override_attributes] != ''
+ render :edit
+ end
+ end
+
+ # DELETE /roles/:id
+ def destroy
+ begin
+ @role = Chef::Role.load(params[:id])
+ rescue Net::HTTPServerException => e
+ raise NotFound, "Cannot load role #{params[:id]}"
+ end
+ @role.destroy
+
+ redirect(absolute_slice_url(:roles), :message => { :notice => "Role #{@role.name} deleted successfully." }, :permanent => true)
+ end
+
+end
diff --git a/chef-server-slice/app/controllers/search.rb b/chef-server-webui/app/controllers/search.rb
index 0f56106492..71eaf86faa 100644
--- a/chef-server-slice/app/controllers/search.rb
+++ b/chef-server-webui/app/controllers/search.rb
@@ -20,15 +20,15 @@
require 'chef' / 'search'
require 'chef' / 'queue'
-class ChefServerSlice::Search < ChefServerSlice::Application
+class ChefServerWebui::Search < ChefServerWebui::Application
- provides :html, :json
+ provides :html
before :login_required
def index
@s = Chef::Search.new
@search_indexes = @s.list_indexes
- display @search_indexes
+ render
end
def show
@@ -37,8 +37,8 @@ class ChefServerSlice::Search < ChefServerSlice::Application
query = params[:q].nil? ? "*" : (params[:q].empty? ? "*" : params[:q])
attributes = params[:a].nil? ? [] : params[:a].split(",").collect { |a| a.to_sym }
@results = @s.search(params[:id], query, attributes)
-
- display @results
+
+ render
end
def destroy
@@ -48,11 +48,7 @@ class ChefServerSlice::Search < ChefServerSlice::Application
Chef::Queue.send_msg(:queue, :remove, entry)
end
@status = 202
- if content_type == :html
- redirect url(:search)
- else
- display @entries
- end
+ redirect url(:search)
end
end
diff --git a/chef-server-slice/app/controllers/search_entries.rb b/chef-server-webui/app/controllers/search_entries.rb
index dfb90e0b27..ab5d034300 100644
--- a/chef-server-slice/app/controllers/search_entries.rb
+++ b/chef-server-webui/app/controllers/search_entries.rb
@@ -20,21 +20,21 @@
require 'chef' / 'search'
require 'chef' / 'queue'
-class ChefServerSlice::SearchEntries < ChefServerSlice::Application
+class ChefServerWebui::SearchEntries < ChefServerWebui::Application
- provides :html, :json
+ provides :html
before :login_required
def index
@s = Chef::Search.new
@entries = @s.search(params[:search_id])
- display @entries
+ render
end
def show
@s = Chef::Search.new
@entry = @s.search(params[:search_id], "id:'#{params[:search_id]}_#{params[:id]}'").first
- display @entry
+ render
end
def create
@@ -44,12 +44,7 @@ class ChefServerSlice::SearchEntries < ChefServerSlice::Application
@to_index["id"] = "#{params[:search_id]}_#{params[:id]}"
@to_index.delete(:search_id)
Chef::Queue.send_msg(:queue, :index, @to_index)
- if content_type == :html
- redirect url(:search)
- else
- @status = 202
- display @to_index
- end
+ redirect url(:search)
end
def update
@@ -63,11 +58,7 @@ class ChefServerSlice::SearchEntries < ChefServerSlice::Application
Chef::Queue.send_msg(:queue, :remove, entry)
end
@status = 202
- if content_type == :html
- redirect url(:search)
- else
- display @entries
- end
+ redirect url(:search)
end
end
diff --git a/chef-server-slice/app/controllers/status.rb b/chef-server-webui/app/controllers/status.rb
index 86cef9d377..fd1cf14695 100644
--- a/chef-server-slice/app/controllers/status.rb
+++ b/chef-server-webui/app/controllers/status.rb
@@ -18,9 +18,9 @@
require 'chef' / 'node'
-class ChefServerSlice::Status < ChefServerSlice::Application
+class ChefServerWebui::Status < ChefServerWebui::Application
- provides :html, :json
+ provides :html
before :login_required
def index
@@ -28,7 +28,7 @@ class ChefServerSlice::Status < ChefServerSlice::Application
result << item["value"]
result
end
- display @status
+ render
end
end
diff --git a/chef-server-slice/app/helpers/application_helper.rb b/chef-server-webui/app/helpers/application_helper.rb
index ca1da4b7b2..1b1d467829 100644
--- a/chef-server-slice/app/helpers/application_helper.rb
+++ b/chef-server-webui/app/helpers/application_helper.rb
@@ -1,7 +1,7 @@
require 'chef/mixin/deep_merge'
module Merb
- module ChefServerSlice
+ module ChefServerWebui
module ApplicationHelper
# @param *segments<Array[#to_s]> Path segments to append.
@@ -36,7 +36,7 @@ module Merb
# @return <String>
# A path relative to the public directory, with added segments.
def public_path_for(type, *segments)
- ::ChefServerSlice.public_path_for(type, *segments)
+ ::ChefServerWebui.public_path_for(type, *segments)
end
# Construct an app-level path.
@@ -47,7 +47,7 @@ module Merb
# @return <String>
# A path within the host application, with added segments.
def app_path_for(type, *segments)
- ::ChefServerSlice.app_path_for(type, *segments)
+ ::ChefServerWebui.app_path_for(type, *segments)
end
# Construct a slice-level path.
@@ -58,7 +58,7 @@ module Merb
# @return <String>
# A path within the slice source (Gem), with added segments.
def slice_path_for(type, *segments)
- ::ChefServerSlice.slice_path_for(type, *segments)
+ ::ChefServerWebui.slice_path_for(type, *segments)
end
def build_tree(name, node, default={}, override={})
diff --git a/chef-server-slice/app/helpers/cookbook_attributes_helper.rb b/chef-server-webui/app/helpers/cookbook_attributes_helper.rb
index 3246d45239..641c0bbfd4 100644
--- a/chef-server-slice/app/helpers/cookbook_attributes_helper.rb
+++ b/chef-server-webui/app/helpers/cookbook_attributes_helper.rb
@@ -1,5 +1,5 @@
module Merb
- module ChefServerSlice
+ module ChefServerWebui
module CookbookAttributesHelper
end
diff --git a/chef-server-slice/app/helpers/cookbook_definitions_helper.rb b/chef-server-webui/app/helpers/cookbook_definitions_helper.rb
index a7d7792261..905db82418 100644
--- a/chef-server-slice/app/helpers/cookbook_definitions_helper.rb
+++ b/chef-server-webui/app/helpers/cookbook_definitions_helper.rb
@@ -1,5 +1,5 @@
module Merb
- module ChefServerSlice
+ module ChefServerWebui
module CookbookDefinitionsHelper
end
diff --git a/chef-server-slice/app/helpers/cookbook_files_helper.rb b/chef-server-webui/app/helpers/cookbook_files_helper.rb
index 9a7b890722..0425d1664e 100644
--- a/chef-server-slice/app/helpers/cookbook_files_helper.rb
+++ b/chef-server-webui/app/helpers/cookbook_files_helper.rb
@@ -1,5 +1,5 @@
module Merb
- module ChefServerSlice
+ module ChefServerWebui
module CookbookFilesHelper
end
diff --git a/chef-server-slice/app/helpers/cookbook_libraries_helper.rb b/chef-server-webui/app/helpers/cookbook_libraries_helper.rb
index f0aaa8578f..bc6eded61b 100644
--- a/chef-server-slice/app/helpers/cookbook_libraries_helper.rb
+++ b/chef-server-webui/app/helpers/cookbook_libraries_helper.rb
@@ -1,5 +1,5 @@
module Merb
- module ChefServerSlice
+ module ChefServerWebui
module CookbookLibrariesHelper
end
diff --git a/chef-server-slice/app/helpers/cookbook_recipes_helper.rb b/chef-server-webui/app/helpers/cookbook_recipes_helper.rb
index 8038ec052c..98aa8a16f8 100644
--- a/chef-server-slice/app/helpers/cookbook_recipes_helper.rb
+++ b/chef-server-webui/app/helpers/cookbook_recipes_helper.rb
@@ -1,5 +1,5 @@
module Merb
- module ChefServerSlice
+ module ChefServerWebui
module CookbookRecipesHelper
end
diff --git a/chef-server-slice/app/helpers/cookbook_templates_helper.rb b/chef-server-webui/app/helpers/cookbook_templates_helper.rb
index d003b794af..ba62387f52 100644
--- a/chef-server-slice/app/helpers/cookbook_templates_helper.rb
+++ b/chef-server-webui/app/helpers/cookbook_templates_helper.rb
@@ -1,5 +1,5 @@
module Merb
- module ChefServerSlice
+ module ChefServerWebui
module CookbookTemplatesHelper
end
diff --git a/chef-server-slice/app/helpers/cookbooks_helper.rb b/chef-server-webui/app/helpers/cookbooks_helper.rb
index 1d52d9ee54..e3e06165d8 100644
--- a/chef-server-slice/app/helpers/cookbooks_helper.rb
+++ b/chef-server-webui/app/helpers/cookbooks_helper.rb
@@ -14,17 +14,18 @@
# 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.
+#
module Merb
- module ChefServerSlice
+ module ChefServerWebui
module CookbooksHelper
def syntax_highlight(code)
+ converter = Syntax::Convertors::HTML.for_syntax "ruby"
if File.exists?(code)
- tokens = CodeRay.scan_file(code, :ruby)
+ converter.convert(File.read(code), false)
else
- tokens = CodeRay.scan(code, :ruby)
+ converter.convert(code, false)
end
- return CodeRay.encode_tokens(tokens, :span)
end
end
end
diff --git a/chef-server-webui/app/helpers/exceptions_helper.rb b/chef-server-webui/app/helpers/exceptions_helper.rb
new file mode 100644
index 0000000000..e27ca180af
--- /dev/null
+++ b/chef-server-webui/app/helpers/exceptions_helper.rb
@@ -0,0 +1,6 @@
+module Merb
+ module ChefServerWebui
+ module ExceptionsHelper
+ end
+ end
+end # Merb
diff --git a/chef-server-slice/app/helpers/global_helpers.rb b/chef-server-webui/app/helpers/global_helpers.rb
index 645343a6a7..17e26abd0e 100644
--- a/chef-server-slice/app/helpers/global_helpers.rb
+++ b/chef-server-webui/app/helpers/global_helpers.rb
@@ -17,7 +17,7 @@
#
module Merb
- module ChefServerSlice
+ module ChefServerWebui
module GlobalHelpers
# helpers defined here available to all views.
diff --git a/chef-server-slice/app/helpers/nodes_helper.rb b/chef-server-webui/app/helpers/nodes_helper.rb
index c108442415..c6ef880ab1 100644
--- a/chef-server-slice/app/helpers/nodes_helper.rb
+++ b/chef-server-webui/app/helpers/nodes_helper.rb
@@ -18,7 +18,7 @@
#
module Merb
- module ChefServerSlice
+ module ChefServerWebui
module NodesHelper
def recipe_list(node)
response = ""
@@ -28,6 +28,14 @@ module Merb
response
end
+ def attribute_list(node)
+ response = ""
+ node.each_attribute do |k,v|
+ response << "<li><b>#{k}</b>: #{v}</li>\n"
+ end
+ response
+ end
+
end
end
end
diff --git a/chef-server-slice/app/helpers/openid_consumer_helper.rb b/chef-server-webui/app/helpers/openid_consumer_helper.rb
index 5564b9a73c..33bfe193ff 100644
--- a/chef-server-slice/app/helpers/openid_consumer_helper.rb
+++ b/chef-server-webui/app/helpers/openid_consumer_helper.rb
@@ -1,5 +1,5 @@
module Merb
- module ChefServerSlice
+ module ChefServerWebui
module OpenidConsumerHelper
end
diff --git a/chef-server-slice/app/helpers/openid_register_helper.rb b/chef-server-webui/app/helpers/openid_register_helper.rb
index 4c31be158f..1eadb4dfeb 100644
--- a/chef-server-slice/app/helpers/openid_register_helper.rb
+++ b/chef-server-webui/app/helpers/openid_register_helper.rb
@@ -1,5 +1,5 @@
module Merb
- module ChefServerSlice
+ module ChefServerWebui
module OpenidRegisterHelper
end
diff --git a/chef-server-slice/app/helpers/openid_server_helper.rb b/chef-server-webui/app/helpers/openid_server_helper.rb
index 2bb6faf912..366e36a091 100644
--- a/chef-server-slice/app/helpers/openid_server_helper.rb
+++ b/chef-server-webui/app/helpers/openid_server_helper.rb
@@ -1,5 +1,5 @@
module Merb
- module ChefServerSlice
+ module ChefServerWebui
module OpenidServerHelper
end
end
diff --git a/chef-server-slice/app/helpers/openid_server_helpers.rb b/chef-server-webui/app/helpers/openid_server_helpers.rb
index facf5d22b9..0e248a6c40 100644
--- a/chef-server-slice/app/helpers/openid_server_helpers.rb
+++ b/chef-server-webui/app/helpers/openid_server_helpers.rb
@@ -17,7 +17,7 @@
#
module Merb
- module ChefServerSlice
+ module ChefServerWebui
module OpenidServerHelper
def url_for_user
diff --git a/chef-server-webui/app/helpers/roles_helper.rb b/chef-server-webui/app/helpers/roles_helper.rb
new file mode 100644
index 0000000000..b6d46cdcd1
--- /dev/null
+++ b/chef-server-webui/app/helpers/roles_helper.rb
@@ -0,0 +1,5 @@
+module Merb
+ module RolesHelper
+
+ end
+end # Merb \ No newline at end of file
diff --git a/chef-server-slice/app/helpers/search_entries_helper.rb b/chef-server-webui/app/helpers/search_entries_helper.rb
index 750ed3749b..08ced2366b 100644
--- a/chef-server-slice/app/helpers/search_entries_helper.rb
+++ b/chef-server-webui/app/helpers/search_entries_helper.rb
@@ -1,5 +1,5 @@
module Merb
- module ChefServerSlice
+ module ChefServerWebui
module SearchEntriesHelper
end
diff --git a/chef-server-slice/app/helpers/search_helper.rb b/chef-server-webui/app/helpers/search_helper.rb
index d7de6e6d70..c6cde5c9f3 100644
--- a/chef-server-slice/app/helpers/search_helper.rb
+++ b/chef-server-webui/app/helpers/search_helper.rb
@@ -1,5 +1,5 @@
module Merb
- module ChefServerSlice
+ module ChefServerWebui
module SearchHelper
def output_path(attributes)
res = Hash.new
diff --git a/chef-server-slice/app/helpers/status_helper.rb b/chef-server-webui/app/helpers/status_helper.rb
index 9f5694ae70..2ce9fa6148 100644
--- a/chef-server-slice/app/helpers/status_helper.rb
+++ b/chef-server-webui/app/helpers/status_helper.rb
@@ -18,7 +18,7 @@
require 'chef' / 'node'
module Merb
- module ChefServerSlice
+ module ChefServerWebui
module StatusHelper
end
diff --git a/chef-server-slice/app/views/cookbook_templates/index.html.haml b/chef-server-webui/app/views/cookbook_templates/index.html.haml
index 0e52aa9212..0e52aa9212 100644
--- a/chef-server-slice/app/views/cookbook_templates/index.html.haml
+++ b/chef-server-webui/app/views/cookbook_templates/index.html.haml
diff --git a/chef-server-slice/app/views/cookbooks/index.html.haml b/chef-server-webui/app/views/cookbooks/index.html.haml
index d8e9eabda6..d8e9eabda6 100644
--- a/chef-server-slice/app/views/cookbooks/index.html.haml
+++ b/chef-server-webui/app/views/cookbooks/index.html.haml
diff --git a/chef-server-slice/app/views/cookbooks/show.html.haml b/chef-server-webui/app/views/cookbooks/show.html.haml
index dedd5fefa0..dedd5fefa0 100644
--- a/chef-server-slice/app/views/cookbooks/show.html.haml
+++ b/chef-server-webui/app/views/cookbooks/show.html.haml
diff --git a/chef-server-webui/app/views/exceptions/bad_request.json.erb b/chef-server-webui/app/views/exceptions/bad_request.json.erb
new file mode 100644
index 0000000000..f266cf99b9
--- /dev/null
+++ b/chef-server-webui/app/views/exceptions/bad_request.json.erb
@@ -0,0 +1 @@
+<%= { :error => params[:exception], :code => 400 }.to_json %> \ No newline at end of file
diff --git a/chef-server-webui/app/views/exceptions/internal_server_error.html.erb b/chef-server-webui/app/views/exceptions/internal_server_error.html.erb
new file mode 100644
index 0000000000..aadbfad350
--- /dev/null
+++ b/chef-server-webui/app/views/exceptions/internal_server_error.html.erb
@@ -0,0 +1,216 @@
+<html>
+<head>
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+ <title><%= @exception_name %></title>
+ <style type="text/css" media="screen">
+ body {
+ font-family:arial;
+ font-size:11px;
+ }
+ h1 {
+ font-size:48px;
+ letter-spacing:-4px;
+ margin:0;
+ line-height:36px;
+ color:#333;
+ }
+ h1 sup {
+ font-size: 0.5em;
+ }
+ h1 sup.error_500, h1 sup.error_400 {
+ color:#990E05;
+ }
+ h1 sup.error_100, h1 sup.error_200 {
+ color:#00BF10;
+ }
+ h1 sup.error_300 {
+ /* pretty sure you cant 'see' status 300
+ errors but if you could I think they
+ would be blue */
+ color:#1B2099;
+ }
+ h2 {
+ font-size:36px;
+ letter-spacing:-3px;
+ margin:0;
+ line-height:28px;
+ color:#444;
+ }
+ a, a:visited {
+ color:#00BF10;
+ }
+ .internalError {
+ width:800px;
+ margin:50px auto;
+ }
+ .header {
+ border-bottom:10px solid #333;
+ margin-bottom:1px;
+ background-image: url("data:image/gif;base64,R0lGODlhAwADAIAAAP///8zMzCH5BAAAAAAALAAAAAADAAMAAAIEBHIJBQA7");
+ padding:20px;
+ }
+ table.trace {
+ width:100%;
+ font-family:courier, monospace;
+ letter-spacing:-1px;
+ border-collapse: collapse;
+ border-spacing:0;
+ }
+ table.trace tr td{
+ padding:0;
+ height:26px;
+ font-size:13px;
+ vertical-align:middle;
+ }
+ table.trace tr.file{
+ border-top:2px solid #fff;
+ background-color:#F3F3F3;
+ }
+ table.trace tr.source {
+ background-color:#F8F8F8;
+ display:none;
+ }
+ table.trace .open tr.source {
+ display:table-row;
+ }
+ table.trace tr.file td.expand {
+ width:23px;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAXCAIAAABvSEP3AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAdVJREFUeNqMVL+TwUAYxaRIOlEhlZHGDAUzzOQ61+AqXMV1lJSU7q/QRqm8KFUcJTNn5qJkaPyoKKVz7y4mF8na5Kt29tt9+/Z97/u81+vVQ4r9frdarS6Xi7ETDIZisRjxMGPfmk4niNPpZE+xLAugbPaZ53nzvtfMBe/3+/3dbuehBrAKhZdUKkVAWa9Xsiybv0CPZDJZLr/qa5/BwgwRjYqOKIvFYjQa/aNommZh0Ww2K5UqzwfoQOPxaLPZ3FAmk0+7lplMpt1u53J5OpBOR0eZEE9wHJfP5zud93g88QhluwWbjW+5VOmKBgKBer3eaDTDYeGBQF8+x7rqIYoiPgixWJazpA6HA+MSxRArkUgMh0M409g8Ho8+9wYxxCqVSq1W26EDHGM2m4HOHQrEc38f/Yn7cLmlIRhBENzcx8cVRZnPZ/YUep2BWkjTIfA+PKVpZAXR5QxsjiqCKvGEqqp443w+0dvy17swqD0HB3S73V5PpkNg1qBqt8kwGCjmPkinM0QJbIoEa7U6UG6ToVgs4V9G2g0ESoP5Aoi7KYX5oCgf8IKbkvn9/mr1LRQKESamzgJy0g0tSZIuB3nuGqRU9Vv9C4sKkUhEkp4soxvxI8AAhWrrtXa3X8EAAAAASUVORK5CYII=);
+ background-position:top left;
+ background-repeat:no-repeat;
+ }
+ table.trace .open tr.file td.expand {
+ width:19px;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAB1CAIAAAAqdO2mAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAXZJREFUeNrslK1ywkAUhcMOBomEOiSdqLxEBJX0NaijOsjyHGGmCGyQQYaiiiw4gktkcOmZbpsuuzQ/M5XnqJ2d3S/n3nM3rTzPLUP7/Tt0+pLcGQwG3W53OLyHzPMtjYL7q9UqSRLrD4E1Gj1orCvKYuFHUWTVkOM44/HjDcp8/lL4r6NerzeZPMm1KFw0QkDn83m5fP2lHA4fNQvRtNvtjsfDd0WzmSfb2e/fdTqdOvdh/HLJZLOn0+d2HJ+KRGzbdl23EpFlmed5cp2maRzHQq1lvQ5KMi6EUZBGfup6E1pTfd+vrGW7jbQ2C9hTt9BpqNyIWaAwAy6xg2eBz5iRC/NomiZhGN5sqmnkauo0BUGgVQoBjQ80oCACgNQdZHfTYBkF2mxCtWWAqunWpahxIDUt3QYUxIFQpJHyIWpXjinabKbbwItMHT+NyjchrP8QKaSQQgoppJBCCimkkEIKKaSQQgoppJBCCimkkEIKKaSo+hRgAEFD17X08O2NAAAAAElFTkSuQmCC);
+ background-position:top left;
+ background-repeat:no-repeat;
+ }
+ table.trace tr.source td.collapse {
+ width:19px;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAB1CAIAAAAqdO2mAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAVxJREFUeNrs0zFygkAUBmBlUkgJHdABlQwVkVJKKUxBYWbkALTxMJwhltyDFkss03IF8pudIcwaDaDl/6pd2P327b7d+eHwMXs4lNkzggoVKlSoUKFChQoVKlSoUKFChQoVKlSoUKFChQqVEYqm6ft9+qiSJEkYho7jTlcw2fd9NOI4nq4gEdFwXXe1Cqco63VkWVbXRTqLhTpOwQRpF7quR1E0TgGhqvLKUFCyoQqG/rks3O6kZKW/eRFpevOCoGTXVTcMQ5EyxyDEkML1c5RzuZOICIyXqn7JBVez6282MWrx731HOv2qB8Hri2lamNk0DfpVVdV1Peodappmmua8bdvzuc7zfNprzrLMth1FnGh/X8MjCAIQv/cFz/+65PcDh7rbvYv2ZUfdj+PxsyzLgVl0hKwgTqeqKApx2LeOc7t98zyv/1FWOgvx9RPii23bmL9cetJ8Ed8CDAC6aFW8bCzFhwAAAABJRU5ErkJggg==);
+ background-position:bottom left;
+ background-repeat:no-repeat;
+ background-color:#6F706F;
+ }
+ table.trace tr td.path {
+ padding-left:10px;
+ }
+ table.trace tr td.code {
+ padding-left:35px;
+ white-space: pre;
+ line-height:9px;
+ padding-bottom:10px;
+ }
+ table.trace tr td.code em {
+ font-weight:bold;
+ color:#00BF10;
+ }
+ table.trace tr td.code a {
+ width: 20px;
+ float: left;
+ }
+ table.trace tr td.code .more {
+ color:#666;
+ }
+ table.trace tr td.line {
+ width:30px;
+ text-align:right;
+ padding-right:4px;
+ }
+ .footer {
+ margin-top:5px;
+ font-size:11px;
+ color:#444;
+ text-align:right;
+ }
+ </style>
+</head>
+<body>
+ <div class="internalError">
+
+ <div class="header">
+ <h1><%= @exception_name %> <sup class="error_<%= @exception.class::STATUS %>"><%= @exception.class::STATUS %></sup></h1>
+ <% if show_details = ::Merb::Config[:exception_details] -%>
+ <h2><%= @exception.message %></h2>
+ <% else -%>
+ <h2>Sorry about that...</h2>
+ <% end -%>
+ <h3>Parameters</h3>
+ <ul>
+ <% params[:original_params].each do |param, value| %>
+ <li><strong><%= param %>:</strong> <%= value.inspect %></li>
+ <% end %>
+ <%= "<li>None</li>" if params[:original_params].empty? %>
+ </ul>
+
+ <h3>Session</h3>
+ <ul>
+ <% params[:original_session].each do |param, value| %>
+ <li><strong><%= param %>:</strong> <%= value.inspect %></li>
+ <% end %>
+ <%= "<li>None</li>" if params[:original_session].empty? %>
+ </ul>
+
+ <h3>Cookies</h3>
+ <ul>
+ <% params[:original_cookies].each do |param, value| %>
+ <li><strong><%= param %>:</strong> <%= value.inspect %></li>
+ <% end %>
+ <%= "<li>None</li>" if params[:original_cookies].empty? %>
+ </ul>
+ </div>
+
+ <% if show_details %>
+ <table class="trace">
+ <% @exception.backtrace.each_with_index do |line, index| %>
+ <tbody class="close">
+ <tr class="file">
+ <td class="expand">
+ </td>
+ <td class="path">
+ <%= (line.match(/^([^:]+)/)[1] rescue 'unknown').sub(/\/((opt|usr)\/local\/lib\/(ruby\/)?(gems\/)?(1.8\/)?(gems\/)?|.+\/app\/)/, '') %>
+ <% unless line.match(/\.erb:/) %>
+ in "<strong><%= line.match(/:in `(.+)'$/)[1] rescue '?' %></strong>"
+ <% else %>
+ (<strong>ERB Template</strong>)
+ <% end %>
+ </td>
+ <td class="line">
+ <a href="txmt://open?url=file://<%=file = (line.match(/^([^:]+)/)[1] rescue 'unknown')%>&amp;line=<%= lineno = line.match(/:([0-9]+):/)[1] rescue '?' %>"><%=lineno%></a>&nbsp;
+ </td>
+ </tr>
+ <tr class="source">
+ <td class="collapse">
+ </td>
+ <td class="code" colspan="2"><% (__caller_lines__(file, lineno, 5) rescue []).each do |llineno, lcode, lcurrent| %>
+<a href="txmt://open?url=file://<%=file%>&amp;line=<%=llineno%>"><%= llineno %></a><%='<em>' if llineno==lineno.to_i %><%= lcode.size > 90 ? CGI.escapeHTML(lcode[0..90])+'<span class="more">......</span>' : CGI.escapeHTML(lcode) %><%='</em>' if llineno==lineno.to_i %>
+<% end %>
+
+</td>
+ </tr>
+ </tbody>
+ <% end %>
+ </table>
+ <script type="text/javascript" charset="utf-8">
+ // swop the open & closed classes
+ els = document.getElementsByTagName('td');
+ for(i=0; i<els.length; i++){
+ if(els[i].className=='expand' || els[i].className=='collapse'){
+ els[i].onclick = function(e){
+ tbody = this.parentNode.parentNode;
+ if(tbody.className=='open'){
+ tbody.className='closed';
+ }else{
+ tbody.className='open';
+ }
+ }
+ }
+ }
+ </script>
+ <% end %>
+ <div class="footer">
+ lots of love, from <a href="#">merb</a>
+ </div>
+ </div>
+</body>
+</html> \ No newline at end of file
diff --git a/chef-server-slice/app/views/exceptions/not_acceptable.html.erb b/chef-server-webui/app/views/exceptions/not_acceptable.html.erb
index f632712bb2..f632712bb2 100644
--- a/chef-server-slice/app/views/exceptions/not_acceptable.html.erb
+++ b/chef-server-webui/app/views/exceptions/not_acceptable.html.erb
diff --git a/chef-server-webui/app/views/exceptions/not_found.html.erb b/chef-server-webui/app/views/exceptions/not_found.html.erb
new file mode 100644
index 0000000000..388c72c31d
--- /dev/null
+++ b/chef-server-webui/app/views/exceptions/not_found.html.erb
@@ -0,0 +1,47 @@
+<div id="container">
+ <div id="header-container">
+ <img src="/images/merb.jpg" />
+ <!-- <h1>Mongrel + Erb</h1> -->
+ <h2>pocket rocket web framework</h2>
+ <hr />
+ </div>
+
+ <div id="left-container">
+ <h3>Exception:</h3>
+ <p><%= params[:exception] %></p>
+ </div>
+
+ <div id="main-container">
+ <h3>Welcome to Merb!</h3>
+ <p>Merb is a light-weight MVC framework written in Ruby. We hope you enjoy it.</p>
+
+ <h3>Where can I find help?</h3>
+ <p>If you have any questions or if you can't figure something out, please take a
+ look at our <a href="http://merbivore.com/"> project page</a>,
+ feel free to come chat at irc.freenode.net, channel #merb,
+ or post to <a href="http://groups.google.com/group/merb">merb mailing list</a>
+ on Google Groups.</p>
+
+ <h3>What if I've found a bug?</h3>
+ <p>If you want to file a bug or make your own contribution to Merb,
+ feel free to register and create a ticket at our
+ <a href="http://merb.lighthouseapp.com/">project development page</a>
+ on Lighthouse.</p>
+
+ <h3>How do I edit this page?</h3>
+ <p>You're seeing this page because you need to edit the following files:
+ <ul>
+ <li>config/router.rb <strong><em>(recommended)</em></strong></li>
+ <li>app/views/exceptions/not_found.html.erb <strong><em>(recommended)</em></strong></li>
+ <li>app/views/layout/application.html.erb <strong><em>(change this layout)</em></strong></li>
+ </ul>
+ </p>
+ </div>
+
+ <div id="footer-container">
+ <hr />
+ <div class="left"></div>
+ <div class="right">&copy; 2007 the merb dev team</div>
+ <p>&nbsp;</p>
+ </div>
+</div>
diff --git a/chef-server-webui/app/views/exceptions/standard_error.html.erb b/chef-server-webui/app/views/exceptions/standard_error.html.erb
new file mode 100644
index 0000000000..edb45ddf90
--- /dev/null
+++ b/chef-server-webui/app/views/exceptions/standard_error.html.erb
@@ -0,0 +1,217 @@
+<html>
+<head>
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+ <title><%= @exception_name %></title>
+ <style type="text/css" media="screen">
+ body {
+ font-family:arial;
+ font-size:11px;
+ }
+ h1 {
+ font-size:48px;
+ letter-spacing:-4px;
+ margin:0;
+ line-height:36px;
+ color:#333;
+ }
+ h1 sup {
+ font-size: 0.5em;
+ }
+ h1 sup.error_500, h1 sup.error_400 {
+ color:#990E05;
+ }
+ h1 sup.error_100, h1 sup.error_200 {
+ color:#00BF10;
+ }
+ h1 sup.error_300 {
+ /* pretty sure you cant 'see' status 300
+ errors but if you could I think they
+ would be blue */
+ color:#1B2099;
+ }
+ h2 {
+ font-size:36px;
+ letter-spacing:-3px;
+ margin:0;
+ line-height:28px;
+ color:#444;
+ }
+ a, a:visited {
+ color:#00BF10;
+ }
+ .internalError {
+ width:800px;
+ margin:50px auto;
+ }
+ .header {
+ border-bottom:10px solid #333;
+ margin-bottom:1px;
+ background-image: url("data:image/gif;base64,R0lGODlhAwADAIAAAP///8zMzCH5BAAAAAAALAAAAAADAAMAAAIEBHIJBQA7");
+ padding:20px;
+ }
+ table.trace {
+ width:100%;
+ font-family:courier, monospace;
+ letter-spacing:-1px;
+ border-collapse: collapse;
+ border-spacing:0;
+ }
+ table.trace tr td{
+ padding:0;
+ height:26px;
+ font-size:13px;
+ vertical-align:middle;
+ }
+ table.trace tr.file{
+ border-top:2px solid #fff;
+ background-color:#F3F3F3;
+ }
+ table.trace tr.source {
+ background-color:#F8F8F8;
+ display:none;
+ }
+ table.trace .open tr.source {
+ display:table-row;
+ }
+ table.trace tr.file td.expand {
+ width:23px;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAXCAIAAABvSEP3AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAdVJREFUeNqMVL+TwUAYxaRIOlEhlZHGDAUzzOQ61+AqXMV1lJSU7q/QRqm8KFUcJTNn5qJkaPyoKKVz7y4mF8na5Kt29tt9+/Z97/u81+vVQ4r9frdarS6Xi7ETDIZisRjxMGPfmk4niNPpZE+xLAugbPaZ53nzvtfMBe/3+/3dbuehBrAKhZdUKkVAWa9Xsiybv0CPZDJZLr/qa5/BwgwRjYqOKIvFYjQa/aNommZh0Ww2K5UqzwfoQOPxaLPZ3FAmk0+7lplMpt1u53J5OpBOR0eZEE9wHJfP5zud93g88QhluwWbjW+5VOmKBgKBer3eaDTDYeGBQF8+x7rqIYoiPgixWJazpA6HA+MSxRArkUgMh0M409g8Ho8+9wYxxCqVSq1W26EDHGM2m4HOHQrEc38f/Yn7cLmlIRhBENzcx8cVRZnPZ/YUep2BWkjTIfA+PKVpZAXR5QxsjiqCKvGEqqp443w+0dvy17swqD0HB3S73V5PpkNg1qBqt8kwGCjmPkinM0QJbIoEa7U6UG6ToVgs4V9G2g0ESoP5Aoi7KYX5oCgf8IKbkvn9/mr1LRQKESamzgJy0g0tSZIuB3nuGqRU9Vv9C4sKkUhEkp4soxvxI8AAhWrrtXa3X8EAAAAASUVORK5CYII=);
+ background-position:top left;
+ background-repeat:no-repeat;
+ }
+ table.trace .open tr.file td.expand {
+ width:19px;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAB1CAIAAAAqdO2mAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAXZJREFUeNrslK1ywkAUhcMOBomEOiSdqLxEBJX0NaijOsjyHGGmCGyQQYaiiiw4gktkcOmZbpsuuzQ/M5XnqJ2d3S/n3nM3rTzPLUP7/Tt0+pLcGQwG3W53OLyHzPMtjYL7q9UqSRLrD4E1Gj1orCvKYuFHUWTVkOM44/HjDcp8/lL4r6NerzeZPMm1KFw0QkDn83m5fP2lHA4fNQvRtNvtjsfDd0WzmSfb2e/fdTqdOvdh/HLJZLOn0+d2HJ+KRGzbdl23EpFlmed5cp2maRzHQq1lvQ5KMi6EUZBGfup6E1pTfd+vrGW7jbQ2C9hTt9BpqNyIWaAwAy6xg2eBz5iRC/NomiZhGN5sqmnkauo0BUGgVQoBjQ80oCACgNQdZHfTYBkF2mxCtWWAqunWpahxIDUt3QYUxIFQpJHyIWpXjinabKbbwItMHT+NyjchrP8QKaSQQgoppJBCCimkkEIKKaSQQgoppJBCCimkkEIKKaSo+hRgAEFD17X08O2NAAAAAElFTkSuQmCC);
+ background-position:top left;
+ background-repeat:no-repeat;
+ }
+ table.trace tr.source td.collapse {
+ width:19px;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAB1CAIAAAAqdO2mAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAVxJREFUeNrs0zFygkAUBmBlUkgJHdABlQwVkVJKKUxBYWbkALTxMJwhltyDFkss03IF8pudIcwaDaDl/6pd2P327b7d+eHwMXs4lNkzggoVKlSoUKFChQoVKlSoUKFChQoVKlSoUKFChQqVEYqm6ft9+qiSJEkYho7jTlcw2fd9NOI4nq4gEdFwXXe1Cqco63VkWVbXRTqLhTpOwQRpF7quR1E0TgGhqvLKUFCyoQqG/rks3O6kZKW/eRFpevOCoGTXVTcMQ5EyxyDEkML1c5RzuZOICIyXqn7JBVez6282MWrx731HOv2qB8Hri2lamNk0DfpVVdV1Peodappmmua8bdvzuc7zfNprzrLMth1FnGh/X8MjCAIQv/cFz/+65PcDh7rbvYv2ZUfdj+PxsyzLgVl0hKwgTqeqKApx2LeOc7t98zyv/1FWOgvx9RPii23bmL9cetJ8Ed8CDAC6aFW8bCzFhwAAAABJRU5ErkJggg==);
+ background-position:bottom left;
+ background-repeat:no-repeat;
+ background-color:#6F706F;
+ }
+ table.trace tr td.path {
+ padding-left:10px;
+ }
+ table.trace tr td.code {
+ padding-left:35px;
+ white-space: pre;
+ line-height:9px;
+ padding-bottom:10px;
+ }
+ table.trace tr td.code em {
+ font-weight:bold;
+ color:#00BF10;
+ }
+ table.trace tr td.code a {
+ width: 20px;
+ float: left;
+ }
+ table.trace tr td.code .more {
+ color:#666;
+ }
+ table.trace tr td.line {
+ width:30px;
+ text-align:right;
+ padding-right:4px;
+ }
+ .footer {
+ margin-top:5px;
+ font-size:11px;
+ color:#444;
+ text-align:right;
+ }
+ </style>
+</head>
+<body>
+ <div class="internalError">
+
+ <div class="header">
+ <h1><%= @exception_name %> <sup class="error_<%= @exception.class::STATUS %>"><%= @exception.class::STATUS %></sup></h1>
+ <% if show_details = ::Merb::Config[:exception_details] -%>
+ <h2><%= @exception.message %></h2>
+ <% else -%>
+ <h2>Sorry about that...</h2>
+ <% end -%>
+ <h3>Parameters</h3>
+ <ul>
+ <% params[:original_params].each do |param, value| %>
+ <li><strong><%= param %>:</strong> <%= value.inspect %></li>
+ <% end %>
+ <%= "<li>None</li>" if params[:original_params].empty? %>
+ </ul>
+
+ <h3>Session</h3>
+ <ul>
+ <% params[:original_session].each do |param, value| %>
+ <li><strong><%= param %>:</strong> <%= value.inspect %></li>
+ <% end %>
+ <%= "<li>None</li>" if params[:original_session].empty? %>
+ </ul>
+
+ <h3>Cookies</h3>
+ <ul>
+ <% params[:original_cookies].each do |param, value| %>
+ <li><strong><%= param %>:</strong> <%= value.inspect %></li>
+ <% end %>
+ <%= "<li>None</li>" if params[:original_cookies].empty? %>
+ </ul>
+ </div>
+
+ <% if show_details %>
+ <table class="trace">
+ <% @exception.backtrace.each_with_index do |line, index| %>
+ <tbody class="close">
+ <tr class="file">
+ <td class="expand">
+ </td>
+ <td class="path">
+ <%= (line.match(/^([^:]+)/)[1] rescue 'unknown').sub(/\/((opt|usr)\/local\/lib\/(ruby\/)?(gems\/)?(1.8\/)?(gems\/)?|.+\/app\/)/, '') %>
+ <% unless line.match(/\.erb:/) %>
+ in "<strong><%= line.match(/:in `(.+)'$/)[1] rescue '?' %></strong>"
+ <% else %>
+ (<strong>ERB Template</strong>)
+ <% end %>
+ </td>
+ <td class="line">
+ <a href="txmt://open?url=file://<%=file = (line.match(/^([^:]+)/)[1] rescue 'unknown')%>&amp;line=<%= lineno = line.match(/:([0-9]+):/)[1] rescue '?' %>"><%=lineno%></a>&nbsp;
+ </td>
+ </tr>
+ <tr class="source">
+ <td class="collapse">
+ </td>
+ <td class="code" colspan="2"><% (__caller_lines__(file, lineno, 5) rescue []).each do |llineno, lcode, lcurrent| %>
+<a href="txmt://open?url=file://<%=file%>&amp;line=<%=llineno%>"><%= llineno %></a><%='<em>' if llineno==lineno.to_i %><%= lcode.size > 90 ? CGI.escapeHTML(lcode[0..90])+'<span class="more">......</span>' : CGI.escapeHTML(lcode) %><%='</em>' if llineno==lineno.to_i %>
+<% end %>
+
+</td>
+ </tr>
+ </tbody>
+ <% end %>
+ </table>
+ <script type="text/javascript" charset="utf-8">
+ // swop the open & closed classes
+ els = document.getElementsByTagName('td');
+ for(i=0; i<els.length; i++){
+ if(els[i].className=='expand' || els[i].className=='collapse'){
+ els[i].onclick = function(e){
+ tbody = this.parentNode.parentNode;
+ if(tbody.className=='open'){
+ tbody.className='closed';
+ }else{
+ tbody.className='open';
+ }
+ }
+ }
+ }
+ </script>
+ <% end %>
+ <div class="footer">
+ lots of love, from <a href="#">merb</a>
+ </div>
+ </div>
+</body>
+</html>
+
diff --git a/chef-server-slice/app/views/layout/chef_server_slice.html.haml b/chef-server-webui/app/views/layout/chef_server_webui.html.haml
index e480ad71e1..e480ad71e1 100644
--- a/chef-server-slice/app/views/layout/chef_server_slice.html.haml
+++ b/chef-server-webui/app/views/layout/chef_server_webui.html.haml
diff --git a/chef-server-slice/app/views/layout/login.html.haml b/chef-server-webui/app/views/layout/login.html.haml
index 8ac6c4d6f4..8ac6c4d6f4 100644
--- a/chef-server-slice/app/views/layout/login.html.haml
+++ b/chef-server-webui/app/views/layout/login.html.haml
diff --git a/chef-server-slice/app/views/main/index.html.erb b/chef-server-webui/app/views/main/index.html.erb
index 755c73de7f..755c73de7f 100644
--- a/chef-server-slice/app/views/main/index.html.erb
+++ b/chef-server-webui/app/views/main/index.html.erb
diff --git a/chef-server-slice/app/views/nodes/_action.html.haml b/chef-server-webui/app/views/nodes/_action.html.haml
index 8adb39338d..8adb39338d 100644
--- a/chef-server-slice/app/views/nodes/_action.html.haml
+++ b/chef-server-webui/app/views/nodes/_action.html.haml
diff --git a/chef-server-slice/app/views/nodes/_form.html.haml b/chef-server-webui/app/views/nodes/_form.html.haml
index d150eb59a7..d150eb59a7 100644
--- a/chef-server-slice/app/views/nodes/_form.html.haml
+++ b/chef-server-webui/app/views/nodes/_form.html.haml
diff --git a/chef-server-slice/app/views/nodes/_navigation.html.haml b/chef-server-webui/app/views/nodes/_navigation.html.haml
index 1cb47f918a..1cb47f918a 100644
--- a/chef-server-slice/app/views/nodes/_navigation.html.haml
+++ b/chef-server-webui/app/views/nodes/_navigation.html.haml
diff --git a/chef-server-slice/app/views/nodes/_resource.html.haml b/chef-server-webui/app/views/nodes/_resource.html.haml
index 7b9776b816..7b9776b816 100644
--- a/chef-server-slice/app/views/nodes/_resource.html.haml
+++ b/chef-server-webui/app/views/nodes/_resource.html.haml
diff --git a/chef-server-slice/app/views/nodes/edit.html.haml b/chef-server-webui/app/views/nodes/edit.html.haml
index dff693ac7d..dff693ac7d 100644
--- a/chef-server-slice/app/views/nodes/edit.html.haml
+++ b/chef-server-webui/app/views/nodes/edit.html.haml
diff --git a/chef-server-slice/app/views/nodes/index.html.haml b/chef-server-webui/app/views/nodes/index.html.haml
index 744d0b7375..744d0b7375 100644
--- a/chef-server-slice/app/views/nodes/index.html.haml
+++ b/chef-server-webui/app/views/nodes/index.html.haml
diff --git a/chef-server-slice/app/views/nodes/new.html.haml b/chef-server-webui/app/views/nodes/new.html.haml
index d2d032599a..d2d032599a 100644
--- a/chef-server-slice/app/views/nodes/new.html.haml
+++ b/chef-server-webui/app/views/nodes/new.html.haml
diff --git a/chef-server-slice/app/views/nodes/show.html.haml b/chef-server-webui/app/views/nodes/show.html.haml
index 121cbcedf4..121cbcedf4 100644
--- a/chef-server-slice/app/views/nodes/show.html.haml
+++ b/chef-server-webui/app/views/nodes/show.html.haml
diff --git a/chef-server-slice/app/views/openid_consumer/index.html.haml b/chef-server-webui/app/views/openid_consumer/index.html.haml
index 1d1572d5c1..1d1572d5c1 100644
--- a/chef-server-slice/app/views/openid_consumer/index.html.haml
+++ b/chef-server-webui/app/views/openid_consumer/index.html.haml
diff --git a/chef-server-slice/app/views/openid_consumer/start.html.haml b/chef-server-webui/app/views/openid_consumer/start.html.haml
index 75ed9a9257..75ed9a9257 100644
--- a/chef-server-slice/app/views/openid_consumer/start.html.haml
+++ b/chef-server-webui/app/views/openid_consumer/start.html.haml
diff --git a/chef-server-slice/app/views/openid_login/index.html.haml b/chef-server-webui/app/views/openid_login/index.html.haml
index e5cf180b9b..e5cf180b9b 100644
--- a/chef-server-slice/app/views/openid_login/index.html.haml
+++ b/chef-server-webui/app/views/openid_login/index.html.haml
diff --git a/chef-server-slice/app/views/openid_register/index.html.haml b/chef-server-webui/app/views/openid_register/index.html.haml
index ba316d6062..ba316d6062 100644
--- a/chef-server-slice/app/views/openid_register/index.html.haml
+++ b/chef-server-webui/app/views/openid_register/index.html.haml
diff --git a/chef-server-slice/app/views/openid_register/show.html.haml b/chef-server-webui/app/views/openid_register/show.html.haml
index 63d84d8364..63d84d8364 100644
--- a/chef-server-slice/app/views/openid_register/show.html.haml
+++ b/chef-server-webui/app/views/openid_register/show.html.haml
diff --git a/chef-server-slice/app/views/openid_server/decide.html.haml b/chef-server-webui/app/views/openid_server/decide.html.haml
index d08b50cd4b..d08b50cd4b 100644
--- a/chef-server-slice/app/views/openid_server/decide.html.haml
+++ b/chef-server-webui/app/views/openid_server/decide.html.haml
diff --git a/chef-server-slice/app/views/roles/_form.html.haml b/chef-server-webui/app/views/roles/_form.html.haml
index ac1591a86b..ac1591a86b 100644
--- a/chef-server-slice/app/views/roles/_form.html.haml
+++ b/chef-server-webui/app/views/roles/_form.html.haml
diff --git a/chef-server-slice/app/views/roles/_navigation.html.haml b/chef-server-webui/app/views/roles/_navigation.html.haml
index 1eb2a51cdf..1eb2a51cdf 100644
--- a/chef-server-slice/app/views/roles/_navigation.html.haml
+++ b/chef-server-webui/app/views/roles/_navigation.html.haml
diff --git a/chef-server-slice/app/views/roles/edit.html.haml b/chef-server-webui/app/views/roles/edit.html.haml
index 7714ed764c..7714ed764c 100644
--- a/chef-server-slice/app/views/roles/edit.html.haml
+++ b/chef-server-webui/app/views/roles/edit.html.haml
diff --git a/chef-server-slice/app/views/roles/index.html.haml b/chef-server-webui/app/views/roles/index.html.haml
index 6ab526b5d1..6ab526b5d1 100644
--- a/chef-server-slice/app/views/roles/index.html.haml
+++ b/chef-server-webui/app/views/roles/index.html.haml
diff --git a/chef-server-slice/app/views/roles/new.html.haml b/chef-server-webui/app/views/roles/new.html.haml
index 3af7544053..3af7544053 100644
--- a/chef-server-slice/app/views/roles/new.html.haml
+++ b/chef-server-webui/app/views/roles/new.html.haml
diff --git a/chef-server-slice/app/views/roles/show.html.haml b/chef-server-webui/app/views/roles/show.html.haml
index 4e3b8e22b0..4e3b8e22b0 100644
--- a/chef-server-slice/app/views/roles/show.html.haml
+++ b/chef-server-webui/app/views/roles/show.html.haml
diff --git a/chef-server-slice/app/views/search/_search_form.html.haml b/chef-server-webui/app/views/search/_search_form.html.haml
index 5396e65123..5396e65123 100644
--- a/chef-server-slice/app/views/search/_search_form.html.haml
+++ b/chef-server-webui/app/views/search/_search_form.html.haml
diff --git a/chef-server-slice/app/views/search/index.html.haml b/chef-server-webui/app/views/search/index.html.haml
index 8f3b199592..8f3b199592 100644
--- a/chef-server-slice/app/views/search/index.html.haml
+++ b/chef-server-webui/app/views/search/index.html.haml
diff --git a/chef-server-slice/app/views/search/show.html.haml b/chef-server-webui/app/views/search/show.html.haml
index f20f9fa2ea..f20f9fa2ea 100644
--- a/chef-server-slice/app/views/search/show.html.haml
+++ b/chef-server-webui/app/views/search/show.html.haml
diff --git a/chef-server-slice/app/views/search_entries/index.html.haml b/chef-server-webui/app/views/search_entries/index.html.haml
index c6fab2b649..c6fab2b649 100644
--- a/chef-server-slice/app/views/search_entries/index.html.haml
+++ b/chef-server-webui/app/views/search_entries/index.html.haml
diff --git a/chef-server-slice/app/views/search_entries/show.html.haml b/chef-server-webui/app/views/search_entries/show.html.haml
index f8a8c3813a..f8a8c3813a 100644
--- a/chef-server-slice/app/views/search_entries/show.html.haml
+++ b/chef-server-webui/app/views/search_entries/show.html.haml
diff --git a/chef-server-slice/app/views/status/index.html.haml b/chef-server-webui/app/views/status/index.html.haml
index c271242c67..c271242c67 100644
--- a/chef-server-slice/app/views/status/index.html.haml
+++ b/chef-server-webui/app/views/status/index.html.haml
diff --git a/chef-server-slice/config/init.rb b/chef-server-webui/config/init.rb
index f418f93a4e..6ee1a8f50e 100644
--- a/chef-server-slice/config/init.rb
+++ b/chef-server-webui/config/init.rb
@@ -43,6 +43,8 @@ Merb::Config.use do |c|
c[:exception_details] = true
c[:reload_classes] = true
c[:log_level] = Chef::Config[:log_level]
- c[:log_file] = Chef::Config[:log_location]
+ if Chef::Config[:log_location].kind_of?(String)
+ c[:log_file] = Chef::Config[:log_location]
+ end
end
diff --git a/chef-server-webui/config/router.rb b/chef-server-webui/config/router.rb
new file mode 100644
index 0000000000..3142a1d1a9
--- /dev/null
+++ b/chef-server-webui/config/router.rb
@@ -0,0 +1,6 @@
+# This file is here so slice can be testing as a stand alone application.
+
+#Merb::Router.prepare do
+# resources :roles
+# ...
+#end
diff --git a/chef-server-slice/lib/chef-server-slice.rb b/chef-server-webui/lib/chef-server-webui.rb
index 46e5b97fdb..43871b2e57 100644
--- a/chef-server-slice/lib/chef-server-slice.rb
+++ b/chef-server-webui/lib/chef-server-webui.rb
@@ -5,9 +5,9 @@ if defined?(Merb::Plugins)
dependency 'chef', :immediate=>true unless defined?(Chef)
require 'chef/role'
- require 'coderay'
+ require 'syntax/convertors/html'
- Merb::Plugins.add_rakefiles "chef-server-slice/merbtasks", "chef-server-slice/slicetasks", "chef-server-slice/spectasks"
+ Merb::Plugins.add_rakefiles "chef-server-webui/merbtasks", "chef-server-webui/slicetasks", "chef-server-webui/spectasks"
# Register the Slice for the current host application
Merb::Slices::register(__FILE__)
@@ -21,19 +21,19 @@ if defined?(Merb::Plugins)
# Configuration options:
# :layout - the layout to use; defaults to :chefserverslice
# :mirror - which path component types to use on copy operations; defaults to all
- Merb::Slices::config[:chef_server_slice][:layout] ||= :chef_server_slice
+ Merb::Slices::config[:chef_server_webui][:layout] ||= :chef_server_webui
# All Slice code is expected to be namespaced inside a module
- module ChefServerSlice
+ module ChefServerWebui
# Slice metadata
- self.description = "ChefServerSlice.. serving up some piping hot infrastructure!"
+ self.description = "ChefServerWebui.. serving up some piping hot infrastructure!"
self.version = Chef::VERSION
self.author = "Opscode"
# Stub classes loaded hook - runs before LoadClasses BootLoader
# right after a slice's classes have been loaded internally.
def self.loaded
- Chef::Queue.connect
+ # Chef::Queue.connect
# create the couch databases for openid association and nonce storage, if configured
if Chef::Config[:openid_store_couchdb] || Chef::Config[:openid_cstore_couchdb]
@@ -48,14 +48,13 @@ if defined?(Merb::Plugins)
end
# create the couch design docs for nodes and openid registrations
- Chef::Node.create_design_document
- Chef::Role.create_design_document
- Chef::OpenIDRegistration.create_design_document
-
- Chef::Log.logger = Merb.logger
- Chef::Log.info("Compiling routes... (totally normal to see 'Cannot find resource model')")
- Chef::Log.info("Loading roles")
- Chef::Role.sync_from_disk_to_couchdb
+ # Chef::Node.create_design_document
+ # Chef::Role.create_design_document
+ # Chef::OpenIDRegistration.create_design_document
+
+ # Chef::Log.info("Compiling routes... (totally normal to see 'Cannot find resource model')")
+ # Chef::Log.info("Loading roles")
+ # Chef::Role.sync_from_disk_to_couchdb
end
# Initialization hook - runs before AfterAppLoads BootLoader
@@ -134,17 +133,17 @@ if defined?(Merb::Plugins)
end
- # Setup the slice layout for ChefServerSlice
+ # Setup the slice layout for ChefServerWebui
#
- # Use ChefServerSlice.push_path and ChefServerSlice.push_app_path
+ # Use ChefServerWebui.push_path and ChefServerWebui.push_app_path
# to set paths to chefserver-level and app-level paths. Example:
#
- # ChefServerSlice.push_path(:application, ChefServerSlice.root)
- # ChefServerSlice.push_app_path(:application, Merb.root / 'slices' / 'chefserverslice')
+ # ChefServerWebui.push_path(:application, ChefServerWebui.root)
+ # ChefServerWebui.push_app_path(:application, Merb.root / 'slices' / 'chefserverslice')
# ...
#
- # Any component path that hasn't been set will default to ChefServerSlice.root
+ # Any component path that hasn't been set will default to ChefServerWebui.root
#
# Or just call setup_default_structure! to setup a basic Merb MVC structure.
- ChefServerSlice.setup_default_structure!
+ ChefServerWebui.setup_default_structure!
end
diff --git a/chef-server-slice/lib/chef-server-slice/merbtasks.rb b/chef-server-webui/lib/chef-server-webui/merbtasks.rb
index 554799bf65..9da80eca15 100644
--- a/chef-server-slice/lib/chef-server-slice/merbtasks.rb
+++ b/chef-server-webui/lib/chef-server-webui/merbtasks.rb
@@ -11,9 +11,9 @@ namespace :slices do
desc "Setup directories"
task :setup_directories do
puts "Creating directories for host application"
- ChefServerSlice.mirrored_components.each do |type|
- if File.directory?(ChefServerSlice.dir_for(type))
- if !File.directory?(dst_path = ChefServerSlice.app_dir_for(type))
+ ChefServerWebui.mirrored_components.each do |type|
+ if File.directory?(ChefServerWebui.dir_for(type))
+ if !File.directory?(dst_path = ChefServerWebui.app_dir_for(type))
relative_path = dst_path.relative_path_from(Merb.root)
puts "- creating directory :#{type} #{File.basename(Merb.root) / relative_path}"
mkdir_p(dst_path)
@@ -24,8 +24,8 @@ namespace :slices do
# desc "Copy stub files to host application"
# task :stubs do
- # puts "Copying stubs for ChefServerSlice - resolves any collisions"
- # copied, preserved = ChefServerSlice.mirror_stubs!
+ # puts "Copying stubs for ChefServerWebui - resolves any collisions"
+ # copied, preserved = ChefServerWebui.mirror_stubs!
# puts "- no files to copy" if copied.empty? && preserved.empty?
# copied.each { |f| puts "- copied #{f}" }
# preserved.each { |f| puts "! preserved override as #{f}" }
@@ -36,8 +36,8 @@ namespace :slices do
desc "Copy public assets to host application"
task :copy_assets do
- puts "Copying assets for ChefServerSlice - resolves any collisions"
- copied, preserved = ChefServerSlice.mirror_public!
+ puts "Copying assets for ChefServerWebui - resolves any collisions"
+ copied, preserved = ChefServerWebui.mirror_public!
puts "- no files to copy" if copied.empty? && preserved.empty?
copied.each { |f| puts "- copied #{f}" }
preserved.each { |f| puts "! preserved override as #{f}" }
@@ -47,21 +47,21 @@ namespace :slices do
task :migrate do # see slicetasks.rb
end
- desc "Freeze ChefServerSlice into your app (only chefserverslice/app)"
+ desc "Freeze ChefServerWebui into your app (only chefserverslice/app)"
task :freeze => [ "freeze:app" ]
namespace :freeze do
- # desc "Freezes ChefServerSlice by installing the gem into application/gems"
+ # desc "Freezes ChefServerWebui by installing the gem into application/gems"
# task :gem do
# ENV["GEM"] ||= "chefserverslice"
# Rake::Task['slices:install_as_gem'].invoke
# end
- desc "Freezes ChefServerSlice by copying all files from chefserverslice/app to your application"
+ desc "Freezes ChefServerWebui by copying all files from chefserverslice/app to your application"
task :app do
puts "Copying all chefserverslice/app files to your application - resolves any collisions"
- copied, preserved = ChefServerSlice.mirror_app!
+ copied, preserved = ChefServerWebui.mirror_app!
puts "- no files to copy" if copied.empty? && preserved.empty?
copied.each { |f| puts "- copied #{f}" }
preserved.each { |f| puts "! preserved override as #{f}" }
@@ -70,7 +70,7 @@ namespace :slices do
desc "Freeze all views into your application for easy modification"
task :views do
puts "Copying all view templates to your application - resolves any collisions"
- copied, preserved = ChefServerSlice.mirror_files_for :view
+ copied, preserved = ChefServerWebui.mirror_files_for :view
puts "- no files to copy" if copied.empty? && preserved.empty?
copied.each { |f| puts "- copied #{f}" }
preserved.each { |f| puts "! preserved override as #{f}" }
@@ -79,19 +79,19 @@ namespace :slices do
desc "Freeze all models into your application for easy modification"
task :models do
puts "Copying all models to your application - resolves any collisions"
- copied, preserved = ChefServerSlice.mirror_files_for :model
+ copied, preserved = ChefServerWebui.mirror_files_for :model
puts "- no files to copy" if copied.empty? && preserved.empty?
copied.each { |f| puts "- copied #{f}" }
preserved.each { |f| puts "! preserved override as #{f}" }
end
- desc "Freezes ChefServerSlice as a gem and copies over chefserver/app"
+ desc "Freezes ChefServerWebui as a gem and copies over chefserver/app"
task :app_with_gem => [:gem, :app]
- desc "Freezes ChefServerSlice by unpacking all files into your application"
+ desc "Freezes ChefServerWebui by unpacking all files into your application"
task :unpack do
- puts "Unpacking ChefServerSlice files to your application - resolves any collisions"
- copied, preserved = ChefServerSlice.unpack_slice!
+ puts "Unpacking ChefServerWebui files to your application - resolves any collisions"
+ copied, preserved = ChefServerWebui.unpack_slice!
puts "- no files to copy" if copied.empty? && preserved.empty?
copied.each { |f| puts "- copied #{f}" }
preserved.each { |f| puts "! preserved override as #{f}" }
diff --git a/chef-server-webui/lib/chef-server-webui/slicetasks.rb b/chef-server-webui/lib/chef-server-webui/slicetasks.rb
new file mode 100644
index 0000000000..187323a299
--- /dev/null
+++ b/chef-server-webui/lib/chef-server-webui/slicetasks.rb
@@ -0,0 +1,20 @@
+namespace :slices do
+ namespace :chefserverslice do
+
+ # add your own chefserver tasks here
+
+ # # Uncomment the following lines and edit the pre defined tasks
+ #
+ # # implement this to test for structural/code dependencies
+ # # like certain directories or availability of other files
+ # desc "Test for any dependencies"
+ # task :preflight do
+ # end
+ #
+ # # implement this to perform any database related setup steps
+ # desc "Migrate the database"
+ # task :migrate do
+ # end
+
+ end
+end
diff --git a/chef-server-slice/lib/chef-server-slice/spectasks.rb b/chef-server-webui/lib/chef-server-webui/spectasks.rb
index 328b517cb0..fb61dcd6a3 100644
--- a/chef-server-slice/lib/chef-server-slice/spectasks.rb
+++ b/chef-server-webui/lib/chef-server-webui/spectasks.rb
@@ -9,7 +9,7 @@ namespace :slices do
slice_root = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
task :explain do
- puts "\nNote: By running ChefServerSlice specs inside the application context any\n" +
+ puts "\nNote: By running ChefServerWebui specs inside the application context any\n" +
"overrides could break existing specs. This isn't always a problem,\n" +
"especially in the case of views. Use these spec tasks to check how\n" +
"well your application conforms to the original slice implementation."
diff --git a/chef-server-slice/public/facebox/README.txt b/chef-server-webui/public/facebox/README.txt
index d4fc2d5e81..d4fc2d5e81 100644
--- a/chef-server-slice/public/facebox/README.txt
+++ b/chef-server-webui/public/facebox/README.txt
diff --git a/chef-server-slice/public/facebox/b.png b/chef-server-webui/public/facebox/b.png
index f184e6269b..f184e6269b 100644
--- a/chef-server-slice/public/facebox/b.png
+++ b/chef-server-webui/public/facebox/b.png
Binary files differ
diff --git a/chef-server-slice/public/facebox/bl.png b/chef-server-webui/public/facebox/bl.png
index f6271859d5..f6271859d5 100644
--- a/chef-server-slice/public/facebox/bl.png
+++ b/chef-server-webui/public/facebox/bl.png
Binary files differ
diff --git a/chef-server-slice/public/facebox/br.png b/chef-server-webui/public/facebox/br.png
index 31f204fc45..31f204fc45 100644
--- a/chef-server-slice/public/facebox/br.png
+++ b/chef-server-webui/public/facebox/br.png
Binary files differ
diff --git a/chef-server-slice/public/facebox/closelabel.gif b/chef-server-webui/public/facebox/closelabel.gif
index 87b4f8bd69..87b4f8bd69 100755
--- a/chef-server-slice/public/facebox/closelabel.gif
+++ b/chef-server-webui/public/facebox/closelabel.gif
Binary files differ
diff --git a/chef-server-slice/public/facebox/facebox.css b/chef-server-webui/public/facebox/facebox.css
index 97ebe3cab3..97ebe3cab3 100644
--- a/chef-server-slice/public/facebox/facebox.css
+++ b/chef-server-webui/public/facebox/facebox.css
diff --git a/chef-server-slice/public/facebox/facebox.js b/chef-server-webui/public/facebox/facebox.js
index cbbb450b10..cbbb450b10 100644
--- a/chef-server-slice/public/facebox/facebox.js
+++ b/chef-server-webui/public/facebox/facebox.js
diff --git a/chef-server-slice/public/facebox/loading.gif b/chef-server-webui/public/facebox/loading.gif
index f864d5fd38..f864d5fd38 100755
--- a/chef-server-slice/public/facebox/loading.gif
+++ b/chef-server-webui/public/facebox/loading.gif
Binary files differ
diff --git a/chef-server-slice/public/facebox/tl.png b/chef-server-webui/public/facebox/tl.png
index d99c8f6c6e..d99c8f6c6e 100644
--- a/chef-server-slice/public/facebox/tl.png
+++ b/chef-server-webui/public/facebox/tl.png
Binary files differ
diff --git a/chef-server-slice/public/facebox/tr.png b/chef-server-webui/public/facebox/tr.png
index e99b6ec831..e99b6ec831 100644
--- a/chef-server-slice/public/facebox/tr.png
+++ b/chef-server-webui/public/facebox/tr.png
Binary files differ
diff --git a/chef-server-webui/public/images/avatar.png b/chef-server-webui/public/images/avatar.png
new file mode 100644
index 0000000000..66488481ae
--- /dev/null
+++ b/chef-server-webui/public/images/avatar.png
Binary files differ
diff --git a/chef-server-slice/public/images/black_big.png b/chef-server-webui/public/images/black_big.png
index 9cfe72609c..9cfe72609c 100644
--- a/chef-server-slice/public/images/black_big.png
+++ b/chef-server-webui/public/images/black_big.png
Binary files differ
diff --git a/chef-server-webui/public/images/indicator.gif b/chef-server-webui/public/images/indicator.gif
new file mode 100644
index 0000000000..085ccaecaf
--- /dev/null
+++ b/chef-server-webui/public/images/indicator.gif
Binary files differ
diff --git a/chef-server-webui/public/images/merb.jpg b/chef-server-webui/public/images/merb.jpg
new file mode 100644
index 0000000000..a19dcf4048
--- /dev/null
+++ b/chef-server-webui/public/images/merb.jpg
Binary files differ
diff --git a/chef-server-slice/public/images/toggle-collapse-dark.png b/chef-server-webui/public/images/toggle-collapse-dark.png
index 76577a57a2..76577a57a2 100644
--- a/chef-server-slice/public/images/toggle-collapse-dark.png
+++ b/chef-server-webui/public/images/toggle-collapse-dark.png
Binary files differ
diff --git a/chef-server-slice/public/images/toggle-collapse-light.png b/chef-server-webui/public/images/toggle-collapse-light.png
index ed1612fd9b..ed1612fd9b 100644
--- a/chef-server-slice/public/images/toggle-collapse-light.png
+++ b/chef-server-webui/public/images/toggle-collapse-light.png
Binary files differ
diff --git a/chef-server-slice/public/images/toggle-collapse.gif b/chef-server-webui/public/images/toggle-collapse.gif
index f0979304ac..f0979304ac 100644
--- a/chef-server-slice/public/images/toggle-collapse.gif
+++ b/chef-server-webui/public/images/toggle-collapse.gif
Binary files differ
diff --git a/chef-server-slice/public/images/toggle-expand-dark.png b/chef-server-webui/public/images/toggle-expand-dark.png
index cfb42a4512..cfb42a4512 100644
--- a/chef-server-slice/public/images/toggle-expand-dark.png
+++ b/chef-server-webui/public/images/toggle-expand-dark.png
Binary files differ
diff --git a/chef-server-slice/public/images/toggle-expand-light.png b/chef-server-webui/public/images/toggle-expand-light.png
index 27b52344dc..27b52344dc 100644
--- a/chef-server-slice/public/images/toggle-expand-light.png
+++ b/chef-server-webui/public/images/toggle-expand-light.png
Binary files differ
diff --git a/chef-server-slice/public/images/toggle-expand.gif b/chef-server-webui/public/images/toggle-expand.gif
index 03fa8360dd..03fa8360dd 100644
--- a/chef-server-slice/public/images/toggle-expand.gif
+++ b/chef-server-webui/public/images/toggle-expand.gif
Binary files differ
diff --git a/chef-server-slice/public/images/treeBuilderImages/Thumbs.db b/chef-server-webui/public/images/treeBuilderImages/Thumbs.db
index 934ed17cb5..934ed17cb5 100755
--- a/chef-server-slice/public/images/treeBuilderImages/Thumbs.db
+++ b/chef-server-webui/public/images/treeBuilderImages/Thumbs.db
Binary files differ
diff --git a/chef-server-slice/public/images/treeBuilderImages/doc.gif b/chef-server-webui/public/images/treeBuilderImages/doc.gif
index a0f4dd5dfb..a0f4dd5dfb 100755
--- a/chef-server-slice/public/images/treeBuilderImages/doc.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/doc.gif
Binary files differ
diff --git a/chef-server-slice/public/images/treeBuilderImages/docNode.gif b/chef-server-webui/public/images/treeBuilderImages/docNode.gif
index 40167db1a5..40167db1a5 100755
--- a/chef-server-slice/public/images/treeBuilderImages/docNode.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/docNode.gif
Binary files differ
diff --git a/chef-server-slice/public/images/treeBuilderImages/docNodeLast.gif b/chef-server-webui/public/images/treeBuilderImages/docNodeLast.gif
index b7b3e55cd0..b7b3e55cd0 100755
--- a/chef-server-slice/public/images/treeBuilderImages/docNodeLast.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/docNodeLast.gif
Binary files differ
diff --git a/chef-server-slice/public/images/treeBuilderImages/docNodeLastFirst.gif b/chef-server-webui/public/images/treeBuilderImages/docNodeLastFirst.gif
index 2cbb765d7c..2cbb765d7c 100755
--- a/chef-server-slice/public/images/treeBuilderImages/docNodeLastFirst.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/docNodeLastFirst.gif
Binary files differ
diff --git a/chef-server-slice/public/images/treeBuilderImages/folder.gif b/chef-server-webui/public/images/treeBuilderImages/folder.gif
index 3952b3d234..3952b3d234 100755
--- a/chef-server-slice/public/images/treeBuilderImages/folder.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/folder.gif
Binary files differ
diff --git a/chef-server-slice/public/images/treeBuilderImages/folderNode.gif b/chef-server-webui/public/images/treeBuilderImages/folderNode.gif
index 5b680136ee..5b680136ee 100755
--- a/chef-server-slice/public/images/treeBuilderImages/folderNode.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/folderNode.gif
Binary files differ
diff --git a/chef-server-slice/public/images/treeBuilderImages/folderNodeFirst.gif b/chef-server-webui/public/images/treeBuilderImages/folderNodeFirst.gif
index 9039327677..9039327677 100755
--- a/chef-server-slice/public/images/treeBuilderImages/folderNodeFirst.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/folderNodeFirst.gif
Binary files differ
diff --git a/chef-server-slice/public/images/treeBuilderImages/folderNodeLast.gif b/chef-server-webui/public/images/treeBuilderImages/folderNodeLast.gif
index b87f003154..b87f003154 100755
--- a/chef-server-slice/public/images/treeBuilderImages/folderNodeLast.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/folderNodeLast.gif
Binary files differ
diff --git a/chef-server-slice/public/images/treeBuilderImages/folderNodeLastFirst.gif b/chef-server-webui/public/images/treeBuilderImages/folderNodeLastFirst.gif
index 64c4d7ae07..64c4d7ae07 100755
--- a/chef-server-slice/public/images/treeBuilderImages/folderNodeLastFirst.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/folderNodeLastFirst.gif
Binary files differ
diff --git a/chef-server-slice/public/images/treeBuilderImages/folderNodeOpen.gif b/chef-server-webui/public/images/treeBuilderImages/folderNodeOpen.gif
index b43ce87bf7..b43ce87bf7 100755
--- a/chef-server-slice/public/images/treeBuilderImages/folderNodeOpen.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/folderNodeOpen.gif
Binary files differ
diff --git a/chef-server-slice/public/images/treeBuilderImages/folderNodeOpenFirst.gif b/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenFirst.gif
index d8ee1fc1b1..d8ee1fc1b1 100755
--- a/chef-server-slice/public/images/treeBuilderImages/folderNodeOpenFirst.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenFirst.gif
Binary files differ
diff --git a/chef-server-slice/public/images/treeBuilderImages/folderNodeOpenLast.gif b/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenLast.gif
index 11ae43a5ae..11ae43a5ae 100755
--- a/chef-server-slice/public/images/treeBuilderImages/folderNodeOpenLast.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenLast.gif
Binary files differ
diff --git a/chef-server-slice/public/images/treeBuilderImages/folderNodeOpenLastFirst.gif b/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenLastFirst.gif
index ba5c0d168d..ba5c0d168d 100755
--- a/chef-server-slice/public/images/treeBuilderImages/folderNodeOpenLastFirst.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenLastFirst.gif
Binary files differ
diff --git a/chef-server-slice/public/images/treeBuilderImages/folderOpen.gif b/chef-server-webui/public/images/treeBuilderImages/folderOpen.gif
index 7df8d32261..7df8d32261 100755
--- a/chef-server-slice/public/images/treeBuilderImages/folderOpen.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/folderOpen.gif
Binary files differ
diff --git a/chef-server-slice/public/images/treeBuilderImages/vertLine.gif b/chef-server-webui/public/images/treeBuilderImages/vertLine.gif
index 63ee93a0f3..63ee93a0f3 100755
--- a/chef-server-slice/public/images/treeBuilderImages/vertLine.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/vertLine.gif
Binary files differ
diff --git a/chef-server-slice/public/javascripts/JSONeditor.js b/chef-server-webui/public/javascripts/JSONeditor.js
index b6dac054ba..b6dac054ba 100755
--- a/chef-server-slice/public/javascripts/JSONeditor.js
+++ b/chef-server-webui/public/javascripts/JSONeditor.js
diff --git a/chef-server-slice/public/javascripts/chef.js b/chef-server-webui/public/javascripts/chef.js
index dfb8b5a1e4..dfb8b5a1e4 100644
--- a/chef-server-slice/public/javascripts/chef.js
+++ b/chef-server-webui/public/javascripts/chef.js
diff --git a/chef-server-slice/public/javascripts/jquery-1.3.2.min.js b/chef-server-webui/public/javascripts/jquery-1.3.2.min.js
index b1ae21d8b2..b1ae21d8b2 100755
--- a/chef-server-slice/public/javascripts/jquery-1.3.2.min.js
+++ b/chef-server-webui/public/javascripts/jquery-1.3.2.min.js
diff --git a/chef-server-slice/public/javascripts/jquery-ui-1.7.1.custom.min.js b/chef-server-webui/public/javascripts/jquery-ui-1.7.1.custom.min.js
index 2ea9f4cd52..2ea9f4cd52 100755
--- a/chef-server-slice/public/javascripts/jquery-ui-1.7.1.custom.min.js
+++ b/chef-server-webui/public/javascripts/jquery-ui-1.7.1.custom.min.js
diff --git a/chef-server-slice/public/javascripts/jquery.editinline.js b/chef-server-webui/public/javascripts/jquery.editinline.js
index e97abe1d15..e97abe1d15 100644
--- a/chef-server-slice/public/javascripts/jquery.editinline.js
+++ b/chef-server-webui/public/javascripts/jquery.editinline.js
diff --git a/chef-server-slice/public/javascripts/jquery.jeditable.mini.js b/chef-server-webui/public/javascripts/jquery.jeditable.mini.js
index fdc46ff456..fdc46ff456 100644
--- a/chef-server-slice/public/javascripts/jquery.jeditable.mini.js
+++ b/chef-server-webui/public/javascripts/jquery.jeditable.mini.js
diff --git a/chef-server-slice/public/javascripts/jquery.livequery.js b/chef-server-webui/public/javascripts/jquery.livequery.js
index dde8ad8e32..dde8ad8e32 100644
--- a/chef-server-slice/public/javascripts/jquery.livequery.js
+++ b/chef-server-webui/public/javascripts/jquery.livequery.js
diff --git a/chef-server-slice/public/javascripts/jquery.localscroll.js b/chef-server-webui/public/javascripts/jquery.localscroll.js
index 596e1ba200..596e1ba200 100644
--- a/chef-server-slice/public/javascripts/jquery.localscroll.js
+++ b/chef-server-webui/public/javascripts/jquery.localscroll.js
diff --git a/chef-server-slice/public/javascripts/jquery.scrollTo.js b/chef-server-webui/public/javascripts/jquery.scrollTo.js
index 688d58e55d..688d58e55d 100644
--- a/chef-server-slice/public/javascripts/jquery.scrollTo.js
+++ b/chef-server-webui/public/javascripts/jquery.scrollTo.js
diff --git a/chef-server-slice/public/javascripts/jquery.tools.min.js b/chef-server-webui/public/javascripts/jquery.tools.min.js
index 4133a43498..4133a43498 100644
--- a/chef-server-slice/public/javascripts/jquery.tools.min.js
+++ b/chef-server-webui/public/javascripts/jquery.tools.min.js
diff --git a/chef-server-slice/public/javascripts/jquery.treeTable.min.js b/chef-server-webui/public/javascripts/jquery.treeTable.min.js
index be94663a80..be94663a80 100644
--- a/chef-server-slice/public/javascripts/jquery.treeTable.min.js
+++ b/chef-server-webui/public/javascripts/jquery.treeTable.min.js
diff --git a/chef-server-webui/public/stylesheets/base.css b/chef-server-webui/public/stylesheets/base.css
new file mode 100644
index 0000000000..0da62086ed
--- /dev/null
+++ b/chef-server-webui/public/stylesheets/base.css
@@ -0,0 +1,336 @@
+* {margin:0;padding:0}
+.clear { clear: both; height: 0; }
+
+h1 { margin: 15px 0; font-size: 22px; font-weight: normal; }
+h2 { font-size: 22px; margin: 15px 0; font-weight: normal;}
+h3 { font-size: 18px; margin: 10px 0; font-weight: normal;}
+h4 { font-size: 16px; margin: 10px 0; font-weight: normal;}
+hr {height: 1px; border: 0; }
+p { margin: 15px 0;}
+a img { border: none; }
+
+body {
+ font-size: 12px;
+ font-family: sans-serif;
+}
+
+#container {
+ min-width: 960px;
+}
+
+#header, #wrapper {
+ padding: 0 20px;
+}
+
+#header {
+ position: relative;
+ padding-top: 1px;
+}
+
+#header h1 {
+ margin: 0;
+ padding: 10px 0;
+ font-size: 30px;
+}
+
+#header h1 a:link, #header h1 a:active, #header h1 a:hover, #header h1 a:visited {
+ text-decoration: none;
+}
+
+#main {
+ width: 80%;
+ float: left;
+}
+
+.actions-bar {
+ padding: 10px 1px;
+}
+
+.actions-bar .actions {
+ float: left;
+}
+
+
+.actions-bar .pagination {
+ float: right;
+ padding: 1px 0;
+}
+
+#sidebar {
+ width: 15%;
+ float: right;
+}
+
+#sidebar h3 {
+ padding: 10px 15px;
+ margin: 0;
+ font-size: 13px;
+}
+
+#sidebar .block {
+ margin-bottom: 20px;
+ padding-bottom: 10px;
+}
+
+#sidebar .block .content {
+ padding: 0 15px;
+}
+
+#sidebar ul.navigation li a:link, #sidebar ul.navigation li a:visited {
+ display: block;
+ padding: 10px 15px;
+}
+
+#sidebar .block .sidebar-block, #sidebar .notice {
+ padding:10px;
+}
+
+#wrapper {
+ padding-top: 20px;
+}
+
+#main .block {
+ margin-bottom: 20px;
+ padding-top: 1px;
+}
+
+#main .block .content .inner {
+ padding: 0 15px 15px;
+}
+
+#main .main p.first {
+ margin-top: 0;
+}
+
+#user-navigation {
+ position: absolute;
+ top: 0px;
+ right: 20px;
+}
+
+#main-navigation {
+ width: 100%;
+}
+
+#user-navigation ul, #main-navigation ul, .secondary-navigation ul, #sidebar ul.navigation {
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+}
+
+#user-navigation ul li, #main-navigation ul li, .secondary-navigation ul li {
+ float: left;
+}
+
+#main-navigation ul li {
+ margin-right: 5px;
+}
+
+#user-navigation ul li {
+ padding: 5px 10px;
+}
+
+#main-navigation ul li a:link, #main-navigation ul li a:visited, #main-navigation ul li a:hover, #main-navigation ul li a:active,
+.secondary-navigation ul li a:link, .secondary-navigation ul li a:visited, .secondary-navigation ul li a:hover, .secondary-navigation ul li a:active,
+#user-navigation ul li a:link, #user-navigation ul li a:visited, #user-navigation ul li a:hover, #user-navigation ul li a:active {
+ text-decoration: none;
+}
+
+#main-navigation ul li a {
+ font-size: 15px;
+ display: block;
+ padding: 8px 15px;
+}
+
+.secondary-navigation {
+ font-size: 13px;
+ border-bottom-width: 10px;
+ border-bottom-style: solid;
+}
+
+.secondary-navigation ul li a {
+ display: block;
+ padding: 10px 15px;
+}
+
+#footer {
+ padding-bottom: 20px;
+}
+
+/* pagination */
+
+.pagination a, .pagination span {
+ padding: 2px 5px;
+ margin-right: 5px;
+ display: block;
+ float: left;
+ border-style: solid;
+ border-width: 1px;
+}
+
+.pagination span.current {
+ font-weight: bold;
+}
+
+.pagination a {
+ text-decoration: none;
+}
+
+/* tables */
+.table {
+ width: 100%;
+ border-collapse: collapse;
+ margin-bottom: 15px;
+}
+
+.table th {
+ padding: 10px;
+ font-weight: bold;
+ text-align: left;
+}
+
+.table th.first {
+ width: 30px;
+}
+
+.table th.last {
+ width: 200px;
+}
+
+.table .checkbox {
+ margin-left: 10px;
+}
+
+.table td {
+ padding: 10px;
+}
+
+.table td.last {
+ text-align: right;
+}
+
+/* forms */
+
+input.checkbox {
+ margin: 0;
+ padding: 0;
+}
+
+.form .group {
+ margin-bottom: 15px;
+}
+
+.form div.left {
+ width: 20%;
+ float: left;
+}
+
+.form div.right {
+ width: 75%;
+ float: right;
+}
+
+.form .columns .column {
+ width: 48%;
+}
+
+.form .columns .left {
+ float: left;
+}
+
+.form .columns .right {
+ float: right;
+}
+
+.form label.label, .form input.text_field, .form textarea.text_area {
+ font-size: 1.2em;
+ padding: 1px 0;
+ margin: 0;
+}
+
+.form label.right {
+ text-align: right;
+}
+
+.form input.checkbox, .form input.radio {
+ margin-right: 5px;
+}
+
+.form label.checkbox, .form label.radio {
+ line-height: 1.5em;
+}
+
+.form label.label {
+ display: block;
+ padding-bottom: 2px;
+ font-weight: bold;
+}
+
+.form div.fieldWithErrors label.label {
+ display: inline;
+}
+
+.form .fieldWithErrors .error {
+ color: red;
+}
+
+.form input.text_field, .form textarea.text_area {
+ width: 100%;
+ border-width: 1px;
+ border-style: solid;
+}
+
+/* lists */
+
+ul.list {
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+}
+
+ul.list li {
+ clear: left;
+ padding-bottom: 5px;
+}
+
+ul.list li .left {
+ float: left;
+}
+
+ul.list li .left .avatar {
+ width: 50px;
+ height: 50px;
+}
+
+ul.list li .item {
+ margin-left: 80px;
+}
+
+ul.list li .item .avatar {
+ float: left;
+ margin: 0 5px 5px 0;
+ width: 30px;
+ height: 30px;
+}
+
+/* box */
+
+#box {
+ width: 500px;
+ margin: 50px auto;
+}
+
+#box .block {
+ margin-bottom: 20px;
+}
+
+#box .block h2 {
+ padding: 10px 15px;
+ margin: 0;
+}
+
+#box .block .content {
+ padding: 10px 20px;
+}
+
+
diff --git a/chef-server-webui/public/stylesheets/chef.css b/chef-server-webui/public/stylesheets/chef.css
new file mode 100644
index 0000000000..52a06d056c
--- /dev/null
+++ b/chef-server-webui/public/stylesheets/chef.css
@@ -0,0 +1,157 @@
+.ruby .normal {}
+.ruby .comment { color: #005; font-style: italic; }
+.ruby .keyword { color: #A00; font-weight: bold; }
+.ruby .method { color: #077; }
+.ruby .class { color: #074; }
+.ruby .module { color: #050; }
+.ruby .punct { color: #447; font-weight: bold; }
+.ruby .symbol { color: #099; }
+.ruby .string { color: #944; background: #FFE; }
+.ruby .char { color: #F07; }
+.ruby .ident { color: #004; }
+.ruby .constant { color: #07F; }
+.ruby .regex { color: #B66; background: #FEF; }
+.ruby .number { color: #F99; }
+.ruby .attribute { color: #7BB; }
+.ruby .global { color: #7FB; }
+.ruby .expr { color: #227; }
+.ruby .escape { color: #277; }
+
+.files {
+ padding-left: 20px;
+}
+
+.code {
+ overflow: auto;
+ font-size: 0.8em;
+}
+
+.search td {
+ /*overflow: auto;*/
+ font-size: 0.8em;
+}
+
+dl dt { font-weight: bold; }
+.content td dl { margin: 0; padding: 0; }
+.content td dt {
+ background: transparent url(/images/toggle-collapse.gif) 0 3px no-repeat;
+ clear: left; color: #333; cursor: pointer; line-height: 1em;
+ margin-left: -12px; padding-left: 14px;
+}
+.content td dd { margin: 0;
+ padding: 0 0 0 1em;
+}
+.content td dt.collapsed {
+ background-image: url(/images/toggle-expand.gif);
+}
+.content td dt.inline { background-image: none; cursor: default;
+ float: left; margin-left: 0; padding-left: 2px; padding-right: .5em;
+ padding-top: 2px;
+}
+
+div.sortable {
+ height: 200px;
+ width: 225px;
+ margin-right: 10px;
+ border: 1px solid black;
+ overflow: scroll;
+ background: #777777;
+}
+
+div.sortable.run-list {
+ height: 430px;
+ width: 225px;
+ margin-right: 10px;
+ border: 1px solid black;
+ overflow: scroll;
+ background: #777777;
+}
+
+div.clear {
+ clear: left;
+}
+
+table.form th {
+ /* vertical-align: top; */
+ font-weight: bold;
+ text-align: right;
+ padding-right: 10px;
+}
+
+div.help {
+ padding: 10px;
+ text-align: left;
+ vertical-align: top;
+}
+
+table.form td {
+ text-align: left;
+ /* vertical-align: top; */
+}
+
+table.sortable td {
+ vertical-align: top;
+}
+
+table.sortable ul {
+ background: #ff00ff;
+ margin: 5px;
+ padding: 5px;
+}
+
+td.table-key {
+ font-weight: bold;
+}
+
+div.json-attr {
+ overflow: auto;
+ max-width: 400px;
+}
+
+td.position {
+ font-weight: bold;
+}
+
+div#tree {
+ float: left;
+}
+
+div#jform {
+ float: left;
+ margin-left: 50px;
+}
+
+div.editor {
+ border: 1px solid black;
+}
+
+#node_available_roles, #node_available_recipes, #for_node, #for_role, #available_recipes { list-style-type: none; margin: 0; padding: 0; float: left; margin-right: 10px; background: #eee; padding: 5px; width: 200px;}
+
+#node_available_roles li, #node_available_recipes li, #for_node li, #for_role li, #available_recipes li { margin: 0 5px 5px 5px; padding: 5px; width: 175px; }
+
+#sidebar_block {
+ display: none;
+}
+
+#sidebar_block_notice {
+ display: none;
+}
+
+div.tooltip {
+ background-color: #000;
+ outline: 1px solid #669;
+ border: 2px solid #fff;
+ padding: 10px 15px;
+ /* width: 200px; */
+ display: none;
+ color: #fff;
+ text-align: left;
+ font-size: 12px;
+ outline-radius: 4px;
+ -moz-outline-radius: 4px;
+ -webkit-outline-radius: 4px;
+}
+
+table.tooltip {
+ width: 190px;
+}
diff --git a/chef-server-slice/public/stylesheets/images/ui-bg_diagonals-small_0_aaaaaa_40x40.png b/chef-server-webui/public/stylesheets/images/ui-bg_diagonals-small_0_aaaaaa_40x40.png
index d5359734ad..d5359734ad 100755
--- a/chef-server-slice/public/stylesheets/images/ui-bg_diagonals-small_0_aaaaaa_40x40.png
+++ b/chef-server-webui/public/stylesheets/images/ui-bg_diagonals-small_0_aaaaaa_40x40.png
Binary files differ
diff --git a/chef-server-slice/public/stylesheets/images/ui-bg_diagonals-thick_15_444444_40x40.png b/chef-server-webui/public/stylesheets/images/ui-bg_diagonals-thick_15_444444_40x40.png
index 88e46a6d36..88e46a6d36 100755
--- a/chef-server-slice/public/stylesheets/images/ui-bg_diagonals-thick_15_444444_40x40.png
+++ b/chef-server-webui/public/stylesheets/images/ui-bg_diagonals-thick_15_444444_40x40.png
Binary files differ
diff --git a/chef-server-slice/public/stylesheets/images/ui-bg_glass_100_f0f0f0_1x400.png b/chef-server-webui/public/stylesheets/images/ui-bg_glass_100_f0f0f0_1x400.png
index 33896e710c..33896e710c 100755
--- a/chef-server-slice/public/stylesheets/images/ui-bg_glass_100_f0f0f0_1x400.png
+++ b/chef-server-webui/public/stylesheets/images/ui-bg_glass_100_f0f0f0_1x400.png
Binary files differ
diff --git a/chef-server-slice/public/stylesheets/images/ui-bg_glass_50_99c2ff_1x400.png b/chef-server-webui/public/stylesheets/images/ui-bg_glass_50_99c2ff_1x400.png
index 8249158b01..8249158b01 100755
--- a/chef-server-slice/public/stylesheets/images/ui-bg_glass_50_99c2ff_1x400.png
+++ b/chef-server-webui/public/stylesheets/images/ui-bg_glass_50_99c2ff_1x400.png
Binary files differ
diff --git a/chef-server-slice/public/stylesheets/images/ui-bg_glass_55_fbf5d0_1x400.png b/chef-server-webui/public/stylesheets/images/ui-bg_glass_55_fbf5d0_1x400.png
index f25dd91065..f25dd91065 100755
--- a/chef-server-slice/public/stylesheets/images/ui-bg_glass_55_fbf5d0_1x400.png
+++ b/chef-server-webui/public/stylesheets/images/ui-bg_glass_55_fbf5d0_1x400.png
Binary files differ
diff --git a/chef-server-slice/public/stylesheets/images/ui-bg_glass_80_e6e6e6_1x400.png b/chef-server-webui/public/stylesheets/images/ui-bg_glass_80_e6e6e6_1x400.png
index abaa23f001..abaa23f001 100755
--- a/chef-server-slice/public/stylesheets/images/ui-bg_glass_80_e6e6e6_1x400.png
+++ b/chef-server-webui/public/stylesheets/images/ui-bg_glass_80_e6e6e6_1x400.png
Binary files differ
diff --git a/chef-server-slice/public/stylesheets/images/ui-bg_glass_95_fef1ec_1x400.png b/chef-server-webui/public/stylesheets/images/ui-bg_glass_95_fef1ec_1x400.png
index 4443fdc1a1..4443fdc1a1 100755
--- a/chef-server-slice/public/stylesheets/images/ui-bg_glass_95_fef1ec_1x400.png
+++ b/chef-server-webui/public/stylesheets/images/ui-bg_glass_95_fef1ec_1x400.png
Binary files differ
diff --git a/chef-server-slice/public/stylesheets/images/ui-bg_highlight-hard_100_f9f9f9_1x100.png b/chef-server-webui/public/stylesheets/images/ui-bg_highlight-hard_100_f9f9f9_1x100.png
index 2cb80364bb..2cb80364bb 100755
--- a/chef-server-slice/public/stylesheets/images/ui-bg_highlight-hard_100_f9f9f9_1x100.png
+++ b/chef-server-webui/public/stylesheets/images/ui-bg_highlight-hard_100_f9f9f9_1x100.png
Binary files differ
diff --git a/chef-server-slice/public/stylesheets/images/ui-bg_highlight-soft_100_e7eef3_1x100.png b/chef-server-webui/public/stylesheets/images/ui-bg_highlight-soft_100_e7eef3_1x100.png
index d39c182270..d39c182270 100755
--- a/chef-server-slice/public/stylesheets/images/ui-bg_highlight-soft_100_e7eef3_1x100.png
+++ b/chef-server-webui/public/stylesheets/images/ui-bg_highlight-soft_100_e7eef3_1x100.png
Binary files differ
diff --git a/chef-server-slice/public/stylesheets/images/ui-icons_222222_256x240.png b/chef-server-webui/public/stylesheets/images/ui-icons_222222_256x240.png
index 67560da9be..67560da9be 100755
--- a/chef-server-slice/public/stylesheets/images/ui-icons_222222_256x240.png
+++ b/chef-server-webui/public/stylesheets/images/ui-icons_222222_256x240.png
Binary files differ
diff --git a/chef-server-slice/public/stylesheets/images/ui-icons_2694e8_256x240.png b/chef-server-webui/public/stylesheets/images/ui-icons_2694e8_256x240.png
index dbd78b68ad..dbd78b68ad 100755
--- a/chef-server-slice/public/stylesheets/images/ui-icons_2694e8_256x240.png
+++ b/chef-server-webui/public/stylesheets/images/ui-icons_2694e8_256x240.png
Binary files differ
diff --git a/chef-server-slice/public/stylesheets/images/ui-icons_2e83ff_256x240.png b/chef-server-webui/public/stylesheets/images/ui-icons_2e83ff_256x240.png
index b425c446d2..b425c446d2 100755
--- a/chef-server-slice/public/stylesheets/images/ui-icons_2e83ff_256x240.png
+++ b/chef-server-webui/public/stylesheets/images/ui-icons_2e83ff_256x240.png
Binary files differ
diff --git a/chef-server-slice/public/stylesheets/images/ui-icons_72a7cf_256x240.png b/chef-server-webui/public/stylesheets/images/ui-icons_72a7cf_256x240.png
index 58aed4074e..58aed4074e 100755
--- a/chef-server-slice/public/stylesheets/images/ui-icons_72a7cf_256x240.png
+++ b/chef-server-webui/public/stylesheets/images/ui-icons_72a7cf_256x240.png
Binary files differ
diff --git a/chef-server-slice/public/stylesheets/images/ui-icons_888888_256x240.png b/chef-server-webui/public/stylesheets/images/ui-icons_888888_256x240.png
index 2e5180e473..2e5180e473 100755
--- a/chef-server-slice/public/stylesheets/images/ui-icons_888888_256x240.png
+++ b/chef-server-webui/public/stylesheets/images/ui-icons_888888_256x240.png
Binary files differ
diff --git a/chef-server-slice/public/stylesheets/images/ui-icons_cd0a0a_256x240.png b/chef-server-webui/public/stylesheets/images/ui-icons_cd0a0a_256x240.png
index 2db88b796a..2db88b796a 100755
--- a/chef-server-slice/public/stylesheets/images/ui-icons_cd0a0a_256x240.png
+++ b/chef-server-webui/public/stylesheets/images/ui-icons_cd0a0a_256x240.png
Binary files differ
diff --git a/chef-server-slice/public/stylesheets/images/ui-icons_ffffff_256x240.png b/chef-server-webui/public/stylesheets/images/ui-icons_ffffff_256x240.png
index 746e6fa257..746e6fa257 100755
--- a/chef-server-slice/public/stylesheets/images/ui-icons_ffffff_256x240.png
+++ b/chef-server-webui/public/stylesheets/images/ui-icons_ffffff_256x240.png
Binary files differ
diff --git a/chef-server-slice/public/stylesheets/jquery-ui-1.7.1.custom.css b/chef-server-webui/public/stylesheets/jquery-ui-1.7.1.custom.css
index 0e93ed5dd1..0e93ed5dd1 100755
--- a/chef-server-slice/public/stylesheets/jquery-ui-1.7.1.custom.css
+++ b/chef-server-webui/public/stylesheets/jquery-ui-1.7.1.custom.css
diff --git a/chef-server-slice/public/stylesheets/jquery.treeTable.css b/chef-server-webui/public/stylesheets/jquery.treeTable.css
index 1f37f1ca15..1f37f1ca15 100644
--- a/chef-server-slice/public/stylesheets/jquery.treeTable.css
+++ b/chef-server-webui/public/stylesheets/jquery.treeTable.css
diff --git a/chef-server-webui/public/stylesheets/themes/bec-green/style.css b/chef-server-webui/public/stylesheets/themes/bec-green/style.css
new file mode 100644
index 0000000000..225b6d4e35
--- /dev/null
+++ b/chef-server-webui/public/stylesheets/themes/bec-green/style.css
@@ -0,0 +1,290 @@
+a:link, a:visited, a:hover, a:active { color: #33f; }
+h1, h2, h3 {color:#444}
+
+body {
+ color: #222;
+ background: #e5e5e5;
+ font-family: "Bitstream Vera Sans", verdana, sans-serif;
+}
+
+hr {
+ background: #f0f0ee;
+}
+
+p {
+ font-size: 14px;
+ line-height: 20px;
+}
+
+input.checkbox {
+ vertical-align:middle;
+}
+
+#header h1 {
+ font-size: 28px;
+ padding: 5px 0;
+ margin: 5px 0;
+}
+
+.hightlight {
+ background-color: #ffc;
+}
+.small {
+ font-size: 11px;
+}
+.gray {
+ color: #999;
+}
+#header {
+ background: #48625B;
+}
+
+#header h1 a:link, #header h1 a:active, #header h1 a:hover, #header h1 a:visited {
+ color: #FFF;
+}
+
+#main {
+ background: #e5e5e5;
+ width: 69%;
+}
+
+#main .block {
+ -moz-border-radius-topleft: 4px;
+ -moz-border-radius-topright: 4px;
+ padding: 0;
+ margin-bottom:20px;
+ padding-bottom: 20px;
+ background: #fff;
+}
+
+#main .block h2.title {
+ margin: 0 0 20px 0;
+ background-color: #E9FAE6;
+ padding: 5px 5px 5px 15px;
+ font-size:18px;
+}
+
+.main_container {
+ padding:10px;
+}
+
+/* #sidebar .block { background: #FFF; padding-bottom:0px; } */
+
+#sidebar .notice {
+ background-color: #ffc;
+ padding: 0 10px;
+ border-bottom:1px solid #ddd;
+ border-right:1px solid #ddd;
+ border-top:1px solid #fff;
+ border-left:1px solid #fff;
+}
+#sidebar .notice h2 {
+ font-size:16px;
+ margin: 5px 0;
+ border-bottom:1px solid #aaa;
+}
+#sidebar .notice p {
+ font-size:12px;
+}
+
+#sidebar .block {
+ padding-bottom: 0;
+}
+
+#sidebar .block .content {
+ padding: 0 10px;
+}
+
+
+#sidebar h3 {
+ background: #c7d8d8;
+ border-bottom:1px solid #999;
+ padding: 5px 10px;
+}
+
+#sidebar ul li a:link, #sidebar ul li a:visited {
+ font-size:14px;
+}
+
+#sidebar ul li a:hover, #sidebar ul li a:active {
+ background: #E9FAE6;
+ color: #444;
+ font-size:14px;
+ text-decoration:underline;
+}
+#sidebar ul.navigation li.last a {
+ border-bottom: none;
+}
+
+#sidebar ul.navigation li a:link,#sidebar ul.navigation li a:visited {
+ padding: 5px 10px;
+ color:#444;
+ text-decoration: none;
+}
+#sidebar ul.navigation li a:hover {
+ text-decoration:underline;
+}
+#sidebar .block .sidebar-block h4 {
+ border-bottom: 1px solid #bbb;
+}
+#main-navigation ul li {
+ background: #30423E;
+}
+
+#main-navigation ul li:hover {
+ background: #23302D;
+}
+
+#main-navigation ul li.active {
+ background: #e5e5e5;
+}
+
+#main-navigation ul li a:link, #main-navigation ul li a:visited, #main-navigation ul li a:hover, #main-navigation ul li a:active,
+.secondary-navigation ul li a:link, .secondary-navigation ul li a:visited, .secondary-navigation ul li a:hover, .secondary-navigation ul li a:active,
+#user-navigation ul li a:link, #user-navigation ul li a:visited, #user-navigation ul li a:hover, #user-navigation ul li a:active {
+ text-decoration: none;
+ color: #FFF;
+}
+
+#main-navigation ul li a {
+ font-size: 14px;
+ padding: 4px 10px;
+}
+
+#main-navigation ul li.active a:link, #main-navigation ul li.active a:visited, #main-navigation ul li.active a:hover, #main-navigation ul li.active a:active {
+ color: #333;
+}
+#user-navigation ul li a:hover {
+ text-decoration: underline;
+}
+.secondary-navigation {
+ background: #48625B;
+ border-bottom-color: #30423e;
+ border-bottom: 5px solid #30423e;
+}
+
+.secondary-navigation ul li.active {
+ background-color: #30423e;
+}
+
+.secondary-navigation ul li:hover {
+ background-color: #23302d;
+}
+
+/* pagination */
+
+.pagination span.current {
+ background: #30423e;
+ color: #FFF;
+ border: 1px solid #30423e;
+ -moz-border-radius:5px;
+}
+
+.pagination a {
+ color: #364B69;
+ border: 1px solid #ddd;
+ -moz-border-radius:5px;
+}
+
+.pagination a:hover {
+ color: #444;
+ background: #E9FAE6;
+}
+
+/* tables */
+
+.table th {
+ background: #48625B;
+ color: #FFF;
+ font-weight:normal;
+ padding:3px;
+}
+
+.table th a.toggle {
+ display: block;
+ width: 12px;
+ height: 12px;
+ background: transparent url('images/tick.png') center no-repeat;
+ text-indent: -9999px;
+ -moz-outline: none;
+}
+
+.table th.first {
+ width: 30px;
+ text-align: center;
+}
+
+.table td {
+ border-bottom: 1px solid #F0F0EE;
+}
+
+/* forms */
+
+.form input.text, .form textarea.textarea {
+ border: 1px solid #ddd;
+ padding: 5px;
+ width: 95%;
+}
+
+.form .navform {
+ padding:10px;
+ background-color: #E9FAE6;
+ font-size:14px;
+ border-bottom:1px solid #ddd;
+ border-right:1px solid #ddd;
+ border-top:1px solid #eee;
+ border-left:1px solid #eee;
+}
+.form .navform input {
+ font-size:14px;
+}
+
+/* flash-messages */
+.flash .message {
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ text-align:center;
+ margin:0 auto 5px;
+ width:80%;
+}
+.flash .message p {
+ margin:8px;
+}
+.flash .error {
+ border: 1px solid #fbb;
+ background-color: #fdd;
+}
+.flash .warning {
+ border: 1px solid #fffaaa;
+ background-color: #ffffcc;
+}
+.flash .notice {
+ border: 1px solid #ddf;
+ background-color: #eef;
+}
+
+/* lists */
+
+ul.list li {
+ border-bottom-color: #F0F0EE;
+}
+
+ul.list li .item .avatar {
+ border-color: #F0F0EE;
+ margin: 3px 10px 0 0;
+}
+
+ul.list li .left {
+ padding: 5px 5px;
+}
+
+/* box */
+
+#box .block {
+ background: #FFF;
+}
+
+#box .block h2 {
+ background: #48625B;
+ color: #FFF;
+}
diff --git a/chef-server-webui/public/stylesheets/themes/bec/style.css b/chef-server-webui/public/stylesheets/themes/bec/style.css
new file mode 100644
index 0000000000..c94474866a
--- /dev/null
+++ b/chef-server-webui/public/stylesheets/themes/bec/style.css
@@ -0,0 +1,301 @@
+a:link, a:visited, a:hover, a:active { color: #33f; }
+h1, h2, h3 {color:#444}
+
+body {
+ color: #333;
+ background: #e5e5e5;
+ font-family: Verdana, Arial, "Bitstream Vera Sans", sans-serif;
+}
+
+hr {
+ background: #f0f0ee;
+}
+
+p {
+ font-size: 12px;
+ line-height: 20px;
+}
+
+input.checkbox {
+ vertical-align:middle;
+}
+
+#header h1 {
+ font-size: 28px;
+ padding: 5px 0;
+ margin: 5px 0;
+}
+
+.hightlight {
+ background-color: #ffc;
+}
+.small {
+ font-size: 11px;
+}
+.gray {
+ color: #999;
+}
+#header {
+ background: #006666;
+}
+
+#header h1 a:link, #header h1 a:active, #header h1 a:hover, #header h1 a:visited {
+ color: #FFF;
+}
+
+#main {
+ background: #e5e5e5;
+ width: 73%;
+}
+
+#main .block {
+ -moz-border-radius-topleft: 4px;
+ -moz-border-radius-topright: 4px;
+ padding: 0;
+ margin-bottom:20px;
+ padding-bottom: 20px;
+ background: #fff;
+}
+
+#main .block h2.title {
+ margin: 0 0 20px 0;
+ background-color: #E6FAFA;
+ padding: 5px 5px 5px 15px;
+ font-size:18px;
+}
+
+.main_container {
+ padding:10px;
+}
+
+#footer .block p {
+ color:#aaa;
+ font-size:11px;
+}
+
+/* #sidebar .block { background: #FFF; padding-bottom:0px; } */
+
+#sidebar .notice {
+ background-color: #ffc;
+ padding: 0 10px;
+ border-bottom:1px solid #ddd;
+ border-right:1px solid #ddd;
+ border-top:1px solid #fff;
+ border-left:1px solid #fff;
+}
+#sidebar .notice h2 {
+ font-size:16px;
+ margin: 5px 0;
+ border-bottom:1px solid #aaa;
+}
+#sidebar .notice p {
+ font-size:12px;
+}
+
+#sidebar .block {
+ padding-bottom: 0;
+}
+
+#sidebar .block .content {
+ padding: 0 10px;
+}
+
+
+#sidebar h3 {
+ background: #c7d8d8;
+ border-bottom:1px solid #999;
+ padding: 5px 10px;
+}
+
+#sidebar ul li a:link, #sidebar ul li a:visited {
+ font-size:12px;
+}
+
+#sidebar ul li a:hover, #sidebar ul li a:active {
+ background: #e1efef;
+ color: #444;
+ font-size:12px;
+ text-decoration:underline;
+}
+#sidebar ul.navigation li.last a {
+ border-bottom: none;
+}
+
+#sidebar ul.navigation li a:link,#sidebar ul.navigation li a:visited {
+ padding: 5px 10px;
+ color:#444;
+ text-decoration: none;
+}
+#sidebar ul.navigation li a:hover {
+ text-decoration:underline;
+}
+#sidebar .block .sidebar-block h4 {
+ border-bottom: 1px solid #bbb;
+}
+#main-navigation ul li {
+ background: #008c8c;
+}
+
+#main-navigation ul li:hover {
+ background: #00b2b2;
+}
+
+#main-navigation ul li.active {
+ background: #f0f0ee;
+}
+
+#main-navigation ul li a:link, #main-navigation ul li a:visited, #main-navigation ul li a:hover, #main-navigation ul li a:active,
+.secondary-navigation ul li a:link, .secondary-navigation ul li a:visited, .secondary-navigation ul li a:hover, .secondary-navigation ul li a:active,
+#user-navigation ul li a:link, #user-navigation ul li a:visited, #user-navigation ul li a:hover, #user-navigation ul li a:active {
+ text-decoration: none;
+ color: #FFF;
+}
+
+#main-navigation ul li a {
+ font-size: 12px;
+ padding: 4px 10px;
+}
+
+#main-navigation ul li.active a:link, #main-navigation ul li.active a:visited, #main-navigation ul li.active a:hover, #main-navigation ul li.active a:active {
+ color: #364b69;
+}
+#user-navigation ul li a:hover {
+ text-decoration: underline;
+}
+.secondary-navigation {
+ background: #006666;
+ border-bottom-color: #008c8c;
+ border-bottom: 5px solid #008c8c;
+}
+
+.secondary-navigation ul li.active {
+ background-color: #008c8c;
+}
+
+.secondary-navigation ul li:hover {
+ background-color: #00b2b2;
+}
+
+/* pagination */
+
+.pagination span.current {
+ background: #008c8c;
+ color: #FFF;
+ border: 1px solid #008c8c;
+ -moz-border-radius:5px;
+}
+
+.pagination a {
+ color: #364B69;
+ border: 1px solid #ddd;
+ -moz-border-radius:5px;
+ font-size:11px;
+}
+
+.pagination a:hover {
+ color: #444;
+ background: #E6FAFA;
+}
+
+/* tables */
+
+.table th {
+ background: #006666;
+ color: #FFF;
+ font-weight:normal;
+ padding:3px;
+}
+
+.table th a.toggle {
+ display: block;
+ width: 12px;
+ height: 12px;
+ background: transparent url('images/tick.png') center no-repeat;
+ text-indent: -9999px;
+ -moz-outline: none;
+}
+
+.table th.first {
+ width: 30px;
+ text-align: center;
+}
+
+.table td {
+ border-bottom: 1px solid #F0F0EE;
+}
+
+/* forms */
+
+.form input.text, .form textarea.textarea {
+ border: 1px solid #ddd;
+ padding: 5px;
+ width:99%;
+}
+
+.form .navform {
+ padding:10px;
+ background-color: #f1f8f8;
+ font-size:14px;
+ border-bottom:1px solid #ddd;
+ border-right:1px solid #ddd;
+ border-top:1px solid #eee;
+ border-left:1px solid #eee;
+}
+.form .navform input {
+ font-size:14px;
+}
+
+.description {
+ color:#aaa;
+ font-family:Georgia, serif;
+}
+
+/* flash-messages */
+.flash .message {
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ text-align:center;
+ margin:0 auto 5px;
+ width:80%;
+}
+.flash .message p {
+ margin:8px;
+}
+.flash .error {
+ border: 1px solid #fbb;
+ background-color: #fdd;
+}
+.flash .warning {
+ border: 1px solid #fffaaa;
+ background-color: #ffffcc;
+}
+.flash .notice {
+ border: 1px solid #ddf;
+ background-color: #eef;
+}
+
+/* lists */
+
+ul.list li {
+ border-bottom-color: #F0F0EE;
+}
+
+ul.list li .item .avatar {
+ border-color: #F0F0EE;
+ margin: 3px 10px 0 0;
+}
+
+ul.list li .left {
+ padding: 5px 5px;
+}
+
+/* box */
+
+#box .block {
+ background: #FFF;
+}
+
+#box .block h2 {
+ background: #006666;
+ color: #FFF;
+}
diff --git a/chef-server-webui/public/stylesheets/themes/blue/style.css b/chef-server-webui/public/stylesheets/themes/blue/style.css
new file mode 100644
index 0000000000..cce8f4bdf0
--- /dev/null
+++ b/chef-server-webui/public/stylesheets/themes/blue/style.css
@@ -0,0 +1,280 @@
+.small { font-size:12px; }
+.gray { color:#999999; }
+.hightlight { background-color:#FFFFCC; }
+
+a:link, a:visited, a:hover, a:active, h1, h2, h3 { color: #2F427A; }
+a { -moz-outline: none; }
+
+body {
+ color: #222;
+ background: #f0f0ee;
+ font-family: helvetica, arial, sans-serif;
+ font-size: 14px;
+}
+
+hr {
+ background: #f0f0ee;
+ color: #f0f0ee;
+}
+
+#header {
+ background: #2F427A;
+}
+
+#header h1 {
+ padding: 20px 0;
+}
+
+#header h1 a:link, #header h1 a:active, #header h1 a:hover, #header h1 a:visited {
+ color: #FFF;
+}
+
+#user-navigation {
+ top: auto;
+ bottom: 5px;
+ right: 25px;
+}
+
+#main .block .content {
+ background: #FFF;
+ padding-top: 1px;
+}
+
+#main .block .content h2 {
+ margin-left: 15px;
+}
+
+#sidebar .block {
+ background: #FFF;
+}
+
+#sidebar .notice {
+ background: #FFFFCC;
+}
+
+#sidebar h3 {
+ background: #2F427A;
+ color: #FFF;
+ border-bottom: 10px solid #262626;
+ font-size: 15px;
+}
+
+#main-navigation ul li {
+ padding-left: 15px;
+}
+
+#main-navigation ul li a {
+ padding: 8px 0;
+}
+
+#main-navigation ul li.active {
+ padding: 0;
+ margin-left: 15px;
+}
+
+#main-navigation ul li.active {
+ margin-left: 15px;
+}
+
+#main-navigation ul li.active a {
+ padding: 8px 15px;
+}
+
+#sidebar ul li a:link, #sidebar ul li a:visited {
+ background: #FFF;
+ border-bottom: 1px solid #F0F0EE;
+ text-decoration: none;
+}
+
+#sidebar ul li a:hover, #sidebar ul li a:active {
+ background: #316291;
+ color: #FFF;
+}
+
+#main-navigation {
+ background: #262626;
+}
+
+#main-navigation ul li {
+ background: #262626;
+ margin-right: 0;
+}
+
+#main-navigation ul li.active {
+ background: #f0f0ee;
+}
+
+#main-navigation ul li a:link, #main-navigation ul li a:visited, #main-navigation ul li a:hover, #main-navigation ul li a:active,
+.secondary-navigation ul li a:link, .secondary-navigation ul li a:visited, .secondary-navigation ul li a:hover, .secondary-navigation ul li a:active,
+#user-navigation ul li a:link, #user-navigation ul li a:visited, #user-navigation ul li a:hover, #user-navigation ul li a:active {
+ text-decoration: none;
+ color: #FFF;
+}
+
+.secondary-navigation li a:hover {
+ background: #5C637A;
+}
+
+#main-navigation ul li.active a:link, #main-navigation ul li.active a:visited, #main-navigation ul li.active a:hover, #main-navigation ul li.active a:active {
+ color: #262626;
+}
+
+.secondary-navigation {
+ background: #2F427A;
+ border-bottom-color: #262626;
+ font-size: 15px;
+}
+
+.secondary-navigation ul li.active, .secondary-navigation ul li.active a:hover {
+ background-color: #262626;
+}
+
+#footer .block {
+ color: #FFF;
+ background: #262626;
+}
+
+#footer .block p {
+ margin: 0;
+ padding: 10px;
+}
+
+/* pagination */
+
+.pagination span, .pagination a {
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ padding-top: 4px;
+}
+
+.pagination span.current {
+ background: #262626;
+ color: #FFF;
+ border-color: #262626;
+}
+
+.pagination a {
+ color: #262626;
+ border-color: #262626;
+}
+
+.pagination a:hover {
+ color: #FFF;
+ background: #262626;
+}
+
+/* tables */
+
+.table th {
+ background: #262626;
+ color: #FFF;
+}
+
+.table td {
+ border-bottom:1px solid #F0F0EE;
+}
+
+/* forms */
+
+.form input.text_field, .form textarea.text_area {
+ width: 100%;
+ border: 1px solid #262626;
+}
+
+.form input.button {
+ background: #EEE;
+ color: #262626;
+ padding: 2px 5px;
+ border: 1px solid #262626;
+ cursor: pointer;
+}
+
+.form .description {
+ font-style: italic;
+ color: #8C8C8C;
+ font-size: .9em;
+}
+
+/* flash-messages */
+.flash .message {
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ text-align:center;
+ margin: 0 auto 15px;
+
+}
+
+.flash .message p {
+ margin:8px;
+}
+.flash .error {
+ border: 1px solid #fbb;
+ background-color: #fdd;
+}
+.flash .warning {
+ border: 1px solid #fffaaa;
+ background-color: #ffffcc;
+}
+.flash .notice {
+ border: 1px solid #1FDF00;
+ background-color: #BBFFB6;
+}
+
+/* lists */
+
+ul.list li {
+ border-bottom-color: #F0F0EE;
+ border-bottom-width: 1px;
+ border-bottom-style: solid;
+}
+
+ul.list li .item .avatar {
+ border-color: #F0F0EE;
+ border-width: 1px;
+ border-style: solid;
+ padding: 2px;
+}
+
+/* box */
+
+#box .block {
+ background: #FFF;
+}
+
+#box .block h2 {
+ background: #2F427A;
+ color: #FFF;
+}
+
+
+/* rounded borders */
+
+#main, #main-navigation, #main-navigation li, .secondary-navigation, #main .block, #sidebar .block, #sidebar h3, ul.list li,
+#footer .block, .form input.button, #box .block, #box .block h2 {
+ -moz-border-radius-topleft: 4px;
+ -webkit-border-top-left-radius: 4px;
+ -moz-border-radius-topright: 4px;
+ -webkit-border-top-right-radius: 4px;
+}
+
+.secondary-navigation li.first a, .secondary-navigation ul li.first, .table th.first, .table th.first {
+ -moz-border-radius-topleft: 4px;
+ -webkit-border-top-left-radius: 4px;
+}
+
+.table th.last {
+ -moz-border-radius-topright: 4px;
+ -webkit-border-top-right-radius: 4px;
+}
+
+.secondary-navigation ul li.first {
+ -moz-border-radius-topleft: 4px;
+ -webkit-border-top-left-radius: 4px;
+}
+
+#sidebar, #sidebar .block, #main .block, #sidebar ul.navigation, ul.list li, #footer .block, .form input.button, #box .block {
+ -moz-border-radius-bottomleft: 4px;
+ -webkit-border-bottom-left-radius: 4px;
+ -moz-border-radius-bottomright: 4px;
+ -webkit-border-bottom-right-radius: 4px;
+} \ No newline at end of file
diff --git a/chef-server-webui/public/stylesheets/themes/default/style.css b/chef-server-webui/public/stylesheets/themes/default/style.css
new file mode 100644
index 0000000000..e7a5ee1271
--- /dev/null
+++ b/chef-server-webui/public/stylesheets/themes/default/style.css
@@ -0,0 +1,267 @@
+.small { font-size:11px; }
+.gray { color:#999999; }
+.hightlight { background-color:#FFFFCC; }
+
+a:link, a:visited, a:hover, a:active, h1, h2, h3 { color: #7A1818; }
+a { -moz-outline: none; }
+
+body {
+ color: #222;
+ background: #f0f0ee;
+ font-family: helvetica, arial, sans-serif;
+}
+
+hr {
+ background: #f0f0ee;
+ color: #f0f0ee;
+}
+
+#header {
+ background: #7A1818;
+}
+
+#header h1 {
+ padding: 20px 0;
+}
+
+#header h1 a:link, #header h1 a:active, #header h1 a:hover, #header h1 a:visited {
+ color: #FFF;
+}
+
+#user-navigation {
+ top: auto;
+ bottom: 5px;
+ right: 25px;
+}
+
+#main .block .content {
+ background: #FFF;
+ padding-top: 1px;
+}
+
+#main .block .content h2 {
+ margin-left: 15px;
+}
+
+#sidebar .block {
+ background: #FFF;
+}
+
+#sidebar h3 {
+ background: #7A1818;
+ color: #FFF;
+ border-bottom: 10px solid #262626;
+}
+
+#main-navigation ul li {
+ padding-left: 15px;
+}
+
+#main-navigation ul li a {
+ padding: 8px 0;
+}
+
+#main-navigation ul li.active {
+ padding: 0;
+ margin-left: 15px;
+}
+
+#main-navigation ul li.active {
+ margin-left: 15px;
+}
+
+#main-navigation ul li.active a {
+ padding: 8px 15px;
+}
+
+#sidebar ul li a:link, #sidebar ul li a:visited {
+ background: #FFF;
+ border-bottom: 1px solid #F0F0EE;
+ text-decoration: none;
+}
+
+#sidebar ul li a:hover, #sidebar ul li a:active {
+ background: #470E0E;
+ color: #FFF;
+}
+
+#main-navigation {
+ background: #262626;
+}
+
+#main-navigation ul li {
+ background: #262626;
+ margin-right: 0;
+}
+
+#main-navigation ul li.active {
+ background: #f0f0ee;
+}
+
+#main-navigation ul li a:link, #main-navigation ul li a:visited, #main-navigation ul li a:hover, #main-navigation ul li a:active,
+.secondary-navigation ul li a:link, .secondary-navigation ul li a:visited, .secondary-navigation ul li a:hover, .secondary-navigation ul li a:active,
+#user-navigation ul li a:link, #user-navigation ul li a:visited, #user-navigation ul li a:hover, #user-navigation ul li a:active {
+ text-decoration: none;
+ color: #FFF;
+}
+
+.secondary-navigation li a:hover {
+ background: #470E0E;
+}
+
+#main-navigation ul li.active a:link, #main-navigation ul li.active a:visited, #main-navigation ul li.active a:hover, #main-navigation ul li.active a:active {
+ color: #262626;
+}
+
+.secondary-navigation {
+ background: #7A1818;
+ border-bottom-color: #262626;
+}
+
+.secondary-navigation ul li.active, .secondary-navigation ul li.active a:hover {
+ background-color: #262626;
+}
+
+#footer .block {
+ color: #FFF;
+ background: #262626;
+}
+
+#footer .block p {
+ margin: 0;
+ padding: 10px;
+}
+
+/* pagination */
+
+.pagination span.current {
+ background: #262626;
+ color: #FFF;
+ border-color: #262626;
+}
+
+.pagination a {
+ color: #262626;
+ border-color: #262626;
+}
+
+.pagination a:hover {
+ color: #FFF;
+ background: #262626;
+}
+
+/* tables */
+
+.table th {
+ background: #262626;
+ color: #FFF;
+}
+
+.table td {
+ border-bottom:1px solid #F0F0EE;
+}
+
+/* forms */
+
+.form input.text_field, .form textarea.text_area {
+ width: 100%;
+ border: 1px solid #262626;
+}
+
+.form input.button {
+ background: #EEE;
+ color: #262626;
+ padding: 2px 5px;
+ border: 1px solid #262626;
+ cursor: pointer;
+}
+
+.form .description {
+ font-style: italic;
+ color: #8C8C8C;
+ font-size: .9em;
+}
+
+/* flash-messages */
+.flash .message {
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ text-align:center;
+ margin: 0 auto 15px;
+
+}
+
+.flash .message p {
+ margin:8px;
+}
+.flash .error {
+ border: 1px solid #fbb;
+ background-color: #fdd;
+}
+.flash .warning {
+ border: 1px solid #fffaaa;
+ background-color: #ffffcc;
+}
+.flash .notice {
+ border: 1px solid #1FDF00;
+ background-color: #BBFFB6;
+}
+
+/* lists */
+
+ul.list li {
+ border-bottom-color: #F0F0EE;
+ border-bottom-width: 1px;
+ border-bottom-style: solid;
+}
+
+ul.list li .item .avatar {
+ border-color: #F0F0EE;
+ border-width: 1px;
+ border-style: solid;
+ padding: 2px;
+}
+
+/* box */
+
+#box .block {
+ background: #FFF;
+}
+
+#box .block h2 {
+ background: #7A1818;
+ color: #FFF;
+}
+
+
+/* rounded borders */
+
+#main, #main-navigation, #main-navigation li, .secondary-navigation, #main .block, #sidebar .block, #sidebar h3, ul.list li,
+#footer .block, .form input.button, #box .block, #box .block h2 {
+ -moz-border-radius-topleft: 4px;
+ -webkit-border-top-left-radius: 4px;
+ -moz-border-radius-topright: 4px;
+ -webkit-border-top-right-radius: 4px;
+}
+
+.secondary-navigation li.first a, .secondary-navigation ul li.first, .table th.first, .table th.first {
+ -moz-border-radius-topleft: 4px;
+ -webkit-border-top-left-radius: 4px;
+}
+
+.table th.last {
+ -moz-border-radius-topright: 4px;
+ -webkit-border-top-right-radius: 4px;
+}
+
+.secondary-navigation ul li.first {
+ -moz-border-radius-topleft: 4px;
+ -webkit-border-top-left-radius: 4px;
+}
+
+#sidebar, #sidebar .block, #main .block, #sidebar ul.navigation, ul.list li, #footer .block, .form input.button, #box .block {
+ -moz-border-radius-bottomleft: 4px;
+ -webkit-border-bottom-left-radius: 4px;
+ -moz-border-radius-bottomright: 4px;
+ -webkit-border-bottom-right-radius: 4px;
+} \ No newline at end of file
diff --git a/chef-server-webui/public/stylesheets/themes/djime-cerulean/style.css b/chef-server-webui/public/stylesheets/themes/djime-cerulean/style.css
new file mode 100644
index 0000000000..9b050c6785
--- /dev/null
+++ b/chef-server-webui/public/stylesheets/themes/djime-cerulean/style.css
@@ -0,0 +1,298 @@
+/**
+ * Cerulean web-app-theme made for Djime: http://github.com/mikl/djime/
+ *
+ * Please note that we're using CSSEdit's @group comment syntax.
+ *
+ * Colour sheme:
+ * Cerulean: #007BA7
+ * Bright blue: #01B8DE
+ * Near-white: #F7F7F8
+ * Silver grey: #C2C8D1
+ * Dark blue: #001C26
+ *
+ * http://www.colourlovers.com/palette/646252/Cerulean_touch
+ */
+
+/* @group General styles */
+
+.small { font-size:11px; }
+.gray { color:#999; }
+.hightlight { background-color:#ffc; }
+
+a:link, a:visited, a:hover, a:active, h1, h2, h3 { color: #007BA7; }
+
+body {
+ color: #222;
+ background: #C2C8D1;
+ font-family: "Helvetica Neue",Helvetica,Arial,"Bitstream Vera Sans",sans-serif;
+}
+
+hr {
+ background: #EEF0F0;
+ color: #EEF0F0;
+}
+
+/* @end */
+
+/* @group Header */
+
+#header {
+ background: #007BA7;
+}
+
+#header h1 {
+ padding: 20px 0;
+}
+
+#header h1 a:link, #header h1 a:active, #header h1 a:hover, #header h1 a:visited {
+ color: #F7F7F8;
+}
+
+/* @end */
+
+#user-navigation {
+ top: auto;
+ bottom: 5px;
+ right: 25px;
+}
+
+#main .block .content {
+ background: #F7F7F8;
+ padding-top: 1px;
+}
+
+#main .block .content h2 {
+ margin-left: 15px;
+}
+
+
+
+/* @group Main navigation */
+
+#main-navigation ul li {
+ padding-left: 0;
+}
+
+#main-navigation ul li a {
+ padding: 8px 0;
+}
+
+#main-navigation ul li a {
+ padding: 8px 15px;
+}
+
+#main-navigation {
+ background-color: #005573;
+}
+
+#main-navigation ul li a:hover {
+ background-color: #001C26;
+}
+
+#main-navigation ul li.active a {
+ background-color: #C2C8D1;
+ background: -webkit-gradient(linear, left top, left bottom, from(#C2C8D1), to(#C2C8D1), color-stop(0.5, #F7F7F8), color-stop(0.5, #F7F7F8));
+
+}
+
+/* @end */
+
+/* @group Secondary navigation */
+
+.secondary-navigation li a:hover {
+ background: #005573;
+}
+
+.secondary-navigation {
+ background: #007BA7;
+ border-bottom-width: 7px;
+ border-bottom-color: #005573;
+}
+
+.secondary-navigation ul li.active, .secondary-navigation ul li.active a:hover {
+ background-color: #005573;
+}
+
+/* @end */
+
+/* @group Sidebar */
+
+#sidebar .block {
+ background: #F7F7F8;
+}
+
+#sidebar h3 {
+ background: #007BA7;
+ color: #F7F7F8;
+ border-bottom: 7px solid #005573;
+}
+
+#sidebar ul li a:link, #sidebar ul li a:visited {
+ background: #F7F7F8;
+ border-bottom: 1px solid #EEF0F0;
+ text-decoration: none;
+}
+
+#sidebar ul li a:hover, #sidebar ul li a:active {
+ background: #005573;
+ color: #F7F7F8;
+}
+
+/* @end */
+
+#main-navigation ul li a:link, #main-navigation ul li a:visited, #main-navigation ul li a:hover, #main-navigation ul li a:active,
+.secondary-navigation ul li a:link, .secondary-navigation ul li a:visited, .secondary-navigation ul li a:hover, .secondary-navigation ul li a:active,
+#user-navigation ul li a:link, #user-navigation ul li a:visited, #user-navigation ul li a:hover, #user-navigation ul li a:active {
+ text-decoration: none;
+ color: #F7F7F8;
+}
+
+#main-navigation ul li.active a:link, #main-navigation ul li.active a:visited, #main-navigation ul li.active a:hover, #main-navigation ul li.active a:active {
+ color: #001C26;
+}
+
+#footer .block {
+ color: #F7F7F8;
+ background: #005573;
+}
+
+#footer .block p {
+ margin: 0;
+ padding: 10px;
+}
+
+/* pagination */
+
+.pagination span.current {
+ background: #005573;
+ color: #F7F7F8;
+ border-color: #005573;
+}
+
+.pagination a,
+.pagination span {
+ color: #001C26;
+ border-color: #005573;
+}
+
+.pagination a:hover {
+ color: #F7F7F8;
+ background: #005573;
+}
+
+/* tables */
+
+.table th {
+ background: #C2C8D1;
+ color: #001C26;
+}
+
+.table td {
+ border-bottom:1px solid #EEF0F0;
+}
+
+/* forms */
+
+.form input.text_field, .form textarea.text_area {
+ width: 100%;
+ border: 1px solid #001C26;
+}
+
+.form input.button {
+ background: #EEE;
+ color: #001C26;
+ padding: 2px 5px;
+ border: 1px solid #001C26;
+ cursor: pointer;
+}
+
+.form .description {
+ color: #8C8C8C;
+ font-size: .9em;
+}
+
+/* @group Flash messages */
+
+.flash .message {
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ text-align:center;
+ margin: 0 auto 15px;
+}
+
+.flash .message p {
+ margin:8px;
+}
+.flash .error {
+ border: 1px solid #fbb;
+ background-color: #fdd;
+}
+.flash .warning {
+ border: 1px solid #e0d300;
+ background-color: #ffffcc;
+}
+.flash .notice {
+ border: 1px solid #8ec4df;
+ background-color: #dffaff;
+}
+
+/* @end */
+
+/* lists */
+
+ul.list li {
+ border-bottom-color: #EEF0F0;
+ border-bottom-width: 1px;
+ border-bottom-style: solid;
+}
+
+ul.list li .item .avatar {
+ border-color: #EEF0F0;
+ border-width: 1px;
+ border-style: solid;
+ padding: 2px;
+}
+
+/* box */
+
+#box .block {
+ background: #F7F7F8;
+}
+
+#box .block h2 {
+ background: #005573;
+ color: #F7F7F8;
+}
+
+
+/* rounded borders */
+
+#main, #main-navigation, #main-navigation li, #main-navigation li a, .secondary-navigation, #main .block, #sidebar .block, #sidebar h3, ul.list li,
+#footer .block, .form input.button, #box .block, #box .block h2 {
+ -moz-border-radius-topleft: 4px;
+ -webkit-border-top-left-radius: 4px;
+ -moz-border-radius-topright: 4px;
+ -webkit-border-top-right-radius: 4px;
+}
+
+.secondary-navigation li.first a, .secondary-navigation ul li.first, .table th.first, .table th.first {
+ -moz-border-radius-topleft: 4px;
+ -webkit-border-top-left-radius: 4px;
+}
+
+.table th.last {
+ -moz-border-radius-topright: 4px;
+ -webkit-border-top-right-radius: 4px;
+}
+
+.secondary-navigation ul li.first {
+ -moz-border-radius-topleft: 4px;
+ -webkit-border-top-left-radius: 4px;
+}
+
+#sidebar, #sidebar .block, #main .block, #sidebar ul.navigation, ul.list li, #footer .block, .form input.button, #box .block {
+ -moz-border-radius-bottomleft: 4px;
+ -webkit-border-bottom-left-radius: 4px;
+ -moz-border-radius-bottomright: 4px;
+ -webkit-border-bottom-right-radius: 4px;
+} \ No newline at end of file
diff --git a/chef-server-webui/public/stylesheets/themes/kathleene/style.css b/chef-server-webui/public/stylesheets/themes/kathleene/style.css
new file mode 100644
index 0000000000..e68a545431
--- /dev/null
+++ b/chef-server-webui/public/stylesheets/themes/kathleene/style.css
@@ -0,0 +1,272 @@
+.small { font-size:11px; }
+.gray { color:#999999; }
+.hightlight { background-color:#FFFFCC; }
+
+a:link, a:visited, a:hover, a:active, h1, h2, h3 { color: #AF0000; }
+a { -moz-outline: none; }
+
+body {
+ color: #222;
+ background: #f0f0ee;
+ font-family: helvetica, arial, sans-serif;
+}
+
+hr {
+ background: #f0f0ee;
+ color: #f0f0ee;
+}
+
+#header {
+ background: #AF0000;
+}
+
+#header h1 {
+ padding: 20px 0;
+}
+
+#header h1 a:link, #header h1 a:active, #header h1 a:hover, #header h1 a:visited {
+ color: #FFF;
+}
+
+#user-navigation {
+ top: auto;
+ bottom: 5px;
+ right: 25px;
+}
+
+#main .block .content {
+ background: #FFF;
+ padding-top: 1px;
+}
+
+#main .block .content h2 {
+ margin-left: 15px;
+}
+
+#sidebar .block {
+ background: #FFF;
+}
+
+#sidebar h3 {
+ background: #AF0000;
+ color: #FFF;
+ border-bottom: 5px solid #2a0000;
+}
+
+#main-navigation ul li {
+ padding-left: 15px;
+}
+
+#main-navigation ul li a {
+ padding: 8px 0;
+}
+
+#main-navigation ul li.active {
+ padding: 0;
+ margin-left: 15px;
+}
+
+#main-navigation ul li.active {
+ margin-left: 15px;
+}
+
+#main-navigation ul li.active a {
+ padding: 8px 15px;
+}
+
+#sidebar ul li a:link, #sidebar ul li a:visited {
+ background: #FFF;
+ border-bottom: 1px solid #F0F0EE;
+ text-decoration: none;
+}
+
+#sidebar ul li a:hover, #sidebar ul li a:active {
+ background: #470E0E;
+ color: #FFF;
+}
+
+#main-navigation {
+ background: #2a0000;
+}
+
+#main-navigation ul li {
+ background: #2a0000;
+ margin-right: 0;
+}
+
+#main-navigation ul li.active {
+ background: #f0f0ee;
+}
+
+#main-navigation ul li a:link, #main-navigation ul li a:visited, #main-navigation ul li a:hover, #main-navigation ul li a:active,
+.secondary-navigation ul li a:link, .secondary-navigation ul li a:visited, .secondary-navigation ul li a:hover, .secondary-navigation ul li a:active,
+#user-navigation ul li a:link, #user-navigation ul li a:visited, #user-navigation ul li a:hover, #user-navigation ul li a:active {
+ text-decoration: none;
+ color: #FFF;
+}
+
+.secondary-navigation li a:hover {
+ background: #470E0E;
+}
+
+#main-navigation ul li.active a:link, #main-navigation ul li.active a:visited, #main-navigation ul li.active a:hover, #main-navigation ul li.active a:active {
+ color: #2a0000;
+}
+
+.secondary-navigation {
+ background: #AF0000;
+ border-bottom-color: #2a0000;
+}
+
+.secondary-navigation ul li.active, .secondary-navigation ul li.active a:hover {
+ background-color: #2a0000;
+}
+
+#footer .block {
+ color: #FFF;
+ background: #2a0000;
+}
+
+#footer .block p {
+ margin: 0;
+ padding: 10px;
+}
+
+/* pagination */
+
+.pagination span.current {
+ background: #2a0000;
+ color: #FFF;
+ border-color: #2a0000;
+}
+
+.pagination a {
+ color: #2a0000;
+ border-color: #2a0000;
+}
+
+.pagination a:hover {
+ color: #FFF;
+ background: #2a0000;
+}
+
+/* tables */
+
+.table th {
+ background: #100000;
+ border-bottom: 3px solid #700000;
+ color: #FFF;
+}
+
+.table td {
+ border-bottom:1px solid #F0F0EE;
+}
+
+/* forms */
+
+.form input.text_field, .form textarea.text_area {
+ width: 100%;
+ border: 1px solid #2a0000;
+}
+
+.form input.button {
+ background: #EEE;
+ color: #2a0000;
+ padding: 2px 5px;
+ border: 1px solid #2a0000;
+ cursor: pointer;
+}
+
+.form .description {
+ font-style: italic;
+ color: #8C8C8C;
+ font-size: .9em;
+}
+
+/* flash-messages */
+.flash .message {
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ text-align:center;
+ margin: 0 auto 15px;
+
+}
+
+.flash .message p {
+ margin:8px;
+}
+.flash .error {
+ border: 1px solid #fbb;
+ background-color: #fdd;
+}
+.flash .warning {
+ border: 1px solid #fffaaa;
+ background-color: #ffffcc;
+}
+.flash .notice {
+ border: 1px solid #1FDF00;
+ background-color: #BBFFB6;
+}
+
+/* lists */
+
+ul.list li {
+ border-bottom-color: #F0F0EE;
+ border-bottom-width: 1px;
+ border-bottom-style: solid;
+}
+
+ul.list li .item .avatar {
+ border-color: #F0F0EE;
+ border-width: 1px;
+ border-style: solid;
+ padding: 2px;
+}
+
+/* box */
+
+#box .block {
+ background: #FFF;
+}
+
+#box .block h2 {
+ background: #AF0000;
+ color: #FFF;
+}
+
+
+/* rounded borders */
+
+#main, #main-navigation, #main-navigation li, .secondary-navigation, #main .block, #sidebar .block, #sidebar h3, ul.list li,
+#footer .block, .form input.button, #box .block, #box .block h2 {
+ -moz-border-radius-topleft: 4px;
+ -webkit-border-top-left-radius: 4px;
+ -moz-border-radius-topright: 4px;
+ -webkit-border-top-right-radius: 4px;
+}
+
+.secondary-navigation li.first a, .secondary-navigation ul li.first, .table th.first, .table th.first {
+ -moz-border-radius-topleft: 4px;
+ -webkit-border-top-left-radius: 4px;
+}
+
+.table th.last {
+ -moz-border-radius-topright: 4px;
+ -webkit-border-top-right-radius: 4px;
+}
+
+.secondary-navigation ul li.first {
+ -moz-border-radius-topleft: 4px;
+ -webkit-border-top-left-radius: 4px;
+}
+
+#sidebar, #sidebar .block, #main .block, #sidebar ul.navigation, ul.list li, #footer .block, .form input.button, #box .block {
+ -moz-border-radius-bottomleft: 4px;
+ -webkit-border-bottom-left-radius: 4px;
+ -moz-border-radius-bottomright: 4px;
+ -webkit-border-bottom-right-radius: 4px;
+}
+
+.secondary-navigation {
+ border-bottom-width: 5px;
+}
diff --git a/chef-server-webui/public/stylesheets/themes/orange/style.css b/chef-server-webui/public/stylesheets/themes/orange/style.css
new file mode 100644
index 0000000000..90e7d8de58
--- /dev/null
+++ b/chef-server-webui/public/stylesheets/themes/orange/style.css
@@ -0,0 +1,263 @@
+.small { font-size:11px; }
+.gray { color:#999999; }
+.hightlight { background-color:#FFFFCC; }
+
+a:link, a:visited, a:hover, a:active, h1, h2, h3 { color: #ff7900; }
+a { -moz-outline: none; }
+
+body {
+ color: #222;
+ background: #f0f0ee;
+ font-family: helvetica, arial, sans-serif;
+}
+
+hr {
+ background: #f0f0ee;
+ color: #f0f0ee;
+}
+
+#header {
+ background: #ff7900;
+}
+
+#header h1 a:link, #header h1 a:active, #header h1 a:hover, #header h1 a:visited {
+ color: #FFF;
+}
+
+#user-navigation {
+ top: auto;
+ bottom: 5px;
+ right: 25px;
+}
+
+#main .block .content {
+ background: #FFF;
+ padding-top: 1px;
+}
+
+#main .block .content h2 {
+ margin-left: 15px;
+}
+
+#sidebar .block {
+ background: #FFF;
+}
+
+#sidebar h3 {
+ background: #ff7900;
+ color: #FFF;
+ border-bottom: 10px solid #262626;
+}
+
+#main-navigation ul li {
+ padding-left: 15px;
+}
+
+#main-navigation ul li a {
+ padding: 8px 0;
+}
+
+#main-navigation ul li.active {
+ padding: 0;
+ margin-left: 15px;
+}
+
+#main-navigation ul li.active {
+ margin-left: 15px;
+}
+
+#main-navigation ul li.active a {
+ padding: 8px 15px;
+}
+
+#sidebar ul li a:link, #sidebar ul li a:visited {
+ background: #FFF;
+ border-bottom: 1px solid #F0F0EE;
+ text-decoration: none;
+}
+
+#sidebar ul li a:hover, #sidebar ul li a:active {
+ background: #863800;
+ color: #FFF;
+}
+
+#main-navigation {
+ background: #262626;
+}
+
+#main-navigation ul li {
+ background: #262626;
+ margin-right: 0;
+}
+
+#main-navigation ul li.active {
+ background: #f0f0ee;
+}
+
+#main-navigation ul li a:link, #main-navigation ul li a:visited, #main-navigation ul li a:hover, #main-navigation ul li a:active,
+.secondary-navigation ul li a:link, .secondary-navigation ul li a:visited, .secondary-navigation ul li a:hover, .secondary-navigation ul li a:active,
+#user-navigation ul li a:link, #user-navigation ul li a:visited, #user-navigation ul li a:hover, #user-navigation ul li a:active {
+ text-decoration: none;
+ color: #FFF;
+}
+
+.secondary-navigation li a:hover {
+ background: #863800;
+}
+
+#main-navigation ul li.active a:link, #main-navigation ul li.active a:visited, #main-navigation ul li.active a:hover, #main-navigation ul li.active a:active {
+ color: #262626;
+}
+
+.secondary-navigation {
+ background: #ff7900;
+ border-bottom-color: #262626;
+}
+
+.secondary-navigation ul li.active, .secondary-navigation ul li.active a:hover {
+ background-color: #262626;
+}
+
+#footer .block {
+ color: #FFF;
+ background: #262626;
+ width: 70%;
+}
+
+#footer .block p {
+ margin: 0;
+ padding: 10px;
+}
+
+/* pagination */
+
+.pagination span.current {
+ background: #262626;
+ color: #FFF;
+ border-color: #262626;
+}
+
+.pagination a {
+ color: #262626;
+ border-color: #262626;
+}
+
+.pagination a:hover {
+ color: #FFF;
+ background: #262626;
+}
+
+/* tables */
+
+.table th {
+ background: #262626;
+ color: #FFF;
+}
+
+.table td {
+ border-bottom:1px solid #F0F0EE;
+}
+
+/* forms */
+
+.form input.text, .form textarea {
+ width: 100%;
+ border: 1px solid #262626;
+}
+
+.form input.button {
+ background: #EEE;
+ color: #262626;
+ padding: 2px 5px;
+ border: 1px solid #262626;
+ cursor: pointer;
+}
+
+.form .description {
+ font-style: italic;
+ color: #8C8C8C;
+ font-size: .9em;
+}
+
+/* flash-messages */
+.flash .message {
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ text-align:center;
+ margin:0 auto 5px;
+
+}
+.flash .message p {
+ margin:8px;
+}
+.flash .error {
+ border: 1px solid #fbb;
+ background-color: #fdd;
+}
+.flash .warning {
+ border: 1px solid #fffaaa;
+ background-color: #ffffcc;
+}
+.flash .notice {
+ border: 1px solid #1FDF00;
+ background-color: #BBFFB6;
+}
+
+/* lists */
+
+ul.list li {
+ border-bottom-color: #F0F0EE;
+ border-bottom-width: 1px;
+ border-bottom-style: solid;
+}
+
+ul.list li .item .avatar {
+ border-color: #F0F0EE;
+ border-width: 1px;
+ border-style: solid;
+ padding: 2px;
+}
+
+/* box */
+
+#box .block {
+ background: #FFF;
+}
+
+#box .block h2 {
+ background: #ff7900;
+ color: #FFF;
+}
+
+
+/* rounded borders */
+
+#main, #main-navigation, #main-navigation li, .secondary-navigation, #main .block, #sidebar .block, #sidebar h3, ul.list li,
+#footer .block, .form input.button, #box .block, #box .block h2 {
+ -moz-border-radius-topleft: 4px;
+ -webkit-border-top-left-radius: 4px;
+ -moz-border-radius-topright: 4px;
+ -webkit-border-top-right-radius: 4px;
+}
+
+.secondary-navigation li.first a, .secondary-navigation ul li.first, .table th.first, .table th.first {
+ -moz-border-radius-topleft: 4px;
+ -webkit-border-top-left-radius: 4px;
+}
+
+.table th.last {
+ -moz-border-radius-topright: 4px;
+ -webkit-border-top-right-radius: 4px;
+}
+
+.secondary-navigation ul li.first {
+ -moz-border-radius-topleft: 4px;
+ -webkit-border-top-left-radius: 4px;
+}
+
+#sidebar, #sidebar .block, #main .block, #sidebar ul.navigation, ul.list li, #footer .block, .form input.button, #box .block {
+ -moz-border-radius-bottomleft: 4px;
+ -webkit-border-bottom-left-radius: 4px;
+ -moz-border-radius-bottomright: 4px;
+ -webkit-border-bottom-right-radius: 4px;
+} \ No newline at end of file
diff --git a/chef-server-webui/public/stylesheets/themes/reidb-greenish/style.css b/chef-server-webui/public/stylesheets/themes/reidb-greenish/style.css
new file mode 100644
index 0000000000..23e5245eb4
--- /dev/null
+++ b/chef-server-webui/public/stylesheets/themes/reidb-greenish/style.css
@@ -0,0 +1,301 @@
+.small { font-size:11px; }
+.gray { color:#a2b0b6; }
+.hightlight { background-color:#d6e7c7; }
+
+a:link, a:visited, a:hover, a:active { color: #81B953; }
+h1, h2, h3 { color: #3B5526; }
+a { -moz-outline: none; }
+
+body {
+ color: #222;
+ background: #e4ebe4;
+ font-family: helvetica, arial, sans-serif;
+}
+
+hr {
+ background: #f0f0ee;
+ color: #f0f0ee;
+}
+
+#header {
+ background: #c9deb7;
+
+}
+
+#header h1{
+ padding: 20px 0;
+ font-weight: bold;
+
+}
+
+#header h1 a:link, #header h1 a:active,
+#header h1 a:hover, #header h1 a:visited {
+ color: #3B5526;
+}
+
+#main .block .content {
+ background: #FFF;
+ padding-top: 1px;
+}
+
+#main .block .content h2 {
+ margin-left: 15px;
+}
+
+#main .content { border: 1px solid #81B953;}
+
+#sidebar .block {
+ background: #FFF;
+ border: none;
+}
+
+#sidebar h3 {
+ padding: 8px 12px;
+ background: #3B5526;
+ color: #FFF;
+ font-weight: bold;
+ border-bottom: 5px solid #81B953;
+}
+
+
+#sidebar ul li a:link,
+#sidebar ul li a:visited {
+ background: #FFF;
+ border-bottom: 1px solid #F0F0EE;
+ text-decoration: none;
+}
+
+#sidebar ul li a:hover,
+#sidebar ul li a:active {
+ background: #D3E8C1;
+ color: #FFF;
+}
+
+#main-navigation {
+ background: #44721e;
+ margin-right: 20px;
+ padding: 7px 7px 0 7px;
+}
+
+#main-navigation ul li {
+ background: #91B96F;
+ margin-right: 7px;
+}
+
+#main-navigation ul li a {
+ padding: 10px 10px 5px 10px;
+}
+
+#main-navigation ul li.active {
+ background: #e4ebe4;
+ border: 1px solid #91B96F;
+ border-bottom: none;
+ font-weight: bold;
+}
+
+#main-navigation ul li a:hover,
+#main-navigation ul li a:link,
+#main-navigation ul li a:visited,
+#main-navigation ul li a:active,
+.secondary-navigation ul li a:link, .secondary-navigation ul li a:visited,
+.secondary-navigation ul li a:hover, .secondary-navigation ul li a:active {
+ text-decoration: none;
+ color: #FFF;
+}
+
+#user-navigation ul li a:link,
+#user-navigation ul li a:visited,
+#user-navigation ul li a:active {
+ color: #3B5526;
+}
+
+#user-navigation ul li a:hover { color: #fff; }
+
+#main-navigation ul li.active a:link,
+#main-navigation ul li.active a:visited,
+#main-navigation ul li.active a:hover,
+#main-navigation ul li.active a:active {
+ color: #262626;
+}
+
+.secondary-navigation {
+ background: #3B5526;
+ border-bottom: 5px solid #81b953;
+}
+
+.secondary-navigation ul li a {
+display:block;
+padding: 8px 12px;
+}
+
+.secondary-navigation ul li.active {background: #81b953; font-weight: bold;}
+.secondary-navigation ul li.active a:hover {
+ background-color: #81B953;
+}
+
+.secondary-navigation li a:hover {
+ background: #81B953;
+}
+
+#footer .block {
+ color: #FFF;
+ background: #3B5526;
+}
+
+#footer .block p {
+ margin: 0;
+ padding: 5px;
+}
+
+/* pagination */
+
+.pagination span.current {
+ background: #262626;
+ color: #FFF;
+ border-color: #262626;
+}
+
+.pagination a {
+ color: #262626;
+ border-color: #262626;
+}
+
+.pagination a:hover {
+ color: #FFF;
+ background: #262626;
+}
+
+/* tables */
+
+
+.table th {
+ background: #253618;
+ color: #FFF;
+}
+
+.table tr th { padding: 5px; }
+
+.table td {
+ border-bottom:1px solid #F0F0EE;
+}
+.table tr.odd {background: #ebfadf;}
+.table tr.even {background: #d3e8c1;}
+
+
+/* forms */
+
+.form input.text_field, .form textarea.text_area {
+ width: 100%;
+ border: 1px solid #262626;
+}
+
+.form input.button {
+ background: #EEE;
+ color: #262626;
+ padding: 2px 5px;
+ border: 1px solid #262626;
+ cursor: pointer;
+}
+
+.form .description {
+ font-style: italic;
+ color: #8C8C8C;
+ font-size: .9em;
+}
+
+/* flash-messages */
+.flash .message {
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ text-align:center;
+ margin: 0 auto 15px;
+
+}
+
+.flash .message p {
+ margin:8px;
+}
+.flash .error {
+ border: 1px solid #fbb;
+ background-color: #fdd;
+}
+.flash .warning {
+ border: 1px solid #fffaaa;
+ background-color: #ffffcc;
+}
+.flash .notice {
+ border: 1px solid #1FDF00;
+ background-color: #BBFFB6;
+}
+
+/* lists */
+
+ul.list li {
+ border-bottom-color: #F0F0EE;
+ border-bottom-width: 1px;
+ border-bottom-style: solid;
+}
+
+ul.list li .item .avatar {
+ border-color: #F0F0EE;
+ border-width: 1px;
+ border-style: solid;
+ padding: 2px;
+}
+
+/* box */
+
+#box .block {
+ background: #FFF;
+}
+
+#box .block h2 {
+ color: #fff;
+ background: #3B5526;
+ border-bottom: 5px solid #81b953;
+}
+
+#box .block .content { border: 1px solid #81b953; border}
+
+/* login */
+
+#block-login { }
+#block-login h2 { background: #3B5526;border-bottom: 5px solid #81b953;}
+
+/* rounded borders */
+
+#main, #main-navigation, #main-navigation li,
+.secondary-navigation, #main .block, #sidebar .block,
+#sidebar h3, ul.list li, #footer .block,
+.form input.button, #box .block, #box .block h2 {
+ -moz-border-radius-topleft: 4px;
+ -webkit-border-top-left-radius: 4px;
+ -moz-border-radius-topright: 4px;
+ -webkit-border-top-right-radius: 4px;
+}
+
+.secondary-navigation li.first a,
+.secondary-navigation ul li.first,
+.table th.first, .table th.first {
+ -moz-border-radius-topleft: 4px;
+ -webkit-border-top-left-radius: 4px;
+}
+
+.table th.last {
+ -moz-border-radius-topright: 4px;
+ -webkit-border-top-right-radius: 4px;
+}
+
+.secondary-navigation ul li.first {
+ -moz-border-radius-topleft: 4px;
+ -webkit-border-top-left-radius: 4px;
+}
+
+#sidebar, #sidebar .block, #main .block,
+#sidebar ul.navigation, ul.list li,
+#footer .block, .form input.button, #box .block {
+ -moz-border-radius-bottomleft: 4px;
+ -webkit-border-bottom-left-radius: 4px;
+ -moz-border-radius-bottomright: 4px;
+ -webkit-border-bottom-right-radius: 4px;
+} \ No newline at end of file
diff --git a/chef-server-webui/stubs/app/controllers/application.rb b/chef-server-webui/stubs/app/controllers/application.rb
new file mode 100644
index 0000000000..0b2f8726fd
--- /dev/null
+++ b/chef-server-webui/stubs/app/controllers/application.rb
@@ -0,0 +1,2 @@
+class ChefServerWebui::Application < Merb::Controller
+end
diff --git a/chef-server-webui/stubs/app/controllers/main.rb b/chef-server-webui/stubs/app/controllers/main.rb
new file mode 100644
index 0000000000..fdb090e693
--- /dev/null
+++ b/chef-server-webui/stubs/app/controllers/main.rb
@@ -0,0 +1,2 @@
+class ChefServerWebui::Main < ChefServerWebui::Application
+end
diff --git a/chef-server/Rakefile b/chef-server/Rakefile
index d60352e421..c686268118 100644
--- a/chef-server/Rakefile
+++ b/chef-server/Rakefile
@@ -18,7 +18,7 @@ require 'chef' unless defined?(Chef)
include FileUtils
GEM = "chef-server"
-CHEF_SERVER_VERSION = "0.7.9"
+CHEF_SERVER_VERSION = "0.8.0"
AUTHOR = "Opscode"
EMAIL = "chef@opscode.com"
HOMEPAGE = "http://wiki.opscode.com/display/chef"
diff --git a/chef-server/bin/chef-server b/chef-server/bin/chef-server
index 70595c07ad..cd4be70a33 100755
--- a/chef-server/bin/chef-server
+++ b/chef-server/bin/chef-server
@@ -26,7 +26,8 @@
require "rubygems"
require "merb-core"
-[ 'chef', 'chef-server-slice' ].each do |lib|
+[ 'chef', 'chef-server-api' ].each do |lib|
+ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "lib")))
library = File.join(File.dirname(__FILE__), "..", "..", lib, "lib", "#{lib}.rb")
require library if File.exists?(library)
end
@@ -50,7 +51,7 @@ if ARGV[0] && ARGV[0] =~ /^[^-]/
ARGV.push "-H"
end
unless %w[-a --adapter -i --irb-console -r --script-runner].any? { |o| ARGV.index(o) }
- ARGV.push *%w[-a mongrel]
+ ARGV.push *%w[-a thin]
end
ARGV.push *[ "-I", File.join(__DIR__, "config", "init.rb") ]
ARGV.push *[ "-m", __DIR__]
diff --git a/chef-server/config/dependencies.rb b/chef-server/config/dependencies.rb
index bb15302631..37c02f1e54 100644
--- a/chef-server/config/dependencies.rb
+++ b/chef-server/config/dependencies.rb
@@ -1,13 +1,24 @@
# dependencies are generated using a strict version, don't forget to edit the dependency versions when upgrading.
merb_gems_version = "> 1.0"
+%w{chef chef-server-api chef-solr}.each do |dep|
+ $: << File.join(File.dirname(__FILE__), "..", "..", dep, "lib")
+end
+
+begin
+ require 'chef'
+ require 'chef-server-api'
+rescue
+end
+
# For more information about each component, please read http://wiki.merbivore.com/faqs/merb_components
dependency "merb-core", merb_gems_version
dependency "merb-assets", merb_gems_version
dependency "merb-helpers", merb_gems_version
dependency "merb-slices", merb_gems_version
if defined?(CHEF_SERVER_VERSION)
- dependency "chef-server-slice", CHEF_SERVER_VERSION unless defined?(ChefServerSlice)
+ dependency "chef-server-api", CHEF_SERVER_VERSION unless defined?(ChefServerApi)
else
- dependency "chef-server-slice" unless defined?(ChefServerSlice)
+ dependency "chef-server-api" unless defined?(ChefServerSlice)
end
+
diff --git a/chef-server/config/init.rb b/chef-server/config/init.rb
index 4af56589d0..b3ba2bf418 100644
--- a/chef-server/config/init.rb
+++ b/chef-server/config/init.rb
@@ -1,5 +1,5 @@
# Go to http://wiki.merbivore.com/pages/init-rb
-
+
require 'config/dependencies.rb'
unless defined?(Chef)
gem "chef", "=" + CHEF_SERVER_VERSION if CHEF_SERVER_VERSION
@@ -17,7 +17,9 @@ Merb::Config.use do |c|
c[:exception_details] = true
c[:reload_classes] = false
c[:log_level] = Chef::Config[:log_level]
- c[:log_stream] = Chef::Config[:log_location]
+ if Chef::Config[:log_location].kind_of?(String)
+ c[:log_file] = Chef::Config[:log_location]
+ end
end
Merb::BootLoader.before_app_loads do
@@ -25,6 +27,6 @@ Merb::BootLoader.before_app_loads do
end
Merb::BootLoader.after_app_loads do
- # This will get executed after your app's classes have been loaded.
- OpenID::Util.logger = Merb.logger
+ # This will get executed after your app's classes have been loaded. OpenID::Util.logger = Merb.logger
end
+
diff --git a/chef-server/config/rack.rb b/chef-server/config/rack.rb
index e6bde4d226..4ed0f70b55 100644
--- a/chef-server/config/rack.rb
+++ b/chef-server/config/rack.rb
@@ -1,3 +1,5 @@
+$: << File.join(File.dirname(__FILE__))
+
# use PathPrefix Middleware if :path_prefix is set in Merb::Config
if prefix = ::Merb::Config[:path_prefix]
use Merb::Rack::PathPrefix, prefix
diff --git a/chef-server/config/router.rb b/chef-server/config/router.rb
index 9123858cfa..cefef0c79a 100644
--- a/chef-server/config/router.rb
+++ b/chef-server/config/router.rb
@@ -29,16 +29,11 @@ Merb.logger.info("Compiling routes...")
Merb::Router.prepare do
# RESTful routes
# resources :posts
-
+
# Adds the required routes for merb-auth using the password slice
# slice(:merb_auth_slice_password, :name_prefix => nil, :path_prefix => "")
- slice(:chef_server_slice)
- # This is the default route for /:controller/:action/:id
- # This is fine for most cases. If you're heavily using resource-based
- # routes, you may want to comment/remove this line to prevent
- # clients from calling your create or destroy actions with a GET
- default_routes
-
- # Change this for your home page to be available at /
- # match('/').to(:controller => 'whatever', :action =>'index')
+
+ slice(:chef_server_api)
+ # slice(:chef_server_webui, :path_prefix => "html")
+
end
diff --git a/chef-solr/.document b/chef-solr/.document
new file mode 100644
index 0000000000..ecf3673194
--- /dev/null
+++ b/chef-solr/.document
@@ -0,0 +1,5 @@
+README.rdoc
+lib/**/*.rb
+bin/*
+features/**/*.feature
+LICENSE
diff --git a/chef-solr/.gitignore b/chef-solr/.gitignore
new file mode 100644
index 0000000000..0ae00dc051
--- /dev/null
+++ b/chef-solr/.gitignore
@@ -0,0 +1,9 @@
+*.sw?
+.DS_Store
+coverage
+rdoc
+pkg
+solr/work
+solr/solr/data/*
+solr/logs
+solr/clustering
diff --git a/chef-solr/README.rdoc b/chef-solr/README.rdoc
new file mode 100644
index 0000000000..722ffd1c00
--- /dev/null
+++ b/chef-solr/README.rdoc
@@ -0,0 +1,7 @@
+= chef-solr
+
+Description goes here.
+
+== Copyright
+
+Copyright (c) 2009 Adam Jacob. See LICENSE for details.
diff --git a/chef-solr/Rakefile b/chef-solr/Rakefile
new file mode 100644
index 0000000000..18d78d2bc6
--- /dev/null
+++ b/chef-solr/Rakefile
@@ -0,0 +1,57 @@
+require 'rubygems'
+require 'rake'
+
+begin
+ require 'jeweler'
+ Jeweler::Tasks.new do |gem|
+ gem.name = "chef-solr"
+ gem.summary = %Q{Search indexing for Chef}
+ gem.email = "adam@opscode.com"
+ gem.homepage = "http://github.com/adamhjk/chef-solr"
+ gem.authors = ["Adam Jacob"]
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
+ end
+
+rescue LoadError
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
+end
+
+require 'spec/rake/spectask'
+Spec::Rake::SpecTask.new(:spec) do |spec|
+ spec.libs << 'lib' << 'spec'
+ spec.spec_files = FileList['spec/**/*_spec.rb']
+ spec.spec_opts = %w{-fs --color}
+end
+
+Spec::Rake::SpecTask.new(:rcov) do |spec|
+ spec.libs << 'lib' << 'spec'
+ spec.pattern = 'spec/**/*_spec.rb'
+ spec.rcov = true
+end
+
+begin
+ require 'cucumber/rake/task'
+ Cucumber::Rake::Task.new(:features)
+rescue LoadError
+ task :features do
+ abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
+ end
+end
+
+task :default => :spec
+
+require 'rake/rdoctask'
+Rake::RDocTask.new do |rdoc|
+ if File.exist?('VERSION.yml')
+ config = YAML.load(File.read('VERSION.yml'))
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
+ else
+ version = ""
+ end
+
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = "chef-solr #{version}"
+ rdoc.rdoc_files.include('README*')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
+
diff --git a/chef-solr/VERSION b/chef-solr/VERSION
new file mode 100644
index 0000000000..a3df0a6959
--- /dev/null
+++ b/chef-solr/VERSION
@@ -0,0 +1 @@
+0.8.0
diff --git a/chef-solr/bin/chef-solr b/chef-solr/bin/chef-solr
new file mode 100755
index 0000000000..b46d351747
--- /dev/null
+++ b/chef-solr/bin/chef-solr
@@ -0,0 +1,27 @@
+#!/usr/bin/ruby
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2009 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
+$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "chef", "lib")))
+
+require 'rubygems'
+require 'chef/solr/application/solr'
+
+Chef::Solr::Application::Solr.new.run
+
diff --git a/chef-solr/bin/chef-solr-indexer b/chef-solr/bin/chef-solr-indexer
new file mode 100755
index 0000000000..6d32b2cbdf
--- /dev/null
+++ b/chef-solr/bin/chef-solr-indexer
@@ -0,0 +1,27 @@
+#!/usr/bin/ruby
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2009 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
+$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "chef", "lib")))
+
+require 'rubygems'
+require 'chef/solr/application/indexer'
+
+Chef::Solr::Application::Indexer.new.run
+
diff --git a/chef-solr/bin/chef-solr-rebuild b/chef-solr/bin/chef-solr-rebuild
new file mode 100755
index 0000000000..78f6eedaca
--- /dev/null
+++ b/chef-solr/bin/chef-solr-rebuild
@@ -0,0 +1,27 @@
+#!/usr/bin/ruby
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2009 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
+$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "chef", "lib")))
+
+require 'rubygems'
+require 'chef/solr/application/rebuild'
+
+Chef::Solr::Application::Rebuild.new.run
+
diff --git a/chef-solr/chef-solr.gemspec b/chef-solr/chef-solr.gemspec
new file mode 100644
index 0000000000..a6d307fdd7
--- /dev/null
+++ b/chef-solr/chef-solr.gemspec
@@ -0,0 +1,37 @@
+# -*- encoding: utf-8 -*-
+
+Gem::Specification.new do |s|
+ s.name = %q{chef-solr}
+ s.version = "0.8.0"
+
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
+ s.authors = ["Adam Jacob"]
+ s.date = %q{2009-07-19}
+ s.email = %q{adam@opscode.com}
+ s.executables = ["chef-solr", "chef-solr-indexer", "chef-solr-rebuild"]
+ s.extra_rdoc_files = [
+ "README.rdoc"
+ ]
+ s.has_rdoc = true
+ s.homepage = %q{http://github.com/adamhjk/chef-solr}
+ s.rdoc_options = ["--charset=UTF-8"]
+ s.require_paths = ["lib"]
+ s.rubygems_version = %q{1.3.1}
+ s.summary = %q{Search indexing for Chef}
+ s.test_files = [
+ "spec/chef/solr/index_spec.rb",
+ "spec/chef/solr/query_spec.rb",
+ "spec/chef/solr_spec.rb",
+ "spec/spec_helper.rb"
+ ]
+
+ if s.respond_to? :specification_version then
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
+ s.specification_version = 2
+
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
+ else
+ end
+ else
+ end
+end
diff --git a/chef-solr/lib/chef/solr.rb b/chef-solr/lib/chef/solr.rb
new file mode 100644
index 0000000000..abc2106231
--- /dev/null
+++ b/chef-solr/lib/chef/solr.rb
@@ -0,0 +1,181 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2009 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require 'rubygems'
+require 'chef/log'
+require 'chef/config'
+require 'chef/couchdb'
+require 'chef/role'
+require 'chef/node'
+require 'chef/data_bag'
+require 'chef/data_bag_item'
+require 'net/http'
+require 'libxml'
+require 'uri'
+
+class Chef
+ class Solr
+
+ attr_accessor :solr_url, :http
+
+ def initialize(solr_url=Chef::Config[:solr_url])
+ @solr_url = solr_url
+ uri = URI.parse(@solr_url)
+ @http = Net::HTTP.new(uri.host, uri.port)
+ end
+
+ def solr_select(database, type, options={})
+ options[:wt] = :ruby
+ options[:indent] = "off"
+ if type.kind_of?(Array)
+ options[:fq] = "+X_CHEF_database_CHEF_X:#{database} +X_CHEF_type_CHEF_X:#{type[0]} +data_bag:#{type[1]}"
+ else
+ options[:fq] = "+X_CHEF_database_CHEF_X:#{database} +X_CHEF_type_CHEF_X:#{type}"
+ end
+ select_url = "/solr/select?#{to_params(options)}"
+ Chef::Log.debug("Sending #{select_url} to Solr")
+ req = Net::HTTP::Get.new(select_url)
+ res = @http.request(req)
+ unless res.kind_of?(Net::HTTPSuccess)
+ res.error!
+ end
+ eval(res.body)
+ end
+
+ def post_to_solr(doc)
+ req = Net::HTTP::Post.new("/solr/update", "Content-Type" => "text/xml")
+ req.body = doc.to_s
+ res = @http.request(req)
+ unless res.kind_of?(Net::HTTPSuccess)
+ res.error!
+ end
+ res
+ end
+
+ def solr_add(data)
+ data = [data] unless data.is_a?(Array)
+
+ xml_document = LibXML::XML::Document.new
+ xml_add = LibXML::XML::Node.new("add")
+ data.each do |doc|
+ xml_doc = LibXML::XML::Node.new("doc")
+ doc.each do |field, values|
+ values = [values] unless values.kind_of?(Array)
+ values.each do |v|
+ xml_field = LibXML::XML::Node.new("field")
+ xml_field["name"] = field
+ xml_field.content = v.to_s
+ xml_doc << xml_field
+ end
+ end
+ xml_add << xml_doc
+ end
+ xml_document.root = xml_add
+ post_to_solr(xml_document.to_s(:indent => false))
+ end
+
+ def solr_commit(opts={})
+ post_to_solr(generate_single_element("commit", opts))
+ end
+
+ def solr_optimize(opts={})
+ post_to_solr(generate_single_element("optimize", opts))
+ end
+
+ def solr_rollback
+ post_to_solr(generate_single_element("rollback"))
+ end
+
+ def solr_delete_by_id(ids)
+ post_to_solr(generate_delete_document("id", ids))
+ end
+
+ def solr_delete_by_query(queries)
+ post_to_solr(generate_delete_document("query", queries))
+ end
+
+ def rebuild_index(url=Chef::Config[:couchdb_url], db=Chef::Config[:couchdb_database])
+ solr_delete_by_query("X_CHEF_database_CHEF_X:#{db}")
+ couchdb = Chef::CouchDB.new(url, db)
+ Chef::Node.list(true, couchdb).each { |i| i.save }
+ Chef::Role.list(true, couchdb).each { |i| i.save }
+ Chef::DataBag.list(true, couchdb).each { |i| i.save; i.list(true).each { |x| x.save } }
+ true
+ end
+
+ private
+
+ def generate_single_element(elem, opts={})
+ xml_document = LibXML::XML::Document.new
+ xml_elem = LibXML::XML::Node.new(elem)
+ opts.each { |k,v| xml_elem[k.to_s] = v.to_s }
+ xml_document.root = xml_elem
+ xml_document.to_s(:indent => false)
+ end
+
+ def generate_delete_document(type, list)
+ list = [list] unless list.is_a?(Array)
+ xml_document = LibXML::XML::Document.new
+ xml_delete = LibXML::XML::Node.new("delete")
+ xml_document.root = xml_delete
+ list.each do |id|
+ xml_id = LibXML::XML::Node.new(type)
+ xml_id.content = id.to_s
+ xml_delete << xml_id
+ end
+ xml_document.to_s(:indent => false)
+ end
+
+ # Thanks to Merb!
+ def to_params(params_hash)
+ params = ''
+ stack = []
+
+ params_hash.each do |k, v|
+ if v.is_a?(Hash)
+ stack << [k,v]
+ else
+ params << "#{k}=#{escape(v)}&"
+ end
+ end
+
+ stack.each do |parent, hash|
+ hash.each do |k, v|
+ if v.is_a?(Hash)
+ stack << ["#{parent}[#{k}]", escape(v)]
+ else
+ params << "#{parent}[#{k}]=#{escape(v)}&"
+ end
+ end
+ end
+
+ params.chop! # trailing &
+ params
+ end
+
+ # escapes a query key/value for http
+ # Thanks to RSolr!
+ def escape(s)
+ s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
+ '%'+$1.unpack('H2'*$1.size).join('%').upcase
+ }.tr(' ', '+')
+ end
+
+ end
+end
+
diff --git a/chef-solr/lib/chef/solr/application/indexer.rb b/chef-solr/lib/chef/solr/application/indexer.rb
new file mode 100644
index 0000000000..e41d9daa22
--- /dev/null
+++ b/chef-solr/lib/chef/solr/application/indexer.rb
@@ -0,0 +1,140 @@
+#
+# Author:: AJ Christensen (<aj@opscode.com)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+
+require 'chef'
+require 'chef/log'
+require 'chef/config'
+require 'chef/application'
+require 'chef/solr'
+require 'chef/solr/index'
+require 'chef/solr/index_actor'
+require 'chef/daemon'
+require 'chef/nanite'
+require 'nanite'
+require 'eventmachine'
+
+class Chef
+ class Solr
+ class Application
+ class Indexer < Chef::Application
+
+ option :config_file,
+ :short => "-c CONFIG",
+ :long => "--config CONFIG",
+ :default => "/etc/chef/solr.rb",
+ :description => "The configuration file to use"
+
+ option :log_level,
+ :short => "-l LEVEL",
+ :long => "--log_level LEVEL",
+ :description => "Set the log level (debug, info, warn, error, fatal)",
+ :proc => lambda { |l| l.to_sym }
+
+ option :log_location,
+ :short => "-L LOGLOCATION",
+ :long => "--logfile LOGLOCATION",
+ :description => "Set the log file location, defaults to STDOUT - recommended for daemonizing",
+ :proc => nil
+
+ option :help,
+ :short => "-h",
+ :long => "--help",
+ :description => "Show this message",
+ :on => :tail,
+ :boolean => true,
+ :show_options => true,
+ :exit => 0
+
+ option :user,
+ :short => "-u USER",
+ :long => "--user USER",
+ :description => "User to set privilege to",
+ :proc => nil
+
+ option :group,
+ :short => "-g GROUP",
+ :long => "--group GROUP",
+ :description => "Group to set privilege to",
+ :proc => nil
+
+ option :daemonize,
+ :short => "-d",
+ :long => "--daemonize",
+ :description => "Daemonize the process",
+ :proc => lambda { |p| true }
+
+ option :nanite_identity,
+ :long => "--nanite-identity ID",
+ :description => "The nanite identity"
+
+ option :nanite_host,
+ :long => "--nanite-host HOST",
+ :description => "The nanite host"
+
+ option :nanite_port,
+ :long => "--nanite-port PORT",
+ :description => "The nanite port"
+
+ option :nanite_user,
+ :long => "--nanite-user USER",
+ :description => "The nanite user"
+
+ option :nanite_pass,
+ :long => "--nanite-pass PASS",
+ :description => "The nanite password"
+
+ option :nanite_vhost,
+ :long => "--nanite-vhost VHOST",
+ :description => "The nanite vhost"
+
+ def initialize
+ super
+
+ @index = Chef::Solr::Index.new
+ ::Nanite::Log.logger = Chef::Log.logger
+ end
+
+ def setup_application
+ Chef::Daemon.change_privilege
+ identity = Chef::Config[:nanite_identity] ? Chef::Config[:nanite_identity] : Chef::Nanite.get_identity("solr-indexer")
+ @nanite_config = {
+ :host => Chef::Config[:nanite_host],
+ :port => 5672,
+ :user => Chef::Config[:nanite_user],
+ :pass => Chef::Config[:nanite_pass],
+ :vhost => Chef::Config[:nanite_vhost],
+ :identity => identity,
+ :format => :json
+ }
+ Chef::Log.level(Chef::Config[:log_level])
+ end
+
+ def run_application
+ if Chef::Config[:daemonize]
+ Chef::Daemon.daemonize("chef-solr-indexer")
+ end
+
+ EM.run do
+ agent = ::Nanite::Agent.start(@nanite_config)
+ agent.register(Chef::Solr::IndexActor.new, 'index')
+ agent.send :advertise_services
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/chef-solr/lib/chef/solr/application/rebuild.rb b/chef-solr/lib/chef/solr/application/rebuild.rb
new file mode 100644
index 0000000000..0c33dc3a92
--- /dev/null
+++ b/chef-solr/lib/chef/solr/application/rebuild.rb
@@ -0,0 +1,120 @@
+#
+# Author:: AJ Christensen (<aj@opscode.com)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+
+require 'chef'
+require 'chef/log'
+require 'chef/config'
+require 'chef/application'
+require 'chef/solr'
+require 'chef/solr/index'
+require 'nanite'
+require 'eventmachine'
+
+class Chef
+ class Solr
+ class Application
+ class Rebuild < Chef::Application
+
+ option :config_file,
+ :short => "-c CONFIG",
+ :long => "--config CONFIG",
+ :default => "/etc/chef/solr.rb",
+ :description => "The configuration file to use"
+
+ option :log_level,
+ :short => "-l LEVEL",
+ :long => "--log_level LEVEL",
+ :description => "Set the log level (debug, info, warn, error, fatal)",
+ :proc => lambda { |l| l.to_sym }
+
+ option :log_location,
+ :short => "-L LOGLOCATION",
+ :long => "--logfile LOGLOCATION",
+ :description => "Set the log file location, defaults to STDOUT - recommended for daemonizing",
+ :proc => nil
+
+ option :help,
+ :short => "-h",
+ :long => "--help",
+ :description => "Show this message",
+ :on => :tail,
+ :boolean => true,
+ :show_options => true,
+ :exit => 0
+
+ option :nanite_identity,
+ :long => "--nanite-identity ID",
+ :description => "The nanite identity"
+
+ option :nanite_host,
+ :long => "--nanite-host HOST",
+ :description => "The nanite host"
+
+ option :nanite_port,
+ :long => "--nanite-port PORT",
+ :description => "The nanite port"
+
+ option :nanite_user,
+ :long => "--nanite-user USER",
+ :description => "The nanite user"
+
+ option :nanite_pass,
+ :long => "--nanite-pass PASS",
+ :description => "The nanite password"
+
+ option :nanite_vhost,
+ :long => "--nanite-vhost VHOST",
+ :description => "The nanite vhost"
+
+ option :couchdb_database,
+ :short => "-d DB",
+ :long => "--couchdb-database DB",
+ :description => "The CouchDB Database to re-index"
+
+ option :couchdb_url,
+ :short => "-u URL",
+ :long => "--couchdb-url URL",
+ :description => "The CouchDB URL"
+
+ def initialize
+ super
+
+ @index = Chef::Solr::Index.new
+ ::Nanite::Log.logger = Chef::Log.logger
+ end
+
+ def setup_application
+ Chef::Log.level(Chef::Config[:log_level])
+ Chef::Log.warn("This operation is destructive!")
+ Chef::Log.warn("I'm going to count to 10, and then delete your Solr index and rebuild it.")
+ Chef::Log.warn("CTRL-C will, of course, stop this disaster.")
+ 0.upto(10) do |num|
+ Chef::Log.warn("... #{num}")
+ sleep 1
+ end
+ Chef::Log.warn("... Bombs away!")
+ end
+
+ def run_application
+ s = Chef::Solr.new(Chef::Config[:solr_url])
+ Chef::Log.info("Destroying the index")
+ s.rebuild_index
+ end
+ end
+ end
+ end
+end
diff --git a/chef-solr/lib/chef/solr/application/solr.rb b/chef-solr/lib/chef/solr/application/solr.rb
new file mode 100644
index 0000000000..44bc80c0a8
--- /dev/null
+++ b/chef-solr/lib/chef/solr/application/solr.rb
@@ -0,0 +1,165 @@
+#
+# Author:: AJ Christensen (<aj@opscode.com)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+
+require 'chef'
+require 'chef/log'
+require 'chef/config'
+require 'chef/application'
+require 'chef/daemon'
+require 'chef/client'
+
+class Chef
+ class Solr
+ class Application
+ class Solr < Chef::Application
+
+ option :config_file,
+ :short => "-c CONFIG",
+ :long => "--config CONFIG",
+ :default => "/etc/chef/solr.rb",
+ :description => "The configuration file to use"
+
+ option :log_level,
+ :short => "-l LEVEL",
+ :long => "--log_level LEVEL",
+ :description => "Set the log level (debug, info, warn, error, fatal)",
+ :proc => lambda { |l| l.to_sym }
+
+ option :log_location,
+ :short => "-L LOGLOCATION",
+ :long => "--logfile LOGLOCATION",
+ :description => "Set the log file location, defaults to STDOUT - recommended for daemonizing",
+ :proc => nil
+
+ option :help,
+ :short => "-h",
+ :long => "--help",
+ :description => "Show this message",
+ :on => :tail,
+ :boolean => true,
+ :show_options => true,
+ :exit => 0
+
+ option :user,
+ :short => "-u USER",
+ :long => "--user USER",
+ :description => "User to set privilege to",
+ :proc => nil
+
+ option :group,
+ :short => "-g GROUP",
+ :long => "--group GROUP",
+ :description => "Group to set privilege to",
+ :proc => nil
+
+ option :daemonize,
+ :short => "-d",
+ :long => "--daemonize",
+ :description => "Daemonize the process",
+ :proc => lambda { |p| true }
+
+ option :solr_jetty_path,
+ :short => "-W PATH",
+ :long => "--solr-jetty-dir PATH",
+ :description => "Where to place the Solr Jetty instance"
+
+ option :solr_data_path,
+ :short => "-D PATH",
+ :long => "--solr-data-dir PATH",
+ :description => "Where the Solr data lives"
+
+ option :solr_home_path,
+ :short => "-H PATH",
+ :long => "--solr-home-dir PATH",
+ :description => "Solr home directory"
+
+ option :solr_heap_size,
+ :short => "-x SIZE",
+ :long => "--solor-heap-size SIZE",
+ :description => "Set the size of the Java Heap"
+
+ option :solr_java_opts,
+ :short => "-j OPTS",
+ :long => "--java-opts OPTS",
+ :description => "Raw options passed to Java"
+
+ def initialize
+ super
+ Chef::Log.level(Chef::Config[:log_level])
+ end
+
+ def setup_application
+ Chef::Daemon.change_privilege
+
+ # Build up a client
+ c = Chef::Client.new
+ c.build_node(nil, true)
+
+ solr_base = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "solr"))
+
+ # Create the Jetty container
+ unless File.directory?(Chef::Config[:solr_jetty_path])
+ Chef::Log.warn("Initializing the Jetty container")
+ solr_jetty_dir = Chef::Resource::Directory.new(Chef::Config[:solr_jetty_path], nil, c.node)
+ solr_jetty_dir.recursive(true)
+ solr_jetty_dir.run_action(:create)
+ solr_jetty_untar = Chef::Resource::Execute.new("untar_jetty", nil, c.node)
+ solr_jetty_untar.command("tar zxvf #{File.join(solr_base, 'solr-jetty.tar.gz')}")
+ solr_jetty_untar.cwd(Chef::Config[:solr_jetty_path])
+ solr_jetty_untar.run_action(:run)
+ end
+
+ # Create the solr home
+ unless File.directory?(Chef::Config[:solr_home_path])
+ Chef::Log.warn("Initializing Solr home directory")
+ solr_home_dir = Chef::Resource::Directory.new(Chef::Config[:solr_home_path], nil, c.node)
+ solr_home_dir.recursive(true)
+ solr_home_dir.run_action(:create)
+ solr_jetty_untar = Chef::Resource::Execute.new("untar_solr_home", nil, c.node)
+ solr_jetty_untar.command("tar zxvf #{File.join(solr_base, 'solr-home.tar.gz')}")
+ solr_jetty_untar.cwd(Chef::Config[:solr_home_path])
+ solr_jetty_untar.run_action(:run)
+ end
+
+ # Create the solr data path
+ unless File.directory?(Chef::Config[:solr_data_path])
+ Chef::Log.warn("Initializing Solr data directory")
+ solr_data_dir = Chef::Resource::Directory.new(Chef::Config[:solr_data_path], nil, c.node)
+ solr_data_dir.recursive(true)
+ solr_data_dir.run_action(:create)
+ end
+ end
+
+ def run_application
+ if Chef::Config[:daemonize]
+ Chef::Daemon.daemonize("chef-solr")
+ end
+ Dir.chdir(Chef::Config[:solr_jetty_path]) do
+ command = "java -Xmx#{Chef::Config[:solr_heap_size]} -Xms#{Chef::Config[:solr_heap_size]}"
+ command << " -Dsolr.solr.data=#{Chef::Config[:solr_data_path]}"
+ command << " -Dsolr.solr.home=#{Chef::Config[:solr_home_path]}"
+ command << " #{Chef::Config[:solr_java_opts]}" if Chef::Config[:solr_java_opts]
+ command << " -jar #{File.join(Chef::Config[:solr_jetty_path], 'start.jar')}"
+ Chef::Log.info("Starting Solr with #{command}")
+ Kernel.exec(command)
+
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/chef-solr/lib/chef/solr/index.rb b/chef-solr/lib/chef/solr/index.rb
new file mode 100644
index 0000000000..0ebd553356
--- /dev/null
+++ b/chef-solr/lib/chef/solr/index.rb
@@ -0,0 +1,153 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2009 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require 'chef/log'
+require 'chef/config'
+require 'chef/solr'
+require 'libxml'
+require 'net/http'
+
+class Chef
+ class Solr
+ class Index < Solr
+
+ def add(id, database, type, item)
+ raise ArgumentError, "Object must respond to keys!" unless item.respond_to?(:keys)
+ to_index = flatten_and_expand(item)
+ to_index["X_CHEF_id_CHEF_X"] = id
+ to_index["X_CHEF_database_CHEF_X"] = database
+ to_index["X_CHEF_type_CHEF_X"] = type
+ solr_add(to_index)
+ to_index
+ end
+
+ def delete(id)
+ solr_delete_by_id(id)
+ end
+
+ def delete_by_query(query)
+ solr_delete_by_query(query)
+ end
+
+ def flatten_and_expand(item, fields=Hash.new, parent=nil)
+ item.keys.each do |key|
+ # If we have a parent, we want to add the current key as a value
+ if parent
+ # foo_bar = bar
+ set_field_value(fields, parent, key)
+ # foo_X = bar, etc.
+ make_expando_fields(parent).each do |ex_key|
+ set_field_value(fields, ex_key, key)
+ end
+ end
+ case item[key]
+ when Hash
+ parent_key = parent ? "#{parent}_#{key}" : key
+ flatten_and_expand(item[key], fields, parent_key)
+ else
+ parent_key = parent ? "#{parent}_#{key}" : key
+ set_field_value(fields, key, item[key])
+ set_field_value(fields, parent_key, item[key]) if parent
+ make_expando_fields(parent_key).each do |ex_key|
+ set_field_value(fields, ex_key, item[key])
+ end
+ end
+ end
+ fields
+ end
+
+ def make_expando_fields(key)
+ key = key.to_s
+ fields = Array.new
+ parts = key.split("_")
+ length = parts.length
+ parts.each_index do |i|
+ beginning = nil
+ remainder = nil
+ if i == 0
+ beginning = "X"
+ else
+ beginning = parts[0..i-1].join("_")
+ end
+
+ if i == length-1
+ remainder = "X"
+ else
+ remainder = parts[i+1..-1].join("_")
+ end
+
+ if beginning == "X" || remainder == "X"
+ unless beginning == "X" && remainder == "X"
+ fields << "#{beginning}_#{remainder}"
+ end
+ else
+ fields << "#{beginning}_X_#{remainder}"
+ end
+ end
+ fields
+ end
+
+ def set_field_value(fields, key, value)
+ key = key.to_s
+ if fields.has_key?(key)
+ convert_field_to_array(fields, key, value) unless fields[key].kind_of?(Array)
+ add_value_to_field_array(fields, key, value)
+ else
+ check_value(value)
+ if value.kind_of?(Array)
+ fields[key] = Array.new
+ value.each do |v|
+ fields[key] << v.to_s
+ end
+ else
+ fields[key] = value.to_s
+ end
+ end
+ fields
+ end
+
+ def add_value_to_field_array(fields, key, value)
+ check_value(value)
+ if value.kind_of?(Array)
+ value.each do |v|
+ check_value(v)
+ fields[key] << v.to_s unless fields[key].include?(v.to_s)
+ end
+ else
+ fields[key] << value.to_s unless fields[key].include?(value.to_s)
+ end
+ fields
+ end
+
+ def convert_field_to_array(fields, key, value)
+ if fields[key] != value
+ safe = fields[key]
+ fields[key] = [ safe ]
+ end
+ fields
+ end
+
+ def check_value(value)
+ raise ArgumentError, "Value must not be a type of hash!" if value.kind_of?(Hash)
+ value
+ end
+
+ end
+ end
+end
+
diff --git a/chef-solr/lib/chef/solr/index_actor.rb b/chef-solr/lib/chef/solr/index_actor.rb
new file mode 100644
index 0000000000..164785889f
--- /dev/null
+++ b/chef-solr/lib/chef/solr/index_actor.rb
@@ -0,0 +1,90 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2009 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require 'rubygems'
+require 'chef/log'
+require 'chef/config'
+require 'chef/solr'
+require 'chef/solr/index'
+require 'chef/node'
+require 'chef/role'
+require 'chef/rest'
+require 'chef/data_bag'
+require 'chef/data_bag_item'
+require 'chef/couchdb'
+require 'nanite/actor'
+
+class Chef
+ class Solr
+ class IndexActor
+ include ::Nanite::Actor
+
+ expose :add, :delete, :commit, :optimize
+
+ def add(payload)
+ index = Chef::Solr::Index.new
+
+ pitem = nil
+ if payload["item"].respond_to?(:keys)
+ pitem = payload["item"]
+ elsif payload["item"].respond_to?(:to_hash)
+ pitem = payload["item"].to_hash
+ else
+ return generate_response() { raise ArgumentError, "Payload item does not respond to :keys or :to_hash, cannot index!" }
+ end
+ response = generate_response { index.add(payload["id"], payload["database"], payload["type"], pitem) }
+ Chef::Log.info("Indexing #{payload["type"]} #{payload["id"]} from #{payload["database"]} status #{response[:status]}#{response[:status] == :error ? ' ' + response[:error] : ''}")
+ response
+ end
+
+ def delete(payload)
+ index = Chef::Solr::Index.new
+ generate_response { index.delete(payload["id"]) }
+ Chef::Log.info("Removed #{payload["id"]} from the index")
+ end
+
+ def commit(payload)
+ index = Chef::Solr::Index.new
+ generate_response { index.solr_commit }
+ Chef::Log.info("Committed the index")
+ end
+
+ def optimize(payload)
+ index = Chef::Solr::Index.new
+ generate_response { index.solr_optimize }
+ Chef::Log.info("Optimized the index")
+ end
+
+ private
+ def generate_response(&block)
+ response = {}
+ begin
+ block.call
+ rescue
+ response[:status] = :error
+ response[:error] = $!
+ else
+ response[:status] = :ok
+ end
+ response
+ end
+
+ end
+ end
+end
+
diff --git a/chef-solr/lib/chef/solr/query.rb b/chef-solr/lib/chef/solr/query.rb
new file mode 100644
index 0000000000..ec788677bf
--- /dev/null
+++ b/chef-solr/lib/chef/solr/query.rb
@@ -0,0 +1,87 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2009 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require 'chef/couchdb'
+require 'chef/node'
+require 'chef/role'
+require 'chef/data_bag'
+require 'chef/data_bag_item'
+require 'chef/solr'
+require 'chef/log'
+require 'chef/config'
+
+class Chef
+ class Solr
+ class Query < Chef::Solr
+
+ # Create a new Query object - takes the solr_url and optional
+ # couchdb_database to inflate objects into.
+ def initialize(solr_url=Chef::Config[:solr_url], database=Chef::Config[:couchdb_database])
+ super(solr_url)
+ @database = database
+ @couchdb = Chef::CouchDB.new(nil, database)
+ end
+
+ # A raw query against CouchDB - takes the type of object to find, and raw
+ # Solr options.
+ #
+ # You'll wind up having to page things yourself.
+ def raw(type, options={})
+ case type
+ when "role",:role,"node",:node
+ qtype = type
+ else
+ qtype = [ "data_bag_item", type ]
+ end
+ Chef::Log.debug("Searching #{@database} #{qtype.inspect} for #{options.inspect}")
+ results = solr_select(@database, qtype, options)
+ if results["response"]["docs"].length > 0
+ objects = @couchdb.bulk_get(
+ results["response"]["docs"].collect { |d| d["X_CHEF_id_CHEF_X"] }
+ )
+ else
+ objects = []
+ end
+ [ objects, results["response"]["start"], results["response"]["numFound"], results["responseHeader"] ]
+ end
+
+ # Search Solr for objects of a given type, for a given query. If you give
+ # it a block, it will handle the paging for you dynamically.
+ def search(type, query="*:*", sort=nil, start=0, rows=20, &block)
+ options = {
+ :q => query,
+ :start => start,
+ :rows => rows
+ }
+ options[:sort] = sort if sort && ! sort.empty?
+ objects, start, total, response_header = raw(type, options)
+ if block
+ objects.each { |o| block.call(o) }
+ unless (start + objects.length) >= total
+ nstart = start + rows
+ search(type, query, sort, nstart, rows, &block)
+ end
+ true
+ else
+ [ objects, start, total ]
+ end
+ end
+ end
+ end
+end
+
diff --git a/chef-solr/solr/solr-home.tar.gz b/chef-solr/solr/solr-home.tar.gz
new file mode 100644
index 0000000000..e1c0ab2db7
--- /dev/null
+++ b/chef-solr/solr/solr-home.tar.gz
Binary files differ
diff --git a/chef-solr/solr/solr-jetty.tar.gz b/chef-solr/solr/solr-jetty.tar.gz
new file mode 100644
index 0000000000..ca010de6cf
--- /dev/null
+++ b/chef-solr/solr/solr-jetty.tar.gz
Binary files differ
diff --git a/chef-solr/spec/chef/solr/index_spec.rb b/chef-solr/spec/chef/solr/index_spec.rb
new file mode 100644
index 0000000000..90a33ecc8f
--- /dev/null
+++ b/chef-solr/spec/chef/solr/index_spec.rb
@@ -0,0 +1,168 @@
+require File.expand_path(File.join("#{File.dirname(__FILE__)}", '..', '..', 'spec_helper'))
+
+describe Chef::Solr::Index do
+ before(:each) do
+ @index = Chef::Solr::Index.new
+ end
+
+ describe "initialize" do
+ it "should return a Chef::Solr::Index" do
+ @index.should be_a_kind_of(Chef::Solr::Index)
+ end
+ end
+
+ describe "add" do
+ before(:each) do
+ @index.stub!(:solr_add).and_return(true)
+ @index.stub!(:solr_commit).and_return(true)
+ end
+
+ it "should take an object that responds to .keys as it's argument" do
+ lambda { @index.add(1, "chef_opscode", "node", { :one => :two }) }.should_not raise_error(ArgumentError)
+ lambda { @index.add(1, "chef_opscode", "node", "SOUP") }.should raise_error(ArgumentError)
+ lambda { @index.add(2, "chef_opscode", "node", mock("Foo", :keys => true)) }.should_not raise_error(ArgumentError)
+ end
+
+ it "should index the object as a single flat hash, with only strings or arrays as values" do
+ validate = {
+ "X_CHEF_id_CHEF_X" => 1,
+ "X_CHEF_database_CHEF_X" => "monkey",
+ "X_CHEF_type_CHEF_X" => "snakes",
+ "foo" => "bar",
+ "battles" => [ "often", "but", "for" ],
+ "battles_often" => "sings like smurfs",
+ "often" => "sings like smurfs",
+ "battles_but" => "still has good records",
+ "but" => "still has good records",
+ "battles_for" => [ "all", "of", "that" ],
+ "for" => [ "all", "of", "that" ],
+ "snoopy" => "sits_in_a_barn",
+ "battles_X" => [ "sings like smurfs", "still has good records", "all", "of", "that" ],
+ "X_often" => "sings like smurfs",
+ "X_but" => "still has good records",
+ "X_for" => [ "all", "of", "that" ]
+ }
+ to_index = @index.add(1, "monkey", "snakes", {
+ "foo" => :bar,
+ "battles" => {
+ "often" => "sings like smurfs",
+ "but" => "still has good records",
+ "for" => [ "all", "of", "that" ]
+ },
+ "snoopy" => "sits_in_a_barn"
+ })
+ validate.each do |k, v|
+ if v.kind_of?(Array)
+ # Every entry in to_index[k] should be in v
+ r = to_index[k] & v
+ r.length.should == to_index[k].length
+ else
+ to_index[k].should == v
+ end
+ end
+ end
+
+ it "should send the document to solr" do
+ @index.should_receive(:solr_add)
+ @index.add(1, "monkey", "snakes", { "foo" => "bar" })
+ end
+ end
+
+ describe "delete" do
+ it "should delete by id" do
+ @index.should_receive(:solr_delete_by_id).with(1)
+ @index.delete(1)
+ end
+ end
+
+ describe "delete_by_query" do
+ it "should delete by query" do
+ @index.should_receive(:solr_delete_by_query).with("foo:bar")
+ @index.delete_by_query("foo:bar")
+ end
+ end
+
+ describe "flatten_and_expand" do
+ before(:each) do
+ @fields = Hash.new
+ end
+
+ it "should set a value for the parent as key, with the key as the value" do
+ @index.flatten_and_expand({ "one" => "woot" }, @fields, "omerta")
+ @fields["omerta"].should == "one"
+ end
+
+ it "should call itself recursively for values that are hashes" do
+ @index.flatten_and_expand({ "one" => { "two" => "three", "four" => { "five" => "six" } }}, @fields)
+ {
+ "one" => [ "two", "four" ],
+ "one_two" => "three",
+ "X_two" => "three",
+ "two" => "three",
+ "one_four" => "five",
+ "X_four" => "five",
+ "one_X" => [ "three", "five" ],
+ "one_four_five" => "six",
+ "X_four_five" => "six",
+ "one_X_five" => "six",
+ "one_four_X" => "six",
+ "five" => "six"
+ }.each do |k, v|
+ @fields[k].should == v
+ end
+ end
+
+ end
+
+ describe "set_field_value" do
+ before(:each) do
+ @fields = Hash.new
+ end
+
+ it "should set a value in the fields hash" do
+ @index.set_field_value(@fields, "one", "two")
+ @fields["one"].should eql("two")
+ end
+
+ it "should create an array of all values, if a field is set twice" do
+ @index.set_field_value(@fields, "one", "two")
+ @index.set_field_value(@fields, "one", "three")
+ @fields["one"].should eql([ "two", "three" ])
+ end
+
+ it "should not add duplicate values to a field when there is one string entry" do
+ @index.set_field_value(@fields, "one", "two")
+ @index.set_field_value(@fields, "one", "two")
+ @fields["one"].should eql("two")
+ end
+
+ it "should not add duplicate values to a field when it is an array" do
+ @index.set_field_value(@fields, "one", "two")
+ @index.set_field_value(@fields, "one", "three")
+ @index.set_field_value(@fields, "one", "two")
+ @fields["one"].should eql([ "two", "three" ])
+ end
+
+ it "should accept arrays as values" do
+ @index.set_field_value(@fields, "one", [ "two", "three" ])
+ @fields["one"].should eql([ "two", "three" ])
+ end
+
+ it "should not duplicate values when a field has been set with multiple arrays" do
+ @index.set_field_value(@fields, "one", [ "two", "three" ])
+ @index.set_field_value(@fields, "one", [ "two", "four" ])
+ @fields["one"].should eql([ "two", "three", "four" ])
+ end
+
+
+ it "should allow you to set a value in the fields hash to an array" do
+ @index.set_field_value(@fields, "one", [ "foo", "bar", "baz" ])
+ end
+
+ it "should not allow you to set a value in the fields hash to a hash" do
+ lambda {
+ @index.set_field_value(@fields, "one", { "two" => "three" })
+ }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/chef-solr/spec/chef/solr/query_spec.rb b/chef-solr/spec/chef/solr/query_spec.rb
new file mode 100644
index 0000000000..ef8afe3127
--- /dev/null
+++ b/chef-solr/spec/chef/solr/query_spec.rb
@@ -0,0 +1,14 @@
+require File.expand_path(File.join("#{File.dirname(__FILE__)}", '..', '..', 'spec_helper'))
+
+describe Chef::Solr::Query do
+ before(:each) do
+ @query = Chef::Solr::Query.new
+ end
+
+ describe "initialize" do
+ it "should return a Chef::Solr::Query" do
+ @query.should be_a_kind_of(Chef::Solr::Query)
+ end
+ end
+end
+
diff --git a/chef-solr/spec/chef/solr_spec.rb b/chef-solr/spec/chef/solr_spec.rb
new file mode 100644
index 0000000000..ef5174756b
--- /dev/null
+++ b/chef-solr/spec/chef/solr_spec.rb
@@ -0,0 +1,167 @@
+require File.expand_path(File.join("#{File.dirname(__FILE__)}", '..', 'spec_helper'))
+
+describe Chef::Solr do
+ before(:each) do
+ @solr = Chef::Solr.new
+ end
+
+ describe "initialize" do
+ it "should create a new Chef::Solr object" do
+ @solr.should be_a_kind_of(Chef::Solr)
+ end
+
+ it "should accept an alternate solr url" do
+ solr = Chef::Solr.new("http://example.com")
+ solr.solr_url.should eql("http://example.com")
+ end
+ end
+
+ describe "solr_select" do
+ before(:each) do
+ @http_response = mock(
+ "Net::HTTP::Response",
+ :kind_of? => Net::HTTPSuccess,
+ :body => "{ :some => :hash }"
+ )
+ @http = mock("Net::HTTP", :request => @http_response)
+ @solr.http = @http
+ end
+
+ it "should call get to /solr/select with the escaped query" do
+ Net::HTTP::Get.should_receive(:new).with(%r(q=hostname%3Alatte))
+ @solr.solr_select("chef_opscode", "node", :q => "hostname:latte")
+ end
+
+ it "should call get to /solr/select with wt=ruby" do
+ Net::HTTP::Get.should_receive(:new).with(%r(wt=ruby))
+ @solr.solr_select("chef_opscode", "node", :q => "hostname:latte")
+ end
+
+ it "should call get to /solr/select with indent=off" do
+ Net::HTTP::Get.should_receive(:new).with(%r(indent=off))
+ @solr.solr_select("chef_opscode", "node", :q => "hostname:latte")
+ end
+
+ it "should call get to /solr/select with filter query" do
+ Net::HTTP::Get.should_receive(:new).with(/fq=%2BX_CHEF_database_CHEF_X%3Achef_opscode\+%2BX_CHEF_type_CHEF_X%3Anode/)
+ @solr.solr_select("chef_opscode", "node", :q => "hostname:latte")
+ end
+
+ it "should return the evaluated response body" do
+ res = @solr.solr_select("chef_opscode", "node", :q => "hostname:latte")
+ res.should == { :some => :hash }
+ end
+ end
+
+
+ describe "post_to_solr" do
+ before(:each) do
+ @http_response = mock(
+ "Net::HTTP::Response",
+ :kind_of? => Net::HTTPSuccess,
+ :body => "{ :some => :hash }"
+ )
+ @http_request = mock(
+ "Net::HTTP::Request",
+ :body= => true
+ )
+ @http = mock("Net::HTTP", :request => @http_response)
+ @solr.http = @http
+ Net::HTTP::Post.stub!(:new).and_return(@http_request)
+ @doc = { "foo" => "bar" }
+ end
+
+ it "should post to /solr/update" do
+ Net::HTTP::Post.should_receive(:new).with("/solr/update", "Content-Type" => "text/xml").and_return(@http_request)
+ @solr.post_to_solr(@doc)
+ end
+
+ it "should set the body of the request to the stringified doc" do
+ @http_request.should_receive(:body=).with("foo")
+ @solr.post_to_solr(:foo)
+ end
+
+ it "should send the request to solr" do
+ @http.should_receive(:request).with(@http_request).and_return(@http_response)
+ @solr.post_to_solr(:foo)
+ end
+ end
+
+ describe "solr_add" do
+ before(:each) do
+ @solr.stub!(:post_to_solr).and_return(true)
+ @data = { "foo" => "bar" }
+ end
+
+ it "should send valid XML to solr" do
+ @solr.should_receive(:post_to_solr).with("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<add><doc><field name=\"foo\">bar</field></doc></add>\n")
+ @solr.solr_add(@data)
+ end
+ end
+
+ describe "solr_commit" do
+ before(:each) do
+ @solr.stub!(:post_to_solr).and_return(true)
+ end
+
+ it "should send valid commit xml to solr" do
+ @solr.should_receive(:post_to_solr).with("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<commit/>\n")
+ @solr.solr_commit
+ end
+ end
+
+ describe "solr_optimize" do
+ before(:each) do
+ @solr.stub!(:post_to_solr).and_return(true)
+ end
+
+ it "should send valid commit xml to solr" do
+ @solr.should_receive(:post_to_solr).with("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<optimize/>\n")
+ @solr.solr_optimize
+ end
+ end
+
+ describe "solr_rollback" do
+ before(:each) do
+ @solr.stub!(:post_to_solr).and_return(true)
+ end
+
+ it "should send valid commit xml to solr" do
+ @solr.should_receive(:post_to_solr).with("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rollback/>\n")
+ @solr.solr_rollback
+ end
+ end
+
+ describe "solr_delete_by_id" do
+ before(:each) do
+ @solr.stub!(:post_to_solr).and_return(true)
+ end
+
+ it "should send valid delete id xml to solr" do
+ @solr.should_receive(:post_to_solr).with("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<delete><id>1</id></delete>\n")
+ @solr.solr_delete_by_id(1)
+ end
+
+ it "should accept multiple ids" do
+ @solr.should_receive(:post_to_solr).with("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<delete><id>1</id><id>2</id></delete>\n")
+ @solr.solr_delete_by_id([ 1, 2 ])
+ end
+ end
+
+ describe "solr_delete_by_query" do
+ before(:each) do
+ @solr.stub!(:post_to_solr).and_return(true)
+ end
+
+ it "should send valid delete id xml to solr" do
+ @solr.should_receive(:post_to_solr).with("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<delete><query>foo:bar</query></delete>\n")
+ @solr.solr_delete_by_query("foo:bar")
+ end
+
+ it "should accept multiple ids" do
+ @solr.should_receive(:post_to_solr).with("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<delete><query>foo:bar</query><query>baz:bum</query></delete>\n")
+ @solr.solr_delete_by_query([ "foo:bar", "baz:bum" ])
+ end
+ end
+
+end
diff --git a/chef-solr/spec/spec_helper.rb b/chef-solr/spec/spec_helper.rb
new file mode 100644
index 0000000000..0a6ee8b08a
--- /dev/null
+++ b/chef-solr/spec/spec_helper.rb
@@ -0,0 +1,13 @@
+require 'rubygems'
+require 'spec'
+
+$LOAD_PATH.unshift(File.dirname(__FILE__))
+$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
+require 'chef'
+require 'chef/solr'
+require 'chef/solr/index'
+require 'chef/solr/query'
+
+Spec::Runner.configure do |config|
+
+end
diff --git a/chef/Rakefile b/chef/Rakefile
index 45a0da15d0..30eb3f7d8a 100644
--- a/chef/Rakefile
+++ b/chef/Rakefile
@@ -4,7 +4,7 @@ require 'rake/rdoctask'
require './tasks/rspec.rb'
GEM = "chef"
-CHEF_VERSION = "0.7.9"
+CHEF_VERSION = "0.8.0"
AUTHOR = "Adam Jacob"
EMAIL = "adam@opscode.com"
HOMEPAGE = "http://wiki.opscode.com/display/chef"
diff --git a/chef/lib/chef.rb b/chef/lib/chef.rb
index 9cec0cb7e3..9551cedc4a 100644
--- a/chef/lib/chef.rb
+++ b/chef/lib/chef.rb
@@ -27,7 +27,7 @@ require 'chef/config'
Dir[File.join(File.dirname(__FILE__), 'chef/mixin/**/*.rb')].sort.each { |lib| require lib }
class Chef
- VERSION = '0.7.9'
+ VERSION = '0.8.0'
end
# Adds a Dir.glob to Ruby 1.8.5, for compat
diff --git a/chef/lib/chef/application/indexer.rb b/chef/lib/chef/application/indexer.rb
deleted file mode 100644
index 92bb9f08a7..0000000000
--- a/chef/lib/chef/application/indexer.rb
+++ /dev/null
@@ -1,141 +0,0 @@
-#
-# Author:: AJ Christensen (<aj@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# 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.
-
-require 'chef/application'
-require 'chef/queue'
-require 'chef/search'
-require 'chef/search_index'
-require 'chef/config'
-require 'chef/daemon'
-require 'chef/log'
-
-
-class Chef::Application::Indexer < Chef::Application
-
- option :config_file,
- :short => "-c CONFIG",
- :long => "--config CONFIG",
- :default => "/etc/chef/server.rb",
- :description => "The configuration file to use"
-
- option :log_level,
- :short => "-l LEVEL",
- :long => "--log_level LEVEL",
- :description => "Set the log level (debug, info, warn, error, fatal)",
- :proc => lambda { |l| l.to_sym }
-
- option :log_location,
- :short => "-L LOGLOCATION",
- :long => "--logfile LOGLOCATION",
- :description => "Set the log file location, defaults to STDOUT - recommended for daemonizing",
- :proc => nil
-
- option :help,
- :short => "-h",
- :long => "--help",
- :description => "Show this message",
- :on => :tail,
- :boolean => true,
- :show_options => true,
- :exit => 0
-
- option :user,
- :short => "-u USER",
- :long => "--user USER",
- :description => "User to set privilege to",
- :proc => nil
-
- option :group,
- :short => "-g GROUP",
- :long => "--group GROUP",
- :description => "Group to set privilege to",
- :proc => nil
-
- option :daemonize,
- :short => "-d",
- :long => "--daemonize",
- :description => "Daemonize the process",
- :proc => lambda { |p| true }
-
- option :version,
- :short => "-v",
- :long => "--version",
- :description => "Show chef version",
- :boolean => true,
- :proc => lambda {|v| puts "Chef: #{::Chef::VERSION}"},
- :exit => 0
-
- def initialize
- super
-
- @chef_search_indexer = nil
- end
-
- # Create a new search indexer and connect to the stomp queues
- def setup_application
- Chef::Daemon.change_privilege
-
- @chef_search_indexer = Chef::SearchIndex.new
- Chef::Queue.connect
- Chef::Queue.subscribe(:queue, "index")
- Chef::Queue.subscribe(:queue, "remove")
- end
-
- # Run the indexer, optionally daemonizing.
- def run_application
- if Chef::Config[:daemonize]
- Chef::Daemon.daemonize("chef-indexer")
- end
-
- if Chef::Config[:queue_prefix]
- queue_prefix = Chef::Config[:queue_prefix]
- queue_partial_url = "/queue/#{queue_prefix}/chef"
- else
- queue_partial_url = "/queue/chef"
- end
-
- loop do
- object, headers = Chef::Queue.receive_msg
- Chef::Log.info("Headers #{headers.inspect}")
- if headers["destination"] == "#{queue_partial_url}/index"
- start_timer = Time.new
- @chef_search_indexer.add(object)
- @chef_search_indexer.commit
- final_timer = Time.new
- Chef::Log.info("Indexed object from #{headers['destination']} in #{final_timer - start_timer} seconds")
- elsif headers["destination"] == "#{queue_partial_url}/remove"
- start_timer = Time.new
- @chef_search_indexer.delete(object)
- @chef_search_indexer.commit
- final_timer = Time.new
- Chef::Log.info("Removed object from #{headers['destination']} in #{final_timer - start_timer} seconds")
- end
- end
- rescue SystemExit => e
- raise
- rescue Exception => e
- if Chef::Config[:interval]
- Chef::Log.error("#{e.class}")
- Chef::Log.fatal("#{e}\n#{e.backtrace.join("\n")}")
- Chef::Log.fatal("Sleeping for #{Chef::Config[:delay]} seconds before trying again")
- sleep Chef::Config[:delay]
- retry
- else
- raise
- end
- end
-end
diff --git a/chef/lib/chef/client.rb b/chef/lib/chef/client.rb
index c561fedb32..7f67c6bef0 100644
--- a/chef/lib/chef/client.rb
+++ b/chef/lib/chef/client.rb
@@ -46,9 +46,14 @@ class Chef
@json_attribs = nil
@node_name = nil
@node_exists = true
- Ohai::Log.logger = Chef::Log.logger
+ Mixlib::Auth::Log.logger = Ohai::Log.logger = Chef::Log.logger
@ohai = Ohai::System.new
- @rest = Chef::REST.new(Chef::Config[:registration_url])
+ @ohai_has_run = false
+ if File.exists?(Chef::Config[:client_key])
+ @rest = Chef::REST.new(Chef::Config[:chef_server_url])
+ else
+ @rest = Chef::REST.new(Chef::Config[:chef_server_url], nil, nil)
+ end
end
# Do a full run for this Chef::Client. Calls:
@@ -71,13 +76,9 @@ class Chef
determine_node_name
register
- authenticate
build_node(@node_name)
save_node
- sync_library_files
- sync_attribute_files
- sync_definitions
- sync_recipes
+ sync_cookbooks
save_node
converge
save_node
@@ -116,9 +117,14 @@ class Chef
end
def determine_node_name
- run_ohai
unless @safe_name && @node_name
- @node_name ||= @ohai[:fqdn] ? @ohai[:fqdn] : @ohai[:hostname]
+ run_ohai
+ if Chef::Config[:node_name]
+ @node_name = Chef::Config[:node_name]
+ else
+ @node_name ||= @ohai[:fqdn] ? @ohai[:fqdn] : @ohai[:hostname]
+ Chef::Config[:node_name] = @node_name
+ end
@safe_name = @node_name.gsub(/\./, '_')
end
@node_name
@@ -177,68 +183,22 @@ class Chef
@node[:tags] = Array.new unless @node.attribute?(:tags)
@node
end
-
- # If this node has been registered before, this method will fetch the current registration
- # data.
- #
- # If it has not, we register it by calling create_registration.
- #
+
+ #
# === Returns
# true:: Always returns true
def register
- determine_node_name unless @node_name
- Chef::Log.debug("Registering #{@safe_name} for an openid")
-
- begin
- if @rest.get_rest("registrations/#{@safe_name}")
- @secret = Chef::FileCache.load(File.join("registration", @safe_name))
- end
- rescue Net::HTTPServerException => e
- case e.message
- when /^404/
- create_registration
- else
- raise
- end
- rescue Chef::Exceptions::FileNotFound
- Chef::Application.fatal! "A remote registration already exists for #{@safe_name}, however the local shared secret does not exist." +
- " To remedy this, you could delete the registration via webUI/REST, change the node_name option in config.rb" +
- " (or use the -N/--node-name option to the CLI) or" +
- " copy the old shared secret to #{File.join(Chef::Config[:file_cache_path], 'registration', @safe_name)}", 3
+ if File.exists?(Chef::Config[:validation_key])
+ @vr = Chef::REST.new(Chef::Config[:client_url], Chef::Config[:validation_client_name], Chef::Config[:validation_key])
+ @vr.register(@node_name, Chef::Config[:client_key])
+ else
+ Chef::Log.debug("Validation key #{Chef::Config[:validation_key]} is not present - skipping registration")
end
-
+ # We now have the client key, and should use it from now on.
+ @rest = Chef::REST.new(Chef::Config[:chef_server_url])
true
end
- # Generates a random secret, stores it in the Chef::Filestore with the "registration" key,
- # and posts our nodes registration information to the server.
- #
- # === Returns
- # true:: Always returns true
- def create_registration
- @secret = random_password(500)
- Chef::FileCache.store(File.join("registration", @safe_name), @secret)
- @rest.post_rest("registrations", { :id => @safe_name, :password => @secret, :validation_token => @validation_token })
- true
- end
-
- # Authenticates the node via OpenID.
- #
- # === Returns
- # true:: Always returns true
- def authenticate
- determine_node_name unless @node_name
- Chef::Log.debug("Authenticating #{@safe_name} via openid")
- response = @rest.post_rest('openid/consumer/start', {
- "openid_identifier" => "#{Chef::Config[:openid_url]}/openid/server/node/#{@safe_name}",
- "submit" => "Verify"
- })
- @rest.post_rest(
- "#{Chef::Config[:openid_url]}#{response["action"]}",
- { "password" => @secret }
- )
- end
-
# Update the file caches for a given cache segment. Takes a segment name
# and a hash that matches one of the cookbooks/_attribute_files style
# remote file listings.
@@ -246,103 +206,81 @@ class Chef
# === Parameters
# segment<String>:: The cache segment to update
# remote_list<Hash>:: A cookbooks/_attribute_files style remote file listing
- def update_file_cache(segment, remote_list)
- # We need the list of known good attribute files, so we can delete any that are
- # just laying about.
+ def update_file_cache(cookbook_name, parts)
+ Chef::Log.debug("Synchronizing cookbook #{cookbook_name}")
+
file_canonical = Hash.new
-
- remote_list.each do |rf|
- cache_file = File.join("cookbooks", rf['cookbook'], segment, rf['name'])
- file_canonical[cache_file] = true
- # For back-compat between older clients and new chef servers
- rf['checksum'] ||= nil
-
- current_checksum = nil
- if Chef::FileCache.has_key?(cache_file)
- current_checksum = checksum(Chef::FileCache.load(cache_file, false))
- end
+ parts.each do |segment, remote_list|
+ # segement = cookbook segment
+ # remote_list = list of file hashes
+ #
+ # We need the list of known good attribute files, so we can delete any that are
+ # just laying about.
+
+
+ remote_list.each do |rf|
+ cache_file = File.join("cookbooks", cookbook_name, segment, rf['name'])
+ file_canonical[cache_file] = true
- rf_url = generate_cookbook_url(
- rf['name'],
- rf['cookbook'],
- segment,
- @node,
- current_checksum ? { 'checksum' => current_checksum } : nil
- )
- Chef::Log.debug(rf_url)
+ # For back-compat between older clients and new chef servers
+ rf['checksum'] ||= nil
+
+ current_checksum = nil
+ if Chef::FileCache.has_key?(cache_file)
+ current_checksum = checksum(Chef::FileCache.load(cache_file, false))
+ end
- if current_checksum != rf['checksum']
- changed = true
- begin
- raw_file = @rest.get_rest(rf_url, true)
- rescue Net::HTTPRetriableError => e
- if e.response.kind_of?(Net::HTTPNotModified)
- changed = false
- Chef::Log.debug("Cache file #{cache_file} is unchanged")
- else
- raise e
+ rf_url = generate_cookbook_url(
+ rf['name'],
+ cookbook_name,
+ segment,
+ @node,
+ current_checksum ? { 'checksum' => current_checksum } : nil
+ )
+ if current_checksum != rf['checksum']
+ changed = true
+ begin
+ raw_file = @rest.get_rest(rf_url, true)
+ rescue Net::HTTPRetriableError => e
+ if e.response.kind_of?(Net::HTTPNotModified)
+ changed = false
+ Chef::Log.debug("Cache file #{cache_file} is unchanged")
+ else
+ raise e
+ end
end
- end
- if changed
- Chef::Log.info("Storing updated #{cache_file} in the cache.")
- Chef::FileCache.move_to(raw_file.path, cache_file)
+ if changed
+ Chef::Log.info("Storing updated #{cache_file} in the cache.")
+ Chef::FileCache.move_to(raw_file.path, cache_file)
+ end
end
end
- end
-
- Chef::FileCache.list.each do |cache_file|
- if cache_file.match("cookbooks/.+?/#{segment}")
- unless file_canonical[cache_file]
- Chef::Log.info("Removing #{cache_file} from the cache; it is no longer on the server.")
- Chef::FileCache.delete(cache_file)
+
+ Chef::FileCache.list.each do |cache_file|
+ if cache_file =~ /^cookbooks\/(recipes|attributes|definitions|libraries)\//
+ unless file_canonical[cache_file]
+ Chef::Log.info("Removing #{cache_file} from the cache; it is no longer on the server.")
+ Chef::FileCache.delete(cache_file)
+ end
end
end
+
end
end
-
- # Gets all the attribute files included in all the cookbooks available on the server,
- # and executes them.
- #
- # === Returns
- # true:: Always returns true
- def sync_attribute_files
- Chef::Log.debug("Synchronizing attributes")
- update_file_cache("attributes", @rest.get_rest("cookbooks/_attribute_files?node=#{@node.name}"))
- true
- end
-
- # Gets all the library files included in all the cookbooks available on the server,
- # and loads them.
- #
- # === Returns
- # true:: Always returns true
- def sync_library_files
- Chef::Log.debug("Synchronizing libraries")
- update_file_cache("libraries", @rest.get_rest("cookbooks/_library_files?node=#{@node.name}"))
- true
- end
-
- # Gets all the definition files included in all the cookbooks available on the server,
- # and loads them.
- #
- # === Returns
- # true:: Always returns true
- def sync_definitions
- Chef::Log.debug("Synchronizing definitions")
- update_file_cache("definitions", @rest.get_rest("cookbooks/_definition_files?node=#{@node.name}"))
- end
-
- # Gets all the recipe files included in all the cookbooks available on the server,
- # and loads them.
+
+ # Synchronizes all the cookbooks from the chef-server.
#
# === Returns
# true:: Always returns true
- def sync_recipes
- Chef::Log.debug("Synchronizing recipes")
- update_file_cache("recipes", @rest.get_rest("cookbooks/_recipe_files?node=#{@node.name}"))
+ def sync_cookbooks
+ Chef::Log.debug("Synchronizing cookbooks")
+ cookbook_hash = @rest.get_rest("nodes/#{@safe_name}/cookbooks")
+ cookbook_hash.each do |cookbook_name, parts|
+ update_file_cache(cookbook_name, parts)
+ end
end
# Updates the current node configuration on the server.
@@ -382,15 +320,6 @@ class Chef
cr.converge
true
end
-
- protected
- # Generates a random password of "len" length.
- def random_password(len)
- chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
- newpass = ""
- 1.upto(len) { |i| newpass << chars[rand(chars.size-1)] }
- newpass
- end
end
end
diff --git a/chef/lib/chef/config.rb b/chef/lib/chef/config.rb
index 977786a37d..11a4e4203d 100644
--- a/chef/lib/chef/config.rb
+++ b/chef/lib/chef/config.rb
@@ -17,6 +17,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+require 'chef/log'
require 'mixlib/config'
class Chef
@@ -54,11 +55,13 @@ class Chef
:template_url,
:remotefile_url,
:search_url,
+ :chef_server_url,
:role_url ].each do |u|
c[u] = url
end
end
end
+
# Override the config dispatch to set the value of log_location configuration option
#
# === Parameters
@@ -79,7 +82,7 @@ class Chef
authorized_openid_identifiers nil
authorized_openid_providers nil
- chef_server_url nil
+ chef_server_url "http://localhost:4000"
cookbook_path [ "/var/chef/site-cookbooks", "/var/chef/cookbooks" ]
couchdb_database "chef"
couchdb_url "http://localhost:5984"
@@ -105,14 +108,9 @@ class Chef
openid_store_path "/var/chef/openid/db"
openid_url "http://localhost:4001"
pid_file nil
- queue_host "localhost"
- queue_password ""
- queue_port 61613
- queue_retry_count 5
- queue_retry_delay 5
- queue_user ""
- queue_prefix nil
registration_url "http://localhost:4000"
+ certificate_url "http://localhost:4000"
+ client_url "http://localhost:4042"
remotefile_url "http://localhost:4000"
rest_timeout 60
run_command_stderr_timeout 120
@@ -130,5 +128,22 @@ class Chef
role_path "/var/chef/roles"
role_url "http://localhost:4000"
recipe_url nil
+ solr_url "http://localhost:8983"
+ solr_jetty_path "/var/chef/solr-jetty"
+ solr_data_path "/var/chef/solr/data"
+ solr_home_path "/var/chef/solr"
+ solr_heap_size "256M"
+ solr_java_opts nil
+ nanite_host '0.0.0.0'
+ nanite_port '5672'
+ nanite_user 'nanite'
+ nanite_pass 'testing'
+ nanite_vhost '/nanite'
+ nanite_identity nil
+
+ client_key "/etc/chef/client.pem"
+ validation_key "/etc/chef/validation.pem"
+ validation_client_name "chef-validator"
+
end
end
diff --git a/chef/lib/chef/cookbook_helper.rb b/chef/lib/chef/cookbook_helper.rb
new file mode 100644
index 0000000000..520fa38fec
--- /dev/null
+++ b/chef/lib/chef/cookbook_helper.rb
@@ -0,0 +1,90 @@
+module CookbookHelper
+
+ require 'aws/s3'
+
+ def put_in_couchdb_and_s3(cookbook_name, file, revision)
+ # TODO: set inital state of cookbook to something like 'pre-upload'
+ cookbook = Cookbook.on(database_from_orgname(params[:organization_id])).new(:display_name => cookbook_name, :revision => revision)
+ save cookbook
+
+ id = cookbook['_id']
+ Merb.logger.debug "Creating cookbook with id = #{id}"
+
+ stream_to_s3(params[:file][:tempfile], id)
+
+ # TODO: if upload successful, set cookbook state to something like 'active'
+ end
+
+ def validate_tarball(filepath, cookbook_name)
+ raise "(try creating with 'tar czf cookbook.tgz cookbook/')" unless system("tar", "tzf", filepath)
+
+ # TODO: modify/implement tests and uncomment the next lines
+
+# required_entry_roots = [cookbook_name]
+# allowed_entry_roots = [cookbook_name, "ignore"]
+
+# entry_roots = `tar tzf #{filepath}`.split("\n").map{|e|e.split('/').first}.uniq
+
+# illegal_roots = entry_roots - allowed_entry_roots
+# raise "tarball root may only contain #{allowed_entry_roots.join(', ')}" unless illegal_roots.empty?
+
+# missing_required_roots = required_entry_roots - entry_roots
+# raise "tarball root must contain #{required_entry_roots.join(', ')}" unless missing_required_roots.empty?
+ end
+
+ def get_all_cookbook_entries(cookbook_name)
+ rows = Cookbook.on(database_from_orgname(params[:organization_id])).by_display_name(:key => cookbook_name, :include_docs => true)
+ Merb.logger.debug "Cookbook has the following entries: #{rows.inspect}"
+ rows
+ end
+
+ def cookbook_id(cookbook_name)
+ rows = get_all_cookbook_entries(cookbook_name)
+ return nil if rows.empty?
+ most_recent_record = rows.sort_by{|row| row['revision'].to_i}.last
+ Merb.logger.debug "Selected #{most_recent_record.inspect}"
+ [most_recent_record['_id'], most_recent_record['revision']]
+ end
+
+ # TODO: should we do this once at start-up and test the connection before establishing it?
+ def establish_connection
+ AWS::S3::Base.establish_connection!(
+ :access_key_id => Merb::Config[:aws_secret_access_key_id],
+ :secret_access_key => Merb::Config[:aws_secret_access_key]
+ )
+ end
+
+ def stream_to_s3(path, object_id)
+ establish_connection
+ AWS::S3::S3Object.store("#{object_id}.tgz", open(path), Merb::Config[:aws_cookbook_tarball_s3_bucket])
+ end
+
+ def stream_from_s3(cookbook_name, id)
+ establish_connection
+ # TODO: if the cookbook is large and the user has a slow connection, will this cause the process's memory to bloat or will it just read from S3 slowly?
+ stream_file do |response|
+ AWS::S3::S3Object.stream("#{id}.tgz", Merb::Config[:aws_cookbook_tarball_s3_bucket]) do |chunk|
+ response.write chunk
+ end
+ end
+ end
+
+ # TODO: the following methods were heisted from opscode-account. if this is how we want to do it, then do some hoisting
+
+ def save(object)
+ if object.valid?
+ object.save
+ else
+ raise BadRequest, object.errors.full_messages
+ end
+ end
+
+ def orgname_to_dbname(orgname)
+ "chef_#{orgname}"
+ end
+
+ def database_from_orgname(orgname)
+ CouchRest::Database.new(CouchRest::Server.new,orgname_to_dbname(orgname))
+ end
+
+end
diff --git a/chef/lib/chef/couchdb.rb b/chef/lib/chef/couchdb.rb
index 10f43b72c8..9f63ad6aaa 100644
--- a/chef/lib/chef/couchdb.rb
+++ b/chef/lib/chef/couchdb.rb
@@ -21,40 +21,77 @@ require 'chef/rest'
require 'chef/log'
require 'digest/sha2'
require 'json'
+require 'uuidtools'
+require 'chef/nanite'
class Chef
class CouchDB
include Chef::Mixin::ParamsValidate
- def initialize(url=nil)
+ def initialize(url=nil, db=Chef::Config[:couchdb_database])
url ||= Chef::Config[:couchdb_url]
- @rest = Chef::REST.new(url)
+ @db = db
+ @rest = Chef::REST.new(url, nil, nil)
+ end
+
+ def couchdb_database(args=nil)
+ if args
+ @db = args
+ else
+ @db
+ end
+ end
+
+ def create_id_map
+ create_design_document(
+ "id_map",
+ {
+ "version" => 1,
+ "language" => "javascript",
+ "views" => {
+ "name_to_id" => {
+ "map" => <<-EOJS
+ function(doc) {
+ emit([ doc.chef_type, doc.name], doc._id);
+ }
+ EOJS
+ },
+ "id_to_name" => {
+ "map" => <<-EOJS
+ function(doc) {
+ emit(doc._id, [ doc.chef_type, doc.name ]);
+ }
+ EOJS
+ }
+ }
+ }
+ )
end
def create_db
@database_list = @rest.get_rest("_all_dbs")
- unless @database_list.detect { |db| db == Chef::Config[:couchdb_database] }
- response = @rest.put_rest(Chef::Config[:couchdb_database], Hash.new)
+ unless @database_list.detect { |db| db == couchdb_database }
+ response = @rest.put_rest(couchdb_database, Hash.new)
end
- Chef::Config[:couchdb_database]
+ couchdb_database
end
def create_design_document(name, data)
create_db
to_update = true
begin
- old_doc = @rest.get_rest("#{Chef::Config[:couchdb_database]}/_design%2F#{name}")
+ old_doc = @rest.get_rest("#{couchdb_database}/_design/#{name}")
if data["version"] != old_doc["version"]
data["_rev"] = old_doc["_rev"]
Chef::Log.debug("Updating #{name} views")
else
to_update = false
end
- rescue
- Chef::Log.debug("Creating #{name} views for the first time")
+ rescue
+ Chef::Log.debug("Creating #{name} views for the first time because: #{$!}")
end
if to_update
- @rest.put_rest("#{Chef::Config[:couchdb_database]}/_design%2F#{name}", data)
+ @rest.put_rest("#{couchdb_database}/_design%2F#{name}", data)
end
true
end
@@ -70,7 +107,29 @@ class Chef
:object => { :respond_to => :to_json },
}
)
- @rest.put_rest("#{Chef::Config[:couchdb_database]}/#{obj_type}_#{safe_name(name)}", object)
+ r = get_view("id_map", "name_to_id", :key => [ obj_type, name ])
+ uuid = nil
+ if r["rows"].length == 1
+ uuid = r["rows"][0]["id"]
+ else
+ uuid = UUIDTools::UUID.random_create.to_s
+ end
+
+ r = @rest.put_rest("#{couchdb_database}/#{uuid}", object)
+ Chef::Log.info("Sending #{uuid} to Nanite for indexing..")
+ n = Chef::Nanite.request(
+ "/index/add",
+ {
+ :id => uuid,
+ :database => couchdb_database,
+ :type => obj_type,
+ :item => object
+ },
+ :persistent => true
+ ) do |response_full|
+ Chef::Log.debug("Finished indexing #{obj_type} #{uuid} in #{couchdb_database}");
+ end
+ r
end
def load(obj_type, name)
@@ -84,7 +143,9 @@ class Chef
:name => { :kind_of => String },
}
)
- @rest.get_rest("#{Chef::Config[:couchdb_database]}/#{obj_type}_#{safe_name(name)}")
+ doc = find_by_name(obj_type, name)
+ doc.couchdb = self if doc.respond_to?(:couchdb)
+ doc
end
def delete(obj_type, name, rev=nil)
@@ -98,15 +159,30 @@ class Chef
:name => { :kind_of => String },
}
)
+ del_id = nil
+ last_obj, obj_id = find_by_name(obj_type, name, true)
unless rev
- last_obj = @rest.get_rest("#{Chef::Config[:couchdb_database]}/#{obj_type}_#{safe_name(name)}")
if last_obj.respond_to?(:couchdb_rev)
rev = last_obj.couchdb_rev
else
rev = last_obj['_rev']
end
end
- @rest.delete_rest("#{Chef::Config[:couchdb_database]}/#{obj_type}_#{safe_name(name)}?rev=#{rev}")
+ r = @rest.delete_rest("#{couchdb_database}/#{obj_id}?rev=#{rev}")
+ r.couchdb = self if r.respond_to?(:couchdb)
+ Chef::Log.info("Sending #{obj_id} to Nanite for deletion..")
+ n = Chef::Nanite.request(
+ "/index/delete",
+ {
+ :id => obj_id,
+ :database => couchdb_database,
+ :type => obj_type
+ },
+ :persistent => true
+ ) do |response_full|
+ Chef::Log.debug("Finished Deleting #{obj_type} #{obj_id} in #{couchdb_database}");
+ end
+ r
end
def list(view, inflate=false)
@@ -119,10 +195,13 @@ class Chef
}
)
if inflate
- @rest.get_rest(view_uri(view, "all"))
+ r = @rest.get_rest(view_uri(view, "all"))
+ r["rows"].each { |i| i["value"].couchdb = self if i["value"].respond_to?(:couchdb=) }
+ r
else
- @rest.get_rest(view_uri(view, "all_id"))
+ r = @rest.get_rest(view_uri(view, "all_id"))
end
+ r
end
def has_key?(obj_type, name)
@@ -137,13 +216,25 @@ class Chef
}
)
begin
- @rest.get_rest("#{Chef::Config[:couchdb_database]}/#{obj_type}_#{safe_name(name)}")
+ find_by_name(obj_type, name)
true
rescue
false
end
end
+ def find_by_name(obj_type, name, with_id=false)
+ r = get_view("id_map", "name_to_id", :key => [ obj_type, name ], :include_docs => true)
+ if r["rows"].length == 0
+ raise Chef::Exceptions::CouchDBNotFound, "Cannot find #{obj_type} #{name} in CouchDB!"
+ end
+ if with_id
+ [ r["rows"][0]["doc"], r["rows"][0]["id"] ]
+ else
+ r["rows"][0]["doc"]
+ end
+ end
+
def get_view(design, view, options={})
view_string = view_uri(design, view)
view_string << "?" if options.length != 0
@@ -151,14 +242,21 @@ class Chef
options.each { |k,v| view_string << "#{first ? '' : '&'}#{k}=#{URI.escape(v.to_json)}"; first = false }
@rest.get_rest(view_string)
end
+
+ def bulk_get(*to_fetch)
+ response = @rest.post_rest("#{couchdb_database}/_all_docs?include_docs=true", { "keys" => to_fetch.flatten })
+ response["rows"].collect { |r| r["doc"].couchdb = self; r["doc"] }
+ end
def view_uri(design, view)
- Chef::Config[:couchdb_version] ||= @rest.run_request(:GET, URI.parse(@rest.url + "/"), false, 10, false)["version"].gsub(/-.+/,"").to_f
+ Chef::Config[:couchdb_version] ||= @rest.run_request(:GET, URI.parse(@rest.url + "/"), {}, false, 10, false)["version"].gsub(/-.+/,"").to_f
case Chef::Config[:couchdb_version]
when 0.9
- "#{Chef::Config[:couchdb_database]}/_design/#{design}/_view/#{view}"
+ "#{couchdb_database}/_design/#{design}/_view/#{view}"
when 0.8
- "#{Chef::Config[:couchdb_database]}/_view/#{design}/#{view}"
+ "#{couchdb_database}/_view/#{design}/#{view}"
+ else
+ "#{couchdb_database}/_design/#{design}/_view/#{view}"
end
end
@@ -167,6 +265,6 @@ class Chef
def safe_name(name)
name.gsub(/\./, "_")
end
-
+
end
end
diff --git a/chef/lib/chef/data_bag.rb b/chef/lib/chef/data_bag.rb
new file mode 100644
index 0000000000..e23ce04c83
--- /dev/null
+++ b/chef/lib/chef/data_bag.rb
@@ -0,0 +1,167 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2009 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require 'chef/config'
+require 'chef/mixin/params_validate'
+require 'chef/mixin/from_file'
+require 'chef/couchdb'
+require 'chef/data_bag_item'
+require 'extlib'
+require 'json'
+
+class Chef
+ class DataBag
+
+ include Chef::Mixin::FromFile
+ include Chef::Mixin::ParamsValidate
+
+ DESIGN_DOCUMENT = {
+ "version" => 2,
+ "language" => "javascript",
+ "views" => {
+ "all" => {
+ "map" => <<-EOJS
+ function(doc) {
+ if (doc.chef_type == "data_bag") {
+ emit(doc.name, doc);
+ }
+ }
+ EOJS
+ },
+ "all_id" => {
+ "map" => <<-EOJS
+ function(doc) {
+ if (doc.chef_type == "data_bag") {
+ emit(doc.name, doc.name);
+ }
+ }
+ EOJS
+ },
+ "entries" => {
+ "map" => <<-EOJS
+ function(doc) {
+ if (doc.chef_type == "data_bag_item") {
+ emit(doc.data_bag, doc.raw_data.id);
+ }
+ }
+ EOJS
+ }
+ }
+ }
+
+ attr_accessor :couchdb_rev
+
+ # Create a new Chef::DataBag
+ def initialize
+ @name = ''
+ @couchdb_rev = nil
+ @couchdb = Chef::CouchDB.new
+ end
+
+ def name(arg=nil)
+ set_or_return(
+ :name,
+ arg,
+ :regex => /^[\-[:alnum:]_]+$/
+ )
+ end
+
+ def to_hash
+ result = {
+ "name" => @name,
+ 'json_class' => self.class.name,
+ "chef_type" => "data_bag",
+ }
+ result["_rev"] = @couchdb_rev if @couchdb_rev
+ result
+ end
+
+ # Serialize this object as a hash
+ def to_json(*a)
+ to_hash.to_json(*a)
+ end
+
+ # Create a Chef::Role from JSON
+ def self.json_create(o)
+ bag = new
+ bag.name(o["name"])
+ bag.couchdb_rev = o["_rev"] if o.has_key?("_rev")
+ bag
+ end
+
+ # List all the Chef::DataBag objects in the CouchDB. If inflate is set to true, you will get
+ # the full list of all Roles, fully inflated.
+ def self.list(inflate=false)
+ couchdb = Chef::CouchDB.new
+ rs = couchdb.list("data_bags", inflate)
+ if inflate
+ rs["rows"].collect { |r| r["value"] }
+ else
+ rs["rows"].collect { |r| r["key"] }
+ end
+ end
+
+ # Load a Data Bag by name from CouchDB
+ def self.load(name)
+ couchdb = Chef::CouchDB.new
+ couchdb.load("data_bag", name)
+ end
+
+ # Remove this Data Bag from CouchDB
+ def destroy
+ removed = @couchdb.delete("data_bag", @name, @couchdb_rev)
+ rs = @couchdb.get_view("data_bags", "entries", :include_docs => true, :startkey => @name, :endkey => @name)
+ rs["rows"].each do |row|
+ row["doc"].couchdb = @couchdb
+ row["doc"].destroy
+ end
+ removed
+ end
+
+ # Save this Data Bag to the CouchDB
+ def save
+ results = @couchdb.store("data_bag", @name, self)
+ @couchdb_rev = results["rev"]
+ end
+
+ # List all the items in this Bag
+ def list(inflate=false)
+ rs = nil
+ if inflate
+ rs = @couchdb.get_view("data_bags", "entries", :include_docs => true, :startkey => @name, :endkey => @name)
+ rs["rows"].collect { |r| r["doc"].couchdb = @couchdb; r["doc"] }
+ else
+ rs = @couchdb.get_view("data_bags", "entries", :startkey => @name, :endkey => @name)
+ rs["rows"].collect { |r| r["value"] }
+ end
+ end
+
+ # Set up our CouchDB design document
+ def self.create_design_document
+ couchdb = Chef::CouchDB.new
+ couchdb.create_design_document("data_bags", DESIGN_DOCUMENT)
+ end
+
+ # As a string
+ def to_s
+ "data_bag[#{@name}]"
+ end
+
+ end
+end
+
diff --git a/chef/lib/chef/data_bag_item.rb b/chef/lib/chef/data_bag_item.rb
new file mode 100644
index 0000000000..b59f4314da
--- /dev/null
+++ b/chef/lib/chef/data_bag_item.rb
@@ -0,0 +1,188 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2009 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require 'chef/config'
+require 'chef/mixin/params_validate'
+require 'chef/mixin/from_file'
+require 'chef/couchdb'
+require 'extlib'
+require 'json'
+
+class Chef
+ class DataBagItem
+
+ include Chef::Mixin::FromFile
+ include Chef::Mixin::ParamsValidate
+
+ DESIGN_DOCUMENT = {
+ "version" => 1,
+ "language" => "javascript",
+ "views" => {
+ "all" => {
+ "map" => <<-EOJS
+ function(doc) {
+ if (doc.chef_type == "data_bag_item") {
+ emit(doc.name, doc);
+ }
+ }
+ EOJS
+ },
+ "all_id" => {
+ "map" => <<-EOJS
+ function(doc) {
+ if (doc.chef_type == "data_bag_item") {
+ emit(doc.name, doc.name);
+ }
+ }
+ EOJS
+ }
+ }
+ }
+
+ attr_accessor :couchdb_rev, :raw_data
+
+ # Create a new Chef::DataBagItem
+ def initialize(couchdb=nil)
+ @couchdb_rev = nil
+ @data_bag = nil
+ @raw_data = Hash.new
+ @couchdb = Chef::CouchDB.new
+ end
+
+ def raw_data
+ @raw_data
+ end
+
+ def raw_data=(new_data)
+ unless new_data.kind_of?(Hash) || new_data.kind_of?(Mash)
+ raise ArgumentError, "Data Bag Items must contain a Hash or Mash!"
+ end
+ unless new_data.has_key?("id")
+ raise ArgumentError, "Data Bag Items must have an id key in the hash! #{new_data.inspect}"
+ end
+ unless new_data["id"] =~ /^[\-[:alnum:]_]+$/
+ raise ArgumentError, "Data Bag Item id does not match alphanumeric/-/_!"
+ end
+ @raw_data = new_data
+ end
+
+ def data_bag(arg=nil)
+ set_or_return(
+ :data_bag,
+ arg,
+ :regex => /^[\-[:alnum:]_]+$/
+ )
+ end
+
+ def name
+ object_name
+ end
+
+ def object_name
+ if raw_data.has_key?('id')
+ id = raw_data['id']
+ else
+ raise ArgumentError, "You must have an 'id' or :id key in the raw data"
+ end
+
+ data_bag_name = self.data_bag
+ unless data_bag_name
+ raise ArgumentError, "You must have declared what bag this item belongs to!"
+ end
+ "data_bag_item_#{data_bag_name}_#{id}"
+ end
+
+ def self.object_name(data_bag_name, id)
+ "data_bag_item_#{data_bag_name}_#{id}"
+ end
+
+ def to_hash
+ result = self.raw_data
+ result["chef_type"] = "data_bag_item"
+ result["data_bag"] = self.data_bag
+ result["_rev"] = @couchdb_rev if @couchdb_rev
+ result
+ end
+
+ # Serialize this object as a hash
+ def to_json(*a)
+ result = {
+ "name" => self.object_name,
+ "json_class" => self.class.name,
+ "chef_type" => "data_bag_item",
+ "data_bag" => self.data_bag,
+ "raw_data" => self.raw_data
+ }
+ result["_rev"] = @couchdb_rev if @couchdb_rev
+ result.to_json(*a)
+ end
+
+ # Create a Chef::DataBagItem from JSON
+ def self.json_create(o)
+ bag_item = new
+ bag_item.data_bag(o["data_bag"])
+ o.delete("data_bag")
+ o.delete("chef_type")
+ o.delete("json_class")
+ o.delete("name")
+ if o.has_key?("_rev")
+ bag_item.couchdb_rev = o["_rev"]
+ o.delete("_rev")
+ end
+ bag_item.raw_data = o["raw_data"]
+ bag_item
+ end
+
+ # The Data Bag Item behaves like a hash - we pass all that stuff along to @raw_data.
+ def method_missing(method_symbol, *args, &block)
+ self.raw_data.send(method_symbol, *args, &block)
+ end
+
+ # Load a Data Bag Item by name from CouchDB
+ def self.load(data_bag, name)
+ couchdb = Chef::CouchDB.new
+ couchdb.load("data_bag_item", object_name(data_bag, name))
+ end
+
+ # Remove this Data Bag Item from CouchDB
+ def destroy
+ removed = @couchdb.delete("data_bag_item", object_name, @couchdb_rev)
+ removed
+ end
+
+ # Save this Data Bag Item to CouchDB
+ def save
+ results = @couchdb.store("data_bag_item", object_name, self)
+ @couchdb_rev = results["rev"]
+ end
+
+ # Set up our CouchDB design document
+ def self.create_design_document
+ couchdb = Chef::CouchDB.new
+ couchdb.create_design_document("data_bag_items", DESIGN_DOCUMENT)
+ end
+
+ # As a string
+ def to_s
+ "data_bag_item[#{@name}]"
+ end
+
+ end
+end
+
+
diff --git a/chef/lib/chef/exceptions.rb b/chef/lib/chef/exceptions.rb
index 7ab7fa5664..13b93502cc 100644
--- a/chef/lib/chef/exceptions.rb
+++ b/chef/lib/chef/exceptions.rb
@@ -32,5 +32,8 @@ class Chef
class Group < RuntimeError; end
class Link < RuntimeError; end
class Mount < RuntimeError; end
+ class CouchDBNotFound < RuntimeError; end
+ class PrivateKeyMissing < RuntimeError; end
+ class CannotWritePrivateKey < RuntimeError; end
end
end
diff --git a/chef/lib/chef/mixin/generate_url.rb b/chef/lib/chef/mixin/generate_url.rb
index 0ea7cc7a31..6a3bfbb73e 100644
--- a/chef/lib/chef/mixin/generate_url.rb
+++ b/chef/lib/chef/mixin/generate_url.rb
@@ -29,19 +29,28 @@ class Chef
else
new_url = "cookbooks/#{cookbook}/#{type}?"
new_url += "id=#{url}"
- platform, version = Chef::Platform.find_platform_and_version(node)
- if type == "files" || type == "templates"
- new_url += "&platform=#{platform}&version=#{version}&fqdn=#{node[:fqdn]}&node_name=#{node.name}"
- end
- if args
- args.each do |key, value|
- new_url += "&#{key}=#{value}"
- end
- end
+ new_url = generate_cookbook_url_from_uri(new_url, node, args)
end
return new_url
end
+
+ def generate_cookbook_url_from_uri(uri, node, args=nil)
+ platform, version = Chef::Platform.find_platform_and_version(node)
+ uri =~ /cookbooks\/(.+?)\/(.+)\?/
+ cookbook = $1
+ type = $2
+ if type == "files" || type == "templates"
+ uri += "&platform=#{platform}&version=#{version}&fqdn=#{node[:fqdn]}&node_name=#{node.name}"
+ end
+ if args
+ args.each do |key, value|
+ uri += "&#{key}=#{value}"
+ end
+ end
+
+ uri
+ end
end
end
diff --git a/chef/lib/chef/mixin/language.rb b/chef/lib/chef/mixin/language.rb
index cf049926de..704dbf0c05 100644
--- a/chef/lib/chef/mixin/language.rb
+++ b/chef/lib/chef/mixin/language.rb
@@ -16,6 +16,8 @@
# limitations under the License.
#
+require 'chef/search/query'
+
class Chef
module Mixin
module Language
@@ -73,6 +75,10 @@ class Chef
has_platform
end
+
+ def search(*args, &block)
+ Chef::Search::Query.new.search(*args, &block)
+ end
end
end
diff --git a/chef/lib/chef/nanite.rb b/chef/lib/chef/nanite.rb
new file mode 100644
index 0000000000..a0f4827a68
--- /dev/null
+++ b/chef/lib/chef/nanite.rb
@@ -0,0 +1,84 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require 'chef'
+require 'chef/config'
+require 'chef/mixin/params_validate'
+require 'nanite'
+require 'json'
+
+class Chef
+ class Nanite
+
+ class << self
+
+ def start_mapper(config={})
+ Chef::Log.info("Running the Nanite Mapper")
+ ::Nanite::Log.logger = Chef::Log.logger
+ identity = Chef::Config[:nanite_identity] ? Chef::Config[:nanite_identity] : get_identity
+ ::Nanite.start_mapper(
+ :host => Chef::Config[:nanite_host],
+ :user => Chef::Config[:nanite_user],
+ :pass => Chef::Config[:nanite_pass],
+ :vhost => Chef::Config[:nanite_vhost],
+ :identity => identity,
+ :format => :json,
+ :log_level => Chef::Config[:log_level]
+ )
+ end
+
+ def get_identity(type="mapper")
+ id = nil
+ if Chef::FileCache.has_key?("nanite-#{type}-identity")
+ id = Chef::FileCache.load("nanite-#{type}-identity")
+ else
+ id = ::Nanite::Identity.generate
+ Chef::FileCache.store("nanite-#{type}-identity", id)
+ end
+ id
+ end
+
+ def request(*args)
+ in_event do
+ ::Nanite.request(*args)
+ end
+ end
+
+ def in_event(&block)
+ if EM.reactor_running?
+ begin
+ ::Nanite.ensure_mapper
+ rescue ::Nanite::MapperNotRunning
+ start_mapper
+ end
+ block.call
+ else
+ Chef::Log.warn("Starting Event Machine Loop")
+ Thread.new do
+ EM.run do
+ start_mapper
+ block.call
+ end
+ end
+ end
+ end
+
+ end
+
+ end
+end
diff --git a/chef/lib/chef/node.rb b/chef/lib/chef/node.rb
index 824db99c2b..11ee5d471f 100644
--- a/chef/lib/chef/node.rb
+++ b/chef/lib/chef/node.rb
@@ -21,7 +21,6 @@ require 'chef/mixin/check_helper'
require 'chef/mixin/params_validate'
require 'chef/mixin/from_file'
require 'chef/couchdb'
-require 'chef/queue'
require 'chef/run_list'
require 'chef/node/attribute'
require 'extlib'
@@ -30,14 +29,14 @@ require 'json'
class Chef
class Node
- attr_accessor :attribute, :recipe_list, :couchdb_rev, :run_state, :run_list, :override, :default
+ attr_accessor :attribute, :recipe_list, :couchdb_rev, :couchdb_id, :run_state, :run_list, :override, :default
include Chef::Mixin::CheckHelper
include Chef::Mixin::FromFile
include Chef::Mixin::ParamsValidate
DESIGN_DOCUMENT = {
- "version" => 8,
+ "version" => 9,
"language" => "javascript",
"views" => {
"all" => {
@@ -120,7 +119,7 @@ class Chef
}
# Create a new Chef::Node object.
- def initialize()
+ def initialize
@name = nil
@attribute = Mash.new
@@ -129,6 +128,7 @@ class Chef
@run_list = Chef::RunList.new
@couchdb_rev = nil
+ @couchdb_id = nil
@couchdb = Chef::CouchDB.new
@run_state = {
:template_cache => Hash.new,
@@ -279,53 +279,32 @@ class Chef
@run_list.detect { |r| r == item } ? true : false
end
- # Turns the node into an object that we can index. I apologize up front for the
- # super confusion that is the recursive index_flatten hash, which comes up next.
- # Faith, young one, faith.
- #
- # === Returns
- # index_hash<Hash>:: A flattened hash of all the nodes attributes, suitable for indexing.
- def to_index
- index_hash = {
- "index_name" => "node",
- "id" => "node_#{@name}",
- "name" => @name,
- }
- @attribute.each do |key, value|
- if value.kind_of?(Hash) || value.kind_of?(Mash)
- index_flatten_hash(key, value).each do |to_index|
- to_index.each do |nk, nv|
- index_hash[nk] = nv
- end
- end
+ # Set an attribute based on the missing method. If you pass an argument, we'll use that
+ # to set the attribute values. Otherwise, we'll wind up just returning the attributes
+ # value.
+ def method_missing(symbol, *args)
+ if args.length != 0
+ @attribute[symbol] = args.length == 1 ? args[0] : args
+ else
+ if @attribute.has_key?(symbol)
+ @attribute[symbol]
else
- index_hash[key] = value
+ raise ArgumentError, "Attribute #{symbol.to_s} is not defined!"
end
end
- index_hash["recipe"] = @run_list.recipes if @run_list.recipes.length > 0
+ end
+
+ # Transform the node to a Hash
+ def to_hash
+ index_hash = @attribute
+ index_hash["chef_type"] = "node"
+ index_hash["name"] = @name
+ index_hash["recipes"] = @run_list.recipes if @run_list.recipes.length > 0
index_hash["roles"] = @run_list.roles if @run_list.roles.length > 0
index_hash["run_list"] = @run_list.run_list if @run_list.run_list.length > 0
index_hash
end
- # Ah, song of my heart, index_flatten_hash. This method flattens a hash in preparation
- # for indexing, by appending the name of it's parent to a current key with an _. Hence,
- # node[:bar][:baz] = 'monkey' becomes bar_baz:monkey.
- #
- # === Returns
- # results<Array>:: An array of hashes with one element.
- def index_flatten_hash(parent_name, hash)
- results = Array.new
- hash.each do |k, v|
- if v.kind_of?(Hash) || v.kind_of?(Mash)
- results << index_flatten_hash("#{parent_name}_#{k}", v)
- else
- results << { "#{parent_name}_#{k}", v }
- end
- end
- results.flatten
- end
-
# Serialize this object as a hash
def to_json(*a)
result = {
@@ -358,13 +337,15 @@ class Chef
o["recipes"].each { |r| node.recipes << r }
end
node.couchdb_rev = o["_rev"] if o.has_key?("_rev")
+ node.couchdb_id = o["_id"] if o.has_key?("_id")
node
end
# List all the Chef::Node objects in the CouchDB. If inflate is set to true, you will get
# the full list of all Nodes, fully inflated.
def self.list(inflate=false)
- rs = Chef::CouchDB.new.list("nodes", inflate)
+ couchdb = Chef::CouchDB.new
+ rs = couchdb.list("nodes", inflate)
if inflate
rs["rows"].collect { |r| r["value"] }
else
@@ -374,25 +355,25 @@ class Chef
# Load a node by name from CouchDB
def self.load(name)
- Chef::CouchDB.new.load("node", name)
+ couchdb = Chef::CouchDB.new
+ couchdb.load("node", name)
end
# Remove this node from the CouchDB
def destroy
- Chef::Queue.send_msg(:queue, :remove, self)
@couchdb.delete("node", @name, @couchdb_rev)
end
# Save this node to the CouchDB
def save
- Chef::Queue.send_msg(:queue, :index, self)
results = @couchdb.store("node", @name, self)
@couchdb_rev = results["rev"]
end
-
+
# Set up our CouchDB design document
def self.create_design_document
- Chef::CouchDB.new.create_design_document("nodes", DESIGN_DOCUMENT)
+ couchdb = Chef::CouchDB.new
+ couchdb.create_design_document("nodes", DESIGN_DOCUMENT)
end
# As a string
diff --git a/chef/lib/chef/openid_registration.rb b/chef/lib/chef/openid_registration.rb
index 0939746494..dbe1b1d45c 100644
--- a/chef/lib/chef/openid_registration.rb
+++ b/chef/lib/chef/openid_registration.rb
@@ -178,4 +178,4 @@ class Chef
end
end
-end \ No newline at end of file
+end
diff --git a/chef/lib/chef/platform.rb b/chef/lib/chef/platform.rb
index e5b0be0d1d..fa24d6a5c8 100644
--- a/chef/lib/chef/platform.rb
+++ b/chef/lib/chef/platform.rb
@@ -142,7 +142,6 @@ class Chef
if pmap.has_key?(rtkey)
pmap[rtkey]
else
- Chef::Log.error("#{rtkey.inspect} #{pmap.inspect}")
raise(
ArgumentError,
"Cannot find a provider for #{resource_type} on #{platform} version #{version}"
diff --git a/chef/lib/chef/provider/http_request.rb b/chef/lib/chef/provider/http_request.rb
index a93c1c062a..447df10b0a 100644
--- a/chef/lib/chef/provider/http_request.rb
+++ b/chef/lib/chef/provider/http_request.rb
@@ -25,7 +25,7 @@ class Chef
attr_accessor :rest
def load_current_resource
- @rest = Chef::REST.new(@new_resource.url)
+ @rest = Chef::REST.new(@new_resource.url, nil, nil)
end
# Send a GET request to @new_resource.url, with ?message=@new_resource.message
@@ -99,4 +99,4 @@ class Chef
end
end
-end \ No newline at end of file
+end
diff --git a/chef/lib/chef/provider/remote_file.rb b/chef/lib/chef/provider/remote_file.rb
index 3aff5650ff..9b5c9db04a 100644
--- a/chef/lib/chef/provider/remote_file.rb
+++ b/chef/lib/chef/provider/remote_file.rb
@@ -95,7 +95,7 @@ class Chef
begin
uri = URI.parse(source)
if uri.absolute
- r = Chef::REST.new(source)
+ r = Chef::REST.new(source, nil, nil)
Chef::Log.debug("Downloading from absolute URI: #{source}")
r.get_rest(source, true).open
end
diff --git a/chef/lib/chef/provider/template.rb b/chef/lib/chef/provider/template.rb
index e3cbbb47ed..7f5b87346d 100644
--- a/chef/lib/chef/provider/template.rb
+++ b/chef/lib/chef/provider/template.rb
@@ -34,7 +34,6 @@ class Chef
include Chef::Mixin::FindPreferredFile
def action_create
- Chef::Log.debug(@node.run_state.inspect)
raw_template_file = nil
cookbook_name = @new_resource.cookbook || @new_resource.cookbook_name
@@ -137,4 +136,4 @@ class Chef
end
end
-end \ No newline at end of file
+end
diff --git a/chef/lib/chef/queue.rb b/chef/lib/chef/queue.rb
deleted file mode 100644
index 72883b4bb2..0000000000
--- a/chef/lib/chef/queue.rb
+++ /dev/null
@@ -1,145 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# 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.
-#
-
-require 'chef/mixin/params_validate'
-require 'json'
-require 'stomp'
-
-class Chef
- class Queue
-
- @client = nil
- @queue_retry_delay = Chef::Config[:queue_retry_delay]
- @queue_retry_count = Chef::Config[:queue_retry_count]
-
- class << self
- include Chef::Mixin::ParamsValidate
-
- def connect
- queue_user = Chef::Config[:queue_user]
- queue_password = Chef::Config[:queue_password]
- queue_host = Chef::Config[:queue_host]
- queue_port = Chef::Config[:queue_port]
- queue_retries = 1 unless queue_retries
-
- # Connection.open(login = "", passcode = "", host='localhost', port=61613, reliable=FALSE, reconnectDelay=5)
- @client = Stomp::Connection.open(queue_user, queue_password, queue_host, queue_port, false)
-
- rescue Errno::ECONNREFUSED
- Chef::Log.error("Connection refused connecting to stomp queue at #{queue_host}:#{queue_port}, retry #{queue_retries}/#{@queue_retry_count}")
- sleep(@queue_retry_delay)
- retry if (queue_retries += 1) < @queue_retry_count
- raise Errno::ECONNREFUSED, "Connection refused connecting to stomp queue at #{queue_host}:#{queue_port}, giving up"
- rescue Timeout::Error
- Chef::Log.error("Timeout connecting to stomp queue at #{queue_host}:#{queue_port}, retry #{queue_retries}/#{@queue_retry_count}")
- sleep(@queue_retry_delay)
- retry if (queue_retries += 1) < @queue_retry_count
- raise Timeout::Error, "Timeout connecting to stomp queue at #{queue_host}:#{queue_port}, giving up"
- else
- queue_retries = 1 # reset the number of retries on success
- end
-
- def make_url(type, name)
- validate(
- {
- :queue_type => type.to_sym,
- :queue_name => name.to_sym,
- },
- {
- :queue_type => {
- :equal_to => [ :topic, :queue ],
- },
- :queue_name => {
- :kind_of => [ String, Symbol ],
- }
- }
- )
- if Chef::Config[:queue_prefix]
- queue_prefix = Chef::Config[:queue_prefix]
- queue_url = "/#{type}/#{queue_prefix}/chef/#{name}"
- else
- queue_url = "/#{type}/chef/#{name}"
- end
- queue_url
- end
-
- def subscribe(type, name)
- queue_url = make_url(type, name)
- Chef::Log.debug("Subscribing to #{queue_url}")
- connect if @client == nil
- @client.subscribe(queue_url)
- end
-
- def send_msg(type, name, msg)
- queue_retries = 1 unless queue_retries
- validate(
- {
- :message => msg,
- },
- {
- :message => {
- :respond_to => :to_json
- }
- }
- )
- queue_url = make_url(type, name)
- json = msg.to_json
- connect if @client == nil
- Chef::Log.debug("Sending to #{queue_url}: #{json}")
- begin
- @client.send(queue_url, json)
- rescue Errno::EPIPE
- Chef::Log.debug("Lost connection to stomp queue, reconnecting")
- connect
- retry if (queue_retries += 1) < @queue_retry_count
- raise Errno::EPIPE, "Lost connection to stomp queue, giving up"
- else
- queue_retries = 1 # reset the number of retries on success
- end
- end
-
- def receive_msg
- connect if @client == nil
- begin
- raw_msg = @client.receive()
- Chef::Log.debug("Received Message from #{raw_msg.headers["destination"]} containing: #{raw_msg.body}")
- rescue
- Chef::Log.debug("Received nil message from stomp, retrying")
- retry
- end
- msg = JSON.parse(raw_msg.body)
- return msg, raw_msg.headers
- end
-
- def poll_msg
- connect if @client == nil
- raw_msg = @client.poll()
- if raw_msg
- msg = JSON.parse(raw_msg.body)
- else
- nil
- end
- end
-
- def disconnect
- raise ArgumentError, "You must call connect before you can disconnect!" unless @client
- @client.disconnect
- end
- end
- end
-end
diff --git a/chef/lib/chef/recipe.rb b/chef/lib/chef/recipe.rb
index 5421c08b57..d3e64d0737 100644
--- a/chef/lib/chef/recipe.rb
+++ b/chef/lib/chef/recipe.rb
@@ -23,7 +23,6 @@ require 'chef/mixin/language'
require 'chef/resource_collection'
require 'chef/cookbook_loader'
require 'chef/rest'
-require 'chef/search/result'
class Chef
class Recipe
@@ -89,21 +88,6 @@ class Chef
@collection.resources(*args)
end
- def search(type, query, attributes=[], &block)
- Chef::Log.debug("Searching #{type} index with #{query}")
- r = Chef::REST.new(Chef::Config[:search_url])
-
- results = r.get_rest("search/#{type}?q=#{query}&a=#{attributes.join(',')}")
- Chef::Log.debug("Searching #{type} index with #{query} returned #{results.length} entries")
- if block
- results.each do |sr|
- block.call(sr)
- end
- else
- results
- end
- end
-
# Sets a tag, or list of tags, for this node. Syntactic sugar for
# @node[:tags].
#
diff --git a/chef/lib/chef/rest.rb b/chef/lib/chef/rest.rb
index 229fac2cdc..0e40068061 100644
--- a/chef/lib/chef/rest.rb
+++ b/chef/lib/chef/rest.rb
@@ -1,7 +1,8 @@
#
# Author:: Adam Jacob (<adam@opscode.com>)
# Author:: Thom May (<thom@clearairturbulence.org>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Author:: Nuo Yan (<nuo@opscode.com>)
+# Copyright:: Copyright (c) 2009 Opscode, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,6 +25,9 @@ require 'uri'
require 'json'
require 'tempfile'
require 'singleton'
+require 'mixlib/auth/signedheaderauth'
+
+include Mixlib::Auth::SignedHeaderAuth
class Chef
class REST
@@ -32,47 +36,57 @@ class Chef
include Singleton
end
- attr_accessor :url, :cookies
+ attr_accessor :url, :cookies, :signing_key
- def initialize(url)
+ def initialize(url, client_name=Chef::Config[:node_name], signing_key=Chef::Config[:client_key])
@url = url
@cookies = CookieJar.instance
+ @client_name = client_name
+ if signing_key
+ @signing_key = load_signing_key(signing_key)
+ else
+ @signing_key = nil
+ end
+ end
+
+ def load_signing_key(key)
+ if File.exists?(key) && File.readable?(key)
+ IO.read(key)
+ else
+ raise Chef::Exceptions::PrivateKeyMissing, "I cannot find #{key}, which you told me to use to sign requests!"
+ end
end
- # Register for an OpenID
- def register(user, pass, validation_token=nil)
- Chef::Log.debug("Registering #{user} for an openid")
- registration = nil
+ # Register the client
+ def register(name=Chef::Config[:node_name], destination=Chef::Config[:client_key])
+
+ if File.exists?(destination)
+ raise Chef::Exceptions::CannotWritePrivateKey, "I cannot write your private key to #{destination} - check permissions?" unless File.writable?(destination)
+ end
+
+ # First, try and create a new registration
begin
- registration = get_rest("registrations/#{user}")
- rescue Net::HTTPServerException => e
- unless e.message =~ /^404/
- raise e
- end
+ Chef::Log.info("Registering API Client #{name}")
+ response = post_rest("clients", {:clientname => name})
+ rescue Net::HTTPServerException
+ # If that fails, go ahead and try and update it
+ response = put_rest("clients/#{name}", { :clientname => name, :private_key => true })
end
- unless registration
- post_rest(
- "registrations",
- {
- :id => user,
- :password => pass,
- :validation_token => validation_token
- }
- )
+
+ Chef::Log.debug("Registration response: #{response.inspect}")
+
+ raise Chef::Exceptions::CannotWritePrivateKey, "The response from the server did not include a private key!" unless response.has_key?("private_key")
+
+ begin
+ # Write out the private key
+ file = File.open(destination, "w")
+ file.print(response["private_key"])
+ file.close
+ rescue
+ raise Chef::Exceptions::CannotWritePrivateKey, "I cannot write your private key to #{destination}"
end
- end
-
- # Authenticate
- def authenticate(user, pass)
- Chef::Log.debug("Authenticating #{user} via openid")
- response = post_rest('openid/consumer/start', {
- "openid_identifier" => "#{Chef::Config[:openid_url]}/openid/server/node/#{user}",
- "submit" => "Verify"
- })
- post_rest(
- "#{Chef::Config[:openid_url]}#{response["action"]}",
- { "password" => pass }
- )
+
+ true
end
# Send an HTTP GET request to the path
@@ -81,23 +95,23 @@ class Chef
# path:: The path to GET
# raw:: Whether you want the raw body returned, or JSON inflated. Defaults
# to JSON inflated.
- def get_rest(path, raw=false)
- run_request(:GET, create_url(path), false, 10, raw)
+ def get_rest(path, raw=false, headers={})
+ run_request(:GET, create_url(path), headers, false, 10, raw)
end
# Send an HTTP DELETE request to the path
- def delete_rest(path)
- run_request(:DELETE, create_url(path))
+ def delete_rest(path, headers={})
+ run_request(:DELETE, create_url(path), headers)
end
# Send an HTTP POST request to the path
- def post_rest(path, json)
- run_request(:POST, create_url(path), json)
+ def post_rest(path, json, headers={})
+ run_request(:POST, create_url(path), headers, json)
end
# Send an HTTP PUT request to the path
- def put_rest(path, json)
- run_request(:PUT, create_url(path), json)
+ def put_rest(path, json, headers={})
+ run_request(:PUT, create_url(path), headers, json)
end
def create_url(path)
@@ -108,6 +122,18 @@ class Chef
end
end
+ def sign_request(http_method, private_key, user_id, body = "", host="localhost")
+ #body = "" if body == false
+ timestamp = Time.now.utc.iso8601
+ sign_obj = Mixlib::Auth::SignedHeaderAuth.signing_object(
+ :http_method=>http_method,
+ :body=>body,
+ :user_id=>user_id,
+ :timestamp=>timestamp)
+ signed = sign_obj.sign(private_key).merge({:host => host})
+ signed.inject({}){|memo, kv| memo["#{kv[0].to_s.upcase}"] = kv[1];memo}
+ end
+
# Actually run an HTTP request. First argument is the HTTP method,
# which should be one of :GET, :PUT, :POST or :DELETE. Next is the
# URL, then an object to include in the body (which will be converted with
@@ -117,7 +143,8 @@ class Chef
# the helper methods (get_rest, post_rest, etc.)
#
# Will return the body of the response on success.
- def run_request(method, url, data=false, limit=10, raw=false)
+ def run_request(method, url, headers={}, data=false, limit=10, raw=false)
+
http_retry_delay = Chef::Config[:http_retry_delay]
http_retry_count = Chef::Config[:http_retry_count]
@@ -129,21 +156,35 @@ class Chef
if Chef::Config[:ssl_verify_mode] == :verify_none
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
- if File.exists?(Chef::Config[:ssl_client_cert])
+ if Chef::Config[:ssl_client_cert] && File.exists?(Chef::Config[:ssl_client_cert])
http.cert = OpenSSL::X509::Certificate.new(File.read(Chef::Config[:ssl_client_cert]))
http.key = OpenSSL::PKey::RSA.new(File.read(Chef::Config[:ssl_client_key]))
end
end
+
http.read_timeout = Chef::Config[:rest_timeout]
- headers = Hash.new
+
unless raw
- headers = {
+ headers = headers.merge({
'Accept' => "application/json",
- }
+ })
end
+
if @cookies.has_key?("#{url.host}:#{url.port}")
headers['Cookie'] = @cookies["#{url.host}:#{url.port}"]
end
+
+ json_body = data ? data.to_json : nil
+
+ if @signing_key
+ Chef::Log.debug("Signing the request as #{@client_name}")
+ if json_body
+ headers.merge!(sign_request(method, OpenSSL::PKey::RSA.new(@signing_key), @client_name, json_body, "#{url.host}:#{url.port}"))
+ else
+ headers.merge!(sign_request(method, OpenSSL::PKey::RSA.new(@signing_key), @client_name, "", "#{url.host}:#{url.port}"))
+ end
+ end
+
req = nil
case method
when :GET
@@ -152,12 +193,16 @@ class Chef
req = Net::HTTP::Get.new(req_path, headers)
when :POST
headers["Content-Type"] = 'application/json' if data
- req = Net::HTTP::Post.new(url.path, headers)
- req.body = data.to_json if data
+ req_path = "#{url.path}"
+ req_path << "?#{url.query}" if url.query
+ req = Net::HTTP::Post.new(req_path, headers)
+ req.body = json_body if json_body
when :PUT
headers["Content-Type"] = 'application/json' if data
- req = Net::HTTP::Put.new(url.path, headers)
- req.body = data.to_json if data
+ req_path = "#{url.path}"
+ req_path << "?#{url.query}" if url.query
+ req = Net::HTTP::Put.new(req_path, headers)
+ req.body = json_body if json_body
when :DELETE
req_path = "#{url.path}"
req_path << "?#{url.query}" if url.query
@@ -165,6 +210,8 @@ class Chef
else
raise ArgumentError, "You must provide :GET, :PUT, :POST or :DELETE as the method"
end
+
+ Chef::Log.debug("Sending HTTP Request via #{req.method} to #{url.host}:#{url.port}/#{req.path}")
# Optionally handle HTTP Basic Authentication
req.basic_auth(url.user, url.password) if url.user
@@ -199,7 +246,8 @@ class Chef
end
response
end
- rescue Errno::ECONNREFUSED
+
+ rescue Errno::ECONNREFUSED => e
Chef::Log.error("Connection refused connecting to #{url.host}:#{url.port} for #{req.path} #{http_retries}/#{http_retry_count}")
sleep(http_retry_delay)
retry if (http_retries += 1) < http_retry_count
@@ -216,7 +264,8 @@ class Chef
@cookies["#{url.host}:#{url.port}"] = res['set-cookie']
end
if res['content-type'] =~ /json/
- JSON.parse(res.body)
+ response_body = res.body.chomp
+ JSON.parse(response_body)
else
if raw
tf
@@ -228,11 +277,11 @@ class Chef
if res['set-cookie']
@cookies["#{url.host}:#{url.port}"] = res['set-cookie']
end
- run_request(:GET, create_url(res['location']), false, limit - 1, raw)
+ run_request(:GET, create_url(res['location']), {}, false, limit - 1, raw)
else
res.error!
end
end
-
+
end
end
diff --git a/chef/lib/chef/role.rb b/chef/lib/chef/role.rb
index 506a197416..461db69eaa 100644
--- a/chef/lib/chef/role.rb
+++ b/chef/lib/chef/role.rb
@@ -30,7 +30,7 @@ class Chef
include Chef::Mixin::ParamsValidate
DESIGN_DOCUMENT = {
- "version" => 3,
+ "version" => 6,
"language" => "javascript",
"views" => {
"all" => {
@@ -50,21 +50,22 @@ class Chef
}
}
EOJS
- },
- },
+ }
+ }
}
- attr_accessor :couchdb_rev
+ attr_accessor :couchdb_rev, :couchdb_id
# Create a new Chef::Role object.
- def initialize()
+ def initialize
@name = ''
@description = ''
@default_attributes = Mash.new
@override_attributes = Mash.new
@recipes = Array.new
@couchdb_rev = nil
- @couchdb = Chef::CouchDB.new
+ @couchdb_id = nil
+ @couchdb = Chef::CouchDB.new
end
def name(arg=nil)
@@ -139,13 +140,15 @@ class Chef
role.override_attributes(o["override_attributes"])
role.recipes(o["recipes"])
role.couchdb_rev = o["_rev"] if o.has_key?("_rev")
+ role.couchdb_id = o["_id"] if o.has_key?("_id")
role
end
# List all the Chef::Role objects in the CouchDB. If inflate is set to true, you will get
# the full list of all Roles, fully inflated.
def self.list(inflate=false)
- rs = Chef::CouchDB.new.list("roles", inflate)
+ couchdb = Chef::CouchDB.new
+ rs = couchdb.list("roles", inflate)
if inflate
rs["rows"].collect { |r| r["value"] }
else
@@ -155,7 +158,8 @@ class Chef
# Load a role by name from CouchDB
def self.load(name)
- Chef::CouchDB.new.load("role", name)
+ couchdb = Chef::CouchDB.new
+ couchdb.load("role", name)
end
# Remove this role from the CouchDB
@@ -186,7 +190,8 @@ class Chef
# Set up our CouchDB design document
def self.create_design_document
- Chef::CouchDB.new.create_design_document("roles", DESIGN_DOCUMENT)
+ couchdb = Chef::CouchDB.new
+ couchdb.create_design_document("roles", DESIGN_DOCUMENT)
end
# As a string
@@ -212,6 +217,7 @@ class Chef
# Sync all the json roles with couchdb from disk
def self.sync_from_disk_to_couchdb
+ couchdb = Chef::CouchDB.new
Dir[File.join(Chef::Config[:role_path], "*.json")].each do |role_file|
short_name = File.basename(role_file, ".json")
Chef::Log.warn("Loading #{short_name}")
@@ -220,7 +226,7 @@ class Chef
couch_role = Chef::Role.load(short_name)
r.couchdb_rev = couch_role.couchdb_rev
Chef::Log.debug("Replacing role #{short_name} with data from #{role_file}")
- rescue Net::HTTPServerException
+ rescue Chef::Exceptions::CouchDBNotFound
Chef::Log.debug("Creating role #{short_name} with data from #{role_file}")
end
r.save
diff --git a/chef/lib/chef/run_list.rb b/chef/lib/chef/run_list.rb
index 6eb0157580..f565185633 100644
--- a/chef/lib/chef/run_list.rb
+++ b/chef/lib/chef/run_list.rb
@@ -111,7 +111,8 @@ class Chef
self
end
- def expand(from='server')
+ def expand(from='server', couchdb=nil)
+ couchdb = couchdb ? couchdb : Chef::CouchDB.new
recipes = Array.new
default_attrs = Mash.new
override_attrs = Mash.new
@@ -132,7 +133,7 @@ class Chef
role = r.get_rest("roles/#{name}")
elsif from == 'couchdb'
# Load the role from couchdb
- role = Chef::Role.load(name)
+ role = Chef::Role.load(name, couchdb)
end
role.recipes.each { |r| recipes << r unless recipes.include?(r) }
default_attrs = Chef::Mixin::DeepMerge.merge(default_attrs, role.default_attributes)
diff --git a/chef/lib/chef/search.rb b/chef/lib/chef/search.rb
deleted file mode 100644
index 0d0c176921..0000000000
--- a/chef/lib/chef/search.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# 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.
-#
-
-require 'chef/search/result'
-require 'ferret'
-
-class Chef
- class Search
-
- attr_reader :index
-
- def initialize
- @index = Ferret::Index::Index.new(:path => Chef::Config[:search_index_path])
- end
-
- def search(type, query="*", attributes=[], &block)
- search_query = build_search_query(type, query)
- start_time = Time.now
- results = []
- block ||= lambda { |b| b }
-
- @index.search_each(search_query, :limit => :all) do |id, score|
- results << block.call(build_hash(@index.doc(id)))
- end
-
- Chef::Log.debug("Search #{search_query} complete in #{Time.now - start_time} seconds")
-
- attributes.empty? ? results : filter_by_attributes(results,attributes)
- end
-
- def filter_by_attributes(results, attributes)
- results.collect do |r|
- nr = Hash.new
- nr[:index_name] = r[:index_name]
- nr[:id] = r[:id]
- attributes.each do |attrib|
- if r.has_key?(attrib)
- nr[attrib] = r[attrib]
- end
- end
- nr
- end
- end
-
- private :filter_by_attributes
-
- def list_indexes
- indexes = Hash.new
- @index.search_each("index_name:*", :limit => :all) do |id, score|
- indexes[@index.doc(id)["index_name"]] = true
- end
- indexes.keys
- end
-
- def has_index?(index)
- list_indexes.detect { |i| i == index }
- end
-
- private
- def build_search_query(type, query)
- query = "id:*" if query == '*'
- "index_name:#{type} AND (#{query})"
- end
-
- def build_hash(doc)
- result = Chef::Search::Result.new
- doc.fields.each do |f|
- result[f] = doc[f]
- end
- result
- end
- end
-end
diff --git a/chef/lib/chef/search/query.rb b/chef/lib/chef/search/query.rb
new file mode 100644
index 0000000000..be5e15269a
--- /dev/null
+++ b/chef/lib/chef/search/query.rb
@@ -0,0 +1,63 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require 'chef/config'
+require 'chef/node'
+require 'chef/role'
+require 'chef/data_bag'
+require 'chef/data_bag_item'
+
+class Chef
+ class Search
+ class Query
+ def initialize(url=nil)
+ url ||= Chef::Config[:search_url]
+ @rest = Chef::REST.new(url)
+ end
+
+ # Search Solr for objects of a given type, for a given query. If you give
+ # it a block, it will handle the paging for you dynamically.
+ def search(type, query="*:*", sort=nil, start=0, rows=20, &block)
+ unless type.kind_of?(String) || type.kind_of?(Symbol)
+ raise ArgumentError, "Type must be a string or a symbol!"
+ end
+
+ response = @rest.get_rest("search/#{type}?q=#{escape(query)}&sort=#{escape(sort)}&start=#{escape(start)}&rows=#{escape(rows)}")
+ if block
+ response["rows"].each { |o| block.call(o) }
+ unless (response["start"] + response["rows"].length) >= response["total"]
+ nstart = response["start"] + rows
+ search(type, query, sort, nstart, rows, &block)
+ end
+ true
+ else
+ [ response["rows"], response["start"], response["total"] ]
+ end
+ end
+
+ private
+ # escapes a query key/value for http
+ # Thanks to RSolr!
+ def escape(s)
+ s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
+ '%'+$1.unpack('H2'*$1.size).join('%').upcase
+ }.tr(' ', '+')
+ end
+ end
+ end
+end
diff --git a/chef/lib/chef/search/result.rb b/chef/lib/chef/search/result.rb
deleted file mode 100644
index 97d35132a8..0000000000
--- a/chef/lib/chef/search/result.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# 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.
-#
-
-require 'extlib'
-
-class Chef
- class Search
- class Result
-
- def initialize
- proc = lambda do |h,k|
- newhash = Mash.new(&proc)
- h.each do |pk, pv|
- rx = /^#{k.to_s}_/
- if pk =~ rx
- newhash[ pk.gsub(rx,'') ] = pv
- end
- end
- newhash
- end
- @internal = Mash.new(&proc)
- end
-
- def method_missing(symbol, *args, &block)
- @internal.send(symbol, *args, &block)
- end
-
- # Serialize this object as a hash
- def to_json(*a)
- result = {
- 'json_class' => self.class.name,
- 'results' => @internal
- }
- result.to_json(*a)
- end
-
- # Create a Chef::Search::Result from JSON
- def self.json_create(o)
- result = self.new
- o['results'].each do |k,v|
- result[k] = v
- end
- result
- end
- end
- end
-end
-
-
diff --git a/chef/lib/chef/search_index.rb b/chef/lib/chef/search_index.rb
deleted file mode 100644
index 6a76595aa1..0000000000
--- a/chef/lib/chef/search_index.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# 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.
-#
-
-require 'ferret'
-
-class Chef
- class SearchIndex
-
- attr_reader :index
-
- def initialize
- @index = Ferret::Index::Index.new(
- :path => Chef::Config[:search_index_path],
- :key => [ :id ]
- )
- end
-
- def add(new_object)
- index_hash = create_index_object(new_object)
- Chef::Log.debug("Indexing #{index_hash[:index_name]} with #{index_hash.inspect}")
- @index.add_document(index_hash)
- end
-
- def create_index_object(new_object)
- index_hash = nil
-
- if new_object.respond_to?(:to_index)
- index_hash = new_object.to_index
- elsif new_object.kind_of?(Hash)
- index_hash = new_object
- else
- raise Chef::Exceptions::SearchIndex, "Cannot transform argument to a Hash!"
- end
-
- unless index_hash.has_key?(:index_name) || index_hash.has_key?("index_name")
- raise Chef::Exceptions::SearchIndex, "Cannot index without an index_name key: #{index_hash.inspect}"
- end
-
- unless index_hash.has_key?(:id) || index_hash.has_key?("id")
- raise Chef::Exceptions::SearchIndex, "Cannot index without an id key: #{index_hash.inspect}"
- end
-
- sanitized_hash = Hash.new
- index_hash.each do |k,v|
- sanitized_hash[k.to_sym] = v
- end
-
- sanitized_hash
- end
-
- def delete(index_obj)
- to_delete = create_index_object(index_obj)
- Chef::Log.debug("Removing #{to_delete.inspect} from the #{to_delete[:index_name]} index")
- @index.delete(to_delete[:id])
- end
-
- def commit
- @index.commit
- end
-
- end
-end
diff --git a/chef/lib/chef/streaming_cookbook_uploader.rb b/chef/lib/chef/streaming_cookbook_uploader.rb
new file mode 100644
index 0000000000..73c9034b42
--- /dev/null
+++ b/chef/lib/chef/streaming_cookbook_uploader.rb
@@ -0,0 +1,178 @@
+require 'net/http'
+require 'mixlib/auth/signedheaderauth'
+require 'openssl'
+
+# inspired by/cargo-culted from http://stanislavvitvitskiy.blogspot.com/2008/12/multipart-post-in-ruby.html
+# TODO: confirm that code is public domain
+class Chef
+ class StreamingCookbookUploader
+
+ DefaultHeaders = { 'accept' => 'application/json' }
+
+ class << self
+
+ def post(to_url, user_id, secret_key_filename, params = {}, headers = {})
+ make_request(:post, to_url, user_id, secret_key_filename, params, headers)
+ end
+
+ def put(to_url, user_id, secret_key_filename, params = {}, headers = {})
+ make_request(:put, to_url, user_id, secret_key_filename, params, headers)
+ end
+
+ def make_request(http_verb, to_url, user_id, secret_key_filename, params = {}, headers = {})
+ boundary = '----RubyMultipartClient' + rand(1000000).to_s + 'ZZZZZ'
+ parts = []
+ content_file = nil
+ content_body = nil
+
+ timestamp = Time.now.utc.iso8601
+ secret_key = OpenSSL::PKey::RSA.new(File.read(secret_key_filename))
+
+ unless params.nil? || params.empty?
+ params.each do |key, value|
+ if value.kind_of?(File)
+ content_file = value
+ filepath = value.path
+ filename = File.basename(filepath)
+ parts << StringPart.new( "--" + boundary + "\r\n" +
+ "Content-Disposition: form-data; name=\"" + key.to_s + "\"; filename=\"" + filename + "\"\r\n" +
+ "Content-Type: application/octet-stream\r\n\r\n")
+ parts << StreamPart.new(value, File.size(filepath))
+ parts << StringPart.new("\r\n")
+ else
+ content_body = value.to_s
+ parts << StringPart.new( "--" + boundary + "\r\n" +
+ "Content-Disposition: form-data; name=\"" + key.to_s + "\"\r\n\r\n")
+ parts << StringPart.new(content_body + "\r\n")
+ end
+ end
+ parts << StringPart.new("--" + boundary + "--\r\n")
+ end
+
+ body_stream = MultipartStream.new(parts)
+
+ timestamp = Time.now.utc.iso8601
+
+ Chef::Log.logger.debug("Signing: method: #{http_verb}, file: #{content_file}, User-id: #{user_id}, Timestamp: #{timestamp}")
+
+ signing_options = {
+ :http_method=>http_verb,
+ :user_id=>user_id,
+ :timestamp=>timestamp}
+ (content_file && signing_options[:file] = content_file) || (signing_options[:body] = (content_body || ""))
+
+ headers.merge!(Mixlib::Auth::SignedHeaderAuth.signing_object(signing_options).sign(secret_key))
+
+ content_file.rewind if content_file
+
+ # net/http doesn't like symbols for header keys, so we'll to_s each one just in case
+ headers = DefaultHeaders.merge(Hash[*headers.map{ |k,v| [k.to_s, v] }.flatten])
+
+ url = URI.parse(to_url)
+ req = case http_verb
+ when :put
+ Net::HTTP::Put.new(url.path, headers)
+ when :post
+ Net::HTTP::Post.new(url.path, headers)
+ end
+ req.content_length = body_stream.size
+ req.content_type = 'multipart/form-data; boundary=' + boundary unless parts.empty?
+ req.body_stream = body_stream
+ res = Net::HTTP.new(url.host, url.port).start {|http| http.request(req) }
+
+ # alias status to code and to_s to body for test purposes
+ # TODO: stop the following madness!
+ class << res
+ alias :to_s :body
+
+ # BUGBUG this makes the response compatible with what respsonse_steps expects to test headers (response.headers[] -> response[])
+ def headers
+ self
+ end
+
+ def status
+ code.to_i
+ end
+ end
+ res
+ end
+
+ end
+
+ class StreamPart
+ def initialize(stream, size)
+ @stream, @size = stream, size
+ end
+
+ def size
+ @size
+ end
+
+ # read the specified amount from the stream
+ def read(offset, how_much)
+ @stream.read(how_much)
+ end
+ end
+
+ class StringPart
+ def initialize(str)
+ @str = str
+ end
+
+ def size
+ @str.length
+ end
+
+ # read the specified amount from the string startiung at the offset
+ def read(offset, how_much)
+ @str[offset, how_much]
+ end
+ end
+
+ class MultipartStream
+ def initialize(parts)
+ @parts = parts
+ @part_no = 0
+ @part_offset = 0
+ end
+
+ def size
+ @parts.inject(0) {|size, part| size + part.size}
+ end
+
+ def read(how_much)
+ return nil if @part_no >= @parts.size
+
+ how_much_current_part = @parts[@part_no].size - @part_offset
+
+ how_much_current_part = if how_much_current_part > how_much
+ how_much
+ else
+ how_much_current_part
+ end
+
+ how_much_next_part = how_much - how_much_current_part
+
+ current_part = @parts[@part_no].read(@part_offset, how_much_current_part)
+
+ # recurse into the next part if the current one was not large enough
+ if how_much_next_part > 0
+ @part_no += 1
+ @part_offset = 0
+ next_part = read(how_much_next_part)
+ current_part + if next_part
+ next_part
+ else
+ ''
+ end
+ else
+ @part_offset += how_much_current_part
+ current_part
+ end
+ end
+ end
+
+ end
+
+
+end
diff --git a/chef/spec/unit/application/indexer_spec.rb b/chef/spec/unit/application/indexer_spec.rb
deleted file mode 100644
index c15ddc71c0..0000000000
--- a/chef/spec/unit/application/indexer_spec.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-#
-# Author:: AJ Christensen (<aj@junglist.gen.nz>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# 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.
-
-require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
-
-describe Chef::Application::Indexer, "initialize" do
- before do
- @app = Chef::Application::Indexer.new
- end
-
- it "should create an instance of Chef::Application::Indexer" do
- @app.should be_kind_of(Chef::Application::Indexer)
- end
-end
-
-describe Chef::Application::Indexer, "setup_application" do
- before do
- Chef::Daemon.stub!(:change_privilege).and_return(true)
- @chef_searchindex = mock("Chef::SearchIndex", :null_object => true)
- Chef::SearchIndex.stub!(:new).and_return(@chef_searchindex)
- Chef::Queue.stub!(:connect).and_return(true)
- Chef::Queue.stub!(:subscribe).and_return(true)
- @app = Chef::Application::Indexer.new
- end
-
- it "should change privileges" do
- Chef::Daemon.should_receive(:change_privilege).and_return(true)
- @app.setup_application
- end
-
- it "should instantiate a chef::client object" do
- Chef::SearchIndex.should_receive(:new).and_return(@chef_searchindex)
- @app.setup_application
- end
-
- it "should connect to the queue" do
- Chef::Queue.should_receive(:connect).and_return(true)
- @app.setup_application
- end
-
- it "should subscribe to index" do
- Chef::Queue.should_receive(:subscribe).with(:queue, "index").and_return(true)
- @app.setup_application
- end
-
- it "should subscribe to remove" do
- Chef::Queue.should_receive(:subscribe).with(:queue, "remove").and_return(true)
- @app.setup_application
- end
-end
diff --git a/chef/spec/unit/client_spec.rb b/chef/spec/unit/client_spec.rb
index 303306a659..eb53803cf9 100644
--- a/chef/spec/unit/client_spec.rb
+++ b/chef/spec/unit/client_spec.rb
@@ -30,13 +30,9 @@ describe Chef::Client, "run" do
to_stub = [
:build_node,
:register,
- :authenticate,
- :sync_library_files,
- :sync_attribute_files,
- :sync_definitions,
- :sync_recipes,
+ :sync_cookbooks,
:save_node,
- :save_node
+ :converge
]
to_stub.each do |method|
@client.stub!(method).and_return(true)
@@ -58,37 +54,17 @@ describe Chef::Client, "run" do
@client.run
end
- it "should register for an openid" do
+ it "should register for a client" do
@client.should_receive(:register).and_return(true)
@client.run
end
- it "should authenticate with the server" do
- @client.should_receive(:authenticate).and_return(true)
+ it "should synchronize the cookbooks from the server" do
+ @client.should_receive(:sync_cookbooks).and_return(true)
@client.run
end
- it "should synchronize definitions from the server" do
- @client.should_receive(:sync_definitions).and_return(true)
- @client.run
- end
-
- it "should synchronize recipes from the server" do
- @client.should_receive(:sync_recipes).and_return(true)
- @client.run
- end
-
- it "should synchronize and load library files from the server" do
- @client.should_receive(:sync_library_files).and_return(true)
- @client.run
- end
-
- it "should synchronize and load attribute files from the server" do
- @client.should_receive(:sync_attribute_files).and_return(true)
- @client.run
- end
-
- it "should save the nodes state on the server (twice!)" do
+ it "should save the nodes state on the server (thrice!)" do
@client.should_receive(:save_node).exactly(3).times.and_return(true)
@client.run
end
@@ -98,11 +74,6 @@ describe Chef::Client, "run" do
@client.run
end
- it "should set the cookbook_path" do
- Chef::Config.should_receive('[]').with(:file_cache_path).
- and_return('/var/chef/cache/cookbooks')
- @client.run
- end
end
describe Chef::Client, "run_solo" do
@@ -149,6 +120,7 @@ describe Chef::Client, "build_node" do
Chef::REST.stub!(:new).and_return(@mock_rest)
@client = Chef::Client.new
Chef::Platform.stub!(:find_platform_and_version).and_return(["FooOS", "1.3.3.7"])
+ Chef::Config[:node_name] = nil
end
it "should set the name equal to the FQDN" do
@@ -208,35 +180,34 @@ describe Chef::Client, "register" do
before do
@mock_rest = mock("Chef::REST", :null_object => true)
@mock_rest.stub!(:get_rest).and_return(true)
+ @mock_rest.stub!(:register).and_return(true)
Chef::REST.stub!(:new).and_return(@mock_rest)
@chef_client = Chef::Client.new
@chef_client.safe_name = "testnode"
+ @chef_client.node_name = "testnode"
@chef_client.stub!(:determine_node_name).and_return(true)
- @chef_client.stub!(:create_registration).and_return(true)
- Chef::Application.stub!(:fatal!).and_return(true)
- Chef::FileCache.stub!(:create_cache_path).and_return("/tmp")
- Chef::FileCache.stub!(:load).and_return("/tmp/testnode")
- end
-
- it "should log an appropriate debug message regarding registering an openid" do
- Chef::Log.should_receive(:debug).with("Registering testnode for an openid").and_return(true)
- @chef_client.register
+ File.stub!(:exists?).and_return(false)
end
+
+ describe "when the validation key is present" do
+ before(:each) do
+ File.stub!(:exists?).with(Chef::Config[:validation_key]).and_return(true)
+ end
- it "should fetch the registration based on safe_name from the chef server" do
- @mock_rest.should_receive(:get_rest).with("registrations/testnode").and_return(true)
- @chef_client.register
- end
+ it "should sign requests with the validation key" do
+ Chef::REST.should_receive(:new).with(Chef::Config[:client_url], Chef::Config[:validation_client_name], Chef::Config[:validation_key]).and_return(@mock_rest)
+ @chef_client.register
+ end
- it "should load the secret from disk" do
- Chef::FileCache.should_receive(:load).with(File.join("registration", "testnode")).and_return("/tmp/testnode")
- @chef_client.register
+ it "should register for a new key-pair" do
+ @mock_rest.should_receive(:register).with("testnode", Chef::Config[:client_key])
+ @chef_client.register
+ end
end
- it "should cause chef to die fatally if the filecache cannot find the registration" do
- Chef::FileCache.stub!(:load).with(File.join("registration", "testnode")).and_raise(Chef::Exceptions::FileNotFound)
- Chef::Application.should_receive(:fatal!).with(/^.*$/, 3).and_return(true)
- @chef_client.register
+ it "should setup the rest client to use the client key-pair" do
+ Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url]).and_return(@mock_rest)
+ @chef_client.register
end
end
@@ -256,3 +227,4 @@ describe Chef::Client, "run_ohai" do
@chef_client.run_ohai
end
end
+
diff --git a/chef/spec/unit/couchdb_spec.rb b/chef/spec/unit/couchdb_spec.rb
index ffcc4d1a2a..5f84384a71 100644
--- a/chef/spec/unit/couchdb_spec.rb
+++ b/chef/spec/unit/couchdb_spec.rb
@@ -18,236 +18,245 @@
require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
-describe Chef::CouchDB, "new" do
- before do
+describe Chef::CouchDB do
+ before(:each) do
@mock_rest = mock("Chef::REST", :null_object => true)
@mock_rest.stub!(:run_request).and_return({"couchdb" => "Welcome", "version" =>"0.9.0"})
@mock_rest.stub!(:url).and_return("http://localhost:5984")
Chef::REST.stub!(:new).and_return(@mock_rest)
- end
-
- it "should create a new Chef::REST object from the default url" do
- Chef::Config[:couchdb_url] = "http://monkey"
- Chef::REST.should_receive(:new).with("http://monkey")
- Chef::CouchDB.new
+ @couchdb = Chef::CouchDB.new
+ Chef::Nanite.stub!(:request).and_return(true)
+ Chef::Nanite.stub!(:in_event).and_return(true)
+ Chef::Nanite.stub!(:start_mapper).and_return(true)
end
- it "should create a new Chef::REST object from a provided url" do
- Chef::REST.should_receive(:new).with("http://monkeypants")
- Chef::CouchDB.new("http://monkeypants")
- end
-end
+ describe "new" do
+ it "should create a new Chef::REST object from the default url" do
+ old_url = Chef::Config[:couchdb_url]
+ Chef::Config[:couchdb_url] = "http://monkey"
+ Chef::REST.should_receive(:new).with("http://monkey", nil, nil)
+ Chef::CouchDB.new
+ Chef::Config[:couchdb_url] = old_url
+ end
-describe Chef::CouchDB, "create_db" do
- before(:each) do
- @mock_rest = mock("Chef::REST", :null_object => true)
- @mock_rest.stub!(:get_rest).and_return([ "chef" ])
- @mock_rest.stub!(:put_rest).and_return(true)
- Chef::REST.stub!(:new).and_return(@mock_rest)
- end
-
- def do_create_db
- couch = Chef::CouchDB.new
- couch.create_db
- end
-
- it "should get a list of current databases" do
- @mock_rest.should_receive(:get_rest).and_return(["chef"])
- do_create_db
- end
-
- it "should create the chef database if it does not exist" do
- @mock_rest.stub!(:get_rest).and_return([])
- @mock_rest.should_receive(:put_rest).with("chef", {}).and_return(true)
- do_create_db
+ it "should create a new Chef::REST object from a provided url" do
+ Chef::REST.should_receive(:new).with("http://monkeypants", nil, nil)
+ Chef::CouchDB.new("http://monkeypants")
+ end
end
-
- it "should not create the chef database if it does exist" do
- @mock_rest.stub!(:get_rest).and_return(["chef"])
- @mock_rest.should_not_receive(:put_rest)
- do_create_db
- end
-
- it "should return 'chef'" do
- do_create_db.should eql("chef")
+
+ describe "create_db" do
+ before(:each) do
+ @couchdb.stub!(:create_design_document).and_return(true)
+ end
+
+ it "should get a list of current databases" do
+ @mock_rest.should_receive(:get_rest).and_return(["chef"])
+ @couchdb.create_db
+ end
+
+ it "should create the chef database if it does not exist" do
+ @mock_rest.stub!(:get_rest).and_return([])
+ @mock_rest.should_receive(:put_rest).with("chef", {}).and_return(true)
+ @couchdb.create_db
+ end
+
+ it "should not create the chef database if it does exist" do
+ @mock_rest.stub!(:get_rest).and_return(["chef"])
+ @mock_rest.should_not_receive(:put_rest)
+ @couchdb.create_db
+ end
+
+ it "should return 'chef'" do
+ @couchdb.create_db.should eql("chef")
+ end
end
-end
-describe Chef::CouchDB, "create_design_document" do
- before(:each) do
- @mock_rest = mock("Chef::REST", :null_object => true)
- @mock_design = {
- "version" => 1,
- "_rev" => 1
- }
- @mock_data = {
- "version" => 1,
- "language" => "javascript",
- "views" => {
- "all" => {
- "map" => <<-EOJS
- function(doc) {
- if (doc.chef_type == "node") {
- emit(doc.name, doc);
+ describe "create_design_document" do
+ before(:each) do
+ @mock_design = {
+ "version" => 1,
+ "_rev" => 1
+ }
+ @mock_data = {
+ "version" => 1,
+ "language" => "javascript",
+ "views" => {
+ "all" => {
+ "map" => <<-EOJS
+ function(doc) {
+ if (doc.chef_type == "node") {
+ emit(doc.name, doc);
+ }
}
- }
- EOJS
- },
+ EOJS
+ },
+ }
}
- }
- @mock_rest.stub!(:get_rest).and_return(@mock_design)
- @mock_rest.stub!(:put_rest).and_return(true)
- Chef::REST.stub!(:new).and_return(@mock_rest)
- @couchdb = Chef::CouchDB.new
- @couchdb.stub!(:create_db).and_return(true)
- end
-
- def do_create_design_document
- @couchdb.create_design_document("bob", @mock_data)
- end
-
- it "should create the database if it does not exist" do
- @couchdb.should_receive(:create_db).and_return(true)
- do_create_design_document
- end
-
- it "should fetch the existing design document" do
- @mock_rest.should_receive(:get_rest).with("chef/_design%2Fbob")
- do_create_design_document
- end
-
- it "should populate the _rev in the new design if the versions dont match" do
- @mock_data["version"] = 2
- do_create_design_document
- @mock_data["_rev"].should eql(1)
- end
-
- it "should create the view if it requires updating" do
- @mock_data["version"] = 2
- @mock_rest.should_receive(:put_rest).with("chef/_design%2Fbob", @mock_data)
- do_create_design_document
- end
-
- it "should not create the view if it does not require updating" do
- @mock_data["version"] = 1
- @mock_rest.should_not_receive(:put_rest)
- do_create_design_document
+ @mock_rest.stub!(:get_rest).and_return(@mock_design)
+ @mock_rest.stub!(:put_rest).and_return(true)
+ @couchdb.stub!(:create_db).and_return(true)
+ end
+
+ def do_create_design_document
+ @couchdb.create_design_document("bob", @mock_data)
+ end
+
+ it "should create the database if it does not exist" do
+ @couchdb.should_receive(:create_db).and_return(true)
+ do_create_design_document
+ end
+
+ it "should fetch the existing design document" do
+ @mock_rest.should_receive(:get_rest).with("chef/_design/bob")
+ do_create_design_document
+ end
+
+ it "should populate the _rev in the new design if the versions dont match" do
+ @mock_data["version"] = 2
+ do_create_design_document
+ @mock_data["_rev"].should eql(1)
+ end
+
+ it "should create the view if it requires updating" do
+ @mock_data["version"] = 2
+ @mock_rest.should_receive(:put_rest).with("chef/_design%2Fbob", @mock_data)
+ do_create_design_document
+ end
+
+ it "should not create the view if it does not require updating" do
+ @mock_data["version"] = 1
+ @mock_rest.should_not_receive(:put_rest)
+ do_create_design_document
+ end
end
-end
-describe Chef::CouchDB, "store" do
- it "should put the object into couchdb" do
- @mock_rest = mock("Chef::REST", :null_object => true)
- @mock_rest.should_receive(:put_rest).with("chef/node_bob", {}).and_return(true)
- Chef::REST.stub!(:new).and_return(@mock_rest)
- Chef::CouchDB.new.store("node", "bob", {})
- end
-end
+ describe "store" do
+ before(:each) do
+ @mock_results = {
+ "rows" => [
+ "id" => 'a0934635-e111-45d9-8223-cb58e1c9434c'
+ ]
+ }
+ @couchdb.stub!(:get_view).with("id_map", "name_to_id", :key => [ "node", "bob" ]).and_return(@mock_results)
+ end
-describe Chef::CouchDB, "load" do
- it "should load the object from couchdb" do
- @mock_rest = mock("Chef::REST", :null_object => true)
- @mock_rest.should_receive(:get_rest).with("chef/node_bob").and_return(true)
- Chef::REST.stub!(:new).and_return(@mock_rest)
- Chef::CouchDB.new.load("node", "bob").should eql(true)
- end
-end
+ it "should put the object into couchdb with a pre-existing GUID" do
+ @mock_rest.should_receive(:put_rest).with("chef/#{@mock_results["rows"][0]["id"]}", {}).and_return(true)
+ @couchdb.store("node", "bob", {})
+ end
-describe Chef::CouchDB, "delete" do
- before(:each) do
- @mock_current = {
- "version" => 1,
- "_rev" => 1
- }
- @mock_rest = mock("Chef::REST", :null_object => true)
- @mock_rest.stub!(:get_rest).and_return(@mock_current)
- @mock_rest.stub!(:delete_rest).and_return(true)
- Chef::REST.stub!(:new).and_return(@mock_rest)
- end
-
- def do_delete(rev=nil)
- Chef::REST.stub!(:new).and_return(@mock_rest)
- Chef::CouchDB.new.delete("node", "bob", rev)
- end
-
- it "should remove the object from couchdb with a specific revision" do
- @mock_rest.should_receive(:delete_rest).with("chef/node_bob?rev=1")
- do_delete(1)
- end
-
- it "should remove the object from couchdb based on the couchdb_rev of the current obj" do
- mock_real = mock("Inflated Object")
- mock_real.stub!(:respond_to?).and_return(true)
- mock_real.stub!(:couchdb_rev).and_return(2)
- @mock_rest.should_receive(:get_rest).with("chef/node_bob").and_return(mock_real)
- @mock_rest.should_receive(:delete_rest).with("chef/node_bob?rev=2")
- do_delete
- end
-
- it "should remove the object from couchdb based on the current objects rev" do
- @mock_rest.should_receive(:delete_rest).with("chef/node_bob?rev=1")
- do_delete
+ it "should put the object into couchdb with a new GUID" do
+ @mock_results = { "rows" => [] }
+ @couchdb.stub!(:get_view).with("id_map", "name_to_id", :key => [ "node", "bob" ]).and_return(@mock_results)
+ UUIDTools::UUID.stub!(:random_create).and_return("aaaaaaaa-xxxx-xxxx-xxxx-xxxxxxxxxxx")
+ @mock_rest.should_receive(:put_rest).with("chef/aaaaaaaa-xxxx-xxxx-xxxx-xxxxxxxxxxx", {}).and_return(true)
+ @couchdb.store("node", "bob", {})
+ end
+
+ it "should send the object to nanite for indexing" do
+ Chef::Nanite.should_receive(:request)
+ @couchdb.store("node", "bob", {})
+ end
end
-end
-describe Chef::CouchDB, "list" do
- before(:each) do
- @mock_rest = mock("Chef::REST", :null_object => true, :url => "http://monkeypants")
- Chef::REST.stub!(:new).and_return(@mock_rest)
- @couch = Chef::CouchDB.new("http://monkeypants")
- Chef::Config.stub!(:[]).with(:couchdb_database).and_return("chef")
+ describe "load" do
+ before(:each) do
+ @mock_node = Chef::Node.new()
+ @mock_node.name("bob")
+ @couchdb.stub!(:find_by_name).with("node", "bob").and_return(@mock_node)
+ end
+
+ it "should load the object from couchdb" do
+ @couchdb.load("node", "bob").should eql(@mock_node)
+ end
end
-
- describe "on couchdb 0.8" do
- before do
- Chef::Config.stub!(:[]).with(:couchdb_version).and_return(0.8)
+
+ describe "delete" do
+ before(:each) do
+ @mock_current = {
+ "version" => 1,
+ "_rev" => 1
+ }
+ @mock_rest.stub!(:get_rest).and_return(@mock_current)
+ @mock_rest.stub!(:delete_rest).and_return(true)
+ @mock_node = Chef::Node.new()
+ @mock_node.name("bob")
+ @mock_node.couchdb_rev = 15
+ @couchdb.stub!(:find_by_name).with("node", "bob", true).and_return([ @mock_node, "ax" ])
end
- it "should get the view for all objects if inflate is true" do
- @mock_rest.should_receive(:get_rest).with("chef/_view/node/all").and_return(true)
- @couch.list("node", true)
+ def do_delete(rev=nil)
+ @couchdb.delete("node", "bob", rev)
end
-
- it "should get the view for just the object id's if inflate is false" do
- @mock_rest.should_receive(:get_rest).with("chef/_view/node/all_id").and_return(true)
- @couch.list("node", false)
+
+ it "should remove the object from couchdb with a specific revision" do
+ @mock_rest.should_receive(:delete_rest).with("chef/ax?rev=1")
+ do_delete(1)
+ end
+
+ it "should remove the object from couchdb based on the couchdb_rev of the current obj" do
+ @mock_rest.should_receive(:delete_rest).with("chef/ax?rev=15")
+ do_delete
end
end
- describe "on couchdb 0.9" do
- before do
- Chef::Config.stub!(:[]).with(:couchdb_version).and_return(0.9)
+ describe "list" do
+ before(:each) do
+ Chef::Config.stub!(:[]).with(:couchdb_database).and_return("chef")
+ @mock_response = mock("Chef::CouchDB::Response", :null_object => true)
end
- it "should get the view for all objects if inflate is true" do
- @mock_rest.should_receive(:get_rest).with("chef/_design/node/_view/all").and_return(true)
- @couch.list("node", true)
+ describe "on couchdb 0.8" do
+ before(:each) do
+ Chef::Config.stub!(:[]).with(:couchdb_version).and_return(0.8)
+ end
+
+ it "should get the view for all objects if inflate is true" do
+ @mock_rest.should_receive(:get_rest).with("chef/_view/node/all").and_return(@mock_response)
+ @couchdb.list("node", true)
+ end
+
+ it "should get the view for just the object id's if inflate is false" do
+ @mock_rest.should_receive(:get_rest).with("chef/_view/node/all_id").and_return(@mock_response)
+ @couchdb.list("node", false)
+ end
end
- it "should get the view for just the object id's if inflate is false" do
- @mock_rest.should_receive(:get_rest).with("chef/_design/node/_view/all_id").and_return(true)
- @couch.list("node", false)
+ describe "on couchdb 0.9" do
+ before do
+ Chef::Config.stub!(:[]).with(:couchdb_version).and_return(0.9)
+ end
+
+ it "should get the view for all objects if inflate is true" do
+ @mock_rest.should_receive(:get_rest).with("chef/_design/node/_view/all").and_return(@mock_response)
+ @couchdb.list("node", true)
+ end
+
+ it "should get the view for just the object id's if inflate is false" do
+ @mock_rest.should_receive(:get_rest).with("chef/_design/node/_view/all_id").and_return(@mock_response)
+ @couchdb.list("node", false)
+ end
end
end
-end
-describe Chef::CouchDB, "has_key?" do
- before(:each) do
- @mock_rest = mock("Chef::REST", :null_object => true)
- Chef::REST.stub!(:new).and_return(@mock_rest)
- end
-
- it "should return true if the object exists" do
- @mock_rest.should_receive(:get_rest).and_return(true)
- Chef::CouchDB.new.has_key?("node", "bob").should eql(true)
- end
-
- it "should return false if the object does not exist" do
- @mock_rest.should_receive(:get_rest).and_raise(ArgumentError)
- Chef::CouchDB.new.has_key?("node", "bob").should eql(false)
+ describe "has_key?" do
+ it "should return true if the object exists" do
+ @couchdb.stub!(:find_by_name).with("node", "bob").and_return(true)
+ @couchdb.has_key?("node", "bob").should eql(true)
+ end
+
+ it "should return false if the object does not exist" do
+ @couchdb.stub!(:find_by_name).and_raise(Chef::Exceptions::CouchDBNotFound)
+ @couchdb.has_key?("node", "bob").should eql(false)
+ end
end
+
end
+
+
+
describe Chef::CouchDB, "get_view" do
before do
@mock_rest = mock("Chef::REST", :null_object => true, :url => "http://monkeypants")
@@ -287,6 +296,7 @@ describe Chef::CouchDB, "view_uri" do
@mock_rest.should_receive(:run_request).with(
:GET,
URI.parse("http://monkeypants/"),
+ {},
false,
10,
false
diff --git a/chef/spec/unit/data_bag_item_spec.rb b/chef/spec/unit/data_bag_item_spec.rb
new file mode 100644
index 0000000000..43eb8f7e3d
--- /dev/null
+++ b/chef/spec/unit/data_bag_item_spec.rb
@@ -0,0 +1,164 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+require 'chef/data_bag_item'
+
+describe Chef::DataBagItem do
+ before(:each) do
+ @data_bag_item = Chef::DataBagItem.new
+ end
+
+ describe "initialize" do
+ it "should be a Chef::DataBagItem" do
+ @data_bag_item.should be_a_kind_of(Chef::DataBagItem)
+ end
+ end
+
+ describe "data_bag" do
+ it "should let you set the data_bag to a string" do
+ @data_bag_item.data_bag("clowns").should == "clowns"
+ end
+
+ it "should return the current data_bag type" do
+ @data_bag_item.data_bag "clowns"
+ @data_bag_item.data_bag.should == "clowns"
+ end
+
+ it "should not accept spaces" do
+ lambda { @data_bag_item.data_bag "clown masters" }.should raise_error(ArgumentError)
+ end
+
+ it "should throw an ArgumentError if you feed it anything but a string" do
+ lambda { @data_bag_item.data_bag Hash.new }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "raw_data" do
+ it "should let you set the raw_data with a hash" do
+ lambda { @data_bag_item.raw_data = { "id" => "octahedron" } }.should_not raise_error
+ end
+
+ it "should let you set the raw_data from a mash" do
+ lambda { @data_bag_item.raw_data = Mash.new({ "id" => "octahedron" }) }.should_not raise_error
+ end
+
+ it "should raise an exception if you set the raw data without a key" do
+ lambda { @data_bag_item.raw_data = { "monkey" => "pants" } }.should raise_error(ArgumentError)
+ end
+
+ it "should raise an exception if you set the raw data to something other than a hash" do
+ lambda { @data_bag_item.raw_data = "katie rules" }.should raise_error(ArgumentError)
+ end
+
+ it "should accept alphanum/-/_ for the id" do
+ lambda { @data_bag_item.raw_data = { "id" => "h1-_" } }.should_not raise_error(ArgumentError)
+ end
+
+ it "should raise an exception if the id contains anything but alphanum/-/_" do
+ lambda { @data_bag_item.raw_data = { "id" => "!@#" } }.should raise_error(ArgumentError)
+ end
+
+ it "should return the raw data" do
+ @data_bag_item.raw_data = { "id" => "highway_of_emptiness" }
+ @data_bag_item.raw_data.should == { "id" => "highway_of_emptiness" }
+ end
+ end
+
+ describe "object_name" do
+ before(:each) do
+ @data_bag_item.data_bag("dreams")
+ @data_bag_item.raw_data = { "id" => "the_beatdown" }
+ end
+
+ it "should return an object name based on the bag name and the raw_data id" do
+ @data_bag_item.object_name.should == "data_bag_item_dreams_the_beatdown"
+ end
+ end
+
+ describe "class method object_name" do
+ it "should return an object name based based on the bag name and an id" do
+ Chef::DataBagItem.object_name("zen", "master").should == "data_bag_item_zen_master"
+ end
+ end
+
+ describe "hash behaviour" do
+ before(:each) do
+ @data_bag_item.raw_data = { "id" => "journey", "trials" => "been through" }
+ end
+
+ it "should respond to keys" do
+ @data_bag_item.keys.should include("id")
+ @data_bag_item.keys.should include("trials")
+ end
+
+ it "should allow lookups with []" do
+ @data_bag_item["id"].should == "journey"
+ end
+ end
+
+ describe "to_hash" do
+ before(:each) do
+ @data_bag_item.data_bag("still_lost")
+ @data_bag_item.raw_data = { "id" => "whoa", "i_know" => "kung_fu" }
+ @to_hash = @data_bag_item.to_hash
+ end
+
+ it "should return a hash" do
+ @to_hash.should be_a_kind_of(Hash)
+ end
+
+ it "should have the raw_data keys as top level keys" do
+ @to_hash["id"].should == "whoa"
+ @to_hash["i_know"].should == "kung_fu"
+ end
+
+ it "should have the chef_type of data_bag_item" do
+ @to_hash["chef_type"].should == "data_bag_item"
+ end
+
+ it "should have the data_bag set" do
+ @to_hash["data_bag"].should == "still_lost"
+ end
+ end
+
+ describe "deserialize" do
+ before(:each) do
+ @data_bag_item.data_bag('mars_volta')
+ @data_bag_item.raw_data = { "id" => "octahedron", "snooze" => { "finally" => :world_will }}
+ @deserial = JSON.parse(@data_bag_item.to_json)
+ end
+
+ it "should deserialize to a Chef::DataBagItem object" do
+ @deserial.should be_a_kind_of(Chef::DataBagItem)
+ end
+
+ it "should have a matching 'data_bag' value" do
+ @deserial.data_bag.should == @data_bag_item.data_bag
+ end
+
+ it "should have a matching 'id' key" do
+ @deserial["id"].should == "octahedron"
+ end
+
+ it "should have a matching 'snooze' key" do
+ @deserial["snooze"].should == { "finally" => "world_will" }
+ end
+ end
+end
+
diff --git a/chef/spec/unit/data_bag_spec.rb b/chef/spec/unit/data_bag_spec.rb
new file mode 100644
index 0000000000..1f140b5f88
--- /dev/null
+++ b/chef/spec/unit/data_bag_spec.rb
@@ -0,0 +1,72 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+require 'chef/data_bag'
+
+describe Chef::DataBag do
+ before(:each) do
+ @data_bag = Chef::DataBag.new
+ end
+
+ describe "initialize" do
+ it "should be a Chef::DataBag" do
+ @data_bag.should be_a_kind_of(Chef::DataBag)
+ end
+ end
+
+ describe "name" do
+ it "should let you set the name to a string" do
+ @data_bag.name("clowns").should == "clowns"
+ end
+
+ it "should return the current name" do
+ @data_bag.name "clowns"
+ @data_bag.name.should == "clowns"
+ end
+
+ it "should not accept spaces" do
+ lambda { @data_bag.name "clown masters" }.should raise_error(ArgumentError)
+ end
+
+ it "should throw an ArgumentError if you feed it anything but a string" do
+ lambda { @data_bag.name Hash.new }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "deserialize" do
+ before(:each) do
+ @data_bag.name('mars_volta')
+ @deserial = JSON.parse(@data_bag.to_json)
+ end
+
+ it "should deserialize to a Chef::DataBag object" do
+ @deserial.should be_a_kind_of(Chef::DataBag)
+ end
+
+ %w{
+ name
+ }.each do |t|
+ it "should match '#{t}'" do
+ @deserial.send(t.to_sym).should == @data_bag.send(t.to_sym)
+ end
+ end
+
+ end
+end
+
diff --git a/chef/spec/unit/node_spec.rb b/chef/spec/unit/node_spec.rb
index 3edf03120a..dc10f8b37a 100644
--- a/chef/spec/unit/node_spec.rb
+++ b/chef/spec/unit/node_spec.rb
@@ -21,6 +21,7 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
describe Chef::Node do
before(:each) do
Chef::Config.node_path(File.join(File.dirname(__FILE__), "..", "data", "nodes"))
+ Nanite.stub!(:request).and_return(true)
@node = Chef::Node.new()
end
@@ -252,17 +253,6 @@ describe Chef::Node do
end
end
- describe "to_index" do
- before(:each) do
- @node.foo("bar")
- end
-
- it "should return a hash with :index attributes" do
- @node.name("airplane")
- @node.to_index.should == { "foo" => "bar", "index_name" => "node", "id" => "node_airplane", "name" => "airplane" }
- end
- end
-
describe "to_s" do
it "should turn into a string like node[name]" do
@node.name("airplane")
@@ -311,7 +301,6 @@ describe Chef::Node do
node = Chef::Node.new
node.name "bob"
node.couchdb_rev = 1
- Chef::Queue.should_receive(:send_msg).with(:queue, :remove, node)
node.destroy
end
end
@@ -320,14 +309,12 @@ describe Chef::Node do
before(:each) do
@mock_couch.stub!(:store).and_return({ "rev" => 33 })
Chef::CouchDB.stub!(:new).and_return(@mock_couch)
- Chef::Queue.stub!(:send_msg).and_return(true)
@node = Chef::Node.new
@node.name "bob"
@node.couchdb_rev = 1
end
it "should save the node to couchdb" do
- Chef::Queue.should_receive(:send_msg).with(:queue, :index, @node)
@mock_couch.should_receive(:store).with("node", "bob", @node).and_return({ "rev" => 33 })
@node.save
end
diff --git a/chef/spec/unit/provider/group/groupadd_spec.rb b/chef/spec/unit/provider/group/groupadd_spec.rb
index 6e8b11a663..e0fb144b93 100644
--- a/chef/spec/unit/provider/group/groupadd_spec.rb
+++ b/chef/spec/unit/provider/group/groupadd_spec.rb
@@ -177,6 +177,7 @@ end
describe Chef::Provider::Group::Groupadd, "load_current_resource" do
before do
@node = mock("Chef::Node", :null_object => true)
+ Chef::Node.stub!(:new).and_return(@node)
@new_resource = mock("Chef::Resource::Group", :null_object => true, :group_name => "aj")
@provider = Chef::Provider::Group::Groupadd.new(@node, @new_resource)
File.stub!(:exists?).and_return(false)
diff --git a/chef/spec/unit/provider/http_request_spec.rb b/chef/spec/unit/provider/http_request_spec.rb
index d47db173cc..5357999589 100644
--- a/chef/spec/unit/provider/http_request_spec.rb
+++ b/chef/spec/unit/provider/http_request_spec.rb
@@ -47,8 +47,8 @@ describe Chef::Provider::HttpRequest, "load_current_resource" do
@provider = Chef::Provider::HttpRequest.new(@node, @new_resource)
end
- it "should set up a Chef::REST client" do
- Chef::REST.should_receive(:new).with(@new_resource.url)
+ it "should set up a Chef::REST client, with no authentication" do
+ Chef::REST.should_receive(:new).with(@new_resource.url, nil, nil)
@provider.load_current_resource
end
end
@@ -205,4 +205,4 @@ describe Chef::Provider::HttpRequest, "action_delete" do
@new_resource.should_receive(:updated=).with(true)
@provider.action_delete
end
-end \ No newline at end of file
+end
diff --git a/chef/spec/unit/queue_spec.rb b/chef/spec/unit/queue_spec.rb
deleted file mode 100644
index 8f0fdcbc73..0000000000
--- a/chef/spec/unit/queue_spec.rb
+++ /dev/null
@@ -1,105 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# 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.
-#
-
-require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
-
-describe Chef::Queue do
-
- it "should connect to a stomp server on localhost and 61613" do
- Stomp::Connection.should_receive(:open).with("", "", "localhost", 61613, false).once
- Chef::Queue.connect
- end
-
- it "should allow config options to override defaults on connect" do
- Chef::Config[:queue_user] = "monkey"
- Chef::Config[:queue_password] = "password"
- Chef::Config[:queue_host] = "10.10.10.10"
- Chef::Config[:queue_port] = 61614
- Stomp::Connection.should_receive(:open).with("monkey", "password", "10.10.10.10", 61614, false).once
- Chef::Queue.connect
- end
-
- it "should make a url based on type and name" do
- Chef::Queue.make_url("topic", "goal").should eql("/topic/chef/goal")
- Chef::Queue.make_url("queue", "pool").should eql("/queue/chef/pool")
- end
-
- it "should allow you to subscribe to a queue" do
- queue = mock("Queue", :null_object => true)
- queue.should_receive(:subscribe).with(Chef::Queue.make_url(:topic, :node)).once
- Stomp::Connection.stub!(:open).and_return(queue)
- Chef::Queue.connect
- Chef::Queue.subscribe(:topic, :node)
- end
-
- it "should allow you to send a message" do
- message = mock("Message", :null_object => true)
- message.should_receive(:to_json).once.and_return("some json")
- connection = mock("Connection", :null_object => true)
- connection.should_receive(:send).with(Chef::Queue.make_url(:queue, :node), "some json").once.and_return(true)
- Stomp::Connection.stub!(:open).and_return(connection)
- Chef::Queue.connect
- Chef::Queue.send_msg(:queue, :node, message)
- end
-
- it "should receive a message with receive_msg" do
- raw_msg = mock("Stomp Message", :null_object => true)
- raw_msg.should_receive(:body).twice.and_return("the body")
- connection = mock("Connection", :null_object => true)
- connection.should_receive(:receive).once.and_return(raw_msg)
- JSON.should_receive(:parse).with("the body").and_return("the body")
- Stomp::Connection.stub!(:open).and_return(connection)
- Chef::Queue.connect
- Chef::Queue.receive_msg.should eql([ "the body", raw_msg ])
- end
-
- it "should poll for a message with poll_msg, returning a message if there is one" do
- raw_msg = mock("Stomp Message", :null_object => true)
- raw_msg.should_receive(:body).once.and_return("the body")
- connection = mock("Connection", :null_object => true)
- connection.should_receive(:poll).once.and_return(raw_msg)
- JSON.should_receive(:parse).with("the body").and_return("the body")
- Stomp::Connection.stub!(:open).and_return(connection)
- Chef::Queue.connect
- Chef::Queue.poll_msg.should eql("the body")
- end
-
- it "should poll for a message with poll_msg, returning nil if there is not a message" do
- connection = mock("Connection", :null_object => true)
- connection.should_receive(:poll).once.and_return(nil)
- JSON.should_not_receive(:parse).with(nil)
- Stomp::Connection.stub!(:open).and_return(connection)
- Chef::Queue.connect
- Chef::Queue.poll_msg.should eql(nil)
- end
-
- it "should raise an exception if you disconnect without a connection" do
- Stomp::Connection.stub!(:open).and_return(nil)
- Chef::Queue.connect
- lambda { Chef::Queue.disconnect }.should raise_error(ArgumentError)
- end
-
- it "should disconnect an active connection" do
- connection = mock("Connection", :null_object => true)
- connection.should_receive(:disconnect).once.and_return(true)
- Stomp::Connection.stub!(:open).and_return(connection)
- Chef::Queue.connect
- Chef::Queue.disconnect
- end
-
-end
diff --git a/chef/spec/unit/rest_spec.rb b/chef/spec/unit/rest_spec.rb
index 3f93e60504..c8bbd854c8 100644
--- a/chef/spec/unit/rest_spec.rb
+++ b/chef/spec/unit/rest_spec.rb
@@ -20,333 +20,365 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
require 'uri'
require 'net/https'
-describe Chef::REST, "initialize method" do
- it "should create a new Chef::REST" do
- Chef::REST.new("url").should be_kind_of(Chef::REST)
+describe Chef::REST do
+ before(:each) do
+ Chef::REST::CookieJar.stub!(:instance).and_return({})
+ @rest = Chef::REST.new("url", nil, nil)
end
-end
-describe Chef::REST, "get_rest method" do
- it "should create a url from the path and base url" do
- URI.should_receive(:parse).with("url/monkey")
- r = Chef::REST.new("url")
- r.stub!(:run_request)
- r.get_rest("monkey")
- end
-
- it "should call run_request :GET with the composed url object" do
- URI.stub!(:parse).and_return(true)
- r = Chef::REST.new("url")
- r.should_receive(:run_request).with(:GET, true, false, 10, false).and_return(true)
- r.get_rest("monkey")
+ describe "initialize" do
+ it "should create a new Chef::REST" do
+ @rest.should be_kind_of(Chef::REST)
+ end
end
-end
-describe Chef::REST, "delete_rest method" do
- it "should create a url from the path and base url" do
- URI.should_receive(:parse).with("url/monkey")
- r = Chef::REST.new("url")
- r.stub!(:run_request)
- r.delete_rest("monkey")
- end
-
- it "should call run_request :DELETE with the composed url object" do
- URI.stub!(:parse).and_return(true)
- r = Chef::REST.new("url")
- r.should_receive(:run_request).with(:DELETE, true).and_return(true)
- r.delete_rest("monkey")
- end
-end
+ describe "load_signing_key" do
+ before(:each) do
+ @private_key = <<EOH
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAx8xAfO2BO8kUughpjWwHPN2rgDcES15PbMEGe6OdJgjFARkt
+FMdEusbGxmXKpk51Ggxi2P6ZYEoZfniZWt4qSt4i1vanDayRlJ1qoRCOaYj5cQS7
+gpHspHWqkY3HfGvx4svdutQ06o/gypx2QYfi68YrIUQexPiTUhsnP9FlgNt40Rl1
+YgBiIlJUk7d3q+1b/+POTNKPeyjGK9hoTloplbSx+cYdZgc4/YpU0eLBoHPuPv5l
+QD+Y8VNS39bvY2NWbCqhV508gExAK26FxXTDNpi2mTZmbRZ8U0PKrCgF6gBSeod5
+EdQnNgoZHmA2fzfPHWfJd2OEuMcNM7DWpPDizQIDAQABAoIBAAGVDYGvw9E8Y2yh
+umxDSb9ipgQK637JTWm4EZwTDKCLezvp/iBm/5VXE6XoknVEs8q0BGhhg8dubstA
+mz5L+hvDrJT1ORdzoWeC46BI6EfPrOIHPpDnJO+cevBSJh1HIZBBOw1KtuyQnSAd
+oxYbxGFHnXnS90dqDIie7G2l897UWoiQWNMLY+A+l5H4GLC+4Phq02pLd4OQwXA3
+Nd+3Nq69aOeccyfSDeeG7u35TKrjQPIxU210aR18d/0trR20BKsKbT30GPE1tQQd
+jm4uReSPttTQ+NjwBQKKYmO2F9b9MPzmQ7c+KycBRmf+IOgZeZ54JN0GzUXsDTjJ
++ZSgdgUCgYEA41aetBJwsKkF973gL54QCB5vFhRw3TdUgYhQgz04B5JGouGTSALy
+u1XtO6il65Zf6FwFSzXiggYYxTKyP/zwL88CQAVA7rleyhoZrw2bD6R2RZLivRba
+50rstltUbjevd96TagFY7i9gVHL9E6DKJH4unZfIM0Bl2IZQraqCR8MCgYEA4PzC
+FfUwiLa5drN6OVWZZfwxOeMbQUsYVeq7pHyeuvIe0euhcCLabBqfVt0pxqf1hem+
+l2+PnSKtvbI9siwt6WvJCtB3e/3aHOA3d6Y9TYxoyJAK007mRlQbbgqLzG83tZH2
+twO2tjo+h1+nv5yjE7aF9ItszegwTWsupvR+Ei8CgYAy0nt6MCEnLTIbV0RWANT+
+q6cT3Y/5tFPc/Vdab4YmEypdYWZmk9olzSjSzHoDN8PLEz9PuAUiIjDJbPLyYR5k
+4bdUDpicha5OKhWRz83Zal/SX+r2cLSRPmu6vKIcXbCJcKWt7g0uekLjvi0bhTeL
+fvX23yavZnceN7Czkkm7twKBgEFTgrNHdykrDRzXLhT5ssm2+UAani5ONKm1t3gi
+KyCS7rn7FevuYsdiz4M0Qk4JNLQGU6262dNBX3smBt32D/qnrj8ymo7o/WzG+bQH
+E+OxcjdSA6KpVRl0kGZaL49Td7SDxkQLkwDEVqWN87IiNAOkSq7f0N7UnTnNdkVJ
+1lVHAoGBANYgMoEj7gIJdch7hMdQcFfq9+4ntAAbsl3JFW+T9ChATn0XHAylP9ha
+ZaGlRrC7vxcF06vMe0HXyH1XVK3J9186zliTa4oDjkQ0D5X7Ga7KktLXAmQTysUH
+V3jwIQbAF6LqLUnGOq6rJzQxrWKvFt0mVDyuJzIJGSbnN/Sl5J6P
+-----END RSA PRIVATE KEY-----
+EOH
+ IO.stub!(:read).and_return(@private_key)
+ end
-describe Chef::REST, "post_rest method" do
- it "should create a url from the path and base url" do
- URI.should_receive(:parse).with("url/monkey")
- r = Chef::REST.new("url")
- r.stub!(:run_request)
- r.post_rest("monkey", "data")
- end
-
- it "should call run_request :POST with the composed url object and data" do
- URI.stub!(:parse).and_return(true)
- r = Chef::REST.new("url")
- r.should_receive(:run_request).with(:POST, true, "data").and_return(true)
- r.post_rest("monkey", "data")
- end
-end
+ it "should return the contents of the key file" do
+ File.stub!(:exists?).and_return(true)
+ File.stub!(:readable?).and_return(true)
+ @rest.load_signing_key("/tmp/keyfile.pem").should be(@private_key)
+ end
-describe Chef::REST, "put_rest method" do
- it "should create a url from the path and base url" do
- URI.should_receive(:parse).with("url/monkey")
- r = Chef::REST.new("url")
- r.stub!(:run_request)
- r.put_rest("monkey", "data")
- end
-
- it "should call run_request :PUT with the composed url object and data" do
- URI.stub!(:parse).and_return(true)
- r = Chef::REST.new("url")
- r.should_receive(:run_request).with(:PUT, true, "data").and_return(true)
- r.put_rest("monkey", "data")
- end
-end
+ it "should raise a Chef::Exceptions::PrivateKeyMissing exception if the key cannot be found" do
+ File.stub!(:exists?).and_return(false)
+ File.stub!(:readable?).and_return(true) #42!
+ lambda {
+ @rest.load_signing_key("/tmp/keyfile.pem")
+ }.should raise_error(Chef::Exceptions::PrivateKeyMissing)
+ end
-describe Chef::REST, "run_request method" do
- before(:each) do
- Chef::REST::CookieJar.stub!(:instance).and_return({})
- @r = Chef::REST.new("url")
- @url_mock = mock("URI", :null_object => true)
- @url_mock.stub!(:host).and_return("one")
- @url_mock.stub!(:port).and_return("80")
- @url_mock.stub!(:path).and_return("/")
- @url_mock.stub!(:query).and_return("foo=bar")
- @url_mock.stub!(:scheme).and_return("https")
- @url_mock.stub!(:user).and_return(nil)
- @url_mock.stub!(:password).and_return(nil)
- @url_mock.stub!(:to_s).and_return("https://one:80/?foo=bar")
- @http_response_mock = mock("Net::HTTPSuccess", :null_object => true)
- @http_response_mock.stub!(:kind_of?).with(Net::HTTPSuccess).and_return(true)
- @http_response_mock.stub!(:body).and_return("ninja")
- @http_response_mock.stub!(:error!).and_return(true)
- @http_response_mock.stub!(:header).and_return({ 'Content-Length' => "5" })
- @http_mock = mock("Net::HTTP", :null_object => true)
- @http_mock.stub!(:verify_mode=).and_return(true)
- @http_mock.stub!(:read_timeout=).and_return(true)
- @http_mock.stub!(:use_ssl=).with(true).and_return(true)
- @data_mock = mock("Data", :null_object => true)
- @data_mock.stub!(:to_json).and_return('{ "one": "two" }')
- @request_mock = mock("Request", :null_object => true)
- @request_mock.stub!(:body=).and_return(true)
- @request_mock.stub!(:method).and_return(true)
- @request_mock.stub!(:path).and_return(true)
- @http_mock.stub!(:request).and_return(@http_response_mock)
- @tf_mock = mock(Tempfile, { :print => true, :close => true, :write => true })
- Tempfile.stub!(:new).with("chef-rest").and_return(@tf_mock)
- end
-
- def do_run_request(method=:GET, data=false, limit=10, raw=false)
- Net::HTTP.stub!(:new).and_return(@http_mock)
- @r.run_request(method, @url_mock, data, limit, raw)
+ it "should raise a Chef::Exceptions::PrivateKeyMissing exception if the key cannot be read" do
+ File.stub!(:exists?).and_return(true)
+ File.stub!(:readable?).and_return(false)
+ lambda {
+ @rest.load_signing_key("/tmp/keyfile.pem")
+ }.should raise_error(Chef::Exceptions::PrivateKeyMissing)
+ end
end
-
- it "should raise an exception if the redirect limit is 0" do
- lambda { @r.run_request(:GET, "/", false, 0)}.should raise_error(ArgumentError)
+
+ describe "get_rest" do
+ it "should create a url from the path and base url" do
+ URI.should_receive(:parse).with("url/monkey")
+ @rest.stub!(:run_request)
+ @rest.get_rest("monkey")
+ end
+
+ it "should call run_request :GET with the composed url object" do
+ URI.stub!(:parse).and_return(true)
+ @rest.should_receive(:run_request).with(:GET, true, {}, false, 10, false).and_return(true)
+ @rest.get_rest("monkey")
+ end
end
- it "should not fail if URI contains %d characters" do
- @http_response_mock.stub!(:read_body).and_yield("ninja")
- @url_mock.stub!(:path).and_return("/myfile?Expires=1247484042&Signature=hpK%2BbHchAmUCErdz1yCBPazzGRQ%3D")
- @http_mock.stub!(:request).and_yield(@http_response_mock).and_return(@http_response_mock)
- @http_response_mock.should_receive(:read_body).and_return(true)
- do_run_request(:GET, false, 10, true)
+ describe "delete_rest" do
+ it "should create a url from the path and base url" do
+ URI.should_receive(:parse).with("url/monkey")
+ @rest.stub!(:run_request)
+ @rest.delete_rest("monkey")
+ end
+
+ it "should call run_request :DELETE with the composed url object" do
+ URI.stub!(:parse).and_return(true)
+ @rest.should_receive(:run_request).with(:DELETE, true, {}).and_return(true)
+ @rest.delete_rest("monkey")
+ end
end
-
- it "should use SSL if the url starts with https" do
- @url_mock.should_receive(:scheme).and_return("https")
- @http_mock.should_receive(:use_ssl=).with(true).and_return(true)
- do_run_request
+
+ describe "post_rest" do
+ it "should create a url from the path and base url" do
+ URI.should_receive(:parse).with("url/monkey")
+ @rest.stub!(:run_request)
+ @rest.post_rest("monkey", "data")
+ end
+
+ it "should call run_request :POST with the composed url object and data" do
+ URI.stub!(:parse).and_return(true)
+ @rest.should_receive(:run_request).with(:POST, true, {}, "data").and_return(true)
+ @rest.post_rest("monkey", "data")
+ end
end
-
- it "should set the OpenSSL Verify Mode to verify_none if requested" do
- @http_mock.should_receive(:verify_mode=).and_return(true)
- do_run_request
+
+ describe "put_rest" do
+ it "should create a url from the path and base url" do
+ URI.should_receive(:parse).with("url/monkey")
+ @rest.stub!(:run_request)
+ @rest.put_rest("monkey", "data")
+ end
+
+ it "should call run_request :PUT with the composed url object and data" do
+ URI.stub!(:parse).and_return(true)
+ @rest.should_receive(:run_request).with(:PUT, true, {}, "data").and_return(true)
+ @rest.put_rest("monkey", "data")
+ end
end
-
- describe "with a client SSL cert" do
+
+ describe Chef::REST, "run_request method" do
before(:each) do
- Chef::Config[:ssl_client_cert] = "/etc/chef/client-cert.pem"
- Chef::Config[:ssl_client_key] = "/etc/chef/client-cert.key"
- File.stub!(:exists?).with("/etc/chef/client-cert.pem").and_return(true)
- File.stub!(:exists?).with("/etc/chef/client-cert.key").and_return(true)
- File.stub!(:read).with("/etc/chef/client-cert.pem").and_return("monkey magic client")
- File.stub!(:read).with("/etc/chef/client-cert.key").and_return("monkey magic key")
- OpenSSL::X509::Certificate.stub!(:new).and_return("monkey magic client data")
- OpenSSL::PKey::RSA.stub!(:new).and_return("monkey magic key data")
+ @url_mock = mock("URI", :null_object => true)
+ @url_mock.stub!(:host).and_return("one")
+ @url_mock.stub!(:port).and_return("80")
+ @url_mock.stub!(:path).and_return("/")
+ @url_mock.stub!(:query).and_return("foo=bar")
+ @url_mock.stub!(:scheme).and_return("https")
+ @url_mock.stub!(:to_s).and_return("https://one:80/?foo=bar")
+ @http_response_mock = mock("Net::HTTPSuccess", :null_object => true)
+ @http_response_mock.stub!(:kind_of?).with(Net::HTTPSuccess).and_return(true)
+ @http_response_mock.stub!(:body).and_return("ninja")
+ @http_response_mock.stub!(:error!).and_return(true)
+ @http_response_mock.stub!(:header).and_return({ 'Content-Length' => "5" })
+ @http_mock = mock("Net::HTTP", :null_object => true)
+ @http_mock.stub!(:verify_mode=).and_return(true)
+ @http_mock.stub!(:read_timeout=).and_return(true)
+ @http_mock.stub!(:use_ssl=).with(true).and_return(true)
+ @data_mock = mock("Data", :null_object => true)
+ @data_mock.stub!(:to_json).and_return('{ "one": "two" }')
+ @request_mock = mock("Request", :null_object => true)
+ @request_mock.stub!(:body=).and_return(true)
+ @request_mock.stub!(:method).and_return(true)
+ @request_mock.stub!(:path).and_return(true)
+ @http_mock.stub!(:request).and_return(@http_response_mock)
+ @tf_mock = mock(Tempfile, { :print => true, :close => true, :write => true })
+ Tempfile.stub!(:new).with("chef-rest").and_return(@tf_mock)
end
-
- it "should check that the client cert file exists" do
- File.should_receive(:exists?).with("/etc/chef/client-cert.pem").and_return(true)
+
+ def do_run_request(method=:GET, data=false, limit=10, raw=false)
+ Net::HTTP.stub!(:new).and_return(@http_mock)
+ @rest.run_request(method, @url_mock, {}, data, limit, raw)
+ end
+
+ it "should raise an exception if the redirect limit is 0" do
+ lambda { @rest.run_request(:GET, "/", {}, false, 0)}.should raise_error(ArgumentError)
+ end
+
+ it "should use SSL if the url starts with https" do
+ @url_mock.should_receive(:scheme).and_return("https")
+ @http_mock.should_receive(:use_ssl=).with(true).and_return(true)
do_run_request
end
-
- it "should read the cert file" do
- File.should_receive(:read).with("/etc/chef/client-cert.pem").and_return("monkey magic client")
+
+ it "should set the OpenSSL Verify Mode to verify_none if requested" do
+ @http_mock.should_receive(:verify_mode=).and_return(true)
do_run_request
end
+
+ describe "with a client SSL cert" do
+ before(:each) do
+ Chef::Config[:ssl_client_cert] = "/etc/chef/client-cert.pem"
+ Chef::Config[:ssl_client_key] = "/etc/chef/client-cert.key"
+ File.stub!(:exists?).with("/etc/chef/client-cert.pem").and_return(true)
+ File.stub!(:exists?).with("/etc/chef/client-cert.key").and_return(true)
+ File.stub!(:read).with("/etc/chef/client-cert.pem").and_return("monkey magic client")
+ File.stub!(:read).with("/etc/chef/client-cert.key").and_return("monkey magic key")
+ OpenSSL::X509::Certificate.stub!(:new).and_return("monkey magic client data")
+ OpenSSL::PKey::RSA.stub!(:new).and_return("monkey magic key data")
+ end
+
+ it "should check that the client cert file exists" do
+ File.should_receive(:exists?).with("/etc/chef/client-cert.pem").and_return(true)
+ do_run_request
+ end
+
+ it "should read the cert file" do
+ File.should_receive(:read).with("/etc/chef/client-cert.pem").and_return("monkey magic client")
+ do_run_request
+ end
+
+ it "should read the cert into OpenSSL" do
+ OpenSSL::X509::Certificate.should_receive(:new).and_return("monkey magic client data")
+ do_run_request
+ end
+
+ it "should set the cert" do
+ @http_mock.should_receive(:cert=).and_return(true)
+ do_run_request
+ end
+
+ it "should read the key file" do
+ File.should_receive(:read).with("/etc/chef/client-cert.key").and_return("monkey magic key")
+ do_run_request
+ end
+
+ it "should read the key into OpenSSL" do
+ OpenSSL::PKey::RSA.should_receive(:new).and_return("monkey magic key data")
+ do_run_request
+ end
+
+ it "should set the key" do
+ @http_mock.should_receive(:key=).and_return(true)
+ do_run_request
+ end
- it "should read the cert into OpenSSL" do
- OpenSSL::X509::Certificate.should_receive(:new).and_return("monkey magic client data")
- do_run_request
end
- it "should set the cert" do
- @http_mock.should_receive(:cert=).and_return(true)
+ it "should set a read timeout based on the rest_timeout config option" do
+ Chef::Config[:rest_timeout] = 10
+ @http_mock.should_receive(:read_timeout=).with(10).and_return(true)
do_run_request
end
-
- it "should read the key file" do
- File.should_receive(:read).with("/etc/chef/client-cert.key").and_return("monkey magic key")
+
+ it "should set the cookie for this request if one exists for the given host:port" do
+ @rest.cookies = { "#{@url_mock.host}:#{@url_mock.port}" => "cookie monster" }
+ Net::HTTP::Get.should_receive(:new).with("/?foo=bar",
+ { 'Accept' => 'application/json', 'Cookie' => 'cookie monster' }
+ ).and_return(@request_mock)
do_run_request
+ @rest.cookies = Hash.new
end
-
- it "should read the key into OpenSSL" do
- OpenSSL::PKey::RSA.should_receive(:new).and_return("monkey magic key data")
+
+ it "should build a new HTTP GET request" do
+ Net::HTTP::Get.should_receive(:new).with("/?foo=bar",
+ { 'Accept' => 'application/json' }
+ ).and_return(@request_mock)
do_run_request
end
-
- it "should set the key" do
- @http_mock.should_receive(:key=).and_return(true)
+
+ it "should build a new HTTP POST request" do
+ Net::HTTP::Post.should_receive(:new).with("/?foo=bar",
+ { 'Accept' => 'application/json', "Content-Type" => 'application/json' }
+ ).and_return(@request_mock)
+ do_run_request(:POST, @data_mock)
+ end
+
+ it "should build a new HTTP PUT request" do
+ Net::HTTP::Put.should_receive(:new).with("/?foo=bar",
+ { 'Accept' => 'application/json', "Content-Type" => 'application/json' }
+ ).and_return(@request_mock)
+ do_run_request(:PUT, @data_mock)
+ end
+
+ it "should build a new HTTP DELETE request" do
+ Net::HTTP::Delete.should_receive(:new).with("/?foo=bar",
+ { 'Accept' => 'application/json' }
+ ).and_return(@request_mock)
+ do_run_request(:DELETE)
+ end
+
+ it "should raise an error if the method is not GET/PUT/POST/DELETE" do
+ lambda { do_run_request(:MONKEY) }.should raise_error(ArgumentError)
+ end
+
+ it "should run an http request" do
+ @http_mock.should_receive(:request).and_return(@http_response_mock)
do_run_request
end
-
- end
-
- it "should set a read timeout based on the rest_timeout config option" do
- Chef::Config[:rest_timeout] = 10
- @http_mock.should_receive(:read_timeout=).with(10).and_return(true)
- do_run_request
- end
-
- it "should set the cookie for this request if one exists for the given host:port" do
- @r.cookies = { "#{@url_mock.host}:#{@url_mock.port}" => "cookie monster" }
- Net::HTTP::Get.should_receive(:new).with("/?foo=bar",
- { 'Accept' => 'application/json', 'Cookie' => 'cookie monster' }
- ).and_return(@request_mock)
- do_run_request
- end
-
- it "should build a new HTTP GET request" do
- Net::HTTP::Get.should_receive(:new).with("/?foo=bar",
- { 'Accept' => 'application/json' }
- ).and_return(@request_mock)
- do_run_request
- end
-
- it "should build a new HTTP POST request" do
- Net::HTTP::Post.should_receive(:new).with("/",
- { 'Accept' => 'application/json', "Content-Type" => 'application/json' }
- ).and_return(@request_mock)
- do_run_request(:POST, @data_mock)
- end
-
- it "should build a new HTTP PUT request" do
- Net::HTTP::Put.should_receive(:new).with("/",
- { 'Accept' => 'application/json', "Content-Type" => 'application/json' }
- ).and_return(@request_mock)
- do_run_request(:PUT, @data_mock)
- end
-
- it "should build a new HTTP DELETE request" do
- Net::HTTP::Delete.should_receive(:new).with("/?foo=bar",
- { 'Accept' => 'application/json' }
- ).and_return(@request_mock)
- do_run_request(:DELETE)
- end
-
- describe "with HTTP Basic Authentication info in the URL" do
- before(:each) do
- @url_mock.stub!(:user).and_return('frodo')
- @url_mock.stub!(:password).and_return('odorf')
+
+ it "should return the body of the response on success" do
+ do_run_request.should eql("ninja")
end
-
- %w(Get Post Put Delete).each do |verb|
- it "should authenticate HTTP #{verb.upcase} requests" do
- Net::HTTP::const_get(verb).stub!(:new).and_return(@request_mock)
- @request_mock.should_receive(:basic_auth).with('frodo', 'odorf')
- do_run_request(verb.upcase.to_sym)
- end
+
+ it "should inflate the body as to an object if JSON is returned" do
+ @http_response_mock.stub!(:[]).with('content-type').and_return("application/json")
+ JSON.should_receive(:parse).with("ninja").and_return(true)
+ do_run_request
+ end
+
+ it "should call run_request again on a Redirect response" do
+ @http_response_mock.stub!(:kind_of?).with(Net::HTTPSuccess).and_return(false)
+ @http_response_mock.stub!(:kind_of?).with(Net::HTTPFound).and_return(true)
+ @http_response_mock.stub!(:[]).with('location').and_return(@url_mock.path)
+ lambda { do_run_request(method=:GET, data=false, limit=1) }.should raise_error(ArgumentError)
end
- end
- it "should raise an error if the method is not GET/PUT/POST/DELETE" do
- lambda { do_run_request(:MONKEY) }.should raise_error(ArgumentError)
- end
-
- it "should run an http request" do
- @http_mock.should_receive(:request).and_return(@http_response_mock)
- do_run_request
- end
-
- it "should return the body of the response on success" do
- do_run_request.should eql("ninja")
- end
-
- it "should inflate the body as to an object if JSON is returned" do
- @http_response_mock.stub!(:[]).with('content-type').and_return("application/json")
- JSON.should_receive(:parse).with("ninja").and_return(true)
- do_run_request
- end
-
- it "should call run_request again on a Redirect response" do
- @http_response_mock.stub!(:kind_of?).with(Net::HTTPSuccess).and_return(false)
- @http_response_mock.stub!(:kind_of?).with(Net::HTTPFound).and_return(true)
- @http_response_mock.stub!(:[]).with('location').and_return(@url_mock.path)
- lambda { do_run_request(method=:GET, data=false, limit=1) }.should raise_error(ArgumentError)
- end
+ it "should call run_request again on a Permanent Redirect response" do
+ @http_response_mock.stub!(:kind_of?).with(Net::HTTPSuccess).and_return(false)
+ @http_response_mock.stub!(:kind_of?).with(Net::HTTPFound).and_return(false)
+ @http_response_mock.stub!(:kind_of?).with(Net::HTTPMovedPermanently).and_return(true)
+ @http_response_mock.stub!(:[]).with('location').and_return(@url_mock.path)
+ lambda { do_run_request(method=:GET, data=false, limit=1) }.should raise_error(ArgumentError)
+ end
+
+ it "should raise an exception on an unsuccessful request" do
+ @http_response_mock.stub!(:kind_of?).with(Net::HTTPSuccess).and_return(false)
+ @http_response_mock.stub!(:kind_of?).with(Net::HTTPFound).and_return(false)
+ @http_response_mock.stub!(:kind_of?).with(Net::HTTPMovedPermanently).and_return(false)
+ @http_response_mock.should_receive(:error!)
+ do_run_request
+ end
+
+ it "should build a new HTTP GET request without the application/json accept header for raw reqs" do
+ Net::HTTP::Get.should_receive(:new).with("/?foo=bar", {}).and_return(@request_mock)
+ do_run_request(:GET, false, 10, true)
+ end
+
+ it "should create a tempfile for the output of a raw request" do
+ @http_mock.stub!(:request).and_yield(@http_response_mock).and_return(@http_response_mock)
+ Tempfile.should_receive(:new).with("chef-rest").and_return(@tf_mock)
+ do_run_request(:GET, false, 10, true).should eql(@tf_mock)
+ end
+
+ it "should read the body of the response in chunks on a raw request" do
+ @http_mock.stub!(:request).and_yield(@http_response_mock).and_return(@http_response_mock)
+ @http_response_mock.should_receive(:read_body).and_return(true)
+ do_run_request(:GET, false, 10, true)
+ end
+
+ it "should populate the tempfile with the value of the raw request" do
+ @http_mock.stub!(:request).and_yield(@http_response_mock).and_return(@http_response_mock)
+ @http_response_mock.stub!(:read_body).and_yield("ninja")
+ @tf_mock.should_receive(:write, "ninja").once.and_return(true)
+ do_run_request(:GET, false, 10, true)
+ end
+
+ it "should close the tempfile if we're doing a raw request" do
+ @http_mock.stub!(:request).and_yield(@http_response_mock).and_return(@http_response_mock)
+ @tf_mock.should_receive(:close).once.and_return(true)
+ do_run_request(:GET, false, 10, true)
+ end
+
+ it "should not raise a divide by zero exception if the size is 0" do
+ @http_mock.stub!(:request).and_yield(@http_response_mock).and_return(@http_response_mock)
+ @http_response_mock.stub!(:header).and_return({ 'Content-Length' => "5" })
+ @http_response_mock.stub!(:read_body).and_yield('')
+ lambda { do_run_request(:GET, false, 10, true) }.should_not raise_error(ZeroDivisionError)
+ end
+
+ it "should not raise a divide by zero exception if the Content-Length is 0" do
+ @http_mock.stub!(:request).and_yield(@http_response_mock).and_return(@http_response_mock)
+ @http_response_mock.stub!(:header).and_return({ 'Content-Length' => "0" })
+ @http_response_mock.stub!(:read_body).and_yield("ninja")
+ lambda { do_run_request(:GET, false, 10, true) }.should_not raise_error(ZeroDivisionError)
+ end
+
+ it "should call read_body without a block if the request is not raw" do
+ @http_mock.stub!(:request).and_yield(@http_response_mock).and_return(@http_response_mock)
+ @http_response_mock.should_receive(:read_body)
+ do_run_request(:GET, false, 10, false)
+ end
- it "should call run_request again on a Permanent Redirect response" do
- @http_response_mock.stub!(:kind_of?).with(Net::HTTPSuccess).and_return(false)
- @http_response_mock.stub!(:kind_of?).with(Net::HTTPFound).and_return(false)
- @http_response_mock.stub!(:kind_of?).with(Net::HTTPMovedPermanently).and_return(true)
- @http_response_mock.stub!(:[]).with('location').and_return(@url_mock.path)
- lambda { do_run_request(method=:GET, data=false, limit=1) }.should raise_error(ArgumentError)
- end
-
- it "should raise an exception on an unsuccessful request" do
- @http_response_mock.stub!(:kind_of?).with(Net::HTTPSuccess).and_return(false)
- @http_response_mock.stub!(:kind_of?).with(Net::HTTPFound).and_return(false)
- @http_response_mock.stub!(:kind_of?).with(Net::HTTPMovedPermanently).and_return(false)
- @http_response_mock.should_receive(:error!)
- do_run_request
- end
-
- it "should build a new HTTP GET request without the application/json accept header for raw reqs" do
- Net::HTTP::Get.should_receive(:new).with("/?foo=bar", {}).and_return(@request_mock)
- do_run_request(:GET, false, 10, true)
- end
-
- it "should create a tempfile for the output of a raw request" do
- @http_mock.stub!(:request).and_yield(@http_response_mock).and_return(@http_response_mock)
- Tempfile.should_receive(:new).with("chef-rest").and_return(@tf_mock)
- do_run_request(:GET, false, 10, true).should eql(@tf_mock)
- end
-
- it "should read the body of the response in chunks on a raw request" do
- @http_mock.stub!(:request).and_yield(@http_response_mock).and_return(@http_response_mock)
- @http_response_mock.should_receive(:read_body).and_return(true)
- do_run_request(:GET, false, 10, true)
- end
-
- it "should populate the tempfile with the value of the raw request" do
- @http_mock.stub!(:request).and_yield(@http_response_mock).and_return(@http_response_mock)
- @http_response_mock.stub!(:read_body).and_yield("ninja")
- @tf_mock.should_receive(:write, "ninja").once.and_return(true)
- do_run_request(:GET, false, 10, true)
- end
-
- it "should close the tempfile if we're doing a raw request" do
- @http_mock.stub!(:request).and_yield(@http_response_mock).and_return(@http_response_mock)
- @tf_mock.should_receive(:close).once.and_return(true)
- do_run_request(:GET, false, 10, true)
- end
-
- it "should not raise a divide by zero exception if the size is 0" do
- @http_mock.stub!(:request).and_yield(@http_response_mock).and_return(@http_response_mock)
- @http_response_mock.stub!(:header).and_return({ 'Content-Length' => "5" })
- @http_response_mock.stub!(:read_body).and_yield('')
- lambda { do_run_request(:GET, false, 10, true) }.should_not raise_error(ZeroDivisionError)
- end
-
- it "should not raise a divide by zero exception if the Content-Length is 0" do
- @http_mock.stub!(:request).and_yield(@http_response_mock).and_return(@http_response_mock)
- @http_response_mock.stub!(:header).and_return({ 'Content-Length' => "0" })
- @http_response_mock.stub!(:read_body).and_yield("ninja")
- lambda { do_run_request(:GET, false, 10, true) }.should_not raise_error(ZeroDivisionError)
- end
-
- it "should call read_body without a block if the request is not raw" do
- @http_mock.stub!(:request).and_yield(@http_response_mock).and_return(@http_response_mock)
- @http_response_mock.should_receive(:read_body)
- do_run_request(:GET, false, 10, false)
end
end
+
diff --git a/chef/spec/unit/run_list_spec.rb b/chef/spec/unit/run_list_spec.rb
index 6e63d9edd4..deafa785af 100644
--- a/chef/spec/unit/run_list_spec.rb
+++ b/chef/spec/unit/run_list_spec.rb
@@ -175,7 +175,7 @@ describe Chef::RunList do
describe "from couchdb" do
it "should load the role from couchdb" do
- Chef::Role.should_receive(:load).with("stubby")
+ Chef::Role.should_receive(:load)
@run_list.expand("couchdb")
end
end
diff --git a/chef/spec/unit/search/query_spec.rb b/chef/spec/unit/search/query_spec.rb
new file mode 100644
index 0000000000..8cb1a87832
--- /dev/null
+++ b/chef/spec/unit/search/query_spec.rb
@@ -0,0 +1,105 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2009 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+require 'chef/search/query'
+
+describe Chef::Search::Query do
+ before(:each) do
+ @rest = mock("Chef::REST", :null_object => true)
+ Chef::REST.stub!(:new).and_return(@rest)
+ @query = Chef::Search::Query.new
+ end
+
+ describe "initialize" do
+ it "should return a Chef::Search::Query" do
+ @query.should be_a_kind_of(Chef::Search::Query)
+ end
+ end
+
+ describe "search" do
+ before(:each) do
+ @response = {
+ "rows" => [
+ { "id" => "for you" },
+ { "id" => "hip hop" },
+ { "id" => "thought was down by law for you" },
+ { "id" => "kept it hard core for you" },
+ ],
+ "start" => 0,
+ "total" => 4
+ }
+ @rest.stub!(:get_rest).and_return(@response)
+ end
+
+ it "should accept a type as the first argument" do
+ lambda { @query.search("foo") }.should_not raise_error(ArgumentError)
+ lambda { @query.search(:foo) }.should_not raise_error(ArgumentError)
+ lambda { @query.search(Hash.new) }.should raise_error(ArgumentError)
+ end
+
+ it "should query for every object of a type by default" do
+ @rest.should_receive(:get_rest).with("search/foo?q=%2A%3A%2A&sort=&start=0&rows=20").and_return(@response)
+ @query = Chef::Search::Query.new
+ @query.search(:foo)
+ end
+
+ it "should allow a custom query" do
+ @rest.should_receive(:get_rest).with("search/foo?q=gorilla%3Adundee&sort=&start=0&rows=20").and_return(@response)
+ @query = Chef::Search::Query.new
+ @query.search(:foo, "gorilla:dundee")
+ end
+
+ it "should let you set a sort order" do
+ @rest.should_receive(:get_rest).with("search/foo?q=gorilla%3Adundee&sort=id+desc&start=0&rows=20").and_return(@response)
+ @query = Chef::Search::Query.new
+ @query.search(:foo, "gorilla:dundee", "id desc")
+ end
+
+ it "should let you set a starting object" do
+ @rest.should_receive(:get_rest).with("search/foo?q=gorilla%3Adundee&sort=id+desc&start=2&rows=20").and_return(@response)
+ @query = Chef::Search::Query.new
+ @query.search(:foo, "gorilla:dundee", "id desc", 2)
+ end
+
+ it "should let you set how many rows to return" do
+ @rest.should_receive(:get_rest).with("search/foo?q=gorilla%3Adundee&sort=id+desc&start=2&rows=40").and_return(@response)
+ @query = Chef::Search::Query.new
+ @query.search(:foo, "gorilla:dundee", "id desc", 2, 40)
+ end
+
+ it "should return the raw rows, start, and total if no block is passed" do
+ rows, start, total = @query.search(:foo)
+ rows.should equal(@response["rows"])
+ start.should equal(@response["start"])
+ total.should equal(@response["total"])
+ end
+
+ it "should call a block for each object in the response" do
+ @call_me = mock("blocky")
+ @response["rows"].each { |r| @call_me.should_receive(:do).with(r) }
+ @query.search(:foo) { |r| @call_me.do(r) }
+ end
+
+ it "should page through the responses" do
+ @call_me = mock("blocky")
+ @response["rows"].each { |r| @call_me.should_receive(:do).with(r) }
+ @query.search(:foo, "*:*", nil, 0, 1) { |r| @call_me.do(r) }
+ end
+ end
+end
diff --git a/chef/spec/unit/search/result_spec.rb b/chef/spec/unit/search/result_spec.rb
deleted file mode 100644
index c72b46fa70..0000000000
--- a/chef/spec/unit/search/result_spec.rb
+++ /dev/null
@@ -1,84 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@opscode.com>)
-# Copyright:: Copyright (c) 2009 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# 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.
-#
-
-require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
-require 'chef/search/result'
-
-describe Chef::Search::Result do
- before(:each) do
- @sr = Chef::Search::Result.new
- end
-
- describe "initialize" do
- it "should return a Chef::Search::Result" do
- @sr.should be_a_kind_of(Chef::Search::Result)
- end
- end
-
- describe "hash like behavior" do
- it "should let you set and retrieve data" do
- @sr[:one] = "two"
- @sr[:one].should == "two"
- end
-
- it "should let you enumerate with each" do
- @sr[:one] = "two"
- @sr[:three] = "four"
- seen = Hash.new
- @sr.each do |k,v|
- seen[k] = v
- end
- seen["one"].should == "two"
- seen["three"].should == "four"
- end
- end
-
- describe "auto-inflate to a nested hash" do
- it "should allow for _ seperated keys to be auto-inflated to nested hashes" do
- @sr["one_two_three"] = "four"
- @sr["one"]["two"]["three"].should == "four"
- end
- end
-
- describe "to_json" do
- it "should serialize to json" do
- @sr[:one] = "two"
- @sr[:three] = "four"
- @sr.to_json.should =~ /"one":"two"/
- @sr.to_json.should =~ /"three":"four"/
- @sr.to_json.should =~ /"json_class":"Chef::Search::Result"/
- end
- end
-
- describe "json_create" do
- before(:each) do
- @sr[:one] = "two"
- @sr[:three] = "four"
- @new_sr = JSON.parse(@sr.to_json)
- end
-
- it "should create a new Chef::Search::Result" do
- @new_sr.should be_a_kind_of(Chef::Search::Result)
- end
-
- it "have all the keys of the original Chef::Search::Result" do
- @new_sr["one"].should == "two"
- @new_sr["three"].should == "four"
- end
- end
-end
diff --git a/chef/tasks/rspec.rb b/chef/tasks/rspec.rb
index 44e9ea2864..45a6faa1c6 100644
--- a/chef/tasks/rspec.rb
+++ b/chef/tasks/rspec.rb
@@ -8,7 +8,7 @@ task :default => :spec
desc "Run all specs in spec directory"
Spec::Rake::SpecTask.new(:spec) do |t|
- t.spec_opts = ['--options', "\"#{CHEF_ROOT}/spec/spec.opts\""]
+ t.spec_opts = ['--format', 'specdoc', '--options', "\"#{CHEF_ROOT}/spec/spec.opts\""]
t.spec_files = FileList['spec/**/*_spec.rb']
end
diff --git a/cucumber.yml b/cucumber.yml
index 03ab26c20c..f2b579cd13 100644
--- a/cucumber.yml
+++ b/cucumber.yml
@@ -1,22 +1,34 @@
default: -f pretty features -r features/steps -r features/support
-api: --tags api --format pretty -r features/steps -r features/support features
-api_roles: --tags roles --format pretty -r features/steps -r features/support features
+api: --tags @api --format pretty -r features/steps -r features/support features
+api_cookbooks: --tags @api,@cookbooks --format pretty -r features/steps -r features/support features
+api_cookbooks_tarballs: --tags @api,@cookbooks,@tarballs --format pretty -r features/steps -r features/support features
+api_roles: --tags @api_roles --format pretty -r features/steps -r features/support features
api_roles_create: --tags roles_create --format pretty -r features/steps -r features/support features
api_roles_delete: --tags roles_delete --format pretty -r features/steps -r features/support features
api_roles_list: --tags roles_list --format pretty -r features/steps -r features/support features
api_roles_show: --tags roles_show --format pretty -r features/steps -r features/support features
api_roles_update: --tags roles_update --format pretty -r features/steps -r features/support features
-api_nodes: --tags nodes --format pretty -r features/steps -r features/support features
+api_nodes: --tags @api_nodes --format pretty -r features/steps -r features/support features
api_nodes_create: --tags nodes_create --format pretty -r features/steps -r features/support features
api_nodes_delete: --tags nodes_delete --format pretty -r features/steps -r features/support features
api_nodes_list: --tags nodes_list --format pretty -r features/steps -r features/support features
api_nodes_show: --tags nodes_show --format pretty -r features/steps -r features/support features
api_nodes_update: --tags nodes_update --format pretty -r features/steps -r features/support features
-client: --tags client --format pretty -r features/steps -r features/support features
+api_data: --tags @api_data --format pretty -r features/steps -r features/support features
+api_data_delete: --tags @api_data_delete --format pretty -r features/steps -r features/support features
+api_data_item: --tags @api_data_item --format pretty -r features/steps -r features/support features
+api_search: --tags @api_search --format pretty -r features/steps -r features/support features
+api_search_list: --tags @api_search_list --format pretty -r features/steps -r features/support features
+api_search_show: --tags @api_search_show --format pretty -r features/steps -r features/support features
+client: --tags @client --format pretty -r features/steps -r features/support features
client_roles: --tags client_roles --format pretty -r features/steps -r features/support features
-search: --tags search --format pretty -r features/steps -r features/support features
-language: --tags language --format pretty -r features/steps -r features/support features
-recipe_inclusion: --tags recipe_inclusion --format pretty -r features/steps -r features/support features
-provider_remote_file: --tags provider,remote_file --format pretty -r features/steps -r features/support features
-provider_package_macports: --tags macports --format pretty -r features/steps -r features/support features
+search: --tags @search --format pretty -r features/steps -r features/support features
+provider: --tags @provider --format pretty -r features/steps -r features/support features
+provider_directory: --tags @provider_directory --format pretty -r features/steps -r features/support features
+provider_execute: --tags @provider_execute --format pretty -r features/steps -r features/support features
+provider_file: --tags @provider_file --format pretty -r features/steps -r features/support features
+provider_remote_file: --tags @remote_file --format pretty -r features/steps -r features/support features
+provider_package_macports: --tags @macports --format pretty -r features/steps -r features/support features
+language: --tags @language --format pretty -r features/steps -r features/support features
+cookbooks: --tags @cookbooks --format pretty -r features/steps -r features/support features
diff --git a/features/api/cookbooks/list_cookbooks_api.feature b/features/api/cookbooks/list_cookbooks_api.feature
new file mode 100644
index 0000000000..8888cfa3ce
--- /dev/null
+++ b/features/api/cookbooks/list_cookbooks_api.feature
@@ -0,0 +1,19 @@
+@api @cookbooks @list_cookbooks
+Feature: List cookbooks via the REST API
+ In order to know what cookbooks are loaded on the Chef Server
+ As a Developer
+ I want to list all the cookbooks
+
+ Scenario: List cookbooks
+ Given a 'registration' named 'bobo' exists
+ When I 'GET' the path '/cookbooks'
+ Then the inflated responses key 'manage_files' should exist
+ And the inflated responses key 'manage_files' should match 'http://[^/]+/organizations/clownco/cookbooks/manage_files'
+ And the inflated responses key 'delayed_notifications' should exist
+ And the inflated responses key 'delayed_notifications' should match 'http://[^/]+/organizations/clownco/cookbooks/delayed_notifications'
+
+ Scenario: List cookbooks with a wrong private key
+ Given a 'registration' named 'bobo' exists
+ When I 'GET' the path '/cookbooks' using a wrong private key
+ Then I should get a '401 "Unauthorized"' exception
+
diff --git a/features/api/cookbooks/show_cookbook_api.feature b/features/api/cookbooks/show_cookbook_api.feature
new file mode 100644
index 0000000000..48e19a2981
--- /dev/null
+++ b/features/api/cookbooks/show_cookbook_api.feature
@@ -0,0 +1,32 @@
+@api @cookbooks @show_cookbook
+Feature: Show a cookbook via the REST API
+ In order to know what the details are for a cookbook
+ As a Developer
+ I want to show the details for a specific cookbook
+
+ Scenario: Show a cookbook
+ Given a 'registration' named 'bobo' exists
+ When I 'GET' the path '/cookbooks/show_cookbook'
+ Then the inflated responses key 'name' should match 'show_cookbook'
+ Then the inflated responses key 'files' should match '^\[.+\]$' as json
+ Then the inflated responses key 'recipes' should match '^\[.+\]$' as json
+ Then the inflated responses key 'metadata' should match '^\{.+\}$' as json
+ Then the inflated responses key 'attributes' should match '^\[.+\]$' as json
+ Then the inflated responses key 'libraries' should match '^\[.+\]$' as json
+ Then the inflated responses key 'definitions' should match '^\[.+\]$' as json
+ Then the inflated responses key 'templates' should match '^\[.+\]$' as json
+
+ Scenario: Show a missing cookbook
+ Given a 'registration' named 'bobo' exists
+ When I download the 'frabjabtasticaliciousmonkeyman' cookbook
+ Then the response code should be '404'
+
+ Scenario: Show a cookbook with a wrong private key
+ Given a 'registration' named 'bobo' exists
+ When I 'GET' the path '/cookbooks/show_cookbook' using a wrong private key
+ Then I should get a '401 "Unauthorized"' exception
+
+ Scenario: Show a cookbook without authenticating
+ When I 'GET' the path '/cookbooks/show_cookbook'
+ Then I should get a '401 "Unauthorized"' exception
+
diff --git a/features/api/cookbooks/show_cookbook_attributes_api.feature b/features/api/cookbooks/show_cookbook_attributes_api.feature
new file mode 100644
index 0000000000..8e4fc0cd1a
--- /dev/null
+++ b/features/api/cookbooks/show_cookbook_attributes_api.feature
@@ -0,0 +1,21 @@
+@api @cookbooks
+Feature: Show a cookbooks attribute files via the REST API
+ In order to know what the details are for a cookbooks attribute files
+ As a Developer
+ I want to show the attribute files for a specific cookbook
+
+ Scenario: Show a cookbooks attribute files
+ Given a 'registration' named 'bobo' exists
+ When I 'GET' the path '/cookbooks/show_cookbook/attributes'
+ Then the inflated response should match '^[.+]$' as json
+
+ Scenario: Show a missing cookbook
+ Given a 'registration' named 'bobo' exists
+ When I 'GET' the path '/cookbooks/frabjabtasticaliciousmonkeyman/attributes'
+ Then I should get a '404 "Not Found"' exception
+
+ Scenario: Show a cookbooks attribute files with a wrong private key
+ Given a 'registration' named 'bobo' exists
+ When I 'GET' the path '/cookbooks/show_cookbook/attributes' using a wrong private key
+ Then I should get a '401 "Unauthorized"' exception
+
diff --git a/features/api/data/create_data_bag_api.feature b/features/api/data/create_data_bag_api.feature
new file mode 100644
index 0000000000..c76018b2f0
--- /dev/null
+++ b/features/api/data/create_data_bag_api.feature
@@ -0,0 +1,26 @@
+@api @data @api_data
+Feature: Create a data bag via the REST API
+ In order to create data bags programatically
+ As a Devleoper
+ I want to create data bags via the REST API
+
+ Scenario: Create a new data bag
+ Given a 'registration' named 'bobo' exists
+ And a 'data_bag' named 'users'
+ When I authenticate as 'bobo'
+ And I 'POST' the 'data_bag' to the path '/data'
+ And the inflated responses key 'uri' should match '^http://.+/data/users$'
+
+ Scenario: Create a data bag that already exists
+ Given a 'registration' named 'bobo' exists
+ And a 'data_bag' named 'users'
+ When I authenticate as 'bobo'
+ And I 'POST' the 'data_bag' to the path '/data'
+ And I 'POST' the 'data_bag' to the path '/data'
+ Then I should get a '403 "Forbidden"' exception
+
+ Scenario: Create a new data bag without authenticating
+ Given a 'data_bag' named 'webserver'
+ When I 'POST' the 'data_bag' to the path '/data'
+ Then I should get a '401 "Unauthorized"' exception
+
diff --git a/features/api/data/create_data_bag_item_api.feature b/features/api/data/create_data_bag_item_api.feature
new file mode 100644
index 0000000000..ebd9637b2b
--- /dev/null
+++ b/features/api/data/create_data_bag_item_api.feature
@@ -0,0 +1,29 @@
+@api @data @api_data @api_data_item
+Feature: Create a data bag item via the REST API
+ In order to store an item in a data bag programatically
+ As a Devleoper
+ I want to store data bag items via the REST API
+
+ Scenario: Create a new data bag item
+ Given a 'registration' named 'bobo' exists
+ And a 'data_bag' named 'users' exists
+ And a 'data_bag_item' named 'francis'
+ When I authenticate as 'bobo'
+ And I 'PUT' the 'data_bag_item' to the path '/data/users/francis'
+ Then the inflated responses key 'id' should match '^francis$'
+
+ Scenario: Update a data bag item that already exists
+ Given a 'registration' named 'bobo' exists
+ And a 'data_bag' named 'users' exists
+ And a 'data_bag_item' named 'francis' exists
+ And a 'data_bag_item' named 'francis_extra'
+ When I authenticate as 'bobo'
+ And I 'PUT' the 'data_bag_item' to the path '/data/users/francis'
+ Then the inflated responses key 'id' should match '^francis$'
+ And the inflated responses key 'extra' should match '^majority$'
+
+ Scenario: Create a new data bag without authenticating
+ Given a 'data_bag' named 'users' exists
+ And a 'data_bag_item' named 'francis'
+ When I 'PUT' the 'data_bag_item' to the path '/data/users/francis'
+ Then I should get a '401 "Unauthorized"' exception
diff --git a/features/api/data/delete_data_bag_api.feature b/features/api/data/delete_data_bag_api.feature
new file mode 100644
index 0000000000..560d1e3ea6
--- /dev/null
+++ b/features/api/data/delete_data_bag_api.feature
@@ -0,0 +1,34 @@
+@api @data @api_data @api_data_delete
+Feature: Delete a Data Bag via the REST API
+ In order to remove a Data Bag
+ As a Developer
+ I want to delete a Data Bag via the REST API
+
+ Scenario: Delete a Data Bag
+ Given a 'registration' named 'bobo' exists
+ And a 'data_bag' named 'users' exists
+ When I authenticate as 'bobo'
+ And I 'DELETE' the path '/data/users'
+ Then the inflated response should respond to 'name' with 'users'
+
+ Scenario: Delete a Data Bag that does not exist
+ Given a 'registration' named 'bobo' exists
+ And there are no Data Bags
+ When I authenticate as 'bobo'
+ When I 'DELETE' the path '/data/users'
+ Then I should get a '404 "Not Found"' exception
+
+ Scenario: Delete a Data Bag that has items in it
+ Given a 'registration' named 'bobo' exists
+ And a 'data_bag' named 'users' exists
+ And a 'data_bag_item' named 'francis' exists
+ When I authenticate as 'bobo'
+ And I 'DELETE' the path '/data/users'
+ Then the inflated response should respond to 'name' with 'users'
+ And the data_bag named 'users' should not have an item named 'francis'
+
+ Scenario: Delete a Data Bag without authenticating
+ Given a 'data_bag' named 'users' exists
+ When I 'DELETE' the path '/data/users'
+ Then I should get a '401 "Unauthorized"' exception
+
diff --git a/features/api/data/delete_data_bag_item.feature b/features/api/data/delete_data_bag_item.feature
new file mode 100644
index 0000000000..057c2e55ec
--- /dev/null
+++ b/features/api/data/delete_data_bag_item.feature
@@ -0,0 +1,27 @@
+@api @data @api_data @api_data_item
+Feature: Delete a Data Bag Item via the REST API
+ In order to remove a Data Bag Item
+ As a Developer
+ I want to delete a Data Bag Item via the REST API
+
+ Scenario: Delete a Data Bag Item
+ Given a 'registration' named 'bobo' exists
+ And a 'data_bag' named 'users' exists
+ And a 'data_bag_item' named 'francis' exists
+ When I authenticate as 'bobo'
+ And I 'DELETE' the path '/data/users/francis'
+ Then the inflated responses key 'id' should match '^francis$'
+
+ Scenario: Delete a Data Bag Item that does not exist
+ Given a 'registration' named 'bobo' exists
+ And a 'data_bag' named 'users' exists
+ When I authenticate as 'bobo'
+ When I 'DELETE' the path '/data/users/francis'
+ Then I should get a '404 "Not Found"' exception
+
+ Scenario: Delete a Data Bag Item without authenticating
+ Given a 'data_bag' named 'users' exists
+ And a 'data_bag_item' named 'francis' exists
+ When I 'DELETE' the path '/data/users/francis'
+ Then I should get a '401 "Unauthorized"' exception
+
diff --git a/features/api/data/list_data_bags.feature b/features/api/data/list_data_bags.feature
new file mode 100644
index 0000000000..97929d7747
--- /dev/null
+++ b/features/api/data/list_data_bags.feature
@@ -0,0 +1,34 @@
+@api @data @api_data
+Feature: List data bags via the REST API
+ In order to know what data bags exists programatically
+ As a Developer
+ I want to list all the data bags
+
+ Scenario: List data bags when none have been created
+ Given a 'registration' named 'bobo' exists
+ And there are no data bags
+ When I authenticate as 'bobo'
+ And I 'GET' the path '/data'
+ Then the inflated response should be an empty array
+
+ Scenario: List data bags when one has been created
+ Given a 'registration' named 'bobo' exists
+ And a 'data_bag' named 'users' exists
+ When I authenticate as 'bobo'
+ And I 'GET' the path '/data'
+ Then the inflated response should include '^http://.+/data/users$'
+
+ Scenario: List data bags when two have been created
+ Given a 'registration' named 'bobo' exists
+ And a 'data_bag' named 'users' exists
+ And a 'data_bag' named 'rubies' exists
+ When I authenticate as 'bobo'
+ And I 'GET' the path '/data'
+ Then the inflated response should be '2' items long
+ And the inflated response should include '^http://.+/data/users$'
+ And the inflated response should include '^http://.+/data/rubies$'
+
+ Scenario: List data bags when you are not authenticated
+ When I 'GET' the path '/data'
+ Then I should get a '401 "Unauthorized"' exception
+
diff --git a/features/api/data/show_data_bag_api.feature b/features/api/data/show_data_bag_api.feature
new file mode 100644
index 0000000000..d27f64093a
--- /dev/null
+++ b/features/api/data/show_data_bag_api.feature
@@ -0,0 +1,44 @@
+@api @data @api_data
+Feature: Show a data_bag via the REST API
+ In order to know what the details are for a data_bag
+ As a Developer
+ I want to show the details for a specific data_bag
+
+ Scenario: Show a data_bag with no entries in it
+ Given a 'registration' named 'bobo' exists
+ And a 'data_bag' named 'users' exists
+ When I authenticate as 'bobo'
+ And I 'GET' the path '/data/users'
+ Then the inflated response should be an empty array
+
+ Scenario: Show a data_bag with one entry in it
+ Given a 'registration' named 'bobo' exists
+ And a 'data_bag' named 'users' exists
+ And a 'data_bag_item' named 'francis' exists
+ When I authenticate as 'bobo'
+ And I 'GET' the path '/data/users'
+ Then the inflated response should include '/data/users/francis'
+
+ Scenario: Show a data_bag with two entries in it
+ Given a 'registration' named 'bobo' exists
+ And a 'data_bag' named 'users' exists
+ And a 'data_bag_item' named 'francis' exists
+ And a 'data_bag_item' named 'axl_rose' exists
+ When I authenticate as 'bobo'
+ And I 'GET' the path '/data/users'
+ Then the inflated response should include '/data/users/francis'
+ And the inflated response should include '/data/users/axl_rose'
+
+ Scenario: Show a missing data_bag
+ Given a 'registration' named 'bobo' exists
+ And there are no data_bags
+ When I authenticate as 'bobo'
+ And I 'GET' the path '/data/users'
+ Then I should get a '404 "Not Found"' exception
+
+ Scenario: Show a data_bag without authenticating
+ Given a 'data_bag' named 'users' exists
+ And I 'GET' the path '/data/users'
+ Then I should get a '401 "Unauthorized"' exception
+
+
diff --git a/features/api/data/show_data_bag_item_api.feature b/features/api/data/show_data_bag_item_api.feature
new file mode 100644
index 0000000000..fd0e474224
--- /dev/null
+++ b/features/api/data/show_data_bag_item_api.feature
@@ -0,0 +1,28 @@
+@api @data @api_data @api_data_item
+Feature: Show a data_bag item via the REST API
+ In order to know what the data is for an item in a data_bag
+ As a Developer
+ I want to retrieve an item from a data_bag
+
+ Scenario: Show a data_bag item
+ Given a 'registration' named 'bobo' exists
+ And a 'data_bag' named 'users' exists
+ And a 'data_bag_item' named 'francis' exists
+ When I authenticate as 'bobo'
+ And I 'GET' the path '/data/users/francis'
+ Then the inflated responses key 'id' should match '^francis$'
+
+ Scenario: Show a missing data_bag item
+ Given a 'registration' named 'bobo' exists
+ And a 'data_bag' named 'users' exists
+ When I authenticate as 'bobo'
+ And I 'GET' the path '/data/users/francis'
+ Then I should get a '404 "Not Found"' exception
+
+ Scenario: Show a data_bag item without authenticating
+ Given a 'data_bag' named 'users' exists
+ And a 'data_bag_item' named 'francis' exists
+ And I 'GET' the path '/data/users/francis'
+ Then I should get a '401 "Unauthorized"' exception
+
+
diff --git a/features/api/nodes/cookbook_sync_api.feature b/features/api/nodes/cookbook_sync_api.feature
new file mode 100644
index 0000000000..3d39796166
--- /dev/null
+++ b/features/api/nodes/cookbook_sync_api.feature
@@ -0,0 +1,25 @@
+@api @nodes @cookbook_sync @api_nodes
+Feature: Synchronize cookbooks to the edge
+ In order to configure my nodes centrally
+ As a Developer
+ I want to synchronize the cookbooks from the server to the edge nodes
+
+ Scenario: Retrieve the list of cookbook files to synchronize
+ Given a 'registration' named 'bobo' exists
+ And a 'node' named 'sync' exists
+ When I 'GET' the path '/nodes/sync/cookbooks'
+ And the inflated responses key 'node_cookbook_sync' should exist
+ And the inflated responses key 'node_cookbook_sync' should match '"recipes":' as json
+ And the inflated responses key 'node_cookbook_sync' should match 'default.rb' as json
+ And the inflated responses key 'node_cookbook_sync' should match '"definitions":' as json
+ And the inflated responses key 'node_cookbook_sync' should match 'def_file.rb' as json
+ And the inflated responses key 'node_cookbook_sync' should match '"libraries":' as json
+ And the inflated responses key 'node_cookbook_sync' should match 'lib_file.rb' as json
+ And the inflated responses key 'node_cookbook_sync' should match '"attributes":' as json
+ And the inflated responses key 'node_cookbook_sync' should match 'attr_file.rb' as json
+
+ Scenario: Retrieve the list of cookbook files to synchronize with a wrong private key
+ Given a 'registration' named 'bobo' exists
+ And a 'node' named 'sync' exists
+ When I 'GET' the path '/nodes/sync/cookbooks' using a wrong private key
+ Then I should get a '401 "Unauthorized"' exception \ No newline at end of file
diff --git a/features/api/nodes/create_node_api.feature b/features/api/nodes/create_node_api.feature
index 5012a8a357..ec5bbb73be 100644
--- a/features/api/nodes/create_node_api.feature
+++ b/features/api/nodes/create_node_api.feature
@@ -1,4 +1,4 @@
-@api @nodes @nodes_create
+@api @api_nodes @nodes_create
Feature: Create a node via the REST API
In order to create nodes programatically
As a Devleoper
@@ -7,19 +7,19 @@ Feature: Create a node via the REST API
Scenario: Create a new node
Given a 'registration' named 'bobo' exists
And a 'node' named 'webserver'
- When I authenticate as 'bobo'
- And I 'POST' the 'node' to the path '/nodes'
+ When I 'POST' the 'node' to the path '/nodes'
And the inflated responses key 'uri' should match '^http://.+/nodes/webserver$'
Scenario: Create a node that already exists
Given a 'registration' named 'bobo' exists
And an 'node' named 'webserver'
- When I authenticate as 'bobo'
- And I 'POST' the 'node' to the path '/nodes'
+ When I 'POST' the 'node' to the path '/nodes'
And I 'POST' the 'node' to the path '/nodes'
Then I should get a '403 "Forbidden"' exception
-
- Scenario: Create a new node without authenticating
- Given a 'node' named 'webserver'
- When I 'POST' the 'node' to the path '/nodes'
+
+ Scenario: Create a node with a wrong private key
+ Given a 'registration' named 'bobo' exists
+ And an 'node' named 'webserver'
+ When I 'POST' the 'node' to the path '/nodes' using a wrong private key
Then I should get a '401 "Unauthorized"' exception
+
diff --git a/features/api/nodes/delete_node_api.feature b/features/api/nodes/delete_node_api.feature
index 7addc6e790..46c027561d 100644
--- a/features/api/nodes/delete_node_api.feature
+++ b/features/api/nodes/delete_node_api.feature
@@ -1,4 +1,4 @@
-@api @nodes @nodes_delete
+@api @api_nodes @nodes_delete
Feature: Delete a node via the REST API
In order to remove a node
As a Developer
@@ -7,19 +7,18 @@ Feature: Delete a node via the REST API
Scenario: Delete a node
Given a 'registration' named 'bobo' exists
And a 'node' named 'webserver' exists
- When I authenticate as 'bobo'
- And I 'DELETE' the path '/nodes/webserver'
+ When I 'DELETE' the path '/nodes/webserver'
Then the inflated response should respond to 'name' with 'webserver'
Scenario: Delete a node that does not exist
Given a 'registration' named 'bobo' exists
And there are no nodes
- When I authenticate as 'bobo'
When I 'DELETE' the path '/nodes/webserver'
Then I should get a '404 "Not Found"' exception
- Scenario: Delete a node without authenticating
- Given a 'node' named 'webserver'
- When I 'DELETE' the path '/nodes/webserver'
+ Scenario: Delete a node with a wrong private key
+ Given a 'registration' named 'bobo' exists
+ And a 'node' named 'webserver' exists
+ When I 'DELETE' the path '/nodes/webserver' using a wrong private key
Then I should get a '401 "Unauthorized"' exception
diff --git a/features/api/nodes/list_nodes_api.feature b/features/api/nodes/list_nodes_api.feature
index 4489c4d49a..7127720947 100644
--- a/features/api/nodes/list_nodes_api.feature
+++ b/features/api/nodes/list_nodes_api.feature
@@ -1,4 +1,4 @@
-@api @nodes @nodes_list
+@api @api_nodes @nodes_list
Feature: List nodes via the REST API
In order to know what nodes exists programatically
As a Developer
@@ -7,28 +7,26 @@ Feature: List nodes via the REST API
Scenario: List nodes when none have been created
Given a 'registration' named 'bobo' exists
And there are no nodes
- When I authenticate as 'bobo'
- And I 'GET' the path '/nodes'
+ When I 'GET' the path '/nodes'
Then the inflated response should be an empty array
Scenario: List nodes when one has been created
Given a 'registration' named 'bobo' exists
Given a 'node' named 'webserver' exists
- When I authenticate as 'bobo'
- And I 'GET' the path '/nodes'
+ When I 'GET' the path '/nodes'
Then the inflated response should include '^http://.+/nodes/webserver$'
-
+
Scenario: List nodes when two have been created
Given a 'registration' named 'bobo' exists
And a 'node' named 'webserver' exists
And a 'node' named 'dbserver' exists
- When I authenticate as 'bobo'
- And I 'GET' the path '/nodes'
+ When I 'GET' the path '/nodes'
Then the inflated response should be '2' items long
And the inflated response should include '^http://.+/nodes/webserver$'
And the inflated response should include '^http://.+/nodes/dbserver$'
- Scenario: List nodes when you are not authenticated
- When I 'GET' the path '/nodes'
+ Scenario: List nodes none have been created with a wrong private key
+ Given a 'registration' named 'bobo' exists
+ And there are no cookbooks
+ When I 'GET' the path '/nodes' using a wrong private key
Then I should get a '401 "Unauthorized"' exception
-
diff --git a/features/api/nodes/show_node_api.feature b/features/api/nodes/show_node_api.feature
index 8a2ab2990d..e26d127a06 100644
--- a/features/api/nodes/show_node_api.feature
+++ b/features/api/nodes/show_node_api.feature
@@ -1,4 +1,4 @@
-@api @nodes @nodes_show
+@api @api_nodes @nodes_show
Feature: Show a node via the REST API
In order to know what the details are for a node
As a Developer
@@ -7,19 +7,18 @@ Feature: Show a node via the REST API
Scenario: Show a node
Given a 'registration' named 'bobo' exists
And a 'node' named 'webserver' exists
- When I authenticate as 'bobo'
- And I 'GET' the path '/nodes/webserver'
+ When I 'GET' the path '/nodes/webserver'
Then the inflated response should respond to 'name' with 'webserver'
Scenario: Show a missing node
Given a 'registration' named 'bobo' exists
And there are no nodes
- When I authenticate as 'bobo'
- And I 'GET' the path '/nodes/bobo'
+ When I 'GET' the path '/nodes/bobo'
Then I should get a '404 "Not Found"' exception
- Scenario: Show a node without authenticating
- Given a 'node' named 'webserver' exists
- And I 'GET' the path '/nodes/webserver'
+ Scenario: Show a node with a wrong private key
+ Given a 'registration' named 'bobo' exists
+ And a 'node' named 'webserver' exists
+ When I 'GET' the path '/nodes/webserver' using a wrong private key
Then I should get a '401 "Unauthorized"' exception
diff --git a/features/api/nodes/update_node_api.feature b/features/api/nodes/update_node_api.feature
index ecf512f64f..4f6ad41f1b 100644
--- a/features/api/nodes/update_node_api.feature
+++ b/features/api/nodes/update_node_api.feature
@@ -1,4 +1,4 @@
-@api @nodes @nodes_update
+@api @api_nodes @nodes_update
Feature: Update a node
In order to keep my node data up-to-date
As a Developer
@@ -8,7 +8,6 @@ Feature: Update a node
Given a 'registration' named 'bobo' exists
And a 'node' named 'webserver' exists
And sending the method '<method>' to the 'node' with '<updated_value>'
- When I authenticate as 'bobo'
When I 'PUT' the 'node' to the path '/nodes/webserver'
Then the inflated response should respond to '<method>' with '<updated_value>'
When I 'GET' the path '/nodes/webserver'
@@ -19,9 +18,10 @@ Feature: Update a node
| run_list | [ "recipe[one]", "recipe[two]" ] |
| snakes | really arent so bad |
- Scenario: Update a node without authenticating
- Given a 'node' named 'webserver'
- And sending the method 'snakes' to the 'node' with 'night train'
- When I 'PUT' the 'node' to the path '/nodes/webserver'
- Then I should get a '401 "Unauthorized"' exception
+ Scenario Outline: Update a node with a wrong private key
+ Given a 'registration' named 'bobo' exists
+ And a 'node' named 'webserver' exists
+ And sending the method 'run_list' to the 'node' with '[ "recipe[one]", "recipe[two]" ]'
+ When I 'PUT' the 'node' to the path '/nodes/webserver' using a wrong private key
+ Then I should get a '401 "Unauthorized"' exception
diff --git a/features/api/roles/create_role_api.feature b/features/api/roles/create_role_api.feature
index 7b04b64294..4282400c7f 100644
--- a/features/api/roles/create_role_api.feature
+++ b/features/api/roles/create_role_api.feature
@@ -1,4 +1,4 @@
-@api @roles @roles_create
+@api @api_roles @roles_create
Feature: Create a role via the REST API
In order to create roles programatically
As a Devleoper
@@ -7,20 +7,18 @@ Feature: Create a role via the REST API
Scenario: Create a new role
Given a 'registration' named 'bobo' exists
And a 'role' named 'webserver'
- When I authenticate as 'bobo'
- And I 'POST' the 'role' to the path '/roles'
+ When I 'POST' the 'role' to the path '/roles'
And the inflated responses key 'uri' should match '^http://.+/roles/webserver$'
Scenario: Create a role that already exists
Given a 'registration' named 'bobo' exists
And an 'role' named 'webserver'
- When I authenticate as 'bobo'
- And I 'POST' the 'role' to the path '/roles'
+ When I 'POST' the 'role' to the path '/roles'
And I 'POST' the 'role' to the path '/roles'
Then I should get a '403 "Forbidden"' exception
- Scenario: Create a new role without authenticating
- Given a 'role' named 'webserver'
- When I 'POST' the 'role' to the path '/roles'
+ Scenario: Create a new role with a wrong private key
+ Given a 'registration' named 'bobo' exists
+ And a 'role' named 'webserver'
+ When I 'POST' the 'role' to the path '/roles' using a wrong private key
Then I should get a '401 "Unauthorized"' exception
-
diff --git a/features/api/roles/delete_role_api.feature b/features/api/roles/delete_role_api.feature
index c6504741f9..0dab4128af 100644
--- a/features/api/roles/delete_role_api.feature
+++ b/features/api/roles/delete_role_api.feature
@@ -1,4 +1,4 @@
-@api @roles @roles_delete
+@api @api_roles @roles_delete
Feature: Delete a Role via the REST API
In order to remove a role
As a Developer
@@ -7,19 +7,18 @@ Feature: Delete a Role via the REST API
Scenario: Delete a Role
Given a 'registration' named 'bobo' exists
And a 'role' named 'webserver' exists
- When I authenticate as 'bobo'
- And I 'DELETE' the path '/roles/webserver'
+ When I 'DELETE' the path '/roles/webserver'
Then the inflated response should respond to 'name' with 'webserver'
Scenario: Delete a Role that does not exist
Given a 'registration' named 'bobo' exists
And there are no roles
- When I authenticate as 'bobo'
When I 'DELETE' the path '/roles/webserver'
Then I should get a '404 "Not Found"' exception
-
- Scenario: Delete a Role without authenticating
- Given a 'role' named 'webserver'
- When I 'DELETE' the path '/roles/webserver'
+
+ Scenario: Delete a Role with a wrong private key
+ Given a 'registration' named 'bobo' exists
+ And a 'role' named 'webserver' exists
+ When I 'DELETE' the path '/roles/webserver' using a wrong private key
Then I should get a '401 "Unauthorized"' exception
diff --git a/features/api/roles/list_roles_api.feature b/features/api/roles/list_roles_api.feature
index edc96e0869..72251d2251 100644
--- a/features/api/roles/list_roles_api.feature
+++ b/features/api/roles/list_roles_api.feature
@@ -1,4 +1,4 @@
-@api @roles @roles_list
+@api @api_roles @roles_list
Feature: List roles via the REST API
In order to know what roles exists programatically
As a Developer
@@ -6,30 +6,29 @@ Feature: List roles via the REST API
Scenario: List roles when none have been created
Given a 'registration' named 'bobo' exists
- And there are no roles
- When I authenticate as 'bobo'
- And I 'GET' the path '/roles'
- Then the inflated response should be an empty array
+ And there are no roles
+ When I 'GET' the path '/roles'
+ Then the inflated response should be '1' items long
Scenario: List roles when one has been created
Given a 'registration' named 'bobo' exists
Given a 'role' named 'webserver' exists
- When I authenticate as 'bobo'
- And I 'GET' the path '/roles'
+ When I 'GET' the path '/roles'
Then the inflated response should include '^http://.+/roles/webserver$'
Scenario: List roles when two have been created
Given a 'registration' named 'bobo' exists
And a 'role' named 'webserver' exists
And a 'role' named 'db' exists
- When I authenticate as 'bobo'
- And I 'GET' the path '/roles'
+ When I 'GET' the path '/roles'
Then the inflated response should be '3' items long
And the inflated response should include '^http://.+/roles/role_test$'
And the inflated response should include '^http://.+/roles/webserver$'
And the inflated response should include '^http://.+/roles/db$'
- Scenario: List roles when you are not authenticated
- When I 'GET' the path '/roles'
+ Scenario: List roles when none have been created with a wrong private key
+ Given a 'registration' named 'bobo' exists
+ And there are no roles
+ When I 'GET' the path '/roles' using a wrong private key
Then I should get a '401 "Unauthorized"' exception
diff --git a/features/api/roles/show_roles_api.feature b/features/api/roles/show_roles_api.feature
index 70fe4aad60..5a7a5e2734 100644
--- a/features/api/roles/show_roles_api.feature
+++ b/features/api/roles/show_roles_api.feature
@@ -1,4 +1,4 @@
-@api @roles @roles_show
+@api @api_roles @roles_show
Feature: Show a role via the REST API
In order to know what the details are for a Role
As a Developer
@@ -7,20 +7,18 @@ Feature: Show a role via the REST API
Scenario: Show a role
Given a 'registration' named 'bobo' exists
And a 'role' named 'webserver' exists
- When I authenticate as 'bobo'
- And I 'GET' the path '/roles/webserver'
+ When I 'GET' the path '/roles/webserver'
Then the inflated response should respond to 'name' with 'webserver'
Scenario: Show a missing role
Given a 'registration' named 'bobo' exists
And there are no roles
- When I authenticate as 'bobo'
- And I 'GET' the path '/roles/bobo'
+ When I 'GET' the path '/roles/bobo'
Then I should get a '404 "Not Found"' exception
- Scenario: Show a role without authenticating
- Given a 'role' named 'webserver' exists
- And I 'GET' the path '/roles/webserver'
+ Scenario: Show a role with a wrong private key
+ Given a 'registration' named 'bobo' exists
+ And a 'role' named 'webserver' exists
+ When I 'GET' the path '/roles/webserver' using a wrong private key
Then I should get a '401 "Unauthorized"' exception
-
diff --git a/features/api/roles/update_roles_api.feature b/features/api/roles/update_roles_api.feature
index 90a6282577..ea72f68c05 100644
--- a/features/api/roles/update_roles_api.feature
+++ b/features/api/roles/update_roles_api.feature
@@ -1,4 +1,4 @@
-@api @roles @roles_update
+@api @api_roles @roles_update
Feature: Update a role
In order to keep my role data up-to-date
As a Developer
@@ -8,7 +8,6 @@ Feature: Update a role
Given a 'registration' named 'bobo' exists
And a 'role' named 'webserver' exists
And sending the method '<method>' to the 'role' with '<updated_value>'
- When I authenticate as 'bobo'
When I 'PUT' the 'role' to the path '/roles/webserver'
Then the inflated response should respond to '<method>' with '<updated_value>'
When I 'GET' the path '/roles/webserver'
@@ -21,9 +20,9 @@ Feature: Update a role
| default_attributes | { "a": "d" } |
| override_attributes | { "c": "e" } |
- Scenario: Update a role without authenticating
- Given a 'role' named 'webserver'
- And sending the method 'description' to the 'role' with 'Is easy'
- When I 'PUT' the 'role' to the path '/roles/webserver'
+ Scenario Outline: Update a role with a wrong private key
+ Given a 'registration' named 'bobo' exists
+ And a 'role' named 'webserver' exists
+ And sending the method '<method>' to the 'role' with '<updated_value>'
+ When I 'PUT' the 'role' to the path '/roles/webserver' using a wrong private key
Then I should get a '401 "Unauthorized"' exception
-
diff --git a/features/api/search/list_search.feature b/features/api/search/list_search.feature
new file mode 100644
index 0000000000..198f065f12
--- /dev/null
+++ b/features/api/search/list_search.feature
@@ -0,0 +1,29 @@
+@api @data @api_search @api_search_list
+Feature: List search endpoints via the REST API
+ In order to know what search endpoints exist programatically
+ As a Developer
+ I want to list all the search indexes
+
+ Scenario: List search indexes when no data bags have been created
+ Given a 'registration' named 'bobo' exists
+ And there are no data bags
+ When I authenticate as 'bobo'
+ And I 'GET' the path '/search'
+ Then the inflated response should include '^http://(.+)/search/node$'
+ And the inflated response should include '^http://(.+)/search/role$'
+ And the inflated response should be '2' items long
+
+ Scenario: List search indexes when a data bag has been created
+ Given a 'registration' named 'bobo' exists
+ And a 'data_bag' named 'users' exists
+ When I authenticate as 'bobo'
+ And I 'GET' the path '/search'
+ Then the inflated response should include '^http://(.+)/search/node$'
+ And the inflated response should include '^http://(.+)/search/role$'
+ And the inflated response should include '^http://(.+)/search/users$'
+ And the inflated response should be '3' items long
+
+ Scenario: List search indexes when you are not authenticated
+ When I 'GET' the path '/search'
+ Then I should get a '401 "Unauthorized"' exception
+
diff --git a/features/api/search/show_search.feature b/features/api/search/show_search.feature
new file mode 100644
index 0000000000..292d043b0d
--- /dev/null
+++ b/features/api/search/show_search.feature
@@ -0,0 +1,115 @@
+@api @data @api_search @api_search_show
+Feature: Search data via the REST API
+ In order to know about objects in the system
+ As a Developer
+ I want to search the objects
+
+ Scenario: Search for objects when none have been created
+ Given a 'registration' named 'bobo' exists
+ And a 'data_bag' named 'users' exists
+ When I authenticate as 'bobo'
+ And I 'GET' the path '/search/users'
+ Then the inflated responses key 'rows' should be '0' items long
+ And the inflated responses key 'start' should be the integer '0'
+ And the inflated responses key 'total' should be the integer '0'
+
+ Scenario: Search for objects when one has been created
+ Given a 'registration' named 'bobo' exists
+ And a 'data_bag' named 'users' exists
+ And a 'data_bag_item' named 'francis' exists
+ And I wait for '10' seconds
+ When I authenticate as 'bobo'
+ And I 'GET' the path '/search/users'
+ Then the inflated responses key 'rows' item '0' should be a kind of 'Chef::DataBagItem'
+ And the inflated responses key 'rows' item '0' key 'id' should be 'francis'
+ And the inflated responses key 'start' should be the integer '0'
+ And the inflated responses key 'total' should be the integer '1'
+
+ Scenario: Search for objects when two have been created
+ Given a 'registration' named 'bobo' exists
+ And a 'data_bag' named 'users' exists
+ And a 'data_bag_item' named 'francis' exists
+ And a 'data_bag_item' named 'axl_rose' exists
+ And I wait for '10' seconds
+ When I authenticate as 'bobo'
+ And I 'GET' the path '/search/users'
+ Then the inflated responses key 'rows' item '0' should be a kind of 'Chef::DataBagItem'
+ And the inflated responses key 'rows' item '0' key 'id' should be 'francis'
+ And the inflated responses key 'rows' item '1' should be a kind of 'Chef::DataBagItem'
+ And the inflated responses key 'rows' item '1' key 'id' should be 'axl_rose'
+ And the inflated responses key 'start' should be the integer '0'
+ And the inflated responses key 'total' should be the integer '2'
+
+ Scenario: Search for objects with a manual ascending sort order
+ Given a 'registration' named 'bobo' exists
+ And a 'data_bag' named 'users' exists
+ And a 'data_bag_item' named 'francis' exists
+ And a 'data_bag_item' named 'axl_rose' exists
+ And I wait for '10' seconds
+ When I authenticate as 'bobo'
+ And I 'GET' the path '/search/users?sort=id+asc'
+ Then the inflated responses key 'rows' item '0' should be a kind of 'Chef::DataBagItem'
+ And the inflated responses key 'rows' item '0' key 'id' should be 'axl_rose'
+ And the inflated responses key 'rows' item '1' should be a kind of 'Chef::DataBagItem'
+ And the inflated responses key 'rows' item '1' key 'id' should be 'francis'
+ And the inflated responses key 'start' should be the integer '0'
+ And the inflated responses key 'total' should be the integer '2'
+
+ Scenario: Search for objects with a manual descending sort order
+ Given a 'registration' named 'bobo' exists
+ And a 'data_bag' named 'users' exists
+ And a 'data_bag_item' named 'francis' exists
+ And a 'data_bag_item' named 'axl_rose' exists
+ And I wait for '10' seconds
+ When I authenticate as 'bobo'
+ And I 'GET' the path '/search/users?sort=id+desc'
+ Then the inflated responses key 'rows' item '0' should be a kind of 'Chef::DataBagItem'
+ And the inflated responses key 'rows' item '0' key 'id' should be 'francis'
+ And the inflated responses key 'rows' item '1' should be a kind of 'Chef::DataBagItem'
+ And the inflated responses key 'rows' item '1' key 'id' should be 'axl_rose'
+ And the inflated responses key 'start' should be the integer '0'
+ And the inflated responses key 'total' should be the integer '2'
+
+ Scenario: Search for objects and page through the results
+ Given a 'registration' named 'bobo' exists
+ And a 'data_bag' named 'users' exists
+ And a 'data_bag_item' named 'francis' exists
+ And a 'data_bag_item' named 'axl_rose' exists
+ And I wait for '10' seconds
+ When I authenticate as 'bobo'
+ And I 'GET' the path '/search/users?rows=1&sort=id+asc'
+ Then the inflated responses key 'rows' item '0' should be a kind of 'Chef::DataBagItem'
+ And the inflated responses key 'rows' item '0' key 'id' should be 'axl_rose'
+ And the inflated responses key 'rows' should be '1' items long
+ And the inflated responses key 'start' should be the integer '0'
+ And the inflated responses key 'total' should be the integer '2'
+ When I 'GET' the path '/search/users?rows=1&start=1&sort=id+asc'
+ Then the inflated responses key 'rows' item '0' should be a kind of 'Chef::DataBagItem'
+ And the inflated responses key 'rows' item '0' key 'id' should be 'francis'
+ And the inflated responses key 'rows' should be '1' items long
+ And the inflated responses key 'start' should be the integer '1'
+ And the inflated responses key 'total' should be the integer '2'
+
+ Scenario: Search for a subset of objects
+ Given a 'registration' named 'bobo' exists
+ And a 'data_bag' named 'users' exists
+ And a 'data_bag_item' named 'francis' exists
+ And a 'data_bag_item' named 'axl_rose' exists
+ And I wait for '10' seconds
+ When I authenticate as 'bobo'
+ And I 'GET' the path '/search/users?q=id:axl_rose'
+ Then the inflated responses key 'rows' item '0' should be a kind of 'Chef::DataBagItem'
+ And the inflated responses key 'rows' item '0' key 'id' should be 'axl_rose'
+ And the inflated responses key 'start' should be the integer '0'
+ And the inflated responses key 'total' should be the integer '1'
+
+ Scenario: Search for a type of object that does not exist
+ Given a 'registration' named 'bobo' exists
+ When I authenticate as 'bobo'
+ And I 'GET' the path '/search/funkensteins'
+ Then I should get a '404 "Not Found"' exception
+
+ Scenario: Search for objects when you are not authenticated
+ When I 'GET' the path '/search/users'
+ Then I should get a '401 "Unauthorized"' exception
+
diff --git a/features/data/config/client.rb b/features/data/config/client.rb
index ab10d750df..5e51b26c82 100644
--- a/features/data/config/client.rb
+++ b/features/data/config/client.rb
@@ -6,9 +6,15 @@ log_location STDOUT
file_cache_path File.join(tmpdir, "cache")
ssl_verify_mode :verify_none
registration_url "http://127.0.0.1:4000"
-openid_url "http://127.0.0.1:4001"
+openid_url "http://127.0.0.1:4000"
template_url "http://127.0.0.1:4000"
remotefile_url "http://127.0.0.1:4000"
search_url "http://127.0.0.1:4000"
-role_url "http://127.0.0.1:4000"
-couchdb_database 'chef_integration'
+role_url "http://127.0.0.1:4000"
+client_url "http://127.0.0.1:4000"
+chef_server_url "http://127.0.0.1:4000"
+validation_client_name "validator"
+systmpdir = File.expand_path(File.join(Dir.tmpdir, "chef_integration"))
+validation_key File.join(systmpdir, "validation.pem")
+client_key File.join(systmpdir, "client.pem")
+
diff --git a/features/data/config/server.rb b/features/data/config/server.rb
index 51a7bacef0..cc7db3a232 100644
--- a/features/data/config/server.rb
+++ b/features/data/config/server.rb
@@ -6,17 +6,30 @@ log_location STDOUT
file_cache_path File.join(tmpdir, "cache")
ssl_verify_mode :verify_none
registration_url "http://127.0.0.1:4000"
-openid_url "http://127.0.0.1:4001"
+openid_url "http://127.0.0.1:4000"
template_url "http://127.0.0.1:4000"
remotefile_url "http://127.0.0.1:4000"
search_url "http://127.0.0.1:4000"
-role_url "http://127.0.0.1:4000"
+role_url "http://127.0.0.1:4000"
+chef_server_url "http://127.0.0.1:4000"
+client_url "http://127.0.0.1:4000"
cookbook_path File.join(supportdir, "cookbooks")
openid_store_path File.join(tmpdir, "openid", "store")
openid_cstore_path File.join(tmpdir, "openid", "cstore")
search_index_path File.join(tmpdir, "search_index")
role_path File.join(supportdir, "roles")
-validation_token 'ceelo'
couchdb_database 'chef_integration'
+systmpdir = File.expand_path(File.join(Dir.tmpdir, "chef_integration"))
+
+validation_client_name "validator"
+validation_key File.join(systmpdir, "validation.pem")
+client_key File.join(systmpdir, "client.pem")
+
+solr_jetty_path File.join(supportdir, "solr", "jetty")
+solr_heap_size "250M"
+solr_data_path File.join(supportdir, "solr", "data")
+solr_home_path File.join(supportdir, "solr", "home")
+solr_heap_size "256M"
+
Chef::Log::Formatter.show_time = true
diff --git a/features/data/cookbooks/integration_setup/recipes/default.rb b/features/data/cookbooks/integration_setup/recipes/default.rb
index 0ada2aa485..fc913b4502 100644
--- a/features/data/cookbooks/integration_setup/recipes/default.rb
+++ b/features/data/cookbooks/integration_setup/recipes/default.rb
@@ -19,7 +19,7 @@
directory node[:int][:tmpdir] do
owner "root"
- mode 1777
+ mode "1777"
action :create
end
diff --git a/features/data/cookbooks/node_cookbook_sync/README.rdoc b/features/data/cookbooks/node_cookbook_sync/README.rdoc
new file mode 100644
index 0000000000..8d774805b9
--- /dev/null
+++ b/features/data/cookbooks/node_cookbook_sync/README.rdoc
@@ -0,0 +1,8 @@
+= DESCRIPTION:
+
+= REQUIREMENTS:
+
+= ATTRIBUTES:
+
+= USAGE:
+
diff --git a/features/data/cookbooks/node_cookbook_sync/attributes/attr_file.rb b/features/data/cookbooks/node_cookbook_sync/attributes/attr_file.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/features/data/cookbooks/node_cookbook_sync/attributes/attr_file.rb
diff --git a/features/data/cookbooks/node_cookbook_sync/definitions/def_file.rb b/features/data/cookbooks/node_cookbook_sync/definitions/def_file.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/features/data/cookbooks/node_cookbook_sync/definitions/def_file.rb
diff --git a/features/data/cookbooks/node_cookbook_sync/libraries/lib_file.rb b/features/data/cookbooks/node_cookbook_sync/libraries/lib_file.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/features/data/cookbooks/node_cookbook_sync/libraries/lib_file.rb
diff --git a/features/data/cookbooks/node_cookbook_sync/metadata.rb b/features/data/cookbooks/node_cookbook_sync/metadata.rb
new file mode 100644
index 0000000000..850a993fbb
--- /dev/null
+++ b/features/data/cookbooks/node_cookbook_sync/metadata.rb
@@ -0,0 +1,6 @@
+maintainer "Opscode"
+maintainer_email "do_not_reply@opscode.com"
+license "Apache 2.0"
+description "Installs/Configures node_cookbook_sync"
+long_description IO.read(File.join(File.dirname(__FILE__), 'README.rdoc'))
+version "0.1"
diff --git a/features/data/cookbooks/node_cookbook_sync/recipes/default.rb b/features/data/cookbooks/node_cookbook_sync/recipes/default.rb
new file mode 100644
index 0000000000..7912164bf3
--- /dev/null
+++ b/features/data/cookbooks/node_cookbook_sync/recipes/default.rb
@@ -0,0 +1,18 @@
+#
+# Cookbook Name:: node_cookbook_sync
+# Recipe:: default
+#
+# Copyright 2009, Opscode
+#
+# 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.
+#
diff --git a/features/data/cookbooks/search/recipes/search_data.rb b/features/data/cookbooks/search/recipes/search_data.rb
index 6113da356f..6deefed9b3 100644
--- a/features/data/cookbooks/search/recipes/search_data.rb
+++ b/features/data/cookbooks/search/recipes/search_data.rb
@@ -17,11 +17,12 @@
# limitations under the License.
#
-node.save
-sleep 5
-search(:node, "*") do |entry|
- Chef::Log.error(entry.inspect)
- entry["search_files"].each do |filename|
- file "#{node[:tmpdir]}/#{filename}"
- end
+# We have to sleep at least 10 seconds to confirm that the data has made it
+# into the index. We can only rely on this because we are in a test environment
+# in real-land Chef, the index is only eventually consistent.. and may take a
+# variable amount of time.
+sleep 10
+search(:users, "*:*") do |entry|
+ file "#{node[:tmpdir]}/#{entry["id"]}"
end
+
diff --git a/features/data/cookbooks/search/recipes/search_data_noblock.rb b/features/data/cookbooks/search/recipes/search_data_noblock.rb
new file mode 100644
index 0000000000..6fb9de6d81
--- /dev/null
+++ b/features/data/cookbooks/search/recipes/search_data_noblock.rb
@@ -0,0 +1,32 @@
+#
+# Cookbook Name:: search
+# Recipe:: default
+#
+# Copyright 2009, Opscode
+#
+# 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.
+#
+
+# We have to sleep at least 10 seconds to confirm that the data has made it
+# into the index. We can only rely on this because we are in a test environment
+# in real-land Chef, the index is only eventually consistent.. and may take a
+# variable amount of time.
+
+
+sleep 10
+objects, start, rows = search(:users, "*:*")
+
+objects.each do |entry|
+ file "#{node[:tmpdir]}/#{entry["id"]}"
+end
+
diff --git a/features/data/cookbooks/show_cookbook/README.rdoc b/features/data/cookbooks/show_cookbook/README.rdoc
new file mode 100644
index 0000000000..8d774805b9
--- /dev/null
+++ b/features/data/cookbooks/show_cookbook/README.rdoc
@@ -0,0 +1,8 @@
+= DESCRIPTION:
+
+= REQUIREMENTS:
+
+= ATTRIBUTES:
+
+= USAGE:
+
diff --git a/features/data/cookbooks/show_cookbook/attributes/attr_file.rb b/features/data/cookbooks/show_cookbook/attributes/attr_file.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/features/data/cookbooks/show_cookbook/attributes/attr_file.rb
diff --git a/features/data/cookbooks/show_cookbook/definitions/def_file.rb b/features/data/cookbooks/show_cookbook/definitions/def_file.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/features/data/cookbooks/show_cookbook/definitions/def_file.rb
diff --git a/features/data/cookbooks/show_cookbook/files/default/prime_time.txt b/features/data/cookbooks/show_cookbook/files/default/prime_time.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/features/data/cookbooks/show_cookbook/files/default/prime_time.txt
diff --git a/features/data/cookbooks/show_cookbook/files/host-latte/prime_time.txt b/features/data/cookbooks/show_cookbook/files/host-latte/prime_time.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/features/data/cookbooks/show_cookbook/files/host-latte/prime_time.txt
diff --git a/features/data/cookbooks/show_cookbook/files/mac_os_x-10.5/prime_time.txt b/features/data/cookbooks/show_cookbook/files/mac_os_x-10.5/prime_time.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/features/data/cookbooks/show_cookbook/files/mac_os_x-10.5/prime_time.txt
diff --git a/features/data/cookbooks/show_cookbook/files/mac_os_x/prime_time.txt b/features/data/cookbooks/show_cookbook/files/mac_os_x/prime_time.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/features/data/cookbooks/show_cookbook/files/mac_os_x/prime_time.txt
diff --git a/features/data/cookbooks/show_cookbook/libraries/lib_file.rb b/features/data/cookbooks/show_cookbook/libraries/lib_file.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/features/data/cookbooks/show_cookbook/libraries/lib_file.rb
diff --git a/features/data/cookbooks/show_cookbook/metadata.rb b/features/data/cookbooks/show_cookbook/metadata.rb
new file mode 100644
index 0000000000..6f06805f62
--- /dev/null
+++ b/features/data/cookbooks/show_cookbook/metadata.rb
@@ -0,0 +1,6 @@
+maintainer "Opscode"
+maintainer_email "do_not_reply@opscode.com"
+license "Apache 2.0"
+description "Installs/Configures show_cookbook"
+long_description IO.read(File.join(File.dirname(__FILE__), 'README.rdoc'))
+version "1.0"
diff --git a/features/data/cookbooks/show_cookbook/recipes/default.rb b/features/data/cookbooks/show_cookbook/recipes/default.rb
new file mode 100644
index 0000000000..6f972192cb
--- /dev/null
+++ b/features/data/cookbooks/show_cookbook/recipes/default.rb
@@ -0,0 +1,18 @@
+#
+# Cookbook Name:: show_cookbook
+# Recipe:: default
+#
+# Copyright 2009, Opscode
+#
+# 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.
+#
diff --git a/features/data/cookbooks/show_cookbook/templates/default/prime_time.txt.erb b/features/data/cookbooks/show_cookbook/templates/default/prime_time.txt.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/features/data/cookbooks/show_cookbook/templates/default/prime_time.txt.erb
diff --git a/features/data/cookbooks/show_cookbook/templates/host-latte/prime_time.txt.erb b/features/data/cookbooks/show_cookbook/templates/host-latte/prime_time.txt.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/features/data/cookbooks/show_cookbook/templates/host-latte/prime_time.txt.erb
diff --git a/features/data/cookbooks/show_cookbook/templates/mac_os_x-10.5/prime_time.txt.erb b/features/data/cookbooks/show_cookbook/templates/mac_os_x-10.5/prime_time.txt.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/features/data/cookbooks/show_cookbook/templates/mac_os_x-10.5/prime_time.txt.erb
diff --git a/features/data/cookbooks/show_cookbook/templates/mac_os_x/prime_time.txt.erb b/features/data/cookbooks/show_cookbook/templates/mac_os_x/prime_time.txt.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/features/data/cookbooks/show_cookbook/templates/mac_os_x/prime_time.txt.erb
diff --git a/features/language/delayed_notifications.feature b/features/language/delayed_notifications.feature
index bc874f0f5e..511711c930 100644
--- a/features/language/delayed_notifications.feature
+++ b/features/language/delayed_notifications.feature
@@ -1,3 +1,4 @@
+@language
Feature: Delayed Notifications
In order to not impact the system we are configuring unduly
As a developer
diff --git a/features/provider/directory/create_directories.feature b/features/provider/directory/create_directories.feature
index 0d2ab35ab9..b2ac1b157c 100644
--- a/features/provider/directory/create_directories.feature
+++ b/features/provider/directory/create_directories.feature
@@ -1,3 +1,4 @@
+@provider @provider_directory
Feature: Create Directories
In order to save time
As a Developer
diff --git a/features/provider/directory/delete_directories.feature b/features/provider/directory/delete_directories.feature
index 1867e6c43c..e98ec2140e 100644
--- a/features/provider/directory/delete_directories.feature
+++ b/features/provider/directory/delete_directories.feature
@@ -1,3 +1,4 @@
+@provider @provider_directory
Feature: Delete Directories
In order to save time
As a Developer
diff --git a/features/provider/execute/run_commands.feature b/features/provider/execute/run_commands.feature
index 96a7a36da9..28dd70482a 100644
--- a/features/provider/execute/run_commands.feature
+++ b/features/provider/execute/run_commands.feature
@@ -1,3 +1,4 @@
+@provider @provider_execute
Feature: Run Commands
In order to utilize the plethora of useful command line utilities
As a Developer
diff --git a/features/provider/file/manage_files.feature b/features/provider/file/manage_files.feature
index 4034fb0f31..762fff3c62 100644
--- a/features/provider/file/manage_files.feature
+++ b/features/provider/file/manage_files.feature
@@ -1,3 +1,4 @@
+@provider @provider_file
Feature: Manage Files
In order to save time
As a Developer
diff --git a/features/search/search_data.feature b/features/search/search_data.feature
index 26b256b077..f6544b7f2e 100644
--- a/features/search/search_data.feature
+++ b/features/search/search_data.feature
@@ -4,11 +4,25 @@ Feature: Search Data
As a Developer
I want to search the data
- Scenario: Search the node index
+ Scenario: Search the user index
Given a validated node
And it includes the recipe 'search::search_data'
+ And a 'data_bag' named 'users' exists
+ And a 'data_bag_item' named 'francis' exists
+ And a 'data_bag_item' named 'axl_rose' exists
When I run the chef-client
Then the run should exit '0'
- And a file named 'search_one.txt' should exist
- And a file named 'search_two.txt' should exist
+ And a file named 'francis' should exist
+ And a file named 'axl_rose' should exist
+
+ Scenario: Search the user index without a block
+ Given a validated node
+ And it includes the recipe 'search::search_data_noblock'
+ And a 'data_bag' named 'users' exists
+ And a 'data_bag_item' named 'francis' exists
+ And a 'data_bag_item' named 'axl_rose' exists
+ When I run the chef-client
+ Then the run should exit '0'
+ And a file named 'francis' should exist
+ And a file named 'axl_rose' should exist
diff --git a/features/steps/cookbook_steps.rb b/features/steps/cookbook_steps.rb
index 202b4c0eda..1c5c0276ae 100644
--- a/features/steps/cookbook_steps.rb
+++ b/features/steps/cookbook_steps.rb
@@ -1,5 +1,6 @@
#
# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Chris Walters (<cw@opscode.com>)
# Copyright:: Copyright (c) 2008 Opscode, Inc.
# License:: Apache License, Version 2.0
#
@@ -51,3 +52,4 @@ When /^I run the rake task to generate cookbook metadata$/ do
end
end
end
+
diff --git a/features/steps/couchdb_steps.rb b/features/steps/couchdb_steps.rb
deleted file mode 100644
index be8b84db0a..0000000000
--- a/features/steps/couchdb_steps.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# 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.
-#
-
-Before do
- system("mkdir -p #{tmpdir}")
- cdb = Chef::CouchDB.new(Chef::Config[:couchdb_url])
- cdb.create_db
- Chef::Node.create_design_document
- Chef::Role.create_design_document
- Chef::Role.sync_from_disk_to_couchdb
- Chef::OpenIDRegistration.create_design_document
-end
-
-After do
- r = Chef::REST.new(Chef::Config[:couchdb_url])
- r.delete_rest("#{Chef::Config[:couchdb_database]}/")
- system("rm -rf #{tmpdir}")
-end
diff --git a/features/steps/fixture_steps.rb b/features/steps/fixture_steps.rb
index 6c9e8c9ed1..5239e5d762 100644
--- a/features/steps/fixture_steps.rb
+++ b/features/steps/fixture_steps.rb
@@ -1,19 +1,53 @@
+require 'ostruct'
+
Before do
@fixtures = {
+ 'signing_caller' =>{
+ :user_id=>'bobo', :secret_key => "/tmp/poop.pem"
+ },
'registration' => {
'bobo' => Proc.new do
- r = Chef::OpenIDRegistration.new
- r.name = "bobo"
- r.set_password('tclown')
- r.validated = true
- r.admin = true
- r
+
+ OpenStruct.new({ :save => true })
+ #Chef::CouchDB.new(Chef::Config[:couchdb_url], "chef_integration"))
+ end
+ },
+ 'data_bag' => {
+ 'users' => Proc.new do
+ b = Chef::DataBag.new(Chef::CouchDB.new(nil, "chef_integration"))
+ b.name "users"
+ b
+ end,
+ 'rubies' => Proc.new do
+ b = Chef::DataBag.new(Chef::CouchDB.new(nil, "chef_integration"))
+ b.name "rubies"
+ b
+ end
+ },
+ 'data_bag_item' => {
+ 'francis' => Proc.new do
+ i = Chef::DataBagItem.new(Chef::CouchDB.new(nil, "chef_integration"))
+ i.data_bag "users"
+ i.raw_data = { "id" => "francis" }
+ i
+ end,
+ 'francis_extra' => Proc.new do
+ i = Chef::DataBagItem.new(Chef::CouchDB.new(nil, "chef_integration"))
+ i.data_bag "users"
+ i.raw_data = { "id" => "francis", "extra" => "majority" }
+ i
+ end,
+ 'axl_rose' => Proc.new do
+ i = Chef::DataBagItem.new(Chef::CouchDB.new(nil, "chef_integration"))
+ i.data_bag "users"
+ i.raw_data = { "id" => "axl_rose" }
+ i
end
},
'role' => {
'webserver' => Proc.new do
- r = Chef::Role.new
+ r = Chef::Role.new(Chef::CouchDB.new(nil, "chef_integration"))
r.name "webserver"
r.description "monkey"
r.recipes("role::webserver", "role::base")
@@ -22,7 +56,7 @@ Before do
r
end,
'db' => Proc.new do
- r = Chef::Role.new
+ r = Chef::Role.new(Chef::CouchDB.new(nil, "chef_integration"))
r.name "db"
r.description "monkey"
r.recipes("role::db", "role::base")
@@ -33,7 +67,7 @@ Before do
},
'node' => {
'webserver' => Proc.new do
- n = Chef::Node.new
+ n = Chef::Node.new(Chef::CouchDB.new(nil, "chef_integration"))
n.name 'webserver'
n.run_list << "tacos"
n.snakes "on a plane"
@@ -41,17 +75,34 @@ Before do
n
end,
'dbserver' => Proc.new do
- n = Chef::Node.new
+ n = Chef::Node.new(Chef::CouchDB.new(nil, "chef_integration"))
n.name 'dbserver'
n.run_list << "oracle"
n.just "kidding - who uses oracle?"
n
+ end,
+ 'sync' => Proc.new do
+ n = Chef::Node.new(Chef::CouchDB.new(nil, "chef_integration"))
+ n.name 'sync'
+ n.run_list << "node_cookbook_sync"
+ n
end
}
}
@stash = {}
end
+def sign_request(http_method, private_key, user_id, body = "")
+ timestamp = Time.now.utc.iso8601
+ sign_obj = Mixlib::Auth::SignedHeaderAuth.signing_object(
+ :http_method=>http_method,
+ :body=>body,
+ :user_id=>user_id,
+ :timestamp=>timestamp)
+ signed = sign_obj.sign(private_key).merge({:host => "localhost"})
+ signed.inject({}){|memo, kv| memo["#{kv[0].to_s.upcase}"] = kv[1];memo}
+end
+
def get_fixture(stash_name, stash_key)
fixy = @fixtures[stash_name][stash_key]
if fixy.kind_of?(Proc)
@@ -62,20 +113,35 @@ def get_fixture(stash_name, stash_key)
end
Given /^an? '(.+)' named '(.+)'$/ do |stash_name, stash_key|
- @stash[stash_name] = get_fixture(stash_name, stash_key)
+ # BUGBUG: I need to reference fixtures individually, but the fixtures, as written, store under the type, not the fixture's identifier and I don't currently have time to re-write the tests
+
+ key = case stash_name
+ when 'file','hash'
+ stash_key
+ else
+ stash_name
+ end
+ @stash[key] = get_fixture(stash_name, stash_key)
end
-Given /^an? '(.+)' named '(.+)' exists$/ do |stash_name, stash_key|
+Given /^an? '(.+)' named '(.+)' exists$/ do |stash_name, stash_key|
@stash[stash_name] = get_fixture(stash_name, stash_key)
- if @stash[stash_name].respond_to?(:save)
- @stash[stash_name].save
- else
- request("/#{stash_name.pluralize}", {
- :method => "POST",
- "HTTP_ACCEPT" => 'application/json',
- "CONTENT_TYPE" => 'application/json',
- :input => @stash[stash_name].to_json
- })
+
+ if stash_name == 'registration'
+ r = Chef::REST.new(Chef::Config[:registration_url], Chef::Config[:validation_user], Chef::Config[:validation_key])
+ r.register("bobo", "#{tmpdir}/bobo.pem")
+ @rest = Chef::REST.new(Chef::Config[:registration_url], 'bobo', "#{tmpdir}/bobo.pem")
+ else
+ if @stash[stash_name].respond_to?(:save)#stash_name == "registration"
+ @stash[stash_name].save
+ else
+ request("#{stash_name.pluralize}", {
+ :method => "POST",
+ "HTTP_ACCEPT" => 'application/json',
+ "CONTENT_TYPE" => 'application/json',
+ :input => @stash[stash_name].to_json
+ }.merge(sign_request("POST", OpenSSL::PKey::RSA.new(IO.read("#{tmpdir}/client.pem")), "bobo")))
+ end
end
end
@@ -98,3 +164,7 @@ Given /^there are no (.+)$/ do |stash_name|
Chef::Role.list(true).each { |r| r.destroy }
end
end
+
+Given /^I wait for '(\d+)' seconds$/ do |time|
+ sleep time.to_i
+end
diff --git a/features/steps/node_steps.rb b/features/steps/node_steps.rb
index d1aa9a0045..3f4f76b870 100644
--- a/features/steps/node_steps.rb
+++ b/features/steps/node_steps.rb
@@ -20,15 +20,15 @@
# Given
###
Given /^a validated node$/ do
- client.validation_token = Chef::Config[:validation_token] = 'ceelo'
+ client.determine_node_name
client.register
- client.authenticate
client.build_node
client.node.recipes << "integration_setup"
end
Given /^it includes the recipe '(.+)'$/ do |recipe|
self.recipe = recipe
+ Chef::Log.error("It's like this we have: #{Chef::Config[:chef_server_url]}")
client.node.recipes << recipe
client.save_node
end
diff --git a/features/steps/request_steps.rb b/features/steps/request_steps.rb
index 156fc88b3a..892195e80e 100644
--- a/features/steps/request_steps.rb
+++ b/features/steps/request_steps.rb
@@ -1,21 +1,55 @@
-When /^I '(.+)' the path '(.+)'$/ do |http_method, request_uri|
+When /^I '([^']*)' (?:to )?the path '([^']*)'$/ do |http_method, request_uri|
begin
self.response = rest.send("#{http_method}_rest".downcase.to_sym, request_uri)
- self.inflated_response = self.response
+ self.inflated_response = self.response
rescue
+ Chef::Log.debug("Caught exception in request: #{$!.message}")
self.exception = $!
end
end
+When /^I '([^']*)' to the path '(.+)'$/ do |http_method, request_uri|
+ When "I '#{http_method}' the path '#{request_uri}'"
+end
+
+When /^I '(.+)' the path '(.+)' using a wrong private key$/ do |http_method, request_uri|
+ key = OpenSSL::PKey::RSA.generate(2048)
+ File.open(File.join(tmpdir, 'false_key.pem'), "w") { |f| f.print key }
+ @rest = Chef::REST.new(Chef::Config[:chef_server_url], 'snakebite' , File.join(tmpdir, 'false_key.pem'))
+
+ When "I '#{http_method}' the path '#{request_uri}'"
+end
+
When /^I '(.+)' the '(.+)' to the path '(.+)'$/ do |http_method, stash_key, request_uri|
begin
- self.response = rest.send("#{http_method}_rest".downcase.to_sym, request_uri, stash[stash_key])
+ self.response = rest.send("#{http_method.to_s.downcase}_rest".downcase.to_sym, request_uri, stash[stash_key])
self.inflated_response = response
rescue
self.exception = $!
end
end
+When /^I '(.+)' the '(.+)' to the path '(.+)' using a wrong private key$/ do |http_method, stash_key, request_uri|
+ key = OpenSSL::PKey::RSA.generate(2048)
+ File.open(File.join(tmpdir, 'false_key.pem'), "w") { |f| f.print key }
+ @rest = Chef::REST.new(Chef::Config[:chef_server_url], 'snakebite' , File.join(tmpdir, 'false_key.pem'))
+
+ When "I '#{http_method}' the '#{stash_key}' to the path '#{request_uri}'"
+end
+
+When /^I delete local private key/ do
+ Chef::FileCache.delete("private_key.pem")
+end
+
+When /^I register '(.+)'$/ do |user|
+ begin
+ rest = Chef::REST.new(Chef::Config[:registration_url])
+ rest.register("bobo")
+ rescue
+ self.exception = $!
+ end
+end
+
When /^I authenticate as '(.+)'$/ do |reg|
begin
rest.authenticate(reg, 'tclown')
@@ -24,3 +58,59 @@ When /^I authenticate as '(.+)'$/ do |reg|
end
end
+
+
+
+# When /^I '(.+)' the path '(.+)'$/ do |http_method, request_uri|
+# begin
+# #if http_method.downcase == 'get'
+# # self.response = @rest.get_rest(request_uri)
+# #else
+# #puts "test test test \n\n\n\n\n\n\n"
+# @response = @rest.send("#{http_method}_rest".downcase.to_sym, request_uri)
+# #end
+# puts "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+# puts @response
+# puts @response['content-type']
+# #puts self.response
+# #puts self.response.inspect
+# #self.inflated_response = self.response
+# @inflated_response = @response#JSON.parse(response.body.to_s)
+# puts "~~~~~~~~INFLATED RESPONSE~~~~~~~~~~~~"
+# puts @inflated_response
+# rescue
+# self.exception = $!
+# end
+# end
+#
+# When /^I '(.+)' the '(.+)' to the path '(.+)'$/ do |http_method, stash_key, request_uri|
+# begin
+# #if http_method.downcase == 'post'
+# # puts "post request"
+# # self.response = @rest.post_rest(request_uri, @stash[stash_key])
+# # puts self.response
+# #else
+# puts "This is the request -- @stash[stash_key]:"
+# puts @stash[stash_key].to_s
+# @response = @rest.send("#{http_method}_rest".downcase.to_sym, request_uri, @stash[stash_key])
+# #end
+# puts "This is the response:"
+# #puts self.response.body.to_s
+# puts @response
+# #self.inflated_response = response
+# @inflated_response = @response#JSON.parse(self.response.body.to_s)
+# puts "~~~~~~~~INFLATED RESPONSE~~~~~~~~~~~~"
+# puts @inflated_response
+# rescue
+# self.exception = $!
+# end
+# end
+#
+# When /^I authenticate as '(.+)'$/ do |reg|
+# begin
+# rest.authenticate(reg, 'tclown')
+# rescue
+# self.exception = $!
+# end
+# end
+#
diff --git a/features/steps/response_steps.rb b/features/steps/response_steps.rb
index 3f3ef28459..0c0fc2d6ab 100644
--- a/features/steps/response_steps.rb
+++ b/features/steps/response_steps.rb
@@ -3,70 +3,101 @@ Then /^I should get a '(.+)' exception$/ do |exception|
end
Then /^the response code should be '(.+)'$/ do |response_code|
- response.status.should == response_code.to_i
+ self.response.status.should == response_code.to_i
+end
+
+Then /^the inflated responses key '(.+)' should be the integer '(\d+)'$/ do |key, int|
+ inflated_response[key].should == int.to_i
end
Then /^the inflated responses key '(.+)' should match '(.+)'$/ do |key, regex|
- inflated_response[key].should =~ /#{regex}/
+ puts self.inflated_response.inspect if ENV['DEBUG']
+ self.inflated_response[key].should =~ /#{regex}/m
+end
+
+Then /^the inflated responses key '(.+)' should match '(.+)' as json$/ do |key, regex|
+ puts self.inflated_response.inspect if ENV["DEBUG"]
+ self.inflated_response[key].to_json.should =~ /#{regex}/m
+end
+
+Then /^the inflated responses key '(.+)' item '(\d+)' should be a kind of '(.+)'$/ do |key, index, constant|
+ inflated_response[key][index.to_i].should be_a_kind_of(eval(constant))
+end
+
+Then /^the inflated responses key '(.+)' item '(\d+)' key '(.+)' should be '(.+)'$/ do |key, index, sub_key, to_equal|
+ inflated_response[key][index.to_i][sub_key].should == to_equal
+end
+
+Then /^the inflated responses key '(.+)' should be '(\d+)' items long$/ do |key, length|
+ inflated_response[key].length.should == length.to_i
end
Then /^the inflated responses key '(.+)' should not exist$/ do |key|
- inflated_response.has_key?(key).should == false
+ self.inflated_response.has_key?(key).should == false
end
Then /^the inflated responses key '(.+)' should exist$/ do |key|
- inflated_response.has_key?(key).should == true
+ self.inflated_response.has_key?(key).should == true
end
Then /^the inflated response should be an empty array$/ do
- inflated_response.should == []
+ self.inflated_response.should == []
end
Then /^the inflated response should include '(.+)'$/ do |entry|
- inflated_response.detect { |n| n =~ /#{entry}/ }.should be(true)
+ self.inflated_response.detect { |n| n =~ /#{entry}/ }.should be(true)
end
Then /^the inflated response should be '(.+)' items long$/ do |length|
- inflated_response.length.should == length.to_i
+ self.inflated_response.length.should == length.to_i
end
Then /^the '(.+)' header should match '(.+)'$/ do |header, regex|
- response.headers[header].should =~ /#{regex}/
+ self.response.headers[header].should =~ /#{regex}/
end
Then /^the inflated responses key '(.+)' should include '(.+)'$/ do |key, regex|
- inflated_response[key].detect { |n| n =~ /#{regex}/ }.should be(true)
+ self.inflated_response[key].detect { |n| n =~ /#{regex}/ }.should be(true)
end
Then /^the inflated response should match the '(.+)'$/ do |stash_name|
stash[stash_name].each do |k,v|
- inflated_response[k.to_s].should == v
+ self.inflated_response[k.to_s].should == v
end
end
Then /^the inflated response should be the '(.+)'$/ do |stash_key|
- stash[stash_key].should == inflated_response
+ stash[stash_key].should == self.inflated_response
end
Then /^the inflated response should be a kind of '(.+)'$/ do |thing|
- inflated_response.should be_a_kind_of(thing)
+ self.inflated_response.should be_a_kind_of(thing)
end
Then /^the inflated response should respond to '(.+)' with '(.+)'$/ do |method, to_match|
to_match = JSON.parse(to_match) if to_match =~ /^\[|\{/
- inflated_response.send(method.to_sym).should == to_match
+ self.inflated_response.send(method.to_sym).should == to_match
end
Then /^the inflated response should respond to '(.+)' and match '(.+)'$/ do |method, to_match|
- inflated_response.send(method.to_sym).should == to_match
+ self.inflated_response.send(method.to_sym).should == to_match
end
Then /^the fields in the inflated response should match the '(.+)'$/ do |stash_name|
- inflated_response.each do |k,v|
+ self.inflated_response.each do |k,v|
unless k =~ /^_/ || k == 'couchrest-type'
stash[stash_name][k.to_sym].should == v
end
end
end
+Then /^the data_bag named '(.+)' should not have an item named '(.+)'$/ do |data_bag, item|
+ exists = true
+ begin
+ Chef::DataBagItem.load(data_bag, item, @couchdb)
+ rescue
+ exists = false
+ end
+ exists.should == false
+end
diff --git a/features/steps/run_client_steps.rb b/features/steps/run_client_steps.rb
index 815c5c7148..25726f59aa 100644
--- a/features/steps/run_client_steps.rb
+++ b/features/steps/run_client_steps.rb
@@ -24,7 +24,7 @@ When /^I run the chef\-client$/ do
@chef_args ||= ""
@config_file ||= File.expand_path(File.join(File.dirname(__FILE__), '..', 'data', 'config', 'client.rb'))
status = Chef::Mixin::Command.popen4(
- "chef-client -l #{@log_level} -c #{@config_file} #{@chef_args}") do |p, i, o, e|
+ "#{File.join(File.dirname(__FILE__), "..", "..", "chef", "bin", "chef-client")} -l #{@log_level} -c #{@config_file} #{@chef_args}") do |p, i, o, e|
@stdout = o.gets(nil)
@stderr = e.gets(nil)
end
@@ -76,7 +76,7 @@ openid_url "http://127.0.0.1:4001"
template_url "http://127.0.0.1:4000"
remotefile_url "http://127.0.0.1:4000"
search_url "http://127.0.0.1:4000"
-couchdb_database 'chef_integration'
+couchdb_database 'chef'
CONFIG
@config_file = File.expand_path(File.join(File.dirname(__FILE__), '..', 'data', 'config', 'client-with-logging.rb'))
@@ -86,12 +86,11 @@ CONFIG
self.cleanup_files << @config_file
- @status = Chef::Mixin::Command.popen4("chef-client -c #{@config_file}") do |p, i, o, e|
+
+ @status = Chef::Mixin::Command.popen4("#{File.join(File.dirname(__FILE__), "..", "..", "chef", "bin", "chef-client")} -l #{@log_level} -c #{@config_file} #{@chef_args}") do |p, i, o, e|
@stdout = o.gets(nil)
@stderr = e.gets(nil)
end
-
-
end
###
diff --git a/features/steps/run_solo.rb b/features/steps/run_solo.rb
index e5397b853d..b2b244cfb6 100644
--- a/features/steps/run_solo.rb
+++ b/features/steps/run_solo.rb
@@ -26,7 +26,7 @@ When /^I run chef-solo with the '(.+)' recipe$/ do |recipe_name|
cleanup_files << config_file
binary_path = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'chef', 'bin', 'chef-solo'))
- command = "chef-solo -c #{config_file} -j #{dna_file}"
+ command = "#{binary_path} -c #{config_file} -j #{dna_file}"
command += " -l debug" if ENV['LOG_LEVEL'] == 'debug'
# Run it
diff --git a/features/steps/webrat_steps.rb b/features/steps/webrat_steps.rb
index c7f78a5406..b152a1501d 100644
--- a/features/steps/webrat_steps.rb
+++ b/features/steps/webrat_steps.rb
@@ -36,4 +36,4 @@ end
When /^I attach the file at "(.*)" to "(.*)" $/ do |path, field|
attach_file(field, path)
end
- \ No newline at end of file
+
diff --git a/features/support/env.rb b/features/support/env.rb
index 2a5b217d29..ca60753df2 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-%w{chef chef-server chef-server-slice}.each do |inc_dir|
+%w{chef chef-server chef-server-slice chef-solr}.each do |inc_dir|
$: << File.join(File.dirname(__FILE__), '..', '..', inc_dir, 'lib')
end
@@ -25,22 +25,92 @@ require 'spec'
require 'chef'
require 'chef/config'
require 'chef/client'
+require 'chef/data_bag'
+require 'chef/data_bag_item'
+require 'chef/solr'
require 'tmpdir'
require 'merb-core'
require 'merb_cucumber/world/webrat'
+require 'opscode/audit'
+require 'chef/streaming_cookbook_uploader'
def Spec.run? ; true; end
-Chef::Config.from_file(File.join(File.dirname(__FILE__), '..', 'data', 'config', 'server.rb'))
-Chef::Config[:log_level] = :error
-Ohai::Config[:log_level] = :error
+ENV['LOG_LEVEL'] ||= 'error'
+
+def setup_logging
+ Chef::Config.from_file(File.join(File.dirname(__FILE__), '..', 'data', 'config', 'server.rb'))
+ Merb.logger.auto_flush = true
+ if ENV['DEBUG'] == 'true' || ENV['LOG_LEVEL'] == 'debug'
+ Chef::Config[:log_level] = :debug
+ Chef::Log.level(:debug)
+ Merb.logger.set_log(STDOUT, :debug)
+ else
+ Chef::Config[:log_level] = ENV['LOG_LEVEL'].to_sym
+ Chef::Log.level(ENV['LOG_LEVEL'].to_sym)
+ Merb.logger.set_log(STDOUT, ENV['LOG_LEVEL'].to_sym)
+ end
+ Nanite::Log.logger = Mixlib::Auth::Log.logger = Ohai::Log.logger = Chef::Log.logger
+end
+
+def setup_nanite
+ Chef::Config[:nanite_identity] = "chef-integration-test"
+ Chef::Nanite.in_event { Chef::Log.debug("Nanite is up!") }
+ Chef::Log.debug("Waiting for Nanites to register with us as a mapper")
+ sleep 10
+end
+
+def delete_databases
+ c = Chef::REST.new(Chef::Config[:couchdb_url], nil, nil)
+ %w{chef_integration}.each do |db|
+ begin
+ c.delete_rest("#{db}/")
+ rescue
+ end
+ end
+end
-if ENV['DEBUG'] = 'true'
- Merb.logger.set_log(STDOUT, :debug) if ENV['DEBUG'] = 'true'
-else
- Merb.logger.set_log(STDOUT, :error)
+def create_databases
+ Chef::Log.info("Creating bootstrap databases")
+ cdb = Chef::CouchDB.new(Chef::Config[:couchdb_url], "chef_integration")
+ cdb.create_db
+ Chef::Node.create_design_document
+ Chef::Role.create_design_document
+ Chef::DataBag.create_design_document
+ Chef::Role.sync_from_disk_to_couchdb
end
+
+def create_validation
+# TODO: Create the validation certificate here
+ File.open("#{Dir.tmpdir}/validation.pem", "w") do |f|
+ f.print response["private_key"]
+ end
+end
+
+def prepare_replicas
+ c = Chef::REST.new(Chef::Config[:couchdb_url], nil, nil)
+ c.put_rest("chef_integration_safe/", nil)
+ c.post_rest("_replicate", { "source" => "#{Chef::Config[:couchdb_url]}/chef_integration", "target" => "#{Chef::Config[:couchdb_url]}/chef_integration_safe" })
+ c.delete_rest("chef_integration")
+end
+
+at_exit do
+ c = Chef::REST.new(Chef::Config[:couchdb_url], nil, nil)
+ c.delete_rest("chef_integration_safe")
+ File.unlink(File.join(Dir.tmpdir, "validation.pem"))
+end
+
+###
+# Pre-testing setup
+###
+setup_logging
+setup_nanite
+delete_databases
+create_databases
+create_validation
+prepare_replicas
+
Merb.start_environment(
:merb_root => File.join(File.dirname(__FILE__), "..", "..", "chef-server"),
:testing => true,
@@ -55,6 +125,11 @@ Spec::Runner.configure do |config|
config.include(Merb::Test::ControllerHelper)
end
+Chef::Log.info("Ready to run tests")
+
+###
+# The Cucumber World
+###
module ChefWorld
attr_accessor :recipe, :cookbook, :response, :inflated_response, :log_level, :chef_args, :config_file, :stdout, :stderr, :status, :exception
@@ -64,7 +139,7 @@ module ChefWorld
end
def rest
- @rest ||= Chef::REST.new('http://localhost:4000')
+ @rest ||= Chef::REST.new('http://localhost:4000/organizations/clownco', nil, nil)
end
def tmpdir
@@ -91,7 +166,23 @@ end
World(ChefWorld)
+Before do
+ system("mkdir -p #{tmpdir}")
+ system("cp -r #{File.join(Dir.tmpdir, "validation.pem")} #{File.join(tmpdir, "validation.pem")}")
+ Chef::CouchDB.new(Chef::Config[:couchdb_url], "chef_integration").create_db
+ c = Chef::REST.new(Chef::Config[:couchdb_url], nil, nil)
+ c.post_rest("_replicate", {
+ "source" => "#{Chef::Config[:couchdb_url]}/chef_integration_safe",
+ "target" => "#{Chef::Config[:couchdb_url]}/chef_integration"
+ })
+end
+
After do
+ r = Chef::REST.new(Chef::Config[:couchdb_url], nil, nil)
+ r.delete_rest("chef_integration/")
+ s = Chef::Solr.new
+ s.solr_delete_by_query("*:*")
+ s.solr_commit
cleanup_files.each do |file|
system("rm #{file}")
end
@@ -104,5 +195,6 @@ After do
end
data_tmp = File.join(File.dirname(__FILE__), "..", "data", "tmp")
system("rm -rf #{data_tmp}/*")
+ system("rm -rf #{tmpdir}")
end
diff --git a/opscode-start b/opscode-start
new file mode 100755
index 0000000000..b2cddf6cef
--- /dev/null
+++ b/opscode-start
@@ -0,0 +1,76 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use FindBin;
+
+my $src_dir = $FindBin::Bin;
+system("sudo echo");
+
+my $osascript = <<EOF;
+ tell application "iTerm"
+ set myterm to (make new terminal)
+ tell myterm
+ launch session "Default session"
+ tell the last session
+ set name to "CouchDB"
+ write text "cd $src_dir && sudo rake dev:features:start:couchdb"
+ end tell
+ launch session "Default session"
+ tell the last session
+ set name to "RabbitMQ"
+ write text "cd $src_dir && sudo rake dev:features:start:rabbitmq"
+ end tell
+ launch session "Default session"
+ tell the last session
+ set name to "Solr"
+ write text "cd $src_dir && sudo rake dev:features:start:chef_solr"
+ end tell
+ launch session "Default session"
+ tell the last session
+ set name to "Dynomite"
+ write text "cd $src_dir && sudo rake dev:features:start:dynomite"
+ end tell
+ launch session "Default session"
+ tell the last session
+ set name to "Opscode GUID"
+ delay 10
+ write text "cd $src_dir && sudo rake dev:features:start:opscode_guid"
+ end tell
+ launch session "Default session"
+ tell the last session
+ set name to "Certificate"
+ write text "cd $src_dir && sudo rake dev:features:start:certificate"
+ end tell
+ launch session "Default session"
+ tell the last session
+ set name to "Opscode Authz"
+ write text "cd $src_dir && sudo rake dev:features:start:opscode_authz"
+ end tell
+ launch session "Default session"
+ tell the last session
+ set name to "Opscode Audit"
+ write text "cd $src_dir && sudo rake dev:features:start:opscode_audit"
+ end tell
+ launch session "Default session"
+ tell the last session
+ set name to "Opscode Account"
+ write text "cd $src_dir && sudo rake dev:features:start:opscode_account"
+ end tell
+ launch session "Default session"
+ tell the last session
+ set name to "Chef Solr Indexer"
+ write text "cd $src_dir && sudo rake dev:features:start:chef_solr_indexer"
+ end tell
+ launch session "Default session"
+ tell the last session
+ set name to "Chef Server"
+ write text "cd $src_dir && sudo rake dev:features:start:chef_server"
+ end tell
+ end tell
+ end tell
+EOF
+
+open(CMD, "| osascript");
+print CMD $osascript;
+close(CMD);