Skip to content

Commit

Permalink
chore: add ephemeral ingresses mvp
Browse files Browse the repository at this point in the history
  • Loading branch information
shumailxyz committed Aug 17, 2023
1 parent 1b89446 commit d08c2d9
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 15 deletions.
2 changes: 1 addition & 1 deletion core/src/plugins/kubernetes/container/deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export async function createContainerManifests({
log,
production,
})
const kubeServices = await createServiceResources(action, namespace)
const kubeServices = await createServiceResources(action, namespace, provider)
const manifests = [workload, ...kubeServices, ...ingresses]

for (const obj of manifests) {
Expand Down
8 changes: 7 additions & 1 deletion core/src/plugins/kubernetes/container/ingress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ import { V1Ingress, V1Secret } from "@kubernetes/client-node"
import { Log } from "../../../logger/log-entry"
import chalk from "chalk"
import { Resolved } from "../../../actions/types"
import { EPHEMERAL_KUBERNETES_PROVIDER_NAME } from "../ephemeral/ephemeral"
import { getEphemeralClusterIngresses } from "../ephemeral/ingress"

// Ingress API versions in descending order of preference
export const supportedIngressApiVersions = ["networking.k8s.io/v1", "networking.k8s.io/v1beta1", "extensions/v1beta1"]

interface ServiceIngressWithCert extends ServiceIngress {
export interface ServiceIngressWithCert extends ServiceIngress {
spec: ContainerIngressSpec
certificate?: IngressTlsCertificate
}
Expand Down Expand Up @@ -211,6 +213,10 @@ export async function getIngresses(
api: KubeApi,
provider: KubernetesProvider
): Promise<ServiceIngress[]> {
if (provider.name === EPHEMERAL_KUBERNETES_PROVIDER_NAME) {
return await getEphemeralClusterIngresses(action, provider)
}

return (await getIngressesWithCert(action, api, provider)).map((ingress) => ({
hostname: ingress.hostname,
path: ingress.path,
Expand Down
24 changes: 21 additions & 3 deletions core/src/plugins/kubernetes/container/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import { getDeploymentSelector } from "./deployment"
import { KubernetesResource } from "../types"
import { find } from "lodash"
import { Resolved } from "../../../actions/types"
import { KubernetesConfig } from "../config"
import { Provider } from "../../../config/provider"
import { EPHEMERAL_KUBERNETES_PROVIDER_NAME } from "../ephemeral/ephemeral"
import { addEphemeralClusterIngressAnnotation } from "../ephemeral/ingress"

function toServicePort(portSpec: ServicePortSpec): V1ServicePort {
const port: V1ServicePort = {
Expand All @@ -30,23 +34,37 @@ function toServicePort(portSpec: ServicePortSpec): V1ServicePort {
}

// todo: consider returning Promise<KubernetesResource<V1Service>[]>
export async function createServiceResources(action: Resolved<ContainerDeployAction>, namespace: string): Promise<any> {
export async function createServiceResources(
action: Resolved<ContainerDeployAction>,
namespace: string,
provider?: Provider<KubernetesConfig>
): Promise<any> {
const specPorts = action.getSpec("ports")

if (!specPorts.length) {
return []
}

const createServiceResource = (containerAction: Resolved<ContainerDeployAction>): KubernetesResource<V1Service> => {
const serviceType = !!find(specPorts, (portSpec) => !!portSpec.nodePort) ? "NodePort" : "ClusterIP"
const serviceType =
provider?.name === EPHEMERAL_KUBERNETES_PROVIDER_NAME
? "LoadBalancer"
: !!find(specPorts, (portSpec) => !!portSpec.nodePort)
? "NodePort"
: "ClusterIP"
const servicePorts = specPorts.map(toServicePort)

const serviceMetadataAnnotations = containerAction.getSpec("annotations")
if (provider?.name === EPHEMERAL_KUBERNETES_PROVIDER_NAME) {
addEphemeralClusterIngressAnnotation(serviceMetadataAnnotations)
}

return {
apiVersion: "v1",
kind: "Service",
metadata: {
name: containerAction.name,
annotations: containerAction.getSpec("annotations"),
annotations: serviceMetadataAnnotations,
namespace,
},
spec: {
Expand Down
18 changes: 10 additions & 8 deletions core/src/plugins/kubernetes/ephemeral/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,21 @@ import { joiProviderName } from "../../../config/common"
import { ConfigureProviderParams } from "../../../plugin/handlers/Provider/configureProvider"
import { containerRegistryConfigSchema } from "../../container/config"
import { KubernetesConfig, k8sContextSchema, kubernetesConfigBase } from "../config"
import { EPHEMERAL_KUBERNETES_PROVIDER_NAME } from "./ephemeral"

export const configSchema = () =>
kubernetesConfigBase().keys({
name: joiProviderName("ephemeral-kubernetes"),
name: joiProviderName(EPHEMERAL_KUBERNETES_PROVIDER_NAME),
context: k8sContextSchema().optional(),
deploymentRegistry: containerRegistryConfigSchema().optional(),
})

export async function configureProvider(params: ConfigureProviderParams<KubernetesConfig>) {
const { base, log, projectName, ctx, config: baseConfig } = params
log.info("Configuring ephemeral-kubernetes provider")
log.info(`Configuring ${EPHEMERAL_KUBERNETES_PROVIDER_NAME} provider`)
if (!ctx.cloudApi) {
throw new Error(
"You are not logged in. You must be logged into Garden Cloud in order to use ephemeral-kubernetes provider."
`You are not logged in. You must be logged into Garden Cloud in order to use ${EPHEMERAL_KUBERNETES_PROVIDER_NAME} provider.`
)
}
const ephemeralClusterDirPath = join(ctx.gardenDirPath, "ephemeral-kubernetes")
Expand All @@ -38,22 +39,23 @@ export async function configureProvider(params: ConfigureProviderParams<Kubernet
const kubeConfig = await ctx.cloudApi.getKubeConfigForCluster(newClusterId)

const kubeconfigFileName = `${newClusterId}-kubeconfig.yaml`
const kubeConfigTmpPath = join(ctx.gardenDirPath, "ephemeral-kubernetes", kubeconfigFileName)
await writeFile(kubeConfigTmpPath, kubeConfig)
log.info(`kubeconfig saved at path: ${kubeConfigTmpPath}`)
const kubeConfigPath = join(ctx.gardenDirPath, "ephemeral-kubernetes", kubeconfigFileName)
await writeFile(kubeConfigPath, kubeConfig)
log.info(`kubeconfig saved at path: ${kubeConfigPath}`)

// const kubeConfig = DUMMY
const parsedKubeConfig: any = load(kubeConfig)
const currentContext = parsedKubeConfig["current-context"]
baseConfig.context = currentContext
baseConfig.kubeconfig = `/Users/shumail/mewtow/garden/test/ephemeral-cluster/.garden/ephemeral-kubernetes/${newClusterId}-kubeconfig.yaml`
baseConfig.kubeconfig = kubeConfigPath

// set deployment registry
baseConfig.deploymentRegistry = {
hostname: newClusterResponse.registry.endpointAddress,
namespace: newClusterResponse.registry.repository,
insecure: false,
}
// set default hostname
baseConfig.defaultHostname = `${newClusterId}.fra1.namespaced.app`
// set imagePullSecrets
baseConfig.imagePullSecrets = [
{
Expand Down
5 changes: 3 additions & 2 deletions core/src/plugins/kubernetes/ephemeral/ephemeral.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ import { createGardenPlugin } from "../../../plugin/plugin"
import { dedent } from "../../../util/string"

const providerUrl = "./kubernetes.md"
export const EPHEMERAL_KUBERNETES_PROVIDER_NAME = "ephemeral-kubernetes"

export const gardenPlugin = () =>
createGardenPlugin({
name: "ephemeral-kubernetes",
name: EPHEMERAL_KUBERNETES_PROVIDER_NAME,
base: "kubernetes",
docs: dedent`
The \`ephemeral-kubernetes\` provider is a specialized version of the [\`kubernetes\` provider](${providerUrl}) that ....
The \`${EPHEMERAL_KUBERNETES_PROVIDER_NAME}\` provider is a specialized version of the [\`kubernetes\` provider](${providerUrl}) that ....
`,
configSchema: configSchema(),
handlers: {
Expand Down
38 changes: 38 additions & 0 deletions core/src/plugins/kubernetes/ephemeral/ingress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2018-2023 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 { Resolved } from "../../../actions/types"
import { ContainerDeployAction } from "../../container/config"
import { KubernetesProvider } from "../config"
import { ServiceIngressWithCert } from "../container/ingress"

export async function getEphemeralClusterIngresses(
action: Resolved<ContainerDeployAction>,
provider: KubernetesProvider
): Promise<ServiceIngressWithCert[]> {
const ingresses = action.getSpec("ingresses")
const portsSpec = action.getSpec("ports")
return ingresses.map((ingressSpec) => {
const ingressPort = portsSpec?.find((p) => p.name === ingressSpec.port)
return {
...ingressSpec,
hostname: `${action.name}-${ingressPort?.servicePort}-${provider.config.defaultHostname}`,
path: ingressSpec.path,
port: undefined,
spec: ingressSpec,
protocol: "https",
}
})
}

export function addEphemeralClusterIngressAnnotation(annotations: { [name: string]: string }): {
[name: string]: string
} {
annotations["kubernetes.namespace.so/expose"] = "true"
return annotations
}

0 comments on commit d08c2d9

Please sign in to comment.