From 03dc3d8d99f3b8c975949b86fd5f6313ba0b27a1 Mon Sep 17 00:00:00 2001 From: Tim Lianov Date: Wed, 4 Apr 2018 22:44:39 +0300 Subject: [PATCH] v3 --- devops-client/lib/devops-client.rb | 6 +- .../handler/bootstrap_templates.rb | 29 - .../lib/devops-client/handler/category.rb | 232 + .../lib/devops-client/handler/chef.rb | 36 + .../handler/deploy_envs/deploy_env.rb | 8 - .../lib/devops-client/handler/environment.rb | 239 + .../lib/devops-client/handler/filter.rb | 50 +- .../lib/devops-client/handler/flavor.rb | 34 - .../lib/devops-client/handler/group.rb | 35 - .../lib/devops-client/handler/handler.rb | 43 +- .../devops-client/handler/handler_factory.rb | 52 +- .../handler/helpers/http_utils.rb | 7 +- .../handler/helpers/input_utils.rb | 32 +- .../handler/helpers/outputtable.rb | 7 +- .../handler/helpers/resources_fetcher.rb | 31 +- .../handler/helpers/resources_selector.rb | 104 +- .../handler/helpers/validators.rb | 16 + .../lib/devops-client/handler/image.rb | 102 +- .../lib/devops-client/handler/key.rb | 29 +- .../lib/devops-client/handler/network.rb | 35 - .../lib/devops-client/handler/project.rb | 487 +- .../lib/devops-client/handler/provider.rb | 94 +- .../lib/devops-client/handler/role.rb | 75 + .../lib/devops-client/handler/server.rb | 134 +- .../lib/devops-client/handler/stack.rb | 39 +- .../devops-client/handler/stack_template.rb | 74 +- .../lib/devops-client/handler/tag.rb | 61 - .../lib/devops-client/handler/user.rb | 86 +- .../options/bootstrap_templates_options.rb | 13 - .../devops-client/options/category_options.rb | 91 + .../lib/devops-client/options/chef_options.rb | 19 + .../devops-client/options/common_options.rb | 40 +- .../devops-client/options/deploy_options.rb | 2 + .../options/environment_options.rb | 108 + .../devops-client/options/filter_options.rb | 5 +- .../devops-client/options/flavor_options.rb | 19 - .../devops-client/options/group_options.rb | 19 - .../devops-client/options/image_options.rb | 11 +- .../lib/devops-client/options/key_options.rb | 4 +- .../lib/devops-client/options/main.rb | 45 +- .../devops-client/options/network_options.rb | 20 - .../devops-client/options/project_options.rb | 80 +- .../devops-client/options/provider_options.rb | 25 +- .../lib/devops-client/options/role_options.rb | 28 + .../devops-client/options/script_options.rb | 3 +- .../devops-client/options/server_options.rb | 117 +- .../devops-client/options/stack_options.rb | 10 +- .../options/stack_template_options.rb | 18 +- .../lib/devops-client/options/tag_options.rb | 14 - .../lib/devops-client/options/user_options.rb | 9 +- .../lib/devops-client/output/base.rb | 35 +- .../output/bootstrap_templates.rb | 29 - .../lib/devops-client/output/category.rb | 96 + .../lib/devops-client/output/chef.rb | 44 + .../lib/devops-client/output/environment.rb | 106 + .../lib/devops-client/output/filters.rb | 9 +- .../lib/devops-client/output/flavors.rb | 48 - .../lib/devops-client/output/groups.rb | 43 - .../lib/devops-client/output/image.rb | 12 +- devops-client/lib/devops-client/output/key.rb | 4 +- .../lib/devops-client/output/network.rb | 48 - .../lib/devops-client/output/project.rb | 72 +- .../lib/devops-client/output/provider.rb | 154 +- .../lib/devops-client/output/role.rb | 75 + .../lib/devops-client/output/server.rb | 18 +- devops-client/lib/devops-client/output/tag.rb | 28 - .../lib/devops-client/output/user.rb | 43 +- .../lib/devops-client/providers/providers.rb | 12 +- devops-client/lib/devops-client/version.rb | 2 +- devops-client/locales/en.yml | 227 +- devops-client/locales/ru.yml | 203 +- devops-service/Gemfile | 13 +- devops-service/Gemfile.lock | 323 +- devops-service/Guardfile | 2 +- .../app/api2/handlers/bootstrap_templates.rb | 18 - devops-service/app/api2/handlers/filter.rb | 27 - devops-service/app/api2/handlers/flavor.rb | 20 - devops-service/app/api2/handlers/group.rb | 24 - devops-service/app/api2/handlers/image.rb | 58 - devops-service/app/api2/handlers/key.rb | 53 - devops-service/app/api2/handlers/network.rb | 25 - devops-service/app/api2/handlers/project.rb | 332 - devops-service/app/api2/handlers/provider.rb | 50 - devops-service/app/api2/handlers/report.rb | 61 - .../app/api2/handlers/request_handler.rb | 26 - devops-service/app/api2/handlers/script.rb | 103 - devops-service/app/api2/handlers/server.rb | 382 - .../app/api2/handlers/stack_template.rb | 81 - devops-service/app/api2/handlers/user.rb | 62 - devops-service/app/api2/helpers/request.rb | 49 - devops-service/app/api2/parsers/deploy.rb | 22 - devops-service/app/api2/parsers/network.rb | 15 - devops-service/app/api2/parsers/user.rb | 37 - .../app/api2/routes/bootstrap_templates.rb | 30 - devops-service/app/api2/routes/deploy.rb | 66 - devops-service/app/api2/routes/filter.rb | 80 - devops-service/app/api2/routes/flavor.rb | 53 - devops-service/app/api2/routes/group.rb | 67 - devops-service/app/api2/routes/image.rb | 160 - devops-service/app/api2/routes/key.rb | 73 - devops-service/app/api2/routes/network.rb | 56 - devops-service/app/api2/routes/project.rb | 625 - devops-service/app/api2/routes/provider.rb | 142 - devops-service/app/api2/routes/report.rb | 38 - devops-service/app/api2/routes/server.rb | 504 - .../app/api2/routes/stack_template.rb | 58 - devops-service/app/api2/routes/statistic.rb | 18 - devops-service/app/api2/routes/tag.rb | 72 - devops-service/app/api2/routes/user.rb | 151 - devops-service/app/api2/routes/v2.0.rb | 175 - devops-service/app/api3/docs/chef.rb | 190 + devops-service/app/api3/docs/devops_error.rb | 20 + .../app/api3/docs/devops_response.rb | 21 + devops-service/app/api3/docs/filter.rb | 146 + devops-service/app/api3/docs/image.rb | 230 + devops-service/app/api3/docs/job_task.rb | 260 + devops-service/app/api3/docs/key.rb | 139 + devops-service/app/api3/docs/project.rb | 1323 + devops-service/app/api3/docs/provider.rb | 607 + devops-service/app/api3/docs/role.rb | 212 + devops-service/app/api3/docs/server.rb | 890 + devops-service/app/api3/docs/server_model.rb | 65 + devops-service/app/api3/docs/user.rb | 435 + devops-service/app/api3/handlers/api-docs.rb | 109 + .../handlers/tag.rb => api3/handlers/chef.rb} | 21 +- .../app/{api2 => api3}/handlers/deploy.rb | 32 +- devops-service/app/api3/handlers/filter.rb | 33 + devops-service/app/api3/handlers/image.rb | 63 + devops-service/app/api3/handlers/job_task.rb | 68 + devops-service/app/api3/handlers/key.rb | 56 + devops-service/app/api3/handlers/project.rb | 439 + devops-service/app/api3/handlers/provider.rb | 86 + .../handlers/provider_notification.rb | 12 +- .../app/api3/handlers/request_handler.rb | 90 + devops-service/app/api3/handlers/roles.rb | 60 + devops-service/app/api3/handlers/script.rb | 83 + devops-service/app/api3/handlers/server.rb | 390 + .../app/{api2 => api3}/handlers/stack.rb | 62 +- .../app/api3/handlers/stack_template.rb | 65 + devops-service/app/api3/handlers/statistic.rb | 42 + devops-service/app/api3/handlers/user.rb | 68 + .../app/{api2 => api3}/helpers/parser.rb | 20 +- .../version_2.rb => api3/helpers/query.rb} | 32 +- .../parsers/tag.rb => api3/parsers/chef.rb} | 5 +- .../parsers/deploy.rb} | 4 +- .../app/{api2 => api3}/parsers/filter.rb | 2 +- .../app/{api2 => api3}/parsers/image.rb | 3 +- .../app/{api2 => api3}/parsers/key.rb | 2 +- .../app/{api2 => api3}/parsers/project.rb | 64 +- .../app/{api2 => api3}/parsers/provider.rb | 2 +- .../{api2 => api3}/parsers/request_parser.rb | 6 +- devops-service/app/api3/parsers/roles.rb | 22 + .../app/{api2 => api3}/parsers/script.rb | 10 +- .../{api2 => api3}/parsers/security_groups.rb | 0 .../app/{api2 => api3}/parsers/server.rb | 54 +- .../app/{api2 => api3}/parsers/stack.rb | 17 +- .../{api2 => api3}/parsers/stack_template.rb | 2 +- devops-service/app/api3/parsers/user.rb | 36 + .../app/api3/public/swagger-ui/css/print.css | 1187 + .../app/api3/public/swagger-ui/css/reset.css | 125 + .../app/api3/public/swagger-ui/css/screen.css | 1300 + .../app/api3/public/swagger-ui/css/style.css | 250 + .../api3/public/swagger-ui/css/typography.css | 14 + .../swagger-ui/fonts/DroidSans-Bold.ttf | Bin 0 -> 42480 bytes .../public/swagger-ui/fonts/DroidSans.ttf | Bin 0 -> 41028 bytes .../public/swagger-ui/images/collapse.gif | Bin 0 -> 69 bytes .../api3/public/swagger-ui/images/expand.gif | Bin 0 -> 73 bytes .../swagger-ui/images/explorer_icons.png | Bin 0 -> 5763 bytes .../swagger-ui/images/favicon-16x16.png | Bin 0 -> 645 bytes .../swagger-ui/images/favicon-32x32.png | Bin 0 -> 1654 bytes .../api3/public/swagger-ui/images/favicon.ico | Bin 0 -> 5430 bytes .../public/swagger-ui/images/logo_small.png | Bin 0 -> 770 bytes .../swagger-ui/images/pet_store_api.png | Bin 0 -> 824 bytes .../public/swagger-ui/images/throbber.gif | Bin 0 -> 9257 bytes .../public/swagger-ui/images/wordnik_api.png | Bin 0 -> 980 bytes .../app/api3/public/swagger-ui/lang/en.js | 55 + .../app/api3/public/swagger-ui/lang/es.js | 52 + .../app/api3/public/swagger-ui/lang/fr.js | 53 + .../app/api3/public/swagger-ui/lang/it.js | 52 + .../app/api3/public/swagger-ui/lang/ja.js | 53 + .../app/api3/public/swagger-ui/lang/pl.js | 53 + .../app/api3/public/swagger-ui/lang/pt.js | 53 + .../app/api3/public/swagger-ui/lang/ru.js | 55 + .../app/api3/public/swagger-ui/lang/tr.js | 53 + .../api3/public/swagger-ui/lang/translator.js | 39 + .../app/api3/public/swagger-ui/lang/zh-cn.js | 53 + .../public/swagger-ui/lib/backbone-min.js | 15 + .../public/swagger-ui/lib/handlebars-2.0.0.js | 28 + .../swagger-ui/lib/highlight.7.3.pack.js | 1 + .../public/swagger-ui/lib/jquery-1.8.0.min.js | 2 + .../swagger-ui/lib/jquery.ba-bbq.min.js | 18 + .../swagger-ui/lib/jquery.slideto.min.js | 1 + .../swagger-ui/lib/jquery.wiggle.min.js | 8 + .../public/swagger-ui/lib/jsoneditor.min.js | 11 + .../app/api3/public/swagger-ui/lib/marked.js | 1272 + .../public/swagger-ui/lib/swagger-oauth.js | 338 + .../public/swagger-ui/lib/underscore-min.js | 6 + .../public/swagger-ui/lib/underscore-min.map | 1 + .../app/api3/public/swagger-ui/o2c.html | 20 + .../app/api3/public/swagger-ui/swagger-ui.js | 26330 ++++++++++++++++ .../api3/public/swagger-ui/swagger-ui.min.js | 11 + devops-service/app/api3/routes/api-docs.rb | 23 + devops-service/app/api3/routes/chef.rb | 49 + devops-service/app/api3/routes/deploy.rb | 30 + devops-service/app/api3/routes/filter.rb | 35 + devops-service/app/api3/routes/image.rb | 49 + devops-service/app/api3/routes/job_task.rb | 49 + devops-service/app/api3/routes/key.rb | 37 + devops-service/app/api3/routes/project.rb | 279 + devops-service/app/api3/routes/provider.rb | 83 + .../routes/provider_notification.rb | 14 +- devops-service/app/api3/routes/roles.rb | 60 + .../app/{api2 => api3}/routes/script.rb | 36 +- devops-service/app/api3/routes/server.rb | 210 + .../app/{api2 => api3}/routes/stack.rb | 127 +- .../app/api3/routes/stack_template.rb | 51 + devops-service/app/api3/routes/statistic.rb | 20 + devops-service/app/api3/routes/user.rb | 67 + devops-service/app/api3/routes/v3.0.rb | 139 + devops-service/app/api3/views/index.html.erb | 124 + .../{api2/routes => api3}/views/report.erb | 0 devops-service/app/client/devops-client.rb | 2 +- devops-service/app/devops-api2.rb | 90 - devops-service/app/devops-api3.rb | 70 + devops-service/app/devops-client.rb | 2 +- devops-service/applications.rb | 2 +- devops-service/auth/devops_auth.rb | 5 +- devops-service/bin/routes.rb | 2 +- .../commands/bootstrap_templates.rb | 2 +- devops-service/commands/image.rb | 15 +- devops-service/commands/knife_commands.rb | 5 +- devops-service/commands/status.rb | 4 + devops-service/config-local.rb | 30 + devops-service/config.rb | 31 +- devops-service/config.ru | 7 +- devops-service/config/mongo.yml | 8 + devops-service/core/devops-db.rb | 46 +- devops-service/core/devops-loader.rb | 15 +- devops-service/core/devops-logger.rb | 4 +- devops-service/core/devops-service.rb | 78 +- devops-service/db/mongo/connectors/base.rb | 3 + devops-service/db/mongo/connectors/filter.rb | 43 - devops-service/db/mongo/connectors/image.rb | 31 - devops-service/db/mongo/connectors/key.rb | 2 + devops-service/db/mongo/connectors/project.rb | 157 - .../db/mongo/connectors/provider_account.rb | 37 - devops-service/db/mongo/connectors/report.rb | 88 - devops-service/db/mongo/connectors/server.rb | 8 +- devops-service/db/mongo/connectors/stack.rb | 57 - .../db/mongo/connectors/stack_template.rb | 31 - .../db/mongo/connectors/statictic.rb | 86 - devops-service/db/mongo/connectors/user.rb | 49 - .../models/deploy_env/cloud_deploy_env.rb | 62 - .../models/deploy_env/deploy_env_base.rb | 124 - .../mongo/models/deploy_env/deploy_env_ec2.rb | 66 - .../models/deploy_env/deploy_env_factory.rb | 25 - .../models/deploy_env/deploy_env_multi.rb | 118 - .../models/deploy_env/deploy_env_openstack.rb | 40 - .../models/deploy_env/deploy_env_static.rb | 31 - .../db/mongo/models/environment/category.rb | 101 + .../configuration_management/chef.rb | 258 + .../configuration_management/tool.rb | 45 + .../mongo/models/environment/environment.rb | 94 + .../models/environment/environments_array.rb | 27 + devops-service/db/mongo/models/filter.rb | 54 + devops-service/db/mongo/models/image.rb | 113 +- devops-service/db/mongo/models/job_task.rb | 57 + devops-service/db/mongo/models/key.rb | 55 +- .../db/mongo/models/model_with_provider.rb | 32 +- .../models/model_with_provider_account.rb | 51 + devops-service/db/mongo/models/mongo_model.rb | 223 - devops-service/db/mongo/models/policy.rb | 23 + devops-service/db/mongo/models/project.rb | 276 +- .../provider_accounts/ec2_provider_account.rb | 63 - .../openstack_provider_account.rb | 50 - .../provider_accounts/provider_account.rb | 72 - .../static_provider_account.rb | 18 - devops-service/db/mongo/models/report.rb | 61 - devops-service/db/mongo/models/role.rb | 77 + devops-service/db/mongo/models/server.rb | 135 +- .../stack/{stack_ec2.rb => stack_aws.rb} | 10 +- .../db/mongo/models/stack/stack_base.rb | 126 +- .../db/mongo/models/stack/stack_factory.rb | 21 +- ..._template_ec2.rb => stack_template_aws.rb} | 14 +- .../stack_template/stack_template_base.rb | 68 +- .../stack_template/stack_template_factory.rb | 14 +- .../stack_template_openstack.rb | 1 + devops-service/db/mongo/models/statistic.rb | 27 + devops-service/db/mongo/models/user.rb | 163 +- devops-service/db/mongo/mongo_connector.rb | 43 +- devops-service/db/mongoid/created.rb | 33 + devops-service/db/mongoid/updated.rb | 45 + devops-service/db/validators/all.rb | 3 +- devops-service/db/validators/base.rb | 5 +- .../db/validators/categories_array.rb | 15 + .../db/validators/deploy_env/run_list.rb | 11 - .../field_validators/named_tasks.rb | 65 - .../validators/field_validators/provider.rb | 2 +- .../db/validators/field_validators/vpc.rb | 2 +- .../db/validators/helpers/run_list.rb | 16 - devops-service/db/validators/name.rb | 10 + devops-service/db/validators/not_nil.rb | 7 + .../db/validators/project/named_tasks.rb | 42 + .../db/validators/run_list_array.rb | 21 + devops-service/db/validators/users_array.rb | 17 + devops-service/doc/chef.md | 126 + devops-service/doc/deploy.md | 39 + devops-service/doc/filters.md | 76 + devops-service/doc/images.md | 140 + devops-service/doc/keys.md | 87 + devops-service/doc/projects.md | 708 + devops-service/doc/provider.md | 252 + devops-service/doc/reports.md | 137 + devops-service/doc/roles.md | 123 + devops-service/doc/server.md | 588 + devops-service/doc/users.md | 57 + devops-service/exceptions/access_error.rb | 11 + devops-service/exceptions/bootstrap_error.rb | 21 + .../exceptions/configuration_error.rb | 12 + .../exceptions/conflict_exception.rb | 15 +- devops-service/exceptions/creation_error.rb | 21 + devops-service/exceptions/dependency_error.rb | 13 +- devops-service/exceptions/deploy_error.rb | 22 + .../exceptions/deploy_info_error.rb | 20 +- .../exceptions/invalid_privileges.rb | 10 +- devops-service/exceptions/parser_error.rb | 13 +- devops-service/exceptions/record_not_found.rb | 11 +- .../exceptions/server_operation_error.rb | 25 + devops-service/exceptions/validation_error.rb | 33 +- .../features/api_v2/00_list/filter.feature | 40 - .../features/api_v2/00_list/flavor.feature | 64 - .../features/api_v2/00_list/group.feature | 43 - .../features/api_v2/00_list/network.feature | 63 - .../features/api_v2/00_list/project.feature | 21 - .../features/api_v2/00_list/provider.feature | 18 - .../features/api_v2/00_list/script.feature | 17 - .../features/api_v2/00_list/templates.feature | 18 - .../api_v2/10_create/00_filter.feature | 193 - .../api_v2/10_create/10_image.feature | 576 - .../api_v2/10_create/20_project.feature | 59 - .../api_v2/10_create/20_script.feature | 20 - .../api_v2/20_update/10_image.feature | 442 - .../api_v2/30_execute/20_script.feature | 13 - .../api_v2/90_delete/10_script.feature | 12 - .../api_v2/90_delete/80_project.feature | 43 - .../api_v2/90_delete/90_image.feature | 42 - .../api_v2/90_delete/99_filter.feature | 173 - devops-service/features/api_v2/server.feature | 61 - .../lib/executors/server_executor.rb | 608 +- .../server_executor/expiration_scheduler.rb | 4 +- .../server_operation_result.rb | 22 +- .../lib/executors/stack_executor.rb | 90 +- .../stack_executor/chef_node_name_builder.rb | 49 +- .../prioritized_groups_bootstrapper.rb | 37 +- ...ootstrapper.rb => servers_bootstrapper.rb} | 53 +- .../stack_executor/stack_creation_waiter.rb | 7 +- .../stack_executor/stack_servers_fetcher.rb | 54 +- .../stack_executor/stack_servers_persister.rb | 88 +- devops-service/lib/helpers/job_waiter.rb | 12 +- devops-service/lib/helpers/result_object.rb | 2 +- devops-service/lib/knife/knife_factory.rb | 21 +- .../lib/project/type/generic_type.rb | 74 +- .../lib/project/type/project_type.rb | 2 +- devops-service/lib/ssh/ssh_utils.rb | 111 + devops-service/lib/sync_stack_lambda.py | 8 +- devops-service/messages/en.yml | 12 +- .../01252016_rename_ec2_provider_to_aws.js | 5 + .../01252016_update_created_at_fields.js | 1 + ...20_04_2016_add_launch_options_to_stacks.js | 14 - .../convert_deploy_envs_devops_3.js | 40 + .../delete_bootstrap_template_from_images.js | 1 + ...ider.rb => abstract_provider_connector.rb} | 23 +- devops-service/providers/accounts_factory.rb | 41 - devops-service/providers/aws.rb | 62 + .../{ec2.rb => aws/aws_connector.rb} | 130 +- .../providers/aws/aws_provider_account.rb | 40 + .../aws/server_category_provider_aws.rb | 83 + .../aws/stack_category_provider_aws.rb | 41 + .../providers/category_cloud_provider.rb | 61 + devops-service/providers/category_provider.rb | 43 + .../providers/ec2_accounts_factory.rb | 38 - .../providers/{ => openstack}/openstack.rb | 37 +- .../openstack/openstack_accounts_factory.rb | 21 + .../openstack/openstack_provider_account.rb | 30 + .../providers/openstack/provider_openstack.rb | 21 + .../providers/openstack_accounts_factory.rb | 6 - devops-service/providers/provider.rb | 90 + devops-service/providers/provider_account.rb | 46 + devops-service/providers/provider_factory.rb | 97 - devops-service/providers/static.rb | 65 +- .../static/category_provider_static.rb | 10 + .../providers/static/static_connector.rb | 59 + .../static/static_provider_account.rb | 12 + .../providers/static_accounts_factory.rb | 35 - .../sinatra/methods_with_headers.rb | 66 +- .../spec/connectors/filter_connector_spec.rb | 88 - .../spec/connectors/image_connector_spec.rb | 35 - .../spec/connectors/key_connector_spec.rb | 60 - .../spec/connectors/project_connector_spec.rb | 293 - .../provider_accounts_connector_spec.rb | 58 - .../connectors/shared_connectors_specs.rb | 127 - .../stack_template_connector_spec.rb | 40 - .../spec/connectors/tester_connector/base.rb | 53 - .../connectors/tester_connector/filter.rb | 6 - .../spec/connectors/tester_connector/image.rb | 6 - .../spec/connectors/tester_connector/key.rb | 6 - .../connectors/tester_connector/project.rb | 6 - .../tester_connector/provider_account.rb | 6 - .../tester_connector/stack_template.rb | 6 - .../spec/executors/helpers/job_waiter_spec.rb | 20 +- .../expiration_scheduler_spec.rb | 1 + .../spec/executors/server_executor_spec.rb | 732 +- .../chef_node_name_builder_spec.rb | 44 +- .../stack_executor/group_bootstrapper_spec.rb | 54 - .../prioritized_groups_bootstrapper_spec.rb | 106 +- .../servers_bootstrapper_spec.rb | 45 + .../stack_creation_waiter_spec.rb | 9 +- .../stack_servers_fetcher_spec.rb | 9 +- .../stack_servers_persister_spec.rb | 64 +- .../spec/executors/stack_executor_spec.rb | 96 +- devops-service/spec/factories/category.rb | 17 + devops-service/spec/factories/deploy_env.rb | 28 - devops-service/spec/factories/environment.rb | 10 + devops-service/spec/factories/image.rb | 5 +- devops-service/spec/factories/key.rb | 1 - devops-service/spec/factories/project.rb | 19 +- .../spec/factories/provider_account.rb | 12 +- devops-service/spec/factories/report.rb | 18 - devops-service/spec/factories/server.rb | 11 +- devops-service/spec/factories/stack.rb | 18 +- .../spec/factories/stack_template.rb | 12 +- devops-service/spec/factories/task.rb | 18 + devops-service/spec/factories/user.rb | 1 + .../models/deploy_env/deploy_env_ec2_spec.rb | 88 - .../deploy_env/deploy_env_openstack_spec.rb | 38 - .../deploy_env/deploy_env_static_spec.rb | 38 - .../shared_cloud_deploy_env_specs.rb | 38 - ...eploy_env_specs.rb => environment_spec.rb} | 121 +- devops-service/spec/models/image_spec.rb | 48 +- devops-service/spec/models/job_task_spec.rb | 39 + devops-service/spec/models/key_spec.rb | 10 +- devops-service/spec/models/project_spec.rb | 260 +- .../aws_provider_account_spec.rb | 34 + .../ec2_provider_account_spec.rb | 70 - .../openstack_provider_account_spec.rb | 12 - devops-service/spec/models/report_spec.rb | 39 - devops-service/spec/models/server_spec.rb | 57 +- .../spec/models/shared_validation_specs.rb | 20 +- .../{stack_ec2_spec.rb => stack_aws_spec.rb} | 119 +- .../shared_stack_template_specs.rb | 39 +- ...ec2_spec.rb => stack_template_aws_spec.rb} | 26 +- .../stack_template_openstack_spec.rb | 7 +- devops-service/spec/models/user_spec.rb | 113 +- .../spec/shared_contexts/clean_db.rb | 7 + .../spec/shared_contexts/stubbed_connector.rb | 6 - .../spec/shared_contexts/type_worker.rb | 6 +- .../shared_contexts/with_tester_connector.rb | 23 - devops-service/spec/spec_helper.rb | 13 +- .../spec/workers/create_server_worker_spec.rb | 31 +- .../workers/stack_bootstrap_worker_spec.rb | 101 +- .../spec/workers/stack_sync_worker_spec.rb | 57 - .../step_definitions/http_queries_steps.rb | 6 +- .../features/step_definitions/server_steps.rb | 69 +- .../step_definitions/variables_steps.rb | 8 + devops-service/tests/features/support/env.rb | 44 +- .../tests/features/support/hooks.rb | 4 + devops-service/tests/generate_tests.rb | 89 +- devops-service/tests/params-dev.yml | 29 + devops-service/tests/params.yml | 2 - .../api_v2/00_list/00_network.feature.erb | 42 - .../00_list/10_bootstrap_template.feature.erb | 18 - .../api_v2/00_list/10_filter.feature.erb | 32 - .../api_v2/00_list/10_group.feature.erb | 32 - .../api_v2/00_list/10_user.feature.erb | 28 - .../api_v2/00_list/20_image.feature.erb | 106 - .../api_v2/00_list/30_project.feature.erb | 6 - .../api_v2/00_list/flavor.feature.erb | 42 - .../api_v2/10_create/00_filter.feature.erb | 74 - .../api_v2/10_create/00_key.feature.erb | 44 - .../api_v2/10_create/00_user.feature.erb | 84 - .../api_v2/10_create/10_image.feature.erb | 85 - .../api_v2/10_create/20_project.feature.erb | 60 - .../10_create/21_deploy_env.feature.erb | 3 - .../api_v2/90_delete/80_project.feature.erb | 63 - .../api_v2/90_delete/90_image.feature.erb | 37 - .../api_v2/90_delete/99_key.feature.erb | 19 - .../00_prepare/00_create_role.feature.erb | 13 + .../00_prepare/10_create_user.feature.erb | 20 + .../api_v3/01_list/00_chef.feature.erb | 7 + .../api_v3/01_list/10_filter.feature.erb | 19 + .../01_list/10_key.feature.erb} | 9 +- .../api_v3/01_list/10_user.feature.erb | 12 + .../api_v3/01_list/11_provider.feature.erb | 12 + .../api_v3/01_list/20_image.feature.erb | 59 + .../api_v3/01_list/30_project.feature.erb | 13 + .../api_v3/10_create/00_filter.feature.erb | 51 + .../api_v3/10_create/00_key.feature.erb | 8 + .../10_create/00_stack_template.feature.erb | 0 .../10_create/01_provider_account.feature.erb | 47 + .../api_v3/10_create/10_image.feature.erb | 32 + .../api_v3/10_create/20_project.feature.erb | 24 + .../10_create/21_deploy_env.feature.erb | 22 + .../10_create/30_script.feature.erb | 0 .../10_create/40_deploy_env.feature.erb | 0 .../api_v3/10_create/40_server.feature.erb | 127 + .../10_create/50_stack.feature.erb | 0 .../20_update/00_user.feature.erb | 0 .../20_update/10_image.feature.erb | 61 +- .../api_v3/20_update/20_project.feature.erb | 65 + .../90_delete/00_stack_template.feature.erb | 0 .../90_delete/10_script.feature.erb | 0 .../90_delete/10_stack.feature.erb | 0 .../90_delete/20_deploy_env.feature.erb | 2 +- .../api_v3/90_delete/60_server.feature.erb | 29 + .../api_v3/90_delete/80_project.feature.erb | 13 + .../api_v3/90_delete/90_image.feature.erb | 24 + .../90_delete/90_user.feature.erb | 0 .../90_delete/98_filter.feature.erb} | 39 +- .../api_v3/90_delete/98_key.feature.erb | 8 + .../api_v3/90_delete/99_finalize.feature.erb | 54 + .../tests/templates/fixtures/aws.yml | 61 + .../tests/templates/fixtures/deploy_env.yml | 107 - .../tests/templates/fixtures/ec2.yml | 33 - .../templates/fixtures/fixture_formatter.rb | 1 + .../tests/templates/fixtures/key.yml | 2 +- .../tests/templates/fixtures/openstack.yml | 39 +- .../tests/templates/fixtures/project.yml | 20 +- .../tests/templates/fixtures/providers.yml | 4 +- .../tests/templates/fixtures/role.yml | 6 + .../tests/templates/fixtures/static.yml | 19 +- .../tests/templates/fixtures/user.yml | 40 +- .../generators/path_scenarios_generator.rb | 188 +- devops-service/workers/bootstrap_worker.rb | 66 +- .../workers/create_server_worker.rb | 86 +- .../workers/delete_expired_server_worker.rb | 38 +- .../workers/delete_server_worker.rb | 39 +- devops-service/workers/deploy_worker.rb | 72 +- devops-service/workers/project_test_worker.rb | 20 +- devops-service/workers/run_workers.rb | 42 +- .../workers/stack_bootstrap_worker.rb | 78 +- devops-service/workers/stack_sync_worker.rb | 42 +- devops-service/workers/unbootstrap_worker.rb | 40 + .../workers/wait_rebootstrap_worker.rb | 43 - devops-service/workers/worker.rb | 90 +- 544 files changed, 49820 insertions(+), 15769 deletions(-) delete mode 100644 devops-client/lib/devops-client/handler/bootstrap_templates.rb create mode 100644 devops-client/lib/devops-client/handler/category.rb create mode 100644 devops-client/lib/devops-client/handler/chef.rb create mode 100644 devops-client/lib/devops-client/handler/environment.rb delete mode 100644 devops-client/lib/devops-client/handler/flavor.rb delete mode 100644 devops-client/lib/devops-client/handler/group.rb create mode 100644 devops-client/lib/devops-client/handler/helpers/validators.rb delete mode 100644 devops-client/lib/devops-client/handler/network.rb create mode 100644 devops-client/lib/devops-client/handler/role.rb delete mode 100644 devops-client/lib/devops-client/handler/tag.rb delete mode 100644 devops-client/lib/devops-client/options/bootstrap_templates_options.rb create mode 100644 devops-client/lib/devops-client/options/category_options.rb create mode 100644 devops-client/lib/devops-client/options/chef_options.rb create mode 100644 devops-client/lib/devops-client/options/environment_options.rb delete mode 100644 devops-client/lib/devops-client/options/flavor_options.rb delete mode 100644 devops-client/lib/devops-client/options/group_options.rb delete mode 100644 devops-client/lib/devops-client/options/network_options.rb create mode 100644 devops-client/lib/devops-client/options/role_options.rb delete mode 100644 devops-client/lib/devops-client/options/tag_options.rb delete mode 100644 devops-client/lib/devops-client/output/bootstrap_templates.rb create mode 100644 devops-client/lib/devops-client/output/category.rb create mode 100644 devops-client/lib/devops-client/output/chef.rb create mode 100644 devops-client/lib/devops-client/output/environment.rb delete mode 100644 devops-client/lib/devops-client/output/flavors.rb delete mode 100644 devops-client/lib/devops-client/output/groups.rb delete mode 100644 devops-client/lib/devops-client/output/network.rb create mode 100644 devops-client/lib/devops-client/output/role.rb delete mode 100644 devops-client/lib/devops-client/output/tag.rb delete mode 100644 devops-service/app/api2/handlers/bootstrap_templates.rb delete mode 100644 devops-service/app/api2/handlers/filter.rb delete mode 100644 devops-service/app/api2/handlers/flavor.rb delete mode 100644 devops-service/app/api2/handlers/group.rb delete mode 100644 devops-service/app/api2/handlers/image.rb delete mode 100644 devops-service/app/api2/handlers/key.rb delete mode 100644 devops-service/app/api2/handlers/network.rb delete mode 100644 devops-service/app/api2/handlers/project.rb delete mode 100644 devops-service/app/api2/handlers/provider.rb delete mode 100644 devops-service/app/api2/handlers/report.rb delete mode 100644 devops-service/app/api2/handlers/request_handler.rb delete mode 100644 devops-service/app/api2/handlers/script.rb delete mode 100644 devops-service/app/api2/handlers/server.rb delete mode 100644 devops-service/app/api2/handlers/stack_template.rb delete mode 100644 devops-service/app/api2/handlers/user.rb delete mode 100644 devops-service/app/api2/helpers/request.rb delete mode 100644 devops-service/app/api2/parsers/deploy.rb delete mode 100644 devops-service/app/api2/parsers/network.rb delete mode 100644 devops-service/app/api2/parsers/user.rb delete mode 100644 devops-service/app/api2/routes/bootstrap_templates.rb delete mode 100644 devops-service/app/api2/routes/deploy.rb delete mode 100644 devops-service/app/api2/routes/filter.rb delete mode 100644 devops-service/app/api2/routes/flavor.rb delete mode 100644 devops-service/app/api2/routes/group.rb delete mode 100644 devops-service/app/api2/routes/image.rb delete mode 100644 devops-service/app/api2/routes/key.rb delete mode 100644 devops-service/app/api2/routes/network.rb delete mode 100644 devops-service/app/api2/routes/project.rb delete mode 100644 devops-service/app/api2/routes/provider.rb delete mode 100644 devops-service/app/api2/routes/report.rb delete mode 100644 devops-service/app/api2/routes/server.rb delete mode 100644 devops-service/app/api2/routes/stack_template.rb delete mode 100644 devops-service/app/api2/routes/statistic.rb delete mode 100644 devops-service/app/api2/routes/tag.rb delete mode 100644 devops-service/app/api2/routes/user.rb delete mode 100644 devops-service/app/api2/routes/v2.0.rb create mode 100644 devops-service/app/api3/docs/chef.rb create mode 100644 devops-service/app/api3/docs/devops_error.rb create mode 100644 devops-service/app/api3/docs/devops_response.rb create mode 100644 devops-service/app/api3/docs/filter.rb create mode 100644 devops-service/app/api3/docs/image.rb create mode 100644 devops-service/app/api3/docs/job_task.rb create mode 100644 devops-service/app/api3/docs/key.rb create mode 100644 devops-service/app/api3/docs/project.rb create mode 100644 devops-service/app/api3/docs/provider.rb create mode 100644 devops-service/app/api3/docs/role.rb create mode 100644 devops-service/app/api3/docs/server.rb create mode 100644 devops-service/app/api3/docs/server_model.rb create mode 100644 devops-service/app/api3/docs/user.rb create mode 100644 devops-service/app/api3/handlers/api-docs.rb rename devops-service/app/{api2/handlers/tag.rb => api3/handlers/chef.rb} (65%) rename devops-service/app/{api2 => api3}/handlers/deploy.rb (76%) create mode 100644 devops-service/app/api3/handlers/filter.rb create mode 100644 devops-service/app/api3/handlers/image.rb create mode 100644 devops-service/app/api3/handlers/job_task.rb create mode 100644 devops-service/app/api3/handlers/key.rb create mode 100644 devops-service/app/api3/handlers/project.rb create mode 100644 devops-service/app/api3/handlers/provider.rb rename devops-service/app/{api2 => api3}/handlers/provider_notification.rb (61%) create mode 100644 devops-service/app/api3/handlers/request_handler.rb create mode 100644 devops-service/app/api3/handlers/roles.rb create mode 100644 devops-service/app/api3/handlers/script.rb create mode 100644 devops-service/app/api3/handlers/server.rb rename devops-service/app/{api2 => api3}/handlers/stack.rb (76%) create mode 100644 devops-service/app/api3/handlers/stack_template.rb create mode 100644 devops-service/app/api3/handlers/statistic.rb create mode 100644 devops-service/app/api3/handlers/user.rb rename devops-service/app/{api2 => api3}/helpers/parser.rb (68%) rename devops-service/app/{api2/helpers/version_2.rb => api3/helpers/query.rb} (60%) rename devops-service/app/{api2/parsers/tag.rb => api3/parsers/chef.rb} (82%) rename devops-service/app/{api2/parsers/bootstrap_template.rb => api3/parsers/deploy.rb} (58%) rename devops-service/app/{api2 => api3}/parsers/filter.rb (95%) rename devops-service/app/{api2 => api3}/parsers/image.rb (87%) rename devops-service/app/{api2 => api3}/parsers/key.rb (97%) rename devops-service/app/{api2 => api3}/parsers/project.rb (61%) rename devops-service/app/{api2 => api3}/parsers/provider.rb (92%) rename devops-service/app/{api2 => api3}/parsers/request_parser.rb (79%) create mode 100644 devops-service/app/api3/parsers/roles.rb rename devops-service/app/{api2 => api3}/parsers/script.rb (77%) rename devops-service/app/{api2 => api3}/parsers/security_groups.rb (100%) rename devops-service/app/{api2 => api3}/parsers/server.rb (72%) rename devops-service/app/{api2 => api3}/parsers/stack.rb (63%) rename devops-service/app/{api2 => api3}/parsers/stack_template.rb (93%) create mode 100644 devops-service/app/api3/parsers/user.rb create mode 100644 devops-service/app/api3/public/swagger-ui/css/print.css create mode 100644 devops-service/app/api3/public/swagger-ui/css/reset.css create mode 100644 devops-service/app/api3/public/swagger-ui/css/screen.css create mode 100644 devops-service/app/api3/public/swagger-ui/css/style.css create mode 100644 devops-service/app/api3/public/swagger-ui/css/typography.css create mode 100644 devops-service/app/api3/public/swagger-ui/fonts/DroidSans-Bold.ttf create mode 100644 devops-service/app/api3/public/swagger-ui/fonts/DroidSans.ttf create mode 100644 devops-service/app/api3/public/swagger-ui/images/collapse.gif create mode 100644 devops-service/app/api3/public/swagger-ui/images/expand.gif create mode 100644 devops-service/app/api3/public/swagger-ui/images/explorer_icons.png create mode 100755 devops-service/app/api3/public/swagger-ui/images/favicon-16x16.png create mode 100755 devops-service/app/api3/public/swagger-ui/images/favicon-32x32.png create mode 100755 devops-service/app/api3/public/swagger-ui/images/favicon.ico create mode 100644 devops-service/app/api3/public/swagger-ui/images/logo_small.png create mode 100644 devops-service/app/api3/public/swagger-ui/images/pet_store_api.png create mode 100644 devops-service/app/api3/public/swagger-ui/images/throbber.gif create mode 100644 devops-service/app/api3/public/swagger-ui/images/wordnik_api.png create mode 100644 devops-service/app/api3/public/swagger-ui/lang/en.js create mode 100644 devops-service/app/api3/public/swagger-ui/lang/es.js create mode 100644 devops-service/app/api3/public/swagger-ui/lang/fr.js create mode 100644 devops-service/app/api3/public/swagger-ui/lang/it.js create mode 100755 devops-service/app/api3/public/swagger-ui/lang/ja.js create mode 100644 devops-service/app/api3/public/swagger-ui/lang/pl.js create mode 100644 devops-service/app/api3/public/swagger-ui/lang/pt.js create mode 100644 devops-service/app/api3/public/swagger-ui/lang/ru.js create mode 100644 devops-service/app/api3/public/swagger-ui/lang/tr.js create mode 100644 devops-service/app/api3/public/swagger-ui/lang/translator.js create mode 100644 devops-service/app/api3/public/swagger-ui/lang/zh-cn.js create mode 100644 devops-service/app/api3/public/swagger-ui/lib/backbone-min.js create mode 100644 devops-service/app/api3/public/swagger-ui/lib/handlebars-2.0.0.js create mode 100644 devops-service/app/api3/public/swagger-ui/lib/highlight.7.3.pack.js create mode 100644 devops-service/app/api3/public/swagger-ui/lib/jquery-1.8.0.min.js create mode 100644 devops-service/app/api3/public/swagger-ui/lib/jquery.ba-bbq.min.js create mode 100644 devops-service/app/api3/public/swagger-ui/lib/jquery.slideto.min.js create mode 100644 devops-service/app/api3/public/swagger-ui/lib/jquery.wiggle.min.js create mode 100644 devops-service/app/api3/public/swagger-ui/lib/jsoneditor.min.js create mode 100644 devops-service/app/api3/public/swagger-ui/lib/marked.js create mode 100644 devops-service/app/api3/public/swagger-ui/lib/swagger-oauth.js create mode 100644 devops-service/app/api3/public/swagger-ui/lib/underscore-min.js create mode 100644 devops-service/app/api3/public/swagger-ui/lib/underscore-min.map create mode 100644 devops-service/app/api3/public/swagger-ui/o2c.html create mode 100644 devops-service/app/api3/public/swagger-ui/swagger-ui.js create mode 100644 devops-service/app/api3/public/swagger-ui/swagger-ui.min.js create mode 100644 devops-service/app/api3/routes/api-docs.rb create mode 100644 devops-service/app/api3/routes/chef.rb create mode 100644 devops-service/app/api3/routes/deploy.rb create mode 100644 devops-service/app/api3/routes/filter.rb create mode 100644 devops-service/app/api3/routes/image.rb create mode 100644 devops-service/app/api3/routes/job_task.rb create mode 100644 devops-service/app/api3/routes/key.rb create mode 100644 devops-service/app/api3/routes/project.rb create mode 100644 devops-service/app/api3/routes/provider.rb rename devops-service/app/{api2 => api3}/routes/provider_notification.rb (63%) create mode 100644 devops-service/app/api3/routes/roles.rb rename devops-service/app/{api2 => api3}/routes/script.rb (66%) create mode 100644 devops-service/app/api3/routes/server.rb rename devops-service/app/{api2 => api3}/routes/stack.rb (53%) create mode 100644 devops-service/app/api3/routes/stack_template.rb create mode 100644 devops-service/app/api3/routes/statistic.rb create mode 100644 devops-service/app/api3/routes/user.rb create mode 100644 devops-service/app/api3/routes/v3.0.rb create mode 100644 devops-service/app/api3/views/index.html.erb rename devops-service/app/{api2/routes => api3}/views/report.erb (100%) delete mode 100644 devops-service/app/devops-api2.rb create mode 100644 devops-service/app/devops-api3.rb create mode 100644 devops-service/config-local.rb create mode 100644 devops-service/config/mongo.yml delete mode 100644 devops-service/db/mongo/connectors/filter.rb delete mode 100644 devops-service/db/mongo/connectors/image.rb delete mode 100644 devops-service/db/mongo/connectors/project.rb delete mode 100644 devops-service/db/mongo/connectors/provider_account.rb delete mode 100644 devops-service/db/mongo/connectors/report.rb delete mode 100644 devops-service/db/mongo/connectors/stack.rb delete mode 100644 devops-service/db/mongo/connectors/stack_template.rb delete mode 100644 devops-service/db/mongo/connectors/statictic.rb delete mode 100644 devops-service/db/mongo/connectors/user.rb delete mode 100644 devops-service/db/mongo/models/deploy_env/cloud_deploy_env.rb delete mode 100644 devops-service/db/mongo/models/deploy_env/deploy_env_base.rb delete mode 100644 devops-service/db/mongo/models/deploy_env/deploy_env_ec2.rb delete mode 100644 devops-service/db/mongo/models/deploy_env/deploy_env_factory.rb delete mode 100644 devops-service/db/mongo/models/deploy_env/deploy_env_multi.rb delete mode 100644 devops-service/db/mongo/models/deploy_env/deploy_env_openstack.rb delete mode 100644 devops-service/db/mongo/models/deploy_env/deploy_env_static.rb create mode 100644 devops-service/db/mongo/models/environment/category.rb create mode 100644 devops-service/db/mongo/models/environment/configuration_management/chef.rb create mode 100644 devops-service/db/mongo/models/environment/configuration_management/tool.rb create mode 100644 devops-service/db/mongo/models/environment/environment.rb create mode 100644 devops-service/db/mongo/models/environment/environments_array.rb create mode 100644 devops-service/db/mongo/models/filter.rb create mode 100644 devops-service/db/mongo/models/job_task.rb create mode 100644 devops-service/db/mongo/models/model_with_provider_account.rb delete mode 100644 devops-service/db/mongo/models/mongo_model.rb create mode 100644 devops-service/db/mongo/models/policy.rb delete mode 100644 devops-service/db/mongo/models/provider_accounts/ec2_provider_account.rb delete mode 100644 devops-service/db/mongo/models/provider_accounts/openstack_provider_account.rb delete mode 100644 devops-service/db/mongo/models/provider_accounts/provider_account.rb delete mode 100644 devops-service/db/mongo/models/provider_accounts/static_provider_account.rb delete mode 100644 devops-service/db/mongo/models/report.rb create mode 100644 devops-service/db/mongo/models/role.rb rename devops-service/db/mongo/models/stack/{stack_ec2.rb => stack_aws.rb} (72%) rename devops-service/db/mongo/models/stack_template/{stack_template_ec2.rb => stack_template_aws.rb} (77%) create mode 100644 devops-service/db/mongo/models/statistic.rb create mode 100644 devops-service/db/mongoid/created.rb create mode 100644 devops-service/db/mongoid/updated.rb create mode 100644 devops-service/db/validators/categories_array.rb delete mode 100644 devops-service/db/validators/deploy_env/run_list.rb delete mode 100644 devops-service/db/validators/field_validators/named_tasks.rb delete mode 100644 devops-service/db/validators/helpers/run_list.rb create mode 100644 devops-service/db/validators/name.rb create mode 100644 devops-service/db/validators/not_nil.rb create mode 100644 devops-service/db/validators/project/named_tasks.rb create mode 100644 devops-service/db/validators/run_list_array.rb create mode 100644 devops-service/db/validators/users_array.rb create mode 100644 devops-service/doc/chef.md create mode 100644 devops-service/doc/deploy.md create mode 100644 devops-service/doc/filters.md create mode 100644 devops-service/doc/images.md create mode 100644 devops-service/doc/keys.md create mode 100644 devops-service/doc/projects.md create mode 100644 devops-service/doc/provider.md create mode 100644 devops-service/doc/reports.md create mode 100644 devops-service/doc/roles.md create mode 100644 devops-service/doc/server.md create mode 100644 devops-service/doc/users.md create mode 100644 devops-service/exceptions/access_error.rb create mode 100644 devops-service/exceptions/bootstrap_error.rb create mode 100644 devops-service/exceptions/configuration_error.rb create mode 100644 devops-service/exceptions/creation_error.rb create mode 100644 devops-service/exceptions/deploy_error.rb create mode 100644 devops-service/exceptions/server_operation_error.rb delete mode 100644 devops-service/features/api_v2/00_list/filter.feature delete mode 100644 devops-service/features/api_v2/00_list/flavor.feature delete mode 100644 devops-service/features/api_v2/00_list/group.feature delete mode 100644 devops-service/features/api_v2/00_list/network.feature delete mode 100644 devops-service/features/api_v2/00_list/project.feature delete mode 100644 devops-service/features/api_v2/00_list/provider.feature delete mode 100644 devops-service/features/api_v2/00_list/script.feature delete mode 100644 devops-service/features/api_v2/00_list/templates.feature delete mode 100644 devops-service/features/api_v2/10_create/00_filter.feature delete mode 100644 devops-service/features/api_v2/10_create/10_image.feature delete mode 100644 devops-service/features/api_v2/10_create/20_project.feature delete mode 100644 devops-service/features/api_v2/10_create/20_script.feature delete mode 100644 devops-service/features/api_v2/20_update/10_image.feature delete mode 100644 devops-service/features/api_v2/30_execute/20_script.feature delete mode 100644 devops-service/features/api_v2/90_delete/10_script.feature delete mode 100644 devops-service/features/api_v2/90_delete/80_project.feature delete mode 100644 devops-service/features/api_v2/90_delete/90_image.feature delete mode 100644 devops-service/features/api_v2/90_delete/99_filter.feature delete mode 100644 devops-service/features/api_v2/server.feature rename devops-service/lib/executors/stack_executor/{group_bootstrapper.rb => servers_bootstrapper.rb} (54%) create mode 100644 devops-service/lib/ssh/ssh_utils.rb create mode 100644 devops-service/migrations/01252016_rename_ec2_provider_to_aws.js create mode 100644 devops-service/migrations/01252016_update_created_at_fields.js delete mode 100644 devops-service/migrations/20_04_2016_add_launch_options_to_stacks.js create mode 100644 devops-service/migrations/convert_deploy_envs_devops_3.js create mode 100644 devops-service/migrations/delete_bootstrap_template_from_images.js rename devops-service/providers/{base_provider.rb => abstract_provider_connector.rb} (67%) delete mode 100644 devops-service/providers/accounts_factory.rb create mode 100644 devops-service/providers/aws.rb rename devops-service/providers/{ec2.rb => aws/aws_connector.rb} (76%) create mode 100644 devops-service/providers/aws/aws_provider_account.rb create mode 100644 devops-service/providers/aws/server_category_provider_aws.rb create mode 100644 devops-service/providers/aws/stack_category_provider_aws.rb create mode 100644 devops-service/providers/category_cloud_provider.rb create mode 100644 devops-service/providers/category_provider.rb delete mode 100644 devops-service/providers/ec2_accounts_factory.rb rename devops-service/providers/{ => openstack}/openstack.rb (90%) create mode 100644 devops-service/providers/openstack/openstack_accounts_factory.rb create mode 100644 devops-service/providers/openstack/openstack_provider_account.rb create mode 100644 devops-service/providers/openstack/provider_openstack.rb delete mode 100644 devops-service/providers/openstack_accounts_factory.rb create mode 100644 devops-service/providers/provider.rb create mode 100644 devops-service/providers/provider_account.rb delete mode 100644 devops-service/providers/provider_factory.rb create mode 100644 devops-service/providers/static/category_provider_static.rb create mode 100644 devops-service/providers/static/static_connector.rb create mode 100644 devops-service/providers/static/static_provider_account.rb delete mode 100644 devops-service/providers/static_accounts_factory.rb delete mode 100644 devops-service/spec/connectors/filter_connector_spec.rb delete mode 100644 devops-service/spec/connectors/image_connector_spec.rb delete mode 100644 devops-service/spec/connectors/key_connector_spec.rb delete mode 100644 devops-service/spec/connectors/project_connector_spec.rb delete mode 100644 devops-service/spec/connectors/provider_accounts_connector_spec.rb delete mode 100644 devops-service/spec/connectors/shared_connectors_specs.rb delete mode 100644 devops-service/spec/connectors/stack_template_connector_spec.rb delete mode 100644 devops-service/spec/connectors/tester_connector/base.rb delete mode 100644 devops-service/spec/connectors/tester_connector/filter.rb delete mode 100644 devops-service/spec/connectors/tester_connector/image.rb delete mode 100644 devops-service/spec/connectors/tester_connector/key.rb delete mode 100644 devops-service/spec/connectors/tester_connector/project.rb delete mode 100644 devops-service/spec/connectors/tester_connector/provider_account.rb delete mode 100644 devops-service/spec/connectors/tester_connector/stack_template.rb delete mode 100644 devops-service/spec/executors/stack_executor/group_bootstrapper_spec.rb create mode 100644 devops-service/spec/executors/stack_executor/servers_bootstrapper_spec.rb create mode 100644 devops-service/spec/factories/category.rb delete mode 100644 devops-service/spec/factories/deploy_env.rb create mode 100644 devops-service/spec/factories/environment.rb delete mode 100644 devops-service/spec/factories/report.rb create mode 100644 devops-service/spec/factories/task.rb delete mode 100644 devops-service/spec/models/deploy_env/deploy_env_ec2_spec.rb delete mode 100644 devops-service/spec/models/deploy_env/deploy_env_openstack_spec.rb delete mode 100644 devops-service/spec/models/deploy_env/deploy_env_static_spec.rb delete mode 100644 devops-service/spec/models/deploy_env/shared_cloud_deploy_env_specs.rb rename devops-service/spec/models/{deploy_env/shared_deploy_env_specs.rb => environment_spec.rb} (54%) create mode 100644 devops-service/spec/models/job_task_spec.rb create mode 100644 devops-service/spec/models/provider_account/aws_provider_account_spec.rb delete mode 100644 devops-service/spec/models/provider_account/ec2_provider_account_spec.rb delete mode 100644 devops-service/spec/models/provider_account/openstack_provider_account_spec.rb delete mode 100644 devops-service/spec/models/report_spec.rb rename devops-service/spec/models/stack/{stack_ec2_spec.rb => stack_aws_spec.rb} (56%) rename devops-service/spec/models/stack_template/{stack_template_ec2_spec.rb => stack_template_aws_spec.rb} (72%) create mode 100644 devops-service/spec/shared_contexts/clean_db.rb delete mode 100644 devops-service/spec/shared_contexts/stubbed_connector.rb delete mode 100644 devops-service/spec/shared_contexts/with_tester_connector.rb delete mode 100644 devops-service/spec/workers/stack_sync_worker_spec.rb create mode 100644 devops-service/tests/features/step_definitions/variables_steps.rb create mode 100644 devops-service/tests/features/support/hooks.rb create mode 100644 devops-service/tests/params-dev.yml delete mode 100644 devops-service/tests/templates/api_v2/00_list/00_network.feature.erb delete mode 100644 devops-service/tests/templates/api_v2/00_list/10_bootstrap_template.feature.erb delete mode 100644 devops-service/tests/templates/api_v2/00_list/10_filter.feature.erb delete mode 100644 devops-service/tests/templates/api_v2/00_list/10_group.feature.erb delete mode 100644 devops-service/tests/templates/api_v2/00_list/10_user.feature.erb delete mode 100644 devops-service/tests/templates/api_v2/00_list/20_image.feature.erb delete mode 100644 devops-service/tests/templates/api_v2/00_list/30_project.feature.erb delete mode 100644 devops-service/tests/templates/api_v2/00_list/flavor.feature.erb delete mode 100644 devops-service/tests/templates/api_v2/10_create/00_filter.feature.erb delete mode 100644 devops-service/tests/templates/api_v2/10_create/00_key.feature.erb delete mode 100644 devops-service/tests/templates/api_v2/10_create/00_user.feature.erb delete mode 100644 devops-service/tests/templates/api_v2/10_create/10_image.feature.erb delete mode 100644 devops-service/tests/templates/api_v2/10_create/20_project.feature.erb delete mode 100644 devops-service/tests/templates/api_v2/10_create/21_deploy_env.feature.erb delete mode 100644 devops-service/tests/templates/api_v2/90_delete/80_project.feature.erb delete mode 100644 devops-service/tests/templates/api_v2/90_delete/90_image.feature.erb delete mode 100644 devops-service/tests/templates/api_v2/90_delete/99_key.feature.erb create mode 100644 devops-service/tests/templates/api_v3/00_prepare/00_create_role.feature.erb create mode 100644 devops-service/tests/templates/api_v3/00_prepare/10_create_user.feature.erb create mode 100644 devops-service/tests/templates/api_v3/01_list/00_chef.feature.erb create mode 100644 devops-service/tests/templates/api_v3/01_list/10_filter.feature.erb rename devops-service/tests/templates/{api_v2/00_list/20_key.feature.erb => api_v3/01_list/10_key.feature.erb} (52%) create mode 100644 devops-service/tests/templates/api_v3/01_list/10_user.feature.erb create mode 100644 devops-service/tests/templates/api_v3/01_list/11_provider.feature.erb create mode 100644 devops-service/tests/templates/api_v3/01_list/20_image.feature.erb create mode 100644 devops-service/tests/templates/api_v3/01_list/30_project.feature.erb create mode 100644 devops-service/tests/templates/api_v3/10_create/00_filter.feature.erb create mode 100644 devops-service/tests/templates/api_v3/10_create/00_key.feature.erb rename devops-service/tests/templates/{api_v2 => api_v3}/10_create/00_stack_template.feature.erb (100%) create mode 100644 devops-service/tests/templates/api_v3/10_create/01_provider_account.feature.erb create mode 100644 devops-service/tests/templates/api_v3/10_create/10_image.feature.erb create mode 100644 devops-service/tests/templates/api_v3/10_create/20_project.feature.erb create mode 100644 devops-service/tests/templates/api_v3/10_create/21_deploy_env.feature.erb rename devops-service/tests/templates/{api_v2 => api_v3}/10_create/30_script.feature.erb (100%) rename devops-service/tests/templates/{api_v2 => api_v3}/10_create/40_deploy_env.feature.erb (100%) create mode 100644 devops-service/tests/templates/api_v3/10_create/40_server.feature.erb rename devops-service/tests/templates/{api_v2 => api_v3}/10_create/50_stack.feature.erb (100%) rename devops-service/tests/templates/{api_v2 => api_v3}/20_update/00_user.feature.erb (100%) rename devops-service/tests/templates/{api_v2 => api_v3}/20_update/10_image.feature.erb (56%) create mode 100644 devops-service/tests/templates/api_v3/20_update/20_project.feature.erb rename devops-service/tests/templates/{api_v2 => api_v3}/90_delete/00_stack_template.feature.erb (100%) rename devops-service/tests/templates/{api_v2 => api_v3}/90_delete/10_script.feature.erb (100%) rename devops-service/tests/templates/{api_v2 => api_v3}/90_delete/10_stack.feature.erb (100%) rename devops-service/tests/templates/{api_v2 => api_v3}/90_delete/20_deploy_env.feature.erb (90%) create mode 100644 devops-service/tests/templates/api_v3/90_delete/60_server.feature.erb create mode 100644 devops-service/tests/templates/api_v3/90_delete/80_project.feature.erb create mode 100644 devops-service/tests/templates/api_v3/90_delete/90_image.feature.erb rename devops-service/tests/templates/{api_v2 => api_v3}/90_delete/90_user.feature.erb (100%) rename devops-service/tests/templates/{api_v2/90_delete/99_filter.feature.erb => api_v3/90_delete/98_filter.feature.erb} (52%) create mode 100644 devops-service/tests/templates/api_v3/90_delete/98_key.feature.erb create mode 100644 devops-service/tests/templates/api_v3/90_delete/99_finalize.feature.erb create mode 100644 devops-service/tests/templates/fixtures/aws.yml delete mode 100644 devops-service/tests/templates/fixtures/deploy_env.yml delete mode 100644 devops-service/tests/templates/fixtures/ec2.yml create mode 100644 devops-service/tests/templates/fixtures/role.yml create mode 100644 devops-service/workers/unbootstrap_worker.rb delete mode 100644 devops-service/workers/wait_rebootstrap_worker.rb diff --git a/devops-client/lib/devops-client.rb b/devops-client/lib/devops-client.rb index 9c581eb..dc13200 100644 --- a/devops-client/lib/devops-client.rb +++ b/devops-client/lib/devops-client.rb @@ -12,14 +12,12 @@ module DevopsClient DEVOPS_HOME = "#{ENV["HOME"]}/.devops/" # properties file key=value @@config_file = File.join(DEVOPS_HOME, "devops-client.conf") - #CONFIG_FILE="#{ENV["HOME"]}/.devops/devops-client.conf" def self.config_file @@config_file end def self.run - DevopsClient::get_config_file_option config = DevopsClient::read_config(@@config_file) @@ -55,7 +53,7 @@ module DevopsClient puts e.message exit(11) rescue NotFound => e - puts "Not found: #{e.message}" + puts e.message exit(12) rescue InvalidQuery => e puts "Invalid query: #{e.message}" @@ -109,7 +107,7 @@ module DevopsClient def self.set_default_config file locales = I18n.locales - config = {:api => "v2.0", :locale => "en"} + config = {:api => "v3", :locale => "en"} I18n.language = config[:locale] puts I18n.t("log.warn", :msg => I18n.t("config.not_exist", :file => file)) config[:locale] = begin diff --git a/devops-client/lib/devops-client/handler/bootstrap_templates.rb b/devops-client/lib/devops-client/handler/bootstrap_templates.rb deleted file mode 100644 index 65a4df5..0000000 --- a/devops-client/lib/devops-client/handler/bootstrap_templates.rb +++ /dev/null @@ -1,29 +0,0 @@ -require "devops-client/handler/handler" -require "devops-client/options/bootstrap_templates_options" -require "json" -require "devops-client/output/bootstrap_templates" - -class BootstrapTemplates < Handler - - output_with Output::BootstrapTemplates - - def initialize(host, def_options={}) - @host, @options = host, def_options - @options_parser = BootstrapTemplatesOptions.new(ARGV, def_options) - end - - def handle - case ARGV[1] - when "list" - @options, @args = @options_parser.parse_options_for!(:list) - list_handler - output - end - end - - def list_handler - @list = get("/templates") - end - -end - diff --git a/devops-client/lib/devops-client/handler/category.rb b/devops-client/lib/devops-client/handler/category.rb new file mode 100644 index 0000000..5ed390e --- /dev/null +++ b/devops-client/lib/devops-client/handler/category.rb @@ -0,0 +1,232 @@ +require "devops-client/handler/handler" +require "devops-client/handler/helpers/validators" +require "devops-client/options/category_options" +require "set" +require "devops-client/output/category" + +class Category < Handler + + output_with Output::Category + + def initialize(host, def_options={}) + @host = host + @options = def_options + end + + def handle + @options_parser = CategoryOptions.new(ARGV, @options) + @command, @options, @args = @options_parser.parse_options_for! + extract_parameters + case @command + when :create + create_handler + when :delete + delete_handler + when :deploy + deploy_handler + when :list + list_handler + output(project: @project_id, env: @environment) + when :servers_list + servers_handler + output(project: @project_id, env: @environment) + when :stacks + stacks_handler + output + when :set_run_list + set_run_list_handler + when :show + show_handler + output(project: @project_id, env: @environment, cat: @category) + when :update + update_handler + when :servers_delete + delete_servers_handler + end + end + + def list_handler + @list = get "/project/#{@project_id}/environment/#{@environment}/categories" + end + + def delete_handler + delete "/project/#{@project_id}/environment/#{@environment}/category/#{@category}" if question(I18n.t("handler.category.question.delete", env: @environment, cat: @category, project: @project_id)) + end + + def show_handler + @show = get("/project/#{@project_id}/environment/#{@environment}/category/#{@category}") + end + + def update_handler args + r = inspect_parameters @options_parser.update_params, args[2], args[3] + unless r.nil? + @options_parser.invalid_update_command + abort(r) + end + update_object_from_file "project", args[2], args[3] + end + + def create_handler + file = self.options[:file] + json = nil + unless file.nil? + json = File.read(file) + begin + JSON.parse(json) + rescue JSON::ParserError => e + abort(I18n.t("handler.category.create.invalid_json", :file => file)) + end + else + cat = create_category + json = JSON.pretty_generate(cat) + end + post_body("/project/#{@project_id}/environment/#{@environment}/category", json) if question(I18n.t("handler.category.question.create")){puts json} + end + + def servers_handler + @list = get "/project/#{@project_id}/environment/#{@environment}/servers" + end + + def stacks_handler + @list = get "/project/#{@project_id}/environment/#{@environment}/stacks" + end + + def set_run_list_handler + run_list = @run_list || [] + if run_list.empty? + exit unless question(I18n.t("handler.project.question.run_list.empty")) + else + exit unless Validators.validate_run_list(run_list) + end + + post "/project/#{@project_id}/environment/#{@environment}/run_list", run_list + end + + def deploy_handler args + q = {} + q[:servers] = options[:servers] unless options[:servers].nil? + job_ids = post "/project/#{args[2]}/deploy", q + reports_urls(job_ids) + end + + def test_handler args + r = inspect_parameters @options_parser.test_params, args[2], args[3] + unless r.nil? + @options_parser.invalid_test_command + abort(r) + end + job_ids = post "/project/test/#{args[2]}/#{args[3]}" + reports_urls(job_ids) + end + +protected + def get_project_info_obj project_id + get("/project/#{project_id}") + end + + def create_category + cat = {id: @category, provider: {}, cm_tool: {}} + + case (self.options[:provider] || resources_selector.select_available_provider) + when 'static' + create_static_category_provider(cat[:provider]) + when 'aws' + create_aws_category_provider(cat[:provider]) + end + fill_configuration_manager_tool(cat[:cm_tool]) + cat + end + + def create_static_category_provider cat_provider + fill_category_provider("static", cat_provider) + end + + def create_aws_category_provider cat_provider + fill_category_provider("aws", cat_provider) + cat_provider[:type] = choose_item_from_list(I18n.t('headers.category_type'), %w(server stack)) + if cat_provider[:type] == 'server' + fill_category_cloud_provider cat_provider do + cat_provider[:vpc_id] = self.options[:vpc_id] || resources_selector.select_available_vpc(provider: cat_provider[:name], account: cat_provider[:account]) + cat_provider[:subnet] = self.options[:subnet] || resources_selector.select_available_network(provider: cat_provider[:name], account: cat_provider[:account], vpc_id: cat_provider[:vpc_id]) + end + else + cat_provider[:stack_template] = self.options[:stack_template] || resources_selector.select_available_stack_template(provider: 'aws') + end + end + + def fill_category_provider provider_name, cat_provider + cat_provider[:name] = provider_name + cat_provider[:account] = self.options[:account] || resources_selector.select_available_provider_account(cat_provider[:name])["account_name"] + end + + def fill_category_cloud_provider cat_provider + cat_provider[:flavor] = self.options[:flavor] || resources_selector.select_available_flavor(provider: cat_provider[:name], account: cat_provider[:account]) + cat_provider[:image] = self.options[:image] || resources_selector.select_available_image(provider: cat_provider[:name], account: cat_provider[:account]) + yield + cat_provider[:security_groups] = self.options[:security_groups] || resources_selector.select_available_security_groups(provider: cat_provider[:name], account: cat_provider[:account], vpc_id: cat_provider[:vpc_id]) +#TODO cat_provider[:stack_template] = self.options[:stack_template] || resources_selector.select_available_stack_template(provider: cat_provider[:name], account: cat_provider[:account]) + end + + def set_identifier d, names + set_parameter d, :id do + begin + n = enter_parameter I18n.t("handler.environment.create.id") + ": " + if names.include?(n) + puts I18n.t("handler.environment.create.id_exists", :env => n) + raise ArgumentError + else + names.push n + n + end + rescue ArgumentError + retry + end + end + end + + def set_parameter obj, key + if self.options[key].nil? + obj[key] = yield + else + obj[key] = self.options[key] + end + end + + def data_to_output + @list || @show || @test + end + + def delete_servers_handler + ask_for_delete_servers(@project_id, @environment) + body = { + dry_run: false + } + response = post("/project/#{@project_id}/environment/#{@environment}/servers/delete", body) + reports_urls(response['reports']) + end + + def fill_configuration_manager_tool tool + tool[:name] = "chef" + tool[:bootstrap_template] = self.options[:bootstrap_template] || resources_selector.select_available_bootstrap_template + tool[:chef_env] = enter_parameter(I18n.t('handler.category.create.chef_env')) + end + private + + def ask_for_delete_servers(project, env) + body = { + dry_run: true + } + to_delete = post("/project/#{project}/environment/#{env}/servers/delete", body) + if to_delete.empty? + abort "There are no servers to delete." + else + puts "Servers to delete:\n----\n" + puts to_delete.join("\n") + puts '----' + end + if @options[:dry_run] || !question('Are you sure to delete them') + abort + end + end + +end diff --git a/devops-client/lib/devops-client/handler/chef.rb b/devops-client/lib/devops-client/handler/chef.rb new file mode 100644 index 0000000..49db406 --- /dev/null +++ b/devops-client/lib/devops-client/handler/chef.rb @@ -0,0 +1,36 @@ +require "devops-client/handler/handler" +require "devops-client/options/chef_options" +require "json" +require "devops-client/output/chef" + +class Chef < Handler + + output_with Output::Chef + + def initialize(host, def_options={}) + @host, @options = host, def_options + @options_parser = ChefOptions.new(ARGV, def_options) + end + + def handle + @command, @options, @args = @options_parser.parse_options_for! + extract_parameters + case @command + when :bootstrap_templates + bootstrap_templates_handler + when :tags_list + tags_list_handler + end + output + end + + def bootstrap_templates_handler + @list = get("/chef/bootstrap_templates") + end + + def tags_list_handler + @list = get("/chef/tags/#{@cm_name}") + end + +end + diff --git a/devops-client/lib/devops-client/handler/deploy_envs/deploy_env.rb b/devops-client/lib/devops-client/handler/deploy_envs/deploy_env.rb index 7994799..ebbcbc6 100644 --- a/devops-client/lib/devops-client/handler/deploy_envs/deploy_env.rb +++ b/devops-client/lib/devops-client/handler/deploy_envs/deploy_env.rb @@ -71,14 +71,6 @@ class DeployEnv d[:users] = d[:users].to_a end - def self.validate_run_list run_list - return true if run_list.empty? - rl = /\Arole|recipe\[[\w-]+(::[\w-]+)?\]\Z/ - e = run_list.select {|l| (rl =~ l).nil?} - res = e.empty? - puts I18n.t("handler.project.create.run_list.invalid", :list => e.join(", ")) unless res - res - end private diff --git a/devops-client/lib/devops-client/handler/environment.rb b/devops-client/lib/devops-client/handler/environment.rb new file mode 100644 index 0000000..a5f86a4 --- /dev/null +++ b/devops-client/lib/devops-client/handler/environment.rb @@ -0,0 +1,239 @@ +require "devops-client/handler/handler" +require "devops-client/handler/helpers/validators" +require "devops-client/options/environment_options" +require "set" +require "devops-client/output/environment" + +class Environment < Handler + + output_with Output::Environment + + def initialize(host, def_options={}) + @host = host + @options = def_options + @options_parser = EnvironmentOptions.new(ARGV, def_options) + end + + def handle + @command, @options, @args = @options_parser.parse_options_for! + extract_parameters + case @command + when :create + create_handler + when :delete + delete_handler + when :deploy + deploy_handler + when :list + list_handler + output(project: @project_id) + when :servers_list + servers_handler + output + when :stacks + stacks_handler + output + when :set_run_list + set_run_list_handler + when :show + show_handler + output(project: @project_id, env: @environment) + when :update + update_handler + when :user_add + user_add_handler + when :user_delete + user_delete_handler + when :servers_delete + delete_servers_handler + end + end + + def list_handler + @list = get "/project/#{@project_id}/environments" + end + + def delete_handler + if question(I18n.t("handler.project.question.delete", name: @environment)) + delete "/project/#{@project_id}/environment/#{@environment}" + end + end + + def show_handler + @show = get("/project/#{@project_id}/environment/#{@environment}") + end + + def update_handler args + update_object_from_file "project", args[2], args[3] + end + + def create_handler + file = self.options[:file] + json = nil + unless file.nil? + json = File.read(file) + begin + JSON.parse(json) + rescue JSON::ParserError => e + abort(I18n.t("handler.environment.create.invalid_json", :file => file)) + end + else + json = JSON.pretty_generate(create_environment) + end + post_body("/project/#{@project_id}/environment", json) if question(I18n.t("handler.environment.question.create")){puts json} + end + + def servers_handler + @list = get "/project/#{@project_id}/environment/#{@environment}/servers" + end + + def stacks_handler + @list = get "/project/#{@project_id}/environment/#{@environment}/stacks" + end + + def user_add_handler + project = get_project_info_obj(@project_id) + users = environment_users(project) + post "/project/#{@project_id}/environment/#{@environment}/users/add", users + end + + def user_delete_handler + post "/project/#{@project_id}/environment/#{@environment}/users/delete", @users + end + + def set_run_list_handler + run_list = @run_list || [] + if run_list.empty? + exit unless question(I18n.t("handler.project.question.run_list.empty")) + else + exit unless Validators.validate_run_list(run_list) + end + + post "/project/#{@project_id}/environment/#{@environment}/run_list", run_list + end + + def deploy_handler args + r = inspect_parameters @options_parser.deploy_params, args[2], args[3] + unless r.nil? + @options_parser.invalid_deploy_command + abort(r) + end + q = {} + q[:servers] = options[:servers] unless options[:servers].nil? + q[:deploy_env] = args[3] unless args[3].nil? + job_ids = post "/project/#{args[2]}/deploy", q + reports_urls(job_ids) + end + + def test_handler args + r = inspect_parameters @options_parser.test_params, args[2], args[3] + unless r.nil? + @options_parser.invalid_test_command + abort(r) + end + job_ids = post "/project/test/#{args[2]}/#{args[3]}" + reports_urls(job_ids) + end + +protected + def get_project_info_obj project_id + get("/project/#{project_id}") + end + + def create_environment + project = get_project_info_obj(@project_id) + names = project["environments"].map{|env| env["id"]} + abort(I18n.t("handler.environment.create.id_exists")) if names.include?(@environment) + e = {categories: [], id: @environment} +# set_identifier(e, names) + + e[:run_list] = if self.options[:run_list] + abort("Invalid run list: '#{self.options[:run_list].join(",")}'") unless Validators.validate_run_list(self.options[:run_list]) + self.options[:run_list] + else + input_run_list + end + + e[:users] = environment_users(project) + + unless self.options[:no_expires] + set_parameter e, :expires do + s = enter_parameter_or_empty(I18n.t("options.project.create.expires") + ": ").strip + s.empty? ? nil : s + end + end + e + end + + def environment_users project + if self.options[:users] + self.options[:users] + else + if question(I18n.t("handler.project.create.question.users_choose")) + #choose + resources_selector.select_available_users(label: I18n.t("handler.project.create.users"), data: project["project_users"] + [project["owner"]]) + else + #enter + get_comma_separated_list(I18n.t("input.message.input_list", cmd: I18n.t("input.user.list"))) + end + end + end + + def set_identifier d, names + set_parameter d, :id do + begin + n = enter_parameter I18n.t("handler.environment.create.id") + ": " + if names.include?(n) + puts I18n.t("handler.environment.create.id_exists", :env => n) + raise ArgumentError + else + names.push n + n + end + rescue ArgumentError + retry + end + end + end + + def set_parameter obj, key + if self.options[key].nil? + obj[key] = yield + else + obj[key] = self.options[key] + end + end + + def data_to_output + @list || @show || @test + end + + def delete_servers_handler + ask_for_delete_servers(@project_id, @environment) + body = { + dry_run: false + } + response = post("/project/#{@project_id}/environment/#{@environment}/servers/delete", body) + reports_urls(response['reports']) + end + + private + + def ask_for_delete_servers(project, env) + body = { + dry_run: true + } + to_delete = post("/project/#{project}/environment/#{env}/servers/delete", body) + if to_delete.empty? + abort "There are no servers to delete." + else + puts "Servers to delete:\n----\n" + puts to_delete.join("\n") + puts '----' + end + if @options[:dry_run] || !question('Are you sure to delete them') + abort + end + end + +end diff --git a/devops-client/lib/devops-client/handler/filter.rb b/devops-client/lib/devops-client/handler/filter.rb index 45a5fbc..1a2010f 100644 --- a/devops-client/lib/devops-client/handler/filter.rb +++ b/devops-client/lib/devops-client/handler/filter.rb @@ -8,57 +8,29 @@ class Filter < Handler output_with Output::Filters def initialize(host, def_options) - self.host = host + @host, @options = host, def_options @options_parser = FilterOptions.new(ARGV, def_options) end def handle - case ARGV[1] - when "image" - provider = ARGV[3] - case ARGV[2] - when "list" - self.options = @options_parser.image_list_options - check_provider provider - @list = get("/filter/#{provider}/images") + @command, @options, @args = @options_parser.parse_options_for! + extract_parameters + case @command + when :image_list + @list = get("/filter/#{@provider}/images") output - when "add" - self.options = @options_parser.image_add_options - check_provider provider - @list = put_body("/filter/#{provider}/image", get_images(ARGV).to_json) + when :image_add + @list = post_body("/filter/#{@provider}/images/add", @image.to_json) @list = @list["images"] unless @list.nil? output - when "delete" - self.options = @options_parser.image_delete_options - check_provider provider - images = get_images(ARGV) - if question(I18n.t("handler.filter.question.delete", :name => images.join("', '"))) - @list = delete_body("/filter/#{provider}/image", images.to_json) + when :image_delete + if question(I18n.t("handler.filter.question.delete", :name => @image.join("', '"))) + @list = post_body("/filter/#{@provider}/images/delete", @image.to_json) @list = @list["images"] unless @list.nil? output end - else - @options_parser.invalid_image_command - abort("Invalid image parameter: #{ARGV[2]}, it should be 'add' or 'delete' or 'list'") - end end end - def check_provider provider - if provider != "ec2" and provider != "openstack" - @options_parser.invalid_image_command - abort("Invalid image parameter: provider '#{provider}', it should be 'ec2' or 'openstack'") - end - end - - def get_images args - images = args[4..-1] - if images.empty? - @options_parser.invalid_image_command - abort("Images list is empty") - end - images - end - end diff --git a/devops-client/lib/devops-client/handler/flavor.rb b/devops-client/lib/devops-client/handler/flavor.rb deleted file mode 100644 index a39d0e4..0000000 --- a/devops-client/lib/devops-client/handler/flavor.rb +++ /dev/null @@ -1,34 +0,0 @@ -require "devops-client/handler/handler" -require "devops-client/options/flavor_options" -require "json" -require "devops-client/output/flavors" - -class Flavor < Handler - - output_with Output::Flavors - - def initialize(host, def_options={}) - @host, @options = host, def_options - @options_parser = FlavorOptions.new(ARGV, def_options) - end - - def handle - case ARGV[1] - when "list" - @options, @args = @options_parser.parse_options_for!(:list) - list_handler - output - end - end - - def list_handler(provider=nil) - provider ||= @args[2] - r = inspect_parameters @options_parser.list_params, provider - unless r.nil? - @options_parser.invalid_list_command - abort(r) - end - @list = get("/flavors/#{provider}").sort!{|x,y| x["id"] <=> y["id"]} - end - -end diff --git a/devops-client/lib/devops-client/handler/group.rb b/devops-client/lib/devops-client/handler/group.rb deleted file mode 100644 index 87c87ff..0000000 --- a/devops-client/lib/devops-client/handler/group.rb +++ /dev/null @@ -1,35 +0,0 @@ -require "devops-client/handler/handler" -require "devops-client/options/group_options" -require "json" -require "devops-client/output/groups" - -class Group < Handler - - output_with Output::Groups - - def initialize(host, def_options={}) - @host, @options = host, def_options - @options_parser = GroupOptions.new(ARGV, def_options) - end - - def handle - case ARGV[1] - when "list" - @options, @args = @options_parser.parse_options_for!(:list) - list_handler(@args[2], @args[3]) - output - end - end - - def list_handler(provider=nil, vpc_id=nil) - r = inspect_parameters @options_parser.list_params, provider - unless r.nil? - @options_parser.invalid_list_command - abort(r) - end - p = {} - p["vpc-id"] = vpc_id unless vpc_id.nil? - @list = get("/groups/#{provider}", p) - end - -end diff --git a/devops-client/lib/devops-client/handler/handler.rb b/devops-client/lib/devops-client/handler/handler.rb index cfd75d3..adaca3d 100644 --- a/devops-client/lib/devops-client/handler/handler.rb +++ b/devops-client/lib/devops-client/handler/handler.rb @@ -23,7 +23,7 @@ class Handler attr_reader :options attr_writer :host - attr_accessor :auth + attr_accessor :auth, :command def host if @host.start_with?('http') @@ -35,11 +35,11 @@ class Handler #TODO: only basic auth now def username - self.options[:username] || self.auth[:username] + self.auth[:username] end def password - self.options[:password] || self.auth[:password] + self.auth[:password] end def options= o @@ -57,13 +57,36 @@ protected @fetcher ||= Helpers::ResourcesFetcher.new(host: @host, handler_options: @options, auth: @auth) end - - def inspect_parameters names, *args + # Parameters + # PARAM - mandatory parameter, will be available by variable @param + # [PARAM] - not mandatory parameter, will be available by variable @param (can be nil) + # PARAM... - mandatory parameters, will be available by variable @param, it is array with one or more elements + # [PARAM...] - not mandatory parameters, will be available by variable @param, it is array with one or more elements or nil + def extract_parameters + cmd = @command + names = @options_parser.send("#{cmd}_params") + return unless names names.each_with_index do |name, i| - next if name.start_with? "[" and name.end_with? "]" - if args[i].nil? or args[i].empty? - return "\n" + I18n.t("handler.error.parameter.undefined", :name => name) + if name.start_with? "[" and name.end_with? "]" + param_name = name.delete("[]") + if param_name.end_with? "..." + param_name = param_name.delete(".") + instance_variable_set "@" + param_name.downcase, @args[i..-1] + return + end + elsif name.end_with? "..." + param_name = name.delete(".") + instance_variable_set "@" + param_name.downcase, @args[i..-1] + return + else + if @args[i].nil? or @args[i].empty? + @options_parser.send("invalid_#{cmd}_command") + res = "\n" + I18n.t("handler.error.parameter.undefined", :name => name) + abort(res) + end + param_name = name end + instance_variable_set "@" + param_name.downcase, @args[i] end nil end @@ -78,8 +101,4 @@ protected end end - def current_command - ARGV[1].to_sym if ARGV[1] - end - end diff --git a/devops-client/lib/devops-client/handler/handler_factory.rb b/devops-client/lib/devops-client/handler/handler_factory.rb index 03ac0a4..572c31f 100644 --- a/devops-client/lib/devops-client/handler/handler_factory.rb +++ b/devops-client/lib/devops-client/handler/handler_factory.rb @@ -1,57 +1,55 @@ +require "devops-client/options/main" + class HandlerFactory def self.create cmd, host, auth, def_options klass = case cmd - when "flavor" - require "devops-client/handler/flavor" - Flavor - when "image" + when ImageOptions::COMMAND_NAME require "devops-client/handler/image" Image - when "filter" + when FilterOptions::COMMAND_NAME require "devops-client/handler/filter" Filter - when "group" - require "devops-client/handler/group" - Group - when "deploy" + when DeployOptions::COMMAND_NAME require "devops-client/handler/deploy" Deploy - when "project" + when ProjectOptions::COMMAND_NAME require "devops-client/handler/project" Project - when "network" - require "devops-client/handler/network" - Network - when "key" + when EnvironmentOptions::COMMAND_NAME + require "devops-client/handler/environment" + Environment + when CategoryOptions::COMMAND_NAME + require "devops-client/handler/category" + Category + when KeyOptions::COMMAND_NAME require "devops-client/handler/key" Key - when "user" + when UserOptions::COMMAND_NAME require "devops-client/handler/user" User - when "provider" + when ProviderOptions::COMMAND_NAME require "devops-client/handler/provider" Provider - when "tag" - require "devops-client/handler/tag" - Tag - when "server" + when ServerOptions::COMMAND_NAME require "devops-client/handler/server" Server - when "script" + when ScriptOptions::COMMAND_NAME require "devops-client/handler/script" Script - when "templates" - require "devops-client/handler/bootstrap_templates" - BootstrapTemplates - when "stack_template" + when ChefOptions::COMMAND_NAME + require "devops-client/handler/chef" + Chef + when StackTemplateOptions::COMMAND_NAME require "devops-client/handler/stack_template" StackTemplate - when "stack" + when StackOptions::COMMAND_NAME require "devops-client/handler/stack" Stack + when RoleOptions::COMMAND_NAME + require "devops-client/handler/role" + Role else - require "devops-client/options/main" Main.new(ARGV, def_options).info exit(10) end diff --git a/devops-client/lib/devops-client/handler/helpers/http_utils.rb b/devops-client/lib/devops-client/handler/helpers/http_utils.rb index 6fcaebc..29445e7 100644 --- a/devops-client/lib/devops-client/handler/helpers/http_utils.rb +++ b/devops-client/lib/devops-client/handler/helpers/http_utils.rb @@ -9,7 +9,7 @@ module HttpUtils end def get path, params={} - get_with_headers path, params, self.headers("Content-Type") + get_with_headers path, params, headers() end def get_with_headers path, params={}, headers={} @@ -108,6 +108,9 @@ module HttpUtils return (res.contenttype.include?("application/json") ? JSON.parse(res.body) : res.body) end case res.status + when 422 + puts res.body + raise InvalidQuery.new(extract_message(res)) when 404 raise NotFound.new(extract_message(res)) when 400 @@ -165,4 +168,4 @@ module HttpUtils r end -end \ No newline at end of file +end diff --git a/devops-client/lib/devops-client/handler/helpers/input_utils.rb b/devops-client/lib/devops-client/handler/helpers/input_utils.rb index 3324cbd..924c9f5 100644 --- a/devops-client/lib/devops-client/handler/helpers/input_utils.rb +++ b/devops-client/lib/devops-client/handler/helpers/input_utils.rb @@ -1,3 +1,5 @@ +require "devops-client/handler/helpers/validators" + module InputUtils def question str @@ -8,7 +10,7 @@ module InputUtils res = false #system("stty raw -echo") begin - print "#{str} (y/n): " + print "#{str}? (y/n): " s = STDIN.gets.strip if s == "y" res = true @@ -25,6 +27,17 @@ module InputUtils res end + def input_run_list + begin + run_list = get_comma_separated_list(I18n.t("input.run_list.label") + ": ") + raise ArgumentError.new(I18n.t("input.run_list.invalid", list: run_list.join(","))) unless Validators.validate_run_list(run_list) + run_list + rescue ArgumentError => e + puts e.message + retry + end + end + def choose_image_cmd images, t=nil abort(I18n.t("handler.error.list.empty", :name => "Image")) if images.empty? if options[:image_id].nil? @@ -84,7 +97,7 @@ module InputUtils puts table end begin - print "#{title}: " + print "#{I18n.t("handler.message.choose", :name => title.downcase)}: " buf = STDIN.gets.strip if buf.empty? and !default.nil? return default @@ -96,6 +109,10 @@ module InputUtils return i - 1 end + def choose_item_from_list(title, items) + items[ choose_number_from_list(title, items) ] + end + def choose_indexes_from_list title, list, table=nil, default=nil, defindex=nil abort(I18n.t("handler.error.list.empty", :name => title)) if list.empty? ar = nil @@ -111,7 +128,7 @@ module InputUtils I18n.t("handler.message.choose_list_default", :name => title, :default => default) end begin - ar = get_comma_separated_list(msg).map do |g| + ar = get_comma_separated_list(msg + ": ").map do |g| n = Integer g.strip raise ArgumentError.new(I18n.t("handler.error.number.invalid")) if n < 1 or n > list.size n @@ -125,4 +142,13 @@ module InputUtils ar.map{|i| i - 1} end + def select_item_from_table(title, items, table) + items[ choose_number_from_list(title, items, table) ] + end + + def select_items_from_table(title, items, table, options={}) + indexes = choose_indexes_from_list(title, items, table, options[:default], options[:default_index]) + items.values_at(*indexes) + end + end diff --git a/devops-client/lib/devops-client/handler/helpers/outputtable.rb b/devops-client/lib/devops-client/handler/helpers/outputtable.rb index aed1478..34dc146 100644 --- a/devops-client/lib/devops-client/handler/helpers/outputtable.rb +++ b/devops-client/lib/devops-client/handler/helpers/outputtable.rb @@ -6,7 +6,7 @@ module Outputtable def outputter raise 'You should use "output_with" method to define outputter' unless self.class.outputter_class - @outputter ||= self.class.outputter_class.new(data_to_output, options.merge(current_command: current_command)) + @outputter ||= self.class.outputter_class.new(data_to_output, options.merge(current_command: @command)) end def output(options={}) @@ -14,7 +14,7 @@ module Outputtable end def report_url(job_id) - create_url "report/#{job_id}" + create_url "task/#{job_id}/report" end def reports_urls(job_ids) @@ -24,7 +24,6 @@ module Outputtable end.join("\n") end - def self.included(base) base.extend(ClassMethods) end @@ -36,4 +35,4 @@ module Outputtable end end -end \ No newline at end of file +end diff --git a/devops-client/lib/devops-client/handler/helpers/resources_fetcher.rb b/devops-client/lib/devops-client/handler/helpers/resources_fetcher.rb index 4514741..8c381d9 100644 --- a/devops-client/lib/devops-client/handler/helpers/resources_fetcher.rb +++ b/devops-client/lib/devops-client/handler/helpers/resources_fetcher.rb @@ -25,9 +25,24 @@ module Helpers def fetch_with_table(collection_name, *args) handler = build_handler(collection_name) + handler.command = :list [handler.list_handler(*args), handler.outputter.table] end + def show_list_data_with_table(collection_name, data) + handler = build_handler(collection_name) + handler.command = :list + handler.instance_variable_set('@list', data) + [data, handler.outputter.table] + end + + def fetch_by_command_with_table(collection_name, command, args={}, options={}) + handler = build_handler(collection_name) + handler.command = command + args.each {|key, val| handler.instance_variable_set("@#{key}", val) } + return handler.send("#{command}_handler"), handler.outputter.output(options) + end + def fetch_project(project_id) @fetched_projects = {} if cached = @fetched_projects[project_id] @@ -37,12 +52,24 @@ module Helpers end end + def fetch_provider_accounts(provider) + handler = build_handler('provider') + [handler.accounts_handler(provider), handler.outputter.output(output_type: :accounts, provider: provider)] + end + + def fetch_category(project, environment_name, category) + environment = fetch_project(project)['environments'].detect do |env| + env['id'] == environment_name + end + environment['categories'].detect {|cat| cat['id'] == category } + end + private def build_handler(collection_name) require_handler_file(collection_name) - handler = resource_handler_klass(collection_name).new(host, options) + handler = resource_handler_klass(collection_name).new(host, self.options) handler.auth = @auth handler end @@ -56,4 +83,4 @@ module Helpers Object.const_get(class_name) end end -end \ No newline at end of file +end diff --git a/devops-client/lib/devops-client/handler/helpers/resources_selector.rb b/devops-client/lib/devops-client/handler/helpers/resources_selector.rb index 33d76c6..a21f558 100644 --- a/devops-client/lib/devops-client/handler/helpers/resources_selector.rb +++ b/devops-client/lib/devops-client/handler/helpers/resources_selector.rb @@ -12,10 +12,25 @@ module Helpers @fetcher = fetcher end - def select_available_provider - providers, table = @fetcher.fetch_with_table('provider') - # somewhy returns provider name as String. - select_item_from_table(I18n.t("headers.provider"), providers, table) + def select_available_provider(options={}) + providers = @fetcher.fetch('provider') + providers -= options[:except] if options[:except] + choose_item_from_list(I18n.t("headers.provider"), providers) + end + + def select_available_bootstrap_template + templates, table = @fetcher.fetch_by_command_with_table('chef', :bootstrap_templates) + select_item_from_table(I18n.t("headers.bootstrap_templates"), templates, table) + end + + def select_available_provider_account provider + accounts, table = @fetcher.fetch_by_command_with_table('provider', :accounts, {provider: provider}) + select_item_from_table(I18n.t("headers.provider_account"), accounts, table) + end + + def select_available_provider_account_image provider, provider_account + images, table = @fetcher.fetch_by_command_with_table('provider', :images, {provider: provider, account: provider_account}) + select_item_from_table(I18n.t("headers.image"), images, table) end def select_available_project @@ -37,41 +52,61 @@ module Helpers stack_template['id'] end - def select_available_flavor(options={}) - flavors, table = @fetcher.fetch_with_table('flavor', options[:provider]) - abort(I18n.t("handler.flavor.list.empty")) if flavors.empty? + def select_available_flavor(args) + flavors, table = @fetcher.fetch_by_command_with_table('provider', :flavors, args, {provider: args[:provider], account: args[:account]}) + abort(I18n.t("handler.provider.flavor.list.empty")) if flavors.empty? - flavors_descriptions = flavors.map {|f| "#{f["id"]}. #{f["name"]} - #{f["ram"]}, #{f["disk"]}, #{f["v_cpus"]} CPU"} - index = choose_number_from_list(I18n.t("headers.flavor"), flavors_descriptions, table) - - flavors[index]['id'] + select_item_from_table(I18n.t("choose.flavor"), flavors, table)["id"] end - def select_available_image(options={}) - images, table = @fetcher.fetch_with_table('image', options[:provider]) - image = select_item_from_table(I18n.t("headers.image"), images, table) - image['id'] + def select_available_image(args) + images, table = @fetcher.fetch_by_command_with_table('image', :list, args, {provider: args[:provider], account: args[:account]}) + abort(I18n.t("handler.provider.images.list.empty")) if images.empty? + + select_item_from_table(I18n.t("choose.image"), images, table)["id"] end - def select_available_network(options={}) - networks, table = @fetcher.fetch_with_table('network', options[:provider]) - table_title = options[:table_title] || I18n.t("headers.network") + def select_available_vpc(args) + vpcs, table = @fetcher.fetch_by_command_with_table('provider', :vpc, args) + abort(I18n.t("handler.provider.vpc.list.empty")) if vpcs.empty? - selected_networks = select_items_from_table(table_title, networks, table) + select_item_from_table(I18n.t("choose.vpc"), vpcs, table)["vpc_id"] + end + + def select_available_network(args, options={}) + networks, table = @fetcher.fetch_by_command_with_table('provider', :networks, args) + select_item_from_table(I18n.t("choose.network"), networks, table)["subnetId"] + end + + def select_available_networks(args) + networks, table = @fetcher.fetch_by_command_with_table('provider', :networks, args) + + selected_networks = select_items_from_table(I18n.t("choose.networks"), networks, table) selected_networks.map {|network| network['name']} end + # options: + # :data - list of strings def select_available_users(options={}) - title = options[:table_title] || I18n.t("handler.project.create.user") - users, table = @fetcher.fetch_with_table('user') + title = options[:label] || I18n.t("input.users.label") + users, table = if options[:data] + @fetcher.show_list_data_with_table('user', options[:data].map{|v| {'id' => v}}) + else + @fetcher.fetch_with_table('user') + end users = select_items_from_table(title, users, table) users.map {|user| user['id']} end - def select_available_groups(options={}) - title = options[:table_title] || I18n.t("options.project.create.groups") - groups, table = @fetcher.fetch_with_table('group', options[:provider], options[:vpc_id]) + def select_available_ssh_keys(options={}) + ssh_keys, table = @fetcher.fetch_with_table('key') + + select_item_from_table(I18n.t("choose.ssh_key"), ssh_keys, table)['id'] + end + + def select_available_security_groups(args) + groups, table = @fetcher.fetch_by_command_with_table('provider', :security_groups, args) # somewhy groups returned as a hash, not array groups_array = [] @@ -81,19 +116,22 @@ module Helpers end default_index = groups_array.index {|t| t['id'] == 'default'} - select_items_from_table(title, groups_array, table, default: 'default', default_index: default_index) + select_items_from_table(I18n.t("choose.security_groups"), groups_array, table, default: 'default', default_index: default_index).map{|g| g["id"]} end - private - - def select_item_from_table(title, items, table) - items[ choose_number_from_list(title, items, table) ] + def select_available_roles used_roles=[] + roles, table = @fetcher.fetch_with_table('role') + roles.delete_if{|role| used_roles.include?(role["id"])} unless used_roles.empty? + roles = select_items_from_table(I18n.t("headers.role"), roles, table) + roles.map{|r| r['id']} end - def select_items_from_table(title, items, table, options={}) - indexes = choose_indexes_from_list(title, items, table, options[:default], options[:default_index]) - items.values_at(*indexes) + def select_user_roles used_roles + roles, table = @fetcher.fetch_with_table('role') + roles.select{|role| used_roles.include?(role["id"])} + roles = select_items_from_table(I18n.t("headers.role"), roles, table) + roles.map{|r| r['id']} end end -end \ No newline at end of file +end diff --git a/devops-client/lib/devops-client/handler/helpers/validators.rb b/devops-client/lib/devops-client/handler/helpers/validators.rb new file mode 100644 index 0000000..862adc5 --- /dev/null +++ b/devops-client/lib/devops-client/handler/helpers/validators.rb @@ -0,0 +1,16 @@ +class Validators + + class << self + + def validate_run_list run_list + return true if run_list.empty? + rl = /\Arole|recipe\[[\w-]+(::[\w-]+)?\]\Z/ + e = run_list.select {|l| (rl =~ l).nil?} + res = e.empty? + puts I18n.t("handler.project.create.run_list.invalid", :list => e.join(", ")) unless res + res + end + + end + +end diff --git a/devops-client/lib/devops-client/handler/image.rb b/devops-client/lib/devops-client/handler/image.rb index 40c245b..054ea7f 100644 --- a/devops-client/lib/devops-client/handler/image.rb +++ b/devops-client/lib/devops-client/handler/image.rb @@ -1,22 +1,21 @@ require "devops-client/handler/handler" require "devops-client/options/image_options" require "devops-client/output/image" -require "devops-client/handler/bootstrap_templates" class Image < Handler output_with Output::Image def initialize(host, def_options={}) - # QUESTION: - # why we set @options variable here while we always reset it in #handle method? - @host, @options = host, def_options - @options_parser = ImageOptions.new(ARGV, def_options) + @host = host + @options = def_options end def handle - @options, @args = @options_parser.parse_options_for!(current_command) - case current_command + @options_parser = ImageOptions.new(ARGV, @options) + @command, @options, @args = @options_parser.parse_options_for! + extract_parameters + case @command when :list list_handler output @@ -32,109 +31,52 @@ class Image < Handler end end - def get_templates - bt = BootstrapTemplates.new(@host, self.options) - bt.auth = self.auth - list = bt.list_handler - return list, nil if list.empty? - [list, bt.outputter.table] - end - def create_handler q = {} q[:provider] = options[:provider] || resources_selector.select_available_provider + q[:provider_account] = options[:provider_account] || resources_selector.select_available_provider_account(q[:provider])["account_name"] + image = resources_selector.select_available_provider_account_image(q[:provider], q[:provider_account]) - provider_images(q[:provider]) - - # QUESTION: - # why we select image in create image handler? - image = nil - if options[:image_id].nil? - image = choose_image_cmd(@list, self.outputter.table) - else - image = @list.detect{|i| i["id"] == options[:image_id]} - abort("Invalid image id '#{options[:image_id]}'") if image.nil? - end q["name"] = image["name"] q["id"] = image["id"] + q["image_id"] = q["id"] q["remote_user"] = options[:ssh_username] || enter_parameter(I18n.t("handler.image.create.ssh_user") + ": ") - q["bootstrap_template"] = if options[:bootstrap_template].nil? and options[:no_bootstrap_template] == false - bt, bt_t = get_templates - if bt.empty? - puts I18n.t("handler.image.create.template_empty") - nil - else - # QUESTION: - # what does this '-1' mean? - i = choose_number_from_list(I18n.t("handler.image.create.template"), bt, bt_t, -1) - if i == -1 - nil - else - bt[i] - end - end - else - nil - end json = JSON.pretty_generate(q) post_body "/image", json if question(I18n.t("handler.image.question.create")){puts json} end - def list_handler(provider=nil) - provider ||= @options[:given_provider] - @list = if provider - provider_images(provider) - else - get("/images") + def list_handler + path = "/images" + params = {} + params["provider"] = @provider if @provider + params["account"] = @account if @account + unless params.empty? + path += "?" + params.map{|k,v| "#{k}=#{v}"}.join("&") end + @list = get(path) end - def provider_images provider + def provider_images provider, provider_account if provider == 'static' @options_parser.invalid_list_command abort() end - if @options[:provider_account] - @list = get("/images/provider/#{provider}/#{@options[:provider_account]}") - else - @list = get("/images/provider/#{provider}") - end + @list = get("/provider/#{provider}/account/#{provider_account}/images") end def show_handler - id = @args[2] - r = inspect_parameters @options_parser.show_params, id - unless r.nil? - @options_parser.invalid_show_command - abort(r) - end - @show = get "/image/#{id}" + @show = get "/image/#{@image}" end def delete_handler - id = @args[2] - r = inspect_parameters @options_parser.delete_params, id - unless r.nil? - @options_parser.invalid_delete_command - abort(r) - end - if question(I18n.t("handler.image.question.delete", :name => id)) - delete "/image/#{id}" - end + delete "/image/#{@image}" if question(I18n.t("handler.image.question.delete", :name => @image)) end - # QUESTION: - # what does inspect_parameters do and what do args[2] and args[3] actually mean? def update_handler - r = inspect_parameters @options_parser.update_params, @args[2], @args[3] - unless r.nil? - @options_parser.invalid_update_command - abort(r) - end - update_object_from_file "image", @args[2], @args[3] + update_object_from_file "image", @image, @file end end diff --git a/devops-client/lib/devops-client/handler/key.rb b/devops-client/lib/devops-client/handler/key.rb index 9b85043..0d339d1 100644 --- a/devops-client/lib/devops-client/handler/key.rb +++ b/devops-client/lib/devops-client/handler/key.rb @@ -13,8 +13,9 @@ class Key < Handler end def handle - @options, @args = @options_parser.parse_options_for!(current_command) - case current_command + @command, @options, @args = @options_parser.parse_options_for! + extract_parameters + case @command when :list list_handler output @@ -26,30 +27,18 @@ class Key < Handler end def add_handler - r = inspect_parameters @options_parser.add_params, @args[2], @args[3] - unless r.nil? - @options_parser.invalid_add_command - abort(r) - end - - content = File.read(@args[3]) + content = File.read(@file) q = { - "key_name" => @args[2], - "file_name" => File.basename(@args[3]), + "key_name" => @key_name, + "file_name" => File.basename(@file), "content" => content } - post "/key", q + json = JSON.pretty_generate(q) + post_body "/key", json if question(I18n.t("handler.key.question.create")){puts json} end def delete_handler - r = inspect_parameters @options_parser.delete_params, @args[2] - unless r.nil? - @options_parser.invalid_delete_command - abort(r) - end - if question(I18n.t("handler.key.question.delete", :name => @args[2])) - delete "/key/#{@args[2]}" - end + delete "/key/#{@key_name}" if question(I18n.t("handler.key.question.delete", :name => @key_name)) end def list_handler diff --git a/devops-client/lib/devops-client/handler/network.rb b/devops-client/lib/devops-client/handler/network.rb deleted file mode 100644 index 4a79e7e..0000000 --- a/devops-client/lib/devops-client/handler/network.rb +++ /dev/null @@ -1,35 +0,0 @@ -require "devops-client/handler/handler" -require "devops-client/options/network_options" -require "json" -require "devops-client/output/network" - -class Network < Handler - - output_with Output::Network - - def initialize(host, def_options={}) - @host, @options = host, def_options - @options_parser = NetworkOptions.new(ARGV, def_options) - end - - def handle - current_command = ARGV[1].to_sym - @options, @args = @options_parser.parse_options_for!(current_command) - case current_command - when :list - list_handler(@args[2]) - output - end - end - - def list_handler(provider=nil) - r = inspect_parameters @options_parser.list_params, provider - unless r.nil? - @options_parser.invalid_list_command - abort(r) - end - @list = get("/networks/#{provider}").sort!{|x,y| x["name"] <=> y["name"]} - end - -end - diff --git a/devops-client/lib/devops-client/handler/project.rb b/devops-client/lib/devops-client/handler/project.rb index c372e3a..11f38af 100644 --- a/devops-client/lib/devops-client/handler/project.rb +++ b/devops-client/lib/devops-client/handler/project.rb @@ -1,94 +1,57 @@ require "devops-client/handler/provider" require "devops-client/handler/image" -require "devops-client/handler/flavor" -require "devops-client/handler/network" -require "devops-client/handler/group" require "devops-client/handler/user" +require "devops-client/handler/helpers/validators" require "devops-client/options/project_options" require "json" require "set" require "devops-client/output/project" -require "devops-client/handler/deploy_envs/deploy_env_factory" class Project < Handler - attr_accessor :def_options output_with Output::Project def initialize(host, def_options={}) - self.host = host - self.def_options = self.options = def_options + @host = host @options_parser = ProjectOptions.new(ARGV, def_options) end def handle - case ARGV[1] - when "create" - self.options = @options_parser.create_options - create_handler @options_parser.args - when "delete" - self.options = @options_parser.delete_options - delete_handler @options_parser.args - when "deploy" - self.options = @options_parser.deploy_options - deploy_handler @options_parser.args - when "list" - self.options = @options_parser.list_options + @command, @options, @args = @options_parser.parse_options_for! + extract_parameters + case @command + when :create + create_handler + when :delete + delete_handler + when :list list_handler output - when "multi" - case ARGV[2] - when "create" - self.options = @options_parser.multi_create_options - multi_create_handler @options_parser.args - else - @options_parser.invalid_multi_command - abort(I18n.t("handler.project.invalid_subcommand", :cmd => ARGV[1], :scmd => ARGV[2])) - end - when "servers" - self.options = @options_parser.servers_options - servers_handler @options_parser.args - output(output_type: :servers) - when "stacks" - self.options = @options_parser.stacks_options - stacks_handler @options_parser.args - output(output_type: :stacks) - when "set" - case ARGV[2] - when "run_list" - self.options = @options_parser.set_run_list_options - set_run_list_handler @options_parser.args - else - @options_parser.invalid_set_command - abort(I18n.t("handler.project.invalid_subcommand", :cmd => ARGV[1], :scmd => ARGV[2])) - end - when "show" - self.options = @options_parser.show_options - show_handler @options_parser.args - output(output_type: :show) - when "update" - self.options = @options_parser.update_options - update_handler @options_parser.args - when "user" - case ARGV[2] - when "add" - self.options = @options_parser.user_add_options - user_add_handler @options_parser.args - when "delete" - self.options = @options_parser.user_delete_options - user_delete_handler @options_parser.args - else - @options_parser.invalid_user_command - abort(I18n.t("handler.project.invalid_subcommand", :cmd => ARGV[1], :scmd => ARGV[2])) - end + when :servers + servers_handler + output + when :stacks + stacks_handler + output + when :set_run_list + set_run_list_handler + when :show + show_handler + output + when :update + puts "TODO: update" + exit + update_handler + when :user_add + user_add_handler + when :user_delete + user_delete_handler + when :archive + archive_handler + when :unarchive + unarchive_handler when "test" - self.options = @options_parser.test_options - test_handler @options_parser.args - when "delete_servers" - self.options = @options_parser.delete_servers_options - delete_servers_handler @options_parser.args - else - @options_parser.invalid_command + test_handler end end @@ -96,32 +59,23 @@ class Project < Handler @list = get "/projects" end - def delete_handler args - r = inspect_parameters @options_parser.delete_params, args[2], args[3] - unless r.nil? - @options_parser.invalid_delete_command - abort(r) - end - o = {} - o[:deploy_env] = args[3] unless args[3].nil? - - message = args[2] - message += ".#{args[3]}" unless args[3].nil? - if question(I18n.t("handler.project.question.delete", :name => message)) - delete "/project/#{args[2]}", o - end + def delete_handler + delete "/project/#{@project_id}" if question(I18n.t("handler.project.question.delete", :name => @project_id)) end - def show_handler args - r = inspect_parameters @options_parser.show_params, args[2] - unless r.nil? - @options_parser.invalid_show_command - abort(r) - end - @show = get_project_info_obj(args[2]) + def show_handler + @show = get("/project/#{@project_id}") end - def update_handler args + def archive_handler + post "/project/#{@project_id}/archive" + end + + def unarchive_handler + post "/project/#{@project_id}/unarchive" + end + + def update_handler r = inspect_parameters @options_parser.update_params, args[2], args[3] unless r.nil? @options_parser.invalid_update_command @@ -130,7 +84,7 @@ class Project < Handler update_object_from_file "project", args[2], args[3] end - def create_handler args + def create_handler file = self.options[:file] unless file.nil? json = File.read(file) @@ -141,145 +95,34 @@ class Project < Handler end post_body("/project", json) else - r = inspect_parameters @options_parser.create_params, args[2] - unless r.nil? - @options_parser.invalid_create_command - abort(r) - end - unless self.options[:username].nil? || self.options[:password].nil? - self.auth[:username] = self.options[:username] - self.auth[:password] = self.options[:password] - self.def_options[:username] = self.auth[:username] - end - create_project args, :create_project_deploy_env_cmd + create_project end end - def servers_handler args - r = inspect_parameters @options_parser.servers_params, args[2], args[3] - unless r.nil? - @options_parser.invalid_servers_command - abort(r) - end - o = {} - unless args[3].nil? - o[:deploy_env] = args[3] - end - @servers = get "/project/#{args[2]}/servers", o + def servers_handler + @list = get "/project/#{@project_id}/servers" end - def stacks_handler args - project, deploy_env = args[2], args[3] - r = inspect_parameters @options_parser.stacks_params, project, deploy_env - unless r.nil? - @options_parser.invalid_stacks_command - abort(r) - end - options = {} - unless deploy_env.nil? - options[:deploy_env] = deploy_env - end - @list = get "/project/#{args[2]}/stacks", options + def stacks_handler + @list = get "/project/#{@project_id}/stacks" end - def user_add_handler args - r = inspect_parameters @options_parser.user_add_params, args[3], args[4] - unless r.nil? - @options_parser.invalid_user_add_command - abort(r) - end - q = {:users => args[4..-1]} - q[:deploy_env] = options[:deploy_env] unless options[:deploy_env].nil? - put "/project/#{args[3]}/user", q + def user_add_handler + post "/project/#{@project_id}/users/add", @user_name end - def user_delete_handler args - r = inspect_parameters @options_parser.user_delete_params, args[3], args[4] - unless r.nil? - @options_parser.invalid_user_delete_command - abort(r) - end - q = {:users => args[4..-1]} - q[:deploy_env] = options[:deploy_env] unless options[:deploy_env].nil? - delete_body "/project/#{args[3]}/user", q.to_json + def user_delete_handler + post "/project/#{@project_id}/users/delete", @user_name end - def multi_create_handler args - r = inspect_parameters @options_parser.multi_create_params, args[3] - unless r.nil? - @options_parser.invalid_multi_create_command - abort(r) - end - - create_project args, :create_project_multi_deploy_env_cmd, :multi - - i = Image.new(@host, self.def_options) - images, ti = i.list_handler, i.table - f = Flavor.new(@host, self.def_options) - flavors, tf = f.list_handler, f.table - g = Group.new(@host, self.def_options) - groups, tg = g.list_handler, g.table - - list = list_handler - info, multi = {}, {:type => "multi", :name => args[3], :deploy_envs => []} - begin # Add environment - nodes, projects, servers = [], [], {} - deploy_env = {:identifier => enter_parameter("Deploy environment identifier: ")} - begin # Add server - server_name = args[3] + "_" + enter_parameter("Server name: " + args[3] + "_") - s = servers[server_name] = {} - s[:groups] = choose_indexes_from_list("Security groups", list, tg, "default", list.index("default")).map{|i| list[i]} - s[:flavor] = choose_flavor_cmd(flavors, tf)["name"] - s[:image] = choose_image_cmd(images, ti)["id"] - subprojects = s[:subprojects] = [] - - begin # Add project - o = {} - o[:project_id] = project_id = choose_project(list, table) - info[project_id] = get_project_info_obj(project_id) unless info.has_key?(project_id) - envs = info[project_id]["deploy_envs"].map{|de| de["identifier"]} - o[:project_env] = ( envs.size > 1 ? choose_project_env(envs) : envs[0] ) - subprojects.push o - end while question("Add project?") - - end while question("Add server?") - - deploy_env[:servers] = servers - multi[:deploy_envs].push deploy_env - end while question(I18n.t("handler.project.question.add_env")) - puts JSON.pretty_generate(multi) - post "/project", :json => multi.to_json if question(I18n.t("handler.project.question.create")) - end - - def set_run_list_handler args - r = inspect_parameters @options_parser.set_run_list_params, args[3], args[4], args[5] - unless r.nil? - @options_parser.invalid_set_run_list_command - abort(r) - end - run_list = [] - args[5..args.size].each do |e| - run_list += e.split(",") - end + def set_run_list_handler + run_list = @run_list || [] if run_list.empty? - exit unless question(I18n.t("handler.project.run_list.empty")) + exit unless question(I18n.t("handler.project.question.run_list.empty")) else - exit unless DeployEnv.validate_run_list(run_list) + exit unless Validators.validate_run_list(run_list) end - put "/project/#{args[3]}/#{args[4]}/run_list", run_list - end - - def deploy_handler args - r = inspect_parameters @options_parser.deploy_params, args[2], args[3] - unless r.nil? - @options_parser.invalid_deploy_command - abort(r) - end - q = {} - q[:servers] = options[:servers] unless options[:servers].nil? - q[:deploy_env] = args[3] unless args[3].nil? - job_ids = post "/project/#{args[2]}/deploy", q - reports_urls(job_ids) + post "/project/#{@project_id}/run_list", run_list end def test_handler args @@ -292,196 +135,36 @@ class Project < Handler reports_urls(job_ids) end -protected - def get_project_info_obj project_id - get("/project/#{project_id}") - end + protected + def create_project - def create_project args, env_method_name, type=nil - project_name = args[2] - providers = {} - providers[:obj], providers[:table] = fetcher.fetch_with_table('provider') - begin - project = get_project_info_obj(project_name) - puts_warn I18n.t("handler.project.exist", :project => project_name) - names = project["deploy_envs"].map{|de| de["identifier"]} - new_envs = [] - while question(I18n.t("handler.project.question.add_env")) - new_envs << method(env_method_name).call(project_name, providers, names) - break if self.options[:no_ask] - end - puts JSON.pretty_generate(new_envs) - if question(I18n.t("handler.project.question.update_with_new_envs")) - new_envs.each do |env| - post "/project/#{project_name}/deploy_env", env - end - end - rescue NotFound => e - project = create_project_cmd(project_name, providers, env_method_name) - project[:name] = args[2] - puts json = JSON.pretty_generate(project) - post_body("/project", json) if question(I18n.t("handler.project.question.create")) - end - end - - def create_project_cmd project_name, providers, env_method - project = {:deploy_envs => []} - names = [] - begin - d = method(env_method).call(project_name, providers, names) - project[:deploy_envs].push d - break if self.options[:no_ask] - end while question(I18n.t("handler.project.question.add_env")) - project - end - - def create_project_deploy_env_cmd project, providers, names - d = {} - set_identifier(d, names) - - set_provider(d, providers) - - de = DeployEnvFactory.create(d[:provider], @host, self.options, self.auth) - de.fill d - -=begin - unless d[:provider] == "static" - set_flavor(d, buf) - set_image(d, buf) - vpc_id = set_subnets(d, buf) - set_groups(d, buf, vpc_id) - end - set_users(d, buf) - - unless self.options[:run_list].nil? - self.options[:run_list] = self.options[:run_list].split(",").map{|e| e.strip} - abort("Invalid run list: '#{self.options[:run_list].join(",")}'") unless Project.validate_run_list(self.options[:run_list]) - end - set_parameter d, :run_list do - set_run_list_cmd project, d[:identifier] - end - - unless self.options[:no_expires] - set_parameter d, :expires do - s = enter_parameter_or_empty(I18n.t("options.project.create.expires") + ": ").strip - s.empty? ? nil : s - end - end -=end - d - end - - def create_project_multi_deploy_env_cmd project, providers, names - d = {} - set_identifier(d, names) - - set_provider(d, providers) - buf = providers[d[:provider]] - - set_flavor(d, buf) - set_image(d, buf) - vpc_id = set_subnets(d, buf) - set_groups(d, buf, vpc_id) - set_users(d, buf) - - unless self.options[:run_list].nil? - self.options[:run_list] = self.options[:run_list].split(",").map{|e| e.strip} - abort("Invalid run list: '#{self.options[:run_list].join(",")}'") unless DeployEnv.validate_run_list(self.options[:run_list]) - end - set_parameter d, :run_list do - set_run_list_cmd project, d[:identifier] - end - - unless self.options[:no_expires] - set_parameter d, :expires do - s = enter_parameter_or_empty(I18n.t("options.project.create.expires") + ": ").strip - s.empty? ? nil : s - end - end - d - end - - def set_identifier d, names - set_parameter d, :identifier do - begin - n = enter_parameter I18n.t("handler.project.create.env") + ": " - if names.include?(n) - puts I18n.t("handler.project.create.env_exist", :env => n) - raise ArgumentError - else - names.push n - n - end - rescue ArgumentError - retry - end - end - end - - def set_provider d, providers - set_parameter d, :provider do - providers[:obj][ choose_number_from_list(I18n.t("headers.provider"), providers[:obj], providers[:table]) ] - end - end - - def set_parameter obj, key - if self.options[key].nil? - obj[key] = yield + project = {:environments => [], :id => @project_id} + project[:description] = options[:description] || enter_parameter_or_empty(I18n.t("handler.project.create.description") + ": ") + project[:project_users] = if options[:project_users] + options[:project_users] else - obj[key] = self.options[key] + if question(I18n.t("handler.project.create.question.users_choose")) + #choose + resources_selector.select_available_users(label: I18n.t("handler.project.create.users")) + else + #enter + get_comma_separated_list(I18n.t("input.message.input_list", cmd: I18n.t("input.user.list"))) + end + end - end - # returns project id - def choose_project projects, table=nil - abort(I18n.t("handler.project.list.empty")) if projects.empty? - projects[ choose_number_from_list(I18n.t("headers.project"), projects, table) ] - end + project[:run_list] = if self.options[:run_list] + abort("Invalid run list: '#{self.options[:run_list].join(",")}'") unless Validators.validate_run_list(self.options[:run_list]) + self.options[:run_list] + else + input_run_list + end - # returns project env - def choose_project_env project_envs, table=nil - abort(I18n.t("handler.project.env.list.empty")) if project_envs.empty? - project_envs[ choose_number_from_list(I18n.t("headers.project_env"), project_envs, table) ] + json = JSON.pretty_generate(project) + post_body("/project", json) if question(I18n.t("handler.project.question.create")){puts json} end def data_to_output - @list || @show || @servers || @test + @list || @show || @test end - - def delete_servers_handler(args) - project, env = args[2], args[3] - if error = inspect_parameters(@options_parser.delete_servers_params, project, env) - @options_parser.invalid_delete_servers_command - abort(error) - end - - ask_for_delete_servers(project, env) - body = { - deploy_env: env, - dry_run: false - } - response = delete("/project/#{project}/servers", body) - reports_urls(response['reports']) - end - - private - - def ask_for_delete_servers(project, env) - body = { - deploy_env: env, - dry_run: true - } - to_delete = delete("/project/#{project}/servers", body)['to_delete'] - if to_delete.empty? - abort "There are no servers to delete." - else - puts "Servers to delete:\n----\n" - puts to_delete.join("\n") - puts '----' - end - if @options[:dry_run] || !question('Are you sure to delete them? ') - abort - end - end - end diff --git a/devops-client/lib/devops-client/handler/provider.rb b/devops-client/lib/devops-client/handler/provider.rb index f3389af..b830da0 100644 --- a/devops-client/lib/devops-client/handler/provider.rb +++ b/devops-client/lib/devops-client/handler/provider.rb @@ -8,33 +8,97 @@ class Provider < Handler def initialize(host, def_options={}) @host, @options = host, def_options - @options_parser = ProviderOptions.new(ARGV, def_options) end def handle - @options, @args = @options_parser.parse_options_for!(current_command) - case current_command - when :list - list_handler - output(output_type: :list) - when :accounts - @provider = @args[2] - accounts_handler - output(output_type: :accounts, provider: @provider) + @options_parser = ProviderOptions.new(ARGV, @options) + @command, @options, @args = @options_parser.parse_options_for! + extract_parameters + case @command + when :account_create + return account_create_handler + when :account_delete + return account_delete_handler end + send("#{@command}_handler") + output_data = { + provider: @provider, + account: @account + } + output(output_data) end def list_handler @list = get("/providers").sort!{|x,y| x["id"] <=> y["id"]} end - def accounts_handler - r = inspect_parameters @options_parser.accounts_params, @provider - unless r.nil? - @options_parser.invalid_accounts_command - abort(r) + def account_create_handler + file = @options[:file] + unless file.nil? + json = File.read(file) + begin + JSON.parse(json) + rescue JSON::ParserError => e + abort(I18n.t("handler.provider.account.create.invalid_json", :file => file)) + end + post_body("/provider/#{@provider}/account", json) + else + create_account end + end + + def account_delete_handler + delete "/provider/#{@provider}/account/#{@account}" if question(I18n.t("handler.provider.account.question.delete", :account => @account)) + end + + def create_account + q = { + "account_name" => @account + } + q["description"] = options[:description] || enter_parameter_or_empty(I18n.t("handler.provider.account.create.description") + ": ") + q["ssh_key"] = options[:ssh_key] || resources_selector.select_available_ssh_keys + + case @provider + when Providers::Aws + q["use_iam_profile"] = options[:use_iam_profile] || question(I18n.t("handler.provider.account.create.aws.use_iam_profile")) + unless q["use_iam_profile"] + q["access_key_id"] = options[:access_key_id] || enter_parameter(I18n.t("handler.provider.account.create.aws.access_key_id") + ": ") + q["secret_access_key"] = options[:secret_access_key] || enter_parameter(I18n.t("handler.provider.account.create.aws.secret_access_key") + ": ") + end + when Providers::Openstack + when Providers::Static + # nothing to do + end + json = JSON.pretty_generate(q) + post_body "/provider/#{@provider}/account", json if question(I18n.t("handler.provider.account.question.create")){puts json} + end + + def accounts_handler @list = get("/provider/#{@provider}/accounts") end + def flavors_handler + @list = get("/provider/#{@provider}/account/#{@account}/flavors").sort!{|x,y| x["id"] <=> y["id"]} + end + + def images_handler + @list = get("/provider/#{@provider}/account/#{@account}/images") + end + + def networks_handler + path = "/provider/#{@provider}/account/#{@account}/networks" + path += "?vpc-id=#{@vpc_id}" if @vpc_id + @list = get(path) + end + + def vpc_handler + abort(I18n.t("handler.provider.vpc.invalid_provider")) unless @provider == Providers::Aws + @list = get("/provider/#{@provider}/account/#{@account}/vpcs") + end + + def security_groups_handler + path = "/provider/#{@provider}/account/#{@account}/security_groups" + path += "?vpc-id=#{@vpc_id}" if @vpc_id + @list = get(path) + end end diff --git a/devops-client/lib/devops-client/handler/role.rb b/devops-client/lib/devops-client/handler/role.rb new file mode 100644 index 0000000..039a974 --- /dev/null +++ b/devops-client/lib/devops-client/handler/role.rb @@ -0,0 +1,75 @@ +require "devops-client/handler/handler" +require "devops-client/options/role_options" +require "devops-client/output/role" + +class Role < Handler + + output_with Output::Role + include InputUtils + + def initialize(host, def_options={}) + @host, @options = host, def_options + @options_parser = RoleOptions.new(ARGV, def_options) + end + + def handle + @command, @options, @args = @options_parser.parse_options_for! + extract_parameters + case @command + when :list + list_handler + output + when :show + show_handler + output + when :create + create_handler + when :delete + delete_handler + when :policies + policies_handler + output + end + end + + def list_handler + @list = get("/security/roles") + end + + def show_handler + @show = get("/security/role/#{@role_name}") + end + + def policies_handler + @list = get("/security/policies") + end + + def create_handler + q = { + "name" => @role_name + } + q["description"] = options[:description] || enter_parameter(I18n.t("handler.role.create.description") + ": ") + all = question(I18n.t("handler.role.question.all_policies")) + if all + q["policies"] = ["all"] + else + policies_handler + + q["policies"] = if options[:policies] + #TODO: validation + options[:policies] + else + @command = :policies + select_items_from_table(I18n.t("handler.role.create.policies"), @list, outputter.table).map{|p| p["id"]} + end + end + + json = JSON.pretty_generate(q) + post_body "/security/role", json if question(I18n.t("handler.role.question.create")){puts json} + end + + def delete_handler + delete("/security/role/#{@role_name}") if question(I18n.t("handler.role.question.delete", role: @role_name)) + end + +end diff --git a/devops-client/lib/devops-client/handler/server.rb b/devops-client/lib/devops-client/handler/server.rb index 07027b6..e9eec04 100644 --- a/devops-client/lib/devops-client/handler/server.rb +++ b/devops-client/lib/devops-client/handler/server.rb @@ -9,18 +9,19 @@ class Server < Handler def initialize(host, def_options={}) @host, @options = host, def_options - @options_parser = ServerOptions.new(ARGV, def_options) end def handle - @options, @args = @options_parser.parse_options_for!(current_command) - case current_command + @options_parser = ServerOptions.new(ARGV, @options) + @command, @options, @args = @options_parser.parse_options_for! + extract_parameters + case @command when :list list_handler output when :show show_handler - output + output(server: @server_id) when :create create_handler when :delete @@ -29,6 +30,8 @@ class Server < Handler delete_list_handler when :bootstrap bootstrap_handler + when :unbootstrap + unbootstrap_handler when :sync sync_handler when :pause @@ -47,35 +50,20 @@ class Server < Handler end def list_handler - if @args[2].nil? - @list = get("/servers") - return @list - end - self.options[:type] = @args[2] - @list = case @args[2] - when "chef" - get("/servers/chef").map {|l| {"chef_node_name" => l}} - when "ec2", "openstack", "static" - get("/servers/#{@args[2]}") - else - @options_parser.invalid_list_command - abort("Invlid argument '#{@args[2]}'") - end + params = {} + params["provider"] = @provider if @provider + @list = get("/servers", params) end def create_handler - r = inspect_parameters @options_parser.create_params, @args[2], @args[3] - unless r.nil? - @options_parser.invalid_create_command - abort(r) - end q = { - :project => @args[2], - :deploy_env => @args[3] + project: @project, + environment: @environment, + category: @category } - [:key, :without_bootstrap, :name, :groups, :force, :private_ip].each do |k| + [:ssh_key, :without_bootstrap, :name, :groups, :skip_rollback, :private_ip].each do |k| q[k] = self.options[k] unless self.options[k].nil? end @@ -84,18 +72,10 @@ class Server < Handler end def delete_handler - @args[2..-1].each do |name| - r = inspect_parameters @options_parser.delete_params, name - unless r.nil? - @options_parser.invalid_delete_command - abort(r) - end - if question(I18n.t("handler.server.question.delete", :name => name)) - jobs_ids = delete("/server/#{name}", options) # returns array with one job id, actually - puts reports_urls(jobs_ids) - end + if question(I18n.t("handler.server.question.delete", :id => @server_id)) + jobs_ids = delete("/server/#{@server_id}", options) # returns array with one job id, actually + reports_urls(jobs_ids) end - nil end # this method differs from #delete_handler in this: @@ -103,10 +83,8 @@ class Server < Handler # Timur said we planned to transfer all server requests to using ids by default, that's why # later we could get rid of #delete_list method. def delete_list_handler - abort "Please specify at least one server id" if @args.length < 3 - server_ids = @args[2..-1] - if question(I18n.t("handler.server.question.delete_list", ids: server_ids.join(', '))) - servers_jobs = post("/server/delete_list", {servers_ids: server_ids}) + if question(I18n.t("handler.server.question.delete_list", ids: @server_id.join(', '))) + servers_jobs = post("/servers/delete", {servers_ids: @server_id}) servers_jobs.each do |server_id, job_id| puts "Report for deleting #{server_id}: #{report_url(job_id)}" end @@ -115,47 +93,39 @@ class Server < Handler end def show_handler - r = inspect_parameters @options_parser.show_params, @args[2] - unless r.nil? - @options_parser.invalid_show_command - abort r - end - @show = get("/server/#{@args[2]}") + @show = get("/server/#{@server_id}") end def bootstrap_handler - r = inspect_parameters @options_parser.bootstrap_params, @args[2] - unless r.nil? - @options_parser.invalid_bootstrap_command - abort(r) - end - q = { - :instance_id => @args[2] - } - [:name, :bootstrap_template, :run_list].each do |k| + q = {} + [:cm_name, :bootstrap_template, :run_list].each do |k| q[k] = self.options[k] unless self.options[k].nil? end if q.has_key?(:run_list) abort unless Project.validate_run_list(q[:run_list]) end - job_ids = post "/server/bootstrap", q + job_ids = post "/server/#{@server_id}/bootstrap", q reports_urls(job_ids) end - def add_static_handler # add --public-ip - r = inspect_parameters @options_parser.add_params, @args[2], @args[3], @args[4], @args[5], @args[6] - unless r.nil? - @options_parser.invalid_add_command - abort(r) + def unbootstrap_handler + if question(I18n.t("handler.server.question.unbootstrap", :id => @server_id)) + job_ids = post("/server/#{@server_id}/unbootstrap") + reports_urls(job_ids) end + end + + def add_static_handler # add --public-ip q = { - :project => @args[2], - :deploy_env => @args[3], - :private_ip => @args[4], - :remote_user => @args[5], - :key => @args[6] + project: @project, + environment: @environment, + category: @category, + private_ip: @ip, + remote_user: @ssh_user, + key: @key_id, + name: @name } - q[:public_ip] = self.options[:public_ip] unless self.options[:public_ip].nil? + q[:public_ip] = self.options[:public_ip] if self.options[:public_ip] post "/server/add", q end @@ -179,39 +149,19 @@ class Server < Handler end def pause_handler - r = inspect_parameters @options_parser.pause_params, @args[2] - unless r.nil? - @options_parser.invalid_pause_command - abort(r) - end - post "/server/#{@args[2]}/pause", options + post "/server/#{@server_id}/pause" end def unpause_handler - r = inspect_parameters @options_parser.unpause_params, @args[2] - unless r.nil? - @options_parser.invalid_unpause_command - abort(r) - end - post "/server/#{@args[2]}/unpause", options + post "/server/#{@server_id}/unpause" end def reserve_handler - r = inspect_parameters @options_parser.reserve_params, @args[2] - unless r.nil? - @options_parser.invalid_reserve_command - abort(r) - end - post "/server/#{@args[2]}/reserve", options + post "/server/#{@server_id}/reserve" end def unreserve_handler - r = inspect_parameters @options_parser.unreserve_params, @args[2] - unless r.nil? - @options_parser.invalid_unreserve_command - abort(r) - end - post "/server/#{@args[2]}/unreserve", options + post "/server/#{@server_id}/unreserve" end end diff --git a/devops-client/lib/devops-client/handler/stack.rb b/devops-client/lib/devops-client/handler/stack.rb index c1f4f58..7656ec3 100644 --- a/devops-client/lib/devops-client/handler/stack.rb +++ b/devops-client/lib/devops-client/handler/stack.rb @@ -13,8 +13,9 @@ class Stack < Handler end def handle - @options, @args = @options_parser.parse_options_for!(current_command) - case current_command + @command, @options, @args = @options_parser.parse_options_for! + extract_parameters + case @command when :list list_handler output @@ -41,11 +42,14 @@ class Stack < Handler end def create_handler - attrs = {} - attrs[:project] = options[:project] || resources_selector.select_available_project - attrs[:deploy_env] = options[:deploy_env] || resources_selector.select_available_env(attrs[:project]) - env = fetcher.fetch_project(attrs[:project])['deploy_envs'].detect {|env| env['identifier'] == attrs[:deploy_env]} - attrs[:provider] = env['provider'] + attrs = { + project: @project, + environment: @environment, + category: @category + } + category = fetcher.fetch_category(@project, @environment, @category) + attrs[:provider] = category['provider']['name'] + attrs[:provider_account] = category['provider']['account'] params_filepath = options[:parameters_file] || enter_parameter_or_empty(I18n.t('handler.stack.create.parameters_file')) if params_filepath.empty? @@ -61,14 +65,11 @@ class Stack < Handler attrs[:tags] = JSON.parse(File.read(tags_filepath)) end - attrs.merge!( - launch_options: { - without_bootstrap: options[:without_bootstrap] || false, - skip_rollback: options[:skip_rollback] || false, - } + json = JSON.pretty_generate( + without_bootstrap: options[:without_bootstrap] || false, + skip_rollback: options[:skip_rollback] || false, + stack_attributes: attrs ) - - json = JSON.pretty_generate(attrs) if question(I18n.t("handler.stack.question.create")) {puts json} job_ids = post_body "/stack", json reports_urls(job_ids) @@ -104,14 +105,8 @@ class Stack < Handler end def delete_handler - stack_id = @args[2] - r = inspect_parameters(@options_parser.delete_params, stack_id) - unless r.nil? - @options_parser.invalid_delete_command - abort(r) - end - if question(I18n.t("handler.stack.question.delete", name: stack_id)) - delete "/stack/#{stack_id}" + if question(I18n.t("handler.stack.question.delete", name: @stack)) + delete "/stack/#{@stack}" end end diff --git a/devops-client/lib/devops-client/handler/stack_template.rb b/devops-client/lib/devops-client/handler/stack_template.rb index 949afe6..292a9e2 100644 --- a/devops-client/lib/devops-client/handler/stack_template.rb +++ b/devops-client/lib/devops-client/handler/stack_template.rb @@ -14,10 +14,11 @@ class StackTemplate < Handler end def handle - @options, @args = @options_parser.parse_options_for!(current_command) - case current_command + @command, @options, @args = @options_parser.parse_options_for! + extract_parameters + case @command when :list - list_handler + list_handler(@provider) output when :show show_handler @@ -34,74 +35,45 @@ class StackTemplate < Handler end def create_handler - q = {} - q[:provider] = options[:provider] || resources_selector.select_available_provider - q[:id] = options[:id] || enter_parameter(I18n.t('handler.stack_template.create.id')) + q = { + id: options[:id], + provider: options[:provider], + provider_account: options[:provider_account] + } + q[:provider] ||= resources_selector.select_available_provider(except: ['static']) + q[:provider_account] ||= resources_selector.select_available_provider_account(q[:provider])['account_name'] + q[:id] ||= enter_parameter(I18n.t('handler.stack_template.create.id')) filepath = options[:template_file] || enter_parameter(I18n.t('handler.stack_template.create.template_file')) q[:template_body] = get_file_contents(filepath) - json = JSON.pretty_generate(q) - post_body("/stack_template/#{q[:provider]}", json) if question(I18n.t('handler.stack_template.question.create')){puts json} + if question(I18n.t('handler.stack_template.question.create')) { puts confirming_json(q) } + post_body("/stack_template/#{q[:provider]}", JSON.pretty_generate(q)) + end end def list_handler(provider=nil) - provider ||= @options[:given_provider] - @list = get_stack_templates(provider) - end - - def get_stack_templates(provider=nil) - if provider + @list = if provider provider_stack_templates(provider) else get("/stack_templates") end end - def show_handler - r = inspect_parameters @options_parser.show_params, @args[2] - unless r.nil? - @options_parser.invalid_show_command - abort(r) - end - @show = get "/stack_template/#{@args[2]}" + @show = get "/stack_template/#{@stack_template}" end def delete_handler - r = inspect_parameters(@options_parser.delete_params, @args[2]) - unless r.nil? - @options_parser.invalid_delete_command - abort(r) - end - if question(I18n.t("handler.stack_template.question.delete", name: @args[2])) - delete "/stack_template/#{@args[2]}" + if question(I18n.t("handler.stack_template.question.delete", name: @stack_template)) + delete "/stack_template/#{@stack_template}" end end - def update_url_handler - r = inspect_parameters @options_parser.update_url_params, @args[2] - unless r.nil? - @options_parser.invalid_update_url_command - abort(r) - end - stack_template = post "/stack_template/#{@args[2]}/update_template_url" - puts stack_template['template_url'] - end - - def update_available_parameters_handler - r = inspect_parameters @options_parser.update_url_params, @args[2] - unless r.nil? - @options_parser.invalid_update_url_command - abort(r) - end - stack_template = post "/stack_template/#{@args[2]}/update_available_parameters" - puts JSON.pretty_generate(stack_template['available_parameters']) - end + private def provider_stack_templates(provider) if Providers.has_functionality?(provider, :stack_templates) - @provider = true @list = get("/stack_templates/provider/#{provider}") else @options_parser.invalid_list_command @@ -109,4 +81,10 @@ class StackTemplate < Handler end end + def confirming_json(hash) + to_output = hash.dup + to_output[:template_body] = to_output[:template_body][0..40] + '…' + JSON.pretty_generate(to_output) + end + end diff --git a/devops-client/lib/devops-client/handler/tag.rb b/devops-client/lib/devops-client/handler/tag.rb deleted file mode 100644 index 92bfcec..0000000 --- a/devops-client/lib/devops-client/handler/tag.rb +++ /dev/null @@ -1,61 +0,0 @@ -require "devops-client/handler/handler" -require "devops-client/options/tag_options" -require "json" -require "devops-client/output/tag" - -class Tag < Handler - - output_with Output::Tag - - def initialize(host, def_options={}) - @host, @options = host, def_options - @options_parser = TagOptions.new(ARGV, def_options) - end - - def handle - current_command = ARGV[1].to_sym - @options, @args = @options_parser.parse_options_for!(current_command) - case current_command - when :list - list_handler - output - when :create - create_handler - when :delete - delete_handler - end - end - - def list_handler - r = inspect_parameters @options_parser.list_params, @args[2] - unless r.nil? - @options_parser.invalid_list_command - abort(r) - end - @list = get("/tags/#{@args[2]}") - end - - def create_handler - if @args.length == 3 - @options_parser.invalid_create_command - abort() - end - node = @args[2] - tags = @args[3..-1] - - post "/tags/#{node}", tags - end - - def delete_handler - if @args.length == 3 - @options_parser.invalid_delete_command - abort() - end - node = @args[2] - tags = @args[3..-1] - - if question(I18n.t("handler.user.question.delete", :name => tags.join("', '"), :node => node)) - delete "/tags/#{node}", tags - end - end -end diff --git a/devops-client/lib/devops-client/handler/user.rb b/devops-client/lib/devops-client/handler/user.rb index a2e994e..f73c02e 100644 --- a/devops-client/lib/devops-client/handler/user.rb +++ b/devops-client/lib/devops-client/handler/user.rb @@ -7,14 +7,15 @@ class User < Handler output_with Output::User def initialize(host, def_options={}) - @host, @options = host, def_options + @host = host + @options = def_options @options_parser = UserOptions.new(ARGV, def_options) end def handle - current_command = ARGV[1].to_sym - @options, @args = @options_parser.parse_options_for!(current_command) - case current_command + @command, @options, @args = @options_parser.parse_options_for! + extract_parameters + case @command when :list list_handler output @@ -22,12 +23,14 @@ class User < Handler create_handler when :delete delete_handler - when :grant - grant_handler when :password password_handler when :email email_handler + when :role_add + role_add_handler + when :role_delete + role_delete_handler end end @@ -36,75 +39,46 @@ class User < Handler end def create_handler - r = inspect_parameters @options_parser.create_params, @args[2], @args[3] - unless r.nil? - @options_parser.invalid_create_command - abort(r) - end - - password = self.options[:new_password] || enter_password(@args[2]) - q = { - "username" => @args[2], - "password" => password, - "email" => @args[3] + "id" => @user_name, + "email" => @email } - post "/user", q + q["password"] = self.options[:new_password] || enter_password(@user_name) + q["roles"] = resources_selector.select_available_roles + + json = JSON.pretty_generate(q) + post_body "/user", json if question(I18n.t("handler.user.question.create")){puts json} end def delete_handler - r = inspect_parameters @options_parser.delete_params, @args[2] - unless r.nil? - @options_parser.invalid_delete_command - abort(r) - end - - if question(I18n.t("handler.user.question.delete", :name => @args[2])) - delete "/user/#{@args[2]}" - end + delete "/user/#{@user_name}" if question(I18n.t("handler.user.question.delete", :name => @user_name)) end def password_handler - r = inspect_parameters @options_parser.password_params, @args[2] - unless r.nil? - @options_parser.invalid_password_command - abort(r) - end - - password = enter_password(@args[2]) q = { - "password" => password + "password" => enter_password(@user_name) } - put "/user/#{@args[2]}/password", q + put "/user/#{@user_name}/password", q end def email_handler - r = inspect_parameters @options_parser.email_params, @args[2], @args[3] - unless r.nil? - @options_parser.invalid_email_command - abort(r) - end q = { - "email" => @args[3] + "email" => @email } - put "/user/#{@args[2]}/email", q + put "/user/#{@user_name}/email", q end - def grant_handler - r = inspect_parameters @options_parser.grant_params, @args[2], @args[3], @args[4] - unless r.nil? - @options_parser.invalid_grant_command - abort(r) - end + def role_add_handler + user = get("/user/#{@user_name}") + q = resources_selector.select_available_roles(user["roles"]) + post "/user/#{@user_name}/roles/add", q + end - @args[3] = '' if @args[3].nil? - q = { - 'cmd' => @args[3], - 'privileges' => @args[4] - } - - put "/user/#{@args[2]}", q + def role_delete_handler + user = get("/user/#{@user_name}") + q = resources_selector.select_user_roles(user["roles"]) + post "/user/#{@user_name}/roles/delete", q end def enter_password user diff --git a/devops-client/lib/devops-client/options/bootstrap_templates_options.rb b/devops-client/lib/devops-client/options/bootstrap_templates_options.rb deleted file mode 100644 index 6e76fd8..0000000 --- a/devops-client/lib/devops-client/options/bootstrap_templates_options.rb +++ /dev/null @@ -1,13 +0,0 @@ -require "devops-client/options/common_options" - -class BootstrapTemplatesOptions < CommonOptions - - commands :list - - def initialize args, def_options - super(args, def_options) - self.header = I18n.t("headers.template") - self.banner_header = "templates" - end - -end diff --git a/devops-client/lib/devops-client/options/category_options.rb b/devops-client/lib/devops-client/options/category_options.rb new file mode 100644 index 0000000..41d5801 --- /dev/null +++ b/devops-client/lib/devops-client/options/category_options.rb @@ -0,0 +1,91 @@ +require "devops-client/options/common_options" +require "set" + +class CategoryOptions < CommonOptions + + commands :create, :delete, :deploy, :list, {:servers => [:list, :delete]}, :stacks, {:set => [:run_list]}, :show, :update + + COMMAND_NAME = "cat" + + def initialize args, def_options + super(args, def_options) + self.header = I18n.t("headers.category") + id = "PROJECT_ID" + env = "ENVIRONMENT" + cat = "CATEGORY" + self.list_params = [id, env] + self.show_params = [id, env, cat] + self.create_params = [id, env, cat] + self.delete_params = [id, env, cat] + self.deploy_params = [id, env, cat] + self.set_run_list_params = [id, env, cat, "[RUN_LIST...]"] + self.servers_list_params = [id, env, cat] + #self.stacks_params = [id, "[#{env}]", "[#{cat}]"] + self.update_params = [id, env, cat, "FILE"] + #self.delete_servers_params = [id, "[#{env}]"] + end + + def create_options + self.options do |parser, options| + parser.banner << self.create_banner + parser.resource_name = :project + + parser.recognize_option_value(:groups, variable: 'GROUP_1,GROUP_2...') do |groups| + options[:groups] = groups.split(",") + end + + parser.recognize_option_value(:file) do |file| + abort("File '#{file}' does not exist") unless File.exist?(file) + options[:file] = file + end + + parser.recognize_option_value(:subnets, variable: 'SUBNET_1,SUBNET_2...') do |subnets| + options[:subnets] = subnets.split(",") + end + + parser.recognize_option_value(:users, variable: 'USER_1,USER_2...') do |users| + options[:users] = Set.new(users.split(",")) + end + + parser.recognize_option_value(:run_list, variable: 'ROLE_1,ROLE_2...') do |run_list| + options[:run_list] = Set.new(run_list.split(",")).to_a + end + + parser.recognize_option_value(:deploy_env, option_key: :identifier) + parser.recognize_option_value(:flavor) + parser.recognize_option_value(:image) + parser.recognize_option_value(:run_list) + parser.recognize_option_value(:provider) + parser.recognize_option_value(:no_expires, type: :switch, switch_value: true, default: false) + parser.recognize_option_value(:expires) + + + # TODO: + # support short options names + # + # options[:file] = nil + # parser.on("-f", "--file FILE", I18n.t("options.project.create.file")) do |file| + # abort("File '#{file}' does not exist") unless File.exist?(file) + # options[:file] = file + # end + + end + end + + def deploy_options + options do |parser, options| + parser.banner << self.deploy_banner + + parser.recognize_option_value(:servers, resource_name: :project, command_name: 'deploy') do |servers| + options[:servers] = servers.split(",") + end + end + end + + def delete_servers_options + self.options do |parser, options| + parser.recognize_option_value(:dry_run, resource_name: :project, type: :switch, default: false, switch_value: true, command_name: 'delete_servers') + end + end + +end diff --git a/devops-client/lib/devops-client/options/chef_options.rb b/devops-client/lib/devops-client/options/chef_options.rb new file mode 100644 index 0000000..2d85e6c --- /dev/null +++ b/devops-client/lib/devops-client/options/chef_options.rb @@ -0,0 +1,19 @@ +require "devops-client/options/common_options" + +class ChefOptions < CommonOptions + + commands :bootstrap_templates, tags: [:list, :set, :unset] + + COMMAND_NAME = "chef" + + def initialize args, def_options + super(args, def_options) + self.header = I18n.t("headers.chef") + self.banner_header = COMMAND_NAME + self.bootstrap_templates_params = [] + self.tags_list_params = ["CM_NAME"] + self.tags_set_params = ["CM_NAME", "TAGS..."] + self.tags_unset_params = ["CM_NAME", "TAGS..."] + end + +end diff --git a/devops-client/lib/devops-client/options/common_options.rb b/devops-client/lib/devops-client/options/common_options.rb index 95bfc1f..748cea2 100644 --- a/devops-client/lib/devops-client/options/common_options.rb +++ b/devops-client/lib/devops-client/options/common_options.rb @@ -13,22 +13,27 @@ class CommonOptions CSV_FORMAT = "csv" OUTPUT_FROMATS = [TABLE_FORMAT, JSON_FORMAT, CSV_FORMAT] + # skip command name and subcommand def initialize args, def_options self.args = args self.default_options = def_options + self.banner_header = self.class::COMMAND_NAME end def self.commands *cmds - define_method :available_commands do - cmds - end - + acommands = [] cmds.each do |cmd| if cmd.is_a?(Hash) key = cmd.keys[0] + acommands.push key.to_sym cmd[key].each do |subcmd| create_command key.to_s, subcmd.to_s end + + define_method "#{key}_subcommands" do + cmd[key] + end + invalid_command_method = "invalid_#{key}_command" banner_method = "#{key}_banner" @@ -40,10 +45,19 @@ class CommonOptions cmd[key].map{|sc| self.send("#{key}_#{sc}_banner")}.join("") + "\n" end else + acommands.push cmd.to_sym create_command cmd.to_s + + define_method "#{cmd}_subcommands" do + [] + end end end + define_method :available_commands do + acommands + end + define_method "banners" do r = [] cmds.each do |cmd| @@ -107,13 +121,25 @@ class CommonOptions end end - # returns [options, args] because they are always needed together + # returns [command, options, args] because they are always needed together # will exit if operation is unsupported - def parse_options_for!(command) + def parse_options_for! + if !ARGV[1] || ARGV[1].start_with?("-") + invalid_command + end + command = ARGV[1].to_sym # available_commands method is defined dinamically in .commands method if available_commands.include?(command) + subcommands = send("#{command}_subcommands") + n = 2 + unless subcommands.empty? + n = 3 + return invalid_command if !ARGV[2] || !subcommands.include?(ARGV[2].to_sym) + command = "#{command}_#{ARGV[2]}".to_sym + end + options = send("#{command}_options") - [options, args] + [command, options, args[n..-1]] else invalid_command end diff --git a/devops-client/lib/devops-client/options/deploy_options.rb b/devops-client/lib/devops-client/options/deploy_options.rb index 27faff1..49c7c14 100644 --- a/devops-client/lib/devops-client/options/deploy_options.rb +++ b/devops-client/lib/devops-client/options/deploy_options.rb @@ -4,6 +4,8 @@ class DeployOptions < CommonOptions attr_accessor :deploy_params + COMMAND_NAME = "deploy" + def initialize args, def_options super(args, def_options) self.header = I18n.t("headers.deploy") diff --git a/devops-client/lib/devops-client/options/environment_options.rb b/devops-client/lib/devops-client/options/environment_options.rb new file mode 100644 index 0000000..6e4d256 --- /dev/null +++ b/devops-client/lib/devops-client/options/environment_options.rb @@ -0,0 +1,108 @@ +require "devops-client/options/common_options" +require "set" + +class EnvironmentOptions < CommonOptions + + commands :create, :delete, :deploy, :list, {:servers => [:list, :delete]}, :stacks, {:set => [:run_list]}, :show, :update, {:user => [:add, :delete]} + + COMMAND_NAME = "env" + + def initialize args, def_options + super(args, def_options) + self.header = I18n.t("headers.environment") + id = "PROJECT_ID" + env = "ENVIRONMENT" + self.list_params = [id] + self.show_params = [id, env] + self.create_params = [id, env] + self.delete_params = [id, env] + self.deploy_params = [id, env] + self.set_run_list_params = [id, env, "[RUN_LIST...]"] + self.servers_list_params = [id, env] + #self.stacks_params = [id, "[#{env}]", "[#{cat}]"] + self.update_params = [id, env, "FILE"] + self.user_add_params = [id, env] + self.user_delete_params = [id, env, "USER..."] + #self.delete_servers_params = [id, "[#{env}]"] + end + + def create_options + self.options do |parser, options| + parser.banner << self.create_banner + parser.resource_name = :project + + parser.recognize_option_value(:groups, variable: 'GROUP_1,GROUP_2...') do |groups| + options[:groups] = groups.split(",") + end + + parser.recognize_option_value(:file) do |file| + abort("File '#{file}' does not exist") unless File.exist?(file) + options[:file] = file + end + + parser.recognize_option_value(:subnets, variable: 'SUBNET_1,SUBNET_2...') do |subnets| + options[:subnets] = subnets.split(",") + end + + parser.recognize_option_value(:users, variable: 'USER_1,USER_2...') do |users| + options[:users] = Set.new(users.split(",")) + end + + parser.recognize_option_value(:run_list, variable: 'ROLE_1,ROLE_2...') do |run_list| + options[:run_list] = Set.new(run_list.split(",")).to_a + end + + parser.recognize_option_value(:deploy_env, option_key: :identifier) + parser.recognize_option_value(:flavor) + parser.recognize_option_value(:image) + parser.recognize_option_value(:run_list) + parser.recognize_option_value(:provider) + parser.recognize_option_value(:no_expires, type: :switch, switch_value: true, default: false) + parser.recognize_option_value(:expires) + + + # TODO: + # support short options names + # + # options[:file] = nil + # parser.on("-f", "--file FILE", I18n.t("options.project.create.file")) do |file| + # abort("File '#{file}' does not exist") unless File.exist?(file) + # options[:file] = file + # end + + end + end + + def user_add_options + self.options do |parser, options| + parser.banner << self.user_add_banner + + parser.recognize_option_value(:deploy_env, resource_name: :project, command_name: 'user_add') + end + end + + def user_delete_options + self.options do |parser, options| + parser.banner << self.user_delete_banner + + parser.recognize_option_value(:deploy_env, resource_name: :project, command_name: 'user_delete') + end + end + + def deploy_options + options do |parser, options| + parser.banner << self.deploy_banner + + parser.recognize_option_value(:servers, resource_name: :project, command_name: 'deploy') do |servers| + options[:servers] = servers.split(",") + end + end + end + + def delete_servers_options + self.options do |parser, options| + parser.recognize_option_value(:dry_run, resource_name: :project, type: :switch, default: false, switch_value: true, command_name: 'delete_servers') + end + end + +end diff --git a/devops-client/lib/devops-client/options/filter_options.rb b/devops-client/lib/devops-client/options/filter_options.rb index 0992eb8..88800c4 100644 --- a/devops-client/lib/devops-client/options/filter_options.rb +++ b/devops-client/lib/devops-client/options/filter_options.rb @@ -5,13 +5,14 @@ class FilterOptions < CommonOptions commands :image => [:add, :delete, :list] + COMMAND_NAME = "filter" + def initialize args, def_options super(args, def_options) self.header = I18n.t("headers.filters") - self.banner_header = "filter" p = "PROVIDER" self.image_list_params = [p] - i = "IMAGE [IMAGE ...]" + i = "IMAGE..." self.image_add_params = [p, i] self.image_delete_params = [p, i] end diff --git a/devops-client/lib/devops-client/options/flavor_options.rb b/devops-client/lib/devops-client/options/flavor_options.rb deleted file mode 100644 index e298cd4..0000000 --- a/devops-client/lib/devops-client/options/flavor_options.rb +++ /dev/null @@ -1,19 +0,0 @@ -require "optparse" -require "devops-client/options/common_options" - -class FlavorOptions < CommonOptions - - commands :list - - def initialize args, def_options - super(args, def_options) - self.header = I18n.t("headers.flavor") - self.banner_header = "flavor" - self.list_params = ["PROVIDER"] - end - - extend_options_method :list_options do |options| - options[:given_provider] = args[2] if args[2] - end - -end diff --git a/devops-client/lib/devops-client/options/group_options.rb b/devops-client/lib/devops-client/options/group_options.rb deleted file mode 100644 index fa23881..0000000 --- a/devops-client/lib/devops-client/options/group_options.rb +++ /dev/null @@ -1,19 +0,0 @@ -require "optparse" -require "devops-client/options/common_options" - -class GroupOptions < CommonOptions - - commands :list - - def initialize args, def_options - super(args, def_options) - self.header = I18n.t("headers.group") - self.banner_header = "group" - self.list_params = ["PROVIDER", "[VPC-ID]"] - end - - extend_options_method :list_options do |options| - options[:given_provider] = args[2] if args[2] - end - -end diff --git a/devops-client/lib/devops-client/options/image_options.rb b/devops-client/lib/devops-client/options/image_options.rb index d217b78..4b12086 100644 --- a/devops-client/lib/devops-client/options/image_options.rb +++ b/devops-client/lib/devops-client/options/image_options.rb @@ -4,13 +4,15 @@ class ImageOptions < CommonOptions commands :create, :delete, :list, :show, :update + COMMAND_NAME = "image" + def initialize args, def_options super(args, def_options) self.header = I18n.t("headers.image") - self.banner_header = "image" - self.list_params = ["[provider]", "[ec2|openstack]"] + self.list_params = ["[PROVIDER]"] self.show_params = ["IMAGE"] - self.delete_params = ["IMAGE"] + self.create_params = [] + self.delete_params = ["IMAGE" ] self.update_params = ["IMAGE", "FILE"] end @@ -20,10 +22,9 @@ class ImageOptions < CommonOptions parser.resource_name = :image parser.recognize_option_value(:provider) + parser.recognize_option_value(:provider_account) parser.recognize_option_value(:image_id) parser.recognize_option_value(:ssh_username) - parser.recognize_option_value(:bootstrap_template) - parser.recognize_option_value(:no_bootstrap_template, type: :switch, switch_value: true, default: false) end end diff --git a/devops-client/lib/devops-client/options/key_options.rb b/devops-client/lib/devops-client/options/key_options.rb index f1cf490..a93331d 100644 --- a/devops-client/lib/devops-client/options/key_options.rb +++ b/devops-client/lib/devops-client/options/key_options.rb @@ -3,10 +3,12 @@ require "devops-client/options/common_options" class KeyOptions < CommonOptions commands :add, :delete, :list + COMMAND_NAME = "key" + def initialize args, def_options super(args, def_options) self.header = I18n.t("headers.key") - self.banner_header = "key" + self.list_params = [] self.add_params = ["KEY_NAME", "FILE"] self.delete_params = ["KEY_NAME"] end diff --git a/devops-client/lib/devops-client/options/main.rb b/devops-client/lib/devops-client/options/main.rb index 04811b8..d8986cc 100644 --- a/devops-client/lib/devops-client/options/main.rb +++ b/devops-client/lib/devops-client/options/main.rb @@ -2,42 +2,49 @@ require "optparse" require "devops-client/options/server_options" require "devops-client/options/image_options" require "devops-client/options/project_options" +require "devops-client/options/environment_options" +require "devops-client/options/category_options" require "devops-client/options/provider_options" -require "devops-client/options/flavor_options" require "devops-client/options/common_options" -require "devops-client/options/group_options" require "devops-client/options/deploy_options" require "devops-client/options/key_options" require "devops-client/options/user_options" -require "devops-client/options/tag_options" require "devops-client/options/script_options" require "devops-client/options/filter_options" -require "devops-client/options/network_options" -require "devops-client/options/bootstrap_templates_options" +require "devops-client/options/chef_options" +require "devops-client/options/role_options" +require "devops-client/options/stack_template_options" +require "devops-client/options/stack_options" class Main < CommonOptions + COMMAND_NAME = "" + def initialize args, def_options super(args, def_options) end def info o = nil + classes = [ + ChefOptions, + DeployOptions, + FilterOptions, + ImageOptions, + KeyOptions, + ProjectOptions, + EnvironmentOptions, + CategoryOptions, + ProviderOptions, + ScriptOptions, + ServerOptions, + UserOptions, + RoleOptions, + StackTemplateOptions, + StackOptions + ] options do |opts, options| - opts.banner << BootstrapTemplatesOptions.new(ARGV, default_options).error_banner - opts.banner << DeployOptions.new(ARGV, default_options).error_banner - opts.banner << FilterOptions.new(ARGV, default_options).error_banner - opts.banner << FlavorOptions.new(ARGV, default_options).error_banner - opts.banner << GroupOptions.new(ARGV, default_options).error_banner - opts.banner << ImageOptions.new(ARGV, default_options).error_banner - opts.banner << KeyOptions.new(ARGV, default_options).error_banner - opts.banner << NetworkOptions.new(ARGV, default_options).error_banner - opts.banner << ProjectOptions.new(ARGV, default_options).error_banner - opts.banner << ProviderOptions.new(ARGV, default_options).error_banner - opts.banner << ScriptOptions.new(ARGV, default_options).error_banner - opts.banner << ServerOptions.new(ARGV, default_options).error_banner - opts.banner << TagOptions.new(ARGV, default_options).error_banner - opts.banner << UserOptions.new(ARGV, default_options).error_banner + classes.each {|cl| opts.banner << cl.new(ARGV, default_options).error_banner} o = opts end puts(o.banner + "\n") diff --git a/devops-client/lib/devops-client/options/network_options.rb b/devops-client/lib/devops-client/options/network_options.rb deleted file mode 100644 index 8e527b9..0000000 --- a/devops-client/lib/devops-client/options/network_options.rb +++ /dev/null @@ -1,20 +0,0 @@ -require "optparse" -require "devops-client/options/common_options" - -class NetworkOptions < CommonOptions - - commands :list - - def initialize args, def_options - super(args, def_options) - self.header = I18n.t("headers.network") - self.banner_header = "network" - self.list_params = ["PROVIDER"] - end - - extend_options_method :list_options do |options| - options[:given_provider] = args[2] if args[2] - end - -end - diff --git a/devops-client/lib/devops-client/options/project_options.rb b/devops-client/lib/devops-client/options/project_options.rb index 64727e0..eeedc29 100644 --- a/devops-client/lib/devops-client/options/project_options.rb +++ b/devops-client/lib/devops-client/options/project_options.rb @@ -3,27 +3,26 @@ require "set" class ProjectOptions < CommonOptions - commands :create, :delete, :deploy, :list, {:multi => [:create]}, :servers, :stacks, {:set => [:run_list]}, :show, :test, :update, {:user => [:add, :delete]}, :delete_servers + commands :create, :delete, :list, {:set => [:run_list]}, :show, :update, {:user => [:add, :delete]}, :archive, :unarchive + + COMMAND_NAME = "project" def initialize args, def_options super(args, def_options) self.header = I18n.t("headers.project") - self.banner_header = "project" id = "PROJECT_ID" - env = "DEPLOY_ENV" + env = "ENVIRONMENT" + cat = "CATEGORY" + self.list_params = [] self.show_params = [id] + self.archive_params = [id] + self.unarchive_params = [id] self.create_params = [id] - self.delete_params = [id, "[#{env}]"] - self.deploy_params = [id, "[#{env}]"] - self.set_run_list_params = [id, env, "[(recipe[mycookbook::myrecipe])|(role[myrole]) ...]"] - self.servers_params = [id, "[#{env}]"] - self.stacks_params = [id, "[#{env}]"] - self.multi_create_params = [id] + self.delete_params = [id] + self.set_run_list_params = [id, "[RUN_LIST...]"] self.update_params = [id, "FILE"] - self.user_add_params = [id, "USER_NAME", "[USER_NAME ...]"] - self.user_delete_params = [id, "USER_NAME", "[USER_NAME ...]"] - self.test_params = [id, env] - self.delete_servers_params = [id, "[#{env}]"] + self.user_add_params = [id, "USER_NAME..."] + self.user_delete_params = [id, "USER_NAME..."] end def create_options @@ -31,31 +30,22 @@ class ProjectOptions < CommonOptions parser.banner << self.create_banner parser.resource_name = :project - parser.recognize_option_value(:groups, variable: 'GROUP_1,GROUP_2...') do |groups| - options[:groups] = groups.split(",") - end - parser.recognize_option_value(:file) do |file| abort("File '#{file}' does not exist") unless File.exist?(file) options[:file] = file end - parser.recognize_option_value(:subnets, variable: 'SUBNET_1,SUBNET_2...') do |subnets| - options[:subnets] = subnets.split(",") + parser.recognize_option_value(:description) do |description| + options[:description] = description end - parser.recognize_option_value(:users, variable: 'USER_1,USER_2...') do |users| - options[:users] = Set.new(users.split(",")) + parser.recognize_option_value(:project_users, variable: 'USER_1,USER_2...') do |users| + options[:users] = Set.new(users.split(",")).to_a end - parser.recognize_option_value(:deploy_env, option_key: :identifier) - parser.recognize_option_value(:flavor) - parser.recognize_option_value(:image) - parser.recognize_option_value(:run_list) - parser.recognize_option_value(:provider) - parser.recognize_option_value(:no_expires, type: :switch, switch_value: true, default: false) - parser.recognize_option_value(:expires) - + parser.recognize_option_value(:run_list, variable: 'ROLE_1,ROLE_2...') do |run_list| + options[:run_list] = Set.new(run_list.split(",")).to_a + end # TODO: # support short options names @@ -69,36 +59,4 @@ class ProjectOptions < CommonOptions end end - def user_add_options - self.options do |parser, options| - parser.banner << self.user_add_banner - - parser.recognize_option_value(:deploy_env, resource_name: :project, command_name: 'user_add') - end - end - - def user_delete_options - self.options do |parser, options| - parser.banner << self.user_delete_banner - - parser.recognize_option_value(:deploy_env, resource_name: :project, command_name: 'user_delete') - end - end - - def deploy_options - options do |parser, options| - parser.banner << self.deploy_banner - - parser.recognize_option_value(:servers, resource_name: :project, command_name: 'deploy') do |servers| - options[:servers] = servers.split(",") - end - end - end - - def delete_servers_options - self.options do |parser, options| - parser.recognize_option_value(:dry_run, resource_name: :project, type: :switch, default: false, switch_value: true, command_name: 'delete_servers') - end - end - end diff --git a/devops-client/lib/devops-client/options/provider_options.rb b/devops-client/lib/devops-client/options/provider_options.rb index adbf7c1..2b645de 100644 --- a/devops-client/lib/devops-client/options/provider_options.rb +++ b/devops-client/lib/devops-client/options/provider_options.rb @@ -3,14 +3,35 @@ require "devops-client/options/common_options" class ProviderOptions < CommonOptions - commands :list, :accounts + commands :list, :accounts, {:account => [:create, :delete]}, :flavors, :images, :networks, :security_groups, :vpc + + COMMAND_NAME = "provider" def initialize args, def_options super(args, def_options) self.header = I18n.t("headers.provider") - self.banner_header = "provider" self.list_params = [] self.accounts_params = %w(PROVIDER) + self.account_create_params = %w(PROVIDER ACCOUNT) + self.account_delete_params = %w(PROVIDER ACCOUNT) + self.flavors_params = %w(PROVIDER ACCOUNT) + self.images_params = %w(PROVIDER ACCOUNT) + self.networks_params = %w(PROVIDER ACCOUNT) + self.security_groups_params = %w(PROVIDER ACCOUNT [VPC_ID]) + self.vpc_params = %w(PROVIDER ACCOUNT) + end + + def account_create_options + self.options do |parser, options| + parser.banner << self.account_create_banner + parser.resource_name = "provider.account" + + parser.recognize_option_value(:description) + parser.recognize_option_value(:ssh_key) + parser.recognize_option_value(:use_iam_profile, type: :switch, switch_value: true, default: false) + parser.recognize_option_value(:access_key_id) + parser.recognize_option_value(:secret_access_key) + end end end diff --git a/devops-client/lib/devops-client/options/role_options.rb b/devops-client/lib/devops-client/options/role_options.rb new file mode 100644 index 0000000..da96262 --- /dev/null +++ b/devops-client/lib/devops-client/options/role_options.rb @@ -0,0 +1,28 @@ +require "devops-client/options/common_options" + +class RoleOptions < CommonOptions + commands :list, :policies, :create, :delete, :show + + COMMAND_NAME = "role" + + def initialize args, def_options + super(args, def_options) + self.header = I18n.t("headers.role") + self.policies_params = [] + self.create_params = ["ROLE_NAME"] + self.delete_params = ["ROLE_NAME"] + self.show_params = ["ROLE_NAME"] + self.list_params = [] + end + +=begin + def create_options + self.options do |parser, options| + parser.banner << self.create_banner + + parser.recognize_option_value(:new_password, resource_name: :user) + end + end +=end + +end diff --git a/devops-client/lib/devops-client/options/script_options.rb b/devops-client/lib/devops-client/options/script_options.rb index 42ffe54..f4394e8 100644 --- a/devops-client/lib/devops-client/options/script_options.rb +++ b/devops-client/lib/devops-client/options/script_options.rb @@ -4,10 +4,11 @@ class ScriptOptions < CommonOptions commands :list, :add, :delete, :run, :command + COMMAND_NAME = "script" + def initialize args, def_options super(args, def_options) self.header = I18n.t("headers.script") - self.banner_header = "script" sname = "SCRIPT_NAME" self.add_params = [sname, "FILE"] self.delete_params = [sname] diff --git a/devops-client/lib/devops-client/options/server_options.rb b/devops-client/lib/devops-client/options/server_options.rb index 0bb0f87..f1bb7b0 100644 --- a/devops-client/lib/devops-client/options/server_options.rb +++ b/devops-client/lib/devops-client/options/server_options.rb @@ -2,23 +2,26 @@ require "devops-client/options/common_options" class ServerOptions < CommonOptions - commands :add, :bootstrap, :create, :delete, :list, :pause, :reserve, :show, :unpause, :unreserve, :delete_list, :add_and_bootstrap_list + commands :add, :bootstrap, :create, :delete, :list, :pause, :reserve, :show, :unpause, :unreserve, :unbootstrap, :delete_list, :add_and_bootstrap_list + + COMMAND_NAME = "server" def initialize args, def_options super(args, def_options) self.header = I18n.t("headers.server") - self.banner_header = "server" - self.list_params = ["[chef|ec2|openstack|static]"] - self.create_params = ["PROJECT_ID", "DEPLOY_ENV"] - node_params = ["NODE_NAME"] - self.delete_params = node_params - self.show_params = node_params - self.pause_params = node_params - self.unpause_params = node_params - self.reserve_params = node_params - self.unreserve_params = node_params - self.bootstrap_params = ["INSTANCE_ID"] - self.add_params = ["PROJECT_ID", "DEPLOY_ENV", "IP", "SSH_USER", "KEY_ID"] + self.list_params = ["[PROVIDER]"] + self.create_params = ["PROJECT", "ENVIRONMENT", "CATEGORY"] + server_id = ["SERVER_ID"] + self.delete_list_params = ["SERVER_ID..."] + self.delete_params = server_id + self.show_params = server_id + self.pause_params = server_id + self.unpause_params = server_id + self.reserve_params = server_id + self.unreserve_params = server_id + self.bootstrap_params = server_id + self.unbootstrap_params = server_id + self.add_params = ["PROJECT", "ENVIRONMENT", "CATEGORY", "IP", "SSH_USER", "KEY_ID", "NAME"] self.add_and_bootstrap_list_params = ["PROJECT_ID", "DEPLOY_ENV", "SSH_USER", "KEY_ID", "IPS_FILE"] end @@ -28,13 +31,6 @@ class ServerOptions < CommonOptions parser.resource_name = :server parser.command_name = :delete - parser.recognize_option_value(:instance, - type: :switch, - default: 'node', - switch_value: 'instance', - option_key: :key - ) - parser.recognize_option_value(:no_ask, type: :switch, default: false, @@ -43,66 +39,6 @@ class ServerOptions < CommonOptions end end - def pause_options - options do |parser, options| - parser.banner << self.delete_banner - - parser.recognize_option_value(:instance, - resource_name: :server, - command_name: :pause, - type: :switch, - default: 'node', - switch_value: 'instance', - option_key: :key - ) - end - end - - def unpause_options - options do |parser, options| - parser.banner << self.delete_banner - - parser.recognize_option_value(:instance, - resource_name: :server, - command_name: :unpause, - type: :switch, - default: 'node', - switch_value: 'instance', - option_key: :key - ) - end - end - - def reserve_options - options do |parser, options| - parser.banner << self.delete_banner - - parser.recognize_option_value(:instance, - resource_name: :server, - command_name: :reserve, - type: :switch, - default: 'node', - switch_value: 'instance', - option_key: :key - ) - end - end - - def unreserve_options - options do |parser, options| - parser.banner << self.delete_banner - - parser.recognize_option_value(:instance, - resource_name: :server, - command_name: :unreserve, - type: :switch, - default: 'node', - switch_value: 'instance', - option_key: :key - ) - end - end - def create_options options do |parser, options| parser.banner << self.create_banner @@ -117,15 +53,22 @@ class ServerOptions < CommonOptions ) parser.recognize_option_value(:name, short: '-N') - parser.recognize_option_value(:force, short: '-f') - parser.recognize_option_value(:key) + parser.recognize_option_value('skip_rollback', + type: :switch, + switch_value: true, + option_key: :skip_rollback, + i18n_scope: 'create' + ) + parser.recognize_option_value(:ssh_key) +=begin parser.recognize_option_value(:groups, short: '-G', variable: 'GROUP_1,GROUP_2...' ) do |groups| options[:groups] = groups.split(",") end +=end parser.recognize_option_value(:private_ip, i18n_scope: 'create') @@ -142,7 +85,7 @@ class ServerOptions < CommonOptions parser.resource_name = :server parser.command_name = :bootstrap - parser.recognize_option_value(:name, short: '-N') + parser.recognize_option_value(:cm_name, short: '-N') parser.recognize_option_value(:bootstrap_template) parser.recognize_option_value(:run_list) do |list| options[:run_list] = list.split(",") @@ -168,12 +111,4 @@ class ServerOptions < CommonOptions end end - def delete_banner - self.banner_header + " delete NODE_NAME [NODE_NAME ...]\n" - end - - def delete_list_banner - self.banner_header + " delete_list INSTANCE_ID [INSTANCE_ID ...]\n" - end - end diff --git a/devops-client/lib/devops-client/options/stack_options.rb b/devops-client/lib/devops-client/options/stack_options.rb index a33a33a..5092a5c 100644 --- a/devops-client/lib/devops-client/options/stack_options.rb +++ b/devops-client/lib/devops-client/options/stack_options.rb @@ -4,12 +4,14 @@ class StackOptions < CommonOptions commands :create, :delete, :list, :show, :sync, :deploy, :reserve, :unreserve, :change_stack_template + COMMAND_NAME = "stack" + def initialize args, def_options super(args, def_options) self.header = I18n.t("headers.stack") - self.banner_header = "stack" - self.list_params = ["[provider]", "[ec2|openstack]"] + self.list_params = ["[provider]"] self.show_params = ["STACK"] + self.create_params = %w(PROJECT ENVIRONMENT CATEGORY) self.delete_params = ["STACK"] self.sync_params = ["STACK"] self.deploy_params = ["STACK"] @@ -22,11 +24,7 @@ class StackOptions < CommonOptions parser.banner << self.create_banner parser.resource_name = :stack - parser.recognize_option_value(:provider) parser.recognize_option_value(:id) - parser.recognize_option_value(:project) - parser.recognize_option_value(:deploy_env) - parser.recognize_option_value(:stack_template) parser.recognize_option_value(:parameters_file) parser.recognize_option_value(:tags_file) parser.recognize_option_value(:run_list) diff --git a/devops-client/lib/devops-client/options/stack_template_options.rb b/devops-client/lib/devops-client/options/stack_template_options.rb index 6f2fb08..be32c95 100644 --- a/devops-client/lib/devops-client/options/stack_template_options.rb +++ b/devops-client/lib/devops-client/options/stack_template_options.rb @@ -2,17 +2,17 @@ require "devops-client/options/common_options" class StackTemplateOptions < CommonOptions - commands :create, :delete, :list, :show, :update_url, :update_available_parameters + commands :create, :delete, :list, :show + + COMMAND_NAME = "stack_template" def initialize args, def_options super(args, def_options) self.header = I18n.t("headers.stack_template") self.banner_header = "stack_template" - self.list_params = ["[provider]", "[ec2|openstack]"] + self.list_params = ["[provider]"] self.show_params = ["STACK_TEMPLATE"] self.delete_params = ["STACK_TEMPLATE"] - self.update_url_params = ["STACK_TEMPLATE"] - self.update_available_parameters_params = ["STACK_TEMPLATE"] end def create_options @@ -21,17 +21,9 @@ class StackTemplateOptions < CommonOptions parser.resource_name = :stack_template parser.recognize_option_value(:provider, default: nil) + parser.recognize_option_value(:provider_account, default: nil) parser.recognize_option_value(:id) parser.recognize_option_value(:template_file) end end - - extend_options_method :list_options do |options| - if args[2] == "provider" and args[3] - options[:given_provider] = args[3] - elsif args[2] - options[:given_provider] = args[2] - end - end - end diff --git a/devops-client/lib/devops-client/options/tag_options.rb b/devops-client/lib/devops-client/options/tag_options.rb deleted file mode 100644 index 5494071..0000000 --- a/devops-client/lib/devops-client/options/tag_options.rb +++ /dev/null @@ -1,14 +0,0 @@ -require "devops-client/options/common_options" - -class TagOptions < CommonOptions - commands :create, :delete, :list - - def initialize args, def_options - super(args, def_options) - self.header = I18n.t("headers.tag") - self.banner_header = "tag" - self.create_params = ["NODE_NAME", "TAG_NAME", "[TAG_NAME ...]"] - self.delete_params = ["NODE_NAME", "TAG_NAME", "[TAG_NAME ...]"] - self.list_params = ["NODE_NAME"] - end -end diff --git a/devops-client/lib/devops-client/options/user_options.rb b/devops-client/lib/devops-client/options/user_options.rb index f6da6e0..9017f19 100644 --- a/devops-client/lib/devops-client/options/user_options.rb +++ b/devops-client/lib/devops-client/options/user_options.rb @@ -1,17 +1,20 @@ require "devops-client/options/common_options" class UserOptions < CommonOptions - commands :create, :delete, :grant, :list, :password, :email + commands :create, :delete, :list, :password, :email, role: [:add, :delete] + + COMMAND_NAME = "user" def initialize args, def_options super(args, def_options) self.header = I18n.t("headers.user") - self.banner_header = "user" + self.list_params = [] self.create_params = ["USER_NAME", "EMAIL"] self.delete_params = ["USER_NAME"] self.password_params = ["USER_NAME"] self.email_params = ["USER_NAME", "EMAIL"] - self.grant_params = ["USER_NAME", "[COMMAND]", "[PRIVILEGES]"] + self.role_add_params = ["USER_NAME"] + self.role_delete_params = ["USER_NAME"] end def create_options diff --git a/devops-client/lib/devops-client/output/base.rb b/devops-client/lib/devops-client/output/base.rb index 8a9d7ec..2aee7dc 100644 --- a/devops-client/lib/devops-client/output/base.rb +++ b/devops-client/lib/devops-client/output/base.rb @@ -50,19 +50,18 @@ module Output headers.unshift(I18n.t("output.table_header.number")) rows.each_with_index {|row, i| row.unshift(i + 1)} end - table = Terminal::Table.new do |t| + Terminal::Table.new do |t| titles = ["#{I18n.t("output.table_header.api_version")}: #{self.options[:api]}", "#{title}" ] t.title = titles.join( "\n" ) t.headings = headers - t.add_row rows[0] - rows[1..-1].each do |r| + t.add_row rows[0] if rows[0] + (rows[1..-1] || []).each do |r| t.add_separator if separator t.add_row r end end - table end def create_csv headers, rows, with_num=true, separator=":" @@ -72,7 +71,16 @@ module Output end c = CSV.new("", {col_sep: separator, headers: true}) c << headers - rows.each{|r| c << r} + rows.each do |r| + row = r.map do |cell| + if cell.is_a?(Array) + cell.join(',') + else + cell.to_s + end + end + c << row + end c.string end @@ -85,12 +93,27 @@ module Output [headers, rows] end + def headers_and_rows_for_array(records, field) + headers = [ I18n.t("output.table_header.#{field}") ] + rows = records.map{|record| [ record ] } + [headers, rows] + end + + def time_to_s value + return "null" if value.nil? + Time.at(value).to_s + end + private def shrink_cells_if_width_exceeded(rows) rows.each do |row| row.each_with_index do |cell, i| - row[i] = split_to_parts_of_size(cell, MAX_CELL_WIDTH) + if cell.is_a?(String) + row[i] = split_to_parts_of_size(cell, MAX_CELL_WIDTH) + elsif cell.is_a?(Array) + row[i] = cell.join("\n") + end end end end diff --git a/devops-client/lib/devops-client/output/bootstrap_templates.rb b/devops-client/lib/devops-client/output/bootstrap_templates.rb deleted file mode 100644 index b1ffeef..0000000 --- a/devops-client/lib/devops-client/output/bootstrap_templates.rb +++ /dev/null @@ -1,29 +0,0 @@ -require "devops-client/output/base" - -module Output - class BootstrapTemplates < Base - - def table - headers, rows = create(@data) - create_table(headers, rows, I18n.t("output.title.bootstrap_template.list")) - end - - def csv - headers, rows = create(@data) - create_csv(headers, rows) - end - - private - def create list - abort I18n.t("output.not_found.bootstrap_template.list") if list.nil? or list.empty? - headers = [ I18n.t("output.table_header.name") ] - rows = [] - list.each do |l| - rows.push [ l ] - end - return headers, rows - end - - end -end - diff --git a/devops-client/lib/devops-client/output/category.rb b/devops-client/lib/devops-client/output/category.rb new file mode 100644 index 0000000..3adcdd6 --- /dev/null +++ b/devops-client/lib/devops-client/output/category.rb @@ -0,0 +1,96 @@ +require "devops-client/output/base" + +module Output + class Category < Base + + def table + title = nil + with_separator = false + headers, rows = case options[:current_command] + when :show + title = I18n.t("output.title.category.show", env: options[:env], project: options[:project], cat: options[:cat]) + with_separator = true + create_show(@data) + when :servers_list + title = I18n.t("output.title.environment.servers", env: options[:env], project: options[:project]) + create_servers(@data) + when :stacks + title = I18n.t("output.title.environment.stacks", env: options[:env], project: options[:project]) + create_stacks(@data) + else + title = I18n.t("output.title.category.list", project: options[:project]) + create_list(@data) + end + create_table(headers, rows, title, with_num?, with_separator) + end + + def csv + with_num = true + headers, rows = case @additional_options[:current_command] + when :list + create_list(@data) + when :show + with_num = false + create_show(@data) + when :servers + create_servers(@data) + when :test + create_test(@data) + end + create_csv(headers, rows, with_num?) + end + + private + def create_list list + abort(I18n.t("output.not_found.category.list")) if list.empty? + rows = list.map {|l| [l["id"]]} + headers = [ I18n.t("output.table_header.id") ] + return headers, rows + end + + def create_show show + provider = show["provider"] + cm_tool = show["cm_tool"] + row = [ + show["id"], + provider["name"], + provider["account"], + provider["flavor"], + provider["image"], + provider["security_groups"], + provider["vpc_id"], + provider["subnet"], + cm_tool["name"], + cm_tool["bootstrap_template"] + ] + headers = [ + I18n.t("output.table_header.id"), + I18n.t("output.table_header.provider"), + I18n.t("output.table_header.provider_account"), + I18n.t("output.table_header.flavor"), + I18n.t("output.table_header.image"), + I18n.t("output.table_header.security_groups"), + I18n.t("output.table_header.vpc_id"), + I18n.t("output.table_header.subnet"), + I18n.t("output.table_header.cm_tool"), + I18n.t("output.table_header.bootstrap_template") + ] + return headers, [row] + end + + def create_servers servers + abort(I18n.t("output.not_found.category.servers", env: options[:env], project: options[:project])) if servers.empty? + fields_to_output = %w(id name project environment category cm_name remote_user provider) + headers_and_rows(servers, fields_to_output) + end + + def create_stacks(stacks) + abort(I18n.t("output.not_found.environment.stacks", env: options[:env], project: options[:project])) if stacks.empty? + + fields_to_output = %w(id deploy_env stack_template cloud_stack_id stack_status provider) + + headers_and_rows(stacks, fields_to_output) + end + + end +end diff --git a/devops-client/lib/devops-client/output/chef.rb b/devops-client/lib/devops-client/output/chef.rb new file mode 100644 index 0000000..85148e7 --- /dev/null +++ b/devops-client/lib/devops-client/output/chef.rb @@ -0,0 +1,44 @@ +require "devops-client/output/base" + +module Output + class Chef < Base + + def table + headers, rows, title = nil, nil, nil + case options[:current_command] + when :bootstrap_templates + headers, rows = create_bootstrap_templates + title = "output.title.chef.bootstrap_templates" + when :tags_list + headers, rows = create_tags_list + title = "output.title.chef.tags_list" + end + create_table(headers, rows, I18n.t(title)) + end + + def csv + headers, rows = nil, nil + case options[:current_command] + when :bootstrap_templates + headers, rows = create_bootstrap_templates + when :tags_list + headers, rows = create_tags_list + end + + create_csv(headers, rows) + end + + private + def create_bootstrap_templates + abort I18n.t("output.not_found.chef.bootstrap_template") if @data.empty? + headers_and_rows_for_array(@data, "name") + end + + def create_tags_list + abort I18n.t("output.not_found.chef.tags_list") if @data.empty? + headers_and_rows_for_array(@data, "name") + end + + end +end + diff --git a/devops-client/lib/devops-client/output/environment.rb b/devops-client/lib/devops-client/output/environment.rb new file mode 100644 index 0000000..862fb95 --- /dev/null +++ b/devops-client/lib/devops-client/output/environment.rb @@ -0,0 +1,106 @@ +require "devops-client/output/base" + +module Output + class Environment < Base + + def table + title = nil + with_separator = false + headers, rows = case options[:current_command] + when :show + title = I18n.t("output.title.environment.show", id: options[:env], project: options[:project]) + with_separator = true + create_show(@data) + when :servers + title = I18n.t("output.title.environment.servers", env: options[:env], project: options[:project]) + create_servers(@data) + when :stacks + title = I18n.t("output.title.environment.stacks", env: options[:env], project: options[:project]) + create_stacks(@data) + when :test + title = I18n.t("output.title.project.test", :project => ARGV[2], :env => ARGV[3]) + create_test(@data) + else + title = I18n.t("output.title.environment.list", project: options[:project]) + create_list(@data) + end + create_table(headers, rows, title, with_num?, with_separator) + end + + def csv + with_num = true + headers, rows = case @additional_options[:current_command] + when :list + create_list(@data) + when :show + with_num = false + create_show(@data) + when :servers + create_servers(@data) + when :test + create_test(@data) + end + create_csv(headers, rows, with_num?) + end + + private + def create_list list + abort(I18n.t("output.not_found.environment.list")) if list.empty? + rows = list.map {|l| [l["id"]]} + headers = [ I18n.t("output.table_header.id") ] + return headers, rows + end + + def create_show show + row = [ + show["id"], + show["categories"].map{|cat| cat["id"]}, + show["users"], + show["run_list"], + show["expires"] + ] + headers = [ + I18n.t("output.table_header.id"), + I18n.t("output.table_header.categories"), + I18n.t("output.table_header.users"), + I18n.t("output.table_header.run_list"), + I18n.t("output.table_header.expires") + ] + return headers, [row] + end + + def create_servers servers + abort(I18n.t("output.not_found.servers.servers", env: options[:env], project: options[:project])) if servers.empty? + fields_to_output = %w(id name project environment category cm_name remote_user provider) + headers_and_rows(servers, fields_to_output) + end + + def create_test test + rows = [] + headers = [ + I18n.t("output.table_header.server"), + I18n.t("output.table_header.node_name"), + I18n.t("output.table_header.creation"), + I18n.t("output.table_header.bootstrap"), + I18n.t("output.table_header.deletion") + ] + test["servers"].each do |s| + rows.push [ s["id"], + s["chef_node_name"], + "#{s["create"]["status"]}\n#{s["create"]["time"]}", + "#{s["bootstrap"]["status"]}\n#{s["bootstrap"]["time"]}", + "#{s["delete"]["status"]}\n#{s["delete"]["time"]}" ] + end + return headers, rows + end + + def create_stacks(stacks) + abort(I18n.t("output.not_found.environment.stacks", env: options[:env], project: options[:project])) if stacks.empty? + + fields_to_output = %w(id deploy_env stack_template cloud_stack_id stack_status provider) + + headers_and_rows(stacks, fields_to_output) + end + + end +end diff --git a/devops-client/lib/devops-client/output/filters.rb b/devops-client/lib/devops-client/output/filters.rb index 7f6c9a9..668cad2 100644 --- a/devops-client/lib/devops-client/output/filters.rb +++ b/devops-client/lib/devops-client/output/filters.rb @@ -15,13 +15,8 @@ module Output private def create list - abort(I18n.t("output.not_found.filter.list")) if list.nil? or list.empty? - headers = [ I18n.t("output.table_header.image_id") ] - rows = [] - list.each do |l| - rows.push [ l ] - end - return headers, rows + abort(I18n.t("output.not_found.filter.list")) if @data.empty? + headers_and_rows_for_array(@data, "image_id") end end end diff --git a/devops-client/lib/devops-client/output/flavors.rb b/devops-client/lib/devops-client/output/flavors.rb deleted file mode 100644 index ffb56a9..0000000 --- a/devops-client/lib/devops-client/output/flavors.rb +++ /dev/null @@ -1,48 +0,0 @@ -require "devops-client/output/base" -require "devops-client/output/concerns/has_provider" - -module Output - class Flavors < Base - include Concerns::HasProvider - - def table - headers, rows = create(@data, @options[:given_provider]) - create_table(headers, rows, I18n.t("output.title.flavor.list")) - end - - def csv - headers, rows = create(@data, @options[:given_provider]) - create_csv(headers, rows) - end - - private - def create list, provider - abort(I18n.t("output.not_found.flavor.list")) if list.nil? or list.empty? - headers = nil - rows = [] - if provider == "openstack" - headers = [ - I18n.t("output.table_header.id"), - I18n.t("output.table_header.virtual_cpus"), - I18n.t("output.table_header.disk"), - I18n.t("output.table_header.ram") - ] - list.each do |l| - rows << [ l["id"], l["v_cpus"], l["disk"], l["ram"] ] - end - elsif provider == "ec2" - headers = [ - I18n.t("output.table_header.name"), - I18n.t("output.table_header.id"), - I18n.t("output.table_header.virtual_cpus"), - I18n.t("output.table_header.disk"), - I18n.t("output.table_header.ram") - ] - list.each do |l| - rows << [ l["name"], l["id"], l["cores"], l["disk"], l["ram"] ] - end - end - return headers, rows - end - end -end diff --git a/devops-client/lib/devops-client/output/groups.rb b/devops-client/lib/devops-client/output/groups.rb deleted file mode 100644 index 3f1e447..0000000 --- a/devops-client/lib/devops-client/output/groups.rb +++ /dev/null @@ -1,43 +0,0 @@ -require "devops-client/output/base" - -module Output - class Groups < Base - - def table - headers, rows = create(@data) - create_table(headers, rows, I18n.t("output.title.group.list"), true, true) - end - - def csv - headers, rows = create(@data) - create_csv(headers, rows) - end - - private - def create list - abort(I18n.t("output.not_found.group.list")) if list.nil? or list.empty? - headers = [ - I18n.t("output.table_header.name"), - I18n.t("output.table_header.protocol"), - I18n.t("output.table_header.from"), - I18n.t("output.table_header.to"), - I18n.t("output.table_header.cidr"), - I18n.t("output.table_header.description") - ] - rows = [] - list.each do |name, v| - next if v.nil? or v.empty? - p, f, t, c = [], [], [], [] - v["rules"].map do |l| - p.push l["protocol"] - f.push l["from"] - t.push l["to"] - c.push l["cidr"] - end - rows.push [ name, p.join("\n"), f.join("\n"), t.join("\n"), c.join("\n"), v["description"] ] - end - return headers, rows - end - - end -end diff --git a/devops-client/lib/devops-client/output/image.rb b/devops-client/lib/devops-client/output/image.rb index 8c16081..b265b32 100644 --- a/devops-client/lib/devops-client/output/image.rb +++ b/devops-client/lib/devops-client/output/image.rb @@ -39,26 +39,26 @@ module Output I18n.t("output.table_header.status") ] else - list.each {|l| rows.push [ l["id"], l["name"], l["bootstrap_template"], l["remote_user"], l["provider"] ] } + list.each {|l| rows.push [ l["id"], l["name"], l["remote_user"], l["provider"], time_to_s(l["created_at"]) ] } [ I18n.t("output.table_header.id"), I18n.t("output.table_header.name"), - I18n.t("output.table_header.template"), I18n.t("output.table_header.remote_user"), - I18n.t("output.table_header.provider") + I18n.t("output.table_header.provider"), + I18n.t("output.table_header.created_at") ] end return headers, rows end def create_show - rows = [ [ @data["id"], @data["name"], @data["bootstrap_template"], @data["remote_user"], @data["provider"] ] ] + rows = [ [ @data["id"], @data["name"], @data["remote_user"], @data["provider"], time_to_s(@data["created_at"]) ] ] headers = [ I18n.t("output.table_header.id"), I18n.t("output.table_header.name"), - I18n.t("output.table_header.template"), I18n.t("output.table_header.remote_user"), - I18n.t("output.table_header.provider") + I18n.t("output.table_header.provider"), + I18n.t("output.table_header.created_at") ] return headers, rows end diff --git a/devops-client/lib/devops-client/output/key.rb b/devops-client/lib/devops-client/output/key.rb index cf9c853..7d89c83 100644 --- a/devops-client/lib/devops-client/output/key.rb +++ b/devops-client/lib/devops-client/output/key.rb @@ -18,8 +18,8 @@ module Output def create list abort(I18n.t("output.not_found.key.list")) if list.nil? or list.empty? rows = [] - list.each {|l| rows.push [ l["id"], l["scope"] ] } - return [ I18n.t("output.table_header.id"), I18n.t("output.table_header.scope") ], rows + list.each {|l| rows.push [ l["id"], l["filename"], time_to_s(l["created_at"]) ] } + return [ I18n.t("output.table_header.id"), I18n.t("output.table_header.filename"), I18n.t("output.table_header.created_at") ], rows end end end diff --git a/devops-client/lib/devops-client/output/network.rb b/devops-client/lib/devops-client/output/network.rb deleted file mode 100644 index da39491..0000000 --- a/devops-client/lib/devops-client/output/network.rb +++ /dev/null @@ -1,48 +0,0 @@ -require "devops-client/output/base" -require "devops-client/output/concerns/has_provider" - -module Output - class Network < Base - include Concerns::HasProvider - - - def table - headers, rows = create(@data, @options[:given_provider]) - create_table(headers, rows, I18n.t("output.title.network.list")) - end - - def csv - headers, rows = create(@data, @options[:given_provider]) - create_csv(headers, rows) - end - - private - def create list, provider - headers = nil - rows = [] - if provider == "openstack" - abort(I18n.t("output.not_found.network.list")) if list.nil? or list.empty? - headers = [ I18n.t("output.table_header.name"), I18n.t("output.table_header.cidr") ] - list.each do |l| - rows.push [ l["name"], l["cidr"] ] - end - elsif provider == "ec2" - if list.nil? or list.empty? - puts(I18n.t("output.not_found.network.list")) - return nil, nil - end - headers = [ - I18n.t("output.table_header.subnet"), - I18n.t("output.table_header.vpc_id"), - I18n.t("output.table_header.cidr"), - I18n.t("output.table_header.zone") - ] - list.each do |l| - rows.push [ l["subnetId"], l["vpcId"], l["cidr"], l["zone"] ] - end - end - return headers, rows - end - end -end - diff --git a/devops-client/lib/devops-client/output/project.rb b/devops-client/lib/devops-client/output/project.rb index 1123c51..0b80270 100644 --- a/devops-client/lib/devops-client/output/project.rb +++ b/devops-client/lib/devops-client/output/project.rb @@ -9,11 +9,11 @@ module Output def table title = nil with_separator = false - headers, rows = case options[:output_type] + headers, rows = case options[:current_command] when :show - title = I18n.t("output.title.project.show", :name => @data["name"]) + title = I18n.t("output.title.project.show", :name => @data["id"]) with_separator = true - create_show(@data) + create_show when :servers title = ARGV[2] title += " " + ARGV[3] unless ARGV[3].nil? @@ -29,7 +29,7 @@ module Output create_test(@data) else title = I18n.t("output.title.project.list") - create_list(@data) + create_list end create_table(headers, rows, title, with_num?, with_separator) end @@ -38,10 +38,10 @@ module Output with_num = true headers, rows = case @additional_options[:output_type] when :list - create_list(@data) + create_list when :show with_num = false - create_show(@data) + create_show when :servers create_servers(@data) when :test @@ -51,57 +51,25 @@ module Output end private - def create_list list - abort(I18n.t("output.not_found.project.list")) if list.empty? - rows = list.map {|l| [l["name"]]} + def create_list + abort(I18n.t("output.not_found.project.list")) if @data.empty? + rows = @data.map {|l| [l["id"]]} headers = [ I18n.t("output.table_header.id") ] return headers, rows end - def create_show show + def create_show rows = [] - if show["type"] == "multi" - show["deploy_envs"].each do |de| - subprojects = [] - nodes = [] - de["servers"].each do |s| - s["subprojects"].each do |sp| - subprojects.push "#{sp["name"]} - #{sp["env"]}" - nodes.push sp["node"] - end - end - rows.push [ de["identifier"], subprojects.join("\n"), nodes.join("\n"), de["users"].join("\n") ] - end - headers = [ - I18n.t("output.table_header.deploy_env"), - I18n.t("output.table_header.subproject") + " - " + I18n.t("output.table_header.deploy_env"), - I18n.t("output.table_header.node_number"), - I18n.t("output.table_header.users") - ] - else - show["deploy_envs"].each_with_index do |de, i| - rows.push [ - show["name"], - de["identifier"], - de["image"], - de["flavor"], - (de["run_list"] || []).join("\n"), - (de["groups"] || []).join("\n"), - (de["subnets"] || []).join("\n"), - (de["users"] || []).join("\n") - ] - end - headers = [ - I18n.t("output.table_header.id"), - I18n.t("output.table_header.deploy_env"), - I18n.t("output.table_header.image_id"), - I18n.t("output.table_header.flavor"), - I18n.t("output.table_header.run_list"), - I18n.t("output.table_header.groups"), - I18n.t("output.table_header.subnets"), - I18n.t("output.table_header.users") - ] - end + rows = [ [@data["id"], @data["description"], @data["environments"].map{|e| e["id"]}, @data["owner"], @data["project_users"], @data["run_list"], time_to_s(@data["created_at"])] ] + headers = [ + I18n.t("output.table_header.id"), + I18n.t("output.table_header.description"), + I18n.t("output.table_header.environments"), + I18n.t("output.table_header.owner"), + I18n.t("output.table_header.users"), + I18n.t("output.table_header.run_list"), + I18n.t("output.table_header.created_at") + ] return headers, rows end diff --git a/devops-client/lib/devops-client/output/provider.rb b/devops-client/lib/devops-client/output/provider.rb index fde48cf..40eda5d 100644 --- a/devops-client/lib/devops-client/output/provider.rb +++ b/devops-client/lib/devops-client/output/provider.rb @@ -4,23 +4,48 @@ module Output class Provider < Base def table - case options[:output_type] + case options[:current_command] when :list headers, rows = create_list title = I18n.t("output.title.provider.list") when :accounts - headers, rows = create_accounts + headers, rows = create_accounts(options[:provider]) title = I18n.t("output.title.provider.accounts", provider: options[:provider]) + when :flavors + headers, rows = create_flavors(options[:provider]) + title = I18n.t("output.title.provider.flavors", provider: options[:provider], account: options[:account]) + when :images + headers, rows = create_images(options[:provider]) + title = I18n.t("output.title.provider.images", provider: options[:provider], account: options[:account]) + when :security_groups + headers, rows = create_security_groups(options[:provider]) + title = I18n.t("output.title.provider.security_groups", provider: options[:provider], account: options[:account]) + when :vpc + headers, rows = create_vpc + title = I18n.t("output.title.provider.vpcs", provider: options[:provider], account: options[:account]) + when :networks + headers, rows = create_networks(options[:provider]) + title = I18n.t("output.title.provider.networks", provider: options[:provider], account: options[:account]) end create_table(headers, rows, title) end def csv - case options[:output_type] + case options[:current_command] when :list headers, rows = create_list when :accounts headers, rows = create_accounts + when :flavors + headers, rows = create_flavors(options[:provider]) + when :images + headers, rows = create_images(options[:provider]) + when :security_groups + headers, rows = create_security_groups(options[:provider]) + when :vpc + headers, rows = create_vpc + when :networks + headers, rows = create_networks(options[:provider]) end create_csv(headers, rows, with_num?) end @@ -30,28 +55,131 @@ module Output def create_list abort(I18n.t("output.not_found.provider.list")) if @data.empty? headers = [ I18n.t("output.table_header.provider") ] - rows = [] - @data.each do |l| - rows.push [ l ] - end + rows = @data.map {|l| [ l ]} return headers, rows end - def create_accounts + def create_accounts provider headers = [ I18n.t("output.table_header.id"), I18n.t("output.table_header.description"), I18n.t("output.table_header.ssh_key") ] + keys = %w(account_name description ssh_key) + case provider + when Providers::Aws + headers.push I18n.t("output.table_header.use_iam_profile") + keys.push "use_iam_profile" + when Providers::Openstack + when Providers::Static + end + headers.push I18n.t("output.table_header.created_at") rows = @data.map do |account| - [ - account['account_name'], - account['description'], - account['ssh_key'] - ] + keys.map{|key| account[key]}.push(time_to_s(account["created_at"])) end [headers, rows] end + def create_flavors provider + abort(I18n.t("output.not_found.flavor.list")) if @data.nil? or @data.empty? + headers = nil + rows = [] + case provider + when Providers::Aws + headers = [ + I18n.t("output.table_header.name"), + I18n.t("output.table_header.id"), + I18n.t("output.table_header.virtual_cpus"), + I18n.t("output.table_header.disk"), + I18n.t("output.table_header.ram") + ] + @data.each do |l| + rows << [ l["name"], l["id"], l["cores"], l["disk"], l["ram"] ] + end + when Providers::Openstack + headers = [ + I18n.t("output.table_header.id"), + I18n.t("output.table_header.virtual_cpus"), + I18n.t("output.table_header.disk"), + I18n.t("output.table_header.ram") + ] + @data.each do |l| + rows << [ l["id"], l["v_cpus"], l["disk"], l["ram"] ] + end + end + return headers, rows + end + + def create_networks provider + headers = nil + rows = [] + case provider + when Providers::Openstack + abort(I18n.t("output.not_found.network.list")) if @data.empty? + headers = [ I18n.t("output.table_header.name"), I18n.t("output.table_header.cidr") ] + @data.each do |l| + rows.push [ l["name"], l["cidr"] ] + end + when Providers::Aws + if @data.empty? + puts(I18n.t("output.not_found.network.list")) + return nil, nil + end + headers = [ + I18n.t("output.table_header.subnet"), + I18n.t("output.table_header.vpc_id"), + I18n.t("output.table_header.cidr"), + I18n.t("output.table_header.zone") + ] + @data.each do |l| + rows.push [ l["subnetId"], l["vpcId"], l["cidr"], l["zone"] ] + end + end + return headers, rows + + end + + def create_images provider + abort(I18n.t("output.not_found.image.list")) if @data.empty? + rows = [] + @data.each {|l| rows.push [ l["name"], l["id"], l["status"] ]} + headers = [ + I18n.t("output.table_header.name"), + I18n.t("output.table_header.id"), + I18n.t("output.table_header.status") + ] + return headers, rows + end + + def create_security_groups provider + abort(I18n.t("output.not_found.security_groups.list")) if @data.empty? + headers = [ + I18n.t("output.table_header.name"), + I18n.t("output.table_header.protocol"), + I18n.t("output.table_header.from"), + I18n.t("output.table_header.to"), + I18n.t("output.table_header.cidr"), + I18n.t("output.table_header.description") + ] + rows = [] + @data.each do |name, v| + next if v.nil? or v.empty? + p, f, t, c = [], [], [], [] + v["rules"].map do |l| + p.push l["protocol"] + f.push l["from"] + t.push l["to"] + c.push l["cidr"] + end + rows.push [ name, p.join("\n"), f.join("\n"), t.join("\n"), c.join("\n"), v["description"] ] + end + return headers, rows + end + + def create_vpc + abort(I18n.t("output.not_found.vpc.list")) if @data.empty? + headers_and_rows(@data, %w(vpc_id cidr)) + end + end end diff --git a/devops-client/lib/devops-client/output/role.rb b/devops-client/lib/devops-client/output/role.rb new file mode 100644 index 0000000..358ec7a --- /dev/null +++ b/devops-client/lib/devops-client/output/role.rb @@ -0,0 +1,75 @@ +require "devops-client/output/base" + +module Output + class Role < Base + + def table + title, headers, rows = nil, nil, nil + case options[:current_command] + when :list + headers, rows = prepare_list + title = "output.title.role.list" + when :show + headers, rows = prepare_show + title = "output.title.role.show" + when :policies + headers, rows = prepare_policies + title = "output.title.role.policies" + end + + create_table headers, rows, I18n.t(title) + end + + def csv + headers, rows = nil, nil + case options[:current_command] + when :list + headers, rows = prepare_list + when :show + headers, rows = prepare_show + when :policies + headers, rows = prepare_policies + end + create_csv headers, rows + end + + private + def prepare_list + headers = [ + I18n.t("output.table_header.id"), + I18n.t("output.table_header.name"), + I18n.t("output.table_header.description"), + I18n.t("output.table_header.policies"), + I18n.t("output.table_header.created_at") + ] + rows = @data.map do |role| + [ role["id"], role["name"], role["description"], role["policies"], time_to_s(role["created_at"]) ] + end + return headers, rows + end + + def prepare_show + headers = [ + I18n.t("output.table_header.id"), + I18n.t("output.table_header.name"), + I18n.t("output.table_header.description"), + I18n.t("output.table_header.policies"), + I18n.t("output.table_header.created_at") + ] + rows = [[ @data["id"], @data["name"], @data["description"], @data["policies"], time_to_s(@data["created_at"]) ]] + return headers, rows + end + + def prepare_policies + headers = [ + I18n.t("output.table_header.id"), + I18n.t("output.table_header.description"), + I18n.t("output.table_header.dependencies") + ] + rows = @data.map do |policy| + [ policy["id"], policy["description"], policy["dependencies"] ] + end + return headers, rows + end + end +end diff --git a/devops-client/lib/devops-client/output/server.rb b/devops-client/lib/devops-client/output/server.rb index 940679c..2770116 100644 --- a/devops-client/lib/devops-client/output/server.rb +++ b/devops-client/lib/devops-client/output/server.rb @@ -18,7 +18,7 @@ module Output end create_list else - title = I18n.t("output.title.server.show", :name => @data["chef_node_name"]) + title = I18n.t("output.title.server.show", :name => options[:server]) create_show end create_table headers, rows, title @@ -73,11 +73,11 @@ module Output I18n.t("output.table_header.created_at") ] else - keys = ["id", "chef_node_name"] + keys = ["id", "name"] title = "Servers" [ I18n.t("output.table_header.instance_id"), - I18n.t("output.table_header.node_name") + I18n.t("output.table_header.name") ] end list.each do |l| @@ -92,18 +92,20 @@ module Output rows = [] headers = [ I18n.t("output.table_header.instance_id"), - I18n.t("output.table_header.node_name"), + I18n.t("output.table_header.name"), I18n.t("output.table_header.project"), - I18n.t("output.table_header.deploy_env"), + I18n.t("output.table_header.environment"), + I18n.t("output.table_header.category"), I18n.t("output.table_header.provider"), I18n.t("output.table_header.remote_user"), I18n.t("output.table_header.private_ip"), - I18n.t("output.table_header.created_at"), - I18n.t("output.table_header.created_by") + I18n.t("output.table_header.created_by"), + I18n.t("output.table_header.created_at") ] - keys = ["id", "chef_node_name", "project", "deploy_env", "provider", "remote_user", "private_ip", "created_at", "created_by"] + keys = ["id", "name", "project", "environment", "category", "provider", "remote_user", "private_ip", "created_by"] row = [] keys.each{|k| row.push @data[k]} + row.push time_to_s(@data["created_at"]) rows.push row return headers, rows end diff --git a/devops-client/lib/devops-client/output/tag.rb b/devops-client/lib/devops-client/output/tag.rb deleted file mode 100644 index 24ef4f9..0000000 --- a/devops-client/lib/devops-client/output/tag.rb +++ /dev/null @@ -1,28 +0,0 @@ -require "devops-client/output/base" - -module Output - class Tag < Base - - def table - headers, rows = create(@data) - create_table(headers, rows, I18n.t("output.title.tag.list")) - end - - def csv - headers, rows = create(@data) - create_csv(headers, rows) - end - - def json - JSON.pretty_generate @data - end - - private - def create list - abort(I18n.t("output.not_found.tag.list")) if list.empty? - headers = [I18n.t("output.table_header.tag")] - rows = list.map {|l| [ l ]} - return headers, rows - end - end -end diff --git a/devops-client/lib/devops-client/output/user.rb b/devops-client/lib/devops-client/output/user.rb index 038a070..6928f22 100644 --- a/devops-client/lib/devops-client/output/user.rb +++ b/devops-client/lib/devops-client/output/user.rb @@ -4,24 +4,39 @@ module Output class User < Base def table - title, headers = nil, nil - rows, with_num = create_subheader, false - rows += create_rows - headers = [ - "", - "", - "", - {:value => I18n.t("output.table_header.privileges"), :colspan => 12, :alignment => :center } - ] + title, headers, rows = nil, nil, nil + with_num = options[:with_num] || true - create_table headers, rows, I18n.t("output.title.users.list"), with_num, true + case options[:current_command] + when :list + headers, rows = prepare_list + title = "output.title.users.list" + when :show + end + + create_table headers, rows, I18n.t(title), with_num, true + end + + def csv + headers, rows = nil, nil + case options[:current_command] + when :list + headers, rows = prepare_list + when :show + end + create_csv headers, rows end private - def csv - rows = create_rows(@data) - headers = create_subheader - create_csv headers, rows + def prepare_list + headers = [ + I18n.t("output.table_header.id"), + I18n.t("output.table_header.email"), + I18n.t("output.table_header.roles"), + I18n.t("output.table_header.created_at") + ] + rows = @data.map{|u| [ u["id"], u["email"], u["roles"], time_to_s(u["created_at"]) ] } + [headers, rows] end def create_subheader diff --git a/devops-client/lib/devops-client/providers/providers.rb b/devops-client/lib/devops-client/providers/providers.rb index 4c7b3c2..141d0b3 100644 --- a/devops-client/lib/devops-client/providers/providers.rb +++ b/devops-client/lib/devops-client/providers/providers.rb @@ -1,21 +1,21 @@ module Providers - Ec2 = 'ec2' + Aws = 'aws' Openstack = 'openstack' Static = 'static' def self.available - [Ec2, Openstack, Static] + [Aws, Openstack, Static] end def self.functionalities { - images: [Ec2, Openstack], - stack_templates: [Ec2, Openstack], - stacks: [Ec2, Openstack] + images: [Aws, Openstack], + stack_templates: [Aws, Openstack], + stacks: [Aws, Openstack] } end def self.has_functionality?(provider, functionality) functionalities.fetch(functionality).include?(provider.to_s) end -end \ No newline at end of file +end diff --git a/devops-client/lib/devops-client/version.rb b/devops-client/lib/devops-client/version.rb index 2759c4b..9bbd342 100644 --- a/devops-client/lib/devops-client/version.rb +++ b/devops-client/lib/devops-client/version.rb @@ -1,3 +1,3 @@ module DevopsClient - VERSION = "2.1.34" + VERSION = "3.0.1" end diff --git a/devops-client/locales/en.yml b/devops-client/locales/en.yml index 0620a69..6c5d27e 100644 --- a/devops-client/locales/en.yml +++ b/devops-client/locales/en.yml @@ -24,95 +24,127 @@ en: warn: "WARN: %{msg}" error: "ERROR: %{msg}" headers: - flavor: "Flavor" - template: "Bootstrap template" - bootstrap_template: "Bootstrap template" + chef: "Chef options" + bootstrap_templates: Bootstrap templates deploy: "Deploy" filters: "Filters" - group: "Security groups" image: "Image" key: "Key" - network: "Network" project: "Project" - project_env: "Project environment" - deploy_env: "Deploy environment" + environment: "Project environment" + category: "Environment category" provider: "Provider" + provider_account: "Provider account" server: "Server" script: "Script" - tag: "Tag" user: "User" stack: "Stack" stack_template: "Stack template" - env: "Deploy environment" + env: "Environment" + role: "Role" + category_type: Category type handler: - flavor: - list: - empty: "Flavors list is empty" - env: + role: + create: + description: "Role description" + policies: "Role policies" + question: + create: "Create role" + delete: "Delete role '%{role}'" + all_policies: "Set all policies for a role" + provider: + vpc: + invalid_provider: "Operation available for 'aws' provider only" list: - empty: "Project environment list is empty" + empty: "VPCs list is empty" + account: + create: + description: "Description" + ssh_key: "SSH key" + aws: + use_iam_profile: "Use IAM profile" + secret_access_key: "Secret access key" + access_key_id: "Access key id" + question: + delete: "Are you sure to delete account '%{account}'" + create: "Create provider account" + tags: + question: + delete: "Are you sure to delete tag(s) '%{name}' from node '%{node}'?" + flavor: + list: + empty: "Flavors list is empty" image: question: delete: "Are you sure to delete image '%{name}'?" - create: "Create image?" + create: "Create image" create: ssh_user: "The ssh username" template: "Bootstrap template or empty value" template_empty: "No bootatrap template found, it will be used default template" filter: question: - delete: "Are you sure to delete image filter(s) '%{name}'?" + delete: "Are you sure to delete image filter(s) '%{name}'" key: question: - delete: "Are you sure to delete key '%{name}'?" + delete: "Are you sure to delete key '%{name}'" + create: "Create key" project: list: empty: "Project list is empty" question: - delete: "Are you sure to delete project '%{name}'?" - create: "Create project?" - update: "Update project?" - add_env: "Add environment?" - update_with_new_envs: "Add these envs to project?" - set_stack_template: "Set stack template?" + delete: "Are you sure to delete project '%{name}'" + create: "Create project" + update: "Update project" + add_env: "Add environment" + update_with_new_envs: "Add these envs to project" + set_stack_template: "Set stack template" + run_list: + empty: "Set empty run_list" parameter: run_list: empty: "WARN: run list is empty, continue?" exist: "Project '%{project}' already exist" create: + description: "Project description" invalid_json: "Invalid JSON in file '%{file}'" - env: "Deploy environment identifier" - env_exist: "Deploy environment '%{env}' already exist" - flavor: - not_found: "No such flavor" - subnet: - ec2: "Subnet (or Enter)" - openstack: "Subnets" - user: "Users, you will be added automatically" + users: "project users" + question: + users_choose: "Would you like to choose users (y) or type manually (n)" run_list: invalid: "ERROR: invalid run list elements: %{list}" - invalid_subcommand: "Invalid subcommand for '%{cmd}': '%{scmd}'" + environment: + create: + id_exists: "Environment with identifier '%{env}' already exists" + question: + delete: "Are you sure to delete environment '%{env}' from project '%{name}'" + create: "Create environment" + category: + question: + delete: "Are you sure to delete category '%{cat}' from environment '%{env}' from project '%{project}'" + create: "Create category" + create: + chef_env: "Chef env: " script: question: - delete: "Are you sure to delete script '%{name}'?" - tag: - question: - delete: "Are you sure to delete tag(s) '%{name}' from node '%{node}'?" + delete: "Are you sure to delete script '%{name}'" user: question: - delete: "Are you sure to delete user '%{name}'?" + create: "Create user" + delete: "Are you sure to delete user '%{name}'" password_for: "Password for %{user}:" server: question: - delete: "Are you sure to delete server '%{name}'?" - delete_list: "Are you sure to delete these servers: %{ids}?" + unbootstrap: "Are you sure to unbootstrap server '%{id}'" + delete: "Are you sure to delete server '%{id}'" + delete_list: "Are you sure to delete these servers: %{ids}" stack_template: create: id: "Id: " template_file: "Path to file with template body: " question: - create: "Are you sure to create stack template?" - delete: "Are you sure to delete stack template '%{name}'?" + create: "Are you sure to create stack template" + delete: "Are you sure to delete stack template '%{name}'" stack: create: id: "Id: " @@ -121,12 +153,12 @@ en: run_list: "Run list: " tags_file: "Path to file with tags in JSON (or Enter): " question: - create: "Are you sure to create stack?" - delete: "Are you sure to delete stack '%{name}'?" + create: "Are you sure to create stack" + delete: "Are you sure to delete stack '%{name}'" message: - choose_list_default: "Choose %{name} (comma separated), like 1,2,3 or empty for default value '%{default}': " - choose_list: "Choose %{name} (comma separated), like 1,2,3: " - choose: "Choose '%{name}': " + choose_list_default: "Choose %{name} (comma separated), like 1,2,3 or empty for default value '%{default}'" + choose_list: "Choose %{name} (comma separated), like 1,2,3" + choose: "Choose %{name}" error: parameter: undefined: "ERROR: parameter '%{name}' is undefined" @@ -137,6 +169,26 @@ en: invalid: "Invalid number" list: empty: "%{name} list is empty" + choose: + flavor: flavor + image: image + vpc: VPC + network: network + networks: networks + security_groups: "security groups" + ssh_key: "SSH key" + input: + run_list: + label: "Run list, like recipe[mycookbook::myrecipe],role[myrole]" + invalid: "Invalid run list '%{list}'" + user: + label: "Users" + list: "users list" + question: + choose: "Choose" + message: + input_list_default: "Input %{cmd} (comma separated) or empty for default value '%{default}': " + input_list: "Input %{cmd} (comma separated): " output: table_header: api_version: "API version" @@ -144,6 +196,8 @@ en: id: "Id" name: "Name" provider: "Provider" + provider_account: "Provider account" + use_iam_profile: "IAM profile" remote_user: "Remote user" disk: "Disk" virtual_cpus: "Virtual CPUs" @@ -155,20 +209,22 @@ en: cidr: "CIDR" description: "Description" status: "Status" - template: "Bootstrap template" - scope: "Scope" + filename: "Filename" subnet: "Subnet" vpc_id: "VPC Id" zone: "Zone" - deploy_env: "Environment" + environments: "Environments" + environment: "Environment" + categories: "Categories" + category: "Category" + expires: "Expires in" node_name: "Node name" image: "Image" flavor: "Flavor" - group: "Group" + security_groups: "Security groups" key: "Keys" templates: "Templates" run_list: "Run list" - groups: "Security groups" subnets: "Subnets" users: "Users" server: "Server" @@ -199,31 +255,44 @@ en: cloud_stack_id: "Cloud Stack id" stack_status: Stack status ssh_key: SSH key + policies: Policies + dependencies: Dependencies + roles: Roles + owner: Owner + cm_tool: CM + bootstrap_template: Bootstrap template title: - flavor: - list: "Flavors" - bootstrap_template: - list: "Bootstrap templates" + chef: + bootstrap_templates: "Bootstrap templates" + tags: "Tags" filter: list: "Filters" - group: - list: "Security groups" image: list: "Images" show: "Image '%{id}' information" key: list: "Keys" - network: - list: "Networks" project: list: "Projects" show: "Project '%{name}' information" servers: "Project '%{title}' servers" test: "Project test: %{project} - %{env}" envs: "Project '%{name}' deploy envs" + environment: + list: "Project '%{project}' environments" + show: "Project '%{project}' environment '%{id}' information" + servers: "Servers for project '%{project}' and environment '%{env}'" + stacks: "Stacks for project '%{project}' and environment '%{env}'" + category: + list: Categories + show: "Information about category '%{cat}' of project '%{project}' and environment '%{env}'" provider: list: "Providers" accounts: "Provider %{provider} accounts" + flavors: "Flavors" + networks: "Networks" + security_groups: "Security groups" + vpcs: "VPCs" script: list: "Scripts" server: @@ -232,8 +301,6 @@ en: ec2: "Ec2 servers" openstack: "Openstack servers" show: "Server '%{name}'" - tag: - list: "Tags" users: list: "Users" stack_template: @@ -242,33 +309,38 @@ en: stack: list: "Stacks" show: "Stack" + role: + list: "Roles" + policies: "Roles policies" not_found: - flavor: - list: "No flavor found" - bootstrap_template: - list: "No bootstrap templates found" + chef: + bootstrap_template: "No bootstrap templates found" + tags: "No tags found" filter: list: "No filters found" - group: - list: "No security groups found" image: list: "No images found" key: list: "No keys found" - network: - list: "No networks found" project: list: "No project found" servers: "No servers for project '%{name}' found" stacks: "No stacks for project '%{name}' found" + environment: + list: "No environments found" + category: + list: "No categories found" + servers: "No servers found" provider: list: "Empty providers list" + flavors: "No flavor found" + network: "No networks found" + security_group: "No security groups found" + image: "No images found" script: list: "No scripts uploaded" server: list: "No servers found" - tag: - list: "No tags found" user: list: "No users found" stack_template: @@ -360,9 +432,9 @@ en: instance: Unreserve server by instance id create: without_bootstrap: 'Run server without bootsraping phase' - name: Set node name + name: Set server name groups: The security groups for this server - force: Cancel rollback operation on error + skip_rollback: Cancel rollback operation on error key: User another key for server private_ip: Private ip for this server bootstrap: @@ -383,7 +455,16 @@ en: without_bootstrap: Skip bootsraping phase on created instances stack_template: provider: Stack template provider + provider_account: Provider account id: Stack template id template_file: Stack template file user: new_password: New user password + provider: + account: + description: "Account description" + ssh_key: "Key id" + use_iam_profile: "Use IAM profile (aws only)" + access_key_id: "Access key id (aws only)" + secret_access_key: "Secret access key (aws only)" + diff --git a/devops-client/locales/ru.yml b/devops-client/locales/ru.yml index 0d25af7..682fb6e 100644 --- a/devops-client/locales/ru.yml +++ b/devops-client/locales/ru.yml @@ -25,84 +25,113 @@ ru: warn: "Предупреждение: %{msg}" error: "Ошибка: %{msg}" headers: - flavor: "Конфигурация" + chef: "Опции Chef" + bootstrap_templates: "Шаблоны" deploy: "Деплой" - template: "Шаблон загрузки" filters: "Фильтры" - group: "Группы безопасности" image: "Образ" - key: "Ключ" - network: "Сеть" + key: "SSH ключ" project: "Проект" - project_env: "Окружение проект" + environment: "Окружение проект" + category: "Категория Окружения" provider: "Провайдер" + provider_account: "Аккаунт провайдера" server: "Сервер" script: "Скрипт" - tag: "Тег" user: "Пользователь" + role: "Роль" handler: - flavor: - list: - empty: "Список конфигураций пуст" + role: + create: + description: "Описание роли" + policies: "Политики роли" + question: + create: "Создать роль" + delete: "Удалить роль '%{role}'" + all_policies: "Установить все политики для роли" + provider: + vpc: + invalid_provider: "Операция доступна только для провайдера 'aws'" + list: + empty: "Список VPC пуст" + account: + create: + description: "Описание" + ssh_key: "SSH ключ" + aws: + use_iam_profile: "Использовать профайл IAM" + secret_access_key: "Ключ доступа" + access_key_id: "Идентификатор ключа доступа" + question: + delete: "Вы уверен, что хотите удалить аккаунт '%{account}'" + create: "Создать аккаунт для провайдера" + tags: + question: + delete: "Вы уверены, что хотите удалить тэг(и) '%{name}' из ноды '%{node}'?" + flavor: + list: + empty: "Список конфигураций пуст" image: question: - delete: "Вы уверены, что хотите удалить образ '%{name}'?" - create: "Создать образ?" + delete: "Вы уверены, что хотите удалить образ '%{name}'" + create: "Создать образ" create: ssh_user: "Имя пользоватлея ssh" - template: "Шаблон загрузки или пустое значение" - template_empty: "Нет ни одного шаблона загрузки, будет использован шаблон по умолчанию" filter: question: - delete: "Вы уверены, что хотите удалить фильтр(ы) образа '%{name}'?" + delete: "Вы уверены, что хотите удалить фильтр(ы) образа '%{name}'" key: question: - delete: "Вы уверены, что хотите удалить ключ '%{name}'?" + delete: "Вы уверены, что хотите удалить ключ '%{name}'" + create: "Создать ключ" project: list: empty: "Список проектов пуст" - env: - list: - empty: "Список окружений проекта пуст" question: - delete: "Вы уверены, что хотите удалить проект '%{name}'?" - create: "Создать проект?" - update: "Обновить проект?" - add_env: "Добавить окружение?" + delete: "Вы уверены, что хотите удалить проект '%{name}'" + create: "Создать проект" + update: "Обновить проект" + add_env: "Добавить окружение" + run_list: + empty: "Сделать run_list пустым" parameter: run_list: - empty: "Предупреждение: список запуска пуст, продолжить?" + empty: "Предупреждение: список запуска пуст, продолжить" exist: "Проект '%{project}' уже существует" create: + question: + users_choose: "Вы хотите выбрать пользователей" + description: "Описание проекта" invalid_json: "Неверный JSON в файле '%{file}'" - env: "Идентификатор окружения" - env_exist: "Окружение '%{env}' уже существует" - flavor: - not_found: "Конфигурация не надена" - subnet: - ec2: "Подсеть (или Enter)" - openstack: "Подсети" - user: "Пользователи, Вы будете добалены автоматически" + users: "пользователи проекта" run_list: invalid: "ОШИБКА: неверные элементы run_list: %{list}" - invalid_subcommand: "Неверная подкомманда для '%{cmd}': '%{scmd}'" + environment: + create: + id_exists: "Окружение с идентификатором '%{env}' уже существует" + question: + delete: "Вы уверены, что хотите удалить окружение '%{env}' из проекта '%{name}'" + create: "Создать окружение" + category: + question: + delete: "Вы уверены, что хотите удалить категорию '%{cat}' из окружения '%{env}' проекта '%{name}'" + create: "Создать категорию" script: question: - delete: "Вы уверены, что хотите удалить скрипт '%{name}'?" - tag: - question: - delete: "Вы уверены, что хотите удалить тэг(и) '%{name}' из ноды '%{node}'?" + delete: "Вы уверены, что хотите удалить скрипт '%{name}'" user: question: - delete: "Вы уверены, что хотите удалить пользователя '%{name}'?" + create: "Создать пользователя" + delete: "Вы уверены, что хотите удалить пользователя '%{name}'" password_for: "Пароль для %{user}:" server: question: - delete: "Вы уверены, что хотите удалить сервер '%{name}'?" + delete: "Вы уверены, что хотите удалить сервер '%{id}'" + unbootstrap: "Вы уверены, что хотите удалить конфигурацию сервера '%{id}'" message: - choose_list_default: "Выберите значения из списка (разделенные запятой), например 1,2,3 или оставте пустым для значеия по умолчанию '%{default}': " - choose_list: "Выберите значения из списка (разделенные запятой), например 1,2,3: " - choose: "Выберите '%{name}': " + choose_list_default: "Выберите значения из списка (разделенные запятой), например 1,2,3 или оставте пустым для значеия по умолчанию '%{default}'" + choose_list: "Выберите значения из списка (разделенные запятой), например 1,2,3" + choose: "Выберите %{name}" error: parameter: undefined: "Ошибка: параметр '%{name}' не указан" @@ -113,6 +142,26 @@ ru: invalid: "Неверный номер" list: empty: "%{name} список пуст" + choose: + flavor: конфигурацию + image: образ + vpc: VPC + network: подсеть + networks: подсети + security_groups: "группы безопасности" + ssh_key: "SSH ключ" + input: + run_list: + label: "Список запуска, например, recipe[mycookbook::myrecipe],role[myrole]" + invalid: "Неверный список запуска" + user: + label: "Пользователи" + list: "список пользователей" + question: + choose: "Выбрать" + message: + input_list_default: "Введите %{cmd} (разделенные запятой) или оставьте пустым для значения по умолчанию '%{default}': " + input_list: "Введите %{cmd} (разделенные запятой): " output: table_header: api_version: "Версия API" @@ -120,6 +169,8 @@ ru: id: "Id" name: "Имя" provider: "Провайдер" + provider_account: "Аккаунт провайдера" + use_iam_profile: "IAM профиль" remote_user: "Пользователь" disk: "Диск" virtual_cpus: "Виртуальные ЦПУ" @@ -131,12 +182,14 @@ ru: cidr: "CIDR" description: "Описание" status: "Статус" - template: "Шаблон" - scope: "Область" + filename: "Имя файла" subnet: "Подсеть" vpc_id: "VPC Id" zone: "Зона" - deploy_env: "Окружение" + environments: "Окружения" + environment: "Окружение" + category: "Категория" + expires: "Время жизни" node_name: "Имя ноды" image: "Образ" flavor: "Конфигурация" @@ -144,7 +197,7 @@ ru: key: "Ключи" templates: "Шаблоны" run_list: "Список запуска" - groups: "Группы безоп." + security_groups: "Группы безоп." subnets: "Подсети" users: "Пользователи" server: "Сервер" @@ -168,29 +221,43 @@ ru: keypair: "Ключ" created_at: "Создан" created_by: "Создал" + policies: Политики + dependencies: Зависимости + roles: Роли + owner: Владелец + cm_tool: УК + bootstrap_template: Шаблон конфигурации title: - flavor: - list: "Конфигурации" - bootstrap_template: - list: "Шаблоны" + chef: + bootstrap_templates: "Шаблоны" + tags: "Теги" filter: list: "Фильтры" - group: - list: "Группы безопасности" image: list: "Образы" show: "Информация об образе '%{id}'" key: list: "Ключи" - network: - list: "Сети" project: list: "Проекты" show: "Информация о проекте '%{name}'" servers: "Сервера проекта '%{title}'" test: "Тестирование проекта: %{project} - %{env}" + environment: + list: "Окружения проекта '%{project}'" + show: "Информация об окружении '%{id}' проекта '%{project}'" + servers: "Сервера для проекта '%{project}' и окружения '%{env}'" + stacks: "Стеки для проекта '%{project}' и окружения '%{env}'" + category: + list: Категории + show: "Информация о категории '%{cat}' проекта '%{project}' и окружения '%{env}'" provider: list: "Провайдеры" + flavors: "Конфигурации" + security_groups: "Группы безопасности" + network: "Сети" + images: "Образы" + vpcs: "VPCs" script: list: "Скрипты" server: @@ -199,36 +266,36 @@ ru: ec2: "Сервера Ec2" openstack: "Сервера Openstack" show: "Сервер '%{name}'" - tag: - list: "Теги" users: list: "Пользователи" + role: + list: "Роли" + policies: "Политики ролей" not_found: - flavor: - list: "Ни одной конфигурации не найдено" - bootstrap_template: - list: "Ни одного шаблона не найдено" + chef: + bootstrap_template: "Ни одного шаблона не найдено" + tags: "Ни одного тега не найдено" filter: list: "Ни одного фильтра не найдено" - group: - list: "Ни одной группы безопасности не найдено" image: list: "Ни одного образа не найдено" key: list: "Ни одного ключа не найдено" - network: - list: "Ни одной сети не найдено" project: list: "Ни одного проекта не найдено" servers: "Ни одного сервера для проекта '%{name}' не найдено" + environment: + list: "Ни одного окружения не найдено" provider: list: "Ни одного провайдера не найдено" + flavors: "Ни одной конфигурации не найдено" + security_groups: "Ни одной группы безопасности не найдено" + network: "Ни одной сети не найдено" + images: "Ни одного образа не найдено" script: list: "Не загружено ни одного скрипта" server: list: "Ни одного сервера не найдено" - tag: - list: "Ни одного тега не найдено" user: list: "Ни одного пользователя не найдено" options: @@ -270,3 +337,7 @@ ru: script: run: params: "Аргументы скрипта (список разделенный запятой)" + description: + provider: + images: + all: "Показать все образы без фильтра" diff --git a/devops-service/Gemfile b/devops-service/Gemfile index 434263d..0944002 100644 --- a/devops-service/Gemfile +++ b/devops-service/Gemfile @@ -4,20 +4,24 @@ source 'https://rubygems.org' gem "thin", "~>1.5.1" gem "mime-types", "~>1.25.1" -gem "sinatra", "1.4.5" +gem "sinatra", '~> 1.4.5' gem "sinatra-contrib"#, "1.4.1" gem "sinatra-websocket"#, "~>0.3.0" -gem "fog", "~>1.20" +gem "fog", "~>1.30" gem "mixlib-shellout" gem "chef", ">=12" -gem "mongo", '1.12.3' +gem "mongo", '~> 2.1' gem "bson_ext" gem "multi_json", "1.7.8" -gem "sidekiq", "3.2.6" +gem "sidekiq", "4.1.0" gem 'rake', '10.2.0' gem 'rack-accept-media-types' gem 'rack', '1.5.2' gem 'hooks' +gem 'mongoid', '~> 5.0.0' +gem 'net-scp', '1.2.1' +gem 'net-ssh', '3.0.2' # with version 3.1.1 scp.upload method does not work +gem 'swagger-blocks', '1.3.3' #gem "devops-nibr", :path => "../../devops-nibr" @@ -30,6 +34,7 @@ group :test do gem 'factory_girl', '~>4.5' gem 'activesupport' gem 'rspec_junit_formatter' + gem 'database_cleaner', '~> 1.5' end group :devepoment do diff --git a/devops-service/Gemfile.lock b/devops-service/Gemfile.lock index 023b96c..2c68783 100644 --- a/devops-service/Gemfile.lock +++ b/devops-service/Gemfile.lock @@ -1,91 +1,105 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (2.3.1) - activesupport (4.2.4) + CFPropertyList (2.3.2) + activemodel (4.2.6) + activesupport (= 4.2.6) + builder (~> 3.1) + activesupport (4.2.6) i18n (~> 0.7) json (~> 1.7, >= 1.7.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - addressable (2.3.8) - backports (3.6.4) - bson (1.12.3) - bson_ext (1.12.3) - bson (~> 1.12.3) + addressable (2.4.0) + backports (3.6.8) + bson (4.1.0) + bson_ext (1.5.1) builder (3.2.2) - byebug (5.0.0) - columnize (= 0.9.0) - celluloid (0.15.2) - timers (~> 1.1.0) - chef (12.1.2) - chef-zero (~> 4.0) + byebug (8.2.4) + chef (12.9.38) + bundler (>= 1.10) + chef-config (= 12.9.38) + chef-zero (~> 4.5) diff-lcs (~> 1.2, >= 1.2.4) erubis (~> 2.7) - ffi-yajl (~> 1.2) + ffi-yajl (~> 2.2) highline (~> 1.6, >= 1.6.9) - mixlib-authentication (~> 1.3) + mixlib-authentication (~> 1.4) mixlib-cli (~> 1.4) - mixlib-config (~> 2.0) mixlib-log (~> 1.3) - mixlib-shellout (>= 2.0.0.rc.0, < 3.0) - net-ssh (~> 2.6) + mixlib-shellout (~> 2.0) + net-sftp (~> 2.1, >= 2.1.2) + net-ssh (>= 2.9, < 4.0) net-ssh-multi (~> 1.1) - ohai (~> 8.0) - plist (~> 3.1.0) - pry (~> 0.9) - rspec-core (~> 3.2) - rspec-expectations (~> 3.2) - rspec-mocks (~> 3.2) + ohai (>= 8.6.0.alpha.1, < 9) + plist (~> 3.2) + proxifier (~> 1.0) + rspec-core (~> 3.4) + rspec-expectations (~> 3.4) + rspec-mocks (~> 3.4) rspec_junit_formatter (~> 0.2.0) serverspec (~> 2.7) specinfra (~> 2.10) - chef-zero (4.2.3) - ffi-yajl (>= 1.1, < 3.0) - hashie (~> 2.0) + syslog-logger (~> 1.6) + uuidtools (~> 2.1.5) + chef-config (12.9.38) + fuzzyurl (~> 0.8.0) + mixlib-config (~> 2.0) + mixlib-shellout (~> 2.0) + chef-zero (4.6.1) + ffi-yajl (~> 2.2) + hashie (>= 2.0, < 4.0) mixlib-log (~> 1.3) rack uuidtools (~> 2.1) - coderay (1.1.0) - columnize (0.9.0) + coderay (1.1.1) + concurrent-ruby (1.0.1) connection_pool (2.2.0) - cucumber (2.0.0) + cucumber (2.3.3) builder (>= 2.1.2) - cucumber-core (~> 1.1.3) + cucumber-core (~> 1.4.0) + cucumber-wire (~> 0.0.1) diff-lcs (>= 1.1.3) - gherkin (~> 2.12) + gherkin (~> 3.2.0) multi_json (>= 1.7.5, < 2.0) multi_test (>= 0.1.2) - cucumber-core (1.1.3) - gherkin (~> 2.12.0) + cucumber-core (1.4.0) + gherkin (~> 3.2.0) + cucumber-wire (0.0.1) daemons (1.2.3) + database_cleaner (1.5.1) diff-lcs (1.2.5) docile (1.1.5) em-websocket (0.3.8) addressable (>= 2.1.1) eventmachine (>= 0.12.9) erubis (2.7.0) - eventmachine (1.0.7) - excon (0.45.3) - factory_girl (4.5.0) + eventmachine (1.2.0.1) + excon (0.49.0) + factory_girl (4.7.0) activesupport (>= 3.0.0) - ffi (1.9.9) - ffi-yajl (1.4.0) - ffi (~> 1.5) + ffi (1.9.10) + ffi-yajl (2.2.3) libyajl2 (~> 1.2) fission (0.5.0) CFPropertyList (~> 2.2) - fog (1.31.0) + fog (1.38.0) + fog-aliyun (>= 0.1.0) fog-atmos - fog-aws (~> 0.0) + fog-aws (>= 0.6.0) fog-brightbox (~> 0.4) - fog-core (~> 1.30) - fog-ecloud - fog-google (>= 0.0.2) + fog-cloudatcost (~> 0.1.0) + fog-core (~> 1.32) + fog-dynect (~> 0.0.2) + fog-ecloud (~> 0.1) + fog-google (<= 0.1.0) fog-json fog-local + fog-openstack fog-powerdns (>= 0.1.1) fog-profitbricks + fog-rackspace fog-radosgw (>= 0.0.2) fog-riakcs fog-sakuracloud (>= 0.0.4) @@ -95,49 +109,71 @@ GEM fog-terremark fog-vmfusion fog-voxel + fog-vsphere (>= 0.4.0) + fog-xenserver fog-xml (~> 0.1.1) ipaddress (~> 0.5) - nokogiri (~> 1.5, >= 1.5.11) + fog-aliyun (0.1.0) + fog-core (~> 1.27) + fog-json (~> 1.0) + ipaddress (~> 0.8) + xml-simple (~> 1.1) fog-atmos (0.1.0) fog-core fog-xml - fog-aws (0.6.0) + fog-aws (0.9.2) fog-core (~> 1.27) fog-json (~> 1.0) fog-xml (~> 0.1) ipaddress (~> 0.8) - fog-brightbox (0.7.2) + fog-brightbox (0.10.1) fog-core (~> 1.22) fog-json inflecto (~> 0.0.2) - fog-core (1.31.1) + fog-cloudatcost (0.1.2) + fog-core (~> 1.36) + fog-json (~> 1.0) + fog-xml (~> 0.1) + ipaddress (~> 0.8) + fog-core (1.37.0) builder excon (~> 0.45) formatador (~> 0.2) - mime-types - net-scp (~> 1.1) - net-ssh (>= 2.1.3) + fog-dynect (0.0.3) + fog-core + fog-json + fog-xml fog-ecloud (0.3.0) fog-core fog-xml - fog-google (0.0.6) + fog-google (0.1.0) fog-core fog-json fog-xml fog-json (1.0.1) fog-core (~> 1.0) multi_json (~> 1.0) - fog-local (0.2.1) + fog-local (0.3.0) fog-core (~> 1.27) + fog-openstack (0.1.2) + fog-core (>= 1.35) + fog-json (>= 1.0) + fog-xml (>= 0.1) + ipaddress (>= 0.8) fog-powerdns (0.1.1) fog-core (~> 1.27) fog-json (~> 1.0) fog-xml (~> 0.1) - fog-profitbricks (0.0.3) + fog-profitbricks (0.0.5) fog-core fog-xml nokogiri - fog-radosgw (0.0.4) + fog-rackspace (0.1.1) + fog-core (>= 1.35) + fog-json (>= 1.0) + fog-xml (>= 0.1) + ipaddress (>= 0.8) + fog-radosgw (0.0.5) fog-core (>= 1.21.0) fog-json fog-xml (>= 0.0.1) @@ -145,13 +181,13 @@ GEM fog-core fog-json fog-xml - fog-sakuracloud (1.0.1) + fog-sakuracloud (1.7.5) fog-core fog-json fog-serverlove (0.1.2) fog-core fog-json - fog-softlayer (0.4.7) + fog-softlayer (1.1.0) fog-core fog-json fog-storm_on_demand (0.1.1) @@ -166,12 +202,18 @@ GEM fog-voxel (0.1.0) fog-core fog-xml + fog-vsphere (0.6.3) + fog-core + rbvmomi (~> 1.8) + fog-xenserver (0.2.3) + fog-core + fog-xml fog-xml (0.1.2) fog-core nokogiri (~> 1.5, >= 1.5.11) formatador (0.2.5) - gherkin (2.12.2) - multi_json (~> 1.3) + fuzzyurl (0.8.0) + gherkin (3.2.0) guard (2.13.0) formatador (>= 0.2.4) listen (>= 2.7, <= 4.0) @@ -182,69 +224,80 @@ GEM shellany (~> 0.0) thor (>= 0.18.1) guard-compat (1.2.1) - guard-rspec (4.6.4) + guard-rspec (4.6.5) guard (~> 2.1) guard-compat (~> 1.1) rspec (>= 2.99.0, < 4.0) - hashie (2.1.2) - highline (1.7.2) - hooks (0.4.0) - uber (~> 0.0.4) - httpclient (2.6.0.1) + hashie (3.4.3) + highline (1.7.8) + hooks (0.4.1) + uber (~> 0.0.14) + httpclient (2.7.1) i18n (0.7.0) inflecto (0.0.2) - ipaddress (0.8.0) + ipaddress (0.8.3) json (1.8.3) libyajl2 (1.2.0) - listen (3.0.4) + listen (3.0.6) rb-fsevent (>= 0.9.3) - rb-inotify (>= 0.9) - lumberjack (1.0.9) + rb-inotify (>= 0.9.7) + lumberjack (1.0.10) method_source (0.8.2) mime-types (1.25.1) - mini_portile (0.6.2) - minitest (5.8.2) - mixlib-authentication (1.3.0) + mini_portile2 (2.0.0) + minitest (5.8.4) + mixlib-authentication (1.4.0) mixlib-log + rspec-core (~> 3.2) + rspec-expectations (~> 3.2) + rspec-mocks (~> 3.2) mixlib-cli (1.5.0) mixlib-config (2.2.1) mixlib-log (1.6.0) - mixlib-shellout (2.1.0) - mongo (1.12.3) - bson (= 1.12.3) + mixlib-shellout (2.2.6) + mongo (2.2.4) + bson (~> 4.0) + mongoid (5.0.2) + activemodel (~> 4.0) + mongo (~> 2.1) + origin (~> 2.1) + tzinfo (>= 0.3.37) multi_json (1.7.8) multi_test (0.1.2) - nenv (0.2.0) - net-dhcp (1.3.2) + nenv (0.3.0) net-scp (1.2.1) net-ssh (>= 2.6.5) - net-ssh (2.9.2) + net-sftp (2.1.2) + net-ssh (>= 2.6.5) + net-ssh (3.0.2) net-ssh-gateway (1.2.0) net-ssh (>= 2.6.5) net-ssh-multi (1.2.1) net-ssh (>= 2.6.5) net-ssh-gateway (>= 1.2.0) - nokogiri (1.6.6.2) - mini_portile (~> 0.6.0) + net-telnet (0.1.1) + nokogiri (1.6.7.2) + mini_portile2 (~> 2.0.0.rc2) notiffany (0.0.8) nenv (~> 0.1) shellany (~> 0.0) - ohai (8.0.1) + ohai (8.14.0) + chef-config (>= 12.5.0.alpha.1, < 13) ffi (~> 1.9) - ffi-yajl (~> 1.1) + ffi-yajl (~> 2.2) ipaddress - mime-types (~> 1.16) mixlib-cli mixlib-config (~> 2.0) mixlib-log mixlib-shellout (~> 2.0) - net-dhcp - rake (~> 10.1) + plist (~> 3.1) systemu (~> 2.6.4) wmi-lite (~> 1.0) - plist (3.1.0) - power_assert (0.2.3) - pry (0.10.1) + origin (2.2.0) + plist (3.2.0) + power_assert (0.2.7) + proxifier (1.0.3) + pry (0.10.3) coderay (~> 1.1.0) method_source (~> 0.8.1) slop (~> 3.4) @@ -255,55 +308,56 @@ GEM rack-test (0.6.3) rack (>= 1.0) rake (10.2.0) - rb-fsevent (0.9.6) - rb-inotify (0.9.5) + rb-fsevent (0.9.7) + rb-inotify (0.9.7) ffi (>= 0.5.0) - redis (3.2.1) - redis-namespace (1.5.2) - redis (~> 3.0, >= 3.0.4) - rspec (3.3.0) - rspec-core (~> 3.3.0) - rspec-expectations (~> 3.3.0) - rspec-mocks (~> 3.3.0) - rspec-core (3.3.1) - rspec-support (~> 3.3.0) - rspec-expectations (3.3.0) + rbvmomi (1.8.2) + builder + nokogiri (>= 1.4.1) + trollop + redis (3.2.2) + rspec (3.4.0) + rspec-core (~> 3.4.0) + rspec-expectations (~> 3.4.0) + rspec-mocks (~> 3.4.0) + rspec-core (3.4.4) + rspec-support (~> 3.4.0) + rspec-expectations (3.4.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.3.0) + rspec-support (~> 3.4.0) rspec-its (1.2.0) rspec-core (>= 3.0.0) rspec-expectations (>= 3.0.0) - rspec-mocks (3.3.1) + rspec-mocks (3.4.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.3.0) - rspec-support (3.3.0) + rspec-support (~> 3.4.0) + rspec-support (3.4.1) rspec_junit_formatter (0.2.3) builder (< 4) rspec-core (>= 2, < 4, != 2.12.0) - serverspec (2.19.0) + serverspec (2.31.1) multi_json rspec (~> 3.0) rspec-its - specinfra (~> 2.35) + specinfra (~> 2.53) + sfl (2.2) shellany (0.0.1) - sidekiq (3.2.6) - celluloid (= 0.15.2) - connection_pool (>= 2.0.0) - json - redis (>= 3.0.6) - redis-namespace (>= 1.3.1) - simplecov (0.11.1) + sidekiq (4.1.0) + concurrent-ruby (~> 1.0) + connection_pool (~> 2.2, >= 2.2.0) + redis (~> 3.2, >= 3.2.1) + simplecov (0.11.2) docile (~> 1.1.0) json (~> 1.8) simplecov-html (~> 0.10.0) simplecov-html (0.10.0) simplecov-rcov (0.2.3) simplecov (>= 0.4.1) - sinatra (1.4.5) - rack (~> 1.4) + sinatra (1.4.7) + rack (~> 1.5) rack-protection (~> 1.4) - tilt (~> 1.3, >= 1.3.4) - sinatra-contrib (1.4.4) + tilt (>= 1.3, < 3) + sinatra-contrib (1.4.7) backports (>= 2.0) multi_json rack-protection @@ -315,11 +369,15 @@ GEM eventmachine thin (>= 1.3.1, < 2.0.0) slop (3.6.0) - specinfra (2.36.6) + specinfra (2.56.1) net-scp - net-ssh + net-ssh (>= 2.7, < 4.0) + net-telnet + sfl + swagger-blocks (1.3.3) + syslog-logger (1.6.8) systemu (2.6.5) - test-unit (3.1.2) + test-unit (3.1.8) power_assert thin (1.5.1) daemons (>= 1.0.9) @@ -327,13 +385,14 @@ GEM rack (>= 1.0.0) thor (0.19.1) thread_safe (0.3.5) - tilt (1.4.1) - timers (1.1.0) + tilt (2.0.2) + trollop (2.1.2) tzinfo (1.2.2) thread_safe (~> 0.1) - uber (0.0.13) + uber (0.0.15) uuidtools (2.1.5) wmi-lite (1.0.0) + xml-simple (1.1.5) PLATFORMS ruby @@ -344,28 +403,30 @@ DEPENDENCIES byebug chef (>= 12) cucumber + database_cleaner (~> 1.5) factory_girl (~> 4.5) - fog (~> 1.20) + fog (~> 1.30) guard-rspec hooks httpclient mime-types (~> 1.25.1) mixlib-shellout - mongo (= 1.12.3) + mongo (~> 2.1) + mongoid (~> 5.0.0) multi_json (= 1.7.8) + net-scp (= 1.2.1) + net-ssh (= 3.0.2) rack (= 1.5.2) rack-accept-media-types rake (= 10.2.0) rspec (~> 3.3) rspec_junit_formatter - sidekiq (= 3.2.6) + sidekiq (= 4.1.0) simplecov simplecov-rcov - sinatra (= 1.4.5) + sinatra (~> 1.4.5) sinatra-contrib sinatra-websocket + swagger-blocks (= 1.3.3) test-unit thin (~> 1.5.1) - -BUNDLED WITH - 1.11.2 diff --git a/devops-service/Guardfile b/devops-service/Guardfile index dd95f5c..8e3499e 100644 --- a/devops-service/Guardfile +++ b/devops-service/Guardfile @@ -24,7 +24,7 @@ # * zeus: 'zeus rspec' (requires the server to be started separately) # * 'just' rspec: 'rspec' -guard :rspec, cmd: "rspec" do +guard :rspec, cmd: "rspec", cmd_additional_args: '--fail-fast --order defined' do require "guard/rspec/dsl" dsl = Guard::RSpec::Dsl.new(self) diff --git a/devops-service/app/api2/handlers/bootstrap_templates.rb b/devops-service/app/api2/handlers/bootstrap_templates.rb deleted file mode 100644 index 4c4e8f8..0000000 --- a/devops-service/app/api2/handlers/bootstrap_templates.rb +++ /dev/null @@ -1,18 +0,0 @@ -require "commands/bootstrap_templates" -require "app/api2/parsers/bootstrap_template" -require_relative "request_handler" - -module Devops - module API2_0 - module Handler - class BootstrapTemplates < RequestHandler - - set_parser Devops::API2_0::Parser::BootstrapTemplateParser - - include BootstrapTemplatesCommands - - end - end - end -end - diff --git a/devops-service/app/api2/handlers/filter.rb b/devops-service/app/api2/handlers/filter.rb deleted file mode 100644 index 8b8d73e..0000000 --- a/devops-service/app/api2/handlers/filter.rb +++ /dev/null @@ -1,27 +0,0 @@ -require "app/api2/parsers/filter" -require_relative "request_handler" - -module Devops - module API2_0 - module Handler - class Filter < RequestHandler - - set_parser Devops::API2_0::Parser::FilterParser - - def available_images provider - Devops::Db.connector.available_images(provider) - end - - def add_images provider - Devops::Db.connector.add_available_images(parser.images, provider) - end - - def delete_images provider - Devops::Db.connector.delete_available_images(parser.images, provider) - end - - end - end - end -end - diff --git a/devops-service/app/api2/handlers/flavor.rb b/devops-service/app/api2/handlers/flavor.rb deleted file mode 100644 index 34ddb12..0000000 --- a/devops-service/app/api2/handlers/flavor.rb +++ /dev/null @@ -1,20 +0,0 @@ -require_relative "request_handler" -require "providers/provider_factory" - -module Devops - module API2_0 - module Handler - class Flavor < RequestHandler - - def flavors provider - flavors_with_account(provider, nil) - end - - def flavors_with_account provider, account - ::Provider::ProviderFactory.get(provider, account).flavors - end - end - end - end -end - diff --git a/devops-service/app/api2/handlers/group.rb b/devops-service/app/api2/handlers/group.rb deleted file mode 100644 index 8b44aed..0000000 --- a/devops-service/app/api2/handlers/group.rb +++ /dev/null @@ -1,24 +0,0 @@ -require "app/api2/parsers/security_groups" -require "providers/provider_factory" -require_relative "request_handler" - -module Devops - module API2_0 - module Handler - class Group < RequestHandler - - set_parser Devops::API2_0::Parser::SecurityGroupsParser - - def groups provider - groups_with_account(provider, nil) - end - - def groups_with_account provider, account - available_keys = ["vpc-id"] - ::Provider::ProviderFactory.get(provider, account).groups(parser.security_groups.select{|k,v| available_keys.include?(k)}) - end - end - end - end -end - diff --git a/devops-service/app/api2/handlers/image.rb b/devops-service/app/api2/handlers/image.rb deleted file mode 100644 index f743081..0000000 --- a/devops-service/app/api2/handlers/image.rb +++ /dev/null @@ -1,58 +0,0 @@ -require "providers/provider_factory" -require "commands/image" -require "app/api2/parsers/image" -require_relative "request_handler" - -module Devops - module API2_0 - module Handler - class Image < RequestHandler - - set_parser Devops::API2_0::Parser::ImageParser - - extend ImageCommands - - def images - Devops::Db.connector.images(parser.images) - end - - def provider_images provider - Image.get_available_provider_images(Devops::Db.connector, provider) - end - - def provider_account_images provider, account - Image.get_available_provider_images(Devops::Db.connector, provider, account) - end - - def image id - Devops::Db.connector.image(id) - end - - def create_image - Devops::Db.connector.image_insert parser.image - end - - def update_image id - Devops::Db.connector.image id - obj = parser.image - obj.id = id - Devops::Db.connector.image_update obj - end - - def delete_image id - projects = Devops::Db.connector.projects_by_image id - unless projects.empty? - ar = [] - projects.each do |p| - ar += p.deploy_envs.select{|e| e.respond_to?(:image)}.select{|e| e.image == id}.map{|e| "#{p.id}.#{e.identifier}"} - end - raise DependencyError.new "Deleting is forbidden: Image is used in #{ar.join(", ")}" - end - - Devops::Db.connector.image_delete id - end - end - end - end -end - diff --git a/devops-service/app/api2/handlers/key.rb b/devops-service/app/api2/handlers/key.rb deleted file mode 100644 index 9bb3b09..0000000 --- a/devops-service/app/api2/handlers/key.rb +++ /dev/null @@ -1,53 +0,0 @@ -require "db/mongo/models/key" -require "fileutils" -require "app/api2/parsers/key" -require_relative "request_handler" - -module Devops - module API2_0 - module Handler - class Key < RequestHandler - - set_parser Devops::API2_0::Parser::KeyParser - - def keys - # Devops::Db.connector.keys({}, {path: false}) - Devops::Db.connector.keys({}) - end - - def create(keys_dir) - body = parser.create - fname = body["file_name"] - file_name = File.join(keys_dir, fname) - raise InvalidRecord.new("File '#{fname}' already exist") if File.exists?(file_name) - File.open(file_name, "w") do |f| - f.write(body["content"]) - f.chmod(0400) - end - - key = Devops::Model::Key.new({"path" => file_name, "id" => body["key_name"]}) - Devops::Db.connector.key_insert key - end - - def delete key_id - mongo = Devops::Db.connector - servers = mongo.servers_by_key key_id - unless servers.empty? - s_str = servers.map{|s| s.id}.join(", ") - raise DependencyError.new "Deleting is forbidden: Key is used in servers: #{s_str}" - end - - k = mongo.key key_id - begin - FileUtils.rm(k.path) - rescue - DevopsLogger.logger.error "Missing key file for #{key_id} - #{k.filename}" - end - mongo.key_delete key_id - end - - end - end - end -end - diff --git a/devops-service/app/api2/handlers/network.rb b/devops-service/app/api2/handlers/network.rb deleted file mode 100644 index 24403eb..0000000 --- a/devops-service/app/api2/handlers/network.rb +++ /dev/null @@ -1,25 +0,0 @@ -require "app/api2/parsers/network" -require "providers/provider_factory" -require_relative "request_handler" - -module Devops - module API2_0 - module Handler - class Network < RequestHandler - - set_parser Devops::API2_0::Parser::NetworkParser - - def networks provider - networks_with_account provider, nil - end - - def networks_with_account provider, account - p = ::Provider::ProviderFactory.get(provider, account) - available_keys = ["vpc-id"] - p.networks_detail(parser.networks.select{|k,v| available_keys.include?(k)}) - end - end - end - end -end - diff --git a/devops-service/app/api2/handlers/project.rb b/devops-service/app/api2/handlers/project.rb deleted file mode 100644 index 873b18d..0000000 --- a/devops-service/app/api2/handlers/project.rb +++ /dev/null @@ -1,332 +0,0 @@ -require "commands/status" -require "db/mongo/models/project" -require "workers/project_test_worker" -require "app/api2/parsers/project" -require "lib/project/type/types_factory" -require "lib/executors/server_executor" -require "workers/delete_server_worker" -require_relative "../helpers/version_2.rb" -require_relative "request_handler" - -module Devops - module API2_0 - module Handler - class Project < RequestHandler - - set_parser Devops::API2_0::Parser::ProjectParser - - include Devops::API2_0::Helpers - - extend StatusCommands - - def project_types - Devops::TypesFactory.types_names - end - - def projects - Devops::Db.connector.projects(nil, nil, parser.projects, parser.archived_projects) - end - - def project id - Devops::Db.connector.project(id) - end - - def project_deploy_envs(id) - project = Devops::Db.connector.project(id) - project.deploy_envs - end - - def project_deploy_env(project_id, env) - project = Devops::Db.connector.project(project_id) - project.deploy_env(env) - end - - def project_servers id - Devops::Db.connector.project(id) - Devops::Db.connector.servers(id, parser.project_servers) - end - - def project_stacks id - # check if project exists - Devops::Db.connector.project(id) - options = {project: id} - deploy_env = parser.project_stacks - options[:deploy_env] = deploy_env if deploy_env - Devops::Db.connector.stacks(options) - end - - def create_project - p = parser.create_project - raise InvalidRecord.new("Project '#{p.id}' already exist") if Devops::Db.connector.is_project_exists?(p) - - p.deploy_envs.each do |env| - env.add_users [parser.current_user] - end - res = p.create - info = if p.multi? - "Project '#{p.id}' with type 'multi' created" - else - "Project '#{p.id}' created." - end -# info << " " + res[:before] if res[:before] -# info << " " + res[:after] if res[:after] - info - end - - def set_project_components id - body = parser.set_project_components - project = Devops::Db.connector.project(id) - project.components = body["components"] - project.validate_components - Devops::Db.connector.project_update_field id, "components", body["components"] - "Updated project '#{project.id}' with components '#{body["components"].inspect}'" - end - - def add_deploy_env id - project = Devops::Db.connector.project(id) - env = parser.add_deploy_env - env.add_users [parser.current_user] - env.validate! - begin - db_env = project.deploy_env(env.identifier) - raise InvalidRecord.new("Can not add new environment for project '#{id}'. Environment '#{env.identifier}' already exist") - rescue RecordNotFound => e - res = project.add_deploy_env env - return "Deploy environment '#{env.identifier}' has been added to project '#{project.id}'." + res, env - end - end - - def update_deploy_env_field id, deploy_env, field - project = Devops::Db.connector.project(id) - db_env = project.deploy_env(deploy_env) - value = parser.update_deploy_env_field - if db_env.respond_to?(field + "=") - if field == "identifier" - Devops::Db.connector.change_servers_deploy_env(db_env.identifier, value) - Devops::Db.connector.change_stacks_deploy_env(db_env.identifier, value) - db_env.rename id, value - "Environment '#{deploy_env}' has been renamed to '#{value}'" - else - db_env.update_field(id, field, value) - "Environment's field '#{field}' has been updated" - end - else - raise RecordNotFound.new("Field '#{field}' does not exist") - end - end - - def update_deploy_env id, deploy_env - project = Devops::Db.connector.project(id) - db_env = project.deploy_env(deploy_env) - env = parser.update_deploy_env - env.identifier = deploy_env if env.identifier.nil? - begin - unless env.identifier == deploy_env - servers = Devops::Db.connector.servers_by_project_and_deploy_env(id, deploy_env) - raise InvalidRecord.new("Environment '#{deploy_env}' can't be updated: it has #{servers.size} running servers.") unless servers.empty? - end - begin - project.deploy_env(env.identifier) - raise InvalidRecord.new("Environment '#{deploy_env}' can't be renamed to '#{env.identifier}', environment '#{env.identifier}' already exists") unless deploy_env == env.identifier - rescue RecordNotFound => e - end - env.validate! - project.delete_deploy_env(deploy_env) - project.add_deploy_env(env) - "Deploy environment '#{deploy_env}' has been updated in project '#{project.id}'" - rescue RecordNotFound => e - env.identifier = deploy_env - res = project.add_deploy_env env - "Deploy environment '#{env.identifier}' has been added to project '#{project.id}'." + res - end - end - - def delete_deploy_env id, deploy_env - project = Devops::Db.connector.project(id) - servers = Devops::Db.connector.servers_by_project_and_deploy_env(id, deploy_env) - raise InvalidRecord.new("Can not delete environment '#{deploy_env}', there are #{servers.size} servers on it") unless servers.empty? - project.delete_deploy_env(deploy_env) - "Deploy environment '#{deploy_env}' has been deleted from project '#{project.id}'." - end - - # updates only run_list and description - def update_project id - body = parser.update - old_project = Devops::Db.connector.project id - Devops::Db.connector.project_update id, body - end - - # TODO: multi project - def update_project_users id - deploy_env, users = parser.project_users - project = Devops::Db.connector.project(id) - Validators::Helpers::Users.new(users).validate! - project.add_authorized_user users, deploy_env - info = "Users '#{users.join("', '")}' has been added to '#{id}' project's authorized users." - info - end - - # TODO: multi project - def delete_project_users id - deploy_env, users = parser.project_users - project = Devops::Db.connector.project(id) - project.remove_authorized_user users, deploy_env - "Users '#{users.join("', '")}' have been removed from '#{id}' project's authorized users" - end - - def set_project_run_list id - list = parser.run_list - project = Devops::Db.connector.project(id) - Devops::Db.connector.set_project_run_list id, list - "Updated project with run_list '#{list.inspect}'" - end - - def set_project_env_run_list id, deploy_env - list = parser.run_list - project = Devops::Db.connector.project(id) - env = project.deploy_env deploy_env - Devops::Db.connector.set_project_env_run_list id, deploy_env, list - "Updated environment '#{env.identifier}' with run_list '#{list.inspect}' in project '#{project.id}'" - end - - def delete_project id - deploy_env = parser.delete - project = Devops::Db.connector.project(id) - if deploy_env.nil? - servers = Devops::Db.connector.servers id - raise DependencyError.new "Deleting project #{id} is forbidden: Project has servers" unless servers.empty? - project.delete - "Project '#{id}' is deleted" - else - servers = Devops::Db.connector.servers id, deploy_env - raise DependencyError.new "Deleting deploy_env #{deploy_env} is forbidden: Project has servers" unless servers.empty? - project.delete_deploy_env(deploy_env) - "Project '#{id}'. Deploy environment '#{deploy_env}' has been deleted" - end - end - - def deploy_project_stream out, id - # check if project exist - project = Devops::Db.connector.project(id) - deploy_env, servers = parser.deploy - keys = {} - dbserver = Devops::Db.connector.servers(id, deploy_env, servers, true) - out << (dbservers.empty? ? "No reserved servers to deploy\n" : "Deploy servers: '#{dbservers.map{|s| s.chef_node_name}.join("', '")}'\n") - status = [] - deploy_info_buf = {} - dbservers.each do |s| - begin - Devops::Db.connector.check_project_auth s.project, s.deploy_env, parser.current_user - rescue InvalidPrivileges, RecordNotFound => e - out << e.message + "\n" - status.push 2 - next - end - deploy_env_model = project.deploy_env(s.deploy_env) - deploy_info = if deploy_info_buf[s.deploy_env] - deploy_info_buf[s.deploy_env] - else - # мы не можем указать один build_number для всех окружений, поэтому nil - deploy_info_buf[s.deploy_env] = project.deploy_info(deploy_env_model, nil) - end - status.push(Devops::Executor::ServerExecutor.new(s, out, current_user: parser.current_user).deploy_server(deploy_info)) - end - status - end - - def deploy_project id - # check if project exist - project_model = Devops::Db.connector.project(id) - deploy_env, servers = parser.deploy - files = [] - dbservers = Devops::Db.connector.servers(id, deploy_env, servers, true) - #out << (dbservers.empty? ? "No reserved servers to deploy\n" : "Deploy servers: '#{dbservers.map{|s| s.chef_node_name}.join("', '")}'\n") - deploy_info_buf = {} - dbservers.each do |s| - begin - Devops::Db.connector.check_project_auth s.project, s.deploy_env, parser.current_user - rescue InvalidPrivileges, RecordNotFound => e - next - end - - deploy_env_model = project_model.deploy_env(s.deploy_env) - deploy_info = if deploy_info_buf[s.deploy_env] - deploy_info_buf[s.deploy_env] - else - # мы не можем указать один build_number для всех окружений, поэтому nil - deploy_info_buf[s.deploy_env] = project_model.deploy_info(deploy_env_model, nil) - end - - jid = Worker.start_async(DeployWorker, - server_attrs: s.to_hash, - owner: parser.current_user, - tags: [], - deploy_info: deploy_info - ) - files.push jid - end - files - end - - def archive_project id - project = Devops::Db.connector.project(id) - Devops::Db.connector.archive_project(id) - msg = "Project '#{id}' has been archived" - DevopsLogger.logger.info msg - msg - end - - def unarchive_project id - project = Devops::Db.connector.project(id) - Devops::Db.connector.unarchive_project(id) - msg = "Project '#{id}' has been unarchived" - DevopsLogger.logger.info msg - msg - end - - def test_project id, deploy_env - project = Devops::Db.connector.project(id) - env = project.deploy_env deploy_env - DevopsLogger.logger.info "Test project '#{project.id}' and environment '#{env.identifier}'" - if env.provider == ::Provider::Static::PROVIDER - msg = "Can not test environment with provider '#{::Provider::Static::PROVIDER}'" - Logger.warn msg - raise InvalidRecord.new(msg) - end - - jid = Worker.start_async(ProjectTestWorker, - project: project.id, - deploy_env: env.identifier, - user: @request.env['REMOTE_USER'] - ) - - sleep 1 - return [jid] - end - - def delete_project_servers(project_id) - env_id, dry_run = parser.delete_project_servers - Devops::Db.connector.project(project_id) - servers = Devops::Db.connector.servers(project_id, env_id) - info = {to_delete: servers.map(&:id)} - if !dry_run - info.merge!(delete_chosen_servers!(servers)) - end - info - end - - private - - def delete_chosen_servers!(servers) - current_user = parser.current_user - reports = servers.map do |server| - Worker.start_async(DeleteServerWorker, 'server_id' => server.id, 'current_user' => current_user) - end - {reports: reports} - end - - end - end - end -end - diff --git a/devops-service/app/api2/handlers/provider.rb b/devops-service/app/api2/handlers/provider.rb deleted file mode 100644 index 7e74f27..0000000 --- a/devops-service/app/api2/handlers/provider.rb +++ /dev/null @@ -1,50 +0,0 @@ -require "app/api2/parsers/provider" -require "providers/provider_factory" -require_relative "request_handler" -require "db/mongo/models/provider_accounts/static_provider_account" -require "db/mongo/models/provider_accounts/openstack_provider_account" -require "db/mongo/models/provider_accounts/ec2_provider_account" - -module Devops - module API2_0 - module Handler - class Provider < RequestHandler - - set_parser Devops::API2_0::Parser::ProviderParser - - def providers - ::Provider::ProviderFactory.providers - end - - def accounts provider - ::Provider::ProviderFactory.get_accounts_factory(provider).accounts - end - - def account_fields provider - ::Provider::ProviderFactory.get_account_class(provider).account_fields - end - - def add_account provider - account = ::Provider::ProviderFactory.get_accounts_factory(provider).create_account(parser.account) - account.validate_fields! - Devops::Db.connector.provider_account_insert(account) - ::Provider::ProviderFactory.add_account(provider, account) - account.to_hash - end - - def delete_account name, provider - account = Devops::Db.connector.provider_account(provider, name) - Devops::Db.connector.provider_account_delete(name) - ::Provider::ProviderFactory.delete_account(provider, account) - account.to_hash - end - - def account_vpcs provider, name - Devops::Db.connector.provider_account(provider, name) - ::Provider::ProviderFactory.get(provider, name).describe_vpcs - end - - end - end - end -end diff --git a/devops-service/app/api2/handlers/report.rb b/devops-service/app/api2/handlers/report.rb deleted file mode 100644 index ec10eea..0000000 --- a/devops-service/app/api2/handlers/report.rb +++ /dev/null @@ -1,61 +0,0 @@ -require_relative "request_handler" - -module Devops - module API2_0 - module Handler - class Report < RequestHandler - - def options - params = @request.params - options = {} - ["project", "deploy_env", "type", "created_by", "date_from", "date_to", "sort", "status", "chef_node_name", "max_number"].each do |k| - options[k] = params[k] unless params[k].nil? - end - #attributes_keys = params.keys.select{|k| k =~ /attributes\.*/} - #attributes_keys.each do |ak| - # options[ak] = params[ak] - #end - options - end - - def all - Devops::Db.connector.reports(options) - end - - def all_latest - Devops::Db.connector.latest_reports(options()) - end - - def attributes name - Devops::Db.connector.reports_attributes_values(name) - end - - def report id - r = Devops::Db.connector.report(id) - file = r.file - raise RecordNotFound.new("Report '#{id}' does not exist") unless File.exists? file - return Rack::Utils.escape_html(File.read(file).encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')), completed?(id) - rescue RecordNotFound => e - if status(id) == Worker::STATUS::IN_QUEUE - return "Task '#{id}' has been queued", false - else - raise e - end - end - - def status id - Sidekiq.redis do |connection| - connection.hget("devops", id) - end - end - - def completed? id - r = self.status(id) - r == "completed" or r == "failed" - end - - end - end - end -end - diff --git a/devops-service/app/api2/handlers/request_handler.rb b/devops-service/app/api2/handlers/request_handler.rb deleted file mode 100644 index cf50caa..0000000 --- a/devops-service/app/api2/handlers/request_handler.rb +++ /dev/null @@ -1,26 +0,0 @@ -module Devops - module API2_0 - module Handler - class RequestHandler - - class << self - def set_parser parser - define_method("parser") do - @request_parser ||= parser.new(@request) - end - end - end - - def initialize request - @request = request - end - - private - - def owner_from_request - @request.env['REMOTE_USER'] - end - end - end - end -end diff --git a/devops-service/app/api2/handlers/script.rb b/devops-service/app/api2/handlers/script.rb deleted file mode 100644 index 80ac4f4..0000000 --- a/devops-service/app/api2/handlers/script.rb +++ /dev/null @@ -1,103 +0,0 @@ -require "providers/provider_factory" -require "fileutils" -require "commands/status" -require "app/api2/parsers/script" -require_relative "request_handler" - -module Devops - module API2_0 - module Handler - class Script < RequestHandler - - set_parser Devops::API2_0::Parser::ScriptParser - - def scripts - res = [] - Dir.foreach(DevopsConfig.config[:scripts_dir]) {|f| res.push(f) unless f.start_with?(".")} - end - - def execute_command out, node_name - cmd = parse.execute_command - user = parser.current_user - s = ::Devops::Db.connector.server_by_chef_node_name node_name - ::Devops::Db.connector.check_project_auth s.project, s.deploy_env, user - cert = ::Devops::Db.connector.key s.key - addr = "#{s.remote_user}@#{s.public_ip || s.private_ip}" - ssh_cmd = "ssh -i %s #{addr} '#{cmd}'" - out << ssh_cmd % File.basename(cert.path) - out << "\n" - IO.popen((ssh_cmd % cert.path) + " 2>&1") do |so| - while line = so.gets do - out << line - end - end - out << "\nDone" - end - - def run_script out, script_name - nodes, script_params = parser.run_script - file = File.join(DevopsConfig.config[:scripts_dir], script_name) - unless File.exists?(file) - out << "File '#{file_name}' does not exist\n" - return - end - servers = ::Devops::Db.connector.servers_by_names(nodes) - if servers.empty? - out << "No servers found for names '#{nodes.join("', '")}'\n" - return - end - user = @request.env['REMOTE_USER'] - servers.each do |s| - ::Devops::Db.connector.check_project_auth s.project, s.deploy_env, user - end - status = [] - servers.each do |s| - cert = begin - ::Devops::Db.connector.key s.key - rescue - out << "No key found for '#{s.chef_node_name}'" - status.push 2 - next - end - ssh_cmd = "ssh -i #{cert.path} #{s.remote_user}@#{s.public_ip || s.private_ip} 'bash -s' < %s" - out << "\nRun script on '#{s.chef_node_name}'\n" - unless script_params.nil? - ssh_cmd += " " + script_params.join(" ") - end - out << (ssh_cmd % [file_name]) - out << "\n" - - begin - IO.popen( (ssh_cmd % [file]) + " 2>&1") do |so| - while line = so.gets do - out << line - end - so.close - status.push $?.to_i - end - rescue IOError => e - logger.error e.message - out << e.message - status.push 3 - end - end - status - end - - def create_script file_name - file = File.join(Devops::Api2.settings.scripts_dir, file_name) - raise RecordNotFound.new("File '#{file_name}' already exist") if File.exists?(file) - File.open(file, "w") {|f| f.write(parser.create_script)} - end - - def delete_script file_name - file = File.join(Devops::Api2.settings.scripts_dir, file_name) - raise RecordNotFound.new("File '#{file_name}' does not exist") unless File.exists?(file) - FileUtils.rm(file) - end - - end - end - end -end - diff --git a/devops-service/app/api2/handlers/server.rb b/devops-service/app/api2/handlers/server.rb deleted file mode 100644 index 069c524..0000000 --- a/devops-service/app/api2/handlers/server.rb +++ /dev/null @@ -1,382 +0,0 @@ -require "uri" - -require "commands/status" -require "commands/bootstrap_templates" - -require "lib/executors/server_executor" -require "providers/provider_factory" - -require "db/mongo/models/server" - -require "workers/create_server_worker" -require "workers/bootstrap_worker" -require "workers/delete_server_worker" -require "app/api2/parsers/server" -require_relative "request_handler" - -module Devops - module API2_0 - module Handler - class Server < RequestHandler - - set_parser Devops::API2_0::Parser::ServerParser - - extend StatusCommands - extend BootstrapTemplatesCommands - - #scheduler = Rufus::Scheduler.new - - def servers - reserved = parser.servers - Devops::Db.connector.servers(nil, nil, nil, reserved).map {|s| s.to_hash} - end - - def chef_servers - KnifeFactory.instance.chef_node_list - end - - def provider_servers provider - provider_servers_with_account provider, nil - end - - def provider_servers_with_account provider, account - ::Provider::ProviderFactory.get(provider, account).servers - end - - def server id - get_server_by_key(id, parser.server) - end - - def delete id - server = get_server_by_key(id, parser.instance_key) - current_user = parser.current_user - Devops::Db.connector.check_project_auth server.project, server.deploy_env, current_user - jid = Worker.start_async(DeleteServerWorker, 'server_id' => server.id, 'current_user' => current_user) - [jid] - end - - def delete_list - server_ids = parser.delete_list.uniq - servers = server_ids.map { |id| Devops::Db.connector.server_by_instance_id(id) } - current_user = parser.current_user - check_servers_list_auth(servers, current_user) - server_ids.inject({}) do |hash, server_id| - hash[server_id] = Worker.start_async(DeleteServerWorker, 'server_id' => server_id, 'current_user' => current_user) - hash - end - end - - def create_server_stream out - status = [] - prepare_create_server do |project, env, user, body| - e = Devops::Executor::ServerExecutor.new(nil, out) - e.project = project - e.deploy_env = env - body["created_by"] = user - res = e.create_server(body) - status.push res - end - status - end - - def create_server - body = parser.create - user = parser.current_user - - check_if_server_attrs_are_valid(body, user) - jid = Worker.start_async(CreateServerWorker, - server_attrs: body, - owner: user - ) - [jid] - end - - def deploy node_name - call_deploy(node_name) do |options| - [ Worker.start_async(DeployWorker, @request, options) ] - end - end - - def deploy_stream node_name, out - call_deploy(node_name) do |options| - Worker.start_sync(DeployWorker, @request, options, out) - end - rescue RecordNotFound => e - out << e.message - -10 - end - - def call_deploy node_name - body = parser.deploy - names = body["names"] - tags = body["tags"] || [] - run_list = body["run_list"] - dir = DevopsConfig.config[:report_dir_v2] - owner = parser.current_user - s = Server.get_server_by_key(node_name, parser.instance_key) - project = Devops::Db.connector.check_project_auth s.project, s.deploy_env, owner - deploy_info = create_deploy_info(s, project, body["build_number"]) - deploy_info["run_list"] = run_list if run_list - - yield({ - server_attrs: s.to_hash, - owner: owner, - tags: tags, - deploy_info: deploy_info - }) - end - - def pause_server node_name - s = get_server_by_key(node_name, parser.instance_key) - ## Authorization - Devops::Db.connector.check_project_auth s.project, s.deploy_env, parser.current_user - provider = s.provider_instance - r = provider.pause_server s - if r.nil? - set_last_operation_and_save(s, Devops::Model::Server::OperationType::PAUSE) - - "Server with instance ID '#{s.id}' and node name '#{node_name}' is paused" - else - raise ConflictException.new("Server with instance ID '#{s.id}' and node name '#{node_name}' can not be paused, It in state '#{r}'", 409) - end - end - - def unpause_server node_name - s = get_server_by_key(node_name, parser.instance_key) - ## Authorization - Devops::Db.connector.check_project_auth s.project, s.deploy_env, parser.current_user - provider = s.provider_instance - r = provider.unpause_server s - if r.nil? - set_last_operation_and_save(s, Devops::Model::Server::OperationType::UNPAUSE) - - "Server with instance ID '#{s.id}' and node name '#{node_name}' is unpaused" - else - raise ConflictException.new("Server with instance ID '#{s.id}' and node name '#{node_name}' can not be unpaused, It in state '#{r}'") - end - end - - def reserve_server node_name - s = get_server_by_key(node_name, parser.instance_key) - user = parser.current_user - Devops::Db.connector.check_project_auth s.project, s.deploy_env, user - raise ConflictException.new("Server '#{node_name}' already reserved") unless s.reserved_by.nil? - s.reserved_by = user - set_last_operation_and_save(s, Devops::Model::Server::OperationType::RESERVE) - end - - def unreserve_server node_name - s = get_server_by_key(node_name, parser.instance_key) - Devops::Db.connector.check_project_auth s.project, s.deploy_env, parser.current_user - raise ConflictException.new("Server '#{node_name}' is not reserved") if s.reserved_by.nil? - s.reserved_by = nil - set_last_operation_and_save(s, Devops::Model::Server::OperationType::UNRESERVE) - end - - # TODO: check bootstrap template name - def bootstrap_server_stream out - options = prepare_bootstrap_server - s = options.delete(:server) - status = [] - cert = Devops::Db.connector.key s.key - DevopsLogger.logger.debug "Bootstrap certificate path: #{cert.path}" - #bootstrap s, out, cert.path, logger - options[:cert_path] = cert.path - executor = Devops::Executor::ServerExecutor.new(s, out, current_user: parser.current_user) - r = executor.two_phase_bootstrap(options) - str = nil - r = if check_server(s) - Devops::Db.connector.server_set_chef_node_name s - str = "Server with id '#{s.id}' is bootstraped" - DevopsLogger.logger.info str - 0 - else - str = "Server with id '#{s.id}' is not bootstraped" - DevopsLogger.logger.warn str - 1 - end - status.push r - out << str - out << "\n" - status - end - - def bootstrap_server - attrs = prepare_bootstrap_server - server = attrs[:server] - bootstrap_template = attrs[:bootstrap_template] - - jid = Worker.start_async(BootstrapWorker, - server_attrs: server.to_mongo_hash, - bootstrap_template: bootstrap_template, - owner: parser.current_user - ) - - [jid] - end - - def prepare_bootstrap_server - body = parser.bootstrap - id = body["instance_id"] - name = body["name"] - rl = body["run_list"] - t = body["bootstrap_template"] - s = Devops::Db.connector.server_by_instance_id(id) - res = {server: s} - - p = Devops::Db.connector.check_project_auth s.project, s.deploy_env, parser.current_user - d = p.deploy_env(s.deploy_env) - - provider = s.provider_instance - - check_chef_node_name(name, provider, KnifeFactory.instance) unless name.nil? - unless t.nil? - templates = get_templates - halt_response("Invalid bootstrap template '#{t}', available values: #{templates.join(", ")}", 400) unless templates.include?(t) - res[:bootstrap_template] = t - end - s.chef_node_name = name || provider.create_default_chef_node_name(s) - res[:run_list] = rl || d.run_list - return res - end - - def unbootstrap_server id - s = get_server_by_key(id, parser.instance_key) - ### Authorization - Devops::Db.connector.check_project_auth s.project, s.deploy_env, parser.current_user - Devops::Executor::ServerExecutor.new(s, "").unbootstrap - end - - def add_server - project, deploy_env, server_attrs = parser.add_server - Devops::Db.connector.check_project_auth project, deploy_env, parser.current_user - - server = add_static_server(server_attrs) - "Server '#{server.id}' has been added" - end - - # returns jobs ids - def add_and_bootstrap_servers - body, servers_attrs = parser.add_and_bootstrap_servers - Devops::Db.connector.check_project_auth body['project'], body['deploy_env'], parser.current_user - servers_attrs.map do |attrs| - server = add_static_server(attrs) - Worker.start_async(BootstrapWorker, - server_attrs: server.to_mongo_hash, - bootstrap_template: body['bootstrap_template'], - owner: parser.current_user - ) - end - end - - def set_tags node_name - tags = parser.tags - prepare_tags do |id, provider| - provider.set_tags id, tags - end - end - - def unset_tags node_name - tags = parser.tags - prepare_tags do |id, provider| - provider.unset_tags id, tags - end - end - - def prepare_tags node_name - s = get_server_by_key(node_name, parser.instance_key) - Devops::Db.connector.check_project_auth s.project, s.deploy_env, parser.current_user - provider = ::Provider::ProviderFactory.get(s.provider) - yield s.id, provider - end - - def set_run_list node_name - s = get_server_by_key(node_name, parser.instance_key) - Devops::Db.connector.check_project_auth s.project, s.deploy_env, parser.current_user - Devops::Db.connector.set_server_run_list(s.id, parser.run_list) - end - - def get_server_by_key id, key - mongo = Devops::Db.connector - key == "instance" ? mongo.server_by_instance_id(id) : mongo.server_by_chef_node_name(id) - end - - def check_chef_node_name name, provider, knife - Devops::Db.connector.server_by_chef_node_name(name) - raise InvalidRecord.new("Server with name '#{name}' already exist") - rescue RecordNotFound => e - # server not found - OK - s = provider.servers.detect {|s| s["name"] == name} - raise InvalidRecord.new("#{provider.name} node with name '#{name}' already exist") unless s.nil? - s = knife.chef_node_list.detect {|n| n == name} - raise InvalidRecord.new("Chef node with name '#{name}' already exist") unless s.nil? - s = knife.chef_client_list.detect {|c| c == name} - raise InvalidRecord.new("Chef client with name '#{name}' already exist") unless s.nil? - end - - private - - # Security checks should be already done here. - # @attrs should be a hash with: - # :project - # :deploy_env - # :key - # :remote_user - # :private_ip - # :public_ip - # :chef_node_name - # :run_list - def add_static_server(attrs) - server = Devops::Model::Server.new( - '_id' => "static_#{attrs[:key]}-#{attrs[:chef_node_name]}-#{Time.now.to_i}", - 'provider' => ::Provider::ProviderFactory.get('static').name, - 'project' => attrs[:project], - 'deploy_env' => attrs[:deploy_env], - 'remote_user' => attrs[:remote_user], - 'private_ip' => attrs[:private_ip], - 'public_ip' => attrs[:public_ip], - 'key' => attrs[:key], - 'chef_node_name' => attrs[:chef_node_name], - 'run_list' => attrs[:run_list] - ) - Devops::Db.connector.server_insert(server) - server - end - - def check_if_server_attrs_are_valid(body, user) - key_name = body["key"] - Devops::Db.connector.key(key_name) unless key_name.nil? - - project = Devops::Db.connector.check_project_auth(body["project"], body["deploy_env"], user) - env = project.deploy_env(body["deploy_env"]) - - provider = env.provider_instance - server_name = body["name"] - check_chef_node_name(server_name, provider, KnifeFactory.instance) unless server_name.nil? - groups = body["groups"] - unless groups.nil? - buf = groups - provider.groups.keys - halt_response("Invalid security groups '#{buf.join("', '")}' for provider '#{provider.name}'") if buf.empty? - end - end - - def set_last_operation_and_save(server, operation_type) - server.set_last_operation(operation_type, parser.current_user) - Devops::Db.connector.server_update(server) - end - - def check_servers_list_auth(servers, current_user) - project_with_env_pairs = servers.map do |server| - [server.project, server.deploy_env] - end - project_with_env_pairs.uniq.each do |pair| - project, env = *pair - Devops::Db.connector.check_project_auth project, env, current_user - end - end - - end - end - end -end - diff --git a/devops-service/app/api2/handlers/stack_template.rb b/devops-service/app/api2/handlers/stack_template.rb deleted file mode 100644 index fe134a8..0000000 --- a/devops-service/app/api2/handlers/stack_template.rb +++ /dev/null @@ -1,81 +0,0 @@ -require 'db/mongo/models/stack_template/stack_template_factory' -require "app/api2/parsers/stack_template" -require_relative "request_handler" - -module Devops - module API2_0 - module Handler - class StackTemplate < RequestHandler - - set_parser Devops::API2_0::Parser::StackTemplateParser - - def stack_templates - Devops::Db.connector.stack_templates - end - - def stack_templates_for_provider provider - Devops::Db.connector.stack_templates(provider) - end - - def create_stack_template provider - body = parser.create - template_model = Model::StackTemplateFactory.create(provider, body) - template_model.owner = parser.current_user - Devops::Db.connector.stack_template_insert(template_model) - template_model - end - - def get_stack_template id - Devops::Db.connector.stack_template(id) - end - - def delete_stack_template id - envs_with_this_template = envs_using_stack_template(id) - - if envs_with_this_template.empty? - Devops::Db.connector.stack_template_delete id - else - raise ConflictException.new("Stack template '#{id}' is already in use in #{envs_with_this_template.map{|project, envs| "#{project}: #{envs.join(', ')}"}.join('; ')}", {projects: envs_with_this_template}) - end - end - - # temp solution to update url on existing stacks - def update_template_url(id) - template = Devops::Db.connector.stack_template(id) - template.update_template_url - Devops::Db.connector.stack_template_update(template) - template - end - - def update_available_parameters(id) - template = Devops::Db.connector.stack_template(id) - template.available_parameters = template.parse_parameters - Devops::Db.connector.stack_template_update(template) - template - end - - private - - # returns: - # { - # "project" => ["deploy_env"] - # } - def envs_using_stack_template(id) - projects = Devops::Db.connector.projects_and_deploy_envs_by_field('stack_template', id) - envs_with_this_template = [] - res = {} - - projects.each do |project| - array = [] - res[project.id] = array - project.deploy_envs.each do |env| - array << env.identifier - end - end - res - end - - end - end - end -end diff --git a/devops-service/app/api2/handlers/user.rb b/devops-service/app/api2/handlers/user.rb deleted file mode 100644 index 5026cfd..0000000 --- a/devops-service/app/api2/handlers/user.rb +++ /dev/null @@ -1,62 +0,0 @@ -require "db/mongo/models/user" -require "app/api2/parsers/user" -require_relative "request_handler" - -module Devops - module API2_0 - module Handler - class User < RequestHandler - - set_parser Devops::API2_0::Parser::UserParser - - def users - Devops::Db.connector.users({}, fields: {password: false}) - end - - def create - Devops::Db.connector.user_insert parser.create - end - - def delete user_id - projects = Devops::Db.connector.projects_by_user user_id - if !projects.empty? - str = "" - projects.each do |p| - p.deploy_envs.each do |e| - str+="#{p.id}.#{e.identifier} " if e.users.include? user_id - end - end - raise DependencyError.new "Deleting is forbidden: User is included in #{str}" - end - Devops::Db.connector.user_delete user_id - end - - def change_user_privileges user_id - cmd, privileges = parser.user_privileges - change_user(user_id) do |user| - user.grant(cmd, privileges) - end - end - - def change_email user_id - change_user(user_id) do |user| - user.email = parser.change_email - end - end - - def change_password user_id - change_user(user_id) do |user| - user.password = parser.change_password - end - end - - def change_user user_id - user = Devops::Db.connector.user user_id - yield(user) - Devops::Db.connector.user_update user - end - end - end - end -end - diff --git a/devops-service/app/api2/helpers/request.rb b/devops-service/app/api2/helpers/request.rb deleted file mode 100644 index d704827..0000000 --- a/devops-service/app/api2/helpers/request.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'sinatra/base' -require "sinatra/json" - -require "providers/provider_factory" - -module Sinatra - module Version2_0 - module Request - module Helpers - -=begin - # Check request headers - def check_headers *headers - ha = (headers.empty? ? [:accept, :content_type] : headers) - ha.each do |h| - case h - when :accept, "accept" - accept_json - when :content_type, "content_type" - request_json - end - end - end - - # Check Accept header - # - # Can client works with JSON? - def accept_json - logger.debug(request.accept) - unless request.accept? 'application/json' - response.headers['Accept'] = 'application/json' - halt_response("Accept header should contains 'application/json' type", 406) - end - rescue NoMethodError => e - #error in sinatra 1.4.4 (https://github.com/sinatra/sinatra/issues/844, https://github.com/sinatra/sinatra/pull/805) - response.headers['Accept'] = 'application/json' - halt_response("Accept header should contains 'application/json' type", 406) - end - - # Check Content-Type header - def request_json - halt_response("Content-Type should be 'application/json'", 415) if request.media_type.nil? or request.media_type != 'application/json' - end -=end - - end - end - end -end diff --git a/devops-service/app/api2/parsers/deploy.rb b/devops-service/app/api2/parsers/deploy.rb deleted file mode 100644 index 084070a..0000000 --- a/devops-service/app/api2/parsers/deploy.rb +++ /dev/null @@ -1,22 +0,0 @@ -require_relative "request_parser" - -module Devops - module API2_0 - module Parser - class DeployParser < RequestParser - - def deploy - r = create_object_from_json_body - names = check_array(r["names"], "Parameter 'names' should be a not empty array of strings") - tags = check_array(r["tags"], "Parameter 'tags' should be an array of strings", String, true) - build_number = check_string(r["build_number"], "Parameter 'build_number' should be a not empty string", true) - rl = check_array(r["run_list"], "Parameter 'run_list' should be an array of string", String, true) - Validators::Helpers::RunList.new(rl).validate! unless rl.nil? - check_string(r["named_task"], "Parameter 'named_task' should be a not empty string", true) - r - end - - end - end - end -end diff --git a/devops-service/app/api2/parsers/network.rb b/devops-service/app/api2/parsers/network.rb deleted file mode 100644 index e808cc3..0000000 --- a/devops-service/app/api2/parsers/network.rb +++ /dev/null @@ -1,15 +0,0 @@ -require_relative "request_parser" - -module Devops - module API2_0 - module Parser - class NetworkParser < RequestParser - - def networks - @params - end - end - end - end -end - diff --git a/devops-service/app/api2/parsers/user.rb b/devops-service/app/api2/parsers/user.rb deleted file mode 100644 index ac7858a..0000000 --- a/devops-service/app/api2/parsers/user.rb +++ /dev/null @@ -1,37 +0,0 @@ -require_relative "request_parser" - -module Devops - module API2_0 - module Parser - class UserParser < RequestParser - - def create - user = create_object_from_json_body - ["username", "password", "email"].each do |p| - check_string(user[p], "Parameter '#{p}' must be a not empty string") - end - Devops::Model::User.new(user) - end - - def user_privileges - data = create_object_from_json_body - cmd = check_string(data["cmd"], "Parameter 'cmd' should be a not empty string", true) - privileges = check_string(data["privileges"], "Parameter 'privileges' should be a string", true, true) - return cmd, privileges - end - - def change_password - body = create_object_from_json_body - check_string(body["password"], "Parameter 'password' must be a not empty string") - end - - def change_email - body = create_object_from_json_body - check_string(body["email"], "Parameter 'email' must be a not empty string") - end - - end - end - end -end - diff --git a/devops-service/app/api2/routes/bootstrap_templates.rb b/devops-service/app/api2/routes/bootstrap_templates.rb deleted file mode 100644 index 60d27a8..0000000 --- a/devops-service/app/api2/routes/bootstrap_templates.rb +++ /dev/null @@ -1,30 +0,0 @@ -module Devops - module API2_0 - module Routes - module BootstrapTemplatesRoutes - - def self.registered(app) - - # Get list of available bootstrap templates - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : array of strings - # [ - # "omnibus" - # ] - app.get_with_headers "/templates", :headers => [:accept] do - check_privileges("templates", "r") - json Devops::API2_0::Handler::BootstrapTemplates.new(request).get_templates - end - - puts "Bootstrap templates routes initialized" - end - - end - end - end -end diff --git a/devops-service/app/api2/routes/deploy.rb b/devops-service/app/api2/routes/deploy.rb deleted file mode 100644 index 7a2a208..0000000 --- a/devops-service/app/api2/routes/deploy.rb +++ /dev/null @@ -1,66 +0,0 @@ - -module Devops - module API2_0 - module Routes - module DeployRoutes - - def self.registered(app) - - # Run chef-client on reserved server - # - # * *Request* - # - method : POST - # - headers : - # - Content-Type: application/json - # - body : - # { - # "names": [], -> array of servers chef node names to run chef-client - # "tags": [], -> array of tags to apply on each server before running chef-client - # "build_number": "", -> string, build number to deploy - # "run_list": [], -> array of strings to set run_list for chef-client - # } - # - # * *Returns* : text stream - app.post_with_headers "/deploy", :headers => [:content_type] do - check_privileges("server", "x") - - if request["X-Stream"] - stream() do |out| - status = [] - begin - status = Devops::API2_0::Handler::Deploy.new(request).deploy_stream(out) - out << create_status(status) - rescue IOError => e - logger.error e.message - break - end - end # stream - else - files = Devops::API2_0::Handler::Deploy.new(request).deploy() - sleep 1 - json files - end - end - - app.get "/deploy/data/:project/:env" do |project, env| - p = Devops::Db.connector.project project - data = p.deploy_info(env, params["build_number"]) - content_type "application/json" - (JSON.pretty_generate data) << "\n" - end - - app.get "/deploy/data/:file" do |file| - dir = DevopsConfig.config[:project_info_dir] - file_path = File.join(dir, file) - return [404, "Data for '#{file}' not found"] unless File.exists?(file_path) - content_type "application/json" - File.read(file_path) + "\n" - end - - puts "Deploy routes initialized" - end - - end - end - end -end diff --git a/devops-service/app/api2/routes/filter.rb b/devops-service/app/api2/routes/filter.rb deleted file mode 100644 index e3efe85..0000000 --- a/devops-service/app/api2/routes/filter.rb +++ /dev/null @@ -1,80 +0,0 @@ -module Devops - module API2_0 - module Routes - module FilterRoutes - - def self.registered(app) - - # Get list of images filters for :provider - # - # Devops can works with images from filters list only - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : array of strings - # - ec2: - # [ - # "ami-83e4bcea" - # ] - # - openstack: - # [ - # "36dc7618-4178-4e29-be43-286fbfe90f50" - # ] - app.get_with_headers "/filter/:provider/images", :headers => [:accept] do |provider| - check_privileges("filter", "r") - check_provider(provider) - json Devops::API2_0::Handler::Filter.new(request).available_images(provider) - end - - hash = {} - - # Add image ids to filter for :provider - # - # * *Request* - # - method : PUT - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # [ - # "image_id" - # ] -> array of image ids to add to filter - # - # * *Returns* : list of images filters for :provider - hash["PUT"] = lambda { |provider| - check_privileges("filter", "w") - check_provider(provider) - create_response("Updated", {:images => Devops::API2_0::Handler::Filter.new(request).add_images(provider)}) - } - - # Delete image ids from filter for :provider - # - # * *Request* - # - method : DELETE - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # [ - # "image_id" - # ] -> array of image ids to delete from filter - # - # * *Returns* : list of images filters for :provider - hash["DELETE"] = lambda { |provider| - check_privileges("filter", "w") - check_provider(provider) - create_response("Deleted", {:images => Devops::API2_0::Handler::Filter.new(request).delete_images(provider)}) - } - - app.multi_routes "/filter/:provider/image", {:headers => [:accept, :content_type]}, hash - - puts "Filter routes initialized" - end - - end - end - end -end diff --git a/devops-service/app/api2/routes/flavor.rb b/devops-service/app/api2/routes/flavor.rb deleted file mode 100644 index 67db888..0000000 --- a/devops-service/app/api2/routes/flavor.rb +++ /dev/null @@ -1,53 +0,0 @@ -module Devops - module API2_0 - module Routes - module FlavorRoutes - - def self.registered(app) - # Get list of flavors for :provider - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : array of objects - # - ec2: - # [ - # { - # "id": "t1.micro", - # "cores": 2, - # "disk": 0, - # "name": "Micro Instance", - # "ram": 613 - # } - # ] - # - openstack: - # [ - # { - # "id": "m1.small", - # "v_cpus": 1, - # "ram": 2048, - # "disk": 20 - # } - # ] - app.get_with_headers "/flavors/:provider", :headers => [:accept] do |provider| - check_privileges("flavor", "r") - check_provider(provider) - json Devops::API2_0::Handler::Flavor.new(request).flavors(provider) - end - - # TODO: check account - app.get_with_headers "/flavors/:provider/:account", :headers => [:accept] do |provider, account| - check_privileges("flavor", "r") - check_provider(provider) - json Devops::API2_0::Handler::Flavor.new(request).flavors_with_account(provider, account) - end - - puts "Flavor routes initialized" - end - - end - end - end -end diff --git a/devops-service/app/api2/routes/group.rb b/devops-service/app/api2/routes/group.rb deleted file mode 100644 index 182d82f..0000000 --- a/devops-service/app/api2/routes/group.rb +++ /dev/null @@ -1,67 +0,0 @@ -# encoding: UTF-8 -module Devops - module API2_0 - module Routes - module GroupRoutes - - def self.registered(app) - - # Get security groups for :provider - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * Params: - # vpc-id - string - # - # * *Returns* : - # - ec2: - # { - # "default": { - # "description": "default group", - # "id": "sg-565cf93f", - # "rules": [ - # { - # "protocol": "tcp", - # "from": 22, - # "to": 22, - # "cidr": "0.0.0.0/0" - # } - # ] - # } - # } - # - openstack: - # { - # "default": { - # "description": "default", - # "rules": [ - # { - # "protocol": null, - # "from": null, - # "to": null, - # "cidr": null - # } - # ] - # } - # } - app.get_with_headers "/groups/:provider", :headers => [:accept] do |provider| - check_privileges("group", "r") - check_provider(provider) - json Devops::API2_0::Handler::Group.new(request).groups(provider) - end - - app.get_with_headers "/groups/:provider/:account", :headers => [:accept] do |provider, account| - check_privileges("group", "r") - check_provider(provider) - json Devops::API2_0::Handler::Group.new(request).groups_with_account(provider, account) - end - - puts "Group routes initialized" - end - - end - end - end -end diff --git a/devops-service/app/api2/routes/image.rb b/devops-service/app/api2/routes/image.rb deleted file mode 100644 index c63ae60..0000000 --- a/devops-service/app/api2/routes/image.rb +++ /dev/null @@ -1,160 +0,0 @@ -module Devops - module API2_0 - module Routes - module ImageRoutes - - def self.registered(app) - - # Get devops images list - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - parameters: - # - provider=ec2|openstack -> return images for provider - # - # * *Returns* : - # [ - # { - # "provider": "openstack", - # "name": "centos-6.4-x86_64", - # "remote_user": "root", - # "bootstrap_template": null, - # "id": "36dc7618-4178-4e29-be43-286fbfe90f50" - # } - # ] - app.get_with_headers "/images", :headers => [:accept] do - check_privileges("image", "r") - json Devops::API2_0::Handler::Image.new(request).images.map(&:to_hash) - end - - # Get raw images for :provider - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : - # - ec2 - # [ - # { - # "id": "ami-83e4bcea", - # "name": "amzn-ami-pv-2013.09.1.x86_64-ebs", - # "status": "available" - # } - # ] - # - openstack - # [ - # { - # "id": "36dc7618-4178-4e29-be43-286fbfe90f50", - # "name": "centos-6.4-x86_64", - # "status": "ACTIVE" - # } - # ] - app.get_with_headers "/images/provider/:provider", :headers => [:accept] do |provider| - check_privileges("image", "r") - check_provider(provider) - json Devops::API2_0::Handler::Image.new(request).provider_images(provider) - end - - app.get_with_headers "/images/provider/:provider/:account", :headers => [:accept] do |provider, account| - check_privileges("image", "r") - check_provider(provider) - json Devops::API2_0::Handler::Image.new(request).provider_account_images(provider, account) - end - - # Create devops image - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "id": "image id", - # "provider": "image provider", - # "remote_user": "user", -> the ssh username - # "bootstrap_template": null, -> specific bootstrap template name or nil - # "name": "image name" - # } - # - # * *Returns* : - # 201 - Created - app.post_with_headers "/image", :headers => [:accept, :content_type] do - check_privileges("image", "w") - Devops::API2_0::Handler::Image.new(request).create_image() - create_response "Created", nil, 201 - end - - hash = {} - # Get devops image by id - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : - # { - # "provider": "openstack", - # "name": "centos-6.4-x86_64", - # "remote_user": "root", - # "bootstrap_template": null, - # "id": "36dc7618-4178-4e29-be43-286fbfe90f50" - # } - hash["GET"] = lambda { |image_id| - check_privileges("image", "r") - json Devops::API2_0::Handler::Image.new(request).image(image_id) - } - - # Update devops image - # - # * *Request* - # - method : PUT - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "id": "image id", - # "provider": "image provider", - # "remote_user": "user" -> the ssh username - # "bootstrap_template": null -> specific bootstrap template name or nil - # "name": "image name" - # } - # - # * *Returns* : - # 200 - Updated - hash["PUT"] = lambda {|image_id| - check_privileges("image", "w") - Devops::API2_0::Handler::Image.new(request).update_image(image_id) - create_response("Image '#{image_id}' has been updated") - } - - # Delete devops image - # - # * *Request* - # - method : DELETE - # - headers : - # - Accept: application/json - # - # * *Returns* : - # 200 - Deleted - hash["DELETE"] = lambda {|image_id| - check_privileges("image", "w") - Devops::API2_0::Handler::Image.new(request).delete_image(image_id) - create_response("Image '#{image_id}' has been removed") - } - - app.multi_routes "/image/:image_id", {}, hash - - puts "Image routes initialized" - end - - end - end - end -end diff --git a/devops-service/app/api2/routes/key.rb b/devops-service/app/api2/routes/key.rb deleted file mode 100644 index a10e816..0000000 --- a/devops-service/app/api2/routes/key.rb +++ /dev/null @@ -1,73 +0,0 @@ -require "json" - -module Devops - module API2_0 - module Routes - module KeyRoutes - - def self.registered(app) - # Get list of available ssh keys - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : array of strings - # [ - # { - # "scope": "system", -> 'system' - key was added by server, 'user' - key was added by user - # "id": "devops" - # } - # ] - app.get_with_headers "/keys", :headers => [:accept] do - check_privileges("key", "r") - json Devops::API2_0::Handler::Key.new(request).keys.map{|k| h = k.to_hash; h.delete("path"); h} - end - - # Create ssh key on devops server - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "file_name": "key file name", - # "key_name": "key name", - # "content": "key content" - # } - # - # * *Returns* : - # 201 - Created - app.post_with_headers "/key", :headers => [:accept, :content_type] do - check_privileges("key", "w") - Devops::API2_0::Handler::Key.new(request).create(settings.keys_dir) - create_response("Created", nil, 201) - end - - # Delete ssh key from devops server - # - # * *Request* - # - method : DELETE - # - headers : - # - Accept: application/json - # - # * *Returns* : - # 200 - Deleted - app.delete_with_headers "/key/:key", :headers => [:accept] do |key| - check_privileges("key", "w") - - r = Devops::API2_0::Handler::Key.new(request).delete key - return [500, r["err"].inspect] if r["err"] - create_response("Key '#{key}' removed") - end - - puts "Key routes initialized" - end - - end - end - end -end diff --git a/devops-service/app/api2/routes/network.rb b/devops-service/app/api2/routes/network.rb deleted file mode 100644 index f984438..0000000 --- a/devops-service/app/api2/routes/network.rb +++ /dev/null @@ -1,56 +0,0 @@ -# encoding: UTF-8 -module Devops - module API2_0 - module Routes - module NetworkRoutes - - def self.registered(app) - - # Get list of networks for :provider - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * Params: - # vpc-id - string - # - # * *Returns* : array of strings - # - ec2: - # [ - # { - # "cidr": "0.0.0.0/16", - # "vpcId": "vpc-1", - # "subnetId": "subnet-1", - # "name": "subnet-1", - # "zone": "us-east-1a" - # } - # ] - # - openstack: - # [ - # { - # "cidr": "0.0.0.0/16", - # "name": "private", - # "id": "b14f8df9-ac27-48e2-8d65-f7ef78dc2654" - # } - # ] - app.get_with_headers "/networks/:provider", :headers => [:accept] do |provider| - check_privileges("network", "r") - check_provider(provider) - json Devops::API2_0::Handler::Network.new(request).networks(provider) - end - - app.get_with_headers "/networks/:provider/:account", :headers => [:accept] do |provider, account| - check_privileges("network", "r") - check_provider(provider) - json Devops::API2_0::Handler::Network.new(request).networks_with_account(provider, account) - end - - puts "Network routes initialized" - end - - end - end - end -end diff --git a/devops-service/app/api2/routes/project.rb b/devops-service/app/api2/routes/project.rb deleted file mode 100644 index 6d195fb..0000000 --- a/devops-service/app/api2/routes/project.rb +++ /dev/null @@ -1,625 +0,0 @@ -module Devops - module API2_0 - module Routes - module ProjectRoutes - - def self.registered(app) - - # Get project types - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : - # [ - # "type" - # ] - app.get_with_headers "/project_types", :headers => [:accept] do - check_privileges("project", "r") - json Devops::API2_0::Handler::Project.new(request).project_types - end - - # Get projects list - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - params : - # - fields - show project fields, available values: deploy_envs, type - # - # * *Returns* : - # [ - # {"name" : "project_1"} - # ] - app.get_with_headers "/projects", :headers => [:accept] do - check_privileges("project", "r") - projects = Devops::API2_0::Handler::Project.new(request).projects - json projects.map(&:to_hash) - end - - # Get project by id - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : - # { - # "deploy_envs": [ - # { - # "flavor": "flavor", - # "identifier": "prod", - # "image": "image id", - # "run_list": [ - # "role[project_1-prod]" - # ], - # "subnets": [ - # "private" - # ], - # "expires": null, - # "provider": "openstack", - # "groups": [ - # "default" - # ], - # "users": [ - # "user" - # ] - # } - # ], - # "name": "project_1" - # } - hash = {} - hash["GET"] = lambda {|project| - check_privileges("project", "r") - json Devops::API2_0::Handler::Project.new(request).project(project) - } - - # Update project - # - # * *Request* - # - method : PUT - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "run_list": [], - # "description": "Description", - # "named_tasks": [{ - # "name": "restart", - # "run_list": ["role[restart_service]"] - # }] - # } - # - # * *Returns* : - # 200 - Updated - hash["PUT"] = lambda { |project| - check_privileges("project", "w") - r = Devops::API2_0::Handler::Project.new(request).update_project project - create_response("Project '#{project}' has been updated.") - } - - # Delete project - # - # * *Request* - # - method : DELETE - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "deploy_env": "env" -> if not null, will be deleted environment only - # } - # - # * *Returns* : - # 200 - Deleted - hash["DELETE"] = lambda {|project| - check_privileges("project", "w") - info = Devops::API2_0::Handler::Project.new(request).delete_project(project) - create_response(info) - } - app.multi_routes "/project/:project", {}, hash - - # Get project servers - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - parameters : - # - deploy_env=:env -> show servers with environment :env - # - # * *Returns* : - # [ - # { - # "provider": "openstack", - # "chef_node_name": "project_1_server", - # "remote_user": "root", - # "project": "project_1", - # "deploy_env": "prod", - # "private_ip": "10.8.8.8", - # "public_ip": null, - # "created_at": "2014-04-23 13:35:18 UTC", - # "created_by": "user", - # "static": false, - # "key": "ssh key", - # "id": "nstance id" - # } - # ] - servers_routes_hash = {} - servers_routes_hash["GET"] = lambda { |project| - check_privileges("project", "r") - json Devops::API2_0::Handler::Project.new(request).project_servers(project).map(&:to_hash) - } - - # Deletes project servers - # - # * *Request* - # - method : DELETE - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "dry_run": false # set to true if you'd like to just see list of servers to delete - # "deploy_env": null # set to env's identifier to delete that env's servers - # } - # - # * *Returns* : - # 200 - - # { - # "to_delete": ['server1', 'server2'], - # "deleted": ['server1'], - # "failed": ['server2'] - # } - servers_routes_hash["DELETE"] = lambda { |project_id| - check_privileges("project", "w") - json Devops::API2_0::Handler::Project.new(request).delete_project_servers(project_id) - } - app.multi_routes "/project/:project/servers", {}, servers_routes_hash - - # Get project stacks - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - parameters : - # - deploy_env=:env -> show stacks with environment :env - # - # * *Returns* : - # [ - # "provider": "openstack", - # "project": "test_openstack", - # "deploy_env": "test", - # "stack_template": "openstack_template", - # "cloud_stack_id": "4c712026-dcd5-4664-90b8-0915494c1332", - # "parameters": { - # "admin_pass": "Pass12", - # "key_name": "devops", - # "image": "CirrOS_0.3.1" - # }, - # "stack_status": null, - # "id": "openstack_stack" - # ] - app.get_with_headers "/project/:project/stacks", :headers => [:accept] do |project| - check_privileges("project", "r") - json Devops::API2_0::Handler::Project.new(request).project_stacks(project).map(&:to_hash) - end - - # Get project deploy environments - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - app.get_with_headers "/project/:project/deploy_envs", :headers => [:accept] do |project| - check_privileges("project", "r") - json Devops::API2_0::Handler::Project.new(request).project_deploy_envs(project) - end - - # Add project deploy environment - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - body : - # { - # "identifier": "prod", - # "provider": "openstack", - # "flavor": "m1.small", - # "image": "image id", - # "subnets": [ - # "private" - # ], - # "groups": [ - # "default" - # ], - # "users": [ - # "user" - # ], - # "run_list": [ ], - # "expires": null - # } - # - # * *Returns* : - # 200 - Added - app.post_with_headers "/project/:project/deploy_env", :headers => [:accept, :content_type] do |project| - check_privileges("project", "w") - res, env = Devops::API2_0::Handler::Project.new(request).add_deploy_env(project) - create_response(res, {environment: env.to_hash}, 200) - end - - deploy_env_hash = {} - # Get project deploy environment - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - deploy_env_hash["GET"] = lambda{|project, env| - check_privileges("project", "r") - json Devops::API2_0::Handler::Project.new(request).project_deploy_env(project, env) - } - - # Update deploy_env, you can send few fields in the body - # - # * *Request* - # - method : PUT - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "identifier": "prod", - # "provider": "openstack", - # "flavor": "m1.small", - # "image": "image id", - # "subnets": [ - # "private" - # ], - # "groups": [ - # "default" - # ], - # "users": [ - # "user" - # ], - # "run_list": [ ], - # "expires": null - # } - # - # * *Returns* : - # 200 - Updated - deploy_env_hash["PUT"] = lambda{|id, deploy_env| - check_privileges("project", "w") - begin - res = Devops::API2_0::Handler::Project.new(request).update_deploy_env(id, deploy_env) - create_response(res, nil, 200) - rescue InvalidRecord => e - halt_response(e.message) - end - } - - # Delete deploy_env - # - # * *Request* - # - method : DELETE - # - headers : - # - Accept: application/json - # - # * *Returns* : - # 200 - Deleted - deploy_env_hash["DELETE"] = lambda{|id, deploy_env| - check_privileges("project", "w") - begin - res = Devops::API2_0::Handler::Project.new(request).delete_deploy_env(id, deploy_env) - create_response(res, nil, 200) - rescue InvalidRecord => e - halt_response(e.message) - end - } - app.multi_routes "/project/:id/deploy_envs/:deploy_env", {}, deploy_env_hash - - app.put_with_headers "/project/:id/deploy_env/:deploy_env/:field", :headers => [:accept, :content_type] do |project_id, deploy_env, field| - check_privileges("project", "w") - res = Devops::API2_0::Handler::Project.new(request).update_deploy_env_field(project_id, deploy_env, field) - create_response(res, nil, 200) - end - - # Create project - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "deploy_envs": [ - # { - # "identifier": "prod", - # "provider": "openstack", - # "flavor": "m1.small", - # "image": "image id", - # "subnets": [ - # "private" - # ], - # "groups": [ - # "default" - # ], - # "users": [ - # "user" - # ], - # "run_list": [ - # - # ], - # "expires": null - # } - # ], - # "name": "project_1" - # } - # - # * *Returns* : - # 201 - Created - app.post_with_headers "/project", :headers => [:accept, :content_type] do - check_privileges("project", "w") - res = Devops::API2_0::Handler::Project.new(request).create_project - res = "Created. " + res - create_response(res, nil, 201) - end - -=begin - # Set components to project - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # components: { - # "some_id": { - # "filename" : "some.war" - # } - # } - # - # * *Returns* : - # 200 - Updated - app.post_with_headers "/project/:id/components", :headers => [:accept, :content_type] do |id| - check_privileges("project", "w") - res = Devops::API2_0::Handler::Project.new(request).set_project_components(id) - create_response(res) - end -=end - - users_hash = {} - # Add users to project environment - # - # * *Request* - # - method : PUT - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "users": [ - # "user1" - # ], - # "deploy_env": "env" -> if null, users will be added to all environments - # } - # - # * *Returns* : - # 200 - Updated - users_hash["PUT"] = lambda { |project| - check_privileges("project", "w") - info = Devops::API2_0::Handler::Project.new(request).update_project_users(project) - create_response(info) - } - - # Delete users from project environment - # - # * *Request* - # - method : DELETE - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "users": [ - # "user1" - # ], - # "deploy_env": "env" -> if null, users will be deleted from all environments - # } - # - # * *Returns* : - # 200 - Updated - users_hash["DELETE"] = lambda {|project| - check_privileges("project", "w") - info = Devops::API2_0::Handler::Project.new(request).delete_project_users(project) - create_response(info) - } - app.multi_routes "/project/:id/user", {}, users_hash - - # Set run_list to project - # - # * *Request* - # - method : PATCH - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # [ - # "role[role_1]", - # "recipe[recipe_1]" - # ] - # - # * *Returns* : - # 200 - Updated - app.patch_with_headers "/project/:id/run_list", :headers => [:accept, :content_type] do |project| - check_privileges("project", "w") - info = Devops::API2_0::Handler::Project.new(request).set_project_run_list(project) - create_response(info) - end - - # Set run_list to project environment - # - # * *Request* - # - method : PATCH - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # [ - # "role[role_1]", - # "recipe[recipe_1]" - # ] - # - # * *Returns* : - # 200 - Updated - app.patch_with_headers "/project/:id/:env/run_list", :headers => [:accept, :content_type] do |project, deploy_env| - check_privileges("project", "w") - info = Devops::API2_0::Handler::Project.new(request).set_project_env_run_list(project, deploy_env) - create_response(info) - end - - # Run chef-client on reserved project servers - # - # * *Request* - # - method : POST - # - headers : - # - X-Stream: true -> return output in text stream - # - Content-Type: application/json - # - body : - # { - # "servers": [ - # "server_1" - # ], -> deploy servers from list, all servers if null - # "deploy_env": "env" -> deploy servers with environment 'env' or all project servers if null - # } - # - # * *Returns* : text stream - app.post_with_headers "/project/:id/deploy", :headers => [:content_type] do |project| - check_privileges("project", "x") - handler = Devops::API2_0::Handler::Project.new(request) - if request["X-Stream"] - stream() do |out| - begin - status = handler.deploy_project_stream out, project - out << create_status(status) - rescue IOError => e - logger.error e.message - end - end - else - json handler.deploy_project project - end - end - - # Set project to archivated state - # - # * *Request* - # - method : POST - # - headers : - # - Content-Type: application/json - app.post_with_headers "/project/:project/archive", :headers => [:content_type] do |project| - check_privileges("project", "w") - info = Devops::API2_0::Handler::Project.new(request).archive_project(project) - create_response(info) - end - - # Set project to normal state - # - # * *Request* - # - method : POST - # - headers : - # - Content-Type: application/json - app.post_with_headers "/project/:project/unarchive", :headers => [:content_type] do |project| - check_privileges("project", "w") - info = Devops::API2_0::Handler::Project.new(request).unarchive_project(project) - create_response(info) - end - - # Test project environment - # - # Run tests: - # - run server - # - bootstrap server - # - delete server - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - # * *Returns* : - # 200 - - # { - # "servers": [ - # { - # "id": "132958f0-61c5-4665-8cc3-66e1bacd285b", - # "create": { - # "status": true, - # "time": "155s" - # }, - # "chef_node_name": "chef name", - # "bootstrap": { - # "status": true, - # "log": "\nWaiting for SSH...\n" - # "return_code": 0 - # }, - # "delete": { - # "status": true, - # "time": "2s" - # "log": { - # "chef_node": "Deleted node[chef name]", - # "chef_client": "Deleted client[chef name]", - # "server": "Server with id '132958f0-61c5-4665-8cc3-66e1bacd285b' terminated" - # } - # }, - # } - # ], - # "project": { - # "deploy_envs": [ - # { - # "flavor": "flavor", - # "identifier": "prod", - # "image": "image id", - # "run_list": [ - # "role[prod]" - # ], - # "subnets": [ - # "private" - # ], - # "expires": null, - # "provider": "openstack", - # "groups": [ - # "default" - # ], - # "users": [ - # "root" - # ] - # } - # ], - # "name": "prject_1" - # }, - # "message": "Test project 'project_1' and environment 'prod'" - # } - app.post_with_headers "/project/test/:id/:env", :headers => [:accept, :content_type] do |project, deploy_env| - check_privileges("project", "r") - json Devops::API2_0::Handler::Project.new(request).test_project(project, deploy_env) - end - - puts "Project routes initialized" - end - - end - end - end -end - diff --git a/devops-service/app/api2/routes/provider.rb b/devops-service/app/api2/routes/provider.rb deleted file mode 100644 index dd51af9..0000000 --- a/devops-service/app/api2/routes/provider.rb +++ /dev/null @@ -1,142 +0,0 @@ -# encoding: UTF-8 - -require "providers/provider_factory" - -module Devops - module API2_0 - module Routes - module ProviderRoutes - - def self.registered(app) - - # Get devops providers - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : - # [ - # "ec2", - # "openstack" - # ] - app.get_with_headers "/providers", :headers => [:accept] do#, &Devops::Version2_0::Handler::Provider.get_providers - check_privileges("provider", "r") - json Devops::API2_0::Handler::Provider.new(request).providers - end - - # Get list of provider account fields - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : hash - # key - field - # value - description - app.get_with_headers "/provider/:provider/account/fields", :headers => [:accept] do |provider| - check_privileges("provider", "r") - check_provider(provider) - json Devops::API2_0::Handler::Provider.new(request).account_fields(provider) - end - - # Get list of provider accounts - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : array of strings - # - ec2: - # { - # "account_name": "", - # "description": "", - # "access_key_id" : "", - # "ssh_key": "", - # "certificate" : "path to file", - # "availability_zone": "" - # - # } - # - openstack: - # { - # "account_name": "", - # "description": "", - # "username": "", - # "auth_url": "", - # "tenant": "", - # "ssh_key": "", - # "certificate" : "path to file" - # } - # - static: - # { - # "account_name": "", - # "description": "", - # "ssh_key": "", - # "certificate" : "path to file" - # } - app.get_with_headers "/provider/:provider/accounts", :headers => [:accept] do |provider| - check_privileges("provider", "r") - check_provider(provider) - json Devops::API2_0::Handler::Provider.new(request).accounts(provider) - end - - # Create provider account for :provider - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # - # - # } - # - # * *Returns* : 201 - app.post_with_headers "/provider/:provider/account", :headers => [:accept, :content_type] do |provider| - check_privileges("provider", "w") - check_provider(provider) - create_response("Created", {:account => Devops::API2_0::Handler::Provider.new(request).add_account(provider)}, 201) - end - - # Delete account with name :account_name for :provider - # - # * *Request* - # - method : DELETE - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - # * *Returns* : 200 - app.delete_with_headers "/provider/:provider/account/:account_name", :headers => [:accept, :content_type] do |provider, account_name| - check_privileges("provider", "w") - check_provider(provider) - create_response("Deleted", {:account => Devops::API2_0::Handler::Provider.new(request).delete_account(account_name, provider)}) - end - - # Describe vpc for account with name :account_name for provider ec2 - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - # * *Returns* : 200 - app.get_with_headers "/provider/ec2/account/:account_name/vpcs", :headers => [:accept, :content_type] do |account_name| - provider = "ec2" - check_privileges("provider", "r") - check_provider(provider) - json Devops::API2_0::Handler::Provider.new(request).account_vpcs(provider, account_name) - end - - puts "Provider routes initialized" - end - end - end - end -end diff --git a/devops-service/app/api2/routes/report.rb b/devops-service/app/api2/routes/report.rb deleted file mode 100644 index bfd45c1..0000000 --- a/devops-service/app/api2/routes/report.rb +++ /dev/null @@ -1,38 +0,0 @@ -module Devops - module API2_0 - module Routes - module ReportRoutes - - def self.registered(app) - - app.get_with_headers "/report/all", headers: [:accept] do - json Devops::API2_0::Handler::Report.new(request).all.map{|r| r.to_hash} - end - - app.get_with_headers "/report/all/latest", headers: [:accept] do - json Devops::API2_0::Handler::Report.new(request).all_latest.map{|r| r.to_hash} - end - - app.get_with_headers "/report/all/attributes/:name", headers: [:accept] do |name| - json Devops::API2_0::Handler::Report.new(request).attributes(name) - end - - app.get "/report/:id" do |id| - @text, @done = Devops::API2_0::Handler::Report.new(request).report(id) - erb :report - end - - app.get "/status/:id" do |id| - r = Devops::API2_0::Handler::Report.new(request).status(id) - return [404, "Job with id '#{id}' not found"] if r.nil? - r - end - - puts "Report routes initialized" - end - - end - end - end -end - diff --git a/devops-service/app/api2/routes/server.rb b/devops-service/app/api2/routes/server.rb deleted file mode 100644 index 5d1f594..0000000 --- a/devops-service/app/api2/routes/server.rb +++ /dev/null @@ -1,504 +0,0 @@ -module Devops - module API2_0 - module Routes - module ServerRoutes - - def self.registered(app) - - # Get devops servers list - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - params : - # - fields - show server fields, available values: project, deploy_env, provider, remote_user, private_ip, public_ip, created_at, created_by, static, key, reserved_by - # - # * *Returns* : - # [ - # { - # "id": "instance id", - # "chef_node_name": "chef name" - # } - # ] - app.get_with_headers "/servers", :headers => [:accept] do - check_privileges("server", "r") - json Devops::API2_0::Handler::Server.new(request).servers - end - - # Get chef nodes list - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : - # [ - # { - # "chef_node_name": "chef name" - # } - # ] - app.get_with_headers "/servers/chef", :headers => [:accept] do - check_privileges("server", "r") - json Devops::API2_0::Handler::Server.new(request).chef_servers - end - - # Get provider servers list - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : - # -ec2 - # [ - # { - # "state": "running", - # "name": "name", - # "image": "ami-83e4bcea", - # "flavor": "m1.small", - # "keypair": "ssh key", - # "instance_id": "i-8441bfd4", - # "dns_name": "ec2-204-236-199-49.compute-1.amazonaws.com", - # "zone": "us-east-1d", - # "private_ip": "10.215.217.210", - # "public_ip": "204.236.199.49", - # "launched_at": "2014-04-25 07:56:33 UTC" - # } - # ] - # -openstack - # [ - # { - # "state": "ACTIVE", - # "name": "name", - # "image": "image id", - # "flavor": null, - # "keypair": "ssh key", - # "instance_id": "instance id", - # "private_ip": "172.17.0.1" - # } - # ] - app.get_with_headers "/servers/:provider", :headers => [:accept] do |provider| - check_privileges("server", "r") - json Devops::API2_0::Handler::Server.new(request).provider_servers(provider) - end - - app.get_with_headers "/servers/provider/:provider/:account", :headers => [:accept] do |provider, account| - check_privileges("server", "r") - json Devops::API2_0::Handler::Server.new(request).provider_servers_with_account(provider, account) - end - - # Get server info by :name - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - parameters: - # key=instance -> search server by instance_id rather then chef_node_name - # - # * *Returns* : - # [ - # { - # "chef_node_name": "chef name" - # } - # ] - hash = {} - hash["GET"] = lambda {|id| - check_privileges("server", "r") - json Devops::API2_0::Handler::Server.new(request).server(id).to_hash - } - - # Delete devops server - # - # * *Request* - # - method : DELETE - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "key": "instance", -> search server by instance_id rather then chef_node_name - # } - # - # * *Returns* : - # 200 - Deleted - hash["DELETE"] = lambda {|id| - check_privileges("server", "w") - json Devops::API2_0::Handler::Server.new(request).delete(id) - } - app.multi_routes "/server/:id", {:headers => [:accept, :content_type]}, hash - - # Run deploy command on reserved server - # - # * *Request* - # - method : POST - # - headers : - # - Content-Type: application/json - # - body : - # { - # "names": [], -> array of servers names to run chef-client - # "tags": [], -> array of tags to apply on each server before running chef-client - # "build_number": "", -> string, build number to deploy - # "run_list": [], -> array of strings to set run_list for chef-client - # } - # - # * *Returns* : text stream - app.post_with_headers "/server/:node_name/deploy", :headers => [:content_type, :accept] do |node_name| - check_privileges("server", "x") - - if request["HTTP_X_STREAM"] - stream() do |out| - status = [] - begin - status = Devops::API2_0::Handler::Server.new(request).deploy_stream(node_name, out) - out << create_status(status) - rescue DeployInfoError => e - msg = "Can not get deploy info: " + e.message - DevopsLogger.logger.error "msg:\n#{e.backtrace.join('\n')}" - out.puts msg - rescue IOError => e - logger.error e.message - break - end - end # stream - else - files = begin - Devops::API2_0::Handler::Server.new(request).deploy(node_name) - rescue DeployInfoError => e - msg = "Can not get deploy info: " + e.message - DevopsLogger.logger.error "#{msg}:\n#{e.backtrace.join("\n")}" - out << "\nError - " - out.puts msg - end - sleep 1 - json files - end - end - - # Create devops server - # - # * *Request* - # - method : POST - # - headers : - # - X-Stream: true -> return output in text stream - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "project": "project name", -> mandatory parameter - # "deploy_env": "env", -> mandatory parameter - # "name": "server_name", -> if null, name will be generated - # "without_bootstrap": null, -> do not install chef on instance if true - # "force": null, -> do not delete server on error - # "groups": [], -> specify special security groups, overrides value from project env - # "key": "ssh key" -> specify ssh key for server, overrides value from project env - # "private_ip": null -> should be string like "172.31.31.203" if present - # "project_info": { - # "deployers": ['user1'] -> currently it's used only in sandbox projects - # } - # } - # - # * *Returns* : text stream - app.post_with_headers "/server", :headers => [:accept, :content_type] do - check_privileges("server", "w") - handler = Devops::API2_0::Handler::Server.new(request) - if request["X-Stream"] or request["HTTP_X_STREAM"] - stream() do |out| - begin - status = handler.create_server_stream out - out << create_status(status) - rescue IOError => e - logger.error e.message - end - end - else - json handler.create_server - end - end - - # Pause devops server by name - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "key": "instance", -> search server by instance_id rather then chef_node_name - # } - # - # * *Returns* : - # 200 - Paused - app.post_with_headers "/server/:node_name/pause", :headers => [:accept, :content_type] do |node_name| - check_privileges("server", "w") - info = Devops::API2_0::Handler::Server.new(request).pause_server(node_name) - create_response(info) - end - - # Unpause devops server by name - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "key": "instance", -> search server by instance_id rather then chef_node_name - # } - # - # * *Returns* : - # 200 - Unpaused - app.post_with_headers "/server/:node_name/unpause", :headers => [:accept, :content_type] do |node_name| - check_privileges("server", "w") - info = Devops::API2_0::Handler::Server.new(request).unpause_server(node_name) - create_response(info) - end - - # Reserve devops server - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "key": "instance", -> search server by instance_id rather then chef_node_name - # } - # - # * *Returns* : - # 200 - Reserved - app.post_with_headers "/server/:node_name/reserve", :headers => [:accept, :content_type] do |node_name| - check_privileges("server", "w") - Devops::API2_0::Handler::Server.new(request).reserve_server(node_name) - create_response("Server '#{node_name}' has been reserved") - end - - # Unreserve devops server - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "key": "instance", -> search server by instance_id rather then chef_node_name - # } - # - # * *Returns* : - # 200 - Unreserved - app.post_with_headers "/server/:node_name/unreserve", :headers => [:accept, :content_type] do |node_name| - check_privileges("server", "w") - Devops::API2_0::Handler::Server.new(request).unreserve_server(node_name) - create_response("Server '#{node_name}' has been unreserved") - end - - # Bootstrap devops server - # - # * *Request* - # - method : POST - # - headers : - # - X-Stream: true -> return output in text stream - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "instance_id": "instance id", -> mandatory parameter - # "name": "server_name", -> if null, name will be generated - # "run_list": [], -> specify list of roles and recipes - # "bootstrap_template": "template" -> specify ssh key for server, overrides value from project env - # } - # - # * *Returns* : text stream - app.post_with_headers "/server/bootstrap", :headers => [:accept, :content_type] do - check_privileges("server", "w") - - handler = Devops::API2_0::Handler::Server.new(request) - if request["X-Stream"] - stream() do |out| - begin - status = handler.bootstrap_server_stream out - out << create_status(status) - rescue IOError => e - logger.error e.message - end - end - else - json handler.bootstrap_server() - end - end - - # Unbootstrap devops server - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "key": "instance", -> search server by instance_id rather then chef_node_name - # } - # - # * *Returns* : 200 - app.post_with_headers "/server/:id/unbootstrap", :headers => [:accept, :content_type] do |id| - check_privileges("server", "w") - info = Devops::API2_0::Handler::Server.new(request).unbootstrap_server(id) - create_response("Unbootstrap", info) - end - - # Add external server to devops - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "project": "project name", -> mandatory parameter - # "deploy_env": "env", -> mandatory parameter - # "key": "ssh key", -> mandatory parameter - # "remote_user": "ssh user", -> mandatory parameter - # "private_ip": "ip", -> mandatory parameter - # "public_ip": "ip" - # } - # - # * *Returns* : - # 200 - Added - app.post_with_headers "/server/add", :headers => [:accept, :content_type] do - check_privileges("server", "w") - info = Devops::API2_0::Handler::Server.new(request).add_server() - create_response(info) - end - - - # Add list of external servers to devops and bootstrap them - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "project": "project name", -> required - # "deploy_env": "env", -> required - # "key": "ssh key", -> required - # "remote_user": "ssh user", -> required - # "ips_with_names": multiline string like, required - # 127.0.0.1:node1 - # 127.0.0.2:node2 - # "private_ip": "ip", -> required - # "public_ip": "ip" - # } - # - # * *Returns* : - # ["report1", "report2"] - app.post_with_headers "/server/add_and_bootstrap_servers", :headers => [:accept, :content_type] do - check_privileges("server", "w") - json Devops::API2_0::Handler::Server.new(request).add_and_bootstrap_servers() - end - - hash = {} - # Add instance tags - # - # * *Request* - # - method : PUT - # - headers : - # - Content-Type: application/json - # - body : - # { - # "tags": {"tag name": "tag value"} - # "key": "instance", -> search server by instance_id rather then chef_node_name - # } - # - # * *Returns* : - # 200 - Added - hash["PUT"] = lambda {|id| - check_privileges("server", "w") - Devops::API2_0::Handler::Server.new(request).set_tags(id) - create_response("Added") - } - - # Delete instance tags - # - # * *Request* - # - method : DELETE - # - headers : - # - Content-Type: application/json - # - body : - # { - # "tags": {"tag name": "tag value"} - # "key": "instance", -> search server by instance_id rather then chef_node_name - # } - # - # * *Returns* : - # 200 - Deleted - hash["DELETE"] = lambda {|id| - check_privileges("server", "w") - Devops::API2_0::Handler::Server.new(request).unset_tags(id) - create_response("Deleted") - } - app.multi_routes "/server/:id/tags", {:headers => [:content_type]}, hash - - # Set run_list to server - # - # * *Request* - # - method : PATCH - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "run_list": [ - # "role[role_1]", - # "recipe[recipe_1]" - # ], - # "key": "instance", -> search server by instance_id rather then chef_node_name - # } - # - # * *Returns* : - # 200 - Updated - app.put_with_headers "/server/:id/run_list", :headers => [:accept, :content_type] do |node_name| - check_privileges("server", "w") - Devops::API2_0::Handler::Server.new(request).set_run_list(node_name) - create_response("Run list has been changed") - end - - - # Delete list of servers - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "servers_ids": [ "server1", "server2"] - # } - # - # * *Returns* : - # { - # "server1": "report_1", - # "server2": "report_2" - # } - app.post_with_headers "/server/delete_list", :headers => [:accept, :content_type] do - check_privileges("server", "w") - json Devops::API2_0::Handler::Server.new(request).delete_list - end - - puts "Server routes initialized" - end - - end - end - end -end diff --git a/devops-service/app/api2/routes/stack_template.rb b/devops-service/app/api2/routes/stack_template.rb deleted file mode 100644 index 5a46f01..0000000 --- a/devops-service/app/api2/routes/stack_template.rb +++ /dev/null @@ -1,58 +0,0 @@ -module Devops - module API2_0 - module Routes - module StackTemplateRoutes - - def self.registered(app) - app.get_with_headers '/stack_templates', :headers => [:accept] do - check_privileges('stack_template', 'r') - json Devops::API2_0::Handler::StackTemplate.new(request).stack_templates.map(&:to_hash) - end - - app.get_with_headers '/stack_templates/provider/:provider', :headers => [:accept] do |provider| - check_privileges('stack_template', 'r') - check_provider(provider) - json Devops::API2_0::Handler::StackTemplate.new(request).stack_templates_for_provider(provider).map(&:to_hash) - end - - app.post_with_headers "/stack_template/:provider", :headers => [:accept] do |provider| - check_privileges('stack_template', 'w') - check_provider(provider) - model = Devops::API2_0::Handler::StackTemplate.new(request).create_stack_template(provider) - create_response 'Created', model.to_hash, 201 - end - - # Temp route just to process migration. Could be deleted safely - app.post_with_headers "/stack_template/:id/update_template_url", :headers => [:accept] do |template_id| - check_privileges('stack_template', 'w') - json Devops::API2_0::Handler::StackTemplate.new(request).update_template_url(template_id).to_hash - end - - # Temp route just to process migration - app.post_with_headers "/stack_template/:id/update_available_parameters", :headers => [:accept] do |template_id| - check_privileges('stack_template', 'w') - json Devops::API2_0::Handler::StackTemplate.new(request).update_available_parameters(template_id).to_hash - end - - hash = {} - - hash['GET'] = lambda {|stack_template_id| - check_privileges('stack_template', 'r') - json Devops::API2_0::Handler::StackTemplate.new(request).get_stack_template(stack_template_id).to_hash - } - - hash['DELETE'] = lambda {|stack_template_id| - check_privileges('stack_template', 'w') - Devops::API2_0::Handler::StackTemplate.new(request).delete_stack_template(stack_template_id) - create_response("Template '#{stack_template_id}' has been removed") - } - - app.multi_routes '/stack_template/:stack_template_id', {}, hash - - puts "Stack_template routes initialized" - end - - end - end - end -end diff --git a/devops-service/app/api2/routes/statistic.rb b/devops-service/app/api2/routes/statistic.rb deleted file mode 100644 index 099ebba..0000000 --- a/devops-service/app/api2/routes/statistic.rb +++ /dev/null @@ -1,18 +0,0 @@ -module Devops - module API2_0 - module Routes - module StatisticRoutes - - def self.registered(app) - app.get_with_headers '/statistic', :headers => [:accept] do - # check_privileges('statistic', 'r') - JSON.pretty_generate Devops::Db.connector.search_statistic(@params) - end - - puts "Statistic routes initialized" - end - - end - end - end -end diff --git a/devops-service/app/api2/routes/tag.rb b/devops-service/app/api2/routes/tag.rb deleted file mode 100644 index ff8ffe0..0000000 --- a/devops-service/app/api2/routes/tag.rb +++ /dev/null @@ -1,72 +0,0 @@ -module Devops - module API2_0 - module Routes - module TagRoutes - - def self.registered(app) - - hash = {} - # Get tags list for :node_name - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : - # [ - # "tag_1" - # ] - hash["GET"] = lambda {|node_name| - check_privileges("server", "r") - json Devops::API2_0::Handler::Tag.new(request).tags(node_name) - } - - # Set tags list to :node_name - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # [ - # "tag_1" - # ] - # - # * *Returns* : - # 200 - hash["POST"] = lambda {|node_name| - check_privileges("server", "w") - tags = Devops::API2_0::Handler::Tag.new(request).set_tags(node_name) - create_response("Set tags for #{node_name}", tags: tags) - } - - # Delete tags from :node_name - # - # * *Request* - # - method : DELETE - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # [ - # "tag_1" - # ] - # - # * *Returns* : - # 200 - hash["DELETE"] = lambda {|node_name| - check_privileges("server", "w") - tags = Devops::API2_0::Handler::Tag.new(request).unset_tags(node_name) - create_response("Deleted tags for #{node_name}", tags: tags) - } - app.multi_routes "/tags/:node_name", {:headers => [:accept, :content_type]}, hash - - puts "Tag routes initialized" - end - - end - end - end -end diff --git a/devops-service/app/api2/routes/user.rb b/devops-service/app/api2/routes/user.rb deleted file mode 100644 index 2ac0dbb..0000000 --- a/devops-service/app/api2/routes/user.rb +++ /dev/null @@ -1,151 +0,0 @@ -module Devops - module API2_0 - module Routes - module UserRoutes - - def self.registered(app) - - # Get users list - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : - # [ - # { - # "email": "test@test.test", - # "privileges": { - # "flavor": "r", - # "group": "r", - # "image": "r", - # "project": "r", - # "server": "r", - # "key": "r", - # "user": "", - # "filter": "r", - # "network": "r", - # "provider": "r", - # "script": "r", - # "templates": "r" - # }, - # "id": "test" - # } - # ] - app.get_with_headers "/users", :headers => [:accept] do - check_privileges("user", "r") - json Devops::API2_0::Handler::User.new(request).users.map(&:to_hash) - end - - # Create user - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "username": "user name", - # "email": "user email", - # "password": "user password" - # } - # - # * *Returns* : - # 201 - Created - app.post_with_headers "/user", :headers => [:accept, :content_type] do - check_privileges("user", "w") - Devops::API2_0::Handler::User.new(request).create - create_response("Created", nil, 201) - end - - hash = {} - # Delete user - # - # * *Request* - # - method : DELETE - # - headers : - # - Accept: application/json - # - # * *Returns* : - # 200 - Deleted - hash["DELETE"] = lambda {|user| - check_privileges("user", "w") - Devops::API2_0::Handler::User.new(request).delete(user) - create_response("User '#{user}' removed") - } - - # Change user privileges - # - # * *Request* - # - method : PUT - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "cmd": "command or all", -> if empty, set default privileges - # "privileges": "priv" -> 'rwx' or '' - # } - # - # * *Returns* : - # 200 - Updated - hash["PUT"] = lambda {|user| - check_privileges("user", "w") - Devops::API2_0::Handler::User.new(request).change_user_privileges(user) - create_response("Updated") - } - app.multi_routes "/user/:user", {:headers => [:accept, :content_type]}, hash - - # Change user email - # - # * *Request* - # - method : PUT - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "email": "new user email", - # } - # - # * *Returns* : - # 200 - Updated - app.put_with_headers %r{\A/user/#{DevopsConfig::OBJECT_NAME}/email\z}, :headers => [:accept, :content_type] do |user| - current_user = request.env['REMOTE_USER'] - check_privileges("user", "w") unless current_user == user - raise InvalidPrivileges.new("Access denied for '#{current_user}'") if user == Devops::Model::User::ROOT_USER_NAME and current_user != Devops::Model::User::ROOT_USER_NAME - Devops::API2_0::Handler::User.new(request).change_email(user) - create_response("Updated") - end - - # Change user password - # - # * *Request* - # - method : PUT - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "password": "new user password", - # } - # - # * *Returns* : - # 200 - Updated - app.put_with_headers %r{\A/user/#{DevopsConfig::OBJECT_NAME}/password\z}, :headers => [:accept, :content_type] do |user| - current_user = request.env['REMOTE_USER'] - check_privileges("user", "w") unless current_user == user - raise InvalidPrivileges.new("Access denied for '#{current_user}'") if user == Devops::Model::User::ROOT_USER_NAME and current_user != Devops::Model::User::ROOT_USER_NAME - Devops::API2_0::Handler::User.new(request).change_password(user) - create_response("Updated") - end - - puts "User routes initialized" - end - - end - end - end -end diff --git a/devops-service/app/api2/routes/v2.0.rb b/devops-service/app/api2/routes/v2.0.rb deleted file mode 100644 index 3645afc..0000000 --- a/devops-service/app/api2/routes/v2.0.rb +++ /dev/null @@ -1,175 +0,0 @@ -require "sinatra/base" -require "sinatra/streaming" -require "json" - -require "fog" - -require "exceptions/invalid_record" -require "exceptions/record_not_found" -require "exceptions/dependency_error" -require "exceptions/conflict_exception" -require "exceptions/parser_error" -require "exceptions/validation_error" -require "exceptions/knife_config_error" -require 'core/devops-logger' - -require_relative "../helpers/version_2" - -module Devops - class Api2 < Sinatra::Base - - include Sinatra::JSON - helpers Sinatra::Streaming - helpers Devops::API2_0::Helpers - - register Sinatra::DevopsAuth - - @@logger = nil - @@access_logger = DevopsLogger.access_logger(File.join(DevopsConfig.config[:log_dir], "devops-api2.access.log")) - - configure :production do - config = DevopsConfig.config - log_file = File.join(config[:log_dir], "devops-api2.log") - @@logger = DevopsLogger.create(log_file, Logger::INFO) -# use Rack::CommonLogger, logger - disable :dump_errors - disable :show_exceptions - #set :logging, Logger::INFO - @@logger.info "Production mode" - end - - configure :development do - config = DevopsConfig.config - log_file = File.join(config[:log_dir], "devops-api2.dev.log") - @@logger = DevopsLogger.create(log_file, Logger::DEBUG) - # logger = DevopsLogger.create(log_file, Logger::DEBUG) -# logger = Logger.new STDOUT -# use Rack::CommonLogger, logger - disable :raise_errors - #set :show_exceptions, :after_handler - set :show_exceptions, false - #set :dump_errors, false - @@logger.info "Development mode" - end - - not_found do - "Not found" - end - - # set current logger and call handlers - def call env - DevopsLogger.logger = @@logger - begin - res = super(env) - rescue ::Devops::Exception::DevopsError => e - return [e.code, {}, e.message] - rescue InvalidRecord => e - return [e.code, {}, e.message] - end - @@access_logger.info(env["REQUEST_METHOD"] + " " + env["REQUEST_URI"] + " - from #{env["HTTP_USER_AGENT"]} (#{env["REMOTE_USER"]}) / #{res[0]}") - res - end - - def handle_exception!(boom) - if boom.is_a?(::Devops::Exception::DevopsError) - boom.http_response - else - super(boom) - end - end - - error Devops::ValidationError do - e = env["sinatra.error"] - #logger.warn e.message - halt_response(e.message, 400) - end - - error Devops::ParserError do - e = env["sinatra.error"] - #logger.warn e.message - halt_response(e.message, 400) - end - - error Devops::Exception::KnifeConfigError do - e = env["sinatra.error"] - logger.error e.message - halt_response(e.message, 500) - end - - error RecordNotFound do - e = env["sinatra.error"] - logger.warn e.message - halt_response(e.message, 404) - end - -=begin - error InvalidRecord do - e = env["sinatra.error"] - logger.warn e.message - logger.warn "Request body: #{request.body.read}" - halt_response(e.message, 400) - end -=end - - error InvalidCommand do - e = env["sinatra.error"] - logger.warn e.message - halt_response(e.message, 400) - end - - error DependencyError do - e = env["sinatra.error"] - logger.warn e.message - halt_response(e.message, 400) - end - - error ConflictException do - e = env["sinatra.error"] - logger.warn e.message - create_response(e.message, e.object, 409) - end - - error InvalidPrivileges do - e = env["sinatra.error"] - logger.warn e.message - halt_response(e.message, 401) - end - - error Excon::Errors::Error do - e = env["sinatra.error"] - logger.warn e.message - halt_response(e.message, 400) - end - - error ::Excon::Errors::Unauthorized do - e = env["sinatra.error"] - resp = e.response - ct = resp.headers["Content-Type"] - msg = unless ct.nil? - if ct.include?("application/json") - json = ::Chef::JSONCompat.from_json(resp.body) - m = "ERROR: Unauthorized (#{json['error']['code']}): #{json['error']['message']}" - logger.error(m) - else - end - m - else - "Unauthorized: #{e.inspect}" - end - halt_response(msg, 500) - end - - error Fog::Compute::AWS::Error do - e = env["sinatra.error"] - logger.error e.message - halt_response(e.message, 500) - end - - error do - e = env["sinatra.error"] - logger.error e.message - halt_response(e.message, 500) - end - - end -end diff --git a/devops-service/app/api3/docs/chef.rb b/devops-service/app/api3/docs/chef.rb new file mode 100644 index 0000000..959ee13 --- /dev/null +++ b/devops-service/app/api3/docs/chef.rb @@ -0,0 +1,190 @@ +require 'swagger/blocks' + +require_relative 'devops_error' +require_relative 'devops_response' + +module Devops + module API3 + module Docs + class ChefRoutes + + include Swagger::Blocks + + swagger_path "/chef/bootstrap_templates" do + operation :get do + key :description, 'Get list of available bootstrap templates' + key :operationId, 'getBootstrapTemplates' + key :tags, [ + 'chef' + ] + response 200 do + key :description, 'List of available bootstrap templates' + schema do + key :type, :array + items do + key :type, :string + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/chef/nodes" do + operation :get do + key :description, 'Get chef nodeslist' + key :operationId, 'getChefNodes' + key :tags, [ + 'chef' + ] + response 200 do + key :description, 'Chef nodes list' + schema do + key :type, :array + items do + key :type, :string + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/chef/tags/{cm_name}" do + operation :get do + key :description, 'Get node tags' + key :operationId, 'getNodeTags' + key :tags, [ + 'chef' + ] + parameter do + key :name, :cm_name + key :in, :path + key :description, 'Chef node name' + key :required, true + key :type, :string + end + response 200 do + key :description, 'Node tags list' + schema do + key :type, :array + items do + key :type, :string + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/chef/tags/{cm_name}/set" do + operation :post do + key :description, 'Set node tags' + key :operationId, 'setNodeTags' + key :tags, [ + 'chef' + ] + parameter do + key :name, :cm_name + key :in, :path + key :description, 'Chef node name' + key :required, true + key :type, :string + end + parameter do + key :name, :tags + key :in, :body + key :description, 'Tags to set to node' + key :required, true + schema do + key :'$ref', :PetInput + end + end + response 200 do + key :description, 'Node tags list' + schema do + key :'$ref', :TagsResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/chef/tags/{cm_name}/unset" do + operation :post do + key :description, 'Unset node tags' + key :operationId, 'unsetNodeTags' + key :tags, [ + 'chef' + ] + parameter do + key :name, :cm_name + key :in, :path + key :description, 'Chef node name' + key :required, true + key :type, :string + end + parameter do + key :name, :tags + key :in, :body + key :description, 'Tags to unset from node' + key :required, true + schema do + key :'$ref', :PetInput + end + end + response 200 do + key :description, 'Node tags list' + schema do + key :'$ref', :TagsResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_schema :TagsResponse do + allOf do + schema do + key :'$ref', :DevopsResponse + end + schema do + property :tags do + key :type, :array + items do + key :type, :string + end + end + end + end + end + + end + end + end +end diff --git a/devops-service/app/api3/docs/devops_error.rb b/devops-service/app/api3/docs/devops_error.rb new file mode 100644 index 0000000..1e434ef --- /dev/null +++ b/devops-service/app/api3/docs/devops_error.rb @@ -0,0 +1,20 @@ +require 'swagger/blocks' + +module Devops + module API3 + module Docs + class DevopsError + + include Swagger::Blocks + + swagger_schema :DevopsError do + key :required, [:message] + property :message do + key :type, :string + end + end + + end + end + end +end diff --git a/devops-service/app/api3/docs/devops_response.rb b/devops-service/app/api3/docs/devops_response.rb new file mode 100644 index 0000000..4d53a76 --- /dev/null +++ b/devops-service/app/api3/docs/devops_response.rb @@ -0,0 +1,21 @@ +require 'swagger/blocks' + +module Devops + module API3 + module Docs + class DevopsResponse + + include Swagger::Blocks + + swagger_schema :DevopsResponse do + key :required, [:message] + property :message do + key :type, :string + end + end + + end + end + end +end + diff --git a/devops-service/app/api3/docs/filter.rb b/devops-service/app/api3/docs/filter.rb new file mode 100644 index 0000000..9e75f38 --- /dev/null +++ b/devops-service/app/api3/docs/filter.rb @@ -0,0 +1,146 @@ +require 'swagger/blocks' + +require_relative 'devops_error' +require_relative 'devops_response' + +module Devops + module API3 + module Docs + class FilterRoutes + + include Swagger::Blocks + + swagger_path "/filter/{provider}/images" do + operation :get do + key :description, 'Get filter for images for provider' + key :operationId, 'getFilterImages' + key :tags, [ + 'filter' + ] + parameter do + key :name, :provider + key :in, :path + key :description, 'Provider name' + key :required, true + key :type, :string + end + response 200 do + key :description, 'List of images filters' + schema do + key :type, :array + items do + key :type, :string + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/filter/{provider}/images/add" do + operation :post do + key :description, 'Add filters for images for provider' + key :operationId, 'addFilterImages' + key :tags, [ + 'filter' + ] + parameter do + key :name, :provider + key :in, :path + key :description, 'Provider name' + key :required, true + key :type, :string + end + parameter do + key :name, :filtes + key :in, :body + key :description, 'Filters for images' + key :required, true + schema do + key :type, :array + items do + key :type, :string + end + end + end + response 200 do + key :description, 'List of images filters' + schema do + key :'$ref', :FilterResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/filter/{provider}/images/delete" do + operation :post do + key :description, 'Delete filters for images for provider' + key :operationId, 'deleteFilterImages' + key :tags, [ + 'filter' + ] + parameter do + key :name, :provider + key :in, :path + key :description, 'Provider name' + key :required, true + key :type, :string + end + parameter do + key :name, :filtes + key :in, :body + key :description, 'Filters for images' + key :required, true + schema do + key :type, :array + items do + key :type, :string + end + end + end + response 200 do + key :description, 'List of images filters' + schema do + key :'$ref', :FilterResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_schema :FilterResponse do + allOf do + schema do + key :'$ref', :DevopsResponse + end + schema do + property :images do + key :type, :array + items do + key :type, :string + end + end + end + end + end + + end + end + end +end diff --git a/devops-service/app/api3/docs/image.rb b/devops-service/app/api3/docs/image.rb new file mode 100644 index 0000000..fbb15eb --- /dev/null +++ b/devops-service/app/api3/docs/image.rb @@ -0,0 +1,230 @@ +require 'swagger/blocks' + +require_relative 'devops_error' +require_relative 'devops_response' + +module Devops + module API3 + module Docs + class ImageRoutes + + include Swagger::Blocks + + swagger_schema :ImageResponse do + allOf do + schema do + key :'$ref', :DevopsResponse + end + schema do + property :id do + key :type, :string + end + end + end + end + + swagger_schema :ImageObject do + allOf do + schema do + key :'$ref', :ImageObjectInput + end + schema do + property :id do + key :type, :string + end + property :created_at do + key :type, :integer + key :format, :int64 + end + end + end + end + + swagger_schema :ImageObjectInput do + key :required, [:image_id, :name, :provider, :provider_account, :remote_user] + property :image_id do + key :type, :string + key :pattern, "^[a-z0-9_-]{0,99}$" + key :maxLength, 100 + end + property :name do + key :type, :string + key :maxLength, 255 + end + property :remote_user do + key :type, :string + key :pattern, "^[a-z_][a-z0-9_-]{0,30}$" + key :maxLength, 31 + end + property :provider do + key :type, :string + end + property :provider_account do + key :type, :string + key :pattern, "^[a-z_][a-z0-9_-]{0,99}$" + key :maxLength, 100 + end + end + + swagger_path "/images" do + operation :get do + key :description, 'Get images list' + key :operationId, 'getImages' + key :tags, [ + 'image' + ] + parameter do + key :name, :provider + key :in, :query + key :description, 'Provider name for filtering images' + key :required, false + schema do + key :type, :string + end + end + response 200 do + key :description, 'Images list' + schema do + key :type, :array + items do + key :'$ref', :ImageObject + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/image" do + operation :post do + key :description, 'Create new image' + key :operationId, 'createImage' + key :tags, [ + 'image' + ] + parameter do + key :name, :image + key :in, :body + key :description, 'New image' + key :required, true + schema do + key :'$ref', :ImageObjectInput + end + end + response 201 do + key :description, 'Create image response' + schema do + key :'$ref', :ImageResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/image/(id)" do + operation :get do + key :description, 'Get image by id' + key :operationId, 'getImage' + key :tags, [ + 'image' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Image id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Image object' + schema do + key :'$ref', :ImageObject + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/image/(id)" do + operation :put do + key :description, 'Update image by id TODO' + key :operationId, 'updateImage' + key :tags, [ + 'image' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Image id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Devops response' + schema do + key :'$ref', :ImageResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/image/(id)" do + operation :delete do + key :description, 'Delete image by id' + key :operationId, 'deleteImage' + key :tags, [ + 'image' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Image id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Devops response' + schema do + key :'$ref', :ImageResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + end + end + end +end diff --git a/devops-service/app/api3/docs/job_task.rb b/devops-service/app/api3/docs/job_task.rb new file mode 100644 index 0000000..e171a3e --- /dev/null +++ b/devops-service/app/api3/docs/job_task.rb @@ -0,0 +1,260 @@ +require 'swagger/blocks' + +require_relative 'devops_error' +require_relative 'devops_response' + +module Devops + module API3 + module Docs + class JobTaskRoutes + + include Swagger::Blocks + + swagger_path "/tasks/all" do + operation :get do + key :description, 'Get tasks list' + key :operationId, 'getTasks' + key :tags, [ + 'task' + ] + parameter do + key :name, :date_from + key :in, :query + key :description, "Date from in format 'YYYY-MM-DD'" + key :required, false + key :type, :string + end + parameter do + key :name, :date_from_l + key :in, :query + key :description, "Date from in unixtime" + key :required, false + key :type, :integer + end + parameter do + key :name, :date_till + key :in, :query + key :description, "Date till in format 'YYYY-MM-DD'" + key :required, false + key :type, :string + end + parameter do + key :name, :date_till_l + key :in, :query + key :description, "Date till in unixtime" + key :required, false + key :type, :integer + end + parameter do + key :name, :sort_field + key :in, :query + key :description, "Field to sort by, default value: created_at" + key :required, false + key :type, :string + end + parameter do + key :name, :sort_order + key :in, :query + key :description, "Sort order, default value: asc" + key :required, false + key :type, :string + end + parameter do + key :name, :limit + key :in, :query + key :description, "Documents number, default value: 10" + key :required, false + key :type, :integer + end + parameter do + key :name, :project + key :in, :query + key :description, "Project id" + key :required, false + key :type, :string + end + parameter do + key :name, :environment + key :in, :query + key :description, "Environment id" + key :required, false + key :type, :string + end + parameter do + key :name, :category + key :in, :query + key :description, "Category id" + key :required, false + key :type, :string + end + parameter do + key :name, :type + key :in, :query + key :description, "Job task type" + key :required, false + key :type, :integer + end + parameter do + key :name, :server_id + key :in, :query + key :description, "Server id" + key :required, false + key :type, :string + end + parameter do + key :name, :created_by + key :in, :query + key :description, "User how created job task" + key :required, false + key :type, :string + end + response 200 do + key :description, 'Tasks response' + schema do + key :type, :array + items do + key :'$ref', :TaskResponse + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/task/{id}" do + operation :get do + key :description, 'Get task' + key :operationId, 'getTask' + key :tags, [ + 'task' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Task id' + key :required, true + key :type, :string + end + response 200 do + key :description, 'Task response' + schema do + key :'$ref', :TaskResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/task/{id}/report" do + operation :get do + key :description, 'Get task report' + key :operationId, 'getTaskReport' + key :tags, [ + 'task' + ] + key :produces, [ + 'text/html' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Task id' + key :required, true + key :type, :string + end + response 200 do + key :description, 'Task report' + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/task/{id}/status" do + operation :get do + key :description, 'Get task status' + key :operationId, 'getTaskStatus' + key :tags, [ + 'task' + ] + key :produces, [ + 'text/html' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Task id' + key :required, true + key :type, :string + end + response 200 do + key :description, 'Task status' + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_schema :TaskResponse do + property :id do + key :type, :string + end + property :server_id do + key :type, :string + end + property :stack do + key :type, :string + end + property :project do + key :type, :string + end + property :environment do + key :type, :string + end + property :category do + key :type, :string + end + property :created_by do + key :type, :string + end + property :status do + key :type, :string + end + property :job_result_code do + key :type, :integer + end + property :type do + key :type, :integer + end + property :created_at do + key :type, :integer + key :format, :int64 + end + property :updated_at do + key :type, :integer + key :format, :int64 + end + end + + end + end + end +end diff --git a/devops-service/app/api3/docs/key.rb b/devops-service/app/api3/docs/key.rb new file mode 100644 index 0000000..b06ff61 --- /dev/null +++ b/devops-service/app/api3/docs/key.rb @@ -0,0 +1,139 @@ +require 'swagger/blocks' + +require_relative 'devops_error' +require_relative 'devops_response' + +module Devops + module API3 + module Docs + class KeyRoutes + + include Swagger::Blocks + + swagger_schema :KeyObject do + property :id do + key :type, :string + end + property :filename do + key :type, :string + end + property :created_at do + key :type, :integer + key :format, :int64 + end + end + + swagger_schema :KeyObjectInput do + key :required, [:file_name, :key_name, :content] + property :file_name do + key :type, :string + key :pattern, "^[a-z0-9_-]{0,99}$" + key :maxLength, 100 + end + property :key_name do + key :type, :string + key :pattern, "^[a-z0-9_-]{0,99}$" + key :maxLength, 100 + end + property :content do + key :type, :string + end + end + + swagger_path "/keys" do + operation :get do + key :description, 'Get keys list' + key :operationId, 'getKeys' + key :tags, [ + 'key' + ] + response 200 do + key :description, 'Keys list' + schema do + key :type, :array + items do + key :'$ref', :KeyObject + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/key" do + operation :post do + key :description, 'Create new key' + key :operationId, 'createKey' + key :tags, [ + 'key' + ] + parameter do + key :name, :key + key :in, :body + key :description, 'New key object' + key :required, true + schema do + key :'$ref', :KeyObjectInput + end + end + response 201 do + key :description, 'Devops response' + schema do + key :type, :array + items do + key :'$ref', :DevopsResponse + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/key/{id}" do + operation :delete do + key :description, 'Delete key by id' + key :operationId, 'deleteKey' + key :tags, [ + 'key' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Key id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Devops response' + schema do + key :type, :array + items do + key :'$ref', :DevopsResponse + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + end + end + end +end diff --git a/devops-service/app/api3/docs/project.rb b/devops-service/app/api3/docs/project.rb new file mode 100644 index 0000000..494b6c0 --- /dev/null +++ b/devops-service/app/api3/docs/project.rb @@ -0,0 +1,1323 @@ +require 'swagger/blocks' + +require_relative 'devops_error' +require_relative 'devops_response' +require_relative 'server_model' + +module Devops + module API3 + module Docs + class ProjectRoutes + + include Swagger::Blocks + + swagger_schema :ProjectResponse do + allOf do + schema do + key :'$ref', :DevopsResponse + end + schema do + property :id do + key :type, :string + end + end + end + end + + swagger_schema :CreateCategoryResponse do + allOf do + schema do + key :'$ref', :DevopsResponse + end + schema do + property :category do + key :'$ref', :CategoryObject + end + end + end + end + + swagger_schema :CreateEnvironmentResponse do + allOf do + schema do + key :'$ref', :DevopsResponse + end + schema do + property :environment do + key :'$ref', :CreateEnvironmentObject + end + end + end + end + + swagger_schema :ProjectCreate do + property :id do + key :type, :string + key :maxLength, 100 + end + property :description do + key :type, :string + key :maxLength, 500 + end + property :run_list do + key :type, :array + items do + key :type, :string + end + end + end + + swagger_schema :CreateEnvironmentObject do + property :id do + key :type, :string + key :maxLength, 100 + end + property :expires do + key :type, :string + end + property :run_list do + key :type, :array + items do + key :type, :string + end + end + property :users do + key :type, :array + items do + key :type, :string + end + end + end + + swagger_schema :ServerLastOperationObject do + property :type do + key :type, :string + end + property :date do + key :type, :int64 + end + property :user do + key :type, :string + end + end + + swagger_schema :EnvUsersResponse do + allOf do + schema do + key :'$ref', :ProjectResponse + end + schema do + property :users do + key :type, :array + items do + key :type, :string + end + end + end + end + end + + swagger_schema :ProjectUsersResponse do + allOf do + schema do + key :'$ref', :ProjectResponse + end + schema do + property :project_users do + key :type, :array + items do + key :type, :string + end + end + end + end + end + + swagger_schema :ProjectRunListResponse do + allOf do + schema do + key :'$ref', :ProjectResponse + end + schema do + property :run_list do + key :type, :array + items do + key :type, :string + end + end + end + end + end + + swagger_schema :CategoryProviderObject do + property :name do + key :type, :string + end + property :account do + key :type, :string + end + property :flavor do + key :type, :string + end + property :image do + key :type, :string + end + property :subnet do + key :type, :string + end + property :vpc_id do + key :type, :string + end + property :security_group do + key :type, :array + items do + key :type, :string + end + end + property :stack_template do + key :type, :string + end + end + + swagger_schema :CategoryCmToolObject do + property :name do + key :type, :string + end + property :bootstrap_template do + key :type, :string + end + end + + swagger_schema :CategoryObject do + property :id do + key :type, :string + key :maxLength, 100 + end + property :provider do + key :'$ref', :CategoryProviderObject + end + property :cm_tool do + key :'$ref', :CategoryCmToolObject + end + end + + swagger_schema :EnvironmentObject do + property :id do + key :type, :string + key :maxLength, 100 + end + property :expires do + key :type, :string + end + property :run_list do + key :type, :array + items do + key :type, :string + end + end + property :users do + key :type, :array + items do + key :type, :string + end + end + property :categories do + key :type, :array + items do + key :'$ref', :CategoryObject + end + end + end + + swagger_schema :ProjectShowObject do + allOf do + schema do + key :'$ref', :ProjectListObject + end + schema do + property :archived do + key :type, :boolean + end + property :environments do + key :type, :array + items do + key :'$ref', :EnvironmentObject + end + end + end + end + end + + swagger_schema :ProjectUpdateObject do + property :description do + key :type, :string + key :maxLength, 500 + end + property :run_list do + key :type, :array + items do + key :type, :string + end + end + property :project_users do + key :type, :array + items do + key :type, :string + end + end + end + + swagger_schema :ProjectListObject do + allOf do + schema do + key :'$ref', :ProjectUpdateObject + end + schema do + property :id do + key :type, :string + key :maxLength, 100 + end + property :owner do + key :type, :string + end + property :created_at do + key :type, :integer + key :format, :int64 + end + end + end + end + + swagger_schema :ProjectObjectInput do + key :required, [:image_id, :name, :provider, :provider_account, :remote_user] + property :image_id do + key :type, :string + key :pattern, "^[a-z0-9_-]{0,99}$" + key :maxLength, 100 + end + property :name do + key :type, :string + key :maxLength, 255 + end + property :remote_user do + key :type, :string + key :pattern, "^[a-z_][a-z0-9_-]{0,30}$" + key :maxLength, 31 + end + property :provider do + key :type, :string + end + property :provider_account do + key :type, :string + key :pattern, "^[a-z_][a-z0-9_-]{0,99}$" + key :maxLength, 100 + end + end + + swagger_path "/projects" do + operation :get do + key :description, 'Get projects list' + key :operationId, 'getProjects' + key :tags, [ + 'project' + ] + response 200 do + key :description, 'Projects list' + schema do + key :type, :array + items do + key :'$ref', :ProjectListObject + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/project/{id}" do + operation :get do + key :description, 'Get project by id' + key :operationId, 'getProjectById' + key :tags, [ + 'project' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Project id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Project object' + schema do + key :'$ref', :ProjectShowObject + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/project/{id}" do + operation :put do + key :description, 'Update project by id' + key :operationId, 'updateProjectById' + key :tags, [ + 'project' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Project id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :project + key :in, :body + key :description, 'Project parameters to rewrite' + key :required, true + schema do + key :'$ref', :ProjectUpdateObject + end + end + response 200 do + key :description, 'Project response' + schema do + key :'$ref', :ProjectResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/project/{id}" do + operation :delete do + key :description, 'Delete project by id' + key :operationId, 'deleteProjectById' + key :tags, [ + 'project' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Project id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Project response' + schema do + key :'$ref', :ProjectResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/project" do + operation :post do + key :description, 'Create project' + key :operationId, 'createProject' + key :tags, [ + 'project' + ] + parameter do + key :name, :project + key :in, :body + key :description, 'New project body' + key :required, true + schema do + key :'$ref', :ProjectCreate + end + end + response 201 do + key :description, 'Create project response' + schema do + key :'$ref', :ProjectResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/project/{id}/users/add" do + operation :post do + key :description, 'Add users to project' + key :operationId, 'addUsersToProject' + key :tags, [ + 'project' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Project id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :users + key :in, :body + key :description, 'Add users to project list' + key :required, true + schema do + key :type, :array + items do + key :type, :string + end + end + end + response 200 do + key :description, 'Project users response' + schema do + key :'$ref', :ProjectUsersResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/project/{id}/users/delete" do + operation :post do + key :description, 'Delete users from project' + key :operationId, 'deleteUsersFromProject' + key :tags, [ + 'project' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Project id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :users + key :in, :body + key :description, 'Delete users from project list' + key :required, true + schema do + key :type, :array + items do + key :type, :string + end + end + end + response 200 do + key :description, 'Project response' + schema do + key :'$ref', :ProjectUsersResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/project/{id}/run_list" do + operation :put do + key :description, 'Set new run_list elements to project' + key :operationId, 'setRunListToProject' + key :tags, [ + 'project' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Project id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :run_list + key :in, :body + key :description, 'run_list elements to set to project list' + key :required, true + schema do + key :type, :array + items do + key :type, :string + end + end + end + response 200 do + key :description, 'Project response' + schema do + key :'$ref', :ProjectRunListResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/project/{id}/run_list/add" do + operation :post do + key :description, 'Add new run_list elements to project' + key :operationId, 'addRunListToProject' + key :tags, [ + 'project' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Project id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :run_list + key :in, :body + key :description, 'run_list elements to add to project list' + key :required, true + schema do + key :type, :array + items do + key :type, :string + end + end + end + response 200 do + key :description, 'Project response' + schema do + key :'$ref', :ProjectRunListResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/project/{id}/run_list/delete" do + operation :post do + key :description, 'Delete run_list elements from project' + key :operationId, 'deleteRunListFromProject' + key :tags, [ + 'project' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Project id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :users + key :in, :body + key :description, 'run_list elements to delete from project list' + key :required, true + schema do + key :type, :array + items do + key :type, :string + end + end + end + response 200 do + key :description, 'Project response' + schema do + key :'$ref', :ProjectRunListResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/project/{project}/archive" do + operation :post do + key :description, 'Archive project' + key :operationId, 'archiveProject' + key :tags, [ + 'project' + ] + parameter do + key :name, :project + key :in, :path + key :description, 'Project id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Project response' + schema do + key :'$ref', :ProjectResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/project/{project}/unarchive" do + operation :post do + key :description, 'Unarchive project' + key :operationId, 'unarchiveProject' + key :tags, [ + 'project' + ] + parameter do + key :name, :project + key :in, :path + key :description, 'Project id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Project response' + schema do + key :'$ref', :ProjectResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/project/{project}/environment/{env}/users/add" do + operation :post do + key :description, 'Add users to environment' + key :operationId, 'addUsersToEnvironment' + key :tags, [ + 'project' + ] + parameter do + key :name, :project + key :in, :path + key :description, 'Project id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :env + key :in, :path + key :description, 'Environment id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :users + key :in, :body + key :description, 'Users list to add to environment list' + key :required, true + schema do + key :type, :array + items do + key :type, :string + end + end + end + response 200 do + key :description, 'Environment users response' + schema do + key :'$ref', :EnvUsersResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/project/{project}/environment/{env}/users/delete" do + operation :post do + key :description, 'Delete users from environment' + key :operationId, 'deleteUsersFromEnvironment' + key :tags, [ + 'project' + ] + parameter do + key :name, :project + key :in, :path + key :description, 'Project id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :env + key :in, :path + key :description, 'Environment id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :users + key :in, :body + key :description, 'Users list to delete from environment list' + key :required, true + schema do + key :type, :array + items do + key :type, :string + end + end + end + response 200 do + key :description, 'Environment users response' + schema do + key :'$ref', :EnvUsersResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/project/{project}/servers" do + operation :get do + key :description, 'Show project servers' + key :operationId, 'showProjectServers' + key :tags, [ + 'project' + ] + parameter do + key :name, :project + key :in, :path + key :description, 'Project id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Project servers response' + schema do + key :type, :array + items do + key :'$ref', :ServerObject + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/project/{project}/environment/{env}/servers" do + operation :get do + key :description, 'Show environment servers' + key :operationId, 'showEnvironmentServers' + key :tags, [ + 'project' + ] + parameter do + key :name, :project + key :in, :path + key :description, 'Project id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :env + key :in, :path + key :description, 'Environment id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Environment servers response' + schema do + key :type, :array + items do + key :'$ref', :ServerObject + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/project/{project}/environment/{env}/stacks" do + operation :get do + key :description, 'Show environment stacks' + key :operationId, 'showEnvironmentStacks' + key :tags, [ + 'project' + ] + parameter do + key :name, :project + key :in, :path + key :description, 'Project id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :env + key :in, :path + key :description, 'Environment id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Environment stacks response' + schema do + key :type, :array + items do + key :'$ref', :StackObject + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/project/{project}/environments" do + operation :get do + key :description, 'Get project environments' + key :operationId, 'getProjectEnvironments' + key :tags, [ + 'project' + ] + parameter do + key :name, :project + key :in, :path + key :description, 'Project id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Environments list' + schema do + key :type, :array + items do + key :'$ref', :EnvironmentObject + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/project/{project}/environment" do + operation :post do + key :description, 'Create project environment' + key :operationId, 'createProjectEnvironment' + key :tags, [ + 'project' + ] + parameter do + key :name, :project + key :in, :path + key :description, 'Project id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :environment + key :in, :body + key :description, 'Environment object' + key :required, true + schema do + key :'$ref', :CreateEnvironmentObject + end + end + response 200 do + key :description, 'Create environment response' + schema do + key :'$ref', :CreateEnvironmentResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/project/{project}/environment/{env}" do + operation :get do + key :description, 'Show project environment' + key :operationId, 'showProjectEnvironment' + key :tags, [ + 'project' + ] + parameter do + key :name, :project + key :in, :path + key :description, 'Project id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :env + key :in, :path + key :description, 'Environment id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Environment object' + schema do + key :'$ref', :EnvironmentObject + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/project/{project}/environment/{env}" do + operation :delete do + key :description, 'Delete project environment' + key :operationId, 'deleteProjectEnvironment' + key :tags, [ + 'project' + ] + parameter do + key :name, :project + key :in, :path + key :description, 'Project id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :env + key :in, :path + key :description, 'Environment id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Delete environment response' + schema do + key :'$ref', :CreateEnvironmentResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/project/{project}/environment/{env}/categories" do + operation :get do + key :description, 'Get environment categories' + key :operationId, 'getEnvironmentCategories' + key :tags, [ + 'project' + ] + parameter do + key :name, :project + key :in, :path + key :description, 'Project id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :env + key :in, :path + key :description, 'Environment id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Categories list' + schema do + key :type, :array + items do + key :'$ref', :CategoryObject + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/project/{project}/environment/{env}/category/{category}" do + operation :get do + key :description, 'Get category by id' + key :operationId, 'getCategoryById' + key :tags, [ + 'project' + ] + parameter do + key :name, :project + key :in, :path + key :description, 'Project id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :env + key :in, :path + key :description, 'Environment id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :category + key :in, :path + key :description, 'Category id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Category' + schema do + key :'$ref', :CategoryObject + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/project/{project}/environment/{env}/category" do + operation :post do + key :description, 'Create new category' + key :operationId, 'createCategory' + key :tags, [ + 'project' + ] + parameter do + key :name, :project + key :in, :path + key :description, 'Project id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :env + key :in, :path + key :description, 'Environment id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :category + key :in, :body + key :description, 'Category object' + key :required, true + schema do + key :'$ref', :CategoryObject + end + end + response 200 do + key :description, 'Create category response' + schema do + key :'$ref', :CreateCategoryResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/project/{project}/environment/{env}/category/{category}" do + operation :delete do + key :description, 'Delete category by id' + key :operationId, 'deleteCategoryById' + key :tags, [ + 'project' + ] + parameter do + key :name, :project + key :in, :path + key :description, 'Project id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :env + key :in, :path + key :description, 'Environment id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :category + key :in, :path + key :description, 'Category id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Delete category response' + schema do + key :'$ref', :CreateCategoryResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + end + end + end +end diff --git a/devops-service/app/api3/docs/provider.rb b/devops-service/app/api3/docs/provider.rb new file mode 100644 index 0000000..203d81e --- /dev/null +++ b/devops-service/app/api3/docs/provider.rb @@ -0,0 +1,607 @@ +require 'swagger/blocks' + +require_relative 'devops_error' +require_relative 'devops_response' + +module Devops + module API3 + module Docs + class ProviderRoutes + + include Swagger::Blocks + + swagger_path "/providers/available" do + operation :get do + key :description, 'Get providers list from configuration file' + key :operationId, 'getProvidersAvailable' + key :tags, [ + 'provider' + ] + response 200 do + key :description, 'Providers response' + schema do + key :type, :array + items do + key :type, :string + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/providers" do + operation :get do + key :description, 'Get providers list' + key :operationId, 'getProviders' + key :tags, [ + 'provider' + ] + response 200 do + key :description, 'Providers response' + schema do + key :type, :array + items do + key :type, :string + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_schema :CommonAccount do + allOf do + schema do + key :'$ref', :CommonAccountInput + end + schema do + property :provider do + key :type, :string + end + property :created_at do + key :type, :integer + key :format, :int64 + end + end + end + end + + swagger_schema :CommonAccountInput do + key :required, [:account_name, :ssh_key] + property :account_name do + key :type, :string + key :pattern, "^[a-z_][a-z0-9_-]{0,99}$" + key :maxLength, 100 + end + property :description do + key :type, :string + key :maxLength, 500 + end + property :ssh_key do + key :type, :string + key :pattern, "^[a-z_][a-z0-9_-]{0,99}$" + key :maxLength, 100 + end + end + + ###### static provider - begin + swagger_path "/provider/static/accounts" do + operation :get do + key :description, 'Get static provider accounts' + key :operationId, 'getStaticProviderAccounts' + key :tags, [ + 'provider' + ] + response 200 do + key :description, 'Static provider account response' + schema do + key :type, :array + items do + key :'$ref', :StaticProviderAccount + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/provider/static/account" do + operation :post do + key :description, 'Create static provider account' + key :operationId, 'createStaticProviderAccount' + key :tags, [ + 'provider' + ] + parameter do + key :name, :account + key :in, :body + key :description, 'Account to add for static provider' + key :required, true + schema do + key :'$ref', :StaticProviderAccountInput + end + end + response 200 do + key :description, 'Static provider account response' + schema do + key :'$ref', :StaticProviderAccountResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_schema :StaticProviderAccount do + allOf do + schema do + key :'$ref', :CommonAccount + end + end + end + + swagger_schema :StaticProviderAccountResponse do + allOf do + schema do + key :'$ref', :DevopsResponse + end + schema do + property :account do + key :'$ref', :StaticProviderAccount + end + end + end + end + + swagger_schema :StaticProviderAccountInput do + allOf do + schema do + key :'$ref', :CommonAccountInput + end + end + end + ###### static provider - end + # + ###### aws provider - begin + swagger_path "/provider/aws/accounts" do + operation :get do + key :description, 'Get aws provider accounts' + key :operationId, 'getAwsProviderAccounts' + key :tags, [ + 'provider' + ] + response 200 do + key :description, 'Aws provider accounts response' + schema do + key :type, :array + items do + key :'$ref', :AwsProviderAccount + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/provider/aws/account" do + operation :post do + key :description, 'Create aws provider account' + key :operationId, 'createAwsProviderAccount' + key :tags, [ + 'provider' + ] + parameter do + key :name, :account + key :in, :body + key :description, 'Account to add for aws provider' + key :required, true + schema do + key :'$ref', :AwsProviderAccountInput + end + end + response 200 do + key :description, 'Aws provider accounts response' + schema do + key :'$ref', :AwsProviderAccountResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/provider/aws/account/{account_name}/vpcs" do + operation :get do + key :description, 'Get aws provider vpcs for account' + key :operationId, 'getAwsProviderVpcs' + key :tags, [ + 'provider' + ] + parameter do + key :name, :account_name + key :in, :path + key :description, 'Aws provider account name' + key :required, true + key :type, :string + end + response 200 do + key :description, 'Aws provider account vpcs response' + schema do + key :type, :array + items do + key :'$ref', :AwsProviderVpcResponse + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_schema :AwsProviderVpcResponse do + property :vpc_id do + key :type, :string + end + property :cidr do + key :type, :string + end + end + + swagger_schema :AwsProviderAccount do + allOf do + schema do + key :'$ref', :CommonAccount + end + schema do + key :'$ref', :AwsProviderAccountInput + end + end + end + + swagger_schema :AwsProviderAccountResponse do + allOf do + schema do + key :'$ref', :DevopsResponse + end + schema do + property :account do + key :'$ref', :AwsProviderAccount + end + end + end + end + + swagger_schema :AwsProviderAccountInput do + allOf do + schema do + key :'$ref', :CommonAccountInput + end + schema do + key :required, [:account_name, :ssh_key, :use_iam_profile] + property :access_key_id do + key :type, :string + end + property :secret_access_key do + key :type, :string + end + property :use_iam_profile do + key :type, :boolean + end + property :storage_bucket_name do + key :type, :string + end + end + end + end + ###### aws provider - end + + swagger_path "/provider/{provider}/account/{account_name}" do + operation :delete do + key :description, 'Delete provider account' + key :operationId, 'deleteProviderAccount' + key :tags, [ + 'provider' + ] + parameter do + key :name, :provider + key :in, :path + key :description, 'Provider name' + key :required, true + key :type, :string + end + parameter do + key :name, :account_name + key :in, :path + key :description, 'Provider account name' + key :required, true + key :type, :string + end + response 200 do + key :description, 'Provider account response' + schema do + key :'$ref', :DevopsResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/provider/{provider}/account/{account_name}/images" do + operation :get do + key :description, 'Get provider account images' + key :operationId, 'getProviderImages' + key :tags, [ + 'provider' + ] + parameter do + key :name, :provider + key :in, :path + key :description, 'Provider name' + key :required, true + key :type, :string + end + parameter do + key :name, :account_name + key :in, :path + key :description, 'Aws provider account name' + key :required, true + key :type, :string + end + response 200 do + key :description, 'Provider images response' + schema do + key :type, :array + items do + key :'$ref', :ProviderImageResponse + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_schema :ProviderImageResponse do + property :id do + key :type, :string + end + property :name do + key :type, :string + end + property :status do + key :type, :string + end + end + + swagger_path "/provider/{provider}/account/{account_name}/networks" do + operation :get do + key :description, 'Get provider account networks' + key :operationId, 'getProviderNetworks' + key :tags, [ + 'provider' + ] + parameter do + key :name, :provider + key :in, :path + key :description, 'Provider name' + key :required, true + key :type, :string + end + parameter do + key :name, :account_name + key :in, :path + key :description, 'Aws provider account name' + key :required, true + key :type, :string + end + response 200 do + key :description, 'Provider networks response' + schema do + key :type, :array + items do + key :'$ref', :ProviderNetworksResponse + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_schema :ProviderNetworksResponse do + property :cidr do + key :type, :string + end + property :vpcId do + key :type, :string + end + property :subnetId do + key :type, :string + end + property :name do + key :type, :string + end + property :zone do + key :type, :string + end + end + + swagger_path "/provider/{provider}/account/{account_name}/flavors" do + operation :get do + key :description, 'Get provider account flavors' + key :operationId, 'getProviderFlavors' + key :tags, [ + 'provider' + ] + parameter do + key :name, :provider + key :in, :path + key :description, 'Provider name' + key :required, true + key :type, :string + end + parameter do + key :name, :account_name + key :in, :path + key :description, 'Aws provider account name' + key :required, true + key :type, :string + end + response 200 do + key :description, 'Provider flavors response' + schema do + key :type, :array + items do + key :'$ref', :ProviderFlavorsResponse + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_schema :ProviderFlavorsResponse do + property :id do + key :type, :string + end + property :cores do + key :type, :integer + end + property :disk do + key :type, :integer + end + property :name do + key :type, :string + end + property :ram do + key :type, :integer + end + end + + swagger_path "/provider/{provider}/account/{account_name}/security_groups" do + operation :get do + key :description, 'Get provider account security_groups' + key :operationId, 'getProviderSecurityGroups' + key :tags, [ + 'provider' + ] + parameter do + key :name, :provider + key :in, :path + key :description, 'Provider name' + key :required, true + key :type, :string + end + parameter do + key :name, :account_name + key :in, :path + key :description, 'Aws provider account name' + key :required, true + key :type, :string + end + response 200 do + key :description, 'Provider security groups response' + schema do + key :'$ref', :ProviderSecurityGroupsResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_schema :ProviderSecurityGroupsResponse do + property :name_key do + key :'$ref', :ProviderSecurityGroupsElem + end + end + + swagger_schema :ProviderSecurityGroupsElem do + property :id do + key :type, :string + end + property :description do + key :type, :string + end + property :rules do + key :type, :array + items do + key :'$ref', :ProviderSecurityGroupsRule + end + end + end + + swagger_schema :ProviderSecurityGroupsRule do + property :protocol do + key :type, :string + end + property :from do + key :type, :integer + end + property :to do + key :type, :integer + end + property :cidr do + key :type, :string + end + end + + end + end + end +end diff --git a/devops-service/app/api3/docs/role.rb b/devops-service/app/api3/docs/role.rb new file mode 100644 index 0000000..558d0d6 --- /dev/null +++ b/devops-service/app/api3/docs/role.rb @@ -0,0 +1,212 @@ +require 'swagger/blocks' + +require_relative 'devops_error' +require_relative 'devops_response' + +module Devops + module API3 + module Docs + class RolesRoutes + + include Swagger::Blocks + + swagger_schema :PolicyObject do + property :id do + key :type, :string + end + property :description do + key :type, :string + end + property :dependencies do + key :type, :array + items do + key :type, :string + end + end + end + + swagger_schema :RoleObject do + allOf do + schema do + key :'$ref', :RoleObjectInput + end + schema do + property :id do + key :type, :string + end + property :created_at do + key :type, :integer + key :format, :int64 + end + end + end + end + + swagger_schema :RoleObjectInput do + key :required, [:image_id, :name, :provider, :provider_account, :remote_user] + property :name do + key :type, :string + key :pattern, "^[a-z_\s]{1,99}$" + key :maxLength, 99 + key :minLength, 1 + end + property :description do + key :type, :string + key :maxLength, 500 + end + property :policies do + key :type, :array + items do + key :type, :string + end + end + end + + swagger_path "/security/policies" do + operation :get do + key :description, 'Get available policies list' + key :operationId, 'getPolicies' + key :tags, [ + 'role' + ] + response 200 do + key :description, 'Policies list' + schema do + key :type, :array + items do + key :'$ref', :PolicyObject + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/security/roles" do + operation :get do + key :description, 'Get roles list' + key :operationId, 'getRoles' + key :tags, [ + 'role' + ] + response 200 do + key :description, 'Roles list' + schema do + key :type, :array + items do + key :'$ref', :RoleObject + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/security/role" do + operation :post do + key :description, 'Create role' + key :operationId, 'createRole' + key :tags, [ + 'role' + ] + parameter do + key :name, :role + key :in, :body + key :description, 'New role' + key :required, true + schema do + key :'$ref', :RoleObjectInput + end + end + response 201 do + key :description, 'Create role response' + schema do + key :'$ref', :DevopsResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/security/role/{id}" do + operation :get do + key :description, 'Get role by id' + key :operationId, 'getRole' + key :tags, [ + 'role' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Role id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Role object' + schema do + key :'$ref', :RoleObject + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/security/role/{id}" do + operation :delete do + key :description, 'Delete role by id' + key :operationId, 'deleteRole' + key :tags, [ + 'role' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Role id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Delete role response' + schema do + key :'$ref', :DevopsResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + end + end + end +end + diff --git a/devops-service/app/api3/docs/server.rb b/devops-service/app/api3/docs/server.rb new file mode 100644 index 0000000..dcb3f61 --- /dev/null +++ b/devops-service/app/api3/docs/server.rb @@ -0,0 +1,890 @@ +require 'swagger/blocks' + +require_relative 'devops_error' +require_relative 'devops_response' + +module Devops + module API3 + module Docs + class ServerRoutes + + include Swagger::Blocks + + swagger_schema :ServerIdObject do + property :id do + key :type, :string + end + end + + swagger_schema :DevopsServerResponse do + allOf do + schema do + key :'$ref', :DevopsResponse + end + schema do + property :id do + key :type, :string + end + end + end + end + + swagger_path "/servers" do + operation :get do + key :description, 'Get servers list' + key :operationId, 'getServers' + key :tags, [ + 'server' + ] + response 200 do + key :description, 'Servers list' + schema do + key :type, :array + items do + key :'$ref', :ServerObject + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/servers/provider/{provider}/{account}" do + operation :get do + key :description, 'Get servers list by provider and provider account' + key :operationId, 'getServersByProviderAccount' + key :tags, [ + 'server' + ] + parameter do + key :name, :provider + key :in, :path + key :description, 'Provider name' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :account + key :in, :path + key :description, 'Provider account id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Servers list' + schema do + key :type, :array + items do + key :'$ref', :ServerObject + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/server/id/{name}" do + operation :get do + key :description, 'Get server id by name' + key :operationId, 'getServerIdByName' + key :tags, [ + 'server' + ] + parameter do + key :name, :name + key :in, :path + key :description, 'Server name' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Server id' + schema do + key :'$ref', :ServerIdObject + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/server/{id}" do + operation :get do + key :description, 'Get server by id' + key :operationId, 'getServerById' + key :tags, [ + 'server' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Server id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Server' + schema do + key :'$ref', :ServerObject + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/server/{id}" do + operation :delete do + key :description, 'Delete server by id' + key :operationId, 'deleteServerById' + key :tags, [ + 'server' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Server id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'List of jobs ids' + schema do + key :type, :array + items do + key :type, :string + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/servers/delete" do + operation :post do + key :description, 'Delete servers' + key :operationId, 'deleteServers' + key :tags, [ + 'server' + ] + parameter do + key :name, :server_ids + key :in, :body + key :description, 'Array of servers ids' + key :required, true + schema do + key :type, :array + items do + key :type, :string + end + end + end + response 200 do + key :description, 'List of jobs ids' + schema do + key :type, :array + items do + key :type, :string + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/servers/{id}/deploy" do + operation :post do + key :description, 'Deploy server' + key :operationId, 'deployServer' + key :tags, [ + 'server' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Server id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :tags + key :in, :body + key :description, 'Chef tags to deploy with' + key :required, false + schema do + key :type, :array + items do + key :type, :string + end + end + end + parameter do + key :name, :run_list + key :in, :body + key :description, 'Chef run_list to deploy with' + key :required, false + schema do + key :type, :array + items do + key :type, :string + end + end + end + parameter do + key :name, :build_number + key :in, :body + key :description, 'Build number to deploy' + key :required, false + schema do + key :type, :string + end + end + response 200 do + key :description, 'List of jobs ids' + schema do + key :type, :array + items do + key :type, :string + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/server" do + operation :post do + key :description, 'Create server' + key :operationId, 'createServer' + key :tags, [ + 'server' + ] + parameter do + key :name, :project + key :in, :body + key :description, 'Project id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :environment + key :in, :body + key :description, 'Environment id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :category + key :in, :body + key :description, 'Category id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :name + key :in, :body + key :description, 'Server name' + key :required, false + schema do + key :type, :string + end + end + parameter do + key :name, :cm_name + key :in, :body + key :description, 'Server name in configuration manager system' + key :required, false + schema do + key :type, :string + end + end + parameter do + key :name, :without_bootstrap + key :in, :body + key :description, 'Skip bootstrap phase' + key :required, false + schema do + key :type, :bool + end + end + parameter do + key :name, :skip_rollback + key :in, :body + key :description, 'Skip rollback on error' + key :required, false + schema do + key :type, :bool + end + end + parameter do + key :name, :run_list + key :in, :body + key :description, 'Chef run_list to deploy with' + key :required, false + schema do + key :type, :array + items do + key :type, :string + end + end + end + response 200 do + key :description, 'List of jobs ids' + schema do + key :type, :array + items do + key :type, :string + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/server/{id}/pause" do + operation :post do + key :description, 'Pause server' + key :operationId, 'pauseServer' + key :tags, [ + 'server' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Server id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Server response' + schema do + key :'$ref', :DevopsServerResponse + end + end + response 409 do + key :description, 'Conflict error, server in invalid state, operation can not be done' + schema do + key :'$ref', :DevopsError + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/server/{id}/unpause" do + operation :post do + key :description, 'Unpause server' + key :operationId, 'unpauseServer' + key :tags, [ + 'server' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Server id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Server response' + schema do + key :'$ref', :DevopsServerResponse + end + end + response 409 do + key :description, 'Conflict error, server in invalid state, operation can not be done' + schema do + key :'$ref', :DevopsError + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/server/{id}/reserve" do + operation :post do + key :description, 'Reserve server' + key :operationId, 'reserveServer' + key :tags, [ + 'server' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Server id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Server response' + schema do + key :'$ref', :DevopsServerResponse + end + end + response 409 do + key :description, 'Conflict error, server in invalid state, operation can not be done' + schema do + key :'$ref', :DevopsError + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/server/{id}/unreserve" do + operation :post do + key :description, 'Unreserve server' + key :operationId, 'unreserveServer' + key :tags, [ + 'server' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Server id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Server response' + schema do + key :'$ref', :DevopsServerResponse + end + end + response 409 do + key :description, 'Conflict error, server in invalid state, operation can not be done' + schema do + key :'$ref', :DevopsError + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/server/{id}/bootstrap" do + operation :post do + key :description, 'Bootstrap server' + key :operationId, 'bootstrapServer' + key :tags, [ + 'server' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Server id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :run_list + key :in, :body + key :description, 'Chef run_list to deploy with' + key :required, false + schema do + key :type, :array + items do + key :type, :string + end + end + end + parameter do + key :name, :bootstrap_template + key :in, :body + key :description, 'Owerride bootstrap_template parameter' + key :required, false + schema do + key :type, :string + end + end + parameter do + key :name, :cm_name + key :in, :body + key :description, 'Server name in configuration manager system' + key :required, false + schema do + key :type, :string + end + end + response 200 do + key :description, 'Server response' + schema do + key :'$ref', :DevopsServerResponse + end + end + response 409 do + key :description, 'Conflict error, server in invalid state, operation can not be done' + schema do + key :'$ref', :DevopsError + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/server/{id}/unbootstrap" do + operation :post do + key :description, 'Unbootstrap server' + key :operationId, 'unbootstrapServer' + key :tags, [ + 'server' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Server id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'Server response' + schema do + key :'$ref', :DevopsServerResponse + end + end + response 409 do + key :description, 'Conflict error, server in invalid state, operation can not be done' + schema do + key :'$ref', :DevopsError + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/server/add" do + operation :post do + key :description, 'Add static server' + key :operationId, 'addServer' + key :tags, [ + 'server' + ] + parameter do + key :name, :project + key :in, :body + key :description, 'Project id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :environment + key :in, :body + key :description, 'Environment id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :category + key :in, :body + key :description, 'Category id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :private_ip + key :in, :body + key :description, 'Server private ip to connect to' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :key + key :in, :body + key :description, 'Key id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :remote_user + key :in, :body + key :description, 'Remote user for server to connect to' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :name + key :in, :body + key :description, 'Server name' + key :required, false + schema do + key :type, :string + end + end + parameter do + key :name, :run_list + key :in, :body + key :description, 'Chef run_list to deploy with' + key :required, false + schema do + key :type, :array + items do + key :type, :string + end + end + end + parameter do + key :name, :public_ip + key :in, :body + key :description, 'Server public ip to connect to' + key :required, false + schema do + key :type, :string + end + end + response 200 do + key :description, 'Server response' + schema do + key :'$ref', :DevopsServerResponse + end + end + response 409 do + key :description, 'Conflict error, server in invalid state, operation can not be done' + schema do + key :'$ref', :DevopsError + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/server/{id}/tags/add" do + operation :post do + key :description, 'Add tags to provider server' + key :operationId, 'addServerTags' + key :tags, [ + 'server' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Server id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :tags + key :in, :body + key :description, 'Key-value object with tags' + key :required, false + schema do + key :type, :string + end + end + response 200 do + key :description, 'Server response' + schema do + key :'$ref', :DevopsServerResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/server/{id}/tags/delete" do + operation :post do + key :description, 'Delete tags from provider server' + key :operationId, 'deleteServerTags' + key :tags, [ + 'server' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Server id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :tags + key :in, :body + key :description, 'Key-value object with tags' + key :required, false + schema do + key :type, :string + end + end + response 200 do + key :description, 'Server response' + schema do + key :'$ref', :DevopsServerResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/server/{id}/run_list" do + operation :post do + key :description, 'Set server run_list' + key :operationId, 'setServerRunList' + key :tags, [ + 'server' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'Server id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :run_list + key :in, :body + key :description, 'New server run_list' + key :required, false + schema do + key :type, :array + items do + key :type, :string + end + end + end + response 200 do + key :description, 'Server response' + schema do + key :'$ref', :DevopsServerResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + end + end + end +end diff --git a/devops-service/app/api3/docs/server_model.rb b/devops-service/app/api3/docs/server_model.rb new file mode 100644 index 0000000..6403cda --- /dev/null +++ b/devops-service/app/api3/docs/server_model.rb @@ -0,0 +1,65 @@ +require 'swagger/blocks' + +module Devops + module API3 + module Docs + class ServerModel + + include Swagger::Blocks + + swagger_schema :ServerObject do + property :id do + key :type, :string + end + property :name do + key :type, :string + end + property :cm_name do + key :type, :string + end + property :remote_user do + key :type, :string + end + property :project do + key :type, :string + end + property :environment do + key :type, :string + end + property :category do + key :type, :string + end + property :private_ip do + key :type, :string + end + property :public_ip do + key :type, :string + end + property :created_by do + key :type, :string + end + property :reserved_by do + key :type, :string + end + property :stack do + key :type, :string + end + property :run_list do + key :type, :array + items do + key :type, :string + end + end + property :ssh_key do + key :type, :string + end + property :last_operation do + key :'$ref', :ServerLastOperationObject + end + + end + + end + end + end +end diff --git a/devops-service/app/api3/docs/user.rb b/devops-service/app/api3/docs/user.rb new file mode 100644 index 0000000..8991b40 --- /dev/null +++ b/devops-service/app/api3/docs/user.rb @@ -0,0 +1,435 @@ +require 'swagger/blocks' + +require_relative 'devops_error' +require_relative 'devops_response' + +module Devops + module API3 + module Docs + class UserRoutes + + include Swagger::Blocks + + swagger_schema :UserResponse do + allOf do + schema do + key :'$ref', :DevopsResponse + end + schema do + property :id do + key :type, :string + end + end + end + end + + swagger_schema :UserObject do + allOf do + schema do + key :'$ref', :UserObjectCommon + end + schema do + property :created_at do + key :type, :integer + key :format, :int64 + end + end + end + end + + swagger_schema :UserObjectInput do + allOf do + schema do + key :'$ref', :UserObjectCommon + end + schema do + property :password do + key :type, :string + key :maxLength, 31 + key :minLength, 5 + end + end + end + end + + swagger_schema :UserEmailBody do + key :required, [:email] + property :email do + key :type, :string + end + end + + swagger_schema :UserPasswordBody do + key :required, [:password] + property :password do + key :type, :string + key :maxLength, 31 + key :minLength, 5 + end + end + + swagger_schema :UserBodyToUpdate do + allOf do + schema do + key :'$ref', :UserEmailBody + end + schema do + key :'$ref', :UserPasswordBody + end + schema do + property :roles do + key :type, :array + items do + key :type, :string + end + end + end + end + end + + swagger_schema :UserObjectCommon do + key :required, [:id, :email, :roles] + property :id do + key :type, :string + key :pattern, "^[\w-]{1,100}$" + key :maxLength, 100 + end + property :email do + key :type, :string + end + property :roles do + key :type, :array + items do + key :type, :string + end + end + end + + swagger_path "/users" do + operation :get do + key :description, 'Get users list' + key :operationId, 'getUsers' + key :tags, [ + 'user' + ] + response 200 do + key :description, 'Users list' + schema do + key :type, :array + items do + key :'$ref', :UserObject + end + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/user/{id}" do + operation :get do + key :description, 'Get user by id' + key :operationId, 'getUserById' + key :tags, [ + 'user' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'User id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'User' + schema do + key :'$ref', :UserObject + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/user" do + operation :post do + key :description, 'Create new user' + key :operationId, 'createUser' + key :tags, [ + 'user' + ] + parameter do + key :name, :user + key :in, :body + key :description, 'New user' + key :required, true + schema do + key :'$ref', :UserObjectInput + end + end + response 201 do + key :description, 'Create user response' + schema do + key :'$ref', :UserResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/user/{id}/roles/add" do + operation :post do + key :description, 'Add roles to user' + key :operationId, 'addRolesToUser' + key :tags, [ + 'user' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'User id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :roles + key :in, :body + key :description, 'Roles list' + key :required, true + schema do + key :type, :array + items do + key :type, :string + end + end + end + response 200 do + key :description, 'User response' + schema do + key :'$ref', :UserResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/user/{id}/roles/delete" do + operation :post do + key :description, 'Delete roles from user' + key :operationId, 'deleteRolesFromUser' + key :tags, [ + 'user' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'User id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :roles + key :in, :body + key :description, 'Roles list' + key :required, true + schema do + key :type, :array + items do + key :type, :string + end + end + end + response 200 do + key :description, 'User response' + schema do + key :'$ref', :UserResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/user/{id}/email" do + operation :put do + key :description, 'Update user email' + key :operationId, 'userEmail' + key :tags, [ + 'user' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'User id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :email + key :in, :body + key :description, 'New email' + key :required, true + schema do + key :'$ref', :UserEmailBody + end + end + response 200 do + key :description, 'User response' + schema do + key :'$ref', :UserResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/user/{id}/password" do + operation :put do + key :description, 'Update user password' + key :operationId, 'userPassword' + key :tags, [ + 'user' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'User id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :password + key :in, :body + key :description, 'New password' + key :required, true + schema do + key :'$ref', :UserPasswordBody + end + end + response 200 do + key :description, 'User response' + schema do + key :'$ref', :UserResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/user/{id}" do + operation :delete do + key :description, 'Delete user by id' + key :operationId, 'deleteUserById' + key :tags, [ + 'user' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'User id' + key :required, true + schema do + key :type, :string + end + end + response 200 do + key :description, 'User response' + schema do + key :'$ref', :UserResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + swagger_path "/user/{id}" do + operation :put do + key :description, 'Update user by id' + key :operationId, 'updateUserById' + key :tags, [ + 'user' + ] + parameter do + key :name, :id + key :in, :path + key :description, 'User id' + key :required, true + schema do + key :type, :string + end + end + parameter do + key :name, :user_data + key :in, :body + key :description, 'User data to update' + key :required, true + schema do + key :'$ref', :UserBodyToUpdate + end + end + response 200 do + key :description, 'User response' + schema do + key :'$ref', :UserResponse + end + end + response :default do + key :description, 'error' + schema do + key :'$ref', :DevopsError + end + end + end + end + + end + end + end +end diff --git a/devops-service/app/api3/handlers/api-docs.rb b/devops-service/app/api3/handlers/api-docs.rb new file mode 100644 index 0000000..7d0981a --- /dev/null +++ b/devops-service/app/api3/handlers/api-docs.rb @@ -0,0 +1,109 @@ +require 'swagger/blocks' +require_relative "request_handler" + +require 'app/api3/docs/devops_error' +require 'app/api3/docs/devops_response' +require 'app/api3/docs/server_model' +require 'app/api3/docs/provider' +require 'app/api3/docs/job_task' +require 'app/api3/docs/chef' +require 'app/api3/docs/filter' +require 'app/api3/docs/image' +require 'app/api3/docs/key' +require 'app/api3/docs/role' +require 'app/api3/docs/user' +require 'app/api3/docs/project' +require 'app/api3/docs/server' + +module Devops + module API3 + module Handler + class ApiDocs < RequestHandler + + include Swagger::Blocks + + swagger_root do + key :swagger, '2.0' + info do + key :version, '3.0' + key :title, 'Devops API' + key :description, 'Devops API v3 specification' + license do + key :name, 'MIT' + key :url, 'https://opensource.org/licenses/MIT' + end + end + + tag do + key :name, 'provider' + key :description, 'Providers operations' + end + tag do + key :name, 'task' + key :description, 'Job tasks operations' + end + tag do + key :name, 'chef' + key :description, 'Chef operations' + end + tag do + key :name, 'filter' + key :description, 'Filter operations' + end + tag do + key :name, 'image' + key :description, 'Image operations' + end + tag do + key :name, 'key' + key :description, 'SSH keys operations' + end + tag do + key :name, 'role' + key :description, 'User roles operations' + end + tag do + key :name, 'user' + key :description, 'Users operations' + end + tag do + key :name, 'project' + key :description, 'Projects operations' + end + tag do + key :name, 'server' + key :description, 'Servers operations' + end +# key :host, 'petstore.swagger.wordnik.com' + key :basePath, DevopsConfig.config[:url_prefix] + '/v3' + key :scheme, 'http' + key :consumes, ['application/json'] + key :produces, ['application/json'] + end + + # A list of all classes that have swagger_* declarations. + SWAGGERED_CLASSES = [ + Devops::API3::Docs::ProviderRoutes, + Devops::API3::Docs::JobTaskRoutes, + Devops::API3::Docs::ChefRoutes, + Devops::API3::Docs::FilterRoutes, + Devops::API3::Docs::ImageRoutes, + Devops::API3::Docs::KeyRoutes, + Devops::API3::Docs::RolesRoutes, + Devops::API3::Docs::UserRoutes, + Devops::API3::Docs::ProjectRoutes, + Devops::API3::Docs::ServerRoutes, + Devops::API3::Docs::DevopsError, + Devops::API3::Docs::DevopsResponse, + Devops::API3::Docs::ServerModel, + self + ].freeze + + def index + Swagger::Blocks.build_root_json(SWAGGERED_CLASSES) + end + + end + end + end +end diff --git a/devops-service/app/api2/handlers/tag.rb b/devops-service/app/api3/handlers/chef.rb similarity index 65% rename from devops-service/app/api2/handlers/tag.rb rename to devops-service/app/api3/handlers/chef.rb index de072e7..afce148 100644 --- a/devops-service/app/api2/handlers/tag.rb +++ b/devops-service/app/api3/handlers/chef.rb @@ -1,18 +1,18 @@ -require "commands/knife_commands" -require "app/api2/parsers/tag" +require "commands/bootstrap_templates" +require "app/api3/parsers/chef" require_relative "request_handler" module Devops - module API2_0 + module API3 module Handler - class Tag < RequestHandler - set_parser Devops::API2_0::Parser::TagParser + class Chef < RequestHandler - def initialize node_name - @node_name = node_name - @server = Devops::DB.connector.server_by_chef_node_name(@node_name) - #TODO: raise - halt_response("No servers found for name '#{@node_name}'", 404) if @server.nil? + set_parser Devops::API3::Parser::ChefParser + + include BootstrapTemplatesCommands + + def chef_nodes + KnifeFactory.instance.chef_node_list end def tags node_name @@ -34,6 +34,7 @@ module Devops halt_response("Cannot delete tags #{tagsStr} from server #{node_name}: #{cmd[0]}", 500) unless cmd[1] tags end + end end end diff --git a/devops-service/app/api2/handlers/deploy.rb b/devops-service/app/api3/handlers/deploy.rb similarity index 76% rename from devops-service/app/api2/handlers/deploy.rb rename to devops-service/app/api3/handlers/deploy.rb index 204d35d..778ebe1 100644 --- a/devops-service/app/api2/handlers/deploy.rb +++ b/devops-service/app/api3/handlers/deploy.rb @@ -2,17 +2,17 @@ require "lib/executors/server_executor" require "commands/status" require "workers/deploy_worker" require "exceptions/deploy_info_error" -require "app/api2/parsers/deploy" +require "app/api3/parsers/deploy" require_relative "request_handler" module Devops - module API2_0 + module API3 module Handler class Deploy < RequestHandler # extend DeployCommands extend StatusCommands - set_parser Devops::API2_0::Parser::DeployParser + set_parser Devops::API3::Parser::DeployParser def deploy body = parser.deploy @@ -25,7 +25,7 @@ module Devops @deploy_info_buf = {} servers(names).each do |s| project = begin - Devops::Db.connector.check_project_auth s.project, s.deploy_env, owner + Devops::Model::Project.check_user_authorization(s.project, s.environment, owner) rescue InvalidPrivileges, RecordNotFound => e DevopsLogger.logger.warn e.message next @@ -45,7 +45,7 @@ module Devops rescue DeployInfoError => e msg = "Can not get deploy info: " + e.message DevopsLogger.logger.error msg - jid = "error_#{s.chef_node_name}_#{Time.new.to_i}" + jid = "error_#{s.name}_#{Time.new.to_i}" file = File.jon(dir, jid) File.open(file, "w") do |out| out.write msg @@ -55,12 +55,12 @@ module Devops "_id" => jid, "created_by" => owner, "project" => s.project, - "deploy_env" => s.deploy_env, - "type" => Report::DEPLOY_TYPE, + "environment" => s.environment, + "type" => JobTask::DEPLOY_TYPE, "status" => Worker::STATUS::FAILED # "attributes" => attributes } - Devops::Db.connector.save_report(Report.new(o)) + JobTask.create(o) end files.push(jid) end @@ -77,8 +77,8 @@ module Devops @deploy_info_buf = {} servers(names).each do |s| project = begin - Devops::Db.connector.check_project_auth s.project, s.deploy_env, owner - rescue InvalidPrivileges, RecordNotFound => e + Devops::Model::Project.check_user_authorization(s.project, s.environment, owner) + rescue InvalidPrivileges, Devops::Exception::RecordNotFound => e out << e.message + "\n" status.push 2 next @@ -97,21 +97,21 @@ module Devops end end status - rescue RecordNotFound => e + rescue Devops::Exception::RecordNotFound => e out << e.message [-10] end def servers names - servers = Devops::Db.connector.servers(nil, nil, names, true) - raise RecordNotFound.new("No reserved servers found for names '#{names.join("', '")}'") if servers.empty? - servers.sort_by!{|s| names.index(s.chef_node_name)} + servers = Devops::Model::Server.find(name: {'$in' => names}, reserved: true) + raise Devops::Exception::RecordNotFound.new("No reserved servers found for names '#{names.join("', '")}'") if servers.empty? + servers.sort_by!{|s| names.index(s.name)} servers end def create_deploy_info server, project, build_number - deploy_env_model = project.deploy_env(server.deploy_env) - buf_key = "#{server.project}_#{server.deploy_env}" + deploy_env_model = project.environment(server.environment) + buf_key = "#{server.project}_#{server.environment}" deploy_info = if @deploy_info_buf.key?(buf_key) @deploy_info_buf[buf_key] else diff --git a/devops-service/app/api3/handlers/filter.rb b/devops-service/app/api3/handlers/filter.rb new file mode 100644 index 0000000..b7850c6 --- /dev/null +++ b/devops-service/app/api3/handlers/filter.rb @@ -0,0 +1,33 @@ +require "app/api3/parsers/filter" +require 'db/mongo/models/filter' +require_relative "request_handler" + +module Devops + module API3 + module Handler + class Filter < RequestHandler + + set_parser Devops::API3::Parser::FilterParser + + def available_images provider + Devops::Model::Filter.available_images(provider) + end + + def add_images provider + images = parser.images + res = Devops::Model::Filter.add_available_images(images, provider) + DevopsLogger.logger.info "Added new image filters '#{images.join("', '")}' for provider #{provider}" + res + end + + def delete_images provider + res = Devops::Model::Filter.delete_available_images(parser.images, provider) + DevopsLogger.logger.info "Removed image filters '#{res.join("', '")}' from provider #{provider}" + res + end + + end + end + end +end + diff --git a/devops-service/app/api3/handlers/image.rb b/devops-service/app/api3/handlers/image.rb new file mode 100644 index 0000000..3a127b7 --- /dev/null +++ b/devops-service/app/api3/handlers/image.rb @@ -0,0 +1,63 @@ +require "commands/image" +require "db/mongo/models/image" +require "app/api3/parsers/image" +require_relative "request_handler" + +module Devops + module API3 + module Handler + class Image < RequestHandler + + set_parser Devops::API3::Parser::ImageParser + + def images + provider = parser.images + if provider + Devops::Model::Image.where(provider: provider) + else + Devops::Model::Image.all + end + end + + def image id + Devops::Model::Image.find(id) + rescue Mongoid::Errors::DocumentNotFound + raise Devops::Exception::RecordNotFound.new("Image with id '#{id}' not found") + end + + def create_image + image = parser.image + image.save! + DevopsLogger.logger.info "Image '#{image.id}' has been created" + image + rescue Mongoid::Errors::Validations => e + raise Devops::Exception::ValidationError.create_from_db_exception(e) + end + + def update_image id + image = image(id) + obj = parser.image + image.update_attributes! obj.to_hash_update + DevopsLogger.logger.info "Image '#{image.id}' has been updated" + image + rescue Mongoid::Errors::Validations => e + raise Devops::Exception::ValidationError.create_from_db_exception(e) + end + + def delete_image id + projects = Devops::Model::Project.find_by('environments.image' => id) + ar = [] + projects.each do |p| + ar += p.environments.select{|e| e.respond_to?(:image)}.select{|e| e.image == id}.map{|e| "#{p.id}.#{e.id}"} + end + raise DependencyError.new "Deleting is forbidden: Image is used in #{ar.join(", ")}" + rescue Mongoid::Errors::DocumentNotFound + image(id).delete + DevopsLogger.logger.info "Image '#{id}' has been deleted" + end + + end + end + end +end + diff --git a/devops-service/app/api3/handlers/job_task.rb b/devops-service/app/api3/handlers/job_task.rb new file mode 100644 index 0000000..ec41851 --- /dev/null +++ b/devops-service/app/api3/handlers/job_task.rb @@ -0,0 +1,68 @@ +require_relative "request_handler" + +module Devops + module API3 + module Handler + class JobTask < RequestHandler + + def options + params = @request.params + options = {} + ["project", "environment", "category", "type", "created_by", "status", "server_id", "max_number"].each do |k| + options[k] = params[k] unless params[k].nil? + end + fill_date_params_for_searching(options) + + #attributes_keys = params.keys.select{|k| k =~ /attributes\.*/} + #attributes_keys.each do |ak| + # options[ak] = params[ak] + #end + options + end + + def all + sort = extract_sort_params_for_searching + limit = extract_limit_params_for_searching(10) + Devops::Model::JobTask.where(options).sort(sort).limit(limit) + end + + def all_latest + # TODO: + #Devops::Db.connector.latest_reports(options()) + [] + end + + def attributes name + # TODO: + #Devops::Db.connector.reports_attributes_values(name) + [] + end + + def task id + Devops::Model::JobTask.find(id) + rescue Mongoid::Errors::DocumentNotFound => e + raise Devops::Exception::RecordNotFound.new("JobTask '#{id}' does not exist") + end + + def report id + t = task(id) + tstatus = t.status + if tstatus == Worker::STATUS::IN_QUEUE + return "Task '#{id}' has been queued", false + end + file = t.file + raise Devops::Exception::RecordNotFound.new("JobTask '#{id}' file does not exist") unless File.exists? file + completed = (tstatus == "completed" or tstatus == "failed") + return Rack::Utils.escape_html(File.read(file).encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')), completed + end + + def status id + t = task(id) + t.status + end + + end + end + end +end + diff --git a/devops-service/app/api3/handlers/key.rb b/devops-service/app/api3/handlers/key.rb new file mode 100644 index 0000000..fd3ee82 --- /dev/null +++ b/devops-service/app/api3/handlers/key.rb @@ -0,0 +1,56 @@ +require "db/mongo/models/key" +require "fileutils" +require "app/api3/parsers/key" +require_relative "request_handler" + +module Devops + module API3 + module Handler + class Key < RequestHandler + + set_parser Devops::API3::Parser::KeyParser + + def keys + Devops::Model::Key.all + end + + def key id + Devops::Model::Key.find(id) + rescue Mongoid::Errors::DocumentNotFound + raise Devops::Exception::RecordNotFound.new("Key with id '#{id}' not found") + end + + def create(keys_dir) + body = parser.create + fname = body["file_name"] + file_name = File.join(keys_dir, fname) + raise Devops::Exception::ValidationError.new("File '#{fname}' already exist") if File.exists?(file_name) + File.open(file_name, "w") do |f| + f.write(body["content"]) + f.chmod(0400) + end + + key = Devops::Model::Key.create!({"path" => file_name, "id" => body["key_name"]}) + DevopsLogger.logger.info "Key '#{key.id}' has been created" + key + end + + def delete key_id + k = key(key_id) + servers = [] + Devops::Model::Server.where(key: k.id).each do |s| + servers << s.id + end + unless servers.empty? + s_str = servers.join(", ") + raise DependencyError.new "Deleting is forbidden: Key is used in servers: #{s_str}" + end + k.destroy + DevopsLogger.logger.info "Key '#{k.id}' has been deleted" + end + + end + end + end +end + diff --git a/devops-service/app/api3/handlers/project.rb b/devops-service/app/api3/handlers/project.rb new file mode 100644 index 0000000..2bdf327 --- /dev/null +++ b/devops-service/app/api3/handlers/project.rb @@ -0,0 +1,439 @@ +require "commands/status" +require "db/mongo/models/project" +require "db/mongo/models/user" +require "workers/project_test_worker" +require "app/api3/parsers/project" +require "lib/project/type/types_factory" +require "lib/executors/server_executor" + +require_relative "request_handler" + +module Devops + module API3 + module Handler + class Project < RequestHandler + + set_parser Devops::API3::Parser::ProjectParser + + include Devops::API3::Helpers + + extend StatusCommands + + def project_types + Devops::TypesFactory.types_names + end + + def projects + user = parser.current_user + query = {} + if user != Devops::Model::User::ROOT_USER_NAME + query['$or'] = [{owner: user}, {project_users: user}] + end + parser.archived_projects ? query["archived"] = true : query["archived"] = {"$exists" => false} + Devops::Model::Project.where(query) + #Devops::Db.connector.projects(nil, nil, parser.projects, parser.archived_projects) + end + + def project id + Devops::Model::Project.find(id) + rescue Mongoid::Errors::DocumentNotFound + raise Devops::Exception::RecordNotFound.new("Project '#{id}' not found") + end + + def project_environments(id) + project = project(id) + project.environments + end + + def get_project_with_environment id, env + #Devops::Model::Project.where({'environments.id' => env}).only('environments.$.id').find(id) ??? TODO: projection with array index + project = Devops::Model::Project.where({'environments.id' => env}).find(id) + project.environments = [ project.environments.detect{|de| de.id == env} ] + project + rescue Mongoid::Errors::DocumentNotFound + raise Devops::Exception::RecordNotFound.new("Project '#{id}' with deploy environment '#{env}' not found") + end + + def project_environment(id, env) + get_project_with_environment(id, env).environments[0] + end + + def project_env_servers id, env + project = get_project_with_environment(id, env) + user = parser.current_user + users = [ project.owner ] + project.project_users + Devop::Exception::AccessError.new("User can not read this project") unless users.include?(user) + Devops::Model::Server.where({'project' => id, 'environment' => env}).all + end + + def pvroject_servers id + project = project(id) + user = parser.current_user + users = [ project.owner ] + project.project_users + Devop::Exception::AccessError.new("User can not read this project") unless users.include?(user) + Devops::Model::Server.where({'project' => id}).all + end + + def add_project_users id + users = parser.users + #TODO: projection + dbusers = Devops::Model::User.where('_id.in' => users).map{|u| u.id} + invalid_users = users - dbusers + raise Devops::Exception::ValidationError.new("Invalid users: '#{invalid_users.join("', '")}'") unless invalid_users.empty? + add_to_project_array id, :project_users, users + end + + def delete_project_users id + pull_from_project_array id, :project_users, parser.users + end + + def set_project_description id + db_project = project(id) + db_project.description = parser.set_description + db_project.save! + end + + def add_to_project_array id, key, list_to_add + db_project = project(id) + set = Set.new(db_project.send(key)).merge(list_to_add) + db_project.add_to_set({key => list_to_add}) + set.to_a + end + + def pull_from_project_array id, key, list_to_pull + db_project = project(id) + set = Set.new(db_project.send(key)).subtract(list_to_pull) + db_project.pull_all({key => list_to_pull}) + set.to_a + end + + def set_project_run_list id + db_project = project(id) + db_project.run_list = parser.run_list + db_project.save! + db_project.run_list + end + + def add_project_run_list id + add_to_project_array id, :run_list, parser.run_list + end + + def delete_project_run_list id + pull_from_project_array id, :run_list, parser.run_list + end + + def add_project_env_users id, env + project = get_project_with_environment(id, env) + users = parser.users + dbusers = project.project_users + [project.owner] + invalid_users = users - dbusers + raise Devops::Exception::ValidationError.new("User(s) '#{invalid_users.join("', '")}' is/are not a project user(s)") unless invalid_users.empty? + Devops::Model::Project.where({'_id' => id, 'environments.id' => env}).add_to_set('environments.$.users' => users) + Set.new(project.environments[0].users + users).to_a + end + + def delete_project_env_users id, env + project = get_project_with_environment(id, env) + users = parser.users + Devops::Model::Project.where({'_id' => id, 'environments.id' => env}).pull_all('environments.$.users' => users) + project.environments[0].users - users + end + + def project_env_stacks id, env + # check if project exists + get_project_with_environment(id, env) + Devops::Model::StackBase.where({project: id, environment: env}).all + end + + def create_project + p = parser.create_project + + p.owner = parser.current_user + p.environments.each do |env| + env.add_users [parser.current_user] + end + p.save! + info = "Project '#{p.id}' has been created." + DevopsLogger.logger.info info + info + end + +=begin + def set_project_components id + body = parser.set_project_components + project = Devops::Db.connector.project(id) + project.components = body["components"] + project.validate_components + Devops::Db.connector.project_update_field id, "components", body["components"] + "Updated project '#{project.id}' with components '#{body["components"].inspect}'" + end +=end + + def add_environment id + db_project = project(id) + env = parser.add_environment + env.validate! + env.add_users [parser.current_user] + begin + db_env = db_project.environment(env.id) + raise Devops::Exception::ValidationError.new("Can not add new environment for project '#{id}'. Environment '#{env.id}' already exist") + rescue Devops::Exception::RecordNotFound => e + db_project.add_environment(env) + info = "Deploy environment '#{env.id}' has been added to project '#{id}'." + DevopsLogger.logger.info info + [info, env] + end + end + + def project_environment_categories project, env + project = get_project_with_environment(project, env) + penv = project.environments[0] + penv.categories + end + + def add_category id, env + project = get_project_with_environment(id, env) + penv = project.environments[0] + cat = parser.add_category + cat.validate! + db_cat = penv.get_category(cat.id) + if db_cat.nil? + category = project.add_category(penv, cat) + roles = category.create_role(id, env) + roles_response = Model::Category.present_created_roles(roles) + info = "New category '#{cat.id}' has been added to environment '#{env}' of project '#{id}'. " + info += roles_response + DevopsLogger.logger.info info + [info, cat] + else + raise Devops::Exception::ValidationError.new("Category '#{cat.id}' for project '#{id}' and deploy environment '#{env}' already exist") + end + end + + def show_category id, env, category + project = get_project_with_environment(id, env) + penv = project.environments[0] + cat = penv.categories.detect{|c| c.id == category} + raise Devops::Exception::RecordNotFound.new("Category '#{category}' for project '#{id}' and environment '#{env}' not found") if cat.nil? + cat + end + + def delete_category id, env, category + project = get_project_with_environment(id, env) + penv = project.environments[0] + project.delete_category(penv, category) + info = "Category '#{category}' has been removed from environment '#{env}' of project '#{id}'" + DevopsLogger.logger.info info + return info + end + + def update_environment_field id, environment, field + project = Devops::Db.connector.project(id) + db_env = project.environment(environment) + value = parser.update_environment_field + if db_env.respond_to?(field + "=") + if field == "id" + db_env.rename id, value + "Environment '#{environment}' has been renamed to '#{value}'" + else + db_env.update_field(id, field, value) + "Environment's field '#{field}' has been updated" + end + else + raise Devops::Exception::RecordNotFound.new("Field '#{field}' does not exist") + end + end + + def update_environment id, environment + project = Devops::Db.connector.project(id) + db_env = project.environment(environment) + env = parser.update_environment + env.id = environment if env.id.nil? + begin + unless env.id == environment + servers = Devops::Db.connector.servers_by_project_and_environment(id, environment) + raise InvalidRecord.new("Environment '#{environment}' can't be updated: it has #{servers.size} running servers.") unless servers.empty? + end + begin + project.environment(env.id) + raise InvalidRecord.new("Environment '#{environment}' can't be renamed to '#{env.id}', environment '#{env.id}' already exists") unless environment == env.id + rescue Devops::Exception::RecordNotFound => e + end + env.validate! + project.delete_environment(environment) + project.add_environment(env) + "Deploy environment '#{environment}' has been updated in project '#{project.id}'" + rescue Devops::Exception::RecordNotFound => e + env.id = environment + res = project.add_environment env + "Deploy environment '#{env.id}' has been added to project '#{project.id}'." + res + end + end + + def delete_environment id, environment + db_project = get_project_with_environment(id, environment) + servers = Devops::Model::Server.find({'project' => id, 'environment' => environment}) + raise Devops::Exception::DependencyError.new("Can not delete environment '#{environment}', there are #{servers.size} servers on it") + rescue Mongoid::Errors::DocumentNotFound + db_project.delete_environment(environment) + DevopsLogger.logger.info "Deploy environment '#{environment}' for project '#{id}' has been deleted" + end + + def update_project id + body = parser.update + db_project = project(id) + %w(description run_list project_users).each do |key| + db_project.send(key + "=", body[key]) if body.key? key + end + db_project.save! + DevopsLogger.logger.info "Project '#{id}' has been updated" + end + + def set_project_env_run_list id, environment + list = parser.run_list + project = Devops::Db.connector.project(id) + env = project.environment environment + Devops::Db.connector.set_project_env_run_list id, environment, list + "Updated environment '#{env.id}' with run_list '#{list.inspect}' in project '#{project.id}'" + end + + def delete_project id + db_project = project(id) + servers_cnt = Devops::Model::Server.where({'project' => id}).count + if servers_cnt != 0 + raise Devops::Exception::DependencyError.new "Deleting project #{id} is forbidden: Project has #{servers_cnt} server(s)" + else + db_project.delete + "Project '#{id}' has been deleted" + end + end + + def deploy_project_stream out, id + # check if project exist + project = Devops::Db.connector.project(id) + environment, servers = parser.deploy + keys = {} + dbserver = Devops::Db.connector.servers(id, environment, servers, true) + out << (dbservers.empty? ? "No reserved servers to deploy\n" : "Deploy servers: '#{dbservers.map{|s| s.name}.join("', '")}'\n") + status = [] + deploy_info_buf = {} + dbservers.each do |s| + begin + Devops::Db.connector.check_project_auth s.project, s.environment, parser.current_user + rescue InvalidPrivileges, Devops::Exception::RecordNotFound => e + out << e.message + "\n" + status.push 2 + next + end + environment_model = project.environment(s.environment) + deploy_info = if deploy_info_buf[s.environment] + deploy_info_buf[s.environment] + else + # мы не можем указать один build_number для всех окружений, поэтому nil + deploy_info_buf[s.environment] = project.deploy_info(environment_model, nil) + end + status.push(Devops::Executor::ServerExecutor.new(s, out, current_user: parser.current_user).deploy_server(deploy_info)) + end + status + end + + def deploy_project id + # check if project exist + project_model = Devops::Db.connector.project(id) + environment, servers = parser.deploy + files = [] + dbservers = Devops::Db.connector.servers(id, environment, servers, true) + #out << (dbservers.empty? ? "No reserved servers to deploy\n" : "Deploy servers: '#{dbservers.map{|s| s.name}.join("', '")}'\n") + deploy_info_buf = {} + dbservers.each do |s| + begin + Devops::Db.connector.check_project_auth s.project, s.environment, parser.current_user + rescue InvalidPrivileges, Devops::Exception::RecordNotFound => e + next + end + + environment_model = project_model.environment(s.environment) + deploy_info = if deploy_info_buf[s.environment] + deploy_info_buf[s.environment] + else + # мы не можем указать один build_number для всех окружений, поэтому nil + deploy_info_buf[s.environment] = project_model.deploy_info(environment_model, nil) + end + + jid = Worker.start_async(DeployWorker, + server_attrs: s.to_hash, + owner: parser.current_user, + tags: [], + deploy_info: deploy_info + ) + files.push jid + end + files + end + + def archive_project id + db_project = project(id) + Devops::Model::Project.where('_id' => id).set('archived' => true) + msg = "Project '#{id}' has been archived" + DevopsLogger.logger.info msg + msg + end + + def unarchive_project id + db_project = project(id) + Devops::Model::Project.where('_id' => id).unset('archived') + msg = "Project '#{id}' has been unarchived" + DevopsLogger.logger.info msg + msg + end + + def test_project id, environment + project = Devops::Db.connector.project(id) + env = project.environment environment + DevopsLogger.logger.info "Test project '#{project.id}' and environment '#{env.id}'" + if env.provider == ::Provider::Static::PROVIDER + msg = "Can not test environment with provider '#{::Provider::Static::PROVIDER}'" + Logger.warn msg + raise InvalidRecord.new(msg) + end + + jid = Worker.start_async(ProjectTestWorker, + project: project.id, + environment: env.id, + user: @request.env['REMOTE_USER'] + ) + + sleep 1 + return [jid] + end + + def delete_project_env_servers(project_id, env) + dry_run = parser.delete_project_env_servers + servers = project_env_servers project_id, env + info = {to_delete: servers.map(&:id)} + if !dry_run + info.merge!(delete_chosen_servers!(servers)) + end + info + end + + private + + def delete_chosen_servers!(servers) + deleted, failed = [], [] + servers.each do |server| + begin + Devops::Executor::ServerExecutor.new(server, '').delete_server + deleted << server.id + rescue + failed << server.id + end + end + {deleted: deleted, failed: failed} + end + + end + end + end +end + diff --git a/devops-service/app/api3/handlers/provider.rb b/devops-service/app/api3/handlers/provider.rb new file mode 100644 index 0000000..6a07064 --- /dev/null +++ b/devops-service/app/api3/handlers/provider.rb @@ -0,0 +1,86 @@ +require "app/api3/parsers/provider" +require_relative "request_handler" +require "commands/image" + +module Devops + module API3 + module Handler + class Provider < RequestHandler + + set_parser Devops::API3::Parser::ProviderParser + + extend ImageCommands + + def flavors provider, account + ::Provider.get_connector(provider, account).flavors + end + + def networks provider, account + p = ::Provider.get_connector(provider, account) + available_keys = ["vpc-id"] + p.networks_detail(@request.params.select{|k,v| available_keys.include?(k)}) + end + + def security_groups provider, account + available_keys = ["vpc-id"] + ::Provider.get_connector(provider, account).security_groups(@request.params.select{|k,v| available_keys.include?(k)}) + end + + def images provider, account + Provider.get_available_provider_images(provider, account) + end + + def available_providers + DevopsConfig.config[:providers] || DevopsConfig.config["providers"] || [] + end + + def providers + ::Provider.providers + end + + def accounts provider + provider_object = ::Provider.provider(provider) + provider_object.accounts + end + + def add_account provider + provider_object = ::Provider.provider(provider) + body = parser.account + body["provider"] = provider + account = provider_object.create_account(body) + account.save! + DevopsLogger.logger.info("Added #{provider} account '#{account.account_name}'") + provider_object.create_connector(DevopsConfig.config, account) + DevopsLogger.logger.info("Connector for provider '#{provider}' and account '#{account.account_name}' has been created") + account + rescue Mongoid::Errors::Validations => e + raise Devops::Exception::ValidationError.create_from_db_exception(e) + end + + def delete_account name, provider + provider_object = ::Provider.provider(provider) + account = provider_object.account(name) + provider_object.delete_connector(name) + DevopsLogger.logger.info("Connector for provider '#{provider}' and account '#{account.account_name}' has been removed") + + account_hash = account.to_hash + account.delete + DevopsLogger.logger.info("Removed #{provider} account '#{account.account_name}'") + account_hash + rescue Mongoid::Errors::DocumentNotFound + raise Devops::Exception::RecordNotFound.new("Account '#{name}' not found") + end + + def account_vpcs provider, name + provider_object = ::Provider.provider(provider) + account = provider_object.account(name) + p = ::Provider.get_connector(provider, name) + p.describe_vpcs + rescue Mongoid::Errors::DocumentNotFound + raise Devops::Exception::RecordNotFound.new("Account '#{name}' not found") + end + + end + end + end +end diff --git a/devops-service/app/api2/handlers/provider_notification.rb b/devops-service/app/api3/handlers/provider_notification.rb similarity index 61% rename from devops-service/app/api2/handlers/provider_notification.rb rename to devops-service/app/api3/handlers/provider_notification.rb index 27a786f..2675d7b 100644 --- a/devops-service/app/api2/handlers/provider_notification.rb +++ b/devops-service/app/api3/handlers/provider_notification.rb @@ -1,13 +1,16 @@ -require 'workers/stack_sync_worker' +require "lib/executors/server_executor" +require "app/api3/parsers/stack" +require 'db/mongo/models/stack/stack_factory' +require 'workers/stack_bootstrap_worker' require_relative "request_handler" module Devops - module API2_0 + module API3 module Handler - class ProviderNotification < RequestHandler + class Stack < RequestHandler def autoscaling_groups_change(group_id, provider_account) - provider = ::Provider::ProviderFactory.get('ec2', provider_account) + provider = ::Provider::ProviderFactory.get('aws', provider_account) stack_id = provider.stack_id_of_autoscaling_group(group_id) stack = ::Devops::Db.connector.stack_by_id(stack_id) jid = Worker.start_async(StackSyncWorker, stack_name: stack.name) @@ -19,4 +22,3 @@ module Devops end end end - diff --git a/devops-service/app/api3/handlers/request_handler.rb b/devops-service/app/api3/handlers/request_handler.rb new file mode 100644 index 0000000..1d8f13f --- /dev/null +++ b/devops-service/app/api3/handlers/request_handler.rb @@ -0,0 +1,90 @@ +require 'date' +require 'exceptions/parser_error' + +module Devops + module API3 + module Handler + class RequestHandler + + DATE_FORMAT = "%Y-%m-%d" + + class << self + def set_parser parser + define_method("parser") do + @request_parser ||= parser.new(@request) + end + end + end + + def initialize request + @request = request + end + + # supported options: + # sort_field: default 'created_at'. Could also be in [user, path, method, body, response_code, created_at]. + # sort_order: default 'asc'. Could also be 'desc' + def extract_sort_params_for_searching + filters = @request.params + sort = {} + sort[filters["sort_field"] || 'created_at'] = (filters["sort_order"] || 'asc') == 'asc' ? 1 : -1 + sort + end + + # supported options: + # date_from_l (format: unixtime) + # date_til_ll (format: unxitime) + # date_from (format: YYYY-MM-DD) + # date_till (format: YYYY-MM-DD) + def fill_date_params_for_searching search_query + filters = @request.params + from = begin + parse_date(filters["date_from"], filters["date_from_l"]) + rescue ArgumentError + raise Devops::Exception::ParserError.new("Invalid parameter 'date_from' or 'date_from_l'") + end + till = begin + parse_date(filters["date_till"], filters["date_till_l"]) + rescue ArgumentError + raise Devops::Exception::ParserError.new("Invalid parameter 'date_till' or 'date_till_l'") + end + from = {'$gte' => from.to_i} if from + till = {'$lt' => till.to_i} if till + if !from.nil? and !till.nil? + search_query["created_at"] = from.merge(till)#{'$and' => [from, till]} + elsif !from.nil? + search_query["created_at"] = from + elsif !till.nil? + search_query["created_at"] = till + end + end + + def extract_limit_params_for_searching _default=20 + filters = @request.params + unless filters["limit"].nil? + begin + buf = Integer(filters["limit"]) + return buf if buf >= 0 + rescue ArgumentError + end + end + _default + end + + private + def parse_date str, unix + if str + Date.strptime(str, DATE_FORMAT) + elsif unix + Integer(unix) + else + nil + end + end + + def owner_from_request + @request.env['REMOTE_USER'] + end + end + end + end +end diff --git a/devops-service/app/api3/handlers/roles.rb b/devops-service/app/api3/handlers/roles.rb new file mode 100644 index 0000000..f2abca7 --- /dev/null +++ b/devops-service/app/api3/handlers/roles.rb @@ -0,0 +1,60 @@ +require "db/mongo/models/role" +require "app/api3/parsers/roles" +require_relative "request_handler" + +module Devops + module API3 + module Handler + class Roles < RequestHandler + + set_parser Devops::API3::Parser::RolesParser + + def policies + Devops::Api3.policies.values + end + + def roles + Devops::Model::Role.all + end + + def role id + Devops::Model::Role.find(id) + rescue Mongoid::Errors::DocumentNotFound + raise Devops::Exception::RecordNotFound.new("Role with id '#{id}' not found") + end + + def create_role + Devops::Model::Role.create!(parser.create) + rescue Mongoid::Errors::Validations => e + raise Devops::Exception::ValidationError.create_from_db_exception(e) + end + + def add_policies role_id + list_to_add = parser.policies + db_role = role(role_id) + db_role.add_to_set({policies: list_to_add}) + end + + def delete_policies role_id + list_to_pull = parser.policies + db_role = role(role_id) + db_role.pull_all({policies: list_to_pull}) + end + + def delete_role id + role = role(id) + users = [] + Devops::Model::User.where(roles: id).each do |u| + users.push u.id + end + unless users.empty? + raise Devops::Exception::ConflictError.new("Role '#{role.name}' used with users '#{users.join("', '")}'") + end + role.delete + end + + end + end + end +end + diff --git a/devops-service/app/api3/handlers/script.rb b/devops-service/app/api3/handlers/script.rb new file mode 100644 index 0000000..adf4cd6 --- /dev/null +++ b/devops-service/app/api3/handlers/script.rb @@ -0,0 +1,83 @@ +require "fileutils" +require "commands/status" +require "app/api3/parsers/script" +require "lib/ssh/ssh_utils" +require_relative "request_handler" + +module Devops + module API3 + module Handler + class Script < RequestHandler + + set_parser Devops::API3::Parser::ScriptParser + + def scripts + res = [] + Dir.foreach(DevopsConfig.config[:scripts_dir]) {|f| res.push(f) unless f.start_with?(".")} + end + + def execute_command out, server_id + cmd = parse.body + s = begin + Devops::Model::Server.find(server_id) + rescue Mongoid::Errors::DocumentNotFound + raise Devops::Exception::RecordNotFound.new("Server '#{id}' not found") + end + Devops::Model::Project.check_user_authorization(s.project, s.environment, parser.current_user) + cert = begin + Devops::Model::Key.find(s.ssh_key) + rescue Mongoid::Errors::DocumentNotFound + raise Devops::Exception::RecordNotFound.new("SSH key '#{s.ssh_key}' not found") + end + Devops::SSH::Utils.run_command_out(cmd, s.public_ip || s.private_ip, s.remote_user, cert.path, out) + end + + def run_script out, script_name + nodes, script_params = parser.run_script + file = File.join(DevopsConfig.config[:scripts_dir], script_name) + unless File.exists?(file) + out << "File '#{script_name}' does not exist\n" + return + end + servers = ::Devops::Model::Server.where('id.in' => nodes) + if servers.empty? + out << "No servers found for ids '#{nodes.join("', '")}'\n" + return + end + user = parser.current_user + servers.each do |s| + Devops::Model::Project.check_user_authorization(s.project, s.environment, user) + end + status = [] + servers.each do |s| + cert = begin + Devops::Model::Key.find(s.ssh_key) + rescue Mongoid::Errors::DocumentNotFound + out.puts "No SSH key found for '#{s.id}'" + out.flush + status.push 2 + next + end + out.puts "\nRun script on '#{s.id}'" + status.push(Devops::SSH::Utils.run_script(file, s.public_ip || s.private_ip, s.remote_user, cert.path, out)) + end + status + end + + def create_script file_name + file = File.join(DevopsConfig.config[:scripts_dir], file_name) + raise Devops::Exception::ConflictError.new("File '#{file_name}' already exist") if File.exists?(file) + File.open(file, "w") {|f| f.write(parser.body)} + end + + def delete_script file_name + file = File.join(DevopsConfig.config[:scripts_dir], file_name) + raise Devops::Exception::RecordNotFound.new("File '#{file_name}' does not exist") unless File.exists?(file) + FileUtils.rm(file) + end + + end + end + end +end + diff --git a/devops-service/app/api3/handlers/server.rb b/devops-service/app/api3/handlers/server.rb new file mode 100644 index 0000000..f745bed --- /dev/null +++ b/devops-service/app/api3/handlers/server.rb @@ -0,0 +1,390 @@ +require "uri" + +require "commands/status" +require "commands/bootstrap_templates" +require 'exceptions/conflict_exception' +require "lib/executors/server_executor" + +require "db/mongo/models/server" + +require "workers/create_server_worker" +require "workers/delete_server_worker" +require "workers/bootstrap_worker" +require "workers/unbootstrap_worker" +require "app/api3/parsers/server" +require_relative "request_handler" + +module Devops + module API3 + module Handler + class Server < RequestHandler + + set_parser Devops::API3::Parser::ServerParser + + extend StatusCommands + extend BootstrapTemplatesCommands + + def servers + Devops::Model::Server.where(parser.servers).all + end + + def provider_servers provider + provider_servers_with_account provider, nil + end + + def provider_servers_with_account provider, account + ::Provider.get_connector(provider, account).servers + end + + def server id + Devops::Model::Server.find(id) + rescue Mongoid::Errors::DocumentNotFound + raise Devops::Exception::RecordNotFound.new("Server '#{id}' not found") + end + + # ids - array of servers ids + # returns array of jid + def delete ids + user = parser.current_user + jids = [] + ids.each do |id| + DevopsLogger.logger.debug("Trying to delete server '#{id}'") + begin + s = server(id) + Devops::Model::Project.check_user_authorization(s.project, s.environment, user) + jid = Worker.start_async(DeleteServerWorker, + server_id: s.id, + user: user + ) + jids << jid + rescue Devops::Exception::RecordNotFound, Devops::Exception::Unauthorized => e + DevopsLogger.logger.warn(e.message) + end + end + jids + end + + def server_id_by_name name + s = Devops::Model::Server.find_by(name: name) + s.id + rescue Mongoid::Errors::DocumentNotFound + raise Devops::Exception::RecordNotFound.new("Server with name '#{name}' not found") + end + + # ids - array of servers ids + def delete_stream ids, out + user = parser.current_user + ids.each do |id| + begin + s = server(id) + ### Authorization + Devops::Model::Project.check_user_authorization(s.project, s.environment, user) + Devops::Executor::ServerExecutor.new(s, out).delete_server + rescue Devops::Exception::RecordNotFound + out.puts "Server '#{id}' not found" + rescue Devops::Exception::Unauthorized + out.puts "User '#{user}' can not delete server '#{id}'" + end + end + end + + def create_server_stream out + status = [] + prepare_create_server do |project, env, user, body| + e = Devops::Executor::ServerExecutor.new(nil, out) + e.project = project + e.environment = env + body["created_by"] = user + res = e.create_server(body) + status.push res + end + status + end + + def create_server + body = parser.create + user = parser.current_user + + check_if_server_attrs_are_valid(body, user) + jid = Worker.start_async(CreateServerWorker, + server_attrs: body, + owner: user + ) + [jid] + end + + def deploy id + call_deploy(id) do |options| + [ Worker.start_async(DeployWorker, options) ] + end + end + + def call_stream out + yield + rescue Devops::Exception::RecordNotFound => e + out << e.message + NOT_FOUND + rescue Devops::Exception::ConflictError => e + out << e.message + CONFLICT + rescue Devops::Exception::Unauthorized => e + out << e.message + UNAUTHORIZED + end + + def deploy_stream id, out + call_stream(out) do + call_deploy(id) do |options| + Worker.start_sync(DeployWorker, options, out) + end + end + end + + def call_deploy id + body = parser.deploy + tags = body["tags"] || [] + run_list = body["run_list"] + owner = parser.current_user + s = server(id) + project = Devops::Model::Project.check_user_authorization(s.project, s.environment, owner) + deploy_info = create_deploy_info(s, project, body["build_number"]) + deploy_info["run_list"] = run_list if run_list + + yield({ + server_id: id, + owner: owner, + tags: tags, + deploy_info: deploy_info + }) + end + + def create_deploy_info server, project, build_number + env_model = project.environment(server.environment) + project.deploy_info(env_model, build_number) + end + + def pause_server id + s = Devops::Model::Server.find(id) + ## Authorization + Devops::Model::Project.check_user_authorization(s.project, s.environment, parser.current_user) + provider = s.provider_instance + r = provider.pause_server s + if r.nil? + set_last_operation_and_save(s, Devops::Model::Server::OperationType::PAUSE) + "Server with instance ID '#{s.id}' has been paused" + else + raise Devops::Exception::ConflictError.new("Server with instance ID '#{s.id}' can not be paused, It in state '#{r}'") + end + end + + def unpause_server id + s = Devops::Model::Server.find(id) + ## Authorization + Devops::Model::Project.check_user_authorization(s.project, s.environment, parser.current_user) + provider = s.provider_instance + r = provider.unpause_server s + if r.nil? + set_last_operation_and_save(s, Devops::Model::Server::OperationType::UNPAUSE) + "Server with instance ID '#{s.id}' has been unpaused" + else + raise Devops::Exception::ConflictError.new("Server with instance ID '#{s.id}' can not be unpaused, It in state '#{r}'") + end + end + + def reserve_server id + s = Devops::Model::Server.find(id) + user = parser.current_user + Devops::Model::Project.check_user_authorization(s.project, s.environment, user) + raise Devops::Exception::ConflictError.new("Server '#{id}' already reserved") unless s.reserved_by.nil? + s.reserved_by = user + set_last_operation_and_save(s, Devops::Model::Server::OperationType::RESERVE) + end + + def unreserve_server id + s = Devops::Model::Server.find(id) + user = parser.current_user + Devops::Model::Project.check_user_authorization(s.project, s.environment, user) + raise Devops::Exception::ConflictError.new("Server '#{id}' is not reserved") if s.reserved_by.nil? + s.reserved_by = nil + set_last_operation_and_save(s, Devops::Model::Server::OperationType::UNRESERVE) + end + + # TODO: check bootstrap template name + def bootstrap_server_stream id, out + call_stream(out) do + call_bootstrap(id) do |options| + Worker.start_sync(BootstrapWorker, options, out) + end + end + end + + def bootstrap_server(id) + call_bootstrap(id) do |options| + Worker.start_async(BootstrapWorker, options) + end + end + + def call_bootstrap id + body = parser.bootstrap + name = body["cm_name"] + rl = body["run_list"] + t = body["bootstrap_template"] + s = server(id) + + user = parser.current_user + p = Devops::Model::Project.check_user_authorization(s.project, s.environment, user) + raise Devops::Exception::ConflictError.new("Server '#{id}' already bootstrapped") unless s.cm_name.nil? + attrs = { + server_id: id, + owner: user + } + + d = p.environment(s.environment) + + provider = s.provider_instance + + if name + d.get_category(s.category).cm_tool.check_node_name(name) + attrs[:cm_name] = name + end + unless t.nil? + templates = get_templates + raise ValidationError.new("Invalid bootstrap template '#{t}', available values: #{templates.join(", ")}") unless templates.include?(t) + attrs[:bootstrap_template] = t + end + attrs[:run_list] = rl if rl + + yield(attrs) + end + + def unbootstrap_server id + call_unbootstrap(id) do |options| + Worker.start_async(UnbootstrapWorker, options) + end + end + + def unbootstrap_server_stream id, out + call_stream(out) do + call_unbootstrap(id) do |options| + Worker.start_sync(UnbootstrapWorker, options) + end + end + end + + def call_unbootstrap id + s = server(id) + user = parser.current_user + ### Authorization + Devops::Model::Project.check_user_authorization(s.project, s.environment, user) + raise Devops::Exception::ConflictError.new("Server '#{id}' is not bootstrapped") if s.cm_name.nil? + attrs = { + server_id: id, + owner: parser.current_user + } + yield(attrs) + end + + def add_server + body = parser.add_server + user = parser.current_user + project = Devops::Model::Project.check_user_authorization(body[:project], body[:environment], user) + env = project.environment(body[:environment]) + category = env.get_category!(body[:category]) + raise 'Provider should be static' if category.provider.name != 'static' + provider_account = Model::ProviderAccount.find(category.provider.account) + provider = ::Provider.provider('static') + Devops::Model::Server.create!( + "provider" => provider.name, + "provider_account" => provider_account.id, + "project" => project.id, + "environment" => env.id, + "category" => category.id, + "remote_user" => body[:remote_user], + "private_ip" => body[:private_ip], + "public_ip" => body[:public_ip], + "id" => "static_#{provider_account.ssh_key}-#{Time.now.to_i}", + "ssh_key" => provider_account.ssh_key, + 'created_by' => user, + 'cm_name' => nil, + 'name' => body[:name] + ) + end + + def set_tags id + tags = parser.tags + prepare_tags(id) do |provider| + provider.set_tags id, tags + end + end + + def unset_tags id + tags = parser.tags + prepare_tags(id) do |provider| + provider.unset_tags id, tags + end + end + + def prepare_tags id + s = Devops::Model::Server.find(id) + p = Devops::Model::Project.check_user_authorization(s.project, s.environment, parser.current_user) + provider = s.provider_instance + yield provider + end + + def set_run_list id + s = Devops::Model::Server.find(id) + Devops::Model::Project.check_user_authorization(s.project, s.environment, parser.current_user) + s.set(run_list: parser.run_list) + end + + private + + def check_if_server_attrs_are_valid(body, user) + if body["project"].nil? or body["environment"].nil? or body["category"].nil? + raise Devops::Exception::ValidationError.new("Properties 'project', 'environment' and 'category' are mandatory") + end + + key_name = body["ssh_key"] + unless key_name.nil? + begin + Devops::Model::Key.find(key_name) + rescue Mongoid::Errors::DocumentNotFound + raise Devops::Exception::ValidationError.new("Key with id '#{key_name}' not found") + end + end + + project = Devops::Model::Project.check_user_authorization(body["project"], body["environment"], user) + env = project.environment(body["environment"]) + category = env.get_category(body["category"]) + if body["name"] + begin + Devops::Model::Server.find_by(name: body["name"]) + raise Devops::Exception::ValidationError.new("Server with name '#{body["name"]}' already exists" ) + rescue Mongoid::Errors::DocumentNotFound + category.provider.provider_instance.check_node_name(body["name"]) + category.cm_tool.check_node_name(body["name"]) + end + end + +=begin + provider = env.provider_instance + server_name = body["name"] + check_chef_node_name(server_name, provider, KnifeFactory.instance) unless server_name.nil? + groups = body["groups"] + unless groups.nil? + buf = groups - provider.groups.keys + halt_response("Invalid security groups '#{buf.join("', '")}' for provider '#{provider.name}'") if buf.empty? + end +=end + end + + def set_last_operation_and_save(server, operation_type) + server.set_last_operation(operation_type, parser.current_user, nil) + server.save + end + + end + end + end +end + diff --git a/devops-service/app/api2/handlers/stack.rb b/devops-service/app/api3/handlers/stack.rb similarity index 76% rename from devops-service/app/api2/handlers/stack.rb rename to devops-service/app/api3/handlers/stack.rb index d22eae5..7472150 100644 --- a/devops-service/app/api2/handlers/stack.rb +++ b/devops-service/app/api3/handlers/stack.rb @@ -1,51 +1,53 @@ require "lib/executors/server_executor" +require "app/api3/parsers/stack" require 'db/mongo/models/stack/stack_factory' -require "app/api2/parsers/stack" require 'workers/stack_bootstrap_worker' require_relative "request_handler" module Devops - module API2_0 + module API3 module Handler class Stack < RequestHandler - set_parser Devops::API2_0::Parser::StackParser + set_parser Devops::API3::Parser::StackParser def stacks - Devops::Db.connector.stacks + Model::StackAws.all end + # We support only aws, actually def stacks_for_provider provider - Devops::Db.connector.stacks(provider: provider) + Model::StackAws.all end def create_stack - stack_attrs = parser.create - project = Devops::Db.connector.project(stack_attrs['project']) - env = project.deploy_env(stack_attrs['deploy_env']) - raise InvalidRecord.new("Environment '#{env.identifier}' of project '#{project.id}' has no stack template") if env.stack_template.nil? - add_stack_attributes(stack_attrs, env, parser) - - jid = Worker.start_async(StackBootstrapWorker, stack_attributes: stack_attrs) + object = parser.create + attrs = object['stack_attributes'] + env, category = fetch_env_and_category(attrs) + fill_attributes_from_category(attrs, env, category) + jid = Worker.start_async(StackBootstrapWorker, + stack_attributes: attrs, + without_bootstrap: object['without_bootstrap'], + skip_rollback: object['skip_rollback'] + ) [jid] end - def stack id - Devops::Db.connector.stack(id) + def stack name + Model::StackAws.where(name: name).first end def delete_stack name stack = self.stack(name) stack.delete_stack_in_cloud! - Devops::Db.connector.stack_servers_delete(stack.name) - Devops::Db.connector.stack_delete(stack.id) + Model::Server.where(stack: stack.id).delete_all + stack.delete end def sync id stack = self.stack(id) stack.sync_status_and_events! Devops::Db.connector.stack_update(stack) - # do not remove syncing status and events, just add stack sync worker here. stack end @@ -118,10 +120,10 @@ module Devops "created_by" => owner, "project" => s.project, "deploy_env" => s.deploy_env, - "type" => Report::DEPLOY_TYPE, + "type" => JobTask::DEPLOY_TYPE, "status" => Worker::STATUS::FAILED } - Devops::Db.connector.save_report(Report.new(o)) + JobTask.create(o) end files.push jid end @@ -199,11 +201,25 @@ module Devops private - def add_stack_attributes(stack_attrs, env, parser) - stack_attrs['stack_template'] = env.stack_template + # env is used in devops-nibr + def fill_attributes_from_category(stack_attrs, env, category) + stack_attrs['stack_template'] = category.provider.stack_template stack_attrs['owner'] = parser.current_user - stack_attrs['provider'] = env.provider - stack_attrs['provider_account'] = env.provider_account + stack_attrs['provider'] = category.provider.name + stack_attrs['provider_account'] = category.provider.account + end + + def fetch_env_and_category(attrs) + project = Model::Project.find_with_category(*attrs.values_at('project', 'environment', 'category')) + unless project + raise InvalidRecord.new("Project '#{attrs['project']}' has no env '#{attrs['environment']}' or category '#{attrs['category']}") + end + env = project.environment(attrs['environment']) + category = env.get_category!(attrs['category']) + if category.provider.type != Model::CategoryProvider::STACK_TYPE + raise InvalidRecord.new("Category '#{category.id}' type isn't 'stack'") + end + [env, category] end end diff --git a/devops-service/app/api3/handlers/stack_template.rb b/devops-service/app/api3/handlers/stack_template.rb new file mode 100644 index 0000000..67f2a2e --- /dev/null +++ b/devops-service/app/api3/handlers/stack_template.rb @@ -0,0 +1,65 @@ +require 'db/mongo/models/stack_template/stack_template_factory' +require 'app/api3/parsers/stack_template' +require_relative "request_handler" +require 'exceptions/conflict_exception' + +module Devops + module API3 + module Handler + class StackTemplate < RequestHandler + + set_parser Devops::API3::Parser::StackTemplateParser + + def stack_templates + # we just need to build json from attributes hash, so there is no need + # to build appropriate (not Base) class + Model::StackTemplateBase.all + end + + def stack_templates_for_provider provider + Model::StackTemplateBase.where(provider: provider) + end + + def create_stack_template provider + template_model = Model::StackTemplateFactory.create(provider, parser.create) + template_model.owner = parser.current_user + template_model.save + template_model + end + + def get_stack_template id + Model::StackTemplateBase.find(id) + end + + def delete_stack_template id + envs_with_this_template = envs_using_stack_template(id) + + if envs_with_this_template.empty? + Devops::Model::StackTemplateBase.find(id).delete + else + raise Exception::ConflictError.new("Stack template '#{id}' is already in use in #{envs_with_this_template.map{|project, envs| "#{project}: #{envs.join(', ')}"}.join('; ')}") + end + end + + private + + # returns: + # { "project" => ["environment"] } + def envs_using_stack_template(stack_template_id) + Devops::Model::Project.all.inject({}) do |usages, project| + envs_with_this_template = project.environments.select do |env| + stack_categories = env.categories.select {|t| t.provider.type == Model::CategoryProvider::STACK_TYPE} + used_templates = stack_categories.map {|c| c.provider.stack_template} + used_templates.include?(stack_template_id) + end + next usages if envs_with_this_template.empty? + + usages[project.id] = envs_with_this_template.map(&:id) + usages + end + end + + end + end + end +end diff --git a/devops-service/app/api3/handlers/statistic.rb b/devops-service/app/api3/handlers/statistic.rb new file mode 100644 index 0000000..c341813 --- /dev/null +++ b/devops-service/app/api3/handlers/statistic.rb @@ -0,0 +1,42 @@ +require 'db/mongo/models/statistic' +require_relative "request_handler" + +module Devops + module API3 + module Handler + class Statistic < RequestHandler + + # supported options: + # user + # response_code (format: 200) + # method + # path + # path_contains + # date_from (format: timestamp) + # date_till (format: timestamp) + # limit: default 20. Zero value is equal to no limit. + # sort_field: default 'created_at'. Could also be in [user, path, method, body, response_code, created_at]. + # sort_order: default 'asc'. Could also be 'desc' + def statistic + filters = @request.params + limit =extract_limit_params_for_searching(20) + sort = extract_sort_params_for_searching + query = {} + %w(user method path).each do |key| + val = filters[key] + query[key] = val if val + end + + query["response_code"] = filters["response_code"].to_i if filters["response_code"] + query["path"] = Regexp.new(filters["path_contains"], 'i') if filters["path_contains"] + + fill_date_params_for_searching(query) + + Devops::Model::Statistic.where(query).sort(sort).limit(limit) + end + + end + end + end +end + diff --git a/devops-service/app/api3/handlers/user.rb b/devops-service/app/api3/handlers/user.rb new file mode 100644 index 0000000..6043510 --- /dev/null +++ b/devops-service/app/api3/handlers/user.rb @@ -0,0 +1,68 @@ +require "db/mongo/models/user" +require "app/api3/parsers/user" +require_relative "request_handler" + +module Devops + module API3 + module Handler + class User < RequestHandler + + set_parser Devops::API3::Parser::UserParser + + def users + Devops::Model::User.all + end + + def create + Devops::Model::User.create! parser.create + end + + def get_user id + Devops::Model::User.find(id) + rescue Mongoid::Errors::DocumentNotFound + raise Devops::Exception::RecordNotFound.new("User with id '#{id}' not found") + end + + def delete user_id + user = get_user(user_id) + projects = Devops::Model::Project.find({'environments.users' => user_id}) + str = "" + projects.each do |p| + p.environments.each do |e| + str+="#{p.id}.#{e.id} " if e.users.include? user_id + end + end + raise Devops::Exception::ConflictError.new "Deleting is forbidden: User is included in #{str}" + rescue Mongoid::Errors::DocumentNotFound + user.delete + end + + def add_roles id + user = get_user(id) + user.add_to_set({roles: parser.roles}) + end + + def delete_roles id + user = get_user(id) + user.pull_all({roles: parser.roles}) + end + + def change user_id, action + user = get_user(user_id) + user.send action + "=", parser.change(action) + user.save! + end + + def update_user id + user = get_user(id) + obj = parser.update_user + %w(email password roles).each do |key| + user.send(key + "=", obj[key]) if obj.key?(key) + end + user.save! + end + end + end + end +end + diff --git a/devops-service/app/api2/helpers/parser.rb b/devops-service/app/api3/helpers/parser.rb similarity index 68% rename from devops-service/app/api2/helpers/parser.rb rename to devops-service/app/api3/helpers/parser.rb index dc5b4d3..9811f26 100644 --- a/devops-service/app/api2/helpers/parser.rb +++ b/devops-service/app/api3/helpers/parser.rb @@ -3,7 +3,7 @@ require "exceptions/parser_error" require "exceptions/validation_error" module Devops - module API2_0 + module API3 module ParserHelpers def create_object_from_json_body type=Hash, empty_body=false @@ -13,15 +13,15 @@ module Devops ::JSON.parse(json) rescue ::JSON::ParserError => e DevopsLogger.logger.error e.message - raise Devops::ParserError.new("Invalid JSON: #{e.message}") + raise Devops::Exception::ParserError.new("Invalid JSON: #{e.message}") end - raise Devops::ParserError.new("Invalid JSON, it should be an #{type == Array ? "array" : "object"}") unless @body_json.is_a?(type) + raise Devops::Exception::ParserError.new("Invalid JSON, it should be an #{type == Array ? "array" : "object"}") unless @body_json.is_a?(type) @body_json end def check_provider provider - list = ::Provider::ProviderFactory.providers - raise Devops::ValidationError.new("Invalid provider '#{provider}', available providers: '#{list.join("', '")}'") unless list.include?(provider) + list = ::Provider.providers + raise Devops::Exception::ValidationError.new("Invalid provider '#{provider}', available providers: '#{list.join("', '")}'") unless list.include?(provider) end def check_string val, msg, _nil=false, empty=false @@ -38,7 +38,7 @@ module Devops def check_array val, msg, vals_type=String, _nil=false, empty=false check_param val, Array, msg, _nil, empty - val.each {|v| raise Devops::ValidationError.new(msg) unless v.is_a?(vals_type)} unless val.nil? + val.each {|v| raise Devops::Exception::ValidationError.new(msg) unless v.is_a?(vals_type)} unless val.nil? val end @@ -47,7 +47,7 @@ module Devops r = Regexp.new("^[\\w _\\-.]{1,255}$", Regexp::IGNORECASE) if r.match(file_name).nil? msg = "Invalid file name '#{file_name}'. Expected name with 'a'-'z', '0'-'9', ' ', '_', '-', '.' symbols with length greate then 0 and less then 256 " - raise Devops::ValidationError.new(msg, json_resp) + raise Devops::Exception::ValidationError.new(msg, json_resp) =begin if json_resp halt_response(msg) @@ -64,14 +64,14 @@ module Devops if _nil return val else - raise Devops::ValidationError.new(msg) + raise Devops::Exception::ParserError.new(msg) end end if val.is_a?(type) - raise Devops::ValidationError.new(msg) if !empty && val.empty? + raise Devops::Exception::ParserError.new(msg) if !empty && val.empty? val else - raise Devops::ValidationError.new(msg) + raise Devops::Exception::ParserError.new(msg) end end diff --git a/devops-service/app/api2/helpers/version_2.rb b/devops-service/app/api3/helpers/query.rb similarity index 60% rename from devops-service/app/api2/helpers/version_2.rb rename to devops-service/app/api3/helpers/query.rb index 2e4ce94..1829a11 100644 --- a/devops-service/app/api2/helpers/version_2.rb +++ b/devops-service/app/api3/helpers/query.rb @@ -1,11 +1,10 @@ require "json" require 'sinatra/base' require "sinatra/json" - -require "providers/provider_factory" +require "db/mongo/models/statistic" module Devops - module API2_0 + module API3 module Helpers include Sinatra::JSON @@ -23,19 +22,25 @@ module Devops halt(rstatus, json(obj)) end - def check_privileges cmd, p - user = request.env['USER'] - user.check_privileges(cmd, p) + def check_provider provider + list = ::Provider.providers + halt_response("Invalid provider '#{provider}', available providers: '#{list.join("', '")}'", 400) unless list.include?(provider) end - def check_provider provider - list = ::Provider::ProviderFactory.providers - halt_response("Invalid provider '#{provider}', available providers: '#{list.join("', '")}'", 400) unless list.include?(provider) + def check_provider_account provider, account + halt_response("Provider acount '#{account}' not found", 400) if ::Provider.get_connector(provider, account).nil? + end + + def check_policy policy + return unless policy + user = request.env['USER'] + user.check_policy(policy) end # Save information about requests with methods POST, PUT, DELETE def insert_statistic msg=nil unless request.get? + return if response.status == 401 and request.env['REMOTE_USER'].nil? # parse body request.body.rewind raw_body = request.body.read @@ -45,8 +50,13 @@ module Devops raw_body end - # store statistic - settings.mongo.insert_statistic request.env['REMOTE_USER'], request.path, request.request_method, body, response.status + Devops::Model::Statistic.create({ + user: request.env['REMOTE_USER'], + path: request.path, + method: request.request_method, + body: body, + response_code: response.status + }) end end diff --git a/devops-service/app/api2/parsers/tag.rb b/devops-service/app/api3/parsers/chef.rb similarity index 82% rename from devops-service/app/api2/parsers/tag.rb rename to devops-service/app/api3/parsers/chef.rb index 2ed6d03..4b04195 100644 --- a/devops-service/app/api2/parsers/tag.rb +++ b/devops-service/app/api3/parsers/chef.rb @@ -1,9 +1,9 @@ require_relative "request_parser" module Devops - module API2_0 + module API3 module Parser - class TagParser < RequestParser + class ChefParser < RequestParser def tags tags = create_object_from_json_body(Array) @@ -14,4 +14,3 @@ module Devops end end end - diff --git a/devops-service/app/api2/parsers/bootstrap_template.rb b/devops-service/app/api3/parsers/deploy.rb similarity index 58% rename from devops-service/app/api2/parsers/bootstrap_template.rb rename to devops-service/app/api3/parsers/deploy.rb index 78ef743..f6a912f 100644 --- a/devops-service/app/api2/parsers/bootstrap_template.rb +++ b/devops-service/app/api3/parsers/deploy.rb @@ -1,9 +1,9 @@ require_relative "request_parser" module Devops - module API2_0 + module API3 module Parser - class BootstrapTemplateParser < RequestParser + class DeployParser < RequestParser end end diff --git a/devops-service/app/api2/parsers/filter.rb b/devops-service/app/api3/parsers/filter.rb similarity index 95% rename from devops-service/app/api2/parsers/filter.rb rename to devops-service/app/api3/parsers/filter.rb index 5752dab..3f1251c 100644 --- a/devops-service/app/api2/parsers/filter.rb +++ b/devops-service/app/api3/parsers/filter.rb @@ -1,7 +1,7 @@ require_relative "request_parser" module Devops - module API2_0 + module API3 module Parser class FilterParser < RequestParser diff --git a/devops-service/app/api2/parsers/image.rb b/devops-service/app/api3/parsers/image.rb similarity index 87% rename from devops-service/app/api2/parsers/image.rb rename to devops-service/app/api3/parsers/image.rb index 4e5c348..1238204 100644 --- a/devops-service/app/api2/parsers/image.rb +++ b/devops-service/app/api3/parsers/image.rb @@ -1,13 +1,12 @@ require_relative "request_parser" module Devops - module API2_0 + module API3 module Parser class ImageParser < RequestParser def images provider = @params["provider"] - puts "Provider: #{provider}" check_provider(provider) if provider provider end diff --git a/devops-service/app/api2/parsers/key.rb b/devops-service/app/api3/parsers/key.rb similarity index 97% rename from devops-service/app/api2/parsers/key.rb rename to devops-service/app/api3/parsers/key.rb index fcf1db2..3d58514 100644 --- a/devops-service/app/api2/parsers/key.rb +++ b/devops-service/app/api3/parsers/key.rb @@ -1,7 +1,7 @@ require_relative "request_parser" module Devops - module API2_0 + module API3 module Parser class KeyParser < RequestParser diff --git a/devops-service/app/api2/parsers/project.rb b/devops-service/app/api3/parsers/project.rb similarity index 61% rename from devops-service/app/api2/parsers/project.rb rename to devops-service/app/api3/parsers/project.rb index 5dbd78f..0b877e5 100644 --- a/devops-service/app/api2/parsers/project.rb +++ b/devops-service/app/api3/parsers/project.rb @@ -1,17 +1,16 @@ -require "app/api2/helpers/version_2" require_relative "request_parser" module Devops - module API2_0 + module API3 module Parser class ProjectParser < RequestParser - include Devops::API2_0::Helpers + include Devops::API3::Helpers def projects fields = [] if @params.key?("fields") and @params["fields"].is_a?(Array) - Devops::Model::Project.fields.each do |k| + Devops::Model::Project.list_fields.each do |k| fields.push k if @params["fields"].include?(k) end end @@ -23,20 +22,27 @@ module Devops end def project_servers - @params["deploy_env"] + @params["environment"] end def project_stacks - @params["deploy_env"] + @params["environment"] end def create_project body = create_object_from_json_body - check_string(body["name"], "Parameter 'name' must be a not empty string") - check_array(body["deploy_envs"], "Parameter 'deploy_envs' must be a not empty array of objects", Hash) - rl = check_array(body["run_list"], "Parameter 'run_list' should be an array of string", String, true, true) - Validators::Helpers::RunList.new(rl).validate! unless rl.nil? - Devops::Model::Project.new(body) + project = Devops::Model::Project.new(body) + #project.validate! + project + end + + def users + create_object_from_json_body(Array) + end + + def set_description + body = create_object_from_json_body + body["description"] end def set_project_components @@ -45,64 +51,70 @@ module Devops c end - def add_deploy_env + def add_environment body = create_object_from_json_body - Devops::Model::DeployEnvFactory.create(body) + Devops::Model::Environment.new(body) end - def update_deploy_env_field + def update_environment_field body = create_object_from_json_body raise InvalidRecord.new("'value' key not found") if body["value"].nil? body["value"] end - def update_deploy_env + def update_environment body = create_object_from_json_body # rl = check_array(body["run_list"], "Parameter 'run_list' should be an array of string", String, false, true) # Validators::Helpers::RunList.new(rl).validate! - Devops::Model::DeployEnvFactory.create(body) + Devops::Model::EnvironmentFactory.create(body) end def update body = create_object_from_json_body +=begin check_string(body["description"], "Parameter 'description' must be a string", true, true) rl = check_array(body["run_list"], "Parameter 'run_list' must be an array of string", String, true, true) Validators::Helpers::RunList.new(rl).validate! unless rl.nil? #Devops::Model::Project.new(body) +=end body end + def add_category + Devops::Model::Category.new(create_object_from_json_body) + end + def delete body = create_object_from_json_body(Hash, true) - deploy_env = unless body.nil? - check_string(body["deploy_env"], "Parameter 'deploy_env' should be a not empty string", true) + environment = unless body.nil? + check_string(body["environment"], "Parameter 'environment' should be a not empty string", true) end end def project_users body = create_object_from_json_body users = check_array(body["users"], "Parameter 'users' must be a not empty array of strings") - deploy_env = check_string(body["deploy_env"], "Parameter 'deploy_env' must be a not empty string", true) - return deploy_env, users + environment = check_string(body["environment"], "Parameter 'environment' must be a not empty string", true) + return environment, users end def run_list list = create_object_from_json_body(Array) - check_array(list, "Body must contains not empty array of strings") + RunListArrayValidator.validate_list(list) + list end def deploy obj = create_object_from_json_body - deploy_env = check_string(obj["deploy_env"], "Parameter 'deploy_env' should be a not empty string", true) + environment = check_string(obj["environment"], "Parameter 'environment' should be a not empty string", true) servers = check_array(obj["servers"], "Parameter 'servers' should be a not empty array of strings", String, true) - return deploy_env, servers + return environment, servers end - def delete_project_servers + def delete_project_env_servers body = create_object_from_json_body dry_run = check_boolean(body["dry_run"], "Parameter 'dry_run' must be a boolean") - deploy_env = check_string(body["deploy_env"], "Parameter 'deploy_env' must be a not empty string", true) - [deploy_env, dry_run] + dry_run end end end diff --git a/devops-service/app/api2/parsers/provider.rb b/devops-service/app/api3/parsers/provider.rb similarity index 92% rename from devops-service/app/api2/parsers/provider.rb rename to devops-service/app/api3/parsers/provider.rb index 38d3670..1638b6f 100644 --- a/devops-service/app/api2/parsers/provider.rb +++ b/devops-service/app/api3/parsers/provider.rb @@ -1,7 +1,7 @@ require_relative "request_parser" module Devops - module API2_0 + module API3 module Parser class ProviderParser < RequestParser diff --git a/devops-service/app/api2/parsers/request_parser.rb b/devops-service/app/api3/parsers/request_parser.rb similarity index 79% rename from devops-service/app/api2/parsers/request_parser.rb rename to devops-service/app/api3/parsers/request_parser.rb index 621a69f..046b749 100644 --- a/devops-service/app/api2/parsers/request_parser.rb +++ b/devops-service/app/api3/parsers/request_parser.rb @@ -1,11 +1,11 @@ -require "app/api2/helpers/parser" +require "app/api3/helpers/parser" module Devops - module API2_0 + module API3 module Parser class RequestParser - include Devops::API2_0::ParserHelpers + include Devops::API3::ParserHelpers def initialize request @request = request diff --git a/devops-service/app/api3/parsers/roles.rb b/devops-service/app/api3/parsers/roles.rb new file mode 100644 index 0000000..89adcf1 --- /dev/null +++ b/devops-service/app/api3/parsers/roles.rb @@ -0,0 +1,22 @@ +require_relative "request_parser" + +module Devops + module API3 + module Parser + class RolesParser < RequestParser + + def create + create_object_from_json_body + end + + def policies + policies = create_object_from_json_body(Array) + res = Role.check_policies(policies) + raise Devops::Exception::ValidationError.new(res) unless res.nil? + policies + end + end + end + end +end + diff --git a/devops-service/app/api2/parsers/script.rb b/devops-service/app/api3/parsers/script.rb similarity index 77% rename from devops-service/app/api2/parsers/script.rb rename to devops-service/app/api3/parsers/script.rb index a495f02..e610f3a 100644 --- a/devops-service/app/api2/parsers/script.rb +++ b/devops-service/app/api3/parsers/script.rb @@ -1,12 +1,12 @@ require_relative "request_parser" module Devops - module API2_0 + module API3 module Parser class ScriptParser < RequestParser - def execute_command - @request.body.read + def body + @body ||= @request.body.read end def run_script @@ -16,10 +16,6 @@ module Devops return nodes, p end - def create_script - @request.body.read - end - end end end diff --git a/devops-service/app/api2/parsers/security_groups.rb b/devops-service/app/api3/parsers/security_groups.rb similarity index 100% rename from devops-service/app/api2/parsers/security_groups.rb rename to devops-service/app/api3/parsers/security_groups.rb diff --git a/devops-service/app/api2/parsers/server.rb b/devops-service/app/api3/parsers/server.rb similarity index 72% rename from devops-service/app/api2/parsers/server.rb rename to devops-service/app/api3/parsers/server.rb index 6675554..ee1ce87 100644 --- a/devops-service/app/api2/parsers/server.rb +++ b/devops-service/app/api3/parsers/server.rb @@ -1,34 +1,38 @@ require_relative "request_parser" module Devops - module API2_0 + module API3 module Parser class ServerParser < RequestParser def servers - (@params.key?("reserved") ? true : nil) + q = {} + if @params.key?("reserved") + q[:reserved] = true if @params["reserved"] == "true" + end + if @params.key?("provider") + q[:provider] = @params["provider"] + end + q end def server @params[:key] || @params["key"] end - def instance_key - @body ||= create_object_from_json_body(Hash, true) - (@body.nil? ? nil : @body["key"]) - end - + # TODO: skip_rollback flag def create @body ||= create_object_from_json_body project_name = check_string(@body["project"], "Parameter 'project' must be a not empty string") - env_name = check_string(@body["deploy_env"], "Parameter 'deploy_env' must be a not empty string") + env_name = check_string(@body["environment"], "Parameter 'environment' must be a not empty string") + category = check_string(@body["category"], "Parameter 'category' must be a not empty string") server_name = check_string(@body["name"], "Parameter 'name' should be null or not empty string", true) without_bootstrap = @body["without_bootstrap"] force = @body["force"] raise InvalidRecord.new("Parameter 'without_bootstrap' should be a null or true") unless without_bootstrap.nil? or without_bootstrap == true raise InvalidRecord.new("Parameter 'force' should be a null or true") unless force.nil? or force == true groups = check_array(@body["groups"], "Parameter 'groups' should be null or not empty array of string", String, true) - key_name = check_string(@body["key"], "Parameter 'key' should be null or not empty string", true) + key_name = check_string(@body["ssh_key"], "Parameter 'key' should be null or not empty string", true) rl = check_array(@body["run_list"], "Parameter 'run_list' should be a not empty array of string", String, true, true) Validators::Helpers::RunList.new(rl).validate! unless rl.nil? @body @@ -36,34 +40,49 @@ module Devops def bootstrap @body ||= create_object_from_json_body(Hash, true) - id = check_string(@body["instance_id"], "Parameter 'instance_id' must be a not empty string") - name = check_string(@body["name"], "Parameter 'name' should be a not empty string", true) + check_string(@body["cm_name"], "Parameter 'cm_name' should be a not empty string", true) rl = check_array(@body["run_list"], "Parameter 'run_list' should be a not empty array of string", String, true) Validators::Helpers::RunList.new(rl).validate! unless rl.nil? t = check_string(@body["bootstrap_template"], "Parameter 'bootstrap_template' should be a not empty string", true) @body end + def deploy + r = create_object_from_json_body + tags = check_array(r["tags"], "Parameter 'tags' should be an array of strings", String, true) + build_number = check_string(r["build_number"], "Parameter 'build_number' should be a not empty string", true) + rl = check_array(r["run_list"], "Parameter 'run_list' should be an array of string", String, true) + Validators::Helpers::RunList.new(rl).validate! unless rl.nil? + r + end + + def delete_list + body = create_object_from_json_body + check_array(body["servers_ids"], "Parameter 'servers_ids' must be not empty array of string", String) + end + def add_server @body ||= create_object_from_json_body project = check_string(@body["project"], "Parameter 'project' must be a not empty string") - deploy_env = check_string(@body["deploy_env"], "Parameter 'deploy_env' must be a not empty string") - key = check_string(@body["key"], "Parameter 'key' must be a not empty string") + env = check_string(@body["environment"], "Parameter 'environment' must be a not empty string") + category = check_string(@body["category"], "Parameter 'category' must be a not empty string") remote_user = check_string(@body["remote_user"], "Parameter 'remote_user' must be a not empty string") private_ip = check_string(@body["private_ip"], "Parameter 'private_ip' must be a not empty string") public_ip = check_string(@body["public_ip"], "Parameter 'public_ip' should be a not empty string", true) rl = check_array(@body["run_list"], "Parameter 'run_list' should be a not empty array of string", String, true, true) Validators::Helpers::RunList.new(rl).validate! unless rl.nil? + name = check_string(@body["name"], "Parameter 'name' should be a not empty string") server_attrs = { project: project, - deploy_env: deploy_env, - key: key, + environment: env, + category: category, + name: name, remote_user: remote_user, private_ip: private_ip, public_ip: public_ip, - run_list: rl + run_list: rl, + name: name } - [project, deploy_env, server_attrs] end def add_and_bootstrap_servers @@ -88,6 +107,7 @@ module Devops run_list: rl } end + servers_attrs[:public_ip] = @body['public_ip'] if @body['public_ip'] [@body, servers_attrs] end diff --git a/devops-service/app/api2/parsers/stack.rb b/devops-service/app/api3/parsers/stack.rb similarity index 63% rename from devops-service/app/api2/parsers/stack.rb rename to devops-service/app/api3/parsers/stack.rb index ef1df79..7062bb7 100644 --- a/devops-service/app/api2/parsers/stack.rb +++ b/devops-service/app/api3/parsers/stack.rb @@ -1,20 +1,19 @@ require_relative "request_parser" module Devops - module API2_0 + module API3 module Parser class StackParser < RequestParser def create @body ||= create_object_from_json_body - project_name = check_string(@body["project"], "Parameter 'project' must be a not empty string") - env_name = check_string(@body["deploy_env"], "Parameter 'deploy_env' must be a not empty string") - check_string(@body["name"], "Parameter 'name' must be a not empty string", true, false) - list = check_array(@body["run_list"], "Parameter 'run_list' is invalid, it should be not empty array of strings", String, true, true) - check_param(@body['launch_options'], Hash, "Parameter 'launch_options' should be a hash with 'without_bootstrap' and 'skip_rollback' keys") - if !@body['launch_options'].key?('without_bootstrap') || !@body['launch_options'].key?('skip_rollback') - raise Devops::ValidationError.new("Parameter 'launch_options' should contain 'without_bootstrap' and 'skip_rollback' keys") - end + + stack_attributes = @body.fetch('stack_attributes') + check_string(stack_attributes['project'], "Parameter 'project' must be a not empty string") + check_string(stack_attributes['environment'], "Parameter 'environment' must be a not empty string") + check_string(stack_attributes['category'], "Parameter 'category' must be a not empty string") + check_string(stack_attributes['name'], "Parameter 'name' must be a not empty string", true, false) + list = check_array(stack_attributes['run_list'], "Parameter 'run_list' is invalid, it should be not empty array of strings", String, true, true) Validators::Helpers::RunList.new(list).validate! unless list.nil? @body end diff --git a/devops-service/app/api2/parsers/stack_template.rb b/devops-service/app/api3/parsers/stack_template.rb similarity index 93% rename from devops-service/app/api2/parsers/stack_template.rb rename to devops-service/app/api3/parsers/stack_template.rb index 3817adf..6be61d9 100644 --- a/devops-service/app/api2/parsers/stack_template.rb +++ b/devops-service/app/api3/parsers/stack_template.rb @@ -1,7 +1,7 @@ require_relative "request_parser" module Devops - module API2_0 + module API3 module Parser class StackTemplateParser < RequestParser diff --git a/devops-service/app/api3/parsers/user.rb b/devops-service/app/api3/parsers/user.rb new file mode 100644 index 0000000..98b5a3f --- /dev/null +++ b/devops-service/app/api3/parsers/user.rb @@ -0,0 +1,36 @@ +require_relative "request_parser" + +module Devops + module API3 + module Parser + class UserParser < RequestParser + + def create + create_object_from_json_body + end + + def update_user + create_object_from_json_body + end + + def roles + roles = create_object_from_json_body(Array) + check_array(roles, "Body must be a not empty array of strings") + roles + end + + def change_password + body = create_object_from_json_body + check_string(body["password"], "Parameter 'password' must be a not empty string") + end + + def change action + body = create_object_from_json_body + check_string(body[action], "Parameter '#{action}' must be a not empty string") + end + + end + end + end +end + diff --git a/devops-service/app/api3/public/swagger-ui/css/print.css b/devops-service/app/api3/public/swagger-ui/css/print.css new file mode 100644 index 0000000..2e6b310 --- /dev/null +++ b/devops-service/app/api3/public/swagger-ui/css/print.css @@ -0,0 +1,1187 @@ +/* Original style from softwaremaniacs.org (c) Ivan Sagalaev */ +.swagger-section pre code { + display: block; + padding: 0.5em; + background: #F0F0F0; +} +.swagger-section pre code, +.swagger-section pre .subst, +.swagger-section pre .tag .title, +.swagger-section pre .lisp .title, +.swagger-section pre .clojure .built_in, +.swagger-section pre .nginx .title { + color: black; +} +.swagger-section pre .string, +.swagger-section pre .title, +.swagger-section pre .constant, +.swagger-section pre .parent, +.swagger-section pre .tag .value, +.swagger-section pre .rules .value, +.swagger-section pre .rules .value .number, +.swagger-section pre .preprocessor, +.swagger-section pre .ruby .symbol, +.swagger-section pre .ruby .symbol .string, +.swagger-section pre .aggregate, +.swagger-section pre .template_tag, +.swagger-section pre .django .variable, +.swagger-section pre .smalltalk .class, +.swagger-section pre .addition, +.swagger-section pre .flow, +.swagger-section pre .stream, +.swagger-section pre .bash .variable, +.swagger-section pre .apache .tag, +.swagger-section pre .apache .cbracket, +.swagger-section pre .tex .command, +.swagger-section pre .tex .special, +.swagger-section pre .erlang_repl .function_or_atom, +.swagger-section pre .markdown .header { + color: #800; +} +.swagger-section pre .comment, +.swagger-section pre .annotation, +.swagger-section pre .template_comment, +.swagger-section pre .diff .header, +.swagger-section pre .chunk, +.swagger-section pre .markdown .blockquote { + color: #888; +} +.swagger-section pre .number, +.swagger-section pre .date, +.swagger-section pre .regexp, +.swagger-section pre .literal, +.swagger-section pre .smalltalk .symbol, +.swagger-section pre .smalltalk .char, +.swagger-section pre .go .constant, +.swagger-section pre .change, +.swagger-section pre .markdown .bullet, +.swagger-section pre .markdown .link_url { + color: #080; +} +.swagger-section pre .label, +.swagger-section pre .javadoc, +.swagger-section pre .ruby .string, +.swagger-section pre .decorator, +.swagger-section pre .filter .argument, +.swagger-section pre .localvars, +.swagger-section pre .array, +.swagger-section pre .attr_selector, +.swagger-section pre .important, +.swagger-section pre .pseudo, +.swagger-section pre .pi, +.swagger-section pre .doctype, +.swagger-section pre .deletion, +.swagger-section pre .envvar, +.swagger-section pre .shebang, +.swagger-section pre .apache .sqbracket, +.swagger-section pre .nginx .built_in, +.swagger-section pre .tex .formula, +.swagger-section pre .erlang_repl .reserved, +.swagger-section pre .prompt, +.swagger-section pre .markdown .link_label, +.swagger-section pre .vhdl .attribute, +.swagger-section pre .clojure .attribute, +.swagger-section pre .coffeescript .property { + color: #8888ff; +} +.swagger-section pre .keyword, +.swagger-section pre .id, +.swagger-section pre .phpdoc, +.swagger-section pre .title, +.swagger-section pre .built_in, +.swagger-section pre .aggregate, +.swagger-section pre .css .tag, +.swagger-section pre .javadoctag, +.swagger-section pre .phpdoc, +.swagger-section pre .yardoctag, +.swagger-section pre .smalltalk .class, +.swagger-section pre .winutils, +.swagger-section pre .bash .variable, +.swagger-section pre .apache .tag, +.swagger-section pre .go .typename, +.swagger-section pre .tex .command, +.swagger-section pre .markdown .strong, +.swagger-section pre .request, +.swagger-section pre .status { + font-weight: bold; +} +.swagger-section pre .markdown .emphasis { + font-style: italic; +} +.swagger-section pre .nginx .built_in { + font-weight: normal; +} +.swagger-section pre .coffeescript .javascript, +.swagger-section pre .javascript .xml, +.swagger-section pre .tex .formula, +.swagger-section pre .xml .javascript, +.swagger-section pre .xml .vbscript, +.swagger-section pre .xml .css, +.swagger-section pre .xml .cdata { + opacity: 0.5; +} +.swagger-section .swagger-ui-wrap { + line-height: 1; + font-family: "Droid Sans", sans-serif; + max-width: 960px; + margin-left: auto; + margin-right: auto; + /* JSONEditor specific styling */ +} +.swagger-section .swagger-ui-wrap b, +.swagger-section .swagger-ui-wrap strong { + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap q, +.swagger-section .swagger-ui-wrap blockquote { + quotes: none; +} +.swagger-section .swagger-ui-wrap p { + line-height: 1.4em; + padding: 0 0 10px; + color: #333333; +} +.swagger-section .swagger-ui-wrap q:before, +.swagger-section .swagger-ui-wrap q:after, +.swagger-section .swagger-ui-wrap blockquote:before, +.swagger-section .swagger-ui-wrap blockquote:after { + content: none; +} +.swagger-section .swagger-ui-wrap .heading_with_menu h1, +.swagger-section .swagger-ui-wrap .heading_with_menu h2, +.swagger-section .swagger-ui-wrap .heading_with_menu h3, +.swagger-section .swagger-ui-wrap .heading_with_menu h4, +.swagger-section .swagger-ui-wrap .heading_with_menu h5, +.swagger-section .swagger-ui-wrap .heading_with_menu h6 { + display: block; + clear: none; + float: left; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + width: 60%; +} +.swagger-section .swagger-ui-wrap table { + border-collapse: collapse; + border-spacing: 0; +} +.swagger-section .swagger-ui-wrap table thead tr th { + padding: 5px; + font-size: 0.9em; + color: #666666; + border-bottom: 1px solid #999999; +} +.swagger-section .swagger-ui-wrap table tbody tr:last-child td { + border-bottom: none; +} +.swagger-section .swagger-ui-wrap table tbody tr.offset { + background-color: #f0f0f0; +} +.swagger-section .swagger-ui-wrap table tbody tr td { + padding: 6px; + font-size: 0.9em; + border-bottom: 1px solid #cccccc; + vertical-align: top; + line-height: 1.3em; +} +.swagger-section .swagger-ui-wrap ol { + margin: 0px 0 10px; + padding: 0 0 0 18px; + list-style-type: decimal; +} +.swagger-section .swagger-ui-wrap ol li { + padding: 5px 0px; + font-size: 0.9em; + color: #333333; +} +.swagger-section .swagger-ui-wrap ol, +.swagger-section .swagger-ui-wrap ul { + list-style: none; +} +.swagger-section .swagger-ui-wrap h1 a, +.swagger-section .swagger-ui-wrap h2 a, +.swagger-section .swagger-ui-wrap h3 a, +.swagger-section .swagger-ui-wrap h4 a, +.swagger-section .swagger-ui-wrap h5 a, +.swagger-section .swagger-ui-wrap h6 a { + text-decoration: none; +} +.swagger-section .swagger-ui-wrap h1 a:hover, +.swagger-section .swagger-ui-wrap h2 a:hover, +.swagger-section .swagger-ui-wrap h3 a:hover, +.swagger-section .swagger-ui-wrap h4 a:hover, +.swagger-section .swagger-ui-wrap h5 a:hover, +.swagger-section .swagger-ui-wrap h6 a:hover { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap h1 span.divider, +.swagger-section .swagger-ui-wrap h2 span.divider, +.swagger-section .swagger-ui-wrap h3 span.divider, +.swagger-section .swagger-ui-wrap h4 span.divider, +.swagger-section .swagger-ui-wrap h5 span.divider, +.swagger-section .swagger-ui-wrap h6 span.divider { + color: #aaaaaa; +} +.swagger-section .swagger-ui-wrap a { + color: #547f00; +} +.swagger-section .swagger-ui-wrap a img { + border: none; +} +.swagger-section .swagger-ui-wrap article, +.swagger-section .swagger-ui-wrap aside, +.swagger-section .swagger-ui-wrap details, +.swagger-section .swagger-ui-wrap figcaption, +.swagger-section .swagger-ui-wrap figure, +.swagger-section .swagger-ui-wrap footer, +.swagger-section .swagger-ui-wrap header, +.swagger-section .swagger-ui-wrap hgroup, +.swagger-section .swagger-ui-wrap menu, +.swagger-section .swagger-ui-wrap nav, +.swagger-section .swagger-ui-wrap section, +.swagger-section .swagger-ui-wrap summary { + display: block; +} +.swagger-section .swagger-ui-wrap pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #fcf6db; + border: 1px solid #e5e0c6; + padding: 10px; +} +.swagger-section .swagger-ui-wrap pre code { + line-height: 1.6em; + background: none; +} +.swagger-section .swagger-ui-wrap .content > .content-type > div > label { + clear: both; + display: block; + color: #0F6AB4; + font-size: 1.1em; + margin: 0; + padding: 15px 0 5px; +} +.swagger-section .swagger-ui-wrap .content pre { + font-size: 12px; + margin-top: 5px; + padding: 5px; +} +.swagger-section .swagger-ui-wrap .icon-btn { + cursor: pointer; +} +.swagger-section .swagger-ui-wrap .info_title { + padding-bottom: 10px; + font-weight: bold; + font-size: 25px; +} +.swagger-section .swagger-ui-wrap .footer { + margin-top: 20px; +} +.swagger-section .swagger-ui-wrap p.big, +.swagger-section .swagger-ui-wrap div.big p { + font-size: 1em; + margin-bottom: 10px; +} +.swagger-section .swagger-ui-wrap form.fullwidth ol li.string input, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.url input, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.text textarea, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.numeric input { + width: 500px !important; +} +.swagger-section .swagger-ui-wrap .info_license { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_tos { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .message-fail { + color: #cc0000; +} +.swagger-section .swagger-ui-wrap .info_url { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_email { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_name { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_description { + padding-bottom: 10px; + font-size: 15px; +} +.swagger-section .swagger-ui-wrap .markdown ol li, +.swagger-section .swagger-ui-wrap .markdown ul li { + padding: 3px 0px; + line-height: 1.4em; + color: #333333; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.string input, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.url input, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.numeric input { + display: block; + padding: 4px; + width: auto; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.string input.title, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.url input.title, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.numeric input.title { + font-size: 1.3em; +} +.swagger-section .swagger-ui-wrap table.fullwidth { + width: 100%; +} +.swagger-section .swagger-ui-wrap .model-signature { + font-family: "Droid Sans", sans-serif; + font-size: 1em; + line-height: 1.5em; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav a { + text-decoration: none; + color: #AAA; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav a:hover { + text-decoration: underline; + color: black; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav .selected { + color: black; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap .model-signature .propType { + color: #5555aa; +} +.swagger-section .swagger-ui-wrap .model-signature pre:hover { + background-color: #ffffdd; +} +.swagger-section .swagger-ui-wrap .model-signature pre { + font-size: .85em; + line-height: 1.2em; + overflow: auto; + max-height: 200px; + cursor: pointer; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav { + display: block; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav li:last-child { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav li { + float: left; + margin: 0 5px 5px 0; + padding: 2px 5px 2px 0; + border-right: 1px solid #ddd; +} +.swagger-section .swagger-ui-wrap .model-signature .propOpt { + color: #555; +} +.swagger-section .swagger-ui-wrap .model-signature .snippet small { + font-size: 0.75em; +} +.swagger-section .swagger-ui-wrap .model-signature .propOptKey { + font-style: italic; +} +.swagger-section .swagger-ui-wrap .model-signature .description .strong { + font-weight: bold; + color: #000; + font-size: .9em; +} +.swagger-section .swagger-ui-wrap .model-signature .description div { + font-size: 0.9em; + line-height: 1.5em; + margin-left: 1em; +} +.swagger-section .swagger-ui-wrap .model-signature .description .stronger { + font-weight: bold; + color: #000; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper { + border-spacing: 0; + position: absolute; + background-color: #ffffff; + border: 1px solid #bbbbbb; + display: none; + font-size: 11px; + max-width: 400px; + line-height: 30px; + color: black; + padding: 5px; + margin-left: 10px; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper th { + text-align: center; + background-color: #eeeeee; + border: 1px solid #bbbbbb; + font-size: 11px; + color: #666666; + font-weight: bold; + padding: 5px; + line-height: 15px; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper .optionName { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:first-child, +.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:last-child { + display: inline; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:not(:first-child):before { + display: block; + content: ''; +} +.swagger-section .swagger-ui-wrap .model-signature .description span:last-of-type.propDesc.markdown > p:only-child { + margin-right: -3px; +} +.swagger-section .swagger-ui-wrap .model-signature .propName { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-container { + clear: both; +} +.swagger-section .swagger-ui-wrap .body-textarea { + width: 300px; + height: 100px; + border: 1px solid #aaa; +} +.swagger-section .swagger-ui-wrap .markdown p code, +.swagger-section .swagger-ui-wrap .markdown li code { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #f0f0f0; + color: black; + padding: 1px 3px; +} +.swagger-section .swagger-ui-wrap .required { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .editor_holder { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap .editor_holder label { + font-weight: normal!important; + /* JSONEditor uses bold by default for all labels, we revert that back to normal to not give the impression that by default fields are required */ +} +.swagger-section .swagger-ui-wrap .editor_holder label.required { + font-weight: bold!important; +} +.swagger-section .swagger-ui-wrap input.parameter { + width: 300px; + border: 1px solid #aaa; +} +.swagger-section .swagger-ui-wrap h1 { + color: black; + font-size: 1.5em; + line-height: 1.3em; + padding: 10px 0 10px 0; + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .heading_with_menu { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap .heading_with_menu ul { + display: block; + clear: none; + float: right; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + margin-top: 10px; +} +.swagger-section .swagger-ui-wrap h2 { + color: black; + font-size: 1.3em; + padding: 10px 0 10px 0; +} +.swagger-section .swagger-ui-wrap h2 a { + color: black; +} +.swagger-section .swagger-ui-wrap h2 span.sub { + font-size: 0.7em; + color: #999999; + font-style: italic; +} +.swagger-section .swagger-ui-wrap h2 span.sub a { + color: #777777; +} +.swagger-section .swagger-ui-wrap span.weak { + color: #666666; +} +.swagger-section .swagger-ui-wrap .message-success { + color: #89BF04; +} +.swagger-section .swagger-ui-wrap caption, +.swagger-section .swagger-ui-wrap th, +.swagger-section .swagger-ui-wrap td { + text-align: left; + font-weight: normal; + vertical-align: middle; +} +.swagger-section .swagger-ui-wrap .code { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.text textarea { + font-family: "Droid Sans", sans-serif; + height: 250px; + padding: 4px; + display: block; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.select select { + display: block; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean label { + display: block; + float: left; + clear: none; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean input { + display: block; + float: left; + clear: none; + margin: 0 5px 0 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.required label { + color: black; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li label { + display: block; + clear: both; + width: auto; + padding: 0 0 3px; + color: #666666; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li label abbr { + padding-left: 3px; + color: #888888; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li p.inline-hints { + margin-left: 0; + font-style: italic; + font-size: 0.9em; + margin: 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.buttons { + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap span.blank, +.swagger-section .swagger-ui-wrap span.empty { + color: #888888; + font-style: italic; +} +.swagger-section .swagger-ui-wrap .markdown h3 { + color: #547f00; +} +.swagger-section .swagger-ui-wrap .markdown h4 { + color: #666666; +} +.swagger-section .swagger-ui-wrap .markdown pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #fcf6db; + border: 1px solid #e5e0c6; + padding: 10px; + margin: 0 0 10px 0; +} +.swagger-section .swagger-ui-wrap .markdown pre code { + line-height: 1.6em; +} +.swagger-section .swagger-ui-wrap div.gist { + margin: 20px 0 25px 0 !important; +} +.swagger-section .swagger-ui-wrap ul#resources { + font-family: "Droid Sans", sans-serif; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource { + border-bottom: 1px solid #dddddd; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:hover div.heading h2 a, +.swagger-section .swagger-ui-wrap ul#resources li.resource.active div.heading h2 a { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:hover div.heading ul.options li a, +.swagger-section .swagger-ui-wrap ul#resources li.resource.active div.heading ul.options li a { + color: #555555; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:last-child { + border-bottom: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading { + border: 1px solid transparent; + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options { + overflow: hidden; + padding: 0; + display: block; + clear: none; + float: right; + margin: 14px 10px 0 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li { + float: left; + clear: none; + margin: 0; + padding: 2px 10px; + border-right: 1px solid #dddddd; + color: #666666; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a { + color: #aaaaaa; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:hover { + text-decoration: underline; + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:hover, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:active, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a.active { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li.last { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 { + color: #999999; + padding-left: 0; + display: block; + clear: none; + float: left; + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a { + color: #999999; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a:hover { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation { + float: none; + clear: both; + overflow: hidden; + display: block; + margin: 0 0 10px; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading { + float: none; + clear: both; + overflow: hidden; + display: block; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 { + display: block; + clear: none; + float: left; + width: auto; + margin: 0; + padding: 0; + line-height: 1.1em; + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path { + padding-left: 10px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a { + color: black; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a:hover { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.http_method a { + text-transform: uppercase; + text-decoration: none; + color: white; + display: inline-block; + width: 50px; + font-size: 0.7em; + text-align: center; + padding: 7px 0 4px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + -o-border-radius: 2px; + -ms-border-radius: 2px; + -khtml-border-radius: 2px; + border-radius: 2px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span { + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options { + overflow: hidden; + padding: 0; + display: block; + clear: none; + float: right; + margin: 6px 10px 0 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li { + float: left; + clear: none; + margin: 0; + padding: 2px 10px; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li a { + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li.access { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content { + border-top: none; + padding: 10px; + -moz-border-radius-bottomleft: 6px; + -webkit-border-bottom-left-radius: 6px; + -o-border-bottom-left-radius: 6px; + -ms-border-bottom-left-radius: 6px; + -khtml-border-bottom-left-radius: 6px; + border-bottom-left-radius: 6px; + -moz-border-radius-bottomright: 6px; + -webkit-border-bottom-right-radius: 6px; + -o-border-bottom-right-radius: 6px; + -ms-border-bottom-right-radius: 6px; + -khtml-border-bottom-right-radius: 6px; + border-bottom-right-radius: 6px; + margin: 0 0 20px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content h4 { + font-size: 1.1em; + margin: 0; + padding: 15px 0 5px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header a { + padding: 4px 0 0 10px; + display: inline-block; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header input.submit { + display: block; + clear: none; + float: left; + padding: 6px 8px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header span.response_throbber { + background-image: url('../images/throbber.gif'); + width: 128px; + height: 16px; + display: block; + clear: none; + float: right; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content form input[type='text'].error { + outline: 2px solid black; + outline-color: #cc0000; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content form select[name='parameterContentType'] { + max-width: 300px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.response div.block pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + padding: 10px; + font-size: 0.9em; + max-height: 400px; + overflow-y: auto; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading { + background-color: #f9f2e9; + border: 1px solid #f0e0ca; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading h3 span.http_method a { + background-color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #f0e0ca; + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li a { + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content { + background-color: #faf5ee; + border: 1px solid #f0e0ca; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content h4 { + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content div.sandbox_header a { + color: #dcb67f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading { + background-color: #fcffcd; + border: 1px solid black; + border-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading h3 span.http_method a { + text-transform: uppercase; + background-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #ffd20f; + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li a { + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content { + background-color: #fcffcd; + border: 1px solid black; + border-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content h4 { + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content div.sandbox_header a { + color: #6fc992; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading { + background-color: #f5e8e8; + border: 1px solid #e8c6c7; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading h3 span.http_method a { + text-transform: uppercase; + background-color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #e8c6c7; + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li a { + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content { + background-color: #f7eded; + border: 1px solid #e8c6c7; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content h4 { + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content div.sandbox_header a { + color: #c8787a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading { + background-color: #e7f6ec; + border: 1px solid #c3e8d1; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading h3 span.http_method a { + background-color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3e8d1; + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li a { + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content { + background-color: #ebf7f0; + border: 1px solid #c3e8d1; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content h4 { + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content div.sandbox_header a { + color: #6fc992; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading { + background-color: #FCE9E3; + border: 1px solid #F5D5C3; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading h3 span.http_method a { + background-color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #f0cecb; + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li a { + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content { + background-color: #faf0ef; + border: 1px solid #f0cecb; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content h4 { + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content div.sandbox_header a { + color: #dcb67f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading { + background-color: #e7f0f7; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading h3 span.http_method a { + background-color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3d9ec; + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li a { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content h4 { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content div.sandbox_header a { + color: #6fa5d2; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading { + background-color: #e7f0f7; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading h3 span.http_method a { + background-color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3d9ec; + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading ul.options li a { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content h4 { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content div.sandbox_header a { + color: #6fa5d2; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content { + border-top: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li.last { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a:hover, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a:active, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a.active { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap p#colophon { + margin: 0 15px 40px 15px; + padding: 10px 0; + font-size: 0.8em; + border-top: 1px solid #dddddd; + font-family: "Droid Sans", sans-serif; + color: #999999; + font-style: italic; +} +.swagger-section .swagger-ui-wrap p#colophon a { + text-decoration: none; + color: #547f00; +} +.swagger-section .swagger-ui-wrap h3 { + color: black; + font-size: 1.1em; + padding: 10px 0 10px 0; +} +.swagger-section .swagger-ui-wrap .markdown ol, +.swagger-section .swagger-ui-wrap .markdown ul { + font-family: "Droid Sans", sans-serif; + margin: 5px 0 10px; + padding: 0 0 0 18px; + list-style-type: disc; +} +.swagger-section .swagger-ui-wrap form.form_box { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; + padding: 10px; +} +.swagger-section .swagger-ui-wrap form.form_box label { + color: #0f6ab4 !important; +} +.swagger-section .swagger-ui-wrap form.form_box input[type=submit] { + display: block; + padding: 10px; +} +.swagger-section .swagger-ui-wrap form.form_box p.weak { + font-size: 0.8em; +} +.swagger-section .swagger-ui-wrap form.form_box p { + font-size: 0.9em; + padding: 0 0 15px; + color: #7e7b6d; +} +.swagger-section .swagger-ui-wrap form.form_box p a { + color: #646257; +} +.swagger-section .swagger-ui-wrap form.form_box p strong { + color: black; +} +.swagger-section .swagger-ui-wrap .operation-status td.markdown > p:last-child { + padding-bottom: 0; +} +.swagger-section .title { + font-style: bold; +} +.swagger-section .secondary_form { + display: none; +} +.swagger-section .main_image { + display: block; + margin-left: auto; + margin-right: auto; +} +.swagger-section .oauth_body { + margin-left: 100px; + margin-right: 100px; +} +.swagger-section .oauth_submit { + text-align: center; +} +.swagger-section .api-popup-dialog { + z-index: 10000; + position: absolute; + width: 500px; + background: #FFF; + padding: 20px; + border: 1px solid #ccc; + border-radius: 5px; + display: none; + font-size: 13px; + color: #777; +} +.swagger-section .api-popup-dialog .api-popup-title { + font-size: 24px; + padding: 10px 0; +} +.swagger-section .api-popup-dialog .api-popup-title { + font-size: 24px; + padding: 10px 0; +} +.swagger-section .api-popup-dialog .error-msg { + padding-left: 5px; + padding-bottom: 5px; +} +.swagger-section .api-popup-dialog .api-popup-authbtn { + height: 30px; +} +.swagger-section .api-popup-dialog .api-popup-cancel { + height: 30px; +} +.swagger-section .api-popup-scopes { + padding: 10px 20px; +} +.swagger-section .api-popup-scopes li { + padding: 5px 0; + line-height: 20px; +} +.swagger-section .api-popup-scopes li input { + position: relative; + top: 2px; +} +.swagger-section .api-popup-scopes .api-scope-desc { + padding-left: 20px; + font-style: italic; +} +.swagger-section .api-popup-actions { + padding-top: 10px; +} +#header { + display: none; +} +.swagger-section .swagger-ui-wrap .model-signature pre { + max-height: none; +} +.swagger-section .swagger-ui-wrap .body-textarea { + width: 100px; +} +.swagger-section .swagger-ui-wrap input.parameter { + width: 100px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options { + display: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints { + display: block !important; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content { + display: block !important; +} diff --git a/devops-service/app/api3/public/swagger-ui/css/reset.css b/devops-service/app/api3/public/swagger-ui/css/reset.css new file mode 100644 index 0000000..b2b0789 --- /dev/null +++ b/devops-service/app/api3/public/swagger-ui/css/reset.css @@ -0,0 +1,125 @@ +/* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 */ +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; +} +body { + line-height: 1; +} +ol, +ul { + list-style: none; +} +blockquote, +q { + quotes: none; +} +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} diff --git a/devops-service/app/api3/public/swagger-ui/css/screen.css b/devops-service/app/api3/public/swagger-ui/css/screen.css new file mode 100644 index 0000000..ef4673d --- /dev/null +++ b/devops-service/app/api3/public/swagger-ui/css/screen.css @@ -0,0 +1,1300 @@ +/* Original style from softwaremaniacs.org (c) Ivan Sagalaev */ +.swagger-section pre code { + display: block; + padding: 0.5em; + background: #F0F0F0; +} +.swagger-section pre code, +.swagger-section pre .subst, +.swagger-section pre .tag .title, +.swagger-section pre .lisp .title, +.swagger-section pre .clojure .built_in, +.swagger-section pre .nginx .title { + color: black; +} +.swagger-section pre .string, +.swagger-section pre .title, +.swagger-section pre .constant, +.swagger-section pre .parent, +.swagger-section pre .tag .value, +.swagger-section pre .rules .value, +.swagger-section pre .rules .value .number, +.swagger-section pre .preprocessor, +.swagger-section pre .ruby .symbol, +.swagger-section pre .ruby .symbol .string, +.swagger-section pre .aggregate, +.swagger-section pre .template_tag, +.swagger-section pre .django .variable, +.swagger-section pre .smalltalk .class, +.swagger-section pre .addition, +.swagger-section pre .flow, +.swagger-section pre .stream, +.swagger-section pre .bash .variable, +.swagger-section pre .apache .tag, +.swagger-section pre .apache .cbracket, +.swagger-section pre .tex .command, +.swagger-section pre .tex .special, +.swagger-section pre .erlang_repl .function_or_atom, +.swagger-section pre .markdown .header { + color: #800; +} +.swagger-section pre .comment, +.swagger-section pre .annotation, +.swagger-section pre .template_comment, +.swagger-section pre .diff .header, +.swagger-section pre .chunk, +.swagger-section pre .markdown .blockquote { + color: #888; +} +.swagger-section pre .number, +.swagger-section pre .date, +.swagger-section pre .regexp, +.swagger-section pre .literal, +.swagger-section pre .smalltalk .symbol, +.swagger-section pre .smalltalk .char, +.swagger-section pre .go .constant, +.swagger-section pre .change, +.swagger-section pre .markdown .bullet, +.swagger-section pre .markdown .link_url { + color: #080; +} +.swagger-section pre .label, +.swagger-section pre .javadoc, +.swagger-section pre .ruby .string, +.swagger-section pre .decorator, +.swagger-section pre .filter .argument, +.swagger-section pre .localvars, +.swagger-section pre .array, +.swagger-section pre .attr_selector, +.swagger-section pre .important, +.swagger-section pre .pseudo, +.swagger-section pre .pi, +.swagger-section pre .doctype, +.swagger-section pre .deletion, +.swagger-section pre .envvar, +.swagger-section pre .shebang, +.swagger-section pre .apache .sqbracket, +.swagger-section pre .nginx .built_in, +.swagger-section pre .tex .formula, +.swagger-section pre .erlang_repl .reserved, +.swagger-section pre .prompt, +.swagger-section pre .markdown .link_label, +.swagger-section pre .vhdl .attribute, +.swagger-section pre .clojure .attribute, +.swagger-section pre .coffeescript .property { + color: #8888ff; +} +.swagger-section pre .keyword, +.swagger-section pre .id, +.swagger-section pre .phpdoc, +.swagger-section pre .title, +.swagger-section pre .built_in, +.swagger-section pre .aggregate, +.swagger-section pre .css .tag, +.swagger-section pre .javadoctag, +.swagger-section pre .phpdoc, +.swagger-section pre .yardoctag, +.swagger-section pre .smalltalk .class, +.swagger-section pre .winutils, +.swagger-section pre .bash .variable, +.swagger-section pre .apache .tag, +.swagger-section pre .go .typename, +.swagger-section pre .tex .command, +.swagger-section pre .markdown .strong, +.swagger-section pre .request, +.swagger-section pre .status { + font-weight: bold; +} +.swagger-section pre .markdown .emphasis { + font-style: italic; +} +.swagger-section pre .nginx .built_in { + font-weight: normal; +} +.swagger-section pre .coffeescript .javascript, +.swagger-section pre .javascript .xml, +.swagger-section pre .tex .formula, +.swagger-section pre .xml .javascript, +.swagger-section pre .xml .vbscript, +.swagger-section pre .xml .css, +.swagger-section pre .xml .cdata { + opacity: 0.5; +} +.swagger-section .swagger-ui-wrap { + line-height: 1; + font-family: "Droid Sans", sans-serif; + max-width: 960px; + margin-left: auto; + margin-right: auto; + /* JSONEditor specific styling */ +} +.swagger-section .swagger-ui-wrap b, +.swagger-section .swagger-ui-wrap strong { + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap q, +.swagger-section .swagger-ui-wrap blockquote { + quotes: none; +} +.swagger-section .swagger-ui-wrap p { + line-height: 1.4em; + padding: 0 0 10px; + color: #333333; +} +.swagger-section .swagger-ui-wrap q:before, +.swagger-section .swagger-ui-wrap q:after, +.swagger-section .swagger-ui-wrap blockquote:before, +.swagger-section .swagger-ui-wrap blockquote:after { + content: none; +} +.swagger-section .swagger-ui-wrap .heading_with_menu h1, +.swagger-section .swagger-ui-wrap .heading_with_menu h2, +.swagger-section .swagger-ui-wrap .heading_with_menu h3, +.swagger-section .swagger-ui-wrap .heading_with_menu h4, +.swagger-section .swagger-ui-wrap .heading_with_menu h5, +.swagger-section .swagger-ui-wrap .heading_with_menu h6 { + display: block; + clear: none; + float: left; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + width: 60%; +} +.swagger-section .swagger-ui-wrap table { + border-collapse: collapse; + border-spacing: 0; +} +.swagger-section .swagger-ui-wrap table thead tr th { + padding: 5px; + font-size: 0.9em; + color: #666666; + border-bottom: 1px solid #999999; +} +.swagger-section .swagger-ui-wrap table tbody tr:last-child td { + border-bottom: none; +} +.swagger-section .swagger-ui-wrap table tbody tr.offset { + background-color: #f0f0f0; +} +.swagger-section .swagger-ui-wrap table tbody tr td { + padding: 6px; + font-size: 0.9em; + border-bottom: 1px solid #cccccc; + vertical-align: top; + line-height: 1.3em; +} +.swagger-section .swagger-ui-wrap ol { + margin: 0px 0 10px; + padding: 0 0 0 18px; + list-style-type: decimal; +} +.swagger-section .swagger-ui-wrap ol li { + padding: 5px 0px; + font-size: 0.9em; + color: #333333; +} +.swagger-section .swagger-ui-wrap ol, +.swagger-section .swagger-ui-wrap ul { + list-style: none; +} +.swagger-section .swagger-ui-wrap h1 a, +.swagger-section .swagger-ui-wrap h2 a, +.swagger-section .swagger-ui-wrap h3 a, +.swagger-section .swagger-ui-wrap h4 a, +.swagger-section .swagger-ui-wrap h5 a, +.swagger-section .swagger-ui-wrap h6 a { + text-decoration: none; +} +.swagger-section .swagger-ui-wrap h1 a:hover, +.swagger-section .swagger-ui-wrap h2 a:hover, +.swagger-section .swagger-ui-wrap h3 a:hover, +.swagger-section .swagger-ui-wrap h4 a:hover, +.swagger-section .swagger-ui-wrap h5 a:hover, +.swagger-section .swagger-ui-wrap h6 a:hover { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap h1 span.divider, +.swagger-section .swagger-ui-wrap h2 span.divider, +.swagger-section .swagger-ui-wrap h3 span.divider, +.swagger-section .swagger-ui-wrap h4 span.divider, +.swagger-section .swagger-ui-wrap h5 span.divider, +.swagger-section .swagger-ui-wrap h6 span.divider { + color: #aaaaaa; +} +.swagger-section .swagger-ui-wrap a { + color: #547f00; +} +.swagger-section .swagger-ui-wrap a img { + border: none; +} +.swagger-section .swagger-ui-wrap article, +.swagger-section .swagger-ui-wrap aside, +.swagger-section .swagger-ui-wrap details, +.swagger-section .swagger-ui-wrap figcaption, +.swagger-section .swagger-ui-wrap figure, +.swagger-section .swagger-ui-wrap footer, +.swagger-section .swagger-ui-wrap header, +.swagger-section .swagger-ui-wrap hgroup, +.swagger-section .swagger-ui-wrap menu, +.swagger-section .swagger-ui-wrap nav, +.swagger-section .swagger-ui-wrap section, +.swagger-section .swagger-ui-wrap summary { + display: block; +} +.swagger-section .swagger-ui-wrap pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #fcf6db; + border: 1px solid #e5e0c6; + padding: 10px; +} +.swagger-section .swagger-ui-wrap pre code { + line-height: 1.6em; + background: none; +} +.swagger-section .swagger-ui-wrap .content > .content-type > div > label { + clear: both; + display: block; + color: #0F6AB4; + font-size: 1.1em; + margin: 0; + padding: 15px 0 5px; +} +.swagger-section .swagger-ui-wrap .content pre { + font-size: 12px; + margin-top: 5px; + padding: 5px; +} +.swagger-section .swagger-ui-wrap .icon-btn { + cursor: pointer; +} +.swagger-section .swagger-ui-wrap .info_title { + padding-bottom: 10px; + font-weight: bold; + font-size: 25px; +} +.swagger-section .swagger-ui-wrap .footer { + margin-top: 20px; +} +.swagger-section .swagger-ui-wrap p.big, +.swagger-section .swagger-ui-wrap div.big p { + font-size: 1em; + margin-bottom: 10px; +} +.swagger-section .swagger-ui-wrap form.fullwidth ol li.string input, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.url input, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.text textarea, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.numeric input { + width: 500px !important; +} +.swagger-section .swagger-ui-wrap .info_license { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_tos { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .message-fail { + color: #cc0000; +} +.swagger-section .swagger-ui-wrap .info_url { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_email { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_name { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_description { + padding-bottom: 10px; + font-size: 15px; +} +.swagger-section .swagger-ui-wrap .markdown ol li, +.swagger-section .swagger-ui-wrap .markdown ul li { + padding: 3px 0px; + line-height: 1.4em; + color: #333333; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.string input, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.url input, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.numeric input { + display: block; + padding: 4px; + width: auto; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.string input.title, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.url input.title, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.numeric input.title { + font-size: 1.3em; +} +.swagger-section .swagger-ui-wrap table.fullwidth { + width: 100%; +} +.swagger-section .swagger-ui-wrap .model-signature { + font-family: "Droid Sans", sans-serif; + font-size: 1em; + line-height: 1.5em; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav a { + text-decoration: none; + color: #AAA; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav a:hover { + text-decoration: underline; + color: black; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav .selected { + color: black; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap .model-signature .propType { + color: #5555aa; +} +.swagger-section .swagger-ui-wrap .model-signature pre:hover { + background-color: #ffffdd; +} +.swagger-section .swagger-ui-wrap .model-signature pre { + font-size: .85em; + line-height: 1.2em; + overflow: auto; + max-height: 200px; + cursor: pointer; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav { + display: block; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav li:last-child { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav li { + float: left; + margin: 0 5px 5px 0; + padding: 2px 5px 2px 0; + border-right: 1px solid #ddd; +} +.swagger-section .swagger-ui-wrap .model-signature .propOpt { + color: #555; +} +.swagger-section .swagger-ui-wrap .model-signature .snippet small { + font-size: 0.75em; +} +.swagger-section .swagger-ui-wrap .model-signature .propOptKey { + font-style: italic; +} +.swagger-section .swagger-ui-wrap .model-signature .description .strong { + font-weight: bold; + color: #000; + font-size: .9em; +} +.swagger-section .swagger-ui-wrap .model-signature .description div { + font-size: 0.9em; + line-height: 1.5em; + margin-left: 1em; +} +.swagger-section .swagger-ui-wrap .model-signature .description .stronger { + font-weight: bold; + color: #000; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper { + border-spacing: 0; + position: absolute; + background-color: #ffffff; + border: 1px solid #bbbbbb; + display: none; + font-size: 11px; + max-width: 400px; + line-height: 30px; + color: black; + padding: 5px; + margin-left: 10px; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper th { + text-align: center; + background-color: #eeeeee; + border: 1px solid #bbbbbb; + font-size: 11px; + color: #666666; + font-weight: bold; + padding: 5px; + line-height: 15px; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper .optionName { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:first-child, +.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:last-child { + display: inline; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:not(:first-child):before { + display: block; + content: ''; +} +.swagger-section .swagger-ui-wrap .model-signature .description span:last-of-type.propDesc.markdown > p:only-child { + margin-right: -3px; +} +.swagger-section .swagger-ui-wrap .model-signature .propName { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-container { + clear: both; +} +.swagger-section .swagger-ui-wrap .body-textarea { + width: 300px; + height: 100px; + border: 1px solid #aaa; +} +.swagger-section .swagger-ui-wrap .markdown p code, +.swagger-section .swagger-ui-wrap .markdown li code { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #f0f0f0; + color: black; + padding: 1px 3px; +} +.swagger-section .swagger-ui-wrap .required { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .editor_holder { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap .editor_holder label { + font-weight: normal!important; + /* JSONEditor uses bold by default for all labels, we revert that back to normal to not give the impression that by default fields are required */ +} +.swagger-section .swagger-ui-wrap .editor_holder label.required { + font-weight: bold!important; +} +.swagger-section .swagger-ui-wrap input.parameter { + width: 300px; + border: 1px solid #aaa; +} +.swagger-section .swagger-ui-wrap h1 { + color: black; + font-size: 1.5em; + line-height: 1.3em; + padding: 10px 0 10px 0; + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .heading_with_menu { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap .heading_with_menu ul { + display: block; + clear: none; + float: right; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + margin-top: 10px; +} +.swagger-section .swagger-ui-wrap h2 { + color: black; + font-size: 1.3em; + padding: 10px 0 10px 0; +} +.swagger-section .swagger-ui-wrap h2 a { + color: black; +} +.swagger-section .swagger-ui-wrap h2 span.sub { + font-size: 0.7em; + color: #999999; + font-style: italic; +} +.swagger-section .swagger-ui-wrap h2 span.sub a { + color: #777777; +} +.swagger-section .swagger-ui-wrap span.weak { + color: #666666; +} +.swagger-section .swagger-ui-wrap .message-success { + color: #89BF04; +} +.swagger-section .swagger-ui-wrap caption, +.swagger-section .swagger-ui-wrap th, +.swagger-section .swagger-ui-wrap td { + text-align: left; + font-weight: normal; + vertical-align: middle; +} +.swagger-section .swagger-ui-wrap .code { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.text textarea { + font-family: "Droid Sans", sans-serif; + height: 250px; + padding: 4px; + display: block; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.select select { + display: block; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean label { + display: block; + float: left; + clear: none; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean input { + display: block; + float: left; + clear: none; + margin: 0 5px 0 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.required label { + color: black; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li label { + display: block; + clear: both; + width: auto; + padding: 0 0 3px; + color: #666666; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li label abbr { + padding-left: 3px; + color: #888888; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li p.inline-hints { + margin-left: 0; + font-style: italic; + font-size: 0.9em; + margin: 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.buttons { + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap span.blank, +.swagger-section .swagger-ui-wrap span.empty { + color: #888888; + font-style: italic; +} +.swagger-section .swagger-ui-wrap .markdown h3 { + color: #547f00; +} +.swagger-section .swagger-ui-wrap .markdown h4 { + color: #666666; +} +.swagger-section .swagger-ui-wrap .markdown pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #fcf6db; + border: 1px solid #e5e0c6; + padding: 10px; + margin: 0 0 10px 0; +} +.swagger-section .swagger-ui-wrap .markdown pre code { + line-height: 1.6em; +} +.swagger-section .swagger-ui-wrap div.gist { + margin: 20px 0 25px 0 !important; +} +.swagger-section .swagger-ui-wrap ul#resources { + font-family: "Droid Sans", sans-serif; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource { + border-bottom: 1px solid #dddddd; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:hover div.heading h2 a, +.swagger-section .swagger-ui-wrap ul#resources li.resource.active div.heading h2 a { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:hover div.heading ul.options li a, +.swagger-section .swagger-ui-wrap ul#resources li.resource.active div.heading ul.options li a { + color: #555555; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:last-child { + border-bottom: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading { + border: 1px solid transparent; + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options { + overflow: hidden; + padding: 0; + display: block; + clear: none; + float: right; + margin: 14px 10px 0 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li { + float: left; + clear: none; + margin: 0; + padding: 2px 10px; + border-right: 1px solid #dddddd; + color: #666666; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a { + color: #aaaaaa; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:hover { + text-decoration: underline; + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:hover, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:active, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a.active { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li.last { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 { + color: #999999; + padding-left: 0; + display: block; + clear: none; + float: left; + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a { + color: #999999; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a:hover { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation { + float: none; + clear: both; + overflow: hidden; + display: block; + margin: 0 0 10px; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading { + float: none; + clear: both; + overflow: hidden; + display: block; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 { + display: block; + clear: none; + float: left; + width: auto; + margin: 0; + padding: 0; + line-height: 1.1em; + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path { + padding-left: 10px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a { + color: black; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a:hover { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.http_method a { + text-transform: uppercase; + text-decoration: none; + color: white; + display: inline-block; + width: 50px; + font-size: 0.7em; + text-align: center; + padding: 7px 0 4px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + -o-border-radius: 2px; + -ms-border-radius: 2px; + -khtml-border-radius: 2px; + border-radius: 2px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span { + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options { + overflow: hidden; + padding: 0; + display: block; + clear: none; + float: right; + margin: 6px 10px 0 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li { + float: left; + clear: none; + margin: 0; + padding: 2px 10px; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li a { + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li.access { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content { + border-top: none; + padding: 10px; + -moz-border-radius-bottomleft: 6px; + -webkit-border-bottom-left-radius: 6px; + -o-border-bottom-left-radius: 6px; + -ms-border-bottom-left-radius: 6px; + -khtml-border-bottom-left-radius: 6px; + border-bottom-left-radius: 6px; + -moz-border-radius-bottomright: 6px; + -webkit-border-bottom-right-radius: 6px; + -o-border-bottom-right-radius: 6px; + -ms-border-bottom-right-radius: 6px; + -khtml-border-bottom-right-radius: 6px; + border-bottom-right-radius: 6px; + margin: 0 0 20px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content h4 { + font-size: 1.1em; + margin: 0; + padding: 15px 0 5px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header a { + padding: 4px 0 0 10px; + display: inline-block; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header input.submit { + display: block; + clear: none; + float: left; + padding: 6px 8px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header span.response_throbber { + background-image: url('../images/throbber.gif'); + width: 128px; + height: 16px; + display: block; + clear: none; + float: right; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content form input[type='text'].error { + outline: 2px solid black; + outline-color: #cc0000; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content form select[name='parameterContentType'] { + max-width: 300px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.response div.block pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + padding: 10px; + font-size: 0.9em; + max-height: 400px; + overflow-y: auto; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading { + background-color: #f9f2e9; + border: 1px solid #f0e0ca; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading h3 span.http_method a { + background-color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #f0e0ca; + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li a { + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content { + background-color: #faf5ee; + border: 1px solid #f0e0ca; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content h4 { + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content div.sandbox_header a { + color: #dcb67f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading { + background-color: #fcffcd; + border: 1px solid black; + border-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading h3 span.http_method a { + text-transform: uppercase; + background-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #ffd20f; + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li a { + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content { + background-color: #fcffcd; + border: 1px solid black; + border-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content h4 { + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content div.sandbox_header a { + color: #6fc992; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading { + background-color: #f5e8e8; + border: 1px solid #e8c6c7; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading h3 span.http_method a { + text-transform: uppercase; + background-color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #e8c6c7; + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li a { + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content { + background-color: #f7eded; + border: 1px solid #e8c6c7; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content h4 { + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content div.sandbox_header a { + color: #c8787a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading { + background-color: #e7f6ec; + border: 1px solid #c3e8d1; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading h3 span.http_method a { + background-color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3e8d1; + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li a { + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content { + background-color: #ebf7f0; + border: 1px solid #c3e8d1; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content h4 { + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content div.sandbox_header a { + color: #6fc992; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading { + background-color: #FCE9E3; + border: 1px solid #F5D5C3; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading h3 span.http_method a { + background-color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #f0cecb; + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li a { + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content { + background-color: #faf0ef; + border: 1px solid #f0cecb; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content h4 { + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content div.sandbox_header a { + color: #dcb67f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading { + background-color: #e7f0f7; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading h3 span.http_method a { + background-color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3d9ec; + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li a { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content h4 { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content div.sandbox_header a { + color: #6fa5d2; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading { + background-color: #e7f0f7; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading h3 span.http_method a { + background-color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3d9ec; + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading ul.options li a { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content h4 { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content div.sandbox_header a { + color: #6fa5d2; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content { + border-top: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li.last { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a:hover, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a:active, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a.active { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap p#colophon { + margin: 0 15px 40px 15px; + padding: 10px 0; + font-size: 0.8em; + border-top: 1px solid #dddddd; + font-family: "Droid Sans", sans-serif; + color: #999999; + font-style: italic; +} +.swagger-section .swagger-ui-wrap p#colophon a { + text-decoration: none; + color: #547f00; +} +.swagger-section .swagger-ui-wrap h3 { + color: black; + font-size: 1.1em; + padding: 10px 0 10px 0; +} +.swagger-section .swagger-ui-wrap .markdown ol, +.swagger-section .swagger-ui-wrap .markdown ul { + font-family: "Droid Sans", sans-serif; + margin: 5px 0 10px; + padding: 0 0 0 18px; + list-style-type: disc; +} +.swagger-section .swagger-ui-wrap form.form_box { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; + padding: 10px; +} +.swagger-section .swagger-ui-wrap form.form_box label { + color: #0f6ab4 !important; +} +.swagger-section .swagger-ui-wrap form.form_box input[type=submit] { + display: block; + padding: 10px; +} +.swagger-section .swagger-ui-wrap form.form_box p.weak { + font-size: 0.8em; +} +.swagger-section .swagger-ui-wrap form.form_box p { + font-size: 0.9em; + padding: 0 0 15px; + color: #7e7b6d; +} +.swagger-section .swagger-ui-wrap form.form_box p a { + color: #646257; +} +.swagger-section .swagger-ui-wrap form.form_box p strong { + color: black; +} +.swagger-section .swagger-ui-wrap .operation-status td.markdown > p:last-child { + padding-bottom: 0; +} +.swagger-section .title { + font-style: bold; +} +.swagger-section .secondary_form { + display: none; +} +.swagger-section .main_image { + display: block; + margin-left: auto; + margin-right: auto; +} +.swagger-section .oauth_body { + margin-left: 100px; + margin-right: 100px; +} +.swagger-section .oauth_submit { + text-align: center; +} +.swagger-section .api-popup-dialog { + z-index: 10000; + position: absolute; + width: 500px; + background: #FFF; + padding: 20px; + border: 1px solid #ccc; + border-radius: 5px; + display: none; + font-size: 13px; + color: #777; +} +.swagger-section .api-popup-dialog .api-popup-title { + font-size: 24px; + padding: 10px 0; +} +.swagger-section .api-popup-dialog .api-popup-title { + font-size: 24px; + padding: 10px 0; +} +.swagger-section .api-popup-dialog .error-msg { + padding-left: 5px; + padding-bottom: 5px; +} +.swagger-section .api-popup-dialog .api-popup-authbtn { + height: 30px; +} +.swagger-section .api-popup-dialog .api-popup-cancel { + height: 30px; +} +.swagger-section .api-popup-scopes { + padding: 10px 20px; +} +.swagger-section .api-popup-scopes li { + padding: 5px 0; + line-height: 20px; +} +.swagger-section .api-popup-scopes li input { + position: relative; + top: 2px; +} +.swagger-section .api-popup-scopes .api-scope-desc { + padding-left: 20px; + font-style: italic; +} +.swagger-section .api-popup-actions { + padding-top: 10px; +} +.swagger-section .access { + float: right; +} +.swagger-section .auth { + float: right; +} +.swagger-section .api-ic { + height: 18px; + vertical-align: middle; + display: inline-block; + background: url(../images/explorer_icons.png) no-repeat; +} +.swagger-section .api-ic .api_information_panel { + position: relative; + margin-top: 20px; + margin-left: -5px; + background: #FFF; + border: 1px solid #ccc; + border-radius: 5px; + display: none; + font-size: 13px; + max-width: 300px; + line-height: 30px; + color: black; + padding: 5px; +} +.swagger-section .api-ic .api_information_panel p .api-msg-enabled { + color: green; +} +.swagger-section .api-ic .api_information_panel p .api-msg-disabled { + color: red; +} +.swagger-section .api-ic:hover .api_information_panel { + position: absolute; + display: block; +} +.swagger-section .ic-info { + background-position: 0 0; + width: 18px; + margin-top: -6px; + margin-left: 4px; +} +.swagger-section .ic-warning { + background-position: -60px 0; + width: 18px; + margin-top: -6px; + margin-left: 4px; +} +.swagger-section .ic-error { + background-position: -30px 0; + width: 18px; + margin-top: -6px; + margin-left: 4px; +} +.swagger-section .ic-off { + background-position: -90px 0; + width: 58px; + margin-top: -4px; + cursor: pointer; +} +.swagger-section .ic-on { + background-position: -160px 0; + width: 58px; + margin-top: -4px; + cursor: pointer; +} +.swagger-section #header { + background-color: #89bf04; + padding: 14px; +} +.swagger-section #input_baseUrl { + width: 400px; +} +.swagger-section #api_selector { + display: block; + clear: none; + float: right; +} +.swagger-section #api_selector .input { + display: block; + clear: none; + float: left; + margin: 0 10px 0 0; +} +.swagger-section #api_selector input { + font-size: 0.9em; + padding: 3px; + margin: 0; +} +.swagger-section #input_apiKey { + width: 200px; +} +.swagger-section #explore { + display: block; + text-decoration: none; + font-weight: bold; + padding: 6px 8px; + font-size: 0.9em; + color: white; + background-color: #547f00; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -o-border-radius: 4px; + -ms-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; +} +.swagger-section #explore:hover { + background-color: #547f00; +} +.swagger-section #header #logo { + font-size: 1.5em; + font-weight: bold; + text-decoration: none; + background: transparent url(../images/logo_small.png) no-repeat left center; + padding: 20px 0 20px 40px; + color: white; +} +.swagger-section #content_message { + margin: 10px 15px; + font-style: italic; + color: #999999; +} +.swagger-section #message-bar { + min-height: 30px; + text-align: center; + padding-top: 10px; +} +.swagger-section .swagger-collapse:before { + content: "-"; +} +.swagger-section .swagger-expand:before { + content: "+"; +} diff --git a/devops-service/app/api3/public/swagger-ui/css/style.css b/devops-service/app/api3/public/swagger-ui/css/style.css new file mode 100644 index 0000000..fc21a31 --- /dev/null +++ b/devops-service/app/api3/public/swagger-ui/css/style.css @@ -0,0 +1,250 @@ +.swagger-section #header a#logo { + font-size: 1.5em; + font-weight: bold; + text-decoration: none; + background: transparent url(../images/logo.png) no-repeat left center; + padding: 20px 0 20px 40px; +} +#text-head { + font-size: 80px; + font-family: 'Roboto', sans-serif; + color: #ffffff; + float: right; + margin-right: 20%; +} +.navbar-fixed-top .navbar-nav { + height: auto; +} +.navbar-fixed-top .navbar-brand { + height: auto; +} +.navbar-header { + height: auto; +} +.navbar-inverse { + background-color: #000; + border-color: #000; +} +#navbar-brand { + margin-left: 20%; +} +.navtext { + font-size: 10px; +} +.h1, +h1 { + font-size: 60px; +} +.navbar-default .navbar-header .navbar-brand { + color: #a2dfee; +} +/* tag titles */ +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a { + color: #393939; + font-family: 'Arvo', serif; + font-size: 1.5em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a:hover { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 { + color: #525252; + padding-left: 0px; + display: block; + clear: none; + float: left; + font-family: 'Arvo', serif; + font-weight: bold; +} +.navbar-default .navbar-collapse, +.navbar-default .navbar-form { + border-color: #0A0A0A; +} +.container1 { + width: 1500px; + margin: auto; + margin-top: 0; + background-image: url('../images/shield.png'); + background-repeat: no-repeat; + background-position: -40px -20px; + margin-bottom: 210px; +} +.container-inner { + width: 1200px; + margin: auto; + background-color: rgba(223, 227, 228, 0.75); + padding-bottom: 40px; + padding-top: 40px; + border-radius: 15px; +} +.header-content { + padding: 0; + width: 1000px; +} +.title1 { + font-size: 80px; + font-family: 'Vollkorn', serif; + color: #404040; + text-align: center; + padding-top: 40px; + padding-bottom: 100px; +} +#icon { + margin-top: -18px; +} +.subtext { + font-size: 25px; + font-style: italic; + color: #08b; + text-align: right; + padding-right: 250px; +} +.bg-primary { + background-color: #00468b; +} +.navbar-default .nav > li > a, +.navbar-default .nav > li > a:focus { + color: #08b; +} +.navbar-default .nav > li > a, +.navbar-default .nav > li > a:hover { + color: #08b; +} +.navbar-default .nav > li > a, +.navbar-default .nav > li > a:focus:hover { + color: #08b; +} +.text-faded { + font-size: 25px; + font-family: 'Vollkorn', serif; +} +.section-heading { + font-family: 'Vollkorn', serif; + font-size: 45px; + padding-bottom: 10px; +} +hr { + border-color: #00468b; + padding-bottom: 10px; +} +.description { + margin-top: 20px; + padding-bottom: 200px; +} +.description li { + font-family: 'Vollkorn', serif; + font-size: 25px; + color: #525252; + margin-left: 28%; + padding-top: 5px; +} +.gap { + margin-top: 200px; +} +.troubleshootingtext { + color: rgba(255, 255, 255, 0.7); + padding-left: 30%; +} +.troubleshootingtext li { + list-style-type: circle; + font-size: 25px; + padding-bottom: 5px; +} +.overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1000; +} +.block.response_body.json:hover { + cursor: pointer; +} +.backdrop { + color: blue; +} +#myModal { + height: 100%; +} +.modal-backdrop { + bottom: 0; + position: fixed; +} +.curl { + padding: 10px; + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + font-size: 0.9em; + max-height: 400px; + margin-top: 5px; + overflow-y: auto; + background-color: #fcf6db; + border: 1px solid #e5e0c6; + border-radius: 4px; +} +.curl_title { + font-size: 1.1em; + margin: 0; + padding: 15px 0 5px; + font-family: 'Open Sans', 'Helvetica Neue', Arial, sans-serif; + font-weight: 500; + line-height: 1.1; +} +.footer { + display: none; +} +.swagger-section .swagger-ui-wrap h2 { + padding: 0; +} +h2 { + margin: 0; + margin-bottom: 5px; +} +.markdown p { + font-size: 15px; + font-family: 'Arvo', serif; +} +.swagger-section .swagger-ui-wrap .code { + font-size: 15px; + font-family: 'Arvo', serif; +} +.swagger-section .swagger-ui-wrap b { + font-family: 'Arvo', serif; +} +#signin:hover { + cursor: pointer; +} +.dropdown-menu { + padding: 15px; +} +.navbar-right .dropdown-menu { + left: 0; + right: auto; +} +#signinbutton { + width: 100%; + height: 32px; + font-size: 13px; + font-weight: bold; + color: #08b; +} +.navbar-default .nav > li .details { + color: #000000; + text-transform: none; + font-size: 15px; + font-weight: normal; + font-family: 'Open Sans', sans-serif; + font-style: italic; + line-height: 20px; + top: -2px; +} +.navbar-default .nav > li .details:hover { + color: black; +} +#signout { + width: 100%; + height: 32px; + font-size: 13px; + font-weight: bold; + color: #08b; +} diff --git a/devops-service/app/api3/public/swagger-ui/css/typography.css b/devops-service/app/api3/public/swagger-ui/css/typography.css new file mode 100644 index 0000000..3235edd --- /dev/null +++ b/devops-service/app/api3/public/swagger-ui/css/typography.css @@ -0,0 +1,14 @@ +/* Google Font's Droid Sans */ +@font-face { + font-family: 'Droid Sans'; + font-style: normal; + font-weight: 400; + src: local('Droid Sans'), local('DroidSans'), url('../fonts/DroidSans.ttf') format('truetype'); +} +/* Google Font's Droid Sans Bold */ +@font-face { + font-family: 'Droid Sans'; + font-style: normal; + font-weight: 700; + src: local('Droid Sans Bold'), local('DroidSans-Bold'), url('../fonts/DroidSans-Bold.ttf') format('truetype'); +} diff --git a/devops-service/app/api3/public/swagger-ui/fonts/DroidSans-Bold.ttf b/devops-service/app/api3/public/swagger-ui/fonts/DroidSans-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..942bbf5ba3aa04288175d094bc9c11d109c5726c GIT binary patch literal 42480 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 literal 0 HcmV?d00001 diff --git a/devops-service/app/api3/public/swagger-ui/fonts/DroidSans.ttf b/devops-service/app/api3/public/swagger-ui/fonts/DroidSans.ttf new file mode 100644 index 0000000000000000000000000000000000000000..efd1f8bbd882a6a434159fa1da7238babbaa2fb3 GIT binary patch literal 41028 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 literal 0 HcmV?d00001 diff --git a/devops-service/app/api3/public/swagger-ui/images/collapse.gif b/devops-service/app/api3/public/swagger-ui/images/collapse.gif new file mode 100644 index 0000000000000000000000000000000000000000..8843e8ce5a46781defd33d5304a0cb4191e72546 GIT binary patch literal 69 zcmZ?wbhEHbwd`J-OQ1PRqNMh0sDWgikt literal 0 HcmV?d00001 diff --git a/devops-service/app/api3/public/swagger-ui/images/expand.gif b/devops-service/app/api3/public/swagger-ui/images/expand.gif new file mode 100644 index 0000000000000000000000000000000000000000..477bf13718dc56928f313ef6eeb1c2b1a47db69a GIT binary patch literal 73 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 literal 0 HcmV?d00001 diff --git a/devops-service/app/api3/public/swagger-ui/images/favicon-32x32.png b/devops-service/app/api3/public/swagger-ui/images/favicon-32x32.png new file mode 100755 index 0000000000000000000000000000000000000000..32f319f89bd07e691de2ccaf21632bb9793a9cd9 GIT binary patch literal 1654 zcmV-+28sEJP)Z zYjBlU6^5U+_c^%{LI@BBNTw~6nu{hdI!rrEwGFgm#aci_3sGk}<7m?sf9eb^ z&gef46cL=-R%Vz2t>aV&tTwa+k{T7cTawZs4M`*flAN4x@8yqkX_Av0()aswzFB*( z_g(v2XYI9wX~KJNyap8@D?yj3`UN7k2td_e5i#!rt)NGYgj$R-e!9LVh4Vp9;GzxA zMK7v)oerFfUJTsuv(5*(Q~8(4BX%1_Ls=xV0tMmqzMUI4PvXOov* zN@+ngMR`jQiMjRax>fZ#BX;*hCo--8?_I7}_iH1eH6lVJdXsn0?&9dFMn390GigKx zvpz#@c|A3YzfF2L8&$=tJ7mOs!x(#QN&()xLa+W!YeGv@mDYd1!hz-=a(&=GlM;_b zPG%vitA9$x!Y2?Bybn%_m{rEu%M%KSMehG%O=wBrhS}Hj9gdybeM`h+QN3s*YnL|? zvgvUVc7-t}c@jwh@o&E0n$ViS4YT|3W1Q?bbX(*J+;!)d`PKvb6Cw_Y*hdmMVUhyW zZ8T!ns4DxK>OV1ZfPY>3J+HpK1yx1F)_HZC5;g(w-sLEI-iR$|x$s+l^XGNR&0(9# z+U1ShGrx|W{)=pX?Y>FLo_?g2xjCQaWXJ2g+O&meFp#vZ`{G`z$~NHDbpvJAgsz!b z^uFGREr>*Ka-jL?Q4VX$H&eA}1J?(yaQ5=yDWg5td6>SNom7^sV|jV~DBHp21|rcL z7_oCzwI0BD?~T9~fOpTln3C=D^Q!>ZbNFi<93yT#96a8@?!#XNptPWRl&yd8BaWWl z34kD5y!Xa<4JwQYEl^dOPB*4F%u0_Hvw`81zwm@e6sU}FPKxd5=|)r)V{EC{pvuH* zJ6!Jl2VH$<$8i_SQ&l%@0nSHJRgly=clG*tI(ymx5M-qRT?Ww6)jY1Vth8L5k8)$E zd)fkqhWl`Sm>KDF#NbSZjd4f1W4iLB@ygIB2Q(Evj zKI~~rAs@*lV&6Bw-ypSUBrp)UhBaZbGx8@q#<9o<&%&CtWIY&*`2|&ppREQs0#KB< zgo0U%##!zYYNsv0+Hfp4R8wsEISVP7vn=s?#2B$H-n(`Yp>|B|1{jR=0g#zCWqX%I zR@!U;hN9O~Y_;X}i3a5ZBlfs4#&|`42~e~6Npdm^M{Vf1dK`d_D_^Fr>iH?jAFkZS zH}BsU+uL<~lx=2a0X5~D0Z?>@F~%DJ5oz@5yVJwjtghZZYSYovJ2-LvC30t!u()K^ zlmg02Rxx|VJX$Zjl5)4yRc$9DJQJ_JTg3bVMs!5ZMK_7qUje-Q*G)8^+cPEEt)Q}O zJ?p-(7XYvB$&d~2f)O2&h~4AW9TE}NE^p+nc@KSJTfS+;@4SL}@UgYJ}w)Q)V=$7{=rRQ;RAgzi>VS}wfAf#wGK z2d~@`@yyISSY5T9%ChzGtaqnG%mc=liQDoKB63yzUJ+Xt8%%ES&Y7Jwo!-IKzH^fj z=jW7BTfT+b^39}&XT}GU;0+^o{j>&?k41Rn)ol`yZ6ims&fdS%-gS)L{`TapmX$u6 zyqTqx=2ufZ=iU*cneSC~&lw5r`oD)&=Y!n94L=*WXvefB)7Ws@5xC*4w>6xOb0Q(H zL4_bI75%KLUtq)*#v*lBL~MLK=dE~!3#Q}ufBOd*=m{K;N&o-=07*qoM6N<$g6kDD A00000 literal 0 HcmV?d00001 diff --git a/devops-service/app/api3/public/swagger-ui/images/favicon.ico b/devops-service/app/api3/public/swagger-ui/images/favicon.ico new file mode 100755 index 0000000000000000000000000000000000000000..8b60bcf06a7685b9ea53983c125e7058906fbcbd GIT binary patch literal 5430 zcmcgwYiv|S6douFYJ&9 zoHH|LSE(UtsLIS#7_-&1VM>ivN@Zu;-zA{00&N^|8r`@w~u}3a1X>=27;pf_a{DPJMTR$JSvjrVZRL?J~Ga>i0GO0Y%B;(7M$}2m+kcrz@>Hae2Pk^@b^f8%H zL>`N;qJuC@Oe+t~7I z<(F|AfU$#EfN{kFKA+53u@A<+4YnEIf**{2=CuCE#{h4Wo9v-X#BN#D=ro%g~u zG`=OQBiQEDZgc6n2RYf*+oRWbKVZW9eJJuGNa8A-F{8(f+y@)q2tylad}QmVTBh{m zkpvl@O*Y<9#KzvA;3if`|g4qjlr75YyFqbGlV2Q--{B?*ERj@$;+A#v6A5% zKJZ-k(ti_bGM?eOj#f{ZHOKc6lK8&sk-j?4Kdjr4z{}bU>r#UnlQAQ!`bOZB;Ct>?+|WpbgrOY2zPu-B!V?-m447=gVK(*?-RQObKSO{281uhZ-RJZ_ax0d9HE}&gAII8uJO7-x&ULT3$-hG# zr#*GDR$v46R``GL&)??M=Z|+Z8{*GeE`I+!{6E}3tWV7S1MCU*9ccYQdsa7AC-|$l z32U8*#-^_c?O%nK{oI ze@n2sr7p*=%uQX9Dk-qo-r2V)7}P0ZWaab6B%)NcuSZfL*ZERjG4kd;J~ udLgV-)@7w`Z&hkIdqAlS_J#Nx!E}|RnRSkVm|Sa24|P&EF^Huxf&C8$As%)B literal 0 HcmV?d00001 diff --git a/devops-service/app/api3/public/swagger-ui/images/logo_small.png b/devops-service/app/api3/public/swagger-ui/images/logo_small.png new file mode 100644 index 0000000000000000000000000000000000000000..5496a65579ae903d4008f9d268fac422ef9d3679 GIT binary patch literal 770 zcmV+d1O5DoP)K11rQipnJ)eVnTSzHNF zN8ab&RhE5cC$$4FI-PZXx$pga@8yN)KS}L2Us~^y$(x-xioWbnFcV+~b9ig=!ft8Q z0RD+rpA8910Smyc0GviVUOPGiY6YM@-r6Nn8S&~cxHl27$l)-R$1(!Xx045RDy;_& zeXkG{;_#i9rz0B6149#Ddj=KM6MV^rTD%ylzGdCBX<^=^@I0X3SCR7OMbn}sUKdeF zKO-flaJa%@kJ27@Rod?J9=+Qx5|=PtG8n> zy~9rIu}+48M}FW5Bbqw3t#po?c?kmG!FX32W(dOjzTb+U@64MzHItoeB!M0Jcd}|E z>ekW`<~FjR_ZVVJkF|_htH&v!({Oad?xax?0K0sLwBY%nr46DpCmIIaa?@|Y&?n0q z@kJlMy`pE2HtEgASNd~xNzt$Kn7w#^Fy5oi`e$bUE*+f>Vk5z7=-2pj68afrqli$_ zvqe##5V?a)QU_-s9+s?mJYT5m`MQDRH4cYs^L1lCW;Dua5Ln9lG0BC@9DJQHA(}y&Z}$apb{kU zbezR}b^|O%6i+$BFsT3zqAe8wg9`vfiRp#{)z2bsJw`vBQL7Bt!IexM3$Hsf0tHK3 z+R=x{lR$K`s;7__?ASPW=3?*xgCpGaiadSEpoi0pw-_V#OXM8Ap{4qlG08x0ig9IY z3Ijqh(t1_=g#jocuqyJO=729e9OSiNDSrhR0Gc5G)(QGH?*IS*07*qoM6N<$f<~fU A82|tP literal 0 HcmV?d00001 diff --git a/devops-service/app/api3/public/swagger-ui/images/pet_store_api.png b/devops-service/app/api3/public/swagger-ui/images/pet_store_api.png new file mode 100644 index 0000000000000000000000000000000000000000..f9f9cd4aeb35a108c4b2f1dddb59977d56c595d8 GIT binary patch literal 824 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 z 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. + * For example - + * + * If you wish to translate some new texsts you should do two things: + * 1. Add a new phrase pair ("New Phrase": "New Translation") into your language file (for example lang/ru.js). It will be great if you add it in other language files too. + * 2. Mark that text it templates this way New Phrase or . + * The main thing here is attribute data-sw-translate. Only inner html, title-attribute and value-attribute are going to translate. + * + */ +window.SwaggerTranslator = { + + _words:[], + + translate: function(sel) { + var $this = this; + sel = sel || '[data-sw-translate]'; + + $(sel).each(function() { + $(this).html($this._tryTranslate($(this).html())); + + $(this).val($this._tryTranslate($(this).val())); + $(this).attr('title', $this._tryTranslate($(this).attr('title'))); + }); + }, + + _tryTranslate: function(word) { + return this._words[$.trim(word)] !== undefined ? this._words[$.trim(word)] : word; + }, + + learn: function(wordsMap) { + this._words = wordsMap; + } +}; diff --git a/devops-service/app/api3/public/swagger-ui/lang/zh-cn.js b/devops-service/app/api3/public/swagger-ui/lang/zh-cn.js new file mode 100644 index 0000000..570319b --- /dev/null +++ b/devops-service/app/api3/public/swagger-ui/lang/zh-cn.js @@ -0,0 +1,53 @@ +'use strict'; + +/* jshint quotmark: double */ +window.SwaggerTranslator.learn({ + "Warning: Deprecated":"警告:已过时", + "Implementation Notes":"实现备注", + "Response Class":"响应类", + "Status":"状态", + "Parameters":"参数", + "Parameter":"参数", + "Value":"值", + "Description":"描述", + "Parameter Type":"参数类型", + "Data Type":"数据类型", + "Response Messages":"响应消息", + "HTTP Status Code":"HTTP状态码", + "Reason":"原因", + "Response Model":"响应模型", + "Request URL":"请求URL", + "Response Body":"响应体", + "Response Code":"响应码", + "Response Headers":"响应头", + "Hide Response":"隐藏响应", + "Headers":"头", + "Try it out!":"试一下!", + "Show/Hide":"显示/隐藏", + "List Operations":"显示操作", + "Expand Operations":"展开操作", + "Raw":"原始", + "can't parse JSON. Raw result":"无法解析JSON. 原始结果", + "Model Schema":"模型架构", + "Model":"模型", + "apply":"应用", + "Username":"用户名", + "Password":"密码", + "Terms of service":"服务条款", + "Created by":"创建者", + "See more at":"查看更多:", + "Contact the developer":"联系开发者", + "api version":"api版本", + "Response Content Type":"响应Content Type", + "fetching resource":"正在获取资源", + "fetching resource list":"正在获取资源列表", + "Explore":"浏览", + "Show Swagger Petstore Example Apis":"显示 Swagger Petstore 示例 Apis", + "Can't read from server. It may not have the appropriate access-control-origin settings.":"无法从服务器读取。可能没有正确设置access-control-origin。", + "Please specify the protocol for":"请指定协议:", + "Can't read swagger JSON from":"无法读取swagger JSON于", + "Finished Loading Resource Information. Rendering Swagger UI":"已加载资源信息。正在渲染Swagger UI", + "Unable to read api":"无法读取api", + "from path":"从路径", + "server returned":"服务器返回" +}); diff --git a/devops-service/app/api3/public/swagger-ui/lib/backbone-min.js b/devops-service/app/api3/public/swagger-ui/lib/backbone-min.js new file mode 100644 index 0000000..a3f544b --- /dev/null +++ b/devops-service/app/api3/public/swagger-ui/lib/backbone-min.js @@ -0,0 +1,15 @@ +// Backbone.js 1.1.2 + +(function(t,e){if(typeof define==="function"&&define.amd){define(["underscore","jquery","exports"],function(i,r,s){t.Backbone=e(t,s,i,r)})}else if(typeof exports!=="undefined"){var i=require("underscore");e(t,exports,i)}else{t.Backbone=e(t,{},t._,t.jQuery||t.Zepto||t.ender||t.$)}})(this,function(t,e,i,r){var s=t.Backbone;var n=[];var a=n.push;var o=n.slice;var h=n.splice;e.VERSION="1.1.2";e.$=r;e.noConflict=function(){t.Backbone=s;return this};e.emulateHTTP=false;e.emulateJSON=false;var u=e.Events={on:function(t,e,i){if(!c(this,"on",t,[e,i])||!e)return this;this._events||(this._events={});var r=this._events[t]||(this._events[t]=[]);r.push({callback:e,context:i,ctx:i||this});return this},once:function(t,e,r){if(!c(this,"once",t,[e,r])||!e)return this;var s=this;var n=i.once(function(){s.off(t,n);e.apply(this,arguments)});n._callback=e;return this.on(t,n,r)},off:function(t,e,r){var s,n,a,o,h,u,l,f;if(!this._events||!c(this,"off",t,[e,r]))return this;if(!t&&!e&&!r){this._events=void 0;return this}o=t?[t]:i.keys(this._events);for(h=0,u=o.length;h").attr(t);this.setElement(r,false)}else{this.setElement(i.result(this,"el"),false)}}});e.sync=function(t,r,s){var n=T[t];i.defaults(s||(s={}),{emulateHTTP:e.emulateHTTP,emulateJSON:e.emulateJSON});var a={type:n,dataType:"json"};if(!s.url){a.url=i.result(r,"url")||M()}if(s.data==null&&r&&(t==="create"||t==="update"||t==="patch")){a.contentType="application/json";a.data=JSON.stringify(s.attrs||r.toJSON(s))}if(s.emulateJSON){a.contentType="application/x-www-form-urlencoded";a.data=a.data?{model:a.data}:{}}if(s.emulateHTTP&&(n==="PUT"||n==="DELETE"||n==="PATCH")){a.type="POST";if(s.emulateJSON)a.data._method=n;var o=s.beforeSend;s.beforeSend=function(t){t.setRequestHeader("X-HTTP-Method-Override",n);if(o)return o.apply(this,arguments)}}if(a.type!=="GET"&&!s.emulateJSON){a.processData=false}if(a.type==="PATCH"&&k){a.xhr=function(){return new ActiveXObject("Microsoft.XMLHTTP")}}var h=s.xhr=e.ajax(i.extend(a,s));r.trigger("request",r,h,s);return h};var k=typeof window!=="undefined"&&!!window.ActiveXObject&&!(window.XMLHttpRequest&&(new XMLHttpRequest).dispatchEvent);var T={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};e.ajax=function(){return e.$.ajax.apply(e.$,arguments)};var $=e.Router=function(t){t||(t={});if(t.routes)this.routes=t.routes;this._bindRoutes();this.initialize.apply(this,arguments)};var S=/\((.*?)\)/g;var H=/(\(\?)?:\w+/g;var A=/\*\w+/g;var I=/[\-{}\[\]+?.,\\\^$|#\s]/g;i.extend($.prototype,u,{initialize:function(){},route:function(t,r,s){if(!i.isRegExp(t))t=this._routeToRegExp(t);if(i.isFunction(r)){s=r;r=""}if(!s)s=this[r];var n=this;e.history.route(t,function(i){var a=n._extractParameters(t,i);n.execute(s,a);n.trigger.apply(n,["route:"+r].concat(a));n.trigger("route",r,a);e.history.trigger("route",n,r,a)});return this},execute:function(t,e){if(t)t.apply(this,e)},navigate:function(t,i){e.history.navigate(t,i);return this},_bindRoutes:function(){if(!this.routes)return;this.routes=i.result(this,"routes");var t,e=i.keys(this.routes);while((t=e.pop())!=null){this.route(t,this.routes[t])}},_routeToRegExp:function(t){t=t.replace(I,"\\$&").replace(S,"(?:$1)?").replace(H,function(t,e){return e?t:"([^/?]+)"}).replace(A,"([^?]*?)");return new RegExp("^"+t+"(?:\\?([\\s\\S]*))?$")},_extractParameters:function(t,e){var r=t.exec(e).slice(1);return i.map(r,function(t,e){if(e===r.length-1)return t||null;return t?decodeURIComponent(t):null})}});var N=e.History=function(){this.handlers=[];i.bindAll(this,"checkUrl");if(typeof window!=="undefined"){this.location=window.location;this.history=window.history}};var R=/^[#\/]|\s+$/g;var O=/^\/+|\/+$/g;var P=/msie [\w.]+/;var C=/\/$/;var j=/#.*$/;N.started=false;i.extend(N.prototype,u,{interval:50,atRoot:function(){return this.location.pathname.replace(/[^\/]$/,"$&/")===this.root},getHash:function(t){var e=(t||this).location.href.match(/#(.*)$/);return e?e[1]:""},getFragment:function(t,e){if(t==null){if(this._hasPushState||!this._wantsHashChange||e){t=decodeURI(this.location.pathname+this.location.search);var i=this.root.replace(C,"");if(!t.indexOf(i))t=t.slice(i.length)}else{t=this.getHash()}}return t.replace(R,"")},start:function(t){if(N.started)throw new Error("Backbone.history has already been started");N.started=true;this.options=i.extend({root:"/"},this.options,t);this.root=this.options.root;this._wantsHashChange=this.options.hashChange!==false;this._wantsPushState=!!this.options.pushState;this._hasPushState=!!(this.options.pushState&&this.history&&this.history.pushState);var r=this.getFragment();var s=document.documentMode;var n=P.exec(navigator.userAgent.toLowerCase())&&(!s||s<=7);this.root=("/"+this.root+"/").replace(O,"/");if(n&&this._wantsHashChange){var a=e.$('