Merged PR 824: AzureFunctions実装3(CSVをストレージアカウントに配置する)

## 概要
[Task3846: AzureFunctions実装3(CSVをストレージアカウントに配置する)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3846)

outputDataを追加
→outputAnalysisLicensesDataに変更(アラートルールなどでログを見るので、何の処理か理解できるように)
blobstorageService.tsに以下を追加
 - uploadFileAnalysisLicensesCSV
(ライセンスCSVを配置する)
- createContainerAnalysisを追加
(コンテナーを作成する)
環境変数の追加

## レビューポイント
- 今回追加されたJP-EASTのストレージアカウントのコンテナーが、第一階層のアカウントのものであるかどうかはソース上は特に意識していないが問題ないでしょうか。

## 動作確認状況
- ローカルで確認(モックでソース上処理が通ることのみ確認のみ)
詳細なテストは別タスクで行う。

## 補足
- 相談、参考資料などがあれば
This commit is contained in:
maruyama.t 2024-03-13 07:54:10 +00:00
parent 83e297cc9b
commit 2b68a9f054
6 changed files with 286 additions and 37 deletions

View File

@ -2,29 +2,52 @@ import {
BlobServiceClient,
StorageSharedKeyCredential,
} from "@azure/storage-blob";
import { IMPORT_USERS_CONTAINER_NAME } from "../constants";
import {
IMPORT_USERS_CONTAINER_NAME,
LICENSE_COUNT_ANALYSIS_CONTAINER_NAME,
} from "../constants";
import { InvocationContext } from "@azure/functions";
export class BlobstorageService {
private readonly blobServiceClient: BlobServiceClient;
private readonly sharedKeyCredential: StorageSharedKeyCredential;
constructor() {
if (
!process.env.STORAGE_ACCOUNT_NAME_IMPORT ||
!process.env.STORAGE_ACCOUNT_KEY_IMPORT ||
!process.env.STORAGE_ACCOUNT_ENDPOINT_IMPORT
) {
throw new Error("Storage account information is missing");
}
this.sharedKeyCredential = new StorageSharedKeyCredential(
process.env.STORAGE_ACCOUNT_NAME_IMPORT,
process.env.STORAGE_ACCOUNT_KEY_IMPORT
);
this.blobServiceClient = new BlobServiceClient(
process.env.STORAGE_ACCOUNT_ENDPOINT_IMPORT,
this.sharedKeyCredential
);
constructor(useAnalysisBlob: boolean = false) {
if (!useAnalysisBlob) {
if (
!process.env.STORAGE_ACCOUNT_NAME_IMPORT ||
!process.env.STORAGE_ACCOUNT_KEY_IMPORT ||
!process.env.STORAGE_ACCOUNT_ENDPOINT_IMPORT
) {
throw new Error("Storage account information is missing");
}
this.sharedKeyCredential = new StorageSharedKeyCredential(
process.env.STORAGE_ACCOUNT_NAME_IMPORT,
process.env.STORAGE_ACCOUNT_KEY_IMPORT
);
this.blobServiceClient = new BlobServiceClient(
process.env.STORAGE_ACCOUNT_ENDPOINT_IMPORT,
this.sharedKeyCredential
);
} else {
if (
!process.env.STORAGE_ACCOUNT_NAME_ANALYSIS ||
!process.env.STORAGE_ACCOUNT_KEY_ANALYSIS ||
!process.env.STORAGE_ACCOUNT_ENDPOINT_ANALYSIS
) {
throw new Error("Storage account information for analysis is missing");
}
this.sharedKeyCredential = new StorageSharedKeyCredential(
process.env.STORAGE_ACCOUNT_NAME_ANALYSIS,
process.env.STORAGE_ACCOUNT_KEY_ANALYSIS
);
this.blobServiceClient = new BlobServiceClient(
process.env.STORAGE_ACCOUNT_ENDPOINT_ANALYSIS,
this.sharedKeyCredential
);
}
}
/**
* Lists blobs
@ -181,4 +204,62 @@ export class BlobstorageService {
context.log(`[OUT] ${this.isFileExists.name}`);
}
}
/**
* uplocad file analysis licenses csv
* @param context
* @param filename
* @param data
* @returns boolean
*/
public async uploadFileAnalysisLicensesCSV(
context: InvocationContext,
filename: string,
data: string
): Promise<boolean> {
context.log(
`[IN] ${this.uploadFileAnalysisLicensesCSV.name} | params: { filename: ${filename} }`
);
try {
const containerClient = this.blobServiceClient.getContainerClient(
LICENSE_COUNT_ANALYSIS_CONTAINER_NAME
);
const { response } = await containerClient.uploadBlockBlob(
filename,
data,
data.length
);
if (response.errorCode) {
context.log(`update failed. response errorCode: ${response.errorCode}`);
return false;
}
return true;
} catch (e) {
context.error(e);
throw e;
} finally {
context.log(`[OUT] ${this.uploadFileAnalysisLicensesCSV.name}`);
}
}
/**
* Create container analysis
* @param context
* @returns container
*/
async createContainerAnalysis(context: InvocationContext): Promise<void> {
context.log(`[IN] ${this.createContainerAnalysis.name}`);
try {
// コンテナ作成
const containerClient = this.blobServiceClient.getContainerClient(
LICENSE_COUNT_ANALYSIS_CONTAINER_NAME
);
await containerClient.create();
} catch (e) {
context.error(e);
throw e;
} finally {
context.log(`[OUT] ${this.createContainerAnalysis.name}`);
}
}
}

View File

@ -390,3 +390,15 @@ export const LICENSE_COUNT_ANALYSIS_ROLE = {
NONE: "None",
UNALLOCATED: "Unallocated",
};
/**
*
* @const {string[]}
*/
export const LICENSE_COUNT_ANALYSIS_FRONT_STRING = "LicenseAggregated";
/**
* CSV用のコンテナー名
* @const {string}
*/
export const LICENSE_COUNT_ANALYSIS_CONTAINER_NAME = "analysis-licenses";

View File

@ -23,9 +23,8 @@ import {
BLOB_STORAGE_REGION_EU,
LICENSE_TYPE,
LICENSE_COUNT_ANALYSIS_HEADER,
LICENSE_COUNT_ANALYSIS_FRONT_STRING,
} from "../constants";
import * as fs from "fs";
import * as path from "path";
import { DateWithDayEndTime } from "../common/types/types";
import { initializeDataSource } from "../database/initializeDataSource";
@ -55,8 +54,11 @@ export async function analysisLicensesProcessing(
baseDataFromDeletedAccounts,
targetMonthYYYYMM
);
// TODO: 後続処理の呼び出しイメージ(別タスクで追加)
// await outputData(context, blobstorageService, outputCsvData);
await outputAnalysisLicensesData(
context,
blobstorageService,
outputCsvData
);
} catch (e) {
context.log("analysisLicensesProcessing failed.");
context.error(e);
@ -336,7 +338,7 @@ export async function analysisLicenses(
dotenv.config({ path: ".env.local", override: true });
try {
const datasource = await initializeDataSource(context);
const blobstorageService = new BlobstorageService();
const blobstorageService = new BlobstorageService(true);
try {
// 現在の日付より、先月の年月をYYYYMM形式で取得
@ -1321,18 +1323,6 @@ export async function transferData(
);
}
}
// outputDataUSをローカルにCSV出力(テスト用、別タスクで消す)
// outputDataUSの配列をCSV形式に変換
/*let csvContentUS = "";
for (let i = 0; i < outputDataUS.length; i++) {
//カンマ区切りの文字列を作成
csvContentUS += outputDataUS[i];
}
// CSVファイルを出力
const filePathUS = path.join(__dirname, "outputDataUS.csv");
fs.writeFileSync(filePathUS, csvContentUS);
*/
return {
outputDataUS: outputDataUS,
outputDataEU: outputDataEU,
@ -1775,3 +1765,79 @@ async function createOutputData(
context.log("[OUT]createOutputData");
}
}
/**
* CSVファイルとして出力する
* @param context
* @param blobstorageService: BlobstorageService,
* @param outputCsvData: outputDataAnalysisLicensesCSV
*/
export async function outputAnalysisLicensesData(
context: InvocationContext,
blobstorageService: BlobstorageService,
outputCsvData: outputDataAnalysisLicensesCSV
): Promise<boolean> {
context.log("[IN]outputAnalysisLicensesData");
try {
let csvContentUS = "";
let csvContentEU = "";
let csvContentAU = "";
// 出力日時を取得
const outputDateTime = new Date().toISOString();
// YYYYMMDDHH24MISS形式に変換
const outputDateTimeYYYYMMDDHH24MISS = outputDateTime.replace(/[-T:]/g, "");
// 出力ファイル名を作成
const outputFileNameUS =
LICENSE_COUNT_ANALYSIS_FRONT_STRING +
`_US_${outputDateTimeYYYYMMDDHH24MISS}.csv`;
const outputFileNameEU =
LICENSE_COUNT_ANALYSIS_FRONT_STRING +
`_EU_${outputDateTimeYYYYMMDDHH24MISS}.csv`;
const outputFileNameAU =
LICENSE_COUNT_ANALYSIS_FRONT_STRING +
`_AU_${outputDateTimeYYYYMMDDHH24MISS}.csv`;
// outputDataUSの配列をCSV形式に変換
for (let i = 0; i < outputCsvData.outputDataUS.length; i++) {
//カンマ区切りの文字列を作成
csvContentUS += outputCsvData.outputDataUS[i];
}
// outputDataEUの配列をCSV形式に変換
for (let i = 0; i < outputCsvData.outputDataEU.length; i++) {
//カンマ区切りの文字列を作成
csvContentEU += outputCsvData.outputDataEU[i];
}
// outputDataAUの配列をCSV形式に変換
for (let i = 0; i < outputCsvData.outputDataAU.length; i++) {
//カンマ区切りの文字列を作成
csvContentAU += outputCsvData.outputDataAU[i];
}
await blobstorageService.createContainerAnalysis(context);
// 出力ファイル名を指定して出力
const resultUS = await blobstorageService.uploadFileAnalysisLicensesCSV(
context,
outputFileNameUS,
outputCsvData.outputDataUS.join("\r\n")
);
context.log("resultUS: " + resultUS);
const resultEU = await blobstorageService.uploadFileAnalysisLicensesCSV(
context,
outputFileNameEU,
outputCsvData.outputDataEU.join("\r\n")
);
const resultAU = await blobstorageService.uploadFileAnalysisLicensesCSV(
context,
outputFileNameAU,
outputCsvData.outputDataAU.join("\r\n")
);
// 出力結果を返却
// 3つのリージョンの出力が全て成功した場合にtrueを返却
return resultUS && resultEU && resultAU;
} catch (e) {
context.log("outputAnalysisLicensesData failed.");
context.error(e);
throw e;
} finally {
context.log("[OUT]outputAnalysisLicensesData");
}
}

View File

@ -23,7 +23,7 @@ export async function analysisLicensesManualRetry(
dotenv.config({ path: ".env" });
dotenv.config({ path: ".env.local", override: true });
const datasource = await initializeDataSource(context);
const blobstorageService = new BlobstorageService();
const blobstorageService = new BlobstorageService(true);
try {
// 現在の日付より、先月の年月をYYYYMM形式で取得

View File

@ -2,6 +2,7 @@ import { DataSource } from "typeorm";
import {
getBaseData,
getBaseDataFromDeletedAccounts,
outputAnalysisLicensesData,
transferData,
} from "../functions/analysisLicenses";
import {
@ -27,6 +28,7 @@ import {
LICENSE_COUNT_ANALYSIS_LICENSE_TYPE,
SWITCH_FROM_TYPE,
} from "../constants";
import { BlobstorageService } from "../blobstorage/blobstorage.service";
describe("analysisLicenses", () => {
dotenv.config({ path: ".env" });
dotenv.config({ path: ".env.local", override: true });
@ -1221,4 +1223,94 @@ describe("analysisLicenses", () => {
expect(transferDataResult.outputDataUS[12]).toEqual(`"",`);
expect(transferDataResult.outputDataUS[13]).toEqual(`"3"` + "\r\n");
});
it("outputDataの確認Mock", async () => {
const blobService = new BlobstorageService();
if (!source) fail();
const context = new InvocationContext();
const currentDate = new DateWithZeroTime();
const expiringSoonDate = new ExpirationThresholdDate(currentDate.getTime());
// 現在の日付を取得
const nowDate = new Date();
// 先月の日付を取得
const lastMonth = new Date(nowDate);
lastMonth.setMonth(nowDate.getMonth() - 1);
const lastMonthYYYYMM = `${lastMonth.getFullYear()}${(
lastMonth.getMonth() + 1
)
.toString()
.padStart(2, "0")}`;
// 先々月の日付を取得
const last2Month = new Date(nowDate);
last2Month.setMonth(nowDate.getMonth() - 2);
const last2MonthYYYYMM = `${last2Month.getFullYear()}${(
last2Month.getMonth() + 1
)
.toString()
.padStart(2, "0")}`;
// tier4とtier5のアカウント管理者を作る
const { account: account4, admin: admin4 } = await makeTestAccount(
source,
{ tier: 4 },
{ external_id: "external_id_tier4admin" }
);
const { account: account5_1, admin: admin5_1_1 } = await makeTestAccount(
source,
{
tier: 5,
parent_account_id: account4.id,
},
{
external_id: "external_id_tier5admin1",
role: "author",
}
);
// 所有ライセンス
// usedTrialLicensesAuthorCount 1件
await createLicense(
source,
1,
null,
account5_1.id,
"TRIAL",
LICENSE_ALLOCATED_STATUS.ALLOCATED,
admin5_1_1.id,
null,
null,
null,
last2Month
);
const result = await getBaseData(context, lastMonthYYYYMM, source);
const result_D = await getBaseDataFromDeletedAccounts(
context,
lastMonthYYYYMM,
source
);
const transferDataResult = await transferData(
context,
result,
result_D,
lastMonthYYYYMM
);
const mockUploadFileAnalysisLicensesCSV = jest.fn().mockReturnValue(true);
const mockCreateContainerAnalysis = jest.fn().mockReturnValue(true);
blobService.uploadFileAnalysisLicensesCSV =
mockUploadFileAnalysisLicensesCSV;
blobService.createContainerAnalysis = mockCreateContainerAnalysis;
const resultOutputData = await outputAnalysisLicensesData(
context,
blobService,
transferDataResult
);
expect(resultOutputData).toEqual(true);
});
});

View File

@ -45,7 +45,6 @@ export class BlobstorageService {
this.configService.getOrThrow<string>('STORAGE_ACCOUNT_NAME_IMPORTS'),
this.configService.getOrThrow<string>('STORAGE_ACCOUNT_KEY_IMPORTS'),
);
this.blobServiceClientUS = new BlobServiceClient(
this.configService.getOrThrow<string>('STORAGE_ACCOUNT_ENDPOINT_US'),
this.sharedKeyCredentialUS,
@ -104,7 +103,6 @@ export class BlobstorageService {
);
}
}
/**
*
* @param context