wip
This commit is contained in:
parent
5ebdf36dde
commit
0e2fa2b27f
2 changed files with 236 additions and 44 deletions
|
@ -4,29 +4,77 @@ import * as k8s from '@kubernetes/client-node';
|
|||
import {Logger} from "winston";
|
||||
import {HttpError} from "@kubernetes/client-node";
|
||||
|
||||
// export function createInvokeArgoAction() {
|
||||
|
||||
|
||||
type argoInput = {
|
||||
name: string
|
||||
namespace: string
|
||||
clusterName: string
|
||||
templateName: string
|
||||
parameters: parameter[]
|
||||
}
|
||||
|
||||
const argoWorkflowsGroup = 'argoproj.io'
|
||||
const argoWorkflowsVersion = 'v1alpha1'
|
||||
const argoWorkFlowPlural = 'workflows'
|
||||
const argoWorkFlowKind = 'Workflow'
|
||||
const argoWorkFlowMetadataDefault: k8s.V1ObjectMeta = {
|
||||
generateName: "backstage-scaffolding-"
|
||||
}
|
||||
|
||||
class Workflow {
|
||||
apiVersion: string = `${argoWorkflowsGroup}/${argoWorkflowsVersion}`
|
||||
kind: string = argoWorkFlowKind
|
||||
metadata: k8s.V1ObjectMeta = argoWorkFlowMetadataDefault
|
||||
spec: workflowSpec
|
||||
|
||||
constructor(templateName: string, namespace: string, params?: parameter[], artifacts?: object[] ) {
|
||||
this.metadata.namespace = namespace
|
||||
const args: argument = {}
|
||||
if (params) {
|
||||
args.parameters = params
|
||||
}
|
||||
if (artifacts) {
|
||||
args.artifacts = artifacts
|
||||
}
|
||||
this.spec = {
|
||||
workflowTemplateRef: {
|
||||
name: templateName
|
||||
},
|
||||
arguments: args
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type workflowSpec = {
|
||||
arguments?: argument
|
||||
entrypoint?: string
|
||||
workflowTemplateRef: workflowTemplateRef
|
||||
}
|
||||
|
||||
type workflowTemplateRef = {
|
||||
clusterScope?: boolean
|
||||
name: string
|
||||
}
|
||||
|
||||
type argument = {
|
||||
artifacts?: object[]
|
||||
parameters?: parameter[]
|
||||
}
|
||||
|
||||
type parameter = {
|
||||
name: string
|
||||
value: string
|
||||
valueFrom?: object
|
||||
}
|
||||
|
||||
export function createInvokeArgoAction(config: Config, logger: Logger) {
|
||||
return createTemplateAction<argoInput>({
|
||||
id: 'workflows:argo:invoke',
|
||||
description:
|
||||
'Append content to the end of the given file, it will create the file if it does not exist.',
|
||||
'Invokes an Argo workflow using a workflow template',
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object',
|
||||
required: ['name', 'namespace', 'clusterName'],
|
||||
required: ['namespace', 'clusterName', 'templateName'],
|
||||
properties: {
|
||||
name: {
|
||||
title: 'Name',
|
||||
description: 'Name of Argo workflow template',
|
||||
type: 'string',
|
||||
},
|
||||
namespace: {
|
||||
title: 'Namespace',
|
||||
description: 'Namespace to run this workflow',
|
||||
|
@ -37,13 +85,34 @@ export function createInvokeArgoAction(config: Config, logger: Logger) {
|
|||
description: 'Name of Cluster',
|
||||
type: 'string',
|
||||
},
|
||||
templateName: {
|
||||
title: 'Template name',
|
||||
description: 'Argo Workflows template name',
|
||||
type: 'string',
|
||||
},
|
||||
parameters: {
|
||||
title: "Argo workflows parameters",
|
||||
description: 'parameters used by the template',
|
||||
type: 'array',
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: {
|
||||
type: "string"
|
||||
},
|
||||
value: {
|
||||
type: "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
output: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
ID: {
|
||||
title: 'ID',
|
||||
title: 'Workflow ID',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
|
@ -52,26 +121,7 @@ export function createInvokeArgoAction(config: Config, logger: Logger) {
|
|||
async handler(ctx: ActionContext<argoInput>) {
|
||||
logger.debug(`Invoked with ${ctx.input}`)
|
||||
|
||||
const c = config.getConfigArray("kubernetes.clusterLocatorMethods")
|
||||
const cc = c.filter(function(val) {
|
||||
return val.getString("type") === "config"
|
||||
})
|
||||
logger.info(`found ${cc.length} statically configured clusters`)
|
||||
|
||||
const clusters = new Array<Config>();
|
||||
// this is shit
|
||||
cc.forEach(function(conf ) {
|
||||
const cl = conf.getConfigArray("clusters")
|
||||
cl.forEach(function(val) {
|
||||
if (val.getString("name") === ctx.input.clusterName) {
|
||||
clusters.push(val)
|
||||
}
|
||||
})
|
||||
})
|
||||
if (clusters.length === 0 ) {
|
||||
throw new Error("Cluster not found")
|
||||
}
|
||||
const targetCluster = clusters[0]
|
||||
const targetCluster = getClusterConfig(ctx.input.clusterName, config)
|
||||
const kc = new k8s.KubeConfig()
|
||||
kc.addCluster({
|
||||
name: ctx.input.clusterName,
|
||||
|
@ -89,23 +139,85 @@ export function createInvokeArgoAction(config: Config, logger: Logger) {
|
|||
name: ctx.input.clusterName
|
||||
})
|
||||
kc.setCurrentContext(ctx.input.clusterName)
|
||||
const client = kc.makeApiClient(k8s.CoreV1Api)
|
||||
logger.info("made client")
|
||||
try {
|
||||
const resp = await client.listNamespace()
|
||||
logger.info(`response: ${resp.body}`)
|
||||
|
||||
} catch (error) {
|
||||
if (error instanceof HttpError) {
|
||||
logger.info(`error : ${error.response.statusCode} ${error.response.statusMessage}`)
|
||||
throw new Error(`Failed to talk to the cluster: ${error.response.statusCode} ${error.response.statusMessage}`)
|
||||
const client = kc.makeApiClient(k8s.CustomObjectsApi)
|
||||
const wf = new Workflow(ctx.input.templateName, ctx.input.namespace, ctx.input.parameters)
|
||||
const body = generateBody(ctx.input.templateName, ctx.input.namespace)
|
||||
|
||||
try {
|
||||
const resp = await client.createNamespacedCustomObject(
|
||||
argoWorkflowsGroup, argoWorkflowsVersion, ctx.input.namespace,
|
||||
argoWorkFlowPlural, body
|
||||
)
|
||||
logger.debug(`response: ${resp.body}`)
|
||||
ctx.output('ID', resp.body.toString())
|
||||
} catch (err) {
|
||||
if (err instanceof HttpError) {
|
||||
let msg = `${err.response.statusMessage}: `
|
||||
if ("kind" in err.body && err.body.kind === "Status" && "message" in err.body) {
|
||||
msg += err.body.message
|
||||
}
|
||||
logger.info(`error : ${err.response.statusCode} ${msg}`)
|
||||
throw new Error(`Failed to talk to the cluster: ${err.response.statusCode} ${err.response.statusMessage} \n ${msg}`)
|
||||
}
|
||||
if (error instanceof Error) {
|
||||
logger.error(`error while talking to cluster: ${error.name} ${error.message}`)
|
||||
if (err instanceof Error) {
|
||||
logger.error(`error while talking to cluster: ${err.name} ${err.message}`)
|
||||
}
|
||||
throw new Error("Unknown exception was encountered.")
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function getClusterConfig(name: string, config: Config): Config {
|
||||
const c = config.getConfigArray("kubernetes.clusterLocatorMethods")
|
||||
const cc = c.filter(function(val) {
|
||||
return val.getString("type") === "config"
|
||||
})
|
||||
|
||||
const clusters = new Array<Config>();
|
||||
// this is shit
|
||||
cc.forEach(function(conf ) {
|
||||
const cl = conf.getConfigArray("clusters")
|
||||
cl.forEach(function(val) {
|
||||
if (val.getString("name") === name) {
|
||||
clusters.push(val)
|
||||
}
|
||||
})
|
||||
})
|
||||
if (clusters.length === 0 ) {
|
||||
throw new Error(`Cluster with name ${name} not found`)
|
||||
}
|
||||
return clusters[0]
|
||||
}
|
||||
|
||||
function generateBody(templateName: string, namespace: string, entrypoint: string): object {
|
||||
let obj = {
|
||||
"apiVersion": "argoproj.io/v1alpha1",
|
||||
"kind": "Workflow",
|
||||
"metadata": {
|
||||
"generateName": "backstage-scaffolding-",
|
||||
"namespace": `${namespace}`
|
||||
},
|
||||
"spec": {
|
||||
"arguments": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "message",
|
||||
"value": "from workflow"
|
||||
}
|
||||
]
|
||||
},
|
||||
"workflowTemplateRef": {
|
||||
"name": `${templateName}`
|
||||
}
|
||||
}
|
||||
}
|
||||
if (entrypoint) {
|
||||
obj.spec.entorypoint = entrypoint
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
|
||||
|
|
80
test-template.yaml
Normal file
80
test-template.yaml
Normal file
|
@ -0,0 +1,80 @@
|
|||
apiVersion: scaffolder.backstage.io/v1beta3
|
||||
kind: Template
|
||||
metadata:
|
||||
name: test-template
|
||||
title: TESTING
|
||||
description: test
|
||||
spec:
|
||||
owner: backstage/techdocs-core
|
||||
type: service
|
||||
# these are the steps which are rendered in the frontend with the form input
|
||||
parameters:
|
||||
- title: Fill in some steps
|
||||
required:
|
||||
- name
|
||||
- owner
|
||||
properties:
|
||||
name:
|
||||
title: Application Name
|
||||
type: string
|
||||
description: Unique name of the component
|
||||
ui:autofocus: true
|
||||
ui:options:
|
||||
rows: 5
|
||||
owner:
|
||||
title: Owner
|
||||
type: string
|
||||
description: Owner of the component
|
||||
ui:field: OwnerPicker
|
||||
ui:options:
|
||||
catalogFilter:
|
||||
kind: Group
|
||||
labels:
|
||||
title: Labels
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Labels to apply to the application
|
||||
namespace:
|
||||
title: Namespace
|
||||
type: string
|
||||
description: Namespace to deploy this application into. Optional. Defaults to application name.
|
||||
ui:options:
|
||||
rows: 5
|
||||
clusterName:
|
||||
title: Cluster Name
|
||||
type: string
|
||||
default: canoe-packaging
|
||||
description: Name of the cluster to run this in
|
||||
- title: Workflow params
|
||||
properties:
|
||||
workflowParams:
|
||||
title: workflow parameters
|
||||
type: array
|
||||
description: workflow parameters
|
||||
ui:autofocus: true
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
required:
|
||||
- name
|
||||
- value
|
||||
name:
|
||||
type: string
|
||||
value:
|
||||
type: string
|
||||
steps:
|
||||
- id: flow
|
||||
name: Flow
|
||||
action: workflows:argo:invoke
|
||||
input:
|
||||
templateName: workflow-template-whalesay-template
|
||||
namespace: admin
|
||||
clusterName: ${{ parameters.clusterName }}
|
||||
parameters: ${{ parameters.workflowParams }}
|
||||
|
||||
# output:
|
||||
# links:
|
||||
# - title: Open in catalog
|
||||
# icon: catalog
|
||||
# entityRef: ${{ steps['register'].output.entityRef }}
|
Loading…
Reference in a new issue