Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit fa10336

Browse files
Matt Calhounmcalhoun
andauthored
fix bug with checksums causing planfile not to be written (cloudposse#13)
Co-authored-by: mcalhoun <mcalhoun@users.noreply.github.com>
1 parent bf8c7f3 commit fa10336

File tree

12 files changed

+142
-82
lines changed

12 files changed

+142
-82
lines changed

.github/workflows/build-and-test.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ jobs:
6161
aws-region: ${{ env.AWS_REGION }}
6262
role-session-name: "atmos-terraform-plan-gitops"
6363

64+
- name: Calculate SHA256 of planfile before storage
65+
id: before-store-checksum
66+
run: |
67+
checksum=$(shasum -a 256 src/__fixtures__/mock.planfile | cut -d ' ' -f 1)
68+
echo "$checksum"
69+
echo "checksum=$checksum" >>$GITHUB_OUTPUT
70+
6471
- uses: ./
6572
id: store-plan
6673
with:
@@ -84,3 +91,17 @@ jobs:
8491
bucketName: ${{ env.ATMOS_PLAN_S3_BUCKET }}
8592
env:
8693
GITHUB_TOKEN: ${{ secrets.PUBLIC_REPO_ACCESS_TOKEN }}
94+
95+
- name: Calculate SHA256 of planfile after retrieval
96+
id: after-get-checksum
97+
run: |
98+
checksum=$(shasum -a 256 ./test.planfile | cut -d ' ' -f 1)
99+
echo "$checksum"
100+
echo "checksum=$checksum" >>$GITHUB_OUTPUT
101+
102+
- name: Compare Before and After Checksums
103+
run: |
104+
if [ "${{ steps.before-store-checksum.outputs.checksum }}" != "${{ steps.after-get-checksum.outputs.checksum }}" ]; then
105+
echo "Checksums do not match"
106+
exit 1
107+
fi

dist/index.js

Lines changed: 59 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -60421,12 +60421,16 @@ exports.taintPlan = taintPlan;
6042160421
"use strict";
6042260422

6042360423
Object.defineProperty(exports, "__esModule", ({ value: true }));
60424-
exports.calculateHash = void 0;
60424+
exports.calculateHashFromBuffer = exports.calculateHash = void 0;
6042560425
const node_crypto_1 = __nccwpck_require__(6005);
6042660426
const calculateHash = (plan) => {
6042760427
return (0, node_crypto_1.createHash)("sha256").update(plan).digest("hex");
6042860428
};
6042960429
exports.calculateHash = calculateHash;
60430+
const calculateHashFromBuffer = (plan) => {
60431+
return (0, node_crypto_1.createHash)("sha256").update(plan).digest("hex");
60432+
};
60433+
exports.calculateHashFromBuffer = calculateHashFromBuffer;
6043060434

6043160435

6043260436
/***/ }),
@@ -61113,7 +61117,32 @@ __exportStar(__nccwpck_require__(51066), exports);
6111361117

6111461118
/***/ }),
6111561119

61116-
/***/ 90320:
61120+
/***/ 24748:
61121+
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
61122+
61123+
"use strict";
61124+
61125+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
61126+
if (k2 === undefined) k2 = k;
61127+
var desc = Object.getOwnPropertyDescriptor(m, k);
61128+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
61129+
desc = { enumerable: true, get: function() { return m[k]; } };
61130+
}
61131+
Object.defineProperty(o, k2, desc);
61132+
}) : (function(o, m, k, k2) {
61133+
if (k2 === undefined) k2 = k;
61134+
o[k2] = m[k];
61135+
}));
61136+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
61137+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
61138+
};
61139+
Object.defineProperty(exports, "__esModule", ({ value: true }));
61140+
__exportStar(__nccwpck_require__(86325), exports);
61141+
61142+
61143+
/***/ }),
61144+
61145+
/***/ 86325:
6111761146
/***/ (function(__unused_webpack_module, exports) {
6111861147

6111961148
"use strict";
@@ -61135,16 +61164,16 @@ var __asyncValues = (this && this.__asyncValues) || function (o) {
6113561164
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
6113661165
};
6113761166
Object.defineProperty(exports, "__esModule", ({ value: true }));
61138-
exports.bufferFromReadable = void 0;
61139-
const bufferFromReadable = (readable) => { var _a, readable_1, readable_1_1; return __awaiter(void 0, void 0, void 0, function* () {
61167+
exports.stringFromReadable = void 0;
61168+
const stringFromReadable = (readable) => { var _a, readable_1, readable_1_1; return __awaiter(void 0, void 0, void 0, function* () {
6114061169
var _b, e_1, _c, _d;
61141-
const buffers = [];
61170+
const chunks = [];
6114261171
try {
6114361172
for (_a = true, readable_1 = __asyncValues(readable); readable_1_1 = yield readable_1.next(), _b = readable_1_1.done, !_b; _a = true) {
6114461173
_d = readable_1_1.value;
6114561174
_a = false;
61146-
const data = _d;
61147-
buffers.push(data);
61175+
const chunk = _d;
61176+
chunks.push(Buffer.from(chunk));
6114861177
}
6114961178
}
6115061179
catch (e_1_1) { e_1 = { error: e_1_1 }; }
@@ -61154,35 +61183,9 @@ const bufferFromReadable = (readable) => { var _a, readable_1, readable_1_1; ret
6115461183
}
6115561184
finally { if (e_1) throw e_1.error; }
6115661185
}
61157-
const finalBuffer = Buffer.concat(buffers);
61158-
return finalBuffer;
61186+
return Buffer.concat(chunks).toString("utf-8");
6115961187
}); };
61160-
exports.bufferFromReadable = bufferFromReadable;
61161-
61162-
61163-
/***/ }),
61164-
61165-
/***/ 24748:
61166-
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
61167-
61168-
"use strict";
61169-
61170-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
61171-
if (k2 === undefined) k2 = k;
61172-
var desc = Object.getOwnPropertyDescriptor(m, k);
61173-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
61174-
desc = { enumerable: true, get: function() { return m[k]; } };
61175-
}
61176-
Object.defineProperty(o, k2, desc);
61177-
}) : (function(o, m, k, k2) {
61178-
if (k2 === undefined) k2 = k;
61179-
o[k2] = m[k];
61180-
}));
61181-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
61182-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
61183-
};
61184-
Object.defineProperty(exports, "__esModule", ({ value: true }));
61185-
__exportStar(__nccwpck_require__(90320), exports);
61188+
exports.stringFromReadable = stringFromReadable;
6118661189

6118761190

6118861191
/***/ }),
@@ -61394,7 +61397,7 @@ const readFile = (path) => {
6139461397
exports.readFile = readFile;
6139561398
const writeFile = (path, contents) => {
6139661399
const outputStream = (0, fs_1.createWriteStream)(path);
61397-
contents.pipe(outputStream);
61400+
contents.pipe(outputStream, { end: true });
6139861401
};
6139961402
exports.writeFile = writeFile;
6140061403

@@ -61519,7 +61522,7 @@ class TerraformPlan extends domain_1.AggregateRoot {
6151961522
if (guardResult.isFailure) {
6152061523
return infrastructure_1.Result.fail(guardResult.getErrorValue());
6152161524
}
61522-
const defaultValues = Object.assign(Object.assign({}, props), { contentsHash: (0, crypto_1.calculateHash)(props.contents), createdAt: props.createdAt ? props.createdAt : new Date(), tainted: props.tainted ? props.tainted : false });
61525+
const defaultValues = Object.assign(Object.assign({}, props), { contentsHash: props.contentsHash || (0, crypto_1.calculateHashFromBuffer)(props.contents), createdAt: props.createdAt ? props.createdAt : new Date(), tainted: props.tainted ? props.tainted : false });
6152361526
const terraformPlan = new TerraformPlan(defaultValues, id);
6152461527
return infrastructure_1.Result.ok(terraformPlan);
6152561528
}
@@ -61866,6 +61869,7 @@ class TerraformPlanDynamoDBMapper extends mapper_1.Mapper {
6186661869
tainted: raw.tainted,
6186761870
createdAt: new Date(raw.createdAt),
6186861871
contents: Buffer.from(""),
61872+
contentsHash: raw.contentsHash,
6186961873
}, new lib_1.UniqueEntityId(raw.id));
6187061874
if (planOrError.isFailure) {
6187161875
throw new Error("Error converting DynamoDB item to domain");
@@ -62294,6 +62298,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
6229462298
Object.defineProperty(exports, "__esModule", ({ value: true }));
6229562299
exports.GetTerraformPlanUseCase = void 0;
6229662300
const fs_1 = __nccwpck_require__(57147);
62301+
const stream_1 = __nccwpck_require__(12781);
6229762302
const crypto_1 = __nccwpck_require__(65788);
6229862303
const infrastructure_1 = __nccwpck_require__(73950);
6229962304
const readable_1 = __nccwpck_require__(24748);
@@ -62305,6 +62310,7 @@ const writePlanFile = (pathToPlan, contents) => __awaiter(void 0, void 0, void 0
6230562310
return (0, infrastructure_1.left)(new errors_1.GetTerraformPlanErrors.PlanAlreadyExistsError(pathToPlan));
6230662311
}
6230762312
(0, system_1.writeFile)(pathToPlan, contents);
62313+
return (0, infrastructure_1.right)(infrastructure_1.Result.ok());
6230862314
});
6230962315
class GetTerraformPlanUseCase {
6231062316
constructor(metaDataRepository, planRepository, codeRepository) {
@@ -62313,31 +62319,39 @@ class GetTerraformPlanUseCase {
6231362319
this.codeRepository = codeRepository;
6231462320
}
6231562321
execute(req) {
62322+
var _a, _b;
6231662323
return __awaiter(this, void 0, void 0, function* () {
6231762324
try {
6231862325
const { commitSHA, component, isMergeCommit, stack, planPath, pr, repoName, repoOwner, } = req;
6231962326
let plan;
62327+
let metadata;
6232062328
if (isMergeCommit) {
6232162329
if (!pr) {
6232262330
return (0, infrastructure_1.left)(new infrastructure_1.AppError.UnexpectedError("PR is required for merge commits"));
6232362331
}
62324-
const metadata = yield this.metaDataRepository.loadLatestForPR(repoOwner, repoName, component, stack, pr);
62332+
metadata = yield this.metaDataRepository.loadLatestForPR(repoOwner, repoName, component, stack, pr);
6232562333
plan = yield this.planRepository.load(repoOwner, repoName, component, stack, metadata.commitSHA);
6232662334
}
6232762335
else {
6232862336
// Non-merge commit, we're on the feature branch
6232962337
if (!commitSHA) {
6233062338
return (0, infrastructure_1.left)(new infrastructure_1.AppError.UnexpectedError("Commit is required for non-merge commits"));
6233162339
}
62332-
const metadata = yield this.metaDataRepository.loadByCommit(repoOwner, repoName, component, stack, commitSHA);
62340+
metadata = yield this.metaDataRepository.loadByCommit(repoOwner, repoName, component, stack, commitSHA);
6233362341
plan = yield this.planRepository.load(repoOwner, repoName, component, stack, metadata.commitSHA);
62334-
const planBuffer = yield (0, readable_1.bufferFromReadable)(plan);
62335-
const hash = yield (0, crypto_1.calculateHash)(planBuffer);
62336-
if (metadata.contentsHash === hash) {
62337-
return (0, infrastructure_1.left)(new errors_1.GetTerraformPlanErrors.ContentsHashMismatch(metadata.contentsHash, hash));
62338-
}
6233962342
}
62340-
yield writePlanFile(planPath, plan);
62343+
const contents = yield (0, readable_1.stringFromReadable)(plan);
62344+
const hash = yield (0, crypto_1.calculateHash)(contents);
62345+
if (metadata.contentsHash != hash) {
62346+
return (0, infrastructure_1.left)(new errors_1.GetTerraformPlanErrors.ContentsHashMismatch((_b = (_a = metadata.contentsHash) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : "", hash));
62347+
}
62348+
const contentsReadable = new stream_1.Readable();
62349+
contentsReadable.push(contents);
62350+
contentsReadable.push(null);
62351+
const result = yield writePlanFile(planPath, contentsReadable);
62352+
if (result.isLeft()) {
62353+
return (0, infrastructure_1.left)(result.value);
62354+
}
6234162355
return (0, infrastructure_1.right)(infrastructure_1.Result.ok());
6234262356
}
6234362357
catch (err) {

dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/lib/crypto/crypto.test.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
1-
import { calculateHash } from "./crypto";
1+
import { calculateHash, calculateHashFromBuffer } from "./crypto";
22

33
describe("crypto", () => {
4-
describe("createHash", () => {
4+
describe("calculateHash", () => {
55
it("should calculate hash", () => {
6-
const hash = calculateHash(Buffer.from("hello"));
6+
const hash = calculateHash("hello");
7+
expect(hash).toEqual(
8+
"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
9+
);
10+
});
11+
});
12+
13+
describe("calculateHashFromBuffer", () => {
14+
it("should calculate hash", () => {
15+
const hash = calculateHashFromBuffer(Buffer.from("hello"));
716
expect(hash).toEqual(
817
"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
918
);

src/lib/crypto/crypto.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { createHash } from "node:crypto";
22

3-
export const calculateHash = (plan: Buffer) => {
3+
export const calculateHash = (plan: string) => {
4+
return createHash("sha256").update(plan).digest("hex");
5+
};
6+
7+
export const calculateHashFromBuffer = (plan: Buffer) => {
48
return createHash("sha256").update(plan).digest("hex");
59
};

src/lib/readable/bufferFromReadable.ts

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/lib/readable/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export * from "./bufferFromReadable";
1+
export * from "./stringFromReadable";
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Readable } from "stream";
2+
3+
export const stringFromReadable = async (
4+
readable: Readable
5+
): Promise<string> => {
6+
const chunks = [];
7+
8+
for await (const chunk of readable) {
9+
chunks.push(Buffer.from(chunk));
10+
}
11+
12+
return Buffer.concat(chunks).toString("utf-8");
13+
};

src/lib/system.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ export const readFile = (path: string): Buffer => {
77

88
export const writeFile = (path: string, contents: Readable): void => {
99
const outputStream = createWriteStream(path);
10-
contents.pipe(outputStream);
10+
contents.pipe(outputStream, { end: true });
1111
};

src/modules/terraformPlan/domain/TerraformPlan.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { calculateHash } from "@lib/crypto";
1+
import { calculateHash, calculateHashFromBuffer } from "@lib/crypto";
22
import { AggregateRoot, UniqueEntityId } from "@lib/domain";
33
import { Guard, IGuardArgument, Result } from "@lib/infrastructure";
44

@@ -69,7 +69,8 @@ export class TerraformPlan extends AggregateRoot<TerraformPlanProps> {
6969

7070
const defaultValues: TerraformPlanProps = {
7171
...props,
72-
contentsHash: calculateHash(props.contents),
72+
contentsHash:
73+
props.contentsHash || calculateHashFromBuffer(props.contents),
7374
createdAt: props.createdAt ? props.createdAt : new Date(),
7475
tainted: props.tainted ? props.tainted : false,
7576
};

0 commit comments

Comments
 (0)