class Playbook(AbstractProjectResourceModel): TYPE_JSON, TYPE_TEXT, TYPE_FILE, TYPE_GIT, TYPE_HTTP, TYPE_LOCAL = ( 'json', 'text', 'file', 'git', 'http', 'local', ) TYPE_CHOICES = ( (TYPE_JSON, TYPE_JSON), (TYPE_TEXT, TYPE_TEXT), (TYPE_FILE, TYPE_FILE), (TYPE_GIT, TYPE_GIT), (TYPE_HTTP, TYPE_HTTP), (TYPE_LOCAL, TYPE_LOCAL), ) UPDATE_POLICY_ALWAYS, UPDATE_POLICY_IF_NOT_PRESENT, UPDATE_POLICY_NEVER = ( 'always', 'if_not_present', 'never') UPDATE_POLICY_CHOICES = ( (UPDATE_POLICY_IF_NOT_PRESENT, _('Always')), (UPDATE_POLICY_ALWAYS, _("If not present")), (UPDATE_POLICY_NEVER, _("Never")), ) name = models.SlugField(max_length=128, allow_unicode=True, verbose_name=_('Name')) alias = models.CharField(max_length=128, blank=True, default='site.yml') type = models.CharField(choices=TYPE_CHOICES, default=TYPE_JSON, max_length=16) plays = models.ManyToManyField('Play', verbose_name='Plays') git = common_models.JsonDictCharField(max_length=4096, default={ 'repo': '', 'branch': 'master' }) url = models.URLField(verbose_name=_("http url"), blank=True) update_policy = models.CharField(choices=UPDATE_POLICY_CHOICES, max_length=16, default=UPDATE_POLICY_IF_NOT_PRESENT) extra_vars = common_models.JsonDictTextField(verbose_name=_('Vars'), blank=True, null=True, default={}) # Extra schedule content is_periodic = models.BooleanField(default=False, verbose_name=_("Enable")) interval = models.CharField(verbose_name=_("Interval"), null=True, blank=True, max_length=128, help_text=_("s/m/d")) crontab = models.CharField(verbose_name=_("Crontab"), null=True, blank=True, max_length=128, help_text=_("5 * * * *")) meta = common_models.JsonDictTextField(blank=True, verbose_name=_("Meta")) execute_times = models.IntegerField(default=0) comment = models.TextField(blank=True, verbose_name=_("Comment")) is_active = models.BooleanField(default=True, verbose_name=_("Active")) created_by = models.CharField(max_length=128, blank=True, null=True) date_created = models.DateTimeField(auto_now_add=True) class Meta: unique_together = ["name", "project"] def __str__(self): return '{}-{}'.format(self.project, self.name) def playbook_dir(self, auto_create=True): path = os.path.join(self.project.playbooks_dir, str(self.name)) if not os.path.isdir(path) and auto_create: os.makedirs(path, exist_ok=True) return path @property def playbook_path(self): path = os.path.join(self.playbook_dir(), self.alias) return path @property def latest_execution(self): try: return self.executions.all().latest() except PlaybookExecution.DoesNotExist: return None def get_plays_data(self, fmt='py'): return Play.get_plays_data(self.plays.all(), fmt=fmt) def install_from_git(self): success, error = True, None if not self.git.get('repo'): success, error = False, 'Not repo get' return success, error try: if os.path.isdir(os.path.join(self.playbook_dir(), '.git')): if self.update_policy == self.UPDATE_POLICY_ALWAYS: print("Update playbook from: {}".format( self.git.get('repo'))) repo = git.Repo(self.playbook_dir()) remote = repo.remote() remote.pull() else: print("Install playbook from: {}".format(self.git.get('repo'))) git.Repo.clone_from( self.git['repo'], self.playbook_dir(), branch=self.git.get('branch'), depth=1, ) except Exception as e: success, error = False, e return success, error def install_from_http(self): if os.listdir(self.playbook_dir()): os.removedirs(self.playbook_dir()) r = requests.get(self.url) tmp_file_path = os.path.join(self.playbook_dir(), 'tmp') with open(tmp_file_path, 'wb') as f: f.write(r.content) # TODO: compress it def install_from_plays(self): for play in self.plays.all(): success, error = play.check_role() if not success: return success, error with open(self.playbook_path, 'w') as f: f.write(self.get_plays_data(fmt='yaml')) return True, None def install_from_local(self): playbook_dir = self.playbook_dir(auto_create=False) if self.update_policy == self.UPDATE_POLICY_NEVER: return True, None if os.path.isfile(self.playbook_path) and \ self.update_policy == self.UPDATE_POLICY_IF_NOT_PRESENT: return True, None shutil.rmtree(playbook_dir, ignore_errors=True) url = self.url if self.url.startswith('file://'): url = self.url.replace('file://', '') try: shutil.copytree(url, playbook_dir, symlinks=True) except Exception as e: return False, e return True, None def install(self): if self.type == self.TYPE_JSON: return self.install_from_plays() elif self.type == self.TYPE_GIT: return self.install_from_git() elif self.type == self.TYPE_LOCAL: return self.install_from_local() else: return False, 'Not support {}'.format(self.type) def execute(self, extra_vars=None): pk = current_task.request.id if current_task else None execution = PlaybookExecution(playbook=self, pk=pk, extra_vars=extra_vars) execution.save() result = execution.start() return result def create_period_task(self): from ..tasks import execute_playbook tasks = { self.__str__(): { "task": execute_playbook.name, "interval": self.interval or None, "crontab": self.crontab or None, "args": (str(self.id), ), "kwargs": { "name": self.__str__() }, "enabled": True, } } create_or_update_periodic_task(tasks) def disable_period_task(self): disable_celery_periodic_task(self.__str__()) def remove_period_task(self): if self.is_periodic: delete_celery_periodic_task(self.__str__()) @property def period_task(self): try: return PeriodicTask.objects.get(name=self.__str__()) except PeriodicTask.DoesNotExist: return None def cleanup(self): self.remove_period_task() shutil.rmtree(self.playbook_dir(), ignore_errors=True)
class Role(AbstractProjectResourceModel): STATE_NOT_INSTALL = 'uninstalled' STATE_INSTALLED = 'installed' STATE_INSTALLING = 'installing' STATE_FAILED = 'failed' STATE_CHOICES = ((STATE_NOT_INSTALL, 'UnInstalled'), (STATE_INSTALLED, 'Installed'), (STATE_INSTALLING, 'Installing'), (STATE_FAILED, 'Failed')) TYPE_GIT = 'git' TYPE_HTTP = 'http' TYPE_GALAXY = 'galaxy' TYPE_FILE = 'file' TYPE_CHOICES = ( (TYPE_GALAXY, 'galaxy'), (TYPE_GIT, 'git'), (TYPE_HTTP, 'http'), (TYPE_FILE, 'file'), ) name = models.CharField(max_length=128, validators=[name_validator]) type = models.CharField(max_length=16, choices=TYPE_CHOICES, default=TYPE_GALAXY) comment = models.CharField(max_length=1024, blank=True, verbose_name=_("Comment")) galaxy_name = models.CharField(max_length=128, blank=True, null=True) git = common_models.JsonDictCharField(max_length=4096, default={ 'repo': '', 'branch': 'master' }) url = models.CharField(max_length=1024, verbose_name=_("Url"), blank=True) logo = models.ImageField(verbose_name='Logo', upload_to="logo", null=True) categories = models.CharField(max_length=256, verbose_name=_("Tags"), blank=True) version = models.CharField(max_length=1024, blank=True, default='master') state = models.CharField(default=STATE_NOT_INSTALL, choices=STATE_CHOICES, max_length=16) meta = common_models.JsonDictTextField(verbose_name=_("Meta"), blank=True) meta_ext = common_models.JsonDictTextField(verbose_name=_("Meta Ext"), blank=True) created_by = models.CharField(max_length=128, blank=True, null=True, default='') date_created = models.DateTimeField(auto_now_add=True) date_updated = models.DateTimeField(auto_now=True) class Meta: unique_together = ('name', 'project') def __str__(self): return self.name def delete(self, using=None, keep_parents=False): role = MyGalaxyRole(self.name, path=self.project.roles_dir) role.remove() return super().delete(using=using, keep_parents=keep_parents) @property def _role(self): role = MyGalaxyRole(self.name, path=self.project.roles_dir) return role @property def variables(self): return self._role.default_variables @property def role_dir(self): return os.path.join(self.project.roles_dir, self.name) @property def meta_all(self): meta = OrderedDict([ ('name', self.name), ('version', self.version), ('comment', self.comment), ('state', self.get_state_display()), ('url', self.url), ('type', self.type), ('categories', self.categories), ]) if isinstance(self.meta, dict): meta.update(self.meta) if isinstance(self.meta_ext, dict): meta.update(self.meta_ext) meta.pop('readme', None) meta.pop('readme_html', None) galaxy_info = meta.pop('galaxy_info', {}) for k, v in galaxy_info.items(): if k == 'platforms': v = ' '.join([i['name'] + str(i['versions']) for i in v]) meta[k] = v return meta def install_from_galaxy(self): api = MyGalaxyAPI() role = MyGalaxyRole(self.galaxy_name, path=self.project.roles_dir) success, error = role.install() if success: self.comment = api.lookup_role_by_name( self.galaxy_name)['description'] self.url = api.role_git_url(self.galaxy_name) self.version = role.version self.meta = role.metadata categories = '' if self.meta and self.meta['galaxy_info'].get('categories'): categories = self.meta['galaxy_info']['categories'] elif self.meta and self.meta['galaxy_info'].get('galaxy_tags'): categories = self.meta['galaxy_info']['galaxy_tags'] self.categories = ','.join(categories) if isinstance( categories, list) else str(categories) os.rename(os.path.join(self.project.roles_dir, self.galaxy_name), self.role_dir) return success, error def install_from_git(self): success, error = True, None if not self.git.get('repo'): success = False error = 'Not repo get' return success, error print("Install playbook from: {}".format(self.git.get('repo'))) try: if os.path.isdir(os.path.join(self.role_dir, '.git')): repo = git.Repo(self.role_dir) remote = repo.remote() remote.pull() else: git.Repo.clone_from( self.git['repo'], self.role_dir, branch=self.git.get('branch'), depth=1, ) except Exception as e: success = False error = e return success, error def install(self): self.state = self.STATE_INSTALLING if self.type == self.TYPE_GALAXY: success, err = self.install_from_galaxy() elif self.type == self.TYPE_GIT: success, err = self.install_from_git() else: success = False err = Exception("From {}, using other function".format(self.type)) if success: self.state = self.STATE_INSTALLED else: self.state = self.STATE_FAILED self.save() return success, err def uninstall(self): role = MyGalaxyRole(self.name, path=self.project.roles_dir) role.remove() @property def logo_url(self): default = settings.STATIC_URL + "ansible/img/role_logo_default.png" if self.logo: return self.logo.url return default