예제 #1
0
    def _get_environment_payload(self):
        payload = {
            "version": get_octoprint_version_string(),
            "os": self._environment["os"]["id"],
            "bits": self._environment["os"]["bits"],
            "python": self._environment["python"]["version"],
            "pip": self._environment["python"]["pip"],
            "cores": self._environment["hardware"]["cores"],
            "freq": self._environment["hardware"]["freq"],
            "ram": self._environment["hardware"]["ram"],
        }

        if ("plugins" in self._environment
                and "pi_support" in self._environment["plugins"]):
            payload["pi_model"] = self._environment["plugins"]["pi_support"][
                "model"]

            if "octopi_version" in self._environment["plugins"]["pi_support"]:
                payload["octopi_version"] = self._environment["plugins"][
                    "pi_support"]["octopi_version"]
            if "octopiuptodate_build" in self._environment["plugins"][
                    "pi_support"]:
                payload["octopiuptodate_build"] = self._environment["plugins"][
                    "pi_support"]["octopiuptodate_build"]

        return payload
예제 #2
0
    def _do_track(self, event, body=False, **kwargs):
        if not self._connectivity_checker.online:
            return

        if not self._settings.get_boolean(["enabled"]):
            return

        unique_id = self._settings.get(["unique_id"])
        if not unique_id:
            return

        server = self._settings.get(["server"])
        url = server.format(id=unique_id, event=event)
        # Don't print the URL or UUID! That would expose the UUID in forums/tickets
        # if pasted. It's okay for the user to know their uuid, but it shouldn't be shared.

        headers = {
            "User-Agent": "OctoPrint/{}".format(get_octoprint_version_string())
        }
        try:
            params = urlencode(kwargs, doseq=True).replace("+", "%20")

            if body:
                requests.post(url, data=params, timeout=3.1, headers=headers)
            else:
                requests.get(url, params=params, timeout=3.1, headers=headers)

            self._logger.info("Sent tracking event {}, payload: {!r}".format(
                event, kwargs))
        except Exception:
            if self._logger.isEnabledFor(logging.DEBUG):
                self._logger.exception(
                    "Error while sending event to anonymous usage tracking")
            else:
                pass
예제 #3
0
	def _do_track(self, event, **kwargs):
		if not self._connectivity_checker.online:
			return

		if not self._settings.get_boolean([b"enabled"]):
			return

		unique_id = self._settings.get([b"unique_id"])
		if not unique_id:
			return

		server = self._settings.get([b"server"])
		url = server.format(id=unique_id, event=event)
		# Don't print the URL or UUID! That would expose the UUID in forums/tickets
		# if pasted. It's okay for the user to know their uuid, but it shouldn't be shared.

		headers = {"User-Agent": "OctoPrint/{}".format(get_octoprint_version_string())}
		try:
			params = urlencode(kwargs, doseq=True).replace("+", "%20")

			requests.get(url,
			             params=params,
			             timeout=3.1,
			             headers=headers)
			self._logger.info("Sent tracking event {}, payload: {!r}".format(event, kwargs))
		except:
			if self._logger.isEnabledFor(logging.DEBUG):
				self._logger.exception("Error while sending event to anonymous usage tracking".format(url))
			else:
				pass
	def on_after_startup(self):
		import socket
		import platform
		import time
		self.octoprint_info.info({
			'octoprint_version': get_octoprint_version_string(),
			'host': socket.gethostname(),
			'platform': platform.system(),
			'app_start': str(int(time.time())),
		})
		pass
예제 #5
0
 def view():
     return jsonify(
         plugins=self._get_plugins(),
         repository=dict(available=self._repository_available,
                         plugins=self._repository_plugins),
         os=get_os(),
         octoprint=get_octoprint_version_string(),
         pip=dict(available=self._pip_caller.available,
                  version=self._pip_caller.version_string,
                  install_dir=self._pip_caller.install_dir,
                  use_user=self._pip_caller.use_user,
                  virtual_env=self._pip_caller.virtual_env,
                  additional_args=self._settings.get(["pip_args"]),
                  python=sys.executable),
         safe_mode=safe_mode,
         online=self._connectivity_checker.online)
예제 #6
0
		def view():
			return jsonify(plugins=self._get_plugins(),
			               repository=dict(
			                   available=self._repository_available,
			                   plugins=self._repository_plugins
			               ),
			               os=get_os(),
			               octoprint=get_octoprint_version_string(),
			               pip=dict(
			                   available=self._pip_caller.available,
			                   version=self._pip_caller.version_string,
			                   install_dir=self._pip_caller.install_dir,
			                   use_user=self._pip_caller.use_user,
			                   virtual_env=self._pip_caller.virtual_env,
			                   additional_args=self._settings.get(["pip_args"]),
			                   python=sys.executable
		                    ),
			               safe_mode=safe_mode,
			               online=self._connectivity_checker.online)
예제 #7
0
	def _track_startup(self):
		if not self._settings.get_boolean(["events", "startup"]):
			return

		payload = dict(version=get_octoprint_version_string(),
		               os=self._environment[b"os"][b"id"],
		               python=self._environment[b"python"][b"version"],
		               pip=self._environment[b"python"][b"pip"],
		               cores=self._environment[b"hardware"][b"cores"],
		               freq=self._environment[b"hardware"][b"freq"],
		               ram=self._environment[b"hardware"][b"ram"])

		if b"plugins" in self._environment and b"pi_support" in self._environment[b"plugins"]:
			payload[b"pi_model"] = self._environment[b"plugins"][b"pi_support"][b"model"]

			if b"octopi_version" in self._environment[b"plugins"][b"pi_support"]:
				payload[b"octopi_version"] = self._environment[b"plugins"][b"pi_support"][b"octopi_version"]

		self._track("startup", **payload)
예제 #8
0
    def _get_environment_payload(self):
        payload = dict(version=get_octoprint_version_string(),
                       os=self._environment["os"]["id"],
                       python=self._environment["python"]["version"],
                       pip=self._environment["python"]["pip"],
                       cores=self._environment["hardware"]["cores"],
                       freq=self._environment["hardware"]["freq"],
                       ram=self._environment["hardware"]["ram"])

        if "plugins" in self._environment and "pi_support" in self._environment[
                "plugins"]:
            payload["pi_model"] = self._environment["plugins"]["pi_support"][
                "model"]

            if "octopi_version" in self._environment["plugins"]["pi_support"]:
                payload["octopi_version"] = self._environment["plugins"][
                    "pi_support"]["octopi_version"]

        return payload
예제 #9
0
def _enable_errortracking():
    # this is a bit hackish, but we want to enable error tracking as early in the platform lifecycle as possible
    # and hence can't wait until our implementation is initialized and injected with settings

    from octoprint.settings import settings

    global _enabled

    if _enabled:
        return

    version = get_octoprint_version_string()

    s = settings()
    plugin_defaults = {"plugins": {"errortracking": SETTINGS_DEFAULTS}}

    enabled = s.getBoolean(["plugins", "errortracking", "enabled"],
                           defaults=plugin_defaults)
    enabled_unreleased = s.getBoolean(
        ["plugins", "errortracking", "enabled_unreleased"],
        defaults=plugin_defaults)
    url_server = s.get(["plugins", "errortracking", "url_server"],
                       defaults=plugin_defaults)
    unique_id = s.get(["plugins", "errortracking", "unique_id"],
                      defaults=plugin_defaults)
    if unique_id is None:
        import uuid

        unique_id = str(uuid.uuid4())
        s.set(["plugins", "errortracking", "unique_id"],
              unique_id,
              defaults=plugin_defaults)
        s.save()

    if _is_enabled(enabled, enabled_unreleased):
        import sentry_sdk

        from octoprint.plugin import plugin_manager

        def _before_send(event, hint):
            if "exc_info" not in hint:
                # we only want exceptions
                return None

            handled = True
            logger = event.get("logger", "")
            plugin = event.get("extra", {}).get("plugin", None)
            callback = event.get("extra", {}).get("callback", None)

            for ignore in IGNORED_EXCEPTIONS:
                if isinstance(ignore, tuple):
                    ignored_exc, matcher = ignore
                else:
                    ignored_exc = ignore
                    matcher = lambda *args: True

                exc = hint["exc_info"][1]
                if isinstance(exc, ignored_exc) and matcher(
                        exc, logger, plugin, callback):
                    # exception ignored for logger, plugin and/or callback
                    return None

                elif isinstance(ignore, type):
                    if isinstance(hint["exc_info"][1], ignore):
                        # exception ignored
                        return None

            if event.get("exception") and event["exception"].get("values"):
                handled = not any(
                    map(
                        lambda x: x.get("mechanism") and not x["mechanism"].
                        get("handled", True),
                        event["exception"]["values"],
                    ))

            if handled:
                # error is handled, restrict further based on logger
                if logger != "" and not (logger.startswith("octoprint.")
                                         or logger.startswith("tornado.")):
                    # we only want errors logged by loggers octoprint.* or tornado.*
                    return None

                if logger.startswith("octoprint.plugins."):
                    plugin_id = logger.split(".")[2]
                    plugin_info = plugin_manager().get_plugin_info(plugin_id)
                    if plugin_info is None or not plugin_info.bundled:
                        # we only want our active bundled plugins
                        return None

                if plugin is not None:
                    plugin_info = plugin_manager().get_plugin_info(plugin)
                    if plugin_info is None or not plugin_info.bundled:
                        # we only want our active bundled plugins
                        return None

            return event

        sentry_sdk.init(url_server, release=version, before_send=_before_send)

        with sentry_sdk.configure_scope() as scope:
            scope.user = {"id": unique_id}

        logging.getLogger("octoprint.plugins.errortracking").info(
            "Initialized error tracking")
        _enabled = True
예제 #10
0
    def _create_backup(cls,
                       name,
                       exclude=None,
                       settings=None,
                       plugin_manager=None,
                       datafolder=None,
                       on_backup_start=None,
                       on_backup_done=None,
                       on_backup_error=None):
        exclude_by_default = (
            "generated",
            "logs",
            "watched",
        )

        try:
            if exclude is None:
                exclude = []
            if not isinstance(exclude, list):
                exclude = list(exclude)

            if "timelapse" in exclude:
                exclude.append("timelapse_tmp")

            configfile = settings._configfile
            basedir = settings._basedir

            temporary_path = os.path.join(datafolder, ".{}".format(name))
            final_path = os.path.join(datafolder, name)

            own_folder = datafolder
            defaults = [os.path.join(basedir, "config.yaml"),] + \
                       [os.path.join(basedir, folder) for folder in default_settings["folder"].keys()]

            # check how much many bytes we are about to backup
            size = os.stat(configfile).st_size
            for folder in default_settings["folder"].keys():
                if folder in exclude or folder in exclude_by_default:
                    continue
                size += cls._get_disk_size(
                    settings.global_get_basefolder(folder),
                    ignored=[
                        own_folder,
                    ])
            size += cls._get_disk_size(basedir,
                                       ignored=defaults + [
                                           own_folder,
                                       ])

            # since we can't know the compression ratio beforehand, we assume we need the same amount of space
            if not cls._free_space(os.path.dirname(temporary_path), size):
                raise InsufficientSpace()

            compression = zipfile.ZIP_DEFLATED if zlib else zipfile.ZIP_STORED

            if callable(on_backup_start):
                on_backup_start(name, temporary_path, exclude)

            with zipfile.ZipFile(temporary_path,
                                 mode="w",
                                 compression=compression,
                                 allowZip64=True) as zip:

                def add_to_zip(source, target, ignored=None):
                    if ignored is None:
                        ignored = []

                    if source in ignored:
                        return

                    if os.path.isdir(source):
                        for entry in scandir(source):
                            add_to_zip(entry.path,
                                       os.path.join(target, entry.name),
                                       ignored=ignored)
                    elif os.path.isfile(source):
                        zip.write(source, arcname=target)

                # add metadata
                metadata = dict(version=get_octoprint_version_string(),
                                excludes=exclude)
                zip.writestr("metadata.json", json.dumps(metadata))

                # backup current config file
                add_to_zip(configfile,
                           "basedir/config.yaml",
                           ignored=[
                               own_folder,
                           ])

                # backup configured folder paths
                for folder in default_settings["folder"].keys():
                    if folder in exclude or folder in exclude_by_default:
                        continue

                    add_to_zip(settings.global_get_basefolder(folder),
                               "basedir/" + folder.replace("_", "/"),
                               ignored=[
                                   own_folder,
                               ])

                # backup anything else that might be lying around in our basedir
                add_to_zip(basedir,
                           "basedir",
                           ignored=defaults + [
                               own_folder,
                           ])

                # add list of installed plugins
                plugins = []
                plugin_folder = settings.global_get_basefolder("plugins")
                for key, plugin in plugin_manager.plugins.items():
                    if plugin.bundled or (
                            isinstance(plugin.origin, FolderOrigin)
                            and plugin.origin.folder == plugin_folder):
                        # ignore anything bundled or from the plugins folder we already include in the backup
                        continue

                    plugins.append(
                        dict(key=plugin.key, name=plugin.name, url=plugin.url))

                if len(plugins):
                    zip.writestr("plugin_list.json", json.dumps(plugins))

            shutil.move(temporary_path, final_path)

            if callable(on_backup_done):
                on_backup_done(name, final_path, exclude)
        except:
            if callable(on_backup_error):
                exc_info = sys.exc_info()
                try:
                    on_backup_error(name, exc_info)
                finally:
                    del exc_info
            raise
예제 #11
0
def _enable_errortracking():
	# this is a bit hackish, but we want to enable error tracking as early in the platform lifecycle as possible
	# and hence can't wait until our implementation is initialized and injected with settings

	from octoprint.settings import settings
	global _enabled

	if _enabled:
		return

	version = get_octoprint_version_string()

	s = settings()
	plugin_defaults = dict(plugins=dict(errortracking=SETTINGS_DEFAULTS))

	enabled = s.getBoolean(["plugins", "errortracking", "enabled"], defaults=plugin_defaults)
	enabled_unreleased = s.getBoolean(["plugins", "errortracking", "enabled_unreleased"], defaults=plugin_defaults)
	url_server = s.get(["plugins", "errortracking", "url_server"], defaults=plugin_defaults)
	unique_id = s.get(["plugins", "errortracking", "unique_id"], defaults=plugin_defaults)
	if unique_id is None:
		import uuid
		unique_id = str(uuid.uuid4())
		s.set(["plugins", "errortracking", "unique_id"], unique_id, defaults=plugin_defaults)
		s.save()

	if _is_enabled(enabled, enabled_unreleased):
		import sentry_sdk
		from octoprint.plugin import plugin_manager

		def _before_send(event, hint):
			if not "exc_info" in hint:
				# we only want exceptions
				return None

			handled = True
			logger = event.get("logger", "")
			plugin = event.get("extra", dict()).get("plugin", None)

			for ignore in IGNORED_EXCEPTIONS:
				if isinstance(ignore, tuple):
					ignored_exc, matcher = ignore
				else:
					ignored_exc = ignore
					matcher = lambda *args: True

				exc = hint["exc_info"][1]
				if isinstance(exc, ignored_exc) and matcher(exc, logger, plugin):
					# exception ignored for logger
					return None

				elif isinstance(ignore, type):
					if isinstance(hint["exc_info"][1], ignore):
						# exception ignored
						return None

			if event.get("exception") and event["exception"].get("values"):
				handled = not any(map(lambda x: x.get("mechanism") and x["mechanism"].get("handled", True) == False,
				                      event["exception"]["values"]))

			if handled:
				# error is handled, restrict further based on logger
				if logger != "" and not (logger.startswith("octoprint.") or logger.startswith("tornado.")):
					# we only want errors logged by loggers octoprint.* or tornado.*
					return None

				if logger.startswith("octoprint.plugins."):
					plugin_id = logger.split(".")[2]
					plugin_info = plugin_manager().get_plugin_info(plugin_id)
					if plugin_info is None or not plugin_info.bundled:
						# we only want our active bundled plugins
						return None

				if plugin is not None:
					plugin_info = plugin_manager().get_plugin_info(plugin)
					if plugin_info is None or not plugin_info.bundled:
						# we only want our active bundled plugins
						return None

			return event

		sentry_sdk.init(url_server,
		                release=version,
		                before_send=_before_send)

		with sentry_sdk.configure_scope() as scope:
			scope.user = dict(id=unique_id)

		logging.getLogger("octoprint.plugins.errortracking").info("Initialized error tracking")
		_enabled = True
예제 #12
0
	def _create_backup(cls, name,
	                   exclude=None,
	                   settings=None,
	                   plugin_manager=None,
	                   datafolder=None,
	                   on_backup_start=None,
	                   on_backup_done=None,
	                   on_backup_error=None):
		try:
			if exclude is None:
				exclude = []

			configfile = settings._configfile
			basedir = settings._basedir

			temporary_path = os.path.join(datafolder, ".{}".format(name))
			final_path = os.path.join(datafolder, name)

			size = cls._get_disk_size(basedir)
			if not cls._free_space(os.path.dirname(temporary_path), size):
				raise InsufficientSpace()

			own_folder = datafolder
			defaults = [os.path.join(basedir, "config.yaml"),] + \
			           [os.path.join(basedir, folder) for folder in default_settings["folder"].keys()]

			compression = zipfile.ZIP_DEFLATED if zlib else zipfile.ZIP_STORED

			if callable(on_backup_start):
				on_backup_start(name, temporary_path, exclude)

			with zipfile.ZipFile(temporary_path, mode="w", compression=compression, allowZip64=True) as zip:
				def add_to_zip(source, target, ignored=None):
					if ignored is None:
						ignored = []

					if source in ignored:
						return

					if os.path.isdir(source):
						for entry in scandir(source):
							add_to_zip(entry.path, os.path.join(target, entry.name), ignored=ignored)
					elif os.path.isfile(source):
						zip.write(source, arcname=target)

				# add metadata
				metadata = dict(version=get_octoprint_version_string(),
				                excludes=exclude)
				zip.writestr("metadata.json", json.dumps(metadata))

				# backup current config file
				add_to_zip(configfile, "basedir/config.yaml", ignored=[own_folder,])

				# backup configured folder paths
				for folder in default_settings["folder"].keys():
					if folder in exclude:
						continue

					if folder in ("generated", "logs", "watched",):
						continue

					add_to_zip(settings.global_get_basefolder(folder),
					           "basedir/" + folder.replace("_", "/"),
					           ignored=[own_folder,])

				# backup anything else that might be lying around in our basedir
				add_to_zip(basedir, "basedir", ignored=defaults + [own_folder, ])

				# add list of installed plugins
				plugins = []
				plugin_folder = settings.global_get_basefolder("plugins")
				for key, plugin in plugin_manager.plugins.items():
					if plugin.bundled or (isinstance(plugin.origin, FolderOrigin) and plugin.origin.folder == plugin_folder):
						# ignore anything bundled or from the plugins folder we already include in the backup
						continue

					plugins.append(dict(key=plugin.key,
					                    name=plugin.name,
					                    url=plugin.url))

				if len(plugins):
					zip.writestr("plugin_list.json", json.dumps(plugins))

			os.rename(temporary_path, final_path)

			if callable(on_backup_done):
				on_backup_done(name, final_path, exclude)
		except:
			if callable(on_backup_error):
				exc_info = sys.exc_info()
				try:
					on_backup_error(name, exc_info)
				finally:
					del exc_info
			raise
예제 #13
0
    def _create_backup(
        cls,
        name,
        exclude=None,
        settings=None,
        plugin_manager=None,
        logger=None,
        datafolder=None,
        on_backup_start=None,
        on_backup_done=None,
        on_backup_error=None,
    ):
        if logger is None:
            logger = logging.getLogger(__name__)

        exclude_by_default = (
            "generated",
            "logs",
            "watched",
        )

        try:
            if exclude is None:
                exclude = []
            if not isinstance(exclude, list):
                exclude = list(exclude)

            if "timelapse" in exclude:
                exclude.append("timelapse_tmp")

            current_excludes = list(exclude)
            additional_excludes = list()
            plugin_data = settings.global_get_basefolder("data")
            for plugin, hook in plugin_manager.get_hooks(
                    "octoprint.plugin.backup.additional_excludes").items():
                try:
                    additional = hook(current_excludes)
                    if isinstance(additional, list):
                        if "." in additional:
                            current_excludes.append(
                                os.path.join("data", plugin))
                            additional_excludes.append(
                                os.path.join(plugin_data, plugin))
                        else:
                            current_excludes += map(
                                lambda x: os.path.join("data", plugin, x),
                                additional)
                            additional_excludes += map(
                                lambda x: os.path.join(plugin_data, plugin, x),
                                additional)
                except Exception:
                    logger.exception(
                        "Error while retrieving additional excludes "
                        "from plugin {name}".format(**locals()),
                        extra={"plugin": plugin},
                    )

            configfile = settings._configfile
            basedir = settings._basedir

            temporary_path = os.path.join(datafolder, ".{}".format(name))
            final_path = os.path.join(datafolder, name)

            own_folder = datafolder
            defaults = [
                os.path.join(basedir, "config.yaml"),
            ] + [
                os.path.join(basedir, folder)
                for folder in default_settings["folder"].keys()
            ]

            # check how many bytes we are about to backup
            size = os.stat(configfile).st_size
            for folder in default_settings["folder"].keys():
                if folder in exclude or folder in exclude_by_default:
                    continue
                size += cls._get_disk_size(
                    settings.global_get_basefolder(folder),
                    ignored=[
                        own_folder,
                    ],
                )
            size += cls._get_disk_size(
                basedir,
                ignored=defaults + [
                    own_folder,
                ],
            )

            # since we can't know the compression ratio beforehand, we assume we need the same amount of space
            if not cls._free_space(os.path.dirname(temporary_path), size):
                raise InsufficientSpace()

            compression = zipfile.ZIP_DEFLATED if zlib else zipfile.ZIP_STORED

            if callable(on_backup_start):
                on_backup_start(name, temporary_path, exclude)

            with zipfile.ZipFile(temporary_path,
                                 mode="w",
                                 compression=compression,
                                 allowZip64=True) as zip:

                def add_to_zip(source, target, ignored=None):
                    if ignored is None:
                        ignored = []

                    if source in ignored:
                        return

                    if os.path.isdir(source):
                        for entry in scandir(source):
                            add_to_zip(
                                entry.path,
                                os.path.join(target, entry.name),
                                ignored=ignored,
                            )
                    elif os.path.isfile(source):
                        zip.write(source, arcname=target)

                # add metadata
                metadata = {
                    "version": get_octoprint_version_string(),
                    "excludes": exclude,
                }
                zip.writestr("metadata.json", json.dumps(metadata))

                # backup current config file
                add_to_zip(
                    configfile,
                    "basedir/config.yaml",
                    ignored=[
                        own_folder,
                    ],
                )

                # backup configured folder paths
                for folder in default_settings["folder"].keys():
                    if folder in exclude or folder in exclude_by_default:
                        continue

                    add_to_zip(
                        settings.global_get_basefolder(folder),
                        "basedir/" + folder.replace("_", "/"),
                        ignored=[
                            own_folder,
                        ] + additional_excludes,
                    )

                # backup anything else that might be lying around in our basedir
                add_to_zip(
                    basedir,
                    "basedir",
                    ignored=defaults + [
                        own_folder,
                    ] + additional_excludes,
                )

                # add list of installed plugins
                plugins = []
                plugin_folder = settings.global_get_basefolder("plugins")
                for plugin in plugin_manager.plugins.values():
                    if plugin.bundled or (
                            isinstance(plugin.origin, FolderOrigin)
                            and plugin.origin.folder == plugin_folder):
                        # ignore anything bundled or from the plugins folder we already include in the backup
                        continue

                    plugins.append({
                        "key": plugin.key,
                        "name": plugin.name,
                        "url": plugin.url
                    })

                if len(plugins):
                    zip.writestr("plugin_list.json", json.dumps(plugins))

            shutil.move(temporary_path, final_path)

            if callable(on_backup_done):
                on_backup_done(name, final_path, exclude)

        except Exception as exc:  # noqa: F841
            # TODO py3: use the exception, not sys.exc_info()
            if callable(on_backup_error):
                exc_info = sys.exc_info()
                try:
                    on_backup_error(name, exc_info)
                finally:
                    del exc_info
            raise