Merge branch 'master' into redis-pubsub-fix
This commit is contained in:
commit
edd2c9d5a9
|
|
@ -11,5 +11,5 @@ coverage:
|
||||||
# See https://docs.codecov.io/docs/commit-status#disabling-a-status.
|
# See https://docs.codecov.io/docs/commit-status#disabling-a-status.
|
||||||
default: false
|
default: false
|
||||||
comment:
|
comment:
|
||||||
# Delete old comment and post new one for new coverage information.
|
# Update old comment with new coverage information if the PR is changed. Avoids triggering multiple emails.
|
||||||
behavior: new
|
behavior: once
|
||||||
|
|
|
||||||
|
|
@ -216,6 +216,7 @@ KEYVAULT_NAME_VAR_NAME="AzureKeyVaultName"
|
||||||
RESOURCE_GROUP_NAME_VAR_NAME="AzureResourceGroupName"
|
RESOURCE_GROUP_NAME_VAR_NAME="AzureResourceGroupName"
|
||||||
|
|
||||||
SERVICE_BUS_CONNECTION_STRING_VAR_NAME="AzureServiceBusConnectionString"
|
SERVICE_BUS_CONNECTION_STRING_VAR_NAME="AzureServiceBusConnectionString"
|
||||||
|
SERVICE_BUS_NAMESPACE_VAR_NAME="AzureServiceBusNamespace"
|
||||||
|
|
||||||
SQL_SERVER_NAME_VAR_NAME="AzureSqlServerName"
|
SQL_SERVER_NAME_VAR_NAME="AzureSqlServerName"
|
||||||
SQL_SERVER_DB_NAME_VAR_NAME="AzureSqlServerDbName"
|
SQL_SERVER_DB_NAME_VAR_NAME="AzureSqlServerDbName"
|
||||||
|
|
@ -613,6 +614,9 @@ echo "Configuring Service Bus test settings ..."
|
||||||
SERVICE_BUS_CONNECTION_STRING="$(az servicebus namespace authorization-rule keys list --name RootManageSharedAccessKey --namespace-name "${SERVICE_BUS_NAME}" --resource-group "${RESOURCE_GROUP_NAME}" --query "primaryConnectionString" --output tsv)"
|
SERVICE_BUS_CONNECTION_STRING="$(az servicebus namespace authorization-rule keys list --name RootManageSharedAccessKey --namespace-name "${SERVICE_BUS_NAME}" --resource-group "${RESOURCE_GROUP_NAME}" --query "primaryConnectionString" --output tsv)"
|
||||||
echo export ${SERVICE_BUS_CONNECTION_STRING_VAR_NAME}=\"${SERVICE_BUS_CONNECTION_STRING}\" >> "${ENV_CONFIG_FILENAME}"
|
echo export ${SERVICE_BUS_CONNECTION_STRING_VAR_NAME}=\"${SERVICE_BUS_CONNECTION_STRING}\" >> "${ENV_CONFIG_FILENAME}"
|
||||||
az keyvault secret set --name "${SERVICE_BUS_CONNECTION_STRING_VAR_NAME}" --vault-name "${KEYVAULT_NAME}" --value "${SERVICE_BUS_CONNECTION_STRING}"
|
az keyvault secret set --name "${SERVICE_BUS_CONNECTION_STRING_VAR_NAME}" --vault-name "${KEYVAULT_NAME}" --value "${SERVICE_BUS_CONNECTION_STRING}"
|
||||||
|
SERVICE_BUS_NAMESPACE="${SERVICE_BUS_NAME}.servicebus.windows.net"
|
||||||
|
echo export ${SERVICE_BUS_NAMESPACE_VAR_NAME}=\"${SERVICE_BUS_NAMESPACE}\" >> "${ENV_CONFIG_FILENAME}"
|
||||||
|
az keyvault secret set --name "${SERVICE_BUS_NAMESPACE_VAR_NAME}" --vault-name "${KEYVAULT_NAME}" --value "${SERVICE_BUS_NAMESPACE}"
|
||||||
|
|
||||||
# ----------------------------------
|
# ----------------------------------
|
||||||
# Populate SQL Server test settings
|
# Populate SQL Server test settings
|
||||||
|
|
@ -726,6 +730,9 @@ az role assignment create --assignee "${CERTIFICATION_SPAUTH_SP_PRINCIPAL_ID}" -
|
||||||
# IOT hub used in eventhubs certification test
|
# IOT hub used in eventhubs certification test
|
||||||
az role assignment create --assignee "${CERTIFICATION_SPAUTH_SP_PRINCIPAL_ID}" --role "Owner" --scope "/subscriptions/${SUB_ID}/resourceGroups/${RESOURCE_GROUP_NAME}/providers/Microsoft.Devices/IotHubs/${IOT_HUB_NAME}"
|
az role assignment create --assignee "${CERTIFICATION_SPAUTH_SP_PRINCIPAL_ID}" --role "Owner" --scope "/subscriptions/${SUB_ID}/resourceGroups/${RESOURCE_GROUP_NAME}/providers/Microsoft.Devices/IotHubs/${IOT_HUB_NAME}"
|
||||||
az role assignment create --assignee "${CERTIFICATION_SPAUTH_SP_PRINCIPAL_ID}" --role "IoT Hub Data Contributor" --scope "/subscriptions/${SUB_ID}/resourceGroups/${RESOURCE_GROUP_NAME}/providers/Microsoft.Devices/IotHubs/${IOT_HUB_NAME}"
|
az role assignment create --assignee "${CERTIFICATION_SPAUTH_SP_PRINCIPAL_ID}" --role "IoT Hub Data Contributor" --scope "/subscriptions/${SUB_ID}/resourceGroups/${RESOURCE_GROUP_NAME}/providers/Microsoft.Devices/IotHubs/${IOT_HUB_NAME}"
|
||||||
|
# Azure Service Bus
|
||||||
|
ASB_ID=$(az servicebus namespace show --resource-group "${RESOURCE_GROUP_NAME}" --name "${SERVICE_BUS_NAME}" --query "id" -otsv)
|
||||||
|
az role assignment create --assignee "${CERTIFICATION_SPAUTH_SP_PRINCIPAL_ID}" --role "Azure Service Bus Data Owner" --scope "${ASB_ID}"
|
||||||
|
|
||||||
# Now export the service principal information
|
# Now export the service principal information
|
||||||
CERTIFICATION_TENANT_ID="$(az ad sp list --display-name "${CERTIFICATION_SPAUTH_SP_NAME}" --query "[].appOwnerTenantId" --output tsv)"
|
CERTIFICATION_TENANT_ID="$(az ad sp list --display-name "${CERTIFICATION_SPAUTH_SP_NAME}" --query "[].appOwnerTenantId" --output tsv)"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
services:
|
||||||
|
memcached:
|
||||||
|
image: docker.io/memcached:1.6
|
||||||
|
ports:
|
||||||
|
- '11211:11211'
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
services:
|
||||||
|
rethinkdb:
|
||||||
|
image: rethinkdb:2.4
|
||||||
|
ports:
|
||||||
|
- 8081:8080
|
||||||
|
- 28015:28015
|
||||||
|
- 29015:29015
|
||||||
|
|
@ -25,24 +25,11 @@ on:
|
||||||
- release-*
|
- release-*
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pre_job:
|
|
||||||
name: Skip Duplicate Actions
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
should_skip: ${{ steps.skip_check.outputs.should_skip }}
|
|
||||||
steps:
|
|
||||||
- id: skip_check
|
|
||||||
uses: fkirc/skip-duplicate-actions@v3.4.0
|
|
||||||
with:
|
|
||||||
cancel_others: 'true'
|
|
||||||
paths_ignore: '["**.md", ".codecov.yaml", ".github/workflows/dapr-automerge.yml"]'
|
|
||||||
# Based on whether this is a PR or a scheduled run, we will run a different
|
# Based on whether this is a PR or a scheduled run, we will run a different
|
||||||
# subset of the certification tests. This allows all the tests not requiring
|
# subset of the certification tests. This allows all the tests not requiring
|
||||||
# secrets to be executed on pull requests.
|
# secrets to be executed on pull requests.
|
||||||
generate-matrix:
|
generate-matrix:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: pre_job
|
|
||||||
if: needs.pre_job.outputs.should_skip != 'true' || github.event_name == 'repository_dispatch'
|
|
||||||
steps:
|
steps:
|
||||||
- name: Install yq
|
- name: Install yq
|
||||||
run: |
|
run: |
|
||||||
|
|
@ -59,6 +46,9 @@ jobs:
|
||||||
- state.postgresql
|
- state.postgresql
|
||||||
- bindings.alicloud.dubbo
|
- bindings.alicloud.dubbo
|
||||||
- bindings.kafka
|
- bindings.kafka
|
||||||
|
- secretstores.local.env
|
||||||
|
- secretstores.local.file
|
||||||
|
- bindings.rabbitmq
|
||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
echo "::set-output name=pr-components::$PR_COMPONENTS"
|
echo "::set-output name=pr-components::$PR_COMPONENTS"
|
||||||
|
|
@ -101,12 +91,16 @@ jobs:
|
||||||
required-secrets: AzureEventHubsBindingsConnectionString,AzureBlobStorageAccount,AzureBlobStorageAccessKey,AzureEventHubsBindingsHub,AzureEventHubsBindingsNamespace,AzureEventHubsBindingsConsumerGroup,AzureCertificationServicePrincipalClientId,AzureCertificationTenantId,AzureCertificationServicePrincipalClientSecret,AzureResourceGroupName,AzureCertificationSubscriptionId,AzureEventHubsBindingsContainer,AzureIotHubEventHubConnectionString,AzureIotHubName,AzureIotHubBindingsConsumerGroup
|
required-secrets: AzureEventHubsBindingsConnectionString,AzureBlobStorageAccount,AzureBlobStorageAccessKey,AzureEventHubsBindingsHub,AzureEventHubsBindingsNamespace,AzureEventHubsBindingsConsumerGroup,AzureCertificationServicePrincipalClientId,AzureCertificationTenantId,AzureCertificationServicePrincipalClientSecret,AzureResourceGroupName,AzureCertificationSubscriptionId,AzureEventHubsBindingsContainer,AzureIotHubEventHubConnectionString,AzureIotHubName,AzureIotHubBindingsConsumerGroup
|
||||||
- component: pubsub.azure.eventhubs
|
- component: pubsub.azure.eventhubs
|
||||||
required-secrets: AzureEventHubsPubsubTopicActiveConnectionString,AzureEventHubsPubsubNamespace,AzureEventHubsPubsubNamespaceConnectionString,AzureBlobStorageAccount,AzureBlobStorageAccessKey,AzureEventHubsPubsubContainer,AzureIotHubName,AzureIotHubEventHubConnectionString,AzureCertificationTenantId,AzureCertificationServicePrincipalClientId,AzureCertificationServicePrincipalClientSecret,AzureResourceGroupName,AzureCertificationSubscriptionId
|
required-secrets: AzureEventHubsPubsubTopicActiveConnectionString,AzureEventHubsPubsubNamespace,AzureEventHubsPubsubNamespaceConnectionString,AzureBlobStorageAccount,AzureBlobStorageAccessKey,AzureEventHubsPubsubContainer,AzureIotHubName,AzureIotHubEventHubConnectionString,AzureCertificationTenantId,AzureCertificationServicePrincipalClientId,AzureCertificationServicePrincipalClientSecret,AzureResourceGroupName,AzureCertificationSubscriptionId
|
||||||
|
- component: pubsub.azure.servicebus
|
||||||
|
required-secrets: AzureServiceBusConnectionString,AzureServiceBusNamespace, AzureCertificationTenantId,AzureCertificationServicePrincipalClientId,AzureCertificationServicePrincipalClientSecret
|
||||||
- component: bindings.azure.blobstorage
|
- component: bindings.azure.blobstorage
|
||||||
required-secrets: AzureBlobStorageAccount,AzureBlobStorageAccessKey,AzureBlobStorageContainer,AzureCertificationTenantId,AzureCertificationServicePrincipalClientId,AzureCertificationServicePrincipalClientSecret
|
required-secrets: AzureBlobStorageAccount,AzureBlobStorageAccessKey,AzureBlobStorageContainer,AzureCertificationTenantId,AzureCertificationServicePrincipalClientId,AzureCertificationServicePrincipalClientSecret
|
||||||
- component: bindings.azure.storagequeues
|
- component: bindings.azure.storagequeues
|
||||||
required-secrets: AzureBlobStorageAccount, AzureBlobStorageAccessKey
|
required-secrets: AzureBlobStorageAccount, AzureBlobStorageAccessKey
|
||||||
- component: state.azure.tablestorage
|
- component: state.azure.tablestorage
|
||||||
required-secrets: AzureBlobStorageAccount, AzureBlobStorageAccessKey, AzureCertificationTenantId, AzureCertificationServicePrincipalClientId, AzureCertificationServicePrincipalClientSecret
|
required-secrets: AzureBlobStorageAccount, AzureBlobStorageAccessKey, AzureCertificationTenantId, AzureCertificationServicePrincipalClientId, AzureCertificationServicePrincipalClientSecret
|
||||||
|
- component: state.azure.blobstorage
|
||||||
|
required-secrets: AzureBlobStorageContainer,AzureBlobStorageAccount, AzureBlobStorageAccessKey, AzureCertificationTenantId, AzureCertificationServicePrincipalClientId, AzureCertificationServicePrincipalClientSecret
|
||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
echo "::set-output name=cloud-components::$CRON_COMPONENTS"
|
echo "::set-output name=cloud-components::$CRON_COMPONENTS"
|
||||||
|
|
@ -156,11 +150,13 @@ jobs:
|
||||||
export TEST_OUTPUT_FILE_PREFIX=$GITHUB_WORKSPACE/test_report
|
export TEST_OUTPUT_FILE_PREFIX=$GITHUB_WORKSPACE/test_report
|
||||||
echo "TEST_OUTPUT_FILE_PREFIX=$TEST_OUTPUT_FILE_PREFIX" >> $GITHUB_ENV
|
echo "TEST_OUTPUT_FILE_PREFIX=$TEST_OUTPUT_FILE_PREFIX" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Configure certification test path
|
- name: Configure certification test and source path
|
||||||
run: |
|
run: |
|
||||||
TEST_COMPONENT=$(echo ${{ matrix.component }} | sed -E 's/\./\//g')
|
TEST_COMPONENT=$(echo ${{ matrix.component }} | sed -E 's/\./\//g')
|
||||||
export TEST_PATH="${PROJECT_PATH}/tests/certification/${TEST_COMPONENT}"
|
export TEST_PATH="${PROJECT_PATH}/tests/certification/${TEST_COMPONENT}"
|
||||||
echo "TEST_PATH=$TEST_PATH" >> $GITHUB_ENV
|
echo "TEST_PATH=$TEST_PATH" >> $GITHUB_ENV
|
||||||
|
export SOURCE_PATH="github.com/dapr/components-contrib/${TEST_COMPONENT}"
|
||||||
|
echo "SOURCE_PATH=$SOURCE_PATH" >> $GITHUB_ENV
|
||||||
|
|
||||||
- uses: Azure/login@v1
|
- uses: Azure/login@v1
|
||||||
with:
|
with:
|
||||||
|
|
@ -194,18 +190,19 @@ jobs:
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: '^1.18'
|
go-version: '^1.19'
|
||||||
|
|
||||||
- name: Download Go dependencies
|
- name: Download Go dependencies
|
||||||
working-directory: ${{ env.TEST_PATH }}
|
working-directory: ${{ env.TEST_PATH }}
|
||||||
run: |
|
run: |
|
||||||
go mod download
|
go mod download
|
||||||
go install gotest.tools/gotestsum@latest
|
go install gotest.tools/gotestsum@latest
|
||||||
|
go install github.com/axw/gocov/gocov@v1.1.0
|
||||||
|
|
||||||
- name: Check that go mod tidy is up-to-date
|
- name: Check that go mod tidy is up-to-date
|
||||||
working-directory: ${{ env.TEST_PATH }}
|
working-directory: ${{ env.TEST_PATH }}
|
||||||
run: |
|
run: |
|
||||||
go mod tidy -compat=1.18
|
go mod tidy -compat=1.19
|
||||||
git diff --exit-code ./go.mod
|
git diff --exit-code ./go.mod
|
||||||
git diff --exit-code ./go.sum
|
git diff --exit-code ./go.sum
|
||||||
|
|
||||||
|
|
@ -218,8 +215,7 @@ jobs:
|
||||||
set +e
|
set +e
|
||||||
gotestsum --jsonfile ${{ env.TEST_OUTPUT_FILE_PREFIX }}_certification.json \
|
gotestsum --jsonfile ${{ env.TEST_OUTPUT_FILE_PREFIX }}_certification.json \
|
||||||
--junitfile ${{ env.TEST_OUTPUT_FILE_PREFIX }}_certification.xml --format standard-quiet -- \
|
--junitfile ${{ env.TEST_OUTPUT_FILE_PREFIX }}_certification.xml --format standard-quiet -- \
|
||||||
-count=1 -timeout=15m
|
-coverprofile=cover.out -covermode=set -coverpkg=${{ env.SOURCE_PATH }}
|
||||||
|
|
||||||
status=$?
|
status=$?
|
||||||
echo "Completed certification tests for ${{ matrix.component }} ... "
|
echo "Completed certification tests for ${{ matrix.component }} ... "
|
||||||
if test $status -ne 0; then
|
if test $status -ne 0; then
|
||||||
|
|
@ -228,6 +224,12 @@ jobs:
|
||||||
fi
|
fi
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
COVERAGE_REPORT=$(gocov convert cover.out | gocov report)
|
||||||
|
COVERAGE_LINE=$(echo $COVERAGE_REPORT | grep -oP '(?<=Total Coverage:).*') # example: "80.00% (40/50)"
|
||||||
|
COVERAGE_PERCENTAGE=$(echo $COVERAGE_LINE | grep -oP '([0-9\.]*)' | head -n 1) # example "80.00"
|
||||||
|
echo "COVERAGE_LINE=$COVERAGE_LINE" >> $GITHUB_ENV
|
||||||
|
echo "COMPONENT_PERCENTAGE=$COVERAGE_PERCENTAGE" >> $GITHUB_ENV
|
||||||
|
|
||||||
# Fail the step if we found no test to run
|
# Fail the step if we found no test to run
|
||||||
if grep -q "\[no test files\]" ${{ env.TEST_OUTPUT_FILE_PREFIX }}_certification.json ; then
|
if grep -q "\[no test files\]" ${{ env.TEST_OUTPUT_FILE_PREFIX }}_certification.json ; then
|
||||||
echo "::error:: No certification test file was found for component ${{ matrix.component }}"
|
echo "::error:: No certification test file was found for component ${{ matrix.component }}"
|
||||||
|
|
@ -245,6 +247,27 @@ jobs:
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Prepare Cert Coverage Info
|
||||||
|
run: |
|
||||||
|
mkdir -p tmp/cov_files
|
||||||
|
SOURCE_PATH_LINEAR=$(echo ${{ env.SOURCE_PATH }} |sed 's#/#\.#g') # converts slashes to dots in this string, so that it doesn't consider them sub-folders
|
||||||
|
echo "${{ env.COVERAGE_LINE }}" >> tmp/cov_files/$SOURCE_PATH_LINEAR.txt
|
||||||
|
|
||||||
|
- name: Upload Cert Coverage Artifact
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: certtest_cov
|
||||||
|
path: tmp/cov_files
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
- name: Component Coverage Discord Notification
|
||||||
|
env:
|
||||||
|
DISCORD_WEBHOOK: ${{ secrets.DISCORD_MONITORING_WEBHOOK_URL }}
|
||||||
|
uses: Ilshidur/action-discord@0c4b27844ba47cb1c7bee539c8eead5284ce9fa9
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
args: 'Cert Test Coverage for {{ SOURCE_PATH }} is {{ COVERAGE_LINE }}'
|
||||||
|
|
||||||
# Upload logs for test analytics to consume
|
# Upload logs for test analytics to consume
|
||||||
- name: Upload test results
|
- name: Upload test results
|
||||||
if: always()
|
if: always()
|
||||||
|
|
@ -252,3 +275,41 @@ jobs:
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.component }}_certification_test
|
name: ${{ matrix.component }}_certification_test
|
||||||
path: ${{ env.TEST_OUTPUT_FILE_PREFIX }}_certification.*
|
path: ${{ env.TEST_OUTPUT_FILE_PREFIX }}_certification.*
|
||||||
|
|
||||||
|
post_job:
|
||||||
|
name: Notify Total coverage
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: certification
|
||||||
|
if: always()
|
||||||
|
steps:
|
||||||
|
- name: Download Cert Coverage Artifact
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
continue-on-error: true
|
||||||
|
id: download
|
||||||
|
with:
|
||||||
|
name: certtest_cov
|
||||||
|
path: tmp/cov_files
|
||||||
|
|
||||||
|
- name: Calculate total coverage
|
||||||
|
run: |
|
||||||
|
ls "${{steps.download.outputs.download-path}}" | while read f; do
|
||||||
|
while read LINE;
|
||||||
|
do
|
||||||
|
ratio=$(echo $LINE | cut -d "(" -f2 | cut -d ")" -f1)
|
||||||
|
tempNumerator=$(echo $ratio | cut -d'/' -f1)
|
||||||
|
tempDenominator=$(echo $ratio | cut -d'/' -f2)
|
||||||
|
export numerator=$(($numerator+$tempNumerator))
|
||||||
|
export denominator=$(($denominator+$tempDenominator))
|
||||||
|
totalPer=$(awk "BEGIN { print (($numerator / $denominator) * 100) }")
|
||||||
|
echo "totalPer=$totalPer" >> $GITHUB_ENV
|
||||||
|
done < ${{steps.download.outputs.download-path}}/$f
|
||||||
|
done
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Final Coverage Discord Notification
|
||||||
|
env:
|
||||||
|
DISCORD_WEBHOOK: ${{ secrets.DISCORD_MONITORING_WEBHOOK_URL }}
|
||||||
|
uses: Ilshidur/action-discord@0c4b27844ba47cb1c7bee539c8eead5284ce9fa9
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
args: 'Total Coverage for Certification Tests is {{ totalPer }}%'
|
||||||
|
|
@ -29,11 +29,11 @@ jobs:
|
||||||
name: Build ${{ matrix.target_os }}_${{ matrix.target_arch }} binaries
|
name: Build ${{ matrix.target_os }}_${{ matrix.target_arch }} binaries
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
env:
|
env:
|
||||||
GOVER: 1.18
|
GOVER: "1.19"
|
||||||
GOOS: ${{ matrix.target_os }}
|
GOOS: ${{ matrix.target_os }}
|
||||||
GOARCH: ${{ matrix.target_arch }}
|
GOARCH: ${{ matrix.target_arch }}
|
||||||
GOPROXY: https://proxy.golang.org
|
GOPROXY: https://proxy.golang.org
|
||||||
GOLANGCI_LINT_VER: v1.45.2
|
GOLANGCI_LINT_VER: "v1.48.0"
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||||
|
|
@ -51,12 +51,6 @@ jobs:
|
||||||
- os: macOS-latest
|
- os: macOS-latest
|
||||||
target_arch: arm
|
target_arch: arm
|
||||||
steps:
|
steps:
|
||||||
- name: Check if need skip
|
|
||||||
id: skip_check
|
|
||||||
uses: fkirc/skip-duplicate-actions@v3.4.0
|
|
||||||
with:
|
|
||||||
cancel_others: 'true'
|
|
||||||
paths_ignore: '["**.md", ".codecov.yaml", ".github/workflows/dapr-automerge.yml"]'
|
|
||||||
- name: Set up Go ${{ env.GOVER }}
|
- name: Set up Go ${{ env.GOVER }}
|
||||||
if: ${{ steps.skip_check.outputs.should_skip != 'true' }}
|
if: ${{ steps.skip_check.outputs.should_skip != 'true' }}
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
|
|
@ -65,19 +59,52 @@ jobs:
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
if: ${{ steps.skip_check.outputs.should_skip != 'true' }}
|
if: ${{ steps.skip_check.outputs.should_skip != 'true' }}
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
- name: Cache Go modules (Linux)
|
||||||
|
if: matrix.target_os == 'linux'
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cache/go-build
|
||||||
|
~/go/pkg/mod
|
||||||
|
key: ${{ matrix.target_os }}-${{ matrix.target_arch }}-go-${{ env.GOVER }}-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ matrix.target_os }}-${{ matrix.target_arch }}-go-${{ env.GOVER }}-
|
||||||
|
- name: Cache Go modules (Windows)
|
||||||
|
if: matrix.target_os == 'windows'
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~\AppData\Local\go-build
|
||||||
|
~\go\pkg\mod
|
||||||
|
key: ${{ matrix.target_os }}-${{ matrix.target_arch }}-go-${{ env.GOVER }}-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ matrix.target_os }}-${{ matrix.target_arch }}-go-${{ env.GOVER }}-
|
||||||
|
- name: Cache Go modules (macOS)
|
||||||
|
if: matrix.target_os == 'darwin'
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/Library/Caches/go-build
|
||||||
|
~/go/pkg/mod
|
||||||
|
key: ${{ matrix.target_os }}-${{ matrix.target_arch }}-go-${{ env.GOVER }}-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ matrix.target_os }}-${{ matrix.target_arch }}-go-${{ env.GOVER }}-
|
||||||
- name: Run golangci-lint
|
- name: Run golangci-lint
|
||||||
if: matrix.target_arch == 'amd64' && matrix.target_os == 'linux' && steps.skip_check.outputs.should_skip != 'true'
|
if: matrix.target_arch == 'amd64' && matrix.target_os == 'linux' && steps.skip_check.outputs.should_skip != 'true'
|
||||||
uses: golangci/golangci-lint-action@v3.1.0
|
uses: golangci/golangci-lint-action@v3.2.0
|
||||||
with:
|
with:
|
||||||
version: ${{ env.GOLANGCI_LINT_VER }}
|
version: ${{ env.GOLANGCI_LINT_VER }}
|
||||||
|
skip-cache: true
|
||||||
|
args: --timeout 15m
|
||||||
- name: Run go mod tidy check diff
|
- name: Run go mod tidy check diff
|
||||||
if: matrix.target_arch == 'amd64' && matrix.target_os == 'linux' && steps.skip_check.outputs.should_skip != 'true'
|
if: matrix.target_arch == 'amd64' && matrix.target_os == 'linux' && steps.skip_check.outputs.should_skip != 'true'
|
||||||
run: make modtidy-all check-diff
|
run: make modtidy-all check-diff
|
||||||
- name: Run make test
|
- name: Run make test
|
||||||
env:
|
env:
|
||||||
COVERAGE_OPTS: "-coverprofile=coverage.txt -covermode=atomic"
|
COVERAGE_OPTS: "-coverprofile=coverage.txt -covermode=atomic"
|
||||||
|
IPFS_TEST: "1"
|
||||||
if: matrix.target_arch != 'arm' && steps.skip_check.outputs.should_skip != 'true'
|
if: matrix.target_arch != 'arm' && steps.skip_check.outputs.should_skip != 'true'
|
||||||
run: make test
|
run: make test
|
||||||
- name: Codecov
|
- name: Codecov
|
||||||
if: matrix.target_arch == 'amd64' && matrix.target_os == 'linux'
|
if: matrix.target_arch == 'amd64' && matrix.target_os == 'linux'
|
||||||
uses: codecov/codecov-action@v1
|
uses: codecov/codecov-action@v3
|
||||||
|
|
|
||||||
|
|
@ -25,24 +25,11 @@ on:
|
||||||
- release-*
|
- release-*
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pre_job:
|
|
||||||
name: Skip Duplicate Actions
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
should_skip: ${{ steps.skip_check.outputs.should_skip }}
|
|
||||||
steps:
|
|
||||||
- id: skip_check
|
|
||||||
uses: fkirc/skip-duplicate-actions@v3.4.0
|
|
||||||
with:
|
|
||||||
cancel_others: 'true'
|
|
||||||
paths_ignore: '["**.md", ".codecov.yaml", ".github/workflows/dapr-automerge.yml"]'
|
|
||||||
# Based on whether this is a PR or a scheduled run, we will run a different
|
# Based on whether this is a PR or a scheduled run, we will run a different
|
||||||
# subset of the conformance tests. This allows all the tests not requiring
|
# subset of the conformance tests. This allows all the tests not requiring
|
||||||
# secrets to be executed on pull requests.
|
# secrets to be executed on pull requests.
|
||||||
generate-matrix:
|
generate-matrix:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: pre_job
|
|
||||||
if: needs.pre_job.outputs.should_skip != 'true' || github.event_name == 'repository_dispatch'
|
|
||||||
steps:
|
steps:
|
||||||
- name: Install yq
|
- name: Install yq
|
||||||
run: |
|
run: |
|
||||||
|
|
@ -59,6 +46,7 @@ jobs:
|
||||||
- bindings.mqtt-mosquitto
|
- bindings.mqtt-mosquitto
|
||||||
- bindings.mqtt-vernemq
|
- bindings.mqtt-vernemq
|
||||||
- bindings.redis
|
- bindings.redis
|
||||||
|
- bindings.rabbitmq
|
||||||
- pubsub.aws.snssqs
|
- pubsub.aws.snssqs
|
||||||
- pubsub.hazelcast
|
- pubsub.hazelcast
|
||||||
- pubsub.in-memory
|
- pubsub.in-memory
|
||||||
|
|
@ -74,12 +62,14 @@ jobs:
|
||||||
- secretstores.localenv
|
- secretstores.localenv
|
||||||
- secretstores.localfile
|
- secretstores.localfile
|
||||||
- state.cassandra
|
- state.cassandra
|
||||||
|
- state.memcached
|
||||||
- state.mongodb
|
- state.mongodb
|
||||||
- state.mysql
|
- state.mysql
|
||||||
- state.postgresql
|
- state.postgresql
|
||||||
- state.redis
|
- state.redis
|
||||||
- state.sqlserver
|
- state.sqlserver
|
||||||
- state.cockroachdb
|
- state.cockroachdb
|
||||||
|
- state.rethinkdb
|
||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
echo "::set-output name=pr-components::$PR_COMPONENTS"
|
echo "::set-output name=pr-components::$PR_COMPONENTS"
|
||||||
|
|
@ -134,6 +124,8 @@ jobs:
|
||||||
required-certs: AzureKeyVaultSecretStoreCert
|
required-certs: AzureKeyVaultSecretStoreCert
|
||||||
- component: secretstores.azure.keyvault.serviceprincipal
|
- component: secretstores.azure.keyvault.serviceprincipal
|
||||||
required-secrets: AzureKeyVaultName,AzureKeyVaultSecretStoreTenantId,AzureKeyVaultSecretStoreServicePrincipalClientId,AzureKeyVaultSecretStoreServicePrincipalClientSecret
|
required-secrets: AzureKeyVaultName,AzureKeyVaultSecretStoreTenantId,AzureKeyVaultSecretStoreServicePrincipalClientId,AzureKeyVaultSecretStoreServicePrincipalClientSecret
|
||||||
|
- component: bindings.azure.cosmosdb
|
||||||
|
required-secrets: AzureCosmosDBMasterKey,AzureCosmosDBUrl,AzureCosmosDB,AzureCosmosDBCollection
|
||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
echo "::set-output name=cron-components::$CRON_COMPONENTS"
|
echo "::set-output name=cron-components::$CRON_COMPONENTS"
|
||||||
|
|
@ -247,6 +239,10 @@ jobs:
|
||||||
- name: Start kafka
|
- name: Start kafka
|
||||||
run: docker-compose -f ./.github/infrastructure/docker-compose-kafka.yml -p kafka up -d
|
run: docker-compose -f ./.github/infrastructure/docker-compose-kafka.yml -p kafka up -d
|
||||||
if: contains(matrix.component, 'kafka')
|
if: contains(matrix.component, 'kafka')
|
||||||
|
|
||||||
|
- name: Start memcached
|
||||||
|
run: docker-compose -f ./.github/infrastructure/docker-compose-memcached.yml -p memcached up -d
|
||||||
|
if: contains(matrix.component, 'memcached')
|
||||||
|
|
||||||
- name: Start natsstreaming
|
- name: Start natsstreaming
|
||||||
run: docker-compose -f ./.github/infrastructure/docker-compose-natsstreaming.yml -p natsstreaming up -d
|
run: docker-compose -f ./.github/infrastructure/docker-compose-natsstreaming.yml -p natsstreaming up -d
|
||||||
|
|
@ -311,6 +307,11 @@ jobs:
|
||||||
docker-compose -f ./.github/infrastructure/docker-compose-cockroachdb.yml -p cockroachdb up -d
|
docker-compose -f ./.github/infrastructure/docker-compose-cockroachdb.yml -p cockroachdb up -d
|
||||||
if: contains(matrix.component, 'cockroachdb')
|
if: contains(matrix.component, 'cockroachdb')
|
||||||
|
|
||||||
|
- name: Start rethinkdb
|
||||||
|
run: |
|
||||||
|
docker-compose -f ./.github/infrastructure/docker-compose-rethinkdb.yml -p rethinkdb up -d
|
||||||
|
if: contains(matrix.component, 'rethinkdb')
|
||||||
|
|
||||||
- name: Setup KinD test data
|
- name: Setup KinD test data
|
||||||
if: contains(matrix.component, 'kubernetes')
|
if: contains(matrix.component, 'kubernetes')
|
||||||
run: |
|
run: |
|
||||||
|
|
@ -320,7 +321,7 @@ jobs:
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: '^1.18'
|
go-version: '^1.19'
|
||||||
|
|
||||||
- name: Download Go dependencies
|
- name: Download Go dependencies
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,12 @@ jobs:
|
||||||
"shubham1172",
|
"shubham1172",
|
||||||
"skyao",
|
"skyao",
|
||||||
"msfussell",
|
"msfussell",
|
||||||
"Taction"
|
"Taction",
|
||||||
|
"RyanLettieri",
|
||||||
|
"DeepanshuA",
|
||||||
|
"yash-nisar",
|
||||||
|
"addjuarez",
|
||||||
|
"tmacam",
|
||||||
];
|
];
|
||||||
const payload = context.payload;
|
const payload = context.payload;
|
||||||
const issue = context.issue;
|
const issue = context.issue;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ run:
|
||||||
concurrency: 4
|
concurrency: 4
|
||||||
|
|
||||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||||
deadline: 5m
|
deadline: 15m
|
||||||
|
|
||||||
# exit code when at least one issue was found, default is 1
|
# exit code when at least one issue was found, default is 1
|
||||||
issues-exit-code: 1
|
issues-exit-code: 1
|
||||||
|
|
@ -244,6 +244,7 @@ linters:
|
||||||
- nestif
|
- nestif
|
||||||
- nlreturn
|
- nlreturn
|
||||||
- exhaustive
|
- exhaustive
|
||||||
|
- exhaustruct
|
||||||
- noctx
|
- noctx
|
||||||
- gci
|
- gci
|
||||||
- golint
|
- golint
|
||||||
|
|
@ -259,7 +260,6 @@ linters:
|
||||||
- godot
|
- godot
|
||||||
- cyclop
|
- cyclop
|
||||||
- varnamelen
|
- varnamelen
|
||||||
- gosec
|
|
||||||
- errorlint
|
- errorlint
|
||||||
- forcetypeassert
|
- forcetypeassert
|
||||||
- ifshort
|
- ifshort
|
||||||
|
|
@ -271,3 +271,8 @@ linters:
|
||||||
- wastedassign
|
- wastedassign
|
||||||
- containedctx
|
- containedctx
|
||||||
- gosimple
|
- gosimple
|
||||||
|
- nonamedreturns
|
||||||
|
- asasalint
|
||||||
|
- rowserrcheck
|
||||||
|
- sqlclosecheck
|
||||||
|
- structcheck
|
||||||
|
|
|
||||||
68
Makefile
68
Makefile
|
|
@ -22,40 +22,76 @@ export GOSUMDB ?= sum.golang.org
|
||||||
GIT_COMMIT = $(shell git rev-list -1 HEAD)
|
GIT_COMMIT = $(shell git rev-list -1 HEAD)
|
||||||
GIT_VERSION = $(shell git describe --always --abbrev=7 --dirty)
|
GIT_VERSION = $(shell git describe --always --abbrev=7 --dirty)
|
||||||
# By default, disable CGO_ENABLED. See the details on https://golang.org/cmd/cgo
|
# By default, disable CGO_ENABLED. See the details on https://golang.org/cmd/cgo
|
||||||
CGO ?= 0
|
CGO ?= 0
|
||||||
|
|
||||||
LOCAL_ARCH := $(shell uname -m)
|
LOCAL_ARCH := $(shell uname -m)
|
||||||
ifeq ($(LOCAL_ARCH),x86_64)
|
ifeq ($(LOCAL_ARCH),x86_64)
|
||||||
TARGET_ARCH_LOCAL=amd64
|
TARGET_ARCH_LOCAL=amd64
|
||||||
else ifeq ($(shell echo $(LOCAL_ARCH) | head -c 5),armv8)
|
else ifeq ($(shell echo $(LOCAL_ARCH) | head -c 5),armv8)
|
||||||
TARGET_ARCH_LOCAL=arm64
|
TARGET_ARCH_LOCAL=arm64
|
||||||
else ifeq ($(shell echo $(LOCAL_ARCH) | head -c 4),armv)
|
else ifeq ($(shell echo $(LOCAL_ARCH) | head -c 4),armv)
|
||||||
TARGET_ARCH_LOCAL=arm
|
TARGET_ARCH_LOCAL=arm
|
||||||
else
|
else
|
||||||
TARGET_ARCH_LOCAL=amd64
|
TARGET_ARCH_LOCAL=amd64
|
||||||
endif
|
endif
|
||||||
export GOARCH ?= $(TARGET_ARCH_LOCAL)
|
export GOARCH ?= $(TARGET_ARCH_LOCAL)
|
||||||
|
|
||||||
LOCAL_OS := $(shell uname)
|
LOCAL_OS := $(shell uname)
|
||||||
ifeq ($(LOCAL_OS),Linux)
|
ifeq ($(LOCAL_OS),Linux)
|
||||||
TARGET_OS_LOCAL = linux
|
TARGET_OS_LOCAL = linux
|
||||||
else ifeq ($(LOCAL_OS),Darwin)
|
else ifeq ($(LOCAL_OS),Darwin)
|
||||||
TARGET_OS_LOCAL = darwin
|
TARGET_OS_LOCAL = darwin
|
||||||
else
|
else
|
||||||
TARGET_OS_LOCAL ?= windows
|
TARGET_OS_LOCAL ?= windows
|
||||||
endif
|
endif
|
||||||
export GOOS ?= $(TARGET_OS_LOCAL)
|
export GOOS ?= $(TARGET_OS_LOCAL)
|
||||||
|
|
||||||
ifeq ($(GOOS),windows)
|
ifeq ($(GOOS),windows)
|
||||||
BINARY_EXT_LOCAL:=.exe
|
FINDBIN := where
|
||||||
GOLANGCI_LINT:=golangci-lint.exe
|
BINARY_EXT_LOCAL:=.exe
|
||||||
# Workaround for https://github.com/golang/go/issues/40795
|
GOLANGCI_LINT:=golangci-lint.exe
|
||||||
BUILDMODE:=-buildmode=exe
|
# Workaround for https://github.com/golang/go/issues/40795
|
||||||
|
BUILDMODE:=-buildmode=exe
|
||||||
else
|
else
|
||||||
BINARY_EXT_LOCAL:=
|
FINDBIN := which
|
||||||
GOLANGCI_LINT:=golangci-lint
|
BINARY_EXT_LOCAL:=
|
||||||
|
GOLANGCI_LINT:=golangci-lint
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# Get linter versions
|
||||||
|
LINTER_BINARY := $(shell $(FINDBIN) $(GOLANGCI_LINT))
|
||||||
|
export GH_LINT_VERSION := $(shell grep 'GOLANGCI_LINT_VER:' .github/workflows/components-contrib.yml | xargs | cut -d" " -f2)
|
||||||
|
ifeq (,$(LINTER_BINARY))
|
||||||
|
INSTALLED_LINT_VERSION := "v0.0.0"
|
||||||
|
else
|
||||||
|
INSTALLED_LINT_VERSION=v$(shell $(LINTER_BINARY) version | grep -Eo '([0-9]+\.)+[0-9]+' - || "")
|
||||||
|
endif
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Linter targets #
|
||||||
|
################################################################################
|
||||||
|
.PHONY: verify-linter-installed
|
||||||
|
verify-linter-installed:
|
||||||
|
@if [ -z $(LINTER_BINARY) ]; then \
|
||||||
|
echo "[!] golangci-lint not installed"; \
|
||||||
|
echo "[!] You can install it from https://golangci-lint.run/usage/install/"; \
|
||||||
|
echo "[!] or by running"; \
|
||||||
|
echo "[!] curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin $(GH_LINT_VERSION)"; \
|
||||||
|
exit 1; \
|
||||||
|
fi;
|
||||||
|
|
||||||
|
.PHONY: verify-linter-version
|
||||||
|
verify-linter-version:
|
||||||
|
@if [ "$(GH_LINT_VERSION)" != "$(INSTALLED_LINT_VERSION)" ]; then \
|
||||||
|
echo "[!] Your locally installed version of golangci-lint is different from the pipeline"; \
|
||||||
|
echo "[!] This will likely cause linting issues for you locally"; \
|
||||||
|
echo "[!] Yours: $(INSTALLED_LINT_VERSION)"; \
|
||||||
|
echo "[!] Theirs: $(GH_LINT_VERSION)"; \
|
||||||
|
echo "[!] Upgrade: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin $(GH_LINT_VERSION)"; \
|
||||||
|
sleep 3; \
|
||||||
|
fi;
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Target: test #
|
# Target: test #
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
@ -67,7 +103,7 @@ test:
|
||||||
# Target: lint #
|
# Target: lint #
|
||||||
################################################################################
|
################################################################################
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint:
|
lint: verify-linter-installed verify-linter-version
|
||||||
# Due to https://github.com/golangci/golangci-lint/issues/580, we need to add --fix for windows
|
# Due to https://github.com/golangci/golangci-lint/issues/580, we need to add --fix for windows
|
||||||
$(GOLANGCI_LINT) run --timeout=20m
|
$(GOLANGCI_LINT) run --timeout=20m
|
||||||
|
|
||||||
|
|
@ -79,7 +115,7 @@ MODFILES := $(shell find . -name go.mod)
|
||||||
define modtidy-target
|
define modtidy-target
|
||||||
.PHONY: modtidy-$(1)
|
.PHONY: modtidy-$(1)
|
||||||
modtidy-$(1):
|
modtidy-$(1):
|
||||||
cd $(shell dirname $(1)); go mod tidy -compat=1.18; cd -
|
cd $(shell dirname $(1)); go mod tidy -compat=1.19; cd -
|
||||||
endef
|
endef
|
||||||
|
|
||||||
# Generate modtidy target action for each go.mod file
|
# Generate modtidy target action for each go.mod file
|
||||||
|
|
|
||||||
|
|
@ -53,12 +53,12 @@ type outgoingWebhook struct {
|
||||||
handler bindings.Handler
|
handler bindings.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
var webhooks = struct { //nolint: gochecknoglobals
|
var webhooks = struct { //nolint:gochecknoglobals
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
m map[string]*outgoingWebhook
|
m map[string]*outgoingWebhook
|
||||||
}{m: make(map[string]*outgoingWebhook)}
|
}{m: make(map[string]*outgoingWebhook)}
|
||||||
|
|
||||||
func NewDingTalkWebhook(l logger.Logger) *DingTalkWebhook {
|
func NewDingTalkWebhook(l logger.Logger) bindings.InputOutputBinding {
|
||||||
// See guidance on proper HTTP client settings here:
|
// See guidance on proper HTTP client settings here:
|
||||||
// https://medium.com/@nate510/don-t-use-go-s-default-http-client-4804cb19f779
|
// https://medium.com/@nate510/don-t-use-go-s-default-http-client-4804cb19f779
|
||||||
dialer := &net.Dialer{ //nolint:exhaustivestruct
|
dialer := &net.Dialer{ //nolint:exhaustivestruct
|
||||||
|
|
@ -162,7 +162,7 @@ func (t *DingTalkWebhook) sendMessage(ctx context.Context, req *bindings.InvokeR
|
||||||
ctx, cancel := context.WithTimeout(ctx, defaultHTTPClientTimeout)
|
ctx, cancel := context.WithTimeout(ctx, defaultHTTPClientTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
httpReq, err := http.NewRequestWithContext(ctx, "POST", postURL, bytes.NewReader(msg))
|
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, postURL, bytes.NewReader(msg))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("dingtalk webhook error: new request failed. %w", err)
|
return fmt.Errorf("dingtalk webhook error: new request failed. %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ package webhook
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/dapr/components-contrib/bindings"
|
"github.com/dapr/components-contrib/bindings"
|
||||||
|
"github.com/dapr/components-contrib/metadata"
|
||||||
"github.com/dapr/kit/logger"
|
"github.com/dapr/kit/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -43,17 +44,17 @@ func TestPublishMsg(t *testing.T) { //nolint:paralleltest
|
||||||
t.Errorf("Expected request to '/test', got '%s'", r.URL.EscapedPath())
|
t.Errorf("Expected request to '/test', got '%s'", r.URL.EscapedPath())
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := io.ReadAll(r.Body)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
assert.Equal(t, msg, string(body))
|
assert.Equal(t, msg, string(body))
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
m := bindings.Metadata{Name: "test", Properties: map[string]string{
|
m := bindings.Metadata{Base: metadata.Base{Name: "test", Properties: map[string]string{
|
||||||
"url": ts.URL + "/test",
|
"url": ts.URL + "/test",
|
||||||
"secret": "",
|
"secret": "",
|
||||||
"id": "x",
|
"id": "x",
|
||||||
}}
|
}}}
|
||||||
|
|
||||||
d := NewDingTalkWebhook(logger.NewLogger("test"))
|
d := NewDingTalkWebhook(logger.NewLogger("test"))
|
||||||
err := d.Init(m)
|
err := d.Init(m)
|
||||||
|
|
@ -67,14 +68,14 @@ func TestPublishMsg(t *testing.T) { //nolint:paralleltest
|
||||||
func TestBindingReadAndInvoke(t *testing.T) { //nolint:paralleltest
|
func TestBindingReadAndInvoke(t *testing.T) { //nolint:paralleltest
|
||||||
msg := "{\"type\": \"text\",\"text\": {\"content\": \"hello\"}}"
|
msg := "{\"type\": \"text\",\"text\": {\"content\": \"hello\"}}"
|
||||||
|
|
||||||
m := bindings.Metadata{
|
m := bindings.Metadata{Base: metadata.Base{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Properties: map[string]string{
|
Properties: map[string]string{
|
||||||
"url": "/test",
|
"url": "/test",
|
||||||
"secret": "",
|
"secret": "",
|
||||||
"id": "x",
|
"id": "x",
|
||||||
},
|
},
|
||||||
}
|
}}
|
||||||
|
|
||||||
d := NewDingTalkWebhook(logger.NewLogger("test"))
|
d := NewDingTalkWebhook(logger.NewLogger("test"))
|
||||||
err := d.Init(m)
|
err := d.Init(m)
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
metadataRpcGroup = "group"
|
metadataRPCGroup = "group"
|
||||||
metadataRpcVersion = "version"
|
metadataRPCVersion = "version"
|
||||||
metadataRpcInterface = "interfaceName"
|
metadataRPCInterface = "interfaceName"
|
||||||
metadataRpcMethodName = "methodName"
|
metadataRPCMethodName = "methodName"
|
||||||
metadataRpcProviderHostname = "providerHostname"
|
metadataRPCProviderHostname = "providerHostname"
|
||||||
metadataRpcProviderPort = "providerPort"
|
metadataRPCProviderPort = "providerPort"
|
||||||
)
|
)
|
||||||
|
|
||||||
type dubboContext struct {
|
type dubboContext struct {
|
||||||
|
|
@ -47,12 +47,12 @@ type dubboContext struct {
|
||||||
|
|
||||||
func newDubboContext(metadata map[string]string) *dubboContext {
|
func newDubboContext(metadata map[string]string) *dubboContext {
|
||||||
dubboMetadata := &dubboContext{}
|
dubboMetadata := &dubboContext{}
|
||||||
dubboMetadata.group = metadata[metadataRpcGroup]
|
dubboMetadata.group = metadata[metadataRPCGroup]
|
||||||
dubboMetadata.interfaceName = metadata[metadataRpcInterface]
|
dubboMetadata.interfaceName = metadata[metadataRPCInterface]
|
||||||
dubboMetadata.version = metadata[metadataRpcVersion]
|
dubboMetadata.version = metadata[metadataRPCVersion]
|
||||||
dubboMetadata.method = metadata[metadataRpcMethodName]
|
dubboMetadata.method = metadata[metadataRPCMethodName]
|
||||||
dubboMetadata.hostname = metadata[metadataRpcProviderHostname]
|
dubboMetadata.hostname = metadata[metadataRPCProviderHostname]
|
||||||
dubboMetadata.port = metadata[metadataRpcProviderPort]
|
dubboMetadata.port = metadata[metadataRPCProviderPort]
|
||||||
dubboMetadata.inited = false
|
dubboMetadata.inited = false
|
||||||
return dubboMetadata
|
return dubboMetadata
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ type DubboOutputBinding struct {
|
||||||
|
|
||||||
var dubboBinding *DubboOutputBinding
|
var dubboBinding *DubboOutputBinding
|
||||||
|
|
||||||
func NewDubboOutput(logger logger.Logger) *DubboOutputBinding {
|
func NewDubboOutput(logger logger.Logger) bindings.OutputBinding {
|
||||||
if dubboBinding == nil {
|
if dubboBinding == nil {
|
||||||
dubboBinding = &DubboOutputBinding{
|
dubboBinding = &DubboOutputBinding{
|
||||||
ctxCache: make(map[string]*dubboContext),
|
ctxCache: make(map[string]*dubboContext),
|
||||||
|
|
|
||||||
|
|
@ -78,10 +78,10 @@ func TestInvoke(t *testing.T) {
|
||||||
// 3. invoke dapr dubbo output binding, get rsp bytes
|
// 3. invoke dapr dubbo output binding, get rsp bytes
|
||||||
rsp, err := output.Invoke(context.Background(), &bindings.InvokeRequest{
|
rsp, err := output.Invoke(context.Background(), &bindings.InvokeRequest{
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
metadataRpcProviderPort: dubboPort,
|
metadataRPCProviderPort: dubboPort,
|
||||||
metadataRpcProviderHostname: localhostIP,
|
metadataRPCProviderHostname: localhostIP,
|
||||||
metadataRpcMethodName: methodName,
|
metadataRPCMethodName: methodName,
|
||||||
metadataRpcInterface: providerInterfaceName,
|
metadataRPCInterface: providerInterfaceName,
|
||||||
},
|
},
|
||||||
Data: reqData,
|
Data: reqData,
|
||||||
Operation: bindings.GetOperation,
|
Operation: bindings.GetOperation,
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
//nolint:nosnakecase
|
||||||
package dubbo
|
package dubbo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
||||||
|
|
@ -54,12 +54,12 @@ type Nacos struct {
|
||||||
watches []configParam
|
watches []configParam
|
||||||
servers []constant.ServerConfig
|
servers []constant.ServerConfig
|
||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
configClient config_client.IConfigClient
|
configClient config_client.IConfigClient //nolint:nosnakecase
|
||||||
readHandler func(ctx context.Context, response *bindings.ReadResponse) ([]byte, error)
|
readHandler func(ctx context.Context, response *bindings.ReadResponse) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewNacos returns a new Nacos instance.
|
// NewNacos returns a new Nacos instance.
|
||||||
func NewNacos(logger logger.Logger) *Nacos {
|
func NewNacos(logger logger.Logger) bindings.OutputBinding {
|
||||||
return &Nacos{
|
return &Nacos{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
watchesLock: sync.Mutex{},
|
watchesLock: sync.Mutex{},
|
||||||
|
|
|
||||||
|
|
@ -25,15 +25,16 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/dapr/components-contrib/bindings"
|
"github.com/dapr/components-contrib/bindings"
|
||||||
|
"github.com/dapr/components-contrib/metadata"
|
||||||
"github.com/dapr/kit/logger"
|
"github.com/dapr/kit/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestInputBindingRead(t *testing.T) { //nolint:paralleltest
|
func TestInputBindingRead(t *testing.T) { //nolint:paralleltest
|
||||||
m := bindings.Metadata{Name: "test", Properties: nil}
|
m := bindings.Metadata{Base: metadata.Base{Name: "test", Properties: nil}}
|
||||||
var err error
|
var err error
|
||||||
m.Properties, err = getNacosLocalCacheMetadata()
|
m.Properties, err = getNacosLocalCacheMetadata()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
n := NewNacos(logger.NewLogger("test"))
|
n := NewNacos(logger.NewLogger("test")).(*Nacos)
|
||||||
err = n.Init(m)
|
err = n.Init(m)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
var count int32
|
var count int32
|
||||||
|
|
@ -68,7 +69,7 @@ func getNacosLocalCacheMetadata() (map[string]string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
cfgFile := path.Join(tmpDir, fmt.Sprintf("%s@@%s@@", dataID, group))
|
cfgFile := path.Join(tmpDir, fmt.Sprintf("%s@@%s@@", dataID, group))
|
||||||
file, err := os.OpenFile(cfgFile, os.O_RDWR|os.O_CREATE, os.ModePerm)
|
file, err := os.OpenFile(cfgFile, os.O_RDWR|os.O_CREATE, os.ModePerm) //nolint:nosnakecase
|
||||||
if err != nil || file == nil {
|
if err != nil || file == nil {
|
||||||
return nil, fmt.Errorf("open %s failed. %w", cfgFile, err)
|
return nil, fmt.Errorf("open %s failed. %w", cfgFile, err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ type ossMetadata struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAliCloudOSS returns a new instance.
|
// NewAliCloudOSS returns a new instance.
|
||||||
func NewAliCloudOSS(logger logger.Logger) *AliCloudOSS {
|
func NewAliCloudOSS(logger logger.Logger) bindings.OutputBinding {
|
||||||
return &AliCloudOSS{logger: logger}
|
return &AliCloudOSS{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -90,11 +90,12 @@ func (a *AliCloudRocketMQ) Read(ctx context.Context, handler bindings.Handler) e
|
||||||
if topicStr == "" {
|
if topicStr == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
mqType, mqExpression, topic, err := parseTopic(topicStr)
|
|
||||||
if err != nil {
|
var mqType, mqExpression, topic string
|
||||||
|
if mqType, mqExpression, topic, err = parseTopic(topicStr); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := consumer.Subscribe(
|
if err = consumer.Subscribe(
|
||||||
topic,
|
topic,
|
||||||
mqc.MessageSelector{
|
mqc.MessageSelector{
|
||||||
Type: mqc.ExpressionType(mqType),
|
Type: mqc.ExpressionType(mqType),
|
||||||
|
|
@ -106,7 +107,7 @@ func (a *AliCloudRocketMQ) Read(ctx context.Context, handler bindings.Handler) e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := consumer.Start(); err != nil {
|
if err = consumer.Start(); err != nil {
|
||||||
return fmt.Errorf("binding-rocketmq: consumer start failed. %w", err)
|
return fmt.Errorf("binding-rocketmq: consumer start failed. %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,7 +122,7 @@ func (a *AliCloudRocketMQ) Read(ctx context.Context, handler bindings.Handler) e
|
||||||
|
|
||||||
innerErr := consumer.Shutdown()
|
innerErr := consumer.Shutdown()
|
||||||
if innerErr != nil && !errors.Is(innerErr, context.Canceled) {
|
if innerErr != nil && !errors.Is(innerErr, context.Canceled) {
|
||||||
a.logger.Warnf("binding-rocketmq: error while shutting down consumer: %v")
|
a.logger.Warnf("binding-rocketmq: error while shutting down consumer: %v", innerErr)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
package sls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
sls "github.com/aliyun/aliyun-log-go-sdk"
|
||||||
|
"github.com/aliyun/aliyun-log-go-sdk/producer"
|
||||||
|
|
||||||
|
"github.com/dapr/components-contrib/bindings"
|
||||||
|
"github.com/dapr/kit/config"
|
||||||
|
"github.com/dapr/kit/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AliCloudSlsLogstorage struct {
|
||||||
|
logger logger.Logger
|
||||||
|
producer *producer.Producer
|
||||||
|
metadata SlsLogstorageMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
type SlsLogstorageMetadata struct {
|
||||||
|
Endpoint string `json:"endpoint"`
|
||||||
|
AccessKeyID string `json:"accessKeyID"`
|
||||||
|
AccessKeySecret string `json:"accessKeySecret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Callback struct {
|
||||||
|
s *AliCloudSlsLogstorage
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse metadata field
|
||||||
|
func (s *AliCloudSlsLogstorage) Init(metadata bindings.Metadata) error {
|
||||||
|
m, err := s.parseMeta(metadata)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.metadata = *m
|
||||||
|
producerConfig := producer.GetDefaultProducerConfig()
|
||||||
|
// the config properties in the component yaml file
|
||||||
|
producerConfig.Endpoint = m.Endpoint
|
||||||
|
producerConfig.AccessKeyID = m.AccessKeyID
|
||||||
|
producerConfig.AccessKeySecret = m.AccessKeySecret
|
||||||
|
s.producer = producer.InitProducer(producerConfig)
|
||||||
|
|
||||||
|
s.producer.Start()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAliCloudSlsLogstorage(logger logger.Logger) bindings.OutputBinding {
|
||||||
|
logger.Debug("initialized Sls log storage binding component")
|
||||||
|
s := &AliCloudSlsLogstorage{
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AliCloudSlsLogstorage) Invoke(ctx context.Context, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
|
||||||
|
// verify the metadata property
|
||||||
|
if logProject := req.Metadata["project"]; logProject == "" {
|
||||||
|
return nil, fmt.Errorf("SLS binding error: project property not supplied")
|
||||||
|
}
|
||||||
|
if logstore := req.Metadata["logstore"]; logstore == "" {
|
||||||
|
return nil, fmt.Errorf("SLS binding error: logstore property not supplied")
|
||||||
|
}
|
||||||
|
if topic := req.Metadata["topic"]; topic == "" {
|
||||||
|
return nil, fmt.Errorf("SLS binding error: topic property not supplied")
|
||||||
|
}
|
||||||
|
if source := req.Metadata["source"]; source == "" {
|
||||||
|
return nil, fmt.Errorf("SLS binding error: source property not supplied")
|
||||||
|
}
|
||||||
|
|
||||||
|
log, err := s.parseLog(req)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Info(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Debug(log)
|
||||||
|
callBack := &Callback{}
|
||||||
|
err = s.producer.SendLogWithCallBack(req.Metadata["project"], req.Metadata["logstore"], req.Metadata["topic"], req.Metadata["source"], log, callBack)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Info(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse the log content
|
||||||
|
func (s *AliCloudSlsLogstorage) parseLog(req *bindings.InvokeRequest) (*sls.Log, error) {
|
||||||
|
var logInfo map[string]string
|
||||||
|
err := json.Unmarshal(req.Data, &logInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return producer.GenerateLog(uint32(time.Now().Unix()), logInfo), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AliCloudSlsLogstorage) parseMeta(metadata bindings.Metadata) (*SlsLogstorageMetadata, error) {
|
||||||
|
var m SlsLogstorageMetadata
|
||||||
|
err := config.Decode(metadata.Properties, &m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AliCloudSlsLogstorage) Operations() []bindings.OperationKind {
|
||||||
|
return []bindings.OperationKind{bindings.CreateOperation}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (callback *Callback) Success(result *producer.Result) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (callback *Callback) Fail(result *producer.Result) {
|
||||||
|
msg := "unknown reason"
|
||||||
|
if result.GetErrorMessage() != "" {
|
||||||
|
msg = result.GetErrorMessage()
|
||||||
|
}
|
||||||
|
if result.GetErrorCode() != "" {
|
||||||
|
callback.s.logger.Debug("Failed error code:", result.GetErrorCode())
|
||||||
|
}
|
||||||
|
|
||||||
|
callback.s.logger.Info("Log storage failed:", msg)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
package sls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/dapr/components-contrib/bindings"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* test the metadata in the yaml file
|
||||||
|
*/
|
||||||
|
func TestSlsLogstorageMetadata(t *testing.T) {
|
||||||
|
m := bindings.Metadata{}
|
||||||
|
m.Properties = map[string]string{
|
||||||
|
"Endpoint": "ENDPOINT",
|
||||||
|
"AccessKeyID": "ACCESSKEYID",
|
||||||
|
"AccessKeySecret": "ACCESSKEYSECRET",
|
||||||
|
}
|
||||||
|
aliCloudSlsLogstorage := AliCloudSlsLogstorage{}
|
||||||
|
|
||||||
|
meta, err := aliCloudSlsLogstorage.parseMeta(m)
|
||||||
|
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "ENDPOINT", meta.Endpoint)
|
||||||
|
assert.Equal(t, "ACCESSKEYID", meta.AccessKeyID)
|
||||||
|
assert.Equal(t, "ACCESSKEYSECRET", meta.AccessKeySecret)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* test the log content
|
||||||
|
*/
|
||||||
|
func TestParseLog(t *testing.T) {
|
||||||
|
aliCloudSlsLogstorage := AliCloudSlsLogstorage{}
|
||||||
|
d, _ := json.Marshal(map[string]string{
|
||||||
|
"log1": "LOG1",
|
||||||
|
"log2": "LOG2",
|
||||||
|
})
|
||||||
|
log := bindings.InvokeRequest{
|
||||||
|
Data: d,
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"project": "PROJECT",
|
||||||
|
"logstore": "LOGSTORE",
|
||||||
|
"topic": "TOPIC",
|
||||||
|
"source": "SOURCE",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
parseLog, _ := aliCloudSlsLogstorage.parseLog(&log)
|
||||||
|
for _, v := range parseLog.Contents {
|
||||||
|
switch *v.Key {
|
||||||
|
case "log1":
|
||||||
|
assert.Equal(t, "LOG1", *v.Value)
|
||||||
|
case "log2":
|
||||||
|
assert.Equal(t, "LOG2", *v.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -51,7 +51,7 @@ type AliCloudTableStore struct {
|
||||||
metadata tablestoreMetadata
|
metadata tablestoreMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAliCloudTableStore(log logger.Logger) *AliCloudTableStore {
|
func NewAliCloudTableStore(log logger.Logger) bindings.OutputBinding {
|
||||||
return &AliCloudTableStore{
|
return &AliCloudTableStore{
|
||||||
logger: log,
|
logger: log,
|
||||||
client: nil,
|
client: nil,
|
||||||
|
|
@ -262,11 +262,11 @@ func (s *AliCloudTableStore) create(req *bindings.InvokeRequest, resp *bindings.
|
||||||
TableName: s.getTableName(req.Metadata),
|
TableName: s.getTableName(req.Metadata),
|
||||||
PrimaryKey: &tablestore.PrimaryKey{PrimaryKeys: pks},
|
PrimaryKey: &tablestore.PrimaryKey{PrimaryKeys: pks},
|
||||||
Columns: columns,
|
Columns: columns,
|
||||||
ReturnType: tablestore.ReturnType_RT_NONE,
|
ReturnType: tablestore.ReturnType_RT_NONE, //nolint:nosnakecase
|
||||||
TransactionId: nil,
|
TransactionId: nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
change.SetCondition(tablestore.RowExistenceExpectation_IGNORE)
|
change.SetCondition(tablestore.RowExistenceExpectation_IGNORE) //nolint:nosnakecase
|
||||||
|
|
||||||
putRequest := &tablestore.PutRowRequest{
|
putRequest := &tablestore.PutRowRequest{
|
||||||
PutRowChange: &change,
|
PutRowChange: &change,
|
||||||
|
|
@ -301,7 +301,7 @@ func (s *AliCloudTableStore) delete(req *bindings.InvokeRequest, resp *bindings.
|
||||||
TableName: s.getTableName(req.Metadata),
|
TableName: s.getTableName(req.Metadata),
|
||||||
PrimaryKey: &tablestore.PrimaryKey{PrimaryKeys: pks},
|
PrimaryKey: &tablestore.PrimaryKey{PrimaryKeys: pks},
|
||||||
}
|
}
|
||||||
change.SetCondition(tablestore.RowExistenceExpectation_IGNORE)
|
change.SetCondition(tablestore.RowExistenceExpectation_IGNORE) //nolint:nosnakecase
|
||||||
deleteReq := &tablestore.DeleteRowRequest{DeleteRowChange: change}
|
deleteReq := &tablestore.DeleteRowRequest{DeleteRowChange: change}
|
||||||
_, err = s.client.DeleteRow(deleteReq)
|
_, err = s.client.DeleteRow(deleteReq)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/dapr/components-contrib/bindings"
|
"github.com/dapr/components-contrib/bindings"
|
||||||
|
"github.com/dapr/components-contrib/metadata"
|
||||||
"github.com/dapr/kit/logger"
|
"github.com/dapr/kit/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -47,9 +48,9 @@ func TestDataEncodeAndDecode(t *testing.T) {
|
||||||
|
|
||||||
aliCloudTableStore := NewAliCloudTableStore(logger.NewLogger("test"))
|
aliCloudTableStore := NewAliCloudTableStore(logger.NewLogger("test"))
|
||||||
|
|
||||||
metadata := bindings.Metadata{
|
metadata := bindings.Metadata{Base: metadata.Base{
|
||||||
Properties: getTestProperties(),
|
Properties: getTestProperties(),
|
||||||
}
|
}}
|
||||||
aliCloudTableStore.Init(metadata)
|
aliCloudTableStore.Init(metadata)
|
||||||
|
|
||||||
// test create
|
// test create
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ type APNS struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAPNS will create a new APNS output binding.
|
// NewAPNS will create a new APNS output binding.
|
||||||
func NewAPNS(logger logger.Logger) *APNS {
|
func NewAPNS(logger logger.Logger) bindings.OutputBinding {
|
||||||
return &APNS{
|
return &APNS{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
client: &http.Client{},
|
client: &http.Client{},
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ package apns
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/dapr/components-contrib/bindings"
|
"github.com/dapr/components-contrib/bindings"
|
||||||
|
"github.com/dapr/components-contrib/metadata"
|
||||||
"github.com/dapr/kit/logger"
|
"github.com/dapr/kit/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -41,136 +42,136 @@ func TestInit(t *testing.T) {
|
||||||
testLogger := logger.NewLogger("test")
|
testLogger := logger.NewLogger("test")
|
||||||
|
|
||||||
t.Run("uses the development service", func(t *testing.T) {
|
t.Run("uses the development service", func(t *testing.T) {
|
||||||
metadata := bindings.Metadata{
|
metadata := bindings.Metadata{Base: metadata.Base{
|
||||||
Properties: map[string]string{
|
Properties: map[string]string{
|
||||||
developmentKey: "true",
|
developmentKey: "true",
|
||||||
keyIDKey: testKeyID,
|
keyIDKey: testKeyID,
|
||||||
teamIDKey: testTeamID,
|
teamIDKey: testTeamID,
|
||||||
privateKeyKey: testPrivateKey,
|
privateKeyKey: testPrivateKey,
|
||||||
},
|
},
|
||||||
}
|
}}
|
||||||
binding := NewAPNS(testLogger)
|
binding := NewAPNS(testLogger).(*APNS)
|
||||||
err := binding.Init(metadata)
|
err := binding.Init(metadata)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, developmentPrefix, binding.urlPrefix)
|
assert.Equal(t, developmentPrefix, binding.urlPrefix)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("uses the production service", func(t *testing.T) {
|
t.Run("uses the production service", func(t *testing.T) {
|
||||||
metadata := bindings.Metadata{
|
metadata := bindings.Metadata{Base: metadata.Base{
|
||||||
Properties: map[string]string{
|
Properties: map[string]string{
|
||||||
developmentKey: "false",
|
developmentKey: "false",
|
||||||
keyIDKey: testKeyID,
|
keyIDKey: testKeyID,
|
||||||
teamIDKey: testTeamID,
|
teamIDKey: testTeamID,
|
||||||
privateKeyKey: testPrivateKey,
|
privateKeyKey: testPrivateKey,
|
||||||
},
|
},
|
||||||
}
|
}}
|
||||||
binding := NewAPNS(testLogger)
|
binding := NewAPNS(testLogger).(*APNS)
|
||||||
err := binding.Init(metadata)
|
err := binding.Init(metadata)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, productionPrefix, binding.urlPrefix)
|
assert.Equal(t, productionPrefix, binding.urlPrefix)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("defaults to the production service", func(t *testing.T) {
|
t.Run("defaults to the production service", func(t *testing.T) {
|
||||||
metadata := bindings.Metadata{
|
metadata := bindings.Metadata{Base: metadata.Base{
|
||||||
Properties: map[string]string{
|
Properties: map[string]string{
|
||||||
keyIDKey: testKeyID,
|
keyIDKey: testKeyID,
|
||||||
teamIDKey: testTeamID,
|
teamIDKey: testTeamID,
|
||||||
privateKeyKey: testPrivateKey,
|
privateKeyKey: testPrivateKey,
|
||||||
},
|
},
|
||||||
}
|
}}
|
||||||
binding := NewAPNS(testLogger)
|
binding := NewAPNS(testLogger).(*APNS)
|
||||||
err := binding.Init(metadata)
|
err := binding.Init(metadata)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, productionPrefix, binding.urlPrefix)
|
assert.Equal(t, productionPrefix, binding.urlPrefix)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("invalid development value", func(t *testing.T) {
|
t.Run("invalid development value", func(t *testing.T) {
|
||||||
metadata := bindings.Metadata{
|
metadata := bindings.Metadata{Base: metadata.Base{
|
||||||
Properties: map[string]string{
|
Properties: map[string]string{
|
||||||
developmentKey: "True",
|
developmentKey: "True",
|
||||||
keyIDKey: testKeyID,
|
keyIDKey: testKeyID,
|
||||||
teamIDKey: testTeamID,
|
teamIDKey: testTeamID,
|
||||||
privateKeyKey: testPrivateKey,
|
privateKeyKey: testPrivateKey,
|
||||||
},
|
},
|
||||||
}
|
}}
|
||||||
binding := NewAPNS(testLogger)
|
binding := NewAPNS(testLogger).(*APNS)
|
||||||
err := binding.Init(metadata)
|
err := binding.Init(metadata)
|
||||||
assert.Error(t, err, "invalid value for development parameter: True")
|
assert.Error(t, err, "invalid value for development parameter: True")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("the key ID is required", func(t *testing.T) {
|
t.Run("the key ID is required", func(t *testing.T) {
|
||||||
metadata := bindings.Metadata{
|
metadata := bindings.Metadata{Base: metadata.Base{
|
||||||
Properties: map[string]string{
|
Properties: map[string]string{
|
||||||
teamIDKey: testTeamID,
|
teamIDKey: testTeamID,
|
||||||
privateKeyKey: testPrivateKey,
|
privateKeyKey: testPrivateKey,
|
||||||
},
|
},
|
||||||
}
|
}}
|
||||||
binding := NewAPNS(testLogger)
|
binding := NewAPNS(testLogger).(*APNS)
|
||||||
err := binding.Init(metadata)
|
err := binding.Init(metadata)
|
||||||
assert.Error(t, err, "the key-id parameter is required")
|
assert.Error(t, err, "the key-id parameter is required")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("valid key ID", func(t *testing.T) {
|
t.Run("valid key ID", func(t *testing.T) {
|
||||||
metadata := bindings.Metadata{
|
metadata := bindings.Metadata{Base: metadata.Base{
|
||||||
Properties: map[string]string{
|
Properties: map[string]string{
|
||||||
keyIDKey: testKeyID,
|
keyIDKey: testKeyID,
|
||||||
teamIDKey: testTeamID,
|
teamIDKey: testTeamID,
|
||||||
privateKeyKey: testPrivateKey,
|
privateKeyKey: testPrivateKey,
|
||||||
},
|
},
|
||||||
}
|
}}
|
||||||
binding := NewAPNS(testLogger)
|
binding := NewAPNS(testLogger).(*APNS)
|
||||||
err := binding.Init(metadata)
|
err := binding.Init(metadata)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, testKeyID, binding.authorizationBuilder.keyID)
|
assert.Equal(t, testKeyID, binding.authorizationBuilder.keyID)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("the team ID is required", func(t *testing.T) {
|
t.Run("the team ID is required", func(t *testing.T) {
|
||||||
metadata := bindings.Metadata{
|
metadata := bindings.Metadata{Base: metadata.Base{
|
||||||
Properties: map[string]string{
|
Properties: map[string]string{
|
||||||
keyIDKey: testKeyID,
|
keyIDKey: testKeyID,
|
||||||
privateKeyKey: testPrivateKey,
|
privateKeyKey: testPrivateKey,
|
||||||
},
|
},
|
||||||
}
|
}}
|
||||||
binding := NewAPNS(testLogger)
|
binding := NewAPNS(testLogger).(*APNS)
|
||||||
err := binding.Init(metadata)
|
err := binding.Init(metadata)
|
||||||
assert.Error(t, err, "the team-id parameter is required")
|
assert.Error(t, err, "the team-id parameter is required")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("valid team ID", func(t *testing.T) {
|
t.Run("valid team ID", func(t *testing.T) {
|
||||||
metadata := bindings.Metadata{
|
metadata := bindings.Metadata{Base: metadata.Base{
|
||||||
Properties: map[string]string{
|
Properties: map[string]string{
|
||||||
keyIDKey: testKeyID,
|
keyIDKey: testKeyID,
|
||||||
teamIDKey: testTeamID,
|
teamIDKey: testTeamID,
|
||||||
privateKeyKey: testPrivateKey,
|
privateKeyKey: testPrivateKey,
|
||||||
},
|
},
|
||||||
}
|
}}
|
||||||
binding := NewAPNS(testLogger)
|
binding := NewAPNS(testLogger).(*APNS)
|
||||||
err := binding.Init(metadata)
|
err := binding.Init(metadata)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, testTeamID, binding.authorizationBuilder.teamID)
|
assert.Equal(t, testTeamID, binding.authorizationBuilder.teamID)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("the private key is required", func(t *testing.T) {
|
t.Run("the private key is required", func(t *testing.T) {
|
||||||
metadata := bindings.Metadata{
|
metadata := bindings.Metadata{Base: metadata.Base{
|
||||||
Properties: map[string]string{
|
Properties: map[string]string{
|
||||||
keyIDKey: testKeyID,
|
keyIDKey: testKeyID,
|
||||||
teamIDKey: testTeamID,
|
teamIDKey: testTeamID,
|
||||||
},
|
},
|
||||||
}
|
}}
|
||||||
binding := NewAPNS(testLogger)
|
binding := NewAPNS(testLogger).(*APNS)
|
||||||
err := binding.Init(metadata)
|
err := binding.Init(metadata)
|
||||||
assert.Error(t, err, "the private-key parameter is required")
|
assert.Error(t, err, "the private-key parameter is required")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("valid private key", func(t *testing.T) {
|
t.Run("valid private key", func(t *testing.T) {
|
||||||
metadata := bindings.Metadata{
|
metadata := bindings.Metadata{Base: metadata.Base{
|
||||||
Properties: map[string]string{
|
Properties: map[string]string{
|
||||||
keyIDKey: testKeyID,
|
keyIDKey: testKeyID,
|
||||||
teamIDKey: testTeamID,
|
teamIDKey: testTeamID,
|
||||||
privateKeyKey: testPrivateKey,
|
privateKeyKey: testPrivateKey,
|
||||||
},
|
},
|
||||||
}
|
}}
|
||||||
binding := NewAPNS(testLogger)
|
binding := NewAPNS(testLogger).(*APNS)
|
||||||
err := binding.Init(metadata)
|
err := binding.Init(metadata)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.NotNil(t, binding.authorizationBuilder.privateKey)
|
assert.NotNil(t, binding.authorizationBuilder.privateKey)
|
||||||
|
|
@ -179,7 +180,7 @@ func TestInit(t *testing.T) {
|
||||||
|
|
||||||
func TestOperations(t *testing.T) {
|
func TestOperations(t *testing.T) {
|
||||||
testLogger := logger.NewLogger("test")
|
testLogger := logger.NewLogger("test")
|
||||||
testBinding := NewAPNS(testLogger)
|
testBinding := NewAPNS(testLogger).(*APNS)
|
||||||
operations := testBinding.Operations()
|
operations := testBinding.Operations()
|
||||||
assert.Equal(t, 1, len(operations))
|
assert.Equal(t, 1, len(operations))
|
||||||
assert.Equal(t, bindings.CreateOperation, operations[0])
|
assert.Equal(t, bindings.CreateOperation, operations[0])
|
||||||
|
|
@ -316,7 +317,7 @@ func TestInvoke(t *testing.T) {
|
||||||
|
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: http.StatusBadRequest,
|
StatusCode: http.StatusBadRequest,
|
||||||
Body: ioutil.NopCloser(strings.NewReader(body)),
|
Body: io.NopCloser(strings.NewReader(body)),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
_, err := testBinding.Invoke(context.TODO(), successRequest)
|
_, err := testBinding.Invoke(context.TODO(), successRequest)
|
||||||
|
|
@ -325,15 +326,15 @@ func TestInvoke(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeTestBinding(t *testing.T, log logger.Logger) *APNS {
|
func makeTestBinding(t *testing.T, log logger.Logger) *APNS {
|
||||||
testBinding := NewAPNS(log)
|
testBinding := NewAPNS(log).(*APNS)
|
||||||
bindingMetadata := bindings.Metadata{
|
bindingMetadata := bindings.Metadata{Base: metadata.Base{
|
||||||
Properties: map[string]string{
|
Properties: map[string]string{
|
||||||
developmentKey: "true",
|
developmentKey: "true",
|
||||||
keyIDKey: testKeyID,
|
keyIDKey: testKeyID,
|
||||||
teamIDKey: testTeamID,
|
teamIDKey: testTeamID,
|
||||||
privateKeyKey: testPrivateKey,
|
privateKeyKey: testPrivateKey,
|
||||||
},
|
},
|
||||||
}
|
}}
|
||||||
err := testBinding.Init(bindingMetadata)
|
err := testBinding.Init(bindingMetadata)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,8 @@ func (a *authorizationBuilder) generateAuthorizationHeader() (string, error) {
|
||||||
a.logger.Debug("Authorization token expired; generating new token")
|
a.logger.Debug("Authorization token expired; generating new token")
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
claims := jwt.StandardClaims{
|
// TODO: Use jwt.RegisteredClaims instead of jwt.StandardClaims.
|
||||||
|
claims := jwt.StandardClaims{ //nolint:staticcheck
|
||||||
IssuedAt: time.Now().Unix(),
|
IssuedAt: time.Now().Unix(),
|
||||||
Issuer: a.teamID,
|
Issuer: a.teamID,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||||
// send push notifications to Apple devices and Mac computers using Apple's
|
// send push notifications to Apple devices and Mac computers using Apple's
|
||||||
// Push Notification Service (APNS).
|
// Push Notification Service (APNS).
|
||||||
//
|
//
|
||||||
// Configuring the Binding
|
// # Configuring the Binding
|
||||||
//
|
//
|
||||||
// To use the APNS output binding, you will need to create the binding
|
// To use the APNS output binding, you will need to create the binding
|
||||||
// configuration and add it to your components directory. The binding
|
// configuration and add it to your components directory. The binding
|
||||||
|
|
@ -31,37 +31,37 @@ limitations under the License.
|
||||||
//
|
//
|
||||||
// A sample configuration file for the APNS binding is shown below:
|
// A sample configuration file for the APNS binding is shown below:
|
||||||
//
|
//
|
||||||
// apiVersion: dapr.io/v1alpha1
|
// apiVersion: dapr.io/v1alpha1
|
||||||
// kind: Component
|
// kind: Component
|
||||||
// metadata:
|
// metadata:
|
||||||
// name: apns
|
// name: apns
|
||||||
// namespace: default
|
// namespace: default
|
||||||
// spec:
|
// spec:
|
||||||
// type: bindings.apns
|
// type: bindings.apns
|
||||||
// metadata:
|
// metadata:
|
||||||
// - name: development
|
// - name: development
|
||||||
// value: false
|
// value: false
|
||||||
// - name: key-id
|
// - name: key-id
|
||||||
// value: PUT-KEY-ID-HERE
|
// value: PUT-KEY-ID-HERE
|
||||||
// - name: team-id
|
// - name: team-id
|
||||||
// value: PUT-APPLE-TEAM-ID-HERE
|
// value: PUT-APPLE-TEAM-ID-HERE
|
||||||
// - name: private-key
|
// - name: private-key
|
||||||
// secretKeyRef:
|
// secretKeyRef:
|
||||||
// name: apns-secrets
|
// name: apns-secrets
|
||||||
// key: private-key
|
// key: private-key
|
||||||
//
|
//
|
||||||
// If using Kubernetes, a sample secret configuration may look like this:
|
// If using Kubernetes, a sample secret configuration may look like this:
|
||||||
//
|
//
|
||||||
// apiVersion: v1
|
// apiVersion: v1
|
||||||
// kind: Secret
|
// kind: Secret
|
||||||
// metadata:
|
// metadata:
|
||||||
// name: apns-secrets
|
// name: apns-secrets
|
||||||
// namespace: default
|
// namespace: default
|
||||||
// stringData:
|
// stringData:
|
||||||
// private-key: |
|
// private-key: |
|
||||||
// -----BEGIN PRIVATE KEY-----
|
// -----BEGIN PRIVATE KEY-----
|
||||||
// KEY-DATA-GOES-HERE
|
// KEY-DATA-GOES-HERE
|
||||||
// -----END PRIVATE KEY-----
|
// -----END PRIVATE KEY-----
|
||||||
//
|
//
|
||||||
// The development parameter can be either "true" or "false". The development
|
// The development parameter can be either "true" or "false". The development
|
||||||
// parameter controls which APNS service is used. If development is set to
|
// parameter controls which APNS service is used. If development is set to
|
||||||
|
|
@ -70,7 +70,7 @@ limitations under the License.
|
||||||
// be used to send push notifications. If not specified, the production service
|
// be used to send push notifications. If not specified, the production service
|
||||||
// will be chosen by default.
|
// will be chosen by default.
|
||||||
//
|
//
|
||||||
// Push Notification Format
|
// # Push Notification Format
|
||||||
//
|
//
|
||||||
// The APNS binding is a pass-through wrapper over the Apple Push Notification
|
// The APNS binding is a pass-through wrapper over the Apple Push Notification
|
||||||
// Service. The APNS binding will send the request directly to the APNS service
|
// Service. The APNS binding will send the request directly to the APNS service
|
||||||
|
|
@ -81,14 +81,14 @@ limitations under the License.
|
||||||
// Requests sent to the APNS binding should be a JSON object. A simple push
|
// Requests sent to the APNS binding should be a JSON object. A simple push
|
||||||
// notification appears below:
|
// notification appears below:
|
||||||
//
|
//
|
||||||
// {
|
// {
|
||||||
// "aps": {
|
// "aps": {
|
||||||
// "alert": {
|
// "alert": {
|
||||||
// "title": "New Updates!",
|
// "title": "New Updates!",
|
||||||
// "body": "New updates are now available for your review."
|
// "body": "New updates are now available for your review."
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// The aps child object contains the push notification details that are used
|
// The aps child object contains the push notification details that are used
|
||||||
// by the Apple Push Notification Service and target devices to route and show
|
// by the Apple Push Notification Service and target devices to route and show
|
||||||
|
|
@ -124,27 +124,27 @@ limitations under the License.
|
||||||
// notifications from a chat room may have the same identifier causing them
|
// notifications from a chat room may have the same identifier causing them
|
||||||
// to show up together in the device's notifications list.
|
// to show up together in the device's notifications list.
|
||||||
//
|
//
|
||||||
// Sending a Push Notification Using the APNS Binding
|
// # Sending a Push Notification Using the APNS Binding
|
||||||
//
|
//
|
||||||
// A simple request to the APNS binding looks like this:
|
// A simple request to the APNS binding looks like this:
|
||||||
//
|
//
|
||||||
// {
|
// {
|
||||||
// "data": {
|
// "data": {
|
||||||
// "aps": {
|
// "aps": {
|
||||||
// "alert": {
|
// "alert": {
|
||||||
// "title": "New Updates!",
|
// "title": "New Updates!",
|
||||||
// "body": "New updates are available for your review."
|
// "body": "New updates are available for your review."
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
// "metadata": {
|
// "metadata": {
|
||||||
// "device-token": "PUT-DEVICE-TOKEN-HERE",
|
// "device-token": "PUT-DEVICE-TOKEN-HERE",
|
||||||
// "apns-push-type": "alert",
|
// "apns-push-type": "alert",
|
||||||
// "apns-priority": "10",
|
// "apns-priority": "10",
|
||||||
// "apns-topic": "com.example.helloworld"
|
// "apns-topic": "com.example.helloworld"
|
||||||
// },
|
// },
|
||||||
// "operation": "create"
|
// "operation": "create"
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// The device-token metadata field is required and should contain the token
|
// The device-token metadata field is required and should contain the token
|
||||||
// for the device that will receive the push notification. Only one device
|
// for the device that will receive the push notification. Only one device
|
||||||
|
|
@ -158,9 +158,9 @@ limitations under the License.
|
||||||
// the apns-id metadata value, then the Apple Push Notification Serivce will
|
// the apns-id metadata value, then the Apple Push Notification Serivce will
|
||||||
// generate a unique ID and will return it.
|
// generate a unique ID and will return it.
|
||||||
//
|
//
|
||||||
// {
|
// {
|
||||||
// "messageID": "12345678-1234-1234-1234-1234567890AB"
|
// "messageID": "12345678-1234-1234-1234-1234567890AB"
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// If the push notification could not be sent due to an authentication error
|
// If the push notification could not be sent due to an authentication error
|
||||||
// or payload error, the error code returned by Apple will be returned. For
|
// or payload error, the error code returned by Apple will be returned. For
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import (
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
|
|
||||||
"github.com/dapr/components-contrib/bindings"
|
"github.com/dapr/components-contrib/bindings"
|
||||||
aws_auth "github.com/dapr/components-contrib/internal/authentication/aws"
|
awsAuth "github.com/dapr/components-contrib/internal/authentication/aws"
|
||||||
"github.com/dapr/kit/logger"
|
"github.com/dapr/kit/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -44,7 +44,7 @@ type dynamoDBMetadata struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDynamoDB returns a new DynamoDB instance.
|
// NewDynamoDB returns a new DynamoDB instance.
|
||||||
func NewDynamoDB(logger logger.Logger) *DynamoDB {
|
func NewDynamoDB(logger logger.Logger) bindings.OutputBinding {
|
||||||
return &DynamoDB{logger: logger}
|
return &DynamoDB{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,7 +104,7 @@ func (d *DynamoDB) getDynamoDBMetadata(spec bindings.Metadata) (*dynamoDBMetadat
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DynamoDB) getClient(metadata *dynamoDBMetadata) (*dynamodb.DynamoDB, error) {
|
func (d *DynamoDB) getClient(metadata *dynamoDBMetadata) (*dynamodb.DynamoDB, error) {
|
||||||
sess, err := aws_auth.GetClient(metadata.AccessKey, metadata.SecretKey, metadata.SessionToken, metadata.Region, metadata.Endpoint)
|
sess, err := awsAuth.GetClient(metadata.AccessKey, metadata.SecretKey, metadata.SessionToken, metadata.Region, metadata.Endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import (
|
||||||
"github.com/vmware/vmware-go-kcl/clientlibrary/worker"
|
"github.com/vmware/vmware-go-kcl/clientlibrary/worker"
|
||||||
|
|
||||||
"github.com/dapr/components-contrib/bindings"
|
"github.com/dapr/components-contrib/bindings"
|
||||||
aws_auth "github.com/dapr/components-contrib/internal/authentication/aws"
|
awsAuth "github.com/dapr/components-contrib/internal/authentication/aws"
|
||||||
"github.com/dapr/kit/logger"
|
"github.com/dapr/kit/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -82,7 +82,7 @@ type recordProcessor struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAWSKinesis returns a new AWS Kinesis instance.
|
// NewAWSKinesis returns a new AWS Kinesis instance.
|
||||||
func NewAWSKinesis(logger logger.Logger) *AWSKinesis {
|
func NewAWSKinesis(logger logger.Logger) bindings.InputOutputBinding {
|
||||||
return &AWSKinesis{logger: logger}
|
return &AWSKinesis{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -313,7 +313,7 @@ func (a *AWSKinesis) waitUntilConsumerExists(ctx aws.Context, input *kinesis.Des
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AWSKinesis) getClient(metadata *kinesisMetadata) (*kinesis.Kinesis, error) {
|
func (a *AWSKinesis) getClient(metadata *kinesisMetadata) (*kinesis.Kinesis, error) {
|
||||||
sess, err := aws_auth.GetClient(metadata.AccessKey, metadata.SecretKey, metadata.SessionToken, metadata.Region, metadata.Endpoint)
|
sess, err := awsAuth.GetClient(metadata.AccessKey, metadata.SecretKey, metadata.SessionToken, metadata.Region, metadata.Endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ import (
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"github.com/dapr/components-contrib/bindings"
|
"github.com/dapr/components-contrib/bindings"
|
||||||
aws_auth "github.com/dapr/components-contrib/internal/authentication/aws"
|
awsAuth "github.com/dapr/components-contrib/internal/authentication/aws"
|
||||||
"github.com/dapr/components-contrib/internal/utils"
|
"github.com/dapr/components-contrib/internal/utils"
|
||||||
"github.com/dapr/kit/logger"
|
"github.com/dapr/kit/logger"
|
||||||
)
|
)
|
||||||
|
|
@ -84,7 +84,7 @@ type listPayload struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAWSS3 returns a new AWSS3 instance.
|
// NewAWSS3 returns a new AWSS3 instance.
|
||||||
func NewAWSS3(logger logger.Logger) *AWSS3 {
|
func NewAWSS3(logger logger.Logger) bindings.OutputBinding {
|
||||||
return &AWSS3{logger: logger}
|
return &AWSS3{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -313,7 +313,7 @@ func (s *AWSS3) parseMetadata(metadata bindings.Metadata) (*s3Metadata, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AWSS3) getSession(metadata *s3Metadata) (*session.Session, error) {
|
func (s *AWSS3) getSession(metadata *s3Metadata) (*session.Session, error) {
|
||||||
sess, err := aws_auth.GetClient(metadata.AccessKey, metadata.SecretKey, metadata.SessionToken, metadata.Region, metadata.Endpoint)
|
sess, err := awsAuth.GetClient(metadata.AccessKey, metadata.SecretKey, metadata.SessionToken, metadata.Region, metadata.Endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@ func TestMergeWithRequestMetadata(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetOption(t *testing.T) {
|
func TestGetOption(t *testing.T) {
|
||||||
s3 := NewAWSS3(logger.NewLogger("s3"))
|
s3 := NewAWSS3(logger.NewLogger("s3")).(*AWSS3)
|
||||||
s3.metadata = &s3Metadata{}
|
s3.metadata = &s3Metadata{}
|
||||||
|
|
||||||
t.Run("return error if key is missing", func(t *testing.T) {
|
t.Run("return error if key is missing", func(t *testing.T) {
|
||||||
|
|
@ -154,7 +154,7 @@ func TestGetOption(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteOption(t *testing.T) {
|
func TestDeleteOption(t *testing.T) {
|
||||||
s3 := NewAWSS3(logger.NewLogger("s3"))
|
s3 := NewAWSS3(logger.NewLogger("s3")).(*AWSS3)
|
||||||
s3.metadata = &s3Metadata{}
|
s3.metadata = &s3Metadata{}
|
||||||
|
|
||||||
t.Run("return error if key is missing", func(t *testing.T) {
|
t.Run("return error if key is missing", func(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import (
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
|
||||||
aws_auth "github.com/dapr/components-contrib/internal/authentication/aws"
|
awsAuth "github.com/dapr/components-contrib/internal/authentication/aws"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/service/ses"
|
"github.com/aws/aws-sdk-go/service/ses"
|
||||||
|
|
||||||
|
|
@ -56,7 +56,7 @@ type sesMetadata struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAWSSES creates a new AWSSES binding instance.
|
// NewAWSSES creates a new AWSSES binding instance.
|
||||||
func NewAWSSES(logger logger.Logger) *AWSSES {
|
func NewAWSSES(logger logger.Logger) bindings.OutputBinding {
|
||||||
return &AWSSES{logger: logger}
|
return &AWSSES{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,7 +192,7 @@ func (metadata sesMetadata) mergeWithRequestMetadata(req *bindings.InvokeRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AWSSES) getClient(metadata *sesMetadata) (*ses.SES, error) {
|
func (a *AWSSES) getClient(metadata *sesMetadata) (*ses.SES, error) {
|
||||||
sess, err := aws_auth.GetClient(metadata.AccessKey, metadata.SecretKey, metadata.SessionToken, metadata.Region, "")
|
sess, err := awsAuth.GetClient(metadata.AccessKey, metadata.SecretKey, metadata.SessionToken, metadata.Region, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("SES binding error: error creating AWS session %w", err)
|
return nil, fmt.Errorf("SES binding error: error creating AWS session %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import (
|
||||||
"github.com/aws/aws-sdk-go/service/sns"
|
"github.com/aws/aws-sdk-go/service/sns"
|
||||||
|
|
||||||
"github.com/dapr/components-contrib/bindings"
|
"github.com/dapr/components-contrib/bindings"
|
||||||
aws_auth "github.com/dapr/components-contrib/internal/authentication/aws"
|
awsAuth "github.com/dapr/components-contrib/internal/authentication/aws"
|
||||||
"github.com/dapr/kit/logger"
|
"github.com/dapr/kit/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -48,7 +48,7 @@ type dataPayload struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAWSSNS creates a new AWSSNS binding instance.
|
// NewAWSSNS creates a new AWSSNS binding instance.
|
||||||
func NewAWSSNS(logger logger.Logger) *AWSSNS {
|
func NewAWSSNS(logger logger.Logger) bindings.OutputBinding {
|
||||||
return &AWSSNS{logger: logger}
|
return &AWSSNS{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,7 +84,7 @@ func (a *AWSSNS) parseMetadata(metadata bindings.Metadata) (*snsMetadata, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AWSSNS) getClient(metadata *snsMetadata) (*sns.SNS, error) {
|
func (a *AWSSNS) getClient(metadata *snsMetadata) (*sns.SNS, error) {
|
||||||
sess, err := aws_auth.GetClient(metadata.AccessKey, metadata.SecretKey, metadata.SessionToken, metadata.Region, metadata.Endpoint)
|
sess, err := awsAuth.GetClient(metadata.AccessKey, metadata.SecretKey, metadata.SessionToken, metadata.Region, metadata.Endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import (
|
||||||
"github.com/aws/aws-sdk-go/service/sqs"
|
"github.com/aws/aws-sdk-go/service/sqs"
|
||||||
|
|
||||||
"github.com/dapr/components-contrib/bindings"
|
"github.com/dapr/components-contrib/bindings"
|
||||||
aws_auth "github.com/dapr/components-contrib/internal/authentication/aws"
|
awsAuth "github.com/dapr/components-contrib/internal/authentication/aws"
|
||||||
"github.com/dapr/kit/logger"
|
"github.com/dapr/kit/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -44,7 +44,7 @@ type sqsMetadata struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAWSSQS returns a new AWS SQS instance.
|
// NewAWSSQS returns a new AWS SQS instance.
|
||||||
func NewAWSSQS(logger logger.Logger) *AWSSQS {
|
func NewAWSSQS(logger logger.Logger) bindings.InputOutputBinding {
|
||||||
return &AWSSQS{logger: logger}
|
return &AWSSQS{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,7 +149,7 @@ func (a *AWSSQS) parseSQSMetadata(metadata bindings.Metadata) (*sqsMetadata, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AWSSQS) getClient(metadata *sqsMetadata) (*sqs.SQS, error) {
|
func (a *AWSSQS) getClient(metadata *sqsMetadata) (*sqs.SQS, error) {
|
||||||
sess, err := aws_auth.GetClient(metadata.AccessKey, metadata.SecretKey, metadata.SessionToken, metadata.Region, metadata.Endpoint)
|
sess, err := awsAuth.GetClient(metadata.AccessKey, metadata.SecretKey, metadata.SessionToken, metadata.Region, metadata.Endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ type listPayload struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAzureBlobStorage returns a new Azure Blob Storage instance.
|
// NewAzureBlobStorage returns a new Azure Blob Storage instance.
|
||||||
func NewAzureBlobStorage(logger logger.Logger) *AzureBlobStorage {
|
func NewAzureBlobStorage(logger logger.Logger) bindings.OutputBinding {
|
||||||
return &AzureBlobStorage{logger: logger}
|
return &AzureBlobStorage{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import (
|
||||||
|
|
||||||
func TestParseMetadata(t *testing.T) {
|
func TestParseMetadata(t *testing.T) {
|
||||||
m := bindings.Metadata{}
|
m := bindings.Metadata{}
|
||||||
blobStorage := NewAzureBlobStorage(logger.NewLogger("test"))
|
blobStorage := NewAzureBlobStorage(logger.NewLogger("test")).(*AzureBlobStorage)
|
||||||
|
|
||||||
t.Run("parse all metadata", func(t *testing.T) {
|
t.Run("parse all metadata", func(t *testing.T) {
|
||||||
m.Properties = map[string]string{
|
m.Properties = map[string]string{
|
||||||
|
|
@ -83,7 +83,7 @@ func TestParseMetadata(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetOption(t *testing.T) {
|
func TestGetOption(t *testing.T) {
|
||||||
blobStorage := NewAzureBlobStorage(logger.NewLogger("test"))
|
blobStorage := NewAzureBlobStorage(logger.NewLogger("test")).(*AzureBlobStorage)
|
||||||
|
|
||||||
t.Run("return error if blobName is missing", func(t *testing.T) {
|
t.Run("return error if blobName is missing", func(t *testing.T) {
|
||||||
r := bindings.InvokeRequest{}
|
r := bindings.InvokeRequest{}
|
||||||
|
|
@ -95,7 +95,7 @@ func TestGetOption(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteOption(t *testing.T) {
|
func TestDeleteOption(t *testing.T) {
|
||||||
blobStorage := NewAzureBlobStorage(logger.NewLogger("test"))
|
blobStorage := NewAzureBlobStorage(logger.NewLogger("test")).(*AzureBlobStorage)
|
||||||
|
|
||||||
t.Run("return error if blobName is missing", func(t *testing.T) {
|
t.Run("return error if blobName is missing", func(t *testing.T) {
|
||||||
r := bindings.InvokeRequest{}
|
r := bindings.InvokeRequest{}
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/a8m/documentdb"
|
"github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos"
|
||||||
backoff "github.com/cenkalti/backoff/v4"
|
|
||||||
|
|
||||||
"github.com/dapr/components-contrib/bindings"
|
"github.com/dapr/components-contrib/bindings"
|
||||||
"github.com/dapr/components-contrib/internal/authentication/azure"
|
"github.com/dapr/components-contrib/internal/authentication/azure"
|
||||||
|
|
@ -30,8 +29,7 @@ import (
|
||||||
|
|
||||||
// CosmosDB allows performing state operations on collections.
|
// CosmosDB allows performing state operations on collections.
|
||||||
type CosmosDB struct {
|
type CosmosDB struct {
|
||||||
client *documentdb.DocumentDB
|
client *azcosmos.ContainerClient
|
||||||
collection string
|
|
||||||
partitionKey string
|
partitionKey string
|
||||||
|
|
||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
|
|
@ -45,10 +43,11 @@ type cosmosDBCredentials struct {
|
||||||
PartitionKey string `json:"partitionKey"`
|
PartitionKey string `json:"partitionKey"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const statusTooManyRequests = "429" // RFC 6585, 4
|
// Value used for timeout durations
|
||||||
|
const timeoutValue = 30
|
||||||
|
|
||||||
// NewCosmosDB returns a new CosmosDB instance.
|
// NewCosmosDB returns a new CosmosDB instance.
|
||||||
func NewCosmosDB(logger logger.Logger) *CosmosDB {
|
func NewCosmosDB(logger logger.Logger) bindings.OutputBinding {
|
||||||
return &CosmosDB{logger: logger}
|
return &CosmosDB{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,57 +61,43 @@ func (c *CosmosDB) Init(metadata bindings.Metadata) error {
|
||||||
c.partitionKey = m.PartitionKey
|
c.partitionKey = m.PartitionKey
|
||||||
|
|
||||||
// Create the client; first, try authenticating with a master key, if present
|
// Create the client; first, try authenticating with a master key, if present
|
||||||
var config *documentdb.Config
|
var client *azcosmos.Client
|
||||||
if m.MasterKey != "" {
|
if m.MasterKey != "" {
|
||||||
config = documentdb.NewConfig(&documentdb.Key{
|
cred, keyErr := azcosmos.NewKeyCredential(m.MasterKey)
|
||||||
Key: m.MasterKey,
|
if keyErr != nil {
|
||||||
})
|
return keyErr
|
||||||
|
}
|
||||||
|
client, err = azcosmos.NewClientWithKey(m.URL, cred, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback to using Azure AD
|
// Fallback to using Azure AD
|
||||||
env, errB := azure.NewEnvironmentSettings("cosmosdb", metadata.Properties)
|
env, errEnv := azure.NewEnvironmentSettings("cosmosdb", metadata.Properties)
|
||||||
if errB != nil {
|
if errEnv != nil {
|
||||||
return errB
|
return errEnv
|
||||||
}
|
}
|
||||||
spt, errB := env.GetServicePrincipalToken()
|
token, errToken := env.GetTokenCredential()
|
||||||
if errB != nil {
|
if errToken != nil {
|
||||||
return errB
|
return errToken
|
||||||
}
|
}
|
||||||
config = documentdb.NewConfigWithServicePrincipal(spt)
|
client, err = azcosmos.NewClient(m.URL, token, nil)
|
||||||
}
|
|
||||||
// disable the identification hydrator (which autogenerates IDs if missing from the request)
|
|
||||||
// so we aren't forced to use a struct by the upstream SDK
|
|
||||||
// this allows us to provide the most flexibility in the request document sent to this binding
|
|
||||||
config.IdentificationHydrator = nil
|
|
||||||
config.WithAppIdentifier("dapr-" + logger.DaprVersion)
|
|
||||||
|
|
||||||
c.client = documentdb.New(m.URL, config)
|
|
||||||
|
|
||||||
// Retries initializing the client if a TooManyRequests error is encountered
|
|
||||||
err = retryOperation(func() (err error) {
|
|
||||||
collLink := fmt.Sprintf("dbs/%s/colls/%s/", m.Database, m.Collection)
|
|
||||||
coll, err := c.client.ReadCollection(collLink)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isTooManyRequestsError(err) {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
return backoff.Permanent(err)
|
|
||||||
} else if coll == nil || coll.Self == "" {
|
|
||||||
return backoff.Permanent(
|
|
||||||
fmt.Errorf("collection %s in database %s for CosmosDB state store not found. This must be created before Dapr uses it", m.Collection, m.Database),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
c.collection = coll.Self
|
// Create a container client
|
||||||
|
dbContainer, err := client.NewContainer(m.Database, m.Collection)
|
||||||
return nil
|
|
||||||
}, func(err error, d time.Duration) {
|
|
||||||
c.logger.Warnf("CosmosDB binding initialization failed: %v; retrying in %s", err, d)
|
|
||||||
}, 5*time.Minute)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
c.client = dbContainer
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), timeoutValue*time.Second)
|
||||||
|
_, err = c.client.Read(ctx, nil)
|
||||||
|
cancel()
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CosmosDB) parseMetadata(metadata bindings.Metadata) (*cosmosDBCredentials, error) {
|
func (c *CosmosDB) parseMetadata(metadata bindings.Metadata) (*cosmosDBCredentials, error) {
|
||||||
|
|
@ -135,7 +120,7 @@ func (c *CosmosDB) Operations() []bindings.OperationKind {
|
||||||
return []bindings.OperationKind{bindings.CreateOperation}
|
return []bindings.OperationKind{bindings.CreateOperation}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CosmosDB) Invoke(_ context.Context, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
|
func (c *CosmosDB) Invoke(ctx context.Context, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
|
||||||
switch req.Operation {
|
switch req.Operation {
|
||||||
case bindings.CreateOperation:
|
case bindings.CreateOperation:
|
||||||
var obj interface{}
|
var obj interface{}
|
||||||
|
|
@ -144,41 +129,34 @@ func (c *CosmosDB) Invoke(_ context.Context, req *bindings.InvokeRequest) (*bind
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
val, err := c.getPartitionKeyValue(c.partitionKey, obj)
|
pkString, err := c.getPartitionKeyValue(c.partitionKey, obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
pk := azcosmos.NewPartitionKeyString(pkString)
|
||||||
|
|
||||||
err = retryOperation(func() error {
|
_, err = c.client.CreateItem(ctx, pk, req.Data, nil)
|
||||||
_, innerErr := c.client.CreateDocument(c.collection, obj, documentdb.PartitionKey(val))
|
|
||||||
if innerErr != nil {
|
|
||||||
if isTooManyRequestsError(innerErr) {
|
|
||||||
return innerErr
|
|
||||||
}
|
|
||||||
return backoff.Permanent(innerErr)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, func(err error, d time.Duration) {
|
|
||||||
c.logger.Warnf("CosmosDB binding Invoke request failed: %v; retrying in %s", err, d)
|
|
||||||
}, 20*time.Second)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("operation kind %s not supported", req.Operation)
|
return nil, fmt.Errorf("operation kind %s not supported", req.Operation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CosmosDB) getPartitionKeyValue(key string, obj interface{}) (interface{}, error) {
|
func (c *CosmosDB) getPartitionKeyValue(key string, obj interface{}) (string, error) {
|
||||||
val, err := c.lookup(obj.(map[string]interface{}), strings.Split(key, "."))
|
valI, err := c.lookup(obj.(map[string]interface{}), strings.Split(key, "."))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("missing partitionKey field %s from request body - %w", c.partitionKey, err)
|
return "", fmt.Errorf("missing partitionKey field %s from request body - %w", c.partitionKey, err)
|
||||||
|
}
|
||||||
|
val, ok := valI.(string)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("partition key is not a string")
|
||||||
}
|
}
|
||||||
|
|
||||||
if val == "" {
|
if val == "" {
|
||||||
return nil, fmt.Errorf("partitionKey field %s from request body is empty", c.partitionKey)
|
return "", fmt.Errorf("partitionKey field %s from request body is empty", c.partitionKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
return val, nil
|
return val, nil
|
||||||
|
|
@ -209,24 +187,3 @@ func (c *CosmosDB) lookup(m map[string]interface{}, ks []string) (val interface{
|
||||||
|
|
||||||
return c.lookup(m, ks[1:])
|
return c.lookup(m, ks[1:])
|
||||||
}
|
}
|
||||||
|
|
||||||
func retryOperation(operation backoff.Operation, notify backoff.Notify, maxElapsedTime time.Duration) error {
|
|
||||||
bo := backoff.NewExponentialBackOff()
|
|
||||||
bo.InitialInterval = 2 * time.Second
|
|
||||||
bo.MaxElapsedTime = maxElapsedTime
|
|
||||||
return backoff.RetryNotify(operation, bo, notify)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isTooManyRequestsError(err error) bool {
|
|
||||||
if err == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if requestError, ok := err.(*documentdb.RequestError); ok {
|
|
||||||
if requestError.Code == statusTooManyRequests {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ type cosmosDBGremlinAPICredentials struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCosmosDBGremlinAPI returns a new CosmosDBGremlinAPI instance.
|
// NewCosmosDBGremlinAPI returns a new CosmosDBGremlinAPI instance.
|
||||||
func NewCosmosDBGremlinAPI(logger logger.Logger) *CosmosDBGremlinAPI {
|
func NewCosmosDBGremlinAPI(logger logger.Logger) bindings.OutputBinding {
|
||||||
return &CosmosDBGremlinAPI{logger: logger}
|
return &CosmosDBGremlinAPI{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Azure/azure-sdk-for-go/services/eventgrid/mgmt/2021-12-01/eventgrid"
|
"github.com/Azure/azure-sdk-for-go/services/eventgrid/mgmt/2021-12-01/eventgrid"
|
||||||
|
|
@ -58,7 +58,7 @@ type azureEventGridMetadata struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAzureEventGrid returns a new Azure Event Grid instance.
|
// NewAzureEventGrid returns a new Azure Event Grid instance.
|
||||||
func NewAzureEventGrid(logger logger.Logger) *AzureEventGrid {
|
func NewAzureEventGrid(logger logger.Logger) bindings.InputOutputBinding {
|
||||||
return &AzureEventGrid{logger: logger}
|
return &AzureEventGrid{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -279,7 +279,7 @@ func (a *AzureEventGrid) createSubscription(ctx context.Context) error {
|
||||||
res := result.FutureAPI.Response()
|
res := result.FutureAPI.Response()
|
||||||
|
|
||||||
if res.StatusCode != fasthttp.StatusCreated {
|
if res.StatusCode != fasthttp.StatusCreated {
|
||||||
bodyBytes, err := ioutil.ReadAll(res.Body)
|
bodyBytes, err := io.ReadAll(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.logger.Debugf("Failed reading error body when creating or updating Event Grid subscription: %v", err)
|
a.logger.Debugf("Failed reading error body when creating or updating Event Grid subscription: %v", err)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Azure/azure-amqp-common-go/v3/aad"
|
"github.com/Azure/azure-amqp-common-go/v3/aad"
|
||||||
|
|
@ -145,7 +146,7 @@ func (m azureEventHubsMetadata) partitioned() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAzureEventHubs returns a new Azure Event hubs instance.
|
// NewAzureEventHubs returns a new Azure Event hubs instance.
|
||||||
func NewAzureEventHubs(logger logger.Logger) *AzureEventHubs {
|
func NewAzureEventHubs(logger logger.Logger) bindings.InputOutputBinding {
|
||||||
return &AzureEventHubs{logger: logger}
|
return &AzureEventHubs{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -154,6 +155,28 @@ func validate(connectionString string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *AzureEventHubs) getStoragePrefixString() (string, error) {
|
||||||
|
hubName, err := a.validateAndGetHubName()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty string in the end of slice to have a suffix "-".
|
||||||
|
return strings.Join([]string{"dapr", hubName, a.metadata.consumerGroup, ""}, "-"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AzureEventHubs) validateAndGetHubName() (string, error) {
|
||||||
|
hubName := a.metadata.eventHubName
|
||||||
|
if hubName == "" {
|
||||||
|
parsed, err := conn.ParsedConnectionFromStr(a.metadata.connectionString)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
hubName = parsed.HubName
|
||||||
|
}
|
||||||
|
return hubName, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Init performs metadata init.
|
// Init performs metadata init.
|
||||||
func (a *AzureEventHubs) Init(metadata bindings.Metadata) error {
|
func (a *AzureEventHubs) Init(metadata bindings.Metadata) error {
|
||||||
m, err := parseMetadata(metadata)
|
m, err := parseMetadata(metadata)
|
||||||
|
|
@ -360,7 +383,13 @@ func (a *AzureEventHubs) RegisterPartitionedEventProcessor(ctx context.Context,
|
||||||
// RegisterEventProcessor - receive eventhub messages by eventprocessor
|
// RegisterEventProcessor - receive eventhub messages by eventprocessor
|
||||||
// host by balancing partitions.
|
// host by balancing partitions.
|
||||||
func (a *AzureEventHubs) RegisterEventProcessor(ctx context.Context, handler bindings.Handler) error {
|
func (a *AzureEventHubs) RegisterEventProcessor(ctx context.Context, handler bindings.Handler) error {
|
||||||
leaserCheckpointer, err := storage.NewStorageLeaserCheckpointer(a.storageCredential, a.metadata.storageAccountName, a.metadata.storageContainerName, *a.azureEnvironment)
|
storagePrefix, err := a.getStoragePrefixString()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
leaserPrefixOpt := storage.WithPrefixInBlobPath(storagePrefix)
|
||||||
|
leaserCheckpointer, err := storage.NewStorageLeaserCheckpointer(a.storageCredential, a.metadata.storageAccountName, a.metadata.storageContainerName, *a.azureEnvironment, leaserPrefixOpt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,15 +17,51 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/dapr/components-contrib/bindings"
|
"github.com/dapr/components-contrib/bindings"
|
||||||
|
"github.com/dapr/components-contrib/metadata"
|
||||||
|
"github.com/dapr/kit/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var testLogger = logger.NewLogger("test")
|
||||||
|
|
||||||
|
func TestGetStoragePrefixString(t *testing.T) {
|
||||||
|
props := map[string]string{"storageAccountName": "fake", "storageAccountKey": "fake", "consumerGroup": "default", "storageContainerName": "test", "eventHub": "hubName", "eventHubNamespace": "fake"}
|
||||||
|
|
||||||
|
metadata := bindings.Metadata{Base: metadata.Base{Properties: props}}
|
||||||
|
m, err := parseMetadata(metadata)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
aeh := &AzureEventHubs{logger: testLogger, metadata: m}
|
||||||
|
|
||||||
|
actual, _ := aeh.getStoragePrefixString()
|
||||||
|
|
||||||
|
assert.Equal(t, "dapr-hubName-default-", actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetStoragePrefixStringWithHubNameFromConnectionString(t *testing.T) {
|
||||||
|
connectionString := "Endpoint=sb://fake.servicebus.windows.net/;SharedAccessKeyName=fakeKey;SharedAccessKey=key;EntityPath=hubName"
|
||||||
|
props := map[string]string{"storageAccountName": "fake", "storageAccountKey": "fake", "consumerGroup": "default", "storageContainerName": "test", "connectionString": connectionString}
|
||||||
|
|
||||||
|
metadata := bindings.Metadata{Base: metadata.Base{Properties: props}}
|
||||||
|
m, err := parseMetadata(metadata)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
aeh := &AzureEventHubs{logger: testLogger, metadata: m}
|
||||||
|
|
||||||
|
actual, _ := aeh.getStoragePrefixString()
|
||||||
|
|
||||||
|
assert.Equal(t, "dapr-hubName-default-", actual)
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseMetadata(t *testing.T) {
|
func TestParseMetadata(t *testing.T) {
|
||||||
t.Run("test valid configuration", func(t *testing.T) {
|
t.Run("test valid configuration", func(t *testing.T) {
|
||||||
props := map[string]string{connectionString: "fake", consumerGroup: "mygroup", storageAccountName: "account", storageAccountKey: "key", storageContainerName: "container"}
|
props := map[string]string{connectionString: "fake", consumerGroup: "mygroup", storageAccountName: "account", storageAccountKey: "key", storageContainerName: "container"}
|
||||||
|
|
||||||
bindingsMetadata := bindings.Metadata{Properties: props}
|
bindingsMetadata := bindings.Metadata{Base: metadata.Base{Properties: props}}
|
||||||
|
|
||||||
m, err := parseMetadata(bindingsMetadata)
|
m, err := parseMetadata(bindingsMetadata)
|
||||||
|
|
||||||
|
|
@ -77,7 +113,7 @@ func TestParseMetadata(t *testing.T) {
|
||||||
|
|
||||||
for _, c := range invalidConfigTestCases {
|
for _, c := range invalidConfigTestCases {
|
||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
bindingsMetadata := bindings.Metadata{Properties: c.config}
|
bindingsMetadata := bindings.Metadata{Base: metadata.Base{Properties: c.config}}
|
||||||
_, err := parseMetadata(bindingsMetadata)
|
_, err := parseMetadata(bindingsMetadata)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, err.Error(), c.errMsg)
|
assert.Equal(t, err.Error(), c.errMsg)
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ import (
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
|
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
|
||||||
|
|
||||||
"github.com/dapr/components-contrib/bindings"
|
"github.com/dapr/components-contrib/bindings"
|
||||||
contrib_metadata "github.com/dapr/components-contrib/metadata"
|
"github.com/dapr/components-contrib/internal/utils"
|
||||||
|
contribMetadata "github.com/dapr/components-contrib/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
type serviceBusQueuesMetadata struct {
|
type serviceBusQueuesMetadata struct {
|
||||||
|
|
@ -25,6 +26,7 @@ type serviceBusQueuesMetadata struct {
|
||||||
LockRenewalInSec int `json:"lockRenewalInSec"`
|
LockRenewalInSec int `json:"lockRenewalInSec"`
|
||||||
MaxConcurrentHandlers int `json:"maxConcurrentHandlers"`
|
MaxConcurrentHandlers int `json:"maxConcurrentHandlers"`
|
||||||
ttl time.Duration
|
ttl time.Duration
|
||||||
|
DisableEntityManagement bool `json:"disableEntityManagement"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -39,6 +41,7 @@ const (
|
||||||
maxActiveMessages = "maxActiveMessages"
|
maxActiveMessages = "maxActiveMessages"
|
||||||
lockRenewalInSec = "lockRenewalInSec"
|
lockRenewalInSec = "lockRenewalInSec"
|
||||||
maxConcurrentHandlers = "maxConcurrentHandlers"
|
maxConcurrentHandlers = "maxConcurrentHandlers"
|
||||||
|
disableEntityManagement = "disableEntityManagement"
|
||||||
|
|
||||||
// Default time to live for queues, which is 14 days. The same way Azure Portal does.
|
// Default time to live for queues, which is 14 days. The same way Azure Portal does.
|
||||||
defaultMessageTimeToLive = time.Hour * 24 * 14
|
defaultMessageTimeToLive = time.Hour * 24 * 14
|
||||||
|
|
@ -64,6 +67,9 @@ const (
|
||||||
// Default rate of retriable errors per second
|
// Default rate of retriable errors per second
|
||||||
defaultMaxRetriableErrorsPerSec = 10
|
defaultMaxRetriableErrorsPerSec = 10
|
||||||
|
|
||||||
|
// By default entity management is enabled
|
||||||
|
defaultDisableEntityManagement = false
|
||||||
|
|
||||||
errorMessagePrefix = "azure service bus error:"
|
errorMessagePrefix = "azure service bus error:"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -82,7 +88,7 @@ func (a *AzureServiceBusQueues) parseMetadata(metadata bindings.Metadata) (*serv
|
||||||
return nil, errors.New("connectionString and namespaceName are mutually exclusive")
|
return nil, errors.New("connectionString and namespaceName are mutually exclusive")
|
||||||
}
|
}
|
||||||
|
|
||||||
ttl, ok, err := contrib_metadata.TryGetTTL(metadata.Properties)
|
ttl, ok, err := contribMetadata.TryGetTTL(metadata.Properties)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -162,5 +168,10 @@ func (a *AzureServiceBusQueues) parseMetadata(metadata bindings.Metadata) (*serv
|
||||||
m.MaxRetriableErrorsPerSec = to.Ptr(mRetriableErrorsPerSec)
|
m.MaxRetriableErrorsPerSec = to.Ptr(mRetriableErrorsPerSec)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m.DisableEntityManagement = defaultDisableEntityManagement
|
||||||
|
if val, ok := metadata.Properties[disableEntityManagement]; ok && val != "" {
|
||||||
|
m.DisableEntityManagement = utils.IsTruthy(val)
|
||||||
|
}
|
||||||
|
|
||||||
return &m, nil
|
return &m, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ func TestParseMetadata(t *testing.T) {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
m := bindings.Metadata{}
|
m := bindings.Metadata{}
|
||||||
m.Properties = tt.properties
|
m.Properties = tt.properties
|
||||||
a := NewAzureServiceBusQueues(logger.NewLogger("test"))
|
a := NewAzureServiceBusQueues(logger.NewLogger("test")).(*AzureServiceBusQueues)
|
||||||
meta, err := a.parseMetadata(m)
|
meta, err := a.parseMetadata(m)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, tt.expectedConnectionString, meta.ConnectionString)
|
assert.Equal(t, tt.expectedConnectionString, meta.ConnectionString)
|
||||||
|
|
@ -137,7 +137,7 @@ func TestParseMetadataWithInvalidTTL(t *testing.T) {
|
||||||
m := bindings.Metadata{}
|
m := bindings.Metadata{}
|
||||||
m.Properties = tt.properties
|
m.Properties = tt.properties
|
||||||
|
|
||||||
a := NewAzureServiceBusQueues(logger.NewLogger("test"))
|
a := NewAzureServiceBusQueues(logger.NewLogger("test")).(*AzureServiceBusQueues)
|
||||||
_, err := a.parseMetadata(m)
|
_, err := a.parseMetadata(m)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
})
|
})
|
||||||
|
|
@ -183,7 +183,7 @@ func TestParseMetadataConnectionStringAndNamespaceNameExclusivity(t *testing.T)
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
m := bindings.Metadata{}
|
m := bindings.Metadata{}
|
||||||
m.Properties = tt.properties
|
m.Properties = tt.properties
|
||||||
a := NewAzureServiceBusQueues(logger.NewLogger("test"))
|
a := NewAzureServiceBusQueues(logger.NewLogger("test")).(*AzureServiceBusQueues)
|
||||||
meta, err := a.parseMetadata(m)
|
meta, err := a.parseMetadata(m)
|
||||||
if tt.expectedErr {
|
if tt.expectedErr {
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import (
|
||||||
"github.com/dapr/components-contrib/bindings"
|
"github.com/dapr/components-contrib/bindings"
|
||||||
azauth "github.com/dapr/components-contrib/internal/authentication/azure"
|
azauth "github.com/dapr/components-contrib/internal/authentication/azure"
|
||||||
impl "github.com/dapr/components-contrib/internal/component/azure/servicebus"
|
impl "github.com/dapr/components-contrib/internal/component/azure/servicebus"
|
||||||
contrib_metadata "github.com/dapr/components-contrib/metadata"
|
contribMetadata "github.com/dapr/components-contrib/metadata"
|
||||||
"github.com/dapr/kit/logger"
|
"github.com/dapr/kit/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -52,7 +52,7 @@ type AzureServiceBusQueues struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAzureServiceBusQueues returns a new AzureServiceBusQueues instance.
|
// NewAzureServiceBusQueues returns a new AzureServiceBusQueues instance.
|
||||||
func NewAzureServiceBusQueues(logger logger.Logger) *AzureServiceBusQueues {
|
func NewAzureServiceBusQueues(logger logger.Logger) bindings.InputOutputBinding {
|
||||||
return &AzureServiceBusQueues{
|
return &AzureServiceBusQueues{
|
||||||
senderLock: sync.RWMutex{},
|
senderLock: sync.RWMutex{},
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
|
@ -76,9 +76,11 @@ func (a *AzureServiceBusQueues) Init(metadata bindings.Metadata) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
a.adminClient, err = sbadmin.NewClientFromConnectionString(a.metadata.ConnectionString, nil)
|
if !a.metadata.DisableEntityManagement {
|
||||||
if err != nil {
|
a.adminClient, err = sbadmin.NewClientFromConnectionString(a.metadata.ConnectionString, nil)
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
settings, innerErr := azauth.NewEnvironmentSettings(azauth.AzureServiceBusResourceName, metadata.Properties)
|
settings, innerErr := azauth.NewEnvironmentSettings(azauth.AzureServiceBusResourceName, metadata.Properties)
|
||||||
|
|
@ -98,37 +100,40 @@ func (a *AzureServiceBusQueues) Init(metadata bindings.Metadata) (err error) {
|
||||||
return innerErr
|
return innerErr
|
||||||
}
|
}
|
||||||
|
|
||||||
a.adminClient, innerErr = sbadmin.NewClient(a.metadata.NamespaceName, token, nil)
|
if !a.metadata.DisableEntityManagement {
|
||||||
if innerErr != nil {
|
a.adminClient, innerErr = sbadmin.NewClient(a.metadata.NamespaceName, token, nil)
|
||||||
return innerErr
|
if innerErr != nil {
|
||||||
|
return innerErr
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), a.timeout)
|
if a.adminClient != nil {
|
||||||
defer cancel()
|
|
||||||
getQueueRes, err := a.adminClient.GetQueue(ctx, a.metadata.QueueName, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if getQueueRes == nil {
|
|
||||||
// Need to create the queue
|
|
||||||
ttlDur := contrib_metadata.Duration{
|
|
||||||
Duration: a.metadata.ttl,
|
|
||||||
}
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), a.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), a.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
_, err = a.adminClient.CreateQueue(ctx, a.metadata.QueueName, &sbadmin.CreateQueueOptions{
|
getQueueRes, err := a.adminClient.GetQueue(ctx, a.metadata.QueueName, nil)
|
||||||
Properties: &sbadmin.QueueProperties{
|
|
||||||
DefaultMessageTimeToLive: to.Ptr(ttlDur.ToISOString()),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if getQueueRes == nil {
|
||||||
|
// Need to create the queue
|
||||||
|
ttlDur := contribMetadata.Duration{
|
||||||
|
Duration: a.metadata.ttl,
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), a.timeout)
|
||||||
|
defer cancel()
|
||||||
|
_, err = a.adminClient.CreateQueue(ctx, a.metadata.QueueName, &sbadmin.CreateQueueOptions{
|
||||||
|
Properties: &sbadmin.QueueProperties{
|
||||||
|
DefaultMessageTimeToLive: to.Ptr(ttlDur.ToISOString()),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.ctx, a.cancel = context.WithCancel(context.Background())
|
||||||
}
|
}
|
||||||
|
|
||||||
a.ctx, a.cancel = context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,7 +160,8 @@ func (a *AzureServiceBusQueues) Invoke(ctx context.Context, req *bindings.Invoke
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := &servicebus.Message{
|
msg := &servicebus.Message{
|
||||||
Body: req.Data,
|
Body: req.Data,
|
||||||
|
ApplicationProperties: make(map[string]interface{}),
|
||||||
}
|
}
|
||||||
if val, ok := req.Metadata[id]; ok && val != "" {
|
if val, ok := req.Metadata[id]; ok && val != "" {
|
||||||
msg.MessageID = &val
|
msg.MessageID = &val
|
||||||
|
|
@ -163,7 +169,17 @@ func (a *AzureServiceBusQueues) Invoke(ctx context.Context, req *bindings.Invoke
|
||||||
if val, ok := req.Metadata[correlationID]; ok && val != "" {
|
if val, ok := req.Metadata[correlationID]; ok && val != "" {
|
||||||
msg.CorrelationID = &val
|
msg.CorrelationID = &val
|
||||||
}
|
}
|
||||||
ttl, ok, err := contrib_metadata.TryGetTTL(req.Metadata)
|
|
||||||
|
// Include incoming metadata in the message to be used when it is read.
|
||||||
|
for k, v := range req.Metadata {
|
||||||
|
// Don't include the values that are saved in MessageID or CorrelationID.
|
||||||
|
if k == id || k == correlationID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
msg.ApplicationProperties[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
ttl, ok, err := contribMetadata.TryGetTTL(req.Metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -262,6 +278,13 @@ func (a *AzureServiceBusQueues) getHandlerFunc(handler bindings.Handler) impl.Ha
|
||||||
metadata[label] = *msg.Subject
|
metadata[label] = *msg.Subject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Passthrough any custom metadata to the handler.
|
||||||
|
for key, val := range msg.ApplicationProperties {
|
||||||
|
if stringVal, ok := val.(string); ok {
|
||||||
|
metadata[key] = stringVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_, err := handler(a.ctx, &bindings.ReadResponse{
|
_, err := handler(a.ctx, &bindings.ReadResponse{
|
||||||
Data: msg.Body,
|
Data: msg.Body,
|
||||||
Metadata: metadata,
|
Metadata: metadata,
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -58,7 +58,7 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSignalR creates a new output binding for Azure SignalR.
|
// NewSignalR creates a new output binding for Azure SignalR.
|
||||||
func NewSignalR(logger logger.Logger) *SignalR {
|
func NewSignalR(logger logger.Logger) bindings.OutputBinding {
|
||||||
return &SignalR{
|
return &SignalR{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
httpClient: httpClient,
|
httpClient: httpClient,
|
||||||
|
|
@ -197,7 +197,7 @@ func (s *SignalR) resolveAPIURL(req *bindings.InvokeRequest) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalR) sendMessageToSignalR(ctx context.Context, url string, token string, data []byte) error {
|
func (s *SignalR) sendMessageToSignalR(ctx context.Context, url string, token string, data []byte) error {
|
||||||
httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(data))
|
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -213,7 +213,7 @@ func (s *SignalR) sendMessageToSignalR(ctx context.Context, url string, token st
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
// Read the body regardless to drain it and ensure the connection can be reused
|
// Read the body regardless to drain it and ensure the connection can be reused
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -263,7 +263,8 @@ func (s *SignalR) getToken(ctx context.Context, url string) (token string, err e
|
||||||
}
|
}
|
||||||
token = at.Token
|
token = at.Token
|
||||||
} else {
|
} else {
|
||||||
claims := &jwt.StandardClaims{
|
// TODO: Use jwt.RegisteredClaims instead
|
||||||
|
claims := &jwt.StandardClaims{ //nolint:staticcheck
|
||||||
ExpiresAt: time.Now().Add(15 * time.Minute).Unix(),
|
ExpiresAt: time.Now().Add(15 * time.Minute).Unix(),
|
||||||
Audience: url,
|
Audience: url,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
@ -186,7 +186,7 @@ func TestConfigurationValid(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
s := NewSignalR(logger.NewLogger("test"))
|
s := NewSignalR(logger.NewLogger("test")).(*SignalR)
|
||||||
err := s.parseMetadata(tt.properties)
|
err := s.parseMetadata(tt.properties)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, tt.expectedEndpoint, s.endpoint)
|
assert.Equal(t, tt.expectedEndpoint, s.endpoint)
|
||||||
|
|
@ -256,7 +256,7 @@ func TestInvalidConfigurations(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
s := NewSignalR(logger.NewLogger("test"))
|
s := NewSignalR(logger.NewLogger("test")).(*SignalR)
|
||||||
err := s.parseMetadata(tt.properties)
|
err := s.parseMetadata(tt.properties)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
})
|
})
|
||||||
|
|
@ -284,10 +284,10 @@ func (t *mockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
|
||||||
func TestWriteShouldFail(t *testing.T) {
|
func TestWriteShouldFail(t *testing.T) {
|
||||||
httpTransport := &mockTransport{
|
httpTransport := &mockTransport{
|
||||||
response: &http.Response{StatusCode: 200, Body: ioutil.NopCloser(strings.NewReader(""))},
|
response: &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(""))},
|
||||||
}
|
}
|
||||||
|
|
||||||
s := NewSignalR(logger.NewLogger("test"))
|
s := NewSignalR(logger.NewLogger("test")).(*SignalR)
|
||||||
s.endpoint = "https://fake.service.signalr.net"
|
s.endpoint = "https://fake.service.signalr.net"
|
||||||
s.accessKey = "G7+nIt9n48+iYSltPRf1v8kE+MupFfEt/9NSNTKOdzA="
|
s.accessKey = "G7+nIt9n48+iYSltPRf1v8kE+MupFfEt/9NSNTKOdzA="
|
||||||
s.httpClient = &http.Client{
|
s.httpClient = &http.Client{
|
||||||
|
|
@ -335,10 +335,10 @@ func TestWriteShouldFail(t *testing.T) {
|
||||||
|
|
||||||
func TestWriteShouldSucceed(t *testing.T) {
|
func TestWriteShouldSucceed(t *testing.T) {
|
||||||
httpTransport := &mockTransport{
|
httpTransport := &mockTransport{
|
||||||
response: &http.Response{StatusCode: 200, Body: ioutil.NopCloser(strings.NewReader(""))},
|
response: &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(""))},
|
||||||
}
|
}
|
||||||
|
|
||||||
s := NewSignalR(logger.NewLogger("test"))
|
s := NewSignalR(logger.NewLogger("test")).(*SignalR)
|
||||||
s.endpoint = "https://fake.service.signalr.net"
|
s.endpoint = "https://fake.service.signalr.net"
|
||||||
s.accessKey = "fakekey"
|
s.accessKey = "fakekey"
|
||||||
s.httpClient = &http.Client{
|
s.httpClient = &http.Client{
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,7 @@ import (
|
||||||
"github.com/dapr/components-contrib/bindings"
|
"github.com/dapr/components-contrib/bindings"
|
||||||
azauth "github.com/dapr/components-contrib/internal/authentication/azure"
|
azauth "github.com/dapr/components-contrib/internal/authentication/azure"
|
||||||
"github.com/dapr/components-contrib/internal/utils"
|
"github.com/dapr/components-contrib/internal/utils"
|
||||||
contrib_metadata "github.com/dapr/components-contrib/metadata"
|
contribMetadata "github.com/dapr/components-contrib/metadata"
|
||||||
mdutils "github.com/dapr/components-contrib/metadata"
|
|
||||||
"github.com/dapr/kit/logger"
|
"github.com/dapr/kit/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -179,7 +178,7 @@ type storageQueuesMetadata struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAzureStorageQueues returns a new AzureStorageQueues instance.
|
// NewAzureStorageQueues returns a new AzureStorageQueues instance.
|
||||||
func NewAzureStorageQueues(logger logger.Logger) *AzureStorageQueues {
|
func NewAzureStorageQueues(logger logger.Logger) bindings.InputOutputBinding {
|
||||||
return &AzureStorageQueues{helper: NewAzureQueueHelper(logger), logger: logger}
|
return &AzureStorageQueues{helper: NewAzureQueueHelper(logger), logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -197,25 +196,25 @@ func parseMetadata(metadata bindings.Metadata) (*storageQueuesMetadata, error) {
|
||||||
var m storageQueuesMetadata
|
var m storageQueuesMetadata
|
||||||
// AccountKey is parsed in azauth
|
// AccountKey is parsed in azauth
|
||||||
|
|
||||||
if val, ok := mdutils.GetMetadataProperty(metadata.Properties, azauth.StorageAccountNameKeys...); ok && val != "" {
|
if val, ok := contribMetadata.GetMetadataProperty(metadata.Properties, azauth.StorageAccountNameKeys...); ok && val != "" {
|
||||||
m.AccountName = val
|
m.AccountName = val
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("missing or empty %s field from metadata", azauth.StorageAccountNameKeys[0])
|
return nil, fmt.Errorf("missing or empty %s field from metadata", azauth.StorageAccountNameKeys[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, ok := mdutils.GetMetadataProperty(metadata.Properties, azauth.StorageQueueNameKeys...); ok && val != "" {
|
if val, ok := contribMetadata.GetMetadataProperty(metadata.Properties, azauth.StorageQueueNameKeys...); ok && val != "" {
|
||||||
m.QueueName = val
|
m.QueueName = val
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("missing or empty %s field from metadata", azauth.StorageQueueNameKeys[0])
|
return nil, fmt.Errorf("missing or empty %s field from metadata", azauth.StorageQueueNameKeys[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, ok := mdutils.GetMetadataProperty(metadata.Properties, azauth.StorageEndpointKeys...); ok && val != "" {
|
if val, ok := contribMetadata.GetMetadataProperty(metadata.Properties, azauth.StorageEndpointKeys...); ok && val != "" {
|
||||||
m.QueueEndpoint = val
|
m.QueueEndpoint = val
|
||||||
}
|
}
|
||||||
|
|
||||||
m.DecodeBase64 = utils.IsTruthy(metadata.Properties["decodeBase64"])
|
m.DecodeBase64 = utils.IsTruthy(metadata.Properties["decodeBase64"])
|
||||||
|
|
||||||
ttl, ok, err := contrib_metadata.TryGetTTL(metadata.Properties)
|
ttl, ok, err := contribMetadata.TryGetTTL(metadata.Properties)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -232,7 +231,7 @@ func (a *AzureStorageQueues) Operations() []bindings.OperationKind {
|
||||||
|
|
||||||
func (a *AzureStorageQueues) Invoke(ctx context.Context, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
|
func (a *AzureStorageQueues) Invoke(ctx context.Context, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
|
||||||
ttlToUse := a.metadata.ttl
|
ttlToUse := a.metadata.ttl
|
||||||
ttl, ok, err := contrib_metadata.TryGetTTL(req.Metadata)
|
ttl, ok, err := contribMetadata.TryGetTTL(req.Metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -287,7 +287,7 @@ func TestReadQueueNoMessage(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseMetadata(t *testing.T) {
|
func TestParseMetadata(t *testing.T) {
|
||||||
var oneSecondDuration time.Duration = time.Second
|
oneSecondDuration := time.Second
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
@ -295,7 +295,7 @@ func TestParseMetadata(t *testing.T) {
|
||||||
// Account key is parsed in azauth
|
// Account key is parsed in azauth
|
||||||
// expectedAccountKey string
|
// expectedAccountKey string
|
||||||
expectedQueueName string
|
expectedQueueName string
|
||||||
expectedQueueEndpointUrl string
|
expectedQueueEndpointURL string
|
||||||
expectedTTL *time.Duration
|
expectedTTL *time.Duration
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
|
@ -303,21 +303,21 @@ func TestParseMetadata(t *testing.T) {
|
||||||
properties: map[string]string{"storageAccessKey": "myKey", "queue": "queue1", "storageAccount": "devstoreaccount1"},
|
properties: map[string]string{"storageAccessKey": "myKey", "queue": "queue1", "storageAccount": "devstoreaccount1"},
|
||||||
// expectedAccountKey: "myKey",
|
// expectedAccountKey: "myKey",
|
||||||
expectedQueueName: "queue1",
|
expectedQueueName: "queue1",
|
||||||
expectedQueueEndpointUrl: "",
|
expectedQueueEndpointURL: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Accout, key, and endpoint",
|
name: "Accout, key, and endpoint",
|
||||||
properties: map[string]string{"accountKey": "myKey", "queueName": "queue1", "storageAccount": "someAccount", "queueEndpointUrl": "https://foo.example.com:10001"},
|
properties: map[string]string{"accountKey": "myKey", "queueName": "queue1", "storageAccount": "someAccount", "queueEndpointUrl": "https://foo.example.com:10001"},
|
||||||
// expectedAccountKey: "myKey",
|
// expectedAccountKey: "myKey",
|
||||||
expectedQueueName: "queue1",
|
expectedQueueName: "queue1",
|
||||||
expectedQueueEndpointUrl: "https://foo.example.com:10001",
|
expectedQueueEndpointURL: "https://foo.example.com:10001",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Empty TTL",
|
name: "Empty TTL",
|
||||||
properties: map[string]string{"storageAccessKey": "myKey", "queue": "queue1", "storageAccount": "devstoreaccount1", metadata.TTLMetadataKey: ""},
|
properties: map[string]string{"storageAccessKey": "myKey", "queue": "queue1", "storageAccount": "devstoreaccount1", metadata.TTLMetadataKey: ""},
|
||||||
// expectedAccountKey: "myKey",
|
// expectedAccountKey: "myKey",
|
||||||
expectedQueueName: "queue1",
|
expectedQueueName: "queue1",
|
||||||
expectedQueueEndpointUrl: "",
|
expectedQueueEndpointURL: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "With TTL",
|
name: "With TTL",
|
||||||
|
|
@ -325,7 +325,7 @@ func TestParseMetadata(t *testing.T) {
|
||||||
// expectedAccountKey: "myKey",
|
// expectedAccountKey: "myKey",
|
||||||
expectedQueueName: "queue1",
|
expectedQueueName: "queue1",
|
||||||
expectedTTL: &oneSecondDuration,
|
expectedTTL: &oneSecondDuration,
|
||||||
expectedQueueEndpointUrl: "",
|
expectedQueueEndpointURL: "",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -340,7 +340,7 @@ func TestParseMetadata(t *testing.T) {
|
||||||
// assert.Equal(t, tt.expectedAccountKey, meta.AccountKey)
|
// assert.Equal(t, tt.expectedAccountKey, meta.AccountKey)
|
||||||
assert.Equal(t, tt.expectedQueueName, meta.QueueName)
|
assert.Equal(t, tt.expectedQueueName, meta.QueueName)
|
||||||
assert.Equal(t, tt.expectedTTL, meta.ttl)
|
assert.Equal(t, tt.expectedTTL, meta.ttl)
|
||||||
assert.Equal(t, tt.expectedQueueEndpointUrl, meta.QueueEndpoint)
|
assert.Equal(t, tt.expectedQueueEndpointURL, meta.QueueEndpoint)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Dapr Authors
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bindings
|
||||||
|
|
||||||
|
// InputOutputBinding is the interface for bindings that implement both input and output bindings.
|
||||||
|
type InputOutputBinding interface {
|
||||||
|
InputBinding
|
||||||
|
OutputBinding
|
||||||
|
}
|
||||||
|
|
@ -43,7 +43,7 @@ type commercetoolsMetadata struct {
|
||||||
scopes string
|
scopes string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCommercetools(logger logger.Logger) *Binding {
|
func NewCommercetools(logger logger.Logger) bindings.OutputBinding {
|
||||||
return &Binding{logger: logger}
|
return &Binding{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ type Binding struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCron returns a new Cron event input binding.
|
// NewCron returns a new Cron event input binding.
|
||||||
func NewCron(logger logger.Logger) *Binding {
|
func NewCron(logger logger.Logger) bindings.InputOutputBinding {
|
||||||
return &Binding{
|
return &Binding{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
parser: cron.NewParser(
|
parser: cron.NewParser(
|
||||||
|
|
@ -47,8 +47,9 @@ func NewCron(logger logger.Logger) *Binding {
|
||||||
|
|
||||||
// Init initializes the Cron binding
|
// Init initializes the Cron binding
|
||||||
// Examples from https://godoc.org/github.com/robfig/cron:
|
// Examples from https://godoc.org/github.com/robfig/cron:
|
||||||
// "15 * * * * *" - Every 15 sec
|
//
|
||||||
// "0 30 * * * *" - Every 30 min
|
// "15 * * * * *" - Every 15 sec
|
||||||
|
// "0 30 * * * *" - Every 30 min
|
||||||
func (b *Binding) Init(metadata bindings.Metadata) error {
|
func (b *Binding) Init(metadata bindings.Metadata) error {
|
||||||
b.name = metadata.Name
|
b.name = metadata.Name
|
||||||
s, f := metadata.Properties["schedule"]
|
s, f := metadata.Properties["schedule"]
|
||||||
|
|
@ -85,12 +86,12 @@ func (b *Binding) Read(ctx context.Context, handler bindings.Handler) error {
|
||||||
b.logger.Debugf("name: %s, next run: %v", b.name, time.Until(c.Entry(id).Next))
|
b.logger.Debugf("name: %s, next run: %v", b.name, time.Until(c.Entry(id).Next))
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
// Wait for a context to be canceled or a message on the stopCh
|
// Wait for a context to be canceled
|
||||||
select {
|
select {
|
||||||
case <-b.runningCtx.Done():
|
case <-b.runningCtx.Done():
|
||||||
// Do nothing
|
// Do nothing
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
b.runningCancel()
|
b.resetContext()
|
||||||
}
|
}
|
||||||
b.logger.Debugf("name: %s, stopping schedule: %s", b.name, b.schedule)
|
b.logger.Debugf("name: %s, stopping schedule: %s", b.name, b.schedule)
|
||||||
c.Stop()
|
c.Stop()
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ func getNewCron() *Binding {
|
||||||
l.SetOutputLevel(logger.DebugLevel)
|
l.SetOutputLevel(logger.DebugLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewCron(l)
|
return NewCron(l).(*Binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
// go test -v -timeout 15s -count=1 ./bindings/cron/.
|
// go test -v -timeout 15s -count=1 ./bindings/cron/.
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
|
@ -79,7 +78,7 @@ type createResponse struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGCPStorage returns a new GCP storage instance.
|
// NewGCPStorage returns a new GCP storage instance.
|
||||||
func NewGCPStorage(logger logger.Logger) *GCPStorage {
|
func NewGCPStorage(logger logger.Logger) bindings.OutputBinding {
|
||||||
return &GCPStorage{logger: logger}
|
return &GCPStorage{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -214,9 +213,9 @@ func (g *GCPStorage) get(ctx context.Context, req *bindings.InvokeRequest) (*bin
|
||||||
}
|
}
|
||||||
defer rc.Close()
|
defer rc.Close()
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(rc)
|
data, err := io.ReadAll(rc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("gcp bucketgcp bucket binding error: ioutil.ReadAll: %v", err)
|
return nil, fmt.Errorf("gcp bucketgcp bucket binding error: io.ReadAll: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if metadata.EncodeBase64 {
|
if metadata.EncodeBase64 {
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ type pubSubMetadata struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGCPPubSub returns a new GCPPubSub instance.
|
// NewGCPPubSub returns a new GCPPubSub instance.
|
||||||
func NewGCPPubSub(logger logger.Logger) *GCPPubSub {
|
func NewGCPPubSub(logger logger.Logger) bindings.InputOutputBinding {
|
||||||
return &GCPPubSub{logger: logger}
|
return &GCPPubSub{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ type GraphQL struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGraphQL returns a new GraphQL binding instance.
|
// NewGraphQL returns a new GraphQL binding instance.
|
||||||
func NewGraphQL(logger logger.Logger) *GraphQL {
|
func NewGraphQL(logger logger.Logger) bindings.OutputBinding {
|
||||||
return &GraphQL{logger: logger}
|
return &GraphQL{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,7 +112,7 @@ func (gql *GraphQL) Invoke(ctx context.Context, req *bindings.InvokeRequest) (*b
|
||||||
|
|
||||||
var graphqlResponse interface{}
|
var graphqlResponse interface{}
|
||||||
|
|
||||||
switch req.Operation { // nolint: exhaustive
|
switch req.Operation { //nolint:exhaustive
|
||||||
case QueryOperation:
|
case QueryOperation:
|
||||||
if err := gql.runRequest(ctx, commandQuery, req, &graphqlResponse); err != nil {
|
if err := gql.runRequest(ctx, commandQuery, req, &graphqlResponse); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
@ -33,6 +32,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTTPSource is a binding for an http url endpoint invocation
|
// HTTPSource is a binding for an http url endpoint invocation
|
||||||
|
//
|
||||||
//revive:disable-next-line
|
//revive:disable-next-line
|
||||||
type HTTPSource struct {
|
type HTTPSource struct {
|
||||||
metadata httpMetadata
|
metadata httpMetadata
|
||||||
|
|
@ -46,7 +46,7 @@ type httpMetadata struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHTTP returns a new HTTPSource.
|
// NewHTTP returns a new HTTPSource.
|
||||||
func NewHTTP(logger logger.Logger) *HTTPSource {
|
func NewHTTP(logger logger.Logger) bindings.OutputBinding {
|
||||||
return &HTTPSource{logger: logger}
|
return &HTTPSource{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,7 +148,7 @@ func (h *HTTPSource) Invoke(ctx context.Context, req *bindings.InvokeRequest) (*
|
||||||
|
|
||||||
// Read the response body. For empty responses (e.g. 204 No Content)
|
// Read the response body. For empty responses (e.g. 204 No Content)
|
||||||
// `b` will be an empty slice.
|
// `b` will be an empty slice.
|
||||||
b, err := ioutil.ReadAll(resp.Body)
|
b, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ package http_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -25,12 +25,13 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/dapr/components-contrib/bindings"
|
"github.com/dapr/components-contrib/bindings"
|
||||||
binding_http "github.com/dapr/components-contrib/bindings/http"
|
bindingHttp "github.com/dapr/components-contrib/bindings/http"
|
||||||
|
"github.com/dapr/components-contrib/metadata"
|
||||||
"github.com/dapr/kit/logger"
|
"github.com/dapr/kit/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOperations(t *testing.T) {
|
func TestOperations(t *testing.T) {
|
||||||
opers := (*binding_http.HTTPSource)(nil).Operations()
|
opers := (*bindingHttp.HTTPSource)(nil).Operations()
|
||||||
assert.Equal(t, []bindings.OperationKind{
|
assert.Equal(t, []bindings.OperationKind{
|
||||||
bindings.CreateOperation,
|
bindings.CreateOperation,
|
||||||
"get",
|
"get",
|
||||||
|
|
@ -53,7 +54,7 @@ func TestInit(t *testing.T) {
|
||||||
input := req.Method
|
input := req.Method
|
||||||
if req.Body != nil {
|
if req.Body != nil {
|
||||||
defer req.Body.Close()
|
defer req.Body.Close()
|
||||||
b, _ := ioutil.ReadAll(req.Body)
|
b, _ := io.ReadAll(req.Body)
|
||||||
if len(b) > 0 {
|
if len(b) > 0 {
|
||||||
input = string(b)
|
input = string(b)
|
||||||
}
|
}
|
||||||
|
|
@ -64,19 +65,19 @@ func TestInit(t *testing.T) {
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
if input == "internal server error" {
|
if input == "internal server error" {
|
||||||
w.WriteHeader(500)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
w.Write([]byte(strings.ToUpper(input)))
|
w.Write([]byte(strings.ToUpper(input)))
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
m := bindings.Metadata{
|
m := bindings.Metadata{Base: metadata.Base{
|
||||||
Properties: map[string]string{
|
Properties: map[string]string{
|
||||||
"url": s.URL,
|
"url": s.URL,
|
||||||
},
|
},
|
||||||
}
|
}}
|
||||||
hs := binding_http.NewHTTP(logger.NewLogger("test"))
|
hs := bindingHttp.NewHTTP(logger.NewLogger("test"))
|
||||||
err := hs.Init(m)
|
err := hs.Init(m)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
@ -70,7 +70,7 @@ type listPayload struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHuaweiOBS returns a new Huawei OBS instance.
|
// NewHuaweiOBS returns a new Huawei OBS instance.
|
||||||
func NewHuaweiOBS(logger logger.Logger) *HuaweiOBS {
|
func NewHuaweiOBS(logger logger.Logger) bindings.OutputBinding {
|
||||||
return &HuaweiOBS{logger: logger}
|
return &HuaweiOBS{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -228,7 +228,7 @@ func (o *HuaweiOBS) get(ctx context.Context, req *bindings.InvokeRequest) (*bind
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(out.Body)
|
data, err := io.ReadAll(out.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("obs binding error. error reading obs object content: %w", err)
|
return nil, fmt.Errorf("obs binding error. error reading obs object content: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"testing/iotest"
|
"testing/iotest"
|
||||||
|
|
@ -61,7 +61,7 @@ func (m *MockHuaweiOBSService) ListObjects(ctx context.Context, input *obs.ListO
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseMetadata(t *testing.T) {
|
func TestParseMetadata(t *testing.T) {
|
||||||
obs := NewHuaweiOBS(logger.NewLogger("test"))
|
obs := NewHuaweiOBS(logger.NewLogger("test")).(*HuaweiOBS)
|
||||||
|
|
||||||
t.Run("Has correct metadata", func(t *testing.T) {
|
t.Run("Has correct metadata", func(t *testing.T) {
|
||||||
m := bindings.Metadata{}
|
m := bindings.Metadata{}
|
||||||
|
|
@ -374,7 +374,7 @@ func TestGetOperation(t *testing.T) {
|
||||||
},
|
},
|
||||||
Metadata: map[string]string{},
|
Metadata: map[string]string{},
|
||||||
},
|
},
|
||||||
Body: ioutil.NopCloser(strings.NewReader("Hello Dapr")),
|
Body: io.NopCloser(strings.NewReader("Hello Dapr")),
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -447,7 +447,7 @@ func TestGetOperation(t *testing.T) {
|
||||||
},
|
},
|
||||||
Metadata: map[string]string{},
|
Metadata: map[string]string{},
|
||||||
},
|
},
|
||||||
Body: ioutil.NopCloser(iotest.ErrReader(errors.New("unexpected data reading error"))),
|
Body: io.NopCloser(iotest.ErrReader(errors.New("unexpected data reading error"))),
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -667,7 +667,7 @@ func TestInvoke(t *testing.T) {
|
||||||
},
|
},
|
||||||
Metadata: map[string]string{},
|
Metadata: map[string]string{},
|
||||||
},
|
},
|
||||||
Body: ioutil.NopCloser(strings.NewReader("Hello Dapr")),
|
Body: io.NopCloser(strings.NewReader("Hello Dapr")),
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ type influxMetadata struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewInflux returns a new kafka binding instance.
|
// NewInflux returns a new kafka binding instance.
|
||||||
func NewInflux(logger logger.Logger) *Influx {
|
func NewInflux(logger logger.Logger) bindings.OutputBinding {
|
||||||
return &Influx{logger: logger}
|
return &Influx{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/dapr/components-contrib/bindings"
|
"github.com/dapr/components-contrib/bindings"
|
||||||
|
"github.com/dapr/components-contrib/metadata"
|
||||||
"github.com/dapr/kit/logger"
|
"github.com/dapr/kit/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -46,13 +47,13 @@ func TestOperations(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInflux_Init(t *testing.T) {
|
func TestInflux_Init(t *testing.T) {
|
||||||
influx := NewInflux(logger.NewLogger("test"))
|
influx := NewInflux(logger.NewLogger("test")).(*Influx)
|
||||||
assert.Nil(t, influx.queryAPI)
|
assert.Nil(t, influx.queryAPI)
|
||||||
assert.Nil(t, influx.writeAPI)
|
assert.Nil(t, influx.writeAPI)
|
||||||
assert.Nil(t, influx.metadata)
|
assert.Nil(t, influx.metadata)
|
||||||
assert.Nil(t, influx.client)
|
assert.Nil(t, influx.client)
|
||||||
|
|
||||||
m := bindings.Metadata{Properties: map[string]string{"Url": "a", "Token": "a", "Org": "a", "Bucket": "a"}}
|
m := bindings.Metadata{Base: metadata.Base{Properties: map[string]string{"Url": "a", "Token": "a", "Org": "a", "Bucket": "a"}}}
|
||||||
err := influx.Init(m)
|
err := influx.Init(m)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,6 @@ func PingInpBinding(inputBinding InputBinding) error {
|
||||||
if inputBindingWithPing, ok := inputBinding.(health.Pinger); ok {
|
if inputBindingWithPing, ok := inputBinding.(health.Pinger); ok {
|
||||||
return inputBindingWithPing.Ping()
|
return inputBindingWithPing.Ping()
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("Ping is not implemented by this input binding")
|
return fmt.Errorf("ping is not implemented by this input binding")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,254 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Dapr Authors
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ipfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
ipfsHttpclient "github.com/ipfs/go-ipfs-http-client"
|
||||||
|
ipfsIcore "github.com/ipfs/interface-go-ipfs-core"
|
||||||
|
ipfsConfig "github.com/ipfs/kubo/config"
|
||||||
|
ipfsCore "github.com/ipfs/kubo/core"
|
||||||
|
ipfsCoreapi "github.com/ipfs/kubo/core/coreapi"
|
||||||
|
ipfsLibp2p "github.com/ipfs/kubo/core/node/libp2p"
|
||||||
|
ipfsLoader "github.com/ipfs/kubo/plugin/loader"
|
||||||
|
ipfsRepo "github.com/ipfs/kubo/repo"
|
||||||
|
ipfsFsrepo "github.com/ipfs/kubo/repo/fsrepo"
|
||||||
|
"github.com/multiformats/go-multiaddr"
|
||||||
|
|
||||||
|
"github.com/dapr/components-contrib/bindings"
|
||||||
|
"github.com/dapr/kit/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
const swarmKeyFile = "swarm.key"
|
||||||
|
|
||||||
|
var (
|
||||||
|
loadPluginsOnce sync.Once
|
||||||
|
httpClient *http.Client
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
httpClient = &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
Dial: (&net.Dialer{
|
||||||
|
Timeout: 5 * time.Second,
|
||||||
|
}).Dial,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPFSBinding is a binding for interacting with an IPFS network.
|
||||||
|
type IPFSBinding struct {
|
||||||
|
metadata ipfsMetadata
|
||||||
|
ipfsAPI ipfsIcore.CoreAPI
|
||||||
|
ipfsNode *ipfsCore.IpfsNode
|
||||||
|
ipfsRepo ipfsRepo.Repo
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
logger logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIPFSBinding returns a new IPFSBinding.
|
||||||
|
func NewIPFSBinding(logger logger.Logger) bindings.OutputBinding {
|
||||||
|
return &IPFSBinding{
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init the binding.
|
||||||
|
func (b *IPFSBinding) Init(metadata bindings.Metadata) (err error) {
|
||||||
|
b.ctx, b.cancel = context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
err = b.metadata.FromMap(metadata.Properties)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.metadata.ExternalAPI == "" {
|
||||||
|
var onceErr error
|
||||||
|
loadPluginsOnce.Do(func() {
|
||||||
|
onceErr = setupPlugins("")
|
||||||
|
})
|
||||||
|
if onceErr != nil {
|
||||||
|
return onceErr
|
||||||
|
}
|
||||||
|
|
||||||
|
err = b.createNode()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to start IPFS node: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if b.metadata.ExternalAPI[0] == '/' {
|
||||||
|
var maddr multiaddr.Multiaddr
|
||||||
|
maddr, err = multiaddr.NewMultiaddr(b.metadata.ExternalAPI)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse external API multiaddr: %v", err)
|
||||||
|
}
|
||||||
|
b.ipfsAPI, err = ipfsHttpclient.NewApiWithClient(maddr, httpClient)
|
||||||
|
} else {
|
||||||
|
b.ipfsAPI, err = ipfsHttpclient.NewURLApiWithClient(b.metadata.ExternalAPI, httpClient)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to initialize external IPFS API: %v", err)
|
||||||
|
}
|
||||||
|
b.logger.Infof("Using IPFS APIs at %s", b.metadata.ExternalAPI)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *IPFSBinding) Close() (err error) {
|
||||||
|
if b.cancel != nil {
|
||||||
|
b.cancel()
|
||||||
|
b.cancel = nil
|
||||||
|
}
|
||||||
|
if b.ipfsNode != nil {
|
||||||
|
err = b.ipfsNode.Close()
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Errorf("Error while closing IPFS node: %v", err)
|
||||||
|
}
|
||||||
|
b.ipfsNode = nil
|
||||||
|
}
|
||||||
|
if b.ipfsRepo != nil {
|
||||||
|
err = b.ipfsRepo.Close()
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Errorf("Error while closing IPFS repo: %v", err)
|
||||||
|
}
|
||||||
|
b.ipfsRepo = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *IPFSBinding) createNode() (err error) {
|
||||||
|
// Init the repo if needed
|
||||||
|
if !ipfsFsrepo.IsInitialized(b.metadata.RepoPath) {
|
||||||
|
var cfg *ipfsConfig.Config
|
||||||
|
cfg, err = b.metadata.IPFSConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = ipfsFsrepo.Init(b.metadata.RepoPath, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if b.metadata.SwarmKey != "" {
|
||||||
|
skPath := filepath.Join(b.metadata.RepoPath, swarmKeyFile)
|
||||||
|
err = os.WriteFile(skPath, []byte(b.metadata.SwarmKey), 0o600)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error writing swarm key to file '%s': %v", skPath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.logger.Infof("Initialized a new IPFS repo at path %s", b.metadata.RepoPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the repo
|
||||||
|
repo, err := ipfsFsrepo.Open(b.metadata.RepoPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.logger.Infof("Opened IPFS repo at path %s", b.metadata.RepoPath)
|
||||||
|
|
||||||
|
// Create the node
|
||||||
|
nodeOptions := &ipfsCore.BuildCfg{
|
||||||
|
Online: true,
|
||||||
|
Repo: repo,
|
||||||
|
}
|
||||||
|
r := strings.ToLower(b.metadata.Routing)
|
||||||
|
switch r {
|
||||||
|
case "", "dht":
|
||||||
|
nodeOptions.Routing = ipfsLibp2p.DHTOption
|
||||||
|
case "dhtclient":
|
||||||
|
nodeOptions.Routing = ipfsLibp2p.DHTClientOption
|
||||||
|
case "dhtserver":
|
||||||
|
nodeOptions.Routing = ipfsLibp2p.DHTServerOption
|
||||||
|
case "none":
|
||||||
|
nodeOptions.Routing = ipfsLibp2p.NilRouterOption
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid value for metadata property 'routing'")
|
||||||
|
}
|
||||||
|
b.ipfsNode, err = ipfsCore.NewNode(b.ctx, nodeOptions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
b.logger.Infof("Started IPFS node %s", b.ipfsNode.Identity)
|
||||||
|
|
||||||
|
// Init API
|
||||||
|
b.ipfsAPI, err = ipfsCoreapi.NewCoreAPI(b.ipfsNode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Operations returns the supported operations for this binding.
|
||||||
|
func (b *IPFSBinding) Operations() []bindings.OperationKind {
|
||||||
|
return []bindings.OperationKind{
|
||||||
|
bindings.GetOperation,
|
||||||
|
bindings.CreateOperation, // alias for "add"
|
||||||
|
bindings.ListOperation, // alias for "ls"
|
||||||
|
bindings.DeleteOperation, // alias for "pin-rm"
|
||||||
|
"add",
|
||||||
|
"ls",
|
||||||
|
"pin-add",
|
||||||
|
"pin-rm",
|
||||||
|
"pin-ls",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoke performs an HTTP request to the configured HTTP endpoint.
|
||||||
|
func (b *IPFSBinding) Invoke(ctx context.Context, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
|
||||||
|
switch req.Operation {
|
||||||
|
case bindings.GetOperation:
|
||||||
|
return b.getOperation(ctx, req)
|
||||||
|
case "add", bindings.CreateOperation:
|
||||||
|
return b.addOperation(ctx, req)
|
||||||
|
case "ls", bindings.ListOperation:
|
||||||
|
return b.lsOperation(ctx, req)
|
||||||
|
case "pin-add":
|
||||||
|
return b.pinAddOperation(ctx, req)
|
||||||
|
case "pin-ls":
|
||||||
|
return b.pinLsOperation(ctx, req)
|
||||||
|
case "pin-rm", bindings.DeleteOperation:
|
||||||
|
return b.pinRmOperation(ctx, req)
|
||||||
|
}
|
||||||
|
return &bindings.InvokeResponse{
|
||||||
|
Data: nil,
|
||||||
|
Metadata: nil,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupPlugins(externalPluginsPath string) error {
|
||||||
|
plugins, err := ipfsLoader.NewPluginLoader("")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error loading plugins: %s", err)
|
||||||
|
}
|
||||||
|
if err := plugins.Initialize(); err != nil {
|
||||||
|
return fmt.Errorf("error initializing plugins: %s", err)
|
||||||
|
}
|
||||||
|
if err := plugins.Inject(); err != nil {
|
||||||
|
return fmt.Errorf("error initializing plugins: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,425 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Dapr Authors
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ipfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/dapr/components-contrib/bindings"
|
||||||
|
"github.com/dapr/components-contrib/internal/utils"
|
||||||
|
"github.com/dapr/components-contrib/metadata"
|
||||||
|
"github.com/dapr/kit/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
if !utils.IsTruthy(os.Getenv("IPFS_TEST")) {
|
||||||
|
log.Println("IPFS_TEST env var is not set to a truthy value; skipping tests")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSingleNodeGlobalNetwork(t *testing.T) {
|
||||||
|
var b *IPFSBinding
|
||||||
|
repoPath := t.TempDir()
|
||||||
|
|
||||||
|
// CIDS contained in the QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG folder
|
||||||
|
folderCids := []string{
|
||||||
|
"QmZTR5bcpQD7cFgTorqxZDYaew1Wqgfbd2ud9QqGPAkK2V",
|
||||||
|
"QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y",
|
||||||
|
"QmY5heUM5qgRubMDD1og9fhCPA6QdkMp3QCwd4s7gJsyE7",
|
||||||
|
"QmdncfsVm2h5Kqq9hPmU7oAVX2zTSVP3L869tgTbPYnsha",
|
||||||
|
"QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB",
|
||||||
|
"QmTumTjvcYCAvRRwQ8sDRxh8ezmrcr88YFU7iYNroGGTBZ",
|
||||||
|
}
|
||||||
|
sort.Strings(folderCids)
|
||||||
|
|
||||||
|
t.Run("init node", func(t *testing.T) {
|
||||||
|
b = NewIPFSBinding(logger.NewLogger("tests")).(*IPFSBinding)
|
||||||
|
err := b.Init(bindings.Metadata{Base: metadata.Base{
|
||||||
|
Properties: map[string]string{
|
||||||
|
"repoPath": repoPath,
|
||||||
|
"routing": "dhtclient",
|
||||||
|
},
|
||||||
|
}})
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("get operation", func(t *testing.T) {
|
||||||
|
t.Run("empty path", func(t *testing.T) {
|
||||||
|
_, err := b.Invoke(context.Background(), &bindings.InvokeRequest{
|
||||||
|
Operation: "get",
|
||||||
|
})
|
||||||
|
if assert.Error(t, err) {
|
||||||
|
assert.Equal(t, err.Error(), "metadata property 'path' is empty")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("retrieve document by CID", func(t *testing.T) {
|
||||||
|
data := getDocument(t, b, "QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB")
|
||||||
|
compareHash(t, data, "a48161fca5edd15f4649bb928c10769216fccdf317265fc75d747c1e6892f53c")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("retrieve document by IPLD", func(t *testing.T) {
|
||||||
|
// Same document, but different addressing method
|
||||||
|
data := getDocument(t, b, "/ipfs/QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG/readme")
|
||||||
|
compareHash(t, data, "a48161fca5edd15f4649bb928c10769216fccdf317265fc75d747c1e6892f53c")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("cannot retrieve folder", func(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, err := b.Invoke(ctx, &bindings.InvokeRequest{
|
||||||
|
Operation: "get",
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"path": "/ipfs/QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if assert.Error(t, err) {
|
||||||
|
assert.Equal(t, err.Error(), "path does not represent a file")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Retrieve files also to speed up pinning later
|
||||||
|
t.Run("retrieve files", func(t *testing.T) {
|
||||||
|
for _, e := range folderCids {
|
||||||
|
getDocument(t, b, e)
|
||||||
|
}
|
||||||
|
getDocument(t, b, "QmdytmR4wULMd3SLo6ePF4s3WcRHWcpnJZ7bHhoj3QB13v")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ls operation", func(t *testing.T) {
|
||||||
|
t.Run("empty path", func(t *testing.T) {
|
||||||
|
_, err := b.Invoke(context.Background(), &bindings.InvokeRequest{
|
||||||
|
Operation: "ls",
|
||||||
|
})
|
||||||
|
if assert.Error(t, err) {
|
||||||
|
assert.Equal(t, err.Error(), "metadata property 'path' is empty")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
testLsOperationResponse := func(t *testing.T, list lsOperationResponse) {
|
||||||
|
cids := make([]string, len(list))
|
||||||
|
for i, e := range list {
|
||||||
|
cids[i] = e.Cid
|
||||||
|
}
|
||||||
|
sort.Strings(cids)
|
||||||
|
assert.True(t, reflect.DeepEqual(cids, folderCids), "received='%v' expected='%v'", cids, folderCids)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("list by CID", func(t *testing.T) {
|
||||||
|
list := listPath(t, b, "QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG")
|
||||||
|
require.NotEmpty(t, list)
|
||||||
|
testLsOperationResponse(t, list)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("list by IPLD", func(t *testing.T) {
|
||||||
|
list := listPath(t, b, "/ipfs/QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG")
|
||||||
|
require.NotEmpty(t, list)
|
||||||
|
testLsOperationResponse(t, list)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pin operations", func(t *testing.T) {
|
||||||
|
t.Run("pin-ls: nothing is pinned on a new node", func(t *testing.T) {
|
||||||
|
list := listPins(t, b)
|
||||||
|
assert.Empty(t, list)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pin-add: pin file by CID", func(t *testing.T) {
|
||||||
|
res, err := b.Invoke(context.Background(), &bindings.InvokeRequest{
|
||||||
|
Operation: "pin-add",
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"path": "QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, res)
|
||||||
|
require.Empty(t, res.Data)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pin-ls: list added pin", func(t *testing.T) {
|
||||||
|
list := listPins(t, b)
|
||||||
|
assert.Len(t, list, 1)
|
||||||
|
assert.Equal(t, "QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB", list[0].Cid)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pin-add: pin file by IPLD", func(t *testing.T) {
|
||||||
|
res, err := b.Invoke(context.Background(), &bindings.InvokeRequest{
|
||||||
|
Operation: "pin-add",
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"path": "/ipfs/QmdytmR4wULMd3SLo6ePF4s3WcRHWcpnJZ7bHhoj3QB13v",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, res)
|
||||||
|
require.Empty(t, res.Data)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pin-add: recursively pin folder", func(t *testing.T) {
|
||||||
|
res, err := b.Invoke(context.Background(), &bindings.InvokeRequest{
|
||||||
|
Operation: "pin-add",
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"path": "QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, res)
|
||||||
|
require.Empty(t, res.Data)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add the folder to the list of expected items
|
||||||
|
expect := make([]string, len(folderCids)+2)
|
||||||
|
expect[0] = "QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG"
|
||||||
|
expect[1] = "QmdytmR4wULMd3SLo6ePF4s3WcRHWcpnJZ7bHhoj3QB13v"
|
||||||
|
copy(expect[2:], folderCids)
|
||||||
|
sort.Strings(expect)
|
||||||
|
|
||||||
|
t.Run("pin-ls: list added folder", func(t *testing.T) {
|
||||||
|
cids := listPinnedCids(t, b)
|
||||||
|
assert.True(t, reflect.DeepEqual(cids, expect), "received='%v' expected='%v'", cids, expect)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pin-rm: remove file", func(t *testing.T) {
|
||||||
|
res, err := b.Invoke(context.Background(), &bindings.InvokeRequest{
|
||||||
|
Operation: "pin-rm",
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"path": "QmdytmR4wULMd3SLo6ePF4s3WcRHWcpnJZ7bHhoj3QB13v",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, res)
|
||||||
|
require.Empty(t, res.Data)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Remove the un-pinned file
|
||||||
|
i := 0
|
||||||
|
for _, e := range expect {
|
||||||
|
if e != "QmdytmR4wULMd3SLo6ePF4s3WcRHWcpnJZ7bHhoj3QB13v" {
|
||||||
|
expect[i] = e
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect = expect[:i]
|
||||||
|
|
||||||
|
t.Run("pin-ls: updated after removed file", func(t *testing.T) {
|
||||||
|
cids := listPinnedCids(t, b)
|
||||||
|
assert.True(t, reflect.DeepEqual(cids, expect), "received='%v' expected='%v'", cids, expect)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pin-rm: recursively remove folder", func(t *testing.T) {
|
||||||
|
res, err := b.Invoke(context.Background(), &bindings.InvokeRequest{
|
||||||
|
Operation: "pin-rm",
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"path": "QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, res)
|
||||||
|
require.Empty(t, res.Data)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pin-rm: remove explicitly-pinned file", func(t *testing.T) {
|
||||||
|
res, err := b.Invoke(context.Background(), &bindings.InvokeRequest{
|
||||||
|
Operation: "pin-rm",
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"path": "QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, res)
|
||||||
|
require.Empty(t, res.Data)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pin-ls: updated list is empty", func(t *testing.T) {
|
||||||
|
list := listPins(t, b)
|
||||||
|
assert.Empty(t, list)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("create operation", func(t *testing.T) {
|
||||||
|
expectPins := []string{}
|
||||||
|
|
||||||
|
t.Run("add with default options", func(t *testing.T) {
|
||||||
|
path := addDocument(t, b,
|
||||||
|
[]byte("Quel ramo del lago di Como, che volge a mezzogiorno"),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Equal(t, "/ipfs/QmRW7jvkePyaAFvtapaqZ9kNkziUrmkhi4ue5oNpXS2qUx", path)
|
||||||
|
|
||||||
|
expectPins = append(expectPins, "QmRW7jvkePyaAFvtapaqZ9kNkziUrmkhi4ue5oNpXS2qUx")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("add with CID v1", func(t *testing.T) {
|
||||||
|
path := addDocument(t, b,
|
||||||
|
[]byte("Quel ramo del lago di Como, che volge a mezzogiorno"),
|
||||||
|
map[string]string{"cidVersion": "1"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Equal(t, "/ipfs/bafkreidhuwuwgycmsbj4sesi3pm6vpxpbm6byt3twex7sc2nadaxksnqeq", path)
|
||||||
|
|
||||||
|
expectPins = append(expectPins, "bafkreidhuwuwgycmsbj4sesi3pm6vpxpbm6byt3twex7sc2nadaxksnqeq")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("added files are pinned", func(t *testing.T) {
|
||||||
|
cids := listPinnedCids(t, b)
|
||||||
|
assert.True(t, reflect.DeepEqual(cids, expectPins), "received='%v' expected='%v'", cids, expectPins)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("add without pinning", func(t *testing.T) {
|
||||||
|
path := addDocument(t, b,
|
||||||
|
[]byte("😁🐶"),
|
||||||
|
map[string]string{"pin": "false"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Equal(t, "/ipfs/QmWsLpV1UUD26qHaEJqXfHazSRRZVX82M51EQ87UT7ryiR", path)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pinned documents haven't changed", func(t *testing.T) {
|
||||||
|
cids := listPinnedCids(t, b)
|
||||||
|
assert.True(t, reflect.DeepEqual(cids, expectPins), "received='%v' expected='%v'", cids, expectPins)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("add inline", func(t *testing.T) {
|
||||||
|
path := addDocument(t, b,
|
||||||
|
[]byte("😁🐶"),
|
||||||
|
map[string]string{"inline": "true"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Equal(t, "/ipfs/bafyaaeakbyeaeeqi6cpzrapqt6ilmgai", path)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if b != nil {
|
||||||
|
b.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDocument(t *testing.T, b *IPFSBinding, path string) []byte {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
res, err := b.Invoke(ctx, &bindings.InvokeRequest{
|
||||||
|
Operation: "get",
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"path": path,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, res)
|
||||||
|
require.NotEmpty(t, res.Data)
|
||||||
|
|
||||||
|
return res.Data
|
||||||
|
}
|
||||||
|
|
||||||
|
func listPath(t *testing.T, b *IPFSBinding, path string) lsOperationResponse {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
res, err := b.Invoke(ctx, &bindings.InvokeRequest{
|
||||||
|
Operation: "ls",
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"path": path,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, res)
|
||||||
|
require.NotEmpty(t, res.Data)
|
||||||
|
|
||||||
|
list := lsOperationResponse{}
|
||||||
|
err = json.Unmarshal(res.Data, &list)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func listPins(t *testing.T, b *IPFSBinding) pinLsOperationResponse {
|
||||||
|
res, err := b.Invoke(context.Background(), &bindings.InvokeRequest{
|
||||||
|
Operation: "pin-ls",
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, res)
|
||||||
|
require.NotEmpty(t, res.Data)
|
||||||
|
|
||||||
|
list := pinLsOperationResponse{}
|
||||||
|
err = json.Unmarshal(res.Data, &list)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func listPinnedCids(t *testing.T, b *IPFSBinding) []string {
|
||||||
|
list := listPins(t, b)
|
||||||
|
require.NotEmpty(t, list)
|
||||||
|
|
||||||
|
cids := make([]string, len(list))
|
||||||
|
for i, e := range list {
|
||||||
|
cids[i] = e.Cid
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(cids)
|
||||||
|
return cids
|
||||||
|
}
|
||||||
|
|
||||||
|
func addDocument(t *testing.T, b *IPFSBinding, data []byte, metadata map[string]string) string {
|
||||||
|
res, err := b.Invoke(context.Background(), &bindings.InvokeRequest{
|
||||||
|
Operation: "create",
|
||||||
|
Data: data,
|
||||||
|
Metadata: metadata,
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, res)
|
||||||
|
require.NotEmpty(t, res.Data)
|
||||||
|
|
||||||
|
o := addOperationResponse{}
|
||||||
|
err = json.Unmarshal(res.Data, &o)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, o.Path)
|
||||||
|
|
||||||
|
return o.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareHash(t *testing.T, data []byte, expect string) {
|
||||||
|
require.NotEmpty(t, data)
|
||||||
|
h := sha256.Sum256(data)
|
||||||
|
digest := hex.EncodeToString(h[:])
|
||||||
|
assert.Equal(t, expect, digest)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Dapr Authors
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ipfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
ipfsOptions "github.com/ipfs/interface-go-ipfs-core/options"
|
||||||
|
ipfsConfig "github.com/ipfs/kubo/config"
|
||||||
|
ipfsFsrepo "github.com/ipfs/kubo/repo/fsrepo"
|
||||||
|
"github.com/libp2p/go-libp2p-core/peer"
|
||||||
|
|
||||||
|
"github.com/dapr/kit/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ipfsMetadata struct {
|
||||||
|
// Path where to store the IPFS repository
|
||||||
|
// It will be initialized automatically if needed
|
||||||
|
// Defaults to the "best known path" set by IPFS
|
||||||
|
RepoPath string `mapstructure:"repoPath"`
|
||||||
|
|
||||||
|
// If set, uses an external IPFS daemon, connecting to its APIs
|
||||||
|
// Can be a HTTP(S) address or a multi-address
|
||||||
|
// If set, a local node will not be initialized
|
||||||
|
ExternalAPI string `mapstructure:"externalAPI"`
|
||||||
|
|
||||||
|
// The options below can only be set when a new local repo is being initialized
|
||||||
|
|
||||||
|
// List of bootstrap nodes, as a comma-separated string
|
||||||
|
// If empty, defaults to the official bootstrap nodes provided by the IPFS project
|
||||||
|
// Users should not modify this unless they're using a private cluster
|
||||||
|
BootstrapNodes string `mapstructure:"bootstrapNodes"`
|
||||||
|
// Swarm key to use for connecting to private IPFS networks
|
||||||
|
// If empty, the node will connect to the default, public IPFS network
|
||||||
|
// Generate with https://github.com/Kubuxu/go-ipfs-swarm-key-gen
|
||||||
|
// When using a swarm key, users should also configure the bootstrap nodes
|
||||||
|
SwarmKey string `mapstructure:"swarmKey"`
|
||||||
|
// Routing mode: "dht" (default) or "dhtclient"
|
||||||
|
Routing string `mapstructure:"routing"`
|
||||||
|
// Max local storage used
|
||||||
|
// Default: the default value used by go-ipfs (currently, "10GB")
|
||||||
|
StorageMax string `mapstructure:"storageMax"`
|
||||||
|
// Watermark for running garbage collection, 0-100 (as a percentage)
|
||||||
|
// Default: the default value used by go-ipfs (currently, 90)
|
||||||
|
StorageGCWatermark int64 `mapstructure:"storageGCWatermark"`
|
||||||
|
// Interval for running garbage collection
|
||||||
|
// Default: the default value used by go-ipfs (currently, "1h")
|
||||||
|
StorageGCPeriod string `mapstructure:"storageGCPeriod"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromMap initializes the metadata object from a map.
|
||||||
|
func (m *ipfsMetadata) FromMap(mp map[string]string) (err error) {
|
||||||
|
if len(mp) > 0 {
|
||||||
|
err = config.Decode(mp, m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.RepoPath == "" {
|
||||||
|
m.RepoPath, err = ipfsFsrepo.BestKnownPath()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error determining the best known repo path: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPFSConfig returns the configuration object for using with the go-ipfs library.
|
||||||
|
// This is executed only when initializing a new repository.
|
||||||
|
func (m *ipfsMetadata) IPFSConfig() (*ipfsConfig.Config, error) {
|
||||||
|
identity, err := ipfsConfig.CreateIdentity(io.Discard, []ipfsOptions.KeyGenerateOption{
|
||||||
|
ipfsOptions.Key.Type(ipfsOptions.Ed25519Key),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := ipfsConfig.InitWithIdentity(identity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.BootstrapNodes != "" {
|
||||||
|
var peers []peer.AddrInfo
|
||||||
|
peers, err = ipfsConfig.ParseBootstrapPeers(
|
||||||
|
strings.Split(m.BootstrapNodes, ","),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid value for metadata property 'bootstrapNodes': %v", err)
|
||||||
|
}
|
||||||
|
cfg.SetBootstrapPeers(peers)
|
||||||
|
}
|
||||||
|
|
||||||
|
r := strings.ToLower(m.Routing)
|
||||||
|
switch r {
|
||||||
|
case "dht", "dhtclient", "dhtserver", "none":
|
||||||
|
cfg.Routing.Type = ipfsConfig.NewOptionalString(r)
|
||||||
|
case "":
|
||||||
|
cfg.Routing.Type = ipfsConfig.NewOptionalString("dht")
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid value for metadata property 'routing'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.StorageMax != "" {
|
||||||
|
cfg.Datastore.StorageMax = m.StorageMax
|
||||||
|
}
|
||||||
|
if m.StorageGCWatermark != 0 {
|
||||||
|
cfg.Datastore.StorageGCWatermark = m.StorageGCWatermark
|
||||||
|
}
|
||||||
|
if m.StorageGCPeriod != "" {
|
||||||
|
cfg.Datastore.GCPeriod = m.StorageGCPeriod
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Dapr Authors
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ipfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
ipfsFiles "github.com/ipfs/go-ipfs-files"
|
||||||
|
ipfsOptions "github.com/ipfs/interface-go-ipfs-core/options"
|
||||||
|
|
||||||
|
"github.com/multiformats/go-multihash"
|
||||||
|
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
|
||||||
|
"github.com/dapr/components-contrib/bindings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler for the "add" operation, which adds a new file
|
||||||
|
func (b *IPFSBinding) addOperation(ctx context.Context, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
|
||||||
|
if len(req.Data) == 0 {
|
||||||
|
return nil, errors.New("data is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
reqMetadata := &addRequestMetadata{}
|
||||||
|
err := reqMetadata.FromMap(req.Metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
opts, err := reqMetadata.UnixfsAddOptions()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f := ipfsFiles.NewBytesFile(req.Data)
|
||||||
|
resolved, err := b.ipfsAPI.Unixfs().Add(ctx, f, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := addOperationResponse{
|
||||||
|
Path: resolved.String(),
|
||||||
|
}
|
||||||
|
enc, err := json.Marshal(res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &bindings.InvokeResponse{
|
||||||
|
Data: enc,
|
||||||
|
Metadata: nil,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type addOperationResponse struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type addRequestMetadata struct {
|
||||||
|
CidVersion *int `mapstructure:"cidVersion"`
|
||||||
|
Pin *bool `mapstructure:"pin"`
|
||||||
|
Hash *string `mapstructure:"hash"`
|
||||||
|
Inline *bool `mapstructure:"inline"`
|
||||||
|
InlineLimit *int `mapstructure:"inlineLimit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *addRequestMetadata) FromMap(mp map[string]string) (err error) {
|
||||||
|
if len(mp) > 0 {
|
||||||
|
err = mapstructure.WeakDecode(mp, m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *addRequestMetadata) UnixfsAddOptions() ([]ipfsOptions.UnixfsAddOption, error) {
|
||||||
|
opts := []ipfsOptions.UnixfsAddOption{}
|
||||||
|
if m.CidVersion != nil {
|
||||||
|
opts = append(opts, ipfsOptions.Unixfs.CidVersion(*m.CidVersion))
|
||||||
|
}
|
||||||
|
if m.Pin != nil {
|
||||||
|
opts = append(opts, ipfsOptions.Unixfs.Pin(*m.Pin))
|
||||||
|
} else {
|
||||||
|
opts = append(opts, ipfsOptions.Unixfs.Pin(true))
|
||||||
|
}
|
||||||
|
if m.Hash != nil {
|
||||||
|
hash, ok := multihash.Names[*m.Hash]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid hash %s", *m.Hash)
|
||||||
|
}
|
||||||
|
opts = append(opts, ipfsOptions.Unixfs.Hash(hash))
|
||||||
|
}
|
||||||
|
if m.Inline != nil {
|
||||||
|
opts = append(opts, ipfsOptions.Unixfs.Inline(*m.Inline))
|
||||||
|
}
|
||||||
|
if m.InlineLimit != nil {
|
||||||
|
opts = append(opts, ipfsOptions.Unixfs.InlineLimit(*m.InlineLimit))
|
||||||
|
}
|
||||||
|
return opts, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Dapr Authors
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ipfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
ipfsFiles "github.com/ipfs/go-ipfs-files"
|
||||||
|
ipfsPath "github.com/ipfs/interface-go-ipfs-core/path"
|
||||||
|
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
|
||||||
|
"github.com/dapr/components-contrib/bindings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler for the "get" operation, which retrieves a document
|
||||||
|
func (b *IPFSBinding) getOperation(ctx context.Context, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
|
||||||
|
reqMetadata := &getRequestMetadata{}
|
||||||
|
err := reqMetadata.FromMap(req.Metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if reqMetadata.Path == "" {
|
||||||
|
return nil, errors.New("metadata property 'path' is empty")
|
||||||
|
}
|
||||||
|
p := ipfsPath.New(reqMetadata.Path)
|
||||||
|
err = p.IsValid()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid value for metadata property 'path': %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := b.ipfsAPI.Unixfs().Get(ctx, p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Close()
|
||||||
|
|
||||||
|
f, ok := res.(ipfsFiles.File)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("path does not represent a file")
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := io.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &bindings.InvokeResponse{
|
||||||
|
Data: data,
|
||||||
|
Metadata: nil,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type getRequestMetadata struct {
|
||||||
|
Path string `mapstructure:"path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *getRequestMetadata) FromMap(mp map[string]string) (err error) {
|
||||||
|
if len(mp) > 0 {
|
||||||
|
err = mapstructure.WeakDecode(mp, m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Dapr Authors
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ipfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
ipfsPath "github.com/ipfs/interface-go-ipfs-core/path"
|
||||||
|
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
|
||||||
|
"github.com/dapr/components-contrib/bindings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler for the "ls" operation, which retrieves a document
|
||||||
|
func (b *IPFSBinding) lsOperation(ctx context.Context, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
|
||||||
|
reqMetadata := &lsRequestMetadata{}
|
||||||
|
err := reqMetadata.FromMap(req.Metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if reqMetadata.Path == "" {
|
||||||
|
return nil, errors.New("metadata property 'path' is empty")
|
||||||
|
}
|
||||||
|
p := ipfsPath.New(reqMetadata.Path)
|
||||||
|
err = p.IsValid()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid value for metadata property 'path': %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ls, err := b.ipfsAPI.Unixfs().Ls(ctx, p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := lsOperationResponse{}
|
||||||
|
for e := range ls {
|
||||||
|
if e.Err != nil {
|
||||||
|
return nil, e.Err
|
||||||
|
}
|
||||||
|
res = append(res, lsOperationResponseItem{
|
||||||
|
Name: e.Name,
|
||||||
|
Size: e.Size,
|
||||||
|
Type: e.Type.String(),
|
||||||
|
Cid: e.Cid.String(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
j, _ := json.Marshal(res)
|
||||||
|
return &bindings.InvokeResponse{
|
||||||
|
Data: j,
|
||||||
|
Metadata: nil,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type lsOperationResponseItem struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Size uint64 `json:"size,omitempty"`
|
||||||
|
Cid string `json:"cid,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type lsOperationResponse []lsOperationResponseItem
|
||||||
|
|
||||||
|
type lsRequestMetadata struct {
|
||||||
|
Path string `mapstructure:"path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *lsRequestMetadata) FromMap(mp map[string]string) (err error) {
|
||||||
|
if len(mp) > 0 {
|
||||||
|
err = mapstructure.WeakDecode(mp, m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Dapr Authors
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ipfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
ipfsOptions "github.com/ipfs/interface-go-ipfs-core/options"
|
||||||
|
ipfsPath "github.com/ipfs/interface-go-ipfs-core/path"
|
||||||
|
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
|
||||||
|
"github.com/dapr/components-contrib/bindings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler for the "pin-add" operation, which adds a new pin
|
||||||
|
func (b *IPFSBinding) pinAddOperation(ctx context.Context, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
|
||||||
|
reqMetadata := &pinAddRequestMetadata{}
|
||||||
|
err := reqMetadata.FromMap(req.Metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if reqMetadata.Path == "" {
|
||||||
|
return nil, errors.New("metadata property 'path' is empty")
|
||||||
|
}
|
||||||
|
p := ipfsPath.New(reqMetadata.Path)
|
||||||
|
err = p.IsValid()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid value for metadata property 'path': %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts, err := reqMetadata.PinAddOptions()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = b.ipfsAPI.Pin().Add(ctx, p, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &bindings.InvokeResponse{
|
||||||
|
Data: nil,
|
||||||
|
Metadata: nil,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type pinAddRequestMetadata struct {
|
||||||
|
Path string `mapstructure:"path"`
|
||||||
|
Recursive *bool `mapstructure:"recursive"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *pinAddRequestMetadata) FromMap(mp map[string]string) (err error) {
|
||||||
|
if len(mp) > 0 {
|
||||||
|
err = mapstructure.WeakDecode(mp, m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *pinAddRequestMetadata) PinAddOptions() ([]ipfsOptions.PinAddOption, error) {
|
||||||
|
opts := []ipfsOptions.PinAddOption{}
|
||||||
|
if m.Recursive != nil {
|
||||||
|
opts = append(opts, ipfsOptions.Pin.Recursive(*m.Recursive))
|
||||||
|
} else {
|
||||||
|
opts = append(opts, ipfsOptions.Pin.Recursive(true))
|
||||||
|
}
|
||||||
|
return opts, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Dapr Authors
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ipfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
ipfsOptions "github.com/ipfs/interface-go-ipfs-core/options"
|
||||||
|
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
|
||||||
|
"github.com/dapr/components-contrib/bindings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler for the "pin-ls" operation, which removes a pin
|
||||||
|
func (b *IPFSBinding) pinLsOperation(ctx context.Context, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
|
||||||
|
reqMetadata := &pinLsRequestMetadata{}
|
||||||
|
err := reqMetadata.FromMap(req.Metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
opts, err := reqMetadata.PinLsOptions()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ls, err := b.ipfsAPI.Pin().Ls(ctx, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := pinLsOperationResponse{}
|
||||||
|
for e := range ls {
|
||||||
|
err = e.Err()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res = append(res, pinLsOperationResponseItem{
|
||||||
|
Type: e.Type(),
|
||||||
|
Cid: e.Path().Cid().String(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
j, _ := json.Marshal(res)
|
||||||
|
return &bindings.InvokeResponse{
|
||||||
|
Data: j,
|
||||||
|
Metadata: nil,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type pinLsOperationResponseItem struct {
|
||||||
|
Cid string `json:"cid,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type pinLsOperationResponse []pinLsOperationResponseItem
|
||||||
|
|
||||||
|
type pinLsRequestMetadata struct {
|
||||||
|
Type *string `mapstructure:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *pinLsRequestMetadata) FromMap(mp map[string]string) (err error) {
|
||||||
|
if len(mp) > 0 {
|
||||||
|
err = mapstructure.WeakDecode(mp, m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *pinLsRequestMetadata) PinLsOptions() ([]ipfsOptions.PinLsOption, error) {
|
||||||
|
opts := []ipfsOptions.PinLsOption{}
|
||||||
|
if m.Type != nil {
|
||||||
|
switch strings.ToLower(*m.Type) {
|
||||||
|
case "direct":
|
||||||
|
opts = append(opts, ipfsOptions.Pin.Ls.Direct())
|
||||||
|
case "recursive":
|
||||||
|
opts = append(opts, ipfsOptions.Pin.Ls.Recursive())
|
||||||
|
case "indirect":
|
||||||
|
opts = append(opts, ipfsOptions.Pin.Ls.Indirect())
|
||||||
|
case "all":
|
||||||
|
opts = append(opts, ipfsOptions.Pin.Ls.All())
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid value for metadata property 'type'")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
opts = append(opts, ipfsOptions.Pin.Ls.All())
|
||||||
|
}
|
||||||
|
return opts, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Dapr Authors
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ipfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
ipfsOptions "github.com/ipfs/interface-go-ipfs-core/options"
|
||||||
|
ipfsPath "github.com/ipfs/interface-go-ipfs-core/path"
|
||||||
|
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
|
||||||
|
"github.com/dapr/components-contrib/bindings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler for the "pin-rm" operation, which removes a pin
|
||||||
|
func (b *IPFSBinding) pinRmOperation(ctx context.Context, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
|
||||||
|
reqMetadata := &pinRmRequestMetadata{}
|
||||||
|
err := reqMetadata.FromMap(req.Metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if reqMetadata.Path == "" {
|
||||||
|
return nil, errors.New("metadata property 'path' is empty")
|
||||||
|
}
|
||||||
|
p := ipfsPath.New(reqMetadata.Path)
|
||||||
|
err = p.IsValid()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid value for metadata property 'path': %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts, err := reqMetadata.PinRmOptions()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = b.ipfsAPI.Pin().Rm(ctx, p, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &bindings.InvokeResponse{
|
||||||
|
Data: nil,
|
||||||
|
Metadata: nil,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type pinRmRequestMetadata struct {
|
||||||
|
Path string `mapstructure:"path"`
|
||||||
|
Recursive *bool `mapstructure:"recursive"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *pinRmRequestMetadata) FromMap(mp map[string]string) (err error) {
|
||||||
|
if len(mp) > 0 {
|
||||||
|
err = mapstructure.WeakDecode(mp, m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *pinRmRequestMetadata) PinRmOptions() ([]ipfsOptions.PinRmOption, error) {
|
||||||
|
opts := []ipfsOptions.PinRmOption{}
|
||||||
|
if m.Recursive != nil {
|
||||||
|
opts = append(opts, ipfsOptions.Pin.RmRecursive(*m.Recursive))
|
||||||
|
} else {
|
||||||
|
opts = append(opts, ipfsOptions.Pin.RmRecursive(true))
|
||||||
|
}
|
||||||
|
return opts, nil
|
||||||
|
}
|
||||||
|
|
@ -38,7 +38,7 @@ type Binding struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewKafka returns a new kafka binding instance.
|
// NewKafka returns a new kafka binding instance.
|
||||||
func NewKafka(logger logger.Logger) *Binding {
|
func NewKafka(logger logger.Logger) bindings.InputOutputBinding {
|
||||||
k := kafka.NewKafka(logger)
|
k := kafka.NewKafka(logger)
|
||||||
// in kafka binding component, disable consumer retry by default
|
// in kafka binding component, disable consumer retry by default
|
||||||
k.DefaultConsumeRetryEnabled = false
|
k.DefaultConsumeRetryEnabled = false
|
||||||
|
|
|
||||||
|
|
@ -31,10 +31,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type kubernetesInput struct {
|
type kubernetesInput struct {
|
||||||
kubeClient kubernetes.Interface
|
kubeClient kubernetes.Interface
|
||||||
namespace string
|
namespace string
|
||||||
resyncPeriodInSec time.Duration
|
resyncPeriod time.Duration
|
||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
type EventResponse struct {
|
type EventResponse struct {
|
||||||
|
|
@ -68,9 +68,9 @@ func (k *kubernetesInput) parseMetadata(metadata bindings.Metadata) error {
|
||||||
intval, err := strconv.ParseInt(val, 10, 64)
|
intval, err := strconv.ParseInt(val, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
k.logger.Warnf("invalid resyncPeriodInSec %s; %v; defaulting to 10s", val, err)
|
k.logger.Warnf("invalid resyncPeriodInSec %s; %v; defaulting to 10s", val, err)
|
||||||
k.resyncPeriodInSec = time.Second * 10
|
k.resyncPeriod = time.Second * 10
|
||||||
} else {
|
} else {
|
||||||
k.resyncPeriodInSec = time.Second * time.Duration(intval)
|
k.resyncPeriod = time.Second * time.Duration(intval)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,11 +84,11 @@ func (k *kubernetesInput) Read(ctx context.Context, handler bindings.Handler) er
|
||||||
k.namespace,
|
k.namespace,
|
||||||
fields.Everything(),
|
fields.Everything(),
|
||||||
)
|
)
|
||||||
var resultChan chan EventResponse = make(chan EventResponse)
|
resultChan := make(chan EventResponse)
|
||||||
_, controller := cache.NewInformer(
|
_, controller := cache.NewInformer(
|
||||||
watchlist,
|
watchlist,
|
||||||
&v1.Event{},
|
&v1.Event{},
|
||||||
k.resyncPeriodInSec,
|
k.resyncPeriod,
|
||||||
cache.ResourceEventHandlerFuncs{
|
cache.ResourceEventHandlerFuncs{
|
||||||
AddFunc: func(obj interface{}) {
|
AddFunc: func(obj interface{}) {
|
||||||
if obj != nil {
|
if obj != nil {
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ func TestParseMetadata(t *testing.T) {
|
||||||
i.parseMetadata(m)
|
i.parseMetadata(m)
|
||||||
|
|
||||||
assert.Equal(t, nsName, i.namespace, "The namespaces should be the same.")
|
assert.Equal(t, nsName, i.namespace, "The namespaces should be the same.")
|
||||||
assert.Equal(t, resyncPeriod, i.resyncPeriodInSec, "The resyncPeriod should be the same.")
|
assert.Equal(t, resyncPeriod, i.resyncPeriod, "The resyncPeriod should be the same.")
|
||||||
})
|
})
|
||||||
t.Run("parse metadata no namespace", func(t *testing.T) {
|
t.Run("parse metadata no namespace", func(t *testing.T) {
|
||||||
m := bindings.Metadata{}
|
m := bindings.Metadata{}
|
||||||
|
|
@ -55,6 +55,6 @@ func TestParseMetadata(t *testing.T) {
|
||||||
|
|
||||||
assert.Nil(t, err, "Expected err to be nil.")
|
assert.Nil(t, err, "Expected err to be nil.")
|
||||||
assert.Equal(t, nsName, i.namespace, "The namespaces should be the same.")
|
assert.Equal(t, nsName, i.namespace, "The namespaces should be the same.")
|
||||||
assert.Equal(t, time.Second*10, i.resyncPeriodInSec, "The resyncPeriod should be the same.")
|
assert.Equal(t, time.Second*10, i.resyncPeriod, "The resyncPeriod should be the same.")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
@ -52,7 +52,7 @@ type createResponse struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLocalStorage returns a new LocalStorage instance.
|
// NewLocalStorage returns a new LocalStorage instance.
|
||||||
func NewLocalStorage(logger logger.Logger) *LocalStorage {
|
func NewLocalStorage(logger logger.Logger) bindings.OutputBinding {
|
||||||
return &LocalStorage{logger: logger}
|
return &LocalStorage{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -153,7 +153,7 @@ func (ls *LocalStorage) get(filename string, req *bindings.InvokeRequest) (*bind
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := ioutil.ReadAll(f)
|
b, err := io.ReadAll(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ls.logger.Debugf("%s", err)
|
ls.logger.Debugf("%s", err)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import (
|
||||||
func TestParseMetadata(t *testing.T) {
|
func TestParseMetadata(t *testing.T) {
|
||||||
m := bindings.Metadata{}
|
m := bindings.Metadata{}
|
||||||
m.Properties = map[string]string{"rootPath": "/files"}
|
m.Properties = map[string]string{"rootPath": "/files"}
|
||||||
localStorage := NewLocalStorage(logger.NewLogger("test"))
|
localStorage := NewLocalStorage(logger.NewLogger("test")).(*LocalStorage)
|
||||||
meta, err := localStorage.parseMetadata(m)
|
meta, err := localStorage.parseMetadata(m)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, "/files", meta.RootPath)
|
assert.Equal(t, "/files", meta.RootPath)
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,9 @@ limitations under the License.
|
||||||
|
|
||||||
package bindings
|
package bindings
|
||||||
|
|
||||||
|
import "github.com/dapr/components-contrib/metadata"
|
||||||
|
|
||||||
// Metadata represents a set of binding specific properties.
|
// Metadata represents a set of binding specific properties.
|
||||||
type Metadata struct {
|
type Metadata struct {
|
||||||
Name string
|
metadata.Base `json:",inline"`
|
||||||
Properties map[string]string `json:"properties"`
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ type MQTT struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMQTT returns a new MQTT instance.
|
// NewMQTT returns a new MQTT instance.
|
||||||
func NewMQTT(logger logger.Logger) *MQTT {
|
func NewMQTT(logger logger.Logger) bindings.InputOutputBinding {
|
||||||
return &MQTT{logger: logger}
|
return &MQTT{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/dapr/components-contrib/bindings"
|
"github.com/dapr/components-contrib/bindings"
|
||||||
|
mdata "github.com/dapr/components-contrib/metadata"
|
||||||
"github.com/dapr/kit/logger"
|
"github.com/dapr/kit/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -21,7 +22,7 @@ const (
|
||||||
// listener 1883
|
// listener 1883
|
||||||
// allow_anonymous true
|
// allow_anonymous true
|
||||||
// And run:
|
// And run:
|
||||||
// nolint:misspell
|
//nolint:misspell
|
||||||
// docker run -d -v mosquitto.conf:/mosquitto/config/mosquitto.conf --name test-mqtt -p 1883:1883 eclipse-mosquitto:2
|
// docker run -d -v mosquitto.conf:/mosquitto/config/mosquitto.conf --name test-mqtt -p 1883:1883 eclipse-mosquitto:2
|
||||||
// In that case the connection string will be: tcp://127.0.0.1:1883
|
// In that case the connection string will be: tcp://127.0.0.1:1883
|
||||||
testMQTTConnectionStringEnvKey = "DAPR_TEST_MQTT_URL"
|
testMQTTConnectionStringEnvKey = "DAPR_TEST_MQTT_URL"
|
||||||
|
|
@ -47,7 +48,7 @@ func TestInvokeWithTopic(t *testing.T) {
|
||||||
const msgCustomized = "hello from customized"
|
const msgCustomized = "hello from customized"
|
||||||
dataCustomized := []byte(msgCustomized)
|
dataCustomized := []byte(msgCustomized)
|
||||||
|
|
||||||
metadata := bindings.Metadata{
|
metadata := bindings.Metadata{Base: mdata.Base{
|
||||||
Name: "testQueue",
|
Name: "testQueue",
|
||||||
Properties: map[string]string{
|
Properties: map[string]string{
|
||||||
"consumerID": uuid.NewString(),
|
"consumerID": uuid.NewString(),
|
||||||
|
|
@ -58,11 +59,11 @@ func TestInvokeWithTopic(t *testing.T) {
|
||||||
"cleanSession": "true",
|
"cleanSession": "true",
|
||||||
"backOffMaxRetries": "0",
|
"backOffMaxRetries": "0",
|
||||||
},
|
},
|
||||||
}
|
}}
|
||||||
|
|
||||||
logger := logger.NewLogger("test")
|
logger := logger.NewLogger("test")
|
||||||
|
|
||||||
r := NewMQTT(logger)
|
r := NewMQTT(logger).(*MQTT)
|
||||||
err := r.Init(metadata)
|
err := r.Init(metadata)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/dapr/components-contrib/bindings"
|
"github.com/dapr/components-contrib/bindings"
|
||||||
|
mdata "github.com/dapr/components-contrib/metadata"
|
||||||
"github.com/dapr/kit/logger"
|
"github.com/dapr/kit/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -41,9 +42,9 @@ func TestParseMetadata(t *testing.T) {
|
||||||
t.Run("metadata is correct", func(t *testing.T) {
|
t.Run("metadata is correct", func(t *testing.T) {
|
||||||
fakeProperties := getFakeProperties()
|
fakeProperties := getFakeProperties()
|
||||||
|
|
||||||
fakeMetaData := bindings.Metadata{
|
fakeMetaData := bindings.Metadata{Base: mdata.Base{
|
||||||
Properties: fakeProperties,
|
Properties: fakeProperties,
|
||||||
}
|
}}
|
||||||
|
|
||||||
m, err := parseMQTTMetaData(fakeMetaData)
|
m, err := parseMQTTMetaData(fakeMetaData)
|
||||||
|
|
||||||
|
|
@ -57,7 +58,7 @@ func TestParseMetadata(t *testing.T) {
|
||||||
|
|
||||||
t.Run("missing topic", func(t *testing.T) {
|
t.Run("missing topic", func(t *testing.T) {
|
||||||
fakeProperties := getFakeProperties()
|
fakeProperties := getFakeProperties()
|
||||||
fakeMetaData := bindings.Metadata{Name: "binging-test", Properties: fakeProperties}
|
fakeMetaData := bindings.Metadata{Base: mdata.Base{Name: "binging-test", Properties: fakeProperties}}
|
||||||
fakeMetaData.Properties["topic"] = ""
|
fakeMetaData.Properties["topic"] = ""
|
||||||
_, err := parseMQTTMetaData(fakeMetaData)
|
_, err := parseMQTTMetaData(fakeMetaData)
|
||||||
|
|
||||||
|
|
@ -67,7 +68,7 @@ func TestParseMetadata(t *testing.T) {
|
||||||
|
|
||||||
t.Run("missing consumerID", func(t *testing.T) {
|
t.Run("missing consumerID", func(t *testing.T) {
|
||||||
fakeProperties := getFakeProperties()
|
fakeProperties := getFakeProperties()
|
||||||
fakeMetaData := bindings.Metadata{Name: "binging-test", Properties: fakeProperties}
|
fakeMetaData := bindings.Metadata{Base: mdata.Base{Name: "binging-test", Properties: fakeProperties}}
|
||||||
fakeMetaData.Properties["consumerID"] = ""
|
fakeMetaData.Properties["consumerID"] = ""
|
||||||
_, err := parseMQTTMetaData(fakeMetaData)
|
_, err := parseMQTTMetaData(fakeMetaData)
|
||||||
|
|
||||||
|
|
@ -78,7 +79,7 @@ func TestParseMetadata(t *testing.T) {
|
||||||
t.Run("url is not given", func(t *testing.T) {
|
t.Run("url is not given", func(t *testing.T) {
|
||||||
fakeProperties := getFakeProperties()
|
fakeProperties := getFakeProperties()
|
||||||
|
|
||||||
fakeMetaData := bindings.Metadata{Name: "binging-test", Properties: fakeProperties}
|
fakeMetaData := bindings.Metadata{Base: mdata.Base{Name: "binging-test", Properties: fakeProperties}}
|
||||||
fakeMetaData.Properties[mqttURL] = ""
|
fakeMetaData.Properties[mqttURL] = ""
|
||||||
|
|
||||||
m, err := parseMQTTMetaData(fakeMetaData)
|
m, err := parseMQTTMetaData(fakeMetaData)
|
||||||
|
|
@ -91,7 +92,7 @@ func TestParseMetadata(t *testing.T) {
|
||||||
t.Run("qos and retain is not given", func(t *testing.T) {
|
t.Run("qos and retain is not given", func(t *testing.T) {
|
||||||
fakeProperties := getFakeProperties()
|
fakeProperties := getFakeProperties()
|
||||||
|
|
||||||
fakeMetaData := bindings.Metadata{Name: "binging-test", Properties: fakeProperties}
|
fakeMetaData := bindings.Metadata{Base: mdata.Base{Name: "binging-test", Properties: fakeProperties}}
|
||||||
fakeMetaData.Properties[mqttQOS] = ""
|
fakeMetaData.Properties[mqttQOS] = ""
|
||||||
fakeMetaData.Properties[mqttRetain] = ""
|
fakeMetaData.Properties[mqttRetain] = ""
|
||||||
|
|
||||||
|
|
@ -107,7 +108,7 @@ func TestParseMetadata(t *testing.T) {
|
||||||
t.Run("invalid clean session field", func(t *testing.T) {
|
t.Run("invalid clean session field", func(t *testing.T) {
|
||||||
fakeProperties := getFakeProperties()
|
fakeProperties := getFakeProperties()
|
||||||
|
|
||||||
fakeMetaData := bindings.Metadata{Name: "binging-test", Properties: fakeProperties}
|
fakeMetaData := bindings.Metadata{Base: mdata.Base{Name: "binging-test", Properties: fakeProperties}}
|
||||||
fakeMetaData.Properties[mqttCleanSession] = "randomString"
|
fakeMetaData.Properties[mqttCleanSession] = "randomString"
|
||||||
|
|
||||||
m, err := parseMQTTMetaData(fakeMetaData)
|
m, err := parseMQTTMetaData(fakeMetaData)
|
||||||
|
|
@ -120,7 +121,7 @@ func TestParseMetadata(t *testing.T) {
|
||||||
|
|
||||||
t.Run("invalid ca certificate", func(t *testing.T) {
|
t.Run("invalid ca certificate", func(t *testing.T) {
|
||||||
fakeProperties := getFakeProperties()
|
fakeProperties := getFakeProperties()
|
||||||
fakeMetaData := bindings.Metadata{Name: "binging-test", Properties: fakeProperties}
|
fakeMetaData := bindings.Metadata{Base: mdata.Base{Name: "binging-test", Properties: fakeProperties}}
|
||||||
fakeMetaData.Properties[mqttCACert] = "randomNonPEMBlockCA"
|
fakeMetaData.Properties[mqttCACert] = "randomNonPEMBlockCA"
|
||||||
_, err := parseMQTTMetaData(fakeMetaData)
|
_, err := parseMQTTMetaData(fakeMetaData)
|
||||||
|
|
||||||
|
|
@ -130,7 +131,7 @@ func TestParseMetadata(t *testing.T) {
|
||||||
|
|
||||||
t.Run("valid ca certificate", func(t *testing.T) {
|
t.Run("valid ca certificate", func(t *testing.T) {
|
||||||
fakeProperties := getFakeProperties()
|
fakeProperties := getFakeProperties()
|
||||||
fakeMetaData := bindings.Metadata{Name: "binging-test", Properties: fakeProperties}
|
fakeMetaData := bindings.Metadata{Base: mdata.Base{Name: "binging-test", Properties: fakeProperties}}
|
||||||
fakeMetaData.Properties[mqttCACert] = "-----BEGIN CERTIFICATE-----\nMIICyDCCAbACCQDb8BtgvbqW5jANBgkqhkiG9w0BAQsFADAmMQswCQYDVQQGEwJJ\nTjEXMBUGA1UEAwwOZGFwck1xdHRUZXN0Q0EwHhcNMjAwODEyMDY1MzU4WhcNMjUw\nODEyMDY1MzU4WjAmMQswCQYDVQQGEwJJTjEXMBUGA1UEAwwOZGFwck1xdHRUZXN0\nQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDEXte1GBxFJaygsEnK\nHV2AxazZW6Vppv+i50AuURHcaGo0i8G5CTfHzSKrYtTFfBskUspl+2N8GPV5c8Eb\ng+PP6YFn1wiHVz+wRSk3BD35DcGOT2o4XsJw5tiAzJkbpAOYCYl7KAM+BtOf41uC\nd6TdqmawhRGtv1ND2WtyJOT6A3KcUfjhL4TFEhWoljPJVay4TQoJcZMAImD/Xcxw\n6urv6wmUJby3/RJ3I46ZNH3zxEw5vSq1TuzuXxQmfPJG0ZPKJtQZ2nkZ3PNZe4bd\nNUa83YgQap7nBhYdYMMsQyLES2qy3mPcemBVoBWRGODel4PMEcsQiOhAyloAF2d3\nhd+LAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAK13X5JYBy78vHYoP0Oq9fe5XBbL\nuRM8YLnet9b/bXTGG4SnCCOGqWz99swYK7SVyR5l2h8SAoLzeNV61PtaZ6fHrbar\noxSL7BoRXOhMH6LQATadyvwlJ71uqlagqya7soaPK09TtfzeebLT0QkRCWT9b9lQ\nDBvBVCaFidynJL1ts21m5yUdIY4JSu4sGZGb4FRGFdBv/hD3wH8LAkOppsSv3C/Q\nkfkDDSQzYbdMoBuXmafvi3He7Rv+e6Tj9or1rrWdx0MIKlZPzz4DOe5Rh112uRB9\n7xPHJt16c+Ya3DKpchwwdNcki0vFchlpV96HK8sMCoY9kBzPhkEQLdiBGv4=\n-----END CERTIFICATE-----\n"
|
fakeMetaData.Properties[mqttCACert] = "-----BEGIN CERTIFICATE-----\nMIICyDCCAbACCQDb8BtgvbqW5jANBgkqhkiG9w0BAQsFADAmMQswCQYDVQQGEwJJ\nTjEXMBUGA1UEAwwOZGFwck1xdHRUZXN0Q0EwHhcNMjAwODEyMDY1MzU4WhcNMjUw\nODEyMDY1MzU4WjAmMQswCQYDVQQGEwJJTjEXMBUGA1UEAwwOZGFwck1xdHRUZXN0\nQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDEXte1GBxFJaygsEnK\nHV2AxazZW6Vppv+i50AuURHcaGo0i8G5CTfHzSKrYtTFfBskUspl+2N8GPV5c8Eb\ng+PP6YFn1wiHVz+wRSk3BD35DcGOT2o4XsJw5tiAzJkbpAOYCYl7KAM+BtOf41uC\nd6TdqmawhRGtv1ND2WtyJOT6A3KcUfjhL4TFEhWoljPJVay4TQoJcZMAImD/Xcxw\n6urv6wmUJby3/RJ3I46ZNH3zxEw5vSq1TuzuXxQmfPJG0ZPKJtQZ2nkZ3PNZe4bd\nNUa83YgQap7nBhYdYMMsQyLES2qy3mPcemBVoBWRGODel4PMEcsQiOhAyloAF2d3\nhd+LAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAK13X5JYBy78vHYoP0Oq9fe5XBbL\nuRM8YLnet9b/bXTGG4SnCCOGqWz99swYK7SVyR5l2h8SAoLzeNV61PtaZ6fHrbar\noxSL7BoRXOhMH6LQATadyvwlJ71uqlagqya7soaPK09TtfzeebLT0QkRCWT9b9lQ\nDBvBVCaFidynJL1ts21m5yUdIY4JSu4sGZGb4FRGFdBv/hD3wH8LAkOppsSv3C/Q\nkfkDDSQzYbdMoBuXmafvi3He7Rv+e6Tj9or1rrWdx0MIKlZPzz4DOe5Rh112uRB9\n7xPHJt16c+Ya3DKpchwwdNcki0vFchlpV96HK8sMCoY9kBzPhkEQLdiBGv4=\n-----END CERTIFICATE-----\n"
|
||||||
m, err := parseMQTTMetaData(fakeMetaData)
|
m, err := parseMQTTMetaData(fakeMetaData)
|
||||||
|
|
||||||
|
|
@ -146,7 +147,7 @@ func TestParseMetadata(t *testing.T) {
|
||||||
|
|
||||||
t.Run("invalid client certificate", func(t *testing.T) {
|
t.Run("invalid client certificate", func(t *testing.T) {
|
||||||
fakeProperties := getFakeProperties()
|
fakeProperties := getFakeProperties()
|
||||||
fakeMetaData := bindings.Metadata{Name: "binging-test", Properties: fakeProperties}
|
fakeMetaData := bindings.Metadata{Base: mdata.Base{Name: "binging-test", Properties: fakeProperties}}
|
||||||
fakeMetaData.Properties[mqttClientCert] = "randomNonPEMBlockClientCert"
|
fakeMetaData.Properties[mqttClientCert] = "randomNonPEMBlockClientCert"
|
||||||
_, err := parseMQTTMetaData(fakeMetaData)
|
_, err := parseMQTTMetaData(fakeMetaData)
|
||||||
|
|
||||||
|
|
@ -156,7 +157,7 @@ func TestParseMetadata(t *testing.T) {
|
||||||
|
|
||||||
t.Run("valid client certificate", func(t *testing.T) {
|
t.Run("valid client certificate", func(t *testing.T) {
|
||||||
fakeProperties := getFakeProperties()
|
fakeProperties := getFakeProperties()
|
||||||
fakeMetaData := bindings.Metadata{Name: "binging-test", Properties: fakeProperties}
|
fakeMetaData := bindings.Metadata{Base: mdata.Base{Name: "binging-test", Properties: fakeProperties}}
|
||||||
fakeMetaData.Properties[mqttClientCert] = "-----BEGIN CERTIFICATE-----\nMIICzDCCAbQCCQDBKDMS3SHsDzANBgkqhkiG9w0BAQUFADAmMQswCQYDVQQGEwJJ\nTjEXMBUGA1UEAwwOZGFwck1xdHRUZXN0Q0EwHhcNMjAwODEyMDY1NTE1WhcNMjEw\nODA3MDY1NTE1WjAqMQswCQYDVQQGEwJJTjEbMBkGA1UEAwwSZGFwck1xdHRUZXN0\nQ2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5IDfsGI2pb4W\nt3CjckrKuNeTrgmla3sXxSI5wfDgLGd/XkNu++M6yi9ABaBiYChpxbylqIeAn/HT\n3r/nhcb+bldMtEkU9tODHy/QDhvN2UGFjRsMfzO9p1oMpTnRdJCHYinE+oqVced5\nHI+UEofAU+1eiIXqJGKrdfn4gvaHst4QfVPvui8WzJq9TMkEhEME+5hs3VKyKZr2\nqjIxzr7nLVod3DBf482VjxRI06Ip3fPvNuMWwzj2G+Rj8PMcBjoKeCLQL9uQh7f1\nTWHuACqNIrmFEUQWdGETnRjHWIvw0NEL40+Ur2b5+7/hoqnTzReJ3XUe1jM3l44f\nl0rOf4hu2QIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQAT9yoIeX0LTsvx7/b+8V3a\nkP+j8u97QCc8n5xnMpivcMEk5cfqXX5Llv2EUJ9kBsynrJwT7ujhTJXSA/zb2UdC\nKH8PaSrgIlLwQNZMDofbz6+zPbjStkgne/ZQkTDIxY73sGpJL8LsQVO9p2KjOpdj\nSf9KuJhLzcHolh7ry3ZrkOg+QlMSvseeDRAxNhpkJrGQ6piXoUiEeKKNa0rWTMHx\nIP1Hqj+hh7jgqoQR48NL2jNng7I64HqTl6Mv2fiNfINiw+5xmXTB0QYkGU5NvPBO\naKcCRcGlU7ND89BogQPZsl/P04tAuQqpQWffzT4sEEOyWSVGda4N2Ys3GSQGBv8e\n-----END CERTIFICATE-----\n"
|
fakeMetaData.Properties[mqttClientCert] = "-----BEGIN CERTIFICATE-----\nMIICzDCCAbQCCQDBKDMS3SHsDzANBgkqhkiG9w0BAQUFADAmMQswCQYDVQQGEwJJ\nTjEXMBUGA1UEAwwOZGFwck1xdHRUZXN0Q0EwHhcNMjAwODEyMDY1NTE1WhcNMjEw\nODA3MDY1NTE1WjAqMQswCQYDVQQGEwJJTjEbMBkGA1UEAwwSZGFwck1xdHRUZXN0\nQ2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5IDfsGI2pb4W\nt3CjckrKuNeTrgmla3sXxSI5wfDgLGd/XkNu++M6yi9ABaBiYChpxbylqIeAn/HT\n3r/nhcb+bldMtEkU9tODHy/QDhvN2UGFjRsMfzO9p1oMpTnRdJCHYinE+oqVced5\nHI+UEofAU+1eiIXqJGKrdfn4gvaHst4QfVPvui8WzJq9TMkEhEME+5hs3VKyKZr2\nqjIxzr7nLVod3DBf482VjxRI06Ip3fPvNuMWwzj2G+Rj8PMcBjoKeCLQL9uQh7f1\nTWHuACqNIrmFEUQWdGETnRjHWIvw0NEL40+Ur2b5+7/hoqnTzReJ3XUe1jM3l44f\nl0rOf4hu2QIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQAT9yoIeX0LTsvx7/b+8V3a\nkP+j8u97QCc8n5xnMpivcMEk5cfqXX5Llv2EUJ9kBsynrJwT7ujhTJXSA/zb2UdC\nKH8PaSrgIlLwQNZMDofbz6+zPbjStkgne/ZQkTDIxY73sGpJL8LsQVO9p2KjOpdj\nSf9KuJhLzcHolh7ry3ZrkOg+QlMSvseeDRAxNhpkJrGQ6piXoUiEeKKNa0rWTMHx\nIP1Hqj+hh7jgqoQR48NL2jNng7I64HqTl6Mv2fiNfINiw+5xmXTB0QYkGU5NvPBO\naKcCRcGlU7ND89BogQPZsl/P04tAuQqpQWffzT4sEEOyWSVGda4N2Ys3GSQGBv8e\n-----END CERTIFICATE-----\n"
|
||||||
m, err := parseMQTTMetaData(fakeMetaData)
|
m, err := parseMQTTMetaData(fakeMetaData)
|
||||||
|
|
||||||
|
|
@ -172,7 +173,7 @@ func TestParseMetadata(t *testing.T) {
|
||||||
|
|
||||||
t.Run("invalid client certificate key", func(t *testing.T) {
|
t.Run("invalid client certificate key", func(t *testing.T) {
|
||||||
fakeProperties := getFakeProperties()
|
fakeProperties := getFakeProperties()
|
||||||
fakeMetaData := bindings.Metadata{Name: "binging-test", Properties: fakeProperties}
|
fakeMetaData := bindings.Metadata{Base: mdata.Base{Name: "binging-test", Properties: fakeProperties}}
|
||||||
fakeMetaData.Properties[mqttClientKey] = "randomNonPEMBlockClientKey"
|
fakeMetaData.Properties[mqttClientKey] = "randomNonPEMBlockClientKey"
|
||||||
_, err := parseMQTTMetaData(fakeMetaData)
|
_, err := parseMQTTMetaData(fakeMetaData)
|
||||||
|
|
||||||
|
|
@ -182,7 +183,7 @@ func TestParseMetadata(t *testing.T) {
|
||||||
|
|
||||||
t.Run("valid client certificate key", func(t *testing.T) {
|
t.Run("valid client certificate key", func(t *testing.T) {
|
||||||
fakeProperties := getFakeProperties()
|
fakeProperties := getFakeProperties()
|
||||||
fakeMetaData := bindings.Metadata{Name: "binging-test", Properties: fakeProperties}
|
fakeMetaData := bindings.Metadata{Base: mdata.Base{Name: "binging-test", Properties: fakeProperties}}
|
||||||
fakeMetaData.Properties[mqttClientKey] = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA5IDfsGI2pb4Wt3CjckrKuNeTrgmla3sXxSI5wfDgLGd/XkNu\n++M6yi9ABaBiYChpxbylqIeAn/HT3r/nhcb+bldMtEkU9tODHy/QDhvN2UGFjRsM\nfzO9p1oMpTnRdJCHYinE+oqVced5HI+UEofAU+1eiIXqJGKrdfn4gvaHst4QfVPv\nui8WzJq9TMkEhEME+5hs3VKyKZr2qjIxzr7nLVod3DBf482VjxRI06Ip3fPvNuMW\nwzj2G+Rj8PMcBjoKeCLQL9uQh7f1TWHuACqNIrmFEUQWdGETnRjHWIvw0NEL40+U\nr2b5+7/hoqnTzReJ3XUe1jM3l44fl0rOf4hu2QIDAQABAoIBAQCVMINb4TP20P55\n9IPyqlxjhPT563hijXK+lhMJyiBDPavOOs7qjLikq2bshYPVbm1o2jt6pkXXqAeB\n5t/d20fheQQurYyPfxecNBZuL78duwbcUy28m2aXLlcVRYO4zGhoMgdW4UajoNLV\nT/UIiDONWGyhTHXMHdP+6h9UOmvs3o4b225AuLrw9n6QO5I1Se8lcfOTIqR1fy4O\nGsUWEQPdW0X3Dhgpx7kDIuBTAQzbjD31PCR1U8h2wsCeEe6hPCrsMbo/D019weol\ndi40tbWR1/oNz0+vro2d9YDPJkXN0gmpT51Z4YJoexZBdyzO5z4DMSdn5yczzt6p\nQq8LsXAFAoGBAPYXRbC4OxhtuC+xr8KRkaCCMjtjUWFbFWf6OFgUS9b5uPz9xvdY\nXo7wBP1zp2dS8yFsdIYH5Six4Z5iOuDR4sVixzjabhwedL6bmS1zV5qcCWeASKX1\nURgSkfMmC4Tg3LBgZ9YxySFcVRjikxljkS3eK7Mp7Xmj5afe7qV73TJfAoGBAO20\nTtw2RGe02xnydZmmwf+NpQHOA9S0JsehZA6NRbtPEN/C8bPJIq4VABC5zcH+tfYf\nzndbDlGhuk+qpPA590rG5RSOUjYnQFq7njdSfFyok9dXSZQTjJwFnG2oy0LmgjCe\nROYnbCzD+a+gBKV4xlo2M80OLakQ3zOwPT0xNRnHAoGATLEj/tbrU8mdxP9TDwfe\nom7wyKFDE1wXZ7gLJyfsGqrog69y+lKH5XPXmkUYvpKTQq9SARMkz3HgJkPmpXnD\nelA2Vfl8pza2m1BShF+VxZErPR41hcLV6vKemXAZ1udc33qr4YzSaZskygSSYy8s\nZ2b9p3BBmc8CGzbWmKvpW3ECgYEAn7sFLxdMWj/+5221Nr4HKPn+wrq0ek9gq884\n1Ep8bETSOvrdvolPQ5mbBKJGsLC/h5eR/0Rx18sMzpIF6eOZ2GbU8z474mX36cCf\nrd9A8Gbbid3+9IE6gHGIz2uYwujw3UjNVbdyCpbahvjJhoQlDePUZVu8tRpAUpSA\nYklZvGsCgYBuIlOFTNGMVUnwfzrcS9a/31LSvWTZa8w2QFjsRPMYFezo2l4yWs4D\nPEpeuoJm+Gp6F6ayjoeyOw9mvMBH5hAZr4WjbiU6UodzEHREAsLAzCzcRyIpnDE6\nPW1c3j60r8AHVufkWTA+8B9WoLC5MqcYTV3beMGnNGGqS2PeBom63Q==\n-----END RSA PRIVATE KEY-----\n"
|
fakeMetaData.Properties[mqttClientKey] = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA5IDfsGI2pb4Wt3CjckrKuNeTrgmla3sXxSI5wfDgLGd/XkNu\n++M6yi9ABaBiYChpxbylqIeAn/HT3r/nhcb+bldMtEkU9tODHy/QDhvN2UGFjRsM\nfzO9p1oMpTnRdJCHYinE+oqVced5HI+UEofAU+1eiIXqJGKrdfn4gvaHst4QfVPv\nui8WzJq9TMkEhEME+5hs3VKyKZr2qjIxzr7nLVod3DBf482VjxRI06Ip3fPvNuMW\nwzj2G+Rj8PMcBjoKeCLQL9uQh7f1TWHuACqNIrmFEUQWdGETnRjHWIvw0NEL40+U\nr2b5+7/hoqnTzReJ3XUe1jM3l44fl0rOf4hu2QIDAQABAoIBAQCVMINb4TP20P55\n9IPyqlxjhPT563hijXK+lhMJyiBDPavOOs7qjLikq2bshYPVbm1o2jt6pkXXqAeB\n5t/d20fheQQurYyPfxecNBZuL78duwbcUy28m2aXLlcVRYO4zGhoMgdW4UajoNLV\nT/UIiDONWGyhTHXMHdP+6h9UOmvs3o4b225AuLrw9n6QO5I1Se8lcfOTIqR1fy4O\nGsUWEQPdW0X3Dhgpx7kDIuBTAQzbjD31PCR1U8h2wsCeEe6hPCrsMbo/D019weol\ndi40tbWR1/oNz0+vro2d9YDPJkXN0gmpT51Z4YJoexZBdyzO5z4DMSdn5yczzt6p\nQq8LsXAFAoGBAPYXRbC4OxhtuC+xr8KRkaCCMjtjUWFbFWf6OFgUS9b5uPz9xvdY\nXo7wBP1zp2dS8yFsdIYH5Six4Z5iOuDR4sVixzjabhwedL6bmS1zV5qcCWeASKX1\nURgSkfMmC4Tg3LBgZ9YxySFcVRjikxljkS3eK7Mp7Xmj5afe7qV73TJfAoGBAO20\nTtw2RGe02xnydZmmwf+NpQHOA9S0JsehZA6NRbtPEN/C8bPJIq4VABC5zcH+tfYf\nzndbDlGhuk+qpPA590rG5RSOUjYnQFq7njdSfFyok9dXSZQTjJwFnG2oy0LmgjCe\nROYnbCzD+a+gBKV4xlo2M80OLakQ3zOwPT0xNRnHAoGATLEj/tbrU8mdxP9TDwfe\nom7wyKFDE1wXZ7gLJyfsGqrog69y+lKH5XPXmkUYvpKTQq9SARMkz3HgJkPmpXnD\nelA2Vfl8pza2m1BShF+VxZErPR41hcLV6vKemXAZ1udc33qr4YzSaZskygSSYy8s\nZ2b9p3BBmc8CGzbWmKvpW3ECgYEAn7sFLxdMWj/+5221Nr4HKPn+wrq0ek9gq884\n1Ep8bETSOvrdvolPQ5mbBKJGsLC/h5eR/0Rx18sMzpIF6eOZ2GbU8z474mX36cCf\nrd9A8Gbbid3+9IE6gHGIz2uYwujw3UjNVbdyCpbahvjJhoQlDePUZVu8tRpAUpSA\nYklZvGsCgYBuIlOFTNGMVUnwfzrcS9a/31LSvWTZa8w2QFjsRPMYFezo2l4yWs4D\nPEpeuoJm+Gp6F6ayjoeyOw9mvMBH5hAZr4WjbiU6UodzEHREAsLAzCzcRyIpnDE6\nPW1c3j60r8AHVufkWTA+8B9WoLC5MqcYTV3beMGnNGGqS2PeBom63Q==\n-----END RSA PRIVATE KEY-----\n"
|
||||||
m, err := parseMQTTMetaData(fakeMetaData)
|
m, err := parseMQTTMetaData(fakeMetaData)
|
||||||
|
|
||||||
|
|
@ -198,7 +199,7 @@ func TestParseMetadata(t *testing.T) {
|
||||||
topic := "/topic/where/the/data/is/from"
|
topic := "/topic/where/the/data/is/from"
|
||||||
|
|
||||||
logger := logger.NewLogger("test")
|
logger := logger.NewLogger("test")
|
||||||
m := NewMQTT(logger)
|
m := NewMQTT(logger).(*MQTT)
|
||||||
m.ctx, m.cancel = context.WithCancel(context.Background())
|
m.ctx, m.cancel = context.WithCancel(context.Background())
|
||||||
|
|
||||||
m.handleMessage(context.Background(), func(ctx context.Context, r *bindings.ReadResponse) ([]byte, error) {
|
m.handleMessage(context.Background(), func(ctx context.Context, r *bindings.ReadResponse) ([]byte, error) {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import (
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -76,7 +76,7 @@ type Mysql struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMysql returns a new MySQL output binding.
|
// NewMysql returns a new MySQL output binding.
|
||||||
func NewMysql(logger logger.Logger) *Mysql {
|
func NewMysql(logger logger.Logger) bindings.OutputBinding {
|
||||||
return &Mysql{logger: logger}
|
return &Mysql{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,7 +155,7 @@ func (m *Mysql) Invoke(ctx context.Context, req *bindings.InvokeRequest) (*bindi
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
switch req.Operation { // nolint: exhaustive
|
switch req.Operation { //nolint:exhaustive
|
||||||
case execOperation:
|
case execOperation:
|
||||||
r, err := m.exec(ctx, s)
|
r, err := m.exec(ctx, s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -263,7 +263,7 @@ func initDB(url, pemPath string) (*sql.DB, error) {
|
||||||
|
|
||||||
if pemPath != "" {
|
if pemPath != "" {
|
||||||
rootCertPool := x509.NewCertPool()
|
rootCertPool := x509.NewCertPool()
|
||||||
pem, err := ioutil.ReadFile(pemPath)
|
pem, err := os.ReadFile(pemPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "Error reading PEM file from %s", pemPath)
|
return nil, errors.Wrapf(err, "Error reading PEM file from %s", pemPath)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/dapr/components-contrib/bindings"
|
"github.com/dapr/components-contrib/bindings"
|
||||||
|
"github.com/dapr/components-contrib/metadata"
|
||||||
"github.com/dapr/kit/logger"
|
"github.com/dapr/kit/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -72,8 +73,8 @@ func TestMysqlIntegration(t *testing.T) {
|
||||||
t.SkipNow()
|
t.SkipNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
b := NewMysql(logger.NewLogger("test"))
|
b := NewMysql(logger.NewLogger("test")).(*Mysql)
|
||||||
m := bindings.Metadata{Properties: map[string]string{connectionURLKey: url}}
|
m := bindings.Metadata{Base: metadata.Base{Properties: map[string]string{connectionURLKey: url}}}
|
||||||
if err := b.Init(m); err != nil {
|
if err := b.Init(m); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -180,7 +180,7 @@ func mockDatabase(t *testing.T) (*Mysql, sqlmock.Sqlmock, error) {
|
||||||
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
|
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
m := NewMysql(logger.NewLogger("test"))
|
m := NewMysql(logger.NewLogger("test")).(*Mysql)
|
||||||
m.db = db
|
m.db = db
|
||||||
|
|
||||||
return m, mock, err
|
return m, mock, err
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,6 @@ func PingOutBinding(outputBinding OutputBinding) error {
|
||||||
if outputBindingWithPing, ok := outputBinding.(health.Pinger); ok {
|
if outputBindingWithPing, ok := outputBinding.(health.Pinger); ok {
|
||||||
return outputBindingWithPing.Ping()
|
return outputBindingWithPing.Ping()
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("Ping is not implemented by this output binding")
|
return fmt.Errorf("ping is not implemented by this output binding")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ type Postgres struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPostgres returns a new PostgreSQL output binding.
|
// NewPostgres returns a new PostgreSQL output binding.
|
||||||
func NewPostgres(logger logger.Logger) *Postgres {
|
func NewPostgres(logger logger.Logger) bindings.OutputBinding {
|
||||||
return &Postgres{logger: logger}
|
return &Postgres{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,7 +107,7 @@ func (p *Postgres) Invoke(ctx context.Context, req *bindings.InvokeRequest) (res
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
switch req.Operation { // nolint: exhaustive
|
switch req.Operation { //nolint:exhaustive
|
||||||
case execOperation:
|
case execOperation:
|
||||||
r, err := p.exec(ctx, sql)
|
r, err := p.exec(ctx, sql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -154,13 +154,13 @@ func (p *Postgres) query(ctx context.Context, sql string) (result []byte, err er
|
||||||
return nil, errors.Wrapf(err, "error executing %s", sql)
|
return nil, errors.Wrapf(err, "error executing %s", sql)
|
||||||
}
|
}
|
||||||
|
|
||||||
rs := make([]interface{}, 0)
|
rs := make([]any, 0)
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
val, rowErr := rows.Values()
|
val, rowErr := rows.Values()
|
||||||
if rowErr != nil {
|
if rowErr != nil {
|
||||||
return nil, errors.Wrapf(rowErr, "error parsing result: %v", rows.Err())
|
return nil, errors.Wrapf(rowErr, "error parsing result: %v", rows.Err())
|
||||||
}
|
}
|
||||||
rs = append(rs, val)
|
rs = append(rs, val) //nolint:asasalint
|
||||||
}
|
}
|
||||||
|
|
||||||
if result, err = json.Marshal(rs); err != nil {
|
if result, err = json.Marshal(rs); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/dapr/components-contrib/bindings"
|
"github.com/dapr/components-contrib/bindings"
|
||||||
|
"github.com/dapr/components-contrib/metadata"
|
||||||
"github.com/dapr/kit/logger"
|
"github.com/dapr/kit/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -61,8 +62,8 @@ func TestPostgresIntegration(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// live DB test
|
// live DB test
|
||||||
b := NewPostgres(logger.NewLogger("test"))
|
b := NewPostgres(logger.NewLogger("test")).(*Postgres)
|
||||||
m := bindings.Metadata{Properties: map[string]string{connectionURLKey: url}}
|
m := bindings.Metadata{Base: metadata.Base{Properties: map[string]string{connectionURLKey: url}}}
|
||||||
if err := b.Init(m); err != nil {
|
if err := b.Init(m); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ type postmarkMetadata struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPostmark returns a new Postmark bindings instance.
|
// NewPostmark returns a new Postmark bindings instance.
|
||||||
func NewPostmark(logger logger.Logger) *Postmark {
|
func NewPostmark(logger logger.Logger) bindings.OutputBinding {
|
||||||
return &Postmark{logger: logger}
|
return &Postmark{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import (
|
||||||
|
|
||||||
"github.com/dapr/components-contrib/bindings"
|
"github.com/dapr/components-contrib/bindings"
|
||||||
"github.com/dapr/components-contrib/internal/utils"
|
"github.com/dapr/components-contrib/internal/utils"
|
||||||
contrib_metadata "github.com/dapr/components-contrib/metadata"
|
contribMetadata "github.com/dapr/components-contrib/metadata"
|
||||||
"github.com/dapr/kit/logger"
|
"github.com/dapr/kit/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -65,7 +65,7 @@ type rabbitMQMetadata struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRabbitMQ returns a new rabbitmq instance.
|
// NewRabbitMQ returns a new rabbitmq instance.
|
||||||
func NewRabbitMQ(logger logger.Logger) *RabbitMQ {
|
func NewRabbitMQ(logger logger.Logger) bindings.InputOutputBinding {
|
||||||
return &RabbitMQ{logger: logger}
|
return &RabbitMQ{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -110,13 +110,13 @@ func (r *RabbitMQ) Invoke(ctx context.Context, req *bindings.InvokeRequest) (*bi
|
||||||
Body: req.Data,
|
Body: req.Data,
|
||||||
}
|
}
|
||||||
|
|
||||||
contentType, ok := contrib_metadata.TryGetContentType(req.Metadata)
|
contentType, ok := contribMetadata.TryGetContentType(req.Metadata)
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
pub.ContentType = contentType
|
pub.ContentType = contentType
|
||||||
}
|
}
|
||||||
|
|
||||||
ttl, ok, err := contrib_metadata.TryGetTTL(req.Metadata)
|
ttl, ok, err := contribMetadata.TryGetTTL(req.Metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -128,7 +128,7 @@ func (r *RabbitMQ) Invoke(ctx context.Context, req *bindings.InvokeRequest) (*bi
|
||||||
pub.Expiration = strconv.FormatInt(ttl.Milliseconds(), 10)
|
pub.Expiration = strconv.FormatInt(ttl.Milliseconds(), 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
priority, ok, err := contrib_metadata.TryGetPriority(req.Metadata)
|
priority, ok, err := contribMetadata.TryGetPriority(req.Metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -196,7 +196,7 @@ func (r *RabbitMQ) parseMetadata(metadata bindings.Metadata) error {
|
||||||
m.MaxPriority = &maxPriority
|
m.MaxPriority = &maxPriority
|
||||||
}
|
}
|
||||||
|
|
||||||
ttl, ok, err := contrib_metadata.TryGetTTL(metadata.Properties)
|
ttl, ok, err := contribMetadata.TryGetTTL(metadata.Properties)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/dapr/components-contrib/bindings"
|
"github.com/dapr/components-contrib/bindings"
|
||||||
contrib_metadata "github.com/dapr/components-contrib/metadata"
|
contribMetadata "github.com/dapr/components-contrib/metadata"
|
||||||
"github.com/dapr/kit/logger"
|
"github.com/dapr/kit/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -72,17 +72,17 @@ func TestQueuesWithTTL(t *testing.T) {
|
||||||
metadata := bindings.Metadata{
|
metadata := bindings.Metadata{
|
||||||
Name: "testQueue",
|
Name: "testQueue",
|
||||||
Properties: map[string]string{
|
Properties: map[string]string{
|
||||||
"queueName": queueName,
|
"queueName": queueName,
|
||||||
"host": rabbitmqHost,
|
"host": rabbitmqHost,
|
||||||
"deleteWhenUnused": strconv.FormatBool(exclusive),
|
"deleteWhenUnused": strconv.FormatBool(exclusive),
|
||||||
"durable": strconv.FormatBool(durable),
|
"durable": strconv.FormatBool(durable),
|
||||||
contrib_metadata.TTLMetadataKey: strconv.FormatInt(ttlInSeconds, 10),
|
contribMetadata.TTLMetadataKey: strconv.FormatInt(ttlInSeconds, 10),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
logger := logger.NewLogger("test")
|
logger := logger.NewLogger("test")
|
||||||
|
|
||||||
r := NewRabbitMQ(logger)
|
r := NewRabbitMQ(logger).(*RabbitMQ)
|
||||||
err := r.Init(metadata)
|
err := r.Init(metadata)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
|
@ -139,7 +139,7 @@ func TestPublishingWithTTL(t *testing.T) {
|
||||||
|
|
||||||
logger := logger.NewLogger("test")
|
logger := logger.NewLogger("test")
|
||||||
|
|
||||||
rabbitMQBinding1 := NewRabbitMQ(logger)
|
rabbitMQBinding1 := NewRabbitMQ(logger).(*RabbitMQ)
|
||||||
err := rabbitMQBinding1.Init(metadata)
|
err := rabbitMQBinding1.Init(metadata)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
|
@ -156,7 +156,7 @@ func TestPublishingWithTTL(t *testing.T) {
|
||||||
writeRequest := bindings.InvokeRequest{
|
writeRequest := bindings.InvokeRequest{
|
||||||
Data: []byte(tooLateMsgContent),
|
Data: []byte(tooLateMsgContent),
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
contrib_metadata.TTLMetadataKey: strconv.Itoa(ttlInSeconds),
|
contribMetadata.TTLMetadataKey: strconv.Itoa(ttlInSeconds),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -170,7 +170,7 @@ func TestPublishingWithTTL(t *testing.T) {
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
|
|
||||||
// Getting before it is expired, should return it
|
// Getting before it is expired, should return it
|
||||||
rabbitMQBinding2 := NewRabbitMQ(logger)
|
rabbitMQBinding2 := NewRabbitMQ(logger).(*RabbitMQ)
|
||||||
err = rabbitMQBinding2.Init(metadata)
|
err = rabbitMQBinding2.Init(metadata)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
|
@ -178,7 +178,7 @@ func TestPublishingWithTTL(t *testing.T) {
|
||||||
writeRequest = bindings.InvokeRequest{
|
writeRequest = bindings.InvokeRequest{
|
||||||
Data: []byte(testMsgContent),
|
Data: []byte(testMsgContent),
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
contrib_metadata.TTLMetadataKey: strconv.Itoa(ttlInSeconds * 1000),
|
contribMetadata.TTLMetadataKey: strconv.Itoa(ttlInSeconds * 1000),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
_, err = rabbitMQBinding2.Invoke(context.Backgound(), &writeRequest)
|
_, err = rabbitMQBinding2.Invoke(context.Backgound(), &writeRequest)
|
||||||
|
|
@ -204,18 +204,18 @@ func TestExclusiveQueue(t *testing.T) {
|
||||||
metadata := bindings.Metadata{
|
metadata := bindings.Metadata{
|
||||||
Name: "testQueue",
|
Name: "testQueue",
|
||||||
Properties: map[string]string{
|
Properties: map[string]string{
|
||||||
"queueName": queueName,
|
"queueName": queueName,
|
||||||
"host": rabbitmqHost,
|
"host": rabbitmqHost,
|
||||||
"deleteWhenUnused": strconv.FormatBool(exclusive),
|
"deleteWhenUnused": strconv.FormatBool(exclusive),
|
||||||
"durable": strconv.FormatBool(durable),
|
"durable": strconv.FormatBool(durable),
|
||||||
"exclusive": strconv.FormatBool(exclusive),
|
"exclusive": strconv.FormatBool(exclusive),
|
||||||
contrib_metadata.TTLMetadataKey: strconv.FormatInt(ttlInSeconds, 10),
|
contribMetadata.TTLMetadataKey: strconv.FormatInt(ttlInSeconds, 10),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
logger := logger.NewLogger("test")
|
logger := logger.NewLogger("test")
|
||||||
|
|
||||||
r := NewRabbitMQ(logger)
|
r := NewRabbitMQ(logger).(*RabbitMQ)
|
||||||
err := r.Init(metadata)
|
err := r.Init(metadata)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
|
@ -267,7 +267,7 @@ func TestPublishWithPriority(t *testing.T) {
|
||||||
|
|
||||||
logger := logger.NewLogger("test")
|
logger := logger.NewLogger("test")
|
||||||
|
|
||||||
r := NewRabbitMQ(logger)
|
r := NewRabbitMQ(logger).(*RabbitMQ)
|
||||||
err := r.Init(metadata)
|
err := r.Init(metadata)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
|
@ -283,7 +283,7 @@ func TestPublishWithPriority(t *testing.T) {
|
||||||
const middlePriorityMsgContent = "middle"
|
const middlePriorityMsgContent = "middle"
|
||||||
_, err = r.Invoke(context.Backgound(), &bindings.InvokeRequest{
|
_, err = r.Invoke(context.Backgound(), &bindings.InvokeRequest{
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
contrib_metadata.PriorityMetadataKey: "5",
|
contribMetadata.PriorityMetadataKey: "5",
|
||||||
},
|
},
|
||||||
Data: []byte(middlePriorityMsgContent),
|
Data: []byte(middlePriorityMsgContent),
|
||||||
})
|
})
|
||||||
|
|
@ -292,7 +292,7 @@ func TestPublishWithPriority(t *testing.T) {
|
||||||
const lowPriorityMsgContent = "low"
|
const lowPriorityMsgContent = "low"
|
||||||
_, err = r.Invoke(context.Backgound(), &bindings.InvokeRequest{
|
_, err = r.Invoke(context.Backgound(), &bindings.InvokeRequest{
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
contrib_metadata.PriorityMetadataKey: "1",
|
contribMetadata.PriorityMetadataKey: "1",
|
||||||
},
|
},
|
||||||
Data: []byte(lowPriorityMsgContent),
|
Data: []byte(lowPriorityMsgContent),
|
||||||
})
|
})
|
||||||
|
|
@ -301,7 +301,7 @@ func TestPublishWithPriority(t *testing.T) {
|
||||||
const highPriorityMsgContent = "high"
|
const highPriorityMsgContent = "high"
|
||||||
_, err = r.Invoke(context.Backgound(), &bindings.InvokeRequest{
|
_, err = r.Invoke(context.Backgound(), &bindings.InvokeRequest{
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
contrib_metadata.PriorityMetadataKey: "10",
|
contribMetadata.PriorityMetadataKey: "10",
|
||||||
},
|
},
|
||||||
Data: []byte(highPriorityMsgContent),
|
Data: []byte(highPriorityMsgContent),
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ type Redis struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRedis returns a new redis bindings instance.
|
// NewRedis returns a new redis bindings instance.
|
||||||
func NewRedis(logger logger.Logger) *Redis {
|
func NewRedis(logger logger.Logger) bindings.OutputBinding {
|
||||||
return &Redis{logger: logger}
|
return &Redis{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ type StateConfig struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRethinkDBStateChangeBinding returns a new RethinkDB actor event input binding.
|
// NewRethinkDBStateChangeBinding returns a new RethinkDB actor event input binding.
|
||||||
func NewRethinkDBStateChangeBinding(logger logger.Logger) *Binding {
|
func NewRethinkDBStateChangeBinding(logger logger.Logger) bindings.InputBinding {
|
||||||
return &Binding{
|
return &Binding{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/dapr/components-contrib/bindings"
|
"github.com/dapr/components-contrib/bindings"
|
||||||
|
"github.com/dapr/components-contrib/metadata"
|
||||||
"github.com/dapr/kit/logger"
|
"github.com/dapr/kit/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -41,7 +42,7 @@ func getNewRethinkActorBinding() *Binding {
|
||||||
l.SetOutputLevel(logger.DebugLevel)
|
l.SetOutputLevel(logger.DebugLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewRethinkDBStateChangeBinding(l)
|
return NewRethinkDBStateChangeBinding(l).(*Binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -63,10 +64,10 @@ func TestBinding(t *testing.T) {
|
||||||
testDuration = d
|
testDuration = d
|
||||||
}
|
}
|
||||||
|
|
||||||
m := bindings.Metadata{
|
m := bindings.Metadata{Base: metadata.Base{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Properties: getTestMetadata(),
|
Properties: getTestMetadata(),
|
||||||
}
|
}}
|
||||||
assert.NotNil(t, m.Properties)
|
assert.NotNil(t, m.Properties)
|
||||||
|
|
||||||
b := getNewRethinkActorBinding()
|
b := getNewRethinkActorBinding()
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ type Metadata struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSMTP returns a new smtp binding instance.
|
// NewSMTP returns a new smtp binding instance.
|
||||||
func NewSMTP(logger logger.Logger) *Mailer {
|
func NewSMTP(logger logger.Logger) bindings.OutputBinding {
|
||||||
return &Mailer{logger: logger}
|
return &Mailer{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ type sendGridRestError struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSendGrid returns a new SendGrid bindings instance.
|
// NewSendGrid returns a new SendGrid bindings instance.
|
||||||
func NewSendGrid(logger logger.Logger) *SendGrid {
|
func NewSendGrid(logger logger.Logger) bindings.OutputBinding {
|
||||||
return &SendGrid{logger: logger}
|
return &SendGrid{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ type twilioMetadata struct {
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSMS(logger logger.Logger) *SMS {
|
func NewSMS(logger logger.Logger) bindings.OutputBinding {
|
||||||
return &SMS{
|
return &SMS{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
httpClient: &http.Client{
|
httpClient: &http.Client{
|
||||||
|
|
@ -112,7 +112,7 @@ func (t *SMS) Invoke(ctx context.Context, req *bindings.InvokeRequest) (*binding
|
||||||
vDr := *strings.NewReader(v.Encode())
|
vDr := *strings.NewReader(v.Encode())
|
||||||
|
|
||||||
twilioURL := fmt.Sprintf("%s%s/Messages.json", twilioURLBase, t.metadata.accountSid)
|
twilioURL := fmt.Sprintf("%s%s/Messages.json", twilioURLBase, t.metadata.accountSid)
|
||||||
httpReq, err := http.NewRequestWithContext(ctx, "POST", twilioURL, &vDr)
|
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, twilioURL, &vDr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ package sms
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
@ -68,14 +68,14 @@ func TestParseDuration(t *testing.T) {
|
||||||
|
|
||||||
func TestWriteShouldSucceed(t *testing.T) {
|
func TestWriteShouldSucceed(t *testing.T) {
|
||||||
httpTransport := &mockTransport{
|
httpTransport := &mockTransport{
|
||||||
response: &http.Response{StatusCode: 200, Body: ioutil.NopCloser(strings.NewReader(""))},
|
response: &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(""))},
|
||||||
}
|
}
|
||||||
m := bindings.Metadata{}
|
m := bindings.Metadata{}
|
||||||
m.Properties = map[string]string{
|
m.Properties = map[string]string{
|
||||||
"toNumber": "toNumber", "fromNumber": "fromNumber",
|
"toNumber": "toNumber", "fromNumber": "fromNumber",
|
||||||
"accountSid": "accountSid", "authToken": "authToken",
|
"accountSid": "accountSid", "authToken": "authToken",
|
||||||
}
|
}
|
||||||
tw := NewSMS(logger.NewLogger("test"))
|
tw := NewSMS(logger.NewLogger("test")).(*SMS)
|
||||||
tw.httpClient = &http.Client{
|
tw.httpClient = &http.Client{
|
||||||
Transport: httpTransport,
|
Transport: httpTransport,
|
||||||
}
|
}
|
||||||
|
|
@ -105,14 +105,14 @@ func TestWriteShouldSucceed(t *testing.T) {
|
||||||
|
|
||||||
func TestWriteShouldFail(t *testing.T) {
|
func TestWriteShouldFail(t *testing.T) {
|
||||||
httpTransport := &mockTransport{
|
httpTransport := &mockTransport{
|
||||||
response: &http.Response{StatusCode: 200, Body: ioutil.NopCloser(strings.NewReader(""))},
|
response: &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(""))},
|
||||||
}
|
}
|
||||||
m := bindings.Metadata{}
|
m := bindings.Metadata{}
|
||||||
m.Properties = map[string]string{
|
m.Properties = map[string]string{
|
||||||
"fromNumber": "fromNumber",
|
"fromNumber": "fromNumber",
|
||||||
"accountSid": "accountSid", "authToken": "authToken",
|
"accountSid": "accountSid", "authToken": "authToken",
|
||||||
}
|
}
|
||||||
tw := NewSMS(logger.NewLogger("test"))
|
tw := NewSMS(logger.NewLogger("test")).(*SMS)
|
||||||
tw.httpClient = &http.Client{
|
tw.httpClient = &http.Client{
|
||||||
Transport: httpTransport,
|
Transport: httpTransport,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ type Binding struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTwitter returns a new Twitter event input binding.
|
// NewTwitter returns a new Twitter event input binding.
|
||||||
func NewTwitter(logger logger.Logger) *Binding {
|
func NewTwitter(logger logger.Logger) bindings.InputOutputBinding {
|
||||||
return &Binding{logger: logger}
|
return &Binding{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ func getRuntimeMetadata() map[string]string {
|
||||||
// go test -v -count=1 ./bindings/twitter/.
|
// go test -v -count=1 ./bindings/twitter/.
|
||||||
func TestInit(t *testing.T) {
|
func TestInit(t *testing.T) {
|
||||||
m := getTestMetadata()
|
m := getTestMetadata()
|
||||||
tw := NewTwitter(logger.NewLogger("test"))
|
tw := NewTwitter(logger.NewLogger("test")).(*Binding)
|
||||||
err := tw.Init(m)
|
err := tw.Init(m)
|
||||||
assert.Nilf(t, err, "error initializing valid metadata properties")
|
assert.Nilf(t, err, "error initializing valid metadata properties")
|
||||||
}
|
}
|
||||||
|
|
@ -66,7 +66,7 @@ func TestInit(t *testing.T) {
|
||||||
// TestReadError excutes the Read method and fails before the Twitter API call
|
// TestReadError excutes the Read method and fails before the Twitter API call
|
||||||
// go test -v -count=1 -run TestReadError ./bindings/twitter/.
|
// go test -v -count=1 -run TestReadError ./bindings/twitter/.
|
||||||
func TestReadError(t *testing.T) {
|
func TestReadError(t *testing.T) {
|
||||||
tw := NewTwitter(logger.NewLogger("test"))
|
tw := NewTwitter(logger.NewLogger("test")).(*Binding)
|
||||||
m := getTestMetadata()
|
m := getTestMetadata()
|
||||||
err := tw.Init(m)
|
err := tw.Init(m)
|
||||||
assert.Nilf(t, err, "error initializing valid metadata properties")
|
assert.Nilf(t, err, "error initializing valid metadata properties")
|
||||||
|
|
@ -90,7 +90,7 @@ func TestRead(t *testing.T) {
|
||||||
m.Properties = getRuntimeMetadata()
|
m.Properties = getRuntimeMetadata()
|
||||||
// add query
|
// add query
|
||||||
m.Properties["query"] = "microsoft"
|
m.Properties["query"] = "microsoft"
|
||||||
tw := NewTwitter(logger.NewLogger("test"))
|
tw := NewTwitter(logger.NewLogger("test")).(*Binding)
|
||||||
tw.logger.SetOutputLevel(logger.DebugLevel)
|
tw.logger.SetOutputLevel(logger.DebugLevel)
|
||||||
err := tw.Init(m)
|
err := tw.Init(m)
|
||||||
assert.Nilf(t, err, "error initializing read")
|
assert.Nilf(t, err, "error initializing read")
|
||||||
|
|
@ -126,7 +126,7 @@ func TestInvoke(t *testing.T) {
|
||||||
}
|
}
|
||||||
m := bindings.Metadata{}
|
m := bindings.Metadata{}
|
||||||
m.Properties = getRuntimeMetadata()
|
m.Properties = getRuntimeMetadata()
|
||||||
tw := NewTwitter(logger.NewLogger("test"))
|
tw := NewTwitter(logger.NewLogger("test")).(*Binding)
|
||||||
tw.logger.SetOutputLevel(logger.DebugLevel)
|
tw.logger.SetOutputLevel(logger.DebugLevel)
|
||||||
err := tw.Init(m)
|
err := tw.Init(m)
|
||||||
assert.Nilf(t, err, "error initializing Invoke")
|
assert.Nilf(t, err, "error initializing Invoke")
|
||||||
|
|
|
||||||
|
|
@ -20,16 +20,17 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/dapr/components-contrib/bindings"
|
"github.com/dapr/components-contrib/bindings"
|
||||||
|
"github.com/dapr/components-contrib/metadata"
|
||||||
"github.com/dapr/kit/logger"
|
"github.com/dapr/kit/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseMetadata(t *testing.T) {
|
func TestParseMetadata(t *testing.T) {
|
||||||
m := bindings.Metadata{Properties: map[string]string{
|
m := bindings.Metadata{Base: metadata.Base{Properties: map[string]string{
|
||||||
"gatewayAddr": "172.0.0.1:1234",
|
"gatewayAddr": "172.0.0.1:1234",
|
||||||
"gatewayKeepAlive": "5s",
|
"gatewayKeepAlive": "5s",
|
||||||
"caCertificatePath": "/cert/path",
|
"caCertificatePath": "/cert/path",
|
||||||
"usePlaintextConnection": "true",
|
"usePlaintextConnection": "true",
|
||||||
}}
|
}}}
|
||||||
client := ClientFactoryImpl{logger: logger.NewLogger("test")}
|
client := ClientFactoryImpl{logger: logger.NewLogger("test")}
|
||||||
meta, err := client.parseMetadata(m)
|
meta, err := client.parseMetadata(m)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
@ -49,7 +50,7 @@ func TestGatewayAddrMetadataIsMandatory(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseMetadataDefaultValues(t *testing.T) {
|
func TestParseMetadataDefaultValues(t *testing.T) {
|
||||||
m := bindings.Metadata{Properties: map[string]string{"gatewayAddr": "172.0.0.1:1234"}}
|
m := bindings.Metadata{Base: metadata.Base{Properties: map[string]string{"gatewayAddr": "172.0.0.1:1234"}}}
|
||||||
client := ClientFactoryImpl{logger: logger.NewLogger("test")}
|
client := ClientFactoryImpl{logger: logger.NewLogger("test")}
|
||||||
meta, err := client.parseMetadata(m)
|
meta, err := client.parseMetadata(m)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue