-
Notifications
You must be signed in to change notification settings - Fork 110
/
shutit_module.py
246 lines (208 loc) · 7.85 KB
/
shutit_module.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
239
240
241
242
243
244
245
246
# The MIT License (MIT)
#
# Copyright (C) 2014 OpenBet Limited
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
# of the Software, and to permit persons to whom the Software is furnished to do
# so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# ITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""Abstract class that defines how a ShutIt module should be written.
"""
from __future__ import print_function
import decimal
import inspect
import shutit_util
from abc import ABCMeta, abstractmethod
from six import with_metaclass, iteritems
# TODO: these don't belong here, but this module is 'top level' and doesn't depend on any other shutit files.
class ShutItException(Exception):
"""Placeholder exception. Implementation TODO.
"""
pass
class ShutItModuleError(ShutItException):
"""Placeholder exception. Implementation TODO.
"""
pass
class ShutItFailException(ShutItException):
"""Placeholder exception. Implementation TODO.
"""
pass
def shutit_method_scope(func):
"""Notifies the ShutIt object whenever we call a shutit module method.
This allows setting values for the 'scope' of a function.
"""
def wrapper(self, shutit):
"""Wrapper to call a shutit module method, notifying the ShutIt object.
"""
ret = func(self, shutit)
return ret
return wrapper
class ShutItMeta(ABCMeta):
"""Abstract class that defines what a ShutIt module must implement
to be registered.
"""
ShutItModule = None
def __new__(mcs, name, bases, local):
"""Checks this is a ShutItModule, and wraps any ShutItModule methods
that have been overridden in the subclass.
"""
# Don't wrap methods of the ShutItModule class, only subclasses
if name != 'ShutItModule':
sim = mcs.ShutItModule
assert sim is not None, shutit_util.print_debug()
# Wrap any of the ShutItModule (self, shutit) methods that have been
# overridden in a subclass
for fname, method in iteritems(local):
if not hasattr(sim, fname):
continue
if not callable(method):
continue
sim_method = getattr(sim, fname)
if sim_method is method: # pragma: no cover
continue
args = inspect.getargspec(sim_method)[0]
if args != ['self', 'shutit']:
continue
local[fname] = shutit_method_scope(method)
cls = super(ShutItMeta, mcs).__new__(mcs, name, bases, local)
if name == 'ShutItModule':
mcs.ShutItModule = cls
return cls
class ShutItModule(with_metaclass(ShutItMeta)):
"""Class that takes a ShutIt object and defines what a ShutIt module must
implement to be registered.
Build order:
- Gather core config (build, remove, tag)
- Gather module-specific config
- FOR MODULE 0
- Build module 0
- FOR ALL MODULES:
- Determine dependency requirements are met
- Determine conflict requirements are met.
- Remove any modules that are configured for removal.
- Build if not installed
- Do repo work if not installed (commit, tag, push)
- Test all modules (in reverse)
- Finalize all modules
- FOR MODULE 0
- Do repo work on build
"""
def __init__(self, module_id, run_order, description='', maintainer='', depends=None, conflicts=None, delivery_methods=None):
"""Constructor.
Sets up module_id, run_order, deps and conflicts.
Also checks types for safety.
"""
# Module id for the module (a string).
# Following the Java standard is recommended, eg 'com.bigcorp.project.alpha.mymodule'
# Duplicate module ids are rejected if within the configured
# shutit_module_path.
self.module_id = module_id
if not isinstance(module_id, str): # pragma: no cover
err = str(module_id) + '\'s module_id is not a string'
shutit_global.shutit_global_object.shutit_print(err)
raise ShutItModuleError(err)
# run_order for the module (a float).
# It should be a float and not duplicated within the shutit_module path.
# Module 0 is special. It is expected to:
# - Set up a target (see shutit_setup.py)
# - Set up pexpect children with relevant keys and populate
# shutit_pexpect_children.
if isinstance(run_order, (float, int, str)):
run_order = decimal.Decimal(run_order)
# Check that run_order is a float - this will throw an error as a
# side effect if float doesn't work.
if not isinstance(run_order, decimal.Decimal): # pragma: no cover
err = module_id + '\'s run order is not a decimal'
shutit_global.shutit_global_object.shutit_print(err)
raise ShutItModuleError(err)
self.run_order = run_order
# module ids depended on
self.depends_on = []
if depends is not None:
self.depends_on = [dep for dep in depends]
# module ids this is known to conflict with.
self.conflicts_with = []
if conflicts is not None:
self.conflicts_with = [conflict for conflict in conflicts]
self.description = description
self.maintainer = maintainer
if not delivery_methods:
# default to all
delivery_methods = ['ssh','dockerfile','bash','docker']
if isinstance(delivery_methods, str):
delivery_methods = [delivery_methods]
self.ok_delivery_methods = delivery_methods
########################################################################
# Abstract methods
########################################################################
def get_config(self, shutit):
"""Gets all config items necessary for this module to be built
"""
return True
def check_ready(self, shutit):
"""Checks whether we are ready to build this module.
This is called before the build, to ensure modules have
their requirements in place before we commence the build.
Checking whether the build will happen at all (and
therefore whether the check should take place) will be
determined by the framework.
Should return True if it's ready to run, else False.
"""
return True
def remove(self, shutit):
"""Remove the module, which should ensure the module has been deleted
from the system.
Returns True if all removed without any errors, else False.
"""
return False
def start(self, shutit):
"""Run when module should be installed (is_installed() or configured
to build is true)
Run after repository work.
Returns True if all started ok.
"""
return True
def stop(self, shutit):
"""Runs when module should be stopped.
Runs before repo work, and before finalize is called.
Returns True if all stopped ok.
"""
return True
def is_installed(self, shutit):
"""Determines whether the module has been built in this target host
already.
Returns True if it is certain it's there, else False.
Required.
"""
return shutit.is_shutit_installed(self.module_id)
@abstractmethod
def build(self, shutit):
"""Runs the build part of the module, which should ensure the module has been set up. If is_installed determines that the module is already there, this is not run.
Returns True if it has succeeded in building, else False.
Required.
"""
pass
def test(self, shutit):
"""Tests the module is OK.
Returns True if all is OK, else False.
This is run regardless of whether the module is installed or not.
"""
return True
def finalize(self, shutit):
"""Finalize the module, ie do things that need doing after final module
has been run and before we exit, eg updatedb.
"""
return True