├── .gitignore
├── MAINTAINERS.md
├── .github
├── workflows
│ ├── check-wip.yaml
│ ├── ps-ce-sdk-unit-tests.yaml
│ ├── ps-ce-sdk-integration-tests.yaml
│ ├── ps-sdk-stale.yaml
│ └── ps-ce-sdk-release.yaml
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
└── PULL_REQUEST_TEMPLATE.md
├── .chglog
├── config.yml
└── CHANGELOG.tpl.md
├── test
├── integration
│ ├── ProtocolConstants.ps1
│ ├── HttpServer.ps1
│ └── HttpIntegration.Tests.ps1
├── unit
│ ├── Read-CloudEventData.Tests.ps1
│ ├── New-CloudEvent.Tests.ps1
│ ├── Set-CloudEventJsonData.Tests.ps1
│ ├── Read-CloudEventJsonData.Tests.ps1
│ ├── Set-CloudEventXmlData.Tests.ps1
│ ├── Read-CloudEventXmlData.Tests.ps1
│ ├── Set-CloudEventData.Tests.ps1
│ ├── ConvertTo-HttpMessage.Tests.ps1
│ └── ConvertFrom-HttpMessage.Tests.ps1
└── RunTests.ps1
├── src
└── CloudEventsPowerShell
│ ├── CloudEventsPowerShell.csproj
│ ├── CloudEvents.Sdk.psd1
│ ├── dataserialization
│ ├── xml.ps1
│ └── xml.Tests.ps1
│ └── CloudEvents.Sdk.psm1
├── publish.ps1
├── RELEASING.md
├── CONTRIBUTING.md
├── CHANGELOG.md
├── README.md
└── LICENSE
/.gitignore:
--------------------------------------------------------------------------------
1 | **/.vs
2 | **/bin
3 | **/obj
4 | /CloudEvents
5 | /CloudEvents.Sdk
--------------------------------------------------------------------------------
/MAINTAINERS.md:
--------------------------------------------------------------------------------
1 | # Maintainers
2 |
3 | Current active maintainers of this SDK:
4 |
5 | - [William Lam](https://github.com/lamw)
6 | - [Mark Peek](https://github.com/markpeek
7 | - [Dimitar Milov](https://github.com/dmilov)
8 | - [Michael Gasch](https://github.com/embano1)
9 | - [Simeon Gerginov](https://github.com/SimeonGerginov)
10 |
--------------------------------------------------------------------------------
/.github/workflows/check-wip.yaml:
--------------------------------------------------------------------------------
1 | # **************************************************************************
2 | # Copyright (c) Cloud Native Foundation.
3 | # SPDX-License-Identifier: Apache-2.0
4 | # **************************************************************************
5 |
6 | name: Check "WIP" in PR Title
7 |
8 | on:
9 | pull_request:
10 | types: [opened, synchronize, reopened, edited]
11 |
12 | jobs:
13 | wip:
14 | runs-on: ubuntu-latest
15 | steps:
16 | - name: Check WIP in PR Title
17 | uses: embano1/wip@v2
18 |
--------------------------------------------------------------------------------
/.chglog/config.yml:
--------------------------------------------------------------------------------
1 | style: github
2 | template: CHANGELOG.tpl.md
3 | info:
4 | title: CHANGELOG
5 | repository_url: https://github.com/cloudevents/sdk-powershell
6 | options:
7 | commits:
8 | filters:
9 | Type:
10 | - fix
11 | - feat
12 | - chore
13 | commit_groups:
14 | title_maps:
15 | fix: 🐞 Fix
16 | feat: 💫 Feature
17 | chore: 🧹 Chore
18 | header:
19 | pattern: "^(\\w*)\\:\\s(.*)$"
20 | pattern_maps:
21 | - Type
22 | - Subject
23 | refs:
24 | actions:
25 | - Closes
26 | - Fixes
27 | notes:
28 | keywords:
29 | - "BREAKING"
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: "[BUG] "
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 |
16 | 1.
17 | 1.
18 | 1.
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Affected version**
24 | Please provide details on the version used, e.g. release tag, commit, module version, etc.
25 |
26 | **Screenshots**
27 | If applicable, add screenshots to help explain your problem.
28 |
29 | **Additional context**
30 | Add any other context about the problem here.
31 |
--------------------------------------------------------------------------------
/test/integration/ProtocolConstants.ps1:
--------------------------------------------------------------------------------
1 | # **************************************************************************
2 | # Copyright (c) Cloud Native Foundation.
3 | # SPDX-License-Identifier: Apache-2.0
4 | # **************************************************************************
5 |
6 | New-Variable -Option Constant -Scope 'script' -Name 'ClientSource' -Value 'ps:test:client'
7 | New-Variable -Option Constant -Scope 'script' -Name 'ServerSource' -Value 'ps:test:server'
8 | New-Variable -Option Constant -Scope 'script' -Name 'EchoBinaryType' -Value 'echo-binary'
9 | New-Variable -Option Constant -Scope 'script' -Name 'EchoStructuredType' -Value 'echo-structured'
10 | New-Variable -Option Constant -Scope 'script' -Name 'ServerStopType' -Value 'server-stop'
11 | New-Variable -Option Constant -Scope 'script' -Name 'ServerPingType' -Value 'server-ping'
--------------------------------------------------------------------------------
/.github/workflows/ps-ce-sdk-unit-tests.yaml:
--------------------------------------------------------------------------------
1 | # **************************************************************************
2 | # Copyright (c) Cloud Native Foundation.
3 | # SPDX-License-Identifier: Apache-2.0
4 | # **************************************************************************
5 |
6 | name: Unit Tests
7 |
8 | on:
9 | push:
10 | branches: ["main"]
11 |
12 | pull_request:
13 | branches: ["main"]
14 |
15 | jobs:
16 | unit-tests:
17 | name: Run Unit Tests
18 | strategy:
19 | matrix:
20 | platform: ["ubuntu-latest", "windows-latest"]
21 |
22 | runs-on: ${{ matrix.platform }}
23 | timeout-minutes: 10
24 |
25 | steps:
26 | - name: Check out code
27 | uses: actions/checkout@v2
28 |
29 | - name: Run Tests
30 | shell: pwsh
31 | run: ./build.ps1 -TestsType unit -ExitProcess
32 |
--------------------------------------------------------------------------------
/.github/workflows/ps-ce-sdk-integration-tests.yaml:
--------------------------------------------------------------------------------
1 | # **************************************************************************
2 | # Copyright (c) Cloud Native Foundation.
3 | # SPDX-License-Identifier: Apache-2.0
4 | # **************************************************************************
5 |
6 | name: Integration Tests
7 |
8 | on:
9 | push:
10 | branches: ["main"]
11 |
12 | pull_request:
13 | branches: ["main"]
14 |
15 | jobs:
16 | integration-tests:
17 | name: Run Integration Tests
18 | strategy:
19 | matrix:
20 | platform: ["ubuntu-latest", "windows-latest"]
21 |
22 | runs-on: ${{ matrix.platform }}
23 | timeout-minutes: 10
24 |
25 | steps:
26 | - name: Check out code
27 | uses: actions/checkout@v2
28 |
29 | - name: Run Tests
30 | shell: pwsh
31 | run: ./build.ps1 -TestsType integration -ExitProcess
32 |
--------------------------------------------------------------------------------
/src/CloudEventsPowerShell/CloudEventsPowerShell.csproj:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 | CloudEventsPowerShell
10 | CloudEventsPowerShell
11 | CloudEvents PowerShell SDK.
12 | netstandard2.0
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | PreserveNewest
22 |
23 |
24 | PreserveNewest
25 |
26 |
27 | PreserveNewest
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/test/unit/Read-CloudEventData.Tests.ps1:
--------------------------------------------------------------------------------
1 | # **************************************************************************
2 | # Copyright (c) Cloud Native Foundation.
3 | # SPDX-License-Identifier: Apache-2.0
4 | # **************************************************************************
5 |
6 | Describe "Read-CloudEventData Function Tests" {
7 | Context "Extracts Data from CloudEvent" {
8 | It 'Reads xml text data' {
9 | # Arrange
10 | $cloudEvent = New-CloudEvent `
11 | -Id ([Guid]::NewGuid()) `
12 | -Type test `
13 | -Source 'urn:test'
14 |
15 |
16 | $expectedData = ''
17 | $expectedDataContentType = 'text/xml'
18 |
19 | $cloudEvent = $cloudEvent | Set-CloudEventData -Data $expectedData -DataContentType $expectedDataContentType
20 |
21 | # Act
22 | $actual = $cloudEvent | Read-CloudEventData
23 |
24 | # Assert
25 | $actual | Should -Not -Be $null
26 | $actual | Should -Be $expectedData
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/.chglog/CHANGELOG.tpl.md:
--------------------------------------------------------------------------------
1 | {{ range .Versions }}
2 |
3 | ## {{ if .Tag.Previous }}[Release {{ .Tag.Name }}]({{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}){{ else }}{{ .Tag.Name }}{{ end }}
4 |
5 | > Release Date: {{ datetime "2006-01-02" .Tag.Date }}
6 |
7 | {{ range .CommitGroups -}}
8 | ### {{ .Title }}
9 |
10 | {{ range .Commits -}}
11 | - [{{ .Hash.Short }}]{{"\t"}}{{ .Subject }}{{ range .Refs }} (#{{ .Ref }}) {{ end }}
12 | {{ end }}
13 | {{ end -}}
14 |
15 | {{- if .RevertCommits -}}
16 | ### ⏮ Reverts
17 |
18 | {{ range .RevertCommits -}}
19 | - [{{ .Hash.Short }}]{{"\t"}}{{ .Revert.Header }}{{ range .Refs }} (#{{ .Ref }}) {{ end }}
20 | {{ end }}
21 | {{ end -}}
22 |
23 | ### ⚠️ BREAKING
24 |
25 | {{ range .Commits -}}
26 | {{ if .Notes -}}
27 | {{ if not .Merge -}}
28 | {{ if not (contains .Header "Update CHANGELOG for" ) -}}
29 | {{ .Subject }} [{{ .Hash.Short }}]:{{"\n"}}{{ range .Notes }}{{ .Body }}
30 | {{ end }}
31 | {{ end -}}
32 | {{ end -}}
33 | {{ end -}}
34 | {{ end -}}
35 |
36 | ### 📖 Commits
37 |
38 | {{ range .Commits -}}
39 | {{ if not .Merge -}}
40 | {{ if not (contains .Header "Update CHANGELOG for" ) -}}
41 | - [{{ .Hash.Short }}]{{"\t"}}{{ .Header }}{{ range .Refs }} (#{{ .Ref }}) {{ end }}
42 | {{ end -}}
43 | {{ end -}}
44 | {{ end -}}
45 |
46 | {{ end -}}
--------------------------------------------------------------------------------
/.github/workflows/ps-sdk-stale.yaml:
--------------------------------------------------------------------------------
1 | # **************************************************************************
2 | # Copyright (c) Cloud Native Foundation.
3 | # SPDX-License-Identifier: Apache-2.0
4 | # **************************************************************************
5 |
6 | name: Close Stale
7 |
8 | on:
9 | schedule:
10 | - cron: "0 1 * * *" # daily
11 |
12 | jobs:
13 | stale:
14 | runs-on: "ubuntu-latest"
15 |
16 | steps:
17 | - uses: "actions/stale@v3"
18 | with:
19 | repo-token: "${{ secrets.GITHUB_TOKEN }}"
20 |
21 | stale-issue-message: |-
22 | This issue is stale because it has been open for 90 days with no
23 | activity. It will automatically close after 30 more days of
24 | inactivity. Mark as fresh by adding the comment `/remove-lifecycle stale`.
25 | stale-issue-label: "lifecycle/stale"
26 | exempt-issue-labels: "lifecycle/frozen"
27 |
28 | stale-pr-message: |-
29 | This Pull Request is stale because it has been open for 90 days with
30 | no activity. It will automatically close after 30 more days of
31 | inactivity. Mark as fresh by adding the comment `/remove-lifecycle stale`.
32 | stale-pr-label: "lifecycle/stale"
33 | exempt-pr-labels: "lifecycle/frozen"
34 |
35 | days-before-stale: 90
36 | days-before-close: 30
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Description
2 |
3 | Please include a summary of the change and which issue is fixed. Please also
4 | include relevant motivation and context. List any dependencies that are required
5 | for this change.
6 |
7 | Closes: #(issue-number)
8 |
9 | ## Type of change
10 |
11 | Please mark options that are relevant:
12 |
13 | - [ ] Bug fix (non-breaking change which fixes an issue)
14 | - [ ] New feature (non-breaking change which adds functionality)
15 | - [ ] Breaking change (fix or feature that would cause existing functionality to
16 | not work as expected)
17 | - [ ] This change requires a documentation update
18 |
19 | ## How Has This Been Tested?
20 |
21 | Please describe the tests that you ran to verify your changes. Provide
22 | instructions so we can reproduce. If applicable, please also list any relevant
23 | details for your test configuration.
24 |
25 | - [ ] Test Description 1
26 | - [ ] Test Description 2
27 |
28 | **Test Configuration**:
29 | * Toolchain:
30 | * SDK:
31 | * (add more if needed)
32 |
33 | ## Checklist:
34 |
35 | - [ ] My code follows the contribution
36 | [guidelines](https://github.com/cloudevents/sdk-powershell/blob/main/CONTRIBUTING.md)
37 | of this project
38 | - [ ] I have commented my code, particularly in hard-to-understand areas
39 | - [ ] I have made corresponding changes to the documentation
40 | - [ ] I have added tests that prove my fix is effective or that my feature works
41 | - [ ] New and existing unit tests pass locally with my changes
42 | - [ ] Any dependent changes have been merged and published in downstream modules
--------------------------------------------------------------------------------
/test/RunTests.ps1:
--------------------------------------------------------------------------------
1 | # **************************************************************************
2 | # Copyright (c) Cloud Native Foundation.
3 | # SPDX-License-Identifier: Apache-2.0
4 | # **************************************************************************
5 |
6 | param(
7 | [Parameter()]
8 | [ValidateScript({Test-Path $_})]
9 | [string]
10 | $CloudEventsModulePath,
11 |
12 | [Parameter()]
13 | [ValidateSet('unit', 'integration', 'all')]
14 | [string]
15 | $TestsType,
16 |
17 | [Parameter()]
18 | [Switch]
19 | $EnableProcessExit
20 | )
21 |
22 | Import-Module $CloudEventsModulePath
23 |
24 | if ($TestsType -eq 'unit' -or $TestsType -eq 'all') {
25 | $pesterContainer = New-PesterContainer -Path (Join-Path $PSScriptRoot 'unit')
26 | $pesterConfiguration = [PesterConfiguration]::Default
27 |
28 | $pesterConfiguration.Run.Path = (Join-Path $PSScriptRoot 'unit')
29 | $pesterConfiguration.Run.Container = $pesterContainer
30 | $pesterConfiguration.Run.Exit = $EnableProcessExit.IsPresent
31 |
32 | Invoke-Pester -Configuration $pesterConfiguration
33 | }
34 |
35 | if ($TestsType -eq 'integration' -or $TestsType -eq 'all') {
36 |
37 | $testsData = @{
38 | CloudEventsModulePath = $CloudEventsModulePath
39 | }
40 |
41 | $pesterContainer = New-PesterContainer -Path (Join-Path $PSScriptRoot 'integration') -Data $testsData
42 | $pesterConfiguration = [PesterConfiguration]::Default
43 |
44 | $pesterConfiguration.Run.Path = (Join-Path $PSScriptRoot 'integration')
45 | $pesterConfiguration.Run.Container = $pesterContainer
46 | $pesterConfiguration.Run.Exit = $EnableProcessExit.IsPresent
47 |
48 | Invoke-Pester -Configuration $pesterConfiguration
49 | }
50 |
--------------------------------------------------------------------------------
/test/unit/New-CloudEvent.Tests.ps1:
--------------------------------------------------------------------------------
1 | # **************************************************************************
2 | # Copyright (c) Cloud Native Foundation.
3 | # SPDX-License-Identifier: Apache-2.0
4 | # **************************************************************************
5 |
6 | Describe "New-CloudEvent Function Tests" {
7 | Context "Create CloudEvent Object" {
8 | It 'Create CloudEvent with required parameters only' {
9 | # Arrange
10 | $expectedType = 'test'
11 | $expectedSource = 'urn:test'
12 | $expectedId = ([Guid]::NewGuid().ToString())
13 |
14 | # Act
15 | $actual = New-CloudEvent `
16 | -Id $expectedId `
17 | -Type $expectedType `
18 | -Source $expectedSource
19 |
20 | # Assert
21 | $actual | Should -Not -Be $null
22 | $actual.Type | Should -Be $expectedType
23 | $actual.Source | Should -Be $expectedSource
24 | $actual.Id | Should -Be $expectedId
25 | }
26 |
27 | It 'Create CloudEvent with all possible parameters' {
28 | # Arrange
29 | $expectedType = 'test'
30 | $expectedSource = 'urn:test'
31 | $expectedId = 'test-id-1'
32 | $expectedTime = Get-Date -Year 2021 -Month 1 -Day 18 -Hour 12 -Minute 30 -Second 0
33 |
34 | # Act
35 | $actual = New-CloudEvent `
36 | -Type $expectedType `
37 | -Source $expectedSource `
38 | -Id $expectedId `
39 | -Time $expectedTime
40 |
41 | # Assert
42 | $actual | Should -Not -Be $null
43 | $actual.Type | Should -Be $expectedType
44 | $actual.Source | Should -Be $expectedSource
45 | $actual.Id | Should -Be $expectedId
46 | $actual.Time | Should -Be $expectedTime
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/publish.ps1:
--------------------------------------------------------------------------------
1 | # **************************************************************************
2 | # Copyright (c) Cloud Native Foundation.
3 | # SPDX-License-Identifier: Apache-2.0
4 | # **************************************************************************
5 |
6 | <#
7 | .SYNOPSIS
8 | Publish the CloudEvents.Sdk module to PSGallery
9 |
10 | .PARAMETER NuGetApiKey
11 | PowerShell Gallery API Key to be used to publish the module
12 |
13 | .PARAMETER ModuleReleaseDir
14 | Parent directory of the 'CloudEvents.Sdk' module that will be published
15 | #>
16 |
17 | param(
18 | [Parameter(Mandatory = $true)]
19 | [string]
20 | $NuGetApiKey,
21 |
22 | [Parameter(Mandatory = $true)]
23 | [ValidateScript({ Test-Path $_ })]
24 | [string]
25 | $ModuleReleaseDir
26 | )
27 |
28 | # Work with full path in case relative path is provided
29 | $ModuleReleaseDir = (Resolve-Path $ModuleReleaseDir).Path
30 |
31 | $moduleName = 'CloudEvents.Sdk'
32 |
33 | # Build is successful and all tests pass
34 | $env:PSModulePath += [IO.Path]::PathSeparator + $ModuleReleaseDir
35 |
36 | $localModule = Get-Module $moduleName -ListAvailable
37 | $psGalleryModule = Find-Module -Name $moduleName -Repository PSGallery
38 |
39 | # Throw an exception if module with the same version is availble on PSGallery
40 | if ( $null -ne $psGalleryModule -and `
41 | $null -ne $localModule -and `
42 | $psGalleryModule.Version -eq $localModule.Version ) {
43 | throw "'$moduleName' module with version '$($localModule.Version)' is already available on PSGallery"
44 | }
45 |
46 | Write-Host "Performing operation: Publish-Module -Name $moduleName -RequiredVersion $($localModule.Version) -NuGetApiKey *** -Repository PSGallery -Confirm:`$false"
47 | Publish-Module -Name $moduleName -RequiredVersion $localModule.Version -NuGetApiKey $NuGetApiKey -Repository PSGallery -Confirm:$false -ErrorAction Stop
48 |
--------------------------------------------------------------------------------
/test/unit/Set-CloudEventJsonData.Tests.ps1:
--------------------------------------------------------------------------------
1 | # **************************************************************************
2 | # Copyright (c) Cloud Native Foundation.
3 | # SPDX-License-Identifier: Apache-2.0
4 | # **************************************************************************
5 |
6 | Describe "Set-CloudEventJsonData Function Tests" {
7 | Context "Sets Json Data" {
8 | It 'Sets json data with depth 1' {
9 | # Arrange
10 | $cloudEvent = New-CloudEvent `
11 | -Id ([Guid]::NewGuid()) `
12 | -Type test `
13 | -Source 'urn:test'
14 |
15 | $expectedJson = '{
16 | "a": "b"
17 | }'
18 |
19 | $htData = @{'a' = 'b'}
20 |
21 | # Act
22 | $actual = $cloudEvent | Set-CloudEventJsonData -Data $htData -Depth 1
23 |
24 | # Assert
25 | $actual | Should -Not -Be $null
26 | $actual.DataContentType.ToString() | Should -Be 'application/json'
27 | $actual.Data | Should -Be $expectedJson
28 | }
29 |
30 | It 'Sets json data with depth 4' {
31 | # Arrange
32 | $cloudEvent = New-CloudEvent `
33 | -Id ([Guid]::NewGuid()) `
34 | -Type test `
35 | -Source 'urn:test'
36 |
37 | $expectedJson = '{
38 | "1": {
39 | "2": {
40 | "3": {
41 | "4": "wow"
42 | }
43 | }
44 | }
45 | }'
46 |
47 | $htData = @{
48 | '1' = @{
49 | '2' = @{
50 | '3' = @{
51 | '4' = 'wow'
52 | }
53 | }
54 | }
55 | }
56 |
57 | # Act
58 | $actual = $cloudEvent | Set-CloudEventJsonData -Data $htData -Depth 4
59 |
60 | # Assert
61 | $actual | Should -Not -Be $null
62 | $actual.DataContentType.ToString() | Should -Be 'application/json'
63 | $actual.Data | Should -Be $expectedJson
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/RELEASING.md:
--------------------------------------------------------------------------------
1 | # How to create a Release
2 |
3 | We use `tags` to drive the creation of the releases. This is handled by the
4 | Github Actions release workflow in
5 | [`ps-ce-sdk-release.yaml`](.github/workflows/ps-ce-sdk-release.yaml).
6 |
7 | A new release will upload the `PowerShell` module artifacts to the `PowerShell`
8 | [gallery](https://www.powershellgallery.com/packages/CloudEvents.Sdk).
9 |
10 | ## Step 1 - Bump Module Version
11 |
12 | Bump the `ModuleVersion` in
13 | [`src/CloudEventsPowerShell/CloudEvents.Sdk.psd1`](./src/CloudEventsPowerShell/CloudEvents.Sdk.psd1)
14 | to the next semantic release version (without `"v"` prefix).
15 |
16 | ```powershell
17 | # Version number of this module.
18 | ModuleVersion = '0.3.0'
19 | ```
20 |
21 | Create a pull request with this change, review and approve it **after** all checks
22 | have passed.
23 |
24 |
25 | ## Step 2 - Update local `main` branch
26 |
27 | Pull in the latest changes, incl. the merged PR above, into your local `main`
28 | branch of this repository **before** creating a `tag` via the `git` CLI.
29 |
30 | ```console
31 | git checkout main
32 | git fetch -avp
33 | git pull upstream main
34 | ```
35 |
36 | **Note:** the above commands assume `upstream` pointing to the remote
37 | `https://github.com/cloudevents/sdk-powershell.git`
38 |
39 | ## Step 3 - Create and push a Tag
40 |
41 |
42 | ```console
43 | RELEASE=v0.3.0
44 | git tag -a $RELEASE -m "Release ${RELEASE}"
45 | git push upstream refs/tags/${RELEASE}
46 | ```
47 |
48 |
49 | This will trigger the release
50 | [workflow](https://github.com/cloudevents/sdk-powershell/actions/workflows/ps-ce-sdk-release.yaml).
51 | **Verify** that it executed successfully and that a new Github
52 | [release](https://github.com/cloudevents/sdk-powershell/releases) was created.
53 |
54 | The release workflow also creates a pull request with the updated
55 | [`CHANGELOG.md`](CHANGELOG.md). **Verify**, approve and merge accordingly.
56 |
57 | If you need to make changes to the Github release notes, you can edit them on the [release](https://github.com/cloudevents/sdk-powershell/releases) page.
58 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to CloudEvents sdk-powershell
2 |
3 | We welcome and appreciate contributions from the community. Please read this document for different ways of getting involved with the sdk-powershell.
4 |
5 | ## Contributing to Issues
6 |
7 | - Ensure that the issue you are about to file is not already open. If someone has already opened a similar issue, please leave a comment or add a GitHub reaction to the top comment to express your interest.
8 | - If you can't find your issue already, [open a new issue](https://github.com/cloudevents/sdk-powershell/issues/new).
9 |
10 | ## Contributing to Code
11 | CloudEvents.SDK PowerShell consists of a .NET project that resolves the [CloudEvents sdk-csharp](https://github.com/cloudevents/sdk-csharp) dependency, PowerShell script module with the sdk advanced functions, and Pester tests.
12 |
13 | ### Required Toolchain
14 | - [.NET 5.0](https://dotnet.microsoft.com/download/dotnet/5.0) SDK
15 | - [PowerShell 7.0](https://github.com/PowerShell/PowerShell#get-powershell) or higher
16 | - [Pester 5.1.1](https://www.powershellgallery.com/packages/Pester/5.1.1) or higher
17 |
18 | ### Building and testing
19 | The CloudEvents.Sdk module source code is in the `src` directory. We have unit tests and localhost integration tests available in the `test` directory.
20 |
21 | The `build.ps1` script is the entry point to build the module and run the tests. It has two parameters
22 | 1. `OutputDir` - The destination directory for the `CloudEvents.Sdk` module
23 | 2. `TestsType` - Specifies which tests (`none` | `unit` | `integration` | `all`) to run on successful module build.
24 | Running the `build.ps1` without specifying parameters produce the module in a `CloudEvents.Sdk` directory under the repository root directory, and runs all tests.
25 |
26 | ### Forks and Pull Requests
27 |
28 | Anyone can [fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) the repository into their own user account, where they can make private changes. To contribute your changes back create a [pull request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests). A pull request should relate to an existing issue.
29 |
30 | Adding new features or fixing bugs might require adding or updating tests. Before creating a pull request make sure all tests pass locally.
--------------------------------------------------------------------------------
/test/unit/Read-CloudEventJsonData.Tests.ps1:
--------------------------------------------------------------------------------
1 | # **************************************************************************
2 | # Copyright (c) Cloud Native Foundation.
3 | # SPDX-License-Identifier: Apache-2.0
4 | # **************************************************************************
5 |
6 | Describe "Read-CloudEventJsonData Function Tests" {
7 | Context "Extracts Json Data from CloudEvent" {
8 | It 'Extracts hashtable from CloudEvent with json data' {
9 | # Arrange
10 | $cloudEvent = New-CloudEvent `
11 | -Id ([Guid]::NewGuid()) `
12 | -Type test `
13 | -Source 'urn:test'
14 |
15 | $expectedHtData = @{'a' = 'b'}
16 |
17 | $cloudEvent = $cloudEvent | Set-CloudEventJsonData -Data $expectedHtData -Depth 1
18 |
19 | # Act
20 | $actual = $cloudEvent | Read-CloudEventJsonData
21 |
22 | # Assert
23 | $actual | Should -Not -Be $null
24 | $actual -is [hashtable] | Should -Be $true
25 | $actual.a | Should -Be 'b'
26 | }
27 |
28 | It 'Expects error when CloudEvent data is not json' {
29 | # Arrange
30 | $cloudEvent = New-CloudEvent `
31 | -Id ([Guid]::NewGuid()) `
32 | -Type test `
33 | -Source 'urn:test'
34 |
35 | $cloudEvent = $cloudEvent | Set-CloudEventData -Data "test" -DataContentType 'application/text'
36 | $pre
37 |
38 | # Act
39 | { $cloudEvent | Read-CloudEventJsonData -ErrorAction Stop } | `
40 | Should -Throw "*Cloud Event '$($cloudEvent.Id)' has no json data*"
41 |
42 | }
43 |
44 | It 'Extracts hashtable from CloudEvent with json data with depth 4' {
45 | # Arrange
46 | $cloudEvent = New-CloudEvent `
47 | -Id ([Guid]::NewGuid()) `
48 | -Type test `
49 | -Source 'urn:test'
50 |
51 | $expectedHtData = @{
52 | 'l1' = @{
53 | 'l2' = @{
54 | 'l3' = @{
55 | 'l4' = 'wow'
56 | }
57 | }
58 | }
59 | }
60 |
61 | $cloudEvent = $cloudEvent | Set-CloudEventJsonData -Data $expectedHtData -Depth 4
62 |
63 | # Act
64 | $actual = $cloudEvent | Read-CloudEventJsonData -Depth 4
65 |
66 | # Assert
67 | $actual | Should -Not -Be $null
68 | $actual -is [hashtable] | Should -Be $true
69 | $actual.l1.l2.l3.l4 | Should -Be 'wow'
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/test/unit/Set-CloudEventXmlData.Tests.ps1:
--------------------------------------------------------------------------------
1 | # **************************************************************************
2 | # Copyright (c) Cloud Native Foundation.
3 | # SPDX-License-Identifier: Apache-2.0
4 | # **************************************************************************
5 |
6 | Describe "Set-CloudEventXmlData Function Tests" {
7 | Context "Sets Xml Data" {
8 | It 'Sets xml data with depth 1' {
9 | # Arrange
10 | $cloudEvent = New-CloudEvent `
11 | -Id ([Guid]::NewGuid()) `
12 | -Type test `
13 | -Source 'urn:test'
14 |
15 | $expectedXml = 'b'
16 |
17 | $htData = @{'a' = 'b'}
18 |
19 | # Act
20 | $actual = $cloudEvent | Set-CloudEventXmlData -Data $htData -AttributesKeysInElementAttributes $false
21 |
22 | # Assert
23 | $actual | Should -Not -Be $null
24 | $actual.DataContentType.ToString() | Should -Be 'application/xml'
25 | $actual.Data | Should -Be $expectedXml
26 | }
27 |
28 | It 'Sets xml data with depth 4' {
29 | # Arrange
30 | $cloudEvent = New-CloudEvent `
31 | -Id ([Guid]::NewGuid()) `
32 | -Type test `
33 | -Source 'urn:test'
34 |
35 | $expectedXml = '<1><2><3><4>wow4>3>2>1>'
36 |
37 | $htData = @{
38 | '1' = @{
39 | '2' = @{
40 | '3' = @{
41 | '4' = 'wow'
42 | }
43 | }
44 | }
45 | }
46 |
47 | # Act
48 | $actual = $cloudEvent | Set-CloudEventXmlData -Data $htData -AttributesKeysInElementAttributes $false
49 |
50 | # Assert
51 | $actual | Should -Not -Be $null
52 | $actual.DataContentType.ToString() | Should -Be 'application/xml'
53 | $actual.Data | Should -Be $expectedXml
54 | }
55 |
56 | It 'Should throw when no single root key hashtable is passed to the Set-CloudEventXmlData Data parameter' {
57 | # Arrange
58 | $cloudEvent = New-CloudEvent `
59 | -Id ([Guid]::NewGuid()) `
60 | -Type test `
61 | -Source 'urn:test'
62 |
63 | $htData = @{
64 | '1' = '2'
65 | '3' = '4'
66 | }
67 |
68 | # Act & Assert
69 | { $cloudEvent | Set-CloudEventXmlData -Data $htData -AttributesKeysInElementAttributes $false} | `
70 | Should -Throw '*Input Hashtable must have single root key*'
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/test/unit/Read-CloudEventXmlData.Tests.ps1:
--------------------------------------------------------------------------------
1 | # **************************************************************************
2 | # Copyright (c) Cloud Native Foundation.
3 | # SPDX-License-Identifier: Apache-2.0
4 | # **************************************************************************
5 |
6 | Describe "Read-CloudEventXmlData Function Tests" {
7 | Context "Extracts Xml Data from CloudEvent" {
8 | It 'Extracts hashtable from CloudEvent with xml data' {
9 | # Arrange
10 | $cloudEvent = New-CloudEvent `
11 | -Id ([Guid]::NewGuid()) `
12 | -Type test `
13 | -Source 'urn:test'
14 |
15 | $xmlData = "b"
16 | $expectedHtData = @{'a' = 'b'}
17 |
18 | $cloudEvent = $cloudEvent | Set-CloudEventData -Data $xmlData -DataContentType 'application/xml'
19 |
20 | # Act
21 | $actual = $cloudEvent | Read-CloudEventXmlData -ConvertMode 'SkipAttributes'
22 |
23 | # Assert
24 | $actual | Should -Not -Be $null
25 | $actual -is [hashtable] | Should -Be $true
26 | $actual.a | Should -Be 'b'
27 | }
28 |
29 | It 'Expects error when CloudEvent data is not xml' {
30 | # Arrange
31 | $cloudEvent = New-CloudEvent `
32 | -Id ([Guid]::NewGuid()) `
33 | -Type test `
34 | -Source 'urn:test'
35 |
36 | $cloudEvent = $cloudEvent | Set-CloudEventData -Data "test" -DataContentType 'application/text'
37 | $pre
38 |
39 | # Act
40 | { $cloudEvent | Read-CloudEventXmlData -ConvertMode 'SkipAttributes' -ErrorAction Stop } | `
41 | Should -Throw "*Cloud Event '$($cloudEvent.Id)' has no xml data*"
42 |
43 | }
44 |
45 | It 'Extracts hashtable from CloudEvent with xml data with depth 4' {
46 | # Arrange
47 | $cloudEvent = New-CloudEvent `
48 | -Id ([Guid]::NewGuid()) `
49 | -Type test `
50 | -Source 'urn:test'
51 |
52 | $xmlData = 'wow'
53 | $expectedHtData = @{
54 | 'l1' = @{
55 | 'l2' = @{
56 | 'l3' = @{
57 | 'l4' = 'wow'
58 | }
59 | }
60 | }
61 | }
62 |
63 | $cloudEvent = $cloudEvent | Set-CloudEventData -Data $xmlData -DataContentType 'application/xml'
64 |
65 | # Act
66 | $actual = $cloudEvent | Read-CloudEventXmlData -ConvertMode 'SkipAttributes'
67 |
68 | # Assert
69 | $actual | Should -Not -Be $null
70 | $actual -is [hashtable] | Should -Be $true
71 | $actual.l1.l2.l3.l4 | Should -Be 'wow'
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/.github/workflows/ps-ce-sdk-release.yaml:
--------------------------------------------------------------------------------
1 | # **************************************************************************
2 | # Copyright (c) Cloud Native Foundation.
3 | # SPDX-License-Identifier: Apache-2.0
4 | # **************************************************************************
5 |
6 | name: Release
7 |
8 | on:
9 | push:
10 | tags:
11 | - v*
12 | workflow_dispatch:
13 |
14 | jobs:
15 | psgallery-publish:
16 | name: Release CloudEvents.Sdk Module
17 | runs-on: "ubuntu-latest"
18 | env:
19 | OUTPUT_DIR: release
20 | CHANGE_LOG_FILE_NAME: RELEASE_CHANGELOG.md
21 |
22 | timeout-minutes: 10
23 |
24 | steps:
25 | - name: Check out code
26 | uses: actions/checkout@v2
27 |
28 | - name: Build and Test
29 | shell: pwsh
30 | run: ./build.ps1 -OutputDir $env:OUTPUT_DIR -TestsType all -ExitProcess
31 |
32 | - name: Publish to PSGallery
33 | shell: pwsh
34 | run: ./publish.ps1 -ModuleReleaseDir $env:OUTPUT_DIR -NuGetApiKey ${{ secrets.CLOUDEVENTS_SDK_PUBLISHER_API_KEY }}
35 |
36 | - name: Create CHANGELOG
37 | env:
38 | IMAGE: quay.io/git-chglog/git-chglog
39 | # https://quay.io/repository/git-chglog/git-chglog from tag v0.14.2
40 | IMAGE_SHA: 998e89dab8dd8284cfff5f8cfb9e9af41fe3fcd4671f2e86a180e453c20959e3
41 | run: |
42 | # generate CHANGELOG for this Github release tag only
43 | docker run --rm -v $PWD:/workdir ${IMAGE}@sha256:${IMAGE_SHA} -o ${CHANGE_LOG_FILE_NAME} $(basename "${{ github.ref }}" )
44 |
45 | - name: Create Github Release
46 | env:
47 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
48 | run: gh release create $(basename "${{ github.ref }}") -F ${CHANGE_LOG_FILE_NAME}
49 |
50 | changelog-pull-request:
51 | needs: psgallery-publish
52 | name: Create CHANGELOG PR
53 | runs-on: ubuntu-latest
54 | continue-on-error: true
55 | env:
56 | CHANGE_LOG_FILE_NAME: CHANGELOG.md
57 |
58 | steps:
59 | - name: Checkout
60 | uses: actions/checkout@v2
61 | with:
62 | # for changelog
63 | fetch-depth: 0
64 | ref: "main"
65 |
66 | - name: Create CHANGELOG commit
67 | env:
68 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
69 | IMAGE: quay.io/git-chglog/git-chglog
70 | # https://quay.io/repository/git-chglog/git-chglog from tag v0.14.2
71 | IMAGE_SHA: 998e89dab8dd8284cfff5f8cfb9e9af41fe3fcd4671f2e86a180e453c20959e3
72 | run: |
73 | # update CHANGELOG.md
74 | docker run --rm -v $PWD:/workdir ${IMAGE}@sha256:${IMAGE_SHA} -o ${CHANGE_LOG_FILE_NAME}
75 |
76 | git config user.email "${{ github.actor }}@users.noreply.github.com"
77 | git config user.name "${{ github.actor }}"
78 | git add ${CHANGE_LOG_FILE_NAME}
79 | git commit -m "Update CHANGELOG for $(basename ${{ github.ref }})"
80 |
81 | - name: Create Pull Request
82 | uses: peter-evans/create-pull-request@v3
83 | with:
84 | delete-branch: true
85 | title: "Update CHANGELOG"
86 | body: |
87 | Update CHANGELOG.md for new release
88 |
89 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## [Release v0.3.3](https://github.com/cloudevents/sdk-powershell/compare/v0.3.2...v0.3.3)
4 |
5 | > Release Date: 2021-09-08
6 |
7 | ### 🧹 Chore
8 |
9 | - [32f333b] Bump module version for new release
10 | - [5e2e221] Trigger release on tag push (#47)
11 |
12 | ### ⚠️ BREAKING
13 |
14 | ### 📖 Commits
15 |
16 | - [32f333b] chore: Bump module version for new release
17 | - [5e2e221] chore: Trigger release on tag push (#47)
18 |
19 |
20 | ## [Release v0.3.2](https://github.com/cloudevents/sdk-powershell/compare/v0.3.1...v0.3.2)
21 |
22 | > Release Date: 2021-09-08
23 |
24 | ### 🧹 Chore
25 |
26 | - [c8b5958] Add Project URI in the module manifest
27 |
28 | ### ⚠️ BREAKING
29 |
30 | ### 📖 Commits
31 |
32 | - [c8b5958] chore: Add Project URI in the module manifest
33 |
34 |
35 | ## [Release v0.3.1](https://github.com/cloudevents/sdk-powershell/compare/v0.3.0...v0.3.1)
36 |
37 | > Release Date: 2021-08-30
38 |
39 | ### 🐞 Fix
40 |
41 | - [7eb0f9b] Incorrect parsing of Binary Content Mode cloud events
42 |
43 | ### 🧹 Chore
44 |
45 | - [941816a] Add RELEASE instructions (#40)
46 |
47 | ### ⚠️ BREAKING
48 |
49 | ### 📖 Commits
50 |
51 | - [7eb0f9b] fix: Incorrect parsing of Binary Content Mode cloud events
52 | - [941816a] chore: Add RELEASE instructions (#40)
53 | - [7876104] Update README with the new version output
54 |
55 |
56 | ## v0.3.0
57 |
58 | > Release Date: 2021-06-21
59 |
60 | ### 🧹 Chore
61 |
62 | - [5b14f75] Include commit details in CHANGELOG (#35)
63 | - [2c4d0ed] Add check WIP action (#31)
64 | - [5764180] Add CHANGELOG configuration (#29)
65 | - [0bd4192] Verify PRs and push to main (#6)
66 | - [e08a275] Add stale action (#7)
67 |
68 | ### ⚠️ BREAKING
69 |
70 | ### 📖 Commits
71 |
72 | - [2144695] Bump module version 0.3.0
73 | - [5b14f75] chore: Include commit details in CHANGELOG (#35)
74 | - [78b7e9e] Implement publish script
75 | - [2c4d0ed] chore: Add check WIP action (#31)
76 | - [5764180] chore: Add CHANGELOG configuration (#29)
77 | - [a0e1078] Add PR template (#3)
78 | - [ae6f848] Update issue templates
79 | - [3a9010a] Improve examples. Remove unnecessary details.
80 | - [401a475] Implement wait server to start in the integrations tests BeforeAll Fix Binary Mode HTTP Header DateTime format. It is UTC time formatted in ISO 8601 format including milliseconds. Bump revision of the module version
81 | - [0bd4192] chore: Verify PRs and push to main (#6)
82 | - [e08a275] chore: Add stale action (#7)
83 | - [3629c77] Add Install Module Instructions to README ([#17](https://github.com/cloudevents/sdk-powershell/issues/17)) (#17) (#16)
84 | - [aac584a] Enable process exit in build.ps1
85 | - [84606b0] Create CONTRIBUTING.md ([#15](https://github.com/cloudevents/sdk-powershell/issues/15)) (#15)
86 | - [d305f16] Renames Add-CloudEvent*Data to Set-CloudEvent*Data ([#12](https://github.com/cloudevents/sdk-powershell/issues/12)) (#12) (#9)
87 | - [f23df54] Adds license headers in source code files ([#11](https://github.com/cloudevents/sdk-powershell/issues/11)) (#11)
88 | - [62f14b6] Remove install instructions from an internal PS repository ([#10](https://github.com/cloudevents/sdk-powershell/issues/10)) (#10) (#1)
89 | - [b2afcf8] Initial commit
90 |
--------------------------------------------------------------------------------
/test/unit/Set-CloudEventData.Tests.ps1:
--------------------------------------------------------------------------------
1 | # **************************************************************************
2 | # Copyright (c) Cloud Native Foundation.
3 | # SPDX-License-Identifier: Apache-2.0
4 | # **************************************************************************
5 |
6 | Describe "Set-CloudEventData Function Tests" {
7 | Context "Sets Data" {
8 | It 'Sets byte[] data' {
9 | # Arrange
10 | $cloudEvent = New-CloudEvent `
11 | -Id ([Guid]::NewGuid()) `
12 | -Type test `
13 | -Source 'urn:test'
14 |
15 | $expectedData = [Text.Encoding]::UTF8.GetBytes("test")
16 | $expectedDataContentType = 'application/octet-stream'
17 |
18 |
19 | # Act
20 | $actual = $cloudEvent | `
21 | Set-CloudEventData `
22 | -Data $expectedData `
23 | -DataContentType $expectedDataContentType
24 |
25 | # Assert
26 | $actual | Should -Not -Be $null
27 | $actual.DataContentType.ToString() | Should -Be $expectedDataContentType
28 | $actual.Data | Should -Be $expectedData
29 | }
30 |
31 | It 'Sets xml text data' {
32 | # Arrange
33 | $cloudEvent = New-CloudEvent `
34 | -Id ([Guid]::NewGuid()) `
35 | -Type test `
36 | -Source 'urn:test'
37 |
38 | $expectedData = ''
39 | $expectedDataContentType = 'application/xml'
40 |
41 |
42 | # Act
43 | $actual = $cloudEvent | `
44 | Set-CloudEventData `
45 | -Data $expectedData `
46 | -DataContentType $expectedDataContentType
47 |
48 | # Assert
49 | $actual | Should -Not -Be $null
50 | $actual.DataContentType.ToString() | Should -Be $expectedDataContentType
51 | $actual.Data | Should -Be $expectedData
52 | }
53 | }
54 |
55 |
56 | Context "Errors on invalid data content type" {
57 | It 'Throws error on invalid MIME content type' {
58 | # Arrange
59 | $invalidContentType = 'invalid'
60 |
61 | $cloudEvent = New-CloudEvent `
62 | -Id ([Guid]::NewGuid()) `
63 | -Type test `
64 | -Source 'urn:test'
65 |
66 | # Act & Assert
67 | { Set-CloudEventData `
68 | -CloudEvent $cloudEvent `
69 | -Data '1' `
70 | -DataContentType $invalidContentType } | `
71 | Should -Throw "*The specified content type is invalid*"
72 | }
73 |
74 | It 'Throws error on empty content type' {
75 | # Arrange
76 | $invalidContentType = [string]::Empty
77 |
78 | $cloudEvent = New-CloudEvent `
79 | -Id ([Guid]::NewGuid()) `
80 | -Type test `
81 | -Source 'urn:test'
82 |
83 | # Act & Assert
84 | { Set-CloudEventData `
85 | -CloudEvent $cloudEvent `
86 | -Data '1' `
87 | -DataContentType $invalidContentType } | `
88 | Should -Throw "*The parameter 'contentType' cannot be an empty string*"
89 | }
90 |
91 | It 'Throws error on null content type' {
92 | # Arrange
93 | $invalidContentType = $null
94 |
95 | $cloudEvent = New-CloudEvent `
96 | -Id ([Guid]::NewGuid()) `
97 | -Type test `
98 | -Source 'urn:test'
99 |
100 | # Act & Assert
101 | { Set-CloudEventData `
102 | -CloudEvent $cloudEvent `
103 | -Data '1' `
104 | -DataContentType $invalidContentType } | `
105 | Should -Throw "*The parameter 'contentType' cannot be an empty string*"
106 | }
107 | }
108 | }
--------------------------------------------------------------------------------
/src/CloudEventsPowerShell/CloudEvents.Sdk.psd1:
--------------------------------------------------------------------------------
1 | # **************************************************************************
2 | # Copyright (c) Cloud Native Foundation.
3 | # SPDX-License-Identifier: Apache-2.0
4 | # **************************************************************************
5 |
6 | @{
7 |
8 | # Script module or binary module file associated with this manifest.
9 | RootModule = 'CloudEvents.Sdk.psm1'
10 |
11 | # Version number of this module.
12 | ModuleVersion = '0.3.3'
13 |
14 | # Supported PSEditions
15 | CompatiblePSEditions = @('Core')
16 |
17 | # ID used to uniquely identify this module
18 | GUID = 'd0d7d392-0eab-40a8-8a3f-78ba41ef2f02'
19 |
20 | # Author of this module
21 | Author = 'dmilov'
22 |
23 | # Company or vendor of this module
24 | CompanyName = 'The CloudEvents Authors'
25 |
26 | # Copyright statement for this module
27 | Copyright = '(c) The CloudEvents Authors'
28 |
29 | # Description of the functionality provided by this module
30 | Description = 'PowerShell CloudEvents SDK'
31 |
32 | # Minimum version of the PowerShell engine required by this module
33 | PowerShellVersion = '7.0'
34 |
35 | # Name of the PowerShell host required by this module
36 | # PowerShellHostName = ''
37 |
38 | # Minimum version of the PowerShell host required by this module
39 | # PowerShellHostVersion = ''
40 |
41 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
42 | # DotNetFrameworkVersion = ''
43 |
44 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
45 | # ClrVersion = ''
46 |
47 | # Processor architecture (None, X86, Amd64) required by this module
48 | # ProcessorArchitecture = ''
49 |
50 | # Modules that must be imported into the global environment prior to importing this module
51 | # RequiredModules = @()
52 |
53 | # Assemblies that must be loaded prior to importing this module
54 | RequiredAssemblies = @('CloudNative.CloudEvents.dll')
55 |
56 | # Script files (.ps1) that are run in the caller's environment prior to importing this module.
57 | # ScriptsToProcess = @()
58 |
59 | # Type files (.ps1xml) to be loaded when importing this module
60 | # TypesToProcess = @()
61 |
62 | # Format files (.ps1xml) to be loaded when importing this module
63 | # FormatsToProcess = @()
64 |
65 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
66 | # NestedModules = @()
67 |
68 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
69 | FunctionsToExport = @(
70 | 'New-CloudEvent', 'Set-CloudEventData', 'Set-CloudEventJsonData', 'Set-CloudEventXmlData', 'Read-CloudEventData', 'Read-CloudEventJsonData', 'Read-CloudEventXmlData', # CloudEvent Object Functions
71 | 'ConvertTo-HttpMessage', 'ConvertFrom-HttpMessage' # Http Binding Functions
72 | )
73 |
74 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
75 | CmdletsToExport = @()
76 |
77 | # Variables to export from this module
78 | VariablesToExport = '*'
79 |
80 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
81 | AliasesToExport = @()
82 |
83 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
84 | PrivateData = @{
85 |
86 | PSData = @{
87 |
88 | # Tags applied to this module. These help with module discovery in online galleries.
89 | Tags = 'CloudEvents','SDK'
90 |
91 | # A URL to the license for this module.
92 | # LicenseUri = ''
93 |
94 | # A URL to the main website for this project.
95 | ProjectUri = 'https://github.com/cloudevents/sdk-powershell'
96 |
97 | # A URL to an icon representing this module.
98 | # IconUri = ''
99 |
100 | # ReleaseNotes of this module
101 | # ReleaseNotes = ''
102 |
103 | # Prerelease string of this module
104 | # Prerelease = ''
105 |
106 | # Flag to indicate whether the module requires explicit user acceptance for install/update
107 | # RequireLicenseAcceptance = $false
108 |
109 | # External dependent modules of this module
110 | # ExternalModuleDependencies = @()
111 |
112 | } # End of PSData hashtable
113 |
114 | } # End of PrivateData hashtable
115 |
116 | }
117 |
118 |
--------------------------------------------------------------------------------
/test/integration/HttpServer.ps1:
--------------------------------------------------------------------------------
1 | # **************************************************************************
2 | # Copyright (c) Cloud Native Foundation.
3 | # SPDX-License-Identifier: Apache-2.0
4 | # **************************************************************************
5 |
6 | param(
7 | [Parameter(Mandatory = $true)]
8 | [ValidateScript( { Test-Path $_ })]
9 | [string]
10 | $CloudEventsModulePath,
11 |
12 | [Parameter(
13 | Mandatory = $true,
14 | ValueFromPipeline = $false,
15 | ValueFromPipelineByPropertyName = $false)]
16 | [ValidateNotNull()]
17 | [string]
18 | $ServerUrl
19 | )
20 |
21 | . (Join-Path $PSScriptRoot 'ProtocolConstants.ps1')
22 |
23 | # Import SDK Module
24 | Import-Module $CloudEventsModulePath
25 |
26 | function Start-HttpCloudEventListener {
27 | <#
28 | .DESCRIPTION
29 | Starts a HTTP CloudEvent Listener on specified Url
30 | #>
31 |
32 | [CmdletBinding()]
33 | param(
34 | [Parameter(
35 | Mandatory = $true,
36 | ValueFromPipeline = $false,
37 | ValueFromPipelineByPropertyName = $false)]
38 | [ValidateNotNull()]
39 | [string]
40 | $Url,
41 |
42 | [Parameter(
43 | Mandatory = $false,
44 | ValueFromPipeline = $false,
45 | ValueFromPipelineByPropertyName = $false)]
46 | [scriptblock]
47 | $Handler
48 | )
49 |
50 | $listener = New-Object -Type 'System.Net.HttpListener'
51 | $listener.AuthenticationSchemes = [System.Net.AuthenticationSchemes]::Anonymous
52 | $listener.Prefixes.Add($Url)
53 |
54 | try {
55 | $listener.Start()
56 |
57 | $context = $listener.GetContext()
58 |
59 | # Read Input Stream
60 | $buffer = New-Object 'byte[]' -ArgumentList 1024
61 | $ms = New-Object 'IO.MemoryStream'
62 | $read = 0
63 | while (($read = $context.Request.InputStream.Read($buffer, 0, 1024)) -gt 0) {
64 | $ms.Write($buffer, 0, $read);
65 | }
66 | $bodyData = $ms.ToArray()
67 | $ms.Dispose()
68 |
69 | # Read Headers
70 | $headers = @{}
71 | for ($i = 0; $i -lt $context.Request.Headers.Count; $i++) {
72 | $headers[$context.Request.Headers.GetKey($i)] = $context.Request.Headers.GetValues($i)
73 | }
74 |
75 | $cloudEvent = ConvertFrom-HttpMessage -Headers $headers -Body $bodyData
76 |
77 | if ( $cloudEvent -ne $null ) {
78 | $Handler.Invoke($cloudEvent, $context.Response)
79 | $context.Response.Close();
80 | }
81 | else {
82 | $context.Response.StatusCode = [int]([System.Net.HttpStatusCode]::BadRequest)
83 | $context.Response.Close();
84 | }
85 |
86 | }
87 | catch {
88 | Write-Error $_
89 | $context.Response.StatusCode = [int]([System.Net.HttpStatusCode]::InternalServerError)
90 | $context.Response.Close();
91 | }
92 | finally {
93 | $listener.Stop()
94 | }
95 | }
96 |
97 | $global:serverStopRequested = $false
98 | while ( -not $global:serverStopRequested ) {
99 | try {
100 | Start-HttpCloudEventListener -Url $ServerUrl -Handler {
101 | $requestCloudEvent = $args[0]
102 | $response = $args[1]
103 |
104 | # When CloudEvent Type is 'echo-structured' or 'echo-binary' the Server responds
105 | # with CloudEvent in corresponding content mode
106 | if ( $requestCloudEvent.Type -eq $script:EchoBinaryType -or `
107 | $requestCloudEvent.Type -eq $script:EchoStructuredType ) {
108 |
109 | # Create Cloud Event for the response
110 | $cloudEvent = New-CloudEvent `
111 | -Type $requestCloudEvent.Type `
112 | -Source $script:ServerSource `
113 | -Time (Get-Date) `
114 | -Id $requestCloudEvent.Id
115 |
116 | # Add Data to the new Cloud Event
117 | $requestCloudEventJsonData = $requestCloudEvent | Read-CloudEventJsonData
118 | $requestCloudEventXmlData = $requestCloudEvent | Read-CloudEventXmlData -ConvertMode 'SkipAttributes'
119 | if ($requestCloudEventJsonData) {
120 | $cloudEvent = $cloudEvent | Set-CloudEventJsonData `
121 | -Data $requestCloudEventJsonData
122 | }
123 | elseif ($requestCloudEventXmlData) {
124 | $cloudEvent = $cloudEvent | Set-CloudEventXmlData `
125 | -Data $requestCloudEventXmlData `
126 | -AttributesKeysInElementAttributes $false
127 | }
128 | else {
129 | $requestCloudEventData = $requestCloudEvent | Read-CloudEventData
130 | $cloudEvent = $cloudEvent | Set-CloudEventData `
131 | -Data $requestCloudEventData `
132 | -DataContentType $requestCloudEvent.DataContentType
133 | }
134 |
135 | # Convert Cloud Event to HTTP Response
136 | $contentMode = $requestCloudEvent.Type.TrimStart('echo-')
137 | $httpMessage = $cloudEvent | ConvertTo-HttpMessage -ContentMode $contentMode
138 |
139 | $response.Headers = New-Object 'System.Net.WebHeaderCollection'
140 | foreach ($keyValue in $httpMessage.Headers.GetEnumerator()) {
141 | $response.Headers.Add($keyValue.Key, $keyValue.Value)
142 | }
143 | $response.ContentLength64 = $httpMessage.Body.Length
144 | $response.OutputStream.Write($httpMessage.Body, 0, $httpMessage.Body.Length)
145 | $response.StatusCode = [int]([System.Net.HttpStatusCode]::OK)
146 |
147 | }
148 | else {
149 | # No Content in all other cases
150 | $response.StatusCode = [int]([System.Net.HttpStatusCode]::NoContent)
151 | }
152 |
153 | if ( $requestCloudEvent.Type -eq $script:ServerStopType ) {
154 | # Server Stop is requested
155 | $global:serverStopRequested = $true
156 | }
157 | }
158 | } catch {
159 | Write-Error $_
160 | break
161 | }
162 | }
--------------------------------------------------------------------------------
/src/CloudEventsPowerShell/dataserialization/xml.ps1:
--------------------------------------------------------------------------------
1 | # **************************************************************************
2 | # Copyright (c) Cloud Native Foundation.
3 | # SPDX-License-Identifier: Apache-2.0
4 | # **************************************************************************
5 |
6 | $SKIPATTR = "SkipAttributes"
7 | $ALWAYSATTRVALUE = "AlwaysAttrValue"
8 | $ATTRVALUEFORELEMENTSWITHATTR = "AttrValueWhenAttributes"
9 | function ConvertFrom-XmlPropertyValue {
10 | param(
11 | [Parameter(Mandatory = $true,
12 | ValueFromPipeline = $false)]
13 | $InputObject,
14 |
15 | [Parameter(Mandatory = $true)]
16 | [ValidateSet("SkipAttributes", "AlwaysAttrValue", "AttrValueWhenAttributes")]
17 | [string]
18 | $ConvertMode
19 | )
20 |
21 | $value = $InputObject
22 | $Attributes = $null
23 |
24 | if ($InputObject -is [Xml.XmlElement]) {
25 | $hasAttributes = (($InputObject | Get-Member -MemberType Properties) | Where-Object {$_.Name -eq '#text'}) -ne $null
26 | if ($hasAttributes) {
27 | $Attributes = @{}
28 | $arrProperties = $InputObject | Get-Member -MemberType Properties
29 | foreach ($p in $arrProperties) {
30 | if ($p.Name -eq '#text') {
31 | $value = $InputObject.'#text'
32 | } else {
33 | $Attributes[$p.Name] = $InputObject.$($p.Name)
34 | }
35 | }
36 |
37 | } else {
38 | $value = ConvertFrom-CEDataXml -InputXmlElement $InputObject -ConvertMode $ConvertMode
39 | }
40 | }
41 |
42 | if ($InputObject -is [array]) {
43 | $value = @()
44 | foreach ($obj in $InputObject) {
45 | $value += ConvertFrom-XmlPropertyValue -InputObject $obj -ConvertMode $ConvertMode
46 | }
47 | }
48 |
49 | if (($ConvertMode -eq $SKIPATTR) -or
50 | ($Attributes -eq $null -and $ConvertMode -eq $ATTRVALUEFORELEMENTSWITHATTR)) {
51 |
52 | $value
53 | }
54 |
55 | if (($ConvertMode -eq $ALWAYSATTRVALUE) -or
56 | ($Attributes -ne $null -and $ConvertMode -eq $ATTRVALUEFORELEMENTSWITHATTR)) {
57 | @{
58 | "Attributes" = $Attributes
59 | "Value" = $value
60 | }
61 | }
62 | }
63 |
64 | function ConvertFrom-CEDataXml {
65 | param(
66 | [Parameter(Mandatory = $true,
67 | ValueFromPipeline = $true,
68 | ParameterSetName="Text")]
69 | [ValidateNotNull()]
70 | [string]
71 | $InputString,
72 |
73 | [Parameter(Mandatory = $true,
74 | ValueFromPipeline = $false,
75 | ParameterSetName="XmlElement")]
76 | [ValidateNotNull()]
77 | [Xml.XmlElement]
78 | $InputXmlElement,
79 |
80 | [Parameter(Mandatory = $true)]
81 | [ValidateSet("SkipAttributes", "AlwaysAttrValue", "AttrValueWhenAttributes")]
82 | [string]
83 | $ConvertMode
84 | )
85 | $result = $null
86 | if ($InputString -ne $null) {
87 | $xmlDocument = [xml]$InputString
88 | }
89 | if ($InputXmlElement -ne $null) {
90 | $xmlDocument = $InputXmlElement
91 | }
92 |
93 | if ($xmlDocument -ne $null) {
94 | $xmlProperties = $xmlDocument | Get-Member -MemberType Properties
95 |
96 | $result = @{}
97 |
98 |
99 | foreach ($property in $xmlProperties) {
100 | $propertyName = $property.Name
101 | $value = ConvertFrom-XmlPropertyValue -InputObject $xmlDocument.$propertyName -ConvertMode $ConvertMode
102 | $result[$propertyName] = $value
103 | }
104 | }
105 |
106 | $result
107 | }
108 |
109 | function New-XmlElement {
110 | param(
111 | [Parameter(Mandatory = $true,
112 | ValueFromPipeline = $false)]
113 | [ValidateNotNull()]
114 | $DictionaryEntry,
115 |
116 | [Parameter(Mandatory = $true,
117 | ValueFromPipeline = $false)]
118 | $XmlDocument,
119 |
120 | [Parameter(Mandatory = $false)]
121 | [Switch]
122 | $AttributesKeysInElementAttributes
123 |
124 | )
125 | $result = $XmlDocument.CreateElement($DictionaryEntry.Name)
126 |
127 | $value = ""
128 |
129 | if ($DictionaryEntry.Value -is [hashtable]) {
130 | if($DictionaryEntry.Value.Keys.Count -eq 2 -and `
131 | $DictionaryEntry.Value['Attributes'] -is [hashtable] -and `
132 | $DictionaryEntry.Value['Value'] -ne $null -and `
133 | $AttributesKeysInElementAttributes) {
134 | foreach ($attKv in $DictionaryEntry.Value['Attributes'].GetEnumerator()) {
135 | $result.SetAttribute($attKv.Name, $attKv.Value)
136 | }
137 | $result.InnerText = $DictionaryEntry.Value['Value'].ToString()
138 |
139 | } else {
140 | foreach ($nameValue in $DictionaryEntry.Value.GetEnumerator()) {
141 | $xmlElement = New-XmlElement -DictionaryEntry $nameValue -XmlDocument $XmlDocument -AttributesKeysInElementAttributes:$AttributesKeysInElementAttributes
142 | $xmlElement | Foreach-Object {
143 | $result.AppendChild($_) | Out-Null
144 | }
145 | }
146 | }
147 | } elseif ($DictionaryEntry.Value -is [array]) {
148 | $result = @()
149 | foreach ($item in $DictionaryEntry.Value) {
150 | $result += (New-XmlElement `
151 | -DictionaryEntry `
152 | (New-Object System.Collections.DictionaryEntry `
153 | -ArgumentList @($DictionaryEntry.Name, $item)) `
154 | -XmlDocument $XmlDocument `
155 | -AttributesKeysInElementAttributes:$AttributesKeysInElementAttributes)
156 | }
157 | } else {
158 | $value = $DictionaryEntry.Value.ToString()
159 | $result.InnerText = $value
160 | }
161 |
162 | $result
163 | }
164 |
165 | function ConvertTo-CEDataXml {
166 | param(
167 | [Parameter(Mandatory = $true,
168 | ValueFromPipeline = $true)]
169 | [ValidateNotNull()]
170 | [Hashtable]
171 | $InputObject,
172 |
173 | [Parameter(Mandatory = $true)]
174 | [bool]
175 | $AttributesKeysInElementAttributes
176 | )
177 | if ($InputObject.Keys.Count -ne 1) {
178 | throw "Input Hashtable must have single root key"
179 | }
180 |
181 | [xml]$resultDocument = New-Object System.Xml.XmlDocument
182 |
183 | foreach ($nameValue in $InputObject.GetEnumerator()) {
184 | $element = New-XmlElement -DictionaryEntry $nameValue -XmlDocument $resultDocument -AttributesKeysInElementAttributes:$AttributesKeysInElementAttributes
185 |
186 | $element | Foreach-Object {
187 | $resultDocument.AppendChild($_) | Out-Null
188 | }
189 | }
190 |
191 | $resultDocument.OuterXml
192 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PowerShell 7.0 SDK for CloudEvents based on [.NET SDK for CloudEvents](https://github.com/cloudevents/sdk-csharp)
2 |
3 | Official CloudEvents SDK to integrate your application with CloudEvents.
4 |
5 | ## Status
6 |
7 | Supported CloudEvents versions:
8 | - v1.0
9 |
10 | Supported Protocols:
11 | - HTTP
12 |
13 | ## **`CloudEvents.Sdk`** Module
14 | The module contains functions to
15 | - Create CloudEvent objects
16 | - Add data to a CloudEvent object
17 | - Read data from a CloudEvent object
18 | - Convert a CloudEvent object to an HTTP Message
19 | - Convert an HTTP Message to a CloudEvent object
20 |
21 | ## Install **`CloudEvents.Sdk`** Module
22 |
23 | ### Prerequisites
24 | - [PowerShell 7.0](https://github.com/PowerShell/PowerShell/releases/tag/v7.0.4)
25 |
26 | ```powershell
27 | Install-Module CloudEvents.Sdk
28 | Import-Module CloudEvents.Sdk
29 | Get-Command -Module CloudEvents.Sdk
30 |
31 | CommandType Name Version Source
32 | ----------- ---- ------- ------
33 | Function ConvertFrom-HttpMessage 0.3.0 CloudEvents.Sdk
34 | Function ConvertTo-HttpMessage 0.3.0 CloudEvents.Sdk
35 | Function New-CloudEvent 0.3.0 CloudEvents.Sdk
36 | Function Read-CloudEventData 0.3.0 CloudEvents.Sdk
37 | Function Read-CloudEventJsonData 0.3.0 CloudEvents.Sdk
38 | Function Read-CloudEventXmlData 0.3.0 CloudEvents.Sdk
39 | Function Set-CloudEventData 0.3.0 CloudEvents.Sdk
40 | Function Set-CloudEventJsonData 0.3.0 CloudEvents.Sdk
41 | Function Set-CloudEventXmlData 0.3.0 CloudEvents.Sdk
42 | ```
43 |
44 | ## Using **`CloudEvents.Sdk`** Module
45 | ## 1. Event Producer
46 | ### Create a CloudEvent object
47 | ```powershell
48 | $cloudEvent = New-CloudEvent -Type 'com.example.object.deleted.v2' -Source 'mailto:cncf-wg-serverless@lists.cncf.io' -Id (New-Guid).Guid -Time (Get-Date)
49 | ```
50 |
51 | ### Set **JSON Data** to a CloudEvent object
52 | ```powershell
53 | $cloudEvent | Set-CloudEventJsonData -Data @{
54 | 'Foo' = 'Hello'
55 | 'Bar' = 'World'
56 | }
57 |
58 |
59 | DataContentType : application/json
60 | Data : {
61 | "Bar": "World",
62 | "Foo": "Hello"
63 | }
64 | Id : ac9b12d9-ae45-4654-a4d7-42bbf0d9816d
65 | DataSchema :
66 | Source : mailto:cncf-wg-serverless@lists.cncf.io
67 | SpecVersion : V1_0
68 | Subject :
69 | Time : 4/26/2021 9:00:45 AM
70 | Type : com.example.object.deleted.v2
71 | ```
72 |
73 | ### Set **XML Data** to a CloudEvent object
74 | ```powershell
75 | $cloudEvent | Set-CloudEventXmlData -Data @{
76 | 'xml' = @{
77 | 'Foo' = 'Hello'
78 | 'Bar' = 'World'
79 | }
80 | } `
81 | -AttributesKeysInElementAttributes $true
82 |
83 |
84 | DataContentType : application/xml
85 | Data : WorldHello
86 | Id : ac9b12d9-ae45-4654-a4d7-42bbf0d9816d
87 | DataSchema :
88 | Source : mailto:cncf-wg-serverless@lists.cncf.io
89 | SpecVersion : V1_0
90 | Subject :
91 | Time : 4/26/2021 9:00:45 AM
92 | Type : com.example.object.deleted.v2
93 | ```
94 | ### Set Custom Format Data to a CloudEvent object
95 | ```powershell
96 | $cloudEvent | Set-CloudEventData -DataContentType 'application/text' -Data 'Hello World!'
97 |
98 | DataContentType : application/text
99 | Data : Hello World!
100 | Id : b1b748cd-e98d-4f5f-80ea-76dea71a53a5
101 | DataSchema :
102 | Source : mailto:cncf-wg-serverless@lists.cncf.io
103 | SpecVersion : V1_0
104 | Subject :
105 | Time : 4/27/2021 7:00:44 PM
106 | Type : com.example.object.deleted.v2
107 | ```
108 |
109 | ### Convert a CloudEvent object to an HTTP message in **Binary** or **Structured** content mode
110 | ```powershell
111 | # Format structured cloud event HTTP message
112 | $cloudEventStructuredHttpMessage = $cloudEvent | ConvertTo-HttpMessage -ContentMode Structured
113 | ```
114 |
115 | ### Send CloudEvent object to HTTP server
116 | ```powershell
117 | Invoke-WebRequest -Method POST -Uri 'http://my.cloudevents.server/' -Headers $cloudEventStructuredHttpMessage.Headers -Body $cloudEventStructuredHttpMessage.Body
118 | ```
119 |
120 | ## 2. Event Consumer
121 | ### Convert an HTTP message to a CloudEvent object
122 | ```powershell
123 | $cloudEvent = ConvertFrom-HttpMessage -Headers -Body
124 | ```
125 |
126 | ### Read CloudEvent **JSON Data** as a **PowerShell Hashtable**
127 | ```powershell
128 | Read-CloudEventJsonData -CloudEvent $cloudEvent
129 |
130 |
131 | Name Value
132 | ---- -----
133 | Foo Hello
134 | Bar World
135 | ```
136 |
137 | ### Read CloudEvent **XML Data** as a **PowerShell Hashtable**
138 | ```powershell
139 | Read-CloudEventXmlData -CloudEvent $cloudEvent -ConvertMode SkipAttributes
140 |
141 | Name Value
142 | ---- -----
143 | xml {Bar, Foo}
144 | ```
145 |
146 | The `ConvertMode` parameter specifies how the xml should be converted to a PowerShell Hashtable. `SkipAttributes` mode skips reading the xml attributes. There are three different modes of conversion. For more details check the help of the `Read-CloudEventXmlData` cmdlet.
147 |
148 | ### Read CloudEvent Custom Format **Data** as a **byte[]**
149 | ```powershell
150 | Read-CloudEventData -CloudEvent $cloudEvent
151 |
152 | 72
153 | 101
154 | 108
155 | 108
156 | 111
157 | 32
158 | 87
159 | 111
160 | 114
161 | 108
162 | 100
163 | 33
164 | ```
165 |
166 | ## Community
167 |
168 | - There are bi-weekly calls immediately following the
169 | [Serverless/CloudEvents call](https://github.com/cloudevents/spec#meeting-time)
170 | at 9am PT (US Pacific). Which means they will typically start at 10am PT, but
171 | if the other call ends early then the SDK call will start early as well. See
172 | the
173 | [CloudEvents meeting minutes](https://docs.google.com/document/d/1OVF68rpuPK5shIHILK9JOqlZBbfe91RNzQ7u_P7YCDE/edit#)
174 | to determine which week will have the call.
175 | - Slack: #cloudeventssdk channel under
176 | [CNCF's Slack workspace](https://slack.cncf.io/).
177 | - Email: https://lists.cncf.io/g/cncf-cloudevents-sdk
178 | - Contact for additional information: Michael Gasch (`@Michael Gasch`
179 | on slack).
180 |
181 | Each SDK may have its own unique processes, tooling and guidelines, common
182 | governance related material can be found in the
183 | [CloudEvents `community`](https://github.com/cloudevents/spec/tree/master/community)
184 | directory. In particular, in there you will find information concerning
185 | how SDK projects are
186 | [managed](https://github.com/cloudevents/spec/blob/master/community/SDK-GOVERNANCE.md),
187 | [guidelines](https://github.com/cloudevents/spec/blob/master/community/SDK-maintainer-guidelines.md)
188 | for how PR reviews and approval, and our
189 | [Code of Conduct](https://github.com/cloudevents/spec/blob/master/community/GOVERNANCE.md#additional-information)
190 | information.
191 |
192 | If there is a security concern with one of the CloudEvents specifications, or
193 | with one of the project's SDKs, please send an email to
194 | [cncf-cloudevents-security@lists.cncf.io](mailto:cncf-cloudevents-security@lists.cncf.io).
195 |
196 | ## Additional SDK Resources
197 |
198 | - [List of current active maintainers](MAINTAINERS.md)
199 | - [How to contribute to the project](CONTRIBUTING.md)
200 | - [SDK's License](LICENSE)
201 | - [SDK's Release process](RELEASING.md)
202 |
--------------------------------------------------------------------------------
/test/unit/ConvertTo-HttpMessage.Tests.ps1:
--------------------------------------------------------------------------------
1 | # **************************************************************************
2 | # Copyright (c) Cloud Native Foundation.
3 | # SPDX-License-Identifier: Apache-2.0
4 | # **************************************************************************
5 |
6 | Describe "ConvertTo-HttpMessage Function Tests" {
7 | BeforeAll {
8 | $expectedSpecVersion = '1.0'
9 | $expectedStructuredContentType = 'application/cloudevents+json'
10 | }
11 |
12 | Context "Converts CloudEvent in Binary Content Mode" {
13 | It 'Converts a CloudEvent with all properties and json format data' {
14 |
15 | # Arrange
16 | $expectedType = 'test'
17 | $expectedSource = 'urn:test'
18 | $expectedId = 'test-id-1'
19 | $expectedTime = Get-Date -Year 2021 -Month 1 -Day 18 -Hour 12 -Minute 30 -Second 0 -Millisecond 0
20 | $expectedDataContentType = 'application/json'
21 |
22 | $cloudEvent = New-CloudEvent `
23 | -Type $expectedType `
24 | -Source $expectedSource `
25 | -Id $expectedId `
26 | -Time $expectedTime
27 |
28 | $expectedData = @{ 'key1' = 'value2'; 'key3' = 'value4' }
29 | $cloudEvent = Set-CloudEventJsonData `
30 | -CloudEvent $cloudEvent `
31 | -Data $expectedData
32 |
33 |
34 | # Act
35 | $actual = $cloudEvent | ConvertTo-HttpMessage -ContentMode Binary
36 |
37 | # Assert
38 | $actual | Should -Not -Be $null
39 | $actual.Headers | Should -Not -Be $null
40 | $actual.Body | Should -Not -Be $null
41 |
42 | ## Assert Headers
43 | $actual.Headers['Content-Type'] | Should -Be $expectedDataContentType
44 | $actual.Headers['ce-source'] | Should -Be $expectedSource
45 | $actual.Headers['ce-specversion'] | Should -Be $expectedSpecVersion
46 | $actual.Headers['ce-type'] | Should -Be $expectedType
47 | $actual.Headers['ce-time'] | Should -Be ($expectedTime.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ'))
48 | $actual.Headers['ce-id'] | Should -Be $expectedId
49 |
50 | ## Assert Body
51 |
52 | ## Expected Body is
53 | ## {
54 | ## "key1": "value2",
55 | ## "key3": "value4"
56 | ## }
57 |
58 |
59 | $actualDecodedBody = [Text.Encoding]::UTF8.GetString($actual.Body) | ConvertFrom-Json -AsHashtable
60 | $actualDecodedBody.Keys.Count | Should -Be 2
61 | $actualDecodedBody.key1 | Should -Be $expectedData.key1
62 | $actualDecodedBody.key3 | Should -Be $expectedData.key3
63 | }
64 |
65 | It 'Converts a CloudEvent with required properties and application/xml format data' {
66 | # Arrange
67 | $expectedType = 'test'
68 | $expectedId = 'test-id-1'
69 | $expectedSource = 'urn:test'
70 | $expectedDataContentType = 'application/xml'
71 |
72 | $cloudEvent = New-CloudEvent `
73 | -Id $expectedId `
74 | -Type $expectedType `
75 | -Source $expectedSource
76 |
77 | $expectedData = ''
78 | $cloudEvent = Set-CloudEventData `
79 | -CloudEvent $cloudEvent `
80 | -Data $expectedData `
81 | -DataContentType $expectedDataContentType
82 |
83 | # Act
84 | $actual = $cloudEvent | ConvertTo-HttpMessage -ContentMode Binary
85 |
86 | # Assert
87 | $actual | Should -Not -Be $null
88 | $actual.Headers | Should -Not -Be $null
89 | $actual.Body | Should -Not -Be $null
90 |
91 | ## Assert Headers
92 | $actual.Headers['Content-Type'] | Should -Be $expectedDataContentType
93 | $actual.Headers['ce-source'] | Should -Be $expectedSource
94 | $actual.Headers['ce-specversion'] | Should -Be $expectedSpecVersion
95 | $actual.Headers['ce-type'] | Should -Be $expectedType
96 | $actual.Headers['ce-id'] | Should -Be $expectedId
97 |
98 | ## Assert Body
99 |
100 | ## Expected Body is
101 | ##
102 | $actualDecodedBody =[Text.Encoding]::UTF8.GetString($actual.Body)
103 | $actualDecodedBody | Should -Be $expectedData
104 | }
105 | }
106 |
107 | Context "Converts CloudEvent in Structured Content Mode" {
108 | It 'Converts a CloudEvent with all properties and json format data' {
109 | # Arrange
110 | $expectedType = 'test'
111 | $expectedSource = 'urn:test'
112 | $expectedId = 'test-id-1'
113 | $expectedTime = Get-Date -Year 2021 -Month 1 -Day 18 -Hour 12 -Minute 30 -Second 0 -Millisecond 0
114 | $expectedDataContentType = 'application/json'
115 |
116 | $cloudEvent = New-CloudEvent `
117 | -Type $expectedType `
118 | -Source $expectedSource `
119 | -Id $expectedId `
120 | -Time $expectedTime
121 |
122 | $expectedData = @{ 'key1' = 'value2'; 'key3' = 'value4' }
123 | $cloudEvent = Set-CloudEventJsonData `
124 | -CloudEvent $cloudEvent `
125 | -Data $expectedData
126 |
127 |
128 | # Act
129 | $actual = $cloudEvent | ConvertTo-HttpMessage -ContentMode Structured
130 |
131 | # Assert
132 | $actual | Should -Not -Be $null
133 | $actual.Headers | Should -Not -Be $null
134 | $actual.Body | Should -Not -Be $null
135 |
136 | ## Assert Headers
137 | $headerContentTypes = $actual.Headers['Content-Type'].ToString().Split(';')
138 | $headerContentTypes[0].Trim() | Should -Be $expectedStructuredContentType
139 | $headerContentTypes[1].Trim() | Should -Be 'charset=utf-8'
140 |
141 | $actual.Headers['ce-source'] | Should -Be $expectedSource
142 | $actual.Headers['ce-specversion'] | Should -Be $expectedSpecVersion
143 | $actual.Headers['ce-type'] | Should -Be $expectedType
144 | $actual.Headers['ce-time'] | Should -Be ($expectedTime.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ'))
145 | $actual.Headers['ce-id'] | Should -Be $expectedId
146 |
147 | ## Assert Body
148 |
149 | ## Expected Body is
150 | ## {
151 | ## "specversion": "1.0",
152 | ## "type": "test",
153 | ## "source": "urn:test",
154 | ## "id": "test-id-1",
155 | ## "time": "2021-01-18T12:30:00.9785466+02:00",
156 | ## "datacontenttype": "application/json",
157 | ## "data": "{
158 | ## "key1": "value2",
159 | ## "key3": "value4"
160 | ## }"
161 | ## }
162 | $actualDecodedBody = [Text.Encoding]::UTF8.GetString($actual.Body) | ConvertFrom-Json -Depth 1
163 |
164 | $actualDecodedBody.specversion | Should -Be $expectedSpecVersion
165 | $actualDecodedBody.type | Should -Be $expectedType
166 | $actualDecodedBody.source | Should -Be $expectedSource
167 | Get-Date $actualDecodedBody.time | Should -Be $expectedTime
168 | $actualDecodedBody.datacontenttype | Should -Be $expectedDataContentType
169 | $actualDecodedData = $actualDecodedBody.data | ConvertFrom-Json -AsHashtable
170 | $actualDecodedData.Keys.Count | Should -Be 2
171 | $actualDecodedData.key1 | Should -Be $expectedData.key1
172 | $actualDecodedData.key3 | Should -Be $expectedData.key3
173 | }
174 |
175 | It 'Converts a CloudEvent with required properties and application/xml format data' {
176 | # Arrange
177 | $expectedId = ([Guid]::NewGuid().ToString())
178 | $expectedType = 'test'
179 | $expectedSource = 'urn:test'
180 | $expectedDataContentType = 'application/xml'
181 |
182 | $cloudEvent = New-CloudEvent `
183 | -Id $expectedId `
184 | -Type $expectedType `
185 | -Source $expectedSource
186 |
187 | $expectedData = ''
188 | $cloudEvent = Set-CloudEventData `
189 | -CloudEvent $cloudEvent `
190 | -Data $expectedData `
191 | -DataContentType $expectedDataContentType
192 |
193 | # Act
194 | $actual = $cloudEvent | ConvertTo-HttpMessage -ContentMode Structured
195 |
196 | # Assert
197 | $actual | Should -Not -Be $null
198 | $actual.Headers | Should -Not -Be $null
199 | $actual.Body | Should -Not -Be $null
200 |
201 | ## Assert Headers
202 | $headerContentTypes = $actual.Headers['Content-Type'].ToString().Split(';')
203 | $headerContentTypes[0].Trim() | Should -Be $expectedStructuredContentType
204 | $headerContentTypes[1].Trim() | Should -Be 'charset=utf-8'
205 | $actual.Headers['ce-source'] | Should -Be $expectedSource
206 | $actual.Headers['ce-specversion'] | Should -Be $expectedSpecVersion
207 | $actual.Headers['ce-type'] | Should -Be $expectedType
208 | $actual.Headers['ce-id'] | Should -Be $expectedId
209 |
210 | ## Assert Body
211 |
212 | ## Expected Body is
213 | ## {
214 | ## "specversion": "1.0",
215 | ## "type": "test",
216 | ## "source": "urn:test",
217 | ## "datacontenttype": "application/xml",
218 | ## "data": ""
219 | ## }
220 | $actualDecodedBody = [Text.Encoding]::UTF8.GetString($actual.Body) | ConvertFrom-Json -Depth 1
221 | $actualDecodedBody.specversion | Should -Be $expectedSpecVersion
222 | $actualDecodedBody.type | Should -Be $expectedType
223 | $actualDecodedBody.source | Should -Be $expectedSource
224 | $actualDecodedBody.datacontenttype | Should -Be $expectedDataContentType
225 | $actualDecodedBody.data | Should -Be $expectedData
226 | }
227 | }
228 | }
229 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2021-Present The CloudEvents Authors
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/test/integration/HttpIntegration.Tests.ps1:
--------------------------------------------------------------------------------
1 | # **************************************************************************
2 | # Copyright (c) Cloud Native Foundation.
3 | # SPDX-License-Identifier: Apache-2.0
4 | # **************************************************************************
5 |
6 | param(
7 | [Parameter()]
8 | [ValidateScript( { Test-Path $_ })]
9 | [string]
10 | $CloudEventsModulePath)
11 |
12 | Describe "Client-Server Integration Tests" {
13 | Context "Send And Receive CloudEvents over Http" {
14 | BeforeAll {
15 | $testServerUrl = 'http://localhost:52673/'
16 |
17 | $serverProcess = $null
18 |
19 | . (Join-Path $PSScriptRoot 'ProtocolConstants.ps1')
20 |
21 | # Starts CloudEvent Test Server
22 | $usePowerShell = (Get-Process -Id $pid).ProcessName
23 | $serverScript = Join-Path $PSScriptRoot 'HttpServer.ps1'
24 | $serverProcessArguments = "-Command $serverScript -CloudEventsModulePath '$CloudEventsModulePath' -ServerUrl '$testServerUrl'"
25 |
26 | $serverProcess = Start-Process `
27 | -FilePath $usePowerShell `
28 | -ArgumentList $serverProcessArguments `
29 | -PassThru `
30 | -NoNewWindow
31 |
32 | # Wait Server to Start
33 | $serverPingRequest = `
34 | New-CloudEvent `
35 | -Id ([Guid]::NewGuid()) `
36 | -Type $script:ServerPingType `
37 | -Source $script:ClientSource | `
38 | ConvertTo-HttpMessage `
39 | -ContentMode Structured
40 |
41 | $serverReady = $false
42 | $maxRetries = 10
43 | do {
44 | try {
45 | Invoke-WebRequest `
46 | -Uri $testServerUrl `
47 | -Headers $serverPingRequest.Headers `
48 | -Body $serverPingRequest.Body | Out-Null
49 | $serverReady = $true
50 | } catch {
51 | Write-Verbose "Wait CloudEvents HTTP Test Server to start"
52 | Start-Sleep -Seconds 1
53 | $maxRetries--
54 | }
55 | } while (-not $serverReady -and $maxRetries -gt 0)
56 |
57 | if ($maxRetries -eq 0) {
58 | throw "CloudEvents HTTP Test Server failed to start."
59 | }
60 | }
61 |
62 | AfterAll {
63 | # Requests Stop CloudEvent Test Server
64 | $serverStopRequest = `
65 | New-CloudEvent `
66 | -Id ([Guid]::NewGuid()) `
67 | -Type $script:ServerStopType `
68 | -Source $script:ClientSource | `
69 | ConvertTo-HttpMessage `
70 | -ContentMode Structured
71 |
72 | Invoke-WebRequest `
73 | -Uri $testServerUrl `
74 | -Headers $serverStopRequest.Headers `
75 | -Body $serverStopRequest.Body | Out-Null
76 |
77 | if ($serverProcess -ne $null -and `
78 | -not $serverProcess.HasExited) {
79 | $serverProcess | Wait-Process
80 | }
81 | }
82 |
83 | It 'Echo binary content mode cloud events' {
84 | # Arrange
85 | $cloudEvent = New-CloudEvent `
86 | -Type $script:EchoBinaryType `
87 | -Source $script:ClientSource `
88 | -Id 'integration-test-1' `
89 | -Time (Get-Date) | `
90 | Set-CloudEventJsonData -Data @{
91 | 'a1' = 'b'
92 | 'a2' = 'c'
93 | 'a3' = 'd'
94 | }
95 |
96 | # Act
97 |
98 | ## Convert CloudEvent to HTTP Message
99 | $httpRequest = ConvertTo-HttpMessage `
100 | -CloudEvent $cloudEvent `
101 | -ContentMode Binary
102 |
103 | ## Invoke WebRequest with the HTTP Message
104 | $httpResponse = Invoke-WebRequest `
105 | -Uri $testServerUrl `
106 | -Headers $httpRequest.Headers `
107 | -Body $httpRequest.Body
108 |
109 | ## Convert HTTP Response to CloudEvent
110 | $resultCloudEvent = ConvertFrom-HttpMessage `
111 | -Headers $httpResponse.Headers `
112 | -Body $httpResponse.Content
113 |
114 | # Assert
115 |
116 | ## Assert echoed CloudEvent
117 | $resultCloudEvent | Should -Not -Be $null
118 | $resultCloudEvent.Source | Should -Be $script:ServerSource
119 | $resultCloudEvent.Type | Should -Be $script:EchoBinaryType
120 | $resultCloudEvent.Id | Should -Be $cloudEvent.Id
121 | $resultCloudEvent.Time | Should -BeGreaterThan $cloudEvent.Time
122 |
123 | ## Assert Result CloudEvent Data
124 | ## Read Data as Json
125 | $resultData = $resultCloudEvent | Read-CloudEventJsonData
126 | $resultData.a1 | Should -Be 'b'
127 | $resultData.a2 | Should -Be 'c'
128 | $resultData.a3 | Should -Be 'd'
129 | }
130 |
131 | It 'Echo binary content mode cloud events with XML data' {
132 | # Arrange
133 | $cloudEvent = New-CloudEvent `
134 | -Type $script:EchoBinaryType `
135 | -Source $script:ClientSource `
136 | -Id 'integration-test-2' `
137 | -Time (Get-Date) | `
138 | Set-CloudEventXmlData -Data @{
139 | 'a1' = @{
140 | 'a2' = 'c'
141 | 'a3' = 'd'
142 | }
143 | } `
144 | -AttributesKeysInElementAttributes $false
145 |
146 | # Act
147 |
148 | ## Convert CloudEvent to HTTP Message
149 | $httpRequest = ConvertTo-HttpMessage `
150 | -CloudEvent $cloudEvent `
151 | -ContentMode Binary
152 |
153 | ## Invoke WebRequest with the HTTP Message
154 | $httpResponse = Invoke-WebRequest `
155 | -Uri $testServerUrl `
156 | -Headers $httpRequest.Headers `
157 | -Body $httpRequest.Body
158 |
159 | ## Convert HTTP Response to CloudEvent
160 | $resultCloudEvent = ConvertFrom-HttpMessage `
161 | -Headers $httpResponse.Headers `
162 | -Body $httpResponse.Content
163 |
164 | # Assert
165 |
166 | ## Assert echoed CloudEvent
167 | $resultCloudEvent | Should -Not -Be $null
168 | $resultCloudEvent.Source | Should -Be $script:ServerSource
169 | $resultCloudEvent.Type | Should -Be $script:EchoBinaryType
170 | $resultCloudEvent.Id | Should -Be $cloudEvent.Id
171 | $resultCloudEvent.Time | Should -BeGreaterThan $cloudEvent.Time
172 |
173 | ## Assert Result CloudEvent Data
174 | ## Read Data as Xml
175 | $resultData = $resultCloudEvent | Read-CloudEventXmlData -ConvertMode 'SkipAttributes'
176 | $resultData -is [hashtable] | Should -Be $true
177 | $resultData.a1 -is [hashtable] | Should -Be $true
178 | $resultData.a1.a2 | Should -Be 'c'
179 | $resultData.a1.a3 | Should -Be 'd'
180 | }
181 |
182 | It 'Echo structured content mode cloud events' {
183 | # Arrange
184 | $cloudEvent = New-CloudEvent `
185 | -Type $script:EchoStructuredType `
186 | -Source $script:ClientSource `
187 | -Id 'integration-test-3' `
188 | -Time (Get-Date) | `
189 | Set-CloudEventJsonData -Data @{
190 | 'b1' = 'd'
191 | 'b2' = 'e'
192 | 'b3' = 'f'
193 | }
194 |
195 | # Act
196 |
197 | ## Convert CloudEvent to HTTP Message
198 | $httpRequest = ConvertTo-HttpMessage `
199 | -CloudEvent $cloudEvent `
200 | -ContentMode Structured
201 |
202 | ## Invoke WebRequest with the HTTP Message
203 | $httpResponse = Invoke-WebRequest `
204 | -Uri $testServerUrl `
205 | -Headers $httpRequest.Headers `
206 | -Body $httpRequest.Body
207 |
208 | ## Convert HTTP Response to CloudEvent
209 | $resultCloudEvent = ConvertFrom-HttpMessage `
210 | -Headers $httpResponse.Headers `
211 | -Body $httpResponse.Content
212 |
213 | # Assert
214 |
215 | ## Assert echoed CloudEvent
216 | $resultCloudEvent | Should -Not -Be $null
217 | $resultCloudEvent.Source | Should -Be $script:ServerSource
218 | $resultCloudEvent.Type | Should -Be $script:EchoStructuredType
219 | $resultCloudEvent.Id | Should -Be $cloudEvent.Id
220 | $resultCloudEvent.Time | Should -BeGreaterThan $cloudEvent.Time
221 |
222 | ## Assert Result CloudEvent Data
223 | ## Read Data as Json
224 | $resultData = $resultCloudEvent | Read-CloudEventJsonData
225 | $resultData.b1 | Should -Be 'd'
226 | $resultData.b2 | Should -Be 'e'
227 | $resultData.b3 | Should -Be 'f'
228 | }
229 |
230 | It 'Echo structured content mode cloud events with XML data' {
231 | # Arrange
232 | $cloudEvent = New-CloudEvent `
233 | -Type $script:EchoStructuredType `
234 | -Source $script:ClientSource `
235 | -Id 'integration-test-4' `
236 | -Time (Get-Date) | `
237 | Set-CloudEventXmlData -Data @{
238 | 'b1' = @{
239 | 'b2' = 'e'
240 | 'b3' = 'f'
241 | }
242 | } `
243 | -AttributesKeysInElementAttributes $false
244 |
245 | # Act
246 |
247 | ## Convert CloudEvent to HTTP Message
248 | $httpRequest = ConvertTo-HttpMessage `
249 | -CloudEvent $cloudEvent `
250 | -ContentMode Structured
251 |
252 | ## Invoke WebRequest with the HTTP Message
253 | $httpResponse = Invoke-WebRequest `
254 | -Uri $testServerUrl `
255 | -Headers $httpRequest.Headers `
256 | -Body $httpRequest.Body
257 |
258 | ## Convert HTTP Response to CloudEvent
259 | $resultCloudEvent = ConvertFrom-HttpMessage `
260 | -Headers $httpResponse.Headers `
261 | -Body $httpResponse.Content
262 |
263 | # Assert
264 |
265 | ## Assert echoed CloudEvent
266 | $resultCloudEvent | Should -Not -Be $null
267 | $resultCloudEvent.Source | Should -Be $script:ServerSource
268 | $resultCloudEvent.Type | Should -Be $script:EchoStructuredType
269 | $resultCloudEvent.Id | Should -Be $cloudEvent.Id
270 | $resultCloudEvent.Time | Should -BeGreaterThan $cloudEvent.Time
271 |
272 | ## Assert Result CloudEvent Data
273 | ## Read Data as Xml
274 | $resultData = $resultCloudEvent | Read-CloudEventXmlData -ConvertMode 'SkipAttributes'
275 | $resultData -is [hashtable] | Should -Be $true
276 | $resultData.b1 -is [hashtable] | Should -Be $true
277 | $resultData.b1.b2 | Should -Be 'e'
278 | $resultData.b1.b3 | Should -Be 'f'
279 | }
280 |
281 | It 'Send cloud event expecting no result' {
282 | # Arrange
283 | $cloudEvent = New-CloudEvent `
284 | -Type 'no-content' `
285 | -Source $script:ClientSource `
286 | -Id 'integration-test-5' `
287 | -Time (Get-Date) | `
288 | Set-CloudEventData `
289 | -Data 'This is text data' `
290 | -DataContentType 'application/text'
291 |
292 | # Act
293 |
294 | ## Convert CloudEvent to HTTP Message
295 | $httpRequest = ConvertTo-HttpMessage `
296 | -CloudEvent $cloudEvent `
297 | -ContentMode Structured
298 |
299 | ## Invoke WebRequest with the HTTP Message
300 | $httpResponse = Invoke-WebRequest `
301 | -Uri $testServerUrl `
302 | -Headers $httpRequest.Headers `
303 | -Body $httpRequest.Body
304 |
305 | # Assert
306 | $httpResponse.StatusCode | Should -Be ([int]([System.Net.HttpStatusCode]::NoContent))
307 | }
308 | }
309 | }
--------------------------------------------------------------------------------
/test/unit/ConvertFrom-HttpMessage.Tests.ps1:
--------------------------------------------------------------------------------
1 | # **************************************************************************
2 | # Copyright (c) Cloud Native Foundation.
3 | # SPDX-License-Identifier: Apache-2.0
4 | # **************************************************************************
5 |
6 | Describe "ConvertFrom-HttpMessage Function Tests" {
7 | BeforeEach {
8 | $script:expectedSpecVersion = '1.0'
9 | $script:expectedStructuredContentType = 'application/cloudevents+json'
10 | }
11 |
12 | Context "Converts CloudEvent in Binary Content Mode" {
13 | It 'Converts a CloudEvent with all properties and json format data' {
14 |
15 | # Arrange
16 | $expectedType = 'test'
17 | $expectedSource = 'urn:test'
18 | $expectedId = 'test-id-1'
19 | $expectedTime = Get-Date `
20 | -Year 2021 `
21 | -Month 1 `
22 | -Day 18 `
23 | -Hour 12 `
24 | -Minute 30 `
25 | -Second 23 `
26 | -MilliSecond 134
27 | $expectedDataContentType = 'application/json'
28 |
29 | $headers = @{
30 | 'Content-Type' = @($expectedDataContentType, 'charset=utf-8')
31 | 'ce-specversion' = $expectedSpecVersion
32 | 'ce-type' = $expectedType
33 | 'ce-time' = $expectedTime.ToString('yyyy-MM-ddTHH:mm:ss.fffZ')
34 | 'ce-id' = $expectedId
35 | 'ce-source' = $expectedSource
36 | }
37 |
38 | $body =[Text.Encoding]::UTF8.GetBytes('{
39 | "l10": {
40 | "l2": {
41 | "l3": "wow"
42 | }
43 | },
44 | "l11": "mhm"
45 | }')
46 |
47 | # Act
48 | $actual = ConvertFrom-HttpMessage `
49 | -Headers $headers `
50 | -Body $body
51 |
52 | # Assert
53 | $actual | Should -Not -Be $null
54 | $actual.Type | Should -Be $expectedType
55 | $actual.Source | Should -Be $expectedSource
56 | $actual.Id | Should -Be $expectedId
57 | $actual.Time.Year | Should -Be $expectedTime.Year
58 | $actual.Time.Month | Should -Be $expectedTime.Month
59 | $actual.Time.Day | Should -Be $expectedTime.Day
60 | $actual.Time.Hours | Should -Be $expectedTime.Hours
61 | $actual.Time.Minutes | Should -Be $expectedTime.Minutes
62 | $actual.Time.Seconds | Should -Be $expectedTime.Seconds
63 | $actual.Time.MilliSeconds | Should -Be $expectedTime.MilliSeconds
64 | $actual.DataContentType | Should -Be $expectedDataContentType
65 |
66 | ## Assert Data
67 | $actualHTData = $actual | Read-CloudEventJsonData -Depth 3
68 |
69 | $actualHTData | Should -Not -Be $null
70 | $actualHTData.l10.l2.l3 | Should -Be 'wow'
71 | $actualHTData.l11 | Should -Be 'mhm'
72 |
73 | }
74 |
75 | It 'Converts a CloudEvent with required properties and application/xml format data' {
76 | # Arrange
77 | $expectedType = 'test'
78 | $expectedSource = 'urn:test'
79 | $expectedDataContentType = 'application/xml'
80 | $expectedId = 'test-id-2'
81 | $expectedData = [Text.Encoding]::UTF8.GetBytes('')
82 |
83 | $headers = @{
84 | 'Content-Type' = @($expectedDataContentType, 'charset=utf-8')
85 | 'ce-specversion' = $expectedSpecVersion
86 | 'ce-type' = $expectedType
87 | 'ce-source' = $expectedSource
88 | 'ce-id' = $expectedId
89 | }
90 |
91 | $body = $expectedData
92 |
93 | # Act
94 | $actual = ConvertFrom-HttpMessage `
95 | -Headers $headers `
96 | -Body $body
97 |
98 | # Assert
99 | $actual | Should -Not -Be $null
100 | $actual.Id | Should -Be $expectedId
101 | $actual.Type | Should -Be $expectedType
102 | $actual.Source | Should -Be $expectedSource
103 | $actual.DataContentType | Should -Be $expectedDataContentType
104 | $actual.Data | Should -Be $expectedData
105 |
106 | ## Assert Data obtained by Read-CloudEventData
107 | $actualData = $actual | Read-CloudEventData
108 |
109 | $actualData | Should -Be $expectedData
110 | }
111 |
112 | It 'Returns null when ce-id is not specified' {
113 | # Arrange
114 | $expectedType = 'test'
115 | $expectedSource = 'urn:test'
116 | $expectedDataContentType = 'application/xml'
117 | $expectedData = [Text.Encoding]::UTF8.GetBytes('')
118 |
119 | $headers = @{
120 | 'Content-Type' = @($expectedDataContentType, 'charset=utf-8')
121 | 'ce-specversion' = $expectedSpecVersion
122 | 'ce-type' = $expectedType
123 | 'ce-source' = $expectedSource
124 | }
125 |
126 | $body = $expectedData
127 |
128 | # Act
129 | $actual = ConvertFrom-HttpMessage `
130 | -Headers $headers `
131 | -Body $body
132 |
133 | # Assert
134 | $actual | Should -Be $null
135 | }
136 |
137 | It 'Returns null when ce-source is not specified' {
138 | # Arrange
139 | $expectedType = 'test'
140 | $expectedDataContentType = 'application/xml'
141 | $expectedId = 'test-id-3'
142 | $expectedData = [Text.Encoding]::UTF8.GetBytes('')
143 |
144 | $headers = @{
145 | 'Content-Type' = @($expectedDataContentType, 'charset=utf-8')
146 | 'ce-specversion' = $expectedSpecVersion
147 | 'ce-type' = $expectedType
148 | 'ce-id' = $expectedId
149 | }
150 |
151 | $body = $expectedData
152 |
153 | # Act
154 | $actual = ConvertFrom-HttpMessage `
155 | -Headers $headers `
156 | -Body $body
157 |
158 | # Assert
159 | $actual | Should -Be $null
160 | }
161 |
162 | It 'Returns null when ce-type is not specified' {
163 | # Arrange
164 | $expectedSource = 'urn:test'
165 | $expectedDataContentType = 'application/xml'
166 | $expectedId = 'test-id-4'
167 | $expectedData = [Text.Encoding]::UTF8.GetBytes('')
168 |
169 | $headers = @{
170 | 'Content-Type' = @($expectedDataContentType, 'charset=utf-8')
171 | 'ce-specversion' = $expectedSpecVersion
172 | 'ce-source' = $expectedSource
173 | 'ce-id' = $expectedId
174 | }
175 |
176 | $body = $expectedData
177 |
178 | # Act
179 | $actual = ConvertFrom-HttpMessage `
180 | -Headers $headers `
181 | -Body $body
182 |
183 | # Assert
184 | $actual | Should -Be $null
185 | }
186 | }
187 |
188 | Context "Converts CloudEvent in Structured Content Mode" {
189 | It 'Converts a CloudEvent with all properties and json format data' {
190 | # Arrange
191 | $expectedType = 'test'
192 | $expectedSource = 'urn:test'
193 | $expectedId = 'test-id-1'
194 | $expectedTime = Get-Date `
195 | -Year 2021 `
196 | -Month 1 `
197 | -Day 18 `
198 | -Hour 12 `
199 | -Minute 30 `
200 | -Second 23 `
201 | -MilliSecond 134
202 | $expectedDataContentType = 'application/json'
203 |
204 | $headers = @{
205 | 'Content-Type' = $expectedStructuredContentType
206 | }
207 |
208 | $eventData = @{
209 | 'l10' = @{
210 | 'l2' = @{
211 | 'l3' = 'wow'
212 | }
213 | }
214 | 'l11' = 'mhm'
215 | }
216 |
217 | $structuredJsonBody = @{
218 | 'specversion' = $expectedSpecVersion
219 | 'type' = $expectedType
220 | 'time' = $expectedTime.ToString('yyyy-MM-ddTHH:mm:ss.fffZ')
221 | 'id' = $expectedId
222 | 'source' = $expectedSource
223 | 'datacontenttype' = $expectedDataContentType
224 | }
225 |
226 | $structuredJsonBody['data_base64'] = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes(($eventData | ConvertTo-Json -Depth 3)))
227 |
228 | $body = [Text.Encoding]::UTF8.GetBytes(($structuredJsonBody | ConvertTo-Json))
229 |
230 | # Act
231 | $actual = ConvertFrom-HttpMessage `
232 | -Headers $headers `
233 | -Body $body
234 |
235 | # Assert
236 | $actual | Should -Not -Be $null
237 | $actual.Type | Should -Be $expectedType
238 | $actual.Source | Should -Be $expectedSource
239 | $actual.Id | Should -Be $expectedId
240 | $actual.Time.Year | Should -Be $expectedTime.Year
241 | $actual.Time.Month | Should -Be $expectedTime.Month
242 | $actual.Time.Day | Should -Be $expectedTime.Day
243 | $actual.Time.Hours | Should -Be $expectedTime.Hours
244 | $actual.Time.Minutes | Should -Be $expectedTime.Minutes
245 | $actual.Time.Seconds | Should -Be $expectedTime.Seconds
246 | $actual.Time.MilliSeconds | Should -Be $expectedTime.MilliSeconds
247 | $actual.DataContentType | Should -Be $expectedDataContentType
248 |
249 | ## Assert Data
250 | $actualHTData = $actual | Read-CloudEventJsonData -Depth 3
251 |
252 | $actualHTData | Should -Not -Be $null
253 | $actualHTData -is [hashtable] | Should -Be $true
254 | $actualHTData.l10.l2.l3 | Should -Be 'wow'
255 | $actualHTData.l11 | Should -Be 'mhm'
256 | }
257 |
258 | It 'Converts a CloudEvent with required properties and application/xml format data' {
259 | # Arrange
260 | $expectedType = 'test'
261 | $expectedSource = 'urn:test'
262 | $expectedDataContentType = 'application/xml'
263 | $expectedData = [Text.Encoding]::UTF8.GetBytes('')
264 |
265 | $headers = @{
266 | 'Content-Type' = $expectedStructuredContentType
267 | }
268 |
269 | $structuredJsonBody = @{
270 | 'specversion' = $expectedSpecVersion
271 | 'type' = $expectedType
272 | 'source' = $expectedSource
273 | 'datacontenttype' = $expectedDataContentType
274 | 'data' = $expectedData
275 | }
276 |
277 | $body = [Text.Encoding]::UTF8.GetBytes(($structuredJsonBody | ConvertTo-Json))
278 |
279 | # Act
280 | $actual = ConvertFrom-HttpMessage `
281 | -Headers $headers `
282 | -Body $body
283 |
284 | # Assert
285 | $actual | Should -Not -Be $null
286 | $actual.Type | Should -Be $expectedType
287 | $actual.Source | Should -Be $expectedSource
288 | $actual.DataContentType | Should -Be $expectedDataContentType
289 | $actual.Data | Should -Be $expectedData
290 |
291 | ## Assert Data obtained by Read-CloudEventData
292 | $actualData = $actual | Read-CloudEventData
293 |
294 | $actualData | Should -Be $expectedData
295 | }
296 |
297 | It 'Throws error when CloudEvent encoding is not non-batching JSON' {
298 | # Arrange
299 | $unsupportedContentFormat = 'application/cloudevents-batch+json'
300 |
301 | $expectedType = 'test'
302 | $expectedSource = 'urn:test'
303 | $expectedDataContentType = 'application/xml'
304 | $expectedData = [Text.Encoding]::UTF8.GetBytes('')
305 |
306 | $headers = @{
307 | 'Content-Type' = $unsupportedContentFormat
308 | }
309 |
310 | $structuredJsonBody = @{
311 | 'specversion' = $expectedSpecVersion
312 | 'type' = $expectedType
313 | 'source' = $expectedSource
314 | 'datacontenttype' = $expectedDataContentType
315 | 'data' = $expectedData
316 | }
317 |
318 | $body = [Text.Encoding]::UTF8.GetBytes(($structuredJsonBody | ConvertTo-Json))
319 |
320 | # Act & Assert
321 | {ConvertFrom-HttpMessage `
322 | -Headers $headers `
323 | -Body $body } | `
324 | Should -Throw "*Unsupported CloudEvents encoding*"
325 | }
326 |
327 | It 'Returns null when no Content-Type header' {
328 | # Arrange
329 |
330 | $expectedType = 'test'
331 | $expectedSource = 'urn:test'
332 | $expectedDataContentType = 'application/xml'
333 | $expectedData = [Text.Encoding]::UTF8.GetBytes('')
334 | $structuredJsonBody = @{
335 | 'specversion' = $expectedSpecVersion
336 | 'type' = $expectedType
337 | 'source' = $expectedSource
338 | 'datacontenttype' = $expectedDataContentType
339 | 'data' = $expectedData
340 | }
341 |
342 | $body = [Text.Encoding]::UTF8.GetBytes(($structuredJsonBody | ConvertTo-Json))
343 |
344 | # Act & Assert
345 | $ce = ConvertFrom-HttpMessage `
346 | -Headers @{} `
347 | -Body $body
348 | $ce | Should -Be $null
349 | }
350 | }
351 | }
352 |
--------------------------------------------------------------------------------
/src/CloudEventsPowerShell/dataserialization/xml.Tests.ps1:
--------------------------------------------------------------------------------
1 | # **************************************************************************
2 | # Copyright (c) Cloud Native Foundation.
3 | # SPDX-License-Identifier: Apache-2.0
4 | # **************************************************************************
5 |
6 | BeforeAll {
7 | . $PSCommandPath.Replace('.Tests.ps1', '.ps1')
8 | }
9 |
10 | Describe "CloudEvent Xml Serializers Unit Tests" {
11 | Context "ConvertFrom-CEDataXml" {
12 | It "Should convert single element XML text to a hashtable" {
13 | # Arrange
14 | $inputXml = "value"
15 | $expected = @{'key' = 'value'}
16 |
17 | # Act
18 | $actual = ConvertFrom-CEDataXml -InputString $inputXml -ConvertMode SkipAttributes
19 |
20 | # Assert
21 | $actual | Should -Not -Be $null
22 | $actual -is [hashtable] | Should -Be $true
23 | $actual.Keys.Count | Should -Be $expected.Keys.Count
24 | $actual.key | Should -Be $expected.key
25 | }
26 |
27 | It "Should convert XML with array of nodes to a hashtable" {
28 | # Arrange
29 | $inputXml = "value1value2value3"
30 | $expected = @{'keys' = @{'key' = @('value1', 'value2', 'value3')}}
31 |
32 | # Act
33 | $actual = ConvertFrom-CEDataXml -InputString $inputXml -ConvertMode SkipAttributes
34 |
35 | # Assert
36 | $actual | Should -Not -Be $null
37 | $actual -is [hashtable] | Should -Be $true
38 | $actual.keys.key[0] | Should -Be $expected.keys.key[0]
39 | $actual.keys.key[1] | Should -Be $expected.keys.key[1]
40 | $actual.keys.key[2] | Should -Be $expected.keys.key[2]
41 | }
42 |
43 | It "Should convert XML with array of nodes to a hashtable and ConvertMode 'AlwaysAttrValue'" {
44 | # Arrange
45 | $inputXml = "value1value2value3"
46 | $expected = @{
47 | 'keys' = @{
48 | 'Attributes' = $null
49 | 'Value' = @{
50 | 'key' = @{
51 | 'Attributes' = $null
52 | 'Value' = @(
53 | @{
54 | 'Attributes' = $null
55 | 'Value' = 'value1'
56 | },
57 | @{
58 | 'Attributes' = $null
59 | 'Value' = 'value2'
60 | },
61 | @{
62 | 'Attributes' = $null
63 | 'Value' = 'value3'
64 | }
65 | )
66 | }
67 | }
68 | }
69 |
70 | }
71 |
72 | # Act
73 | $actual = ConvertFrom-CEDataXml -InputString $inputXml -ConvertMode AlwaysAttrValue
74 |
75 | # Assert
76 | $actual | Should -Not -Be $null
77 | $actual -is [hashtable] | Should -Be $true
78 | $actual.keys.Attributes | Should -Be $null
79 | $actual.keys.Value.key.Value[0].Value | Should -Be $expected.keys.Value.key.Value[0].Value
80 | $actual.keys.Value.key.Attributes | Should -Be $null
81 | $actual.keys.Value.key.Value[0].Attributes | Should -Be $null
82 | $actual.keys.Value.key.Value[1].Value | Should -Be $expected.keys.Value.key.Value[1].Value
83 | $actual.keys.Value.key.Value[2].Value | Should -Be $expected.keys.Value.key.Value[2].Value
84 | }
85 |
86 | It "Should convert single element XML with ConvertMode = 'AlwaysAttrValue'" {
87 | # Arrange
88 | $inputXml = "value-1value-2"
89 | $expected = @{
90 | 'root' = @{
91 | 'Attributes' = $null
92 | 'Value' = @{
93 | 'key1' = @{
94 | 'Attributes' = @{
95 | 'att1' = 'true'
96 | }
97 | 'Value' = 'value-1'
98 | }
99 | 'key2' = @{
100 | 'Attributes' = $null
101 | 'Value' = 'value-2'
102 | }
103 | }
104 | }
105 | }
106 |
107 | # Act
108 | $actual = ConvertFrom-CEDataXml -InputString $inputXml -ConvertMode AlwaysAttrValue
109 |
110 | # Assert
111 | $actual | Should -Not -Be $null
112 | $actual -is [hashtable] | Should -Be $true
113 | $actual.root.Value.key1.Attributes.att1 | Should -Be $expected.root.Value.key1.Attributes.att1
114 | $actual.root.Value.key1.Value | Should -Be $expected.root.Value.key1.Value
115 | $actual.root.Value.key2.Attributes | Should -Be $expected.root.Value.key2.Attributes
116 | $actual.root.Value.key2.Value | Should -Be $expected.root.Value.key2.Value
117 | }
118 |
119 | It "Should convert elements with attributes with ConvertMode = 'AttrValueWhenAttributes'" {
120 | # Arrange
121 | $inputXml = "valuevalue-1"
122 | $expected = @{
123 | 'root' = @{
124 | 'key' = 'value'
125 | 'withattr' = @{
126 | 'Attributes' = @{
127 | 'att1' = 'true'
128 | 'att2' = 'false'
129 | }
130 | 'Value' = 'value-1'
131 | }
132 | }
133 | }
134 |
135 | # Act
136 | $actual = ConvertFrom-CEDataXml -InputString $inputXml -ConvertMode AttrValueWhenAttributes
137 |
138 | # Assert
139 | $actual | Should -Not -Be $null
140 | $actual -is [hashtable] | Should -Be $true
141 | $actual.root.key | Should -Be $expected.root.key
142 | $actual.root.withattr.Attributes.att1 | Should -Be $expected.root.withattr.Attributes.att1
143 | $actual.root.withattr.Attributes.att2 | Should -Be $expected.root.withattr.Attributes.att2
144 | $actual.root.withattr.Value | Should -Be $expected.root.withattr.Value
145 | }
146 |
147 | It "Should convert XML with nested elements to a hashtable skipping attribute properties" {
148 | # Arrange
149 | $inputXml = '857085702021-02-04T08:51:53.723999ZdcuivcqaDCdatacenter-2clsdomain-c710.161.140.163host-21User dcui@127.0.0.1 logged in as VMware-client/6.5.0127.0.0.1VMware-client/6.4.0en52b910cf-661f-f72d-9f86-fb82113404b7'
150 | $expected = @{
151 | 'UserLoginSessionEvent' = @{
152 | 'key' = '8570'
153 | 'createdTime' = '2021-02-04T08:51:53.723999Z'
154 | 'userName' = 'dcui'
155 | 'datacenter' = @{
156 | 'name' = 'vcqaDC'
157 | 'datacenter' = 'datacenter-2'
158 | }
159 | 'computeResource' = @{
160 | 'name' = 'cls'
161 | 'computeResource' = 'domain-c7'
162 | }
163 | 'host' = @{
164 | 'name' = '10.161.140.163'
165 | 'host' = 'host-21'
166 | }
167 | 'fullFormattedMessage' = 'User dcui@127.0.0.1 logged in as VMware-client/6.5.0'
168 | 'ipAddress' = '127.0.0.1'
169 | 'userAgent' = 'VMware-client/6.4.0'
170 | 'locale' = 'en'
171 | 'sessionId' = '52b910cf-661f-f72d-9f86-fb82113404b7'
172 | }
173 | }
174 |
175 | # Act
176 | $actual = ConvertFrom-CEDataXml -InputString $inputXml -ConvertMode SkipAttributes
177 |
178 | # Assert
179 | $actual | Should -Not -Be $null
180 | $actual -is [hashtable] | Should -Be $true
181 | $actual.UserLoginSessionEvent -is [hashtable] | Should -Be $true
182 | $actual.UserLoginSessionEvent.key | Should -Be $expected.UserLoginSessionEvent.key
183 | $actual.UserLoginSessionEvent.createdTime | Should -Be $expected.UserLoginSessionEvent.createdTime
184 | $actual.UserLoginSessionEvent.userName | Should -Be $expected.UserLoginSessionEvent.userName
185 | $actual.UserLoginSessionEvent.datacenter -is [hashtable] | Should -Be $true
186 | $actual.UserLoginSessionEvent.datacenter.name | Should -Be $expected.UserLoginSessionEvent.datacenter.name
187 | $actual.UserLoginSessionEvent.datacenter.datacenter | Should -Be $expected.UserLoginSessionEvent.datacenter.datacenter
188 | $actual.UserLoginSessionEvent.host -is [hashtable] | Should -Be $true
189 | $actual.UserLoginSessionEvent.host.name | Should -Be $expected.UserLoginSessionEvent.host.name
190 | $actual.UserLoginSessionEvent.host.host | Should -Be $expected.UserLoginSessionEvent.host.host
191 | $actual.UserLoginSessionEvent.fullFormattedMessage | Should -Be $expected.UserLoginSessionEvent.fullFormattedMessage
192 | }
193 |
194 | It "Should convert XML with nested elements to a hashtable with ConvertMode = 'AttrValueWhenAttributes'" {
195 | # Arrange
196 | $inputXml = '857085702021-02-04T08:51:53.723999ZdcuivcqaDCdatacenter-2clsdomain-c710.161.140.163host-21User dcui@127.0.0.1 logged in as VMware-client/6.5.0127.0.0.1VMware-client/6.4.0en52b910cf-661f-f72d-9f86-fb82113404b7'
197 | $expected = @{
198 | 'UserLoginSessionEvent' = @{
199 | 'key' = '8570'
200 | 'createdTime' = '2021-02-04T08:51:53.723999Z'
201 | 'userName' = 'dcui'
202 | 'datacenter' = @{
203 | 'name' = 'vcqaDC'
204 | 'datacenter' = @{
205 | 'Attributes' = @{
206 | 'type' = 'Datacenter'
207 | }
208 | 'Value' = 'datacenter-2'
209 | }
210 | }
211 | 'computeResource' = @{
212 | 'name' = 'cls'
213 | 'computeResource' = @{
214 | 'Attributes' = @{
215 | 'type' = 'ClusterComputeResource'
216 | }
217 | 'Value' = 'domain-c7'
218 | }
219 | }
220 | 'host' = @{
221 | 'name' = '10.161.140.163'
222 | 'host' = @{
223 | 'Attributes' = @{
224 | 'type' = 'HostSystem'
225 | }
226 | 'Value' = 'host-21'
227 | }
228 | }
229 | 'fullFormattedMessage' = 'User dcui@127.0.0.1 logged in as VMware-client/6.5.0'
230 | 'ipAddress' = '127.0.0.1'
231 | 'userAgent' = 'VMware-client/6.4.0'
232 | 'locale' = 'en'
233 | 'sessionId' = '52b910cf-661f-f72d-9f86-fb82113404b7'
234 | }
235 | }
236 |
237 | # Act
238 | $actual = ConvertFrom-CEDataXml -InputString $inputXml -ConvertMode AttrValueWhenAttributes
239 |
240 | # Assert
241 | $actual | Should -Not -Be $null
242 | $actual -is [hashtable] | Should -Be $true
243 | $actual.UserLoginSessionEvent -is [hashtable] | Should -Be $true
244 | $actual.UserLoginSessionEvent.key | Should -Be $expected.UserLoginSessionEvent.key
245 | $actual.UserLoginSessionEvent.createdTime | Should -Be $expected.UserLoginSessionEvent.createdTime
246 | $actual.UserLoginSessionEvent.userName | Should -Be $expected.UserLoginSessionEvent.userName
247 | $actual.UserLoginSessionEvent.datacenter -is [hashtable] | Should -Be $true
248 | $actual.UserLoginSessionEvent.datacenter.name | Should -Be $expected.UserLoginSessionEvent.datacenter.name
249 | $actual.UserLoginSessionEvent.datacenter.datacenter.Attributes.type | Should -Be $expected.UserLoginSessionEvent.datacenter.datacenter.Attributes.type
250 | $actual.UserLoginSessionEvent.datacenter.datacenter.Value | Should -Be $expected.UserLoginSessionEvent.datacenter.datacenter.Value
251 | $actual.UserLoginSessionEvent.computeResource.computeResource.Attributes.type | Should -Be $expected.UserLoginSessionEvent.computeResource.computeResource.Attributes.type
252 | $actual.UserLoginSessionEvent.computeResource.computeResource.Value | Should -Be $expected.UserLoginSessionEvent.computeResource.computeResource.Value
253 | $actual.UserLoginSessionEvent.host.host.Attributes.type | Should -Be $expected.UserLoginSessionEvent.host.host.Attributes.type
254 | $actual.UserLoginSessionEvent.host.host.Value | Should -Be $expected.UserLoginSessionEvent.host.host.Value
255 | }
256 | }
257 |
258 | Context "ConvertTo-CEDataXml" {
259 | It "Should convert single hashtable to XML" {
260 | # Arrange
261 | $inputHashtable = @{'key' = 'value'}
262 | $expected = "value"
263 |
264 | # Act
265 | $actual = ConvertTo-CEDataXml -InputObject $inputHashtable -AttributesKeysInElementAttributes $false
266 |
267 | # Assert
268 | $actual | Should -Be $expected
269 | }
270 |
271 | It "Should convert hashtable with array values to XML " {
272 | # Arrange
273 | $inputHashtable = @{'keys' = @{'key' = @('value1', 'value2', 'value3')}}
274 | $expected = "value1value2value3"
275 |
276 | # Act
277 | $actual = ConvertTo-CEDataXml -InputObject $inputHashtable -AttributesKeysInElementAttributes $false
278 |
279 | # Assert
280 | $actual | Should -Be $expected
281 | }
282 |
283 | It "Should convert hashtable with hashtable values to XML " {
284 | # Arrange
285 | $inputHashtable = @{
286 | 'UserLoginSessionEvent' = @{
287 | 'datacenter' = @{
288 | 'datacenter' = 'datacenter-2'
289 | }
290 | }
291 | }
292 | $expected = 'datacenter-2'
293 |
294 | # Act
295 | $actual = ConvertTo-CEDataXml -InputObject $inputHashtable -AttributesKeysInElementAttributes $false
296 |
297 | # Assert
298 | $actual | Should -Be $expected
299 | }
300 |
301 | It "Should convert hashtable with Attributes keys to XML elements without attributes" {
302 | # Arrange
303 | $inputHashtable = @{
304 | 'UserLoginSessionEvent' = @{
305 | 'computeResource' = @{
306 | 'name' = 'cls'
307 | 'computeResource' = @{
308 | 'Attributes' = @{
309 | 'type' = 'ClusterComputeResource'
310 | }
311 | 'Value' = 'domain-c7'
312 | }
313 | }
314 | }
315 | }
316 | $expected = 'ClusterComputeResourcedomain-c7cls'
317 |
318 | # Act
319 | $actual = ConvertTo-CEDataXml -InputObject $inputHashtable -AttributesKeysInElementAttributes $false
320 |
321 | # Assert
322 | ## We can not expected the xml elements to be ordered as in the expected string,
323 | ## so test Xml ignoring the elements order
324 | $actual.Contains('ClusterComputeResource') | Should -Be $true
325 | $actual.Contains('domain-c7') | Should -Be $true
326 | $actual.Contains('cls') | Should -Be $true
327 | $actual.IndexOf('') | Should -BeGreaterThan $actual.IndexOf('')
328 | $actual.IndexOf('') | Should -BeLessThan $actual.IndexOf('')
329 | }
330 |
331 |
332 | It "Should convert hashtable with Attributes keys to XML elements with attributes" {
333 | # Arrange
334 | $inputHashtable = @{
335 | 'UserLoginSessionEvent' = @{
336 | 'computeResource' = @{
337 | 'name' = 'cls'
338 | 'computeResource' = @{
339 | 'Attributes' = @{
340 | 'type' = 'ClusterComputeResource'
341 | }
342 | 'Value' = 'domain-c7'
343 | }
344 | }
345 | }
346 | }
347 | $expected = 'domain-c7cls'
348 |
349 | # Act
350 | $actual = ConvertTo-CEDataXml -InputObject $inputHashtable -AttributesKeysInElementAttributes $true
351 |
352 | # Assert
353 | ## We can not expected the xml elements to be ordered as in the expected string,
354 | ## so test Xml ignoring the elements order
355 | $actual.StartsWith('') | Should -Be $true
356 | $actual.Contains('domain-c7') | Should -Be $true
357 | $actual.Contains('cls') | Should -Be $true
358 | }
359 |
360 | It "Should throw when input hashtable has more than one root" {
361 | # Arrange
362 | $inputHashtable = @{'key1' = 'val1'; 'key2' = 'val2'}
363 |
364 | # Act & Assert
365 | { ConvertTo-CEDataXml -InputObject $inputHashtable -AttributesKeysInElementAttributes $false } | Should -Throw 'Input Hashtable must have single root key'
366 | }
367 | }
368 | }
--------------------------------------------------------------------------------
/src/CloudEventsPowerShell/CloudEvents.Sdk.psm1:
--------------------------------------------------------------------------------
1 | # **************************************************************************
2 | # Copyright (c) Cloud Native Foundation.
3 | # SPDX-License-Identifier: Apache-2.0
4 | # **************************************************************************
5 |
6 | $xmlDataSerilizationLibPath = Join-Path (Join-Path $PSScriptRoot 'dataserialization') 'xml.ps1'
7 | . $xmlDataSerilizationLibPath
8 |
9 | function New-CloudEvent {
10 | <#
11 | .SYNOPSIS
12 | This function creates a new cloud event.
13 |
14 |
15 | .DESCRIPTION
16 | This function creates a new cloud event object with the provided parameters.
17 | The result cloud event object has no data. Use Add-CloudEvent* functions to
18 | add data to the cloud event object.
19 |
20 | .PARAMETER Type
21 | Specifies the 'type' attribute of the cloud event.
22 |
23 | .PARAMETER Source
24 | Specifies the 'source' attribute of the cloud event.
25 |
26 | .PARAMETER Id
27 | Specifies the 'id' attribute of the cloud event.
28 |
29 | .PARAMETER Time
30 | Specifies the 'time' attribute of the cloud event.
31 |
32 | .EXAMPLE
33 | New-CloudEvent -Type 'com.example.object.deleted.v2' -Source 'mailto:cncf-wg-serverless@lists.cncf.io' -Id '6e8bc430-9c3a-11d9-9669-0800200c9a66' -Time (Get-Date)
34 |
35 | Creates a cloud event with Type, Source, Id, and Time
36 | #>
37 |
38 | [CmdletBinding()]
39 | param(
40 | [Parameter(Mandatory = $true)]
41 | [ValidateNotNullOrEmpty()]
42 | [string]
43 | $Type,
44 |
45 | [Parameter(Mandatory = $true)]
46 | [ValidateNotNullOrEmpty()]
47 | [System.Uri]
48 | $Source,
49 |
50 | [Parameter(Mandatory = $true)]
51 | [ValidateNotNullOrEmpty()]
52 | [string]
53 | $Id,
54 |
55 | [Parameter(Mandatory = $false)]
56 | [ValidateNotNullOrEmpty()]
57 | [DateTime]
58 | $Time
59 | )
60 |
61 | PROCESS {
62 | $cloudEvent = New-Object `
63 | -TypeName 'CloudNative.CloudEvents.CloudEvent' `
64 | -ArgumentList @(
65 | $Type,
66 | $Source,
67 | $Id,
68 | $Time,
69 | @())
70 |
71 | Write-Output $cloudEvent
72 | }
73 | }
74 |
75 | #region Set Data Functions
76 | function Set-CloudEventData {
77 | <#
78 | .SYNOPSIS
79 | This function sets data in a cloud event.
80 |
81 | .DESCRIPTION
82 | This function sets data in a cloud event object with the provided parameters.
83 |
84 | .PARAMETER CloudEvent
85 | Specifies the cloud event object that receives the data.
86 |
87 | .PARAMETER Data
88 | Specifies the data object for the cloud event 'data' attribute.
89 |
90 | .PARAMETER DataContentType
91 | Specifies the 'datacontenttype' attribute of the cloud event.
92 |
93 |
94 | .EXAMPLE
95 | $cloudEvent = New-CloudEvent -Type 'com.example.object.deleted.v2' -Source 'mailto:cncf-wg-serverless@lists.cncf.io' -Id '6e8bc430-9c3a-11d9-9669-0800200c9a66' -Time (Get-Date)
96 | $cloudEvent | Set-CloudEventData -Data '' -DataContentType 'application/xml'
97 |
98 | Sets xml data to the cloud event
99 | #>
100 |
101 | [CmdletBinding()]
102 | param(
103 | [Parameter(Mandatory = $true,
104 | ValueFromPipeline = $true)]
105 | [ValidateNotNullOrEmpty()]
106 | [CloudNative.CloudEvents.CloudEvent]
107 | $CloudEvent,
108 |
109 | [Parameter(Mandatory = $true,
110 | ValueFromPipeline = $false)]
111 | [ValidateNotNullOrEmpty()]
112 | [object]
113 | $Data,
114 |
115 | # CloudEvent 'datacontenttype' attribute. Content type of the 'data' attribute value.
116 | # This attribute enables the data attribute to carry any type of content, whereby
117 | # format and encoding might differ from that of the chosen event format.
118 | [Parameter(Mandatory = $false,
119 | ValueFromPipeline = $false)]
120 | [string]
121 | $DataContentType)
122 |
123 | PROCESS {
124 |
125 | # https://github.com/cloudevents/spec/blob/master/spec.md#datacontenttype
126 | $contentType = New-Object `
127 | -TypeName 'System.Net.Mime.ContentType' `
128 | -ArgumentList ($DataContentType)
129 |
130 | $cloudEvent.Data = $Data
131 | $cloudEvent.DataContentType = $dataContentType
132 |
133 | Write-Output $CloudEvent
134 | }
135 |
136 | }
137 |
138 | function Set-CloudEventJsonData {
139 | <#
140 | .SYNOPSIS
141 | This function sets JSON format data in a cloud event.
142 |
143 | .DESCRIPTION
144 | This function converts a PowerShell hashtable to JSON format data and sets it in a cloud event.
145 |
146 | .PARAMETER CloudEvent
147 | Specifies the cloud event object that receives the data.
148 |
149 | .PARAMETER Data
150 | Specifies the PowerShell hashtable object that is set as JSON on the cloud event 'data' attribute.
151 | The 'datacontenttype' attribute is set to 'application/json'
152 |
153 | .PARAMETER Depth
154 | The maximum depth of the input hashtable specified on the `Data` parameter that will be converted to JSON.
155 | This parameter is passed on the `-Depth` parameter of the `ConvertTo-Json` cmdlet.
156 | The default value is 3
157 |
158 |
159 | .EXAMPLE
160 | $cloudEvent = New-CloudEvent -Type 'com.example.object.deleted.v2' -Source 'mailto:cncf-wg-serverless@lists.cncf.io' -Id '6e8bc430-9c3a-11d9-9669-0800200c9a66' -Time (Get-Date)
161 | $cloudEvent | Set-CloudEventJsonData -Data @{ 'key1' = 'value1'; 'key2' = 'value2'; }
162 |
163 | Sets JSON data to the cloud event
164 | #>
165 |
166 | [CmdletBinding()]
167 | param(
168 | [Parameter(Mandatory = $true,
169 | ValueFromPipeline = $true)]
170 | [ValidateNotNullOrEmpty()]
171 | [CloudNative.CloudEvents.CloudEvent]
172 | $CloudEvent,
173 |
174 | [Parameter(Mandatory = $true,
175 | ValueFromPipeline = $false)]
176 | [ValidateNotNull()]
177 | [Hashtable]
178 | $Data,
179 |
180 | [Parameter(Mandatory = $false,
181 | ValueFromPipeline = $false)]
182 | [int]
183 | $Depth = 3)
184 |
185 | PROCESS {
186 |
187 | # DataContentType is set to 'application/json'
188 | # https://github.com/cloudevents/spec/blob/master/spec.md#datacontenttype
189 | $dataContentType = New-Object `
190 | -TypeName 'System.Net.Mime.ContentType' `
191 | -ArgumentList ([System.Net.Mime.MediaTypeNames+Application]::Json)
192 |
193 | $cloudEvent.DataContentType = $dataContentType
194 | $cloudEvent.Data = ConvertTo-Json -InputObject $Data -Depth $Depth
195 |
196 | Write-Output $CloudEvent
197 | }
198 |
199 | }
200 |
201 | function Set-CloudEventXmlData {
202 | <#
203 | .SYNOPSIS
204 | This function sets XML format data in a cloud event.
205 |
206 | .DESCRIPTION
207 | This function converts a PowerShell hashtable to XML format data and sets it in a cloud event.
208 |
209 | .PARAMETER CloudEvent
210 | Specifies the cloud event object that receives the data.
211 |
212 | .PARAMETER Data
213 | Specifies the PowerShell hashtable object that is set as XML on the cloud event 'data' attribute.
214 | The 'datacontenttype' attribute is set to 'application/xml'
215 |
216 | .PARAMETER AttributesKeysInElementAttributes
217 | Specifies how to format the XML. If specified and the input Data hashtable has pairs of 'Attributes', 'Value' keys
218 | creates XML element with attributes, otherwise each key is formatted as XML element.
219 | If true
220 | @{'root' = @{'Attributes' = @{'att1' = 'true'}; 'Value' = 'val-1'}} would be 'val-1'
221 | Otherwise
222 | @{'root' = @{'Attributes' = @{'att1' = 'true'}; 'Value' = 'val-1'}} would be 'trueval-1'
223 |
224 |
225 | .EXAMPLE
226 | $cloudEvent = New-CloudEvent -Type 'com.example.object.deleted.v2' -Source 'mailto:cncf-wg-serverless@lists.cncf.io' -Id '6e8bc430-9c3a-11d9-9669-0800200c9a66' -Time (Get-Date)
227 | $cloudEvent | Set-CloudEventXmlData -Data @{ 'key1' = 'value1'; 'key2' = 'value2'; } -AttributesKeysInElementAttributes $true
228 |
229 | Sets XML data in the cloud event
230 | #>
231 |
232 | [CmdletBinding()]
233 | param(
234 | [Parameter(Mandatory = $true,
235 | ValueFromPipeline = $true)]
236 | [ValidateNotNullOrEmpty()]
237 | [CloudNative.CloudEvents.CloudEvent]
238 | $CloudEvent,
239 |
240 | [Parameter(Mandatory = $true,
241 | ValueFromPipeline = $false)]
242 | [ValidateNotNull()]
243 | [Hashtable]
244 | $Data,
245 |
246 | [Parameter(Mandatory = $true)]
247 | [bool]
248 | $AttributesKeysInElementAttributes)
249 |
250 | PROCESS {
251 |
252 | # DataContentType is set to 'application/xml'
253 | $dataContentType = New-Object `
254 | -TypeName 'System.Net.Mime.ContentType' `
255 | -ArgumentList ([System.Net.Mime.MediaTypeNames+Application]::Xml)
256 |
257 | $cloudEvent.DataContentType = $dataContentType
258 | $cloudEvent.Data = ConvertTo-CEDataXml -InputObject $Data -AttributesKeysInElementAttributes $AttributesKeysInElementAttributes
259 |
260 | Write-Output $CloudEvent
261 | }
262 |
263 | }
264 | #endregion Set Data Functions
265 |
266 | #region Read Data Functions
267 | function Read-CloudEventData {
268 | <#
269 | .SYNOPSIS
270 | This function gets the data from a cloud event.
271 |
272 | .DESCRIPTION
273 | This function gets the data as-is from a cloud event. It is equiualent of accessing the Data property of a CloudEvent object
274 |
275 | .PARAMETER CloudEvent
276 | Specifies the cloud event object to get data from.
277 |
278 | .EXAMPLE
279 | $cloudEvent = ConvertFrom-HttpMessage -Headers $httpResponse.Headers -Body $httpResponse.Content
280 | $cloudEvent | Read-CloudEventData
281 |
282 | Reads data from a cloud event received on the http response
283 | #>
284 |
285 | [CmdletBinding()]
286 | param(
287 | [Parameter(Mandatory = $true,
288 | ValueFromPipeline = $true)]
289 | [ValidateNotNullOrEmpty()]
290 | [CloudNative.CloudEvents.CloudEvent]
291 | $CloudEvent
292 | )
293 |
294 | PROCESS {
295 | Write-Output $CloudEvent.Data
296 | }
297 |
298 | }
299 |
300 | function Read-CloudEventJsonData {
301 | <#
302 | .SYNOPSIS
303 | This function gets JSON fromat data from a cloud event as a PowerShell hashtable.
304 |
305 | .DESCRIPTION
306 | This function gets the data from a cloud event and converts it to a PowerShell hashtable.
307 | If the cloud event datacontenttype is not 'application/json' nothing is returned.
308 |
309 | .PARAMETER CloudEvent
310 | Specifies the cloud event object to get data from.
311 |
312 | .PARAMETER Depth
313 | Specifies how many levels of contained objects are included in the JSON representation. The default value is 3.
314 |
315 | .EXAMPLE
316 | $cloudEvent = ConvertFrom-HttpMessage -Headers $httpResponse.Headers -Body $httpResponse.Content
317 | $hashtable = $cloudEvent | Read-CloudEventJsonData
318 |
319 | Reads JSON data as a hashtable from a cloud event received on the http response
320 | #>
321 |
322 |
323 | <#
324 | .DESCRIPTION
325 | Returns PowerShell hashtable that represents the CloudEvent Json Data
326 | if the data content type is 'application/json', otherwise otherwise non-terminating error and no result
327 | #>
328 |
329 | [CmdletBinding()]
330 | param(
331 | [Parameter(Mandatory = $true,
332 | ValueFromPipeline = $true)]
333 | [ValidateNotNullOrEmpty()]
334 | [CloudNative.CloudEvents.CloudEvent]
335 | $CloudEvent,
336 |
337 | [Parameter(Mandatory = $false,
338 | ValueFromPipeline = $false)]
339 | [int]
340 | $Depth = 3
341 | )
342 |
343 | PROCESS {
344 |
345 | # DataContentType is expected to be 'application/json'
346 | # https://github.com/cloudevents/spec/blob/master/spec.md#datacontenttype
347 | $dataContentType = New-Object `
348 | -TypeName 'System.Net.Mime.ContentType' `
349 | -ArgumentList ([System.Net.Mime.MediaTypeNames+Application]::Json)
350 |
351 | if ($CloudEvent.DataContentType -eq $dataContentType -or `
352 | ($CloudEvent.DataContentType -eq $null -and # Datacontent Type is Optional, if it is not specified we assume it is JSON as per https://github.com/cloudevents/spec/blob/v1.0.1/spec.md#datacontenttype
353 | $cloudEvent.Data -is [Newtonsoft.Json.Linq.JObject])) {
354 |
355 | $data = $cloudEvent.Data
356 |
357 | if ($cloudEvent.Data -is [byte[]]) {
358 | $data = [System.Text.Encoding]::UTF8.GetString($data)
359 | }
360 |
361 | $result = $data.ToString() | ConvertFrom-Json -AsHashtable -Depth $Depth
362 |
363 | Write-Output $result
364 | }
365 | else {
366 | Write-Error "Cloud Event '$($cloudEvent.Id)' has no json data"
367 | }
368 | }
369 |
370 | }
371 |
372 | function Read-CloudEventXmlData {
373 | <#
374 | .SYNOPSIS
375 | This function gets XML fromat data from a cloud event as a PowerShell hashtable.
376 |
377 | .DESCRIPTION
378 | This function gets the data from a cloud event and converts it to a PowerShell hashtable.
379 | If the cloud event datacontenttype is not 'application/xml' nothing is returned.
380 |
381 | .PARAMETER CloudEvent
382 | Specifies the cloud event object to get data from.
383 |
384 | .PARAMETER ConvertMode
385 | Specifies the how to convert the xml data to a hashtable
386 | 'SkipAttributes' - Skips attributes of the XML elements. XmlElement is represented as a
387 | Key-Value pair where key is the xml element name, and the value is the xml element inner text
388 |
389 | Example:
390 | "value1" is converted to
391 | @{'key' = 'value-1'}
392 |
393 | 'AlwaysAttrValue' - Each element is represented as a hashtable with two keys
394 | 'Attributes' - key-value pair of the cml element attributes if any, otherwise null
395 | 'Value' - string value represinting the xml element inner text
396 |
397 | Example:
398 | "value1value2" is converted to
399 | @{
400 | 'key1' = @{
401 | 'Attributes' = @{
402 | 'att' = 'true'
403 | }
404 | 'Value' = 'value1'
405 | }
406 | 'key2' = @{
407 | 'Attributes' = $null
408 | 'Value' = 'value2'
409 | }
410 |
411 | }
412 | 'AttrValueWhenAttributes' - Uses 'SkipAttributes' for xml elements without attributes and
413 | 'AlwaysAttrValue' for xml elements with attributes
414 | Example:
415 | "value1value2" is converted to
416 | @{
417 | 'key1' = @{
418 | 'Attributes' = @{
419 | 'att' = 'true'
420 | }
421 | 'Value' = 'value1'
422 | }
423 | 'key2' = 'value2'
424 | }
425 |
426 | .EXAMPLE
427 | $cloudEvent = ConvertFrom-HttpMessage -Headers $httpResponse.Headers -Body $httpResponse.Content
428 | $hashtable = $cloudEvent | Read-CloudEventXmlData -ConvertMode AttrValueWhenAttributes
429 |
430 | Reads XML data as a hashtable from a cloud event received on the http response
431 | #>
432 |
433 |
434 | <#
435 | .DESCRIPTION
436 | Returns PowerShell hashtable that represents the CloudEvent Xml Data
437 | if the data content type is 'application/xml', otherwise non-terminating error and no result
438 | #>
439 |
440 | [CmdletBinding()]
441 | param(
442 | [Parameter(Mandatory = $true,
443 | ValueFromPipeline = $true)]
444 | [ValidateNotNullOrEmpty()]
445 | [CloudNative.CloudEvents.CloudEvent]
446 | $CloudEvent,
447 |
448 | [Parameter(Mandatory = $true)]
449 | [ValidateSet("SkipAttributes", "AlwaysAttrValue", "AttrValueWhenAttributes")]
450 | [string]
451 | $ConvertMode
452 | )
453 |
454 | PROCESS {
455 |
456 | # DataContentType is expected to be 'application/xml'
457 | $dataContentType = New-Object `
458 | -TypeName 'System.Net.Mime.ContentType' `
459 | -ArgumentList ([System.Net.Mime.MediaTypeNames+Application]::Xml)
460 |
461 | if ($CloudEvent.DataContentType -eq $dataContentType) {
462 |
463 | $data = $cloudEvent.Data
464 |
465 | if ($cloudEvent.Data -is [byte[]]) {
466 | $data = [System.Text.Encoding]::UTF8.GetString($data)
467 | }
468 |
469 | $result = $data.ToString() | ConvertFrom-CEDataXml -ConvertMode $ConvertMode
470 |
471 | Write-Output $result
472 | }
473 | else {
474 | Write-Error "Cloud Event '$($cloudEvent.Id)' has no xml data"
475 | }
476 | }
477 |
478 | }
479 | #endregion Read Data Functions
480 |
481 | #region HTTP Protocol Binding Conversion Functions
482 | function ConvertTo-HttpMessage {
483 | <#
484 | .SYNOPSIS
485 | This function converts a cloud event object to a Http Message.
486 |
487 | .DESCRIPTION
488 | This function converts a cloud event object to a PSObject with Headers and Body properties.
489 | The 'Headers' propery is a hashtable that can pe provided to the 'Headers' parameter of the Inveok-WebRequest cmdlet.
490 | The 'Body' propery is byte[] that can pe provided to the 'Body' parameter of the Inveok-WebRequest cmdlet.
491 |
492 | .PARAMETER CloudEvent
493 | Specifies the cloud event object to convert.
494 |
495 | .PARAMETER ContentMode
496 | Specifies the cloud event content mode. Structured and Binary content modes are supporterd.
497 |
498 | .EXAMPLE
499 | $cloudEvent = New-CloudEvent -Type 'com.example.object.deleted.v2' -Source 'mailto:cncf-wg-serverless@lists.cncf.io' -Id '6e8bc430-9c3a-11d9-9669-0800200c9a66' -Time (Get-Date)
500 | $cloudEvent | Add-CloudEventJsonData -Data @{ 'key1' = 'value1'; 'key2' = 'value2'; }
501 |
502 | $cloudEvent | ConvertTo-HttpMessage -ContentMode Binary
503 |
504 | Converts a cloud event object to Headers and Body formatted in Binary content mode.
505 |
506 | .EXAMPLE
507 | $cloudEvent = New-CloudEvent -Type 'com.example.object.deleted.v2' -Source 'mailto:cncf-wg-serverless@lists.cncf.io' -Id '6e8bc430-9c3a-11d9-9669-0800200c9a66' -Time (Get-Date)
508 | $cloudEvent | Add-CloudEventJsonData -Data @{ 'key1' = 'value1'; 'key2' = 'value2'; }
509 |
510 | $cloudEvent | ConvertTo-HttpMessage -ContentMode Structured
511 |
512 | Converts a cloud event object to Headers and Body formatted in Structured content mode.
513 |
514 | .EXAMPLE
515 | $httpMessage = New-CloudEvent -Type 'com.example.object.deleted.v2' -Source 'mailto:cncf-wg-serverless@lists.cncf.io' -Id '6e8bc430-9c3a-11d9-9669-0800200c9a66' -Time (Get-Date) | `
516 | Add-CloudEventJsonData -Data @{ 'key1' = 'value1'; 'key2' = 'value2'; } | `
517 | ConvertTo-HttpMessage -ContentMode Structured
518 |
519 | Invoke-WebRequest -Uri 'http://localhost:52673/' -Headers $httpMessage.Headers -Body $httpMessage.Body
520 |
521 | Sends a cloud event http requests to a server
522 | #>
523 |
524 | [CmdletBinding()]
525 | param(
526 | [Parameter(
527 | Mandatory = $true,
528 | ValueFromPipeline = $true,
529 | ValueFromPipelineByPropertyName = $false)]
530 | [ValidateNotNull()]
531 | [CloudNative.CloudEvents.CloudEvent]
532 | $CloudEvent,
533 |
534 | [Parameter(
535 | Mandatory = $true,
536 | ValueFromPipeline = $false,
537 | ValueFromPipelineByPropertyName = $false)]
538 | [CloudNative.CloudEvents.ContentMode]
539 | $ContentMode)
540 |
541 | PROCESS {
542 | # Output Object
543 | $result = New-Object -TypeName PSCustomObject
544 |
545 | $cloudEventFormatter = New-Object 'CloudNative.CloudEvents.JsonEventFormatter'
546 |
547 | $HttpHeaderPrefix = "ce-";
548 | $SpecVersionHttpHeader1 = $HttpHeaderPrefix + "cloudEventsVersion";
549 | $SpecVersionHttpHeader2 = $HttpHeaderPrefix + "specversion";
550 |
551 | $headers = @{}
552 |
553 | # Build HTTP headers
554 | foreach ($attribute in $cloudEvent.GetAttributes()) {
555 | if (-not $attribute.Key.Equals([CloudNative.CloudEvents.CloudEventAttributes]::DataAttributeName($cloudEvent.SpecVersion)) -and `
556 | -not $attribute.Key.Equals([CloudNative.CloudEvents.CloudEventAttributes]::DataContentTypeAttributeName($cloudEvent.SpecVersion))) {
557 | if ($attribute.Value -is [string]) {
558 | $headers.Add(($HttpHeaderPrefix + $attribute.Key), $attribute.Value.ToString())
559 | }
560 | elseif ($attribute.Value -is [DateTime]) {
561 | $headers.Add(($HttpHeaderPrefix + $attribute.Key), $attribute.Value.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ'))
562 | }
563 | elseif ($attribute.Value -is [Uri] -or $attribute.Value -is [int]) {
564 | $headers.Add(($HttpHeaderPrefix + $attribute.Key), $attribute.Value.ToString())
565 | }
566 | else {
567 | $headers.Add(($HttpHeaderPrefix + $attribute.Key),
568 | [System.Text.Encoding]::UTF8.GetString($cloudEventFormatter.EncodeAttribute($cloudEvent.SpecVersion, $attribute.Key,
569 | $attribute.Value,
570 | $cloudEvent.Extensions.Values)));
571 | }
572 | }
573 | }
574 |
575 | # Add Headers property to the output object
576 | $result | Add-Member -MemberType NoteProperty -Name 'Headers' -Value $headers
577 |
578 | # Process Structured Mode
579 | # Structured Mode supports non-batching JSON format only
580 | # https://github.com/cloudevents/spec/blob/v1.0.1/http-protocol-binding.md#14-event-formats
581 | if ($ContentMode -eq [CloudNative.CloudEvents.ContentMode]::Structured) {
582 | # Format Body as byte[]
583 | $contentType = $null
584 |
585 | # CloudEventFormatter is instance of 'CloudNative.CloudEvents.JsonEventFormatter' from the
586 | # .NET CloudEvents SDK for the purpose of fomatting structured mode
587 | $buffer = $cloudEventFormatter.EncodeStructuredEvent($cloudEvent, [ref] $contentType)
588 | $result | Add-Member -MemberType NoteProperty -Name 'Body' -Value $buffer
589 | $result.Headers.Add('Content-Type', $contentType)
590 | }
591 |
592 | # Process Binary Mode
593 | if ($ContentMode -eq [CloudNative.CloudEvents.ContentMode]::Binary) {
594 | $bodyData = $null
595 |
596 | if ($cloudEvent.DataContentType -ne $null) {
597 | $result.Headers.Add('Content-Type', $cloudEvent.DataContentType)
598 | }
599 |
600 | if ($cloudEvent.Data -is [byte[]]) {
601 | $bodyData = $cloudEvent.Data
602 | }
603 | elseif ($cloudEvent.Data -is [string]) {
604 | $bodyData = [System.Text.Encoding]::UTF8.GetBytes($cloudEvent.Data.ToString())
605 | }
606 | elseif ($cloudEvent.Data -is [IO.Stream]) {
607 | $buffer = New-Object 'byte[]' -ArgumentList 1024
608 |
609 | $ms = New-Object 'IO.MemoryStream'
610 |
611 | try {
612 | $read = 0
613 | while (($read = $cloudEvent.Data.Read($buffer, 0, 1024)) -gt 0) {
614 | $ms.Write($buffer, 0, $read);
615 | }
616 | $bodyData = $ms.ToArray()
617 | }
618 | finally {
619 | $ms.Dispose()
620 | }
621 |
622 | }
623 | else {
624 | $bodyData = $cloudEventFormatter.EncodeAttribute($cloudEvent.SpecVersion,
625 | [CloudNative.CloudEvents.CloudEventAttributes]::DataAttributeName($cloudEvent.SpecVersion),
626 | $cloudEvent.Data, $cloudEvent.Extensions.Values)
627 | }
628 |
629 | # Add Body property to the output object
630 | $result | Add-Member -MemberType NoteProperty -Name 'Body' -Value $bodyData
631 | }
632 |
633 | Write-Output $result
634 | }
635 | }
636 |
637 | function ConvertFrom-HttpMessage {
638 | <#
639 | .SYNOPSIS
640 | This function converts a Http Message to a cloud event object
641 |
642 | .DESCRIPTION
643 | This function converts a Http Message (Headers and Body) to a cloud event object.
644 | Result of Invoke-WebRequest that contains a cloud event can be passed as input to this
645 | function binding the the 'Headers' and 'Content' properties to the 'Headers' and 'Body' paramters.
646 |
647 | .PARAMETER Headers
648 | Specifies the Http Headers as a PowerShell hashtable.
649 |
650 | .PARAMETER Body
651 | Specifies the Http body as string or byte[].
652 |
653 | .EXAMPLE
654 | $httpReponse = Invoke-WebRequest -Uri 'http://localhost:52673/' -Headers $httpMessage.Headers -Body $httpMessage.Body
655 | $cloudEvent = ConvertFrom-HttpMessage -Headers $httpResponse.Headers -Body $httpResponse.Content
656 |
657 | Converts a http response to a cloud event object
658 | #>
659 |
660 | [CmdletBinding()]
661 | param(
662 | [Parameter(
663 | Mandatory = $true,
664 | ValueFromPipeline = $false,
665 | ValueFromPipelineByPropertyName = $false)]
666 | [ValidateNotNull()]
667 | [hashtable]
668 | $Headers,
669 |
670 | [Parameter(
671 | Mandatory = $false,
672 | ValueFromPipeline = $false,
673 | ValueFromPipelineByPropertyName = $false)]
674 | [ValidateNotNull()]
675 | $Body)
676 |
677 | PROCESS {
678 | $HttpHeaderPrefix = "ce-";
679 | $SpecVersionHttpHeader = $HttpHeaderPrefix + "specversion";
680 |
681 | $result = $null
682 |
683 | # Always Convert Body to byte[]
684 | # Conversion works with byte[] while
685 | # body can be string in HTTP responses
686 | # for text content type
687 | if ($Body -is [string]) {
688 | $Body = [System.Text.Encoding]::UTF8.GetBytes($Body)
689 | }
690 |
691 | if ($null -ne $Headers['Content-Type']) {
692 | $ContentType = $Headers['Content-Type']
693 | if ($ContentType -is [array]) {
694 | # Get the first content-type value
695 | $ContentType = $ContentType[0]
696 | }
697 |
698 | if ($ContentType.StartsWith([CloudNative.CloudEvents.CloudEvent]::MediaType,
699 | [StringComparison]::InvariantCultureIgnoreCase)) {
700 |
701 | # Handle Structured Mode
702 | $ctParts = $ContentType.Split(';')
703 | if ($ctParts[0].Trim().StartsWith(([CloudNative.CloudEvents.CloudEvent]::MediaType) + ([CloudNative.CloudEvents.JsonEventFormatter]::MediaTypeSuffix),
704 | [StringComparison]::InvariantCultureIgnoreCase)) {
705 |
706 | # Structured Mode supports non-batching JSON format only
707 | # https://github.com/cloudevents/spec/blob/v1.0.1/http-protocol-binding.md#14-event-formats
708 |
709 | # .NET SDK 'CloudNative.CloudEvents.JsonEventFormatter' type is used
710 | # to decode the Structured Mode CloudEvents
711 |
712 | $json = [System.Text.Encoding]::UTF8.GetString($Body)
713 | $jObject = [Newtonsoft.Json.Linq.JObject]::Parse($json)
714 | $formatter = New-Object 'CloudNative.CloudEvents.JsonEventFormatter'
715 | $result = $formatter.DecodeJObject($jObject, $null)
716 |
717 | $result.Data = $result.Data
718 | }
719 | else {
720 | # Throw error for unsupported encoding
721 | throw "Unsupported CloudEvents encoding"
722 | }
723 | }
724 | else {
725 | # Handle Binary Mode
726 | $version = $null
727 | if ($Headers.Contains($SpecVersionHttpHeader) -and `
728 | $null -ne $Headers[$SpecVersionHttpHeader] -and `
729 | ($Headers[$SpecVersionHttpHeader] | Select-Object -First 1).StartsWith('1.0')) {
730 | # We do support the 1.0 cloud event version
731 | $version = [CloudNative.CloudEvents.CloudEventsSpecVersion]::V1_0
732 | }
733 |
734 | if ($null -ne $version) {
735 | # SpecVersion is REQUIRED attribute, it it is not specified this is not a CloudEvent
736 | # https://github.com/cloudevents/spec/blob/v1.0.1/spec.md#specversion
737 | $cloudEvent = New-Object `
738 | -TypeName 'CloudNative.CloudEvents.CloudEvent' `
739 | -ArgumentList @($version, $null);
740 |
741 | $attributes = $cloudEvent.GetAttributes();
742 |
743 | # Get attributes from HTTP Headers
744 | foreach ($httpHeader in $Headers.GetEnumerator()) {
745 | if ($httpHeader.Key.Equals($SpecVersionHttpHeader, [StringComparison]::InvariantCultureIgnoreCase)) {
746 | continue
747 | }
748 |
749 | if ($httpHeader.Key.StartsWith($HttpHeaderPrefix, [StringComparison]::InvariantCultureIgnoreCase)) {
750 | $headerValue = $httpHeader.Value
751 | if ($headerValue -is [array]) {
752 | # Get the first object
753 | $headerValue = $headerValue[0]
754 | }
755 | $name = $httpHeader.Key.Substring(3);
756 |
757 | # Abolished structures in headers in 1.0
758 | if ( $null -ne $headerValue -and `
759 | $headerValue.StartsWith('"') -and `
760 | $headerValue.EndsWith('"') -or `
761 | $headerValue.StartsWith("'") -and $headerValue.EndsWith("'") -or `
762 | $headerValue.StartsWith("{") -and $headerValue.EndsWith("}") -or `
763 | $headerValue.StartsWith("[") -and $headerValue.EndsWith("]")) {
764 |
765 | $jsonFormatter = New-Object 'CloudNative.CloudEvents.JsonEventFormatter'
766 |
767 | $attributes[$name] = $jsonFormatter.DecodeAttribute($version, $name,
768 | [System.Text.Encoding]::UTF8.GetBytes($headerValue), $null);
769 | }
770 | else {
771 | $attributes[$name] = $headerValue
772 | }
773 | }
774 | }
775 |
776 | # Verify parsed attributes from HTTP Headers
777 | if ($null -ne $attributes['datacontenttype']) {
778 | # ce-datatype is prohibitted by the protocol -> throw error
779 | # https://github.com/cloudevents/spec/blob/v1.0.1/http-protocol-binding.md#311-http-content-type
780 | throw "'ce-datacontenttype' HTTP header is prohibited for Binary ContentMode CloudEvent"
781 | }
782 |
783 | if ($Headers['Content-Type'] -is [string]) {
784 | $cloudEvent.DataContentType = New-Object 'System.Net.Mime.ContentType' -ArgumentList @($Headers['Content-Type'])
785 | } elseif ($Headers['Content-Type'][0] -is [string]) {
786 | $cloudEvent.DataContentType = New-Object 'System.Net.Mime.ContentType' -ArgumentList @($Headers['Content-Type'][0])
787 | }
788 |
789 | # Id, Type, and Source are reuiqred to be non-empty strings otherwise consider this is not a CloudEvent
790 | # https://github.com/cloudevents/spec/blob/v1.0.1/spec.md#required-attributes
791 | if ( -not [string]::IsNullOrEmpty($cloudEvent.Id) -and `
792 | -not [string]::IsNullOrEmpty($cloudEvent.Source) -and `
793 | -not [string]::IsNullOrEmpty($cloudEvent.Type)) {
794 | # Get Data from HTTP Body
795 | $cloudEvent.Data = $Body
796 |
797 | $result = $cloudEvent
798 | }
799 | }
800 | }
801 | }
802 |
803 | Write-Output $result
804 | }
805 | }
806 | #endregion HTTP Protocol Binding Conversion Functions
--------------------------------------------------------------------------------