Skip to content

Commit

Permalink
local build (#340)
Browse files Browse the repository at this point in the history
  • Loading branch information
Roy Razon authored Dec 3, 2023
1 parent 32e2fef commit 4eb0db3
Show file tree
Hide file tree
Showing 81 changed files with 2,092 additions and 656 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ Visit The full documentation here: https://preevy.dev/
- [CLI](#cli)
- [Tunnel server](#tunnel-server)
- [CI Integration](#ci-integration)
- [Faster builds in CI](#faster-builds-in-ci)
- [Security](#security)
- [Private environments](#private-environments)
- [Notice on preview environments exposure](#notice-on-preview-environments-exposure)
Expand Down Expand Up @@ -150,7 +151,7 @@ A Docker/OCI image is available on ghcr.io: ghcr.io/livecycle/preevy/tunnel-serv

## CI Integration

Preevy is also designed to work seamlessly with your CI, allowing you to easily import a shared preview profile shared in AWS S3 and Google Cloud Storage (GCS).
Preevy is designed to work seamlessly with your CI, allowing you to easily import a shared preview profile shared in AWS S3 and Google Cloud Storage (GCS).

Profiles are created using `preevy init`. Choose a S3/GCS URL for storing the profile - Preevy will create a bucket if one doesn't exist.

Expand All @@ -162,6 +163,10 @@ Examples:
- [Using AWS Lightsail](https://preevy.dev/ci/example-github-actions)
- [Using Google Cloud Engine](https://preevy.dev/ci/example-github-actions-gce)

### Faster builds in CI

Check out our [documentation](https://preevy.dev/recipes/faster-build) to find out how to speed up your builds and reduce the costs of your preview environments by running Preevy with BuildKit builders in CI.

## Security

In case you find a security issue or have something you would like to discuss, refer to our [security policy](https://github.com/livecycle/preevy/blob/main/security.md).
Expand Down Expand Up @@ -333,6 +338,8 @@ The Preevy CLI collects telemetry data to help us understand product usage and d
The data collected is *anonymous* and cannot be used to uniquely identify a user.
Access to the data is limited to Livecycle's employees and not shared with 3rd parties.

To see the collected data, set the environment variable `PREEVY_TELEMETRY_FILE` to a filename.

We appreciate the usage data sent to us as - it's the most basic and raw type of feedback we get from our users. However, if you are concerned about sending out data, you may choose to disable telemetry.

Telemetry collection can be disabled by setting the environment variable `PREEVY_DISABLE_TELEMETRY` to `1` or `true`.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"patch-package": "^8.0.0",
"postinstall-postinstall": "^2.1.0",
"syncpack": "^9.8.4",
"typescript": "^5.0.4"
"typescript": "^5.2.2"
},
"resolutions": {
"**/@oclif/core": "livecycle/oclif-core-patched-for-preevy#v3.12.0-preevy-patch-10"
Expand Down
6 changes: 3 additions & 3 deletions packages/cli-common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
"@jest/globals": "29.7.0",
"@types/lodash": "^4.14.192",
"@types/node": "18",
"@typescript-eslint/eslint-plugin": "6.7.4",
"@typescript-eslint/parser": "6.7.4",
"@typescript-eslint/eslint-plugin": "6.10.0",
"@typescript-eslint/parser": "6.10.0",
"eslint": "^8.36.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-plugin-import": "^2.27.5",
Expand All @@ -29,7 +29,7 @@
"jest": "29.7.0",
"shx": "^0.3.3",
"tslib": "^2.5.0",
"typescript": "^5.0.4"
"typescript": "^5.2.2"
},
"scripts": {
"lint": "eslint . --ext .ts,.tsx --cache",
Expand Down
27 changes: 19 additions & 8 deletions packages/cli-common/src/commands/base-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ import {
LogLevel, Logger, logLevels, ComposeModel, ProcessError, telemetryEmitter,
} from '@preevy/core'
import { asyncReduce } from 'iter-tools-es'
import { ParsingToken } from '@oclif/core/lib/interfaces/parser'
import { commandLogger } from '../lib/log'
import { composeFlags, pluginFlags } from '../lib/common-flags'

// eslint-disable-next-line no-use-before-define
export type Flags<T extends typeof Command> = Interfaces.InferredFlags<typeof BaseCommand['baseFlags'] & T['flags']>
export type Args<T extends typeof Command> = Interfaces.InferredArgs<T['args']>

const argsFromRaw = (raw: ParsingToken[]) => raw.filter(arg => arg.type === 'arg').map(arg => arg.input).filter(Boolean)

abstract class BaseCommand<T extends typeof Command=typeof Command> extends Command {
static baseFlags = {
'log-level': Flags.custom<LogLevel>({
Expand All @@ -35,24 +38,29 @@ abstract class BaseCommand<T extends typeof Command=typeof Command> extends Comm

protected flags!: Flags<T>
protected args!: Args<T>
#rawArgs!: ParsingToken[]

#userModel?: ComposeModel
protected async userModel() {
const { initialUserModel, preevyHooks } = this.config
const { initialUserModel } = this.config
if (initialUserModel instanceof Error) {
return initialUserModel
}

if (!this.#userModel) {
this.#userModel = await asyncReduce(
initialUserModel,
(userModel, hook) => hook({ log: this.logger, userModel }, undefined),
preevyHooks?.userModelFilter || [],
)
this.#userModel = await this.modelFilter(initialUserModel)
}
return this.#userModel
}

protected get modelFilter() {
return (model: ComposeModel) => asyncReduce(
model,
(filteredModel, hook) => hook({ log: this.logger, userModel: filteredModel }, undefined),
this.config.preevyHooks?.userModelFilter || [],
)
}

protected get preevyConfig() {
return this.config.preevyConfig
}
Expand All @@ -76,24 +84,27 @@ abstract class BaseCommand<T extends typeof Command=typeof Command> extends Comm

public async init(): Promise<void> {
await super.init()
const { args, flags } = await this.parse({
const { args, flags, raw } = await this.parse({
flags: this.ctor.flags,
baseFlags: super.ctor.baseFlags,
args: this.ctor.args,
strict: this.ctor.strict,
strict: false,
})
this.args = args as Args<T>
this.flags = flags as Flags<T>
if (this.flags.debug) {
oclifSettings.debug = true
}
this.#rawArgs = raw
this.logger = commandLogger(this, this.flags.json ? 'stderr' : 'stdout')
this.stdErrLogger = commandLogger(this, 'stderr')
}

protected logger!: Logger
protected stdErrLogger!: Logger

protected get rawArgs() { return argsFromRaw(this.#rawArgs) }

public get logLevel(): LogLevel {
return this.flags['log-level'] ?? this.flags.debug ? 'debug' : 'info'
}
Expand Down
1 change: 1 addition & 0 deletions packages/cli-common/src/hooks/init/load-plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export const initHook: OclifHook<'init'> = async function hook(args) {
async () => await localComposeClient({
composeFiles,
projectName: flags.project,
projectDirectory: process.cwd(),
}).getModelOrError(),
{
text: `Loading compose file${composeFiles.length > 1 ? 's' : ''}: ${composeFiles.join(', ')}`,
Expand Down
6 changes: 4 additions & 2 deletions packages/cli-common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ export * from './lib/plugins/model'
export * as text from './lib/text'
export { HookName, HookFunc, HooksListeners, Hooks } from './lib/hooks'
export { PluginContext, PluginInitContext } from './lib/plugins/context'
export { composeFlags, pluginFlags, envIdFlags, tunnelServerFlags, urlFlags } from './lib/common-flags'
export { formatFlagsToArgs, parseFlags, ParsedFlags, argsFromRaw } from './lib/flags'
export {
composeFlags, pluginFlags, envIdFlags, tunnelServerFlags, urlFlags, buildFlags, tableFlags, parseBuildFlags,
} from './lib/common-flags'
export { formatFlagsToArgs, parseFlags, ParsedFlags } from './lib/flags'
export { initHook } from './hooks/init/load-plugins'
export { default as BaseCommand } from './commands/base-command'
69 changes: 69 additions & 0 deletions packages/cli-common/src/lib/common-flags/build-flags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Flags } from '@oclif/core'
import { InferredFlags } from '@oclif/core/lib/interfaces'
import { BuildSpec, parseRegistry } from '@preevy/core'

const helpGroup = 'BUILD'

export const buildFlags = {
'no-build': Flags.boolean({
description: 'Do not build images',
helpGroup,
allowNo: false,
default: false,
required: false,
}),
registry: Flags.string({
description: 'Image registry. If this flag is specified, the "build-context" flag defaults to "*local"',
helpGroup,
required: false,
}),
'registry-single-name': Flags.string({
description: 'Use single name for image registry, ECR-style. Default: auto-detect from "registry" flag',
helpGroup,
required: false,
dependsOn: ['registry'],
}),
'no-registry-single-name': Flags.boolean({
description: 'Disable auto-detection for ECR-style registry single name',
helpGroup,
allowNo: false,
required: false,
exclusive: ['registry-single-name'],
}),
'no-registry-cache': Flags.boolean({
description: 'Do not add the registry as a cache source and target',
helpGroup,
allowNo: false,
required: false,
dependsOn: ['registry'],
}),
builder: Flags.string({
description: 'Builder to use',
helpGroup,
required: false,
}),
'no-cache': Flags.boolean({
description: 'Do not use cache when building the images',
helpGroup,
allowNo: false,
required: false,
}),
} as const

export const parseBuildFlags = (flags: Omit<InferredFlags<typeof buildFlags>, 'json'>): BuildSpec | undefined => {
if (flags['no-build']) {
return undefined
}

return {
builder: flags.builder,
noCache: flags['no-cache'],
cacheFromRegistry: !flags['no-registry-cache'],
...flags.registry && {
registry: parseRegistry({
registry: flags.registry,
singleName: flags['no-registry-single-name'] ? false : flags['registry-single-name'],
}),
},
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import { Flags } from '@oclif/core'
import { DEFAULT_PLUGINS } from './plugins/default-plugins'
import { Flags, ux } from '@oclif/core'
import { mapValues } from 'lodash'
import { EOL } from 'os'
import { DEFAULT_PLUGINS } from '../plugins/default-plugins'

export * from './build-flags'

export const tableFlags = mapValues(ux.table.flags(), f => ({ ...f, helpGroup: 'OUTPUT' })) as ReturnType<typeof ux.table['flags']>

const projectFlag = {
project: Flags.string({
char: 'p',
description: 'Project name. Defaults to the Compose project name',
summary: 'Project name. Defaults to the Compose project name',
required: false,
helpGroup: 'GLOBAL',
}),
}
export const composeFlags = {
file: Flags.string({
description: 'Compose configuration file',
summary: 'Compose configuration file',
multiple: true,
delimiter: ',',
singleValue: true,
Expand All @@ -21,7 +27,7 @@ export const composeFlags = {
helpGroup: 'GLOBAL',
}),
'system-compose-file': Flags.string({
description: 'Add extra Compose configuration file without overriding the defaults',
summary: 'Add extra Compose configuration file without overriding the defaults',
multiple: true,
delimiter: ',',
singleValue: true,
Expand Down Expand Up @@ -52,38 +58,40 @@ export const pluginFlags = {

export const envIdFlags = {
id: Flags.string({
description: 'Environment id - affects created URLs. If not specified, will try to detect automatically',
summary: 'Environment id',
description: `Affects created URLs${EOL}If not specified, will detect from the current Git context`,
required: false,
}),
...projectFlag,
} as const

export const tunnelServerFlags = {
'tunnel-url': Flags.string({
description: 'Tunnel url, specify ssh://hostname[:port] or ssh+tls://hostname[:port]',
summary: 'Tunnel url, specify ssh://hostname[:port] or ssh+tls://hostname[:port]',
char: 't',
default: 'ssh+tls://livecycle.run' ?? process.env.PREVIEW_TUNNEL_OVERRIDE,
}),
'tls-hostname': Flags.string({
description: 'Override TLS server name when tunneling via HTTPS',
summary: 'Override TLS server name when tunneling via HTTPS',
required: false,
}),
'insecure-skip-verify': Flags.boolean({
description: 'Skip TLS or SSH certificate verification',
summary: 'Skip TLS or SSH certificate verification',
default: false,
}),
} as const

export const urlFlags = {
'include-access-credentials': Flags.boolean({
description: 'Include access credentials for basic auth for each service URL',
summary: 'Include access credentials for basic auth for each service URL',
default: false,
}),
'show-preevy-service-urls': Flags.boolean({
description: 'Show URLs for internal Preevy services',
summary: 'Show URLs for internal Preevy services',
default: false,
}),
'access-credentials-type': Flags.custom<'browser' | 'api'>({
summary: 'Access credentials type',
options: ['api', 'browser'],
dependsOn: ['include-access-credentials'],
default: 'browser',
Expand Down
3 changes: 0 additions & 3 deletions packages/cli-common/src/lib/flags.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Flag } from '@oclif/core/lib/interfaces'
import { ParsingToken } from '@oclif/core/lib/interfaces/parser'
import { Parser } from '@oclif/core/lib/parser/parse'

type FlagSpec<T> =Pick<Flag<T>, 'type' | 'default'>
Expand Down Expand Up @@ -35,5 +34,3 @@ export const parseFlags = async <T extends {}>(def: T, argv: string[]) => (await
}).parse()).flags

export type ParsedFlags<T extends {}> = Omit<Awaited<ReturnType<typeof parseFlags<T>>>, 'json'>

export const argsFromRaw = (raw: ParsingToken[]) => raw.filter(arg => arg.type === 'arg').map(arg => arg.input).filter(Boolean)
1 change: 1 addition & 0 deletions packages/cli/bin/dev
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env node

require('disposablestack/auto')
const oclif = require('@oclif/core')

const path = require('path')
Expand Down
1 change: 1 addition & 0 deletions packages/cli/bin/run
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env node
require('source-map-support').install()
require('disposablestack/auto')

const oclif = require('@oclif/core')

Expand Down
7 changes: 4 additions & 3 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@preevy/driver-kube-pod": "0.0.56",
"@preevy/driver-lightsail": "0.0.56",
"@preevy/plugin-github": "0.0.56",
"disposablestack": "^1.1.2",
"inquirer": "^8.0.0",
"inquirer-autocomplete-prompt": "^2.0.0",
"iter-tools-es": "^7.5.3",
Expand All @@ -44,8 +45,8 @@
"@types/lodash": "^4.14.192",
"@types/node": "18",
"@types/shell-escape": "^0.2.1",
"@typescript-eslint/eslint-plugin": "6.7.4",
"@typescript-eslint/parser": "6.7.4",
"@typescript-eslint/eslint-plugin": "6.10.0",
"@typescript-eslint/parser": "6.10.0",
"eslint": "^8.36.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-oclif": "^4",
Expand All @@ -61,7 +62,7 @@
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0",
"tslib": "^2.5.0",
"typescript": "^5.0.4"
"typescript": "^5.2.2"
},
"oclif": {
"bin": "preevy",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/down.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default class Down extends DriverCommand<typeof Down> {

async run(): Promise<unknown> {
const log = this.logger
const { flags } = await this.parse(Down)
const { flags } = this
const driver = await this.driver()

const envId = await findEnvId({
Expand Down
Loading

0 comments on commit 4eb0db3

Please sign in to comment.