From 5c9a0d55c329e5a148998c618bff76bfc551ea07 Mon Sep 17 00:00:00 2001 From: Raileen Del Rosario <43893067+raileendr@users.noreply.github.com> Date: Fri, 21 Nov 2025 08:16:46 -0800 Subject: [PATCH 1/4] Add reference implementation link to README Added a reference implementation link for data IO extensions. Signed-off-by: Raileen Del Rosario <43893067+raileendr@users.noreply.github.com> --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 508c2dc..da08109 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ To test a read-only data IO extension app, modify one of the `ReadOnlyManifest.j To test a data IO extension app with both read and write capabilities, modify one of the `ReadWriteManifest.json` files. +For an example of a reference implementation with multiple types of extensions, see the [Data IO and Data Verification Reference Implementation](https://github.com/docusign/extension-app-data-io-and-verification-reference-implementation/). + ## Authentication This reference implementation supports two [authentication](https://developers.docusign.com/extension-apps/build-an-extension-app/it-infrastructure/authorization/) flows: * [Authorization Code Grant](https://developers.docusign.com/extension-apps/build-an-extension-app/it-infrastructure/authorization/#authorization-code-grant) – required for public extension apps From 4422ef4b6f777659e4e7fa7a25d4f1daff46a242 Mon Sep 17 00:00:00 2001 From: Raileen Del Rosario <43893067+raileendr@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:02:36 -0800 Subject: [PATCH 2/4] Fix formatting in README for reference implementation Signed-off-by: Raileen Del Rosario <43893067+raileendr@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index da08109..6647cbb 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ To test a read-only data IO extension app, modify one of the `ReadOnlyManifest.j To test a data IO extension app with both read and write capabilities, modify one of the `ReadWriteManifest.json` files. -For an example of a reference implementation with multiple types of extensions, see the [Data IO and Data Verification Reference Implementation](https://github.com/docusign/extension-app-data-io-and-verification-reference-implementation/). +For an example of a reference implementation with multiple extensions, see the [Data IO and Data Verification Reference Implementation](https://github.com/docusign/extension-app-data-io-and-verification-reference-implementation/). ## Authentication This reference implementation supports two [authentication](https://developers.docusign.com/extension-apps/build-an-extension-app/it-infrastructure/authorization/) flows: From 9444989f9ee767ac7aa34fa602387e13146b9b11 Mon Sep 17 00:00:00 2001 From: "julie.gordon" Date: Thu, 18 Dec 2025 12:02:12 -0500 Subject: [PATCH 3/4] Add executeAll to QueryExecutor and update searchRecords to return multiple matches --- src/services/dataio.service.ts | 14 +++++++++----- src/utils/queryExecutor.ts | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/services/dataio.service.ts b/src/services/dataio.service.ts index 039290a..e334ed4 100644 --- a/src/services/dataio.service.ts +++ b/src/services/dataio.service.ts @@ -200,15 +200,19 @@ export const searchRecords = (req: IReq, res: IRes): IRes => } const db: FileDB = new FileDB(generateFilePath(query.from)); const data: object[] = db.readFile(); - const index: number = QueryExecutor.execute(query, data); - if (index === -1) { + const indices: number[] = QueryExecutor.executeAll(query, data); + if (!indices || indices.length === 0) { console.log('No results found'); return res.json({ records: [] }) } - const dataResult: object = data[index]; - convertDateToISO8601(dataResult, query.from); - return res.json({ records: [ResultRehydrator.filterAndRehydrate(query.attributesToSelect, data[index])] }); + const records = indices.map((idx: number) => { + const record: object = data[idx]; + convertDateToISO8601(record, query.from); + return ResultRehydrator.filterAndRehydrate(query.attributesToSelect, record); + }); + + return res.json({ records }); } catch (err) { console.log(`Encountered an error searching data: ${err.message}`); return res.status(500).json(generateErrorResponse(ErrorCode.INTERNAL_ERROR, err)).send(); diff --git a/src/utils/queryExecutor.ts b/src/utils/queryExecutor.ts index 9310103..a9c77fe 100644 --- a/src/utils/queryExecutor.ts +++ b/src/utils/queryExecutor.ts @@ -31,6 +31,23 @@ export class QueryExecutor { return -1; } + /** + * Executes a query on the given input data and returns all matching indices. + * @param {IQuery} query - The query to execute. + * @param {object[]} inputData - The data to query against. + * @returns {number[]} An array of indices for matching records (empty if none). + */ + public static executeAll(query: IQuery, inputData: object[]): number[] { + const operation: OperationUnion = query.queryFilter.operation; + const results: number[] = []; + for (let index: number = 0; index < inputData.length; index++) { + if (this.evaluateOperation(operation, inputData[index])) { + results.push(index); + } + } + return results; + } + /** * Evaluates an operation on a given record. * @param {OperationUnion} operation - The operation to evaluate. From 3744604ee072dbdd788e2a83835538af790c7732 Mon Sep 17 00:00:00 2001 From: Karissa Jacobsen Date: Tue, 13 Jan 2026 08:53:48 -0800 Subject: [PATCH 4/4] add ci/cd updates --- .github/workflows/terraform-deploy.yml | 112 ++++++++++++++++++ terraform/azure/main.tf | 1 + terraform/azure/variables.tf | 6 + terraform/common/modules/template/main.tf | 8 +- terraform/common/modules/template/outputs.tf | 2 +- .../common/modules/template/variables.tf | 11 ++ 6 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/terraform-deploy.yml diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml new file mode 100644 index 0000000..e6adb70 --- /dev/null +++ b/.github/workflows/terraform-deploy.yml @@ -0,0 +1,112 @@ +name: Terraform CI/CD + +on: + pull_request: + branches: [deploy] + push: + branches: [deploy] + +permissions: + id-token: write # Required for Azure OIDC + contents: write # Needed to commit manifest updates + pull-requests: write # Needed to open PRs + +env: + TF_VAR_application_name: ${{ vars.APPLICATION_NAME }} + TF_VAR_application_oauth_client_id: ${{ secrets.APPLICATION_OAUTH_CLIENT_ID }} + TF_VAR_application_oauth_client_secret: ${{ secrets.APPLICATION_OAUTH_CLIENT_SECRET }} + TF_VAR_location: ${{ vars.AZURE_LOCATION }} + TF_VAR_execution_mode: ci + ACR_NAME: ${{ vars.ACR_NAME }} + +jobs: + terraform: + runs-on: ubuntu-latest + + steps: + # Checkout repo + - name: Checkout repository + uses: actions/checkout@v4 + + # Install Terraform + - name: Setup Terraform + uses: hashicorp/setup-terraform@v2 + with: + terraform_version: 1.10.5 + + # Temporarily remove repo provider configuration + - name: Disable repo azurerm provider + working-directory: terraform/azure + run: mv providers.tf providers.tf.bak + + # Create CI-only backend + OIDC provider + - name: Create CI Azure backend/provider + working-directory: terraform/azure + run: | + cat < azure_ci.tf + terraform { + backend "azurerm" {} + } + + provider "azurerm" { + features {} + } + EOF + + - name: Configure Azure OIDC environment + run: | + echo "ARM_USE_OIDC=true" >> $GITHUB_ENV + echo "ARM_CLIENT_ID=${{ secrets.AZURE_CLIENT_ID }}" >> $GITHUB_ENV + echo "ARM_TENANT_ID=${{ secrets.AZURE_TENANT_ID }}" >> $GITHUB_ENV + echo "ARM_SUBSCRIPTION_ID=${{ secrets.AZURE_SUBSCRIPTION_ID }}" >> $GITHUB_ENV + + - name: Azure Login + uses: azure/login@v1 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: Login to ACR + run: | + TOKEN=$(az acr login --name ${{ env.ACR_NAME }} --expose-token --output tsv --query accessToken) + docker login -u 00000000-0000-0000-0000-000000000000 --password-stdin ${{ env.ACR_NAME }}.azurecr.io <<< $TOKEN + + - name: Terraform Init + working-directory: terraform/azure + run: | + terraform init -upgrade -reconfigure\ + -backend-config="resource_group_name=${{ vars.AZURE_RG }}" \ + -backend-config="storage_account_name=${{ vars.AZURE_STORAGE_ACCOUNT }}" \ + -backend-config="container_name=${{ vars.AZURE_CONTAINER }}" \ + -backend-config="key=${{ vars.AZURE_TFSTATE_KEY }}" \ + -backend-config="use_oidc=true" + + - name: Debug backend values + run: | + terraform state pull | head -20 + echo "RG=${{ vars.AZURE_RG }}" + echo "SA=${{ vars.AZURE_STORAGE_ACCOUNT }}" + echo "CONTAINER=${{ vars.AZURE_CONTAINER }}" + echo "KEY=${{ vars.AZURE_TFSTATE_KEY }}" + + # Terraform plan (PR only) + - name: Terraform Plan + if: github.event_name == 'pull_request' + working-directory: terraform/azure + run: terraform plan -out=tfplan + + # Terraform apply (merge to main only) + - name: Terraform Apply + if: github.event_name == 'push' + working-directory: terraform/azure + run: | + terraform plan -out=tfplan + terraform apply -auto-approve tfplan + + # Cleanup CI-only files + - name: Cleanup CI Terraform files + working-directory: terraform/azure + run: | + rm -f azure_ci.tf + mv providers.tf.bak providers.tf \ No newline at end of file diff --git a/terraform/azure/main.tf b/terraform/azure/main.tf index 26e5a5b..e065e1d 100644 --- a/terraform/azure/main.tf +++ b/terraform/azure/main.tf @@ -61,6 +61,7 @@ module "manifest" { "${basename(dirname(var.manifest_files_paths[count.index]))}.${basename(var.manifest_files_paths[count.index])}" ]) + execution_mode = var.execution_mode client_id = local.application_oauth_client_id client_secret = local.application_oauth_client_secret base_url = local.application_service_url diff --git a/terraform/azure/variables.tf b/terraform/azure/variables.tf index 9c191bf..4cb0d12 100644 --- a/terraform/azure/variables.tf +++ b/terraform/azure/variables.tf @@ -276,3 +276,9 @@ variable "tags" { description = "A map of the tags to apply to various resources" default = {} } + +variable "execution_mode" { + description = "Execution mode: 'local' or 'ci'" + type = string + default = "local" +} \ No newline at end of file diff --git a/terraform/common/modules/template/main.tf b/terraform/common/modules/template/main.tf index 33228c9..9ab9c52 100644 --- a/terraform/common/modules/template/main.tf +++ b/terraform/common/modules/template/main.tf @@ -25,11 +25,15 @@ locals { ]... ) ]) - - output_file_path = nonsensitive(local_sensitive_file.this.filename != "/dev/null" ? local_sensitive_file.this.filename : "") } resource "local_sensitive_file" "this" { + count = var.execution_mode == "local" ? 1 : 0 content = local.output_file_content filename = local.enabled ? var.output_file_path : "/dev/null" } + +output "manifest_file_path" { + description = "The intended output file path" + value = var.output_file_path +} \ No newline at end of file diff --git a/terraform/common/modules/template/outputs.tf b/terraform/common/modules/template/outputs.tf index a57d302..07ec913 100644 --- a/terraform/common/modules/template/outputs.tf +++ b/terraform/common/modules/template/outputs.tf @@ -1,4 +1,4 @@ output "output_file_path" { description = "The absolute path to the output file" - value = local.output_file_path + value = var.output_file_path } diff --git a/terraform/common/modules/template/variables.tf b/terraform/common/modules/template/variables.tf index daa27d0..99d4d12 100644 --- a/terraform/common/modules/template/variables.tf +++ b/terraform/common/modules/template/variables.tf @@ -1,3 +1,14 @@ +variable "execution_mode" { + description = "Controls whether Terraform writes manifests to disk (local) or only renders them (ci)" + type = string + default = "local" + + validation { + condition = contains(["local", "ci"], var.execution_mode) + error_message = "execution_mode must be 'local' or 'ci'." + } +} + variable "input_file_path" { description = "The absolute path to the input file. If it doesn't exist, the module will not do anything" type = string