@@ -1407,5 +1407,149 @@ extension TestCLIBuildBase {
14071407 )
14081408 #expect( try self . inspectImage ( imageName) == imageName, " expected to have successfully built \( imageName) " )
14091409 }
1410+
1411+ @Test func testCopyFromLocalImage( ) throws {
1412+ let baseTempDir : URL = try createTempDir ( )
1413+ let tempDir : URL = try createTempDir ( )
1414+ defer {
1415+ try ! FileManager . default. removeItem ( at: baseTempDir)
1416+ try ! FileManager . default. removeItem ( at: tempDir)
1417+ }
1418+
1419+ let baseImageName = " local-base: \( UUID ( ) . uuidString) "
1420+ let baseDockerfile =
1421+ """
1422+ FROM scratch
1423+ ADD hello.txt /hello.txt
1424+ """
1425+ let baseContext : [ FileSystemEntry ] = [
1426+ . file( " hello.txt " , content: . data( " hello \n " . data ( using: . utf8) !) )
1427+ ]
1428+ try createContext ( tempDir: baseTempDir, dockerfile: baseDockerfile, context: baseContext)
1429+
1430+ try self . build ( tag: baseImageName, tempDir: baseTempDir)
1431+ #expect( try self . inspectImage ( baseImageName) == baseImageName, " expected to have successfully built \( baseImageName) " )
1432+
1433+ let dockerfile =
1434+ """
1435+ FROM ghcr.io/linuxcontainers/alpine:3.20
1436+ COPY --from= \( baseImageName) /hello.txt /copied.txt
1437+ RUN cat /copied.txt
1438+ """
1439+ try createContext ( tempDir: tempDir, dockerfile: dockerfile)
1440+
1441+ let imageName = " registry.local/copy-from-local: \( UUID ( ) . uuidString) "
1442+ try self . build ( tag: imageName, tempDir: tempDir)
1443+ #expect( try self . inspectImage ( imageName) == imageName, " expected to have successfully built \( imageName) " )
1444+ }
1445+
1446+ @Test func testCopyFromBuildStage( ) throws {
1447+ let tempDir : URL = try createTempDir ( )
1448+ defer {
1449+ try ! FileManager . default. removeItem ( at: tempDir)
1450+ }
1451+
1452+ let dockerfile =
1453+ """
1454+ FROM scratch AS builder
1455+ ADD hello.txt /hello.txt
1456+
1457+ FROM ghcr.io/linuxcontainers/alpine:3.20
1458+ COPY --from=builder /hello.txt /copied.txt
1459+ RUN cat /copied.txt
1460+ """
1461+ let context : [ FileSystemEntry ] = [
1462+ . file( " hello.txt " , content: . data( " hello \n " . data ( using: . utf8) !) )
1463+ ]
1464+ try createContext ( tempDir: tempDir, dockerfile: dockerfile, context: context)
1465+
1466+ let imageName = " registry.local/copy-from-stage: \( UUID ( ) . uuidString) "
1467+ try self . build ( tag: imageName, tempDir: tempDir)
1468+ #expect( try self . inspectImage ( imageName) == imageName, " expected to have successfully built \( imageName) " )
1469+ }
1470+
1471+ @Test func testCopyRenameFromStage( ) throws {
1472+ let tempDir : URL = try createTempDir ( )
1473+ defer {
1474+ try ! FileManager . default. removeItem ( at: tempDir)
1475+ }
1476+
1477+ let dockerfile =
1478+ """
1479+ FROM scratch AS builder
1480+ ADD hello.txt /hello.txt
1481+
1482+ FROM ghcr.io/linuxcontainers/alpine:3.20
1483+ COPY --from=builder /hello.txt /renamed.txt
1484+ RUN cat /renamed.txt
1485+ """
1486+ let context : [ FileSystemEntry ] = [
1487+ . file( " hello.txt " , content: . data( " hello \n " . data ( using: . utf8) !) )
1488+ ]
1489+ try createContext ( tempDir: tempDir, dockerfile: dockerfile, context: context)
1490+
1491+ let imageName = " registry.local/copy-rename: \( UUID ( ) . uuidString) "
1492+ try self . build ( tag: imageName, tempDir: tempDir)
1493+ #expect( try self . inspectImage ( imageName) == imageName, " expected to have successfully built \( imageName) " )
1494+ }
1495+
1496+ @Test func testCopyMissingFileFails( ) throws {
1497+ let tempDir : URL = try createTempDir ( )
1498+ defer {
1499+ try ! FileManager . default. removeItem ( at: tempDir)
1500+ }
1501+
1502+ let dockerfile =
1503+ """
1504+ FROM scratch AS builder
1505+
1506+ FROM ghcr.io/linuxcontainers/alpine:3.20
1507+ COPY --from=builder /does-not-exist.txt /copied.txt
1508+ """
1509+ try createContext ( tempDir: tempDir, dockerfile: dockerfile)
1510+
1511+ let imageName = " registry.local/copy-missing: \( UUID ( ) . uuidString) "
1512+ #expect( throws: Error . self) {
1513+ try self . build ( tag: imageName, tempDir: tempDir)
1514+ }
1515+ }
1516+ }
1517+
1518+ @Test func testCopyInvalidStageFails( ) throws {
1519+ let tempDir : URL = try createTempDir ( )
1520+ defer {
1521+ try ! FileManager . default. removeItem ( at: tempDir)
1522+ }
1523+
1524+ let dockerfile =
1525+ """
1526+ FROM ghcr.io/linuxcontainers/alpine:3.20
1527+ COPY --from=not_a_stage /hello.txt /copied.txt
1528+ """
1529+ try createContext ( tempDir: tempDir, dockerfile: dockerfile)
1530+
1531+ let imageName = " registry.local/copy-invalid-stage: \( UUID ( ) . uuidString) "
1532+ #expect( throws: Error . self) {
1533+ try self . build ( tag: imageName, tempDir: tempDir)
1534+ }
1535+ }
1536+
1537+ @Test func testCopyFromNonexistentImageFails( ) throws {
1538+ let tempDir : URL = try createTempDir ( )
1539+ defer {
1540+ try ! FileManager . default. removeItem ( at: tempDir)
1541+ }
1542+
1543+ let dockerfile =
1544+ """
1545+ FROM ghcr.io/linuxcontainers/alpine:3.20
1546+ COPY --from=doesnotexist:latest /hello.txt /copied.txt
1547+ """
1548+ try createContext ( tempDir: tempDir, dockerfile: dockerfile)
1549+
1550+ let imageName = " registry.local/copy-bad-image: \( UUID ( ) . uuidString) "
1551+ #expect( throws: Error . self) {
1552+ try self . build ( tag: imageName, tempDir: tempDir)
1553+ }
14101554 }
14111555}
0 commit comments