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 DeisException('version cannot be below 0') elif version == 1: raise DeisException('Cannot roll back to initial release.') prev = self.app.release_set.get(version=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: if 'new_release' in locals(): new_release.delete() raise DeisException(str(e)) from e
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 DeisException('version cannot be below 0') elif version == 1: raise DeisException('Cannot roll back to initial release.') prev = self.app.release_set.get(version=version) if prev.failed: raise DeisException('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 new_release.save() raise DeisException(str(e)) from e
def new(self, user, config, build, summary=None, source_version='latest'): """ Create a new application release using the provided Build and Config on behalf of a user. Releases start at v1 and auto-increment. """ # construct fully-qualified target image new_version = self.app.release_set.latest().version + 1 # create new release and auto-increment version release = Release.objects.create( owner=user, app=self.app, config=config, build=build, version=new_version, summary=summary ) try: release.publish() except DeisException as e: # If we cannot publish this app, just log and carry on self.app.log(e) pass except RegistryException as e: self.app.log(e) raise DeisException(str(e)) from e return release
def save(self, *args, **kwargs): self.summary = [] previous_settings = None try: previous_settings = self.app.appsettings_set.latest() except AppSettings.DoesNotExist: pass try: self.update_maintenance(previous_settings) self.update_routable(previous_settings) self.update_whitelist(previous_settings) self.update_autoscale(previous_settings) except (UnprocessableEntity, NotFound): raise except Exception as e: self.delete() raise DeisException(str(e)) from e if not self.summary and previous_settings: self.delete() raise AlreadyExists("{} changed nothing".format(self.owner)) summary = ' '.join(self.summary) self.app.log('summary of app setting changes: {}'.format(summary), logging.DEBUG) return super(AppSettings, self).save(**kwargs)
def set_tags(self, previous_config): """verify the tags exist on any nodes as labels""" if not self.tags: if settings.DEIS_DEFAULT_CONFIG_TAGS: try: tags = json.loads(settings.DEIS_DEFAULT_CONFIG_TAGS) self.tags = tags except json.JSONDecodeError: 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 DeisException(message)
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 new_release.save() else: self.delete() raise DeisException(str(e)) from e
def get_port(self, routable=False): """ 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: deis_registry = bool(self.build.source_based) envs = self.config.values creds = self.get_registry_auth() port = None # Only care about port for routable application if not routable: return port 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: if envs.get('PORT', None) is None: self.app.log( 'Private registry detected but no $PORT defined. Defaulting to $PORT 5000', logging.WARNING) # noqa return 5000 # User provided PORT return envs.get('PORT') # If the user provides PORT if envs.get('PORT', None): return envs.get('PORT') # discover port from docker image port = docker_get_port(self.image, deis_registry, creds) if port is None: msg = "Expose a port or make the app non routable by changing the process type" self.app.log(msg, logging.ERROR) raise DeisException(msg) return port except Exception as e: raise DeisException(str(e)) from e
def check_image_access(self): try: deis_registry = bool(self.build.source_based) if deis_registry: return # we always have access to our own registry creds = self.get_registry_auth() docker_check_access(self.image, creds) except Exception as e: raise DeisException(str(e)) from e
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: deis_registry = bool(self.build.source_based) 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): if envs.get('PORT', None) is None: if not self.app.appsettings_set.latest().routable: return None raise DeisException( '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 if envs.get('PORT', None): return int(envs.get('PORT')) # discover port from docker image if deis_registry: creds = self._get_private_registry_creds() port = docker_get_port(self.image, creds) if port is None and self.app.appsettings_set.latest().routable: msg = "Expose a port or make the app non routable by changing the process type" self.app.log(msg, logging.ERROR) raise DeisException(msg) return port except Exception as e: raise DeisException(str(e)) from e
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 DeisException( 'PORT needs to be set in the config ' 'when using a private registry')
def publish(self): if self.build is None: raise DeisException( 'No build associated with this release to publish') # If the build has a SHA, assume it's from deis-builder and in the deis-registry already if self.build.source_based: return # Builder pushes to internal registry, exclude SHA based images from being returned early registry = self.config.registry if (registry.get('username', None) and registry.get('password', None) and # SHA means it came from a git push (builder) not self.build.sha and # hostname tells Builder where to push images not registry.get('hostname', None)) or ( settings.REGISTRY_LOCATION != 'on-cluster'): self.app.log( '{} exists in the target registry. Using image for release {} of app {}' .format(self.build.image, self.version, self.app)) # noqa return # return image if it is already in the registry, test host and then host + port if (self.build.image.startswith(settings.REGISTRY_HOST) or self.build.image.startswith(settings.REGISTRY_URL)): self.app.log( '{} exists in the target registry. Using image for release {} of app {}' .format(self.build.image, self.version, self.app)) # noqa return # add tag if it was not provided source_image = self.build.image if ':' not in source_image: source_image = "{}:{}".format(source_image, self.build.version) # if build is source based then it was pushed into the deis registry deis_registry = bool(self.build.source_based) publish_release(source_image, self.image, deis_registry, self.get_registry_auth())