Skip to content

Commit 0bb130c

Browse files
committed
Merge branch 'master' into docker_inspect
2 parents 676c378 + d987346 commit 0bb130c

File tree

8 files changed

+360
-33
lines changed

8 files changed

+360
-33
lines changed

Diff for: README.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ A rule based 'linter' for [Dockerfiles](https://docs.docker.com/reference/builde
77
#Quickstart
88

99
1. Change to directory where you have a Dockerfile
10-
1. run
10+
2. run
1111
* Atomic CLI
1212

1313
atomic run projectatomic/dockerfile-lint
@@ -17,6 +17,12 @@ A rule based 'linter' for [Dockerfiles](https://docs.docker.com/reference/builde
1717
docker run -it --rm --privileged -v `pwd`:/root/ \
1818
projectatomic/dockerfile-lint \
1919
dockerfile_lint -f Dockerfile
20+
21+
By default, the linter runs in strict mode (errors and/or warnings result in non-zero return code). Run the command with '-p' or '--permissive to
22+
run in permissive mode:
23+
docker run -it --rm --privileged -v `pwd`:/root/ \
24+
projectatomic/dockerfile-lint \
25+
dockerfile_lint -p -f Dockerfile
2026

2127
#Extending and Customizing: Rule Files
2228
Rule files are written in [yaml](http://www.yaml.org/). See the example rule file **sample_rules.yaml** in the root folder of the project.

Diff for: bin/dockerfile_lint

+30-11
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ var fs = require('fs'),
88
commandline = require('commander'),
99
logger = require("../lib/logger"),
1010
config = require('../config/config'),
11+
yamlParser = require('js-yaml'),
12+
loadRules = require("../lib/rulefile-loader").load,
1113
DockeFileValidator = require('../');
1214

1315

@@ -98,16 +100,19 @@ function printJsonResults(results) {
98100

99101
var dockerfileLocation = null;
100102
var rulefileLocation = null;
101-
var dockerfile = null;
102-
var rulefile = null;
103+
var dockerfile = null ;
104+
var rulefile = null ;
103105
var printJson = false;
104106
var remoteFile = false;
107+
var strictMode = true;
105108

106109

107110
commandline.option('-j, --json', 'Show results in JSON format')
108111
.option('-r, --rulefile [rulefile] (optional)', 'Rule file', rulefile)
109112
.option('-f, --dockerfile [dockerfile] (required)', 'File to lint. Accepts a local file or an http(s) URL', dockerfile)
110113
.option('-v, --verbose', 'Show debugging logs')
114+
.option('-p, --permissive', 'Run in permissive mode (return 1 only on error but not on warning)')
115+
.option('-e, --export-rules', 'Dump the effective rule file. All other options except -r are ignored.')
111116
.parse(process.argv);
112117

113118
if (commandline.verbose) {
@@ -119,15 +124,34 @@ if (commandline.json) {
119124
printJson = true;
120125
}
121126

122-
if (!commandline.dockerfile) {
127+
if (!commandline.dockerfile && !commandline.exportRules) {
123128
commandline.help();
124129
}
125130

126-
dockerfileLocation = commandline.dockerfile;
127131
if (commandline.rulefile) {
128132
rulefileLocation = commandline.rulefile;
129133
}
130134

135+
if (rulefileLocation !== null) {
136+
if (!fs.existsSync(rulefileLocation)) {
137+
console.error('ERROR: Rule file not found -> ' + rulefileLocation);
138+
process.exit(1);
139+
}
140+
}
141+
142+
if (commandline.exportRules) {
143+
var rules = loadRules(rulefileLocation);
144+
console.log(yamlParser.dump(rules));
145+
process.exit(0);
146+
}
147+
148+
if (commandline.permissive) {
149+
strictMode = false;
150+
}
151+
152+
dockerfileLocation = commandline.dockerfile;
153+
154+
131155
try {
132156
dockerfile = fs.readFileSync(dockerfileLocation, 'UTF-8');
133157
} catch (e) {
@@ -138,12 +162,7 @@ try {
138162
process.exit(1);
139163
}
140164
}
141-
if (rulefileLocation !== null) {
142-
if (!fs.existsSync(rulefileLocation)) {
143-
console.error('ERROR: Rule file not found -> ' + rulefileLocation);
144-
process.exit(1);
145-
}
146-
}
165+
147166

148167
function runValidation(dockerfile, rulefileLocation) {
149168
var validator = new DockeFileValidator(rulefileLocation);
@@ -154,7 +173,7 @@ function runValidation(dockerfile, rulefileLocation) {
154173
printResults(results);
155174
}
156175

157-
if (results.error.count > 0) {
176+
if ((results.error.count > 0) || (strictMode && results.warn.count > 0)) {
158177
process.exit(1);
159178
} else {
160179
process.exit(0);

Diff for: lib/linter.js

+18-8
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
var helper = require('./linter-utils'),
44
parser = require('./parser'),
55
loadRules = require('./rulefile-loader').load,
6-
logger = require('./logger');
6+
logger = require('./logger'),
7+
_ = require('lodash');
78

89
//TODO move to utils
910
function printObject(obj) {
@@ -61,12 +62,6 @@ function validateCommand(command, context, result) {
6162
function Linter(ruleFilePath) {
6263
if (!ruleFilePath) logger.info("No rule file provided, using default rules");
6364
this.rules = loadRules(ruleFilePath);
64-
this.context = {
65-
rules: this.rules,
66-
validInstructionsRegex: helper.createValidCommandRegex(this.rules.general.valid_instructions),
67-
requiredInstructions: helper.createReqInstructionHash(this.rules),
68-
requiredNameVals: helper.createRequiredNameValDict(this.rules)
69-
};
7065
helper.initLineRulesRegexes(this.rules);
7166
}
7267

@@ -76,12 +71,26 @@ function Linter(ruleFilePath) {
7671
*/
7772
Linter.prototype.getProfile = function () {
7873
return this.rules.profile;
79-
};
74+
}
75+
76+
77+
Linter.prototype.initContext = function(){
78+
var ruleCopy = _.cloneDeep(this.rules);
79+
this.context = {
80+
rules: ruleCopy,
81+
validInstructionsRegex: helper.createValidCommandRegex(ruleCopy.general.valid_instructions),
82+
requiredInstructions: helper.createReqInstructionHash(ruleCopy),
83+
requiredNameVals: helper.createRequiredNameValDict(ruleCopy)
84+
};
85+
}
8086

8187
/**
8288
* Validate dockerfile contents string and returns the array of analysis
8389
* results.
8490
*
91+
* The validate can be called safely multiple times with the same instance of
92+
* of the Linter
93+
*
8594
* @param contents {String} The dockerfile file content.
8695
* @returns {Array}
8796
*/
@@ -92,6 +101,7 @@ Linter.prototype.validate = function (contents) {
92101
}]);
93102
}
94103
var result = helper.newResult();
104+
this.initContext(); //start with a clean context to allow multiple calls.
95105
var options = {includeComments: false};
96106
var commands = parser.parse(contents, options);
97107
// @see parser.parse for format of commands

Diff for: lib/rulefile-loader.js

+2
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ function load(ruleFilePath) {
6565
var baseRuleLocation = fs.readFileSync(config.BASE_RULES, 'UTF-8');
6666
var rules = yamlParser.safeLoad(baseRuleLocation);
6767
if (!ruleFilePath) {
68+
logger.debug("Effective rule set is :\n" + yamlParser.dump(rules));
6869
return rules;
6970
} else {
7071
var includedRuleProfiles = [];
@@ -78,6 +79,7 @@ function load(ruleFilePath) {
7879
delete profile.sourceFilename; // no longer needed part of hack!
7980
extend(rules, profile);
8081
});
82+
logger.debug("Effective rule set is :\n" + yamlParser.dump(rules));
8183
return rules;
8284
}
8385
}

Diff for: sample_rules/basic_rules.yaml

+76-10
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,24 @@
22
profile:
33
name: "Default"
44
description: "Default Profile. Checks basic syntax."
5+
includes:
6+
- recommended_label_rules.yaml
57
line_rules:
68
FROM:
79
paramSyntaxRegex: /^[a-z0-9./-]+(:[a-z0-9.]+)?$/
810
rules:
911
-
1012
label: "is_latest_tag"
1113
regex: /latest/
12-
level: "info"
14+
level: "error"
1315
message: "base image uses 'latest' tag"
14-
description: "using the 'latest' tag may cause unpredictable builds. It is recommended that a specific tag is used in the FROM line."
16+
description: "using the 'latest' tag may cause unpredictable builds. It is recommended that a specific tag is used in the FROM line or *-released which is the latest supported release."
1517
reference_url:
1618
- "https://docs.docker.com/reference/builder/"
1719
- "#from"
18-
-
1920
label: "no_tag"
2021
regex: /^[:]/
21-
level: "warn"
22+
level: "error"
2223
message: "No tag is used"
2324
description: "lorem ipsum tar"
2425
reference_url:
@@ -32,18 +33,83 @@
3233
rules:
3334
-
3435
label: "no_yum_clean_all"
35-
regex: /yum ((?!clean all).)* .+/
36+
regex: /yum(?!.+clean all|.+\.repo)/g
3637
level: "warn"
3738
message: "yum clean all is not used"
3839
description: "the yum cache will remain in this layer making the layer unnecessarily large"
39-
reference_url: "None"
40+
reference_url:
41+
- "http://docs.projectatomic.io/container-best-practices/#"
42+
- "_clear_packaging_caches_and_temporary_package_downloads"
4043
-
44+
label: "yum_update_all"
45+
regex: /yum(.+update all|.+upgrade|.+update)/g
46+
level: "warn"
47+
message: "updating the entire base image may add unnecessary size to the container"
48+
description: "update the entire base image may add unnecessary size to the container"
49+
reference_url:
50+
- "http://docs.projectatomic.io/container-best-practices/#"
51+
- "_clear_packaging_caches_and_temporary_package_downloads"
52+
-
53+
label: "no_dnf_clean_all"
54+
regex: /dnf(?!.+clean all|.+\.repo)/g
55+
level: "warn"
56+
message: "dnf clean all is not used"
57+
description: "the dnf cache will remain in this layer making the layer unnecessarily large"
58+
reference_url:
59+
- "http://docs.projectatomic.io/container-best-practices/#"
60+
- "_clear_packaging_caches_and_temporary_package_downloads"
61+
-
62+
label: "no_rvm_cleanup_all"
63+
regex: /rvm install(?!.+cleanup all)/g
64+
level: "warn"
65+
message: "rvm cleanup is not used"
66+
description: "the rvm cache will remain in this layer making the layer unnecessarily large"
67+
reference_url:
68+
- "http://docs.projectatomic.io/container-best-practices/#"
69+
- "_clear_packaging_caches_and_temporary_package_downloads"
70+
-
71+
label: "no_gem_clean_all"
72+
regex: /gem install(?!.+cleanup|.+\rvm cleanup all)/g
73+
level: "warn"
74+
message: "gem cleanup all is not used"
75+
description: "the gem cache will remain in this layer making the layer unnecessarily large"
76+
reference_url:
77+
- "http://docs.projectatomic.io/container-best-practices/#"
78+
- "_clear_packaging_caches_and_temporary_package_downloads"
79+
-
80+
label: "no_apt-get_clean"
81+
regex: /apt-get install(?!.+clean)/g
82+
level: "warn"
83+
message: "apt-get clean is not used"
84+
description: "the apt-get cache will remain in this layer making the layer unnecessarily large"
85+
reference_url:
86+
- "http://docs.projectatomic.io/container-best-practices/#"
87+
- "_clear_packaging_caches_and_temporary_package_downloads"
88+
-
89+
label: "privileged_run_container"
90+
regex: /privileged/
91+
level: "warn"
92+
message: "a privileged run container is allowed access to host devices"
93+
description: "Does this run need to be privileged?"
94+
reference_url:
95+
- "http://docs.docker.com/engine/reference/run/#"
96+
- "runtime-privilege-and-linux-capabilities"
97+
-
4198
label: "installing_ssh"
4299
regex: /ssh/
43100
level: "warn"
44101
message: "installing SSH in a container is not recommended"
45102
description: "Do you really need SSH in this image?"
46-
reference_url: "https://github.com/jpetazzo/nsenter"
103+
reference_url: "https://github.com/jpetazzo/nsenter"
104+
-
105+
label: "no_ampersand_usage"
106+
regex: /\;/
107+
level: "warn"
108+
message: "using ; instead of && will evaluate each expression regardless of the success of the previous command"
109+
description: "RUN do_1 && do_2: The ampersands change the resulting evaluation into do_1 and then do_2 only if do_1 was successful."
110+
reference_url:
111+
- "http://docs.projectatomic.io/container-best-practices/#"
112+
- "#_using_semi_colons_vs_double_ampersands"
47113
EXPOSE:
48114
paramSyntaxRegex: /^[0-9]+([0-9\s]+)?$/
49115
rules: []
@@ -58,8 +124,8 @@
58124
ENTRYPOINT:
59125
paramSyntaxRegex: /.+/
60126
rules: []
61-
VOLUME:
62-
paramSyntaxRegex: /^~?([A-z0-9\/_.-]+|\["[A-z0-9\/_.-]+"\])$/
127+
VOLUME:
128+
paramSyntaxRegex: /.+/
63129
rules: []
64130
USER:
65131
paramSyntaxRegex: /^[a-z_][a-z0-9_]{0,30}$/
@@ -74,7 +140,7 @@
74140
-
75141
instruction: "MAINTAINER"
76142
count: 1
77-
level: "info"
143+
level: "error"
78144
message: "Maintainer is not defined"
79145
description: "The MAINTAINER line is useful for identifying the author in the form of MAINTAINER Joe Smith <[email protected]>"
80146
reference_url:

Diff for: sample_rules/recommended_label_rules.yaml

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
---
2+
profile:
3+
name: "Label"
4+
description: "Label checking rule file."
5+
line_rules:
6+
LABEL:
7+
#paramSyntaxRegex: /.+/
8+
# Use defined_label_rules to defined a set of labels for your dockerfile
9+
# In this example, the labels "Vendor","Authoritative_Registry","BZComponent"
10+
# have been defined. A label value is 'valid' if matches the regular
11+
# expression 'valueRegex', otherwise an warn is logged with the string "message"
12+
# at level 'level'. 'reference_url' provides a web link where the user can
13+
# get more information about the rule.
14+
#
15+
defined_namevals:
16+
Name:
17+
valueRegex: /([\w]+)./
18+
message: "Label 'Name' is missing or has invalid format"
19+
level: "warn"
20+
required: true
21+
reference_url:
22+
- "http://docs.projectatomic.io/container-best-practices/#"
23+
- "_recommended_labels_for_your_project"
24+
Version:
25+
valueRegex: /[\d+.\d+]+/
26+
message: "Label 'Version' is missing or has invalid format"
27+
level: "warn"
28+
required: true
29+
reference_url:
30+
- "http://docs.projectatomic.io/container-best-practices/#"
31+
- "_recommended_labels_for_your_project"
32+
Release:
33+
valueRegex: /[\d+.\d+]+/
34+
message: "Label 'Release' is missing or has invalid format"
35+
level: "warn"
36+
required: true
37+
reference_url:
38+
- "http://docs.projectatomic.io/container-best-practices/#"
39+
- "_recommended_labels_for_your_project"
40+
Architecture:
41+
valueRegex: /[\w]*[6,8][4,6]|[.]*86[.]*64/
42+
message: "Label 'Architure' is missing or has invalid format: x86, i386, x86_64"
43+
level: "warn"
44+
required: true
45+
reference_url:
46+
- "http://docs.projectatomic.io/container-best-practices/#"
47+
- "_recommended_labels_for_your_project"
48+
Vendor:
49+
valueRegex: /([\w]+).+/
50+
message: "Label 'Vendor' is missing or has invalid format"
51+
level: "warn"
52+
required: true
53+
reference_url:
54+
- "http://docs.projectatomic.io/container-best-practices/#"
55+
- "_recommended_labels_for_your_project"
56+
57+
# The 'required_instructions' lists mandatory instructions
58+
#
59+
#required_instructions:
60+
#
61+
# This rule says, you must have 1 "LABEL" instruction
62+
# -
63+
# instruction: "LABEL"
64+
# count: 1
65+
# level: "warn"
66+
# message: "No LABELs are defined"
67+
# description: "Labels are needed because...."
68+
# reference_url:
69+
# - "https://docs.docker.com/reference/builder/"
70+
# - "#label"
71+

0 commit comments

Comments
 (0)