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

feat(server): add k8s patch method to k8s service #430

Merged
merged 1 commit into from
Nov 25, 2022
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
11 changes: 11 additions & 0 deletions server/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"@prisma/client": "^4.6.1",
"casdoor-nodejs-sdk": "^1.3.0",
"dotenv": "^16.0.3",
"fast-json-patch": "^3.1.1",
"nanoid": "^3.3.4",
"passport-jwt": "^4.0.0",
"passport-local": "^1.0.0",
Expand Down
12 changes: 6 additions & 6 deletions server/src/applications/applications.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ export class ApplicationsService {

async findAll(labelSelector?: string): Promise<ApplicationList> {
const res = await this.k8sClient.customObjectApi.listClusterCustomObject(
Application.Group,
Application.Version,
Application.PluralName,
Application.GVK.group,
Application.GVK.version,
Application.GVK.plural,
undefined,
undefined,
undefined,
Expand All @@ -95,10 +95,10 @@ export class ApplicationsService {
try {
const appRes =
await this.k8sClient.customObjectApi.getNamespacedCustomObject(
Application.Group,
Application.Version,
Application.GVK.group,
Application.GVK.version,
namespace,
Application.PluralName,
Application.GVK.plural,
name,
)
return appRes.body as Application
Expand Down
25 changes: 14 additions & 11 deletions server/src/applications/entities/application.entity.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { KubernetesObject } from '@kubernetes/client-node'
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
import { ResourceLabels } from 'src/constants'
import { Condition, ObjectMeta } from '../../core/kubernetes.interface'
import { ResourceLabels } from '../../constants'
import {
Condition,
GroupVersionKind,
ObjectMeta,
} from '../../core/kubernetes.interface'
import { BundleSpec } from './bundle.entity'
import { RuntimeSpec } from './runtime.entity'

Expand Down Expand Up @@ -84,17 +88,16 @@ export class Application implements KubernetesObject {
@ApiPropertyOptional()
status?: ApplicationStatus

static readonly Group = 'application.laf.dev'
static readonly Version = 'v1'
static readonly PluralName = 'applications'
static readonly Kind = 'Application'
static get GroupVersion() {
return `${this.Group}/${this.Version}`
}
static readonly GVK = new GroupVersionKind(
'application.laf.dev',
'v1',
'Application',
'applications',
)

constructor(name: string, namespace: string) {
this.apiVersion = Application.GroupVersion
this.kind = Application.Kind
this.apiVersion = Application.GVK.apiVersion
this.kind = Application.GVK.kind
this.metadata = new ObjectMeta(name, namespace)
this.metadata.labels = {}
this.spec = new ApplicationSpec()
Expand Down
2 changes: 1 addition & 1 deletion server/src/applications/entities/bundle.entity.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { KubernetesObject } from '@kubernetes/client-node'
import { ApiProperty } from '@nestjs/swagger'
import { ObjectMeta } from 'src/core/kubernetes.interface'
import { ObjectMeta } from '../../core/kubernetes.interface'

export class BundleSpec {
@ApiProperty()
Expand Down
2 changes: 1 addition & 1 deletion server/src/applications/entities/runtime.entity.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { KubernetesObject } from '@kubernetes/client-node'
import { ApiProperty } from '@nestjs/swagger'
import { ObjectMeta } from 'src/core/kubernetes.interface'
import { ObjectMeta } from '../../core/kubernetes.interface'

export class RuntimeVersion {
@ApiProperty()
Expand Down
16 changes: 8 additions & 8 deletions server/src/buckets/buckets.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Injectable, Logger } from '@nestjs/common'
import { GetApplicationNamespaceById } from 'src/common/getter'
import { KubernetesService } from 'src/core/kubernetes.service'
import { GetApplicationNamespaceById } from '../common/getter'
import { KubernetesService } from '../core/kubernetes.service'
import { CreateBucketDto } from './dto/create-bucket.dto'
import { UpdateBucketDto } from './dto/update-bucket.dto'
import { Bucket, BucketList } from './entities/bucket.entity'
Expand Down Expand Up @@ -36,10 +36,10 @@ export class BucketsService {
async findAll(appid: string, labelSelector?: string) {
const namespace = GetApplicationNamespaceById(appid)
const res = await this.k8sClient.customObjectApi.listNamespacedCustomObject(
Bucket.Group,
Bucket.Version,
Bucket.GVK.group,
Bucket.GVK.version,
namespace,
Bucket.PluralName,
Bucket.GVK.plural,
undefined,
undefined,
undefined,
Expand All @@ -54,10 +54,10 @@ export class BucketsService {
try {
const res =
await this.k8sClient.customObjectApi.getNamespacedCustomObject(
Bucket.Group,
Bucket.Version,
Bucket.GVK.group,
Bucket.GVK.version,
namespace,
Bucket.PluralName,
Bucket.GVK.plural,
name,
)
return res.body as Bucket
Expand Down
25 changes: 14 additions & 11 deletions server/src/buckets/entities/bucket.entity.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { KubernetesObject, V1ObjectMeta } from '@kubernetes/client-node'
import { KubernetesObject } from '@kubernetes/client-node'
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
import { Condition, ObjectMeta } from '../../core/kubernetes.interface'
import {
Condition,
GroupVersionKind,
ObjectMeta,
} from '../../core/kubernetes.interface'

export enum BucketPolicy {
Private = 'private',
Expand Down Expand Up @@ -68,17 +72,16 @@ export class Bucket implements KubernetesObject {
@ApiPropertyOptional()
status?: BucketStatus

static readonly Group = 'oss.laf.dev'
static readonly Version = 'v1'
static readonly PluralName = 'buckets'
static readonly Kind = 'Bucket'
static get GroupVersion() {
return `${this.Group}/${this.Version}`
}
static readonly GVK = new GroupVersionKind(
'oss.laf.dev',
'v1',
'Bucket',
'buckets',
)

constructor(name: string, namespace: string) {
this.apiVersion = Bucket.GroupVersion
this.kind = Bucket.Kind
this.apiVersion = Bucket.GVK.apiVersion
this.kind = Bucket.GVK.kind
this.metadata = new ObjectMeta(name, namespace)
this.spec = new BucketSpec()
}
Expand Down
42 changes: 41 additions & 1 deletion server/src/core/kubernetes.interface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { V1ObjectMeta } from '@kubernetes/client-node'
import { KubernetesObject, V1ObjectMeta } from '@kubernetes/client-node'
import { ApiProperty } from '@nestjs/swagger'
import * as assert from 'node:assert'

export enum ConditionStatus {
ConditionTrue = 'True',
Expand Down Expand Up @@ -52,3 +53,42 @@ export class Condition {
@ApiProperty()
message?: string
}

export class GroupVersionKind {
group: string

version: string

kind: string

plural: string

constructor(group: string, version: string, kind: string, plural?: string) {
assert(group, 'group is required')
assert(version, 'version is required')
assert(kind, 'kind is required')

this.group = group
this.version = version
this.kind = kind
this.plural = plural
if (!plural) {
this.plural = kind.toLowerCase() + 's'
}
}

static fromKubernetesObject(obj: KubernetesObject): GroupVersionKind {
assert(obj.apiVersion, 'apiVersion is required')
assert(obj.kind, 'kind is required')

return new GroupVersionKind(
obj.apiVersion.split('/')[0],
obj.apiVersion.split('/')[1],
obj.kind,
)
}

get apiVersion(): string {
return `${this.group}/${this.version}`
}
}
29 changes: 29 additions & 0 deletions server/src/core/kubernetes.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { Test, TestingModule } from '@nestjs/testing'
import {
Application,
ApplicationState,
} from '../applications/entities/application.entity'
import { ResourceLabels } from '../constants'
import { KubernetesService } from './kubernetes.service'

Expand Down Expand Up @@ -86,3 +90,28 @@ describe.skip('list custom objects with label', () => {
console.log(res.body)
})
})

describe.skip('patch custom objects', () => {
it('should be able to patch custom objects', async () => {
const name = '1i43zq'
const namespace = name
const res = await service.customObjectApi.getNamespacedCustomObject(
Application.GVK.group,
Application.GVK.version,
namespace,
Application.GVK.plural,
name,
)

const data = res.body as Application
data.spec = {
...data.spec,
state: ApplicationState.ApplicationStateRunning,
}

const res2 = await service.patchCustomObject(data).catch((err) => {
console.log(err)
})
console.log('patched', res2)
})
})
43 changes: 43 additions & 0 deletions server/src/core/kubernetes.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { Injectable } from '@nestjs/common'
import * as k8s from '@kubernetes/client-node'
import { KubernetesObject } from '@kubernetes/client-node'
import { compare } from 'fast-json-patch'
import { GroupVersionKind } from './kubernetes.interface'
import path from 'path'

/**
* Single instance of the Kubernetes API client.
Expand Down Expand Up @@ -113,4 +117,43 @@ export class KubernetesService {
}
return deleted
}

async patchCustomObject(spec: KubernetesObject) {
const client = this.customObjectApi
const gvk = GroupVersionKind.fromKubernetesObject(spec)

// get the current spec
const res = await client.getNamespacedCustomObject(
gvk.group,
gvk.version,
spec.metadata.namespace,
gvk.plural,
spec.metadata.name,
)
const currentSpec = res.body as KubernetesObject

// calculate the patch
const patch = compare(currentSpec, spec)
const options = {
headers: {
'Content-Type': k8s.PatchUtils.PATCH_FORMAT_JSON_PATCH,
},
}

// apply the patch
const response = await client.patchNamespacedCustomObject(
gvk.group,
gvk.version,
spec.metadata.namespace,
gvk.plural,
spec.metadata.name,
patch,
undefined,
undefined,
undefined,
options,
)

return response.body
}
}
19 changes: 9 additions & 10 deletions server/src/functions/entities/function.entity.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { KubernetesListObject, KubernetesObject } from '@kubernetes/client-node'
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
import { ObjectMeta } from 'src/core/kubernetes.interface'
import { GroupVersionKind, ObjectMeta } from '../../core/kubernetes.interface'

export class CloudFunctionSource {
@ApiProperty()
Expand Down Expand Up @@ -62,17 +62,16 @@ export class CloudFunction implements KubernetesObject {
@ApiPropertyOptional()
status?: CloudFunctionStatus

static readonly Group = 'runtime.laf.dev'
static readonly Version = 'v1'
static readonly PluralName = 'functions'
static readonly Kind = 'Function'
static get GroupVersion() {
return `${this.Group}/${this.Version}`
}
static readonly GVK = new GroupVersionKind(
'runtime.laf.dev',
'v1',
'Function',
'functions',
)

constructor(name: string, namespace: string) {
this.apiVersion = CloudFunction.GroupVersion
this.kind = CloudFunction.Kind
this.apiVersion = CloudFunction.GVK.apiVersion
this.kind = CloudFunction.GVK.kind
this.metadata = {
name,
namespace,
Expand Down
Loading