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

Codeclimate #590

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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
5 changes: 5 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/.*
/node_modules
/appscan-config.xml
/*.md
/LICENSE
15 changes: 15 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
image-build:
image:
name: quay.io/buildah/stable:latest
stage: build
before_script:
- 'echo "$CI_REGISTRY_PASSWORD" | buildah login --username "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY'
script:
- buildah bud
-f "Containerfile"
-t "$CI_REGISTRY_IMAGE/codeclimate-openapi:latest"
--layers
--cache-from=$CI_REGISTRY_IMAGE/codeclimate-openapi/cache
--cache-to=$CI_REGISTRY_IMAGE/codeclimate-openapi/cache
.
- buildah push "$CI_REGISTRY_IMAGE/codeclimate-openapi:latest"
19 changes: 19 additions & 0 deletions Containerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM node:current-alpine3.17 AS builder

COPY package.json package-lock.json ./
RUN npm ci

COPY . ./
RUN cd packages/validator && npm pack && mv ibm-openapi-validator-*.tgz /tmp/ibm-openapi-validator-latest.tgz

FROM node:current-alpine3.17

RUN apk add --no-cache git

COPY --from=builder /tmp/ibm-openapi-validator-latest.tgz /tmp
# not possible to install from github directly https://github.com/npm/npm/issues/2974
RUN npm install -g /tmp/ibm-openapi-validator-latest.tgz \
&& npm cache clean --force

WORKDIR /code
ENTRYPOINT ["lint-openapi", "--config", "/config.json", "--codeclimate"]
23 changes: 15 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ The IBM OpenAPI Validator lets you validate OpenAPI 3.x documents according to t
- [Validator Output](#validator-output)
* [Text](#text)
* [JSON](#json)
* [CodeClimate](#codeclimate)
- [Logging](#logging)
- [Contributing](#contributing)
- [License](#license)
Expand Down Expand Up @@ -115,6 +116,7 @@ Options:
-e, --errors-only include only errors in the output and skip warnings (default is false)
-i, --ignore <file> avoid validating <file> (e.g. -i /dir1/ignore-file1.json --ignore /dir2/ignore-file2.yaml ...) (default is []) (default: [])
-j, --json produce JSON output (default is text)
--codeclimate produce JSON output according to CodeClimate spec
-l, --log-level <loglevel> set the log level for one or more loggers (e.g. -l root=info -l ibm-schema-description-exists=debug ...) (default: [])
-n, --no-colors disable colorizing of the output (default is false)
-r, --ruleset <file> use Spectral ruleset contained in `<file>` ("default" forces use of default IBM Cloud Validation Ruleset)
Expand All @@ -130,18 +132,18 @@ The validator supports OpenAPI documents in either JSON or YAML format, using th
.yaml
.yml
```
Assuming your command shell supports the use of wildcards, you can use wildcards when specifying the names of files to be validated.
If the string ends with '/', it will be searched recursively for supported files.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary for the code climate support?

Copy link
Contributor Author

@Blaimi Blaimi May 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not at all, but a config.json passed by codeclimate file looks like this:

{
  "enabled":true,
  "channel":"stable",
  "include_paths": [
    ".gitignore",
    "public/",
    ".gitattributes",
    "LICENSE",
    ".gitlab-ci.yml",
    "Dockerfile",
    "docker-entrypoint.d/",
    ".codeclimate.yml"
  ],
  "debug":"1"
}

when executing in biletado/apidocs.

One can still add a files section in .codeclimate.yml like I do at the moment for testing until the implementation for the recursive filesearch is implemented. This is then added to the config.json. You can have a look at the .codeclimate.yml I used in the mentioned project for testing this as an engine.

For example, to run the validator on all `.yaml` files contained in the `/my/apis` directory, you could use
this command:
```bash
lint-openapi /my/apis/*.yaml
lint-openapi /my/apis/
```

Note that the `-i`/`--ignore` option can be particularly useful when using wildcards because it allows you to skip the
Note that the `-i`/`--ignore` option can be particularly useful when using wildcards or directories because it allows you to skip the
validation of specific files which might otherwise be included in a validation run.
For example, to validate all `.yaml` files in the `/my/apis` directory, except for `/my/apis/broken-api.yaml` use the command:
```bash
lint-openapi /my/apis/*.yaml -i /my/apis/broken-api.yaml
lint-openapi /my/apis/ -i /my/apis/broken-api.yaml
```

### Configuration
Expand Down Expand Up @@ -269,7 +271,8 @@ module.exports = {
<tr>
<td>The <code>files</code> configuration property corresponds to positional command-line arguments (i.e. <code>[file...]</code>).
You can set this property to the names of the OpenAPI documents to be validated. If any filenames are also entered as positional arguments
on the command-line, they will override any values specified in this configuration property.</td>
on the command-line, they will override any values specified in this configuration property.
`input_path` is an alternative key for `files`. If both are set, `input_paths` will be used.</td>
<td><code>[]</code>(empty list)</td>
</tr>
</table>
Expand Down Expand Up @@ -482,9 +485,9 @@ module.exports = {
<td width=25%><b>Default</b></td>
</tr>
<tr>
<td>You can set the <code>outputFormat</code> configuration property to either <code>text</code> or <code>json</code>
<td>You can set the <code>outputFormat</code> configuration property to either <code>text</code>, <code>json</code> or <code>codeclimate</code>
to indicate the type of output you want the validator to produce.
This property corresponds to the <code>-j</code>/<code>--json</code> command-line option.</td>
This property corresponds to the <code>-j</code>/<code>--json</code>/<code>--codeclimate</code> command-line option.</td>
<td><code>text</code></td>
</tr>
</table>
Expand Down Expand Up @@ -621,7 +624,7 @@ module.exports = {

## Validator Output
The validator can produce output in either text or JSON format. The default is `text` output, and this can be
controlled with the `-j`/`--json` command-line option or `outputFormat` configuration property.
controlled with the `-j`/`--json`/`--codeclimate` command-line option or `outputFormat` configuration property.

### Text
Here is an example of text output:
Expand Down Expand Up @@ -753,6 +756,10 @@ Here is an example of JSON output:
The JSON output is also affected by the `-s`/`--summary-only` and `-e`/`--errors-only` options as well as the `summaryOnly` and `errorsOnly`
configuration properties.

### CodeClimate
When displaying CodeClimate JSON output, the validator will produce a null-byte separated stream of JSON objects
which complies with [the CodeClimate Output format](https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#output).

## Logging
The validator uses a *logger* for displaying messages on the console.
The core validator uses a single logger named `root`, while each of the rules contained in the
Expand Down
18 changes: 11 additions & 7 deletions packages/validator/src/cli-validator/run-validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const ext = require('./utils/file-extension-validator');
const preprocessFile = require('./utils/preprocess-file');
const print = require('./utils/print-results');
const { printJson } = require('./utils/json-results');
const { printCCJson } = require('./utils/codeclimate-results');
const { runSpectral } = require('../spectral/spectral-validator');
const getCopyrightString = require('./utils/get-copyright-string');

Expand Down Expand Up @@ -72,14 +73,17 @@ async function runValidator(cliArgs, parseOptions = {}) {

context.chalk = chalk;

if (context.config.outputFormat !== 'json') {
if (context.config.outputFormat === 'text') {
console.log(getCopyrightString());
}

//
// Run the validator on the files specified via command-line or config file.
//

// TODO: implement recursive filesearch when arg ends with '/'
// must be done before filtering

// Ignore files listed in the config object's "ignoreFiles" field
// by comparing absolute paths.
// "filteredArgs" will be "args" minus any ignored files.
Expand All @@ -97,8 +101,6 @@ async function runValidator(cliArgs, parseOptions = {}) {

// At this point, "args" is an array of file names passed in by the user,
// but with the ignored files removed.
// Nothing in "args" will be a glob type, as glob types are automatically
// converted to arrays of matching file names by the shell.
const supportedFileTypes = ['json', 'yml', 'yaml'];
const filesWithValidExtensions = [];
let unsupportedExtensionsFound = false;
Expand Down Expand Up @@ -157,7 +159,7 @@ async function runValidator(cliArgs, parseOptions = {}) {
let originalFile;
let input;

if (context.config.outputFormat != 'json') {
if (context.config.outputFormat === 'text') {
console.log('');
console.log(chalk.underline(`Validation Results for ${validFile}:\n`));
}
Expand Down Expand Up @@ -202,15 +204,15 @@ async function runValidator(cliArgs, parseOptions = {}) {

// Check to see if we should be passing back a non-zero exit code.
if (results.error.summary.total) {
// If we have any errors, then exit code 1 is returned.
exitCode = 1;
// If we have any errors, then exit code 1 is returned, except when running for codeclimate.
exitCode = context.config.outputFormat === 'codeclimate' ? 0 : 1;
}

// If the # of warnings exceeded the warnings limit, then this is an error.
const numWarnings = results.warning.summary.total;
const warningsLimit = context.config.limits.warnings;
if (warningsLimit >= 0 && numWarnings > warningsLimit) {
exitCode = 1;
exitCode = context.config.outputFormat === 'codeclimate' ? 0 : 1;
logger.error(
`Number of warnings (${numWarnings}) exceeds warnings limit (${warningsLimit}).`
);
Expand All @@ -219,6 +221,8 @@ async function runValidator(cliArgs, parseOptions = {}) {
// Now print the results, either JSON or text.
if (context.config.outputFormat === 'json') {
printJson(context, results);
} else if (context.config.outputFormat === 'codeclimate') {
printCCJson(validFile, results);
} else {
if (results.hasResults) {
print(context, results);
Expand Down
4 changes: 4 additions & 0 deletions packages/validator/src/cli-validator/utils/cli-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ function createCLIOptions() {
[]
)
.option('-j, --json', 'produce JSON output (default is text)')
.option(
'--codeclimate',
'produce JSON output according to CodeClimate spec'
)
.option(
'-l, --log-level <loglevel>',
'set the log level for one or more loggers (e.g. -l root=info -l ibm-schema-description-exists=debug ...) ',
Expand Down
47 changes: 47 additions & 0 deletions packages/validator/src/cli-validator/utils/codeclimate-results.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Copyright 2023 IBM Corporation, Matthias Blümel.
* SPDX-License-Identifier: Apache2.0
*/

const each = require('lodash/each');

function printCCJson(validFile, results) {
const types = ['error', 'warning', 'info', 'hint'];
const ccTypeMap = {
error: 'critical',
warning: 'major',
info: 'minor',
hint: 'info',
};

types.forEach(type => {
each(results[type].results, result => {
let content;
if (result.path.length !== 0) {
let markdown = '';
each(result.path, pathItem => {
markdown += '* ' + pathItem + '\n';
});
content = { body: markdown };
}
const ccResult = {
type: 'issue',
check_name: result.rule,
description: result.message,
content: content,
categories: ['Style'], // required by codeclimate, ignored by gitlab; has to be defined by the rule.
location: {
path: validFile,
lines: {
begin: result.line,
end: result.line,
},
},
severity: ccTypeMap[type],
};
console.log(JSON.stringify(ccResult) + '\0\n');
});
});
}

module.exports.printCCJson = printCCJson;
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ const defaultConfig = {
files: [
// 'my-api.yaml'
],
input_paths: [
// alternative to files for CodeClimate compatibility
],
limits: {
warnings: -1,
},
Expand Down Expand Up @@ -169,9 +172,9 @@ async function processArgs(args, cliParseOptions) {
// so overlay CLI options onto our config object.

// Filenames specified on the command-line will be in the "args" field.
const cliFiles = command.args || [];
if (cliFiles.length) {
configObj.files = cliFiles;
const prioFiles = command.args || configObj.input_paths || [];
if (prioFiles.length) {
configObj.files = prioFiles;
}

// Process each loglevel entry supplied on the command line.
Expand Down Expand Up @@ -224,6 +227,10 @@ async function processArgs(args, cliParseOptions) {
configObj.outputFormat = 'json';
}

if ('codeclimate' in opts) {
configObj.outputFormat = 'codeclimate';
}

if ('ruleset' in opts) {
configObj.ruleset = opts.ruleset;
}
Expand Down
15 changes: 12 additions & 3 deletions packages/validator/src/schemas/config-file.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ title: IBM OpenAPI Validator Configuration File Schema
description: >
The structure of the configuration file supported by the IBM OpenAPI Validator
type: object
additionalProperties: false
# allow additionalProperties on root-level to be compatible with CodeClimate config.json
additionalProperties: true
properties:
colorizeOutput:
description: A flag that indicates whether the validator should colorize its output
Expand All @@ -16,9 +17,16 @@ properties:
files:
description: >
The names of the files to be validated.
Each element of the array is a glob-like string that will be evaluated relative
Each element of the array is a string that will be evaluated relative
to the current directory at the time the validator is being run.
Examples: 'a.yaml', '../b.json', '../../c/foo.yml'
If the string ends with '/', it will be searched recursively for supported files.
Each file must end with the extension '.json', '.yaml' or '.yml' and contain a key 'openapi' in it's root level.
Examples: 'a.yaml', '../b.json', '../../c/foo/'
type: array
items:
type: string
include_paths:
description: replaces 'files' when used
type: array
items:
type: string
Expand Down Expand Up @@ -59,6 +67,7 @@ properties:
type: string
enum:
- json
- codeclimate
- text
default: text
ruleset:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"errors": 0,
"warnings": "text",
"population": 10
"population": 10,
"errorsOnly": "yes"
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ describe('run-validator tests', function () {

expect(allOutput).toMatch(/Invalid configuration file/);
expect(allOutput).toMatch(
/schema validation error: must NOT have additional properties/
/schema validation error: '\/errorsOnly': must be boolean/
);
expect(allOutput).toMatch(/validator will use a default config/);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,6 @@ describe('Schema validator tests', function () {

it('invalid config object should return errors', function () {
const configObj = {
xlogLevels: {
root: 'debug',
},
limits: {
xwarnings: 10,
},
Expand All @@ -88,7 +85,7 @@ describe('Schema validator tests', function () {
const results = validate(configObj, configFileSchema);
expect(results).toHaveLength(1);
expect(results[0]).toBe(
`schema validation error: must NOT have additional properties`
`schema validation error: '/limits': must have required property 'warnings'`
);
});
});