Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

dockerfilelint

Package Overview
Dependencies
Maintainers
1
Versions
19
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

dockerfilelint - npm Package Compare versions

Comparing version 1.0.0 to 1.1.1

.dockerfilelintrc

121

lib/index.js
'use strict';
var path = require('path');
var fs = require('fs');
var yaml = require('js-yaml');
var checks = require('./checks');
var run_checks = require('./run_checks');
var command_parser = require('./command_parser');
var apk = require('./apk');
var apt = require('./apt');
var messages = require('./messages');
module.exports.run = function(content) {
module.exports.run = function(configPath, content) {
// Parse the file into an array of instructions

@@ -50,3 +55,4 @@ var instructions = {};

cmdFound: false,
items: []
items: [],
rules: loadRules(configPath)
}

@@ -70,2 +76,25 @@

function loadRules(configPath) {
// Find any .dockerfilelintrc
var configFileContents = tryLoadFile(path.join(configPath, '.dockerfilelintrc'))
if (!configFileContents) {
return {};
}
var rc = yaml.safeLoad(configFileContents);
return rc.rules;
}
function tryLoadFile(filename) {
try {
var stats = fs.lstatSync(filename);
if (stats.isFile()) {
return fs.readFileSync(filename, 'UTF-8');
}
return null;
} catch (e) {
return null;
}
}
function runLine(state, instructions, idx) {

@@ -79,3 +108,3 @@ // return items in an object with the line number as key, value is array of items for this line

if (instruction.trim().match(/\S+/g).length === 1) {
items.push(messages.build('required_params', line));
items.push(messages.build(state.rules, 'required_params', line));
}

@@ -88,3 +117,3 @@

if (instruction.trim().match(/\S+/g)[0] !== cmd.toUpperCase()) {
items.push(messages.build('uppercase_commands', line));
items.push(messages.build(state.rules, 'uppercase_commands', line));
}

@@ -95,3 +124,3 @@

if ((state.instructionsProcessed === 0 && cmd !== 'from') || (state.instructionsProcessed !== 0 && cmd === 'from')) {
items.push(messages.build('from_first', line));
items.push(messages.build(state.rules, 'from_first', line));
}

@@ -103,3 +132,3 @@

if (!args) {
items.push(messages.build('invalid_line', line));
items.push(messages.build(state.rules, 'invalid_line', line));
}

@@ -111,3 +140,3 @@

if (arg.trim().toLowerCase() === 'sudo') {
items.push(messages.build('sudo_usage', line));
items.push(messages.build(state.rules, 'sudo_usage', line));
}

@@ -120,3 +149,3 @@ }.bind(this));

checks.base_image_tag(args).forEach(function(item) {
items.push(messages.build(item, line));
items.push(messages.build(state.rules, item, line));
});

@@ -126,3 +155,3 @@ break;

checks.valid_maintainer(args).forEach(function(item) {
items.push(messages.build(item, line));
items.push(messages.build(state.rules, item, line));
});

@@ -132,9 +161,11 @@ break;

var aptgetSubcommands = [];
var commands = run_checks.split_commands(args);
run_checks.aptget_commands(commands).forEach(function(aptget_command, index) {
var subcommand = run_checks.aptget_subcommand(aptget_command);
var commands = command_parser.split_commands(args);
// parse apt commands
apt.aptget_commands(commands).forEach(function(aptget_command, index) {
var subcommand = apt.aptget_subcommand(aptget_command);
aptgetSubcommands.push(subcommand);
if (["install", "remove", "upgrade"].indexOf(subcommand) > -1) {
if (!run_checks.aptget_hasyes(aptget_command)) {
items.push(messages.build('apt-get_missing_param', line));
if (!apt.aptget_hasyes(aptget_command)) {
items.push(messages.build(state.rules, 'apt-get_missing_param', line));
}

@@ -144,26 +175,43 @@ }

if (subcommand === 'install') {
if (!run_checks.aptget_hasnorecommends(aptget_command)) {
items.push(messages.build('apt-get_recommends', line));
if (!apt.aptget_hasnorecommends(aptget_command)) {
items.push(messages.build(state.rules, 'apt-get_recommends', line));
}
} else if (subcommand === 'update') {
if (!run_checks.follows_rmaptlists(commands, index)) {
items.push(messages.build('apt-get_missing_rm', line));
if (!apt.follows_rmaptlists(commands, index)) {
items.push(messages.build(state.rules, 'apt-get_missing_rm', line));
}
} else if (subcommand === 'upgrade') {
items.push(messages.build('apt-get-upgrade', line));
items.push(messages.build(state.rules, 'apt-get-upgrade', line));
} else if (subcommand === 'dist-upgrade') {
items.push(messages.build('apt-get-dist-upgrade', line));
items.push(messages.build(state.rules, 'apt-get-dist-upgrade', line));
}
});
if (aptgetSubcommands.indexOf('update') > -1 && aptgetSubcommands.indexOf('install') === -1) {
items.push(messages.build('apt-get-update_require_install', line));
items.push(messages.build(state.rules, 'apt-get-update_require_install', line));
}
run_checks.apk_commands(commands).forEach(function(apk_command, index) {
var subcommand = run_checks.apk_subcommand(apk_command);
// parse apk commands
var num_apkdel = 0;
apk.apk_commands(commands).forEach(function(apk_command) {
var subcommand = apk.apk_subcommand(apk_command);
if (subcommand === 'del') {
num_apkdel++;
}
});
apk.apk_commands(commands).forEach(function(apk_command, index) {
var subcommand = apk.apk_subcommand(apk_command);
if (subcommand === 'add') {
if (!run_checks.apkadd_hasnocache(apk_command)) {
if (!run_checks.apkadd_hasupdate(apk_command) || !run_checks.follows_rmapkcache(commands, index)) {
items.push(messages.build('apkadd-missing_nocache_or_updaterm', line));
// If there is more than 1 package we are adding and we are also del'ing, we should suggest --virtual
if (apk.apkadd_numpackages(apk_command) > 1) {
if (num_apkdel > 0) {
if (!apk.apkadd_hasvirtual(apk_command)) {
items.push(messages.build(state.rules, 'apkadd-missing-virtual', line));
}
}
}
if (!apk.apkadd_hasnocache(apk_command)) {
if (!apk.apkadd_hasupdate(apk_command) || !apk.follows_rmapkcache(commands, index)) {
items.push(messages.build(state.rules, 'apkadd-missing_nocache_or_updaterm', line));
}
}
}

@@ -176,3 +224,3 @@ });

checks.label_format(args).forEach(function(item) {
items.push(messages.build(item, line));
items.push(messages.build(state.rules, item, line));
});

@@ -182,3 +230,3 @@ break;

checks.expose_container_port_only(args).forEach(function(item) {
items.push(messages.build(item, line));
items.push(messages.build(state.rules, item, line));
});

@@ -188,3 +236,3 @@ args.match(/\S+/g).forEach(function(port) {

if (!port.includes(':')) { // Just eliminate a double message here
items.push(messages.build('invalid_port', line));
items.push(messages.build(state.rules, 'invalid_port', line));
}

@@ -196,3 +244,3 @@ }

checks.is_valid_env(args).forEach(function(item) {
items.push(messages.build(item, line));
items.push(messages.build(state.rules, item, line));
});

@@ -202,3 +250,3 @@ break;

checks.is_valid_add(args).forEach(function(item) {
items.push(messages.build(item, line));
items.push(messages.build(state.rules, item, line));
});

@@ -214,3 +262,3 @@ break;

checks.valid_user(args).forEach(function(item) {
items.push(messages.build(item, line));
items.push(messages.build(state.rules, item, line));
});

@@ -220,3 +268,3 @@ break;

checks.is_valid_workdir(args).forEach(function(item) {
items.push(messages.build(item, line));
items.push(messages.build(state.rules, item, line));
});

@@ -231,3 +279,3 @@ break;

default:
items.push(messages.build('invalid_command', line));
items.push(messages.build(state.rules, 'invalid_command', line));
break;

@@ -237,2 +285,5 @@ }

// Remove any null items from the result
items = items.filter(function(n){ return n != null });
return {

@@ -239,0 +290,0 @@ command: cmd,

@@ -5,3 +5,17 @@

var messages = module.exports = {
build: function(name, line) {
parseBool: function(s) {
s = s.toLowerCase();
if (s === 'off' || s === 'false' || s === '0' || s === 'n') {
return false;
}
return true;
},
build: function(rules, name, line) {
if (name in rules) {
if (!this.parseBool(rules[name])) {
return null;
}
}
var message = reference[name];

@@ -16,4 +30,5 @@ if (!message) {

message.line = line;
message.rule = name;
return message;
},
};

@@ -90,2 +90,8 @@ var reference = module.exports = {

},
'apkadd-missing-virtual': {
'title': 'Consider `--virtual` when using apk add and del together in a command.',
'description': `Consider using a \`--virtual\` or \`-t\` switch to group multiple packages for easy cleanup. This will help ensure future authors will continue to clean up build dependencies and other temporary packages. For
more information, see [this link](https://github.com/gliderlabs/docker-alpine/blob/master/docs/usage.md#virtual-packages)`,
'category': 'Optimization'
},
'invalid_port': {

@@ -92,0 +98,0 @@ 'title': 'Invalid Port Exposed',

{
"name": "dockerfilelint",
"version": "1.0.0",
"version": "1.1.1",
"description": "A linter for Dockerfiles to find bugs and encourage best practices",

@@ -31,2 +31,3 @@ "main": "./lib/index.js",

"cliui": "^3.1.0",
"js-yaml": "^3.6.0",
"lodash": "^4.3.0",

@@ -33,0 +34,0 @@ "yargs": "^3.32.0"

@@ -12,3 +12,3 @@ # Linter and validator for Dockerfile

## Running
From the command line:
#### From the command line:
```shell

@@ -18,4 +18,49 @@ ./bin/dockerfilelint <path/to/Dockerfile>

If you don't want to install this locally you can try it out [here](https://www.fromlatest.io/#/).
#### Configuring
You can configure the linter by creating a `.dockerfilelintrc` with the following syntax:
```yaml
rules:
uppercase_commands: off
```
The keys for the rules can be any file in the /lib/reference.js file. At this time, it's only possible to disable rules. They are all enabled by default.
The following rules are supported:
```
required_params
uppercase_commands
from_first
invalid_line
sudo_usage
apt-get_missing_param
apt-get_recommends
apt-get-upgrade
apt-get-dist-upgrade
apt-get-update_require_install
apkadd-missing_nocache_or_updaterm
apkadd-missing-virtual
invalid_port
invalid_command
expose_host_port
label_invalid
missing_tag
latest_tag
extra_args
missing_args
add_src_invalid
add_dest_invalid
invalid_workdir
invalid_format
apt-get_missing_rm
```
#### From a Docker container
(Replace the ``pwd``/Dockerfile with the path to your local Dockerfile)
```shell
sudo docker run -v `pwd`/Dockerfile:/Dockerfile dockerfilelint /Dockerfile
```
#### Online
If you don't want to install this locally you can try it out on [https://fromlatest.io](https://www.fromlatest.io/#/).
## Checks performed

@@ -41,3 +86,3 @@ ### `FROM`

- [x] apk add commands should include a `--no-cache` flag or be paired with an `--update` flag with `rm -rf /var/cache/apk/*` in the same layer
- [ ] apk add support for --virtual flag
- [x] apk add support for --virtual flag
- [ ] handle best practices for yum operations and cleanup

@@ -44,0 +89,0 @@

@@ -101,3 +101,3 @@ 'use strict';

expect(fileReport.uniqueIssues).to.equal(3);
expect(fileReport.contentArray).to.have.length(33);
expect(fileReport.contentArray).to.have.length(34);
expect(fileReport.itemsByLine).to.deep.equal({

@@ -130,3 +130,3 @@ '5': [ items[0] ],

expect(fileReport.uniqueIssues).to.equal(1);
expect(fileReport.contentArray).to.have.length(33);
expect(fileReport.contentArray).to.have.length(34);
expect(fileReport.itemsByLine).to.deep.equal({

@@ -176,3 +176,3 @@ '6': [ items[0] ]

expect(file2Report.uniqueIssues).to.equal(2);
expect(file2Report.contentArray).to.have.length(33);
expect(file2Report.contentArray).to.have.length(34);
expect(file2Report.itemsByLine).to.deep.equal({

@@ -250,3 +250,3 @@ '5': [ file2Items[0] ],

category: 'Deprecation',
line: 20
line: 21
}

@@ -276,3 +276,3 @@ ];

'',
'Line 20: ' + chalk.magenta('EXPOSE 80:80'),
'Line 21: ' + chalk.magenta('EXPOSE 80:80'),
'Issue Category Title Description',

@@ -279,0 +279,0 @@ ' ' + chalk.red('4') + ' ' + chalk.red.inverse('Deprecation') + ' ' + chalk.red('Expose Only') + ' ' + chalk.gray('Using `EXPOSE` to specify a host port is not allowed.'),

@@ -9,3 +9,3 @@ var expect = require('chai').expect

it("validates the busybox Dockerfile has no issues", function(){
expect(dockerfilelint.run(fs.readFileSync('./test/examples/Dockerfile.busybox', 'UTF-8'))).to.be.empty;
expect(dockerfilelint.run('./test/examples', fs.readFileSync('./test/examples/Dockerfile.busybox', 'UTF-8'))).to.be.empty;
});

@@ -16,3 +16,3 @@ });

it("validates the debian Dockerfile has no issues", function(){
expect(dockerfilelint.run(fs.readFileSync('./test/examples/Dockerfile.debian', 'UTF-8'))).to.be.empty;
expect(dockerfilelint.run('./test/invalid', fs.readFileSync('./test/examples/Dockerfile.debian', 'UTF-8'))).to.be.empty;
});

@@ -23,3 +23,3 @@ });

it("validates the mongo Dockerfile has 1 issue (known issue)", function(){
expect(dockerfilelint.run(fs.readFileSync('./test/examples/Dockerfile.mongo', 'UTF-8'))).to.have.length(1);
expect(dockerfilelint.run('./test/examples', fs.readFileSync('./test/examples/Dockerfile.mongo', 'UTF-8'))).to.have.length(1);
});

@@ -30,3 +30,3 @@ });

it("validates the mysql Dockerfile has 1 issue (known issue)", function(){
expect(dockerfilelint.run(fs.readFileSync('./test/examples/Dockerfile.mysql', 'UTF-8'))).to.have.length(1);
expect(dockerfilelint.run('./test/examples', fs.readFileSync('./test/examples/Dockerfile.mysql', 'UTF-8'))).to.have.length(1);
});

@@ -37,3 +37,3 @@ });

it("validates the nginx Dockerfile has 1 issue (known issue)", function(){
expect(dockerfilelint.run(fs.readFileSync('./test/examples/Dockerfile.nginx', 'UTF-8'))).to.have.length(1);
expect(dockerfilelint.run('./test/examples', fs.readFileSync('./test/examples/Dockerfile.nginx', 'UTF-8'))).to.have.length(1);
});

@@ -44,3 +44,3 @@ });

it("validates the node Dockerfile has no issues", function(){
expect(dockerfilelint.run(fs.readFileSync('./test/examples/Dockerfile.node', 'UTF-8'))).to.be.empty;
expect(dockerfilelint.run('./test/examples', fs.readFileSync('./test/examples/Dockerfile.node', 'UTF-8'))).to.be.empty;
});

@@ -51,3 +51,3 @@ });

it("validates the redis Dockerfile has no issues", function(){
expect(dockerfilelint.run(fs.readFileSync('./test/examples/Dockerfile.redis', 'UTF-8'))).to.be.empty;
expect(dockerfilelint.run('./test/examples', fs.readFileSync('./test/examples/Dockerfile.redis', 'UTF-8'))).to.be.empty;
});

@@ -58,3 +58,3 @@ });

it("validates the mysql Dockerfile has 1 issue (known issue)", function(){
expect(dockerfilelint.run(fs.readFileSync('./test/examples/Dockerfile.registry', 'UTF-8'))).to.have.length(1);
expect(dockerfilelint.run('./test/examples', fs.readFileSync('./test/examples/Dockerfile.registry', 'UTF-8'))).to.have.length(1);
});

@@ -65,3 +65,3 @@ });

it("validates the swarm Dockerfile has no issues", function(){
expect(dockerfilelint.run(fs.readFileSync('./test/examples/Dockerfile.swarm', 'UTF-8'))).to.be.empty;
expect(dockerfilelint.run('./test/examples', fs.readFileSync('./test/examples/Dockerfile.swarm', 'UTF-8'))).to.be.empty;
});

@@ -72,3 +72,3 @@ });

it("validates the ubuntu Dockerfile has no issues", function(){
expect(dockerfilelint.run(fs.readFileSync('./test/examples/Dockerfile.ubuntu', 'UTF-8'))).to.be.empty;
expect(dockerfilelint.run('./test/examples', fs.readFileSync('./test/examples/Dockerfile.ubuntu', 'UTF-8'))).to.be.empty;
});

@@ -81,60 +81,91 @@ });

{ title: 'First Command Must Be FROM',
rule: 'from_first',
line: 6 },
{ title: 'First Command Must Be FROM',
rule: 'from_first',
line: 6 },
{ title: 'Base Image Missing Tag',
rule: 'missing_tag',
line: 5 },
{ title: 'First Command Must Be FROM',
rule: 'from_first',
line: 6 },
{ title: 'Base Image Latest Tag',
rule: 'latest_tag',
line: 6 },
{ title: 'Capitalize Dockerfile Instructions',
rule: 'uppercase_commands',
line: 7 },
{ title: 'Invalid Command',
rule: 'invalid_command',
line: 7 },
{ title: 'Missing Arguments',
rule: 'missing_args',
line: 9 },
{ title: 'Missing Required Arguments',
rule: 'required_params',
line: 11 },
{ title: 'Invalid Line',
rule: 'invalid_line',
line: 11 },
{ title: 'Use Of sudo Is Not Allowed',
rule: 'sudo_usage',
line: 12 },
{ title: 'Missing parameter for `apt-get`',
rule: 'apt-get_missing_param',
line: 14 },
{ title: '`apt-get upgrade` Is Not Allowed',
rule: 'apt-get-upgrade',
line: 13 },
{ title: 'Missing parameter for `apt-get`',
rule: 'apt-get_missing_param',
line: 14 },
{ title: 'Consider `--no-install-recommends`',
rule: 'apt-get_recommends',
line: 14 },
{ title: 'apt-get dist-upgrade Is Not Allowed',
rule: 'apt-get-dist-upgrade',
line: 15 },
{ title: 'apt-get update with matching cache rm',
rule: 'apt-get_missing_rm',
line: 16 },
{ title: 'apt-get update without matching apt-get install',
rule: 'apt-get-update_require_install',
line: 16 },
{ title: 'Consider `--no-cache or --update with rm -rf /var/cache/apk/*`',
line: 17 },
{ title: 'Invalid Port Exposed',
line: 19 },
{ title: 'Expose Only Container Port',
line: 20 },
{ title: 'Invalid Argument Format',
line: 22 },
{ title: 'Label Is Invalid',
line: 23 },
{ title: 'Label Is Invalid',
line: 23 },
{ title: 'Extra Arguments',
line: 25 },
{ title: 'Invalid WORKDIR',
line: 26 },
{ title: 'Invalid ADD Source',
line: 30 },
{ title: 'Invalid ADD Destination',
line: 30 }
{ title: 'Consider `--no-cache or --update with rm -rf /var/cache/apk/*`',
rule: 'apkadd-missing_nocache_or_updaterm',
line: 17 },
{ title: 'Consider `--virtual` when using apk add and del together in a command.',
rule: 'apkadd-missing-virtual',
line: 18 },
{ title: 'Invalid Port Exposed',
rule: 'invalid_port',
line: 20 },
{ title: 'Expose Only Container Port',
rule: 'expose_host_port',
line: 21 },
{ title: 'Invalid Argument Format',
rule: 'invalid_format',
line: 23 },
{ title: 'Label Is Invalid',
rule: 'label_invalid',
line: 24 },
{ title: 'Label Is Invalid',
rule: 'label_invalid',
line: 24 },
{ title: 'Extra Arguments',
rule: 'extra_args',
line: 26 },
{ title: 'Invalid WORKDIR',
rule: 'invalid_workdir',
line: 27 },
{ title: 'Invalid ADD Source',
rule: 'add_src_invalid',
line: 31 },
{ title: 'Invalid ADD Destination',
rule: 'add_dest_invalid',
line: 31 }
];
var result = dockerfilelint.run(fs.readFileSync('./test/examples/Dockerfile.misc', 'UTF-8'));
var result = dockerfilelint.run('./test/examples', fs.readFileSync('./test/examples/Dockerfile.misc', 'UTF-8'));
expect(result).to.have.length(expected.length);

@@ -141,0 +172,0 @@

@@ -5,8 +5,15 @@ var expect = require('chai').expect

describe("messages", function(){
describe("#ignore_works", function(){
it("validates that disabled commands are ignored", function(){
expect(messages.build({latest_tag: 'off'}, 'latest_tag', 1)).to.be.null;
expect(messages.build({latest_tag: 'TRUE'}, 'latest_tag', 1)).to.have.property('line', 1);
});
}),
describe("#build_a_message", function(){
it("validates that line numbers are added to messages", function(){
expect(messages.build('latest_tag', 1)).to.have.property('line', 1);
expect(messages.build('', 1)).to.have.property('line', 1);
expect(messages.build({}, 'latest_tag', 1)).to.have.property('line', 1);
expect(messages.build({}, '', 1)).to.have.property('line', 1);
});
});
});

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc