Skip to content

Commit

Permalink
Sustainable Kibana Architecture: Relocate script v4 (#204383)
Browse files Browse the repository at this point in the history
## Summary

* Auto-detect "upstream" and "origin" remotes (instead of assuming their
names).
* Allow relocating modules that are already in a "sustainable" folder.
  * Filter out modules that are in the correct locations.
* Update the list of _modules to relocate_ to show only those modules
that are actually moved.

---------

Co-authored-by: Alejandro Fernández Haro <[email protected]>
  • Loading branch information
gsoldevila and afharo authored Dec 16, 2024
1 parent 713d4bb commit d18a44c
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 59 deletions.
5 changes: 1 addition & 4 deletions packages/kbn-relocate/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,9 @@ You must have `gh` CLI tool installed. You can install it by running:

```sh
brew install gh
gh auth login
```

You must have `elastic/kibana` remote configured under the name `upstream`.

You must have a remote named `origin` pointing to your fork of the Kibana repo.

## Usage

First of all, you need to decide whether you want to contribute to an existing PR or to create a new one. Use the `--pr` flag to specify the PR you are trying to update:
Expand Down
27 changes: 20 additions & 7 deletions packages/kbn-relocate/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,26 @@ export const BASE_FOLDER = process.cwd() + '/';
export const BASE_FOLDER_DEPTH = process.cwd().split('/').length;
export const KIBANA_FOLDER = process.cwd().split('/').pop()!;
export const EXCLUDED_MODULES = ['@kbn/core'];
export const TARGET_FOLDERS = [
'src/platform/plugins/',
'src/platform/packages/',
'x-pack/platform/plugins/',
'x-pack/platform/packages/',
'x-pack/solutions/',
];
export const TARGET_FOLDERS: Record<string, string[]> = {
'platform:private': [
'src/platform/packages/private/',
'src/platform/plugins/private/',
'x-pack/platform/packages/private/',
'x-pack/platform/plugins/private/',
],
'platform:shared': [
'src/platform/packages/shared/',
'src/platform/plugins/shared/',
'x-pack/platform/packages/shared/',
'x-pack/platform/plugins/shared/',
],
'observability:private': [
'x-pack/solutions/observability/packages/',
'x-pack/solutions/observability/plugins/',
],
'search:private': ['x-pack/solutions/search/packages/', 'x-pack/solutions/search/plugins/'],
'security:private': ['x-pack/solutions/security/packages/', 'x-pack/solutions/security/plugins/'],
};
export const EXTENSIONS = [
'eslintignore',
'gitignore',
Expand Down
20 changes: 11 additions & 9 deletions packages/kbn-relocate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,17 @@ export const runKbnRelocateCli = () => {
await findAndMoveModule(flags.moveOnly, log);
} else {
const { pr, team, path, include, exclude, baseBranch } = flags;
await findAndRelocateModules({
prNumber: toOptString('prNumber', pr),
baseBranch: toOptString('baseBranch', baseBranch, 'main')!,
teams: toStringArray(team),
paths: toStringArray(path),
included: toStringArray(include),
excluded: toStringArray(exclude),
log,
});
await findAndRelocateModules(
{
prNumber: toOptString('prNumber', pr),
baseBranch: toOptString('baseBranch', baseBranch, 'main')!,
teams: toStringArray(team),
paths: toStringArray(path),
included: toStringArray(include),
excluded: toStringArray(exclude),
},
log
);
}
},
{
Expand Down
56 changes: 30 additions & 26 deletions packages/kbn-relocate/relocate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,17 @@ import type { ToolingLog } from '@kbn/tooling-log';
import { getPackages } from '@kbn/repo-packages';
import { REPO_ROOT } from '@kbn/repo-info';
import type { Package } from './types';
import {
DESCRIPTION,
EXCLUDED_MODULES,
KIBANA_FOLDER,
NEW_BRANCH,
TARGET_FOLDERS,
} from './constants';
import { DESCRIPTION, EXCLUDED_MODULES, KIBANA_FOLDER, NEW_BRANCH } from './constants';
import {
belongsTo,
calculateModuleTargetFolder,
isInTargetFolder,
replaceReferences,
replaceRelativePaths,
} from './utils.relocate';
import { safeExec } from './utils.exec';
import { relocatePlan, relocateSummary } from './utils.logging';
import { checkoutBranch, checkoutResetPr } from './utils.git';
} from './utils/relocate';
import { safeExec } from './utils/exec';
import { relocatePlan, relocateSummary } from './utils/logging';
import { checkoutBranch, checkoutResetPr, findGithubLogin, findRemoteName } from './utils/git';

const moveModule = async (module: Package, log: ToolingLog) => {
const destination = calculateModuleTargetFolder(module);
Expand All @@ -52,11 +47,6 @@ const relocateModules = async (toMove: Package[], log: ToolingLog): Promise<numb
for (let i = 0; i < toMove.length; ++i) {
const module = toMove[i];

if (TARGET_FOLDERS.some((folder) => module.directory.includes(folder))) {
log.warning(`The module ${module.id} is already in a "sustainable" folder. Skipping`);
// skip modules that are already moved
continue;
}
log.info('');
log.info('--------------------------------------------------------------------------------');
log.info(`\t${module.id} (${i + 1} of ${toMove.length})`);
Expand Down Expand Up @@ -93,10 +83,9 @@ export interface RelocateModulesParams {
paths: string[];
included: string[];
excluded: string[];
log: ToolingLog;
}

const findModules = ({ teams, paths, included, excluded }: FindModulesParams) => {
const findModules = ({ teams, paths, included, excluded }: FindModulesParams, log: ToolingLog) => {
// get all modules
const modules = getPackages(REPO_ROOT);

Expand All @@ -123,24 +112,39 @@ const findModules = ({ teams, paths, included, excluded }: FindModulesParams) =>
paths.some((path) => module.directory.includes(path))
)
// the module is not explicitly excluded
.filter(({ id }) => !excluded.includes(id)),
'id'
.filter(({ id }) => !excluded.includes(id))
// exclude modules that are in the correct folder
.filter((module) => !isInTargetFolder(module, log))
);
};

export const findAndMoveModule = async (moduleId: string, log: ToolingLog) => {
const modules = findModules({ teams: [], paths: [], included: [moduleId], excluded: [] });
const modules = findModules({ teams: [], paths: [], included: [moduleId], excluded: [] }, log);
if (!modules.length) {
log.warning(`Cannot move ${moduleId}, either not found or not allowed!`);
} else {
await moveModule(modules[0], log);
}
};

export const findAndRelocateModules = async (params: RelocateModulesParams) => {
const { prNumber, log, baseBranch, ...findParams } = params;
export const findAndRelocateModules = async (params: RelocateModulesParams, log: ToolingLog) => {
const upstream = await findRemoteName('elastic/kibana');
if (!upstream) {
log.error(
'This repository does not have a remote pointing to the elastic/kibana repository. Aborting'
);
return;
}

const origin = await findRemoteName(`${await findGithubLogin()}/kibana`);
if (!origin) {
log.error('This repository does not have a remote pointing to your Kibana fork. Aborting');
return;
}

const { prNumber, baseBranch, ...findParams } = params;

const toMove = findModules(findParams);
const toMove = findModules(findParams, log);
if (!toMove.length) {
log.info(
`No packages match the specified filters. Please tune your '--path' and/or '--team' and/or '--include' flags`
Expand All @@ -164,7 +168,7 @@ export const findAndRelocateModules = async (params: RelocateModulesParams) => {
await safeExec(`git restore --staged .`);
await safeExec(`git restore .`);
await safeExec(`git clean -f -d`);
await safeExec(`git checkout ${baseBranch} && git pull upstream ${baseBranch}`);
await safeExec(`git checkout ${baseBranch} && git pull ${upstream} ${baseBranch}`);

if (prNumber) {
// checkout existing PR, reset all commits, rebase from baseBranch
Expand Down Expand Up @@ -204,7 +208,7 @@ export const findAndRelocateModules = async (params: RelocateModulesParams) => {

const pushCmd = prNumber
? `git push --force-with-lease`
: `git push --set-upstream origin ${NEW_BRANCH}`;
: `git push --set-upstream ${origin} ${NEW_BRANCH}`;

if (!res2.pushBranch) {
log.info(`Remember to push changes with "${pushCmd}"`);
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,26 @@
*/

import inquirer from 'inquirer';
import type { Commit, PullRequest } from './types';
import { safeExec } from './utils.exec';
import type { Commit, PullRequest } from '../types';
import { safeExec } from './exec';

export const findRemoteName = async (repo: string) => {
const res = await safeExec('git remote -v');
const remotes = res.stdout.split('\n').map((line) => line.split(/\t| /).filter(Boolean));
return remotes.find(([_, url]) => url.includes(`github.com/${repo}`))?.[0];
};

export const findGithubLogin = async () => {
const res = await safeExec('gh auth status');
// e.g. ✓ Logged in to github.com account gsoldevila (/Users/gsoldevila/.config/gh/hosts.yml)
const loginLine = res.stdout
.split('\n')
.find((line) => line.includes('Logged in'))
?.split(/\t| /)
.filter(Boolean);

return loginLine?.[loginLine?.findIndex((fragment) => fragment === 'account') + 1];
};

export const findPr = async (number: string): Promise<PullRequest> => {
const res = await safeExec(`gh pr view ${number} --json commits,headRefName`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@
import type { ToolingLog } from '@kbn/tooling-log';
import { appendFileSync, writeFileSync } from 'fs';
import dedent from 'dedent';
import type { Package } from './types';
import { calculateModuleTargetFolder } from './utils.relocate';
import type { Package } from '../types';
import { calculateModuleTargetFolder } from './relocate';
import {
BASE_FOLDER,
DESCRIPTION,
GLOBAL_DESCRIPTION,
SCRIPT_ERRORS,
UPDATED_REFERENCES,
UPDATED_RELATIVE_PATHS,
} from './constants';
} from '../constants';

export const relocatePlan = (modules: Package[], log: ToolingLog) => {
const plugins = modules.filter((module) => module.manifest.type === 'plugin');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import { join } from 'path';
import type { ToolingLog } from '@kbn/tooling-log';
import { orderBy } from 'lodash';
import type { Package } from './types';
import type { Package } from '../types';
import { applyTransforms } from './transforms';
import {
BASE_FOLDER,
Expand All @@ -22,8 +22,8 @@ import {
TARGET_FOLDERS,
UPDATED_REFERENCES,
UPDATED_RELATIVE_PATHS,
} from './constants';
import { quietExec, safeExec } from './utils.exec';
} from '../constants';
import { quietExec, safeExec } from './exec';

export const belongsTo = (module: Package, owner: string): boolean => {
return Array.from(module.manifest.owner)[0] === owner;
Expand All @@ -40,11 +40,18 @@ export const calculateModuleTargetFolder = (module: Package): string => {
const isPlugin = module.manifest.type === 'plugin';
const fullPath = join(BASE_FOLDER, module.directory);
let moduleDelimiter = isPlugin ? '/plugins/' : '/packages/';
if (TARGET_FOLDERS.some((folder) => module.directory.includes(folder)) && group === 'platform') {
// if a platform module has already been relocated, strip the /private/ or /shared/ part too
moduleDelimiter += `${module.visibility}/`;

// for platform modules that are in a sustainable folder, strip the /private/ or /shared/ part too
if (module.directory.includes(`${moduleDelimiter}private/`)) {
moduleDelimiter += 'private/';
} else if (module.directory.includes(`${moduleDelimiter}shared/`)) {
moduleDelimiter += 'shared/';
}
const moduleFolder = fullPath.split(moduleDelimiter).pop()!;

const chunks = fullPath.split(moduleDelimiter);
chunks.shift(); // remove the base path up to '/packages/' or '/plugins/'
const moduleFolder = chunks.join(moduleDelimiter); // in case there's an extra /packages/ or /plugins/ folder

let path: string;

if (group === 'platform') {
Expand Down Expand Up @@ -79,6 +86,26 @@ export const calculateModuleTargetFolder = (module: Package): string => {
return applyTransforms(module, path);
};

export const isInTargetFolder = (module: Package, log: ToolingLog): boolean => {
if (!module.group || !module.visibility) {
log.warning(`The module '${module.id}' is missing the group/visibility information`);
return true;
}

const baseTargetFolders = TARGET_FOLDERS[`${module.group}:${module.visibility}`];
const baseTargetFolder = baseTargetFolders.find((candidate) => {
return module.directory.includes(candidate);
});
if (baseTargetFolder) {
log.info(
`The module ${module.id} is already in the correct folder: '${baseTargetFolder}'. Skipping`
);
return true;
}

return false;
};

export const replaceReferences = async (module: Package, destination: string, log: ToolingLog) => {
const dir = module.directory;
const source =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import type { Package } from './types';
import type { Package } from '../types';

type TransformFunction = (param: string) => string;
const TRANSFORMS: Record<string, string | TransformFunction> = {
Expand Down

0 comments on commit d18a44c

Please sign in to comment.