diff --git a/lib/index.js b/lib/index.js index b40737c..426faf8 100644 --- a/lib/index.js +++ b/lib/index.js @@ -50,8 +50,10 @@ module.exports.run = function(configPath, content) { }); var state = { - instructionsProcessed: 0, - cmdFound: false, + stages: [{ + instructionsProcessed: 0, + cmdFound: false // this doesn't seem to be used anywhere + }], items: [], rules: loadRules(configPath) } @@ -62,11 +64,17 @@ module.exports.run = function(configPath, content) { // We care about only having 1 cmd instruction if (result.command === 'cmd') { - state.cmdFound = true; + state.stages[state.stages.length - 1].cmdFound = true; + } else if (result.command === 'from' && state.stages[state.stages.length - 1].instructionsProcessed !== 0) { + // Each FROM command constitutes a new stage in a multistage build + state.stages.push({ + instructionsProcessed: 0, + cmdFound: false + }); } // And we also care about knowing if this is the first command or not - state.instructionsProcessed = state.instructionsProcessed + 1; + state.stages[state.stages.length - 1].instructionsProcessed++; }; return state.items; @@ -116,7 +124,7 @@ function runLine(state, instructions, idx) { // check that the first command is a FROM, this might get reported twice, if the FROM command does exist, // but is not the time (non blank, non commented) line - if ((state.instructionsProcessed === 0 && cmd !== 'from') || (state.instructionsProcessed !== 0 && cmd === 'from')) { + if (state.stages[state.stages.length - 1].instructionsProcessed === 0 && cmd !== 'from') { items.push(messages.build(state.rules, 'from_first', line)); } diff --git a/test/examples/Dockerfile.multistage b/test/examples/Dockerfile.multistage new file mode 100644 index 0000000..f3d8d4b --- /dev/null +++ b/test/examples/Dockerfile.multistage @@ -0,0 +1,11 @@ +FROM golang:1.7.3 +WORKDIR /go/src/github.com/alexellis/href-counter/ +RUN go get -d -v golang.org/x/net/html +COPY app.go . +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . + +FROM alpine:3.6 +RUN apk --no-cache add ca-certificates +WORKDIR /root/ +COPY --from=0 /go/src/github.com/alexellis/href-counter/app . +CMD ["./app"] diff --git a/test/examples/Dockerfile.multistagenamed b/test/examples/Dockerfile.multistagenamed new file mode 100644 index 0000000..1169dd8 --- /dev/null +++ b/test/examples/Dockerfile.multistagenamed @@ -0,0 +1,11 @@ +FROM golang:1.7.3 as builder +WORKDIR /go/src/github.com/alexellis/href-counter/ +RUN go get -d -v golang.org/x/net/html +COPY app.go . +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . + +FROM alpine:3.6 +RUN apk --no-cache add ca-certificates +WORKDIR /root/ +COPY --from=builder /go/src/github.com/alexellis/href-counter/app . +CMD ["./app"] diff --git a/test/index.js b/test/index.js index f2d35db..ef7645d 100644 --- a/test/index.js +++ b/test/index.js @@ -64,6 +64,16 @@ describe("index", function(){ }); }); + describe("#multistage", function(){ + it("validates the multistage Dockerfile has no issues", function(){ + expect(dockerfilelint.run('./test/examples', fs.readFileSync('./test/examples/Dockerfile.multistage', 'UTF-8'))).to.be.empty; + }); + + it("validates the multistagenamed Dockerfile has no issues", function(){ + expect(dockerfilelint.run('./test/examples', fs.readFileSync('./test/examples/Dockerfile.multistagenamed', 'UTF-8'))).to.be.empty; + }); + }); + describe("#shell", function() { it("validates the shell command is accepted when entered correctly", function() { expect(dockerfilelint.run('./test/examples', fs.readFileSync('./test/examples/Dockerfile.shell.pass', 'UTF-8'))).to.be.empty; @@ -91,15 +101,9 @@ describe("index", function(){ { title: 'First Command Must Be FROM', rule: 'from_first', line: 4 }, - { title: 'First Command Must Be FROM', - rule: 'from_first', - line: 5 }, { 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 },