Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/export #5

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions examples/export/.hammerkit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
tasks:
example_file:
image: alpine
generates:
- path: test.txt
export: true
cmds:
- echo "hello" > test.txt

example_dir:
image: alpine
generates:
- path: dist
export: true
cmds:
- echo "hello" > dist/test.txt
1 change: 1 addition & 0 deletions examples/include/.hammerkit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ tasks:
example:
description: run ref task
deps: [foo:bar]
needs: [foo:bardb]
cmds:
- echo foo

Expand Down
4 changes: 4 additions & 0 deletions examples/include/foo/build.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
services:
bardb:
image: postgres:12-alpine

tasks:
bar:
description: example task
Expand Down
13 changes: 13 additions & 0 deletions examples/invalid_loop/.hammerkit.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
services:
foodb:
image: postgres:12-alpine
needs: [bardb]
bardb:
image: postgres:12-alpine
needs: [foodb]

tasks:
loopservice:
needs: [foodb]
cmds:
- echo bar

bar:
deps: [foo]
cmds:
Expand Down
1 change: 1 addition & 0 deletions examples/reference/.hammerkit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ tasks:
example:
description: example with dep
deps: [foo:bar]
needs: [foo:bardb]
cmds:
- echo hammertime

Expand Down
4 changes: 4 additions & 0 deletions examples/reference/foo/build.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
services:
bardb:
image: postgres:12-alpine

tasks:
bar:
description: ref task
Expand Down
21 changes: 21 additions & 0 deletions examples/services/.hammerkit.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
services:
api:
image: node:16.6.0-alpine
deps: [install]
needs: [postgres]
ports:
- 3000
labels:
task: dev
mounts:
- server.js
- config.json
cmd: node server.js
healthcheck:
cmd: "wget -qO- http://localhost:3000"

postgres:
image: postgres:12-alpine
labels:
Expand Down Expand Up @@ -27,6 +42,12 @@ tasks:
cmds:
- npm install

test:
image: node:16.6.0-alpine
needs: [api]
cmds:
- wget -qO- http://api:3000

api:
image: node:16.6.0-alpine
deps: [install]
Expand Down
26 changes: 26 additions & 0 deletions examples/services/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const { Client } = require('pg')
const { createServer } = require('http')
const config = require('./config.json')

const client = new Client(`postgres://${config.dbUser}:${config.dbPassword}@${config.dbHost}:5432/${config.dbName}`)

const server = createServer(async (req, res) => {
const result = await client.query('SELECT $1::text as message', ['Hello world!'])
res.writeHead(200)
res.end(result.rows[0].message)
})

async function main() {
await client.connect()
server.listen(3000)
}

process.on('SIGINT', async function () {
server.close()
await client.end()
})

main().catch((err) => {
console.error(err)
process.exit(1)
})
207 changes: 118 additions & 89 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { iterateWorkNodes, iterateWorkServices } from './planner/utils/plan-work
import { createSchedulerState } from './executer/create-scheduler-state'
import { isCI } from './utils/ci'
import { getLogger } from './console/get-logger'
import { schedule } from './executer/schedule'
import { scheduleExecution } from './executer/schedule-execution'
import { cleanCache, restoreCache, storeCache } from './executer/event-cache'
import { validate } from './planner/validate'
import { WorkTree } from './planner/work-tree'
Expand All @@ -17,10 +17,16 @@ import { ReadonlyState } from './executer/readonly-state'
import { ProcessManager } from './executer/process-manager'
import { WorkService } from './planner/work-service'
import { removeContainer } from './docker/remove-container'
import { updateServiceStatus } from './service/update-service-status'
import { WorkLabelScope } from './executer/work-scope'
import { scheduleUp } from './executer/schedule-up'
import { scheduleDown } from './executer/schedule-down'
import { State } from './executer/state'

export interface CliExecOptions {
workers: number
watch: boolean
daemon: boolean
logMode: LogMode
cacheDefault: CacheMethod
}
Expand All @@ -47,103 +53,126 @@ export const isCliTask = (val: CliItem): val is CliTaskItem => val.type === 'tas
export const isCliService = (val: CliItem): val is CliServiceItem => val.type === 'service'
export type CliItem = CliTaskItem | CliServiceItem

export interface Cli {
exec(options?: Partial<CliExecOptions>): Promise<SchedulerResult>
execWatch(options?: Partial<CliExecOptions>): CliExecResult
export class Cli {
constructor(private workTree: WorkTree, private environment: Environment) {}

store(path: string): Promise<void>
async setup(
scheduler: (process: ProcessManager, state: State, environment: Environment) => Promise<SchedulerResult>,
options?: Partial<CliExecOptions>
): Promise<CliExecResult> {
const processManager = new ProcessManager(this.environment, options?.workers ?? 0)
const logMode: LogMode = options?.logMode ?? (isCI ? 'live' : 'interactive')
const state = createSchedulerState({
daemon: options?.daemon ?? false,
services: this.workTree.services,
nodes: this.workTree.nodes,
watch: options?.watch ?? false,
logMode,
cacheMethod: options?.cacheDefault ?? 'checksum',
})

restore(path: string): Promise<void>
await updateServiceStatus(state, this.environment)

clean(options?: Partial<CliCleanOptions>): Promise<void>
const logger = getLogger(logMode, state, this.environment)

validate(): AsyncGenerator<WorkNodeValidation>

ls(): CliItem[]

shutdown(): Promise<void>

node(name: string): WorkNode
}
return {
state,
processManager,
start: async () => {
const result = await scheduler(processManager, state, this.environment)
await logger.complete(result, this.environment)
return result
},
}
}

export function getCli(workTree: WorkTree, environment: Environment): Cli {
return {
execWatch(options?: Partial<CliExecOptions>): CliExecResult {
const processManager = new ProcessManager(environment, options?.workers ?? 0)
const state = createSchedulerState({
services: workTree.services,
nodes: workTree.nodes,
watch: options?.watch ?? false,
logMode: options?.logMode ?? isCI ? 'live' : 'interactive',
cacheMethod: options?.cacheDefault ?? 'checksum',
})
const logMode: LogMode = options?.logMode ?? (isCI ? 'live' : 'interactive')
const logger = getLogger(logMode, state, environment)

return {
state,
processManager,
start: async () => {
const result = await schedule(processManager, state, environment)
await logger.complete(result, environment)
return result
async shutdown(): Promise<void> {
for (const node of iterateWorkNodes(this.workTree.nodes)) {
const containers = await this.environment.docker.listContainers({
all: true,
filters: {
label: [`hammerkit-id=${node.id}`],
},
})
for (const container of containers) {
await removeContainer(this.environment.docker.getContainer(container.Id))
}
},
async shutdown(): Promise<void> {
for (const node of iterateWorkNodes(workTree.nodes)) {
const containers = await environment.docker.listContainers({
all: true,
filters: {
label: [`hammerkit-id=${node.id}`],
},
})
for (const container of containers) {
await removeContainer(environment.docker.getContainer(container.Id))
}
}
}

for (const service of iterateWorkServices(workTree.services)) {
const containers = await environment.docker.listContainers({
all: true,
filters: {
label: [`hammerkit-id=${service.id}`],
},
})
for (const container of containers) {
await removeContainer(environment.docker.getContainer(container.Id))
}
}
},
async exec(options?: Partial<CliExecOptions>): Promise<SchedulerResult> {
return this.execWatch(options).start()
},
async clean(options?: Partial<CliCleanOptions>): Promise<void> {
await cleanCache(workTree, environment, {
service: options?.service ?? false,
for (const service of iterateWorkServices(this.workTree.services)) {
const containers = await this.environment.docker.listContainers({
all: true,
filters: {
label: [`hammerkit-id=${service.id}`],
},
})
},
async restore(path: string): Promise<void> {
await restoreCache(path, workTree, environment)
},
async store(path: string): Promise<void> {
await storeCache(path, workTree, environment)
},
ls(): CliItem[] {
return [
...Array.from(iterateWorkServices(workTree.services)).map<CliItem>((item) => ({ item, type: 'service' })),
...Array.from(iterateWorkNodes(workTree.nodes)).map<CliItem>((item) => ({ item, type: 'task' })),
]
},
validate(): AsyncGenerator<WorkNodeValidation> {
return validate(workTree, environment)
},
node(name: string): WorkNode {
const node = Object.values(workTree.nodes).find((n) => n.name == name)
if (!node) {
throw new Error(`unable to find node ${name}`)
for (const container of containers) {
await removeContainer(this.environment.docker.getContainer(container.Id))
}
return node
},
}
}

async up(options?: Partial<CliExecOptions>): Promise<CliExecResult> {
return await this.setup(scheduleUp, options)
}

async runUp(options?: Partial<CliExecOptions>): Promise<SchedulerResult> {
const run = await this.up(options)
return await run.start()
}

async down(): Promise<CliExecResult> {
return await this.setup(scheduleDown, {})
}

async runDown(): Promise<SchedulerResult> {
const run = await this.down()
return await run.start()
}

async exec(options?: Partial<CliExecOptions>): Promise<CliExecResult> {
return await this.setup(scheduleExecution, options)
}

async runExec(options?: Partial<CliExecOptions>): Promise<SchedulerResult> {
const run = await this.exec(options)
return await run.start()
}

async clean(options?: Partial<CliCleanOptions>): Promise<void> {
await cleanCache(this.workTree, this.environment, {
service: options?.service ?? false,
})
}

async restore(path: string): Promise<void> {
await restoreCache(path, this.workTree, this.environment)
}

async store(path: string): Promise<void> {
await storeCache(path, this.workTree, this.environment)
}

ls(): CliItem[] {
return [
...Array.from(iterateWorkServices(this.workTree.services)).map<CliItem>((item) => ({ item, type: 'service' })),
...Array.from(iterateWorkNodes(this.workTree.nodes)).map<CliItem>((item) => ({ item, type: 'task' })),
]
}

validate(): AsyncGenerator<WorkNodeValidation> {
return validate(this.workTree, this.environment)
}

node(name: string): WorkNode {
const node = Object.values(this.workTree.nodes).find((n) => n.name == name)
if (!node) {
throw new Error(`unable to find node ${name}`)
}
return node
}
}

export function getCli(workTree: WorkTree, environment: Environment): Cli {
return new Cli(workTree, environment)
}
11 changes: 11 additions & 0 deletions src/executer/are-all-services-down.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { SchedulerState } from './scheduler/scheduler-state'
import { iterateWorkServices } from '../planner/utils/plan-work-nodes'

export function areAllServicesDown(state: SchedulerState): boolean {
for (const service of iterateWorkServices(state.service)) {
if (service.type === 'running' || service.type === 'starting' || service.type === 'canceled') {
return false
}
}
return true
}
11 changes: 11 additions & 0 deletions src/executer/are-all-services-running.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { SchedulerState } from './scheduler/scheduler-state'
import { iterateWorkServices } from '../planner/utils/plan-work-nodes'

export function areAllServicesRunning(state: SchedulerState): boolean {
for (const service of iterateWorkServices(state.service)) {
if (service.type !== 'running') {
return false
}
}
return true
}
Loading