Last active
June 19, 2021 13:27
-
-
Save williammartin/6b1c46711dd484042b4441a284a9b22a to your computer and use it in GitHub Desktop.
Empty GCP bucket
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as core from '@actions/core'; | |
import { Storage } from '@google-cloud/storage'; | |
import * as E from 'fp-ts/lib/Either'; | |
import { pipe } from 'fp-ts/lib/function'; | |
import * as IOEither from 'fp-ts/lib/IOEither'; | |
import * as T from 'fp-ts/lib/Task'; | |
import * as TE from 'fp-ts/lib/TaskEither'; | |
import * as t from 'io-ts'; | |
import { failure } from 'io-ts/PathReporter'; | |
const ParsedCredentials = t.type({ | |
type: t.string, | |
project_id: t.string, | |
private_key_id: t.string, | |
private_key: t.string, | |
client_email: t.string, | |
client_id: t.string, | |
auth_uri: t.string, | |
token_uri: t.string, | |
auth_provider_x509_cert_url: t.string, | |
client_x509_cert_url: t.string, | |
}); | |
type ParsedCredentials = t.TypeOf<typeof ParsedCredentials>; | |
// Utility to convert caught "things" of unknown shape into Errors | |
const unknownReasonAsError = (reason: unknown) => | |
reason instanceof Error ? reason : new Error(String(reason)); | |
// Parse JSON into Either | |
const safeParseJSON = E.tryCatchK(JSON.parse, unknownReasonAsError); | |
// IO wrapper around loading GitHub Actions Inputs | |
// which load from Environment Variables | |
const safeGetInput = (input: string) => | |
IOEither.tryCatch( | |
() => core.getInput(input, { required: true }), | |
unknownReasonAsError, | |
); | |
const parseCredentials = (serialisedMaybeCredentials: string) => | |
pipe( | |
serialisedMaybeCredentials, | |
safeParseJSON, | |
E.chainW(ParsedCredentials.decode), | |
E.mapLeft( | |
(e) => | |
new Error( | |
`failed to parse credentials because: ${ | |
e instanceof Error ? e : failure(e).join('\n') | |
}`, | |
), | |
), | |
); | |
// Wrapper around creating Google Cloud Storage client | |
const createStorage = (credentials: ParsedCredentials) => { | |
return new Storage({ | |
userAgent: 'delete-gcs-bucket-contents/0.0.1', | |
credentials, | |
}); | |
}; | |
// Empty the Bucket | |
const emptyBucket = (storage: Storage) => (bucket: string) => | |
pipe( | |
bucket, | |
TE.tryCatchK( | |
(bucket: string) => storage.bucket(bucket).deleteFiles(), | |
unknownReasonAsError, | |
), | |
); | |
const run: T.Task<void> = pipe( | |
// This nested pipe seems like something that should be removable, but I'm not sure how? | |
pipe(safeGetInput('credentials'), IOEither.chainEitherK(parseCredentials)), | |
IOEither.map(createStorage), | |
TE.fromIOEither, | |
TE.chain((storage) => | |
// It would be nice if I could avoid the closing over `storage` and instead flow it somehow? | |
// Again, another nested pipe I'm not sure about | |
// Also, I lifted fromIOEither in two places which seems like it might be funny? | |
pipe( | |
safeGetInput('bucket'), | |
TE.fromIOEither, | |
TE.chain(emptyBucket(storage)), | |
), | |
), | |
// This map seems a bit weird, especially with the right side that looks like a unit... | |
// I was trying to follow patterns from: https://dev.to/anthonyjoeseph/should-i-use-fp-ts-task-h52 | |
T.map( | |
E.fold( | |
(error) => { | |
throw error; | |
}, | |
() => {}, | |
), | |
), | |
); | |
pipe(run, (invoke) => invoke()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I had a look at the rule in the article you linked to. I think there is some merit to that rule and it may help in certain circumstances to enforce error handling.
Personally I'm not a big fan of enforcing everything to be
Task<void>
. Often in my real work codebase I would be invoking aTask
in an Express route handler which calls the core logic, then converts the success/error to the relevant HTTP response. In those cases I usually end up invoking aTask<Response>
(whereResponse
is from the express library), and I wouldn't want to make that aTask<void>
as it correctly forces the code to actually return a response in all possible scenarios.Something like this: