foreman_acd 0.4.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +84 -84
- data/app/controllers/foreman_acd/ansible_playbooks_controller.rb +103 -11
- data/app/controllers/foreman_acd/api/v2/ansible_playbooks_controller.rb +21 -3
- data/app/controllers/foreman_acd/api/v2/app_definitions_controller.rb +1 -0
- data/app/controllers/foreman_acd/api/v2/app_instances_controller.rb +9 -1
- data/app/controllers/foreman_acd/app_definitions_controller.rb +117 -15
- data/app/controllers/foreman_acd/app_instances_controller.rb +104 -30
- data/app/controllers/foreman_acd/concerns/ansible_playbook_parameters.rb +1 -1
- data/app/controllers/foreman_acd/concerns/app_definition_parameters.rb +1 -1
- data/app/controllers/foreman_acd/concerns/app_instance_mixins.rb +36 -0
- data/app/controllers/foreman_acd/concerns/app_instance_parameters.rb +1 -1
- data/app/controllers/foreman_acd/remote_execution_controller.rb +36 -23
- data/app/controllers/ui_acd_controller.rb +46 -0
- data/app/lib/actions/foreman_acd/deploy_all_hosts.rb +47 -0
- data/app/lib/actions/foreman_acd/run_configurator.rb +45 -0
- data/app/models/concerns/foreman_acd/host_managed_extensions.rb +39 -0
- data/app/models/foreman_acd/acd_provider.rb +36 -0
- data/app/models/foreman_acd/ansible_playbook.rb +32 -14
- data/app/models/foreman_acd/app_definition.rb +24 -1
- data/app/models/foreman_acd/app_instance.rb +85 -5
- data/app/models/foreman_acd/foreman_host.rb +31 -0
- data/app/models/foreman_acd/taxonomy_extensions.rb +17 -0
- data/app/services/foreman_acd/acd_proxy_proxy_selector.rb +17 -0
- data/app/services/foreman_acd/app_configurator.rb +64 -36
- data/app/services/foreman_acd/app_deployer.rb +83 -48
- data/app/services/foreman_acd/inventory_creator.rb +36 -25
- data/app/views/foreman_acd/ansible_playbooks/_form.html.erb +50 -7
- data/app/views/foreman_acd/ansible_playbooks/edit.html.erb +9 -1
- data/app/views/foreman_acd/ansible_playbooks/index.html.erb +3 -3
- data/app/views/foreman_acd/api/v2/ansible_playbooks/base.json.rabl +2 -0
- data/app/views/foreman_acd/api/v2/ansible_playbooks/index.json.rabl +2 -0
- data/app/views/foreman_acd/api/v2/ansible_playbooks/show.json.rabl +6 -0
- data/app/views/foreman_acd/api/v2/app_definitions/base.json.rabl +2 -0
- data/app/views/foreman_acd/api/v2/app_definitions/index.json.rabl +2 -0
- data/app/views/foreman_acd/api/v2/app_definitions/show.json.rabl +6 -0
- data/app/views/foreman_acd/api/v2/app_instances/base.json.rabl +3 -1
- data/app/views/foreman_acd/api/v2/app_instances/index.json.rabl +2 -0
- data/app/views/foreman_acd/api/v2/app_instances/show.json.rabl +2 -0
- data/app/views/foreman_acd/app_definitions/_form.html.erb +9 -1
- data/app/views/foreman_acd/app_definitions/edit.html.erb +10 -5
- data/app/views/foreman_acd/app_definitions/import.html.erb +20 -1
- data/app/views/foreman_acd/app_definitions/index.html.erb +5 -8
- data/app/views/foreman_acd/app_instances/_form.html.erb +4 -4
- data/app/views/foreman_acd/app_instances/edit.html.erb +10 -0
- data/app/views/foreman_acd/app_instances/index.html.erb +93 -14
- data/app/views/foreman_acd/app_instances/report.html.erb +12 -4
- data/app/views/templates/job/run_acd_ansible_playbook.erb +28 -15
- data/app/views/ui_acd/app_definition.json.rabl +1 -1
- data/app/views/ui_acd/host_report.json.rabl +4 -0
- data/app/views/ui_acd/report_data.json.rabl +10 -0
- data/app/views/ui_acd/validate_hostname.json.rabl +6 -0
- data/config/routes.rb +12 -3
- data/db/migrate/20200917120220_add_ansible_playbook_id.rb +1 -1
- data/db/migrate/20201016002819_add_ansible_vars_all_to_app_definitions.rb +3 -0
- data/db/migrate/20201016104338_add_ansible_vars_all_to_app_instances.rb +3 -0
- data/db/migrate/20210112111548_add_organization_to_app_instance.rb +22 -0
- data/db/migrate/20210112113853_add_location_to_app_instance.rb +8 -0
- data/db/migrate/20210202141658_create_foreman_hosts.rb +24 -0
- data/db/migrate/20210204111306_remove_hosts_from_app_instances.rb +8 -0
- data/db/migrate/20210209091014_rename_acd_tables.rb +16 -0
- data/db/migrate/20210216083522_add_last_progress_report.rb +8 -0
- data/db/migrate/20210216091529_add_last_deploy_task.rb +8 -0
- data/db/migrate/20210316151145_add_git_commit_to_ansible_playbooks.rb +8 -0
- data/db/migrate/20210503122809_add_git_url_to_ansible_playbooks.rb +8 -0
- data/db/migrate/20210818125913_add_is_existing_host_to_foreman_host.rb +8 -0
- data/db/migrate/20210902110645_add_initial_configure_task.rb +8 -0
- data/db/seeds.d/62_acd_proxy_feature.rb +4 -0
- data/db/seeds.d/75-job_templates.rb +6 -1
- data/lib/foreman_acd/engine.rb +40 -3
- data/lib/foreman_acd/plugin.rb +60 -45
- data/lib/foreman_acd/version.rb +1 -1
- data/lib/foreman_acd.rb +30 -0
- data/lib/tasks/foreman_acd_tasks.rake +0 -12
- data/locale/en/foreman_acd.edit.po +326 -0
- data/locale/en/foreman_acd.po +232 -2
- data/{app/controllers/foreman_acd/api/v2/app_playbooks_controller.rb → locale/en/foreman_acd.po.time_stamp} +0 -0
- data/locale/foreman_acd.pot +343 -8
- data/package.json +8 -8
- data/test/controllers/ansible_playbooks_controller_test.rb +27 -0
- data/test/controllers/app_instances_controller_test.rb +8 -3
- data/test/controllers/ui_acd_controller_test.rb +22 -6
- data/test/factories/foreman_acd_factories.rb +18 -4
- data/test/models/acd_provider_test.rb +37 -0
- data/test/models/ansible_playbook_test.rb +11 -0
- data/test/models/app_definition_test.rb +1 -1
- data/test/models/app_instance_test.rb +2 -0
- data/test/models/concerns/host_extensions_test.rb +26 -0
- data/test/models/foreman_host_test.rb +12 -0
- data/webpack/__mocks__/foremanReact/API.js +2 -0
- data/webpack/__mocks__/foremanReact/common/I18n.js +3 -0
- data/webpack/__mocks__/foremanReact/common/helpers.js +2 -0
- data/webpack/__mocks__/foremanReact/components/ForemanModal/ForemanModalActions.js +2 -0
- data/webpack/__mocks__/foremanReact/components/ForemanModal.js +7 -0
- data/webpack/__mocks__/foremanReact/components/common/forms/CommonForm.js +2 -0
- data/webpack/__mocks__/foremanReact/components/common/forms/TextInput.js +2 -0
- data/webpack/__mocks__/foremanReact/components/hosts/powerStatus.js +1 -0
- data/webpack/__snapshots__/helper.test.js.snap +14 -0
- data/webpack/components/ApplicationDefinition/ApplicationDefinition.js +55 -21
- data/webpack/components/ApplicationDefinition/ApplicationDefinitionActions.js +14 -0
- data/webpack/components/ApplicationDefinition/ApplicationDefinitionConstants.js +2 -0
- data/webpack/components/ApplicationDefinition/ApplicationDefinitionReducer.js +48 -1
- data/webpack/components/ApplicationDefinition/ApplicationDefinitionSelectors.js +4 -0
- data/webpack/components/ApplicationDefinition/__fixtures__/applicationDefinitionConfData_1.fixtures.js +288 -0
- data/webpack/components/ApplicationDefinition/__fixtures__/applicationDefinitionReducer.fixtures.js +79 -0
- data/webpack/components/ApplicationDefinition/__tests__/ApplicationDefinition.test.js +26 -0
- data/webpack/components/ApplicationDefinition/__tests__/ApplicationDefinitionReducer.test.js +119 -0
- data/webpack/components/ApplicationDefinition/__tests__/ApplicationDefinitionSelectors.test.js +41 -0
- data/webpack/components/ApplicationDefinition/__tests__/__snapshots__/ApplicationDefinition.test.js.snap +225 -0
- data/webpack/components/ApplicationDefinition/__tests__/__snapshots__/ApplicationDefinitionReducer.test.js.snap +3033 -0
- data/webpack/components/ApplicationDefinition/__tests__/__snapshots__/ApplicationDefinitionSelectors.test.js.snap +299 -0
- data/webpack/components/ApplicationDefinition/components/AnsiblePlaybookSelector.js +2 -1
- data/webpack/components/ApplicationDefinition/components/__tests__/AnsiblePlaybookSelector.test.js +41 -0
- data/webpack/components/ApplicationDefinition/components/__tests__/__snapshots__/AnsiblePlaybookSelector.test.js.snap +121 -0
- data/webpack/components/ApplicationDefinition/index.js +8 -0
- data/webpack/components/ApplicationDefinitionImport/ApplicationDefinitionImport.js +214 -0
- data/webpack/components/ApplicationDefinitionImport/ApplicationDefinitionImport.scss +1 -0
- data/webpack/components/ApplicationDefinitionImport/ApplicationDefinitionImportActions.js +161 -0
- data/webpack/components/ApplicationDefinitionImport/ApplicationDefinitionImportConstants.js +6 -0
- data/webpack/components/ApplicationDefinitionImport/ApplicationDefinitionImportReducer.js +79 -0
- data/webpack/components/ApplicationDefinitionImport/ApplicationDefinitionImportSelectors.js +8 -0
- data/webpack/components/ApplicationDefinitionImport/__fixtures__/applicationDefinitionImportConfData_1.fixtures.js +129 -0
- data/webpack/components/ApplicationDefinitionImport/__fixtures__/applicationDefinitionImportReducer.fixtures.js +29 -0
- data/webpack/components/ApplicationDefinitionImport/__tests__/ApplicationDefinitionImport.test.js +20 -0
- data/webpack/components/ApplicationDefinitionImport/__tests__/ApplicationDefinitionImportReducer.test.js +43 -0
- data/webpack/components/ApplicationDefinitionImport/__tests__/ApplicationDefinitionImportSelectors.test.js +29 -0
- data/webpack/components/ApplicationDefinitionImport/__tests__/__snapshots__/ApplicationDefinitionImport.test.js.snap +62 -0
- data/webpack/components/ApplicationDefinitionImport/__tests__/__snapshots__/ApplicationDefinitionImportReducer.test.js.snap +362 -0
- data/webpack/components/ApplicationDefinitionImport/__tests__/__snapshots__/ApplicationDefinitionImportSelectors.test.js.snap +130 -0
- data/webpack/components/ApplicationDefinitionImport/index.js +32 -0
- data/webpack/components/ApplicationInstance/ApplicationInstance.js +153 -45
- data/webpack/components/ApplicationInstance/ApplicationInstanceActions.js +120 -6
- data/webpack/components/ApplicationInstance/ApplicationInstanceConstants.js +5 -0
- data/webpack/components/ApplicationInstance/ApplicationInstanceHelper.js +15 -0
- data/webpack/components/ApplicationInstance/ApplicationInstanceReducer.js +77 -22
- data/webpack/components/ApplicationInstance/ApplicationInstanceSelectors.js +4 -0
- data/webpack/components/ApplicationInstance/__fixtures__/applicationInstanceConfData_1.fixtures.js +263 -0
- data/webpack/components/ApplicationInstance/__fixtures__/applicationInstanceReducer.fixtures.js +80 -0
- data/webpack/components/ApplicationInstance/__tests__/ApplicationInstance.test.js +24 -0
- data/webpack/components/ApplicationInstance/__tests__/ApplicationInstanceReducer.test.js +131 -0
- data/webpack/components/ApplicationInstance/__tests__/ApplicationInstanceSelectors.test.js +44 -0
- data/webpack/components/ApplicationInstance/__tests__/__snapshots__/ApplicationInstance.test.js.snap +299 -0
- data/webpack/components/ApplicationInstance/__tests__/__snapshots__/ApplicationInstanceReducer.test.js.snap +2990 -0
- data/webpack/components/ApplicationInstance/__tests__/__snapshots__/ApplicationInstanceSelectors.test.js.snap +276 -0
- data/webpack/components/ApplicationInstance/components/AppDefinitionSelector.js +1 -0
- data/webpack/components/ApplicationInstance/components/Service.js +1 -1
- data/webpack/components/ApplicationInstance/components/ServiceCounter.js +1 -1
- data/webpack/components/ApplicationInstance/helper.js +0 -0
- data/webpack/components/ApplicationInstance/index.js +8 -0
- data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReport.js +128 -60
- data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReport.scss +17 -0
- data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReportActions.js +40 -50
- data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReportConstants.js +5 -4
- data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReportReducer.js +19 -14
- data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReportSelectors.js +4 -1
- data/webpack/components/ApplicationInstanceReport/__fixtures__/applicationInstanceReportData_1.fixtures.js +349 -0
- data/webpack/components/ApplicationInstanceReport/__fixtures__/applicationInstanceReportReducer.fixtures.js +20 -0
- data/webpack/components/ApplicationInstanceReport/__tests__/ApplicationInstanceReport.test.js +47 -0
- data/webpack/components/ApplicationInstanceReport/__tests__/ApplicationInstanceReportReducer.test.js +41 -0
- data/webpack/components/ApplicationInstanceReport/__tests__/ApplicationInstanceReportSelectors.test.js +26 -0
- data/webpack/components/ApplicationInstanceReport/__tests__/__snapshots__/ApplicationInstanceReport.test.js.snap +7 -0
- data/webpack/components/ApplicationInstanceReport/__tests__/__snapshots__/ApplicationInstanceReportReducer.test.js.snap +718 -0
- data/webpack/components/ApplicationInstanceReport/__tests__/__snapshots__/ApplicationInstanceReportSelectors.test.js.snap +347 -0
- data/webpack/components/ApplicationInstanceReport/components/ReportViewer.js +1 -1
- data/webpack/components/ApplicationInstanceReport/components/__tests__/ReportViewer.test.js +24 -0
- data/webpack/components/ApplicationInstanceReport/components/__tests__/__snapshots__/ReportViewer.test.js.snap +24 -0
- data/webpack/components/ApplicationInstanceReport/index.js +8 -3
- data/webpack/components/ExistingHostSelection/ExistingHostSelection.js +104 -0
- data/webpack/components/ExistingHostSelection/ExistingHostSelection.scss +15 -0
- data/webpack/components/ExistingHostSelection/ExistingHostSelectionActions.js +71 -0
- data/webpack/components/ExistingHostSelection/ExistingHostSelectionConstants.js +4 -0
- data/webpack/components/ExistingHostSelection/ExistingHostSelectionHelper.js +0 -0
- data/webpack/components/ExistingHostSelection/ExistingHostSelectionReducer.js +90 -0
- data/webpack/components/ExistingHostSelection/ExistingHostSelectionSelectors.js +8 -0
- data/webpack/components/ExistingHostSelection/__fixtures__/existingHostSelectionConfData_1.fixtures.js +191 -0
- data/webpack/components/ExistingHostSelection/__fixtures__/existingHostSelectionReducer.fixtures.js +203 -0
- data/webpack/components/ExistingHostSelection/__tests__/ExistingHostSelection.test.js +19 -0
- data/webpack/components/ExistingHostSelection/__tests__/ExistingHostSelectionReducer.test.js +59 -0
- data/webpack/components/ExistingHostSelection/__tests__/ExistingHostSelectionSelectors.test.js +36 -0
- data/webpack/components/ExistingHostSelection/__tests__/__snapshots__/ExistingHostSelection.test.js.snap +35 -0
- data/webpack/components/ExistingHostSelection/__tests__/__snapshots__/ExistingHostSelectionReducer.test.js.snap +614 -0
- data/webpack/components/ExistingHostSelection/__tests__/__snapshots__/ExistingHostSelectionSelectors.test.js.snap +27 -0
- data/webpack/components/ExistingHostSelection/components/ServiceSelector.js +48 -0
- data/webpack/components/ExistingHostSelection/components/__tests__/ServiceSelector.test.js +35 -0
- data/webpack/components/ExistingHostSelection/components/__tests__/__snapshots__/ServiceSelector.test.js.snap +77 -0
- data/webpack/components/ExistingHostSelection/index.js +26 -0
- data/webpack/components/ParameterSelection/ParameterSelection.js +138 -15
- data/webpack/components/ParameterSelection/ParameterSelection.scss +7 -0
- data/webpack/components/ParameterSelection/ParameterSelectionActions.js +52 -9
- data/webpack/components/ParameterSelection/ParameterSelectionConstants.js +3 -0
- data/webpack/components/ParameterSelection/ParameterSelectionReducer.js +62 -25
- data/webpack/components/ParameterSelection/ParameterSelectionSelectors.js +1 -0
- data/webpack/components/ParameterSelection/__fixtures__/parameterSelectionData_1.fixtures.js +116 -84
- data/webpack/components/ParameterSelection/__fixtures__/parameterSelectionReducer.fixtures.js +10 -4
- data/webpack/components/ParameterSelection/__tests__/ParameterSelection.test.js +36 -46
- data/webpack/components/ParameterSelection/__tests__/ParameterSelectionReducer.test.js +33 -25
- data/webpack/components/ParameterSelection/__tests__/ParameterSelectionSelectors.test.js +6 -6
- data/webpack/components/ParameterSelection/__tests__/__snapshots__/ParameterSelection.test.js.snap +84 -112
- data/webpack/components/ParameterSelection/__tests__/__snapshots__/ParameterSelectionReducer.test.js.snap +1488 -872
- data/webpack/components/ParameterSelection/__tests__/__snapshots__/ParameterSelectionSelectors.test.js.snap +117 -79
- data/webpack/components/ParameterSelection/index.js +2 -1
- data/webpack/components/SyncGitRepo/SyncGitRepo.js +202 -0
- data/webpack/components/SyncGitRepo/SyncGitRepo.scss +1 -0
- data/webpack/components/SyncGitRepo/SyncGitRepoActions.js +123 -0
- data/webpack/components/SyncGitRepo/SyncGitRepoConstants.js +8 -0
- data/webpack/components/SyncGitRepo/SyncGitRepoReducer.js +80 -0
- data/webpack/components/SyncGitRepo/SyncGitRepoSelectors.js +6 -0
- data/webpack/components/SyncGitRepo/__fixtures__/syncGitRepoConfData_1.fixtures.js +7 -0
- data/webpack/components/SyncGitRepo/__fixtures__/syncGitRepoReducer.fixtures.js +44 -0
- data/webpack/components/SyncGitRepo/__tests__/SyncGitRepo.test.js +27 -0
- data/webpack/components/SyncGitRepo/__tests__/SyncGitRepoReducer.test.js +95 -0
- data/webpack/components/SyncGitRepo/__tests__/SyncGitRepoSelectors.test.js +32 -0
- data/webpack/components/SyncGitRepo/__tests__/__snapshots__/SyncGitRepo.test.js.snap +31 -0
- data/webpack/components/SyncGitRepo/__tests__/__snapshots__/SyncGitRepoReducer.test.js.snap +137 -0
- data/webpack/components/SyncGitRepo/__tests__/__snapshots__/SyncGitRepoSelectors.test.js.snap +13 -0
- data/webpack/components/SyncGitRepo/components/FormTextInput.js +42 -0
- data/webpack/components/SyncGitRepo/components/ScmTypeSelector.js +47 -0
- data/webpack/components/SyncGitRepo/index.js +28 -0
- data/webpack/components/common/DeleteTableEntry.js +18 -4
- data/webpack/components/common/EditTableEntry.js +50 -0
- data/webpack/components/common/ExtTextInput.js +43 -0
- data/webpack/components/common/LockTableEntry.js +60 -0
- data/webpack/components/common/__tests__/EditTableEntry.test.js +53 -0
- data/webpack/components/common/__tests__/LockTableEntry.test.js +35 -0
- data/webpack/components/common/__tests__/__snapshots__/DeleteTableEntry.test.js.snap +40 -2
- data/webpack/components/common/__tests__/__snapshots__/EditTableEntry.test.js.snap +81 -0
- data/webpack/components/common/__tests__/__snapshots__/LockTableEntry.test.js.snap +60 -0
- data/webpack/helper.js +20 -1
- data/webpack/helper.test.js +37 -0
- data/webpack/index.js +7 -0
- data/webpack/js-yaml.js +3874 -0
- data/webpack/reducer.js +16 -1
- metadata +182 -11
- data/app/views/foreman_acd/app_instances/deploy.html.erb +0 -19
- data/webpack/components/common/EasyHeaderFormatter.js +0 -18
- data/webpack/components/common/__tests__/__snapshots__/AddParameter.test.js.snap +0 -35
- data/webpack/components/common/__tests__/__snapshots__/DeleteParameter.test.js.snap +0 -41
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ca6ffd04ba17f399f8ecbf17615fde7ba6391cf389e16d83e427adb2c15cff9f
|
|
4
|
+
data.tar.gz: 9de27331a51e5bb6887250b01e40d6557fb9e5f66f96005f16e8d5fb2e6999f8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ee8e1c5f4b7d842ce1f153c282b74a07a83edd6405e0b0ba7476602bd0c225bf8fefdb7e19aacfd2d79033f0c5429bc675e9c0dd1d794b2f1cfbe9d91764e9ed
|
|
7
|
+
data.tar.gz: 1d74484d949e080478432b0d76cba89b87fde3eda56a1d6cf7c69dc2207cdca2308a73b9f11de6a25d5713b2de12a546423b3d251d793925f55c33440cad108c
|
data/README.md
CHANGED
|
@@ -1,142 +1,142 @@
|
|
|
1
|
-
[](https://travis-ci.org/ATIX-AG/foreman_acd)
|
|
2
|
-
|
|
3
1
|
# Foreman Application Centric Deployment
|
|
4
2
|
|
|
5
|
-
A plugin
|
|
6
|
-
|
|
3
|
+
A Foreman plugin providing application centric deployment and a self-service portal.
|
|
7
4
|
|
|
8
|
-
#
|
|
5
|
+
# Introduction
|
|
9
6
|
|
|
10
|
-
The target of this plugin is
|
|
11
|
-
configure them using an Ansible Playbook / saltstack state.
|
|
7
|
+
The target of this plugin is to deploy whole applications which include multiple hosts and configure them using an Ansible playbook.
|
|
12
8
|
|
|
13
9
|
This plugin follows the idea of different user types working together.
|
|
14
|
-
The
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
The _administrative user_ creates Application Definitions including multiple servers and configuration management items (Ansible Playbooks).
|
|
11
|
+
The _user_ can create and deploy new Application Instances with an easy to use self-service portal.
|
|
12
|
+
|
|
13
|
+
*Example Application Definition*
|
|
14
|
+
To run a complex web application, a load balancer is required.
|
|
15
|
+
The load balancer routes the requests to three different web servers.
|
|
16
|
+
The web servers are using a database which runs in high availability mode on two hosts.
|
|
17
|
+
Therefore, six hosts in total are required.
|
|
18
|
+
|
|
19
|
+
This plugin aims to setup all six hosts and to deploy the application.
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
The loadbalancer routes the requests to 3 different web servers.
|
|
20
|
-
The web servers are using a database which is in high availability mode on 2 hosts.
|
|
21
|
-
=> 6 hosts are required.
|
|
21
|
+
# Architecture
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
## Ansible Playbooks
|
|
24
24
|
|
|
25
|
+
* Specify the path on your Foreman server to the Ansible playbook and playfile
|
|
26
|
+
* Read groups configured in the Ansible playbook
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
## Application Definitions
|
|
27
29
|
|
|
28
|
-
|
|
30
|
+
* Use the configured Ansible playbook in an Application Definition
|
|
31
|
+
* Overwrite group variables of the Ansible playbook for the Application Definition
|
|
32
|
+
* Set Foreman parameters in the Application Definition
|
|
33
|
+
* Setup various services like web servers, database servers, etc.
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
- Use the configured ansible playbook in a Application Definition
|
|
32
|
-
- Overwrite ansible playbook's group variables for the Application Definition
|
|
33
|
-
- Set foreman parameters in the Application Definition
|
|
34
|
-
- Setup various services like webservers, database-servers, etc.
|
|
35
|
+
## Application Instances
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
* Use an Application Definition for your Application Instance
|
|
38
|
+
* Configure specific hosts which use the Application Definition's services
|
|
39
|
+
* Deploy these hosts
|
|
40
|
+
* Configure the hosts using the configured Ansible playbook
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
- Configure specific hosts which uses the Application Definitions services
|
|
40
|
-
- Deploy these hosts
|
|
41
|
-
- Configure the hosts using the configured ansible playbook
|
|
42
|
+
# How It Works
|
|
42
43
|
|
|
44
|
+
* Configure an Ansible playbook, an Application Definition, and create an Application Instance.
|
|
45
|
+
* All hosts are created when deploying the Application Instance.
|
|
46
|
+
* After provisioning, the hosts are configured with the linked Ansible playbook.
|
|
47
|
+
* This uses the [Smart Proxy ACD](https://github.com/ATIX-AG/smart_proxy_acd) component.
|
|
48
|
+
* The job to configure the hosts will be send to the Smart Proxy ACD component which will
|
|
49
|
+
* download the Ansible playbook from your Foreman server (provided by an foreman_acd API);
|
|
50
|
+
* extract the Ansible playbook on the Smart Proxy;
|
|
51
|
+
* and run the Ansible playbook on the Smart Proxy.
|
|
52
|
+
* You can see the output of the Ansible playbook run on the *Monitor > Job* page.
|
|
43
53
|
|
|
44
|
-
|
|
45
|
-
-
|
|
46
|
-
- Add Application deployment with single host requirements
|
|
47
|
-
- Add Application deployment with multi host requirements
|
|
54
|
+
:warning: This plugin is still in development.
|
|
48
55
|
|
|
49
|
-
##
|
|
56
|
+
## Documentation
|
|
50
57
|
|
|
51
|
-
|
|
58
|
+
See [Application Centric Deployment Guide](https://docs.theforeman.org/nightly/Application_Centric_Deployment/index-foreman-el.html)
|
|
52
59
|
|
|
53
60
|
## Installation
|
|
54
61
|
|
|
55
|
-
See [
|
|
56
|
-
for how to install Foreman plugins.
|
|
62
|
+
See the [installation](https://theforeman.org/plugins/#2.Installation) chapter of the Foreman plugins documentation on how to install Foreman plugins.
|
|
57
63
|
|
|
58
64
|
### TL;DR:
|
|
59
65
|
|
|
60
66
|
yum install tfm-rubygem-foreman_acd
|
|
61
|
-
foreman-maintain service restart
|
|
67
|
+
foreman-maintain service restart
|
|
62
68
|
|
|
63
|
-
In some cases you need to
|
|
69
|
+
In some cases, you need to manually run
|
|
64
70
|
|
|
65
71
|
foreman-rake db:migrate
|
|
66
72
|
foreman-rake db:seed
|
|
67
73
|
|
|
68
|
-
###
|
|
74
|
+
### Smart Proxy Installation
|
|
69
75
|
|
|
70
|
-
|
|
76
|
+
You will need to install [Smart Proxy ACD](https://github.com/ATIX-AG/smart_proxy_acd), too.
|
|
71
77
|
|
|
72
|
-
|
|
78
|
+
yum install tfm-rubygem-smart_proxy_acd tfm-rubygem-smart_proxy_acd_core
|
|
79
|
+
foreman-maintain service restart
|
|
73
80
|
|
|
74
|
-
|
|
81
|
+
You need to refresh the smart proxy features in *Infrastructure > Smart Proxies > Your Smart-Proxy > Actions > Refresh* after the installation of the Smart Proxy ACD components.
|
|
75
82
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
Make sure
|
|
83
|
+
### Tips
|
|
84
|
+
|
|
85
|
+
* Make sure you have the [Katello](https://theforeman.org/plugins/katello/) plugin installed.
|
|
86
|
+
* Make sure the Job Template `Run ACD Ansible Playbook - ACD Default` is part of your organization/location context.
|
|
79
87
|
|
|
80
88
|
## Usage
|
|
81
89
|
|
|
82
90
|
### Ansible Playbook
|
|
83
91
|
|
|
84
|
-
* Copy (or checkout a git repository)
|
|
85
|
-
|
|
86
|
-
*
|
|
87
|
-
*
|
|
92
|
+
* Copy (or checkout a git repository) an Ansible playbook.
|
|
93
|
+
Store it in `/var/lib/foreman/foreman_acd/ansible-playbooks/` so that SELinux is able to read it.
|
|
94
|
+
* Add a new Ansible Playbook via *Applications > Ansible Playbooks*.
|
|
95
|
+
* Specify the path to the Ansible playbook and name of the playbook file. (e.g. `site.yml`).
|
|
96
|
+
* Save it and press *Import group variables* for this newly created Ansible playbook.
|
|
88
97
|
|
|
89
|
-
### Application Definition (
|
|
98
|
+
### Application Definition (for Admins)
|
|
90
99
|
|
|
91
|
-
* Create an Application Definition
|
|
100
|
+
* Create an Application Definition via *Applications > Application Definitions*.
|
|
92
101
|
* Select the Ansible Playbook you want to use.
|
|
93
|
-
* Add new services and
|
|
94
|
-
*
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
### Application Instance (User)
|
|
102
|
+
* Add new services and specify the host group you want to use.
|
|
103
|
+
* Specify any values a user will be allowed to overwrite.
|
|
104
|
+
You may also set a default value.
|
|
98
105
|
|
|
99
|
-
|
|
100
|
-
* Select the Application Definition which should be used
|
|
101
|
-
* Set the values.
|
|
102
|
-
Remember, all parameters need to have a value.
|
|
103
|
-
* Save the Application Instance
|
|
106
|
+
### Application Instance (for Users)
|
|
104
107
|
|
|
105
|
-
|
|
108
|
+
* Create an Application Instance via *Applications > Application Instances*.
|
|
109
|
+
* Select the Application Definition you want to use.
|
|
110
|
+
* Overwrite desired values.
|
|
111
|
+
All Foreman parameters require a value.
|
|
112
|
+
* Save the Application Instance.
|
|
106
113
|
|
|
107
|
-
|
|
108
|
-
on the Application Instance index site.
|
|
109
|
-
* See if the hosts are deployed via action selection dropdown -> report.
|
|
110
|
-
* After all hosts are deployed, run the ansible playbook via
|
|
111
|
-
action selection dropdown -> Run ansible playbook
|
|
114
|
+
### Deploy and Configure the Application Instance (for Users)
|
|
112
115
|
|
|
116
|
+
* Select *Deploy* in the action drop down menu via *Applications > Application Instance* to deploy a host.
|
|
117
|
+
* Verify if the hosts are deployed via the *Report* button from the action drop down menu.
|
|
118
|
+
* Run the Ansible playbook via *Run Ansible playbook* from the action drop down menu after all hosts are deployed.
|
|
113
119
|
|
|
114
120
|
## TODO
|
|
115
121
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
- Deliver ansible playbooks to the connected foreman smart proxies
|
|
121
|
-
|
|
122
|
+
* Add `git` support for the Ansible playbooks.
|
|
123
|
+
* Provide application templates which contains application definition and the required Ansible-playbook.
|
|
124
|
+
* Add Saltstack support to configure the application.
|
|
125
|
+
* Extend the Foreman parameter and value validation.
|
|
122
126
|
|
|
123
127
|
## Contributing
|
|
124
128
|
|
|
125
|
-
Fork and send a Pull Request.
|
|
129
|
+
Fork and send a Pull Request.
|
|
130
|
+
Thanks!
|
|
126
131
|
|
|
127
132
|
## Copyright
|
|
128
133
|
|
|
129
|
-
Copyright (c)
|
|
134
|
+
Copyright (c) 2021 ATIX AG
|
|
130
135
|
|
|
131
|
-
This program is free software: you can redistribute it and/or modify
|
|
132
|
-
it under the terms of the GNU General Public License as published by
|
|
133
|
-
the Free Software Foundation, either version 3 of the License, or
|
|
134
|
-
(at your option) any later version.
|
|
136
|
+
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
|
135
137
|
|
|
136
|
-
This program is distributed in the hope that it will be useful,
|
|
137
|
-
|
|
138
|
-
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
139
|
-
GNU General Public License for more details.
|
|
138
|
+
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
139
|
+
See the GNU General Public License for more details.
|
|
140
140
|
|
|
141
|
-
You should have received a copy of the GNU General Public License
|
|
142
|
-
|
|
141
|
+
You should have received a copy of the GNU General Public License along with this program.
|
|
142
|
+
If not, see <http://www.gnu.org/licenses/>.
|
|
@@ -7,6 +7,7 @@ module ForemanAcd
|
|
|
7
7
|
include ::ForemanAcd::Concerns::AnsiblePlaybookParameters
|
|
8
8
|
|
|
9
9
|
before_action :find_resource, :only => [:edit, :update, :destroy, :import_vars]
|
|
10
|
+
after_action :delete_synced_repo, :only => [:new, :edit, :create, :update, :destroy, :index]
|
|
10
11
|
|
|
11
12
|
def index
|
|
12
13
|
@ansible_playbooks = resource_base.search_for(params[:search], :order => params[:order]).paginate(:page => params[:page])
|
|
@@ -18,17 +19,36 @@ module ForemanAcd
|
|
|
18
19
|
|
|
19
20
|
def create
|
|
20
21
|
@ansible_playbook = AnsiblePlaybook.new(ansible_playbook_params)
|
|
22
|
+
if session[:git_path]
|
|
23
|
+
@ansible_playbook.update(:path => ansible_playbook_full_path(ansible_playbook_rename(@ansible_playbook[:name])))
|
|
24
|
+
FileUtils.mv(session[:git_path], @ansible_playbook[:path])
|
|
25
|
+
session[:git_path] = nil
|
|
26
|
+
end
|
|
21
27
|
if @ansible_playbook.save
|
|
22
|
-
process_success
|
|
28
|
+
process_success :success_msg => _("Successfully created %s. You need to press the \"Import groups\" button
|
|
29
|
+
before this ansible playbook can be used in App Definitions!") % @ansible_playbook
|
|
23
30
|
else
|
|
24
31
|
process_error
|
|
25
32
|
end
|
|
26
33
|
end
|
|
27
34
|
|
|
28
|
-
def edit
|
|
29
|
-
end
|
|
35
|
+
def edit; end
|
|
30
36
|
|
|
31
37
|
def update
|
|
38
|
+
# Move synced repo to new path if ansible_playbook name is changed
|
|
39
|
+
if !session[:git_path].nil? && ansible_playbook_params[:name] != @ansible_playbook[:name]
|
|
40
|
+
FileUtils.mv(@ansible_playbook[:path], ansible_playbook_full_path(ansible_playbook_rename(ansible_playbook_params[:name])))
|
|
41
|
+
@ansible_playbook.update(:path => ansible_playbook_full_path(ansible_playbook_rename(ansible_playbook_params[:name])))
|
|
42
|
+
session[:git_path] = nil
|
|
43
|
+
|
|
44
|
+
# Remove old version and copy new version of synced repository
|
|
45
|
+
elsif !session[:git_path].nil? && ansible_playbook_params[:name] == @ansible_playbook.name
|
|
46
|
+
remove_ansible_dir(@ansible_playbook[:path]) if @ansible_playbook.path
|
|
47
|
+
@ansible_playbook.update(:path => ansible_playbook_full_path(ansible_playbook_rename(@ansible_playbook[:name])))
|
|
48
|
+
FileUtils.mv(session[:git_path], @ansible_playbook[:path])
|
|
49
|
+
session[:git_path] = nil
|
|
50
|
+
end
|
|
51
|
+
|
|
32
52
|
if @ansible_playbook.update(ansible_playbook_params)
|
|
33
53
|
process_success
|
|
34
54
|
else
|
|
@@ -44,10 +64,64 @@ module ForemanAcd
|
|
|
44
64
|
end
|
|
45
65
|
end
|
|
46
66
|
|
|
67
|
+
def sync_git_repo
|
|
68
|
+
@ansible_playbook = AnsiblePlaybook.new
|
|
69
|
+
sync_params = params[:ansible_playbook]
|
|
70
|
+
dir = Dir.mktmpdir
|
|
71
|
+
|
|
72
|
+
begin
|
|
73
|
+
git = Git.init(dir)
|
|
74
|
+
|
|
75
|
+
if ForemanAcd.proxy_setting.present?
|
|
76
|
+
git.config('http.proxy', ForemanAcd.proxy_setting)
|
|
77
|
+
logger.info("HTTP Proxy used: #{git.config['http.proxy']}")
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
if sync_params[:git_commit].empty?
|
|
81
|
+
if ForemanAcd.proxy_setting.present?
|
|
82
|
+
err_msg = _('Please set the Git Branch/Tag/Commit. This setting is necessary if a HTTP proxy is used!')
|
|
83
|
+
raise StandardError.new err_msg
|
|
84
|
+
else
|
|
85
|
+
commit = Git.ls_remote(sync_params[:git_url])['head'][:sha]
|
|
86
|
+
end
|
|
87
|
+
else
|
|
88
|
+
commit = sync_params[:git_commit]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
git.add_remote('origin', sync_params[:git_url])
|
|
92
|
+
git.fetch
|
|
93
|
+
git.checkout(commit)
|
|
94
|
+
|
|
95
|
+
session[:git_path] = git.dir.path
|
|
96
|
+
rescue StandardError => e
|
|
97
|
+
logger.error("Failed to sync git repository: #{e}")
|
|
98
|
+
render :json => { :status => 'error', :message => e }, :status => :internal_server_error
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Remove abandoned synced git repositories
|
|
103
|
+
def delete_synced_repo
|
|
104
|
+
names = []
|
|
105
|
+
AnsiblePlaybook.all.each do |ansible_playbook|
|
|
106
|
+
names.push(ansible_playbook_rename(ansible_playbook.name))
|
|
107
|
+
end
|
|
108
|
+
names.push('.', '..')
|
|
109
|
+
return unless Dir.exist?(ForemanAcd.ansible_playbook_path)
|
|
110
|
+
Dir.foreach(ForemanAcd.ansible_playbook_path) do |dirname|
|
|
111
|
+
next if names.include? dirname
|
|
112
|
+
remove_ansible_dir(ansible_playbook_full_path(dirname))
|
|
113
|
+
logger.info("Successfully removed #{dirname}")
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
47
117
|
def action_permission
|
|
48
118
|
case params[:action]
|
|
49
119
|
when 'import_vars'
|
|
50
120
|
:import_vars
|
|
121
|
+
when 'sync_git_repo'
|
|
122
|
+
:sync_git_repo
|
|
123
|
+
when 'grab'
|
|
124
|
+
:grab
|
|
51
125
|
else
|
|
52
126
|
super
|
|
53
127
|
end
|
|
@@ -87,15 +161,15 @@ module ForemanAcd
|
|
|
87
161
|
# We need to support: group_vars/group_file and group_vars/group_dir/yaml_files
|
|
88
162
|
dir_and_file = File.split(vars_file)
|
|
89
163
|
|
|
90
|
-
if File.basename(dir_and_file[0]) == 'group_vars' # in case of group_vars/group_file
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
164
|
+
group_name = if File.basename(dir_and_file[0]) == 'group_vars' # in case of group_vars/group_file
|
|
165
|
+
File.basename(dir_and_file[1], '.*')
|
|
166
|
+
else # in case of group_vars/group_dir/yaml_files
|
|
167
|
+
File.basename(dir_and_file[0])
|
|
168
|
+
end
|
|
95
169
|
|
|
96
170
|
logger.debug("Add ansible vars from file #{vars_file} to group #{group_name}")
|
|
97
171
|
|
|
98
|
-
if vars.
|
|
172
|
+
if vars.key?(group_name)
|
|
99
173
|
vars[group_name].merge!(loaded_yaml)
|
|
100
174
|
else
|
|
101
175
|
vars[group_name] = loaded_yaml
|
|
@@ -104,7 +178,7 @@ module ForemanAcd
|
|
|
104
178
|
|
|
105
179
|
errors << "No ansible group variable in #{playbook_path} defined." if everything_empty
|
|
106
180
|
|
|
107
|
-
|
|
181
|
+
[vars, errors]
|
|
108
182
|
end
|
|
109
183
|
|
|
110
184
|
def import_vars
|
|
@@ -113,10 +187,28 @@ module ForemanAcd
|
|
|
113
187
|
@ansible_playbook.vars = vars.to_json
|
|
114
188
|
@ansible_playbook.save
|
|
115
189
|
if errors.empty?
|
|
116
|
-
process_success :success_msg => _(
|
|
190
|
+
process_success :success_msg => _('Successfully loaded ansible group variables from %s') % @ansible_playbook.name, :redirect => ansible_playbooks_path
|
|
117
191
|
else
|
|
118
192
|
process_error :error_msg => _(errors.join(' ')), :redirect => ansible_playbooks_path
|
|
119
193
|
end
|
|
120
194
|
end
|
|
195
|
+
|
|
196
|
+
private
|
|
197
|
+
|
|
198
|
+
def ansible_playbook_rename(name)
|
|
199
|
+
name.split(/\W+/).join('_')
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def remove_ansible_dir(dirpath)
|
|
203
|
+
unless dirpath.start_with? ForemanAcd.ansible_playbook_path
|
|
204
|
+
logger.error("Sorry, the directory #{dirpath} is not within #{ForemanAcd.ansible_playbook_path} and will therefore not be cleaned up!")
|
|
205
|
+
return false
|
|
206
|
+
end
|
|
207
|
+
FileUtils.rm_rf(dirpath) if Dir.exist?(dirpath)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def ansible_playbook_full_path(dirname)
|
|
211
|
+
File.join(ForemanAcd.ansible_playbook_path, dirname)
|
|
212
|
+
end
|
|
121
213
|
end
|
|
122
214
|
end
|
|
@@ -5,9 +5,12 @@ module ForemanAcd
|
|
|
5
5
|
module V2
|
|
6
6
|
# API controller for Ansible Playbooks
|
|
7
7
|
class AnsiblePlaybooksController < ::ForemanAcd::Api::V2::BaseController
|
|
8
|
+
include ::Foreman::Controller::SmartProxyAuth
|
|
8
9
|
include ::ForemanAcd::Concerns::AnsiblePlaybookParameters
|
|
9
10
|
|
|
10
|
-
before_action :find_resource, :except => [:index, :create]
|
|
11
|
+
before_action :find_resource, :except => [:index, :create, :grab]
|
|
12
|
+
|
|
13
|
+
add_smart_proxy_filters :grab, :features => 'ACD'
|
|
11
14
|
|
|
12
15
|
api :GET, '/ansible_playbooks/:id', N_('Show ansible playbook')
|
|
13
16
|
param :id, :identifier, :required => true
|
|
@@ -23,6 +26,7 @@ module ForemanAcd
|
|
|
23
26
|
def_param_group :ansible_playbook do
|
|
24
27
|
param :ansible_playbook, Hash, :required => true, :action_aware => true do
|
|
25
28
|
param :name, String, :required => true
|
|
29
|
+
param_group :taxonomies, ::Api::V2::BaseController
|
|
26
30
|
param :description, String, :required => true
|
|
27
31
|
param :services, String, :required => true
|
|
28
32
|
end
|
|
@@ -41,8 +45,22 @@ module ForemanAcd
|
|
|
41
45
|
process_response @ansible_playbook.destroy
|
|
42
46
|
end
|
|
43
47
|
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
api :GET, '/ansible_playbooks/:id/grab', N_('Grab ansible playbook')
|
|
49
|
+
param :id, :identifier, :required => true
|
|
50
|
+
def grab
|
|
51
|
+
ap = resource_class.find(params['id'])
|
|
52
|
+
command = "tar cz -C #{ap.path} --exclude \".git\" . 2>/dev/null | base64"
|
|
53
|
+
result = `#{command}`
|
|
54
|
+
send_data result, :type => 'text/plain', :disposition => 'inline'
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def action_permission
|
|
58
|
+
case params[:action]
|
|
59
|
+
when 'grab'
|
|
60
|
+
'grab'
|
|
61
|
+
else
|
|
62
|
+
super
|
|
63
|
+
end
|
|
46
64
|
end
|
|
47
65
|
|
|
48
66
|
def resource_class
|
|
@@ -23,6 +23,7 @@ module ForemanAcd
|
|
|
23
23
|
def_param_group :app_definition do
|
|
24
24
|
param :app_definition, Hash, :required => true, :action_aware => true do
|
|
25
25
|
param :name, String, :required => true
|
|
26
|
+
param_group :taxonomies, ::Api::V2::BaseController
|
|
26
27
|
param :description, String, :required => true
|
|
27
28
|
param :services, String, :required => true
|
|
28
29
|
end
|
|
@@ -11,18 +11,26 @@ module ForemanAcd
|
|
|
11
11
|
|
|
12
12
|
api :GET, '/app_instances/:id', N_('Show application instance')
|
|
13
13
|
param :id, :identifier, :required => true
|
|
14
|
+
param :organization_id, :identifier, :required => true
|
|
15
|
+
param :location_id, :identifier, :required => true
|
|
14
16
|
def show; end
|
|
15
17
|
|
|
16
18
|
api :GET, '/app_instances', N_('List application instances')
|
|
19
|
+
param :organization_id, :identifier, :required => true
|
|
20
|
+
param :location_id, :identifier, :required => true
|
|
17
21
|
param_group :search_and_pagination, ::Api::V2::BaseController
|
|
18
22
|
add_scoped_search_description_for(AppInstance)
|
|
19
23
|
def index
|
|
20
|
-
|
|
24
|
+
scope = resource_scope_for_index.where(:organization => params[:organization_id]) if params[:organization_id].present?
|
|
25
|
+
scope = scope.where(:location => params[:location_id]) if params[:location_id].present?
|
|
26
|
+
@app_instances = scope
|
|
21
27
|
end
|
|
22
28
|
|
|
23
29
|
def_param_group :app_instance do
|
|
24
30
|
param :app_instance, Hash, :required => true, :action_aware => true do
|
|
25
31
|
param :name, String, :required => true
|
|
32
|
+
param :organization_id, :identifier, :required => true
|
|
33
|
+
param :location_id, :identifier, :required => true
|
|
26
34
|
param :description, String, :required => true
|
|
27
35
|
param :services, String, :required => true
|
|
28
36
|
end
|