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

fix(k8s): enable publishing container modules when using remote builders #872

Merged
merged 1 commit into from
Jun 24, 2019
Merged
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
2 changes: 1 addition & 1 deletion garden-service/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ export class ActionHelper implements TypeGuard {
async publishModule<T extends Module>(
params: ModuleActionHelperParams<PublishModuleParams<T>>,
): Promise<PublishResult> {
return this.callModuleHandler({ params, actionType: "publishModule", defaultHandler: dummyPublishHandler })
return this.callModuleHandler({ params, actionType: "publish", defaultHandler: dummyPublishHandler })
}

async runModule<T extends Module>(params: ModuleActionHelperParams<RunModuleParams<T>>): Promise<RunResult> {
Expand Down
26 changes: 2 additions & 24 deletions garden-service/src/plugins/container/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import { ContainerModule, containerModuleSpecSchema } from "./config"
import { buildContainerModule, getContainerBuildStatus } from "./build"
import { KubernetesProvider } from "../kubernetes/config"
import { ConfigureModuleParams } from "../../types/plugin/module/configure"
import { PublishModuleParams } from "../../types/plugin/module/publishModule"
import { HotReloadServiceParams } from "../../types/plugin/service/hotReloadService"
import { joi } from "../../config/common"
import { publishContainerModule } from "./publish"

export const containerModuleOutputsSchema = joi.object()
.keys({
Expand Down Expand Up @@ -149,29 +149,7 @@ export const gardenPlugin = (): GardenPlugin => ({
configure: configureContainerModule,
getBuildStatus: getContainerBuildStatus,
build: buildContainerModule,

async publishModule({ module, log }: PublishModuleParams<ContainerModule>) {
if (!(await containerHelpers.hasDockerfile(module))) {
log.setState({ msg: `Nothing to publish` })
return { published: false }
}

const localId = await containerHelpers.getLocalImageId(module)
const remoteId = await containerHelpers.getPublicImageId(module)

log.setState({ msg: `Publishing image ${remoteId}...` })

if (localId !== remoteId) {
await containerHelpers.dockerCli(module, ["tag", localId, remoteId])
}

// TODO: log error if it occurs
// TODO: stream output to log if at debug log level
// TODO: check if module already exists remotely?
await containerHelpers.dockerCli(module, ["push", remoteId])

return { published: true, message: `Published ${remoteId}` }
},
publish: publishContainerModule,

async hotReloadService(_: HotReloadServiceParams) {
return {}
Expand Down
32 changes: 32 additions & 0 deletions garden-service/src/plugins/container/publish.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2018 Garden Technologies, Inc. <info@garden.io>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import { ContainerModule } from "./config"
import { PublishModuleParams } from "../../types/plugin/module/publishModule"
import { containerHelpers } from "./helpers"

export async function publishContainerModule({ module, log }: PublishModuleParams<ContainerModule>) {
if (!(await containerHelpers.hasDockerfile(module))) {
log.setState({ msg: `Nothing to publish` })
return { published: false }
}

const localId = await containerHelpers.getLocalImageId(module)
const remoteId = await containerHelpers.getPublicImageId(module)

log.setState({ msg: `Publishing image ${remoteId}...` })

if (localId !== remoteId) {
await containerHelpers.dockerCli(module, ["tag", localId, remoteId])
}

// TODO: stream output to log if at debug log level
await containerHelpers.dockerCli(module, ["push", remoteId])

return { published: true, message: `Published ${remoteId}` }
}
2 changes: 2 additions & 0 deletions garden-service/src/plugins/kubernetes/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@
*/

export const RSYNC_PORT = 873
export const CLUSTER_REGISTRY_PORT = 5000
export const CLUSTER_REGISTRY_DEPLOYMENT_NAME = "garden-docker-registry"
40 changes: 5 additions & 35 deletions garden-service/src/plugins/kubernetes/container/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,18 @@ import { posix, resolve } from "path"
import { KubeApi } from "../api"
import { kubectl } from "../kubectl"
import { LogEntry } from "../../../logger/log-entry"
import { KubernetesProvider, ContainerBuildMode } from "../config"
import { KubernetesProvider, ContainerBuildMode, KubernetesPluginContext } from "../config"
import { PluginError } from "../../../exceptions"
import axios from "axios"
import { runPod } from "../run"
import { getRegistryHostname } from "../init"
import { getManifestFromRegistry } from "./util"

const dockerDaemonDeploymentName = "garden-docker-daemon"
const dockerDaemonContainerName = "docker-daemon"
// TODO: make build timeout configurable
const buildTimeout = 600
// Note: v0.9.0 appears to be completely broken: https://github.com/GoogleContainerTools/kaniko/issues/268
const kanikoImage = "gcr.io/kaniko-project/executor:v0.8.0"
const registryDeploymentName = "garden-docker-registry"
const registryPort = 5000
const syncDataVolumeName = "garden-build-sync"
const syncDeploymentName = "garden-build-sync"
Expand Down Expand Up @@ -78,39 +77,10 @@ const getLocalBuildStatus: BuildStatusHandler = async (params) => {

const getRemoteBuildStatus: BuildStatusHandler = async (params) => {
const { ctx, module, log } = params
const provider = <KubernetesProvider>ctx.provider

const registryFwd = await getPortForward({
ctx,
log,
namespace: systemNamespace,
targetDeployment: `Deployment/${registryDeploymentName}`,
port: registryPort,
})
const k8sCtx = ctx as KubernetesPluginContext
const manifest = await getManifestFromRegistry(k8sCtx, module, log)

const imageId = await containerHelpers.getDeploymentImageId(module, provider.config.deploymentRegistry)
const imageName = containerHelpers.unparseImageId({
...containerHelpers.parseImageId(imageId),
host: undefined,
tag: undefined,
})

const url = `http://localhost:${registryFwd.localPort}/v2/${imageName}/manifests/${module.version.versionString}`

try {
const res = await axios({ url })
log.silly(res.data)
return { ready: true }
} catch (err) {
if (err.response && err.response.status === 404) {
return { ready: false }
} else {
throw new PluginError(`Could not query in-cluster registry: ${err}`, {
message: err.message,
response: err.response,
})
}
}
return { ready: !!manifest }
}

const buildStatusHandlers: { [mode in ContainerBuildMode]: BuildStatusHandler } = {
Expand Down
2 changes: 2 additions & 0 deletions garden-service/src/plugins/kubernetes/container/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { ContainerModule } from "../../container/config"
import { configureMavenContainerModule, MavenContainerModule } from "../../maven-container/maven-container"
import { getTaskResult } from "../task-results"
import { k8sBuildContainer, k8sGetContainerBuildStatus } from "./build"
import { k8sPublishContainerModule } from "./publish"

async function configure(params: ConfigureModuleParams<ContainerModule>) {
params.moduleConfig = await configureContainerModule(params)
Expand All @@ -44,6 +45,7 @@ export const containerHandlers = {
getServiceStatus: getContainerServiceStatus,
getTestResult,
hotReloadService: hotReloadContainer,
publish: k8sPublishContainerModule,
runModule: runContainerModule,
runService: runContainerService,
runTask: runContainerTask,
Expand Down
48 changes: 48 additions & 0 deletions garden-service/src/plugins/kubernetes/container/publish.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2018 Garden Technologies, Inc. <info@garden.io>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import { ContainerModule } from "../../container/config"
import { PublishModuleParams } from "../../../types/plugin/module/publishModule"
import { containerHelpers } from "../../container/helpers"
import { KubernetesPluginContext } from "../config"
import { publishContainerModule } from "../../container/publish"
import { getRegistryPortForward } from "./util"
import execa = require("execa")

export async function k8sPublishContainerModule(params: PublishModuleParams<ContainerModule>) {
const { ctx, module, log } = params
const k8sCtx = ctx as KubernetesPluginContext
const provider = k8sCtx.provider

if (!(await containerHelpers.hasDockerfile(module))) {
log.setState({ msg: `Nothing to publish` })
return { published: false }
}

if (provider.config.buildMode !== "local-docker") {
// First pull from the in-cluster registry, then resume standard publish flow.
// This does mean we require a local docker as a go-between, but the upside is that we can rely on the user's
// standard authentication setup, instead of having to re-implement or account for all the different ways the
// user might be authenticating with their registries.
log.setState(`Pulling from cluster container registry...`)

const fwd = await getRegistryPortForward(k8sCtx, log)

const imageId = await containerHelpers.getDeploymentImageId(module, ctx.provider.config.deploymentRegistry)
const pullImageName = containerHelpers.unparseImageId({
...containerHelpers.parseImageId(imageId),
// Note: using localhost directly here has issues with Docker for Mac.
// https://github.com/docker/for-mac/issues/3611
host: `local.app.garden:${fwd.localPort}`,
})

await execa("docker", ["pull", pullImageName])
}

return publishContainerModule(params)
}
64 changes: 64 additions & 0 deletions garden-service/src/plugins/kubernetes/container/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright (C) 2018 Garden Technologies, Inc. <info@garden.io>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import { resolve } from "url"
import { ContainerModule } from "../../container/config"
import { getPortForward } from "../util"
import { systemNamespace } from "../system"
import { CLUSTER_REGISTRY_DEPLOYMENT_NAME, CLUSTER_REGISTRY_PORT } from "../constants"
import { containerHelpers } from "../../container/helpers"
import { PluginError } from "../../../exceptions"
import { PluginContext } from "../../../plugin-context"
import { LogEntry } from "../../../logger/log-entry"
import { KubernetesPluginContext } from "../config"
import axios from "axios"

export async function getRegistryPortForward(ctx: PluginContext, log: LogEntry) {
return getPortForward({
ctx,
log,
namespace: systemNamespace,
targetDeployment: `Deployment/${CLUSTER_REGISTRY_DEPLOYMENT_NAME}`,
port: CLUSTER_REGISTRY_PORT,
})
}

export async function getManifestFromRegistry(
ctx: KubernetesPluginContext, module: ContainerModule, log: LogEntry,
) {
const url = await getImageRegistryUrl(ctx, module, log, `manifests/${module.version.versionString}`)

try {
const res = await axios({ url })
log.silly(res.data)
return res.data
} catch (err) {
if (err.response && err.response.status === 404) {
return null
} else {
throw new PluginError(`Could not query in-cluster registry: ${err}`, {
message: err.message,
response: err.response,
})
}
}
}

async function getImageRegistryUrl(ctx: KubernetesPluginContext, module: ContainerModule, log: LogEntry, path: string) {
const registryFwd = await getRegistryPortForward(ctx, log)
const imageId = await containerHelpers.getDeploymentImageId(module, ctx.provider.config.deploymentRegistry)
const imageName = containerHelpers.unparseImageId({
...containerHelpers.parseImageId(imageId),
host: undefined,
tag: undefined,
})

const baseUrl = `http://localhost:${registryFwd.localPort}/v2/${imageName}/`

return resolve(baseUrl, path)
}
2 changes: 1 addition & 1 deletion garden-service/src/types/plugin/outputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ export interface ModuleActionOutputs extends ServiceActionOutputs {
configure: Promise<ConfigureModuleResult>
getBuildStatus: Promise<BuildStatus>
build: Promise<BuildResult>
publishModule: Promise<PublishResult>
publish: Promise<PublishResult>
runModule: Promise<RunResult>
testModule: Promise<TestResult>
getTestResult: Promise<TestResult | null>
Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/types/plugin/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ export interface ModuleActionParams<T extends Module = Module> {
configure: ConfigureModuleParams<T>
getBuildStatus: GetBuildStatusParams<T>
build: BuildModuleParams<T>
publishModule: PublishModuleParams<T>
publish: PublishModuleParams<T>
runModule: RunModuleParams<T>
testModule: TestModuleParams<T>
getTestResult: GetTestResultParams<T>
Expand Down
6 changes: 3 additions & 3 deletions garden-service/src/types/plugin/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ export interface ModuleActionParams<T extends Module = Module> {
configure: ConfigureModuleParams<T>
getBuildStatus: GetBuildStatusParams<T>
build: BuildModuleParams<T>
publishModule: PublishModuleParams<T>
publish: PublishModuleParams<T>
runModule: RunModuleParams<T>
testModule: TestModuleParams<T>
getTestResult: GetTestResultParams<T>
Expand All @@ -174,7 +174,7 @@ export interface ModuleActionOutputs extends ServiceActionOutputs {
configure: Promise<ConfigureModuleResult>
getBuildStatus: Promise<BuildStatus>
build: Promise<BuildResult>
publishModule: Promise<PublishResult>
publish: Promise<PublishResult>
runModule: Promise<RunResult>
testModule: Promise<TestResult>
getTestResult: Promise<TestResult | null>
Expand All @@ -186,7 +186,7 @@ export const moduleActionDescriptions:
configure,
getBuildStatus,
build,
publishModule,
publish: publishModule,
runModule,
testModule,
getTestResult,
Expand Down
4 changes: 2 additions & 2 deletions garden-service/test/unit/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,8 +467,8 @@ const testPlugin: PluginFactory = async () => ({
return {}
},

publishModule: async (params) => {
validate(params, moduleActionDescriptions.publishModule.paramsSchema)
publish: async (params) => {
validate(params, moduleActionDescriptions.publish.paramsSchema)
return { published: true }
},

Expand Down
2 changes: 1 addition & 1 deletion garden-service/test/unit/src/commands/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const testProvider: PluginFactory = () => {
configure: configureTestModule,
getBuildStatus,
build,
publishModule,
publish: publishModule,
},
},
}
Expand Down
2 changes: 1 addition & 1 deletion garden-service/test/unit/src/plugins/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ describe("plugins.container", () => {
const handler = gardenPlugin()
const configure = handler.moduleActions!.container!.configure!
const build = handler.moduleActions!.container!.build!
const publishModule = handler.moduleActions!.container!.publishModule!
const publishModule = handler.moduleActions!.container!.publish!
const getBuildStatus = handler.moduleActions!.container!.getBuildStatus!

const baseConfig: ModuleConfig<ContainerModuleSpec, any, any> = {
Expand Down