@@ -86,6 +86,8 @@ let localStorageKey = 'wsk.apihost',
8686 localStorageKeyIgnoreCerts = 'wsk.apihost.ignoreCerts' ,
8787 apiHost = process . env . __OW_API_HOST || wskprops . APIHOST || localStorage . getItem ( localStorageKey ) || 'https://openwhisk.ng.bluemix.net' ,
8888 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 ,
8991 ow
9092
9193let userRequestedIgnoreCerts = localStorage . getItem ( localStorageKeyIgnoreCerts ) !== undefined
@@ -94,16 +96,20 @@ let ignoreCerts = apiHost => userRequestedIgnoreCerts || apiHost.indexOf('localh
9496/** these are the module's exported functions */
9597let self = { }
9698
97- debug ( 'initOW' )
9899const initOW = ( ) => {
99- ow = self . ow = openwhisk ( {
100+ const owConfig = {
100101 apihost : apiHost ,
101102 api_key : auth ,
103+ apigw_token, apigw_space_guid,
102104 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' )
104111}
105112if ( apiHost && auth ) initOW ( )
106- debug ( 'initOW done' )
107113
108114/** is a given entity type CRUDable? i.e. does it have get and update operations, and parameters and annotations properties? */
109115const isCRUDable = {
@@ -514,6 +520,162 @@ const standardViewModes = (defaultMode, fn) => {
514520 }
515521}
516522
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+
517679const actionSpecificModes = [ { mode : 'code' , defaultMode : true } , { mode : 'limits' } ]
518680specials . actions = {
519681 get : standardViewModes ( actionSpecificModes ) ,
@@ -845,6 +1007,10 @@ const executor = (_entity, _verb, verbSynonym, commandTree, preflight) => (block
8451007 }
8461008 }
8471009
1010+ // pre and post-process the output of openwhisk; default is do nothing
1011+ let postprocess = x => x
1012+ let preprocess = x => x
1013+
8481014 if ( specials [ entity ] && specials [ entity ] [ verb ] ) {
8491015 const res = specials [ entity ] [ verb ] ( options , argv . slice ( restIndex ) , verb )
8501016 if ( res && res . verb ) {
@@ -857,6 +1023,16 @@ const executor = (_entity, _verb, verbSynonym, commandTree, preflight) => (block
8571023 if ( res && res . options ) {
8581024 options = res . options
8591025 }
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+ }
8601036 }
8611037 // process the entity-naming "nominal" argument
8621038 //if (!(syn_options && syn_options.noNominalArgument) && argv_without_options[idx]) {
@@ -932,7 +1108,9 @@ const executor = (_entity, _verb, verbSynonym, commandTree, preflight) => (block
9321108 } )
9331109
9341110 return preflight ( verb , options )
1111+ . then ( preprocess )
9351112 . then ( options => ow [ entity ] [ verb ] ( options ) )
1113+ . then ( postprocess )
9361114 . then ( response => {
9371115 // amend the history entry with a selected subset of the response
9381116 if ( execOptions && execOptions . history ) {
0 commit comments