def get_port(self): """ Get application port for a given release. If pulling from private registry then use default port or read from ENV var, otherwise attempt to pull from the docker image """ try: envs = self.config.values creds = self.get_registry_auth() if self.build.type == "buildpack": self.app.log('buildpack type detected. Defaulting to $PORT 5000') return 5000 # application has registry auth - $PORT is required if (creds is not None) or (settings.REGISTRY_LOCATION != 'on-cluster'): if envs.get('PORT', None) is None: if not self.app.appsettings_set.latest().routable: return None raise DryccException( 'PORT needs to be set in the application config ' 'when using a private registry' ) # User provided PORT return int(envs.get('PORT')) # If the user provides PORT return int(envs.get('PORT', 5000)) except Exception as e: raise DryccException(str(e)) from e
def path(self, request, *args, **kwargs): new_path = request.data.get('path') if new_path is None: raise DryccException("path is a required field") obj = self.get_object() container_types = [ _ for _ in new_path.keys() if _ not in obj.app.types or _ not in obj.app.structure.keys() ] if container_types: raise DryccException( "process type {} is not included in profile".format( ','.join(container_types))) if set(new_path.items()).issubset(set(obj.path.items())): raise DryccException("mount path not changed") other_volumes = self.get_app().volume_set.exclude(name=obj.name) type_paths = {} # {'type1':[path1,path2], tyep2:[path3,path4]} for _ in other_volumes: for k, v in _.path.items(): if k not in type_paths: type_paths[k] = [v] else: type_paths[k].append(k) repeat_path = [ v for k, v in new_path.items() if v in type_paths.get(k, []) ] # noqa if repeat_path: raise DryccException("path {} is used by another volume".format( ','.join(repeat_path))) path = obj.path pre_path = deepcopy(path) # merge mount path # remove path keys if a null value is provided for key, value in new_path.items(): if value is None: # error if unsetting non-existing key if key not in path: raise UnprocessableEntity( '{} does not exist under {}'.format(key, "volume")) # noqa path.pop(key) else: path[key] = value obj.path = path # after merge path obj.save() self.deploy(obj, pre_path) serializer = self.get_serializer(obj, many=False) return Response(serializer.data)
def create(self, user, *args, **kwargs): latest_release = self.app.release_set.filter(failed=False).latest() latest_version = self.app.release_set.latest().version try: new_release = latest_release.new(user, build=self, config=latest_release.config, source_version=self.version) self.app.deploy(new_release) return new_release except Exception as e: # check if the exception is during create or publish if ('new_release' not in locals() and self.app.release_set.latest().version == latest_version + 1): new_release = self.app.release_set.latest() if 'new_release' in locals(): new_release.failed = True new_release.summary = "{} deployed {} which failed".format( self.owner, str(self.uuid)[:7]) # noqa # Get the exception that has occured new_release.exception = "error: {}".format(str(e)) new_release.save() else: self.delete() raise DryccException(str(e)) from e
def set_tags(self, previous_config): """verify the tags exist on any nodes as labels""" if not self.tags: if settings.DRYCC_DEFAULT_CONFIG_TAGS: try: tags = json.loads(settings.DRYCC_DEFAULT_CONFIG_TAGS) self.tags = tags except json.JSONDecodeError as e: logger.exception(e) return else: return # Get all nodes with label selectors nodes = self._scheduler.node.get(labels=self.tags).json() if nodes['items']: return labels = ['{}={}'.format(key, value) for key, value in self.tags.items()] message = 'No nodes matched the provided labels: {}'.format(', '.join(labels)) # Find out if there are any other tags around old_tags = getattr(previous_config, 'tags') if old_tags: old = ['{}={}'.format(key, value) for key, value in old_tags.items()] new = set(labels) - set(old) if new: message += ' - Addition of {} is the cause'.format(', '.join(new)) raise DryccException(message)
def deploy(self, volume, pre_mount_path): app = self.get_app() latest_release = app.release_set.filter(failed=False).latest() latest_version = app.release_set.latest().version try: summary = "{user} changed volume mount for {volume}".\ format(user=self.request.user, volume=volume.name) self.release = latest_release.new(self.request.user, config=latest_release.config, build=latest_release.build, summary=summary) # It's possible to mount volume before a build if self.release.build is not None: app.deploy(self.release) except Exception as e: if (not hasattr(self, 'release') and app.release_set.latest().version == latest_version + 1): self.release = app.release_set.latest() if hasattr(self, 'release'): self.release.failed = True self.release.summary = "{} deploy with a volume that failed".\ format(self.request.user) # noqa # Get the exception that has occured self.release.exception = "error: {}".format(str(e)) self.release.save() volume.path = pre_mount_path volume.save() if isinstance(e, AlreadyExists): raise raise DryccException(str(e)) from e
def delete(self, *args, **kwargs): if self.binding == "Ready": raise DryccException("the plan is still binding") # Deatch ServiceInstance, updates k8s self.detach(*args, **kwargs) # Delete from DB return super(Resource, self).delete(*args, **kwargs)
def post_save(self, config): release = config.app.release_set.filter(failed=False).latest() latest_version = config.app.release_set.latest().version try: self.release = release.new(self.request.user, config=config, build=release.build) # It's possible to set config values before a build if self.release.build is not None: config.app.deploy(self.release) except Exception as e: if (not hasattr(self, 'release') and config.app.release_set.latest().version == latest_version + 1): self.release = config.app.release_set.latest() if hasattr(self, 'release'): self.release.failed = True self.release.summary = "{} deployed a config that failed".format( self.request.user) # noqa # Get the exception that has occured self.release.exception = "error: {}".format(str(e)) self.release.save() else: config.delete() if isinstance(e, AlreadyExists): raise raise DryccException(str(e)) from e
def delete(self, *args, **kwargs): if self.path: raise DryccException("the volume is not unmounted") # Deatch volume, updates k8s self.detach() # Delete from DB return super(Volume, self).delete(*args, **kwargs)
def save(self, *args, **kwargs): previous_settings = None try: previous_settings = self.app.appsettings_set.latest() except AppSettings.DoesNotExist: pass try: self._update_routable(previous_settings) self._update_allowlist(previous_settings) self._update_autoscale(previous_settings) self._update_label(previous_settings) except (UnprocessableEntity, NotFound): raise except Exception as e: self.delete() raise DryccException(str(e)) from e if not self.summary and previous_settings: self.delete() raise AlreadyExists("{} changed nothing".format(self.owner)) summary = ' '.join(self.summary) try: return super(AppSettings, self).save(**kwargs) finally: self.app.refresh() self.app.log('summary of app setting changes: {}'.format(summary), logging.DEBUG)
def update(self, instance, validated_data): if instance.plan.split(':')[0] != validated_data.get('plan', '').split(':')[0]: # noqa raise DryccException("the resource cann't changed") instance.plan = validated_data.get('plan') instance.options.update(validated_data.get('options', {})) instance.attach_update() instance.save() return instance
def run(self, request, **kwargs): app = self.get_object() if not request.data.get('command'): raise DryccException("command is a required field") volumes = request.data.get('volumes', None) if volumes: serializers.VolumeSerializer().validate_path(volumes) rc, output = app.run(self.request.user, request.data['command'], volumes) return Response({'exit_code': rc, 'output': str(output)})
def unblock(self, request, **kwargs): try: models.blocklist.Blocklist.objects.filter( id=kwargs['id'], type=models.blocklist.Blocklist.get_type( kwargs["type"])).delete() return HttpResponse(status=204) except ValueError as e: logger.info(e) raise DryccException("Unsupported block type: %s" % kwargs["type"])
def set_registry(self): # lower case all registry options for consistency self.registry = {key.lower(): value for key, value in self.registry.copy().items()} # PORT must be set if private registry is being used if self.registry and self.values.get('PORT', None) is None: # only thing that can get past post_save in the views raise DryccException( 'PORT needs to be set in the config ' 'when using a private registry')
def unbind(self, *args, **kwargs): if not self.binding: raise DryccException("the resource is not binding") try: # We raise an exception when a resource doesn't exist self._scheduler.svcat.get_binding(self.app.id, self.name) self._scheduler.svcat.delete_binding(self.app.id, self.name) self.binding = None self.data = {} self.save() except KubeException as e: raise ServiceUnavailable("Could not unbind resource {} for application {}".format(self.name, self.app_id)) from e # noqa
def attach(self, request, *args, **kwargs): try: if "domain" not in kwargs and not request.data.get('domain'): raise DryccException("domain is a required field") elif request.data.get('domain'): kwargs['domain'] = request.data['domain'] self.get_object().attach(*args, **kwargs) except Http404: raise return Response(status=status.HTTP_201_CREATED)
def bind(self, *args, **kwargs): if self.status != "Ready": raise DryccException("the resource is not ready") if self.binding == "Ready": raise DryccException("the resource is binding") self.binding = "Binding" self.save() try: self._scheduler.svcat.get_binding(self.app.id, self.name) err = "Resource {} is binding".format(self.name) self.log(err, logging.INFO) raise AlreadyExists(err) except KubeException as e: logger.info(e) try: self._scheduler.svcat.create_binding(self.app.id, self.name, **kwargs) except KubeException as e: msg = 'There was a problem binding the resource ' \ '{} for {}'.format(self.name, self.app_id) raise ServiceUnavailable(msg) from e
def block(self, request, **kwargs): try: blocklist, _ = models.blocklist.Blocklist.objects.get_or_create( id=kwargs['id'], type=models.blocklist.Blocklist.get_type(kwargs["type"]), defaults={"remark": request.data.get("remark")}) for app in blocklist.related_apps: scale_app.delay(app, app.owner, {key: 0 for key in app.structure.keys()}) return HttpResponse(status=201) except ValueError as e: logger.info(e) raise DryccException("Unsupported block type: %s" % kwargs["type"])
def rollback(self, user, version=None): try: # if no version is provided then grab version from object version = (self.version - 1) if version is None else int(version) if version < 1: raise DryccException('version cannot be below 0') elif version == 1: raise DryccException('Cannot roll back to initial release.') prev = self.app.release_set.get(version=version) if prev.failed: raise DryccException('Cannot roll back to failed release.') latest_version = self.app.release_set.latest().version new_release = self.new( user, build=prev.build, config=prev.config, summary="{} rolled back to v{}".format(user, version), source_version='v{}'.format(version) ) if self.build is not None: self.app.deploy(new_release, force_deploy=True) return new_release except Exception as e: # check if the exception is during create or publish if ('new_release' not in locals() and 'latest_version' in locals() and self.app.release_set.latest().version == latest_version+1): new_release = self.app.release_set.latest() if 'new_release' in locals(): new_release.failed = True new_release.summary = "{} performed roll back to a release that failed".format(self.owner) # noqa # Get the exception that has occured new_release.exception = "error: {}".format(str(e)) new_release.save() raise DryccException(str(e)) from e