Skip to content

Commit

Permalink
Merge pull request #211 from kubero-dev/feature/add-custom-template-c…
Browse files Browse the repository at this point in the history
…atalog

Feature / Add template catalog and make it configurable
  • Loading branch information
mms-gianni authored Oct 9, 2023
2 parents bf38abb + 2accea0 commit e9d069e
Show file tree
Hide file tree
Showing 12 changed files with 150 additions and 16 deletions.
4 changes: 3 additions & 1 deletion client/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
</v-list-item-icon>
<v-list-item-title>Add-Ons</v-list-item-title>
</v-list-item>
<v-list-item link to="/templates">
<v-list-item link to="/templates" v-if="templatesEnabled">
<v-list-item-icon>
<v-icon>mdi-palette-outline</v-icon>
</v-list-item-icon>
Expand Down Expand Up @@ -147,6 +147,7 @@ export default {
popup: "false",
session: false,
isAuthenticated: false,
templatesEnabled: true,
version: "dev",
banner: {
show: false,
Expand Down Expand Up @@ -203,6 +204,7 @@ export default {
console.log("isAuthenticated: " + result.data.isAuthenticated);
this.session = result.data.isAuthenticated;
this.version = result.data.version;
this.templatesEnabled = result.data.templatesEnabled;
// safe version to vuetufy gloabl scope for use in components
this.$vuetify.version = this.version;
Expand Down
8 changes: 4 additions & 4 deletions client/src/components/apps/new.vue
Original file line number Diff line number Diff line change
Expand Up @@ -983,8 +983,8 @@ export default {
this.loadBuildpacks();
this.loadApp(); // this may lead into a race condition with the buildpacks loaded in loadPipeline
if (this.$route.query.service) {
this.loadTemplate(this.$route.query.service);
if (this.$route.query.template) {
this.loadTemplate(this.$route.query.catalogId, this.$route.query.template);
}
//this.buildPipeline = this.$vuetify.buildPipeline
Expand All @@ -995,8 +995,8 @@ export default {
breadcrumbs: () => import('../breadcrumbs.vue'),
},
methods: {
loadTemplate(service) {
axios.get('/api/services/'+service).then(response => {
loadTemplate(catalogId, template) {
axios.get('/api/templates/'+catalogId+'/'+template).then(response => {
this.appname = response.data.name;
this.containerPort = response.data.image.containerPort;
Expand Down
44 changes: 37 additions & 7 deletions client/src/components/services/list.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@
</v-col>
</v-row>
<v-row>
<v-tabs v-if="templates.catalogs.length > 1">
<template>
<v-tab
v-for="(catalog, index) in Object.entries(templates.catalogs)"
:key="index"
@click="loadTemplates(catalog[1].index.url)"
>
{{ catalog[1].name }}
</v-tab>
</template>
</v-tabs>
<v-col cols="12" sm="12" md="3"
v-for="template in services.services" :key="template.name">
<v-card
Expand Down Expand Up @@ -93,7 +104,7 @@
color="primary"
dark
:disabled="!pipeline || !phase"
@click="openInstall(clickedTemplate.dirname, pipeline, phase)"
@click="openInstall(clickedTemplate.dirname, pipeline, phase, catalogId)"
>
Install
</v-btn>
Expand All @@ -110,7 +121,7 @@ export default {
sockets: {
},
mounted() {
this.loadTemplatesList();
this.loadCatalogs(this.catalogId);
this.loadPipelinesList();
},
data: () => ({
Expand All @@ -121,6 +132,12 @@ export default {
services: [],
dialog: false,
clickedTemplate: {},
catalogId: 0,
templates: {
enabled: true,
catalogs: [],
},
catalogTabs: [],
}),
components: {
},
Expand All @@ -133,19 +150,32 @@ export default {
}
this.dialog = false;
},
openInstall(templatename, pipeline, phase) {
openInstall(templatename, pipeline, phase, catalogId) {
// redirect to install page
console.log(`/#/pipeline/${pipeline}/${phase}/apps/new?service=${templatename}`);
window.location.href = `/#/pipeline/${pipeline}/${phase}/apps/new?service=${templatename}`;
window.location.href = `/#/pipeline/${pipeline}/${phase}/apps/new?template=${templatename}&catalogId=${catalogId}`;
},
openInstallDialog(template) {
this.clickedTemplate = template;
this.dialog = true;
},
loadTemplatesList() {
loadCatalogs(catalogId) {
const self = this;
axios.get(`/api/config/catalogs`)
.then(response => {
self.templates = response.data;
if (self.templates.catalogs.length > 0 && self.templates.enabled == true) {
self.loadTemplates(self.templates.catalogs[catalogId].index.url)
}
})
.catch(error => {
console.log(error);
});
},
async loadTemplates(indexUrl) {
const self = this;
axios.get(`https://services.kubero.dev`)
axios.get(indexUrl)
.then(response => {
self.services = response.data;
})
Expand Down
1 change: 1 addition & 0 deletions client/src/components/settings/form.vue
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ export default {
},
podSizeList: [],
buildpacks: [],
templateCatalogs: [],
}
}),
components: {
Expand Down
9 changes: 9 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ kubero:
apps:
pipelines:
- janitor/ttl=5m
templates:
enabled: true
catalogs:
- name: "Kubero"
description: "Kubero templates"
templateBasePath: "https://raw.githubusercontent.com/kubero-dev/kubero/main/services/"
index:
url: "https://raw.githubusercontent.com/kubero-dev/templates/main/index.json"
format: "json" # json or yaml # TODO has no effect yet. json is always used
buildpacks:
- name: NodeJS
language: JavaScript
Expand Down
33 changes: 32 additions & 1 deletion src/kubero.ts
Original file line number Diff line number Diff line change
Expand Up @@ -630,11 +630,30 @@ export class Kubero {
}
}

// Loads the app config from the config file
// Loads the Kubero config from the local config file
private loadConfig(path:string): IKuberoConfig {
try {
let config = YAML.parse(fs.readFileSync(path, 'utf8')) as IKuberoConfig;


// backward compatibility. Add default if template does not exist
if (!config.templates) {
config.templates = {
enabled: true,
catalogs: [
{
name: 'Kubero',
description: 'Kubero Templates',
templateBasePath: 'https://raw.githubusercontent.com/kubero-dev/kubero/main/services/',
index: {
url: 'https://raw.githubusercontent.com/kubero-dev/templates/main/index.json',
format: 'json',
}
}
]
};
}

// override env vars with config values
if (config.kubero) {
if (config.kubero.namespace && process.env.KUBERO_NAMESPACE === undefined) {
Expand Down Expand Up @@ -1054,4 +1073,16 @@ export class Kubero {
app: appName
};
}

public async getTemplateConfig() {
return this.config.templates;
}

public async getTemplateBasePath(catalogId: number) {
return this.config.templates.catalogs[catalogId].templateBasePath;
}

public getTemplateEnabled() {
return this.config.templates.enabled;
}
}
2 changes: 1 addition & 1 deletion src/modules/kubectl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ export class Kubectl {
public async getKuberoconfig(): Promise<V1ConfigMap | void> {
let config = await this.coreV1Api.readNamespacedConfigMap(
'kubero-config',
'kubero'
'kubero' // TODO: This should be configurable
).catch((error: any) => {
debug.log(error);
})
Expand Down
1 change: 1 addition & 0 deletions src/modules/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class Settings {
const configMap = YAML.parse(settings.data["config.yaml"]) as IKuberoConfig
config["podSizeList"] = configMap.podSizeList
config["buildpacks"] = configMap.buildpacks
config["templates"] = configMap.templates
}

// TODO: not sure if it is a good idea to expose the whole env to the frontend
Expand Down
6 changes: 6 additions & 0 deletions src/routes/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Router.all("/session", (req: Request, res: Response) => {

let status = 200
let isAuthenticated = false
let templatesEnabled = true

if (auth.authentication === true) {
isAuthenticated = req.isAuthenticated()
Expand All @@ -25,10 +26,15 @@ Router.all("/session", (req: Request, res: Response) => {
buildPipeline = true
}


templatesEnabled = true

let message = {
"isAuthenticated": isAuthenticated,
"version": process.env.npm_package_version,
"buildPipeline": buildPipeline,
"templatesEnabled": req.app.locals.kubero.getTemplateEnabled(),
//"templatesEnabled": true,
}
res.status(status).send(message)
})
Expand Down
6 changes: 6 additions & 0 deletions src/routes/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,9 @@ Router.get('/config/storageclasses', authMiddleware, async function (req: Reques
// #swagger.summary = 'Get the available storageclasses'
res.send(await req.app.locals.kubero.getStorageglasses());
});

Router.get('/config/catalogs', authMiddleware, async function (req: Request, res: Response) {
// #swagger.tags = ['UI']
// #swagger.summary = 'Get a list of available catalogs'
res.send(await req.app.locals.kubero.getTemplateConfig());
});
38 changes: 36 additions & 2 deletions src/routes/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const auth = new Auth();
auth.init();
export const authMiddleware = auth.getAuthMiddleware();
export const bearerMiddleware = auth.getBearerMiddleware();

/*
// load all services from github repo
Router.get('/services', authMiddleware, async function (req: Request, res: Response) {
// #swagger.tags = ['UI']
Expand All @@ -22,6 +22,7 @@ Router.get('/services', authMiddleware, async function (req: Request, res: Respo
Router.get('/services/:name', authMiddleware, async function (req: Request, res: Response) {
// #swagger.tags = ['UI']
// #swagger.summary = 'Get a specific service'
// #deprecated = true // since v1.11.0
const serviceName = req.params.name.replace(/[^\w.-]+/g, '');
Expand All @@ -35,4 +36,37 @@ Router.get('/services/:name', authMiddleware, async function (req: Request, res:
const ret = YAML.parse(service.data);
res.send(ret.spec);
}
});
});
*/

// load a specific service from github repo
Router.get('/templates/:catalogId/:template', authMiddleware, async function (req: Request, res: Response) {
// #swagger.tags = ['UI']
// #swagger.summary = 'Get a specific template'

const templateName = req.params.template.replace(/[^\w.-]+/g, '');
const templateBasePath = await req.app.locals.kubero.getTemplateBasePath(parseInt(req.params.catalogId));

const template = await axios.get(templateBasePath + templateName + '/app.yaml')
.catch((err) => {
res
.status(500)
.send(err);
});
if (template) {
const ret = YAML.parse(template.data);
res.send(ret.spec);
}
});

// load a specific service from github repo
Router.get('/templates/:catalogId', authMiddleware, async function (req: Request, res: Response) {
// #swagger.tags = ['UI']
// #swagger.summary = 'Get a specific template'

const templateBasePath = await req.app.locals.kubero.getTemplateBasePath(parseInt(req.params.catalogId));


axios.get(templateBasePath + '/index.yaml')
});

14 changes: 14 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,20 @@ export interface IBuildpack {
export interface IKuberoConfig {
podSizeList: IPodSize[];
buildpacks: IBuildpack[];
templates: { // introduced v1.11.0
enabled: boolean;
catalogs: [
{
name: string;
description: string;
templateBasePath: string;
index: {
url: string;
format: string;
}
}
]
}
kubero: {
namespace?: string; // deprecated v1.9.0
readonly: boolean;
Expand Down

0 comments on commit e9d069e

Please sign in to comment.