diff --git a/.changeset/nine-forks-sleep.md b/.changeset/nine-forks-sleep.md new file mode 100644 index 0000000000..7edbca2169 --- /dev/null +++ b/.changeset/nine-forks-sleep.md @@ -0,0 +1,5 @@ +--- +'@backstage/integration': minor +--- + +Add the basics of cross-integration concerns diff --git a/packages/integration/src/ScmIntegrationsImpl.ts b/packages/integration/src/ScmIntegrationsImpl.ts new file mode 100644 index 0000000000..ef6a6835b9 --- /dev/null +++ b/packages/integration/src/ScmIntegrationsImpl.ts @@ -0,0 +1,51 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Config } from '@backstage/config'; +import { AzureIntegration } from './azure/AzureIntegration'; +import { BitbucketIntegration } from './bitbucket/BitbucketIntegration'; +import { GitHubIntegration } from './github/GitHubIntegration'; +import { GitLabIntegration } from './gitlab/GitLabIntegration'; +import { + ScmIntegration, + ScmIntegrationPredicateTuple, + ScmIntegrations, +} from './types'; + +export class ScmIntegrationsImpl implements ScmIntegrations { + static fromConfig(config: Config): ScmIntegrationsImpl { + return new ScmIntegrationsImpl([ + ...AzureIntegration.factory({ config }), + ...BitbucketIntegration.factory({ config }), + ...GitHubIntegration.factory({ config }), + ...GitLabIntegration.factory({ config }), + ]); + } + + constructor(private readonly integrations: ScmIntegrationPredicateTuple[]) {} + + list(): ScmIntegration[] { + return this.integrations.map(i => i.integration); + } + + byName(name: string): ScmIntegration | undefined { + return this.integrations.map(i => i.integration).find(i => i.name === name); + } + + byUrl(url: string): ScmIntegration | undefined { + return this.integrations.find(i => i.predicate(new URL(url)))?.integration; + } +} diff --git a/packages/integration/src/azure/AzureIntegration.test.ts b/packages/integration/src/azure/AzureIntegration.test.ts new file mode 100644 index 0000000000..d0d3cf65ad --- /dev/null +++ b/packages/integration/src/azure/AzureIntegration.test.ts @@ -0,0 +1,48 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ConfigReader } from '@backstage/config'; +import { AzureIntegration } from './AzureIntegration'; + +describe('AzureIntegration', () => { + it('has a working factory', () => { + const integrations = AzureIntegration.factory({ + config: ConfigReader.fromConfigs([ + { + context: '', + data: { + integrations: { + azure: [ + { + host: 'h.com', + token: 'token', + }, + ], + }, + }, + }, + ]), + }); + expect(integrations.length).toBe(2); // including default + expect(integrations[0].predicate(new URL('https://h.com/a'))).toBe(true); + }); + + it('returns the basics', () => { + const integration = new AzureIntegration({ host: 'h.com' } as any); + expect(integration.type).toBe('azure'); + expect(integration.name).toBe('h.com'); + }); +}); diff --git a/packages/integration/src/azure/AzureIntegration.ts b/packages/integration/src/azure/AzureIntegration.ts new file mode 100644 index 0000000000..2e1e4d9795 --- /dev/null +++ b/packages/integration/src/azure/AzureIntegration.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ScmIntegration, ScmIntegrationFactory } from '../types'; +import { AzureIntegrationConfig, readAzureIntegrationConfigs } from './config'; + +export class AzureIntegration implements ScmIntegration { + static factory: ScmIntegrationFactory = ({ config }) => { + const configs = readAzureIntegrationConfigs( + config.getOptionalConfigArray('integrations.azure') ?? [], + ); + return configs.map(integration => ({ + predicate: (url: URL) => url.host === integration.host, + integration: new AzureIntegration(integration), + })); + }; + + constructor(private readonly config: AzureIntegrationConfig) {} + + get type(): string { + return 'azure'; + } + + get name(): string { + return this.config.host; + } +} diff --git a/packages/integration/src/bitbucket/BitbucketIntegration.test.ts b/packages/integration/src/bitbucket/BitbucketIntegration.test.ts new file mode 100644 index 0000000000..9678b35014 --- /dev/null +++ b/packages/integration/src/bitbucket/BitbucketIntegration.test.ts @@ -0,0 +1,51 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ConfigReader } from '@backstage/config'; +import { BitbucketIntegration } from './BitbucketIntegration'; + +describe('BitbucketIntegration', () => { + it('has a working factory', () => { + const integrations = BitbucketIntegration.factory({ + config: ConfigReader.fromConfigs([ + { + context: '', + data: { + integrations: { + bitbucket: [ + { + host: 'h.com', + apiBaseUrl: 'a', + token: 't', + username: 'u', + appPassword: 'p', + }, + ], + }, + }, + }, + ]), + }); + expect(integrations.length).toBe(2); // including default + expect(integrations[0].predicate(new URL('https://h.com/a'))).toBe(true); + }); + + it('returns the basics', () => { + const integration = new BitbucketIntegration({ host: 'h.com' } as any); + expect(integration.type).toBe('bitbucket'); + expect(integration.name).toBe('h.com'); + }); +}); diff --git a/packages/integration/src/bitbucket/BitbucketIntegration.ts b/packages/integration/src/bitbucket/BitbucketIntegration.ts new file mode 100644 index 0000000000..e06b539611 --- /dev/null +++ b/packages/integration/src/bitbucket/BitbucketIntegration.ts @@ -0,0 +1,43 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ScmIntegration, ScmIntegrationFactory } from '../types'; +import { + BitbucketIntegrationConfig, + readBitbucketIntegrationConfigs, +} from './config'; + +export class BitbucketIntegration implements ScmIntegration { + static factory: ScmIntegrationFactory = ({ config }) => { + const configs = readBitbucketIntegrationConfigs( + config.getOptionalConfigArray('integrations.bitbucket') ?? [], + ); + return configs.map(integration => ({ + predicate: (url: URL) => url.host === integration.host, + integration: new BitbucketIntegration(integration), + })); + }; + + constructor(private readonly config: BitbucketIntegrationConfig) {} + + get type(): string { + return 'bitbucket'; + } + + get name(): string { + return this.config.host; + } +} diff --git a/packages/integration/src/github/GitHubIntegration.test.ts b/packages/integration/src/github/GitHubIntegration.test.ts new file mode 100644 index 0000000000..5a8800200f --- /dev/null +++ b/packages/integration/src/github/GitHubIntegration.test.ts @@ -0,0 +1,50 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ConfigReader } from '@backstage/config'; +import { GitHubIntegration } from './GitHubIntegration'; + +describe('GitHubIntegration', () => { + it('has a working factory', () => { + const integrations = GitHubIntegration.factory({ + config: ConfigReader.fromConfigs([ + { + context: '', + data: { + integrations: { + github: [ + { + host: 'h.com', + apiBaseUrl: 'a', + rawBaseUrl: 'r', + token: 't', + }, + ], + }, + }, + }, + ]), + }); + expect(integrations.length).toBe(2); // including default + expect(integrations[0].predicate(new URL('https://h.com/a'))).toBe(true); + }); + + it('returns the basics', () => { + const integration = new GitHubIntegration({ host: 'h.com' } as any); + expect(integration.type).toBe('github'); + expect(integration.name).toBe('h.com'); + }); +}); diff --git a/packages/integration/src/github/GitHubIntegration.ts b/packages/integration/src/github/GitHubIntegration.ts new file mode 100644 index 0000000000..f70684975d --- /dev/null +++ b/packages/integration/src/github/GitHubIntegration.ts @@ -0,0 +1,43 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ScmIntegration, ScmIntegrationFactory } from '../types'; +import { + GitHubIntegrationConfig, + readGitHubIntegrationConfigs, +} from './config'; + +export class GitHubIntegration implements ScmIntegration { + static factory: ScmIntegrationFactory = ({ config }) => { + const configs = readGitHubIntegrationConfigs( + config.getOptionalConfigArray('integrations.github') ?? [], + ); + return configs.map(integration => ({ + predicate: (url: URL) => url.host === integration.host, + integration: new GitHubIntegration(integration), + })); + }; + + constructor(private readonly config: GitHubIntegrationConfig) {} + + get type(): string { + return 'github'; + } + + get name(): string { + return this.config.host; + } +} diff --git a/packages/integration/src/gitlab/GitLabIntegration.test.ts b/packages/integration/src/gitlab/GitLabIntegration.test.ts new file mode 100644 index 0000000000..5c46610c53 --- /dev/null +++ b/packages/integration/src/gitlab/GitLabIntegration.test.ts @@ -0,0 +1,48 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ConfigReader } from '@backstage/config'; +import { GitLabIntegration } from './GitLabIntegration'; + +describe('GitLabIntegration', () => { + it('has a working factory', () => { + const integrations = GitLabIntegration.factory({ + config: ConfigReader.fromConfigs([ + { + context: '', + data: { + integrations: { + gitlab: [ + { + host: 'h.com', + token: 't', + }, + ], + }, + }, + }, + ]), + }); + expect(integrations.length).toBe(2); // including default + expect(integrations[0].predicate(new URL('https://h.com/a'))).toBe(true); + }); + + it('returns the basics', () => { + const integration = new GitLabIntegration({ host: 'h.com' } as any); + expect(integration.type).toBe('gitlab'); + expect(integration.name).toBe('h.com'); + }); +}); diff --git a/packages/integration/src/gitlab/GitLabIntegration.ts b/packages/integration/src/gitlab/GitLabIntegration.ts new file mode 100644 index 0000000000..6e94e084cc --- /dev/null +++ b/packages/integration/src/gitlab/GitLabIntegration.ts @@ -0,0 +1,43 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ScmIntegration, ScmIntegrationFactory } from '../types'; +import { + GitLabIntegrationConfig, + readGitLabIntegrationConfigs, +} from './config'; + +export class GitLabIntegration implements ScmIntegration { + static factory: ScmIntegrationFactory = ({ config }) => { + const configs = readGitLabIntegrationConfigs( + config.getOptionalConfigArray('integrations.gitlab') ?? [], + ); + return configs.map(integration => ({ + predicate: (url: URL) => url.host === integration.host, + integration: new GitLabIntegration(integration), + })); + }; + + constructor(private readonly config: GitLabIntegrationConfig) {} + + get type(): string { + return 'gitlab'; + } + + get name(): string { + return this.config.host; + } +} diff --git a/packages/integration/src/index.ts b/packages/integration/src/index.ts index bfed81824f..9d42914fdb 100644 --- a/packages/integration/src/index.ts +++ b/packages/integration/src/index.ts @@ -18,3 +18,4 @@ export * from './azure'; export * from './bitbucket'; export * from './github'; export * from './gitlab'; +export type { ScmIntegration, ScmIntegrations } from './types'; diff --git a/packages/integration/src/types.ts b/packages/integration/src/types.ts new file mode 100644 index 0000000000..6f9290a948 --- /dev/null +++ b/packages/integration/src/types.ts @@ -0,0 +1,58 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Config } from '@backstage/config'; + +/** + * Encapsulates a single SCM integration. + */ +export type ScmIntegration = { + type: string; + name: string; +}; + +/** + * Holds all registered SCM integrations. + */ +export type ScmIntegrations = { + /** + * Lists all registered integrations. + */ + list(): ScmIntegration[]; + + /** + * Fetches a named integration + * + * @param name The name of a registered integration + */ + byName(name: string): ScmIntegration | undefined; + + /** + * Fetches an integration by URL + * + * @param name A URL that matches a registered integration + */ + byUrl(url: string): ScmIntegration | undefined; +}; + +export type ScmIntegrationPredicateTuple = { + predicate: (url: URL) => boolean; + integration: ScmIntegration; +}; + +export type ScmIntegrationFactory = (options: { + config: Config; +}) => ScmIntegrationPredicateTuple[];