@@ -38,6 +38,9 @@ enum AllowHTTP: String, ExpressibleByArgument, CaseIterable { case source, desti
3838 @Argument ( help: " Executable to package " )
3939 private var executable : String
4040
41+ @Option ( help: " Resource bundle directory " )
42+ private var resources : [ String ] = [ ]
43+
4144 @Option ( help: " Username " )
4245 private var username : String ?
4346
@@ -72,9 +75,6 @@ enum AllowHTTP: String, ExpressibleByArgument, CaseIterable { case source, desti
7275 let baseimage = try ImageReference ( fromString: from, defaultRegistry: defaultRegistry)
7376 var destination_image = try ImageReference ( fromString: repository, defaultRegistry: defaultRegistry)
7477
75- let executableURL = URL ( fileURLWithPath: executable)
76- let payload = try Data ( contentsOf: executableURL)
77-
7878 let authProvider : AuthorizationProvider ?
7979 if !netrc {
8080 authProvider = nil
@@ -87,8 +87,9 @@ enum AllowHTTP: String, ExpressibleByArgument, CaseIterable { case source, desti
8787 authProvider = try NetrcAuthorizationProvider ( defaultNetrc)
8888 }
8989
90- // Create clients for the source and destination registries
91- // The base image may be stored on a different registry, so two clients are needed.
90+ // MARK: Create registry clients
91+
92+ // The base image may be stored on a different registry to the final destination, so two clients are needed.
9293 // `scratch` is a special case and requires no source client.
9394 let source : RegistryClient ?
9495 if from == " scratch " {
@@ -113,7 +114,10 @@ enum AllowHTTP: String, ExpressibleByArgument, CaseIterable { case source, desti
113114
114115 // MARK: Find the base image
115116
116- let elfheader = ELF . read ( [ UInt8] ( payload) )
117+ // Try to detect the architecture of the application executable so a suitable base image can be selected.
118+ // This reduces the risk of accidentally creating an image which stacks an aarch64 executable on top of an x86_64 base image.
119+ let executableURL = URL ( fileURLWithPath: executable)
120+ let elfheader = try ELF . read ( at: executableURL)
117121 let architecture =
118122 architecture
119123 ?? ProcessInfo . processInfo. environment [ " CONTAINERTOOL_ARCHITECTURE " ]
@@ -146,20 +150,30 @@ enum AllowHTTP: String, ExpressibleByArgument, CaseIterable { case source, desti
146150 if verbose { log ( " Using scratch as base image " ) }
147151 }
148152
149- // MARK: Build the application layer
153+ // MARK: Upload resource layers
150154
151- let payload_name = executableURL. lastPathComponent
152- let tardiff = try tar ( [ UInt8] ( payload) , filename: payload_name)
153- log ( " Built application layer " )
155+ var resourceLayers : [ RegistryClient . ImageLayer ] = [ ]
156+ for resourceDir in resources {
157+ let layer = try await destination. uploadLayer (
158+ repository: destination_image. repository,
159+ contents: try Archive ( ) . appendingRecursively ( atPath: resourceDir) . bytes
160+ )
154161
155- // MARK: Upload the application layer
162+ if verbose {
163+ log ( " resource layer: \( layer. descriptor. digest) ( \( layer. descriptor. size) bytes) " )
164+ }
156165
157- let application_layer = try await destination. uploadImageLayer (
166+ resourceLayers. append ( layer)
167+ }
168+
169+ // MARK: Upload the application layer
170+ let applicationLayer = try await destination. uploadLayer (
158171 repository: destination_image. repository,
159- layer : Data ( tardiff )
172+ contents : try Archive ( ) . appendingFile ( at : executableURL ) . bytes
160173 )
161-
162- if verbose { log ( " application layer: \( application_layer. digest) ( \( application_layer. size) bytes) " ) }
174+ if verbose {
175+ log ( " application layer: \( applicationLayer. descriptor. digest) ( \( applicationLayer. descriptor. size) bytes) " )
176+ }
163177
164178 // MARK: Create the application configuration
165179
@@ -168,7 +182,7 @@ enum AllowHTTP: String, ExpressibleByArgument, CaseIterable { case source, desti
168182 // Inherit the configuration of the base image - UID, GID, environment etc -
169183 // and override the entrypoint.
170184 var inherited_config = baseimage_config. config ?? . init( )
171- inherited_config. Entrypoint = [ " / \( payload_name ) " ]
185+ inherited_config. Entrypoint = [ " / \( executableURL . lastPathComponent ) " ]
172186 inherited_config. Cmd = [ ]
173187 inherited_config. WorkingDir = " / "
174188
@@ -182,7 +196,9 @@ enum AllowHTTP: String, ExpressibleByArgument, CaseIterable { case source, desti
182196 // The diff_id is the digest of the _uncompressed_ layer archive.
183197 // It is used by the runtime, which might not store the layers in
184198 // the compressed form in which it received them from the registry.
185- diff_ids: baseimage_config. rootfs. diff_ids + [ digest ( of: tardiff) ]
199+ diff_ids: baseimage_config. rootfs. diff_ids
200+ + resourceLayers. map { $0. diffID }
201+ + [ applicationLayer. diffID]
186202 ) ,
187203 history: [ . init( created: timestamp, created_by: " containertool " ) ]
188204 )
@@ -200,7 +216,9 @@ enum AllowHTTP: String, ExpressibleByArgument, CaseIterable { case source, desti
200216 schemaVersion: 2 ,
201217 mediaType: " application/vnd.oci.image.manifest.v1+json " ,
202218 config: config_blob,
203- layers: baseimage_manifest. layers + [ application_layer]
219+ layers: baseimage_manifest. layers
220+ + resourceLayers. map { $0. descriptor }
221+ + [ applicationLayer. descriptor]
204222 )
205223
206224 // MARK: Upload base image
@@ -237,3 +255,16 @@ enum AllowHTTP: String, ExpressibleByArgument, CaseIterable { case source, desti
237255 print ( destination_image)
238256 }
239257}
258+
259+ extension RegistryClient {
260+ typealias DiffID = String
261+ struct ImageLayer {
262+ var descriptor : ContentDescriptor
263+ var diffID : DiffID
264+ }
265+ func uploadLayer( repository: String , contents: [ UInt8 ] ) async throws -> ImageLayer {
266+ let diffID = digest ( of: contents)
267+ let descriptor = try await self . uploadImageLayer ( repository: repository, layer: contents)
268+ return ImageLayer ( descriptor: descriptor, diffID: diffID)
269+ }
270+ }
0 commit comments