dockerfilelint
Advanced tools
Comparing version 0.0.8 to 0.0.9
@@ -122,6 +122,7 @@ 'use strict'; | ||
case 'run': | ||
var subcommands = []; | ||
run_checks.aptget_commands(args).forEach(function(aptget_command) { | ||
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); | ||
subcommands.push(subcommand); | ||
aptgetSubcommands.push(subcommand); | ||
if (["install", "remove", "upgrade"].indexOf(subcommand) > -1) { | ||
@@ -138,3 +139,3 @@ if (!run_checks.aptget_hasyes(aptget_command)) { | ||
} else if (subcommand === 'update') { | ||
if (!run_checks.includes_rmaptlists(args)) { | ||
if (!run_checks.follows_rmaptlists(commands, index)) { | ||
items.push(messages.build('apt-get_missing_rm', line)); | ||
@@ -148,5 +149,15 @@ } | ||
}); | ||
if ((subcommands.indexOf('update') > -1) && (subcommands.indexOf('install') === -1)) { | ||
if (aptgetSubcommands.indexOf('update') > -1 && aptgetSubcommands.indexOf('install') === -1) { | ||
items.push(messages.build('apt-get-update_require_install', line)); | ||
} | ||
run_checks.apk_commands(commands).forEach(function(apk_command, index) { | ||
var subcommand = run_checks.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)); | ||
} | ||
} | ||
} | ||
}); | ||
break; | ||
@@ -153,0 +164,0 @@ case 'cmd': |
@@ -84,2 +84,8 @@ var reference = module.exports = { | ||
}, | ||
'apkadd-missing_nocache_or_updaterm': { | ||
'title': 'Consider `--no-cache or --update with rm -rf /var/cache/apk/*`', | ||
'description': `Consider using a \`--no-cache\` (supported in alpine linux >= 3.3) or \`--update\` followed by the command \`rm -rf /var/cache/apk/*\` when \`apk\` adding packages. This will result in a smaller image size. For | ||
more information, see [this link](https://github.com/gliderlabs/docker-alpine/blob/master/docs/usage.md)`, | ||
'category': 'Optimization' | ||
}, | ||
'invalid_port': { | ||
@@ -86,0 +92,0 @@ 'title': 'Invalid Port Exposed', |
var commands = module.exports = { | ||
// Given a bash command (command && command && command, etc), return an array | ||
// of all apt-get full commands found | ||
aptget_commands: function(args) { | ||
var aptgetCommands = []; | ||
var commands = args.split('&&'); | ||
// return an array of all `prefix` commands found | ||
var filter_commands = function(commands, prefix) { | ||
var prefixCommands = []; | ||
if (commands) { | ||
commands.forEach(function(command) { | ||
command = command.trim(); | ||
// Treat apt-get as a special command and split it apart | ||
if (command.startsWith('apt-get')) { | ||
aptgetCommands.push(command); | ||
if (command.startsWith(prefix)) { | ||
prefixCommands.push(command); | ||
} | ||
}); | ||
} | ||
return prefixCommands; | ||
}; | ||
return aptgetCommands; | ||
var get_subcommand = function(command) { | ||
var subcommand = ''; | ||
command.match(/\S+/g).slice(1).some(function(part) { | ||
if (!part.startsWith('-')) { | ||
subcommand = part; | ||
return true; | ||
} | ||
}); | ||
return subcommand; | ||
}; | ||
var command_has_part = function(command, commandPart) { | ||
return command.match(/\S+/g).some(function(part) { | ||
return part.trim() === commandPart; | ||
}); | ||
}; | ||
var command_has_flag = function(command, commandFlag) { | ||
if (commandFlag.startsWith('--')) { | ||
return command.match(/\S+/g).some(function(part) { | ||
return part.trim() === commandFlag; | ||
}); | ||
} else if (commandFlag.startsWith('-')) { | ||
return command.match(/\S+/g).some(function(part) { | ||
if (part.startsWith('--') || !part.startsWith('-')) { | ||
return false; | ||
} | ||
return part.trim().substr(1).split('').some(function(char) { | ||
return char === commandFlag.substr(1); | ||
}); | ||
}); | ||
} | ||
throw 'bad flag'; | ||
}; | ||
var get_nextcommands = function(commands, index) { | ||
return commands.slice(index+1); | ||
}; | ||
var commands = module.exports = { | ||
// Given a bash command (command && command && command, etc), return an array | ||
// of all commands | ||
split_commands: function(args) { | ||
return args.split('&&').map(function(command) { | ||
return command.trim(); | ||
}); | ||
}, | ||
aptget_commands: function(commands) { | ||
return filter_commands(commands, 'apt-get'); | ||
}, | ||
// return the subcommand from apt-get | ||
aptget_subcommand: function(aptgetCommand) { | ||
var commandParts = aptgetCommand.match(/\S+/g); | ||
if (commandParts.length === 1) { | ||
return ""; | ||
} | ||
// Is the subcommand always immediately after apt-get? | ||
return commandParts[1]; | ||
return get_subcommand(aptgetCommand); | ||
}, | ||
@@ -33,15 +75,5 @@ | ||
aptget_hasyes: function(aptgetCommand) { | ||
var commandParts = aptgetCommand.match(/\S+/g); | ||
if (commandParts.length === 1) { | ||
return ""; | ||
} | ||
var found = false; | ||
commandParts.forEach(function(commandPart) { | ||
if (commandPart.trim().toLowerCase() === "-y") { | ||
found = true; | ||
} | ||
}); | ||
return found; | ||
return command_has_flag(aptgetCommand, '-y') | ||
|| command_has_flag(aptgetCommand, '--yes') | ||
|| command_has_flag(aptgetCommand, '--assume-yes'); | ||
}, | ||
@@ -51,41 +83,67 @@ | ||
aptget_hasnorecommends: function(aptgetCommand) { | ||
var commandParts = aptgetCommand.match(/\S+/g); | ||
if (commandParts.length === 1) { | ||
return ""; | ||
} | ||
return command_has_flag(aptgetCommand, '--no-install-recommends'); | ||
}, | ||
var found = false; | ||
commandParts.forEach(function(commandPart) { | ||
if (commandPart.trim().toLowerCase() === "--no-install-recommends") { | ||
found = true; | ||
// check that command includes a rm -rf /var/lib/apt/lists/* | ||
follows_rmaptlists: function(commands, index) { | ||
return get_nextcommands(commands, index).some(function(command) { | ||
if (command.split(' ')[0] !== 'rm') { | ||
return false; | ||
} | ||
if (!command_has_part(command, '/var/lib/apt/lists/*')) { | ||
return false; | ||
} | ||
if (!command_has_flag(command, '-r') | ||
&& !command_has_flag(command, '-R') | ||
&& !command_has_flag(command, '--recursive')) { | ||
return false; | ||
} | ||
if (!command_has_flag(command, '-f') | ||
&& !command_has_flag(command, '--force')) { | ||
return false; | ||
} | ||
return true; | ||
}); | ||
}, | ||
return found; | ||
apk_commands: function(commands) { | ||
return filter_commands(commands, 'apk'); | ||
}, | ||
// check that command includes a rm -rf /var/lib/apt/lists/* | ||
includes_rmaptlists: function(args) { | ||
var isAllFound = false; | ||
var commands = args.split('&&'); | ||
commands.forEach(function(command) { | ||
command = command.trim(); | ||
if (command.startsWith('rm')) { | ||
var commandParts = command.match(/\S+/g); | ||
var isCorrectPath = false, | ||
isCorrectFlags = false; | ||
commandParts.forEach(function(commandPart) { | ||
if (commandPart.includes('-') && commandPart.includes('r') && commandPart.includes('f')) { | ||
isCorrectFlags = true; | ||
} else if (commandPart === '/var/lib/apt/lists/*') { | ||
isCorrectPath = true; | ||
} | ||
}); | ||
// return the subcommand from apk | ||
apk_subcommand: function(apkCommand) { | ||
return get_subcommand(apkCommand); | ||
}, | ||
isAllFound = isAllFound || (isCorrectFlags && isCorrectPath); | ||
// check that an apk add command has --no-cache flag | ||
apkadd_hasnocache: function(apkaddCommand) { | ||
return command_has_flag(apkaddCommand, '--no-cache'); | ||
}, | ||
// check that an apk add command has --update flag | ||
apkadd_hasupdate: function(apkaddCommand) { | ||
return command_has_flag(apkaddCommand, '--update'); | ||
}, | ||
// check that command includes a rm -rf /var/cache/apk/* | ||
follows_rmapkcache: function(commands, index) { | ||
return get_nextcommands(commands, index).some(function(command) { | ||
if (command.split(' ')[0] !== 'rm') { | ||
return false; | ||
} | ||
if (!command_has_part(command, '/var/cache/apk/*')) { | ||
return false; | ||
} | ||
if (!command_has_flag(command, '-r') | ||
&& !command_has_flag(command, '-R') | ||
&& !command_has_flag(command, '--recursive')) { | ||
return false; | ||
} | ||
if (!command_has_flag(command, '-f') | ||
&& !command_has_flag(command, '--force')) { | ||
return false; | ||
} | ||
return true; | ||
}); | ||
return isAllFound; | ||
} | ||
} | ||
}; |
{ | ||
"name": "dockerfilelint", | ||
"version": "0.0.8", | ||
"version": "0.0.9", | ||
"description": "A linter for Dockerfiles to find bugs and encourage best practices", | ||
"main": "./lib/index.js", | ||
"scripts": { | ||
"test": "mocha" | ||
"test": "nyc mocha", | ||
"coverage": "nyc report --reporter=text-lcov | coveralls" | ||
}, | ||
@@ -25,6 +26,13 @@ "repository": { | ||
"dependencies": { | ||
"lodash": "^4.3.0", | ||
"yargs": "^3.32.0" | ||
}, | ||
"devDependencies": { | ||
"chai": "^3.5.0", | ||
"coveralls": "^2.11.6", | ||
"eslint": "^1.10.3", | ||
"eslint-config-defaults": "^9.0.0", | ||
"mocha": "^2.4.5", | ||
"yargs": "^3.32.0" | ||
"nyc": "^5.6.0" | ||
} | ||
} |
# Linter and validator for Dockerfile | ||
[![Coverage Status](https://coveralls.io/repos/github/replicatedhq/dockerfilelint/badge.svg?branch=master)](https://coveralls.io/github/replicatedhq/dockerfilelint?branch=master) | ||
[![Build Status](https://travis-ci.org/replicatedhq/dockerfilelint.svg?branch=master)](https://travis-ci.org/replicatedhq/dockerfilelint.svg?branch=master) | ||
`Dockerfileint` is an npm module that analyzes a Dockerfile and looks for common traps, mistakes and helps enforce best practices: | ||
@@ -33,2 +36,4 @@ | ||
- [x] Never run `apt-get update` without `apt-get install` on the same line | ||
- [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 | ||
- [ ] handle best practices for yum operations and cleanup | ||
@@ -35,0 +40,0 @@ |
@@ -5,8 +5,15 @@ var expect = require('chai').expect | ||
describe("run_checks", function(){ | ||
describe("#aptget_commands(args)", function(){ | ||
describe("#split_commands(args)", function(){ | ||
it("splits commands separated by && into individual commands", function(){ | ||
expect(run_checks.split_commands("apt-get install -y python-pip")).to.have.length(1); | ||
expect(run_checks.split_commands("apt-get install -y python-pip && apt-get remove -y python-pip")).to.have.length(2); | ||
}); | ||
}); | ||
describe("#aptget_commands(commands)", function(){ | ||
it("extracts apt-get commands from longer bash command", function(){ | ||
expect(run_checks.aptget_commands("apt-get install -y python-pip")).to.have.length(1); | ||
expect(run_checks.aptget_commands("# apt-get install -y python-pip")).to.have.length(0); | ||
expect(run_checks.aptget_commands("echo test && apt-get install -y python-pip")).to.have.length(1); | ||
expect(run_checks.aptget_commands("apt-get install -y python-pip && apt-get remove -y python-pip")).to.have.length(2); | ||
expect(run_checks.aptget_commands(["apt-get install -y python-pip"])).to.have.length(1); | ||
expect(run_checks.aptget_commands(["# apt-get install -y python-pip"])).to.have.length(0); | ||
expect(run_checks.aptget_commands(["echo test", "apt-get install -y python-pip"])).to.have.length(1); | ||
expect(run_checks.aptget_commands(["apt-get install -y python-pip", "apt-get remove -y python-pip"])).to.have.length(2); | ||
}); | ||
@@ -22,5 +29,7 @@ }); | ||
describe("#aptget_with_yes(command)", function(){ | ||
describe("#aptget_hasyes(command)", function(){ | ||
it("validates that -y flag is present on apt-get (update|install|remove) commands", function(){ | ||
expect(run_checks.aptget_hasyes("apt-get install -y python-pip")).to.equal(true); | ||
expect(run_checks.aptget_hasyes("apt-get install --yes python-pip")).to.equal(true); | ||
expect(run_checks.aptget_hasyes("apt-get install --assume-yes python-pip")).to.equal(true); | ||
expect(run_checks.aptget_hasyes("apt-get install python-pip")).to.equal(false); | ||
@@ -30,3 +39,3 @@ }); | ||
describe("#aptget_install_with_norecommends(command)", function(){ | ||
describe("#aptget_hasnorecommends(command)", function(){ | ||
it("validates that --no-recommends flag is present on apt-get install commands", function(){ | ||
@@ -38,9 +47,61 @@ expect(run_checks.aptget_hasnorecommends("apt-get install -y --no-install-recommends python-pip")).to.equal(true); | ||
describe("#aptget_install_with_rmaptlist(command)", function(){ | ||
describe("#follows_rmaptlists(command)", function(){ | ||
it("validates that a matching rm command is present on apt-get install commands", function(){ | ||
expect(run_checks.includes_rmaptlists("apt-get install -y --no--install-recommends python-pip")).to.equal(false); | ||
expect(run_checks.includes_rmaptlists("apt-get install -y --no-install-recommends python-pip && rm -rf /var/lib/apt/lists/*")).to.equal(true); | ||
expect(run_checks.includes_rmaptlists("apt-get install -y --no-install-recommends python-pip && rm -fr /var/lib/apt/lists/*")).to.equal(true); | ||
expect(run_checks.follows_rmaptlists(["apt-get install -y --no--install-recommends python-pip"], 0)).to.equal(false); | ||
expect(run_checks.follows_rmaptlists(["apt-get install -y --no-install-recommends python-pip", "rm -rf /var/lib/apt/lists/*"], 1)).to.equal(false); | ||
expect(run_checks.follows_rmaptlists(["apt-get install -y --no-install-recommends python-pip", "rmrf -rf /var/lib/apt/lists/*"], 0)).to.equal(false); | ||
expect(run_checks.follows_rmaptlists(["apt-get install -y --no-install-recommends python-pip", "rm -rf /var/lib/apt/lists"], 0)).to.equal(false); | ||
expect(run_checks.follows_rmaptlists(["apt-get install -y --no-install-recommends python-pip", "rm --force /var/lib/apt/lists/*"], 0)).to.equal(false); | ||
expect(run_checks.follows_rmaptlists(["apt-get install -y --no-install-recommends python-pip", "rm -r /var/lib/apt/lists/*"], 0)).to.equal(false); | ||
expect(run_checks.follows_rmaptlists(["apt-get install -y --no-install-recommends python-pip", "rm -rfv /var/lib/apt/lists/*"], 0)).to.equal(true); | ||
expect(run_checks.follows_rmaptlists(["apt-get install -y --no-install-recommends python-pip", "rm -fR /var/lib/apt/lists/*"], 0)).to.equal(true); | ||
expect(run_checks.follows_rmaptlists(["apt-get install -y --no-install-recommends python-pip", "rm --force -r /var/lib/apt/lists/*"], 0)).to.equal(true); | ||
expect(run_checks.follows_rmaptlists(["apt-get install -y --no-install-recommends python-pip", "rm --force --recursive /var/lib/apt/lists/*"], 0)).to.equal(true); | ||
}); | ||
}); | ||
describe("#apk_commands(commands)", function(){ | ||
it("extracts apk commands from longer bash command", function(){ | ||
expect(run_checks.apk_commands(["apk add python-pip"])).to.have.length(1); | ||
expect(run_checks.apk_commands(["# apk add python-pip"])).to.have.length(0); | ||
expect(run_checks.apk_commands(["echo test", "apk add python-pip"])).to.have.length(1); | ||
expect(run_checks.apk_commands(["apk add python-pip", "apk remove python-pip"])).to.have.length(2); | ||
}); | ||
}); | ||
describe("#apk_subcommand(command)", function(){ | ||
it("retrieves the subcommand passed to apk", function(){ | ||
expect(run_checks.apk_subcommand("apk --no-cache add python-pip")).to.equal('add'); | ||
expect(run_checks.apk_subcommand("apk remove python-pip")).to.equal('remove'); | ||
}); | ||
}); | ||
describe("#apkadd_hasnocache(command)", function(){ | ||
it("validates that --no-cache flag is present on apk add commands", function(){ | ||
expect(run_checks.apkadd_hasnocache("apk --no-cache add python-pip")).to.equal(true); | ||
expect(run_checks.apkadd_hasnocache("apk --update add python-pip")).to.equal(false); | ||
}); | ||
}); | ||
describe("#apkadd_hasupdate(command)", function(){ | ||
it("validates that --no-cache flag is present on apk add commands", function(){ | ||
expect(run_checks.apkadd_hasupdate("apk --no-cache add python-pip")).to.equal(false); | ||
expect(run_checks.apkadd_hasupdate("apk --update add python-pip")).to.equal(true); | ||
}); | ||
}); | ||
describe("#follows_rmapkcache(command)", function(){ | ||
it("validates that a matching rm command is present on apk add commands", function(){ | ||
expect(run_checks.follows_rmapkcache(["apk add --update python-pip"], 0)).to.equal(false); | ||
expect(run_checks.follows_rmapkcache(["apk add --update python-pip", "rm -rf /var/cache/apk"], 0)).to.equal(false); | ||
expect(run_checks.follows_rmapkcache(["apk add --update python-pip", "rmrf -rf /var/cache/apk/*"], 0)).to.equal(false); | ||
expect(run_checks.follows_rmapkcache(["apk add --update python-pip", "rm -rf /var/cache/apk/*"], 1)).to.equal(false); | ||
expect(run_checks.follows_rmapkcache(["apk add --update python-pip", "rm --force /var/cache/apk/*"], 0)).to.equal(false); | ||
expect(run_checks.follows_rmapkcache(["apk add --update python-pip", "rm -r /var/cache/apk/*"], 0)).to.equal(false); | ||
expect(run_checks.follows_rmapkcache(["apk add --update python-pip", "rm -rfv /var/cache/apk/*"], 0)).to.equal(true); | ||
expect(run_checks.follows_rmapkcache(["apk add --update python-pip", "rm -fR /var/cache/apk/*"], 0)).to.equal(true); | ||
expect(run_checks.follows_rmapkcache(["apk add --update python-pip", "rm --force -r /var/cache/apk/*"], 0)).to.equal(true); | ||
expect(run_checks.follows_rmapkcache(["apk add --update python-pip", "rm --force --recursive /var/cache/apk/*"], 0)).to.equal(true); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
57115
2
29
957
99
6
1
+ Addedlodash@^4.3.0
+ Addedlodash@4.17.21(transitive)
- Removedchai@^3.5.0
- Removedmocha@^2.4.5
- Removedassertion-error@1.1.0(transitive)
- Removedchai@3.5.0(transitive)
- Removedcommander@0.6.12.3.0(transitive)
- Removeddebug@2.2.0(transitive)
- Removeddeep-eql@0.1.3(transitive)
- Removeddiff@1.4.0(transitive)
- Removedescape-string-regexp@1.0.2(transitive)
- Removedglob@3.2.11(transitive)
- Removedgrowl@1.9.2(transitive)
- Removedinherits@2.0.4(transitive)
- Removedjade@0.26.3(transitive)
- Removedlru-cache@2.7.3(transitive)
- Removedminimatch@0.3.0(transitive)
- Removedminimist@0.0.8(transitive)
- Removedmkdirp@0.3.00.5.1(transitive)
- Removedmocha@2.5.3(transitive)
- Removedms@0.7.1(transitive)
- Removedsigmund@1.0.1(transitive)
- Removedsupports-color@1.2.0(transitive)
- Removedto-iso-string@0.0.2(transitive)
- Removedtype-detect@0.1.11.0.0(transitive)