Skip to content

Commit

Permalink
fix(crux-ui): compose env_file apply (#968)
Browse files Browse the repository at this point in the history
  • Loading branch information
m8vago authored May 7, 2024
1 parent 61f28e5 commit fceb8e8
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 48 deletions.
13 changes: 7 additions & 6 deletions web/crux-ui/src/components/composer/compose-environment.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import DyoWrap from '@app/elements/dyo-wrap'
import { DotEnvironment } from '@app/models'
import useTranslation from 'next-translate/useTranslation'
import DotEnvFileCard from './dot-env-file-card'
import {
Expand All @@ -21,9 +22,9 @@ const ComposeEnvironment = (props: ComposeEnvironmentProps) => {

const { t } = useTranslation('compose')

const onEnvFileChange = (name: string, text: string) => dispatch(convertEnvFile(t, name, text))
const onEnvNameChange = (from: string, to: string) => dispatch(changeEnvFileName(from, to))
const onRemoveDotEnv = (name: string) => dispatch(removeEnvFile(name))
const onEnvFileChange = (target: DotEnvironment, text: string) => dispatch(convertEnvFile(t, target, text))
const onEnvNameChange = (target: DotEnvironment, to: string) => dispatch(changeEnvFileName(target, to))
const onRemoveDotEnv = (target: DotEnvironment) => dispatch(removeEnvFile(t, target))

const defaultDotEnv = selectDefaultEnvironment(state)

Expand All @@ -33,9 +34,9 @@ const ComposeEnvironment = (props: ComposeEnvironmentProps) => {
<DotEnvFileCard
key={`dot-env-${index}`}
dotEnv={it}
onEnvChange={text => onEnvFileChange(it.name, text)}
onNameChange={it !== defaultDotEnv ? name => onEnvNameChange(it.name, name) : null}
onRemove={it !== defaultDotEnv ? () => onRemoveDotEnv(it.name) : null}
onEnvChange={text => onEnvFileChange(it, text)}
onNameChange={it !== defaultDotEnv ? name => onEnvNameChange(it, name) : null}
onRemove={it !== defaultDotEnv ? () => onRemoveDotEnv(it) : null}
/>
))}
</DyoWrap>
Expand Down
109 changes: 76 additions & 33 deletions web/crux-ui/src/components/composer/use-composer-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,17 @@ const applyEnvironments = (
const appliedServices = services.map(entry => {
const [key, service] = entry

const dotEnvName = service.env_file ?? DEFAULT_ENVIRONMENT_NAME
const dotEnv = envs.find(it => it.name === dotEnvName)

const applied = applyDotEnvToComposeService(service, dotEnv.environment)
const envFile: string[] = !service.env_file
? [DEFAULT_ENVIRONMENT_NAME]
: typeof service.env_file === 'string'
? [service.env_file]
: service.env_file
const dotEnvs = envFile.map(envName => envs.find(it => it.name === envName)).filter(it => !!it)

let applied = service
dotEnvs.forEach(it => {
applied = applyDotEnvToComposeService(applied, it.environment)
})

return [key, applied]
})
Expand All @@ -90,11 +97,15 @@ type ApplyComposeToStateOptions = {
envedCompose: Compose
t: Translate
}
const applyComposeToState = (state: ComposerState, options: ApplyComposeToStateOptions) => {
const applyComposeToState = (
state: ComposerState,
options: ApplyComposeToStateOptions,
environment: DotEnvironment[],
) => {
const { t } = options

try {
const newContainers = mapComposeServices(options.envedCompose)
const newContainers = mapComposeServices(options.envedCompose, environment)

return {
...state,
Expand Down Expand Up @@ -170,23 +181,27 @@ export const convertComposeFile =
services: applyEnvironments(compose?.services, state.environment),
}

return applyComposeToState(state, {
compose: {
text,
yaml: compose,
error: null,
return applyComposeToState(
state,
{
compose: {
text,
yaml: compose,
error: null,
},
envedCompose,
t,
},
envedCompose,
t,
})
state.environment,
)
}

export const convertEnvFile =
(t: Translate, name: string, text: string): ComposerAction =>
(t: Translate, target: DotEnvironment, text: string): ComposerAction =>
state => {
const { environment } = state

const index = environment.findIndex(it => it.name === name)
const index = environment.findIndex(it => it === target)
if (index < 0) {
return state
}
Expand Down Expand Up @@ -223,38 +238,44 @@ export const convertEnvFile =
const newEnv = [...environment]
newEnv[index] = dotEnv

let newState = state
const { compose } = state
if (compose) {
const envedCompose = {
...compose.yaml,
services: applyEnvironments(compose?.yaml?.services, newEnv),
}

const envedCompose = {
...compose.yaml,
services: applyEnvironments(compose?.yaml?.services, newEnv),
newState = applyComposeToState(
state,
{
compose,
envedCompose,
t,
},
newEnv,
)
}

const newState = applyComposeToState(state, {
compose,
envedCompose,
t,
})

return {
...newState,
environment: newEnv,
}
}

export const changeEnvFileName =
(from: string, to: string): ComposerAction =>
(target: DotEnvironment, name: string): ComposerAction =>
state => {
const { environment } = state

const index = environment.findIndex(it => it.name === from)
const index = environment.findIndex(it => it === target)
if (index < 0) {
return state
}

const dotEnv: DotEnvironment = {
...environment[index],
name: to,
name,
}

const newEnv = [...environment]
Expand Down Expand Up @@ -285,23 +306,45 @@ export const addEnvFile = (): ComposerAction => state => {
}

export const removeEnvFile =
(name: string): ComposerAction =>
(t: Translate, target: DotEnvironment): ComposerAction =>
state => {
if (name === DEFAULT_ENVIRONMENT_NAME) {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
const defaultDotEnv = selectDefaultEnvironment(state)
if (defaultDotEnv === target) {
return state
}

const { environment } = state

const index = environment.findIndex(it => it.name === name)
const index = environment.findIndex(it => it === target)
if (index < 0) {
return state
}

return {
const newState = {
...state,
environment: environment.filter(it => it.name !== name),
environment: environment.filter(it => it !== target),
}

const compose = newState.compose?.yaml
if (!compose) {
return newState
}

const envedCompose = {
...compose,
services: applyEnvironments(compose.services, newState.environment),
}

return applyComposeToState(
newState,
{
compose: newState.compose,
envedCompose,
t,
},
newState.environment,
)
}

// selectors
Expand Down
59 changes: 54 additions & 5 deletions web/crux-ui/src/models/compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import {
UniqueKeyValue,
VolumeType,
} from './container'
import { VersionType } from './version'
import { Project } from './project'
import { VersionType } from './version'

export const COMPOSE_RESTART_VALUES = ['no', 'always', 'on-failure', 'unless-stopped'] as const
export type ComposeRestart = (typeof COMPOSE_RESTART_VALUES)[number]
Expand Down Expand Up @@ -52,7 +52,7 @@ export type ComposeService = {
tty?: boolean
working_dir?: string
user?: string // we only support numbers, so '0' will work but root won't
env_file?: string
env_file?: string | string[]
}

export type Compose = {
Expand Down Expand Up @@ -188,6 +188,29 @@ const mapUser = (user: string): number => {
}
}

const mapKeyValuesToRecord = (items: string[] | null): Record<string, string> =>
items?.reduce((result, it) => {
const [key, value] = it.split('=')

result[key] = value
return result
}, {})

const mapRecordToKeyValues = (map: Record<string, string> | null): UniqueKeyValue[] | null => {
if (!map) {
return null
}

return Object.entries(map).map(entry => {
const [key, value] = entry
return {
id: uuid(),
key,
value,
}
})
}

const mapKeyValues = (items: string[] | null): UniqueKeyValue[] | null =>
items?.map(it => {
const [key, value] = it.split('=')
Expand All @@ -210,6 +233,7 @@ const mapStringOrStringArray = (candidate: string | string[]): UniqueKey[] =>
export const mapComposeServiceToContainerConfig = (
service: ComposeService,
serviceKey: string,
envs: DotEnvironment[],
): ContainerConfigData => {
const ports: ContainerConfigPort[] = []
const portRanges: ContainerConfigPortRange[] = []
Expand All @@ -222,9 +246,34 @@ export const mapComposeServiceToContainerConfig = (
}
})

let environment = mapKeyValuesToRecord(service.environment)
if (service.env_file) {
const envFile = typeof service.env_file === 'string' ? [service.env_file] : service.env_file

const dotEnvs = envs.filter(it => envFile.includes(it.name))
if (dotEnvs.length > 0) {
if (!environment) {
environment = {}
}

const mergedEnvs = dotEnvs.reduce(
(result, it) => ({
...result,
...it.environment,
}),
{},
)

environment = {
...mergedEnvs,
...environment,
}
}
}

return {
name: service.container_name ?? serviceKey,
environment: mapKeyValues(service.environment),
environment: mapRecordToKeyValues(environment),
commands: mapStringOrStringArray(service.entrypoint),
args: mapStringOrStringArray(service.command),
ports: ports.length > 0 ? ports : null,
Expand Down Expand Up @@ -263,13 +312,13 @@ export const mapComposeServiceToContainerConfig = (
}
}

export const mapComposeServices = (compose: Compose): ConvertedContainer[] =>
export const mapComposeServices = (compose: Compose, envs: DotEnvironment[]): ConvertedContainer[] =>
Object.entries(compose.services).map(entry => {
const [key, service] = entry

return {
image: service.image,
config: mapComposeServiceToContainerConfig(service, key),
config: mapComposeServiceToContainerConfig(service, key, envs),
}
})

Expand Down
6 changes: 3 additions & 3 deletions web/crux-ui/src/pages/composer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import DyoToggle from '@app/elements/dyo-toggle'
import DyoWrap from '@app/elements/dyo-wrap'
import useSubmit from '@app/hooks/use-submit'
import useTeamRoutes from '@app/hooks/use-team-routes'
import { Project, Registry, VersionDetails, findRegistryByUrl, imageUrlOfImageName } from '@app/models'
import { DotEnvironment, Project, Registry, VersionDetails, findRegistryByUrl, imageUrlOfImageName } from '@app/models'
import { appendTeamSlug } from '@app/providers/team-routes'
import { ROUTE_COMPOSER, ROUTE_INDEX } from '@app/routes'
import { fetcher, redirectTo, teamSlugOrFirstTeam, withContextAuthorization } from '@app/utils'
Expand Down Expand Up @@ -72,7 +72,7 @@ const ComposerPage = () => {
const onComposeFileChange = (text: string) => dispatch(convertComposeFile(t, text))
const onToggleShowDefaultDotEnv = () => dispatch(toggleShowDefaultDotEnv())

const onEnvFileChange = (name: string, text: string) => dispatch(convertEnvFile(t, name, text))
const onEnvFileChange = (target: DotEnvironment, text: string) => dispatch(convertEnvFile(t, target, text))
const onAddDotEnv = () => dispatch(addEnvFile())

const onActivateGenerate = () => dispatch(activateUpperSection('generate'))
Expand Down Expand Up @@ -117,7 +117,7 @@ const ComposerPage = () => {
/>

{showDefaultEnv && (
<DotEnvFileCard dotEnv={defaultDotEnv} onEnvChange={text => onEnvFileChange(defaultDotEnv.name, text)} />
<DotEnvFileCard dotEnv={defaultDotEnv} onEnvChange={text => onEnvFileChange(defaultDotEnv, text)} />
)}
</div>
) : (
Expand Down
2 changes: 1 addition & 1 deletion web/crux-ui/src/validations/compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export const composeServiceSchema = yup.object().shape({

return it
}),
env_file: yup.string().optional().nullable(),
env_file: mixedStringOrStringArrayRule.optional().nullable(),
})

export const composeSchema = yup
Expand Down

0 comments on commit fceb8e8

Please sign in to comment.