Skip to content

Commit

Permalink
Extract ResourceDisk to its own file
Browse files Browse the repository at this point in the history
  • Loading branch information
jacob-carlborg committed May 9, 2022
1 parent c41c517 commit 384d08e
Show file tree
Hide file tree
Showing 9 changed files with 747 additions and 358 deletions.
467 changes: 291 additions & 176 deletions dist/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

74 changes: 7 additions & 67 deletions src/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import flatMap from 'array.prototype.flatmap'
import * as architecture from './architecture'
import * as hostModule from './host'
import * as os from './operating_system'
import ResourceDisk from './resource_disk'
import * as vmModule from './vm'

export enum ImplementationKind {
Expand All @@ -19,13 +20,14 @@ export enum ImplementationKind {
}

export class Action {
readonly tempPath: string
readonly host: hostModule.Host
readonly operatingSystem: os.OperatingSystem

private readonly input = new Input()
private readonly resourceDisk: ResourceDisk
private readonly operatingSystem: os.OperatingSystem
private readonly implementation: Implementation
private readonly host: hostModule.Host
private readonly workDirectory
private readonly tempPath: string
private readonly sshDirectory: string
private readonly privateSshKey: fs.PathLike
private readonly publicSshKey: fs.PathLike
Expand All @@ -35,14 +37,15 @@ export class Action {
constructor() {
this.host = hostModule.Host.create()
this.tempPath = fs.mkdtempSync('/tmp/resources')
this.resourceDisk = new ResourceDisk(this.tempPath, this.host)

this.operatingSystem = os.OperatingSystem.create(
this.input.operatingSystem,
architecture.Kind.x86_64,
this.input.version
)

this.resourceDisk = ResourceDisk.for(this)

this.implementation = this.getImplementation(
this.operatingSystem.actionImplementationKind
)
Expand Down Expand Up @@ -308,69 +311,6 @@ class QemuImplementation extends Implementation {
}
}

class ResourceDisk {
readonly diskPath: string

private readonly mountName = 'RES'
private mountPath!: string

private readonly host: hostModule.Host
private readonly tempPath: string
private devicePath!: string

constructor(tempPath: string, host: hostModule.Host) {
this.host = host
this.tempPath = tempPath
this.diskPath = path.join(this.tempPath, 'res.raw')
}

async create(): Promise<string> {
core.debug('Creating resource disk')
await this.createDiskFile()
this.devicePath = await this.createDiskDevice()
await this.partitionDisk()

const mountPath = path.join(this.tempPath, 'mount/RES')

return (this.mountPath = await this.mountDisk(mountPath))
}

async unmount(): Promise<void> {
await this.unmountDisk()
await this.detachDevice()
}

private async createDiskFile(): Promise<void> {
core.debug('Creating disk file')
await this.host.createDiskFile('40m', this.diskPath)
}

private async createDiskDevice(): Promise<string> {
core.debug('Creating disk file')
return await this.host.createDiskDevice(this.diskPath)
}

private async partitionDisk(): Promise<void> {
core.debug('Partitioning disk')
await this.host.partitionDisk(this.devicePath, this.mountName)
}

private async mountDisk(mountPath: string): Promise<string> {
core.debug('mounting disk')
return await this.host.mountDisk(this.devicePath, mountPath)
}

private async unmountDisk(): Promise<void> {
core.debug('Unmounting disk')
await exec.exec('sudo', ['umount', this.mountPath])
}

private async detachDevice(): Promise<void> {
core.debug('Detaching device')
await this.host.detachDevice(this.devicePath)
}
}

class Input {
private run_?: string
private operatingSystem_?: os.Kind
Expand Down
123 changes: 19 additions & 104 deletions src/host.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import {promises as fs} from 'fs'
import * as process from 'process'
import * as os from 'os'

import * as exec from '@actions/exec'

import * as architecture from './architecture'
import * as qemu from './qemu_vm'
import {execWithOutput} from './utility'
import path from 'path'
import * as xhyve from './xhyve_vm'

export enum Kind {
Expand Down Expand Up @@ -39,6 +33,11 @@ export function toString(value: Kind): string {
}
}

interface Implementation<MacOsType, LinuxType> {
macos: MacOsType
linux: LinuxType
}

export abstract class Host {
static create(): Host {
switch (kind) {
Expand All @@ -53,12 +52,11 @@ export abstract class Host {

abstract get workDirectory(): string
abstract get vmModule(): typeof xhyve | typeof qemu
abstract resolve<MacOsType, LinuxType>(
implementation: Implementation<MacOsType, LinuxType>
): MacOsType | LinuxType

abstract canRunXhyve(arch: architecture.Architecture): boolean
abstract createDiskFile(size: string, diskPath: string): Promise<void>
abstract createDiskDevice(diskPath: string): Promise<string>
abstract partitionDisk(devicePath: string, mountName: string): Promise<void>
abstract mountDisk(devicePath: string, mountPath: string): Promise<string>
abstract detachDevice(devicePath: string): Promise<void>
}

class MacOs extends Host {
Expand All @@ -70,48 +68,14 @@ class MacOs extends Host {
return xhyve
}

canRunXhyve(arch: architecture.Architecture): boolean {
return arch.kind === architecture.Kind.x86_64
resolve<MacOs, Linux>(
implementation: Implementation<MacOs, Linux>
): MacOs | Linux {
return implementation.macos
}

async createDiskFile(size: string, diskPath: string): Promise<void> {
await exec.exec('mkfile', ['-n', size, diskPath])
}

async createDiskDevice(diskPath: string): Promise<string> {
const devicePath = await execWithOutput(
'hdiutil',
[
'attach',
'-imagekey',
'diskimage-class=CRawDiskImage',
'-nomount',
diskPath
],
{silent: true}
)

return devicePath.trim()
}

async partitionDisk(devicePath: string, mountName: string): Promise<void> {
await exec.exec('diskutil', [
'partitionDisk',
devicePath,
'1',
'GPT',
'fat32',
mountName,
'100%'
])
}

async mountDisk(_devicePath: string, mountPath: string): Promise<string> {
return path.join('/Volumes', path.basename(mountPath))
}

async detachDevice(devicePath: string): Promise<void> {
await exec.exec('hdiutil', ['detach', devicePath])
canRunXhyve(arch: architecture.Architecture): boolean {
return arch.kind === architecture.Kind.x86_64
}
}

Expand All @@ -130,59 +94,10 @@ class Linux extends Host {
return false
}

async createDiskFile(size: string, diskPath: string): Promise<void> {
await exec.exec('truncate', ['-s', size, diskPath])

const input = Buffer.from('n\np\n1\n\n\nw\n')
// technically, this is partitioning the disk
await exec.exec('fdisk', [diskPath], {input})
}

async createDiskDevice(diskPath: string): Promise<string> {
// the offset and size limit are retrieved by running:
// `sfdisk -d ${diskPath}` and multiply the start and size by 512.
// https://checkmk.com/linux-knowledge/mounting-partition-loop-device

// prettier-ignore
const devicePath = await execWithOutput(
'sudo',
[
'losetup',
'-f',
'--show',
'--offset', '1048576',
'--sizelimit', '40894464',
diskPath
],
{silent: true}
)

return devicePath.trim()
}

/* eslint-disable @typescript-eslint/no-unused-vars */
async partitionDisk(devicePath: string, _mountName: string): Promise<void> {
/* eslint-enable @typescript-eslint/no-unused-vars */
// technically, this is creating the filesystem on the partition
await exec.exec('sudo', ['mkfs.fat', '-F32', devicePath])
}

async mountDisk(devicePath: string, mountPath: string): Promise<string> {
await fs.mkdir(mountPath, {recursive: true})
const uid = os.userInfo().uid
await exec.exec('sudo', [
'mount',
'-o',
`uid=${uid}`,
devicePath,
mountPath
])

return mountPath
}

async detachDevice(devicePath: string): Promise<void> {
await exec.exec('sudo', ['losetup', '-d', devicePath])
resolve<MacOs, Linux>(
implementation: Implementation<MacOs, Linux>
): MacOs | Linux {
return implementation.linux
}
}

Expand Down
Loading

0 comments on commit 384d08e

Please sign in to comment.