Skip to content

Commit

Permalink
fix(CRDs): Use versions list for spec paths (#484)
Browse files Browse the repository at this point in the history
Currently, it's only possible to specify one api
version for CRD objects via the deprecated
`version` field when building their spec paths.
This commit adds support for building spec paths
for multiple CRD versions specified in the
`versions` list while maintaining compatibility
with `version`.

Fixes #458
  • Loading branch information
iffyio authored and Silas Boyd-Wickizer committed May 29, 2019
1 parent afc33a9 commit 3c3111c
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 59 deletions.
8 changes: 6 additions & 2 deletions examples/crontabs-crd.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
"kind": "CustomResourceDefinition",
"spec": {
"scope": "Namespaced",
"version": "v1",
"group": "stable.example.com",
"versions": [{
"name": "v1",
"served": true,
"storage": true
}],
"group": "stable.example.com",
"names": {
"shortNames": [
"ct"
Expand Down
8 changes: 6 additions & 2 deletions examples/deploymentnotifier-crd.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
"kind": "CustomResourceDefinition",
"spec": {
"scope": "Namespaced",
"version": "v1",
"group": "kubernetes-client.io",
"versions": [{
"name": "v1",
"served": true,
"storage": true
}],
"group": "kubernetes-client.io",
"names": {
"shortNames": [
"dn"
Expand Down
1 change: 0 additions & 1 deletion examples/vpa/vpa-crd.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
"singular": "verticalpodautoscaler"
},
"scope": "Namespaced",
"version": "v1beta2",
"versions": [{
"name": "v1beta1",
"served": true,
Expand Down
114 changes: 61 additions & 53 deletions lib/swagger-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,71 +60,79 @@ class Root extends Component {

addCustomResourceDefinition (manifest) {
const group = manifest.spec.group
const version = manifest.spec.version
const name = manifest.spec.names.plural
const spec = { paths: {} }
const namespace = manifest.spec.scope === 'Cluster' ? '' : '/namespaces/{namespace}'

//
// Make just enough of Swagger spec to generate some useful endpoints.
//
const templatePath = `/apis/${group}/${version}${namespace}/${name}/{name}`
spec.paths[templatePath] = ['delete', 'get', 'patch', 'put'].reduce((acc, method) => {
acc[method] = { operationId: `${method}Template${name}` }
return acc
}, {})

const resourcePath = `/apis/${group}/${version}${namespace}/${name}`
spec.paths[resourcePath] = ['get', 'post'].reduce((acc, method) => {
acc[method] = { operationId: `${method}${name}` }
return acc
}, {})
//
// Namespaced CRDs get a cluster-level GET endpoint.
// Similar to GET /api/v1/pods.
//
if (manifest.spec.scope === 'Namespaced') {
const clusterPath = `/apis/${group}/${version}/${name}`
spec.paths[clusterPath] = {
get: {
operationId: `getCluster${name}`
const addSpec = (version) => {
const spec = { paths: {} }
//
// Make just enough of Swagger spec to generate some useful endpoints.
//
const templatePath = `/apis/${group}/${version}${namespace}/${name}/{name}`
spec.paths[templatePath] = ['delete', 'get', 'patch', 'put'].reduce((acc, method) => {
acc[method] = { operationId: `${method}Template${name}` }
return acc
}, {})

const resourcePath = `/apis/${group}/${version}${namespace}/${name}`
spec.paths[resourcePath] = ['get', 'post'].reduce((acc, method) => {
acc[method] = { operationId: `${method}${name}` }
return acc
}, {})
//
// Namespaced CRDs get a cluster-level GET endpoint.
// Similar to GET /api/v1/pods.
//
if (manifest.spec.scope === 'Namespaced') {
const clusterPath = `/apis/${group}/${version}/${name}`
spec.paths[clusterPath] = {
get: {
operationId: `getCluster${name}`
}
}
}
}

const watchPaths = {
watchCluster: `/apis/${group}/${version}/watch/${name}`,
watchNamespace: `/apis/${group}/${version}/watch${namespace}/${name}`,
watchResource: `/apis/${group}/${version}/watch${namespace}/${name}/{name}`
}
Object.keys(watchPaths).forEach(operationId => {
const watchPath = watchPaths[operationId]
spec.paths[watchPath] = {
get: {
operationId: `operationId${name}`
const watchPaths = {
watchCluster: `/apis/${group}/${version}/watch/${name}`,
watchNamespace: `/apis/${group}/${version}/watch${namespace}/${name}`,
watchResource: `/apis/${group}/${version}/watch${namespace}/${name}/{name}`
}
Object.keys(watchPaths).forEach(operationId => {
const watchPath = watchPaths[operationId]
spec.paths[watchPath] = {
get: {
operationId: `operationId${name}`
}
}
})

// Add status endpoint - see https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#customresourcesubresourcestatus-v1beta1-apiextensions-k8s-io
if (manifest.spec.subresources && manifest.spec.subresources.status) {
const statusPath = `/apis/${group}/${version}${namespace}/${name}/{name}/status`
spec.paths[statusPath] = ['get', 'put'].reduce((acc, method) => {
acc[method] = { operationId: `${method}Template${name}` }
return acc
}, {})
}
})

// Add status endpoint - see https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#customresourcesubresourcestatus-v1beta1-apiextensions-k8s-io
if (manifest.spec.subresources && manifest.spec.subresources.status) {
const statusPath = `/apis/${group}/${version}${namespace}/${name}/{name}/status`
spec.paths[statusPath] = ['get', 'put'].reduce((acc, method) => {
acc[method] = { operationId: `${method}Template${name}` }
return acc
}, {})
}
// Add scale endpoints - see https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#customresourcesubresourcescale-v1beta1-apiextensions-k8s-io
if (manifest.spec.subresources && manifest.spec.subresources.scale) {
const statusPath = `/apis/${group}/${version}${namespace}/${name}/{name}/scale`
spec.paths[statusPath] = ['get', 'put'].reduce((acc, method) => {
acc[method] = { operationId: `${method}Template${name}` }
return acc
}, {})
}

// Add scale endpoints - see https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#customresourcesubresourcescale-v1beta1-apiextensions-k8s-io
if (manifest.spec.subresources && manifest.spec.subresources.scale) {
const statusPath = `/apis/${group}/${version}${namespace}/${name}/{name}/scale`
spec.paths[statusPath] = ['get', 'put'].reduce((acc, method) => {
acc[method] = { operationId: `${method}Template${name}` }
return acc
}, {})
this._addSpec(spec)
}

this._addSpec(spec)
if (manifest.spec.version) {
addSpec(manifest.spec.version)
} else {
const versions = manifest.spec.versions || []
versions.forEach(version => addSpec(version.name))
}
}
}

Expand Down
81 changes: 80 additions & 1 deletion lib/swagger-client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,85 @@ describe('lib.swagger-client', () => {
})

it('adds functions for Namespaced CustomResourceDefinitions', () => {
const client = new Client({ spec: { paths: {} }, backend: {} })
const versions = [
{
name: 'v1beta1',
served: true,
storage: false
},
{
'name': 'v1beta2',
'served': true,
'storage': true
}
]
const crd = {
spec: {
scope: 'Namespaced',
group: 'stable.example.com',
versions,
names: {
plural: 'foos'
}
}
}
client.addCustomResourceDefinition(crd)
versions.forEach(({ name: version }) => {
expect(client.apis['stable.example.com'][version].foos.get).is.a('function')
expect(client.apis['stable.example.com'][version].namespaces('default').foos.get).is.a('function')
expect(client.apis['stable.example.com'][version].namespaces('default').foos.post).is.a('function')
expect(client.apis['stable.example.com'][version].namespaces('default').foos('blah').get).is.a('function')
expect(client.apis['stable.example.com'][version].namespaces('default').foos('blah').delete).is.a('function')
expect(client.apis['stable.example.com'][version].namespaces('default').foos('blah').get).is.a('function')
expect(client.apis['stable.example.com'][version].namespaces('default').foos('blah').patch).is.a('function')
expect(client.apis['stable.example.com'][version].namespaces('default').foos('blah').put).is.a('function')
expect(client.apis['stable.example.com'][version].watch.foos.getStream).is.a('function')
expect(client.apis['stable.example.com'][version].watch.namespaces('default').foos.getStream).is.a('function')
expect(client.apis['stable.example.com'][version].watch.namespaces('default').foos('blah').getStream).is.a('function')
})
})

it('adds functions for Cluster CustomResourceDefinitions', () => {
const client = new Client({ spec: { paths: {} }, backend: {} })
const versions = [
{
name: 'v1beta1',
served: true,
storage: false
},
{
'name': 'v1beta2',
'served': true,
'storage': true
}
]
const crd = {
spec: {
scope: 'Cluster',
group: 'stable.example.com',
versions,
names: {
plural: 'foos'
}
}
}
client.addCustomResourceDefinition(crd)
versions.forEach(({ name: version }) => {
expect(client.apis['stable.example.com'][version].foos.get).is.a('function')
expect(client.apis['stable.example.com'][version].foos.post).is.a('function')
expect(client.apis['stable.example.com'][version].foos('blah').get).is.a('function')
expect(client.apis['stable.example.com'][version].foos('blah').delete).is.a('function')
expect(client.apis['stable.example.com'][version].foos('blah').get).is.a('function')
expect(client.apis['stable.example.com'][version].foos('blah').patch).is.a('function')
expect(client.apis['stable.example.com'][version].foos('blah').put).is.a('function')
expect(client.apis['stable.example.com'][version].watch.foos.getStream).is.a('function')
expect(client.apis['stable.example.com'][version].watch.foos.getStream).is.a('function')
expect(client.apis['stable.example.com'][version].watch.foos('blah').getStream).is.a('function')
})
})

it('supports deprecated "version" field for Namespaced CustomResourceDefinitions', () => {
const client = new Client({ spec: { paths: {} }, backend: {} })
const crd = {
spec: {
Expand All @@ -190,7 +269,7 @@ describe('lib.swagger-client', () => {
expect(client.apis['stable.example.com'].v1.watch.namespaces('default').foos('blah').getStream).is.a('function')
})

it('adds functions for Cluster CustomResourceDefinitions', () => {
it('supports deprecated "version" field for Cluster CustomResourceDefinitions', () => {
const client = new Client({ spec: { paths: {} }, backend: {} })
const crd = {
spec: {
Expand Down

0 comments on commit 3c3111c

Please sign in to comment.