Skip to content

Commit

Permalink
fix: copying a deployment now does not copies the secret values to a …
Browse files Browse the repository at this point in the history
…different node (#828)
  • Loading branch information
m8vago authored Sep 19, 2023
1 parent 215c3f0 commit 21ae397
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 11 deletions.
2 changes: 1 addition & 1 deletion web/crux-ui/e2e/utils/test.fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ test.beforeEach(async ({ page }, testInfo) => {
return
}

console.log(`[${testInfo.title}] ${type.toUpperCase()} ${it.text()}`)
console.info(`[${testInfo.title}] ${type.toUpperCase()} ${it.text()}`)
})

if (CPU_THROTTLE) {
Expand Down
20 changes: 20 additions & 0 deletions web/crux-ui/e2e/utils/websocket-match.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { UniqueSecretKeyValue } from '@app/models'

export const wsPatchMatchPorts = (internalPort: string, externalPort?: string) => (payload: any) => {
const internal = Number.parseInt(internalPort, 10)
const external = externalPort ? Number.parseInt(externalPort, 10) : null
Expand All @@ -22,6 +24,24 @@ export const wsPatchMatchPortRange =
)
}

export const wsPatchMatchEverySecret =
(secretKeys: string[]) =>
(payload: any): boolean => {
const payloadSecretKeys: string[] = payload.config?.secrets?.map(it => it?.key) ?? []
return secretKeys.every(it => payloadSecretKeys.includes(it))
}

export const wsPatchMatchNonNullSecretValues =
(secretKeys: string[]) =>
(payload: any): boolean => {
const payloadSecrets: UniqueSecretKeyValue[] = payload.config?.secrets ?? []

return (
secretKeys.every(secKey => payloadSecrets.find(it => it.key === secKey)) &&
payloadSecrets.every(it => typeof it.value === 'string')
)
}

export const wsPatchMatchSecret = (secret: string, required: boolean) => (payload: any) =>
payload.config?.secrets?.some(it => it.key === secret && it.required === required)

Expand Down
159 changes: 159 additions & 0 deletions web/crux-ui/e2e/with-login/deployment/deployment-copy.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { UniqueSecretKeyValue, WS_TYPE_PATCH_IMAGE, WS_TYPE_PATCH_INSTANCE } from '@app/models'
import { Page, expect } from '@playwright/test'
import { wsPatchMatchEverySecret, wsPatchMatchNonNullSecretValues } from 'e2e/utils/websocket-match'
import { DAGENT_NODE, NGINX_TEST_IMAGE_WITH_TAG, TEAM_ROUTES, waitForURLExcept } from '../../utils/common'
import { createNode } from '../../utils/nodes'
import {
addDeploymentToVersion,
createImage,
createProject,
createVersion,
fillDeploymentPrefix,
} from '../../utils/projects'
import { test } from '../../utils/test.fixture'
import { waitSocketRef, wsPatchSent } from '../../utils/websocket'

const addSecretToImage = async (
page: Page,
projectId: string,
versionId: string,
imageId: string,
secretKeys: string[],
): Promise<void> => {
const sock = waitSocketRef(page)
await page.goto(TEAM_ROUTES.project.versions(projectId).imageDetails(versionId, imageId))
await page.waitForSelector('h2:text-is("Image")')
const ws = await sock
const wsRoute = TEAM_ROUTES.project.versions(projectId).detailsSocket(versionId)

const jsonEditorButton = await page.waitForSelector('button:has-text("JSON")')
await jsonEditorButton.click()

const jsonEditor = await page.locator('textarea')
const json = JSON.parse(await jsonEditor.inputValue())
json.secrets = secretKeys.map(key => ({ key, required: false }))

const wsSent = wsPatchSent(ws, wsRoute, WS_TYPE_PATCH_IMAGE, wsPatchMatchEverySecret(secretKeys))
await jsonEditor.fill(JSON.stringify(json))
await wsSent
}

const openContainerConfigByDeploymentTable = async (page: Page, containerName: string): Promise<void> => {
const instancesTabelBody = await page.locator('.table-row-group')
const instanceRows = await instancesTabelBody.locator('.table-row')
await expect(instanceRows).toHaveCount(1)

await expect(page.locator(`div.table-cell:has-text("${containerName}")`).first()).toBeVisible()
const containerSettingsButton = await page.waitForSelector(
`[src="/instance_config_icon.svg"]:right-of(:text("${containerName}"))`,
)
await containerSettingsButton.click()

await page.waitForSelector(`h2:has-text("Container")`)
}

test.describe('Deployment Copy', () => {
const projectName = 'depl-cpy'
const newNodeName = projectName
const originalPrefix = `dcpy`

let originalDeploymentId: string
const secretKeys = ['secretOne', 'secretTwo']
const newSecretKey = 'new-secret'
const newSecretKeyList = [...secretKeys, newSecretKey]

test.beforeAll(async ({ browser }) => {
const ctx = await browser.newContext()
const page = await ctx.newPage()

const projectId = await createProject(page, projectName, 'versioned')
await createNode(page, newNodeName)

const versionId = await createVersion(page, projectId, '0.1.0', 'Incremental')
const imageId = await createImage(page, projectId, versionId, NGINX_TEST_IMAGE_WITH_TAG)

await addSecretToImage(page, projectId, versionId, imageId, secretKeys)

const { id: deploymentId } = await addDeploymentToVersion(page, projectId, versionId, DAGENT_NODE, {
prefix: originalPrefix,
})
originalDeploymentId = deploymentId

const sock = waitSocketRef(page)
await page.goto(TEAM_ROUTES.deployment.details(originalDeploymentId))
await page.waitForSelector('h2:text-is("Deployments")')
const ws = await sock

await openContainerConfigByDeploymentTable(page, 'nginx')

const newSecretValue = 'new-secret-value'

const newSecertKeyInput = page.locator('input[placeholder="Key"][value=""]:below(label:has-text("SECRETS"))')
await newSecertKeyInput.fill(newSecretKey)

const newSecretValueInput = page.locator(
`input[placeholder="Value"][value=""]:near(input[placeholder="Key"][value="${newSecretKey}"], 10)`,
)
await newSecretValueInput.fill(newSecretValue)

const wsRoute = TEAM_ROUTES.deployment.detailsSocket(originalDeploymentId)
const wsSent = wsPatchSent(ws, wsRoute, WS_TYPE_PATCH_INSTANCE, wsPatchMatchNonNullSecretValues(newSecretKeyList))
await page.locator(`button:has-text("Save"):below(input[value="${newSecretValue}"])`).click()
await wsSent

await page.close()
await ctx.close()
})

test('should copy secret values to the same node', async ({ page }) => {
await page.goto(TEAM_ROUTES.deployment.details(originalDeploymentId))
await page.waitForSelector('h2:text-is("Deployments")')

const copyButton = page.locator('button:has-text("Copy")')
await copyButton.click()

const newPrefix = 'dcpy-second'
await page.locator(`button:has-text("${DAGENT_NODE}")`).click()
await fillDeploymentPrefix(page, newPrefix)

const currentUrl = page.url()
await page.locator('button:has-text("Copy")').click()
await waitForURLExcept(page, { startsWith: `${TEAM_ROUTES.deployment.list()}/`, except: currentUrl })
await page.waitForSelector('h2:text-is("Deployments")')

await expect(page.locator('.bg-dyo-turquoise:has-text("Preparing")')).toHaveCount(1)

await openContainerConfigByDeploymentTable(page, 'nginx')

const newSecretValueInput = page.locator(
`input[placeholder="Value"]:near(input[placeholder="Key"][value="${newSecretKey}"], 10)`,
)

await expect(newSecretValueInput).toBeDisabled()
await expect(newSecretValueInput).toHaveValue(/^(?!\s*$).+/) // match anything but an empty string
})

test('should delete secret values to a different node', async ({ page }) => {
await page.goto(TEAM_ROUTES.deployment.details(originalDeploymentId))
await page.waitForSelector('h2:text-is("Deployments")')

const copyButton = page.locator('button:has-text("Copy")')
await copyButton.click()

await page.locator(`button:has-text("${newNodeName}")`).click()
await fillDeploymentPrefix(page, originalPrefix)

const currentUrl = page.url()
await page.locator('button:has-text("Copy")').click()
await waitForURLExcept(page, { startsWith: `${TEAM_ROUTES.deployment.list()}/`, except: currentUrl })
await page.waitForSelector('h2:text-is("Deployments")')

await expect(page.locator('.bg-dyo-turquoise:has-text("Preparing")')).toHaveCount(1)

await openContainerConfigByDeploymentTable(page, 'nginx')

const newSecretKeyInput = page.locator(`input[placeholder="Key"][value="${newSecretKey}"]`)

await expect(await newSecretKeyInput.count()).toEqual(0)
})
})
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { ProjectType, WS_TYPE_PATCH_IMAGE } from '@app/models'
import { expect, Page } from '@playwright/test'
import { test } from '../../utils/test.fixture'
import { Page, expect } from '@playwright/test'
import { NGINX_TEST_IMAGE_WITH_TAG, TEAM_ROUTES, waitForURLExcept } from '../../utils/common'
import { deployWithDagent } from '../../utils/node-helper'
import { createNode } from '../../utils/nodes'
Expand All @@ -11,7 +10,8 @@ import {
createVersion,
fillDeploymentPrefix,
} from '../../utils/projects'
import { waitSocketRef as waitSocketRef, wsPatchSent } from '../../utils/websocket'
import { test } from '../../utils/test.fixture'
import { waitSocketRef, wsPatchSent } from '../../utils/websocket'

const setup = async (
page: Page,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ const AddDeploymentCard = (props: AddDeploymentCardProps) => {
) : !versions && formik.values.projectId ? (
<DyoLabel>{t('common:loading')}</DyoLabel>
) : versions.length === 0 ? (
<DyoLabel>{t('noVersions')}</DyoLabel>
<DyoMessage message={t('noVersions')} />
) : (
currentProject.type === 'versioned' && (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ import { useEffect } from 'react'
import toast from 'react-hot-toast'
import useSWR from 'swr'

interface AddDeploymentCardProps {
interface AddDeploymentToVersionCardProps {
className?: string
projectName: string
versionId: string
onAdd: (deploymentId: string) => void
onDiscard: VoidFunction
}

const AddDeploymentCard = (props: AddDeploymentCardProps) => {
const AddDeploymentToVersionCard = (props: AddDeploymentToVersionCardProps) => {
const { projectName, versionId, className, onAdd, onDiscard } = props

const { t } = useTranslation('deployments')
Expand Down Expand Up @@ -171,4 +171,4 @@ const AddDeploymentCard = (props: AddDeploymentCardProps) => {
)
}

export default AddDeploymentCard
export default AddDeploymentToVersionCard
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ProjectDetails, VERSION_SECTIONS_STATE_VALUES } from '@app/models'
import { parseStringUnionType } from '@app/utils'
import { useRouter } from 'next/dist/client/router'
import React, { useEffect, useRef } from 'react'
import AddDeploymentCard from './deployments/add-deployment-card'
import AddDeploymentToVersionCard from './deployments/add-deployment-to-version-card'
import CopyDeploymentCard from './deployments/copy-deployment-card'
import AddImagesCard from './images/add-images-card'
import { VerionState, VersionActions, VersionSection } from './use-version-state'
Expand Down Expand Up @@ -59,7 +59,7 @@ const VersionSections = (props: VersionSectionsProps) => {
) : state.addSection === 'image' ? (
<AddImagesCard onImagesSelected={actions.addImages} onDiscard={actions.discardAddSection} />
) : state.addSection === 'deployment' ? (
<AddDeploymentCard
<AddDeploymentToVersionCard
className="mb-4 p-8"
projectName={project.name}
versionId={state.version.id}
Expand Down
4 changes: 3 additions & 1 deletion web/crux/src/app/deploy/deploy.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,8 @@ export default class DeployService {
},
})

const differentNode = oldDeployment.nodeId !== newDeployment.nodeId

await this.prisma.$transaction(
oldDeployment.instances.map(it =>
this.prisma.instance.create({
Expand All @@ -962,7 +964,7 @@ export default class DeployService {
commands: toPrismaJson(it.config.commands),
args: toPrismaJson(it.config.args),
environment: toPrismaJson(it.config.environment),
secrets: toPrismaJson(it.config.secrets),
secrets: differentNode ? null : toPrismaJson(it.config.secrets),
initContainers: toPrismaJson(it.config.initContainers),
logConfig: toPrismaJson(it.config.logConfig),
restartPolicy: it.config.restartPolicy,
Expand Down

0 comments on commit 21ae397

Please sign in to comment.