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

Feature / Add template catalog and make it configurable #211

Merged
merged 5 commits into from
Oct 9, 2023
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
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) {

Check failure

Code scanning / CodeQL

Missing rate limiting

This route handler performs [authorization](1), but is not rate-limited.
// #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) {

Check failure

Code scanning / CodeQL

Missing rate limiting

This route handler performs [authorization](1), but is not rate-limited.
// #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) {

Check failure

Code scanning / CodeQL

Missing rate limiting

This route handler performs [authorization](1), but is not rate-limited.
// #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