UNPKG

@atomist/sdm

Version:

Atomist Software Delivery Machine SDK

140 lines (135 loc) 5.79 kB
/* * 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. */ import { RemoteRepoRef } from "@atomist/automation-client/lib/operations/common/RepoId"; import { execPromise } from "@atomist/automation-client/lib/util/child_process"; import { logger } from "@atomist/automation-client/lib/util/logger"; import * as stringify from "json-stringify-safe"; import * as _ from "lodash"; import { minimalClone } from "../../../api-helper/goal/minimalClone"; import { LogSuppressor } from "../../../api-helper/log/logInterpreters"; import { whenPushSatisfies } from "../../../api/dsl/goalDsl"; import { ExecuteGoalResult } from "../../../api/goal/ExecuteGoalResult"; import { ExecuteGoal } from "../../../api/goal/GoalInvocation"; import { goals } from "../../../api/goal/Goals"; import { GoalWithFulfillment } from "../../../api/goal/GoalWithFulfillment"; import { IndependentOfEnvironment } from "../../../api/goal/support/environment"; import { SoftwareDeliveryMachine } from "../../../api/machine/SoftwareDeliveryMachine"; import { pushTest, PushTest, } from "../../../api/mapping/PushTest"; import { isInLocalMode } from "../../../core/machine/modes"; import { KubernetesSyncOptions, SyncRepoRef, } from "../config"; import { k8sErrMsg } from "../support/error"; import { changeResource } from "./change"; import { diffPush } from "./diff"; import { isRemoteRepo } from "./repo"; import { commitTag } from "./tag"; export function isSyncRepoCommit(sdm: SoftwareDeliveryMachine): PushTest | undefined { const syncOptions: KubernetesSyncOptions = _.get(sdm, "configuration.sdm.k8s.options.sync"); if (!syncOptions || !syncOptions.repo) { logger.debug(`SDM configuration contains no sync repo, will not create sync repo push test`); return undefined; } return pushTest("IsSyncRepoCommit", async p => { const repo: SyncRepoRef | RemoteRepoRef = _.get(sdm, "configuration.sdm.k8s.options.sync.repo"); if (!isRemoteRepo(repo) && !isInLocalMode()) { throw new Error(`SyncRepoRef did not get converted to proper RemoteRepoRef at startup: ${stringify(repo)}`); } if (isInLocalMode() || (p.id.providerType === (repo as RemoteRepoRef).providerType) && p.id.owner === repo.owner && p.id.repo === repo.repo && p.id.branch === repo.branch) { const tag = commitTag(sdm.configuration); return p.push.commits.some(c => !c.message.includes(tag)); } return false; }); } /** * Add goals for pushes on the sync repo. */ export function syncGoals(sdm: SoftwareDeliveryMachine): SoftwareDeliveryMachine { const syncRepoPushTest = isSyncRepoCommit(sdm); if (!syncRepoPushTest) { logger.debug(`No sync repo push test, not adding sync repo goals`); return sdm; } const sync = new GoalWithFulfillment({ uniqueName: "sync", environment: IndependentOfEnvironment, displayName: "sync", workingDescription: "Syncing", completedDescription: "Synced", failedDescription: "Sync failed", isolated: true, }).with({ name: "K8sSyncRepo", goalExecutor: K8sSync, logInterpreter: LogSuppressor, }); const syncGoalSet = goals("Sync Kubernetes Resources").plan(sync); sdm.addGoalContributions(whenPushSatisfies(syncRepoPushTest).setGoals(syncGoalSet)); return sdm; } /** * Create resources of added specs, update resources of changed specs, * and remove resources of deleted specs. */ export const K8sSync: ExecuteGoal = async gi => { const push = gi.goalEvent.push; const log = gi.progressLog; const params = { cloneOptions: minimalClone(push), context: gi.context, credentials: gi.credentials, id: gi.id, log, readOnly: true, }; const tag = commitTag(gi.configuration); return gi.configuration.sdm.projectLoader.doWithProject<ExecuteGoalResult>(params, async p => { if (params.cloneOptions.depth > 1) { // work around https://github.com/atomist/sdm/issues/729 try { await execPromise("git", ["fetch", `--depth=${params.cloneOptions.depth}`], { cwd: p.baseDir }); } catch (e) { logger.warn(`Failed to undo shallow clone, proceeding anyway: ${e.message}`); } } const changes = await diffPush(p, push, tag, log); const errs: Error[] = []; for (const change of changes) { const verb = (change.change === "delete") ? "Deleting" : "Applying"; log.write(`${verb} '${change.path}' from commit ${change.sha}`); try { await changeResource(p, change); } catch (e) { e.message = `Failed to ${change.change} '${change.path}' resource for commit ${change.sha}: ${k8sErrMsg(e)}`; log.write(e.message); errs.push(e); } } if (errs.length > 0) { return { code: errs.length, message: errs.map(e => e.message).join("; ") }; } const message = `Changed ${changes.length} resources`; log.write(message); return { code: 0, message }; }); };