Skip to content

Commit

Permalink
Merge pull request #371 from guardian/ts/best-practices
Browse files Browse the repository at this point in the history
feat: add best practices markdown and generator script
  • Loading branch information
tjsilver authored Oct 17, 2023
2 parents 0458764 + 141bda3 commit 1320bb3
Show file tree
Hide file tree
Showing 8 changed files with 317 additions and 0 deletions.
19 changes: 19 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions packages/best-practices/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Best Practice Generator
This is a small TypeScript script to generate the [best practices markdown file](best-practices.md).

## Adding a new best practice
To add a new best practice, follow these steps:
1. Update [definitions.ts](src/definitions.ts). Before adding a best practice, consider:
- How one can identify if it's being followed (e.g. a dashboard, or a command to run)
- How to exempt from it.
2. Generate the markdown file via `npm -w best-practices run generate` (this will also type-check, and format your changes).
3. Raise a PR with your changes.
37 changes: 37 additions & 0 deletions packages/best-practices/best-practices.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@


# Best Practices


This file is auto-generated from `definitions.ts`. Do not edit this file directly, but instead edit `definitions.ts` and then run `npm -w best-practices run generate`.

This document defines a list of best practices we have defined.

An Owner is someone/a team that is responsible for tracking compliance of the best practice. They can also be approached for guidance on how to adhere. Typically this will be a DevX team.

<!-- contentstart -->
## Repository
| ID | Name | Owner | Description | How to check compliance | How to exempt |
| ------------- | ------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| REPOSITORY-01 | Default Branch Name | [@guardian](https://github.com/orgs/guardian/teams/all) | The default branch name should be `main`.<br>See the [master-to-main tool](https://github.com/guardian/master-to-main/blob/main/migrating.md). | [Grafana](https://metrics.gutools.co.uk/d/2uaV8PiIz/repocop?orgId=1) | Archived repositories are exempt. |
| REPOSITORY-02 | Branch Protection | [@guardian](https://github.com/orgs/guardian/teams/all) | Enable branch protection for the default branch, ensuring changes are reviewed before being deployed. | [Grafana](https://metrics.gutools.co.uk/d/2uaV8PiIz/repocop?orgId=1) | Archived repositories are exempt. |
| REPOSITORY-03 | Team-based Access | [@guardian](https://github.com/orgs/guardian/teams/all) | Grant access on a team basis, rather than directly to individuals. | Manual. View the repository on https://github.com | Repositories with one of following topics are exempt: `hackday`, `learning`, `prototype`. |
| REPOSITORY-04 | Admin Access | [@guardian](https://github.com/orgs/guardian/teams/all) | Grant at least one GitHub team Admin access - typically, the dev team that own the project. | [Grafana](https://metrics.gutools.co.uk/d/2uaV8PiIz/repocop?orgId=1) | Repositories with one of following topics are exempt: `hackday`, `learning`, `prototype`. Archived repositories are exempt. |
| REPOSITORY-05 | Archiving | [DevX Operations](https://github.com/orgs/guardian/teams/devx-operations) | Repositories that are no longer used should be archived. | Manual. View the repository on https://github.com | N/A |
| REPOSITORY-06 | Topics | [DevX Security](https://github.com/orgs/guardian/teams/devx-security) | Repositories should have one of the following topics, to help understand what is in production. `production`, `testing`, `documentation`, `hackday`, `prototype`, `learning`, `interactive` | [Grafana](https://metrics.gutools.co.uk/d/2uaV8PiIz/repocop?orgId=1) | Archived repositories are exempt. |
| REPOSITORY-07 | Contents | [DevX Security](https://github.com/orgs/guardian/teams/devx-security) | Never commit secret information. Avoid private information in public repositories. | Manual. View the repository on https://github.com | N/A |
## AWS
| ID | Name | Owner | Description | How to check compliance | How to exempt |
| ------ | ---------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | ------------- |
| AWS-01 | Resource Tagging | [DevX Operations](https://github.com/orgs/guardian/teams/devx-operations) | AWS resources should be tagged (where supported) with `Stack`, `Stage`, and `App`.<br>This aids service discovery, and cost allocation. | TBD | N/A |
## GalaxiesPerson
| ID | Name | Owner | Description | How to check compliance | How to exempt |
| ----------------- | ---------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ----------------------- | ------------------------------------------------------------------- |
| GALAXIESPERSON-01 | GitHub Usernames | [DevX Operations](https://github.com/orgs/guardian/teams/devx-operations) | Developers should update their [Galaxies profiles](https://forms.gle/7Yye3KfHefgYqg3c7) with their GitHub usernames | View on Galaxies | Your Galaxies role is something other than an engineer/data analyst |
## GalaxiesTeam
| ID | Name | Owner | Description | How to check compliance | How to exempt |
| --------------- | ------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| GALAXIESTEAM-01 | Github Team | [DevX Operations](https://github.com/orgs/guardian/teams/devx-operations) | Teams should have their GitHub team names in their galaxies entry | Check in [this file](https://github.com/guardian/galaxies/blob/main/shared/data/teams.ts) in the Galaxies repo | Teams that don't use GitHub are exempt |
| GALAXIESTEAM-02 | Team Emails | [DevX Operations](https://github.com/orgs/guardian/teams/devx-operations) | A team on Galaxies should have an email address entry | Check in [this file](https://github.com/guardian/galaxies/blob/main/shared/data/teams.ts) in the Galaxies repo | N/A |
| GALAXIESTEAM-03 | Team Channels | [DevX Operations](https://github.com/orgs/guardian/teams/devx-operations) | A team on Galaxies should have a public chat channel key listed | Check in [this file](https://github.com/guardian/galaxies/blob/main/shared/data/teams.ts) in the Galaxies repo | It's generally good practice to do this, but teams that don't use GitHub are exempt |
<!-- contentend -->
12 changes: 12 additions & 0 deletions packages/best-practices/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "best-practices",
"version": "1.0.0",
"description": "A markdown file of best practices generated by a script from a definitions file",
"type": "module",
"scripts": {
"generate": "ts-node src/index.ts"
},
"dependencies": {
"markdown-table": "^3.0.3"
}
}
134 changes: 134 additions & 0 deletions packages/best-practices/src/definitions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import type { IAllBestPractice, IBestPractice } from './types.ts';

const repository: readonly IBestPractice[] = [
{
name: 'Default Branch Name',
owner: '[@guardian](https://github.com/orgs/guardian/teams/all)',
description:
'The default branch name should be `main`.<br>See the [master-to-main tool](https://github.com/guardian/master-to-main/blob/main/migrating.md).',
howToCheck:
'[Grafana](https://metrics.gutools.co.uk/d/2uaV8PiIz/repocop?orgId=1)',
howToExempt: 'Archived repositories are exempt.',
},
{
name: 'Branch Protection',
owner: '[@guardian](https://github.com/orgs/guardian/teams/all)',
description:
'Enable branch protection for the default branch, ensuring changes are reviewed before being deployed.',
howToCheck:
'[Grafana](https://metrics.gutools.co.uk/d/2uaV8PiIz/repocop?orgId=1)',
howToExempt: 'Archived repositories are exempt.',
},
{
name: 'Team-based Access',
owner: '[@guardian](https://github.com/orgs/guardian/teams/all)',
description:
'Grant access on a team basis, rather than directly to individuals.',
howToCheck: 'Manual. View the repository on https://github.com',
howToExempt:
'Repositories with one of following topics are exempt: `hackday`, `learning`, `prototype`.',
},
{
name: 'Admin Access',
owner: '[@guardian](https://github.com/orgs/guardian/teams/all)',
description:
'Grant at least one GitHub team Admin access - typically, the dev team that own the project.',
howToCheck:
'[Grafana](https://metrics.gutools.co.uk/d/2uaV8PiIz/repocop?orgId=1)',
howToExempt:
'Repositories with one of following topics are exempt: `hackday`, `learning`, `prototype`. Archived repositories are exempt.',
},
{
name: 'Archiving',
owner:
'[DevX Operations](https://github.com/orgs/guardian/teams/devx-operations)',
description: 'Repositories that are no longer used should be archived.',
howToCheck: 'Manual. View the repository on https://github.com',
howToExempt: 'N/A',
},
{
name: 'Topics',
owner:
'[DevX Security](https://github.com/orgs/guardian/teams/devx-security)',
description:
'Repositories should have one of the following topics, to help understand what is in production. `production`, `testing`, `documentation`, `hackday`, `prototype`, `learning`, `interactive`',
howToCheck:
'[Grafana](https://metrics.gutools.co.uk/d/2uaV8PiIz/repocop?orgId=1)',
howToExempt: 'Archived repositories are exempt.',
},
{
name: 'Contents',
owner:
'[DevX Security](https://github.com/orgs/guardian/teams/devx-security)',
description:
'Never commit secret information. Avoid private information in public repositories.',
howToCheck: 'Manual. View the repository on https://github.com',
howToExempt: 'N/A',
},
] as const satisfies readonly IBestPractice[];

const aws: readonly IBestPractice[] = [
{
name: 'Resource Tagging',
owner:
'[DevX Operations](https://github.com/orgs/guardian/teams/devx-operations)',
description:
'AWS resources should be tagged (where supported) with `Stack`, `Stage`, and `App`.<br>This aids service discovery, and cost allocation.',
howToCheck: 'TBD',
howToExempt: 'N/A',
},
] as const satisfies readonly IBestPractice[];

const galaxiesPerson: readonly IBestPractice[] = [
{
name: 'GitHub Usernames',
owner:
'[DevX Operations](https://github.com/orgs/guardian/teams/devx-operations)',
description:
'Developers should update their [Galaxies profiles](https://forms.gle/7Yye3KfHefgYqg3c7) with their GitHub usernames',
howToCheck: 'View on Galaxies',
howToExempt:
'Your Galaxies role is something other than an engineer/data analyst',
},
];

const galaxiesTeam: readonly IBestPractice[] = [
{
name: 'Github Team',
owner:
'[DevX Operations](https://github.com/orgs/guardian/teams/devx-operations)',
description:
'Teams should have their GitHub team names in their galaxies entry',
howToCheck:
'Check in [this file](https://github.com/guardian/galaxies/blob/main/shared/data/teams.ts) in the Galaxies repo',
howToExempt: "Teams that don't use GitHub are exempt",
},
{
name: 'Team Emails',
owner:
'[DevX Operations](https://github.com/orgs/guardian/teams/devx-operations)',
description: 'A team on Galaxies should have an email address entry',
howToCheck:
'Check in [this file](https://github.com/guardian/galaxies/blob/main/shared/data/teams.ts) in the Galaxies repo',
howToExempt: 'N/A',
},
{
name: 'Team Channels',
owner:
'[DevX Operations](https://github.com/orgs/guardian/teams/devx-operations)',
description:
'A team on Galaxies should have a public chat channel key listed',
howToCheck:
'Check in [this file](https://github.com/guardian/galaxies/blob/main/shared/data/teams.ts) in the Galaxies repo',
//We rely on this information this for repocop alerts, so only teams that have repos are relevant at this stage
howToExempt:
"It's generally good practice to do this, but teams that don't use GitHub are exempt",
},
] as const satisfies readonly IBestPractice[];

export const AllBestPractices: IAllBestPractice = {
Repository: repository,
AWS: aws,
GalaxiesPerson: galaxiesPerson,
GalaxiesTeam: galaxiesTeam,
};
57 changes: 57 additions & 0 deletions packages/best-practices/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* This script generates the best-practices.md file from the definitions in the definitions.ts file.
*
* Add new best practices to the definitions.ts file.
*/
import * as fs from 'fs';
import { markdownTable } from 'markdown-table';
import { AllBestPractices } from './definitions.ts';

const markdownFilepath = './best-practices.md';

const file = fs.readFileSync(markdownFilepath, 'utf-8');

const startMark = '<!-- contentstart -->';
const endMark = '<!-- contentend -->';

if (!file.includes(startMark) || !file.includes(endMark)) {
throw new Error(
`Could not find start (${startMark}) and end markers (${endMark}) in ${markdownFilepath}`,
);
}

const tableHeaderRow = [
'ID', // This will be auto-generated
'Name',
'Owner',
'Description',
'How to check compliance',
'How to exempt',
];

const markdownContent = Object.entries(AllBestPractices).flatMap(
([section, bestPractices]) => {
const markdownH2 = `## ${section}`;

const tableRows = bestPractices.map((row, index) => {
const id = [section, (index + 1).toString().padStart(2, '0')]
.join('-')
.toUpperCase();

// eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-assignment -- testing
return [id, ...Object.values(row)];
});

const table = markdownTable([tableHeaderRow, ...tableRows]);

return [markdownH2, table];
},
);

// Find the markers, and replace them, and any text in between, with the new content.
const re = new RegExp(`${startMark}(.|\n)*${endMark}`, 'm');
const updatedFile = file.replace(
re,
[startMark, ...markdownContent, endMark].join('\n'),
);
fs.writeFileSync(markdownFilepath, updatedFile, 'utf-8');
38 changes: 38 additions & 0 deletions packages/best-practices/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
export interface IBestPractice {
/**
* The name of the best practice. Should be short.
*/
name: string;

/**
* The team responsible for monitoring, and communicating this best practice.
*
* This field supports Markdown notation, allowing you to, for example, link to a team's GitHub page.
*/
owner: string;

/**
* A description of the best practice, explaining why it's important.
*
* This field supports Markdown notation.
*/
description: string;

/**
* How to check if the best practice is being followed.
*
* For example, a link to a dashboard, or a command to run.
*/
howToCheck: string;

/**
* How to exempt from the best practice.
*/
howToExempt: string;
}

/**
* A list of best practices, grouped by section.
* The section will be used as a heading in the Markdown file.
*/
export type IAllBestPractice = Record<string, readonly IBestPractice[]>;
10 changes: 10 additions & 0 deletions packages/best-practices/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"module": "es6",
"allowImportingTsExtensions": true
},
"ts-node": {
"esm": true,
}
}

0 comments on commit 1320bb3

Please sign in to comment.