@@ -1613,4 +1613,96 @@ extension IntegrationSuite {
16131613 throw error
16141614 }
16151615 }
1616+
1617+ func testReadOnlyRootfs( ) async throws {
1618+ let id = " test-readonly-rootfs "
1619+
1620+ let bs = try await bootstrap ( id)
1621+ var rootfs = bs. rootfs
1622+ rootfs. options. append ( " ro " )
1623+ let container = try LinuxContainer ( id, rootfs: rootfs, vmm: bs. vmm) { config in
1624+ config. process. arguments = [ " touch " , " /testfile " ]
1625+ config. bootLog = bs. bootLog
1626+ }
1627+
1628+ try await container. create ( )
1629+ try await container. start ( )
1630+
1631+ let status = try await container. wait ( )
1632+ try await container. stop ( )
1633+
1634+ // touch should fail on a read-only rootfs
1635+ guard status. exitCode != 0 else {
1636+ throw IntegrationError . assert ( msg: " touch should have failed on read-only rootfs " )
1637+ }
1638+ }
1639+
1640+ func testReadOnlyRootfsHostsFileWritten( ) async throws {
1641+ let id = " test-readonly-rootfs-hosts "
1642+
1643+ let bs = try await bootstrap ( id)
1644+ var rootfs = bs. rootfs
1645+ rootfs. options. append ( " ro " )
1646+ let buffer = BufferWriter ( )
1647+ let entry = Hosts . Entry. localHostIPV4 ( comment: " ReadOnlyTest " )
1648+ let container = try LinuxContainer ( id, rootfs: rootfs, vmm: bs. vmm) { config in
1649+ // Verify /etc/hosts was written before rootfs was remounted read-only
1650+ config. process. arguments = [ " cat " , " /etc/hosts " ]
1651+ config. process. stdout = buffer
1652+ config. hosts = Hosts ( entries: [ entry] )
1653+ config. bootLog = bs. bootLog
1654+ }
1655+
1656+ try await container. create ( )
1657+ try await container. start ( )
1658+
1659+ let status = try await container. wait ( )
1660+ try await container. stop ( )
1661+
1662+ guard status. exitCode == 0 else {
1663+ throw IntegrationError . assert ( msg: " cat /etc/hosts failed with status \( status) " )
1664+ }
1665+
1666+ guard let output = String ( data: buffer. data, encoding: . utf8) else {
1667+ throw IntegrationError . assert ( msg: " failed to convert stdout to UTF8 " )
1668+ }
1669+
1670+ guard output. contains ( " ReadOnlyTest " ) else {
1671+ throw IntegrationError . assert ( msg: " expected /etc/hosts to contain our entry, got: \( output) " )
1672+ }
1673+ }
1674+
1675+ func testReadOnlyRootfsDNSConfigured( ) async throws {
1676+ let id = " test-readonly-rootfs-dns "
1677+
1678+ let bs = try await bootstrap ( id)
1679+ var rootfs = bs. rootfs
1680+ rootfs. options. append ( " ro " )
1681+ let buffer = BufferWriter ( )
1682+ let container = try LinuxContainer ( id, rootfs: rootfs, vmm: bs. vmm) { config in
1683+ // Verify /etc/resolv.conf was written before rootfs was remounted read-only
1684+ config. process. arguments = [ " cat " , " /etc/resolv.conf " ]
1685+ config. process. stdout = buffer
1686+ config. dns = DNS ( nameservers: [ " 8.8.8.8 " , " 8.8.4.4 " ] )
1687+ config. bootLog = bs. bootLog
1688+ }
1689+
1690+ try await container. create ( )
1691+ try await container. start ( )
1692+
1693+ let status = try await container. wait ( )
1694+ try await container. stop ( )
1695+
1696+ guard status. exitCode == 0 else {
1697+ throw IntegrationError . assert ( msg: " cat /etc/resolv.conf failed with status \( status) " )
1698+ }
1699+
1700+ guard let output = String ( data: buffer. data, encoding: . utf8) else {
1701+ throw IntegrationError . assert ( msg: " failed to convert stdout to UTF8 " )
1702+ }
1703+
1704+ guard output. contains ( " 8.8.8.8 " ) && output. contains ( " 8.8.4.4 " ) else {
1705+ throw IntegrationError . assert ( msg: " expected /etc/resolv.conf to contain DNS servers, got: \( output) " )
1706+ }
1707+ }
16161708}
0 commit comments