foreman_ansible 1.0 → 1.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of foreman_ansible might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Rakefile +6 -27
- data/app/controllers/ansible_roles_controller.rb +55 -0
- data/app/controllers/api/v2/ansible_roles_controller.rb +52 -0
- data/app/controllers/foreman_ansible/concerns/hosts_controller_extensions.rb +11 -10
- data/app/helpers/foreman_ansible/ansible_plugin_helper.rb +8 -0
- data/app/helpers/foreman_ansible/ansible_reports_helper.rb +6 -4
- data/app/helpers/foreman_ansible/ansible_roles_helper.rb +17 -3
- data/app/helpers/foreman_ansible/hosts_helper_extensions.rb +4 -3
- data/app/lib/actions/foreman_ansible/play_host_roles.rb +56 -0
- data/app/lib/actions/foreman_ansible/play_hosts_roles.rb +26 -0
- data/app/lib/proxy_api/ansible.rb +28 -0
- data/app/models/ansible_role.rb +8 -0
- data/app/models/concerns/foreman_ansible/has_many_ansible_roles.rb +13 -0
- data/app/models/concerns/foreman_ansible/host_managed_extensions.rb +5 -1
- data/app/models/concerns/foreman_ansible/hostgroup_extensions.rb +19 -0
- data/app/models/host_ansible_role.rb +0 -2
- data/app/models/hostgroup_ansible_role.rb +8 -0
- data/app/overrides/ansible_roles_tab.rb +2 -2
- data/app/overrides/hostgroup_ansible_roles_tab.rb +14 -0
- data/app/services/foreman_ansible/api_roles_importer.rb +16 -0
- data/app/services/foreman_ansible/fact_importer.rb +2 -1
- data/app/services/foreman_ansible/inventory_creator.rb +37 -5
- data/app/services/foreman_ansible/playbook_creator.rb +3 -4
- data/app/services/foreman_ansible/proxy_selector.rb +19 -0
- data/app/services/foreman_ansible/roles_importer.rb +42 -15
- data/app/services/foreman_ansible/ui_roles_importer.rb +26 -0
- data/app/views/ansible_roles/import.html.erb +51 -0
- data/app/views/ansible_roles/index.html.erb +30 -0
- data/app/views/ansible_roles/welcome.html.erb +14 -0
- data/app/views/api/v2/ansible_roles/import.json.rabl +3 -0
- data/app/views/api/v2/ansible_roles/index.json.rabl +3 -0
- data/app/views/api/v2/ansible_roles/obsolete.json.rabl +3 -0
- data/app/views/api/v2/ansible_roles/show.json.rabl +3 -0
- data/app/views/foreman_ansible/ansible_roles/_select_tab_content.html.erb +6 -0
- data/app/views/foreman_ansible/{hosts/_tab_title.html.erb → ansible_roles/_select_tab_title.html.erb} +0 -0
- data/config/routes.rb +25 -2
- data/db/migrate/20160705082036_create_ansible_role.rb +2 -1
- data/db/migrate/20160706074540_create_join_table_hosts_ansible_roles.rb +1 -0
- data/db/migrate/20160707195442_create_host_ansible_roles.rb +1 -0
- data/db/migrate/20160729094457_add_columns_to_ansible_role.rb +12 -0
- data/db/migrate/20160802153302_create_join_table_hostgroup_ansible_roles.rb +10 -0
- data/db/migrate/20160805094233_add_primary_key_hostgroup_ansible_roles.rb +6 -0
- data/db/seeds.d/{62-ansible_proxy_feature.rb → 62_ansible_proxy_feature.rb} +0 -0
- data/lib/foreman_ansible.rb +1 -0
- data/lib/foreman_ansible/engine.rb +54 -4
- data/lib/foreman_ansible/version.rb +1 -1
- data/locale/en/foreman_ansible.po +139 -2
- data/locale/foreman_ansible.pot +200 -8
- data/test/factories/ansible_proxy.rb +7 -0
- data/test/factories/ansible_roles.rb +2 -2
- data/test/fixtures/ansible_permissions.yml +11 -0
- data/test/functional/ansible_roles_controller_test.rb +16 -0
- data/test/functional/api/v2/ansible_roles_controller_test.rb +24 -0
- data/test/functional/hosts_controller_test.rb +65 -24
- data/test/support/fixture_support.rb +25 -0
- data/test/support/foreman_tasks/task.rb +47 -0
- data/test/support/foreman_test_helper_additions.rb +22 -0
- data/test/test_plugin_helper.rb +5 -2
- data/test/unit/ansible_role_test.rb +6 -2
- data/test/unit/concerns/host_managed_extensions_test.rb +37 -0
- data/test/unit/concerns/hostgroup_extensions_test.rb +36 -0
- data/test/unit/fact_importer_test.rb +11 -8
- data/test/unit/fact_parser_test.rb +2 -0
- data/test/unit/fact_sparser_test.rb +1 -0
- data/test/unit/helpers/foreman_ansible/ansible_reports_helper_test.rb +1 -0
- data/test/unit/host_ansible_role_test.rb +10 -0
- data/test/unit/hostgroup_ansible_role_test.rb +19 -0
- data/test/unit/lib/foreman_ansible_core/playbook_runner_test.rb +28 -0
- data/test/unit/lib/proxy_api/ansible_test.rb +23 -0
- data/test/unit/services/api_roles_importer_test.rb +28 -0
- data/test/unit/services/proxy_selector_test.rb +28 -0
- data/test/unit/services/roles_importer_test.rb +17 -0
- data/test/unit/services/ui_roles_importer_test.rb +30 -0
- metadata +108 -14
- data/app/jobs/foreman_ansible/run_playbook_job.rb +0 -28
- data/app/services/foreman_ansible/role_player.rb +0 -26
- data/app/views/foreman_ansible/hosts/_tab_content.html.erb +0 -4
- data/lib/tasks/foreman_ansible_tasks.rake +0 -40
@@ -1,8 +1,6 @@
|
|
1
1
|
# Join model that hosts the connection between hosts and ansible_roles
|
2
2
|
class HostAnsibleRole < ActiveRecord::Base
|
3
3
|
audited :associated_with => :host, :allow_mass_assignment => true
|
4
|
-
attr_accessible :host_id, :host, :ansible_role_id, :ansible_role
|
5
|
-
|
6
4
|
belongs_to_host
|
7
5
|
belongs_to :ansible_role
|
8
6
|
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# Join model that hosts the connection between hostgroups and ansible_roles
|
2
|
+
class HostgroupAnsibleRole < ActiveRecord::Base
|
3
|
+
belongs_to :hostgroup
|
4
|
+
belongs_to :ansible_role
|
5
|
+
|
6
|
+
validates :ansible_role_id, :presence => true,
|
7
|
+
:uniqueness => { :scope => :hostgroup_id }
|
8
|
+
end
|
@@ -3,12 +3,12 @@ Deface::Override.new(
|
|
3
3
|
:virtual_path => 'hosts/_form',
|
4
4
|
:name => 'ansible_roles_tab',
|
5
5
|
:insert_after => 'li.active',
|
6
|
-
:partial => 'foreman_ansible/
|
6
|
+
:partial => 'foreman_ansible/ansible_roles/select_tab_title'
|
7
7
|
)
|
8
8
|
|
9
9
|
Deface::Override.new(
|
10
10
|
:virtual_path => 'hosts/_form',
|
11
11
|
:name => 'ansible_roles_tab_content',
|
12
12
|
:insert_after => 'div.tab-pane.active',
|
13
|
-
:partial => 'foreman_ansible/
|
13
|
+
:partial => 'foreman_ansible/ansible_roles/select_tab_content'
|
14
14
|
)
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Displays Ansible roles tab on Hostgroup form
|
2
|
+
Deface::Override.new(
|
3
|
+
:virtual_path => 'hostgroups/_form',
|
4
|
+
:name => 'hostgroup_ansible_roles_tab',
|
5
|
+
:insert_after => 'li.active',
|
6
|
+
:partial => 'foreman_ansible/ansible_roles/select_tab_title'
|
7
|
+
)
|
8
|
+
|
9
|
+
Deface::Override.new(
|
10
|
+
:virtual_path => 'hostgroups/_form',
|
11
|
+
:name => 'hostgroup_ansible_roles_tab_content',
|
12
|
+
:insert_after => 'div.tab-pane.active',
|
13
|
+
:partial => 'foreman_ansible/ansible_roles/select_tab_content'
|
14
|
+
)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module ForemanAnsible
|
2
|
+
# imports Ansible roles through API
|
3
|
+
class ApiRolesImporter < RolesImporter
|
4
|
+
def import!
|
5
|
+
new_roles = import_role_names[:new]
|
6
|
+
new_roles.map(&:save)
|
7
|
+
new_roles
|
8
|
+
end
|
9
|
+
|
10
|
+
def obsolete!
|
11
|
+
obsolete_roles = import_role_names[:obsolete]
|
12
|
+
obsolete_roles.map(&:destroy)
|
13
|
+
obsolete_roles
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -20,7 +20,8 @@ module ForemanAnsible
|
|
20
20
|
@counters[:added] = 0
|
21
21
|
add_missing_facts(FactSparser.unsparse(@original_facts))
|
22
22
|
logger.debug(
|
23
|
-
"Merging facts for '#{host}': added #{@counters[:added]} facts"
|
23
|
+
"Merging facts for '#{host}': added #{@counters[:added]} facts"
|
24
|
+
)
|
24
25
|
end
|
25
26
|
|
26
27
|
def add_missing_facts(imported_facts, parent = nil, prefix = '')
|
@@ -8,11 +8,43 @@ module ForemanAnsible
|
|
8
8
|
@hosts = hosts
|
9
9
|
end
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
# It returns a hash in a format that Ansible understands.
|
12
|
+
# See http://docs.ansible.com/ansible/developing_inventory.html
|
13
|
+
# for more details.
|
14
|
+
# For now, we don't group the hosts based on different paramters
|
15
|
+
# (use https://github.com/theforeman/foreman_ansible_inventory for
|
16
|
+
# more advanced cases). Therefore we have only the 'all' group
|
17
|
+
# with all hosts.
|
18
|
+
def to_hash
|
19
|
+
{ 'all' => { 'hosts' => hosts.map(&:fqdn) },
|
20
|
+
'_meta' => { 'hostvars' => host_vars } }
|
21
|
+
end
|
22
|
+
|
23
|
+
def host_vars
|
24
|
+
hosts.reduce({}) do |hash, host|
|
25
|
+
hash.update(host.fqdn =>
|
26
|
+
{ 'foreman' => host_attributes(host),
|
27
|
+
'foreman_params' => host_params(host),
|
28
|
+
'foreman_ansible_roles' => host_roles(host) })
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def host_roles(host)
|
33
|
+
host.all_ansible_roles.map(&:name)
|
34
|
+
end
|
35
|
+
|
36
|
+
def host_attributes(host)
|
37
|
+
render_rabl(host, 'api/v2/hosts/main')
|
38
|
+
end
|
39
|
+
|
40
|
+
def host_params(host)
|
41
|
+
host.host_params
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def render_rabl(host, template)
|
47
|
+
Rabl.render(host, template, :format => 'hash')
|
16
48
|
end
|
17
49
|
end
|
18
50
|
end
|
@@ -1,15 +1,14 @@
|
|
1
1
|
module ForemanAnsible
|
2
2
|
# Service to generate a playbook given roles and a list of hosts
|
3
3
|
class PlaybookCreator
|
4
|
-
attr_reader :
|
4
|
+
attr_reader :role_names
|
5
5
|
|
6
|
-
def initialize(
|
7
|
-
@fqdn = fqdn
|
6
|
+
def initialize(role_names)
|
8
7
|
@role_names = role_names
|
9
8
|
end
|
10
9
|
|
11
10
|
def roles_playbook
|
12
|
-
playbook = ['hosts' =>
|
11
|
+
playbook = ['hosts' => 'all', 'roles' => role_names]
|
13
12
|
playbook.to_yaml
|
14
13
|
end
|
15
14
|
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ForemanAnsible
|
2
|
+
# Contains proxy selection rules for a host playbook run
|
3
|
+
class ProxySelector < ::ForemanTasks::ProxySelector
|
4
|
+
def available_proxies(host)
|
5
|
+
proxies = {}
|
6
|
+
proxies[:fallback] = host.smart_proxies.with_features('Ansible')
|
7
|
+
proxies[:global] = proxy_scope(host).authorized.with_features('Ansible')
|
8
|
+
proxies
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def proxy_scope(host)
|
14
|
+
return ::SmartProxy unless Taxonomy.enabled_taxonomies.any?
|
15
|
+
::SmartProxy.with_taxonomy_scope_override(host.location,
|
16
|
+
host.organization)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,24 +1,51 @@
|
|
1
1
|
module ForemanAnsible
|
2
|
-
#
|
3
|
-
# Should be extracted to a gem that both the proxy and Foreman can use.
|
2
|
+
# imports roles from smart proxy
|
4
3
|
class RolesImporter
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
4
|
+
attr_reader :ansible_proxy
|
5
|
+
|
6
|
+
def initialize(proxy = nil)
|
7
|
+
@ansible_proxy = proxy
|
8
|
+
end
|
9
|
+
|
10
|
+
def import_role_names
|
11
|
+
return import_roles remote_roles if ansible_proxy
|
12
|
+
import_roles local_roles
|
13
|
+
end
|
14
|
+
|
15
|
+
def import_roles(roles)
|
16
|
+
imported = roles.map do |role_name|
|
17
|
+
::AnsibleRole.find_or_initialize_by(:name => role_name)
|
13
18
|
end
|
19
|
+
detect_changes imported
|
20
|
+
end
|
21
|
+
|
22
|
+
def detect_changes(imported)
|
23
|
+
changes = {}.with_indifferent_access
|
24
|
+
old, changes[:new] = imported.partition { |role| role.id.present? }
|
25
|
+
changes[:obsolete] = ::AnsibleRole.where.not(:id => old.map(&:id))
|
26
|
+
changes
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
14
30
|
|
15
|
-
|
31
|
+
def find_proxy_api
|
32
|
+
raise ::Foreman::Exception.new(N_('Proxy not found')) unless ansible_proxy
|
33
|
+
@proxy_api = ::ProxyAPI::Ansible.new(:url => ansible_proxy.url)
|
34
|
+
end
|
16
35
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
36
|
+
def proxy_api
|
37
|
+
return @proxy_api if @proxy_api
|
38
|
+
find_proxy_api
|
39
|
+
end
|
40
|
+
|
41
|
+
def local_roles
|
42
|
+
Dir.glob('/etc/ansible/roles/*').map do |path|
|
43
|
+
path.split('/').last
|
21
44
|
end
|
22
45
|
end
|
46
|
+
|
47
|
+
def remote_roles
|
48
|
+
proxy_api.roles
|
49
|
+
end
|
23
50
|
end
|
24
51
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ForemanAnsible
|
2
|
+
# imports ansible roles through UI
|
3
|
+
class UiRolesImporter < RolesImporter
|
4
|
+
def import!
|
5
|
+
import_role_names
|
6
|
+
end
|
7
|
+
|
8
|
+
def finish_import(changes)
|
9
|
+
return unless changes.present?
|
10
|
+
create_new_roles changes['new'] if changes['new']
|
11
|
+
delete_old_roles changes['obsolete'] if changes['obsolete']
|
12
|
+
end
|
13
|
+
|
14
|
+
def create_new_roles(changes)
|
15
|
+
changes.values.each do |new_role|
|
16
|
+
::AnsibleRole.create(JSON.parse(new_role))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def delete_old_roles(changes)
|
21
|
+
changes.values.each do |old_role|
|
22
|
+
::AnsibleRole.find(JSON.parse(old_role)['id']).destroy
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
<% title _("Changed Ansible roles") %>
|
2
|
+
<%= form_tag confirm_import_ansible_roles_path do %>
|
3
|
+
<h4><%= _("Select the changes you want to realize in Foreman") %></h4>
|
4
|
+
<h6>
|
5
|
+
<%= _("Toggle") %>:
|
6
|
+
<%= link_to_function(icon_text("check", _("New")),
|
7
|
+
"toggleCheckboxesBySelector('.role_select_boxes_new')",
|
8
|
+
:title => _("Check/Uncheck new")) %> |
|
9
|
+
<%= link_to_function(icon_text("check", _("Obsolete")),
|
10
|
+
"toggleCheckboxesBySelector('.role_select_boxes_obsolete')",
|
11
|
+
:title => _("Check/Uncheck obsolete")) %>
|
12
|
+
</h6>
|
13
|
+
<table class="<%= table_css_classes %>">
|
14
|
+
<thead>
|
15
|
+
<tr>
|
16
|
+
<th class="ca">
|
17
|
+
<%= link_to_function(icon_text("check"),
|
18
|
+
"toggleCheckboxesBySelector('.role_select_boxes')",
|
19
|
+
:title => _("Check/Uncheck all")) %>
|
20
|
+
</th>
|
21
|
+
<th><%= _("Name") %></th>
|
22
|
+
<th class="col-md-2"><%= _("Hosts count") %></th>
|
23
|
+
<th class="col-md-2"><%= _("Hostgroups count") %></th>
|
24
|
+
<th><%= _("Operation") %></th>
|
25
|
+
</tr>
|
26
|
+
</thead>
|
27
|
+
<tbody>
|
28
|
+
<% changed.each do |kind, roles| %>
|
29
|
+
<% roles.each do |role| %>
|
30
|
+
<tr>
|
31
|
+
<td>
|
32
|
+
<%= check_box_tag "changed[#{kind}][#{role}]", role.to_json, false, :class => "role_select_boxes role_select_boxes_#{kind} role_select_boxes_role_#{role}" %>
|
33
|
+
</td>
|
34
|
+
<td>
|
35
|
+
<%= link_to_function("#{role}", "toggleCheckboxesBySelector('.role_select_boxes_role_#{role}')", :title => _("Check/Uncheck all %s changes") % role) %>
|
36
|
+
</td>
|
37
|
+
<td><%= role.hosts.count %></td>
|
38
|
+
<td><%= role.hostgroups.count %></td>
|
39
|
+
<td>
|
40
|
+
<%= { "new" => _("Add"), "obsolete" => _("Remove") }[kind] %>
|
41
|
+
</td>
|
42
|
+
</tr>
|
43
|
+
<% end %>
|
44
|
+
<% end %>
|
45
|
+
</tbody>
|
46
|
+
</table>
|
47
|
+
<div>
|
48
|
+
<%= link_to _("Cancel"), ansible_roles_path, :class => "btn btn-default" %>
|
49
|
+
<%= submit_tag _("Update"), :class => "btn btn-primary" %>
|
50
|
+
</div>
|
51
|
+
<% end %>
|
@@ -0,0 +1,30 @@
|
|
1
|
+
<% title _("Ansible Roles") %>
|
2
|
+
|
3
|
+
<% title_actions ansible_proxy_import(hash_for_import_ansible_roles_path) %>
|
4
|
+
|
5
|
+
<table class="<%= table_css_classes 'table-fixed' %>">
|
6
|
+
<thead>
|
7
|
+
<tr>
|
8
|
+
<th class="col-md-3"><%= sort :name, :as => s_("Role|Name") %></th>
|
9
|
+
<th class="col-md-3"><%= _("Imported at") %></th>
|
10
|
+
<th class="col-md-1"><%= _("Actions") %></th>
|
11
|
+
</tr>
|
12
|
+
</thead>
|
13
|
+
<tbody>
|
14
|
+
<% @ansible_roles.each do |role| %>
|
15
|
+
<tr>
|
16
|
+
<td class="ellipsis"><%= role.name %></td>
|
17
|
+
<td class="ellipsis"><%= import_time role %></td>
|
18
|
+
<td>
|
19
|
+
<%
|
20
|
+
links = [display_delete_if_authorized(hash_for_ansible_role_path(:id => role).merge(:auth_object => role, :authorizer => authorizer),
|
21
|
+
:data => { :confirm => _("Delete %s?") % role.name }, :action => :delete)]
|
22
|
+
%>
|
23
|
+
<%= action_buttons(*links) %>
|
24
|
+
</td>
|
25
|
+
</tr>
|
26
|
+
<% end %>
|
27
|
+
</tbody>
|
28
|
+
</table>
|
29
|
+
|
30
|
+
<%= will_paginate_with_info @ansible_roles %>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<% content_for(:title, _("Ansible Roles")) %>
|
2
|
+
<div class="blank-slate-pf">
|
3
|
+
<div class="blank-slate-pf-icon">
|
4
|
+
<%= icon_text("play", "", :kind => "fa") %>
|
5
|
+
</div>
|
6
|
+
<h1><%= _('Ansible Roles') %></h1>
|
7
|
+
<p><%= _('No ansible roles were found in Foreman. If you want to assign roles to your hosts,
|
8
|
+
you have to import them first.').html_safe %>
|
9
|
+
</p>
|
10
|
+
<p><%= link_to(_('Learn more about this in the documentation.'), ansible_doc_url) %></p>
|
11
|
+
<div class="blank-slate-pf-secondary-action">
|
12
|
+
<%= ansible_proxy_import(hash_for_import_ansible_roles_path) %>
|
13
|
+
</div>
|
14
|
+
</div>
|
@@ -0,0 +1,6 @@
|
|
1
|
+
<div class='tab-pane' id='ansible_roles'>
|
2
|
+
<%= multiple_selects(f, :ansible_roles, AnsibleRole, f.object.all_ansible_roles.map(&:id),
|
3
|
+
{:disabled => f.object.inherited_ansible_roles.map(&:id),
|
4
|
+
:label => _('Available roles')},
|
5
|
+
{ 'data-inheriteds' => f.object.inherited_ansible_roles.map(&:id).to_json }) %>
|
6
|
+
</div>
|
File without changes
|
data/config/routes.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Rails.application.routes.draw do
|
2
|
-
scope
|
3
|
-
constraints(:id =>
|
2
|
+
scope '/ansible' do
|
3
|
+
constraints(:id => %r{[^\/]+}) do
|
4
4
|
resources :hosts, :only => [] do
|
5
5
|
member do
|
6
6
|
get :play_roles
|
@@ -10,5 +10,28 @@ Rails.application.routes.draw do
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
13
|
+
|
14
|
+
resources :ansible_roles, :only => [:index, :destroy] do
|
15
|
+
collection do
|
16
|
+
get :import
|
17
|
+
post :confirm_import
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
namespace :api do
|
22
|
+
scope '(:apiv)',
|
23
|
+
:module => :v2,
|
24
|
+
:defaults => { :apiv => 'v2' },
|
25
|
+
:apiv => /v1|v2/,
|
26
|
+
:constraints => ApiConstraints.new(:version => 2) do
|
27
|
+
|
28
|
+
resources :ansible_roles, :only => [:show, :index, :destroy] do
|
29
|
+
collection do
|
30
|
+
put :import
|
31
|
+
put :obsolete
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
13
36
|
end
|
14
37
|
end
|
@@ -1,7 +1,8 @@
|
|
1
|
+
# Creates simple model of Ansible Role
|
1
2
|
class CreateAnsibleRole < ActiveRecord::Migration
|
2
3
|
def change
|
3
4
|
create_table :ansible_roles do |t|
|
4
|
-
t.string :name, :null => false, :limit => 255
|
5
|
+
t.string :name, :null => false, :limit => 255, :unique => true
|
5
6
|
end
|
6
7
|
end
|
7
8
|
end
|