Skip to content

Commit

Permalink
add --project-directory flag (#411)
Browse files Browse the repository at this point in the history
- add flag to allow setting the project directory like in the `docker compose` CLI
- remove usages of cwd where applicable

fixes #382
  • Loading branch information
Roy Razon authored Jan 29, 2024
1 parent 6dc4d60 commit d4f746c
Show file tree
Hide file tree
Showing 12 changed files with 50 additions and 41 deletions.
9 changes: 6 additions & 3 deletions packages/cli-common/src/hooks/init/load-plugins.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import path from 'path'
import { Hook as OclifHook, Command, Flags } from '@oclif/core'
import { Parser } from '@oclif/core/lib/parser/parse.js'
import { BooleanFlag, Config, Topic } from '@oclif/core/lib/interfaces'
Expand Down Expand Up @@ -41,9 +42,11 @@ export const initHook: OclifHook<'init'> = async function hook(args) {
argv,
} as const).parse()

const composeFiles = await resolveComposeFiles({
const { files: composeFiles, projectDirectory } = await resolveComposeFiles({
userSpecifiedFiles: flags.file,
userSpecifiedSystemFiles: flags['system-compose-file'],
userSpecifiedProjectDirectory: flags['project-directory'],
cwd: process.cwd(),
})

const userModelOrError = composeFiles.length
Expand All @@ -53,7 +56,7 @@ export const initHook: OclifHook<'init'> = async function hook(args) {
async () => await localComposeClient({
composeFiles,
projectName: flags.project,
projectDirectory: process.cwd(),
projectDirectory,
}).getModelOrError(),
{
text: `Loading compose file${composeFiles.length > 1 ? 's' : ''}: ${composeFiles.join(', ')}`,
Expand All @@ -76,7 +79,7 @@ export const initHook: OclifHook<'init'> = async function hook(args) {
(config as InternalConfig).loadTopics({ commands, topics })

Object.assign(config, {
composeFiles,
composeFiles: { files: composeFiles, projectDirectory },
initialUserModel: userModelOrError,
preevyConfig,
preevyHooks: hooksFromPlugins(loadedPlugins.map(p => p.initResults)),
Expand Down
4 changes: 4 additions & 0 deletions packages/cli-common/src/lib/common-flags/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ export const composeFlags = {
default: [],
helpGroup: 'GLOBAL',
}),
'project-directory': Flags.string({
required: false,
summary: 'Alternate working directory (default: the path of the first specified Compose file)',
}),
...projectFlag,
} as const

Expand Down
4 changes: 2 additions & 2 deletions packages/cli-common/src/lib/plugins/model.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { FlagProps } from '@oclif/core/lib/interfaces/parser.js'
import { Topic } from '@oclif/core/lib/interfaces'
import { Command } from '@oclif/core'
import { ComposeModel, config as coreConfig } from '@preevy/core'
import { ComposeFiles, ComposeModel, config as coreConfig } from '@preevy/core'
import { PluginInitContext } from './context.js'
import { HookFuncs, HooksListeners } from '../hooks.js'
import PreevyConfig = coreConfig.PreevyConfig
Expand All @@ -25,7 +25,7 @@ export type PluginModule = {

declare module '@oclif/core/lib/config/config.js' {
export interface Config {
composeFiles: string[]
composeFiles: ComposeFiles
initialUserModel: ComposeModel | Error
preevyHooks: HooksListeners
preevyConfig: PreevyConfig
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export default class Logs extends DriverCommand<typeof Logs> {
const compose = localComposeClient({
composeFiles: Buffer.from(yaml.stringify(addBaseComposeTunnelAgentService(userModel))),
projectName: flags.project,
projectDirectory: process.cwd(),
projectDirectory: this.config.composeFiles.projectDirectory,
})

await using dockerContext = await dockerEnvContext({ connection, log })
Expand Down
3 changes: 1 addition & 2 deletions packages/cli/src/commands/up.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export default class Up extends MachineCreationDriverCommand<typeof Up> {
...tunnelServerFlags,
...buildFlags,
'skip-volume': Flags.string({
description: 'Additional volume glob patterns to skip copying',
description: 'Additional volume glob patterns to skip copying (relative to project directory)',
multiple: true,
multipleNonGreedy: true,
default: [],
Expand Down Expand Up @@ -199,7 +199,6 @@ export default class Up extends MachineCreationDriverCommand<typeof Up> {
dataDir: this.config.dataDir,
sshTunnelPrivateKey: tunnelingKey,
allowedSshHostKeys: hostKey,
cwd: process.cwd(),
skipUnchangedFiles: flags['skip-unchanged-files'],
version: this.config.version,
buildSpec,
Expand Down
4 changes: 1 addition & 3 deletions packages/core/src/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ const buildCommand = async ({
log,
composeModel,
projectLocalDataDir,
cwd,
buildSpec,
machineDockerPlatform,
env,
Expand All @@ -23,7 +22,6 @@ const buildCommand = async ({
log: Logger
composeModel: ComposeModel
projectLocalDataDir: string
cwd: string
buildSpec: BuildSpec
machineDockerPlatform: string
env?: Record<string, string>
Expand All @@ -47,7 +45,7 @@ const buildCommand = async ({
]

log.info(`Running: docker ${dockerArgs.join(' ')}`)
const { elapsedTimeSec } = await measureTime(() => childProcessPromise(spawn('docker', dockerArgs, { stdio: 'inherit', cwd, env })))
const { elapsedTimeSec } = await measureTime(() => childProcessPromise(spawn('docker', dockerArgs, { stdio: 'inherit', env })))
telemetryEmitter().capture('build success', {
elapsed_sec: elapsedTimeSec,
has_registry: Boolean(buildSpec.registry),
Expand Down
7 changes: 2 additions & 5 deletions packages/core/src/commands/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { MachineStatusCommand, ScriptInjection } from '@preevy/common'
import path from 'path'
import { rimraf } from 'rimraf'
import { TunnelOpts } from '../ssh/index.js'
import { ComposeModel, remoteComposeModel } from '../compose/index.js'
import { ComposeFiles, ComposeModel, remoteComposeModel } from '../compose/index.js'
import { createCopiedFileInDataDir } from '../remote-files.js'
import { Logger } from '../log.js'
import { EnvId } from '../env-id.js'
Expand All @@ -21,7 +21,6 @@ const composeModel = async ({
dataDir,
allowedSshHostKeys: hostKey,
sshTunnelPrivateKey,
cwd,
version,
envId,
expectedServiceUrls,
Expand All @@ -35,13 +34,12 @@ const composeModel = async ({
userSpecifiedProjectName: string | undefined
userSpecifiedServices: string[]
volumeSkipList: string[]
composeFiles: string[]
composeFiles: ComposeFiles
log: Logger
dataDir: string
scriptInjections?: Record<string, ScriptInjection>
sshTunnelPrivateKey: string | Buffer
allowedSshHostKeys: Buffer
cwd: string
version: string
envId: EnvId
expectedServiceUrls: { name: string; port: number; url: string }[]
Expand All @@ -60,7 +58,6 @@ const composeModel = async ({
volumeSkipList,
composeFiles,
log,
cwd,
expectedServiceUrls,
projectName,
modelFilter,
Expand Down
9 changes: 2 additions & 7 deletions packages/core/src/commands/up.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { MachineStatusCommand, ScriptInjection } from '@preevy/common'
import yaml from 'yaml'
import { TunnelOpts } from '../ssh/index.js'
import { ComposeModel, composeModelFilename, localComposeClient } from '../compose/index.js'
import { ComposeFiles, ComposeModel, composeModelFilename, localComposeClient } from '../compose/index.js'
import { dockerEnvContext } from '../docker.js'
import { MachineConnection } from '../driver/index.js'
import { remoteProjectDir } from '../remote-files.js'
Expand Down Expand Up @@ -54,7 +54,6 @@ const up = async ({
dataDir,
allowedSshHostKeys,
sshTunnelPrivateKey,
cwd,
skipUnchangedFiles,
version,
envId,
Expand All @@ -72,13 +71,12 @@ const up = async ({
userSpecifiedProjectName: string | undefined
userSpecifiedServices: string[]
volumeSkipList: string[]
composeFiles: string[]
composeFiles: ComposeFiles
log: Logger
dataDir: string
scriptInjections?: Record<string, ScriptInjection>
sshTunnelPrivateKey: string | Buffer
allowedSshHostKeys: Buffer
cwd: string
skipUnchangedFiles: boolean
version: string
envId: EnvId
Expand All @@ -100,7 +98,6 @@ const up = async ({
log,
machineStatusCommand,
userAndGroup,
cwd,
tunnelOpts,
userSpecifiedProjectName,
userSpecifiedServices,
Expand All @@ -126,7 +123,6 @@ const up = async ({
composeModel = (await buildCommand({
log,
buildSpec,
cwd,
composeModel,
projectLocalDataDir,
machineDockerPlatform: dockerPlatform,
Expand All @@ -151,7 +147,6 @@ const up = async ({

const compose = localComposeClient({
composeFiles: [composeFilePath.local],
projectDirectory: cwd,
})

const composeArgs = [
Expand Down
19 changes: 14 additions & 5 deletions packages/core/src/compose/files.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import fs from 'fs'
import path from 'path'
import { ComposeFiles } from './model.js'

const DEFAULT_BASE_FILES = ['compose', 'docker-compose']
const DEFAULT_OVERRIDE_FILES = DEFAULT_BASE_FILES.map(f => `${f}.override`)
Expand Down Expand Up @@ -46,11 +48,18 @@ const findDefaultFiles = async () => (await oneYamlFileArray(DEFAULT_BASE_FILES,
const findDefaultSystemFiles = async () => (await oneYamlFileArray(DEFAULT_SYSTEM_FILES, 'default system Compose')) ?? []

export const resolveComposeFiles = async (
{ userSpecifiedFiles, userSpecifiedSystemFiles }: {
{ userSpecifiedFiles, userSpecifiedSystemFiles, userSpecifiedProjectDirectory, cwd }: {
userSpecifiedFiles: string[]
userSpecifiedSystemFiles: string[]
userSpecifiedProjectDirectory: string
cwd: string
},
): Promise<string[]> => [
...(userSpecifiedSystemFiles.length ? userSpecifiedSystemFiles : await findDefaultSystemFiles()),
...(userSpecifiedFiles.length ? userSpecifiedFiles : await findDefaultFiles()),
]
): Promise<ComposeFiles> => {
const files = (userSpecifiedFiles.length ? userSpecifiedFiles : await findDefaultFiles())
const systemFiles = (userSpecifiedSystemFiles.length ? userSpecifiedSystemFiles : await findDefaultSystemFiles())

return {
files: [...systemFiles, ...files],
projectDirectory: path.resolve(userSpecifiedProjectDirectory ?? files.length ? path.dirname(files[0]) : cwd),
}
}
5 changes: 5 additions & 0 deletions packages/core/src/compose/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,8 @@ export type ComposeModel = {
}

export const composeModelFilename = 'docker-compose.yaml'

export type ComposeFiles = {
files: string[]
projectDirectory: string
}
24 changes: 11 additions & 13 deletions packages/core/src/compose/remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import yaml from 'yaml'
import path from 'path'
import { mapValues } from 'lodash-es'
import { MMRegExp, makeRe } from 'minimatch'
import { asyncMap, asyncToArray } from 'iter-tools-es'
import { asyncMap, asyncToArray, compose } from 'iter-tools-es'
import { COMPOSE_TUNNEL_AGENT_SERVICE_NAME, MachineStatusCommand, ScriptInjection, formatPublicKey } from '@preevy/common'
import { MachineConnection } from '../driver/index.js'
import { ComposeModel, ComposeSecretOrConfig, composeModelFilename } from './model.js'
import { ComposeFiles, ComposeModel, ComposeSecretOrConfig, composeModelFilename } from './model.js'
import { REMOTE_DIR_BASE, remoteProjectDir } from '../remote-files.js'
import { TunnelOpts } from '../ssh/index.js'
import { addComposeTunnelAgentService } from '../compose-tunnel-agent-client.js'
Expand Down Expand Up @@ -42,9 +42,9 @@ const toPosix = (x:string) => x.split(path.sep).join(path.posix.sep)
export type SkippedVolume = { service: string; source: string; matchingRule: string }

const fixModelForRemote = async (
{ skipServices = [], cwd, remoteBaseDir, volumeSkipList = defaultVolumeSkipList }: {
{ skipServices = [], projectDirectory, remoteBaseDir, volumeSkipList = defaultVolumeSkipList }: {
skipServices?: string[]
cwd: string
projectDirectory: string
remoteBaseDir: string
volumeSkipList: string[]
},
Expand All @@ -55,7 +55,7 @@ const fixModelForRemote = async (
skippedVolumes: SkippedVolume[]
}> => {
const volumeSkipRes = volumeSkipList
.map(s => makeRe(path.resolve(cwd, s)))
.map(s => makeRe(path.resolve(projectDirectory, s)))
.map((r, i) => {
if (!r) {
throw new Error(`Invalid glob pattern in volumeSkipList: "${volumeSkipList[i]}"`)
Expand All @@ -70,7 +70,7 @@ const fixModelForRemote = async (
if (!path.isAbsolute(absolutePath)) {
throw new Error(`expected absolute path: "${absolutePath}"`)
}
const relativePath = toPosix(path.relative(cwd, absolutePath))
const relativePath = toPosix(path.relative(projectDirectory, absolutePath))

return relativePath.startsWith('..')
? path.posix.join('absolute', absolutePath)
Expand Down Expand Up @@ -166,7 +166,6 @@ export const remoteComposeModel = async ({
volumeSkipList,
composeFiles,
log,
cwd,
expectedServiceUrls,
projectName,
agentSettings,
Expand All @@ -176,33 +175,32 @@ export const remoteComposeModel = async ({
userSpecifiedProjectName: string | undefined
userSpecifiedServices: string[]
volumeSkipList: string[]
composeFiles: string[]
composeFiles: ComposeFiles
log: Logger
cwd: string
expectedServiceUrls: { name: string; port: number; url: string }[]
projectName: string
agentSettings?: AgentSettings
modelFilter: (userModel: ComposeModel) => Promise<ComposeModel>
}) => {
const remoteDir = remoteProjectDir(projectName)

log.debug(`Using compose files: ${composeFiles.join(', ')}`)
log.debug(`Using compose files: ${composeFiles.files.join(', ')} and project directory "${composeFiles.projectDirectory}"`)

const linkEnvVars = serviceLinkEnvVars(expectedServiceUrls)

const composeClientWithInjectedArgs = localComposeClient({
composeFiles,
composeFiles: composeFiles.files,
env: linkEnvVars,
projectName: userSpecifiedProjectName,
projectDirectory: cwd,
projectDirectory: composeFiles.projectDirectory,
})

const services = userSpecifiedServices.length
? [...userSpecifiedServices].concat(COMPOSE_TUNNEL_AGENT_SERVICE_NAME)
: []

const { model: fixedModel, filesToCopy, skippedVolumes } = await fixModelForRemote(
{ cwd, remoteBaseDir: remoteDir, volumeSkipList },
{ projectDirectory: composeFiles.projectDirectory, remoteBaseDir: remoteDir, volumeSkipList },
await modelFilter(await composeClientWithInjectedArgs.getModel(services)),
)

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export {
localComposeClient, ComposeModel, resolveComposeFiles, getExposedTcpServicePorts,
fetchRemoteUserModel as remoteUserModel, NoComposeFilesError,
addScriptInjectionsToServices as addScriptInjectionsToModel,
ComposeFiles,
defaultVolumeSkipList,
} from './compose/index.js'
export { withSpinner } from './spinner.js'
Expand Down

0 comments on commit d4f746c

Please sign in to comment.