"use strict";
/*
 * 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