diff --git a/cli-readme/README.md b/cli-readme/README.md deleted file mode 100644 index 0c8c59e3..00000000 --- a/cli-readme/README.md +++ /dev/null @@ -1,74 +0,0 @@ -# Welcome to Your Plural Repo - -This is the repo that will house all your Plural installations for this cluster. We'll give you a quick walkthrough how everything works here. In general, there are two commands you'll run in your Plural workflow: - -``` -plural build // generates infrastructure as code for your installations -plural deploy // deploys your IAC resources in dependency-order -``` - - -If you want to install a new app, you can run: - -``` -plural bundle install APP BUNDLE_NAME -``` - -and you can discover the various cloud provider bundles for an app with `plural bundle list APP`. To traverse our marketplace, run `plural repos list` or go to [https://app.plural.sh/marketplace](https://app.plural.sh/marketplace) -If you want an at-a-glance view of the health of any application, you can run: - -``` -plural watch APP_NAME -``` - -but the best way to manage and monitor your applications is with the Plural Console, which you can install with `plural bundle install console console-{aws,gcp,azure,etc}`. We strongly recommend doing this. - -## Workspace Structure - -We generate infrastructure as code resources in a consistent format: - -``` - -- helm - - - - helm - - templates - - ... - - values.yaml # custom config values you provide - - default-values.yaml # configuration we generate w/ `plural build` -- terraform - - - - ... - - main.tf # can override submodule configuration here, see that file for more info -``` - -In general we expect you to customize the repo as you wish, and any standard Terraform or Helm command should work as expected. Let's say you wanted to switch the database for an app to RDS. To do this, you can use the app's dedicated Terraform folder in the repo to define the database and inject its secret into Kubernetes right there. -There are two other metadata files you should be aware of as well: - -* `context.yaml` - this is the initial app configuration you provide when running `plural bundle install`. We use this to generate the resources that are present in your app folder above. You can reconfigure settings here if you'd like, or occasionally enable different configurations. Airbyte basic auth requires a `context.yaml` change for instance. - -* `workspace.yaml` - this is the high level workspace metadata about your workspace, like the cloud account, cluster name, dns domain, etc. For the most part, this file should remain read-only. - -## Secret Management - -We automatically encrypt this repo using a go reimplementation of [git-crypt](https://github.com/AGWA/git-crypt). You'll be able to see the AES key you're currently using with `plural crypto export`. We also drop the key fingerprint into the repo at `.keyid` to validate and provide appropriate error messages. - -It's strongly recommended you backup this key, and we provide a backup API for you with `plural crypto backups create`. You can sync any backup with `plural crypto backups resource NAME`. You can also use popular solutions for this like 1password, Vault or your cloud's secrets manager. - -If you reclone this repo on a new machine, its contents will be fully encrypted. In that case you'll need to set up the encryption credentials for the repo, then run: - -``` -plural crypto init -plural crypto unlock -``` - - -## Destroying Your Cluster - -You can tear down the cluster at any time using `plural destroy`. If it gets jammed on a specific app, you can simply rerun with `plural destroy --from APP_NAME`. - -In break glass scenarios where destroy doesn't want to behave, the bulk of the cloud resources are in the bootstrap app, and you can simply run `cd bootstrap && terraform destroy` manually. If you encounter this feel free to let us know so we can improve our packaging. - -## Support - -If you have any issues with plural or any of the apps in the catalog, we pride ourselves on providing prompt support. You can join our Discord at [https://discord.gg/pluralsh](https://discord.gg/pluralsh) and we'll try to troubleshoot the issue for you. You can find common troubleshooting tips [here](https://docs.plural.sh/reference/troubleshooting) as well. \ No newline at end of file diff --git a/pages/applications/[repo].tsx b/pages/applications/[repo].tsx index 8a2063c6..0c6529ec 100644 --- a/pages/applications/[repo].tsx +++ b/pages/applications/[repo].tsx @@ -75,6 +75,7 @@ import { type RecipesQuery, type RecipesQueryVariables, } from '@src/generated/graphqlPlural' +import { combineErrors } from '@src/utils/combineErrors' import { type GlobalProps, propsWithGlobalSettings, @@ -478,12 +479,12 @@ export const getStaticProps: GetStaticProps = async (context) => { (appExtras.case_study?.stack_apps as string[]) || [] ), footerVariant: FooterVariant.kitchenSink, - errors: [ - ...(reposError ? [reposError] : []), - ...(stacksError ? [stacksError] : []), - ...(repoError ? [repoError] : []), - ...(appError ? [appError] : []), - ...(faqError ? [faqError] : []), - ], + errors: combineErrors([ + reposError, + stacksError, + repoError, + appError, + faqError, + ]), }) } diff --git a/pages/careers/hire/[job].tsx b/pages/careers/hire/[job].tsx index bac39d05..6e1553be 100644 --- a/pages/careers/hire/[job].tsx +++ b/pages/careers/hire/[job].tsx @@ -16,6 +16,7 @@ import { getJobListing, getJobListingSlugs } from '@src/data/getJobListings' import { type FullJobListingFragment } from '@src/generated/graphqlDirectus' import { ReadMdContent } from '@src/markdoc/mdParser' import { type MarkdocPage } from '@src/markdoc/mdSchema' +import { combineErrors } from '@src/utils/combineErrors' import { propsWithGlobalSettings } from '@src/utils/getGlobalProps' const PAGE_PARAM_NAME = 'job' as const @@ -118,6 +119,6 @@ export const getStaticProps: GetStaticProps = async (context) => { footerVariant: FooterVariant.kitchenSink, job, markdoc, - errors: [...(jobError ? [jobError] : [])], + errors: combineErrors([jobError]), }) } diff --git a/pages/careers/index.tsx b/pages/careers/index.tsx index 7bb36a27..78ab01dd 100644 --- a/pages/careers/index.tsx +++ b/pages/careers/index.tsx @@ -21,6 +21,7 @@ import { ScrollToLink } from '@src/components/ScrollToLink' import { CenteredSectionHead } from '@src/components/SectionHeads' import { getJobListings } from '@src/data/getJobListings' import { type MinJobListingFragment } from '@src/generated/graphqlDirectus' +import { combineErrors } from '@src/utils/combineErrors' import { propsWithGlobalSettings } from '@src/utils/getGlobalProps' import { ValueCard } from '../../src/components/ValueCard' @@ -292,6 +293,6 @@ export const getStaticProps = async () => { 'We are a growing team working on interesting problems in the cloud with Kubernetes, Elixir, Go, and React. We’re always interested in hiring new talent!', footerVariant: FooterVariant.kitchenSink, jobs: jobs || [], - errors: [...(jobsError ? [jobsError.message] : [])], + errors: combineErrors([jobsError]), }) } diff --git a/pages/community.tsx b/pages/community.tsx index 516ce12f..2d5dcebb 100644 --- a/pages/community.tsx +++ b/pages/community.tsx @@ -18,6 +18,7 @@ import EventsSection from '@src/components/page-sections/EventsSection' import FeaturedContributorsSection from '@src/components/page-sections/FeaturedContributorsSection' import { ScrollToLink } from '@src/components/ScrollToLink' import { ResponsiveText } from '@src/components/Typography' +import { DISCORD_LINK } from '@src/consts' import { type Callouts, getCommunityPageData, @@ -28,6 +29,7 @@ import { getContributors } from '@src/data/getGithubData' import { propsWithGlobalSettings } from '@src/utils/getGlobalProps' import { HeaderPad } from '../src/components/layout/HeaderPad' +import { combineErrors } from '../src/utils/combineErrors' export default function Community({ contributors, @@ -83,7 +85,7 @@ export default function Community({ target="_blank" rel="noopener noreferrer" startIcon={} - href="https://discord.gg/pluralsh" + href={DISCORD_LINK} > Discord @@ -174,11 +176,19 @@ export const getStaticProps: GetStaticProps = async ( footerVariant: FooterVariant.kitchenSink, events: events || [], callouts: pageData.callouts, - errors: [ - ...(githubError ? [githubError] : []), - ...(eventsError ? [eventsError] : []), - ...(featuredContributorsError ? [featuredContributorsError] : []), - ...(pageDataError ? [pageDataError] : []), - ], + errors: combineErrors([ + githubError, + eventsError, + featuredContributorsError, + pageDataError, + ]), }) } + +type BaseError = { + name: string + message: string +} +export type FullError = BaseError & { + graphQLErrors?: readonly BaseError[] | undefined +} diff --git a/pages/index.tsx b/pages/index.tsx index 06e2d81f..92ea1e67 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -59,6 +59,7 @@ import { StandardPageWidth, } from '../src/components/layout/LayoutHelpers' import { HomepageFeaturesSection } from '../src/components/page-sections/HomepageFeaturesSection' +import { combineErrors } from '../src/utils/combineErrors' const HeroImagesSC = styled.div(({ theme: _theme }) => { const baseWidth = 1432 @@ -585,10 +586,6 @@ export const getStaticProps = async () => { featuredQuote: page?.featured_quote || null, buildStackTabs, footerVariant: FooterVariant.kitchenSink, - errors: [ - ...(error ? [`${error}`] : []), - ...(stacksError ? [stacksError] : []), - ...(reposError ? [reposError] : []), - ], + errors: combineErrors([error, stacksError, reposError]), }) } diff --git a/pages/legal/[legal].tsx b/pages/legal/[legal].tsx index a0838467..0e38d51e 100644 --- a/pages/legal/[legal].tsx +++ b/pages/legal/[legal].tsx @@ -17,6 +17,7 @@ import { getLegalPageData, getLegalPageSlugs } from '@src/data/getLegalPageData' import { type MarkdownPageFragment } from '@src/generated/graphqlDirectus' import { readMdPage } from '@src/markdoc/mdParser' import { type MarkdocPage } from '@src/markdoc/mdSchema' +import { combineErrors } from '@src/utils/combineErrors' import { propsWithGlobalSettings } from '@src/utils/getGlobalProps' import { HeaderPad } from '../../src/components/layout/HeaderPad' @@ -133,6 +134,6 @@ export const getStaticProps: GetStaticProps = async ( title: page.title, subtitle: page.subtitle, markdoc, - errors: [...(pageDataError ? [pageDataError] : [])], + errors: combineErrors([pageDataError]), }) } diff --git a/pages/marketplace.tsx b/pages/marketplace.tsx index 4590f959..61b794a6 100644 --- a/pages/marketplace.tsx +++ b/pages/marketplace.tsx @@ -55,6 +55,7 @@ import { type FaqListQueryVariables, } from '@src/generated/graphqlDirectus' import { type BasicRepoFragment } from '@src/generated/graphqlPlural' +import { combineErrors } from '@src/utils/combineErrors' import { type GlobalProps, propsWithGlobalSettings, @@ -612,10 +613,6 @@ export const getStaticProps: GetStaticProps = async () => { tags: tags || [], categories: categories || [], faqs: normalizeM2mItems(faqData.collapsible_lists?.[0]) || [], - errors: [ - ...(reposError ? [reposError] : []), - ...(stacksError ? [reposError] : []), - ...(faqError ? [faqError] : []), - ], + errors: combineErrors([reposError, stacksError, faqError]), }) } diff --git a/pages/plural-stacks/[stack].tsx b/pages/plural-stacks/[stack].tsx index 03f0ac4c..e7167145 100644 --- a/pages/plural-stacks/[stack].tsx +++ b/pages/plural-stacks/[stack].tsx @@ -60,6 +60,7 @@ import { type BasicRepoFragment, type StackCollectionFragment, } from '@src/generated/graphqlPlural' +import { combineErrors } from '@src/utils/combineErrors' import { type GlobalProps, propsWithGlobalSettings, @@ -400,12 +401,12 @@ export const getStaticProps: GetStaticProps = async ( (stackExtras.case_study?.stack_apps as string[]) || [] ), footerVariant: FooterVariant.kitchenSink, - errors: [ - ...(reposError ? [reposError] : []), - ...(stacksError ? [stacksError] : []), - ...(stackError ? [stackError] : []), - ...(appError ? [appError] : []), - ...(faqError ? [faqError] : []), - ], + errors: combineErrors([ + reposError, + stacksError, + stackError, + appError, + faqError, + ]), }) } diff --git a/pages/pricing.tsx b/pages/pricing.tsx index e79f9438..fb2e6ebf 100644 --- a/pages/pricing.tsx +++ b/pages/pricing.tsx @@ -24,6 +24,7 @@ import { type FaqListQuery, type FaqListQueryVariables, } from '@src/generated/graphqlDirectus' +import { combineErrors } from '@src/utils/combineErrors' import { propsWithGlobalSettings } from '@src/utils/getGlobalProps' import { normalizeM2mItems } from '@src/utils/normalizeQuotes' @@ -255,9 +256,6 @@ export const getStaticProps: GetStaticProps = async ( ...pricing, faqs: normalizeM2mItems(faqData.collapsible_lists?.[0]) || [], footerVariant: FooterVariant.kitchenSink, - errors: [ - ...(pricingError ? [pricingError] : []), - ...(faqError ? [faqError] : []), - ], + errors: combineErrors([pricingError, faqError]), }) } diff --git a/pages/product.tsx b/pages/product.tsx index 2aa7c52e..8ab367fd 100644 --- a/pages/product.tsx +++ b/pages/product.tsx @@ -25,6 +25,7 @@ import { WhatIsPluralSection } from '@src/components/page-sections/WhatIsPluralS import { CenteredSectionHead } from '@src/components/SectionHeads' import { getProductPageData } from '@src/data/getProductPageData' import { useAnimationPauser } from '@src/hooks/useAnimationPauser' +import { combineErrors } from '@src/utils/combineErrors' import { propsWithGlobalSettings } from '@src/utils/getGlobalProps' import { normalizeM2mItems } from '@src/utils/normalizeQuotes' @@ -254,6 +255,6 @@ export const getStaticProps = async () => { featuredQuote: pageData?.featured_quote, faqs: normalizeM2mItems(pageData?.faq), footerVariant: FooterVariant.kitchenSink, - errors: [...(pageDataError ? [pageDataError] : [])], + errors: combineErrors([pageDataError]), }) } diff --git a/pages/solutions/[solution].tsx b/pages/solutions/[solution].tsx index 33345464..774b3513 100644 --- a/pages/solutions/[solution].tsx +++ b/pages/solutions/[solution].tsx @@ -50,6 +50,7 @@ import { type SolutionsSlugsQuery, type SolutionsSlugsQueryVariables, } from '@src/generated/graphqlDirectus' +import { combineErrors } from '@src/utils/combineErrors' import { type GlobalProps, propsWithGlobalSettings, @@ -248,11 +249,6 @@ export const getStaticProps: GetStaticProps = async (context) => { featuredQuote: solution.featured_quote || null, buildStackTabs, footerVariant: FooterVariant.kitchenSink, - errors: [ - ...(solutionError ? [solutionError] : []), - ...(faqError ? [faqError] : []), - ...(reposError ? [reposError] : []), - ...(stacksError ? [stacksError] : []), - ], + errors: combineErrors([solutionError, faqError, reposError, stacksError]), }) } diff --git a/src/components/BasicFooter.tsx b/src/components/BasicFooter.tsx index 89191243..1d15fb62 100644 --- a/src/components/BasicFooter.tsx +++ b/src/components/BasicFooter.tsx @@ -3,6 +3,7 @@ import { DiscordIcon } from '@pluralsh/design-system' import styled from 'styled-components' import { mqs } from '@src/breakpoints' +import { DISCORD_LINK } from '@src/consts' import GithubStars from './GithubStars' import { FullPageWidth } from './layout/LayoutHelpers' @@ -15,7 +16,7 @@ export function BasicFooter({ className }: { className?: string }) {
} label="Discord" - href="https://discord.gg/pluralsh" + href={DISCORD_LINK} /> } diff --git a/src/consts.ts b/src/consts.ts index fc4aa301..b77216d8 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -7,6 +7,7 @@ export const ROOT_TITLE = 'Plural | Open-source application deployment, faster than ever without sacrificing compliance.' export const PAGE_TITLE_PREFIX = 'Plural | ' export const PAGE_TITLE_SUFFIX = '' +export const DISCORD_LINK = 'https://discord.com/invite/bEBAMXV64s' export const getAppMeta = (repo: BasicRepo): GlobalPageProps => { const displayName = repo.displayName || repo.name diff --git a/src/utils/combineErrors.tsx b/src/utils/combineErrors.tsx new file mode 100644 index 00000000..8726c2c4 --- /dev/null +++ b/src/utils/combineErrors.tsx @@ -0,0 +1,29 @@ +import { isNonNullable } from '@src/utils/isNonNullable' + +import { type FullError } from '../../pages/community' + +export function combineErrors( + errors: (FullError | undefined | null)[] | null | undefined +) { + return errors?.filter(isNonNullable).map(serializableError) ?? [] +} + +export function serializableError(err: FullError) { + if (!err) { + return err + } + const { name, message, graphQLErrors } = err + + return { + name, + message, + ...(graphQLErrors + ? { + graphQLErrors: graphQLErrors.map((gqlErr) => ({ + name: gqlErr.name, + message: gqlErr.message, + })), + } + : {}), + } +} diff --git a/src/utils/getGlobalProps.tsx b/src/utils/getGlobalProps.tsx index 91fa56cd..812675c0 100644 --- a/src/utils/getGlobalProps.tsx +++ b/src/utils/getGlobalProps.tsx @@ -10,6 +10,8 @@ import { import { REVALIDATE_TIME } from '@src/consts' import { getSiteSettings } from '@src/data/getSiteSettings' +import { combineErrors } from './combineErrors' + async function getGlobalProps() { const { data: githubData, error: githubError } = await until(() => getGithubDataServer() @@ -28,7 +30,7 @@ async function getGlobalProps() { swrConfig: { fallback: swrFallback, }, - errors: [...(githubError ? [githubError] : [])], + errors: combineErrors([githubError]), } } type AsyncReturnType Promise> = T extends ( diff --git a/src/utils/isNonNullable.ts b/src/utils/isNonNullable.ts new file mode 100644 index 00000000..bcb7e286 --- /dev/null +++ b/src/utils/isNonNullable.ts @@ -0,0 +1,5 @@ +export function isNonNullable( + value: TValue +): value is NonNullable { + return value != null +}