@atomist/sdm
Version:
Atomist Software Delivery Machine SDK
259 lines • 11.7 kB
JavaScript
;
/*
* Copyright © 2020 Atomist, Inc.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.repoSlug = exports.scmCredentials = exports.repoCredentials = exports.isRemoteRepo = exports.queryForScmProvider = void 0;
const RepoId_1 = require("@atomist/automation-client/lib/operations/common/RepoId");
const GitCommandGitProject_1 = require("@atomist/automation-client/lib/project/git/GitCommandGitProject");
const GraphClient_1 = require("@atomist/automation-client/lib/spi/graph/GraphClient");
const logger_1 = require("@atomist/automation-client/lib/util/logger");
const stringify = require("json-stringify-safe");
const _ = require("lodash");
const types_1 = require("../../../typings/types");
const clone_1 = require("./clone");
const defaultDefaultBranch = "master";
/**
* If called and no sync repo is provided in the SDM configuration, a
* warning is emitted and `undefined` is returned.
*
* If the SDM configuration contains a RemoteRepoRef as the value of
* the `sdm.configuration.sdm.k8s.options.sync.repo` option and truthy
* credentials, those are returned.
*
* Otherwise, the code cycles through all workspaces, querying cortex
* for the information it needs. If the value of the `sync.repo`
* option is a [[SyncRepoRef]], each workspace is queried for a repo
* matching the provided sync repo. If none is found, it cycles
* through all the workspaces again querying for all SCM providers and
* try to clone the repo with the provided credentials or, if no
* credentials are provided, the SCM provider credentials from cortex.
* Once a repo is found using either method, an object is returned
* with its remote repo ref and the credentials able to clone it. In
* addition, the `sdm` passed in will have its
* `sdm.configuration.sdm.k8s.options.sync.repo`
* and`sdm.configuration.sdm.k8s.options.sync.credentials` updated
* with the objects appropriate objects.
*
* @param sdm this SDM object (modified if repo credentials found)
* @param repoRef repository to look for
* @return true if sync options set and repo found or false and sync options deleted
*/
async function queryForScmProvider(configuration) {
var _a, _b, _c;
const syncOptions = (_c = (_b = (_a = configuration.sdm) === null || _a === void 0 ? void 0 : _a.k8s) === null || _b === void 0 ? void 0 : _b.options) === null || _c === void 0 ? void 0 : _c.sync;
if (!syncOptions) {
logger_1.logger.debug(`SDM configuration contains no sync repo`);
return false;
}
const repoRef = syncOptions.repo;
if (!repoRef || !repoRef.owner || !repoRef.repo) {
logger_1.logger.error(`Provided sync repo does not contain all required properties: ${stringify(repoRef)}`);
return false;
}
const repoProvided = isRemoteRepo(repoRef);
const credsProvided = !!syncOptions.credentials;
if (repoProvided && credsProvided) {
logger_1.logger.info(`Using provided remote repo ref and credentials for sync repo`);
return true;
}
const repoCreds = await queryRepo(configuration) || await queryScm(configuration);
if (repoCreds) {
if (!repoProvided) {
configuration.sdm.k8s.options.sync.repo = repoCreds.repo;
}
if (!credsProvided) {
configuration.sdm.k8s.options.sync.credentials = repoCreds.credentials;
}
return true;
}
logger_1.logger.warn(`Failed to find sync repo: ${stringify(repoRef)}`);
return false;
}
exports.queryForScmProvider = queryForScmProvider;
/**
* See if provided sync repo is a RemoteRepoRef.
*/
function isRemoteRepo(repo) {
return !!repo && RepoId_1.isRemoteRepoRef(repo);
}
exports.isRemoteRepo = isRemoteRepo;
/**
* Query cortex across all available workspaces for repo.
*/
async function queryRepo(configuration) {
const repoRef = syncRepoRef(configuration);
const slug = repoSlug(repoRef);
const repoProviderId = repoRef.providerId;
for (const workspaceId of configuration.workspaceIds) {
const graphClient = configuration.graphql.client.factory.create(workspaceId, configuration);
logger_1.logger.debug(`Querying workspace ${workspaceId} for repo ${slug}`);
const repos = await graphClient.query({
name: "RepoScmProvider",
variables: { repo: repoRef.repo, owner: repoRef.owner },
options: GraphClient_1.QueryNoCacheOptions,
});
if (!repos || !repos.Repo || repos.Repo.length < 1) {
logger_1.logger.debug(`Repo ${slug} not found in workspace ${workspaceId}`);
continue;
}
let searchRepos = repos.Repo;
if (repoProviderId) {
logger_1.logger.debug(`Filtering repos from workspace ${workspaceId} on providerId '${repoProviderId}'`);
searchRepos = searchRepos.filter(r => r.org.scmProvider.providerId === repoProviderId);
}
if (searchRepos.length > 1) {
logger_1.logger.warn(`More than one repo found in workspace ${workspaceId} with owner/repo ${slug}`);
}
for (const repo of searchRepos) {
const rc = await repoCredentials(configuration, repo, workspaceId);
if (rc) {
rc.repo.branch = rc.repo.branch || repo.defaultBranch || defaultDefaultBranch;
logger_1.logger.info(`Returning first ${slug} repo with valid SCM provider`);
return rc;
}
}
}
return undefined;
}
/**
* For each SDM provider in cortex in each workspace, try to clone the
* sync repo. Return the information for the first successful clone.
*/
async function queryScm(configuration) {
const repoRef = syncRepoRef(configuration);
const slug = repoSlug(repoRef);
const repoProviderId = repoRef.providerId;
for (const workspaceId of configuration.workspaceIds) {
const graphClient = configuration.graphql.client.factory.create(workspaceId, configuration);
logger_1.logger.debug(`Querying workspace ${workspaceId} for SCM providers`);
const providers = await graphClient.query({
name: "ScmProviders",
options: GraphClient_1.QueryNoCacheOptions,
});
if (!providers || !providers.SCMProvider || providers.SCMProvider.length < 1) {
logger_1.logger.debug(`Found no SCM providers in workspace ${workspaceId}`);
continue;
}
for (const provider of providers.SCMProvider) {
if (repoProviderId && provider.providerId !== repoProviderId) {
logger_1.logger.debug(`SCM provider '${provider.providerId}' does not match '${repoProviderId}'`);
continue;
}
const rc = await scmCredentials(configuration, provider, workspaceId);
if (rc) {
logger_1.logger.debug(`Attempting to clone ${slug} using ${rc.repo.cloneUrl}`);
try {
const p = await GitCommandGitProject_1.GitCommandGitProject.cloned(rc.credentials, rc.repo, clone_1.defaultCloneOptions);
if (p) {
rc.repo.branch = rc.repo.branch || p.branch || defaultDefaultBranch;
return rc;
}
}
catch (e) {
logger_1.logger.debug(`Failed to clone ${slug} from ${rc.repo.cloneUrl}: ${e.message}`);
}
}
}
}
return undefined;
}
/**
* Create RemoteRepoRef and Credentials object from SDM and repo from
* cortex. If the provided repo does not contain an org with a
* provider, it returns `undefined`. Otherwise it uses the SCM
* provider to call [[scmCredentials]] and return its value.
*/
async function repoCredentials(configuration, repo, workspaceId) {
if (repo.org && repo.org.scmProvider) {
return scmCredentials(configuration, repo.org.scmProvider, workspaceId);
}
return undefined;
}
exports.repoCredentials = repoCredentials;
/**
* Given the SDM and an SCM, use the configured repo ref resolver to
* create a RemoteRepoRef. Use the SDM Kubernetes option sync
* credentials or SCM `credential.secret` to create the credentials,
* giving the SDM sync credentials preference. Return `undefined` if
* there is not enough information to created the repo credential
* object.
*/
async function scmCredentials(configuration, scm, workspaceId) {
var _a, _b, _c, _d, _e;
const repoRef = syncRepoRef(configuration);
const credentials = (_d = (_c = (_b = (_a = configuration.sdm) === null || _a === void 0 ? void 0 : _a.k8s) === null || _b === void 0 ? void 0 : _b.options) === null || _c === void 0 ? void 0 : _c.sync) === null || _d === void 0 ? void 0 : _d.credentials;
let secret = (_e = scm === null || scm === void 0 ? void 0 : scm.credential) === null || _e === void 0 ? void 0 : _e.secret;
if (!credentials && !secret && scm.providerType === types_1.ProviderType.github_com) {
const graphClient = configuration.graphql.client.factory.create(workspaceId, configuration);
const app = await graphClient.query({
name: "GitHubAppInstallationByOwner",
variables: {
name: repoRef.owner,
},
});
secret = _.get(app, "GitHubAppInstallation[0].token.secret");
}
if (repoRef && repoRef.owner && repoRef.repo && scm.apiUrl && (credentials || secret)) {
const repoResolver = configuration.sdm.repoRefResolver;
const repoFrag = {
owner: repoRef.owner,
name: repoRef.repo,
org: {
owner: repoRef.owner,
provider: {
providerId: scm.providerId,
providerType: scm.providerType,
apiUrl: scm.apiUrl,
url: scm.url,
},
},
};
const options = {
branch: repoRef.branch,
};
try {
const repo = repoResolver.toRemoteRepoRef(repoFrag, options);
return {
credentials: credentials || { token: secret },
repo,
};
}
catch (e) {
logger_1.logger.warn(`Failed to resolve remote repo ref for ${repoFrag.owner}/${repoFrag.name}: ${e.message}`);
}
}
return undefined;
}
exports.scmCredentials = scmCredentials;
/** Create repo slug string. */
function repoSlug(repo) {
return `${repo.owner}/${repo.repo}`;
}
exports.repoSlug = repoSlug;
/**
* Extract the Kubernetes option sync repo from the SDM configuration.
* This function should only be called if the sync repo object is
* defined.
*/
function syncRepoRef(configuration) {
var _a, _b, _c, _d;
const repo = (_d = (_c = (_b = (_a = configuration.sdm) === null || _a === void 0 ? void 0 : _a.k8s) === null || _b === void 0 ? void 0 : _b.options) === null || _c === void 0 ? void 0 : _c.sync) === null || _d === void 0 ? void 0 : _d.repo;
if (!repo) {
throw new Error(`Failed to get sync repo from SDM configuration`);
}
return repo;
}
//# sourceMappingURL=repo.js.map