3434import com .microsoft .azure .common .function .handlers .artifact .MSDeployArtifactHandlerImpl ;
3535import com .microsoft .azure .common .function .handlers .artifact .RunFromBlobArtifactHandlerImpl ;
3636import com .microsoft .azure .common .function .handlers .artifact .RunFromZipArtifactHandlerImpl ;
37+ import com .microsoft .azure .common .function .model .FunctionResource ;
3738import com .microsoft .azure .common .handlers .ArtifactHandler ;
3839import com .microsoft .azure .common .handlers .artifact .ArtifactHandlerBase ;
3940import com .microsoft .azure .common .handlers .artifact .FTPArtifactHandlerImpl ;
4041import com .microsoft .azure .common .handlers .artifact .ZIPArtifactHandlerImpl ;
4142import com .microsoft .azure .common .utils .AppServiceUtils ;
43+ import com .microsoft .azure .functions .annotation .AuthorizationLevel ;
4244import com .microsoft .azure .management .appservice .FunctionApp ;
4345import com .microsoft .azure .management .appservice .FunctionApp .Update ;
4446import com .microsoft .intellij .runner .functions .library .IAppServiceContext ;
4547import com .microsoft .intellij .runner .functions .library .IPrompter ;
48+ import org .apache .commons .collections4 .CollectionUtils ;
4649import org .apache .commons .lang3 .StringUtils ;
4750
51+ import java .io .IOException ;
52+ import java .util .List ;
4853import java .util .Map ;
4954import java .util .function .Consumer ;
55+ import java .util .stream .Collectors ;
5056
5157import static com .microsoft .azure .common .appservice .DeploymentType .*;
5258
5359/**
54- * Deploy artifacts to target Azure Functions in Azure. If target Azure
55- * Functions doesn't exist, it will be created.
60+ * Deploy artifacts to target Azure Functions in Azure.
61+ * Todo: Move the handler to tools-common
5662 */
5763public class DeployFunctionHandler {
64+ private static final int LIST_TRIGGERS_MAX_RETRY = 3 ;
65+ private static final int LIST_TRIGGERS_RETRY_PERIOD_IN_SECONDS = 10 ;
5866 private static final String FUNCTIONS_WORKER_RUNTIME_NAME = "FUNCTIONS_WORKER_RUNTIME" ;
5967 private static final String FUNCTIONS_WORKER_RUNTIME_VALUE = "java" ;
6068 private static final String SET_FUNCTIONS_WORKER_RUNTIME = "Set function worker runtime to java" ;
@@ -70,6 +78,19 @@ public class DeployFunctionHandler {
7078 private static final String FUNCTION_APP_UPDATE_DONE = "Successfully updated the function app %s." ;
7179 private static final String UNKNOW_DEPLOYMENT_TYPE = "The value of <deploymentType> is unknown, supported values are: " +
7280 "ftp, zip, msdeploy, run_from_blob and run_from_zip." ;
81+ private static final String FAILED_TO_LIST_TRIGGERS = "Deployment succeeded, but failed to list http trigger urls." ;
82+ private static final String UNABLE_TO_LIST_NONE_ANONYMOUS_HTTP_TRIGGERS = "Some http trigger urls cannot be displayed " +
83+ "because they are non-anonymous. To access the non-anonymous triggers, "
84+ + "please refer https://aka.ms/azure-functions-key." ;
85+ private static final String HTTP_TRIGGER_URLS = "HTTP Trigger Urls:" ;
86+ private static final String NO_ANONYMOUS_HTTP_TRIGGER = "No anonymous HTTP Triggers found in deployed function app, "
87+ + "skip list triggers." ;
88+ private static final String AUTH_LEVEL = "authLevel" ;
89+ private static final String HTTP_TRIGGER = "httpTrigger" ;
90+ private static final String NO_TRIGGERS_FOUNDED = "No triggers found in deployed function app, " +
91+ "please try recompile the project by `Build` -> `Build Project` and deploy again." ;
92+ private static final String SYNCING_TRIGGERS_AND_FETCH_FUNCTION_INFORMATION = "Syncing triggers and fetching "
93+ + "function information (Attempt %d/%d)..." ;
7394
7495 private static final OperatingSystemEnum DEFAULT_OS = OperatingSystemEnum .Windows ;
7596 private IAppServiceContext ctx ;
@@ -89,6 +110,7 @@ public FunctionApp execute() throws Exception {
89110 prompt (DEPLOY_START );
90111 getArtifactHandler ().publish (deployTarget );
91112 prompt (String .format (DEPLOY_FINISH , ctx .getAppName ()));
113+ listHTTPTriggerUrls ();
92114 return (FunctionApp ) deployTarget .getApp ();
93115 }
94116
@@ -107,6 +129,68 @@ private void configureAppSettings(final Consumer<Map> withAppSettings, final Map
107129 }
108130 }
109131
132+ /**
133+ * List anonymous HTTP Triggers url after deployment
134+ */
135+ private void listHTTPTriggerUrls () {
136+ try {
137+ final List <FunctionResource > triggers = listFunctions ();
138+ final List <FunctionResource > httpFunction =
139+ triggers .stream ()
140+ .filter (function -> function .getTrigger () != null &&
141+ StringUtils .equalsIgnoreCase (function .getTrigger ().getType (), HTTP_TRIGGER ))
142+ .collect (Collectors .toList ());
143+ final List <FunctionResource > anonymousTriggers =
144+ httpFunction .stream ()
145+ .filter (bindingResource -> bindingResource .getTrigger () != null &&
146+ StringUtils .equalsIgnoreCase (
147+ (CharSequence ) bindingResource .getTrigger ().getProperty (AUTH_LEVEL ),
148+ AuthorizationLevel .ANONYMOUS .toString ()))
149+ .collect (Collectors .toList ());
150+ if (CollectionUtils .isEmpty (httpFunction ) || CollectionUtils .isEmpty (anonymousTriggers )) {
151+ prompt (NO_ANONYMOUS_HTTP_TRIGGER );
152+ return ;
153+ }
154+ prompt (HTTP_TRIGGER_URLS );
155+ anonymousTriggers .forEach (trigger -> prompt (String .format ("\t %s : %s" , trigger .getName (), trigger .getTriggerUrl ())));
156+ if (anonymousTriggers .size () < httpFunction .size ()) {
157+ prompt (UNABLE_TO_LIST_NONE_ANONYMOUS_HTTP_TRIGGERS );
158+ }
159+ } catch (InterruptedException | IOException e ) {
160+ prompt (FAILED_TO_LIST_TRIGGERS );
161+ } catch (AzureExecutionException e ) {
162+ prompt (e .getMessage ());
163+ }
164+ }
165+
166+ /**
167+ * Sync triggers and return function list of deployed function app
168+ * Will retry when get empty result, the max retry times is LIST_TRIGGERS_MAX_RETRY
169+ * @return List of functions in deployed function app
170+ * @throws AzureExecutionException Throw if get empty result after LIST_TRIGGERS_MAX_RETRY times retry
171+ * @throws IOException Throw if meet IOException while getting Azure client
172+ * @throws InterruptedException Throw when thread was interrupted while sleeping between retry
173+ */
174+ private List <FunctionResource > listFunctions () throws AzureExecutionException , InterruptedException , IOException {
175+ final FunctionApp functionApp = getFunctionApp ();
176+ for (int i = 0 ; i < LIST_TRIGGERS_MAX_RETRY ; i ++) {
177+ Thread .sleep (LIST_TRIGGERS_RETRY_PERIOD_IN_SECONDS * 1000 );
178+ prompt (String .format (SYNCING_TRIGGERS_AND_FETCH_FUNCTION_INFORMATION , i + 1 , LIST_TRIGGERS_MAX_RETRY ));
179+ functionApp .syncTriggers ();
180+ final List <FunctionResource > triggers =
181+ ctx .getAzureClient ().appServices ().functionApps ()
182+ .listFunctions (ctx .getResourceGroup (),
183+ ctx .getAppName ()).stream ()
184+ .map (envelope -> FunctionResource .parseFunction (envelope ))
185+ .filter (function -> function != null )
186+ .collect (Collectors .toList ());
187+ if (CollectionUtils .isNotEmpty (triggers )) {
188+ return triggers ;
189+ }
190+ }
191+ throw new AzureExecutionException (NO_TRIGGERS_FOUNDED );
192+ }
193+
110194 private OperatingSystemEnum getOsEnum () throws AzureExecutionException {
111195 final RuntimeConfiguration runtime = ctx .getRuntime ();
112196 if (runtime != null && StringUtils .isNotBlank (runtime .getOs ())) {
0 commit comments