@@ -86,6 +86,8 @@ let localStorageKey = 'wsk.apihost',
86
86
localStorageKeyIgnoreCerts = 'wsk.apihost.ignoreCerts' ,
87
87
apiHost = process . env . __OW_API_HOST || wskprops . APIHOST || localStorage . getItem ( localStorageKey ) || 'https://openwhisk.ng.bluemix.net' ,
88
88
auth = process . env . __OW_API_KEY || wskprops . AUTH ,
89
+ apigw_token = process . env . __OW_APIGW_TOKEN || wskprops . APIGW_ACCESS_TOKEN || 'localhostNeedsSomething' , // localhost needs some non-empty string
90
+ apigw_space_guid = process . env . __OW_APIGW_SPACE_GUID || wskprops . APIGW_SPACE_GUID ,
89
91
ow
90
92
91
93
let userRequestedIgnoreCerts = localStorage . getItem ( localStorageKeyIgnoreCerts ) !== undefined
@@ -94,16 +96,20 @@ let ignoreCerts = apiHost => userRequestedIgnoreCerts || apiHost.indexOf('localh
94
96
/** these are the module's exported functions */
95
97
let self = { }
96
98
97
- debug ( 'initOW' )
98
99
const initOW = ( ) => {
99
- ow = self . ow = openwhisk ( {
100
+ const owConfig = {
100
101
apihost : apiHost ,
101
102
api_key : auth ,
103
+ apigw_token, apigw_space_guid,
102
104
ignore_certs : ignoreCerts ( apiHost )
103
- } )
105
+ }
106
+ debug ( 'initOW' , owConfig )
107
+ ow = self . ow = openwhisk ( owConfig )
108
+ ow . api = ow . routes
109
+ delete ow . routes
110
+ debug ( 'initOW done' )
104
111
}
105
112
if ( apiHost && auth ) initOW ( )
106
- debug ( 'initOW done' )
107
113
108
114
/** is a given entity type CRUDable? i.e. does it have get and update operations, and parameters and annotations properties? */
109
115
const isCRUDable = {
@@ -514,6 +520,162 @@ const standardViewModes = (defaultMode, fn) => {
514
520
}
515
521
}
516
522
523
+ /** flatten an array of arrays */
524
+ const flatten = arrays => [ ] . concat . apply ( [ ] , arrays )
525
+
526
+ /** api gateway actions */
527
+ specials . api = {
528
+ get : ( options , argv ) => {
529
+ if ( ! options ) return
530
+ const maybeVerb = argv [ 1 ]
531
+ const split = options . name . split ( '/' )
532
+ let path = options . name
533
+ if ( split . length > 0 ) {
534
+ options . name = `/${ split [ 1 ] } `
535
+ path = `/${ split [ 2 ] } `
536
+ }
537
+ return {
538
+ postprocess : res => {
539
+ // we need to present the user with an entity of some
540
+ // sort; the "api create" api does not return a usual
541
+ // entity, as does the rest of the openwhisk API; so
542
+ // we have to manufacture something reasonable here
543
+ debug ( 'raw output of api get' , res )
544
+ const { apidoc } = res . apis [ 0 ] . value
545
+ const { basePath } = apidoc
546
+ const apipath = apidoc . paths [ path ]
547
+ const verb = maybeVerb || Object . keys ( apipath ) [ 0 ]
548
+ const { action :name , namespace } = apipath [ verb ] [ 'x-openwhisk' ]
549
+ debug ( 'api details' , namespace , name , verb )
550
+
551
+ // our "something reasonable" is the action impl, but
552
+ // decorated with the name of the API and the verb
553
+ return repl . qexec ( `wsk action get "/${ namespace } /${ name } "` )
554
+ . then ( action => Object . assign ( action , {
555
+ name, namespace,
556
+ packageName : `${ verb } ${ basePath } ${ path } `
557
+ } ) )
558
+ }
559
+ }
560
+ } ,
561
+ create : ( options , argv ) => {
562
+ if ( argv && argv . length === 3 ) {
563
+ options . basepath = options . name
564
+ options . relpath = argv [ 0 ]
565
+ options . operation = argv [ 1 ]
566
+ options . action = argv [ 2 ]
567
+ } else if ( argv && argv . length === 2 ) {
568
+ options . relpath = options . name
569
+ options . operation = argv [ 0 ]
570
+ options . action = argv [ 1 ]
571
+ } else if ( options && options [ 'config-file' ] ) {
572
+ //fs.readFileSync(options['config-file'])
573
+ throw new Error ( 'config-file support not yet implemented' )
574
+ }
575
+
576
+ return {
577
+ preprocess : _ => {
578
+ // we need to confirm that the action is web-exported
579
+
580
+ // this is the desired action impl for the api
581
+ const name = argv [ argv . length - 1 ]
582
+ debug ( 'fetching action' , name )
583
+
584
+ return ow . actions . get ( owOpts ( { name } ) )
585
+ . then ( action => {
586
+ const isWebExported = action . annotations . find ( ( { key} ) => key === 'web-export' )
587
+ if ( ! isWebExported ) {
588
+ const error = new Error ( `Action '${ name } ' is not a web action. Issue 'wsk action update "${ name } " --web true' to convert the action to a web action.` )
589
+ error . code = 412 // precondition failed
590
+ throw error
591
+ }
592
+ } )
593
+ . then ( ( ) => _ ) // on success, return whatever preprocess was given as input
594
+ . catch ( err => {
595
+ if ( err . statusCode === 404 ) {
596
+ const error = new Error ( `Unable to get action '${ name } ': The requested resource does not exist.` )
597
+ error . code = 404 // not found
598
+ throw error
599
+ } else {
600
+ throw err
601
+ }
602
+ } )
603
+ } ,
604
+ postprocess : ( { apidoc} ) => {
605
+ const { basePath } = apidoc
606
+ const path = Object . keys ( apidoc . paths ) [ 0 ]
607
+ const api = apidoc . paths [ path ]
608
+ const verb = Object . keys ( api ) [ 0 ]
609
+ const { action :name , namespace} = api [ verb ] [ 'x-openwhisk' ]
610
+
611
+ // manufacture an entity-like object
612
+ return repl . qexec ( `wsk action get "/${ namespace } /${ name } "` )
613
+ . then ( action => Object . assign ( action , {
614
+ name, namespace,
615
+ packageName : `${ verb } ${ basePath } ${ path } `
616
+ } ) )
617
+ }
618
+ }
619
+ } ,
620
+ list : ( ) => {
621
+ return {
622
+ // turn the result into an entity tuple model
623
+ postprocess : res => {
624
+ debug ( 'raw output of api list' , res )
625
+
626
+ // main list for each api
627
+ return flatten ( ( res . apis || [ ] ) . map ( ( { value} ) => {
628
+ // one sublist for each path
629
+ const basePath = value . apidoc . basePath
630
+ const baseUrl = value . gwApiUrl
631
+
632
+ return flatten ( Object . keys ( value . apidoc . paths ) . map ( path => {
633
+ const api = value . apidoc . paths [ path ]
634
+
635
+ // one sub-sublist for each verb of the api
636
+ return Object . keys ( api ) . map ( verb => {
637
+ const { action, namespace } = api [ verb ] [ 'x-openwhisk' ]
638
+ const name = `${ basePath } ${ path } `
639
+ const url = `${ baseUrl } ${ path } `
640
+ const actionFqn = `/${ namespace } /${ action } `
641
+
642
+ // here is the entity for that api/path/verb:
643
+ return {
644
+ name, namespace,
645
+ onclick : ( ) => {
646
+ return repl . pexec ( `wsk api get ${ repl . encodeComponent ( name ) } ${ verb } ` )
647
+ } ,
648
+ attributes : [
649
+ { key : 'verb' , value : verb } ,
650
+ { key : 'action' , value : action , onclick : ( ) => repl . pexec ( `wsk action get ${ repl . encodeComponent ( actionFqn ) } ` ) } ,
651
+ { key : 'url' , value : url , fontawesome : 'fas fa-external-link-square-alt' ,
652
+ css : 'clickable clickable-blatant' , onclick : ( ) => window . open ( url , '_blank' ) } ,
653
+ { key : 'copy' , fontawesome : 'fas fa-clipboard' , css : 'clickable clickable-blatant' ,
654
+ onclick : evt => {
655
+ const target = evt . currentTarget
656
+ require ( 'electron' ) . clipboard . writeText ( url )
657
+
658
+ const svg = target . querySelector ( 'svg' )
659
+ svg . classList . remove ( 'fa-clipboard' )
660
+ svg . classList . add ( 'fa-clipboard-check' )
661
+
662
+ setTimeout ( ( ) => {
663
+ const svg = target . querySelector ( 'svg' )
664
+ svg . classList . remove ( 'fa-clipboard-check' )
665
+ svg . classList . add ( 'fa-clipboard' )
666
+ } , 1500 )
667
+ }
668
+ }
669
+ ]
670
+ }
671
+ } )
672
+ } ) )
673
+ } ) )
674
+ }
675
+ }
676
+ }
677
+ }
678
+
517
679
const actionSpecificModes = [ { mode : 'code' , defaultMode : true } , { mode : 'limits' } ]
518
680
specials . actions = {
519
681
get : standardViewModes ( actionSpecificModes ) ,
@@ -845,6 +1007,10 @@ const executor = (_entity, _verb, verbSynonym, commandTree, preflight) => (block
845
1007
}
846
1008
}
847
1009
1010
+ // pre and post-process the output of openwhisk; default is do nothing
1011
+ let postprocess = x => x
1012
+ let preprocess = x => x
1013
+
848
1014
if ( specials [ entity ] && specials [ entity ] [ verb ] ) {
849
1015
const res = specials [ entity ] [ verb ] ( options , argv . slice ( restIndex ) , verb )
850
1016
if ( res && res . verb ) {
@@ -857,6 +1023,16 @@ const executor = (_entity, _verb, verbSynonym, commandTree, preflight) => (block
857
1023
if ( res && res . options ) {
858
1024
options = res . options
859
1025
}
1026
+
1027
+ if ( res && res . preprocess ) {
1028
+ // postprocess the output of openwhisk
1029
+ preprocess = res . preprocess
1030
+ }
1031
+
1032
+ if ( res && res . postprocess ) {
1033
+ // postprocess the output of openwhisk
1034
+ postprocess = res . postprocess
1035
+ }
860
1036
}
861
1037
// process the entity-naming "nominal" argument
862
1038
//if (!(syn_options && syn_options.noNominalArgument) && argv_without_options[idx]) {
@@ -932,7 +1108,9 @@ const executor = (_entity, _verb, verbSynonym, commandTree, preflight) => (block
932
1108
} )
933
1109
934
1110
return preflight ( verb , options )
1111
+ . then ( preprocess )
935
1112
. then ( options => ow [ entity ] [ verb ] ( options ) )
1113
+ . then ( postprocess )
936
1114
. then ( response => {
937
1115
// amend the history entry with a selected subset of the response
938
1116
if ( execOptions && execOptions . history ) {
0 commit comments