Skip to content

Commit

Permalink
feat: improve config handling
Browse files Browse the repository at this point in the history
Signed-off-by: mbwhite <[email protected]>
  • Loading branch information
mbwhite committed Jan 9, 2024
1 parent 0e4c5d8 commit 8387381
Show file tree
Hide file tree
Showing 10 changed files with 143 additions and 101 deletions.
1 change: 0 additions & 1 deletion .tektonlintrc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,3 @@ rules: # error | warning | off
no-deprecated-resource: warning
no-missing-hashbang: warning


14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,21 @@ Examples:
## What yaml files to include?
Only the yaml files that are specified are linted; depdending on how your Tekton instance is configured sometimes you might be using task descriptions that aren't part of your project or repository. In this case you'll get linter errors that "Task XYZ can't be found" - the latest v1 version of tool has added the concept of 'external tasks'.
Only the yaml files that are specified are linted; these can be defined either on the command line, or via the `.tektonlintrc.yaml` file.
```yaml
globs:
- example*.yaml
```
If both command line and the configuration file have patterns specified, both are used but the command line options come first.
Depdending on how your Tekton instance is configured sometimes you might be using task descriptions that aren't part of your project or repository. In this case you'll get linter errors that "Task XYZ can't be found" - the latest v1 version of tool has added the concept of 'external tasks'.
For example if you are using some of the OpenToolchain Tasks, you can add the following to the [configuration file](#configuring-tekton-lint)
```
```yaml
---
external-tasks:
- name: git-clone-repo
Expand Down
21 changes: 12 additions & 9 deletions _docs/usage_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@
- it is an ESM module therefore add `"type":"module"` to your `package.json`

```js
const main = async () =>{

const cfg = getDefaultConfig()
import {Problem, Config, Linter} from '@ibm/tekton-linter'

const main = async () =>{

const yamlSrcPath = "./example-task.yaml"
const problems = await linter(cfg,[yamlSrcPath])
const cfg = Config.getDefaultConfig()
cfg.globs = ["./example-task.yaml"]

const problems = await Linter.run(cfg)
problems.forEach(element => {
console.log(`${p.rule}:: ${p.message}`)
});
Expand All @@ -43,15 +46,15 @@ The API is used as part of the regression testing, see [regression.test.ts](../r
### ToolConfig

```ts
getDefaultConfig(): ToolConfig
Config.getDefaultConfig(): Config
```


Structure is

```ts
interface ToolConfig {
tektonlintrc: string;
interface Config {

cache_dir: string;
max_warnings: number;
globs: string[];
Expand All @@ -64,10 +67,10 @@ Structure is
### Linter API

```ts
async linter(cfg: ToolConfig, globs: string[]): Problems[]
async Linter.run(cfg: ToolConfig): Promise<Problems[]>
```

An async API, taking a `ToolConfig` instance and array of glob patterns for YAML files. These are appended to any if the config object.
An async API, taking a `Config` instance containing options including the array of glob patterns for YAML files.


### Problems
Expand Down
7 changes: 4 additions & 3 deletions regression-tests/regression.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fs from 'node:fs'
import fg from 'fast-glob';
import {Problem, ToolConfig, getDefaultConfig,linter} from '../src/index'
import {Problem, Config, Linter} from '../src/index'

const pattern = "./regression-tests/general/*.yaml"
const yamlfiles = fg.globSync(pattern)
Expand All @@ -9,8 +9,9 @@ const yamlfiles = fg.globSync(pattern)
describe("Regression Tests",()=>{

test.each(yamlfiles)("%s",async (yamlSrcPath)=>{
const cfg: ToolConfig = getDefaultConfig()
const problems: Problem[] = await linter(cfg,[yamlSrcPath])
const cfg: Config = Config.getDefaultConfig()
cfg.globs=[yamlSrcPath]
const problems: Problem[] = await Linter.run(cfg)

const expectedPath =`${yamlSrcPath}.expect.json`

Expand Down
143 changes: 94 additions & 49 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,65 +3,110 @@ import path from 'node:path';
import env from 'env-var';
import fs from 'node:fs';
import yaml from 'yaml';
// import url from 'node:url';
import defaultConfig from './default-rule-config.js';

import { logger } from './logger.js';

import { RulesConfig } from './interfaces/common.js';
import json from './formatters/json.js';
export class Config {
private _rulesConfig: RulesConfig;

private _cache_dir: string;
private _max_warnings: number;
private _globs: string[];
private _refresh_cache: boolean;
private _format: string;
private _quiet: boolean;

public constructor(cliConfig: any) {
let user_tektonlintrc = path.resolve(cliConfig['config']);
if (fs.lstatSync(user_tektonlintrc).isDirectory()) {
user_tektonlintrc = path.join(user_tektonlintrc, '.tektonlintrc.yaml');
}

if (fs.existsSync(user_tektonlintrc)) {
const customRcFile = fs.readFileSync(user_tektonlintrc, 'utf8');
const customConfig = yaml.parse(customRcFile);
this._rulesConfig = { ...defaultConfig.rules, ...customConfig.rules };

logger.info('Using .tektonlintrc.yaml at %s', user_tektonlintrc);
logger.info('customConfig %o', customConfig);
this._rulesConfig = customConfig;
this._globs = [...cliConfig['_'], ...(customConfig.globs ? customConfig.globs : [])];
} else {
logger.warn('Unable to find configuration - using defaults');
this._rulesConfig = defaultConfig;
this._globs = [...cliConfig['_']];
}

this._cache_dir = env.get('TL_CACHE_DIR').default(path.join(os.homedir(), '.tekton-lint')).asString();
this._max_warnings = cliConfig['max-warnings'];
this._refresh_cache = cliConfig['refresh-cache'];
this._format = cliConfig['format'];
this._quiet = cliConfig['quiet'];

export const getRulesConfig = (cfg: ToolConfig): RulesConfig => {
// read the default file
// const defaultRcFile = fs.readFileSync(
// path.resolve(path.dirname(new url.URL(import.meta.url).pathname), '..', '.tektonlintrc.yaml'),
// 'utf8',
// );
// const defaultConfig = yaml.parse(defaultRcFile);

let user_tektonlintrc = path.resolve(cfg.tektonlintrc);
if (fs.lstatSync(user_tektonlintrc).isDirectory()) {
user_tektonlintrc = path.join(user_tektonlintrc, '.tektonlintrc.yaml');
if (!fs.existsSync(this._cache_dir)) {
fs.mkdirSync(this._cache_dir, { recursive: true });
}
}

logger.info('Using .tektonlintrc.yaml at %s', user_tektonlintrc);
public get rulesConfig() {
return this._rulesConfig;
}

if (fs.existsSync(user_tektonlintrc)) {
const customRcFile = fs.readFileSync(user_tektonlintrc, 'utf8');
const customConfig = yaml.parse(customRcFile);
customConfig.rules = { ...defaultConfig.rules, ...customConfig.rules };
public get cache_dir() {
return this._cache_dir;
}

logger.info('customConfig %o', customConfig);
return customConfig;
public get max_warnings() {
return this._max_warnings;
}

logger.info(defaultConfig);
return defaultConfig;
};

export interface ToolConfig {
tektonlintrc: string;
cache_dir: string;
max_warnings: number;
globs: string[];
refresh_cache: string;
format: string;
quiet: boolean;
}
public get globs() {
return this._globs;
}

export const toolConfig = (cliConfig: any): ToolConfig => {
const CFG = {
tektonlintrc: cliConfig['config'],
cache_dir: env.get('TL_CACHE_DIR').default(path.join(os.homedir(), '.tekton-lint')).asString(),
max_warnings: cliConfig['max-warnings'],
globs: cliConfig['_'],
refresh_cache: cliConfig['refresh-cache'],
format: cliConfig['format'],
quiet: cliConfig['quiet'],
};

if (!fs.existsSync(CFG.cache_dir)) {
fs.mkdirSync(CFG.cache_dir, { recursive: true });
public set globs(globs: string[]) {
this._globs = globs;
}

return CFG;
};
public get refreshCache() {
return this._refresh_cache;
}

public set refreshCache(refresh: boolean) {
this._refresh_cache = refresh;
}

public get format() {
return this._format;
}

public set format(format: string) {
this._format = format;
}

public get quiet() {
return this._quiet;
}

public set quiet(quiet: boolean) {
this._quiet = quiet;
}

public static getDefaultConfig(): Config {
// create default cli-proxy

const argv = {
watch: false,
color: true,
format: json,
quite: false,
config: process.cwd(),
'refresh-cache': false,
_: [],
};

const config = new Config(argv);
return config;
}
}
8 changes: 4 additions & 4 deletions src/external.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import os from 'node:os';
import path from 'node:path';
// import stream from 'node:stream';
import { logger } from './logger.js';
import { ToolConfig, getRulesConfig } from './config.js';
import { Config } from './config.js';

import { ExternalResource } from './interfaces/common.js';

const appPrefix = 'tektonlint';

export function getExternal(repo: string, subpath: string[], cfg: ToolConfig) {
export function getExternal(repo: string, subpath: string[], cfg: Config) {
const cacheDir = cfg.cache_dir;
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), appPrefix));

Expand All @@ -30,10 +30,10 @@ export function getExternal(repo: string, subpath: string[], cfg: ToolConfig) {
});
}

export function collectAllExternal(cfg: ToolConfig): string[] {
export function collectAllExternal(cfg: Config): string[] {
// get the configuration for the external references

const rc = getRulesConfig(cfg);
const rc = cfg.rulesConfig;
const repoList: { [key: string]: string[] } = {};

if ('external-tasks' in rc) {
Expand Down
34 changes: 9 additions & 25 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,13 @@
import { ToolConfig, toolConfig } from './config.js';
import json from './formatters/json.js';
import { Config } from './config.js';

import run from './runner.js';
import { Problem, Location } from './interfaces/common.js';

const linter = async (cfg: ToolConfig, globs: string[]): Promise<Problem[]> => {
cfg.globs = [...cfg.globs, ...globs];
const problems = await run(cfg);
return problems;
};

const getDefaultConfig = (): ToolConfig => {
// create default cli-proxy

const argv = {
watch: false,
color: true,
format: json,
quite: false,
config: process.cwd(),
'refresh-cache': false,
_: [],
};

const config = toolConfig(argv);
return config;
};
class Linter {
public static async run(cfg: Config): Promise<Problem[]> {
const problems = await run(cfg);
return problems;
}
}

export { Problem, Location, ToolConfig, getDefaultConfig, linter };
export { Problem, Location, Config, Linter };
4 changes: 2 additions & 2 deletions src/lint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import yargs from 'yargs/yargs';
import * as fs from 'node:fs';
import * as path from 'node:path';
import url from 'node:url';
import { toolConfig } from './config.js';
import { Config } from './config.js';
import run from './runner.js';
import logProblems from './log-problems.js';

Expand Down Expand Up @@ -50,7 +50,7 @@ const parser = yargs(process.argv.slice(2))
logger.info(argv);

try {
const cfg = toolConfig(argv);
const cfg = new Config(argv);

const problems = await run(cfg);
logProblems(cfg, problems);
Expand Down
4 changes: 2 additions & 2 deletions src/log-problems.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import stylish from './formatters/stylish.js';
import vscode from './formatters/vscode.js';
import json from './formatters/json.js';
import { ToolConfig } from './config.js';
import { Config } from './config.js';

const formatters = {
stylish,
Expand All @@ -11,7 +11,7 @@ const formatters = {

const onlyErrors = (problems) => problems.filter((problem) => problem.level === 'error');

export default (cfg: ToolConfig, problems) => {
export default (cfg: Config, problems) => {
if (!(cfg.format in formatters)) {
process.exitCode = 1;
return console.log(`Formatter "${cfg.format}" is not available!`);
Expand Down
8 changes: 4 additions & 4 deletions src/runner.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ToolConfig, getRulesConfig } from './config.js';
import { Config } from './config.js';
import Reporter from './reporter.js';
import { lint as doLint } from './rules.js';

Expand All @@ -10,7 +10,7 @@ import { logger } from './logger.js';
import { Doc } from './interfaces/common.js';

/* Collect paths based on the glob pattern passed in */
const collector = async (paths: string[], cfg: ToolConfig) => {
const collector = async (paths: string[], cfg: Config) => {
const docs: Doc[] = [];
const files = await glob(paths);
logger.info('Found these files %j', files);
Expand All @@ -31,7 +31,7 @@ const collector = async (paths: string[], cfg: ToolConfig) => {
return docs;
};

export default async function runner(cfg: ToolConfig) {
export default async function runner(cfg: Config) {
// setup the cache of the external tasks
const external_cached = collectAllExternal(cfg);

Expand All @@ -43,7 +43,7 @@ export default async function runner(cfg: ToolConfig) {
return doLint(
docs.map((doc: any) => doc.content),
reporter,
getRulesConfig(cfg),
cfg.rulesConfig,
);
} else {
throw new Error(`No paths match glob "${cfg.globs}" - did you mean to add "*.yaml"`);
Expand Down

0 comments on commit 8387381

Please sign in to comment.