Skip to content

Commit

Permalink
feat: support devbox token to fetch details API
Browse files Browse the repository at this point in the history
  • Loading branch information
zjy365 committed Oct 15, 2024
1 parent ed4e7b1 commit ce33985
Show file tree
Hide file tree
Showing 8 changed files with 345 additions and 5 deletions.
10 changes: 8 additions & 2 deletions frontend/providers/devbox/app/api/getSSHConnectionInfo/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { NextRequest } from 'next/server'

import { KBRuntimeType } from '@/types/k8s'
import { runtimeNamespace } from '@/stores/static'
import { authSession } from '@/services/backend/auth'
import { authSession, generateAccessToken } from '@/services/backend/auth'
import { jsonRes } from '@/services/backend/response'
import { getK8s } from '@/services/backend/kubernetes'

Expand All @@ -24,6 +24,12 @@ export async function GET(req: NextRequest) {
const base64PublicKey = response.body.data?.['SEALOS_DEVBOX_PUBLIC_KEY'] as string
const base64PrivateKey = response.body.data?.['SEALOS_DEVBOX_PRIVATE_KEY'] as string

const jwtSecret = Buffer.from(
response.body.data?.['SEALOS_DEVBOX_JWT_SECRET'] as string,
'base64'
).toString('utf-8')
const token = generateAccessToken({ namespace, devboxName }, jwtSecret)

const { body: runtime } = (await k8sCustomObjects.getNamespacedCustomObject(
'devbox.sealos.io',
'v1alpha1',
Expand All @@ -34,7 +40,7 @@ export async function GET(req: NextRequest) {

const userName = runtime.spec.config.user

return jsonRes({ data: { base64PublicKey, base64PrivateKey, userName } })
return jsonRes({ data: { base64PublicKey, base64PrivateKey, userName, token } })
} catch (err: any) {
return jsonRes({
code: 500,
Expand Down
99 changes: 99 additions & 0 deletions frontend/providers/devbox/app/api/v1/getDevboxDetail/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { NextRequest } from 'next/server'

import { devboxKey } from '@/constants/devbox'
import { getPayloadWithoutVerification, verifyToken } from '@/services/backend/auth'
import { getK8s } from '@/services/backend/kubernetes'
import { jsonRes } from '@/services/backend/response'
import { KBDevboxType } from '@/types/k8s'
import { adaptDBListItem, adaptDevboxListItem, adaptIngressListItem } from '@/utils/adapt'
import { KbPgClusterType } from '@/types/cluster'
import { V1Ingress } from '@kubernetes/client-node'

export const dynamic = 'force-dynamic'

export async function GET(req: NextRequest) {
try {
const { payload, token } = getPayloadWithoutVerification(req.headers)
if (!payload || !token) {
return jsonRes({
code: 401,
error: 'Unauthorized'
})
}
const devboxName = payload.devboxName
const namespace = payload.namespace

const { k8sCore, k8sCustomObjects, k8sNetworkingApp } = await getK8s({
useDefaultConfig: true
})

const response = await k8sCore.readNamespacedSecret(devboxName, namespace)

const jwtSecret = Buffer.from(
response.body.data?.['SEALOS_DEVBOX_JWT_SECRET'] as string,
'base64'
).toString('utf-8')

if (!verifyToken(token, jwtSecret)) {
return jsonRes({
code: 401,
error: 'Unauthorized'
})
}

const results = await Promise.allSettled([
k8sCustomObjects.getNamespacedCustomObject(
'devbox.sealos.io',
'v1alpha1',
namespace,
'devboxes',
devboxName
),
k8sCustomObjects.listNamespacedCustomObject(
'apps.kubeblocks.io',
'v1alpha1',
namespace,
'clusters'
),
k8sNetworkingApp.listNamespacedIngress(
namespace,
undefined,
undefined,
undefined,
undefined,
`${devboxKey}=${devboxName}`
)
])

const [devboxResult, clustersResult, ingressesResult] = results

let devbox, clusters, ingresses

if (devboxResult.status === 'fulfilled') {
devbox = adaptDevboxListItem(devboxResult.value.body as KBDevboxType)
}

if (clustersResult.status === 'fulfilled') {
clusters = (clustersResult.value.body as { items: KbPgClusterType[] }).items.map(
adaptDBListItem
)
}

if (ingressesResult.status === 'fulfilled') {
ingresses = ingressesResult.value.body.items.map(adaptIngressListItem)
}

return jsonRes({
data: {
devbox,
clusters,
ingresses
}
})
} catch (err: any) {
return jsonRes({
code: 500,
error: err?.body || err
})
}
}
2 changes: 2 additions & 0 deletions frontend/providers/devbox/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@kubernetes/client-node": "^0.21.0",
"@sealos/ui": "workspace:^",
"@tanstack/react-query": "^4.35.3",
"@types/jsonwebtoken": "^9.0.3",
"axios": "^1.7.3",
"date-fns": "^2.30.0",
"dayjs": "^1.11.10",
Expand All @@ -29,6 +30,7 @@
"ini": "^4.1.3",
"js-cookie": "^3.0.5",
"js-yaml": "^4.1.0",
"jsonwebtoken": "^9.0.2",
"jszip": "^3.10.1",
"lodash": "^4.17.21",
"nanoid": "^4.0.2",
Expand Down
43 changes: 43 additions & 0 deletions frontend/providers/devbox/services/backend/auth.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { decode, JwtPayload, sign, verify } from 'jsonwebtoken'
import { ERROR_ENUM } from '../error'

interface CustomJwtPayload extends JwtPayload {
namespace: string
devboxName: string
}

export const authSession = async (headers: Headers) => {
if (!headers) return Promise.reject(ERROR_ENUM.unAuthorization)

Expand All @@ -14,3 +20,40 @@ export const authSession = async (headers: Headers) => {
return Promise.reject(ERROR_ENUM.unAuthorization)
}
}

export const getPayloadWithoutVerification = <T = CustomJwtPayload>(
headers: Headers
): { payload: T | null; token: string | null } => {
try {
const authHeader = headers.get('authorization')
if (!authHeader) {
return { payload: null, token: null }
}
const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : authHeader
const payload = decode(token) as T
return { payload, token }
} catch (err) {
console.log(err)
return { payload: null, token: null }
}
}

export const verifyToken = async (
token: string,
secret: string
): Promise<CustomJwtPayload | null> => {
try {
const payload = verify(token, secret) as CustomJwtPayload
return payload
} catch (err) {
return null
}
}

export const generateAccessToken = (
payload: {
namespace: string
devboxName: string
},
secret: string
) => sign(payload, secret, { expiresIn: '365d' })
10 changes: 8 additions & 2 deletions frontend/providers/devbox/services/backend/kubernetes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,14 @@ export async function getUserBalance(kc: k8s.KubeConfig) {
return 5
}

export async function getK8s({ kubeconfig }: { kubeconfig: string }) {
const kc = K8sApi(kubeconfig)
export async function getK8s({
kubeconfig,
useDefaultConfig = false
}: {
kubeconfig?: string
useDefaultConfig?: boolean
}) {
const kc = useDefaultConfig ? K8sApiDefault() : K8sApi(kubeconfig)
const kube_user = kc.getCurrentUser()
const client = k8s.KubernetesObjectApi.makeApiClient(kc)

Expand Down
105 changes: 105 additions & 0 deletions frontend/providers/devbox/types/cluster.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
export enum DBTypeEnum {
postgresql = 'postgresql',
mongodb = 'mongodb',
mysql = 'apecloud-mysql',
redis = 'redis',
kafka = 'kafka',
qdrant = 'qdrant',
nebula = 'nebula',
weaviate = 'weaviate',
milvus = 'milvus'
}

export enum DBStatusEnum {
Creating = 'Creating',
Starting = 'Starting',
Stopping = 'Stopping',
Stopped = 'Stopped',
Running = 'Running',
Updating = 'Updating',
SpecUpdating = 'SpecUpdating',
Rebooting = 'Rebooting',
Upgrade = 'Upgrade',
VerticalScaling = 'VerticalScaling',
VolumeExpanding = 'VolumeExpanding',
Failed = 'Failed',
UnKnow = 'UnKnow',
Deleting = 'Deleting'
}

export type KbPgClusterType = {
apiVersion: 'apps.kubeblocks.io/v1alpha1'
kind: 'Cluster'
metadata: {
annotations: Record<string, string>
creationTimestamp: Date
labels: {
'clusterdefinition.kubeblocks.io/name': `${DBTypeEnum}`
'clusterversion.kubeblocks.io/name': string
'sealos-db-provider/postgresql': string
[key: string]: string
}
name: string
namespace: string
uid: string
}
spec: KubeBlockClusterSpec
status?: KubeBlockClusterStatus
}

export interface KubeBlockClusterSpec {
clusterDefinitionRef: `${DBTypeEnum}`
clusterVersionRef: string
terminationPolicy: string
componentSpecs: {
componentDefRef: `${DBTypeEnum}`
name: `${DBTypeEnum}`
replicas: number
resources: {
limits: {
cpu: string
memory: string
}
requests: {
cpu: string
memory: string
}
}
volumeClaimTemplates: {
name: 'data'
spec: {
accessModes: ['ReadWriteOnce']
resources: {
requests: {
storage: string
}
}
}
}[]
}[]
backup: {
enabled: boolean
cronExpression: string
method: string
pitrEnabled: boolean
repoName: string
retentionPeriod: string
}
}
export interface KubeBlockClusterStatus {
clusterDefGeneration: number
components: object
conditions: k8s.V1Condition[]
observedGeneration: number
phase: `${DBStatusEnum}`
}

export interface DBListItemType {
id: string
name: string
dbType: string
createTime: string
cpu: number
memory: number
storage: string
}
29 changes: 29 additions & 0 deletions frontend/providers/devbox/types/ingress.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export interface IngressListItemType {
metadata: {
name: string
namespace: string
creationTimestamp?: Date
labels?: { [key: string]: string }
}
spec: {
rules: {
host: string
http: {
paths: {
path: string
pathType: string
backend: {
service: {
name: string
port: number
}
}
}[]
}
}[]
tls?: {
hosts: string[]
secretName: string
}[]
}
}
Loading

0 comments on commit ce33985

Please sign in to comment.