Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#171079260, #171678283, #171678912] add scheduled function to update service cache #26

Merged
merged 3 commits into from
Mar 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ they may be customized as needed.

| Variable name | Description | type |
| -------------------------------------- | -------------------------------------------------------------------------------------------------- | ------- |
| StorageConnection | Storage connection string to store computed visible-service.json (retrieved by io-functions-app) | string |
| CUSTOMCONNSTR_COSMOSDB_URI | CosmosDB connection URI | string |
| CUSTOMCONNSTR_COSMOSDB_KEY | CosmoDB connection key | string |
| COSMOSDB_NAME | CosmosDB database name | string |
| LOGOS_URL | The url of the service logos storage | string |
| LogosStorageConnection | The connection string used to connect to Azure Blob Storage containing the service logos | string |
| AssetsStorageConnection | The connection string used to connect to Azure Blob Storage containing the service cache | string |
| SERVICE_PRINCIPAL_CLIENT_ID | The service principal name used to get the token credentials to connect to the APIM | string |
| SERVICE_PRINCIPAL_SECRET | The service principal secret used to get the token credentials to connect to the APIM | string |
| SERVICE_PRINCIPAL_TENANT_ID | The service principal tenant id used to get the token credentials to connect to the APIM | string |
Expand Down
38 changes: 38 additions & 0 deletions UpdateVisibleServicesCache/function.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"disabled": false,
"bindings": [
{
"schedule": "0 0 * * * *",
"name": "updateVisibleServiceCacheTimer",
"type": "timerTrigger",
"direction": "in"
},
{
"name": "starter",
"type": "orchestrationClient",
"direction": "in"
},
{
"name": "visibleServicesBlob",
"type": "blob",
"path": "cached/visible-services.json",
"connection": "StorageConnection",
"direction": "in"
},
{
"name": "visibleServicesCacheBlob",
"type": "blob",
"path": "services/visible-services.json",
"connection": "AssetsStorageConnection",
"direction": "out"
},
{
"name": "visibleServicesByScopeCacheBlob",
"type": "blob",
"path": "services/visible-services-by-scope.json",
"connection": "AssetsStorageConnection",
"direction": "out"
}
],
"scriptFile": "../dist/UpdateVisibleServicesCache/index.js"
}
71 changes: 71 additions & 0 deletions UpdateVisibleServicesCache/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* This time triggered function creates a cache for visible services:
*
* - read the cached visible-service.json (input binding)
* - create a version of services/visible-services.json suitable to be consumed by the mobile APP
* - put the generated JSON into the assets storage (which is reachable behind the CDN)
* - loop on visible services and store services/<serviceid>.json (output binding)
*
* The tuple stored is (serviceId, version, scope).
*
* TODO: delete blobs for services that aren't visible anymore.
*/
import { Context } from "@azure/functions";

import { isLeft } from "fp-ts/lib/Either";
import { StrMap } from "fp-ts/lib/StrMap";
import { VisibleService } from "io-functions-commons/dist/src/models/visible_service";

import * as df from "durable-functions";
import * as t from "io-ts";

export const VisibleServices = t.record(t.string, VisibleService);

async function UpdateVisibleServiceCache(context: Context): Promise<void> {
const errorOrVisibleServices = VisibleServices.decode(
context.bindings.visibleServicesBlob
);

if (isLeft(errorOrVisibleServices)) {
context.log.info(
"UpdateVisibleServiceCache|Cannot decode visible services"
);
return;
}

const visibleServiceJson = errorOrVisibleServices.value;
const visibleServices = new StrMap(visibleServiceJson);

const visibleServicesTuples = visibleServices.mapWithKey((_, v) => ({
id: v.id,
scope: v.serviceMetadata ? v.serviceMetadata.scope : undefined,
version: v.version
}));

// store visible services in the blob
// tslint:disable-next-line: no-object-mutation
context.bindings.visibleServicesCacheBlob = visibleServicesTuples;

const { left: NATIONAL, right: LOCAL } = visibleServices.partition(
s => s.serviceMetadata && s.serviceMetadata.scope === "LOCAL"
);

// store visible services partitioned by scope
// tslint:disable-next-line: no-object-mutation
context.bindings.visibleServicesByScopeCacheBlob = {
LOCAL: LOCAL.map(_ => _.serviceId).reduce([], (p, c) => [...p, c]),
NATIONAL: NATIONAL.map(_ => _.serviceId).reduce([], (p, c) => [...p, c])
};

// start orchestrator to loop on every visible service
// and to store it in a blob
await df
.getClient(context)
.startNew(
"UpdateVisibleServicesCacheOrchestrator",
undefined,
visibleServiceJson
);
}

export { UpdateVisibleServiceCache as index };
18 changes: 18 additions & 0 deletions UpdateVisibleServicesCacheActivity/function.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"disabled": false,
"bindings": [
{
"name": "visibleServiceJson",
"type": "activityTrigger",
"direction": "in"
},
{
"name": "visibleServiceCacheBlob",
"type": "blob",
"path": "services/{serviceId}.json",
"connection": "AssetsStorageConnection",
"direction": "out"
}
],
"scriptFile": "../dist/UpdateVisibleServicesCacheActivity/index.js"
}
38 changes: 38 additions & 0 deletions UpdateVisibleServicesCacheActivity/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Take service json as input and store the JSON into
* services/<serviceid>.json through output binding.
*/
import { Context } from "@azure/functions";
import { isLeft } from "fp-ts/lib/Either";
import { VisibleService } from "io-functions-commons/dist/src/models/visible_service";
import { toApiServiceMetadata } from "../utils/conversions";

async function UpdateVisibleServiceCacheActivity(
context: Context
): Promise<void> {
const visibleServiceJson = context.bindings.visibleServiceJson;
const errorOrVisibleService = VisibleService.decode(visibleServiceJson);

if (isLeft(errorOrVisibleService)) {
context.log.error(
"UpdateVisibleServiceCacheActivity|Cannot decode visible service JSON"
);
return;
}
const visibleService = errorOrVisibleService.value;

context.log.info(
"UpdateVisibleServiceCacheActivity|SERVICE_ID=",
visibleService.serviceId
);
// we don't want to pollute the table storage
// (where the activity result is saved),
// so we return void from this method and
// use context bindings
// tslint:disable-next-line: no-object-mutation
context.bindings.visibleServiceCacheBlob = toApiServiceMetadata(
visibleService
);
}

export default UpdateVisibleServiceCacheActivity;
12 changes: 12 additions & 0 deletions UpdateVisibleServicesCacheOrchestrator/function.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"disabled": false,
"bindings": [
{
"name": "context",
"type": "orchestrationTrigger",
"direction": "in"
}
],
"scriptFile": "../dist/UpdateVisibleServicesCacheOrchestrator/index.js"
}

35 changes: 35 additions & 0 deletions UpdateVisibleServicesCacheOrchestrator/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Calls an activity function for each visible service
* found into the stored JSON. The activity function will store
* one JSON into the blob storage for each visible service found.
*/

import { IFunctionContext, Task } from "durable-functions/lib/src/classes";

import * as df from "durable-functions";
import { isLeft } from "fp-ts/lib/Either";
import { VisibleServices } from "../UpdateVisibleServicesCache";

const UpdateVisibleServicesCacheOrchestrator = df.orchestrator(function*(
context: IFunctionContext
): IterableIterator<Task> {
const visibleServicesJson = context.df.getInput();
const errorOrVisibleServices = VisibleServices.decode(visibleServicesJson);

if (isLeft(errorOrVisibleServices)) {
context.log.error(
"UpdateVisibleServicesCacheOrchestrator|Error decoding visible services JSON."
);
return;
}
const visibleServices = errorOrVisibleServices.value;

for (const visibleServiceId of Object.keys(visibleServices)) {
yield context.df.callActivity(
"UpdateVisibleServicesCacheActivity",
visibleServices[visibleServiceId]
);
}
});

export default UpdateVisibleServicesCacheOrchestrator;
2 changes: 1 addition & 1 deletion UploadServiceLogo/function.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"type": "blob",
"name": "logo",
"path": "services/{serviceId}.png",
"connection": "LogosStorageConnection",
"connection": "AssetsStorageConnection",
"direction": "out"
}
],
Expand Down
2 changes: 1 addition & 1 deletion env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
AZURE_APIM_HOST=localhost
LOGOS_URL=localhost
LogosStorageConnection=DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;
AssetsStorageConnection=DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;

SERVICE_PRINCIPAL_CLIENT_ID=service-principal-client-id
SERVICE_PRINCIPAL_SECRET=service-principal-secret
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,15 @@
"documentdb": "^1.12.2",
"durable-functions": "^1.2.4",
"express": "^4.15.3",
"fp-ts": "1.12.0",
"fp-ts": "1.17.0",
"io-functions-commons": "^1.12.1",
"io-functions-express": "^0.1.0",
"io-ts": "1.8.5",
"italia-ts-commons": "^5.1.11",
"winston": "^3.2.1"
},
"resolutions": {
"handlebars": "~4.5.3"
"handlebars": "~4.5.3",
"fp-ts": "1.17.0"
}
}
30 changes: 15 additions & 15 deletions utils/conversions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,22 +55,22 @@ export function apiServiceToService(service: ApiService): Service {
}

// Returns an API Service Metadata from an internal Service model
function toApiServiceMetadata(
retrievedService: RetrievedService
export function toApiServiceMetadata(
service: RetrievedService | VisibleService
): ApiServiceMetadata {
return retrievedService.serviceMetadata
return service.serviceMetadata
? {
address: retrievedService.serviceMetadata.address,
app_android: retrievedService.serviceMetadata.appAndroid,
app_ios: retrievedService.serviceMetadata.appIos,
description: retrievedService.serviceMetadata.description,
email: retrievedService.serviceMetadata.email,
pec: retrievedService.serviceMetadata.pec,
phone: retrievedService.serviceMetadata.phone,
privacy_url: retrievedService.serviceMetadata.privacyUrl,
scope: retrievedService.serviceMetadata.scope,
tos_url: retrievedService.serviceMetadata.tosUrl,
web_url: retrievedService.serviceMetadata.webUrl
address: service.serviceMetadata.address,
app_android: service.serviceMetadata.appAndroid,
app_ios: service.serviceMetadata.appIos,
description: service.serviceMetadata.description,
email: service.serviceMetadata.email,
pec: service.serviceMetadata.pec,
phone: service.serviceMetadata.phone,
privacy_url: service.serviceMetadata.privacyUrl,
scope: service.serviceMetadata.scope,
tos_url: service.serviceMetadata.tosUrl,
web_url: service.serviceMetadata.webUrl
}
: undefined;
}
Expand Down Expand Up @@ -111,7 +111,7 @@ export function retrievedServiceToVisibleService(
organizationFiscalCode: retrievedService.organizationFiscalCode,
organizationName: retrievedService.organizationName,
serviceId: retrievedService.serviceId,
serviceMetadata: toApiServiceMetadata(retrievedService),
serviceMetadata: retrievedService.serviceMetadata,
serviceName: retrievedService.serviceName,
version: retrievedService.version
};
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2707,10 +2707,10 @@ forwarded@~0.1.2:
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=

fp-ts@1.12.0, fp-ts@^1.0.0:
version "1.12.0"
resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-1.12.0.tgz#d333310e4ac104cdcb6bea47908e381bb09978e7"
integrity sha512-fWwnAgVlTsV26Ruo9nx+fxNHIm6l1puE1VJ/C0XJ3nRQJJJIgRHYw6sigB3MuNFZL1o4fpGlhwFhcbxHK0RsOA==
fp-ts@1.12.0, fp-ts@1.17.0, fp-ts@^1.0.0:
version "1.17.0"
resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-1.17.0.tgz#289127353ddbb4622ada1920d4ad6643182c1f1f"
integrity sha512-nBq25aCAMbCwVLobUUuM/MZihPKyjn0bCVBf6xMAGriHlf8W8Ze9UhyfLnbmfp0ekFTxMuTfLXrCzpJ34px7PQ==

fragment-cache@^0.2.1:
version "0.2.1"
Expand Down