Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for camelCase #111

Merged
merged 1 commit into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .tektonlintrc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ rules: # error | warning | off
no-latest-image: warning
prefer-beta: warning
prefer-kebab-case: warning
prefer-camel-kebab-case: off
no-unused-param: warning
no-missing-resource: error
no-undefined-param: error
Expand Down
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,24 @@ If you see an error like ` Pipeline 'pipeline-test-perf-tag' references task 'un
- Unused `Pipeline` parameters
- Unused `TriggerTemplate` parameters
- Unpinned images in `Task` steps
- _kebab-case_ naming violations
- _kebab-case_ and OR _camelCase_ naming violations
- `Task` & `Pipeline` definitions with `tekton.dev/v1alpha1` `apiVersion`
- Missing `TriggerBinding` parameter values
- Usage of deprecated `Condition` instead of `WhenExpression`
- Usage of deprecated resources (resources marked with `tekton.dev/deprecated` label)
- Missing `hashbang` line from a `Step`s `script`

The default rule is for preferring _kebab-case_; _camelCase_ is equally popular and many of the official examples on the Tekton website are in camel case. To use the rule that accepts camel case as well swap the rules in the `.tektonrc.yaml` file

```yaml
---
rules: # error | warning | off
prefer-kebab-case: off
prefer-camel-kebab-case: warning
```



## Configuring `tekton-lint`

You can configure `tekton-lint` with a configuration file ([`.tektonlintrc.yaml`](./.tektonlintrc.yaml)). You can decide which rules are enabled and at what error level. In addition you can specify external tekton tasks defined in a git repository; for example [OpenToolChain](https://github.com/open-toolchain/tekton-catalog) provides a set of tasks that are helpful. But if you lint just your own tekton files there will be errors about not being able to find `git-clone-repo` for example. Not will any checks be made to see if your usage is correct.
Expand Down
8 changes: 8 additions & 0 deletions regression-tests/customconfig/.tektonlintrc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
rules: # error | warning | off
prefer-kebab-case: off
prefer-camel-kebab-case: warning

# custom:
# my_rules: custom_rules

72 changes: 72 additions & 0 deletions regression-tests/customconfig/ace-pipeline.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: ace-pipeline
spec:
params:
- name: outputRegistry
type: string
- name: url
type: string
default: "https://github.com/ot4i/ace-demo-pipeline"
- name: revision
type: string
default: "main"
- name: buildImage
type: string
- name: runtimeBaseImage
type: string
- name: knativeDeploy
type: string
default: "false"
tasks:
- name: build-from-source
taskRef:
name: aceBuild
params:
- name: outputRegistry
value: $(params.outputRegistry)
- name: url
value: $(params.url)
- name: revision
value: $(params.revision)
- name: buildImage
value: $(params.buildImage)
- name: runtimeBaseImage
value: $(params.runtimeBaseImage)
- name: deploy-to-cluster
taskRef:
name: deploy-to-cluster
params:
- name: dockerRegistry
value: $(params.outputRegistry)
- name: url
value: $(params.url)
- name: revision
value: $(params.revision)
- name: tag
value: "$(tasks.build-from-source.results.tag)"
runAfter:
- build-from-source
when:
- input: "$(params.knativeDeploy)"
operator: in
values: ["false"]
- name: deploy-knative-to-cluster
taskRef:
name: knative-deploy
params:
- name: dockerRegistry
value: $(params.outputRegistry)
- name: url
value: $(params.url)
- name: revision
value: $(params.revision)
- name: tag
value: "$(tasks.build-from-source.results.tag)"
runAfter:
- build-from-source
when:
- input: "$(params.knativeDeploy)"
operator: in
values: ["true"]
53 changes: 53 additions & 0 deletions regression-tests/customconfig/ace-pipeline.yaml.expect.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
[
{
"message": "Pipeline 'ace-pipeline' references task 'aceBuild' but the referenced task cannot be found. To fix this, include all the task definitions to the lint task for this pipeline.",
"rule": "no-missing-resource",
"level": "error",
"path": "./regression-tests/customconfig/ace-pipeline.yaml",
"loc": {
"range": [
521,
529,
530
],
"startLine": 25,
"startColumn": 15,
"endLine": 25,
"endColumn": 23
}
},
{
"message": "Pipeline 'ace-pipeline' references task 'deploy-to-cluster' but the referenced task cannot be found. To fix this, include all the task definitions to the lint task for this pipeline.",
"rule": "no-missing-resource",
"level": "error",
"path": "./regression-tests/customconfig/ace-pipeline.yaml",
"loc": {
"range": [
930,
947,
948
],
"startLine": 39,
"startColumn": 15,
"endLine": 39,
"endColumn": 32
}
},
{
"message": "Pipeline 'ace-pipeline' references task 'knative-deploy' but the referenced task cannot be found. To fix this, include all the task definitions to the lint task for this pipeline.",
"rule": "no-missing-resource",
"level": "error",
"path": "./regression-tests/customconfig/ace-pipeline.yaml",
"loc": {
"range": [
1442,
1456,
1457
],
"startLine": 57,
"startColumn": 15,
"endLine": 57,
"endColumn": 29
}
}
]
26 changes: 24 additions & 2 deletions regression-tests/regression.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import fs from 'node:fs'
import fg from 'fast-glob';
import path from 'node:path';
import {Problem, Config, Linter} from '../src/index'

const pattern = "./regression-tests/general/*.yaml"
const yamlfiles = fg.globSync(pattern)
const customconfig = "./regression-tests/customconfig/*.yaml"


describe("Regression Tests",()=>{
describe("Default Config Regression Tests",()=>{

test.each(yamlfiles)("%s",async (yamlSrcPath)=>{
const cfg: Config = Config.getDefaultConfig()
Expand All @@ -23,4 +24,25 @@ describe("Regression Tests",()=>{
expect(problems).toMatchObject(expected)
})

})

describe("Custom Config Regression Tests",()=>{

test.each(fg.globSync(customconfig))("%s", async (yamlSrcPath)=>{
const cfgPath = path.resolve(path.dirname(yamlSrcPath))
const cfg: Config = Config.getConfig(cfgPath);
cfg.globs=[yamlSrcPath]
const problems: Problem[] = await Linter.run(cfg)

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

if (!fs.existsSync(expectedPath)){
fs.writeFileSync(expectedPath, JSON.stringify(problems))
}

const expected = JSON.parse(fs.readFileSync(expectedPath,'utf-8'))
expect(problems).toMatchObject(expected)

})

})
9 changes: 8 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,18 +103,25 @@ export class Config {

public static getDefaultConfig(): Config {
// create default cli-proxy
return Config.getConfig(process.cwd());
}

public static getConfig(dir: string): Config {
// create default cli-proxy

const argv = {
watch: false,
color: true,
format: json,
quite: false,
config: process.cwd(),
config: dir,
'refresh-cache': false,

_: [],
};

const config = new Config(argv);
return config;
}

}
1 change: 1 addition & 0 deletions src/default-rule-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const defaultRules: RulesConfig = {
'no-latest-image': 'warning',
'prefer-beta': 'warning',
'prefer-kebab-case': 'warning',
'prefer-camel-kebab-case': 'off',
'no-unused-param': 'warning',
'no-missing-resource': 'error',
'no-undefined-param': 'error',
Expand Down
3 changes: 3 additions & 0 deletions src/rule-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ const defaultRules = {
// prefer-kebab-case
'prefer-kebab-case': (await import('./rules/prefer-kebab-case.js')).default,

// prefer-camel-kebab-case
'prefer-camel-kebab-case': (await import('./rules/prefer-camel-kebab-case.js')).default,

// prefer-when-expression
'prefer-when-expression': (await import('./rules/prefer-when-expression.js')).default,

Expand Down
65 changes: 65 additions & 0 deletions src/rules/prefer-camel-kebab-case.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { walk, pathToString } from '../walk.js';

const isValidKebabName = (name) => {
const valid = new RegExp('^[a-z0-9-()$.]*$');
return valid.test(name);
};

const isValidCamelName = (name) => {
const valid = new RegExp('^[a-z_][a-z0-9A-Z()$.]*$');
return valid.test(name);
};

const isValidName = (name) => {
return isValidKebabName(name) || isValidCamelName(name);
};

const naming = (resource, prefix, report) => (node, path, parent) => {
let name = node;
const isNameDefinition = /.name$/.test(path);

if (path.includes('env') && path.includes('name')) return;

if (isNameDefinition && !isValidName(name)) {
report(
`Invalid name for '${name}' at ${pathToString(
path,
)} in '${resource}'. Names should be in lowercase, alphanumeric, kebab-case or camelCase format.`,
parent,
'name',
);
return;
}

const parameterPlacementRx = new RegExp(`\\$\\(${prefix}.(.*?)\\)`);
const m = node && node.toString().match(parameterPlacementRx);

if (m) {
name = m[1];
if (!isValidName(name)) {
report(
`Invalid name for '${name}' at ${pathToString(
path,
)} in '${resource}'. Names should be in lowercase, alphanumeric, kebab-case or camelCase format.`,
parent,
path[path.length - 1],
);
}
}
};

export default (docs, tekton, report) => {
for (const pipeline of Object.values<any>(tekton.pipelines)) {
walk(pipeline.spec.tasks, ['spec', 'tasks'], naming(pipeline.metadata.name, 'params', report));
walk(pipeline.spec.finally, ['spec', 'finally'], naming(pipeline.metadata.name, 'params', report));
}

for (const pipeline of Object.values<any>(tekton.pipelineRuns)) {
walk(pipeline.spec.tasks, ['spec', 'pipelineSpec', 'tasks'], naming(pipeline.metadata.name, 'params', report));
walk(
pipeline.spec.finally,
['spec', 'pipelineSpec', 'finally'],
naming(pipeline.metadata.name, 'params', report),
);
}
};
Loading