diff --git a/.changeset/nasty-fireants-double.md b/.changeset/nasty-fireants-double.md new file mode 100644 index 000000000..69e21c413 --- /dev/null +++ b/.changeset/nasty-fireants-double.md @@ -0,0 +1,5 @@ +--- +"@monokle/synchronizer": patch +--- + +Fixed invalid git URL parsing diff --git a/package-lock.json b/package-lock.json index 4537e58f8..519600a3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8008,6 +8008,12 @@ "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==", "dev": true }, + "node_modules/@types/git-url-parse": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@types/git-url-parse/-/git-url-parse-9.0.1.tgz", + "integrity": "sha512-Zf9mY4Mz7N3Nyi341nUkOtgVUQn4j6NS4ndqEha/lOgEbTkHzpD7wZuRagYKzrXNtvawWfsrojoC1nhsQexvNA==", + "dev": true + }, "node_modules/@types/glob": { "version": "7.2.0", "license": "MIT", @@ -15558,6 +15564,23 @@ "node": ">=0.10.0" } }, + "node_modules/git-up": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-7.0.0.tgz", + "integrity": "sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==", + "dependencies": { + "is-ssh": "^1.4.0", + "parse-url": "^8.1.0" + } + }, + "node_modules/git-url-parse": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-13.1.0.tgz", + "integrity": "sha512-5FvPJP/70WkIprlUZ33bm4UAaFdjcLkJLpWft1BeZKqwR0uhhNGoKwlUaPtVb4LxCSQ++erHapRak9kWGj+FCA==", + "dependencies": { + "git-up": "^7.0.0" + } + }, "node_modules/github-slugger": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz", @@ -16907,6 +16930,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-ssh": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.0.tgz", + "integrity": "sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==", + "dependencies": { + "protocols": "^2.0.1" + } + }, "node_modules/is-stream": { "version": "2.0.1", "dev": true, @@ -20164,6 +20195,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse-path": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz", + "integrity": "sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==", + "dependencies": { + "protocols": "^2.0.0" + } + }, + "node_modules/parse-url": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-8.1.0.tgz", + "integrity": "sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==", + "dependencies": { + "parse-path": "^7.0.0" + } + }, "node_modules/parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", @@ -21038,6 +21085,11 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/protocols": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.1.tgz", + "integrity": "sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -23428,6 +23480,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/slugify": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", + "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/smartwrap": { "version": "2.0.2", "license": "MIT", @@ -28241,20 +28301,23 @@ }, "packages/synchronizer": { "name": "@monokle/synchronizer", - "version": "0.0.1", + "version": "0.3.0", "license": "MIT", "dependencies": { "@monokle/validation": "*", "env-paths": "^3.0.0", + "git-url-parse": "^13.1.0", "mkdirp": "^3.0.1", "node-fetch": "^3.3.2", "normalize-url": "^8.0.0", "openid-client": "^5.4.3", "simple-git": "^3.19.1", + "slugify": "^1.6.6", "yaml": "^2.3.1" }, "devDependencies": { "@types/chai": "^4.3.5", + "@types/git-url-parse": "^9.0.1", "@types/mocha": "^10.0.1", "@types/sinon": "^10.0.16", "c8": "^8.0.1", @@ -31823,11 +31886,13 @@ "requires": { "@monokle/validation": "*", "@types/chai": "^4.3.5", + "@types/git-url-parse": "^9.0.1", "@types/mocha": "^10.0.1", "@types/sinon": "^10.0.16", "c8": "^8.0.1", "chai": "^4.3.7", "env-paths": "^3.0.0", + "git-url-parse": "^13.1.0", "mkdirp": "^3.0.1", "mocha": "^10.2.0", "node-fetch": "^3.3.2", @@ -31835,6 +31900,7 @@ "openid-client": "^5.4.3", "simple-git": "^3.19.1", "sinon": "^15.2.0", + "slugify": "^1.6.6", "yaml": "^2.3.1" }, "dependencies": { @@ -35438,6 +35504,12 @@ "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==", "dev": true }, + "@types/git-url-parse": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@types/git-url-parse/-/git-url-parse-9.0.1.tgz", + "integrity": "sha512-Zf9mY4Mz7N3Nyi341nUkOtgVUQn4j6NS4ndqEha/lOgEbTkHzpD7wZuRagYKzrXNtvawWfsrojoC1nhsQexvNA==", + "dev": true + }, "@types/glob": { "version": "7.2.0", "requires": { @@ -41203,6 +41275,23 @@ "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", "dev": true }, + "git-up": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-7.0.0.tgz", + "integrity": "sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==", + "requires": { + "is-ssh": "^1.4.0", + "parse-url": "^8.1.0" + } + }, + "git-url-parse": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-13.1.0.tgz", + "integrity": "sha512-5FvPJP/70WkIprlUZ33bm4UAaFdjcLkJLpWft1BeZKqwR0uhhNGoKwlUaPtVb4LxCSQ++erHapRak9kWGj+FCA==", + "requires": { + "git-up": "^7.0.0" + } + }, "github-slugger": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz", @@ -42118,6 +42207,14 @@ "call-bind": "^1.0.2" } }, + "is-ssh": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.0.tgz", + "integrity": "sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==", + "requires": { + "protocols": "^2.0.1" + } + }, "is-stream": { "version": "2.0.1", "dev": true @@ -44622,6 +44719,22 @@ "lines-and-columns": "^1.1.6" } }, + "parse-path": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz", + "integrity": "sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==", + "requires": { + "protocols": "^2.0.0" + } + }, + "parse-url": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-8.1.0.tgz", + "integrity": "sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==", + "requires": { + "parse-path": "^7.0.0" + } + }, "parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", @@ -45222,6 +45335,11 @@ "xtend": "^4.0.0" } }, + "protocols": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.1.tgz", + "integrity": "sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==" + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -46921,6 +47039,11 @@ } } }, + "slugify": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", + "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==" + }, "smartwrap": { "version": "2.0.2", "requires": { diff --git a/packages/synchronizer/README.md b/packages/synchronizer/README.md index e279ba665..1bf34d7c6 100644 --- a/packages/synchronizer/README.md +++ b/packages/synchronizer/README.md @@ -99,7 +99,7 @@ const policy = await authenticator.getPolicy('/home/kubeshope/...'); // By repo data const policy = await authenticator.getPolicy({ - provider: 'github', + provider: 'github.com', remote: 'origin', owner: 'kubeshop', name: 'monokle-core', diff --git a/packages/synchronizer/package.json b/packages/synchronizer/package.json index b449fc844..8d5df18cb 100644 --- a/packages/synchronizer/package.json +++ b/packages/synchronizer/package.json @@ -41,15 +41,18 @@ "dependencies": { "@monokle/validation": "*", "env-paths": "^3.0.0", + "git-url-parse": "^13.1.0", "mkdirp": "^3.0.1", "node-fetch": "^3.3.2", "normalize-url": "^8.0.0", "openid-client": "^5.4.3", "simple-git": "^3.19.1", + "slugify": "^1.6.6", "yaml": "^2.3.1" }, "devDependencies": { "@types/chai": "^4.3.5", + "@types/git-url-parse": "^9.0.1", "@types/mocha": "^10.0.1", "@types/sinon": "^10.0.16", "c8": "^8.0.1", diff --git a/packages/synchronizer/src/__tests__/fixtures/github-kubeshop-monokle-core.policy.yaml b/packages/synchronizer/src/__tests__/fixtures/githubcom-kubeshop-monokle-core.policy.yaml similarity index 100% rename from packages/synchronizer/src/__tests__/fixtures/github-kubeshop-monokle-core.policy.yaml rename to packages/synchronizer/src/__tests__/fixtures/githubcom-kubeshop-monokle-core.policy.yaml diff --git a/packages/synchronizer/src/__tests__/synchronizer.spec.ts b/packages/synchronizer/src/__tests__/synchronizer.spec.ts index 354b4147b..8d8a4554b 100644 --- a/packages/synchronizer/src/__tests__/synchronizer.spec.ts +++ b/packages/synchronizer/src/__tests__/synchronizer.spec.ts @@ -42,7 +42,7 @@ describe('Synchronizer Tests', () => { const synchronizer = createDefaultMonokleSynchronizer(new StorageHandlerPolicy(storagePath)); const policy = await synchronizer.getPolicy({ - provider: 'github', + provider: 'github.com', remote: 'origin', owner: 'kubeshop', name: 'monokle-core', @@ -55,7 +55,7 @@ describe('Synchronizer Tests', () => { }); it('returns policy if there is policy file (from path)', async () => { - const storagePath = await createTmpConfigDir('github-kubeshop-monokle-core.policy.yaml'); + const storagePath = await createTmpConfigDir('githubcom-kubeshop-monokle-core.policy.yaml'); const synchronizer = createDefaultMonokleSynchronizer(new StorageHandlerPolicy(storagePath)); const policy = await synchronizer.getPolicy(storagePath); @@ -63,16 +63,16 @@ describe('Synchronizer Tests', () => { assert.isObject(policy); assert.isTrue(policy.valid); assert.isNotEmpty(policy.path); - assert.match(policy.path, /github-kubeshop-monokle-core.policy.yaml$/); + assert.match(policy.path, /githubcom-kubeshop-monokle-core.policy.yaml$/); assert.isNotEmpty(policy.policy); }); it('returns policy if there is policy file (from git data)', async () => { - const storagePath = await createTmpConfigDir('github-kubeshop-monokle-core.policy.yaml'); + const storagePath = await createTmpConfigDir('githubcom-kubeshop-monokle-core.policy.yaml'); const synchronizer = createDefaultMonokleSynchronizer(new StorageHandlerPolicy(storagePath)); const policy = await synchronizer.getPolicy({ - provider: 'github', + provider: 'github.com', remote: 'origin', owner: 'kubeshop', name: 'monokle-core', @@ -81,7 +81,7 @@ describe('Synchronizer Tests', () => { assert.isObject(policy); assert.isTrue(policy.valid); assert.isNotEmpty(policy.path); - assert.match(policy.path, /github-kubeshop-monokle-core.policy.yaml$/); + assert.match(policy.path, /githubcom-kubeshop-monokle-core.policy.yaml$/); assert.isNotEmpty(policy.policy); assert.isNotEmpty(policy.policy.plugins); }); @@ -172,7 +172,7 @@ describe('Synchronizer Tests', () => { stubs.push(queryApiStub); const repoData = { - provider: 'github', + provider: 'github.com', remote: 'origin', owner: 'kubeshop', name: 'monokle-core', @@ -187,7 +187,7 @@ describe('Synchronizer Tests', () => { assert.isObject(newPolicy); assert.isTrue(newPolicy.valid); assert.isNotEmpty(newPolicy.path); - assert.match(newPolicy.path, /github-kubeshop-monokle-core.policy.yaml$/); + assert.match(newPolicy.path, /githubcom-kubeshop-monokle-core.policy.yaml$/); assert.isNotEmpty(newPolicy.policy); assert.isNotEmpty(newPolicy.policy.plugins); assert.isNotEmpty(newPolicy.policy.rules); @@ -271,7 +271,7 @@ describe('Synchronizer Tests', () => { stubs.push(queryApiStub); const repoData = { - provider: 'github', + provider: 'github.com', remote: 'origin', owner: 'kubeshop', name: 'monokle-core', @@ -283,7 +283,7 @@ describe('Synchronizer Tests', () => { assert.isObject(policy); assert.isTrue(policy.valid); assert.isNotEmpty(policy.path); - assert.match(policy.path, /github-kubeshop-monokle-core.policy.yaml$/); + assert.match(policy.path, /githubcom-kubeshop-monokle-core.policy.yaml$/); assert.isNotEmpty(policy.policy); assert.isNotEmpty(policy.policy.plugins); assert.isNotEmpty(policy.policy.rules); diff --git a/packages/synchronizer/src/handlers/gitHandler.ts b/packages/synchronizer/src/handlers/gitHandler.ts index cd2efc535..450625121 100644 --- a/packages/synchronizer/src/handlers/gitHandler.ts +++ b/packages/synchronizer/src/handlers/gitHandler.ts @@ -1,3 +1,4 @@ +import gitUrlParse from 'git-url-parse'; import {simpleGit} from 'simple-git'; import type {RemoteWithRefs} from 'simple-git'; @@ -20,18 +21,18 @@ export class GitHandler { } const url = remote.refs.push; - // With generic git support in Cloud, this should also become generic. The same for 'provider' field. - const match = url.match(/github\.com(\/|:)([^\/]+)\/([^\/]+)\.git/); - if (!match) { + try { + const urlParts = gitUrlParse(url); + + return { + provider: urlParts.source, + remote: remote.name, + owner: urlParts.owner, + name: urlParts.name, + }; + } catch (err: any) { return undefined; } - - return { - provider: 'github', - remote: remote.name, - owner: match[2], - name: match[3], - }; } async isGitRepo(folderPath: string) { diff --git a/packages/synchronizer/src/utils/synchronizer.ts b/packages/synchronizer/src/utils/synchronizer.ts index 5d0cd3508..66247be81 100644 --- a/packages/synchronizer/src/utils/synchronizer.ts +++ b/packages/synchronizer/src/utils/synchronizer.ts @@ -1,3 +1,4 @@ +import slugify from 'slugify'; import {EventEmitter} from 'events'; import {StorageHandlerPolicy} from '../handlers/storageHandlerPolicy.js'; import {ApiHandler} from '../handlers/apiHandler.js'; @@ -155,17 +156,26 @@ export class Synchronizer extends EventEmitter { } private getPolicyPath(repoData: RepoRemoteData) { - const fileName = `${repoData.provider}-${repoData.owner}-${repoData.name}.policy.yaml`; - return this._storageHandler.getStoreDataFilePath(fileName); + return this._storageHandler.getStoreDataFilePath(this.getPolicyFileName(repoData)); } private async storePolicy(policyContent: StoragePolicyFormat, repoData: RepoRemoteData, comment: string) { - const fileName = `${repoData.provider}-${repoData.owner}-${repoData.name}.policy.yaml`; - return this._storageHandler.setStoreData(policyContent, fileName, comment); + return this._storageHandler.setStoreData(policyContent, this.getPolicyFileName(repoData), comment); } private async readPolicy(repoData: RepoRemoteData) { - const fileName = `${repoData.provider}-${repoData.owner}-${repoData.name}.policy.yaml`; - return this._storageHandler.getStoreData(fileName); + return this._storageHandler.getStoreData(this.getPolicyFileName(repoData)); + } + + private getPolicyFileName(repoData: RepoRemoteData) { + const provider = slugify(repoData.provider, { + replacement: '_', + lower: true, + strict: true, + locale: 'en', + trim: true + }); + + return `${provider}-${repoData.owner}-${repoData.name}.policy.yaml`; } }