Gitlab as a Plugin repository

I am in the process of developing a plugin and going to host it on Gitlab. After checking forums and the OctoPrint source code, I couldn't find anything that would indicate Gitlab can be used as a repository for hosting a plugin (code).

It seems only Github is supported. Is that correct?

Thanks! Sacha

That depends on how you define "supported". You can host your plugin where ever you want, all that the plugin manager (and the repository) really needs is some archive URL to use for installing it via pip.

However, if you want to enable automatic updating of your plugin through OctoPrint, the most commonly used version check mechanism you can use for that relies on Github's releases API. I honestly do not know if Gitlab has a similar API, but if it has I would have no problem with merging a PR that adds that as a new version check gitlab_release (and maybe also gitlab_commit?).

Thanks @foosel for the quick reply!

Perhaps worded it not correctly, as I know OctoPrint also can use Bitbucket as a repository :slight_smile:

Gitlab does have a Releases feature similar to that of Github. Not sure how it actually works though but I guess a gitlab_release should be possible. A gitlab_commit equally shouldn't be hard either as Gitlab is just a Git Repository service.

Not sure if I have the time to do a PR, but let me check your documentation and that of Gitlab how that could work.

Thanks again!

Sacha

1 Like

Mosaic Manufacturing is using GitLab and auto notices come through for that...

https://gitlab.com/mosaic-mfg/canvas-plugin/blob/master/octoprint_canvas/__init__.py#L73

@jneilliii Thanks! That looks like a nice way of doing it.

However your solution is using a custom shell script to check for the latest version. I am considering distributing my plugin, so how do you manage distributing that check script since it is not part of your plugin? (at least I couldn't find it)

Not my solution. But isn't the following doing that part maybe?

https://gitlab.com/mosaic-mfg/canvas-plugin/blob/master/octoprint_canvas/__init__.py#L45

That seems to be getting some other software/code. Not sure what that method is doing as I couldn't find any other reference to it.

This line shows there is a shell command for checking the latest plugin version:
https://gitlab.com/mosaic-mfg/canvas-plugin/blob/master/octoprint_canvas/init.py#L84

I think it's because it's used as a hook when python_updater is the method. I think you'd just update the code to pull the url https://gitlab.com/api/v4/projects/mosaic-mfg%2Fcanvas-plugin/releases (in the case of this plugin) and process the json response comparing it to current version etc. The api documentation for listing the releases in gitlab is over here. I looked at my pi where the plugin is installed and it doesn't have that sh file on it where it's listed as being, so maybe there is some other magic they are doing. Maybe just using their own server to control the release of the version versus using the gitlab release mechanism and checking against that.

This might work, no guarantees....you would replace the parts to point to your gitlab repo, etc. You'll have to make sure you import both requests and LooseVersion at the beginning of your init file. What I'm not 100% sure on is if the pip declaration will replace the tokens in the url or not, but if not you could just force it to the main zip download url of your master repo.

	def get_latest(self, target, check, full_data=False, online=True):
		request_url = "https://gitlab.com/api/v4/projects/{user}%2F{repo}/releases".format(check)
		resp = requests.get(request_url)
		version_data = resp.json()
		version = version_data[0]["name"]
		current_version = check.get("current")
		information = dict(
			local=dict(
				name=current_version,
				value=current_version,
			),
			remote=dict(
				name=version,
				value=version
			)
		)
		self._logger.info("current version: %s" % current_version)
		self._logger.info("remote version: %s" % version)
		needs_update = LooseVersion(current_version) < LooseVersion(version)
		self._logger.info("needs update: %s" % needs_update)

		return information, not needs_update

	def get_update_information(self):
		return dict(
			canvas=dict(
				displayName="Canvas Plugin",
				displayVersion=self._plugin_version,
				current=self._plugin_version,
				python_checker=self,
				type="python_checker",
				user="mosaic-mfg",
				repo="canvas-plugin"
				pip="https://gitlab.com/mosaic-mfg/canvas-plugin/-/archive/{target_version}/canvas-plugin-{target_version}.zip"
			)
		)

@jneilliii Thanks for the example! Pretty clear now. I will give this a try.

Cheers! Sacha

Could you make any progress about this? I tried to implement my own gitlab repo url watching the examples and I got it to work until "get_update_information" but the function "get_latest" is never called and there isn't any useful information on the logs.

I'm stucked here, I tried checking other plugin examples and looks like I'm doing correctly but it didn't work and now I'm starting to think that this method is not working for anyone.

Here is my config:

def get_update_information(self):
	self._logger.info("Get Update Info")
	return dict(
		filament_health=dict(
			displayName="Filament Helath Plugin",
			displayVersion=self._plugin_version,

			current=self._plugin_version,
			python_checker=self,
			type="python_checker",
			user="drunkly",
			repo="octoprint_spool_health",
			# update method: pip
			pip=constants.PLUGIN_UPDATE_URL,
		)
	)
def get_latest(self, target, check, full_data=False, online=True):
	self._logger.info("Get Latest")
	request_url = "https://my.gitlab.repo/api/v4/projects/{user}%2F{repo}/releases".format(check)
	resp = requests.get(request_url)
	version_data = resp.json()
	version = version_data[0]["name"]
	current_version = check.get("current")
	information = dict(
		local=dict(
			name=current_version,
			value=current_version,
		),
		remote=dict(
			name=version,
			value=version
		)
	)
	self._logger.info("current version: %s" % current_version)
	self._logger.info("remote version: %s" % version)
	needs_update = LooseVersion(current_version) < LooseVersion(version)
	self._logger.info("needs update: %s" % needs_update)

	return information, not needs_update

Then I go to Octopring -> Software Update -> Advanced Options -> Force update check.
As I said on the logs I got the next message after trying an update :

2021-01-11 09:21:38,599 - octoprint.plugins.filament_health - INFO - Get Update Info

But the "Get Latest" is never called. I would appreciate if someone can point me on the right direction.

Am I maybe expecting

Try python_checker=self.get_latest?

Thanks for the tip, I tried already but had no luck, it is not detecting the new releases.

Another thing I realize now is when I go to "Software Updates" all the plugins disappears if I enable my plugin. After disable my plugin all the other plugins appears again on the list. It is like my plugin were breaking the update workflow but I can't see any error on the logs.

Strange that the rest of them disappear. If I get a chance later today maybe I'll read through the software update plugin docs and double check. It does seem like maybe there is an error in the check stuff, that wipes them all out, maybe there's some over-protective error handling that is silencing things there.

If I enable my plugin and try to change a "Software Update" setting then I get the next log entries. It looks like there is something broken related to "access_methods". Someone has a lead about what can I check?

2021-01-19 15:36:41,977 - octoprint.plugins.softwareupdate - INFO - Minimum free storage across all update relevant locations is 69.2GB. That is considered sufficient for updating.
2021-01-19 15:36:42,722 - octoprint.plugins.filament_health - INFO - Get Update Info
2021-01-19 15:36:42,733 - octoprint.server.api.settings - ERROR - Could not load settings for plugin Software Update (None)
Traceback (most recent call last):
  File "/home/pi/Proyectos/OctoPrint/venv/lib/python3.7/site-packages/octoprint/server/api/settings.py", line 353, in _get_plugin_settings
    result = plugin.on_settings_load()
  File "/home/pi/Proyectos/OctoPrint/venv/lib/python3.7/site-packages/octoprint/util/__init__.py", line 1890, in wrapper
    return f(*args, **kwargs)
  File "/home/pi/Proyectos/OctoPrint/venv/lib/python3.7/site-packages/octoprint/plugins/softwareupdate/__init__.py", line 552, in on_settings_load
    checks = self._get_configured_checks()
  File "/home/pi/Proyectos/OctoPrint/venv/lib/python3.7/site-packages/octoprint/plugins/softwareupdate/__init__.py", line 225, in _get_configured_checks
    effective_config = copy.deepcopy(default_config)
  File "/usr/lib/python3.7/copy.py", line 150, in deepcopy
    y = copier(x, memo)
  File "/usr/lib/python3.7/copy.py", line 240, in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
  File "/usr/lib/python3.7/copy.py", line 180, in deepcopy
    y = _reconstruct(x, memo, *rv)
  File "/usr/lib/python3.7/copy.py", line 280, in _reconstruct
    state = deepcopy(state, memo)
  File "/usr/lib/python3.7/copy.py", line 150, in deepcopy
    y = copier(x, memo)
  File "/usr/lib/python3.7/copy.py", line 240, in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
  File "/usr/lib/python3.7/copy.py", line 180, in deepcopy
    y = _reconstruct(x, memo, *rv)
  File "/usr/lib/python3.7/copy.py", line 281, in _reconstruct
    if hasattr(y, '__setstate__'):
  File "/home/pi/Proyectos/OctoPrint/venv/lib/python3.7/site-packages/octoprint/plugin/__init__.py", line 599, in __getattr__
    all_access_methods = list(self.access_methods.keys()) + list(
  File "/home/pi/Proyectos/OctoPrint/venv/lib/python3.7/site-packages/octoprint/plugin/__init__.py", line 599, in __getattr__
    all_access_methods = list(self.access_methods.keys()) + list(
  File "/home/pi/Proyectos/OctoPrint/venv/lib/python3.7/site-packages/octoprint/plugin/__init__.py", line 599, in __getattr__
    all_access_methods = list(self.access_methods.keys()) + list(
  [Previous line repeated 468 more times]
RecursionError: maximum recursion depth exceeded
2021-01-19 15:36:43,606 - octoprint.plugins.softwareupdate - WARNING - Could not check octoprint for updates due to running into a rate limit: Github rate limit exceeded, reset at 2021-01-19 15:06
2021-01-19 15:36:43,640 - octoprint.plugins.softwareupdate - INFO - Saved version cache to disk

Ah, I forgot about replying to this one. From what I can see in the source, what you have configured is correct, using python_checker=self, and the correct type etc.

Without setting the debugger on it myself I don't have many suggestions other than to open a bug report, since something is broken that shouldn't be there. If it turns out to be a configuration issue, then the bug can be closed and what looks like some better error handling could be implemented.

Is it available publicly? I don't see user drunkly on gitlab...the URL below doesn't respond...

https://my.gitlab.repo/api/v4/projects/drunkly%2Foctoprint_spool_health/releases

Actually I created the gitlab repository and is hosted on my own Virtual Machine which is not running 24/7 while I'm developing it.

But if you are looking for source code I will attach here the last release I built

This is what I'm trying to say. If the octoprint server can't load that link there's no way that the update check would work.

Agree. However for testing the machine is up and I have checked the url is reachable from the octoprint raspberry and validated the output json.

However the problem must be happening before because I added a log message at the beginning of the "get_latest" function which is never printed because this function for some reason is never called.