shields icon indicating copy to clipboard operation
shields copied to clipboard

OSS Lifecycle GitLab

Open JoeIzzard opened this issue 1 year ago • 2 comments

📋 Description

There are already OSS Lifecycle and OSS Lifecycle Branch badges for GitHub, however it would be great to have a version that is compatible with GitLab. This would look and work like the existing OSS Lifecycle badge: Static Badge

🔗 Data

The data for this would be pulled from the Raw GitLab content for that repo. On GitLab this takes the form: https://gitlab.com/{USER}/{REPO}/-/raw/{BRANCH}/{FILE}?ref_type=heads

🎤 Motivation

I largely use GitLab and would like to be able to use the badge.

JoeIzzard avatar Jun 10 '24 15:06 JoeIzzard

I'd propose that the current OSS Lifecycle badges are renamed to indicate that they are for GitHub, with the endpoint kept the same, and a new badge pulling from GitLab with the options common for GitLab badges such as the instance URL are created.

Happy to do the work to implement and get a PR ready once a direction is confirmed.

JoeIzzard avatar Jun 10 '24 15:06 JoeIzzard

We have a number of badges that are not using any API. They just look at a file which could be anywhere but are hard-coded to assume that file is on GitHub. I would really like to gradually move these to being agnostic about where the file is hosted. So they will work for projects on GitHub, GitLab, BitBucket, Forgejo, your self-hosted instance of Gitea.. all the places. OSSLifeCycle is one of these.

I think my suggestion here would be that we migrate this so that instead of

https://img.shields.io/osslifecycle/Netflix/osstracker

the URL becomes

https://img.shields.io/osslifecycle?file=https%3A//github.com/Netflix/osstracker/blob/master/OSSMETADATA

but file can live anywhere. To maintain backwards compatibility for existing users, we can use a redirect to redirect calls to osslifecycle/{user}/{repo}/{branch} to https://img.shields.io/osslifecycle?file=https%3A//github.com/{user}/{repo}/blob/{branch}/OSSMETADATA.

chris48s avatar Jun 11 '24 18:06 chris48s

I have been having a look at this, but have run into some issues. Has this been implemented for any other endpoints I can look over as an example implementation?

So far I have added a file_url query parameter, and some logic so that if it is provided we fetch that otherwise use the existing URL structure.

The issue I am finding is with creating the bare osslifecycle without path parameters. Removing the pattern aspect of the route results in an error which I believe is an assertion error for having less than one extension, crash log below:

Crash Log
[server] /Users/jizzard/Desktop/Projects/Personal/Open Source/Shields/node_modules/joi/lib/errors.js:193
[server]         return new exports.ValidationError(message, details, original);
[server]                ^
[server] 
[server] Error [ValidationError]: Route for OssTracker {
[server]   "base": "osslifecycle",
[server]   "queryParamSchema": {
[server]     "type": "object",
[server]     "$_root": {
[server]       "_types": {},
[server]       "alternatives": function (...args) {\n\n            Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');\n            return internals.generate(this, internals.types[type], args);\n        },
[server]       "any": function (...args) {\n\n            Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');\n            return internals.generate(this, internals.types[type], args);\n        },
[server]       "array": function (...args) {\n\n            Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');\n            return internals.generate(this, internals.types[type], args);\n        },
[server]       "boolean": function (...args) {\n\n            Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');\n            return internals.generate(this, internals.types[type], args);\n        },
[server]       "date": function (...args) {\n\n            Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');\n            return internals.generate(this, internals.types[type], args);\n        },
[server]       "function": function (...args) {\n\n            Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');\n            return internals.generate(this, internals.types[type], args);\n        },
[server]       "link": function (...args) {\n\n            Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');\n            return internals.generate(this, internals.types[type], args);\n        },
[server]       "number": function (...args) {\n\n            Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');\n            return internals.generate(this, internals.types[type], args);\n        },
[server]       "object": function (...args) {\n\n            Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');\n            return internals.generate(this, internals.types[type], args);\n        },
[server]       "string": function (...args) {\n\n            Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');\n            return internals.generate(this, internals.types[type], args);\n        },
[server]       "symbol": function (...args) {\n\n            Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');\n            return internals.generate(this, internals.types[type], args);\n        },
[server]       "binary": function (...args) {\n\n            Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');\n            return internals.generate(this, internals.types[type], args);\n        },
[server]       "allow": function (...args) {\n\n            return this.any()[method](...args);\n        },
[server]       "custom": function (...args) {\n\n            return this.any()[method](...args);\n        },
[server]       "disallow": function (...args) {\n\n            return this.any()[method](...args);\n        },
[server]       "equal": function (...args) {\n\n            return this.any()[method](...args);\n        },
[server]       "exist": function (...args) {\n\n            return this.any()[method](...args);\n        },
[server]       "forbidden": function (...args) {\n\n            return this.any()[method](...args);\n        },
[server]       "invalid": function (...args) {\n\n            return this.any()[method](...args);\n        },
[server]       "not": function (...args) {\n\n            return this.any()[method](...args);\n        },
[server]       "only": function (...args) {\n\n            return this.any()[method](...args);\n        },
[server]       "optional": function (...args) {\n\n            return this.any()[method](...args);\n        },
[server]       "options": function (...args) {\n\n            return this.any()[method](...args);\n        },
[server]       "prefs": function (...args) {\n\n            return this.any()[method](...args);\n        },
[server]       "preferences": function (...args) {\n\n            return this.any()[method](...args);\n        },
[server]       "required": function (...args) {\n\n            return this.any()[method](...args);\n        },
[server]       "strip": function (...args) {\n\n            return this.any()[method](...args);\n        },
[server]       "valid": function (...args) {\n\n            return this.any()[method](...args);\n        },
[server]       "when": function (...args) {\n\n            return this.any()[method](...args);\n        },
[server]       "ValidationError": "[class extends Error {\n\n    constructor(message, details, original) {\n\n        super(message);\n        this._original = original;\n        this.details = details;\n    }\n\n    static isError(err) {\n\n        return err instanceof exports.ValidationError;\n    }\n}]",
[server]       "version": "17.13.3",
[server]       "cache": {
[server]         "provision": "[provision(options) {\n\n        return new internals.Cache(options);\n    }]"
[server]       },
[server]       "assert": "[assert(value, schema, ...args /* [message], [options] */) {\n\n        internals.assert(value, schema, true, args);\n    }]",
[server]       "attempt": "[attempt(value, schema, ...args /* [message], [options] */) {\n\n        return internals.assert(value, schema, false, args);\n    }]",
[server]       "build": "[build(desc) {\n\n        Assert(typeof Manifest.build === 'function', 'Manifest functionality disabled');\n        return Manifest.build(this, desc);\n    }]",
[server]       "checkPreferences": "[checkPreferences(prefs) {\n\n        Common.checkPreferences(prefs);\n    }]",
[server]       "compile": "[compile(schema, options) {\n\n        return Compile.compile(this, schema, options);\n    }]",
[server]       "defaults": "[defaults(modifier) {\n\n        Assert(typeof modifier === 'function', 'modifier must be a function');\n\n        const joi = Object.assign({}, this);\n        for (const type of joi._types) {\n            const schema = modifier(joi[type]());\n            Assert(Common.isSchema(schema), 'modifier must return a valid schema object');\n\n            joi[type] = function (...args) {\n\n                return internals.generate(this, schema, args);\n            };\n        }\n\n        return joi;\n    }]",
[server]       "expression": "[expression(...args) {\n\n        return new Template(...args);\n    }]",
[server]       "extend": "[extend(...extensions) {\n\n        Common.verifyFlat(extensions, 'extend');\n\n        Schemas = Schemas || require('./schemas');\n\n        Assert(extensions.length, 'You need to provide at least one extension');\n        this.assert(extensions, Schemas.extensions);\n\n        const joi = Object.assign({}, this);\n        joi._types = new Set(joi._types);\n\n        for (let extension of extensions) {\n            if (typeof extension === 'function') {\n                extension = extension(joi);\n            }\n\n            this.assert(extension, Schemas.extension);\n\n            const expanded = internals.expandExtension(extension, joi);\n            for (const item of expanded) {\n                Assert(joi[item.type] === undefined || joi._types.has(item.type), 'Cannot override name', item.type);\n\n                const base = item.base || this.any();\n                const schema = Extend.type(base, item);\n\n                joi._types.add(item.type);\n                joi[item.type] = function (...args) {\n\n                    return internals.generate(this, schema, args);\n                };\n            }\n        }\n\n        return joi;\n    }]",
      "isError": "[isError(err) {\n\n        return e[nodemon] app crashed - waiting for file changes before starting...

I also tried making the pattern optional but get an error than the Route and OpenAPI Spec don't match, and it doesn't seem to be possible to in OpenAPI to have an optional path parameter.

JoeIzzard avatar Sep 01 '24 19:09 JoeIzzard

Yeah sure. Why not look at Python Version from pyproject.toml service

https://github.com/badges/shields/blob/master/services/python/python-version-from-toml.service.js

https://shields.io/badges/python-version-from-pep-621-toml

It is difficult to tell exactly why you are getting the errors you are getting because I can't see your code, but my guess is it is because you're changing the route object but not updating the signature of the handle() function and/or the openApi object to match it.

chris48s avatar Sep 01 '24 21:09 chris48s

Thanks @chris48s, that pointed me in the right direction! It was mostly down to having osslifecycle as the base, moving it to the pattern solved the issue as it was considered part of the extension.

JoeIzzard avatar Sep 02 '24 13:09 JoeIzzard