@@ -48,7 +48,7 @@ import scala.util.Try
48
48
*/
49
49
object DockerPlugin extends AutoPlugin {
50
50
51
- object autoImport extends DockerKeys {
51
+ object autoImport extends DockerKeysEx {
52
52
val Docker : Configuration = config(" docker" )
53
53
54
54
val DockerAlias = com.typesafe.sbt.packager.docker.DockerAlias
@@ -57,7 +57,7 @@ object DockerPlugin extends AutoPlugin {
57
57
import autoImport ._
58
58
59
59
/**
60
- * The separator used by makeAdd should be always forced to UNIX separator.
60
+ * The separator used by makeCopy should be always forced to UNIX separator.
61
61
* The separator doesn't depend on the OS where Dockerfile is being built.
62
62
*/
63
63
val UnixSeparatorChar = '/'
@@ -66,6 +66,11 @@ object DockerPlugin extends AutoPlugin {
66
66
67
67
override def projectConfigurations : Seq [Configuration ] = Seq (Docker )
68
68
69
+ override lazy val globalSettings : Seq [Setting [_]] = Seq (
70
+ dockerPermissionStrategy := DockerPermissionStrategy .MultiStage ,
71
+ dockerChmodType := DockerChmodType .UserGroupReadExecute
72
+ )
73
+
69
74
override lazy val projectSettings : Seq [Setting [_]] = Seq (
70
75
dockerBaseImage := " openjdk:8" ,
71
76
dockerExposedPorts := Seq (),
@@ -102,19 +107,48 @@ object DockerPlugin extends AutoPlugin {
102
107
dockerRmiCommand := dockerExecCommand.value ++ Seq (" rmi" ),
103
108
dockerBuildCommand := dockerExecCommand.value ++ Seq (" build" ) ++ dockerBuildOptions.value ++ Seq (" ." ),
104
109
dockerCommands := {
110
+ val strategy = dockerPermissionStrategy.value
105
111
val dockerBaseDirectory = (defaultLinuxInstallLocation in Docker ).value
106
112
val user = (daemonUser in Docker ).value
107
113
val group = (daemonGroup in Docker ).value
114
+ val base = dockerBaseImage.value
115
+ val uid = 1001
116
+ val gid = 0
117
+
118
+ val generalCommands = makeFrom(base) +: makeMaintainer((maintainer in Docker ).value).toSeq
119
+ val stage0name = " stage0"
120
+ val stage0 : Seq [CmdLike ] = strategy match {
121
+ case DockerPermissionStrategy .MultiStage =>
122
+ Seq (
123
+ makeFromAs(base, stage0name),
124
+ makeWorkdir(dockerBaseDirectory),
125
+ makeUserAdd(user, uid, gid),
126
+ makeCopy(dockerBaseDirectory),
127
+ makeChmod(dockerChmodType.value, Seq (dockerBaseDirectory)),
128
+ DockerStageBreak
129
+ )
130
+ case _ => Seq ()
131
+ }
108
132
109
- val generalCommands = makeFrom(dockerBaseImage.value) +: makeMaintainer((maintainer in Docker ).value).toSeq
110
-
111
- generalCommands ++
112
- Seq (makeWorkdir(dockerBaseDirectory)) ++ makeAdd(dockerVersion.value, dockerBaseDirectory, user, group) ++
133
+ val stage1 : Seq [CmdLike ] = generalCommands ++
134
+ Seq (makeUserAdd(user, uid, gid), makeWorkdir(dockerBaseDirectory)) ++
135
+ (strategy match {
136
+ case DockerPermissionStrategy .MultiStage =>
137
+ Seq (makeCopyFrom(dockerBaseDirectory, stage0name, user, group))
138
+ case DockerPermissionStrategy .Run =>
139
+ Seq (makeCopy(dockerBaseDirectory), makeChmod(dockerChmodType.value, Seq (dockerBaseDirectory)))
140
+ case DockerPermissionStrategy .CopyChown =>
141
+ Seq (makeCopyChown(dockerBaseDirectory, user, group))
142
+ case DockerPermissionStrategy .None =>
143
+ Seq (makeCopy(dockerBaseDirectory))
144
+ }) ++
113
145
dockerLabels.value.map(makeLabel) ++
114
146
dockerEnvVars.value.map(makeEnvVar) ++
115
147
makeExposePorts(dockerExposedPorts.value, dockerExposedUdpPorts.value) ++
116
148
makeVolumes(dockerExposedVolumes.value, user, group) ++
117
- Seq (makeUser(user), makeEntrypoint(dockerEntrypoint.value), makeCmd(dockerCmd.value))
149
+ Seq (makeUser(uid), makeEntrypoint(dockerEntrypoint.value), makeCmd(dockerCmd.value))
150
+
151
+ stage0 ++ stage1
118
152
}
119
153
) ++ mapGenericFilesToDocker ++ inConfig(Docker )(
120
154
Seq (
@@ -153,15 +187,16 @@ object DockerPlugin extends AutoPlugin {
153
187
stagingDirectory := (target in Docker ).value / " stage" ,
154
188
target := target.value / " docker" ,
155
189
daemonUser := " daemon" ,
156
- daemonGroup := daemonUser.value ,
190
+ daemonGroup := " root " ,
157
191
defaultLinuxInstallLocation := " /opt/docker" ,
158
192
validatePackage := Validation
159
193
.runAndThrow(validatePackageValidators.value, streams.value.log),
160
194
validatePackageValidators := Seq (
161
195
nonEmptyMappings((mappings in Docker ).value),
162
196
filesExist((mappings in Docker ).value),
163
197
validateExposedPorts(dockerExposedPorts.value, dockerExposedUdpPorts.value),
164
- validateDockerVersion(dockerVersion.value)
198
+ validateDockerVersion(dockerVersion.value),
199
+ validateDockerPermissionStrategy(dockerPermissionStrategy.value, dockerVersion.value)
165
200
),
166
201
dockerPackageMappings := MappingsHelper .contentOf(sourceDirectory.value),
167
202
dockerGenerateConfig := {
@@ -185,6 +220,14 @@ object DockerPlugin extends AutoPlugin {
185
220
private final def makeFrom (dockerBaseImage : String ): CmdLike =
186
221
Cmd (" FROM" , dockerBaseImage)
187
222
223
+ /**
224
+ * @param dockerBaseImage
225
+ * @param name
226
+ * @return FROM command
227
+ */
228
+ private final def makeFromAs (dockerBaseImage : String , name : String ): CmdLike =
229
+ Cmd (" FROM" , dockerBaseImage, " as" , name)
230
+
188
231
/**
189
232
* @param label
190
233
* @return LABEL command
@@ -210,29 +253,49 @@ object DockerPlugin extends AutoPlugin {
210
253
Cmd (" WORKDIR" , dockerBaseDirectory)
211
254
212
255
/**
213
- * @param dockerVersion
214
256
* @param dockerBaseDirectory the installation directory
257
+ * @return COPY command copying all files inside the installation directory
258
+ */
259
+ private final def makeCopy (dockerBaseDirectory : String ): CmdLike = {
260
+
261
+ /**
262
+ * This is the file path of the file in the Docker image, and does not depend on the OS where the image
263
+ * is being built. This means that it needs to be the Unix file separator even when the image is built
264
+ * on e.g. Windows systems.
265
+ */
266
+ val files = dockerBaseDirectory.split(UnixSeparatorChar )(1 )
267
+ Cmd (" COPY" , s " $files / $files" )
268
+ }
269
+
270
+ /**
271
+ * @param dockerBaseDirectory the installation directory
272
+ * @param from files are copied from the given build stage
215
273
* @param daemonUser
216
274
* @param daemonGroup
217
- * @return ADD command adding all files inside the installation directory
275
+ * @return COPY command copying all files inside the directory from another build stage.
218
276
*/
219
- private final def makeAdd (dockerVersion : Option [DockerVersion ],
220
- dockerBaseDirectory : String ,
221
- daemonUser : String ,
222
- daemonGroup : String ): Seq [CmdLike ] = {
277
+ private final def makeCopyFrom (dockerBaseDirectory : String ,
278
+ from : String ,
279
+ daemonUser : String ,
280
+ daemonGroup : String ): CmdLike =
281
+ Cmd (" COPY" , s " --from= $from --chown= $daemonUser: $daemonGroup $dockerBaseDirectory $dockerBaseDirectory" )
282
+
283
+ /**
284
+ * @param dockerBaseDirectory the installation directory
285
+ * @param from files are copied from the given build stage
286
+ * @param daemonUser
287
+ * @param daemonGroup
288
+ * @return COPY command copying all files inside the directory from another build stage.
289
+ */
290
+ private final def makeCopyChown (dockerBaseDirectory : String , daemonUser : String , daemonGroup : String ): CmdLike = {
223
291
224
292
/**
225
293
* This is the file path of the file in the Docker image, and does not depend on the OS where the image
226
294
* is being built. This means that it needs to be the Unix file separator even when the image is built
227
295
* on e.g. Windows systems.
228
296
*/
229
297
val files = dockerBaseDirectory.split(UnixSeparatorChar )(1 )
230
-
231
- if (dockerVersion.exists(DockerSupport .chownFlag)) {
232
- Seq (Cmd (" ADD" , s " --chown= $daemonUser: $daemonGroup $files / $files" ))
233
- } else {
234
- Seq (Cmd (" ADD" , s " $files / $files" ), makeChown(daemonUser, daemonGroup, " ." :: Nil ))
235
- }
298
+ Cmd (" COPY" , s " --chown= $daemonUser: $daemonGroup $files / $files" )
236
299
}
237
300
238
301
/**
@@ -243,12 +306,41 @@ object DockerPlugin extends AutoPlugin {
243
306
private final def makeChown (daemonUser : String , daemonGroup : String , directories : Seq [String ]): CmdLike =
244
307
ExecCmd (" RUN" , Seq (" chown" , " -R" , s " $daemonUser: $daemonGroup" ) ++ directories : _* )
245
308
309
+ /**
310
+ * @return chown command, owning the installation directory with the daemonuser
311
+ */
312
+ private final def makeChmod (chmodType : DockerChmodType , directories : Seq [String ]): CmdLike =
313
+ ExecCmd (" RUN" , Seq (" chmod" , " -R" , chmodType.argument) ++ directories : _* )
314
+
246
315
/**
247
316
* @param daemonUser
317
+ * @param userId
318
+ * @param groupId
319
+ * @return useradd to create the daemon user with the given userId and groupId
320
+ */
321
+ private final def makeUserAdd (daemonUser : String , userId : Int , groupId : Int ): CmdLike =
322
+ Cmd (
323
+ " RUN" ,
324
+ " id" ,
325
+ " -u" ,
326
+ daemonUser,
327
+ " ||" ,
328
+ " useradd" ,
329
+ " --system" ,
330
+ " --create-home" ,
331
+ " --uid" ,
332
+ userId.toString,
333
+ " --gid" ,
334
+ groupId.toString,
335
+ daemonUser
336
+ )
337
+
338
+ /**
339
+ * @param userId userId of the daemon user
248
340
* @return USER docker command
249
341
*/
250
- private final def makeUser (daemonUser : String ): CmdLike =
251
- Cmd (" USER" , daemonUser )
342
+ private final def makeUser (userId : Int ): CmdLike =
343
+ Cmd (" USER" , userId.toString )
252
344
253
345
/**
254
346
* @param entrypoint
@@ -467,11 +559,52 @@ object DockerPlugin extends AutoPlugin {
467
559
|As a last resort you could hard code the docker version, but it's not recommended!!
468
560
|
469
561
| import com.typesafe.sbt.packager.docker.DockerVersion
470
- | dockerVersion := Some(DockerVersion(17, 5 , 0, Some("ce"))
562
+ | dockerVersion := Some(DockerVersion(18, 9 , 0, Some("ce"))
471
563
""" .stripMargin
472
564
)
473
565
)
474
566
}
475
567
}
476
568
569
+ private [this ] def validateDockerPermissionStrategy (strategy : DockerPermissionStrategy ,
570
+ dockerVersion : Option [DockerVersion ]): Validation .Validator =
571
+ () => {
572
+ (strategy, dockerVersion) match {
573
+ case (DockerPermissionStrategy .MultiStage , Some (ver)) if ! DockerSupport .multiStage(ver) =>
574
+ List (
575
+ ValidationError (
576
+ description =
577
+ s " The detected Docker version $ver is not compatible with DockerPermissionStrategy.MultiStage " ,
578
+ howToFix =
579
+ """ |sbt-native packager tries to parse the `docker version` output.
580
+ |To use multi-stage build, upgrade your Docker, pick another strategy, or override dockerVersion:
581
+ |
582
+ | import com.typesafe.sbt.packager.docker.DockerPermissionStrategy
583
+ | dockerPermissionStrategy := DockerPermissionStrategy.Run
584
+ |
585
+ | import com.typesafe.sbt.packager.docker.DockerVersion
586
+ | dockerVersion := Some(DockerVersion(18, 9, 0, Some("ce"))
587
+ """ .stripMargin
588
+ )
589
+ )
590
+ case (DockerPermissionStrategy .CopyChown , Some (ver)) if ! DockerSupport .chownFlag(ver) =>
591
+ List (
592
+ ValidationError (
593
+ description =
594
+ s " The detected Docker version $ver is not compatible with DockerPermissionStrategy.CopyChown " ,
595
+ howToFix = """ |sbt-native packager tries to parse the `docker version` output.
596
+ |To use --chown flag, upgrade your Docker, pick another strategy, or override dockerVersion:
597
+ |
598
+ | import com.typesafe.sbt.packager.docker.DockerPermissionStrategy
599
+ | dockerPermissionStrategy := DockerPermissionStrategy.Run
600
+ |
601
+ | import com.typesafe.sbt.packager.docker.DockerVersion
602
+ | dockerVersion := Some(DockerVersion(18, 9, 0, Some("ce"))
603
+ """ .stripMargin
604
+ )
605
+ )
606
+ case _ => List .empty
607
+ }
608
+ }
609
+
477
610
}
0 commit comments