@@ -18,103 +18,90 @@ type Selector = {
1818 fields ?: { [ key : string ] : string } ;
1919} ;
2020
21- const PATH_RE = new RegExp (
22- '(?:k8s|search)/(?:(?:ns/([^/]+))|cluster|all-namespaces)(?:/([^/]+)(?:/([^/]+))?)?(/events)?/?$' ,
21+ const pathRE = new RegExp (
22+ '(?<prefix>k8s|search|api-resource)' +
23+ '/((ns/(?<namespace>[^/]+))|cluster|all-namespaces)' +
24+ '(/(?<resource>[^/]+)(/(?<name>([^/]+))(?<events>/events)?)?)?' ,
2325) ;
24- const VERSION_RE = '( v[0-9]+(?:(?: alpha|beta)[0-9]*)?)' ;
25- const CLASS_RE = new RegExp ( `^([^./]+)(?: .${ VERSION_RE } )?(?:.( [^/]*))?$` ) ;
26+ const versionRE = / (?< version > v [ 0 - 9 ] + ( ( a l p h a | b e t a ) [ 0 - 9 ] * ) ? ) / ;
27+ const classRE = new RegExp ( `^(?<kind> [^./]+)(\\ .${ versionRE . source } )?(.(?<group> [^/]*))?$` ) ;
2628
2729export class K8sDomain extends Domain {
2830 constructor ( ) {
2931 super ( 'k8s' ) ;
3032 }
3133
3234 class ( name : string ) : Class {
33- const m = name . match ( CLASS_RE ) ;
34- if ( ! m ) throw this . badClass ( name ) ;
35- const model = findGVK ( m [ 3 ] , m [ 2 ] , m [ 1 ] ) ;
35+ const model = this . classModel ( name ) ;
3636 if ( ! model ) throw this . badClass ( name ) ;
3737 return this . modelClass ( model ) ;
3838 }
3939
40+ private classModel ( name : string ) : Model | undefined {
41+ const g = name . match ( classRE ) ?. groups ;
42+ if ( ! g ) return ;
43+ return findGVK ( g . group , g . version , g . kind ) ;
44+ }
45+
4046 private modelClass ( model : Model ) : Class {
4147 const version = model . apiVersion || 'v1' ;
4248 const dotGroup = model . apiGroup ? `.${ model . apiGroup } ` : '' ;
4349 return new Class ( this . name , `${ model . kind } .${ version } ${ dotGroup } ` ) ;
4450 }
4551
4652 linkToQuery ( link : URIRef ) : Query {
47- const m = link . pathname . match ( PATH_RE ) ;
48- if ( ! m ) throw this . badLink ( link ) ;
49- const [ , namespace , resource , name , events ] = m ;
50- const model = findResource ( resource , link . searchParams . get ( 'kind' ) ) ;
51- if ( ! model || ! model . kind ) throw this . badLink ( link , `unknown resource "${ resource } "` ) ;
52- if ( events ) {
53- const event = eventModel ( ) ;
54- const about = eventAboutField ( event ) ;
55- const apiVersion = `${ model . apiGroup ? `${ model . apiGroup } /` : '' } ${ model . apiVersion || 'v1' } ` ;
56- const data = {
57- fields : {
58- [ `${ about } .namespace` ] : namespace ,
59- [ `${ about } .name` ] : name ,
60- [ `${ about } .apiVersion` ] : apiVersion ,
61- [ `${ about } .kind` ] : model . kind ,
62- } ,
63- } ;
64- return this . modelClass ( event ) . query ( JSON . stringify ( data ) ) ;
65- } else {
66- const data = {
67- namespace : namespace ,
68- name : name ,
69- labels : K8sDomain . parseSelector ( link . searchParams . get ( 'labels' ) ) || undefined ,
70- } ;
71- return this . modelClass ( model ) . query ( JSON . stringify ( data ) ) ;
72- }
53+ const g = link . pathname . match ( pathRE ) ?. groups ;
54+ if ( ! g ) throw this . badLink ( link ) ;
55+ const resource = g . resource || link . searchParams . get ( 'kind' ) ;
56+ const model = findResource ( resource ) ;
57+ if ( ! model ?. kind ) throw this . badLink ( link , `unknown resource: ${ resource } ` ) ;
58+ const name = g . prefix === 'api-resource' ? undefined : g . name ;
59+ const data = {
60+ namespace : g . namespace ,
61+ name,
62+ labels : K8sDomain . parseSelector ( link . searchParams . get ( 'labels' ) ) || undefined ,
63+ } ;
64+ return this . modelClass ( model ) . query ( JSON . stringify ( data ) ) ;
7365 }
7466
7567 // NOTE: k8s queries don't support query constraints, so neither do console k8s URIs.
7668 queryToLink ( query : Query ) : URIRef {
77- let data : Selector ;
69+ let selector : Selector ;
7870 try {
79- data = JSON . parse ( query . selector ) as Selector ;
71+ selector = JSON . parse ( query . selector ) as Selector ;
8072 } catch ( e ) {
8173 throw this . badQuery ( query , e . message ) ;
8274 }
83- const m = query . class . name . match ( / ^ ( [ ^ . ] + ) (?: \. ( [ ^ . ] * ) (?: \. ( .* ) ) ? ) ? $ / ) ?? [ ] ;
84- if ( ! m ) throw this . badQuery ( query , 'incorrect format' ) ;
85- let model = findGVK ( m [ 3 ] , m [ 2 ] , m [ 1 ] ) ;
86- if ( ! model ) throw this . badQuery ( query , 'no matching resource' ) ;
87- let namespace = data . namespace ;
88- let name = data . name ;
75+ let model = this . classModel ( query . class . name ) ;
76+ if ( ! model ) throw this . badQuery ( query , `no resources match class` ) ;
77+ let namespace = selector . namespace ;
78+ let name = selector . name ;
8979 let events = '' ;
9080 if ( isEvent ( model ) ) {
91- // Special treatment for event objects: focus on the involved object, not the event .
92- events = '/events' ;
81+ // Special case for events, use involved object with '/events' modifier .
82+ const eventClass = this . modelClass ( model ) ;
9383 const about = eventAboutField ( model ) ;
94- const [ group , version ] = parseAPIVersion ( data . fields [ `${ about } .apiVersion` ] ) ;
95- const kind = data . fields [ `${ about } .kind` ] ;
84+ const [ group , version ] = parseAPIVersion ( selector . fields [ `${ about } .apiVersion` ] ) ;
85+ const kind = selector . fields [ `${ about } .kind` ] ;
9686 model = findGVK ( group , version , kind ) ;
97- if ( ! model )
98- throw this . badQuery (
99- query ,
100- `no resource for group=${ group } version=${ version } kind=${ kind } ` ,
101- ) ;
102- namespace = data . fields [ `${ about } .namespace` ] || '' ;
103- name = data . fields [ `${ about } .name` ] || '' ;
87+ if ( ! model ) throw this . badQuery ( query , `no resource matching ${ eventClass } .${ about } ` ) ;
88+ namespace = selector . fields [ `${ about } .namespace` ] || '' ;
89+ name = selector . fields [ `${ about } .name` ] || '' ;
90+ events = '/events' ;
10491 }
10592 // Prepare parts of the URL
10693 const nsPath = namespace ? `ns/${ namespace } ` : 'all-namespaces' ;
10794 const kind = `${ model . apiGroup || 'core' } ~${ model . apiVersion } ~${ model . kind } ` ;
10895 const params = {
109- labels : keyValueList ( data . labels ) || undefined ,
110- fields : ( ! events && keyValueList ( data . fields ) ) || undefined ,
96+ labels : keyValueList ( selector . labels ) || undefined ,
97+ fields : ( ! events && keyValueList ( selector . fields ) ) || undefined ,
11198 } ;
11299 if ( ! name && ! namespace && ( params . labels || params . fields ) ) {
113100 // Search URL
114101 return new URIRef ( `search/${ nsPath } ` , { ...params , kind } ) ;
115102 } else {
116103 // Specific resource URL
117- return new URIRef ( `k8s/${ nsPath } /${ kind } ${ name ? `/${ name } ` : '' } ${ events } ` , params ) ;
104+ return new URIRef ( `k8s/${ nsPath } /${ kind } ${ name ? `/${ name } ` : '' } ${ events } ` , { ... params } ) ;
118105 }
119106 }
120107
@@ -146,54 +133,42 @@ function isEvent(m: Model): boolean {
146133 ( ! m . apiGroup || m . apiGroup === EVENT . group )
147134 ) ;
148135}
149- function eventModel ( ) : Model {
150- return findGVK ( EVENT . group , EVENT . version , EVENT . kind ) || findGVK ( '' , EVENT . version , EVENT . kind ) ;
151- }
152136
153137function eventAboutField ( m : Model ) : string {
154138 return m ?. apiGroup === EVENT . group ? 'regarding' : 'involvedObject' ;
155139}
156140
157- // Find the cached resource model for a GVK. Same defaulting rules as korrel8r..
141+ // Find the cached resource model for a GVK. Same defaulting rules as korrel8r.
158142function findGVK ( group : string , version : string , kind : string ) : Model {
159143 version = version || 'v1' ;
160144 group = group || '' ;
161- return getCachedResources ( ) ?. models . find ( ( m : Model ) => {
162- return (
145+ return getCachedResources ( ) ?. models . find (
146+ ( m : Model ) =>
163147 m . kind === kind &&
164148 m . verbs . includes ( 'watch' ) &&
165149 m . apiVersion === version &&
166- ( m . apiGroup || '' ) === group
167- ) ;
168- } ) ;
150+ ( m . apiGroup || '' ) === group ,
151+ ) ;
169152}
170153
171- // Return a model for the resource (path) or undefined
172- function findResource ( resource : string , kind : string ) : Model {
154+ // Return a model for the resource, can be G~V~K or path. Return undefined if not found.
155+ function findResource ( resource : string ) : Model {
156+ if ( ! resource ) return ;
157+ // Try as a G~V~K string.
158+ const [ g , v , k ] = resource . split ( '~' ) ;
159+ if ( k ) return findGVK ( g === 'core' ? '' : g , v , k ) ;
160+ // Try as a resource path
173161 if ( resource === 'projects' ) resource = 'namespaces' ; // Alias
174- if ( resource && ! resource . includes ( '~' ) ) {
175- // Try the resource as a straight resource name
176- const model = getCachedResources ( ) ?. models . find (
177- ( m : Model ) => m . path === resource && m . verbs . includes ( 'watch' ) ,
178- ) ;
179- if ( model ) return model ;
180- }
181- // Either kind or resource may be a g~v~k string.
182- const parts = ( resource || kind ) ?. split ( '~' ) ;
183- if ( ! parts || parts . length !== 3 || ! parts [ 2 ] ) return ;
184- return findGVK (
185- ! parts [ 0 ] || parts [ 0 ] === 'core' ? undefined : parts [ 0 ] ,
186- parts [ 1 ] || undefined ,
187- parts [ 2 ] ,
162+ return getCachedResources ( ) ?. models ?. find (
163+ ( m : Model ) => m . path === resource && m . verbs . includes ( 'watch' ) ,
188164 ) ;
189165}
190166
191- const VERSION_ONLY_RE = new RegExp ( `^${ VERSION_RE } $` ) ;
192167function parseAPIVersion ( apiVersion : string ) : [ group : string , version : string ] | undefined {
193- const gv = apiVersion . split ( '/' ) || [ ] ;
168+ const gv = apiVersion ? .split ( '/' ) || [ ] ;
194169 switch ( gv . length ) {
195170 case 1 :
196- return gv [ 0 ] . match ( VERSION_ONLY_RE ) ? [ '' , gv [ 0 ] ] : [ gv [ 0 ] , '' ] ;
171+ return gv [ 0 ] . match ( versionRE ) ? [ '' , gv [ 0 ] ] : [ gv [ 0 ] , '' ] ;
197172 case 2 :
198173 return [ gv [ 0 ] , gv [ 1 ] ] ;
199174 default :
0 commit comments