-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathcreate_inventory.py
238 lines (186 loc) · 9.07 KB
/
create_inventory.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# create_inventory.py - Create inventory file for openshift-ansible
#
# Copyright © 2017 Red Hat Inc.
#
# 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.
#
# 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. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function, unicode_literals
import ipaddress
import argparse
import sys
import os.path
# These are compoments that a user can disable or enable
OPTIONAL_COMPONENTS = ['logging', 'loggingops', 'metrics', 'prometheus', 'manageiq']
# These are components that are mandatory
MANDATORY_COMPONENTS = ['hosted_registry']
ALL_COMPONENTS = MANDATORY_COMPONENTS + OPTIONAL_COMPONENTS
DEFAULT_IMAGE_VERSION = 'v3.7'
DEFAULT_WILDCARD_DNS = 'nip.io'
PATH = os.path.dirname(os.path.abspath(__file__))
BLOCKS_PATH = os.path.join(PATH, "inventory_blocks")
def str2bool(s):
""" Convert str argument to bool """
if s.lower() in ('', 'yes', 'true', 't', 'y', '1'):
return True
elif s.lower() in ('no', 'false', 'f', 'n', '0'):
return False
else:
raise argparse.ArgumentTypeError('Boolean value expected.')
def validate_ip(ipaddr):
""" Ensure a given IP address is valid """
try:
ipaddress.ip_address(u''+ipaddr)
return True
except ValueError:
return False
def validate_ips(ips, name):
""" Validate ip addresses and return a list of invalid ones """
invalid = []
for ip in ips:
if not validate_ip(ip):
print("Invalid ip '{0}' in {1}".format(ip, name), file=sys.stderr)
invalid.append(ip)
return invalid
def format_host(node_type, name_prefix, index, ip, wildcard_dns):
""" Build a hostname for a node """
host = "{name_prefix}-{node_type}{i:03d}.{ip}.{wildcard_dns}"
host = host.format(name_prefix=name_prefix,
node_type=node_type,
i=index,
ip=ip,
wildcard_dns=wildcard_dns)
if len(host) > 63:
raise SystemExit("hostname too long: {0}".format(host))
return host
def build_spec(node_type, ips, name_prefix, wildcard_dns):
""" Build spec for compute/infra nodes """
spec_list = ""
if node_type == "infra":
node_labels = "{'region': 'infra', 'zone': 'default'}"
else:
node_labels = "{'region': 'primary', 'zone': 'default'}"
for i, ip in enumerate(ips, 1):
host = format_host(node_type, name_prefix, i, ip, wildcard_dns)
spec = "{host} openshift_hostname={host} openshift_node_labels=\"{node_labels}\"\n"
spec = spec.format(node_labels=node_labels, host=host)
spec_list += spec
return spec_list
def format_block(component, storage_type, format_args):
""" Format the block from the templates """
# Main block - settings that are required no matter what is the storage type
block_file = BLOCKS_PATH + "/{0}.ini".format(component)
if not os.path.exists(block_file):
block_file = BLOCKS_PATH + "/DEFAULT.ini"
with open(block_file, 'r') as f:
component_block_template = f.read()
# Storage_type specific settings
storage_block_file = BLOCKS_PATH + "/{component}_{storage}.ini".format(component=component,
storage=storage_type)
if not os.path.exists(storage_block_file):
storage_block_file = BLOCKS_PATH + "/DEFAULT_{0}.ini".format(storage_type)
with open(storage_block_file, 'r') as f:
component_block_template += '\n'
component_block_template += f.read()
format_args['component'] = component
return component_block_template.format(**format_args)
def create_inventory(master, infra, compute, args):
""" Create inventory file from template. `args` are the command line arguments """
# Build host specs
master_hostname = format_host("master", args.name_prefix, 1, master,
args.wildcard_dns)
master_spec = "{master_hostname} openshift_hostname={master_hostname}".format(master_hostname=master_hostname)
# prepare arguments for formatting the template
format_args = vars(args) # start with the command line arguments
# Build the spec for the nodes
format_args['master_spec'] = master_spec
format_args['infra_spec'] = build_spec("infra", infra, args.name_prefix, args.wildcard_dns)
format_args['compute_spec'] = build_spec("compute", compute, args.name_prefix, args.wildcard_dns)
format_args['infra_router_ip'] = infra[0]
if args.storage == 'internal_nfs':
# Add "nfs" to OSv3:children
format_args['nfs_child'] = 'nfs'
# Add the nfs block and point it to the selected NFS server
# (or master, if nfs server is not defined)
if not args.nfs_server:
format_args['nfs_block'] = '[nfs]\n' + master_spec
else:
format_args['nfs_block'] = '[nfs]\n' + args.nfs_server
else:
# No internal NFS
format_args['nfs_child'] = ''
format_args['nfs_block'] = ''
# Build the component blocks
for component in ALL_COMPONENTS:
if component in OPTIONAL_COMPONENTS and not format_args['enable_' + component]:
# This component is optional and is disabled
format_args[component + '_block'] = "# No {0}".format(component)
continue
# This componet is enabled, fromat the block
format_args[component + '_block'] = format_block(component,
args.storage,
format_args)
# read the main template
with open(PATH+"/inventory-template.ini", "r") as f:
template = f.read()
print(template.format(**format_args))
def main():
# Parse command line arguments
parser = argparse.ArgumentParser(description='Build inventory file for openshift-ansible')
# options for storage
parser.add_argument('--storage', nargs='?', default="external_nfs",
choices=['external_nfs', 'internal_nfs'])
parser.add_argument('--nfs-server', nargs='?', type=str)
parser.add_argument('--nfs-export-path', nargs='?', type=str)
# Enable/Disable various components
for component in OPTIONAL_COMPONENTS:
is_default = component != "manageiq"
parser.add_argument('--enable-{0}'.format(component), nargs='?',
type=str2bool, const=True, default=is_default)
parser.add_argument('--install-examples', const=True, nargs='?', type=str2bool, default=True)
# optional parameters
parser.add_argument('--additional-registries', type=str, default="")
parser.add_argument('--additional-repos', type=str, default="")
parser.add_argument('--image-version', type=str, default=DEFAULT_IMAGE_VERSION)
parser.add_argument('--ldap-providers', type=str, default="")
parser.add_argument('--ca-path', type=str, default="")
parser.add_argument('--wildcard-dns', type=str, default=DEFAULT_WILDCARD_DNS)
parser.add_argument('--manageiq-image', type=str, default="docker.io/containermgmt/manageiq-pods")
# Mandatory parameters: cluster IPs, name prefix
parser.add_argument('--name-prefix', type=str, required=True)
parser.add_argument('--master-ip', type=str, required=True)
parser.add_argument('--infra-ips', type=str, required=True)
parser.add_argument('--compute-ips', type=str, required=True)
args = parser.parse_args()
# Make sure external NFS is properly defined
if args.storage == "external_nfs":
if args.nfs_server is None or args.nfs_export_path is None:
raise SystemExit("--nfs-server and --nfs-export-path are required when storage is set to ext_nfs")
# Validate IPs and provide detailed information if any is invalid
master = args.master_ip.strip()
infra_list = args.infra_ips.strip().split(" ")
compute_list = args.compute_ips.strip().split(" ")
invalid = []
invalid += validate_ips([master], "master")
invalid += validate_ips(infra_list, "infra")
invalid += validate_ips(compute_list, "compute")
if invalid:
raise SystemExit("Can't create inventory: {0} invalid IPs specified".format(len(invalid)))
if master in infra_list or master in compute_list:
raise SystemExit("Can't create inventory: Master IP ({0}) present in compute / infra".format(master))
if not set(infra_list).isdisjoint(compute_list):
raise SystemExit("Can't create inventory: Same IP used for both an infra node and a compute node. This is not supported.")
create_inventory(master, infra_list, compute_list, args)
if __name__ == "__main__":
main()