Beispiel #1
0
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)
Beispiel #2
0
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