├── templates
├── docker-keystone.service.j2
├── clouds.yaml.j2
└── openrc.j2
├── meta
└── main.yml
├── handlers
└── main.yml
├── defaults
└── main.yml
├── tasks
├── certificates.yml
├── main.yml
└── access.yml
├── README.md
├── LICENSE
└── library
└── keystone
/templates/docker-keystone.service.j2:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Keystone Container for CIAO
3 | Requires={{ docker_service }}
4 | After={{ docker_service }}
5 |
6 | [Service]
7 | Restart=always
8 | ExecStart=/usr/bin/docker start -a ciao-keystone
9 | ExecStop=/usr/bin/docker stop -t 2 ciao-keystone
10 |
11 | [Install]
12 | WantedBy=default.target
13 |
--------------------------------------------------------------------------------
/templates/clouds.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | # {{ ansible_managed }}
3 |
4 | clouds:
5 | default:
6 | auth:
7 | username: admin
8 | password: {{ keystone_admin_password }}
9 | project_name: admin
10 | tenant_name: admin
11 | project_domain_id: default
12 | user_domain_id: default
13 | auth_url: https://{{ keystone_fqdn }}:35357/v3
14 | identity_api_version: 3
15 | cacert: certificates/keystone/keystone_cert.pem
16 |
--------------------------------------------------------------------------------
/templates/openrc.j2:
--------------------------------------------------------------------------------
1 | # {{ ansible_managed }}
2 |
3 | export OS_PROJECT_DOMAIN_ID=default
4 | export OS_USER_DOMAIN_ID=default
5 | export OS_PROJECT_NAME=admin
6 | export OS_TENANT_NAME=admin
7 | export OS_USERNAME=admin
8 | export OS_PASSWORD={{ keystone_admin_password }}
9 | export OS_AUTH_URL=https://{{ keystone_fqdn }}:35357/v3
10 | export OS_CACERT=certificates/keystone/keystone_cert.pem
11 | export OS_IDENTITY_API_VERSION=3
12 | export OS_IMAGE_API_VERSION=2
13 |
--------------------------------------------------------------------------------
/meta/main.yml:
--------------------------------------------------------------------------------
1 | galaxy_info:
2 | author: Alberto Murillo
3 | description: This role installs clearlinux/keystone docker container
4 | company: Intel
5 |
6 | issue_tracker_url: https://github.com/clearlinux/ansible-role-keystone
7 |
8 | license: Apache
9 |
10 | min_ansible_version: 2.1
11 |
12 | platforms:
13 | - name: Ubuntu
14 | versions:
15 | - xenial
16 | - name: ClearLinux
17 |
18 | galaxy_tags:
19 | - ubuntu
20 | - xenial
21 | - clearlinux
22 | - docker
23 | - keystone
24 |
25 | dependencies: []
26 |
--------------------------------------------------------------------------------
/handlers/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # Copyright (c) 2016 Intel Corporation
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 |
16 | - name: reload systemd config
17 | command: systemctl daemon-reload
18 |
--------------------------------------------------------------------------------
/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # Copyright (c) 2016 Intel Corporation
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 |
16 | # Fully Qualified Domain Name for Keystone node
17 | keystone_fqdn: "{{ ansible_fqdn }}"
18 |
19 | # Password for the 'admin' user in OpenStack
20 | keystone_admin_password: adminUserPassword
21 |
22 | # Path for mysql data
23 | mysql_data: /var/lib/mysql
24 |
25 | # A dictionary of host mappings to ips
26 | etc_hosts: {}
27 |
28 | ### The folllowing variables can be used to specify ###
29 | ### Custom services, projects, users and roles. ###
30 | # keystone_services:
31 | # - service: nova
32 | # type: compute
33 | # description: OpenStack Compute Service
34 |
35 | # keystone_projects:
36 | # - project: demo
37 | # description: Demo Project
38 |
39 | # keystone_users:
40 | # - user: demo
41 | # password: secret
42 | # project: demo
43 | # email: demo@example.com
44 |
45 | keystone_roles:
46 | - user
47 |
48 | # keystone_user_roles:
49 | # - user: demo
50 | # project: demo
51 | # role: demo
52 |
--------------------------------------------------------------------------------
/tasks/certificates.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # Copyright (c) 2016 Intel Corporation
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 |
16 | - name: Create a local certificates directory if it does not exist
17 | become: no
18 | connection: local
19 | file: path=certificates/keystone state=directory
20 |
21 | - name: Create self-signed SSL cert
22 | become: no
23 | connection: local
24 | command: >
25 | openssl req -new -nodes -x509 -subj "/CN={{ keystone_fqdn }}" -days 365
26 | -keyout certificates/keystone/keystone_key.pem
27 | -out certificates/keystone/keystone_cert.pem -extensions v3_ca
28 | args:
29 | creates: certificates/keystone/keystone_key.pem
30 |
31 | - name: Create /etc/keystone/ssl directory
32 | file: path=/etc/keystone/ssl state=directory
33 |
34 | - name: Copy keystone certificates
35 | copy: src={{ item.src }} dest={{ item.dest }}
36 | with_items:
37 | - src: certificates/keystone/keystone_key.pem
38 | dest: /etc/keystone/ssl/keystone_key.pem
39 | - src: certificates/keystone/keystone_cert.pem
40 | dest: /etc/keystone/ssl/keystone_cert.pem
41 |
--------------------------------------------------------------------------------
/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # Copyright (c) 2016 Intel Corporation
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 |
16 | - name: Create directory for mariadb database
17 | file: path="{{ mysql_data }}" state=directory
18 |
19 | - include: certificates.yml
20 |
21 | - name: Create docker-keystone systemd unit
22 | template: src=docker-keystone.service.j2 dest=/etc/systemd/system/docker-keystone.service mode=0600
23 | notify: reload systemd config
24 |
25 | - name: Enable docker-keystone systemd unit
26 | service: name=docker-keystone.service enabled=yes
27 |
28 | - name: Start keystone container
29 | docker_container:
30 | name: ciao-keystone
31 | image: clearlinux/keystone
32 | state: started
33 | privileged: True
34 | etc_hosts: "{{ etc_hosts }}"
35 | published_ports:
36 | - 35357:35357
37 | - 5000:5000
38 | env:
39 | IDENTITY_HOST: "{{ keystone_fqdn }}"
40 | KEYSTONE_ADMIN_PASSWORD: "{{ keystone_admin_password }}"
41 | volumes:
42 | - "{{ mysql_data }}:/var/lib/mysql:rw"
43 | - "/etc/keystone/ssl/keystone_key.pem:/etc/nginx/ssl/keystone_key.pem:ro"
44 | - "/etc/keystone/ssl/keystone_cert.pem:/etc/nginx/ssl/keystone_cert.pem:ro"
45 |
46 | - name: Create openrc file
47 | become: no
48 | connection: local
49 | template: src=openrc.j2 dest=./openrc mode=0600
50 |
51 | - name: Create clouds.yaml file
52 | become: no
53 | connection: local
54 | template: src=clouds.yaml.j2 dest=./clouds.yaml mode=0600
55 |
56 | - include: access.yml
57 | connection: local
58 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # clearlinux.keystone
2 | This role installs clearlinux/keystone docker container
3 |
4 | ## Requirements
5 | * docker
6 |
7 | ## Role variables
8 |
9 | Variable | Default Value | Description
10 | -------- | ------------- | -----------
11 | keystone_fqdn | `{{ ansible_fqdn }}` | Fully Qualified Domain Name for Keystone server
12 | keystone_admin_password | adminUserPassword | Password for the admin user in keystone
13 | mysql_data | /var/lib/mysql | Path to hold mysql database file
14 |
15 | #### The following variables can be used to specify custom services, projects, users and roles
16 |
17 |
18 |
19 | | Variable |
20 | Example |
21 | Description |
22 |
23 |
24 |
25 | | keystone_services |
26 |
27 | keystone_services:
28 | - service: nova
29 | type: compute
30 | description: OpenStack Compute Service
31 |
|
32 | A list of services to be created |
33 |
34 |
35 |
36 | | keystone_projects |
37 |
38 | keystone_projects:
39 | - project: demo
40 | description: Demo Project
41 |
|
42 | A list of projects to be created |
43 |
44 |
45 |
46 | | keystone_users |
47 |
48 | keystone_users:
49 | - user: demo
50 | password: secret
51 | project: demo
52 | email: demo@example.com
53 |
|
54 | A list of users to be created |
55 |
56 |
57 |
58 | | keystone_roles |
59 |
60 | keystone_roles:
61 | - demo
62 | - admin
63 |
|
64 | A list of roles to be created |
65 |
66 |
67 |
68 | | keystone_user_roles |
69 |
70 | keystone_user_roles:
71 | - user: demo
72 | project: demo
73 | role: demo
74 |
|
75 | A list of user, role mappings |
76 |
77 |
78 |
79 |
80 | ## Dependencies
81 | None
82 |
83 | ## Example playbook
84 | file *ciao.yml*
85 | ```
86 | - hosts: controllers
87 | roles:
88 | - clearlinux.keystone
89 | ```
90 |
91 | file *group_vars/all*
92 | ```
93 | keystone_fqdn: identity.example.com
94 | keystone_admin_password: adminUserPassword
95 | mysql_data: /var/lib/mysql
96 |
97 | keystone_projects:
98 | - project: demo
99 | description: Demo Project
100 |
101 | keystone_users:
102 | - user: demo
103 | password: demoUserPassword
104 | project: demo
105 |
106 | keystone_roles:
107 | - demo
108 |
109 | keystone_user_roles:
110 | - user: demo
111 | project: demo
112 | role: demo
113 | ```
114 |
115 | ## Contribution
116 | **Pull Requests and Issues should be opened at [clearlinux/clear-config-management](https://github.com/clearlinux/clear-config-management).**
117 |
118 | ## License
119 | Apache-2.0
120 |
121 | ## Author Information
122 | This role was created by [Leoswaldo Macias](leoswaldo.macias@intel.com) and [Obed Munoz](obed.n.munoz@intel.com)
123 |
--------------------------------------------------------------------------------
/tasks/access.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # Copyright (c) 2016 Intel Corporation
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 |
16 | - name: Wait for keystone to be ready
17 | wait_for: host="{{ keystone_fqdn }}" port=5000 state=started
18 |
19 | - name: Create custom services
20 | keystone:
21 | command: "ensure_service"
22 | service_name: "{{ item.service }}"
23 | service_type: "{{ item.type }}"
24 | description: "{{ item.description | default('') }}"
25 | endpoint: "https://{{ keystone_fqdn }}:35357/v3"
26 | login_user: "admin"
27 | login_password: "{{ keystone_admin_password }}"
28 | login_project_name: "admin"
29 | insecure: yes
30 | with_items: "{{ keystone_services | default([]) }}"
31 |
32 | - name: Create custom projects
33 | keystone:
34 | command: ensure_project
35 | project_name: "{{ item.project }}"
36 | domain_name: Default
37 | description: "{{ item.description | default('') }}"
38 | endpoint: "https://{{ keystone_fqdn }}:35357/v3"
39 | login_user: admin
40 | login_password: "{{ keystone_admin_password }}"
41 | login_project_name: "admin"
42 | insecure: yes
43 | with_items: "{{ keystone_projects | default([]) }}"
44 |
45 | - name: Create custom roles
46 | keystone:
47 | command: "ensure_role"
48 | role_name: "{{ item }}"
49 | endpoint: "https://{{ keystone_fqdn }}:35357/v3"
50 | login_user: admin
51 | login_password: "{{ keystone_admin_password }}"
52 | login_project_name: "admin"
53 | insecure: yes
54 | with_items: "{{ keystone_roles | default([]) }}"
55 |
56 | - name: Create custom users
57 | keystone:
58 | command: "ensure_user"
59 | user_name: "{{ item.user }}"
60 | project_name: "{{ item.project }}"
61 | password: "{{ item.password }}"
62 | email: "{{ item.email | default('') }}"
63 | domain_name: "Default"
64 | endpoint: "https://{{ keystone_fqdn }}:35357/v3"
65 | login_user: admin
66 | login_password: "{{ keystone_admin_password }}"
67 | login_project_name: "admin"
68 | insecure: yes
69 | with_items: "{{ keystone_users | default([]) }}"
70 | no_log: true
71 |
72 | - name: Map users and roles
73 | keystone:
74 | command: "ensure_user_role"
75 | user_name: "{{ item.user }}"
76 | project_name: "{{ item.project }}"
77 | role_name: "{{ item.role }}"
78 | endpoint: "https://{{ keystone_fqdn }}:35357/v3"
79 | login_user: "admin"
80 | login_password: "{{ keystone_admin_password }}"
81 | login_project_name: "admin"
82 | insecure: yes
83 | with_items: "{{ keystone_user_roles | default([]) }}"
84 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
--------------------------------------------------------------------------------
/library/keystone:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | # (c) 2014, Kevin Carter
4 | #
5 | # Copyright 2014, Rackspace US, Inc.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 |
19 | # Based on Jimmy Tang's implementation
20 |
21 | DOCUMENTATION = """
22 | ---
23 | module: keystone
24 | version_added: "1.6.2"
25 | short_description:
26 | - Manage OpenStack Identity (keystone) users, projects, roles, and
27 | endpoints.
28 | description:
29 | - Manage OpenStack Identity (keystone) users, projects, roles, and
30 | endpoints.
31 | options:
32 | return_code:
33 | description:
34 | - Allow for return Codes other than 0 when executing commands.
35 | - This is a comma separated list of acceptable return codes.
36 | default: 0
37 | login_user:
38 | description:
39 | - login username to authenticate to keystone
40 | required: false
41 | default: admin
42 | login_user_domain_name:
43 | description:
44 | - The domain login_user belongs to
45 | required: false
46 | default: None
47 | login_password:
48 | description:
49 | - Password of login user
50 | required: false
51 | default: 'yes'
52 | login_project_name:
53 | description:
54 | - The project login_user belongs to
55 | required: false
56 | default: None
57 | login_project_domain_name:
58 | description:
59 | - The domain login_project belongs to
60 | required: false
61 | default: None
62 | login_tenant_name:
63 | description:
64 | - The tenant login_user belongs to
65 | required: false
66 | default: None
67 | token:
68 | description:
69 | - The token to be uses in case the password is not specified
70 | required: false
71 | default: None
72 | endpoint:
73 | description:
74 | - The keystone url for authentication
75 | required: false
76 | password:
77 | description:
78 | - The password to be assigned to the user
79 | required: false
80 | default: None
81 | user_name:
82 | description:
83 | - The name of the user that has to added/removed from OpenStack
84 | required: false
85 | default: None
86 | project_name:
87 | description:
88 | - The project name that has be added/removed
89 | required: false
90 | default: None
91 | tenant_name:
92 | description:
93 | - The tenant name that has be added/removed
94 | required: false
95 | default: None
96 | role_name:
97 | description:
98 | - The name of the role to be assigned or created
99 | required: false
100 | service_name:
101 | description:
102 | - Name of the service.
103 | required: false
104 | default: None
105 | region_name:
106 | description:
107 | - Name of the region.
108 | required: false
109 | default: None
110 | domain_name:
111 | description:
112 | - Name of the domain to add a project to.
113 | required: false
114 | default: 'Default'
115 | description:
116 | description:
117 | - A description for the project
118 | required: false
119 | default: None
120 | email:
121 | description:
122 | - Email address for the user, this is only used in "ensure_user"
123 | required: false
124 | default: None
125 | service_type:
126 | description:
127 | - Type of service.
128 | required: false
129 | default: None
130 | endpoint_list:
131 | description:
132 | - List of endpoints to add to keystone for a service
133 | required: false
134 | default: None
135 | type: list
136 | group_name:
137 | description:
138 | - A name for the group
139 | required: False
140 | default: None
141 | idp_name:
142 | description:
143 | - A name for the identity provider
144 | required: False
145 | default: None
146 | idp_remote_ids:
147 | description:
148 | - A URL that identifies the remote identity provider
149 | required: False
150 | default: None
151 | idp_enabled:
152 | description:
153 | - Set whether a remote identity provider is enabled
154 | required: False
155 | default: True
156 | sp_name:
157 | description:
158 | - A name for the service provider
159 | required: False
160 | default: None
161 | sp_enabled:
162 | description:
163 | - Set whether a service provider is enabled
164 | required: False
165 | default: True
166 | sp_url:
167 | description:
168 | - URL where the service provider expects to receive SAML assertions
169 | - eg: http(s)://${SP_HOST}:5000/Shibboleth.sso/SAML2/ECP
170 | required: False
171 | default: None
172 | sp_auth_url:
173 | description:
174 | - URL for federated users to request tokens from
175 | - eg: http(s)://${SP_HOST}:5000/v3/OS-FEDERATION
176 | /identity_providers/${IDP_ID}/saml2/auth
177 | required: False
178 | default: None
179 | protocol_name:
180 | description:
181 | - A name for the protocol
182 | required: False
183 | default: None
184 | mapping_name:
185 | description:
186 | - A name for the mapping
187 | required: False
188 | default: None
189 | mapping_rules:
190 | description:
191 | - A dictionary mapping federated users to local groups.
192 | - see: http://specs.openstack.org/openstack/keystone-specs
193 | /api/v3/identity-api-v3-os-federation-ext.html#mappings
194 | required: False
195 | default: None
196 | domain_enabled:
197 | description:
198 | - Name for a domain
199 | required: False
200 | default: True
201 | state:
202 | description:
203 | - Ensuring the endpoint is either present, absent, or update
204 | required: False
205 | default: 'present'
206 | command:
207 | description:
208 | - Indicate desired state of the resource
209 | choices: ['get_tenant', 'get_project', 'get_user', 'get_role',
210 | 'ensure_service', 'ensure_endpoint', 'ensure_role',
211 | 'ensure_user', 'ensure_user_role', 'ensure_tenant',
212 | 'ensure_project', 'ensure_service_provider',
213 | 'ensure_group', 'ensure_identity_provider',
214 | 'ensure_protocol', ensure_mapping',
215 | 'ensure_group_role']
216 | required: true
217 | insecure:
218 | description:
219 | - Explicitly allow client to perform "insecure" TLS
220 | choices:
221 | - false
222 | - true
223 | default: false
224 | requirements: [ python-keystoneclient ]
225 | author: Kevin Carter
226 | """
227 |
228 | EXAMPLES = """
229 | # Create an admin project
230 | - keystone:
231 | command: "ensure_project"
232 | project_name: "admin"
233 | domain_name: "Default"
234 | description: "Admin project"
235 |
236 | # Create a service project
237 | - keystone:
238 | command: "ensure_project"
239 | project_name: "service"
240 | description: "Service project"
241 |
242 | # Create an admin user
243 | - keystone:
244 | command: "ensure_user"
245 | user_name: "admin"
246 | project_name: "admin"
247 | password: "secrete"
248 | email: "admin@some-domain.com"
249 |
250 | # Create an admin role
251 | - keystone:
252 | command: "ensure_role"
253 | role_name: "admin"
254 |
255 | # Create a user
256 | - keystone:
257 | command: "ensure_user"
258 | user_name: "glance"
259 | project_name: "service"
260 | password: "secrete"
261 | domain_name: "Default"
262 | email: "glance@some-domain.com"
263 |
264 | # Add a role to a user
265 | - keystone:
266 | command: "ensure_user_role"
267 | user_name: "glance"
268 | project_name: "service"
269 | role_name: "admin"
270 |
271 | # Add a project role to a group
272 | - keystone:
273 | command: "ensure_group_role"
274 | group_name: "fedgroup"
275 | project_name: "fedproject"
276 | role_name: "_member_"
277 |
278 | # Create a service
279 | - keystone:
280 | command: "ensure_service"
281 | service_name: "glance"
282 | service_type: "image"
283 | description: "Glance Image Service"
284 |
285 | # Create an endpoint
286 | - keystone:
287 | command: "ensure_endpoint"
288 | region_name: "RegionOne"
289 | service_name: "glance"
290 | service_type: "image"
291 | endpoint_list:
292 | - url: "http://127.0.0.1:9292"
293 | interface: "public"
294 | - url: "http://127.0.0.1:9292"
295 | interface: "admin"
296 | - url: "http://127.0.0.1:9292"
297 | interface: "internal"
298 |
299 | # Get project id
300 | - keystone:
301 | command: "get_project"
302 | project_name: "admin"
303 |
304 | # Get user id
305 | - keystone:
306 | command: "get_user"
307 | user_name: "admin"
308 |
309 | # Get role id
310 | - keystone:
311 | command: "get_role"
312 | user_name: "admin"
313 |
314 | """
315 |
316 | COMMAND_MAP = {
317 | 'get_tenant': {
318 | 'variables': [
319 | 'project_name',
320 | 'tenant_name'
321 | ]
322 | },
323 | 'get_project': {
324 | 'variables': [
325 | 'project_name',
326 | 'tenant_name'
327 | ]
328 | },
329 | 'get_user': {
330 | 'variables': [
331 | 'user_name'
332 | ]
333 | },
334 | 'get_role': {
335 | 'variables': [
336 | 'role_name',
337 | 'project_name',
338 | 'tenant_name',
339 | 'user_name'
340 | ]
341 | },
342 | 'ensure_service': {
343 | 'variables': [
344 | 'service_name',
345 | 'service_type',
346 | 'description'
347 | ]
348 | },
349 | 'ensure_endpoint': {
350 | 'variables': [
351 | 'region_name',
352 | 'service_name',
353 | 'service_type',
354 | 'endpoint_list',
355 | 'state'
356 | ]
357 | },
358 | 'ensure_role': {
359 | 'variables': [
360 | 'role_name'
361 | ]
362 | },
363 | 'ensure_user': {
364 | 'variables': [
365 | 'project_name',
366 | 'tenant_name',
367 | 'user_name',
368 | 'password',
369 | 'email',
370 | 'domain_name'
371 | ]
372 | },
373 | 'ensure_user_role': {
374 | 'variables': [
375 | 'user_name',
376 | 'project_name',
377 | 'tenant_name',
378 | 'role_name',
379 | 'domain_name'
380 | ]
381 | },
382 | 'ensure_group_role': {
383 | 'variables': [
384 | 'group_name',
385 | 'project_name',
386 | 'role_name',
387 | 'domain_name'
388 | ]
389 | },
390 | 'ensure_project': {
391 | 'variables': [
392 | 'project_name',
393 | 'tenant_name',
394 | 'description',
395 | 'domain_name'
396 | ]
397 | },
398 | 'ensure_tenant': {
399 | 'variables': [
400 | 'project_name',
401 | 'tenant_name',
402 | 'description',
403 | 'domain_name'
404 | ]
405 | },
406 | 'ensure_group': {
407 | 'variables': [
408 | 'group_name',
409 | 'domain_name'
410 | ]
411 | },
412 | 'ensure_identity_provider': {
413 | 'variables': [
414 | 'idp_name',
415 | 'idp_remote_ids',
416 | 'idp_enabled'
417 | ]
418 | },
419 | 'ensure_service_provider': {
420 | 'variables': [
421 | 'sp_name',
422 | 'sp_url',
423 | 'sp_auth_url',
424 | 'sp_enabled'
425 | ]
426 | },
427 | 'ensure_protocol': {
428 | 'variables': [
429 | 'protocol_name',
430 | 'idp_name',
431 | 'mapping_name'
432 | ]
433 | },
434 | 'ensure_mapping': {
435 | 'variables': [
436 | 'mapping_name',
437 | 'mapping_rules',
438 | ]
439 | },
440 | 'ensure_domain': {
441 | 'variables': [
442 | 'domain_name',
443 | 'domain_enabled'
444 | ]
445 | }
446 | }
447 |
448 | try:
449 | from keystoneclient import exceptions as kexceptions
450 | from keystoneclient.v3 import client
451 | except ImportError:
452 | keystoneclient_found = False
453 | else:
454 | keystoneclient_found = True
455 |
456 |
457 | class ManageKeystone(object):
458 | def __init__(self, module):
459 | """Manage Keystone via Ansible."""
460 | self.state_change = False
461 | self.keystone = None
462 |
463 | # Load AnsibleModule
464 | self.module = module
465 |
466 | def command_router(self):
467 | """Run the command as its provided to the module."""
468 | command_name = self.module.params['command']
469 | if command_name not in COMMAND_MAP:
470 | self.failure(
471 | error='No Command Found',
472 | rc=2,
473 | msg='Command [ %s ] was not found.' % command_name
474 | )
475 |
476 | action_command = COMMAND_MAP[command_name]
477 | if hasattr(self, '%s' % command_name):
478 | action = getattr(self, '%s' % command_name)
479 | facts = action(variables=action_command['variables'])
480 | if facts is None:
481 | self.module.exit_json(changed=self.state_change)
482 | else:
483 | self.module.exit_json(
484 | changed=self.state_change,
485 | ansible_facts=facts
486 | )
487 | else:
488 | self.failure(
489 | error='Command not in ManageKeystone class',
490 | rc=2,
491 | msg='Method [ %s ] was not found.' % command_name
492 | )
493 |
494 | @staticmethod
495 | def _facts(facts):
496 | """Return a dict for our Ansible facts.
497 |
498 | :param facts: ``dict`` Dict with data to return
499 | """
500 | return {'keystone_facts': facts}
501 |
502 | def _get_vars(self, variables, required=None):
503 | """Return a dict of all variables as found within the module.
504 |
505 | :param variables: ``list`` List of all variables that are available to
506 | use within the Keystone Command.
507 | :param required: ``list`` Name of variables that are required.
508 | """
509 | return_dict = {}
510 | for variable in variables:
511 | return_dict[variable] = self.module.params.get(variable)
512 | else:
513 | if isinstance(required, list):
514 | for var_name in required:
515 | check = return_dict.get(var_name)
516 | if check is None:
517 | self.failure(
518 | error='Missing [ %s ] from Task or found a None'
519 | ' value' % var_name,
520 | rc=000,
521 | msg='variables %s - available params [ %s ]'
522 | % (variables, self.module.params)
523 | )
524 | return return_dict
525 |
526 | def failure(self, error, rc, msg):
527 | """Return a Failure when running an Ansible command.
528 |
529 | :param error: ``str`` Error that occurred.
530 | :param rc: ``int`` Return code while executing an Ansible command.
531 | :param msg: ``str`` Message to report.
532 | """
533 | self.module.fail_json(msg=msg, rc=rc, err=error)
534 |
535 | def _authenticate(self):
536 | """Return a keystone client object."""
537 | required_vars = ['endpoint']
538 | variables = [
539 | 'endpoint',
540 | 'login_user',
541 | 'login_password',
542 | 'login_project_name',
543 | 'login_tenant_name',
544 | 'login_user_domain_name',
545 | 'login_project_domain_name',
546 | 'token',
547 | 'insecure'
548 | ]
549 | variables_dict = self._get_vars(variables, required=required_vars)
550 |
551 | endpoint = variables_dict.pop('endpoint')
552 | login_user = variables_dict.pop('login_user')
553 | login_password = variables_dict.pop('login_password')
554 | login_project_name = (variables_dict.pop('login_project_name', None) or
555 | variables_dict.pop('login_tenant_name'))
556 | user_domain_name = variables_dict.pop('login_user_domain_name',
557 | 'Default')
558 | project_domain_name = variables_dict.pop('login_project_domain_name',
559 | 'Default')
560 | token = variables_dict.pop('token')
561 | insecure = variables_dict.pop('insecure')
562 |
563 | if token is None:
564 | if login_project_name is None:
565 | self.failure(
566 | error='Missing Project Name',
567 | rc=2,
568 | msg='If you do not specify a token you must use a project'
569 | ' name for authentication. Try adding'
570 | ' [ login_project_name ] to the task'
571 | )
572 | if login_password is None:
573 | self.failure(
574 | error='Missing Password',
575 | rc=2,
576 | msg='If you do not specify a token you must use a password'
577 | ' name for authentication. Try adding'
578 | ' [ login_password ] to the task'
579 | )
580 |
581 | if token:
582 | self.keystone = client.Client(
583 | insecure=insecure,
584 | endpoint=endpoint,
585 | token=token
586 | )
587 | else:
588 | self.keystone = client.Client(
589 | insecure=insecure,
590 | auth_url=endpoint,
591 | username=login_user,
592 | user_domain_name=user_domain_name,
593 | password=login_password,
594 | project_name=login_project_name,
595 | project_domain_name=project_domain_name,
596 | )
597 |
598 | def _get_domain_from_vars(self, variables):
599 | # NOTE(sigmavirus24): Since we don't require domain, this will be None
600 | # in the dictionary. When we pop it, we can't provide a default
601 | # because 'domain' exists and is None. In order to use a default
602 | # value, we need to use `or 'default'` here to make sure we default to
603 | # the default domain. If we don't do it this way, Keystone throws a
604 | # 401 Unauthorized which is just plain wrong.
605 | domain_name = variables.pop('domain_name', None) or 'Default'
606 |
607 | return self._get_domain(name=domain_name)
608 |
609 | def _get_domain(self, name):
610 | """Return domain information.
611 |
612 | :param str name: Name of the domain.
613 | """
614 | for entry in self.keystone.domains.list():
615 | if entry.name == name:
616 | return entry
617 | else:
618 | return None
619 |
620 | def _get_project(self, name):
621 | """Return project information.
622 |
623 | Formerly, _get_tenant
624 |
625 | :param name: ``str`` Name of the project.
626 | """
627 | for entry in self.keystone.projects.list():
628 | if entry.name == name:
629 | return entry
630 | else:
631 | return None
632 |
633 | def get_tenant(self, variables):
634 | return self.get_project(variables)
635 |
636 | def get_project(self, variables):
637 | """Return a project id.
638 |
639 | This will return `None` if the ``name`` is not found.
640 |
641 | :param variables: ``list`` List of all variables that are available to
642 | use within the Keystone Command.
643 | """
644 | self._authenticate()
645 | variables_dict = self._get_vars(variables)
646 | project_name = (variables_dict.pop('project_name', None) or
647 | variables_dict.pop('tenant_name'))
648 | project = self._get_project(name=project_name)
649 | if project is None:
650 | self.failure(
651 | error='project [ %s ] was not found.' % project_name,
652 | rc=2,
653 | msg='project was not found, does it exist?'
654 | )
655 |
656 | return self._facts(facts={'id': project.id})
657 |
658 | def ensure_tenant(self, variables):
659 | return self.ensure_project(variables)
660 |
661 | def ensure_project(self, variables):
662 | """Create a new project within Keystone if it does not exist.
663 |
664 | Returns the project ID on a successful run.
665 |
666 | :param variables: ``list`` List of all variables that are available to
667 | use within the Keystone Command.
668 | """
669 | self._authenticate()
670 | variables_dict = self._get_vars(variables)
671 | project_name = (variables_dict.pop('project_name', None) or
672 | variables_dict.pop('tenant_name'))
673 | project_description = variables_dict.pop('description')
674 | if project_description is None:
675 | project_description = 'Project %s' % project_name
676 |
677 | domain = self._get_domain_from_vars(variables_dict)
678 | project = self._get_project(name=project_name)
679 | if project is None:
680 | self.state_change = True
681 | project = self.keystone.projects.create(
682 | name=project_name,
683 | description=project_description,
684 | domain=domain,
685 | enabled=True
686 | )
687 |
688 | return self._facts(facts={'id': project.id})
689 |
690 | def _get_user(self, name, domain):
691 | """Return a user information.
692 |
693 | This will return `None` if the ``name`` is not found.
694 |
695 | :param name: ``str`` Name of the user.
696 | """
697 | for entry in self.keystone.users.list(domain=domain):
698 | if getattr(entry, 'name', None) == name:
699 | return entry
700 | else:
701 | return None
702 |
703 | def get_user(self, variables):
704 | """Return a project id.
705 |
706 | This will return `None` if the ``name`` is not found.
707 |
708 | :param variables: ``list`` List of all variables that are available to
709 | use within the Keystone Command.
710 | """
711 | self._authenticate()
712 | variables_dict = self._get_vars(variables, required=['user_name'])
713 | user_name = variables_dict.pop('user_name')
714 | domain = self._get_domain_from_vars(variables_dict)
715 | user = self._get_user(name=user_name, domain=domain)
716 | if user is None:
717 | self.failure(
718 | error='user [ %s ] was not found.' % user_name,
719 | rc=2,
720 | msg='user was not found, does it exist?'
721 | )
722 |
723 | return self._facts(facts={'id': user.id})
724 |
725 | def ensure_user(self, variables):
726 | """Create a new user within Keystone if it does not exist.
727 |
728 | Returns the user ID on a successful run.
729 |
730 | :param variables: ``list`` List of all variables that are available to
731 | use within the Keystone Command.
732 | """
733 | self._authenticate()
734 | required_vars = ['user_name', 'password']
735 | variables_dict = self._get_vars(variables, required=required_vars)
736 | project_name = (variables_dict.pop('project_name', None) or
737 | variables_dict.pop('tenant_name'))
738 | password = variables_dict.pop('password')
739 | user_name = variables_dict.pop('user_name')
740 | email = variables_dict.pop('email')
741 |
742 | domain = self._get_domain_from_vars(variables_dict)
743 | project = self._get_project(name=project_name)
744 | if project is None and project_name is not None:
745 | self.failure(
746 | error='project [ %s ] was not found.' % project_name,
747 | rc=2,
748 | msg='project was not found, does it exist?'
749 | )
750 |
751 | user = self._get_user(name=user_name, domain=domain)
752 | if user is None:
753 | self.state_change = True
754 | user = self.keystone.users.create(
755 | name=user_name,
756 | password=password,
757 | email=email,
758 | domain=domain,
759 | default_project=project
760 | )
761 |
762 | return self._facts(facts={'id': user.id})
763 |
764 | def _get_role(self, name, domain):
765 | """Return a role by name.
766 |
767 | This will return `None` if the ``name`` is not found.
768 |
769 | :param name: ``str`` Name of the role.
770 | :param domain: ``str`` ID of the domain
771 | """
772 | for entry in self.keystone.roles.list(domain=domain):
773 | if entry.name == name:
774 | return entry
775 | else:
776 | return None
777 |
778 | def _get_group(self, name, domain='Default'):
779 | """Return a group by name.
780 |
781 | This will return `None` if the ``name`` is not found.
782 |
783 | :param name: ``str`` Name of the role.
784 | """
785 | for entry in self.keystone.groups.list(domain=domain):
786 | if domain is None:
787 | if entry.name == name:
788 | return entry
789 | else:
790 | if entry.name == name and entry.domain_id == domain.id:
791 | return entry
792 | else:
793 | return None
794 |
795 | def get_role(self, variables):
796 | """Return a role by name.
797 |
798 | This will return `None` if the ``name`` is not found.
799 |
800 | :param variables: ``list`` List of all variables that are available to
801 | use within the Keystone Command.
802 | """
803 | self._authenticate()
804 | variables_dict = self._get_vars(variables, required=['role_name'])
805 | role_name = variables_dict.pop('role_name')
806 | domain = self._get_domain_from_vars(variables_dict)
807 | role_data = self._get_role(name=role_name, domain=domain)
808 | if role_data is None:
809 | self.failure(
810 | error='role [ %s ] was not found.' % role_name,
811 | rc=2,
812 | msg='role was not found, does it exist?'
813 | )
814 |
815 | return self._facts(facts={'id': role_data.id})
816 |
817 | def _get_role_data(self, user_name, project_name, role_name, group_name,
818 | domain):
819 | if user_name is not None:
820 | user = self._get_user(name=user_name, domain=domain)
821 | if user is None:
822 | self.failure(
823 | error='user [ %s ] was not found.' % user_name,
824 | rc=2,
825 | msg='User was not found, does it exist?'
826 | )
827 | else:
828 | user = None
829 |
830 | project = self._get_project(name=project_name)
831 | if project is None and project_name is not None:
832 | self.failure(
833 | error='project [ %s ] was not found.' % project_name,
834 | rc=2,
835 | msg='project was not found, does it exist?'
836 | )
837 |
838 | role = self._get_role(name=role_name, domain=domain)
839 | if role is None:
840 | self.failure(
841 | error='role [ %s ] was not found.' % role_name,
842 | rc=2,
843 | msg='role was not found, does it exist?'
844 | )
845 |
846 | if group_name is not None:
847 | group = self._get_group(name=group_name, domain=domain)
848 | if group is None:
849 | self.failure(
850 | error='group [ %s ] was not found.' % group_name,
851 | rc=2,
852 | msg='group was not found, does it exist?'
853 | )
854 | else:
855 | group = None
856 |
857 | return user, project, role, group
858 |
859 | def ensure_role(self, variables):
860 | """Create a new role within Keystone if it does not exist.
861 |
862 | Returns the user ID on a successful run.
863 |
864 | :param variables: ``list`` List of all variables that are available to
865 | use within the Keystone Command.
866 | """
867 | self._authenticate()
868 | variables_dict = self._get_vars(variables, required=['role_name'])
869 | domain = self._get_domain_from_vars(variables_dict)
870 | role_name = variables_dict.pop('role_name')
871 |
872 | role = self._get_role(name=role_name, domain=domain)
873 | if role is None:
874 | self.state_change = True
875 | role = self.keystone.roles.create(role_name)
876 |
877 | return self._facts(facts={'id': role.id})
878 |
879 | def _get_user_roles(self, name, user, project, domain):
880 | role_list = self.keystone.roles.list(
881 | user=user,
882 | project=project,
883 | domain=domain
884 | )
885 | for entry in role_list:
886 | if entry.name == name:
887 | return entry
888 | else:
889 | return None
890 |
891 | def _get_group_roles(self, name, group, project):
892 | group_list = self.keystone.roles.list(
893 | group=group,
894 | project=project
895 | )
896 | for entry in group_list:
897 | if entry.name == name:
898 | return entry
899 | else:
900 | return None
901 |
902 | def ensure_user_role(self, variables):
903 | self._authenticate()
904 | required_vars = ['user_name', 'role_name']
905 | variables_dict = self._get_vars(variables, required=required_vars)
906 | domain = self._get_domain_from_vars(variables_dict)
907 | user_name = variables_dict.pop('user_name')
908 | # NOTE(sigmavirus24): Try to get the project_name, but
909 | # don't error out on it. This will change when the playbooks are
910 | # updated to use project_name instead of tenant_name
911 | project_name = (variables_dict.pop('project_name', None) or
912 | variables_dict.pop('tenant_name'))
913 | role_name = variables_dict.pop('role_name')
914 |
915 | if project_name is not None:
916 | domain = None
917 |
918 | user, project, role, group = self._get_role_data(
919 | user_name=user_name, project_name=project_name,
920 | role_name=role_name, group_name=None, domain=domain
921 | )
922 |
923 | user_role = self._get_user_roles(
924 | name=role_name, user=user, project=project, domain=domain
925 | )
926 |
927 | if user_role is None:
928 | self.state_change = True
929 | self.keystone.roles.grant(
930 | user=user, role=role, project=project, domain=domain
931 | )
932 | user_role = self._get_user_roles(
933 | name=role_name, user=user, project=project, domain=domain
934 | )
935 |
936 | return self._facts(facts={'id': user_role.id})
937 |
938 | def ensure_group_role(self, variables):
939 | self._authenticate()
940 | required_vars = ['group_name', 'project_name', 'role_name']
941 | variables_dict = self._get_vars(variables, required=required_vars)
942 | domain = self._get_domain_from_vars(variables_dict)
943 | group_name = variables_dict.pop('group_name')
944 | project_name = variables_dict.pop('project_name')
945 | role_name = variables_dict.pop('role_name')
946 |
947 | if project_name is not None:
948 | domain = None
949 |
950 | user, project, role, group = self._get_role_data(
951 | group_name=group_name, project_name=project_name,
952 | role_name=role_name, user_name=None, domain=domain
953 | )
954 |
955 | group_role = self._get_group_roles(
956 | name=role_name, group=group, project=project, domain=domain
957 | )
958 |
959 | if group_role is None:
960 | self.state_change = True
961 | self.keystone.roles.grant(
962 | group=group, role=role, project=project, domain=domain
963 | )
964 | group_role = self._get_group_roles(
965 | name=role_name,
966 | group=group,
967 | project=project,
968 | domain=domain
969 | )
970 |
971 | return self._facts(facts={'id': group_role.id})
972 |
973 | def ensure_group(self, variables):
974 | """Create a new group within Keystone if it does not exist.
975 |
976 | Returns the group ID on a successful run.
977 |
978 | :param variables: ``list`` List of all variables that are available to
979 | use within the Keystone Command.
980 | """
981 |
982 | self._authenticate()
983 | required_vars = ['group_name', 'domain_name']
984 | variables_dict = self._get_vars(variables, required=required_vars)
985 | group_name = variables_dict.pop('group_name')
986 |
987 | domain = self._get_domain_from_vars(variables_dict)
988 |
989 | group = self._get_group(
990 | name=group_name, domain=domain
991 | )
992 |
993 | if group is None:
994 | self.state_change = True
995 | group = self.keystone.groups.create(
996 | name=group_name, domain=domain
997 | )
998 |
999 | return self._facts(facts={'id': group.id})
1000 |
1001 | def _get_service(self, name, srv_type=None):
1002 | for entry in self.keystone.services.list():
1003 | if srv_type is not None:
1004 | if entry.type == srv_type and name == entry.name:
1005 | return entry
1006 | elif entry.name == name:
1007 | return entry
1008 | else:
1009 | return None
1010 |
1011 | def ensure_service(self, variables):
1012 | """Create a new service within Keystone if it does not exist.
1013 |
1014 | Returns the service ID on a successful run.
1015 |
1016 | :param variables: ``list`` List of all variables that are available to
1017 | use within the Keystone Command.
1018 | """
1019 | self._authenticate()
1020 | required_vars = ['service_name', 'service_type']
1021 | variables_dict = self._get_vars(variables, required=required_vars)
1022 |
1023 | service_name = variables_dict.pop('service_name')
1024 | description = variables_dict.pop('description')
1025 | service_type = variables_dict.pop('service_type')
1026 |
1027 | service = self._get_service(name=service_name, srv_type=service_type)
1028 | if service is None or service.type != service_type:
1029 | self.state_change = True
1030 | service = self.keystone.services.create(
1031 | name=service_name,
1032 | type=service_type,
1033 | description=description
1034 | )
1035 |
1036 | return self._facts(facts={'id': service.id})
1037 |
1038 | def _get_endpoint_by_details(self, region, service_id, interface):
1039 | """ Getting endpoints per complete definition
1040 |
1041 | Returns the endpoint details for an endpoint matching
1042 | region, service id and interface.
1043 |
1044 | :param interface: ``str`` ‘public’, ‘admin’ or ‘internal’ network
1045 | interface
1046 | :param service_id: service to which the endpoint belongs
1047 | :param region: geographic location of the endpoint
1048 |
1049 | """
1050 | for entry in self.keystone.endpoints.list():
1051 | check = [
1052 | entry.region == region,
1053 | entry.service_id == service_id,
1054 | entry.interface == interface
1055 | ]
1056 | if all(check):
1057 | return entry
1058 | else:
1059 | return None
1060 |
1061 | def _get_endpoint(self, region, url, interface):
1062 | """ Getting endpoints per URL
1063 | Returns the endpoint details for an endpoint matching
1064 | URL, region and interface.
1065 | This interface should be deprecated in next release.
1066 | """
1067 | for entry in self.keystone.endpoints.list():
1068 | check = [
1069 | entry.region == region,
1070 | entry.url == url,
1071 | entry.interface == interface
1072 | ]
1073 | if all(check):
1074 | return entry
1075 | else:
1076 | return None
1077 |
1078 | def ensure_endpoint(self, variables):
1079 | """Ensures the deletion/modification/addition of endpoints
1080 | within Keystone.
1081 |
1082 | Returns the endpoint ID on a successful run.
1083 |
1084 | :param variables: ``list`` List of all variables that are available to
1085 | use within the Keystone Command.
1086 | """
1087 | self._authenticate()
1088 | required_vars = [
1089 | 'region_name',
1090 | 'service_name',
1091 | 'service_type',
1092 | 'endpoint_list'
1093 | ]
1094 | variables_dict = self._get_vars(variables, required=required_vars)
1095 |
1096 | service_name = variables_dict.pop('service_name')
1097 | service_type = variables_dict.pop('service_type')
1098 | region = variables_dict.pop('region_name')
1099 | endpoint_list = variables_dict.pop('endpoint_list')
1100 | state = variables_dict.pop('state')
1101 |
1102 | service = self._get_service(name=service_name, srv_type=service_type)
1103 | if service is None:
1104 | self.failure(
1105 | error='service [ %s ] was not found.' % service_name,
1106 | rc=2,
1107 | msg='Service was not found, does it exist?'
1108 | )
1109 |
1110 | endpoints = {}
1111 | for endpoint_dict in endpoint_list:
1112 | url = endpoint_dict.pop('url')
1113 | interface = endpoint_dict.pop('interface')
1114 | endpoint = self._get_endpoint(
1115 | region=region,
1116 | url=url,
1117 | interface=interface
1118 | )
1119 | if state == 'present':
1120 | ''' Creating an endpoint for this url
1121 | (if it does not exist)
1122 | '''
1123 | if endpoint is None:
1124 | self.state_change = True
1125 | endpoint = self.keystone.endpoints.create(
1126 | region=region,
1127 | service=service,
1128 | url=url,
1129 | interface=interface
1130 | )
1131 | elif state == 'update':
1132 | ''' Checking if there is a similar endpoint with a
1133 | different url. Update it if there is one, create
1134 | if there is none.
1135 | '''
1136 | similar_endpoint = self._get_endpoint_by_details(
1137 | region=region,
1138 | service_id=service.id,
1139 | interface=interface
1140 | )
1141 | if similar_endpoint is None:
1142 | self.state_change = True
1143 | endpoint = self.keystone.endpoints.create(
1144 | region=region,
1145 | service=service,
1146 | url=url,
1147 | interface=interface
1148 | )
1149 | elif similar_endpoint.url != url:
1150 | self.state_change = True
1151 | endpoint = self.keystone.endpoints.update(
1152 | endpoint=similar_endpoint,
1153 | url=url
1154 | )
1155 | elif state == 'absent':
1156 | if endpoint is not None:
1157 | self.state_change = True
1158 | result = self.keystone.endpoints.delete(endpoint.id)
1159 | if result[0].status_code != 204:
1160 | module.fail()
1161 |
1162 | if state != 'absent':
1163 | endpoints[interface] = endpoint
1164 | return self._facts({'%sid' % interface: endpoint.id
1165 | for interface, endpoint in endpoints.items()})
1166 | else:
1167 | return self._facts({})
1168 |
1169 | def _ensure_generic(self, manager, required_vars, variables):
1170 | """Try and create a new 'thing' in keystone.
1171 |
1172 | Thing type is determined by the manager passed in.
1173 |
1174 | :param: manager - openstack object manager eg self.keystone.groups
1175 | :param: required_vars - dictionary:
1176 | ansible module argument name : manager argument name
1177 | eg {'group_name': 'name'}
1178 |
1179 | :returns: Facts dictionary with things =
1180 |
1181 |
1182 | TODO: make this handle updates as well as creates
1183 | TODO (maybe, if we decide to use this module long term):
1184 | migrate other ensures to use this
1185 | """
1186 |
1187 | # Get values for variables
1188 | variables_dict = self._get_vars(variables,
1189 | required=required_vars.keys())
1190 |
1191 | # Translate ansible module argument names to manager expected names
1192 | args_dict = {required_vars[k]: v for k, v in variables_dict.items()}
1193 |
1194 | try:
1195 | manager.create(**args_dict)
1196 | self.state_change = True
1197 | except kexceptions.Conflict:
1198 | self.state_change = False
1199 |
1200 | try:
1201 | return self._facts(facts={
1202 | manager.collection_key:
1203 | [x.to_dict() for x in manager.list()]
1204 | })
1205 | except TypeError:
1206 | # some managers require arguments to their list functions :/
1207 | # return no facts in this case.
1208 | return self._facts(facts={})
1209 |
1210 | def ensure_identity_provider(self, variables):
1211 | self._authenticate()
1212 | return self._ensure_generic(
1213 | manager=self.keystone.federation.identity_providers,
1214 | required_vars={'idp_name': 'id',
1215 | 'idp_remote_ids': 'remote_ids',
1216 | 'idp_enabled': 'enabled'},
1217 | variables=variables
1218 | )
1219 |
1220 | def ensure_service_provider(self, variables):
1221 | self._authenticate()
1222 | return self._ensure_generic(
1223 | manager=self.keystone.federation.service_providers,
1224 | required_vars={'sp_name': 'id',
1225 | 'sp_auth_url': 'auth_url',
1226 | 'sp_url': 'sp_url',
1227 | 'sp_enabled': 'enabled'},
1228 | variables=variables
1229 | )
1230 |
1231 | def ensure_protocol(self, variables):
1232 | """Facts not returned
1233 |
1234 | This is because you can't list protocols without
1235 | specifying an identity provider
1236 | """
1237 |
1238 | self._authenticate()
1239 | return self._ensure_generic(
1240 | manager=self.keystone.federation.protocols,
1241 | required_vars={'protocol_name': 'protocol_id',
1242 | 'idp_name': 'identity_provider',
1243 | 'mapping_name': 'mapping'},
1244 | variables=variables
1245 | )
1246 |
1247 | def ensure_mapping(self, variables):
1248 | self._authenticate()
1249 | return self._ensure_generic(
1250 | manager=self.keystone.federation.mappings,
1251 | required_vars={'mapping_name': 'mapping_id',
1252 | 'mapping_rules': 'rules'},
1253 | variables=variables
1254 | )
1255 |
1256 | def ensure_domain(self, variables):
1257 | self._authenticate()
1258 | return self._ensure_generic(
1259 | manager=self.keystone.domains,
1260 | required_vars={'domain_name': 'name',
1261 | 'domain_enabled': 'enabled'},
1262 | variables=variables
1263 | )
1264 |
1265 |
1266 | def main():
1267 | module = AnsibleModule(
1268 | argument_spec=dict(
1269 | login_user=dict(
1270 | required=False
1271 | ),
1272 | login_user_domain_name=dict(
1273 | required=False
1274 | ),
1275 | login_password=dict(
1276 | required=False
1277 | ),
1278 | login_tenant_name=dict(
1279 | required=False
1280 | ),
1281 | login_project_name=dict(
1282 | required=False
1283 | ),
1284 | login_project_domain_name=dict(
1285 | required=False
1286 | ),
1287 | token=dict(
1288 | required=False
1289 | ),
1290 | password=dict(
1291 | required=False
1292 | ),
1293 | endpoint=dict(
1294 | required=True,
1295 | ),
1296 | user_name=dict(
1297 | required=False
1298 | ),
1299 | tenant_name=dict(
1300 | required=False
1301 | ),
1302 | project_name=dict(
1303 | required=False
1304 | ),
1305 | domain_name=dict(
1306 | required=False
1307 | ),
1308 | role_name=dict(
1309 | required=False
1310 | ),
1311 | service_name=dict(
1312 | required=False
1313 | ),
1314 | region_name=dict(
1315 | required=False
1316 | ),
1317 | description=dict(
1318 | required=False
1319 | ),
1320 | email=dict(
1321 | required=False
1322 | ),
1323 | service_type=dict(
1324 | required=False
1325 | ),
1326 | endpoint_list=dict(
1327 | required=False,
1328 | type='list'
1329 | ),
1330 | command=dict(
1331 | required=True,
1332 | choices=COMMAND_MAP.keys()
1333 | ),
1334 | insecure=dict(
1335 | default=False,
1336 | required=False,
1337 | type='bool'
1338 | ),
1339 | return_code=dict(
1340 | type='str',
1341 | default='0'
1342 | ),
1343 | group_name=dict(
1344 | type='str',
1345 | required=False
1346 | ),
1347 | idp_remote_ids=dict(
1348 | type='list',
1349 | required=False,
1350 | ),
1351 | idp_name=dict(
1352 | type='str',
1353 | required=False,
1354 | ),
1355 | idp_enabled=dict(
1356 | type='bool',
1357 | default=True,
1358 | required=False,
1359 | ),
1360 | sp_name=dict(
1361 | type='str',
1362 | required=False,
1363 | ),
1364 | sp_auth_url=dict(
1365 | type='str',
1366 | required=False,
1367 | ),
1368 | sp_url=dict(
1369 | type='str',
1370 | required=False,
1371 | ),
1372 | sp_enabled=dict(
1373 | type='bool',
1374 | default=True,
1375 | required=False,
1376 | ),
1377 | protocol_name=dict(
1378 | type='str',
1379 | required=False,
1380 | ),
1381 | mapping_name=dict(
1382 | type='str',
1383 | required=False,
1384 | ),
1385 | mapping_rules=dict(
1386 | type='list',
1387 | required=False,
1388 | ),
1389 | domain_enabled=dict(
1390 | type='bool',
1391 | required=False,
1392 | default=True
1393 | ),
1394 | state=dict(
1395 | choices=['present', 'absent', 'update'],
1396 | required=False,
1397 | default='present'
1398 | )
1399 | ),
1400 | supports_check_mode=False,
1401 | mutually_exclusive=[
1402 | ['token', 'login_user'],
1403 | ['token', 'login_password'],
1404 | ['token', 'login_tenant_name']
1405 | ]
1406 | )
1407 |
1408 | km = ManageKeystone(module=module)
1409 | if not keystoneclient_found:
1410 | km.failure(
1411 | error='python-keystoneclient is missing',
1412 | rc=2,
1413 | msg='keystone client was not importable, is it installed?'
1414 | )
1415 |
1416 | return_code = module.params.get('return_code', '').split(',')
1417 | module.params['return_code'] = return_code
1418 | km.command_router()
1419 |
1420 |
1421 | # import module snippets
1422 | from ansible.module_utils.basic import * # NOQA
1423 | if __name__ == '__main__':
1424 | main()
1425 |
--------------------------------------------------------------------------------