summaryrefslogtreecommitdiffstats
path: root/utils/src/ooinstall/oo_config.py
blob: ea9638fe96af1aa45ecb45f63a09a1a11dfe822d (plain)
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
import os
import yaml
from pkg_resources import resource_filename

PERSIST_SETTINGS = [
    'ansible_ssh_user',
    'ansible_config',
    'ansible_log_path',
    'variant',
    'variant_version',
    ]
REQUIRED_FACTS = ['ip', 'public_ip', 'hostname', 'public_hostname']


class OOConfigFileError(Exception):
    """The provided config file path can't be read/written
    """
    pass


class OOConfigInvalidHostError(Exception):
    """ Host in config is missing both ip and hostname. """
    pass


class Host(object):
    """ A system we will or have installed OpenShift on. """
    def __init__(self, **kwargs):
        self.ip = kwargs.get('ip', None)
        self.hostname = kwargs.get('hostname', None)
        self.public_ip = kwargs.get('public_ip', None)
        self.public_hostname = kwargs.get('public_hostname', None)

        # Should this host run as an OpenShift master:
        self.master = kwargs.get('master', False)

        # Should this host run as an OpenShift node:
        self.node = kwargs.get('node', False)
        self.containerized = kwargs.get('containerized', False)

        if self.ip is None and self.hostname is None:
            raise OOConfigInvalidHostError("You must specify either 'ip' or 'hostname'")

        if self.master is False and self.node is False:
            raise OOConfigInvalidHostError(
                "You must specify each host as either a master or a node.")

        # Hosts can be specified with an ip, hostname, or both. However we need
        # something authoritative we can connect to and refer to the host by.
        # Preference given to the IP if specified as this is more specific.
        # We know one must be set by this point.
        self.name = self.ip if self.ip is not None else self.hostname

    def __str__(self):
        return self.name

    def __repr__(self):
        return self.name

    def to_dict(self):
        """ Used when exporting to yaml. """
        d = {}
        for prop in ['ip', 'hostname', 'public_ip', 'public_hostname',
                     'master', 'node', 'containerized']:
            # If the property is defined (not None or False), export it:
            if getattr(self, prop):
                d[prop] = getattr(self, prop)
        return d


class OOConfig(object):
    new_config = True
    default_dir = os.path.normpath(
        os.environ.get('XDG_CONFIG_HOME',
                       os.environ['HOME'] + '/.config/') + '/openshift/')
    default_file = '/installer.cfg.yml'

    def __init__(self, config_path):
        if config_path:
            self.config_path = os.path.normpath(config_path)
        else:
            self.config_path = os.path.normpath(self.default_dir +
                                                self.default_file)
        self.settings = {}
        self.read_config()
        self.set_defaults()

    def read_config(self, is_new=False):
        self.hosts = []
        try:
            new_settings = None
            if os.path.exists(self.config_path):
                cfgfile = open(self.config_path, 'r')
                new_settings = yaml.safe_load(cfgfile.read())
                cfgfile.close()
            if new_settings:
                self.settings = new_settings
                # Parse the hosts into DTO objects:
                if 'hosts' in self.settings:
                    for host in self.settings['hosts']:
                        self.hosts.append(Host(**host))

                # Watchout for the variant_version coming in as a float:
                if 'variant_version' in self.settings:
                    self.settings['variant_version'] = \
                        str(self.settings['variant_version'])

        except IOError, ferr:
            raise OOConfigFileError('Cannot open config file "{}": {}'.format(ferr.filename,
                                                                              ferr.strerror))
        except yaml.scanner.ScannerError:
            raise OOConfigFileError('Config file "{}" is not a valid YAML document'.format(self.config_path))
        self.new_config = is_new

    def set_defaults(self):

        if 'ansible_inventory_directory' not in self.settings:
            self.settings['ansible_inventory_directory'] = \
                self._default_ansible_inv_dir()
        if not os.path.exists(self.settings['ansible_inventory_directory']):
            os.makedirs(self.settings['ansible_inventory_directory'])
        if 'ansible_plugins_directory' not in self.settings:
            self.settings['ansible_plugins_directory'] = resource_filename(__name__, 'ansible_plugins')

        if 'ansible_callback_facts_yaml' not in self.settings:
            self.settings['ansible_callback_facts_yaml'] = '{}/callback_facts.yaml'.format(self.settings['ansible_inventory_directory'])

        if 'ansible_ssh_user' not in self.settings:
            self.settings['ansible_ssh_user'] = ''

        self.settings['ansible_inventory_path'] = '{}/hosts'.format(self.settings['ansible_inventory_directory'])

        # clean up any empty sets
        for setting in self.settings.keys():
            if not self.settings[setting]:
                self.settings.pop(setting)

    def _default_ansible_inv_dir(self):
        return os.path.normpath(
            os.path.dirname(self.config_path) + "/.ansible")

    def calc_missing_facts(self):
        """
        Determine which host facts are not defined in the config.

        Returns a hash of host to a list of the missing facts.
        """
        result = {}

        for host in self.hosts:
            missing_facts = []
            for required_fact in REQUIRED_FACTS:
                if not getattr(host, required_fact):
                    missing_facts.append(required_fact)
            if len(missing_facts) > 0:
                result[host.name] = missing_facts
        return result

    def save_to_disk(self):
        out_file = open(self.config_path, 'w')
        out_file.write(self.yaml())
        out_file.close()

    def persist_settings(self):
        p_settings = {}
        for setting in PERSIST_SETTINGS:
            if setting in self.settings and self.settings[setting]:
                p_settings[setting] = self.settings[setting]
        p_settings['hosts'] = []
        for host in self.hosts:
            p_settings['hosts'].append(host.to_dict())

        if self.settings['ansible_inventory_directory'] != \
                self._default_ansible_inv_dir():
            p_settings['ansible_inventory_directory'] = \
                self.settings['ansible_inventory_directory']

        return p_settings

    def yaml(self):
        return yaml.safe_dump(self.persist_settings(), default_flow_style=False)

    def __str__(self):
        return self.yaml()

    def get_host(self, name):
        for host in self.hosts:
            if host.name == name:
                return host
        return None