Exemple #1
0
    def loadStoreData(self):
        installers = dict()
        updateSource = self.ConfigManager.getModulesUpdateSource()
        req = requests.get(
            url=
            'https://api.github.com/search/code?q=extension:install+repo:project-alice-powered-by-snips/ProjectAliceModules/',
            auth=GithubCloner.getGithubAuth())
        results = req.json()
        if results:
            for module in results['items']:
                try:
                    req = requests.get(
                        url=f"{module['url'].split('?')[0]}?ref={updateSource}",
                        headers={
                            'Accept': 'application/vnd.github.VERSION.raw'
                        },
                        auth=GithubCloner.getGithubAuth())
                    installer = req.json()
                    if installer:
                        installers[installer['name']] = installer

                except Exception:
                    continue

        actualVersion = Version(constants.VERSION)
        return {
            moduleName: moduleInfo
            for moduleName, moduleInfo in installers.items()
            if self.ModuleManager.getModuleInstance(moduleName=moduleName,
                                                    silent=True) is None
            and actualVersion >= Version(moduleInfo['aliceMinVersion'])
        }
Exemple #2
0
	def revert(self, skillName: str) -> Response:
		"""
		reverts the skill to its official state. Removes the "modified" status and runs an update.
		:param skillName:
		:return:
		"""
		if skillName not in self.SkillManager.allSkills:
			return self.skillNotFound()
		self.SkillManager.getSkillInstance(skillName=skillName).modified = False
		gitCloner = GithubCloner(baseUrl=f'{constants.GITHUB_URL}/skill_{skillName}.git',
		                         dest=Path(self.Commons.rootDir()) / 'skills' / skillName,
		                         skillName=skillName)
		gitCloner.checkoutMaster()
		return self.checkUpdate(skillName)
    def getModulesUpdateSource(self) -> str:
        updateSource = 'master'
        if self.getAliceConfigByName('updateChannel') == 'master':
            return updateSource

        req = requests.get(
            'https://api.github.com/repos/project-alice-powered-by-snips/ProjectAliceModules/branches',
            auth=GithubCloner.getGithubAuth())
        result = req.json()
        if result:
            userUpdatePref = self.getAliceConfigByName('updateChannel')
            versions = list()
            for branch in result:
                repoVersion = Version(branch['name'])
                if not repoVersion.isVersionNumber:
                    continue

                if userUpdatePref == 'alpha' and repoVersion.infos[
                        'releaseType'] in ('master', 'rc', 'b', 'a'):
                    versions.append(repoVersion)
                elif userUpdatePref == 'beta' and repoVersion.infos[
                        'releaseType'] in ('master', 'rc', 'b'):
                    versions.append(repoVersion)
                elif userUpdatePref == 'rc' and repoVersion.infos[
                        'releaseType'] in ('master', 'rc'):
                    versions.append(repoVersion)

            if len(versions) > 0:
                versions.sort(reverse=True)
                updateSource = versions[0]

        return updateSource
    def uploadToGithub(self):
        try:
            skillName = request.form.get('skillName', '')
            skillDesc = request.form.get('skillDesc', '')

            if not skillName:
                raise Exception

            skillName = skillName[0].upper() + skillName[1:]
            data = {
                'name': skillName,
                'description': skillDesc,
                'has-issues': True,
                'has-wiki': False
            }
            req = requests.post('https://api.github.com/user/repos',
                                data=json.dumps(data),
                                auth=GithubCloner.getGithubAuth())

            if req.status_code == 201:
                return jsonify(success=True)
            else:
                raise Exception

        except Exception as e:
            self.logError(f'Failed uploading to github: {e}')
            return jsonify(success=False)
Exemple #5
0
	def setModified(self, skillName: str) -> Response:
		"""
		sets the modified status for that skill.
		creates a private repository for the user and checks out the fork
		:param skillName:
		:return:
		"""
		if skillName not in self.SkillManager.allSkills:
			return self.skillNotFound()

		if not GithubCloner.hasAuth():
			return self.githubMissing()

		self.SkillManager.getSkillInstance(skillName=skillName).modified = True
		gitCloner = GithubCloner(baseUrl=f'{constants.GITHUB_URL}/skill_{skillName}.git',
		                         dest=Path(self.Commons.rootDir()) / 'skills' / skillName,
		                         skillName=skillName)
		if not gitCloner.checkOwnRepoAvailable(skillName=skillName):
			gitCloner.createForkForSkill(skillName=skillName)
		gitCloner.checkoutOwnFork()
		return jsonify(success=True)
Exemple #6
0
	def upload(self, skillName: str) -> Response:
		"""
		upload the skill to the private repository.
		Will create a repository if required, add all untracked changes, create a commit and push
		:param skillName:
		:return:
		"""
		if skillName not in self.SkillManager.allSkills:
			return self.skillNotFound()
		self.SkillManager.getSkillInstance(skillName=skillName).modified = True

		gitCloner = GithubCloner(baseUrl=f'{constants.GITHUB_URL}/skill_{skillName}.git',
		                         dest=Path(self.Commons.rootDir()) / 'skills' / skillName,
		                         skillName=skillName)
		if not gitCloner.isRepo() and not gitCloner.createRepo():
			return jsonify(success=False, message="Failed creating repository")
		elif not gitCloner.add():
			return jsonify(success=False, message="Failed adding to git")
		elif not gitCloner.commit(message="pushed via API"):
			return jsonify(success=False, message="Failed creating commit")
		elif not gitCloner.push():
			return jsonify(success=False, message="Failed pushing to git")
		else:
			return jsonify(success=True)
Exemple #7
0
	def getGitStatus(self, skillName: str) -> Response:
		"""
		returns a list containing the public and private github URL of that skill.
		The repository does not have to exist yet!
		The current status of the repository is included as well
		Currently possible status: True/False
		:param skillName:
		:return:
		"""
		if skillName not in self.SkillManager.allSkills:
			return self.skillNotFound()

		gitCloner = GithubCloner(baseUrl=f'{constants.GITHUB_URL}/skill_{skillName}.git',
		                         dest=Path(self.Commons.rootDir()) / 'skills' / skillName,
		                         skillName=skillName)

		return jsonify(success=True,
		               result={'Public' : {'name'  : 'Public',
		                                   'url'   : gitCloner.getRemote(origin=True, noToken=True),
		                                   'status': gitCloner.checkRemote(origin=True)},
		                       'Private': {'name'  : 'Private',
		                                   'url'   : gitCloner.getRemote(AliceSK=True, noToken=True),
		                                   'status': gitCloner.checkRemote(AliceSK=True)}})
    def _installModules(self, modules: list) -> dict:
        root = Path(commons.rootDir(), 'system/moduleInstallTickets')
        availableModules = self.ConfigManager.modulesConfigurations
        modulesToBoot = dict()
        self.MqttManager.broadcast(topic='hermes/leds/systemUpdate',
                                   payload={'sticky': True})
        for file in modules:
            moduleName = Path(file).with_suffix('')

            self._logger.info('[{}] Now taking care of module {}'.format(
                self.name, moduleName.stem))
            res = root / file

            try:
                updating = False

                installFile = json.loads(res.read_text())

                moduleName = installFile['name']
                path = Path(installFile['author'], moduleName)

                if not moduleName:
                    self._logger.error(
                        '[{}] Module name to install not found, aborting to avoid casualties!'
                        .format(self.name))
                    continue

                directory = Path(commons.rootDir()) / 'modules' / moduleName

                if moduleName in availableModules:
                    localVersionDirExists = directory.is_dir()
                    localVersionAttributeExists: bool = 'version' in availableModules[
                        moduleName]

                    localVersionIsLatest: bool = \
                     localVersionDirExists and \
                     localVersionAttributeExists and \
                     float(availableModules[moduleName]['version']) >= float(installFile['version'])

                    if localVersionIsLatest:
                        self._logger.warning(
                            '[{}] Module "{}" is already installed, skipping'.
                            format(self.name, moduleName))
                        subprocess.run(['sudo', 'rm', res])
                        continue
                    else:
                        self._logger.warning(
                            '[{}] Module "{}" needs updating'.format(
                                self.name, moduleName))
                        updating = True

                if moduleName in self._modules:
                    try:
                        self._modules[moduleName]['instance'].onStop()
                    except Exception as e:
                        self._logger.error(
                            '[{}] Error stopping "{}" for update: {}'.format(
                                self.name, moduleName, e))

                gitCloner = GithubCloner(baseUrl=self.GITHUB_API_BASE_URL,
                                         path=path,
                                         dest=directory)

                if gitCloner.clone():
                    self._logger.info(
                        '[{}] Module successfully downloaded'.format(
                            self.name))
                    try:
                        pipReq = installFile.get('pipRequirements', None)
                        sysReq = installFile.get('systemRequirements', None)
                        scriptReq = installFile.get('script', None)

                        if pipReq:
                            for requirement in pipReq:
                                subprocess.run([
                                    './venv/bin/pip3', 'install', requirement
                                ])

                        if sysReq:
                            for requirement in sysReq:
                                subprocess.run([
                                    'sudo', 'apt-get', 'install', '-y',
                                    requirement
                                ])

                        if scriptReq:
                            subprocess.run([
                                'sudo', 'chmod', '+x',
                                str(directory / scriptReq)
                            ])
                            subprocess.run(
                                ['sudo', str(directory / scriptReq)])

                        node = {
                            'active': True,
                            'version': installFile['version'],
                            'author': installFile['author'],
                            'conditions': installFile['conditions']
                        }

                        self.ConfigManager.addModuleToAliceConfig(
                            installFile['name'], node)
                        subprocess.run(['mv', res, directory])
                        modulesToBoot[moduleName] = {'update': updating}
                    except Exception as e:
                        self._logger.error(
                            '[{}] Failed installing module "{}": {}'.format(
                                self.name, moduleName, e))
                        res.unlink()
                else:
                    self._logger.error('[{}] Failed cloning module'.format(
                        self.name))
                    res.unlink()

            except Exception as e:
                self._logger.error(
                    '[{}] Failed installing module "{}": {}'.format(
                        self.name, moduleName, e))
                res.unlink()

        self.MqttManager.broadcast(topic='hermes/leds/clear')
        return modulesToBoot
Exemple #9
0
	def uploadSkillToGithub(self, skillName: str, skillDesc: str) -> bool:
		try:
			self.logInfo(f'Uploading {skillName} to Github')

			skillName = skillName[0].upper() + skillName[1:]

			localDirectory = Path('/home', getpass.getuser(), f'ProjectAlice/skills/{skillName}')
			if not localDirectory.exists():
				raise Exception("Local skill doesn't exist")

			data = {
				'name'       : f'skill_{skillName}',
				'description': skillDesc,
				'has-issues' : True,
				'has-wiki'   : False
			}
			req = requests.post('https://api.github.com/user/repos', data=json.dumps(data), auth=GithubCloner.getGithubAuth())

			if req.status_code != 201:
				raise Exception("Couldn't create the repository on Github")

			self.Commons.runSystemCommand(['rm', '-rf', f'{str(localDirectory)}/.git'])
			self.Commons.runSystemCommand(['git', '-C', str(localDirectory), 'init'])

			self.Commons.runSystemCommand(['git', 'config', '--global', 'user.email', '*****@*****.**'])
			self.Commons.runSystemCommand(['git', 'config', '--global', 'user.name', '*****@*****.**'])

			remote = f'https://{self.ConfigManager.getAliceConfigByName("githubUsername")}:{self.ConfigManager.getAliceConfigByName("githubToken")}@github.com/{self.ConfigManager.getAliceConfigByName("githubUsername")}/skill_{skillName}.git'
			self.Commons.runSystemCommand(['git', '-C', str(localDirectory), 'remote', 'add', 'origin', remote])

			self.Commons.runSystemCommand(['git', '-C', str(localDirectory), 'add', '--all'])
			self.Commons.runSystemCommand(['git', '-C', str(localDirectory), 'commit', '-m', '"Initial upload"'])
			self.Commons.runSystemCommand(['git', '-C', str(localDirectory), 'push', '--set-upstream', 'origin', 'master'])

			url = f'https://github.com/{self.ConfigManager.getAliceConfigByName("githubUsername")}/skill_{skillName}.git'
			self.logInfo(f'Skill uploaded! You can find it on {url}')
			return True
		except Exception as e:
			self.logWarning(f'Something went wrong uploading skill to Github: {e}')
			return False
Exemple #10
0
	def _installSkills(self, skills: list) -> dict:
		root = Path(self.Commons.rootDir(), constants.SKILL_INSTALL_TICKET_PATH)
		availableSkills = self.ConfigManager.skillsConfigurations
		skillsToBoot = dict()
		self.MqttManager.mqttBroadcast(topic='hermes/leds/systemUpdate', payload={'sticky': True})
		for file in skills:
			skillName = Path(file).with_suffix('')

			self.logInfo(f'Now taking care of skill {skillName.stem}')
			res = root / file

			try:
				updating = False

				installFile = json.loads(res.read_text())

				skillName = installFile['name']
				path = Path(installFile['author'], skillName)

				if not skillName:
					self.logError('Skill name to install not found, aborting to avoid casualties!')
					continue

				directory = Path(self.Commons.rootDir()) / 'skills' / skillName

				conditions = {
					'aliceMinVersion': installFile['aliceMinVersion'],
					**installFile.get('conditions', dict())
				}

				self.checkSkillConditions(skillName, conditions, availableSkills)

				if skillName in availableSkills:
					installedVersion = Version.fromString(availableSkills[skillName]['version'])
					remoteVersion = Version.fromString(installFile['version'])
					localVersionIsLatest: bool = \
						directory.is_dir() and \
						'version' in availableSkills[skillName] and \
						installedVersion >= remoteVersion

					if localVersionIsLatest:
						self.logWarning(f'Skill "{skillName}" is already installed, skipping')
						self.Commons.runRootSystemCommand(['rm', res])
						continue
					else:
						self.logWarning(f'Skill "{skillName}" needs updating')
						updating = True

				if skillName in self._activeSkills:
					try:
						self._activeSkills[skillName].onStop()
					except Exception as e:
						self.logError(f'Error stopping "{skillName}" for update: {e}')
						raise

				gitCloner = GithubCloner(baseUrl=f'{constants.GITHUB_URL}/skill_{skillName}.git', path=path, dest=directory)

				try:
					gitCloner.clone(skillName=skillName)
					self.logInfo('Skill successfully downloaded')
					self._installSkill(res)
					skillsToBoot[skillName] = {
						'update': updating
					}
				except (GithubTokenFailed, GithubRateLimit):
					self.logError('Failed cloning skill')
					raise
				except GithubNotFound:
					if self.ConfigManager.getAliceConfigByName('devMode'):
						if not Path(f'{self.Commons.rootDir}/skills/{skillName}').exists() or not \
								Path(f'{self.Commons.rootDir}/skills/{skillName}/{skillName.py}').exists() or not \
								Path(f'{self.Commons.rootDir}/skills/{skillName}/dialogTemplate').exists() or not \
								Path(f'{self.Commons.rootDir}/skills/{skillName}/talks').exists():
							self.logWarning(f'Skill "{skillName}" cannot be installed in dev mode due to missing base files')
						else:
							self._installSkill(res)
							skillsToBoot[skillName] = {
								'update': updating
							}
						continue
					else:
						self.logWarning(f'Skill "{skillName}" is not available on Github, cannot install')
						raise

			except SkillNotConditionCompliant as e:
				self.logInfo(f'Skill "{skillName}" does not comply to "{e.condition}" condition, required "{e.conditionValue}"')
				if res.exists():
					res.unlink()

				self.broadcast(
					method=constants.EVENT_SKILL_INSTALL_FAILED,
					exceptions=self._name,
					skill=skillName
				)

			except Exception as e:
				self.logError(f'Failed installing skill "{skillName}": {e}')
				if res.exists():
					res.unlink()

				self.broadcast(
					method=constants.EVENT_SKILL_INSTALL_FAILED,
					exceptions=self.name,
					skill=skillName
				)
				raise

		return skillsToBoot