@@ -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,52 @@ 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
+ case _ => Seq ()
130
+ }
108
131
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) ++
132
+ val stage1 : Seq [CmdLike ] = generalCommands ++
133
+ Seq (
134
+ makeUserAdd(user, uid, gid),
135
+ makeWorkdir(dockerBaseDirectory)) ++
136
+ (strategy match {
137
+ case DockerPermissionStrategy .MultiStage =>
138
+ Seq (makeCopyFrom(dockerBaseDirectory, stage0name, user, group))
139
+ case DockerPermissionStrategy .Run =>
140
+ Seq (makeCopy(dockerBaseDirectory), makeChmod(dockerChmodType.value, Seq (dockerBaseDirectory)))
141
+ case DockerPermissionStrategy .CopyChown =>
142
+ Seq (makeCopyChown(dockerBaseDirectory, user, group))
143
+ case DockerPermissionStrategy .None =>
144
+ Seq (makeCopy(dockerBaseDirectory))
145
+ }) ++
113
146
dockerLabels.value.map(makeLabel) ++
114
147
dockerEnvVars.value.map(makeEnvVar) ++
115
148
makeExposePorts(dockerExposedPorts.value, dockerExposedUdpPorts.value) ++
116
149
makeVolumes(dockerExposedVolumes.value, user, group) ++
117
- Seq (makeUser(user), makeEntrypoint(dockerEntrypoint.value), makeCmd(dockerCmd.value))
150
+ Seq (
151
+ makeUser(uid),
152
+ makeEntrypoint(dockerEntrypoint.value),
153
+ makeCmd(dockerCmd.value))
154
+
155
+ stage0 ++ stage1
118
156
}
119
157
) ++ mapGenericFilesToDocker ++ inConfig(Docker )(
120
158
Seq (
@@ -153,15 +191,16 @@ object DockerPlugin extends AutoPlugin {
153
191
stagingDirectory := (target in Docker ).value / " stage" ,
154
192
target := target.value / " docker" ,
155
193
daemonUser := " daemon" ,
156
- daemonGroup := daemonUser.value ,
194
+ daemonGroup := " root " ,
157
195
defaultLinuxInstallLocation := " /opt/docker" ,
158
196
validatePackage := Validation
159
197
.runAndThrow(validatePackageValidators.value, streams.value.log),
160
198
validatePackageValidators := Seq (
161
199
nonEmptyMappings((mappings in Docker ).value),
162
200
filesExist((mappings in Docker ).value),
163
201
validateExposedPorts(dockerExposedPorts.value, dockerExposedUdpPorts.value),
164
- validateDockerVersion(dockerVersion.value)
202
+ validateDockerVersion(dockerVersion.value),
203
+ validateDockerPermissionStrategy(dockerPermissionStrategy.value, dockerVersion.value)
165
204
),
166
205
dockerPackageMappings := MappingsHelper .contentOf(sourceDirectory.value),
167
206
dockerGenerateConfig := {
@@ -185,6 +224,14 @@ object DockerPlugin extends AutoPlugin {
185
224
private final def makeFrom (dockerBaseImage : String ): CmdLike =
186
225
Cmd (" FROM" , dockerBaseImage)
187
226
227
+ /**
228
+ * @param dockerBaseImage
229
+ * @param name
230
+ * @return FROM command
231
+ */
232
+ private final def makeFromAs (dockerBaseImage : String , name : String ): CmdLike =
233
+ Cmd (" FROM" , dockerBaseImage, " as" , name)
234
+
188
235
/**
189
236
* @param label
190
237
* @return LABEL command
@@ -210,29 +257,46 @@ object DockerPlugin extends AutoPlugin {
210
257
Cmd (" WORKDIR" , dockerBaseDirectory)
211
258
212
259
/**
213
- * @param dockerVersion
214
260
* @param dockerBaseDirectory the installation directory
261
+ * @return COPY command copying all files inside the installation directory
262
+ */
263
+ private final def makeCopy (dockerBaseDirectory : String ): CmdLike = {
264
+
265
+ /**
266
+ * This is the file path of the file in the Docker image, and does not depend on the OS where the image
267
+ * is being built. This means that it needs to be the Unix file separator even when the image is built
268
+ * on e.g. Windows systems.
269
+ */
270
+ val files = dockerBaseDirectory.split(UnixSeparatorChar )(1 )
271
+ Cmd (" COPY" , s " $files / $files" )
272
+ }
273
+
274
+ /**
275
+ * @param dockerBaseDirectory the installation directory
276
+ * @param from files are copied from the given build stage
215
277
* @param daemonUser
216
278
* @param daemonGroup
217
- * @return ADD command adding all files inside the installation directory
279
+ * @return COPY command copying all files inside the directory from another build stage.
218
280
*/
219
- private final def makeAdd (dockerVersion : Option [DockerVersion ],
220
- dockerBaseDirectory : String ,
221
- daemonUser : String ,
222
- daemonGroup : String ): Seq [CmdLike ] = {
281
+ private final def makeCopyFrom (dockerBaseDirectory : String , from : String , daemonUser : String , daemonGroup : String ): CmdLike =
282
+ Cmd (" COPY" , s " --from= $from --chown= $daemonUser: $daemonGroup $dockerBaseDirectory $dockerBaseDirectory" )
283
+
284
+ /**
285
+ * @param dockerBaseDirectory the installation directory
286
+ * @param from files are copied from the given build stage
287
+ * @param daemonUser
288
+ * @param daemonGroup
289
+ * @return COPY command copying all files inside the directory from another build stage.
290
+ */
291
+ private final def makeCopyChown (dockerBaseDirectory : String , daemonUser : String , daemonGroup : String ): CmdLike = {
223
292
224
293
/**
225
294
* This is the file path of the file in the Docker image, and does not depend on the OS where the image
226
295
* is being built. This means that it needs to be the Unix file separator even when the image is built
227
296
* on e.g. Windows systems.
228
297
*/
229
298
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
- }
299
+ Cmd (" COPY" , s " --chown= $daemonUser: $daemonGroup $files / $files" )
236
300
}
237
301
238
302
/**
@@ -243,12 +307,29 @@ object DockerPlugin extends AutoPlugin {
243
307
private final def makeChown (daemonUser : String , daemonGroup : String , directories : Seq [String ]): CmdLike =
244
308
ExecCmd (" RUN" , Seq (" chown" , " -R" , s " $daemonUser: $daemonGroup" ) ++ directories : _* )
245
309
310
+ /**
311
+ * @return chown command, owning the installation directory with the daemonuser
312
+ */
313
+ private final def makeChmod (chmodType : DockerChmodType , directories : Seq [String ]): CmdLike = {
314
+ ExecCmd (" RUN" , Seq (" chmod" , " -R" , chmodType.argument) ++ directories : _* )
315
+ }
316
+
246
317
/**
247
318
* @param daemonUser
319
+ * @param userId
320
+ * @param groupId
321
+ * @return useradd to create the daemon user with the given userId and groupId
322
+ */
323
+ private final def makeUserAdd (daemonUser : String , userId : Int , groupId : Int ): CmdLike =
324
+ Cmd (" RUN" , " id" , " -u" , daemonUser, " ||" ,
325
+ " useradd" , " --system" , " --create-home" , " --uid" , userId.toString, " --gid" , groupId.toString, daemonUser)
326
+
327
+ /**
328
+ * @param userId userId of the daemon user
248
329
* @return USER docker command
249
330
*/
250
- private final def makeUser (daemonUser : String ): CmdLike =
251
- Cmd (" USER" , daemonUser )
331
+ private final def makeUser (userId : Int ): CmdLike =
332
+ Cmd (" USER" , userId.toString )
252
333
253
334
/**
254
335
* @param entrypoint
@@ -467,10 +548,50 @@ object DockerPlugin extends AutoPlugin {
467
548
|As a last resort you could hard code the docker version, but it's not recommended!!
468
549
|
469
550
| import com.typesafe.sbt.packager.docker.DockerVersion
470
- | dockerVersion := Some(DockerVersion(17, 5, 0, Some("ce"))
551
+ | dockerVersion := Some(DockerVersion(18, 9, 0, Some("ce"))
552
+ """ .stripMargin
553
+ )
554
+ )
555
+ }
556
+ }
557
+
558
+ private [this ] def validateDockerPermissionStrategy (
559
+ strategy : DockerPermissionStrategy ,
560
+ dockerVersion : Option [DockerVersion ]): Validation .Validator = () => {
561
+ (strategy, dockerVersion) match {
562
+ case (DockerPermissionStrategy .MultiStage , Some (ver)) if ! DockerSupport .multiStage(ver) =>
563
+ List (
564
+ ValidationError (
565
+ description =
566
+ s " The detected Docker version $ver is not compatible with DockerPermissionStrategy.MultiStage " ,
567
+ howToFix = """ |sbt-native packager tries to parse the `docker version` output.
568
+ |To use multi-stage build, upgrade your Docker, pick another strategy, or override dockerVersion:
569
+ |
570
+ | import com.typesafe.sbt.packager.docker.DockerPermissionStrategy
571
+ | dockerPermissionStrategy := DockerPermissionStrategy.Run
572
+ |
573
+ | import com.typesafe.sbt.packager.docker.DockerVersion
574
+ | dockerVersion := Some(DockerVersion(18, 9, 0, Some("ce"))
575
+ """ .stripMargin
576
+ )
577
+ )
578
+ case (DockerPermissionStrategy .CopyChown , Some (ver)) if ! DockerSupport .chownFlag(ver) =>
579
+ List (
580
+ ValidationError (
581
+ description =
582
+ s " The detected Docker version $ver is not compatible with DockerPermissionStrategy.CopyChown " ,
583
+ howToFix = """ |sbt-native packager tries to parse the `docker version` output.
584
+ |To use --chown flag, upgrade your Docker, pick another strategy, or override dockerVersion:
585
+ |
586
+ | import com.typesafe.sbt.packager.docker.DockerPermissionStrategy
587
+ | dockerPermissionStrategy := DockerPermissionStrategy.Run
588
+ |
589
+ | import com.typesafe.sbt.packager.docker.DockerVersion
590
+ | dockerVersion := Some(DockerVersion(18, 9, 0, Some("ce"))
471
591
""" .stripMargin
472
592
)
473
593
)
594
+ case _ => List .empty
474
595
}
475
596
}
476
597
0 commit comments