@@ -128,6 +128,20 @@ object DockerBuildPlugin extends AutoPlugin {
128
128
" The value to use for WORKDIR when generating your Dockerfile. Defaults to \" /stage\" ."
129
129
)
130
130
131
+ // //////////////////////////////////////////////////////////////////////////////////////////////
132
+ // The following settings affect how the plugin behaves, but don't affect file or image output.
133
+ // //////////////////////////////////////////////////////////////////////////////////////////////
134
+
135
+ val verifyDockerfileIsStrict : SettingKey [Boolean ] = Def .settingKey[Boolean ](
136
+ " If true, the `verifyDockerfile` task will raise an error if the current Dockerfile does " +
137
+ " not match the generated one. Defaults to true."
138
+ )
139
+
140
+ val verifyDockerfileOnBuild : SettingKey [Boolean ] = Def .settingKey[Boolean ](
141
+ " If true, `dockerBuild` will depend on `verifyDockerfile`, and will not build if the " +
142
+ " Dockerfile is not up-to-date. Defaults to false."
143
+ )
144
+
131
145
// //////////////////////////////////////////////////////////////////////////////////////////////
132
146
// The following keys are for generating dockerfiles; and staging, building, running, and
133
147
// pushing images. These should not be overridden from the defaults unless you know what you're
@@ -139,6 +153,12 @@ object DockerBuildPlugin extends AutoPlugin {
139
153
" `dockerfileLocation`."
140
154
)
141
155
156
+ val verifyDockerfile : TaskKey [Boolean ] = Def .taskKey[Boolean ](
157
+ " Checks if the Dockerfile that would be generated by `generateDockerfile` is the same as " +
158
+ " the Dockerfile at `dockerfileLocation`. This will raise an error if " +
159
+ " `verifyDockerfileIsStrict` is true, and print a warning otherwise."
160
+ )
161
+
142
162
val dockerDependencyStage : TaskKey [File ] = Def .taskKey[File ](
143
163
" Builds a staged directory under target/docker/dependencies containing project " +
144
164
" dependencies. This will include a generated Dockerfile. This returns the staging " +
@@ -301,12 +321,27 @@ object DockerBuildPlugin extends AutoPlugin {
301
321
}
302
322
}
303
323
304
- // ////////////////////////////////////////////////////////////////////////////////////////////////
305
- // Definitions for plugin tasks.
306
- // ////////////////////////////////////////////////////////////////////////////////////////////////
324
+ /** Task to read the current text of the main Dockerfile below the sigil line. This returns None
325
+ * if and only if the file exists, but the sigil line cannot be found, since this is considered
326
+ * a potential user error.
327
+ */
328
+ lazy val customDockerfileContents : Def .Initialize [Task [Option [String ]]] = Def .task {
329
+ val dockerfile = dockerfileLocation.value
330
+ if (dockerfile.exists) {
331
+ val lines = IO .readLines(dockerfile)
332
+ val remainder = lines.dropWhile(_ != DOCKERFILE_SIGIL )
333
+ if (remainder.nonEmpty) {
334
+ Some (remainder.tail.mkString(" \n " ) + " \n " )
335
+ } else {
336
+ None
337
+ }
338
+ } else {
339
+ Some (" " )
340
+ }
341
+ }
307
342
308
- /** Task to build a dockerfile using the current project's sbt settings to populate it. */
309
- lazy val generateDockerfileDef : Def .Initialize [Task [Unit ]] = Def .task {
343
+ /** Task to build dockerfile contents using the current project's sbt settings and return it. */
344
+ lazy val generatedDockerfileContents : Def .Initialize [Task [String ]] = Def .task {
310
345
val logger = Keys .streams.value.log
311
346
312
347
// Create the copy commands.
@@ -352,7 +387,7 @@ object DockerBuildPlugin extends AutoPlugin {
352
387
// Create the text for dockerMainArgs and CMD command.
353
388
val dockerMainArgsText = dockerMainArgs.value.map('"' + _ + '"' ).mkString(" , " )
354
389
355
- val dockerfileContents = s """ # AUTOGENERATED
390
+ s """ # AUTOGENERATED
356
391
# Most lines in this file are derived from sbt settings. These settings are printed above the lines
357
392
# they affect.
358
393
#
@@ -413,23 +448,52 @@ COPY lib lib
413
448
# Do not remove this line unless you want your changes overwritten!
414
449
$DOCKERFILE_SIGIL
415
450
"""
451
+ }
416
452
417
- // If there is already a Dockerfile, retain any contents past the sigil.
453
+ /** Task to check the current dockerfile contents against the generated contents, returning true
454
+ * if they are up-to-date.
455
+ */
456
+ lazy val checkDockerfile : Def .Initialize [Task [Boolean ]] = Def .task {
457
+ val newContents = {
458
+ val generatedContents = generatedDockerfileContents.value
459
+ val customContents = customDockerfileContents.value.getOrElse(" " )
460
+ generatedContents + customContents
461
+ }
418
462
val dockerfile = dockerfileLocation.value
419
- val existingContents = if (dockerfile.exists) {
420
- val lines = IO .readLines(dockerfile)
421
- val remainder = lines.dropWhile(_ != DOCKERFILE_SIGIL )
422
- if (remainder.nonEmpty) {
423
- remainder.tail.mkString(" \n " )
424
- } else {
425
- logger.warn(s " Overwriting Dockerfile at $dockerfile... " )
426
- " "
427
- }
428
- } else {
463
+ val oldContents = if (dockerfile.exists) IO .read(dockerfileLocation.value) else " "
464
+
465
+ newContents == oldContents
466
+ }
467
+
468
+ // ////////////////////////////////////////////////////////////////////////////////////////////////
469
+ // Definitions for plugin tasks.
470
+ // ////////////////////////////////////////////////////////////////////////////////////////////////
471
+
472
+ /** Task to build a dockerfile using the current project's sbt settings to populate it. */
473
+ lazy val generateDockerfileDef : Def .Initialize [Task [Unit ]] = Def .task {
474
+ val generatedContents = generatedDockerfileContents.value
475
+ val customContents = customDockerfileContents.value.getOrElse {
476
+ Keys .streams.value.log(s " Overwriting Dockerfile at ${dockerfileLocation.value}... " )
429
477
" "
430
478
}
479
+ IO .write(dockerfileLocation.value, generatedContents + customContents)
480
+ }
431
481
432
- IO .write(dockerfile, dockerfileContents + existingContents + " \n " )
482
+ /** Task verify that the current Dockerfile is up-to-date. Raises an error if
483
+ * verifyDockerfileIsStrict is true.
484
+ */
485
+ lazy val verifyDockerfileDef : Def .Initialize [Task [Boolean ]] = Def .task {
486
+ val isUnchanged = checkDockerfile.value
487
+ if (! isUnchanged) {
488
+ val message = s " Dockerfile ${dockerfileLocation.value} is not up-to-date. Run " +
489
+ " the `generateDockerfile` task to fix."
490
+ if (verifyDockerfileIsStrict.value) {
491
+ sys.error(message)
492
+ } else {
493
+ Keys .streams.value.log.warn(message)
494
+ }
495
+ }
496
+ isUnchanged
433
497
}
434
498
435
499
/** Task to stage a docker image containing the dependencies of the current project. This is used
@@ -577,6 +641,27 @@ $DOCKERFILE_SIGIL
577
641
dockerDependencyBuild.value
578
642
dockerMainStage.value
579
643
644
+ // Verify the dockerfile.
645
+ if (verifyDockerfileOnBuild.value) {
646
+ val isUnchanged = checkDockerfile.value
647
+ // Note that due to sbt dependency semantics (the fact that all dependencies are always run),
648
+ // we duplicate the below logic in the verifyDockerfile task def. Otherwise, we couldn't
649
+ // control the error behavior via the verifyDockerfileOnBuild flag above.
650
+ if (! isUnchanged) {
651
+ if (verifyDockerfileIsStrict.value) {
652
+ sys.error(
653
+ s " Dockerfile ${dockerfileLocation.value} is not up-to-date, stopping build. Run " +
654
+ " `generateDockerfile` to update."
655
+ )
656
+ } else {
657
+ Keys .streams.value.log.warn(
658
+ s " Dockerfile ${dockerfileLocation.value} is not up-to-date. Run `generateDockerfile` " +
659
+ " to update."
660
+ )
661
+ }
662
+ }
663
+ }
664
+
580
665
val logger = Keys .streams.value.log
581
666
logger.info(s " Building main image ${mainImageNameSuffix.value}... " )
582
667
@@ -690,7 +775,10 @@ $DOCKERFILE_SIGIL
690
775
},
691
776
dockerMainArgs := Seq .empty,
692
777
dockerWorkdir := " /stage" ,
778
+ verifyDockerfileIsStrict := true ,
779
+ verifyDockerfileOnBuild := false ,
693
780
generateDockerfile := generateDockerfileDef.value,
781
+ verifyDockerfile := verifyDockerfileDef.value,
694
782
dockerDependencyStage := dependencyStageDef.value,
695
783
dockerMainStage := mainImageStageDef.value,
696
784
dockerDependencyBuild := dependencyBuildDef.value,
0 commit comments