class TestHelm(EventTestCase): def setUp(self): super(TestHelm, self).setUp() self.sensors = Mock() self.logger = Mock() self.steerer = Mock() self.helm = Helm(self.exchange, self.sensors, self.steerer, self.logger, HELM_CONFIG) self.helm.previous_heading = 180 def currently_tracking(self, previous_heading, current_track, rudder_angle=0): self.sensors.compass_heading_smoothed = current_track self.sensors.compass_heading_average = NaN self.sensors.rate_of_turn = current_track - previous_heading self.sensors.rate_of_turn_average = self.sensors.rate_of_turn def averagely_tracking(self, previous_heading, current_track): self.sensors.compass_heading_average = current_track self.sensors.compass_heading_smoothed = NaN self.sensors.rate_of_turn = 0 self.sensors.rate_of_turn_average = current_track - previous_heading def test_should_steer_following_set_course_event(self): self.averagely_tracking(204, 200) self.steerer.on_course.side_effect = [False, False] self.exchange.publish(Event(EventName.set_course, heading=196)) self.steerer.steer.assert_called_with(196, 200, -4) self.averagely_tracking(200, 202) self.sensors.rate_of_turn_average = -20 self.exchange.publish(Event(EventName.check_course)) self.steerer.steer.assert_called_with(196, 202, -20) def test_should_not_steer_if_steerer_says_we_are_on_course(self): self.averagely_tracking(300, 290) self.steerer.on_course.side_effect = [True] self.exchange.publish(Event(EventName.set_course, heading=294)) self.assertEqual(self.steerer.steer.call_count, 0) def test_should_steer_if_off_course_by_more_than_configured_20_degrees( self): self.helm.requested_heading = 90 self.averagely_tracking(50, 60) self.steerer.on_course.side_effect = [False] self.helm.check_course(Event(EventName.check_course)) self.steerer.steer.assert_called_with(90, 60, 10) def test_should_subscribe_check_course_every_10_seconds(self): self.listen(EventName.every) helm = Helm(self.exchange, self.sensors, self.steerer, self.logger, HELM_CONFIG) self.assertEqual(self.events[EventName.every][0].next_event.name, EventName.check_course)
def __init__(self, file): """ Parse course.yml contents into instances. """ self.config = Config() self.helm = Helm() self._dict = yaml.load(file) self._repositories = [] self._charts = [] for name, repository in self._dict.get('repositories', {}).iteritems(): repository['name'] = name self._repositories.append(Repository(repository)) for name, chart in self._dict.get('charts', {}).iteritems(): self._charts.append(Chart({name: chart})) for repo in self._repositories: type(repo) if not self.config.local_development: logging.debug("Installing repository: {}".format(repo)) repo.install() self.helm.repo_update() if not self.config.local_development: self._compare_required_versions()
def setUp(self): super(TestHelm, self).setUp() self.sensors = Mock() self.logger = Mock() self.steerer = Mock() self.helm = Helm(self.exchange, self.sensors, self.steerer, self.logger, HELM_CONFIG) self.helm.previous_heading = 180
def install(self, namespace=None, context=None): """ Description: - Uprade --install the course chart Arguments: - namespace (string). Passed in but will be overriddne by Chart().namespace if set Returns: - Bool """ helm = Helm() # Set the namespace if self.namespace is None: self._namespace = namespace # Set the context if self.context is None: self._context = context self.pre_install_hook() # TODO: Improve error handling of a repository installation self.repository.install(self.name, self.version) self.chart_path = self.repository.chart_path # Update the helm dependencies if self.repository.git is None: self.update_dependencies() # Build the args for the chart installation # And add any extra arguments self.args = ['{}'.format(self._release_name), self.chart_path, ] self.args.append('--namespace={}'.format(self.namespace)) if self.context is not None: self.args.append('--kube-context={}'.format(self.context)) self.args.extend(self.debug_args) self.args.extend(self.helm_args) if self.version: self.args.append('--version={}'.format(self.version)) for file in self.files: self.args.append("-f={}".format(file)) for key, value in self.values.iteritems(): for k, v in self._format_set(key, value): self.args.append("--set={}={}".format(k, v)) for key, value in self.values_strings.iteritems(): for k, v in self._format_set(key, value): self.args.append("--set={}={}".format(k, v)) self.__check_env_vars() try: helm.upgrade(self.args) except ReckonerCommandException, e: logging.error(e.stderr) raise e
def __init__(self, chart): self.helm = Helm() self.config = Config() self._release_name = chart.keys()[0] self._chart = chart[self._release_name] self._repository = Repository(self._chart.get('repository', default_repository)) self._chart['values'] = self.ordereddict_to_dict(self._chart.get('values', {})) value_strings = self._chart.get('values-strings', {}) self._chart['values_strings'] = self.ordereddict_to_dict(value_strings) if value_strings != {}: del(self._chart['values-strings'])
def __init__(self, file=None, dryrun=False, debug=False, helm_args=None, local_development=False): self.config = Config() self.config.dryrun = dryrun self.config.debug = debug self.config.helm_args = helm_args self.config.local_development = local_development if self.config.debug: logging.warn( "The --debug flag will be deprecated. Please use --helm-args or --dry-run instead." ) if self.config.helm_args: logging.warn( "Specifying --helm-args on the cli will override helm_args in the course file." ) try: self.helm = Helm() except Exception, e: logging.error(e) sys.exit(1)
def test_should_subscribe_check_course_every_10_seconds(self): self.listen(EventName.every) helm = Helm(self.exchange, self.sensors, self.steerer, self.logger, HELM_CONFIG) self.assertEqual(self.events[EventName.every][0].next_event.name, EventName.check_course)
def test_should_navigate_to_next_waypoint_with_kink_in_route(self): destination = Waypoint(Position(10.03, 10.03), 10) gps = FakeMovingGPS([ Position(10, 10), Position(10.01, 10.01), Position(10.025, 10.015), Position(10.03, 10.03) ]) sensors = FakeSensors(gps, 1, 45) steerer = Steerer(self.servo, self.logger, CONFIG['steerer']) helm = Helm(self.exchange, sensors, steerer, self.logger, CONFIG['helm']) navigator = Navigator(sensors, Globe(), self.exchange, self.logger, CONFIG['navigator']) self.exchange.publish(Event(EventName.navigate, waypoint=destination)) self.ticks(number=14, duration=200) self.logger.info.assert_has_calls([ call( 'Navigator, steering to +10.030000,+10.030000, bearing 44.6, distance 4681.8m, review after 600s' ), call( 'Navigator, steering to +10.030000,+10.030000, bearing 44.6, distance 3121.2m, review after 600s' ), call( 'Navigator, steering to +10.030000,+10.030000, bearing 71.3, distance 1734.0m, review after 600s' ), call('Navigator, arrived at +10.030000,+10.030000') ])
def test_should_steer_repeatedly_during_navigation(self): logger = Mock() destination = Waypoint(Position(10.0003, 10.0003), 10) gps = FakeMovingGPS([ Position(10, 10), Position(10.0001, 10.00015), Position(10.00025, 10.0002), Position(10.0003, 10.0003) ]) sensors = FakeSensors(gps, 1, 45) steerer = Steerer(self.servo, logger, CONFIG['steerer']) helm = Helm(self.exchange, sensors, steerer, logger, CONFIG['helm']) navigator = Navigator(sensors, Globe(), self.exchange, logger, CONFIG['navigator']) self.exchange.publish(Event(EventName.navigate, waypoint=destination)) self.ticks(number=7, duration=20) logger.debug.assert_has_calls([ call( 'Navigator, distance from waypoint +46.819018, combined tolerance +10.000000' ), call( 'Navigator, distance from waypoint +27.647432, combined tolerance +10.000000' ), call( 'Steerer, steering 36.4, heading 45.0, rate of turn +1.0, rudder +0.0, new rudder +4.6' ), call( 'Steerer, steering 36.4, heading 45.0, rate of turn +1.0, rudder +4.6, new rudder +9.2' ), call( 'Navigator, distance from waypoint +12.281099, combined tolerance +10.000000' ), call( 'Steerer, steering 63.1, heading 45.0, rate of turn +1.0, rudder +9.2, new rudder +0.4' ), call( 'Navigator, distance from waypoint +0.000000, combined tolerance +10.000000' ), call( 'Steerer, steering 63.1, heading 45.0, rate of turn +1.0, rudder +0.4, new rudder -8.3' ), call( 'Steerer, steering 63.1, heading 45.0, rate of turn +1.0, rudder -8.3, new rudder -17.1' ) ]) logger.info.assert_has_calls([ call( 'Navigator, steering to +10.000300,+10.000300, bearing 44.6, distance 46.8m, review after 23s' ), call( 'Navigator, steering to +10.000300,+10.000300, bearing 36.4, distance 27.6m, review after 13s' ), call( 'Navigator, steering to +10.000300,+10.000300, bearing 63.1, distance 12.3m, review after 6s' ), call('Navigator, arrived at +10.000300,+10.000300') ])
def install(self, chart_name=None, version=None): """ Install Helm repository """ from helm import Helm # currently cheating to get around a circular import issue helm = Helm() if self.git is None: self._chart_path = "{}/{}".format(self.name, chart_name) if self not in helm.repositories: try: return helm.repo_add(str(self.name), str(self.url)) except AutoHelmCommandException, e: logging.warn("Unable to install repository {}: {}".format(self.name, e.stderr)) return False else: logging.debug("Chart repository {} already installed".format(self.name)) return True
def __init__(self, gps=False, servo_port=SERVO_PORT): # devices self._gps = gps self.windsensor = WindSensor(I2C(WINDSENSOR_I2C_ADDRESS)) self.compass = Compass(I2C(COMPASS_I2C_ADDRESS), I2C(ACCELEROMETER_I2C_ADDRESS)) self.red_led = GpioWriter(17, os) self.green_led = GpioWriter(18, os) # Navigation self.globe = Globe() self.timer = Timer() self.application_logger = self._rotating_logger(APPLICATION_NAME) self.position_logger = self._rotating_logger("position") self.exchange = Exchange(self.application_logger) self.timeshift = TimeShift(self.exchange, self.timer.time) self.event_source = EventSource(self.exchange, self.timer, self.application_logger, CONFIG['event source']) self.sensors = Sensors(self.gps, self.windsensor, self.compass, self.timer.time, self.exchange, self.position_logger, CONFIG['sensors']) self.gps_console_writer = GpsConsoleWriter(self.gps) self.rudder_servo = Servo(serial.Serial(servo_port), RUDDER_SERVO_CHANNEL, RUDDER_MIN_PULSE, RUDDER_MIN_ANGLE, RUDDER_MAX_PULSE, RUDDER_MAX_ANGLE) self.steerer = Steerer(self.rudder_servo, self.application_logger, CONFIG['steerer']) self.helm = Helm(self.exchange, self.sensors, self.steerer, self.application_logger, CONFIG['helm']) self.course_steerer = CourseSteerer(self.sensors, self.helm, self.timer, CONFIG['course steerer']) self.navigator = Navigator(self.sensors, self.globe, self.exchange, self.application_logger, CONFIG['navigator']) self.self_test = SelfTest(self.red_led, self.green_led, self.timer, self.rudder_servo, RUDDER_MIN_ANGLE, RUDDER_MAX_ANGLE) # Tracking self.tracking_logger = self._rotating_logger("track") self.tracking_sensors = Sensors(self.gps, self.windsensor, self.compass, self.timer.time, self.exchange, self.tracking_logger, CONFIG['sensors']) self.tracker = Tracker(self.tracking_logger, self.tracking_sensors, self.timer)
def __init__(self, chart): self.helm = Helm() self.config = Config() self._release_name = chart.keys()[0] self._chart = chart[self._release_name] self._repository = Repository(self._chart.get('repository', default_repository)) self._chart['values'] = self._chart.get('values', {}) self._namespace = self._chart.get('namespace') self._context = self._chart.get('context') value_strings = self._chart.get('values-strings', {}) self._chart['values_strings'] = value_strings if value_strings != {}: del(self._chart['values-strings'])
def __init__(self): self.globe = Globe() self.console_logger = self._console_logger() self.exchange = Exchange(self.console_logger) self.gps = SimulatedGPS(CHORLTON.position,0,0.1) self.vehicle = SimulatedVehicle(self.gps, self.globe,self.console_logger,single_step=False) self.timeshift = TimeShift(self.exchange,self.vehicle.timer.time) self.event_source = EventSource(self.exchange,self.vehicle.timer,self.console_logger,CONFIG['event source']) self.sensors = Sensors(self.vehicle.gps, self.vehicle.windsensor,self.vehicle.compass,self.vehicle.timer.time,self.exchange,self.console_logger,CONFIG['sensors']) self.steerer = Steerer(self.vehicle.rudder,self.console_logger,CONFIG['steerer']) self.helm = Helm(self.exchange, self.sensors, self.steerer, self.console_logger, CONFIG['helm']) self.course_steerer = CourseSteerer(self.sensors,self.helm,self.vehicle.timer, CONFIG['course steerer']) self.navigator_simulator = Navigator(self.sensors,self.globe,self.exchange,self.console_logger,CONFIG['navigator']) self.tracking_timer = Timer() self.tracker_simulator = Tracker(self.console_logger,self.sensors,self.tracking_timer)
def __init__(self, file=None, dryrun=False, debug=False, helm_args=None, local_development=False): self.config = Config() self.config.dryrun = dryrun self.config.debug = debug self.config.helm_args = helm_args self.config.local_development = local_development try: self.helm = Helm() except Exception, e: logging.error(e) sys.exit(1)
def __init__(self, file=None, dryrun=False, debug=False, helm_args=None, local_development=False): self.config = Config() self.config.dryrun = dryrun self.config.debug = debug self.config.helm_args = helm_args self.config.local_development = local_development self.helm = Helm() if not self.config.local_development and not self.helm.server_version: logging.error( "Tiller not present in cluster. Have you run `helm init`?") sys.exit(1) self.course = Course(file)
def setUp(self): super(TestHelmOscillation, self).setUp() # Sensors mock_angle = PropertyMock(return_value=3.0) self.time = 0 self.gps = StubGPS() self.windsensor = Mock() self.compass = Mock() self.compass.bearing = 0 type(self.windsensor).angle = mock_angle self.logger = Mock() self.sensors = Sensors(self.gps, self.windsensor, self.compass, self.mock_time, self.exchange, self.logger, SENSOR_CONFIG) self.rudder_servo = RudderSimulator() self.steerer = Steerer(self.rudder_servo, self.logger, STEERER_CONFIG) self.helm = Helm(self.exchange, self.sensors, self.steerer, self.logger, HELM_CONFIG) self.helm.previous_heading = 0
class TestCluster: def __init__(self, kube_config, storage_provisioner, registry): self.kube_config = kube_config self.registry = registry self.storage_provisioner = storage_provisioner self.current_context = None self.helm = None self.namespaces = [] def _load_kube_config(self): config.load_kube_config(config_file=self.kube_config) _, context = config.list_kube_config_contexts(config_file=self.kube_config) self.current_context = context["name"] def create_image_pull_secret(self, namespace="default"): secret_metadata = client.V1ObjectMeta(name="image-pull-secret") auth_string = str.encode(f"{self.registry['user']}:{self.registry['pwd']}") secret_data = { "auths": { self.registry["url"]: { "auth": base64.b64encode(auth_string).decode("utf-8") } } } secret_data = json.dumps(secret_data).encode() secret_body = client.V1Secret( api_version="v1", kind="Secret", metadata=secret_metadata, type="kubernetes.io/dockerconfigjson", data={".dockerconfigjson": base64.b64encode(secret_data).decode("utf-8")}, ) core_v1 = client.CoreV1Api() try: core_v1.create_namespaced_secret(namespace, secret_body) except client.rest.ApiException as exc: if exc.status == 409 and exc.reason == "Conflict": warnings.warn( "Kubernetes Cluster not empty. Image pull secret already exists." ) else: raise exc def create_namespace(self, name): namespace_metadata = client.V1ObjectMeta(name=name) namespace_body = client.V1Namespace( kind="Namespace", api_version="v1", metadata=namespace_metadata ) core_v1 = client.CoreV1Api() core_v1.create_namespace(body=namespace_body) self.namespaces.append(name) self.create_image_pull_secret(name) def delete_namespace(self, name): core_v1 = client.CoreV1Api() core_v1.delete_namespace(name, body=client.V1DeleteOptions()) self.namespaces.remove(name) def install_storage_provisioner(self): self.storage_provisioner.set_helm_connector(self.helm) self.storage_provisioner.deploy() def setup(self): self._load_kube_config() self.create_image_pull_secret() self.helm = Helm(self.kube_config, self.current_context) self.install_storage_provisioner() def cleanup(self): while self.namespaces: self.helm.delete_all( namespace=self.namespaces[0], exceptions=[self.storage_provisioner.name] ) self.delete_namespace(self.namespaces[0]) self.storage_provisioner.delete() core_v1 = client.CoreV1Api() core_v1.delete_namespaced_secret( "image-pull-secret", "default", body=client.V1DeleteOptions() )
class Course(object): """ Description: - Top level class for the attribues of the course.yml file - Parses yaml file into verious Reckoner classes Arguments: - file (File) Attributes: - config: Instance of Config() - helm: Instance of Helm() - charts: List of Chart() instances - repositories: List of Repository() instances """ def __init__(self, file): """ Parse course.yml contents into instances. """ self.config = Config() self.helm = Helm() self._dict = yaml.load(file) self._repositories = [] self._charts = [] for name, repository in self._dict.get('repositories', {}).iteritems(): repository['name'] = name self._repositories.append(Repository(repository)) for name, chart in self._dict.get('charts', {}).iteritems(): self._charts.append(Chart({name: chart})) for repo in self._repositories: type(repo) if not self.config.local_development: logging.debug("Installing repository: {}".format(repo)) repo.install() self.helm.repo_update() if not self.config.local_development: self._compare_required_versions() def __str__(self): return str(self._dict) @property def repositories(self): """ Course repositories """ return self._repositories def __getattr__(self, key): return self._dict.get(key) @property def charts(self): """ List of Chart() instances """ return self._charts def plot(self, charts_to_install): """ Accepts charts_to_install, an interable of the names of the charts to install. This method compares the charts in the argument to the charts in the course and calls Chart.install() """ _charts = [] _failed_charts = [] self._charts_to_install = [] try: iter(charts_to_install) except TypeError: charts_to_install = (charts_to_install) for chart in self.charts: if chart.release_name in charts_to_install: self._charts_to_install.append(chart) for chart in self._charts_to_install: logging.info("Installing {}".format(chart.release_name)) try: chart.install(namespace=self.namespace, context=self.context) except (Exception, ReckonerCommandException), e: if type(e) == ReckonerCommandException: logging.error(e.stderr) logging.error('Helm upgrade failed. Rolling back {}'.format( chart.release_name)) logging.debug(traceback.format_exc()) chart.rollback _failed_charts.append(chart) if _failed_charts: logging.error( "ERROR: Some charts failed to install and were rolled back") for chart in _failed_charts: logging.error(" - {}".format(chart.release_name)) return True
def setup(self): self._load_kube_config() self.create_image_pull_secret() self.helm = Helm(self.kube_config, self.current_context) self.install_storage_provisioner()
class TestHelm(EventTestCase): def setUp(self): super(TestHelm, self).setUp() self.sensors = Mock() self.logger = Mock() self.steerer = Mock() self.helm = Helm(self.exchange, self.sensors, self.steerer, self.logger, HELM_CONFIG) self.helm.previous_heading = 180 def reduction_factor(self): factor = float(HELM_CONFIG["turn steer interval"]) / (HELM_CONFIG["on course check interval"]) return factor def currently_tracking(self, previous_heading, current_track, rudder_angle=0): self.sensors.compass_heading_instant = current_track self.sensors.compass_heading_average = NaN self.sensors.rate_of_turn = current_track - previous_heading self.sensors.rate_of_turn_average = self.sensors.rate_of_turn def averagely_tracking(self, previous_heading, current_track): self.sensors.compass_heading_average = current_track self.sensors.compass_heading_instant = NaN self.sensors.rate_of_turn = 0 self.sensors.rate_of_turn_average = current_track - previous_heading def test_should_steer_following_set_course_event(self): self.currently_tracking(204, 200) self.exchange.publish(Event(EventName.set_course, heading=196)) self.steerer.steer.assert_called_with(196, 200, -4) self.sensors.track = 202 self.sensors.rate_of_turn = -20 self.exchange.publish(Event(EventName.steer)) self.steerer.steer.assert_called_with(196, 200, -20) def test_should_use_instant_heading_when_turning(self): self.helm.requested_heading = 5 self.currently_tracking(340, 350) self.helm.turn(Event(EventName.steer)) self.steerer.steer.assert_called_with(5, 350, 10) def test_should_use_average_heading_and_reduction_factor_when_checking_course(self): self.helm.requested_heading = 5 self.averagely_tracking(335, 350) self.helm.check_course(Event(EventName.steer)) self.steerer.steer.assert_called_with(5, 350, 15, self.reduction_factor()) def test_should_trigger_turning_if_off_course_by_more_than_configured_20_degrees(self): self.exchange.unsubscribe(EventName.steer, self.helm.turn) self.helm.requested_heading = 90 self.averagely_tracking(50, 60) self.helm.check_course(Event(EventName.steer)) self.steerer.steer.assert_called_with(90, 60, 10, self.reduction_factor()) self.assertIn(self.helm.turn, self.exchange.register[EventName.steer]) self.assertTrue(self.helm.turning) def test_should_immediately_change_to_turning_when_course_is_set(self): self.exchange.unsubscribe(EventName.steer, self.helm.turn) self.currently_tracking(50, 60) self.exchange.publish(Event(EventName.set_course, heading=90)) self.steerer.steer.assert_called_with(90, 60, 10) self.assertIn(self.helm.turn, self.exchange.register[EventName.steer]) self.assertTrue(self.helm.turning) def test_should_unsubscribe_turn_to_steer_event_when_on_course_after_configured_three_checks(self): self.currently_tracking(120, 125) self.exchange.publish(Event(EventName.set_course, heading=90)) self.currently_tracking(95, 85) self.exchange.publish(Event(EventName.steer)) self.currently_tracking(85, 87) self.exchange.publish(Event(EventName.steer)) self.currently_tracking(85, 87) self.exchange.publish(Event(EventName.steer)) self.currently_tracking(85, 87) self.exchange.publish(Event(EventName.steer)) self.assertNotIn(self.helm.turn, self.exchange.register[EventName.steer]) self.assertFalse(self.helm.turning) def test_should_continue_turning_if_on_course_with_high_rate_of_turn(self): self.currently_tracking(95, 85) self.exchange.publish(Event(EventName.set_course, heading=90)) self.steerer.on_course.side_effect = [False, False, False, False] self.currently_tracking(85, 95) self.exchange.publish(Event(EventName.steer)) self.currently_tracking(95, 85) self.exchange.publish(Event(EventName.steer)) self.currently_tracking(85, 95) self.exchange.publish(Event(EventName.steer)) self.currently_tracking(95, 85) self.exchange.publish(Event(EventName.steer)) self.assertIn(self.helm.turn, self.exchange.register[EventName.steer]) self.assertTrue(self.helm.turning) def test_should_subscribe_check_course_every_10_seconds(self): self.listen(EventName.every) helm = Helm(self.exchange, self.sensors, self.steerer, self.logger, HELM_CONFIG) self.assertEqual(self.events[EventName.every][0].next_event.name, EventName.check_course) def test_should_generate_repeating_steer_event_according_to_config(self): self.listen(EventName.every) helm = Helm(self.exchange, self.sensors, self.steerer, self.logger, HELM_CONFIG) repeating_steer_event = self.events[EventName.every][1] self.assertEqual(repeating_steer_event.next_event.name, EventName.steer) self.assertEqual(repeating_steer_event.seconds, 3) def test_check_course_should_not_steer_if_we_are_turning(self): self.helm.turning = True self.helm.check_course(Event(EventName.steer)) self.assertEqual(self.steerer.steer.call_count, 0)
class Course(object): """ Description: - Top level class for the attribues of the course.yml file - Parses yaml file into verious AutoHelm classes Arguments: - file (File) Attributes: - config: Instance of Config() - helm: Instance of Helm() - charts: List of Chart() instances - repositories: List of Repository() instances """ def __init__(self, file): """ Parse course.yml contents into instances. """ self.config = Config() self.helm = Helm() self._dict = yaml.load(file) self._repositories = [] self._charts = [] for name, repository in self._dict.get('repositories', {}).iteritems(): repository['name'] = name self._repositories.append(Repository(repository)) for name, chart in self._dict.get('charts', {}).iteritems(): self._charts.append(Chart({name: chart})) for repo in self._repositories: type(repo) if not self.config.local_development: logging.debug("Installing repository: {}".format(repo)) repo.install() self.helm.repo_update() if not self.config.local_development: self._compare_required_versions() def __str__(self): return str(self._dict) @property def repositories(self): """ Course repositories """ return self._repositories def __getattr__(self, key): return self._dict.get(key) @property def charts(self): """ List of Chart() instances """ return self._charts def plot(self, charts_to_install): """ Accepts charts_to_install, an interable of the names of the charts to install. This method compares the charts in the argument to the charts in the course and calls Chart.install() """ _charts = [] _failed_charts = [] self._charts_to_install = [] try: iter(charts_to_install) except TypeError: charts_to_install = (charts_to_install) for chart in self.charts: if chart.release_name in charts_to_install: self._charts_to_install.append(chart) for chart in self._charts_to_install: logging.debug("Installing {}".format(chart.name)) if not chart.install(self.namespace): logging.error( 'Helm upgrade failed on {}. Rolling back...'.format(chart)) chart.rollback _failed_charts.append(chart) if _failed_charts: logging.error( "ERROR: Some charts failed to install and were rolled back") for chart in _failed_charts: logging.error(" - {}".format(chart.release_name)) return True def _compare_required_versions(self): """ Compare installed versions of helm and autohelm to the minimum versions required by the course.yml Accepts no arguments """ if self.minimum_versions is None: return True helm_minimum_version = self.minimum_versions.get('helm', '0.0.0') autohelm_minimum_version = self.minimum_versions.get( 'autohelm', '0.0.0') logging.debug( "Helm Minimum Version is: {}".format(helm_minimum_version)) logging.debug("Helm Installed Version is {}".format( self.helm.client_version)) logging.debug( "Autohelm Minimum Version is {}".format(autohelm_minimum_version)) logging.debug( "Autohelm Installed Version is {}".format(autohelm_version)) r1 = semver.compare(autohelm_version, autohelm_minimum_version) if r1 < 0: raise MinimumVersionException( "autohelm Minimum Version {} not met.".format( autohelm_minimum_version)) if not self.config.local_development: r2 = semver.compare(self.helm.client_version, helm_minimum_version) if r2 < 0: raise MinimumVersionException( "helm Minimum Version {} not met.".format( helm_minimum_version)) return True
class Chart(object): """ Description: - Chart class for each release in the course.yml Arguments: - chart (dict): Attributes: - config: Instance of Config() - helm: Instance of Helm() - release_name : String. Name of the release - name: String. Name of chart - files: List. Values files - namespace Returns: - Instance of Response() is truthy where Reponse.exitcode == 0 - Instance of Response() is falsey where Reponse.exitcode != 0 """ def __init__(self, chart): self.helm = Helm() self.config = Config() self._release_name = chart.keys()[0] self._chart = chart[self._release_name] self._repository = Repository(self._chart.get('repository', default_repository)) self._chart['values'] = self.ordereddict_to_dict(self._chart.get('values', {})) value_strings = self._chart.get('values-strings', {}) self._chart['values_strings'] = self.ordereddict_to_dict(value_strings) if value_strings != {}: del(self._chart['values-strings']) @property def release_name(self): """ Returns release name of course chart """ return self._release_name @property def name(self): """ Retturns chart name of course chart """ return self._chart.get('chart', self._release_name) def ordereddict_to_dict(self, value): """ Converts an OrderedDict to a standard dict """ for k, v in value.items(): if type(v) == OrderedDict: value[k] = self.ordereddict_to_dict(v) if type(v) == list: for item in v: if type(item) == OrderedDict: v.remove(item) v.append(self.ordereddict_to_dict(item)) return dict(value) @property def files(self): """ List of values files from the course chart """ return dict(self._chart.get('files', [])) @property def namespace(self): """ Namespace to install the course chart """ return self._namespace @property def repository(self): """ Repository object parsed from course chart """ return self._repository def __getattr__(self, key): return self._chart.get(key) def __str__(self): return str(dict(self._chart)) def run_hook(self, coms): """ Expects a list of shell commands. Runs the commands defined by the hook """ if type(coms) == str: coms = [coms] for com in coms: logging.debug("Running Hook {}".format(com)) ret = subprocess.call(com, shell=True, executable="/bin/bash") if ret != 0: logging.error("Hook command `{}` returned non-zero exit code".format(com)) sys.exit(1) def rollback(self): """ Rollsback most recent release of the course chart """ release = [release for release in self.helm.releases.deployed if release.name == self._release_name][0] if release: release.rollback() def update_dependencies(self): """ Update the course chart dependencies """ if self.config.local_development or self.config.dryrun: return True logging.debug("Updating chart dependencies: {}".format(self.chart_path)) if os.path.exists(self.chart_path): try: r = self.helm.dependency_update(self.chart_path) except AutoHelmCommandException, e: logging.warn("Unable to update chart dependancies: {}".format(e.stderr))