├── .github └── workflows │ ├── lint.yml │ ├── pages.yml │ └── release.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── LICENSE ├── Makefile ├── assets ├── example.js ├── index.html └── wasm_exec.js ├── cmd ├── cli │ └── main.go ├── main.go └── server │ └── main.go ├── configunmarshaler └── defaultunmarshaler.go ├── go.mod ├── go.sum ├── otel-vscode ├── .eslintrc.json ├── .gitignore ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── lib │ ├── download.js │ └── postinstall.js ├── package-lock.json ├── package.json ├── src │ ├── extension.ts │ ├── pipeline.ts │ ├── test │ │ ├── runTest.ts │ │ └── suite │ │ │ ├── extension.test.ts │ │ │ └── index.ts │ └── types.ts └── tsconfig.json ├── readme.md ├── test-adot.yml ├── test.yml ├── types └── types.go ├── validator ├── components.go ├── components_wasm.go └── validator.go └── version └── version.go /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | paths-ignore: 5 | - 'otel-vscode' 6 | tags: 7 | - v* 8 | branches: 9 | - master 10 | - main 11 | pull_request: 12 | paths-ignore: 13 | - 'otel-vscode' 14 | permissions: 15 | contents: read 16 | # Optional: allow read access to pull request. Use with `only-new-issues` option. 17 | # pull-requests: read 18 | jobs: 19 | golangci: 20 | name: lint 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/setup-go@v3 24 | with: 25 | go-version: 1.19.2 26 | - uses: actions/checkout@v3 27 | - name: golangci-lint 28 | uses: golangci/golangci-lint-action@v3 29 | with: 30 | version: v1.50.1 31 | args: --timeout=5m 32 | -------------------------------------------------------------------------------- /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | on: 3 | workflow_dispatch: 4 | push: 5 | paths-ignore: 6 | - 'README.md' 7 | branches: 8 | - main 9 | 10 | permissions: 11 | contents: write 12 | jobs: 13 | build-and-deploy: 14 | concurrency: ci-${{ github.ref }} # Recommended if you intend to make multiple deployments in quick succession. 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 🛎️ 18 | uses: actions/checkout@v3 19 | 20 | - uses: actions/setup-go@v3 21 | with: 22 | go-version: 1.19.1 23 | 24 | - name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built. 25 | run: | 26 | make 27 | 28 | - name: Deploy 🚀 29 | uses: JamesIves/github-pages-deploy-action@v4 30 | with: 31 | folder: assets # The folder the action should deploy. 32 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | tags: 5 | - 'v*' 6 | jobs: 7 | goreleaser: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - 11 | name: Checkout 12 | uses: actions/checkout@v2.4.0 13 | - 14 | name: Unshallow 15 | run: git fetch --prune --unshallow 16 | - 17 | name: Set up Go 18 | uses: actions/setup-go@v2 19 | with: 20 | go-version: 1.19 21 | - 22 | name: Run GoReleaser 23 | uses: goreleaser/goreleaser-action@v2 24 | with: 25 | version: latest 26 | args: release --rm-dist 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | out/ 3 | dist/ 4 | assets/conftest.wasm 5 | otel-config-validator 6 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | skip-files: 3 | - cmd/main.go -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # Visit https://goreleaser.com for documentation on how to customize this 2 | # behavior. 3 | before: 4 | hooks: 5 | # this is just an example and not a requirement for provider building/publishing 6 | - go mod tidy 7 | builds: 8 | - main: ./cmd/cli 9 | env: 10 | # goreleaser does not work with CGO, it could also complicate 11 | # usage by users in CI/CD systems like Terraform Cloud where 12 | # they are unable to install libraries. 13 | - CGO_ENABLED=0 14 | mod_timestamp: '{{ .CommitTimestamp }}' 15 | flags: 16 | - -trimpath 17 | ldflags: 18 | - '-s -w -X main.commit={{.Commit}} -X github.com/lightstep/otel-config-validator/version.ValidatorVersion={{.Version}}' 19 | goos: 20 | - windows 21 | - linux 22 | - darwin 23 | goarch: 24 | - amd64 25 | - '386' 26 | - arm 27 | - arm64 28 | ignore: 29 | - goos: darwin 30 | goarch: '386' 31 | # Windows on arm not building due to github.com/DataDog/gohai/cpu 32 | - goos: windows 33 | goarch: arm 34 | - goos: windows 35 | goarch: arm64 36 | binary: '{{ .ProjectName }}_v{{ .Version }}' 37 | archives: 38 | - format: zip 39 | name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' 40 | checksum: 41 | #extra_files: 42 | # - glob: 'terraform-registry-manifest.json' 43 | # name_template: '{{ .ProjectName }}_{{ .Version }}_manifest.json' 44 | name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS' 45 | algorithm: sha256 46 | release: 47 | #extra_files: 48 | # - glob: 'terraform-registry-manifest.json' 49 | # name_template: '{{ .ProjectName }}_{{ .Version }}_manifest.json' 50 | # If you want to manually examine the release before its live, uncomment this line: 51 | # draft: true 52 | changelog: 53 | skip: true 54 | -------------------------------------------------------------------------------- /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 [yyyy] [name of copyright owner] 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | # go build -o out/conftest cmd/main.go 3 | # GOARCH=amd64 GOOS=linux go build -o out/conftest-darwin main.go 4 | GOOS=js GOARCH=wasm go build -o assets/conftest.wasm cmd/main.go 5 | 6 | build-cli: 7 | go build -o out/otel-config-validator ./cmd/cli/main.go -------------------------------------------------------------------------------- /assets/example.js: -------------------------------------------------------------------------------- 1 | window.README_DOC = ` 2 | 3 | # OpenTelemetry Collector Generated Configuration 4 | 5 | ### Usage: Plain external OTLP Endpoint 6 | 7 | \` 8 | export OTEL_EXPORTER_OTLP_INSECURE=false|true 9 | export OTEL_EXPORTER_OTLP_ENDPOINT=your-endpoint:4317 10 | docker-compose up opentelemetry-collector-contrib 11 | \` 12 | 13 | ### Usage: Collector with Lightstep Public Satellites 14 | 15 | \` 16 | export OTEL_EXPORTER_OTLP_INSECURE=false 17 | export LS_ACCESS_TOKEN= 18 | export OTEL_EXPORTER_OTLP_ENDPOINT=ingest.lightstep.com:443 19 | \` 20 | 21 | Start Collector and Synthetic Trace Generator: 22 | 23 | \` 24 | docker-compose up opentelemetry-collector-contrib generator 25 | \` 26 | 27 | ### Usage: Collector with Lightstep Satellite 28 | 29 | \` 30 | export OTEL_EXPORTER_OTLP_INSECURE=true 31 | export LS_COLLECTOR_SATELLITE_KEY= 32 | export LS_ACCESS_TOKEN= 33 | export OTEL_EXPORTER_OTLP_ENDPOINT=satellite:8383 34 | \` 35 | 36 | Start Collector, Satellite, and Synthetic Trace Generator: 37 | 38 | \` 39 | docker-compose up opentelemetry-collector-contrib satellite generator 40 | \` 41 | `; 42 | 43 | window.DOCKER_COMPOSE_YML = ` 44 | version: "3" 45 | services: 46 | generator: 47 | image: ghcr.io/lightstep/lightstep-partner-toolkit-collector:latest 48 | depends_on: 49 | - opentelemetry-collector-contrib 50 | environment: 51 | LS_ACCESS_TOKEN: \${LS_ACCESS_TOKEN} 52 | OTEL_INSECURE: true 53 | OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: opentelemetry-collector-contrib:4317 54 | 55 | satellite: 56 | image: lightstep/microsatellite:latest 57 | environment: 58 | - COLLECTOR_SATELLITE_KEY=\${LS_COLLECTOR_SATELLITE_KEY} 59 | - COLLECTOR_PLAIN_PORT=8383 60 | - COLLECTOR_SECURE_PORT=9393 61 | - COLLECTOR_DIAGNOSTIC_PORT=8000 62 | - COLLECTOR_ADMIN_PLAIN_PORT=8180 63 | ports: 64 | - "8383:8383" #Span ingest, Required for unsecure traffic, or secure traffic that terminates it's secure status before it hits the satellite 65 | - "9393:9393" #Span ingest Required for secure traffic 66 | - "8000:8000" #Diagnostics 67 | - "8180:8180" #COLLECTOR_ADMIN_PLAIN_PORT, Required for health checks 68 | 69 | opentelemetry-collector-contrib: 70 | image: otel/opentelemetry-collector-contrib:0.59.0 71 | command: ["--config=/etc/otel-collector-config.yml"] 72 | depends_on: 73 | - satellite 74 | environment: 75 | LS_ACCESS_TOKEN: \${LS_ACCESS_TOKEN} 76 | OTEL_EXPORTER_OTLP_ENDPOINT: \${OTEL_EXPORTER_OTLP_ENDPOINT} 77 | OTEL_EXPORTER_OTLP_INSECURE: \${OTEL_EXPORTER_OTLP_INSECURE} 78 | ports: 79 | - 4317:4317 80 | volumes: 81 | - ./otel-collector-config.yaml:/etc/otel-collector-config.yml 82 | `; 83 | 84 | window.OTEL_EXAMPLES = { 85 | "otlp": `receivers: 86 | otlp: 87 | protocols: 88 | grpc: 89 | http: 90 | 91 | processors: 92 | batch: 93 | 94 | exporters: 95 | otlp: 96 | endpoint: \${OTEL_EXPORTER_OTLP_ENDPOINT} 97 | headers: 98 | 99 | extensions: 100 | health_check: 101 | zpages: 102 | 103 | service: 104 | extensions: [health_check,zpages] 105 | pipelines: 106 | traces: 107 | receivers: [otlp] 108 | processors: [batch] 109 | exporters: [otlp] 110 | metrics: 111 | receivers: [otlp] 112 | processors: [batch] 113 | exporters: [otlp] 114 | logs: 115 | receivers: [otlp] 116 | processors: [batch] 117 | exporters: [otlp] 118 | `, 119 | "lightstep-rec": `receivers: 120 | otlp: 121 | protocols: 122 | grpc: 123 | 124 | processors: 125 | batch: 126 | memory_limiter: 127 | check_interval: 1s 128 | limit_percentage: 50 129 | spike_limit_percentage: 30 130 | 131 | exporters: 132 | logging: 133 | otlp/lightstep: 134 | endpoint: \${OTEL_EXPORTER_OTLP_ENDPOINT} 135 | tls: 136 | insecure_skip_verify: true 137 | headers: 138 | "lightstep-access-token": "\${LS_ACCESS_TOKEN}" 139 | 140 | extensions: 141 | health_check: 142 | 143 | service: 144 | extensions: [health_check] 145 | pipelines: 146 | metrics: 147 | receivers: 148 | - otlp 149 | processors: 150 | - memory_limiter 151 | - batch 152 | exporters: 153 | - otlp/lightstep 154 | - logging 155 | 156 | traces: 157 | receivers: 158 | - otlp 159 | processors: 160 | - memory_limiter 161 | - batch 162 | exporters: 163 | - logging 164 | - otlp/lightstep 165 | `, 166 | "cloudwatch-rec": `receivers: 167 | otlp: 168 | protocols: 169 | grpc: 170 | 171 | processors: 172 | batch: 173 | memory_limiter: 174 | check_interval: 1s 175 | limit_percentage: 50 176 | spike_limit_percentage: 30 177 | 178 | exporters: 179 | awsemf: 180 | region: 'us-west-2' 181 | resource_to_telemetry_conversion: 182 | enabled: true 183 | 184 | extensions: 185 | health_check: 186 | 187 | service: 188 | extensions: [health_check] 189 | pipelines: 190 | metrics: 191 | receivers: 192 | - otlp 193 | processors: 194 | - memory_limiter 195 | - batch 196 | exporters: 197 | - awsemf 198 | ` 199 | } -------------------------------------------------------------------------------- /assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 16 | 17 | 18 | 19 |
20 |

OpenTelemetry Collector Configuration Validator

21 |
22 | 62 | 65 |
66 | 114 | 115 | -------------------------------------------------------------------------------- /assets/wasm_exec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | "use strict"; 6 | 7 | (() => { 8 | const enosys = () => { 9 | const err = new Error("not implemented"); 10 | err.code = "ENOSYS"; 11 | return err; 12 | }; 13 | 14 | if (!globalThis.fs) { 15 | let outputBuf = ""; 16 | globalThis.fs = { 17 | constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused 18 | writeSync(fd, buf) { 19 | outputBuf += decoder.decode(buf); 20 | const nl = outputBuf.lastIndexOf("\n"); 21 | if (nl != -1) { 22 | console.log(outputBuf.substr(0, nl)); 23 | outputBuf = outputBuf.substr(nl + 1); 24 | } 25 | return buf.length; 26 | }, 27 | write(fd, buf, offset, length, position, callback) { 28 | if (offset !== 0 || length !== buf.length || position !== null) { 29 | callback(enosys()); 30 | return; 31 | } 32 | const n = this.writeSync(fd, buf); 33 | callback(null, n); 34 | }, 35 | chmod(path, mode, callback) { callback(enosys()); }, 36 | chown(path, uid, gid, callback) { callback(enosys()); }, 37 | close(fd, callback) { callback(enosys()); }, 38 | fchmod(fd, mode, callback) { callback(enosys()); }, 39 | fchown(fd, uid, gid, callback) { callback(enosys()); }, 40 | fstat(fd, callback) { callback(enosys()); }, 41 | fsync(fd, callback) { callback(null); }, 42 | ftruncate(fd, length, callback) { callback(enosys()); }, 43 | lchown(path, uid, gid, callback) { callback(enosys()); }, 44 | link(path, link, callback) { callback(enosys()); }, 45 | lstat(path, callback) { callback(enosys()); }, 46 | mkdir(path, perm, callback) { callback(enosys()); }, 47 | open(path, flags, mode, callback) { callback(enosys()); }, 48 | read(fd, buffer, offset, length, position, callback) { callback(enosys()); }, 49 | readdir(path, callback) { callback(enosys()); }, 50 | readlink(path, callback) { callback(enosys()); }, 51 | rename(from, to, callback) { callback(enosys()); }, 52 | rmdir(path, callback) { callback(enosys()); }, 53 | stat(path, callback) { callback(enosys()); }, 54 | symlink(path, link, callback) { callback(enosys()); }, 55 | truncate(path, length, callback) { callback(enosys()); }, 56 | unlink(path, callback) { callback(enosys()); }, 57 | utimes(path, atime, mtime, callback) { callback(enosys()); }, 58 | }; 59 | } 60 | 61 | if (!globalThis.process) { 62 | globalThis.process = { 63 | getuid() { return -1; }, 64 | getgid() { return -1; }, 65 | geteuid() { return -1; }, 66 | getegid() { return -1; }, 67 | getgroups() { throw enosys(); }, 68 | pid: -1, 69 | ppid: -1, 70 | umask() { throw enosys(); }, 71 | cwd() { throw enosys(); }, 72 | chdir() { throw enosys(); }, 73 | } 74 | } 75 | 76 | if (!globalThis.crypto) { 77 | throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)"); 78 | } 79 | 80 | if (!globalThis.performance) { 81 | throw new Error("globalThis.performance is not available, polyfill required (performance.now only)"); 82 | } 83 | 84 | if (!globalThis.TextEncoder) { 85 | throw new Error("globalThis.TextEncoder is not available, polyfill required"); 86 | } 87 | 88 | if (!globalThis.TextDecoder) { 89 | throw new Error("globalThis.TextDecoder is not available, polyfill required"); 90 | } 91 | 92 | const encoder = new TextEncoder("utf-8"); 93 | const decoder = new TextDecoder("utf-8"); 94 | 95 | globalThis.Go = class { 96 | constructor() { 97 | this.argv = ["js"]; 98 | this.env = {}; 99 | this.exit = (code) => { 100 | if (code !== 0) { 101 | console.warn("exit code:", code); 102 | } 103 | }; 104 | this._exitPromise = new Promise((resolve) => { 105 | this._resolveExitPromise = resolve; 106 | }); 107 | this._pendingEvent = null; 108 | this._scheduledTimeouts = new Map(); 109 | this._nextCallbackTimeoutID = 1; 110 | 111 | const setInt64 = (addr, v) => { 112 | this.mem.setUint32(addr + 0, v, true); 113 | this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true); 114 | } 115 | 116 | const getInt64 = (addr) => { 117 | const low = this.mem.getUint32(addr + 0, true); 118 | const high = this.mem.getInt32(addr + 4, true); 119 | return low + high * 4294967296; 120 | } 121 | 122 | const loadValue = (addr) => { 123 | const f = this.mem.getFloat64(addr, true); 124 | if (f === 0) { 125 | return undefined; 126 | } 127 | if (!isNaN(f)) { 128 | return f; 129 | } 130 | 131 | const id = this.mem.getUint32(addr, true); 132 | return this._values[id]; 133 | } 134 | 135 | const storeValue = (addr, v) => { 136 | const nanHead = 0x7FF80000; 137 | 138 | if (typeof v === "number" && v !== 0) { 139 | if (isNaN(v)) { 140 | this.mem.setUint32(addr + 4, nanHead, true); 141 | this.mem.setUint32(addr, 0, true); 142 | return; 143 | } 144 | this.mem.setFloat64(addr, v, true); 145 | return; 146 | } 147 | 148 | if (v === undefined) { 149 | this.mem.setFloat64(addr, 0, true); 150 | return; 151 | } 152 | 153 | let id = this._ids.get(v); 154 | if (id === undefined) { 155 | id = this._idPool.pop(); 156 | if (id === undefined) { 157 | id = this._values.length; 158 | } 159 | this._values[id] = v; 160 | this._goRefCounts[id] = 0; 161 | this._ids.set(v, id); 162 | } 163 | this._goRefCounts[id]++; 164 | let typeFlag = 0; 165 | switch (typeof v) { 166 | case "object": 167 | if (v !== null) { 168 | typeFlag = 1; 169 | } 170 | break; 171 | case "string": 172 | typeFlag = 2; 173 | break; 174 | case "symbol": 175 | typeFlag = 3; 176 | break; 177 | case "function": 178 | typeFlag = 4; 179 | break; 180 | } 181 | this.mem.setUint32(addr + 4, nanHead | typeFlag, true); 182 | this.mem.setUint32(addr, id, true); 183 | } 184 | 185 | const loadSlice = (addr) => { 186 | const array = getInt64(addr + 0); 187 | const len = getInt64(addr + 8); 188 | return new Uint8Array(this._inst.exports.mem.buffer, array, len); 189 | } 190 | 191 | const loadSliceOfValues = (addr) => { 192 | const array = getInt64(addr + 0); 193 | const len = getInt64(addr + 8); 194 | const a = new Array(len); 195 | for (let i = 0; i < len; i++) { 196 | a[i] = loadValue(array + i * 8); 197 | } 198 | return a; 199 | } 200 | 201 | const loadString = (addr) => { 202 | const saddr = getInt64(addr + 0); 203 | const len = getInt64(addr + 8); 204 | return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); 205 | } 206 | 207 | const timeOrigin = Date.now() - performance.now(); 208 | this.importObject = { 209 | go: { 210 | // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) 211 | // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported 212 | // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). 213 | // This changes the SP, thus we have to update the SP used by the imported function. 214 | 215 | // func wasmExit(code int32) 216 | "runtime.wasmExit": (sp) => { 217 | sp >>>= 0; 218 | const code = this.mem.getInt32(sp + 8, true); 219 | this.exited = true; 220 | delete this._inst; 221 | delete this._values; 222 | delete this._goRefCounts; 223 | delete this._ids; 224 | delete this._idPool; 225 | this.exit(code); 226 | }, 227 | 228 | // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) 229 | "runtime.wasmWrite": (sp) => { 230 | sp >>>= 0; 231 | const fd = getInt64(sp + 8); 232 | const p = getInt64(sp + 16); 233 | const n = this.mem.getInt32(sp + 24, true); 234 | fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); 235 | }, 236 | 237 | // func resetMemoryDataView() 238 | "runtime.resetMemoryDataView": (sp) => { 239 | sp >>>= 0; 240 | this.mem = new DataView(this._inst.exports.mem.buffer); 241 | }, 242 | 243 | // func nanotime1() int64 244 | "runtime.nanotime1": (sp) => { 245 | sp >>>= 0; 246 | setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); 247 | }, 248 | 249 | // func walltime() (sec int64, nsec int32) 250 | "runtime.walltime": (sp) => { 251 | sp >>>= 0; 252 | const msec = (new Date).getTime(); 253 | setInt64(sp + 8, msec / 1000); 254 | this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true); 255 | }, 256 | 257 | // func scheduleTimeoutEvent(delay int64) int32 258 | "runtime.scheduleTimeoutEvent": (sp) => { 259 | sp >>>= 0; 260 | const id = this._nextCallbackTimeoutID; 261 | this._nextCallbackTimeoutID++; 262 | this._scheduledTimeouts.set(id, setTimeout( 263 | () => { 264 | this._resume(); 265 | while (this._scheduledTimeouts.has(id)) { 266 | // for some reason Go failed to register the timeout event, log and try again 267 | // (temporary workaround for https://github.com/golang/go/issues/28975) 268 | console.warn("scheduleTimeoutEvent: missed timeout event"); 269 | this._resume(); 270 | } 271 | }, 272 | getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early 273 | )); 274 | this.mem.setInt32(sp + 16, id, true); 275 | }, 276 | 277 | // func clearTimeoutEvent(id int32) 278 | "runtime.clearTimeoutEvent": (sp) => { 279 | sp >>>= 0; 280 | const id = this.mem.getInt32(sp + 8, true); 281 | clearTimeout(this._scheduledTimeouts.get(id)); 282 | this._scheduledTimeouts.delete(id); 283 | }, 284 | 285 | // func getRandomData(r []byte) 286 | "runtime.getRandomData": (sp) => { 287 | sp >>>= 0; 288 | crypto.getRandomValues(loadSlice(sp + 8)); 289 | }, 290 | 291 | // func finalizeRef(v ref) 292 | "syscall/js.finalizeRef": (sp) => { 293 | sp >>>= 0; 294 | const id = this.mem.getUint32(sp + 8, true); 295 | this._goRefCounts[id]--; 296 | if (this._goRefCounts[id] === 0) { 297 | const v = this._values[id]; 298 | this._values[id] = null; 299 | this._ids.delete(v); 300 | this._idPool.push(id); 301 | } 302 | }, 303 | 304 | // func stringVal(value string) ref 305 | "syscall/js.stringVal": (sp) => { 306 | sp >>>= 0; 307 | storeValue(sp + 24, loadString(sp + 8)); 308 | }, 309 | 310 | // func valueGet(v ref, p string) ref 311 | "syscall/js.valueGet": (sp) => { 312 | sp >>>= 0; 313 | const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); 314 | sp = this._inst.exports.getsp() >>> 0; // see comment above 315 | storeValue(sp + 32, result); 316 | }, 317 | 318 | // func valueSet(v ref, p string, x ref) 319 | "syscall/js.valueSet": (sp) => { 320 | sp >>>= 0; 321 | Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); 322 | }, 323 | 324 | // func valueDelete(v ref, p string) 325 | "syscall/js.valueDelete": (sp) => { 326 | sp >>>= 0; 327 | Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16)); 328 | }, 329 | 330 | // func valueIndex(v ref, i int) ref 331 | "syscall/js.valueIndex": (sp) => { 332 | sp >>>= 0; 333 | storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); 334 | }, 335 | 336 | // valueSetIndex(v ref, i int, x ref) 337 | "syscall/js.valueSetIndex": (sp) => { 338 | sp >>>= 0; 339 | Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); 340 | }, 341 | 342 | // func valueCall(v ref, m string, args []ref) (ref, bool) 343 | "syscall/js.valueCall": (sp) => { 344 | sp >>>= 0; 345 | try { 346 | const v = loadValue(sp + 8); 347 | const m = Reflect.get(v, loadString(sp + 16)); 348 | const args = loadSliceOfValues(sp + 32); 349 | const result = Reflect.apply(m, v, args); 350 | sp = this._inst.exports.getsp() >>> 0; // see comment above 351 | storeValue(sp + 56, result); 352 | this.mem.setUint8(sp + 64, 1); 353 | } catch (err) { 354 | sp = this._inst.exports.getsp() >>> 0; // see comment above 355 | storeValue(sp + 56, err); 356 | this.mem.setUint8(sp + 64, 0); 357 | } 358 | }, 359 | 360 | // func valueInvoke(v ref, args []ref) (ref, bool) 361 | "syscall/js.valueInvoke": (sp) => { 362 | sp >>>= 0; 363 | try { 364 | const v = loadValue(sp + 8); 365 | const args = loadSliceOfValues(sp + 16); 366 | const result = Reflect.apply(v, undefined, args); 367 | sp = this._inst.exports.getsp() >>> 0; // see comment above 368 | storeValue(sp + 40, result); 369 | this.mem.setUint8(sp + 48, 1); 370 | } catch (err) { 371 | sp = this._inst.exports.getsp() >>> 0; // see comment above 372 | storeValue(sp + 40, err); 373 | this.mem.setUint8(sp + 48, 0); 374 | } 375 | }, 376 | 377 | // func valueNew(v ref, args []ref) (ref, bool) 378 | "syscall/js.valueNew": (sp) => { 379 | sp >>>= 0; 380 | try { 381 | const v = loadValue(sp + 8); 382 | const args = loadSliceOfValues(sp + 16); 383 | const result = Reflect.construct(v, args); 384 | sp = this._inst.exports.getsp() >>> 0; // see comment above 385 | storeValue(sp + 40, result); 386 | this.mem.setUint8(sp + 48, 1); 387 | } catch (err) { 388 | sp = this._inst.exports.getsp() >>> 0; // see comment above 389 | storeValue(sp + 40, err); 390 | this.mem.setUint8(sp + 48, 0); 391 | } 392 | }, 393 | 394 | // func valueLength(v ref) int 395 | "syscall/js.valueLength": (sp) => { 396 | sp >>>= 0; 397 | setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); 398 | }, 399 | 400 | // valuePrepareString(v ref) (ref, int) 401 | "syscall/js.valuePrepareString": (sp) => { 402 | sp >>>= 0; 403 | const str = encoder.encode(String(loadValue(sp + 8))); 404 | storeValue(sp + 16, str); 405 | setInt64(sp + 24, str.length); 406 | }, 407 | 408 | // valueLoadString(v ref, b []byte) 409 | "syscall/js.valueLoadString": (sp) => { 410 | sp >>>= 0; 411 | const str = loadValue(sp + 8); 412 | loadSlice(sp + 16).set(str); 413 | }, 414 | 415 | // func valueInstanceOf(v ref, t ref) bool 416 | "syscall/js.valueInstanceOf": (sp) => { 417 | sp >>>= 0; 418 | this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0); 419 | }, 420 | 421 | // func copyBytesToGo(dst []byte, src ref) (int, bool) 422 | "syscall/js.copyBytesToGo": (sp) => { 423 | sp >>>= 0; 424 | const dst = loadSlice(sp + 8); 425 | const src = loadValue(sp + 32); 426 | if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { 427 | this.mem.setUint8(sp + 48, 0); 428 | return; 429 | } 430 | const toCopy = src.subarray(0, dst.length); 431 | dst.set(toCopy); 432 | setInt64(sp + 40, toCopy.length); 433 | this.mem.setUint8(sp + 48, 1); 434 | }, 435 | 436 | // func copyBytesToJS(dst ref, src []byte) (int, bool) 437 | "syscall/js.copyBytesToJS": (sp) => { 438 | sp >>>= 0; 439 | const dst = loadValue(sp + 8); 440 | const src = loadSlice(sp + 16); 441 | if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { 442 | this.mem.setUint8(sp + 48, 0); 443 | return; 444 | } 445 | const toCopy = src.subarray(0, dst.length); 446 | dst.set(toCopy); 447 | setInt64(sp + 40, toCopy.length); 448 | this.mem.setUint8(sp + 48, 1); 449 | }, 450 | 451 | "debug": (value) => { 452 | console.log(value); 453 | }, 454 | } 455 | }; 456 | } 457 | 458 | async run(instance) { 459 | if (!(instance instanceof WebAssembly.Instance)) { 460 | throw new Error("Go.run: WebAssembly.Instance expected"); 461 | } 462 | this._inst = instance; 463 | this.mem = new DataView(this._inst.exports.mem.buffer); 464 | this._values = [ // JS values that Go currently has references to, indexed by reference id 465 | NaN, 466 | 0, 467 | null, 468 | true, 469 | false, 470 | globalThis, 471 | this, 472 | ]; 473 | this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id 474 | this._ids = new Map([ // mapping from JS values to reference ids 475 | [0, 1], 476 | [null, 2], 477 | [true, 3], 478 | [false, 4], 479 | [globalThis, 5], 480 | [this, 6], 481 | ]); 482 | this._idPool = []; // unused ids that have been garbage collected 483 | this.exited = false; // whether the Go program has exited 484 | 485 | // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. 486 | let offset = 4096; 487 | 488 | const strPtr = (str) => { 489 | const ptr = offset; 490 | const bytes = encoder.encode(str + "\0"); 491 | new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes); 492 | offset += bytes.length; 493 | if (offset % 8 !== 0) { 494 | offset += 8 - (offset % 8); 495 | } 496 | return ptr; 497 | }; 498 | 499 | const argc = this.argv.length; 500 | 501 | const argvPtrs = []; 502 | this.argv.forEach((arg) => { 503 | argvPtrs.push(strPtr(arg)); 504 | }); 505 | argvPtrs.push(0); 506 | 507 | const keys = Object.keys(this.env).sort(); 508 | keys.forEach((key) => { 509 | argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); 510 | }); 511 | argvPtrs.push(0); 512 | 513 | const argv = offset; 514 | argvPtrs.forEach((ptr) => { 515 | this.mem.setUint32(offset, ptr, true); 516 | this.mem.setUint32(offset + 4, 0, true); 517 | offset += 8; 518 | }); 519 | 520 | // The linker guarantees global data starts from at least wasmMinDataAddr. 521 | // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr. 522 | const wasmMinDataAddr = 4096 + 8192; 523 | if (offset >= wasmMinDataAddr) { 524 | throw new Error("total length of command line and environment variables exceeds limit"); 525 | } 526 | 527 | this._inst.exports.run(argc, argv); 528 | if (this.exited) { 529 | this._resolveExitPromise(); 530 | } 531 | await this._exitPromise; 532 | } 533 | 534 | _resume() { 535 | if (this.exited) { 536 | throw new Error("Go program has already exited"); 537 | } 538 | this._inst.exports.resume(); 539 | if (this.exited) { 540 | this._resolveExitPromise(); 541 | } 542 | } 543 | 544 | _makeFuncWrapper(id) { 545 | const go = this; 546 | return function () { 547 | const event = { id: id, this: this, args: arguments }; 548 | go._pendingEvent = event; 549 | go._resume(); 550 | return event.result; 551 | }; 552 | } 553 | } 554 | })(); 555 | -------------------------------------------------------------------------------- /cmd/cli/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io" 7 | "os" 8 | "path/filepath" 9 | 10 | "github.com/lightstep/otel-config-validator/types" 11 | "github.com/lightstep/otel-config-validator/validator" 12 | "github.com/lightstep/otel-config-validator/version" 13 | ) 14 | 15 | func main() { 16 | 17 | filename := flag.String("f", "", "collector yaml file") 18 | ver := flag.Bool("version", false, "prints current version") 19 | stdin := flag.Bool("stdin", false, "read from stdin") 20 | json := flag.Bool("json", false, "output json") 21 | 22 | flag.Parse() 23 | 24 | if *ver { 25 | fmt.Printf("otel-config-validator version: %v\n", version.ValidatorVersion) 26 | os.Exit(0) 27 | } 28 | 29 | isValid := false 30 | var content []byte 31 | var err error 32 | var summary *types.PipelineOutput 33 | if *stdin { 34 | content, err = io.ReadAll(os.Stdin) 35 | if err != nil { 36 | panic(err) 37 | } 38 | } else if len(*filename) > 0 { 39 | content, err = os.ReadFile(filepath.Clean(*filename)) 40 | if err != nil { 41 | fmt.Printf("error reading config file: %v", err) 42 | os.Exit(1) 43 | } 44 | } else { 45 | fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) 46 | flag.PrintDefaults() 47 | os.Exit(0) 48 | } 49 | 50 | cfg, err := validator.IsValid(content) 51 | if cfg != nil && err == nil { 52 | isValid = true 53 | } 54 | 55 | summary = types.NewPipelineOutput(isValid, *filename, cfg, err) 56 | if *json { 57 | fmt.Printf("%s", summary.JSONString()) 58 | } else { 59 | fmt.Printf("%s", summary.String()) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "syscall/js" 6 | 7 | "github.com/lightstep/otel-config-validator/validator" 8 | ) 9 | 10 | func validateWrapper() js.Func { 11 | jsonFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} { 12 | if len(args) != 1 { 13 | return "Invalid no of arguments passed" 14 | } 15 | inputYAML := args[0].String() 16 | fmt.Printf("input %s\n", inputYAML) 17 | cfg, err := validator.IsValid([]byte(inputYAML)) 18 | if cfg == nil { 19 | fmt.Printf("unable to parse YAML %s\n", err) 20 | return err.Error() 21 | } 22 | if err == nil { 23 | return "" 24 | } 25 | 26 | return err.Error() 27 | }) 28 | return jsonFunc 29 | } 30 | 31 | func main() { 32 | fmt.Println("Go WASM Module Loaded") 33 | js.Global().Set("validateYAML", validateWrapper()) 34 | <-make(chan bool) 35 | } 36 | -------------------------------------------------------------------------------- /cmd/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | func main() { 10 | port := flag.String("p", "8100", "port to serve on") 11 | directory := flag.String("d", "./assets", "the directory of static file to host") 12 | flag.Parse() 13 | 14 | http.Handle("/", http.FileServer(http.Dir(*directory))) 15 | 16 | log.Printf("Serving %s on HTTP port: %s\n", *directory, *port) 17 | log.Fatal(http.ListenAndServe(":"+*port, nil)) 18 | } 19 | -------------------------------------------------------------------------------- /configunmarshaler/defaultunmarshaler.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package configunmarshaler // import "go.opentelemetry.io/collector/service/internal/configunmarshaler" 16 | 17 | import ( 18 | "fmt" 19 | "reflect" 20 | 21 | "go.uber.org/zap/zapcore" 22 | 23 | "go.opentelemetry.io/collector/component" 24 | "go.opentelemetry.io/collector/config" 25 | "go.opentelemetry.io/collector/config/configtelemetry" 26 | "go.opentelemetry.io/collector/confmap" 27 | "go.opentelemetry.io/collector/service/telemetry" 28 | ) 29 | 30 | // These are errors that can be returned by Unmarshal(). Note that error codes are not part 31 | // of Unmarshal()'s public API, they are for internal unit testing only. 32 | type configErrorCode int 33 | 34 | const ( 35 | // Skip 0, start errors codes from 1. 36 | _ configErrorCode = iota 37 | 38 | errUnmarshalTopLevelStructure 39 | errUnmarshalExtension 40 | errUnmarshalReceiver 41 | errUnmarshalProcessor 42 | errUnmarshalExporter 43 | errUnmarshalService 44 | ) 45 | 46 | type configError struct { 47 | // the original error. 48 | error 49 | 50 | // internal error code. 51 | code configErrorCode 52 | } 53 | 54 | // YAML top-level configuration keys. 55 | const ( 56 | // extensionsKeyName is the configuration key name for extensions section. 57 | extensionsKeyName = "extensions" 58 | 59 | // receiversKeyName is the configuration key name for receivers section. 60 | receiversKeyName = "receivers" 61 | 62 | // exportersKeyName is the configuration key name for exporters section. 63 | exportersKeyName = "exporters" 64 | 65 | // processorsKeyName is the configuration key name for processors section. 66 | processorsKeyName = "processors" 67 | 68 | // pipelinesKeyName is the configuration key name for pipelines section. 69 | pipelinesKeyName = "pipelines" 70 | ) 71 | 72 | type configSettings struct { 73 | Receivers map[config.ComponentID]map[string]interface{} `mapstructure:"receivers"` 74 | Processors map[config.ComponentID]map[string]interface{} `mapstructure:"processors"` 75 | Exporters map[config.ComponentID]map[string]interface{} `mapstructure:"exporters"` 76 | Extensions map[config.ComponentID]map[string]interface{} `mapstructure:"extensions"` 77 | Service map[string]interface{} `mapstructure:"service"` 78 | } 79 | 80 | type ConfigUnmarshaler struct{} 81 | 82 | // New returns a default ConfigUnmarshaler that unmarshalls every component's configuration 83 | // using the custom unmarshaler if present or default strict unmarshaler otherwise. 84 | func New() ConfigUnmarshaler { 85 | return ConfigUnmarshaler{} 86 | } 87 | 88 | // Unmarshal the config.Config from a confmap.Conf. 89 | // After the config is unmarshalled, `Validate()` must be called to validate. 90 | func (ConfigUnmarshaler) Unmarshal(v *confmap.Conf, factories component.Factories) (*config.Config, error) { 91 | var cfg config.Config 92 | 93 | // Unmarshal top level sections and validate. 94 | rawCfg := configSettings{} 95 | if err := v.Unmarshal(&rawCfg, confmap.WithErrorUnused()); err != nil { 96 | return nil, configError{ 97 | error: fmt.Errorf("error reading top level configuration sections: %w", err), 98 | code: errUnmarshalTopLevelStructure, 99 | } 100 | } 101 | 102 | var err error 103 | if cfg.Extensions, err = unmarshalExtensions(rawCfg.Extensions, factories.Extensions); err != nil { 104 | return nil, configError{ 105 | error: err, 106 | code: errUnmarshalExtension, 107 | } 108 | } 109 | 110 | if cfg.Receivers, err = unmarshalReceivers(rawCfg.Receivers, factories.Receivers); err != nil { 111 | return nil, configError{ 112 | error: err, 113 | code: errUnmarshalReceiver, 114 | } 115 | } 116 | 117 | if cfg.Processors, err = unmarshalProcessors(rawCfg.Processors, factories.Processors); err != nil { 118 | return nil, configError{ 119 | error: err, 120 | code: errUnmarshalProcessor, 121 | } 122 | } 123 | 124 | if cfg.Exporters, err = unmarshalExporters(rawCfg.Exporters, factories.Exporters); err != nil { 125 | return nil, configError{ 126 | error: err, 127 | code: errUnmarshalExporter, 128 | } 129 | } 130 | 131 | if cfg.Service, err = unmarshalService(rawCfg.Service); err != nil { 132 | return nil, configError{ 133 | error: err, 134 | code: errUnmarshalService, 135 | } 136 | } 137 | 138 | return &cfg, nil 139 | } 140 | 141 | func unmarshalExtensions(exts map[config.ComponentID]map[string]interface{}, factories map[config.Type]component.ExtensionFactory) (map[config.ComponentID]config.Extension, error) { 142 | // Prepare resulting map. 143 | extensions := make(map[config.ComponentID]config.Extension) 144 | 145 | // Iterate over extensions and create a config for each. 146 | for id, value := range exts { 147 | // Find extension factory based on "type" that we read from config source. 148 | factory, ok := factories[id.Type()] 149 | if !ok { 150 | return nil, errorUnknownType(extensionsKeyName, id, reflect.ValueOf(factories).MapKeys()) 151 | } 152 | 153 | // Create the default config for this extension. 154 | extensionCfg := factory.CreateDefaultConfig() 155 | extensionCfg.SetIDName(id.Name()) 156 | 157 | // Now that the default config struct is created we can Unmarshal into it, 158 | // and it will apply user-defined config on top of the default. 159 | if err := config.UnmarshalExtension(confmap.NewFromStringMap(value), extensionCfg); err != nil { 160 | return nil, errorUnmarshalError(extensionsKeyName, id, err) 161 | } 162 | 163 | extensions[id] = extensionCfg 164 | } 165 | 166 | return extensions, nil 167 | } 168 | 169 | func unmarshalService(srvRaw map[string]interface{}) (config.Service, error) { 170 | // Setup default telemetry values as in service/logger.go. 171 | // TODO: Add a component.ServiceFactory to allow this to be defined by the Service. 172 | srv := config.Service{ 173 | Telemetry: telemetry.Config{ 174 | Logs: telemetry.LogsConfig{ 175 | Level: zapcore.InfoLevel, 176 | Development: false, 177 | Encoding: "console", 178 | OutputPaths: []string{"stderr"}, 179 | ErrorOutputPaths: []string{"stderr"}, 180 | DisableCaller: false, 181 | DisableStacktrace: false, 182 | InitialFields: map[string]interface{}(nil), 183 | }, 184 | Metrics: defaultServiceTelemetryMetricsSettings(), 185 | }, 186 | } 187 | 188 | if err := confmap.NewFromStringMap(srvRaw).Unmarshal(&srv, confmap.WithErrorUnused()); err != nil { 189 | return srv, fmt.Errorf("error reading service configuration: %w", err) 190 | } 191 | 192 | for id := range srv.Pipelines { 193 | if id.Type() != config.TracesDataType && id.Type() != config.MetricsDataType && id.Type() != config.LogsDataType { 194 | return srv, fmt.Errorf("unknown %q datatype %q for %v", pipelinesKeyName, id.Type(), id) 195 | } 196 | } 197 | return srv, nil 198 | } 199 | 200 | func defaultServiceTelemetryMetricsSettings() telemetry.MetricsConfig { 201 | return telemetry.MetricsConfig{ 202 | Level: configtelemetry.LevelBasic, //nolint:staticcheck 203 | Address: ":8888", 204 | } 205 | } 206 | 207 | // LoadReceiver loads a receiver config from componentConfig using the provided factories. 208 | func LoadReceiver(componentConfig *confmap.Conf, id config.ComponentID, factory component.ReceiverFactory) (config.Receiver, error) { 209 | // Create the default config for this receiver. 210 | receiverCfg := factory.CreateDefaultConfig() 211 | receiverCfg.SetIDName(id.Name()) 212 | 213 | // Now that the default config struct is created we can Unmarshal into it, 214 | // and it will apply user-defined config on top of the default. 215 | if err := config.UnmarshalReceiver(componentConfig, receiverCfg); err != nil { 216 | return nil, errorUnmarshalError(receiversKeyName, id, err) 217 | } 218 | 219 | return receiverCfg, nil 220 | } 221 | 222 | func unmarshalReceivers(recvs map[config.ComponentID]map[string]interface{}, factories map[config.Type]component.ReceiverFactory) (map[config.ComponentID]config.Receiver, error) { 223 | // Prepare resulting map. 224 | receivers := make(map[config.ComponentID]config.Receiver) 225 | 226 | // Iterate over input map and create a config for each. 227 | for id, value := range recvs { 228 | // Find receiver factory based on "type" that we read from config source. 229 | factory := factories[id.Type()] 230 | if factory == nil { 231 | return nil, errorUnknownType(receiversKeyName, id, reflect.ValueOf(factories).MapKeys()) 232 | } 233 | 234 | receiverCfg, err := LoadReceiver(confmap.NewFromStringMap(value), id, factory) 235 | if err != nil { 236 | // LoadReceiver already wraps the error. 237 | return nil, err 238 | } 239 | 240 | receivers[id] = receiverCfg 241 | } 242 | 243 | return receivers, nil 244 | } 245 | 246 | func unmarshalExporters(exps map[config.ComponentID]map[string]interface{}, factories map[config.Type]component.ExporterFactory) (map[config.ComponentID]config.Exporter, error) { 247 | // Prepare resulting map. 248 | exporters := make(map[config.ComponentID]config.Exporter) 249 | 250 | // Iterate over Exporters and create a config for each. 251 | for id, value := range exps { 252 | // Find exporter factory based on "type" that we read from config source. 253 | factory := factories[id.Type()] 254 | if factory == nil { 255 | return nil, errorUnknownType(exportersKeyName, id, reflect.ValueOf(factories).MapKeys()) 256 | } 257 | 258 | // Create the default config for this exporter. 259 | exporterCfg := factory.CreateDefaultConfig() 260 | exporterCfg.SetIDName(id.Name()) 261 | 262 | // Now that the default config struct is created we can Unmarshal into it, 263 | // and it will apply user-defined config on top of the default. 264 | if err := config.UnmarshalExporter(confmap.NewFromStringMap(value), exporterCfg); err != nil { 265 | return nil, errorUnmarshalError(exportersKeyName, id, err) 266 | } 267 | 268 | exporters[id] = exporterCfg 269 | } 270 | 271 | return exporters, nil 272 | } 273 | 274 | func unmarshalProcessors(procs map[config.ComponentID]map[string]interface{}, factories map[config.Type]component.ProcessorFactory) (map[config.ComponentID]config.Processor, error) { 275 | // Prepare resulting map. 276 | processors := make(map[config.ComponentID]config.Processor) 277 | 278 | // Iterate over processors and create a config for each. 279 | for id, value := range procs { 280 | // Find processor factory based on "type" that we read from config source. 281 | factory := factories[id.Type()] 282 | if factory == nil { 283 | return nil, errorUnknownType(processorsKeyName, id, reflect.ValueOf(factories).MapKeys()) 284 | } 285 | 286 | // Create the default config for this processor. 287 | processorCfg := factory.CreateDefaultConfig() 288 | processorCfg.SetIDName(id.Name()) 289 | 290 | // Now that the default config struct is created we can Unmarshal into it, 291 | // and it will apply user-defined config on top of the default. 292 | if err := config.UnmarshalProcessor(confmap.NewFromStringMap(value), processorCfg); err != nil { 293 | return nil, errorUnmarshalError(processorsKeyName, id, err) 294 | } 295 | 296 | processors[id] = processorCfg 297 | } 298 | 299 | return processors, nil 300 | } 301 | 302 | func errorUnknownType(component string, id config.ComponentID, factories []reflect.Value) error { 303 | return fmt.Errorf("unknown %s type %q for %q (valid values: %v)", component, id.Type(), id, factories) 304 | } 305 | 306 | func errorUnmarshalError(component string, id config.ComponentID, err error) error { 307 | return fmt.Errorf("error reading %s configuration for %q: %w", component, id, err) 308 | } 309 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/lightstep/otel-config-validator 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/open-telemetry/opentelemetry-collector-contrib/exporter/awsemfexporter v0.63.0 7 | github.com/open-telemetry/opentelemetry-collector-contrib/exporter/awsxrayexporter v0.63.0 8 | github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter v0.63.0 9 | github.com/open-telemetry/opentelemetry-collector-contrib/exporter/dynatraceexporter v0.63.0 10 | github.com/open-telemetry/opentelemetry-collector-contrib/exporter/fileexporter v0.63.0 11 | github.com/open-telemetry/opentelemetry-collector-contrib/exporter/logzioexporter v0.63.0 12 | github.com/open-telemetry/opentelemetry-collector-contrib/exporter/prometheusexporter v0.62.0 13 | github.com/open-telemetry/opentelemetry-collector-contrib/exporter/prometheusremotewriteexporter v0.62.0 14 | github.com/open-telemetry/opentelemetry-collector-contrib/exporter/sapmexporter v0.63.0 15 | github.com/open-telemetry/opentelemetry-collector-contrib/exporter/signalfxexporter v0.60.0 16 | github.com/open-telemetry/opentelemetry-collector-contrib/processor/attributesprocessor v0.62.0 17 | github.com/open-telemetry/opentelemetry-collector-contrib/processor/cumulativetodeltaprocessor v0.62.0 18 | github.com/open-telemetry/opentelemetry-collector-contrib/processor/deltatorateprocessor v0.62.0 19 | github.com/open-telemetry/opentelemetry-collector-contrib/processor/filterprocessor v0.62.0 20 | github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricsgenerationprocessor v0.62.0 21 | github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstransformprocessor v0.62.0 22 | github.com/open-telemetry/opentelemetry-collector-contrib/processor/probabilisticsamplerprocessor v0.62.0 23 | github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourceprocessor v0.62.0 24 | github.com/open-telemetry/opentelemetry-collector-contrib/processor/spanprocessor v0.62.0 25 | github.com/open-telemetry/opentelemetry-collector-contrib/receiver/awsecscontainermetricsreceiver v0.63.0 26 | github.com/open-telemetry/opentelemetry-collector-contrib/receiver/awsxrayreceiver v0.63.0 27 | github.com/open-telemetry/opentelemetry-collector-contrib/receiver/statsdreceiver v0.63.0 28 | github.com/open-telemetry/opentelemetry-collector-contrib/receiver/zipkinreceiver v0.63.0 29 | go.opentelemetry.io/collector v0.63.1 30 | go.opentelemetry.io/collector/exporter/loggingexporter v0.63.1 31 | go.opentelemetry.io/collector/exporter/otlpexporter v0.63.1 32 | go.opentelemetry.io/collector/exporter/otlphttpexporter v0.63.1 33 | go.uber.org/zap v1.23.0 34 | ) 35 | 36 | require ( 37 | cloud.google.com/go/compute v1.10.0 // indirect 38 | github.com/DataDog/agent-payload/v5 v5.0.37 // indirect 39 | github.com/DataDog/datadog-agent/pkg/obfuscate v0.40.0-rc.2 // indirect 40 | github.com/DataDog/datadog-agent/pkg/otlp/model v0.40.0-rc.2.0.20221013201707-c46e9de16283 // indirect 41 | github.com/DataDog/datadog-agent/pkg/quantile v0.40.0-rc.2.0.20221013201707-c46e9de16283 // indirect 42 | github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.40.0-rc.2 // indirect 43 | github.com/DataDog/datadog-agent/pkg/trace v0.40.0-rc.2.0.20221013201707-c46e9de16283 // indirect 44 | github.com/DataDog/datadog-agent/pkg/util/cgroups v0.40.0-rc.2 // indirect 45 | github.com/DataDog/datadog-agent/pkg/util/log v0.40.0-rc.2 // indirect 46 | github.com/DataDog/datadog-agent/pkg/util/scrubber v0.40.0-rc.2 // indirect 47 | github.com/DataDog/datadog-api-client-go/v2 v2.4.0 // indirect 48 | github.com/DataDog/datadog-go/v5 v5.1.1 // indirect 49 | github.com/DataDog/gohai v0.0.0-20220718130825-1776f9beb9cc // indirect 50 | github.com/DataDog/sketches-go v1.4.1 // indirect 51 | github.com/DataDog/zstd v1.5.0 // indirect 52 | github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v0.34.1 // indirect 53 | github.com/Microsoft/go-winio v0.5.2 // indirect 54 | github.com/antonmedv/expr v1.9.0 // indirect 55 | github.com/apache/thrift v0.17.0 // indirect 56 | github.com/aws/aws-sdk-go v1.44.122 // indirect 57 | github.com/beorn7/perks v1.0.1 // indirect 58 | github.com/cenkalti/backoff v2.2.1+incompatible // indirect 59 | github.com/cenkalti/backoff/v4 v4.1.3 // indirect 60 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 61 | github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 // indirect 62 | github.com/containerd/cgroups v1.0.4 // indirect 63 | github.com/coreos/go-systemd/v22 v22.3.2 // indirect 64 | github.com/davecgh/go-spew v1.1.1 // indirect 65 | github.com/dgraph-io/ristretto v0.1.0 // indirect 66 | github.com/docker/go-units v0.4.0 // indirect 67 | github.com/dustin/go-humanize v1.0.0 // indirect 68 | github.com/dynatrace-oss/dynatrace-metric-utils-go v0.5.0 // indirect 69 | github.com/emicklei/go-restful/v3 v3.8.0 // indirect 70 | github.com/fatih/color v1.13.0 // indirect 71 | github.com/felixge/httpsnoop v1.0.3 // indirect 72 | github.com/fsnotify/fsnotify v1.6.0 // indirect 73 | github.com/go-logfmt/logfmt v0.5.1 // indirect 74 | github.com/go-logr/logr v1.2.3 // indirect 75 | github.com/go-logr/stdr v1.2.2 // indirect 76 | github.com/go-ole/go-ole v1.2.6 // indirect 77 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 78 | github.com/go-openapi/jsonreference v0.20.0 // indirect 79 | github.com/go-openapi/swag v0.22.1 // indirect 80 | github.com/go-stack/stack v1.8.1 // indirect 81 | github.com/gobwas/glob v0.2.3 // indirect 82 | github.com/godbus/dbus/v5 v5.0.6 // indirect 83 | github.com/gogo/protobuf v1.3.2 // indirect 84 | github.com/golang/glog v1.0.0 // indirect 85 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 86 | github.com/golang/protobuf v1.5.2 // indirect 87 | github.com/golang/snappy v0.0.4 // indirect 88 | github.com/google/gnostic v0.5.7-v3refs // indirect 89 | github.com/google/gofuzz v1.2.0 // indirect 90 | github.com/google/uuid v1.3.0 // indirect 91 | github.com/hashicorp/errwrap v1.1.0 // indirect 92 | github.com/hashicorp/go-hclog v1.3.1 // indirect 93 | github.com/hashicorp/go-multierror v1.1.1 // indirect 94 | github.com/imdario/mergo v0.3.12 // indirect 95 | github.com/jaegertracing/jaeger v1.38.1 // indirect 96 | github.com/jmespath/go-jmespath v0.4.0 // indirect 97 | github.com/josharian/intern v1.0.0 // indirect 98 | github.com/json-iterator/go v1.1.12 // indirect 99 | github.com/karrick/godirwalk v1.17.0 // indirect 100 | github.com/klauspost/compress v1.15.11 // indirect 101 | github.com/lightstep/go-expohisto v1.0.0 // indirect 102 | github.com/lufia/plan9stats v0.0.0-20220517141722-cf486979b281 // indirect 103 | github.com/mailru/easyjson v0.7.7 // indirect 104 | github.com/mattn/go-colorable v0.1.12 // indirect 105 | github.com/mattn/go-isatty v0.0.14 // indirect 106 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect 107 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 108 | github.com/modern-go/reflect2 v1.0.2 // indirect 109 | github.com/mostynb/go-grpc-compression v1.1.17 // indirect 110 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 111 | github.com/open-telemetry/opentelemetry-collector-contrib/internal/aws/awsutil v0.63.0 // indirect 112 | github.com/open-telemetry/opentelemetry-collector-contrib/internal/aws/cwlogs v0.63.0 // indirect 113 | github.com/open-telemetry/opentelemetry-collector-contrib/internal/aws/ecsutil v0.63.0 // indirect 114 | github.com/open-telemetry/opentelemetry-collector-contrib/internal/aws/metrics v0.63.0 // indirect 115 | github.com/open-telemetry/opentelemetry-collector-contrib/internal/aws/proxy v0.63.0 // indirect 116 | github.com/open-telemetry/opentelemetry-collector-contrib/internal/aws/xray v0.63.0 // indirect 117 | github.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.63.0 // indirect 118 | github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.63.0 // indirect 119 | github.com/open-telemetry/opentelemetry-collector-contrib/internal/k8sconfig v0.63.0 // indirect 120 | github.com/open-telemetry/opentelemetry-collector-contrib/internal/metadataproviders v0.63.0 // indirect 121 | github.com/open-telemetry/opentelemetry-collector-contrib/internal/sharedcomponent v0.63.0 // indirect 122 | github.com/open-telemetry/opentelemetry-collector-contrib/internal/splunk v0.63.0 // indirect 123 | github.com/open-telemetry/opentelemetry-collector-contrib/pkg/batchperresourceattr v0.63.0 // indirect 124 | github.com/open-telemetry/opentelemetry-collector-contrib/pkg/experimentalmetricmetadata v0.63.0 // indirect 125 | github.com/open-telemetry/opentelemetry-collector-contrib/pkg/resourcetotelemetry v0.63.0 // indirect 126 | github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/jaeger v0.63.0 // indirect 127 | github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheus v0.62.0 // indirect 128 | github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheusremotewrite v0.62.0 // indirect 129 | github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/signalfx v0.63.0 // indirect 130 | github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/zipkin v0.63.0 // indirect 131 | github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 // indirect 132 | github.com/openshift/api v0.0.0-20210521075222-e273a339932a // indirect 133 | github.com/openshift/client-go v0.0.0-20210521082421-73d9475a9142 // indirect 134 | github.com/opentracing/opentracing-go v1.2.0 // indirect 135 | github.com/openzipkin/zipkin-go v0.4.1 // indirect 136 | github.com/patrickmn/go-cache v2.1.0+incompatible // indirect 137 | github.com/philhofer/fwd v1.1.1 // indirect 138 | github.com/pkg/errors v0.9.1 // indirect 139 | github.com/pmezard/go-difflib v1.0.0 // indirect 140 | github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect 141 | github.com/prometheus/client_golang v1.13.0 // indirect 142 | github.com/prometheus/client_model v0.3.0 // indirect 143 | github.com/prometheus/common v0.37.0 // indirect 144 | github.com/prometheus/procfs v0.8.0 // indirect 145 | github.com/prometheus/prometheus v0.38.0 // indirect 146 | github.com/rs/cors v1.8.2 // indirect 147 | github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect 148 | github.com/shirou/gopsutil/v3 v3.22.9 // indirect 149 | github.com/signalfx/com_signalfx_metrics_protobuf v0.0.3 // indirect 150 | github.com/signalfx/gohistogram v0.0.0-20160107210732-1ccfd2ff5083 // indirect 151 | github.com/signalfx/golib/v3 v3.3.46 // indirect 152 | github.com/signalfx/sapm-proto v0.12.0 // indirect 153 | github.com/signalfx/signalfx-agent/pkg/apm v0.0.0-20201202163743-65b4fa925fc8 // indirect 154 | github.com/spf13/cast v1.5.0 // indirect 155 | github.com/spf13/pflag v1.0.5 // indirect 156 | github.com/stretchr/objx v0.5.0 // indirect 157 | github.com/stretchr/testify v1.8.1 // indirect 158 | github.com/theupdateframework/go-tuf v0.3.0 // indirect 159 | github.com/tidwall/gjson v1.10.2 // indirect 160 | github.com/tidwall/match v1.1.1 // indirect 161 | github.com/tidwall/pretty v1.2.0 // indirect 162 | github.com/tidwall/tinylru v1.1.0 // indirect 163 | github.com/tidwall/wal v1.1.7 // indirect 164 | github.com/tinylib/msgp v1.1.6 // indirect 165 | github.com/tklauser/go-sysconf v0.3.10 // indirect 166 | github.com/tklauser/numcpus v0.5.0 // indirect 167 | github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect 168 | github.com/uber/jaeger-lib v2.4.1+incompatible // indirect 169 | github.com/yusufpapurcu/wmi v1.2.2 // indirect 170 | go.opencensus.io v0.23.0 // indirect 171 | go.opentelemetry.io/collector/pdata v0.63.1 // indirect 172 | go.opentelemetry.io/collector/semconv v0.63.1 // indirect 173 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.36.4 // indirect 174 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.36.4 // indirect 175 | go.opentelemetry.io/contrib/zpages v0.36.4 // indirect 176 | go.opentelemetry.io/otel v1.11.1 // indirect 177 | go.opentelemetry.io/otel/metric v0.33.0 // indirect 178 | go.opentelemetry.io/otel/sdk v1.11.1 // indirect 179 | go.opentelemetry.io/otel/trace v1.11.1 // indirect 180 | golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect 181 | golang.org/x/net v0.0.0-20221004154528-8021a29435af // indirect 182 | golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect 183 | golang.org/x/sys v0.1.0 // indirect 184 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect 185 | golang.org/x/text v0.4.0 // indirect 186 | golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect 187 | gonum.org/v1/gonum v0.12.0 // indirect 188 | google.golang.org/appengine v1.6.7 // indirect 189 | google.golang.org/genproto v0.0.0-20221018160656-63c7b68cfc55 // indirect 190 | google.golang.org/grpc v1.50.1 // indirect 191 | google.golang.org/protobuf v1.28.1 // indirect 192 | gopkg.in/inf.v0 v0.9.1 // indirect 193 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect 194 | gopkg.in/yaml.v2 v2.4.0 // indirect 195 | gopkg.in/zorkian/go-datadog-api.v2 v2.30.0 // indirect 196 | k8s.io/api v0.25.3 // indirect 197 | k8s.io/apimachinery v0.25.3 // indirect 198 | k8s.io/client-go v0.25.3 // indirect 199 | k8s.io/klog/v2 v2.70.1 // indirect 200 | k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect 201 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect 202 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 203 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 204 | sigs.k8s.io/yaml v1.3.0 // indirect 205 | ) 206 | 207 | require ( 208 | github.com/knadh/koanf v1.4.4 // indirect 209 | github.com/mitchellh/copystructure v1.2.0 // indirect 210 | github.com/mitchellh/mapstructure v1.5.0 // indirect 211 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 212 | go.uber.org/atomic v1.10.0 // indirect 213 | go.uber.org/multierr v1.8.0 // indirect 214 | gopkg.in/yaml.v3 v3.0.1 215 | ) 216 | 217 | replace github.com/lightstep/otel-config-validator/configunmarshaler v0.0.0 => ./configunmarshaler 218 | -------------------------------------------------------------------------------- /otel-vscode/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "plugins": [ 9 | "@typescript-eslint" 10 | ], 11 | "rules": { 12 | "@typescript-eslint/naming-convention": "warn", 13 | "@typescript-eslint/semi": "warn", 14 | "curly": "warn", 15 | "eqeqeq": "warn", 16 | "no-throw-literal": "warn", 17 | "semi": "off" 18 | }, 19 | "ignorePatterns": [ 20 | "out", 21 | "dist", 22 | "**/*.d.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /otel-vscode/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | bin/ 4 | *.vsix -------------------------------------------------------------------------------- /otel-vscode/.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | src/** 4 | .gitignore 5 | .yarnrc 6 | vsc-extension-quickstart.md 7 | **/tsconfig.json 8 | **/.eslintrc.json 9 | **/*.map 10 | **/*.ts 11 | -------------------------------------------------------------------------------- /otel-vscode/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "otel-vscode" extension will be documented in this file. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | ## [Unreleased] 8 | 9 | - Initial release -------------------------------------------------------------------------------- /otel-vscode/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 [yyyy] [name of copyright owner] 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 | -------------------------------------------------------------------------------- /otel-vscode/README.md: -------------------------------------------------------------------------------- 1 | # otel-vscode 2 | 3 | Experimental Visual Studio Code extension for working with OpenTelemetry. 4 | 5 | ## Features 6 | 7 | Currently, only validation of OpenTelemetry Collector configuration files is supported. 8 | 9 | ## Requirements 10 | 11 | * Mac or Linux (ARM or AMD64 support) 12 | * Windows (AMD64 support only) 13 | 14 | ## Known Issues 15 | 16 | * TBD :) 17 | 18 | ## Building and Installing Manually 19 | 20 | ``` 21 | npm install 22 | vsce package 23 | code --install-extension otel-vscode-version.vsix 24 | ``` 25 | 26 | ## For more information 27 | 28 | * TBD :) 29 | 30 | **Enjoy!** 31 | -------------------------------------------------------------------------------- /otel-vscode/lib/download.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 'use strict'; 3 | 4 | const path = require('path'); 5 | const fs = require('fs'); 6 | const os = require('os'); 7 | const https = require('https'); 8 | const util = require('util'); 9 | const url = require('url'); 10 | const URL = url.URL; 11 | const child_process = require('child_process'); 12 | const proxy_from_env = require('proxy-from-env'); 13 | 14 | const packageVersion = require('../package.json').version; 15 | const tmpDir = path.join(os.tmpdir(), `vscode-otel-config-validator-cache-${packageVersion}`); 16 | 17 | const fsUnlink = util.promisify(fs.unlink); 18 | const fsExists = util.promisify(fs.exists); 19 | const fsMkdir = util.promisify(fs.mkdir); 20 | 21 | const isWindows = os.platform() === 'win32'; 22 | 23 | const REPO = 'lightstep/otel-config-validator'; 24 | 25 | function isGithubUrl(_url) { 26 | return url.parse(_url).hostname === 'api.github.com'; 27 | } 28 | 29 | function downloadWin(url, dest, opts) { 30 | return new Promise((resolve, reject) => { 31 | let userAgent; 32 | if (opts.headers['user-agent']) { 33 | userAgent = opts.headers['user-agent']; 34 | delete opts.headers['user-agent']; 35 | } 36 | const headerValues = Object.keys(opts.headers) 37 | .map(key => `\\"${key}\\"=\\"${opts.headers[key]}\\"`) 38 | .join('; '); 39 | const headers = `@{${headerValues}}`; 40 | console.log('Downloading with Invoke-WebRequest'); 41 | dest = sanitizePathForPowershell(dest); 42 | let iwrCmd = `[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -URI ${url} -UseBasicParsing -OutFile ${dest} -Headers ${headers}`; 43 | if (userAgent) { 44 | iwrCmd += ' -UserAgent ' + userAgent; 45 | } 46 | if (opts.proxy) { 47 | iwrCmd += ' -Proxy ' + opts.proxy; 48 | 49 | try { 50 | const { username, password } = new URL(opts.proxy); 51 | if (username && password) { 52 | const decodedPassword = decodeURIComponent(password); 53 | iwrCmd += ` -ProxyCredential (New-Object PSCredential ('${username}', (ConvertTo-SecureString '${decodedPassword}' -AsPlainText -Force)))`; 54 | } 55 | } catch (err) { 56 | reject(err); 57 | } 58 | } 59 | 60 | iwrCmd = `powershell "${iwrCmd}"`; 61 | 62 | child_process.exec(iwrCmd, err => { 63 | if (err) { 64 | reject(err); 65 | return; 66 | } 67 | resolve(); 68 | }); 69 | }); 70 | } 71 | 72 | function download(_url, dest, opts) { 73 | 74 | const proxy = proxy_from_env.getProxyForUrl(url.parse(_url)); 75 | if (proxy !== '') { 76 | var HttpsProxyAgent = require('https-proxy-agent'); 77 | opts = { 78 | ...opts, 79 | "agent": new HttpsProxyAgent(proxy), 80 | proxy 81 | }; 82 | } 83 | 84 | if (isWindows) { 85 | // This alternative strategy shouldn't be necessary but sometimes on Windows the file does not get closed, 86 | // so unzipping it fails, and I don't know why. 87 | return downloadWin(_url, dest, opts); 88 | } 89 | 90 | if (opts.headers && opts.headers.authorization && !isGithubUrl(_url)) { 91 | delete opts.headers.authorization; 92 | } 93 | 94 | return new Promise((resolve, reject) => { 95 | console.log(`Download options: ${JSON.stringify(opts)}`); 96 | const outFile = fs.createWriteStream(dest); 97 | const mergedOpts = { 98 | ...url.parse(_url), 99 | ...opts 100 | }; 101 | https.get(mergedOpts, response => { 102 | console.log('statusCode: ' + response.statusCode); 103 | if (response.statusCode === 302) { 104 | console.log('Following redirect to: ' + response.headers.location); 105 | return download(response.headers.location, dest, opts) 106 | .then(resolve, reject); 107 | } else if (response.statusCode !== 200) { 108 | reject(new Error('Download failed with ' + response.statusCode)); 109 | return; 110 | } 111 | 112 | response.pipe(outFile); 113 | outFile.on('finish', () => { 114 | resolve(); 115 | }); 116 | }).on('error', async err => { 117 | await fsUnlink(dest); 118 | reject(err); 119 | }); 120 | }); 121 | } 122 | 123 | function get(_url, opts) { 124 | console.log(`GET ${_url}`); 125 | 126 | const proxy = proxy_from_env.getProxyForUrl(url.parse(_url)); 127 | if (proxy !== '') { 128 | var HttpsProxyAgent = require('https-proxy-agent'); 129 | opts = { 130 | ...opts, 131 | "agent": new HttpsProxyAgent(proxy) 132 | }; 133 | } 134 | 135 | return new Promise((resolve, reject) => { 136 | let result = ''; 137 | opts = { 138 | ...url.parse(_url), 139 | ...opts 140 | }; 141 | https.get(opts, response => { 142 | if (response.statusCode !== 200) { 143 | reject(new Error('Request failed: ' + response.statusCode)); 144 | } 145 | 146 | response.on('data', d => { 147 | result += d.toString(); 148 | }); 149 | 150 | response.on('end', () => { 151 | resolve(result); 152 | }); 153 | 154 | response.on('error', e => { 155 | reject(e); 156 | }); 157 | }); 158 | }); 159 | } 160 | 161 | function getApiUrl(repo, tag) { 162 | return `https://api.github.com/repos/${repo}/releases/tags/${tag}`; 163 | } 164 | 165 | /** 166 | * @param {{ force: boolean; token: string; version: string; }} opts 167 | * @param {string} assetName 168 | * @param {string} downloadFolder 169 | */ 170 | async function getAssetFromGithubApi(opts, assetName, downloadFolder) { 171 | const assetDownloadPath = path.join(downloadFolder, assetName); 172 | 173 | // We can just use the cached binary 174 | if (!opts.force && await fsExists(assetDownloadPath)) { 175 | console.log('Using cached download: ' + assetDownloadPath); 176 | return assetDownloadPath; 177 | } 178 | 179 | const downloadOpts = { 180 | headers: { 181 | 'user-agent': 'vscode-otel-config-validator' 182 | } 183 | }; 184 | 185 | if (opts.token) { 186 | downloadOpts.headers.authorization = `token ${opts.token}`; 187 | } 188 | 189 | console.log(`Finding release for ${opts.version}`); 190 | const release = await get(getApiUrl(REPO, opts.version), downloadOpts); 191 | let jsonRelease; 192 | try { 193 | jsonRelease = JSON.parse(release); 194 | } catch (e) { 195 | throw new Error('Malformed API response: ' + e.stack); 196 | } 197 | 198 | if (!jsonRelease.assets) { 199 | throw new Error('Bad API response: ' + JSON.stringify(release)); 200 | } 201 | 202 | const asset = jsonRelease.assets.find(a => a.name === assetName); 203 | if (!asset) { 204 | throw new Error('Asset not found with name: ' + assetName); 205 | } 206 | 207 | console.log(`Downloading from ${asset.url}`); 208 | console.log(`Downloading to ${assetDownloadPath}`); 209 | 210 | downloadOpts.headers.accept = 'application/octet-stream'; 211 | await download(asset.url, assetDownloadPath, downloadOpts); 212 | } 213 | 214 | function unzipWindows(zipPath, destinationDir) { 215 | return new Promise((resolve, reject) => { 216 | zipPath = sanitizePathForPowershell(zipPath); 217 | destinationDir = sanitizePathForPowershell(destinationDir); 218 | const expandCmd = 'powershell -ExecutionPolicy Bypass -Command Expand-Archive ' + ['-Path', zipPath, '-DestinationPath', destinationDir, '-Force'].join(' '); 219 | child_process.exec(expandCmd, (err, _stdout, stderr) => { 220 | if (err) { 221 | reject(err); 222 | return; 223 | } 224 | 225 | if (stderr) { 226 | console.log(stderr); 227 | reject(new Error(stderr)); 228 | return; 229 | } 230 | 231 | console.log('Expand-Archive completed'); 232 | resolve(); 233 | }); 234 | }); 235 | } 236 | 237 | // Handle whitespace in filepath as powershell split's path with whitespaces 238 | function sanitizePathForPowershell(path) { 239 | path = path.replace(/ /g, '` '); // replace whitespace with "` " as solution provided here https://stackoverflow.com/a/18537344/7374562 240 | return path; 241 | } 242 | 243 | function untar(zipPath, destinationDir) { 244 | return new Promise((resolve, reject) => { 245 | const unzipProc = child_process.spawn('tar', ['xvf', zipPath, '-C', destinationDir], { stdio: 'inherit' }); 246 | unzipProc.on('error', err => { 247 | reject(err); 248 | }); 249 | unzipProc.on('close', code => { 250 | console.log(`tar xvf exited with ${code}`); 251 | if (code !== 0) { 252 | reject(new Error(`tar xvf exited with ${code}`)); 253 | return; 254 | } 255 | 256 | resolve(); 257 | }); 258 | }); 259 | } 260 | 261 | async function unzipRipgrep(zipPath, destinationDir, version) { 262 | if (isWindows) { 263 | await unzipWindows(zipPath, destinationDir); 264 | } else { 265 | await untar(zipPath, destinationDir); 266 | } 267 | 268 | const expectedName = path.join(destinationDir, `otel-config-validator_${version}`); 269 | if (await fsExists(expectedName)) { 270 | return expectedName; 271 | } 272 | 273 | if (await fsExists(expectedName + '.exe')) { 274 | return expectedName + '.exe'; 275 | } 276 | 277 | throw new Error(`Expecting otel-config-validator_${version} or otel-config-validator_${version}.exe unzipped into ${destinationDir}, didn't find one.`); 278 | } 279 | 280 | module.exports = async opts => { 281 | if (!opts.version) { 282 | return Promise.reject(new Error('Missing version')); 283 | } 284 | 285 | if (!opts.target) { 286 | return Promise.reject(new Error('Missing target')); 287 | } 288 | 289 | const extension = '.zip'; 290 | const assetName = ['otel-config-validator', opts.version.replace('v', ''), opts.target].join('_') + extension; 291 | 292 | if (!await fsExists(tmpDir)) { 293 | await fsMkdir(tmpDir); 294 | } 295 | 296 | const assetDownloadPath = path.join(tmpDir, assetName); 297 | try { 298 | await getAssetFromGithubApi(opts, assetName, tmpDir); 299 | } catch (e) { 300 | console.log('Deleting invalid download cache'); 301 | try { 302 | await fsUnlink(assetDownloadPath); 303 | } catch (e) { } 304 | 305 | throw e; 306 | } 307 | 308 | console.log(`Unzipping to ${opts.destDir}`); 309 | try { 310 | const destinationPath = await unzipRipgrep(assetDownloadPath, opts.destDir, opts.version); 311 | if (!isWindows) { 312 | await util.promisify(fs.chmod)(destinationPath, '755'); 313 | } 314 | } catch (e) { 315 | console.log('Deleting invalid download'); 316 | 317 | try { 318 | await fsUnlink(assetDownloadPath); 319 | } catch (e) { } 320 | 321 | throw e; 322 | } 323 | }; 324 | -------------------------------------------------------------------------------- /otel-vscode/lib/postinstall.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | "use strict"; 3 | 4 | // via https://github.com/microsoft/vscode-platform-specific-sample 5 | 6 | const os = require("os"); 7 | const fs = require("fs"); 8 | const path = require("path"); 9 | const util = require("util"); 10 | const child_process = require("child_process"); 11 | 12 | const download = require("./download"); 13 | 14 | const fsExists = util.promisify(fs.exists); 15 | const mkdir = util.promisify(fs.mkdir); 16 | const exec = util.promisify(child_process.exec); 17 | 18 | const forceInstall = process.argv.includes("--force"); 19 | if (forceInstall) { 20 | console.log("--force, ignoring caches"); 21 | } 22 | 23 | const VERSION = "v0.0.1"; 24 | const BIN_PATH = path.join(__dirname, "../bin"); 25 | 26 | process.on("unhandledRejection", (reason, promise) => { 27 | console.log("Unhandled rejection: ", promise, "reason:", reason); 28 | }); 29 | 30 | async function isMusl() { 31 | let stderr; 32 | try { 33 | stderr = (await exec("ldd --version")).stderr; 34 | } catch (err) { 35 | stderr = err.stderr; 36 | } 37 | if (stderr.indexOf("musl") > -1) { 38 | return true; 39 | } 40 | return false; 41 | } 42 | 43 | async function getTarget() { 44 | const arch = process.env.npm_config_arch || os.arch(); 45 | 46 | switch (os.platform()) { 47 | case "darwin": 48 | return arch === "arm64" ? "darwin_arm64" : "darwin_amd64"; 49 | case "win32": 50 | return arch === "x64" 51 | ? "windows_amd64" 52 | : arch === "arm" 53 | ? "windows_arm" 54 | : "i686-pc-windows-msvc"; 55 | case "linux": 56 | return arch === "x64" 57 | ? "linux_amd64" 58 | : arch === "arm" 59 | ? "linux_arm" 60 | : arch === "arm64" 61 | ? "aarch64-unknown-linux-gnu" 62 | : "i686-unknown-linux-musl"; 63 | default: 64 | throw new Error("Unknown platform: " + os.platform()); 65 | } 66 | } 67 | 68 | async function main() { 69 | const binExists = await fsExists(BIN_PATH); 70 | if (!forceInstall && binExists) { 71 | console.log("bin/ folder already exists, exiting"); 72 | process.exit(0); 73 | } 74 | 75 | if (!binExists) { 76 | await mkdir(BIN_PATH); 77 | } 78 | 79 | const opts = { 80 | version: VERSION, 81 | token: process.env["GITHUB_TOKEN"], 82 | target: await getTarget(), 83 | destDir: BIN_PATH, 84 | force: forceInstall, 85 | }; 86 | try { 87 | await download(opts); 88 | } catch (err) { 89 | console.error(`Downloading otel-config-validator failed: ${err.stack}`); 90 | process.exit(1); 91 | } 92 | } 93 | 94 | main(); 95 | -------------------------------------------------------------------------------- /otel-vscode/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "otel-vscode", 3 | "displayName": "otel-vscode", 4 | "description": "", 5 | "version": "0.0.1", 6 | "engines": { 7 | "vscode": "^1.73.0" 8 | }, 9 | "categories": [ 10 | "Other" 11 | ], 12 | "activationEvents": [ 13 | "onCommand:otel-vscode.validateOtelConfig" 14 | ], 15 | "repository": { 16 | "url": "https://github.com/lightstep/otel-config-validator" 17 | }, 18 | "main": "./out/extension.js", 19 | "contributes": { 20 | "commands": [ 21 | { 22 | "command": "otel-vscode.validateOtelConfig", 23 | "title": "Validate OpenTelemetry Collector Config" 24 | } 25 | ], 26 | "views": { 27 | "explorer": [ 28 | { 29 | "id": "otelCollectorPipeline", 30 | "name": "Pipelines" 31 | } 32 | ] 33 | } 34 | }, 35 | "scripts": { 36 | "vscode:prepublish": "npm run compile", 37 | "compile": "tsc -p ./", 38 | "watch": "tsc -watch -p ./", 39 | "pretest": "npm run compile && npm run lint", 40 | "lint": "eslint src --ext ts", 41 | "test": "node ./out/test/runTest.js", 42 | "postinstall": "node ./lib/postinstall.js" 43 | }, 44 | "dependencies": { 45 | "https-proxy-agent": "^5.0.0", 46 | "proxy-from-env": "^1.1.0" 47 | }, 48 | "devDependencies": { 49 | "@types/vscode": "^1.73.0", 50 | "@types/glob": "^8.0.0", 51 | "@types/mocha": "^10.0.0", 52 | "@types/node": "16.x", 53 | "@typescript-eslint/eslint-plugin": "^5.42.0", 54 | "@typescript-eslint/parser": "^5.42.0", 55 | "eslint": "^8.26.0", 56 | "glob": "^8.0.3", 57 | "mocha": "^10.1.0", 58 | "typescript": "^4.8.4", 59 | "@vscode/test-electron": "^2.2.0" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /otel-vscode/src/extension.ts: -------------------------------------------------------------------------------- 1 | // The module 'vscode' contains the VS Code extensibility API 2 | // Import the module and reference it with the alias vscode in your code below 3 | import * as vscode from "vscode"; 4 | 5 | import * as cp from "child_process"; 6 | import { PipelineProvider } from "./pipeline"; 7 | import { CollectorPipeline } from "./types"; 8 | import * as path from 'node:path'; 9 | 10 | const VERSION = 'v0.0.1'; 11 | const BIN_PATH = path.join(__dirname, '../bin'); 12 | 13 | const execWithStdin = (cmd: string, input: string) => 14 | new Promise((resolve, reject) => { 15 | const script = cp.exec(cmd, (err, out) => { 16 | if (err) { 17 | return reject(err); 18 | } 19 | return resolve(out); 20 | }); 21 | script.stdin?.write(input); 22 | script.stdin?.end(); 23 | }); 24 | 25 | 26 | // This method is called when your extension is activated 27 | // Your extension is activated the very first time the command is executed 28 | export function activate(context: vscode.ExtensionContext) { 29 | // Use the console to output diagnostic information (console.log) and errors (console.error) 30 | // This line of code will only be executed once when your extension is activated 31 | console.log("otel-vscode extension is active"); 32 | 33 | let pipelineProvider = new PipelineProvider(); 34 | vscode.window.createTreeView("otelCollectorPipeline", { 35 | treeDataProvider: pipelineProvider, 36 | }); 37 | 38 | // The command has been defined in the package.json file 39 | // Now provide the implementation of the command with registerCommand 40 | // The commandId parameter must match the command field in package.json 41 | let disposable = vscode.commands.registerCommand( 42 | "otel-vscode.validateOtelConfig", 43 | async () => { 44 | // Get the active text editor 45 | const editor = vscode.window.activeTextEditor; 46 | 47 | if (editor) { 48 | let document = editor.document; 49 | 50 | const documentText = document.getText(); 51 | try { 52 | const output = await execWithStdin( 53 | `"${BIN_PATH}/otel-config-validator_${VERSION}" --json --stdin`, 54 | documentText 55 | ); 56 | const result = JSON.parse(output); 57 | if (result.valid) { 58 | vscode.window.showInformationMessage("Collector config is valid!"); 59 | pipelineProvider.refresh(result.pipelines as [CollectorPipeline]); 60 | } else { 61 | vscode.window.showWarningMessage( 62 | `Collector config is invalid: ${result.error}` 63 | ); 64 | pipelineProvider.refresh([]); 65 | } 66 | } catch (err) { 67 | vscode.window.showWarningMessage( 68 | `Error validating configuration: ${err}` 69 | ); 70 | } 71 | } 72 | } 73 | ); 74 | 75 | context.subscriptions.push(disposable); 76 | } 77 | 78 | // This method is called when your extension is deactivated 79 | export function deactivate() {} 80 | -------------------------------------------------------------------------------- /otel-vscode/src/pipeline.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { Event, EventEmitter } from "vscode"; 3 | import { CollectorPipeline } from "./types"; 4 | 5 | export class PipelineProvider implements vscode.TreeDataProvider { 6 | private pipelineConfig: CollectorPipeline[]; 7 | private viewUpdatedEventEmitter: EventEmitter = 8 | new EventEmitter(); 9 | readonly onDidChangeTreeData: Event = 10 | this.viewUpdatedEventEmitter.event; 11 | 12 | constructor() { 13 | this.pipelineConfig = []; 14 | } 15 | 16 | refresh(pipelines: CollectorPipeline[]) { 17 | this.pipelineConfig = pipelines; 18 | this.viewUpdatedEventEmitter.fire(null); 19 | } 20 | 21 | getTreeItem(element: PipelineItem): vscode.TreeItem { 22 | return element; 23 | } 24 | 25 | getChildren(element?: PipelineItem): Thenable { 26 | if (this.pipelineConfig.length === 0) { 27 | return Promise.resolve([]); 28 | } 29 | 30 | if (element) { 31 | return Promise.resolve( 32 | this.getPipelineComponents(element.label, element.pipelineConfig) 33 | ); 34 | } else { 35 | return Promise.resolve(this.getPipelineComponents()); 36 | } 37 | } 38 | 39 | private getPipelineComponents( 40 | label: string = "", 41 | pipelineItem: CollectorPipeline | undefined = undefined 42 | ): PipelineItem[] { 43 | if (pipelineItem === undefined) { 44 | return this.pipelineConfig.map( 45 | (pc) => 46 | new PipelineItem( 47 | pc.name, 48 | vscode.TreeItemCollapsibleState.Collapsed, 49 | pc, 50 | [] 51 | ) 52 | ); 53 | } else if (label === "traces" || label === "metrics" || label === "logs") { 54 | const items = []; 55 | if (pipelineItem.receivers) { 56 | items.push(new PipelineItem( 57 | "receivers", 58 | vscode.TreeItemCollapsibleState.Collapsed, 59 | pipelineItem, 60 | pipelineItem.receivers 61 | )); 62 | } 63 | 64 | if (pipelineItem.processors) { 65 | items.push(new PipelineItem( 66 | "processors", 67 | vscode.TreeItemCollapsibleState.Collapsed, 68 | pipelineItem, 69 | pipelineItem.processors 70 | )); 71 | } 72 | 73 | if (pipelineItem.exporters) { 74 | items.push(new PipelineItem( 75 | "exporters", 76 | vscode.TreeItemCollapsibleState.Collapsed, 77 | pipelineItem, 78 | pipelineItem.processors 79 | )); 80 | } 81 | 82 | return items; 83 | } else if (label === "receivers" && pipelineItem.receivers) { 84 | return pipelineItem.receivers!.map( 85 | (pr) => 86 | new PipelineItem( 87 | pr, 88 | vscode.TreeItemCollapsibleState.None, 89 | pipelineItem, 90 | [] 91 | ) 92 | ); 93 | } else if (label === "processors" && pipelineItem.processors) { 94 | return pipelineItem.processors!.map( 95 | (pr) => 96 | new PipelineItem( 97 | pr, 98 | vscode.TreeItemCollapsibleState.None, 99 | pipelineItem, 100 | [] 101 | ) 102 | ); 103 | } else if (label === "exporters" && pipelineItem.exporters) { 104 | return pipelineItem.exporters!.map( 105 | (pr) => 106 | new PipelineItem( 107 | pr, 108 | vscode.TreeItemCollapsibleState.None, 109 | pipelineItem, 110 | [] 111 | ) 112 | ); 113 | } 114 | 115 | return []; 116 | } 117 | } 118 | 119 | class PipelineItem extends vscode.TreeItem { 120 | constructor( 121 | public readonly label: string, 122 | public readonly collapsibleState: vscode.TreeItemCollapsibleState, 123 | public pipelineConfig: CollectorPipeline, 124 | public components: string[] | null 125 | ) { 126 | super(label, collapsibleState); 127 | this.tooltip = `${this.label}`; 128 | this.description = false; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /otel-vscode/src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from '@vscode/test-electron'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 10 | 11 | // The path to test runner 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error('Failed to run tests'); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /otel-vscode/src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | import * as vscode from 'vscode'; 6 | // import * as myExtension from '../../extension'; 7 | 8 | suite('Extension Test Suite', () => { 9 | vscode.window.showInformationMessage('Start all tests.'); 10 | 11 | test('Sample test', () => { 12 | assert.strictEqual(-1, [1, 2, 3].indexOf(5)); 13 | assert.strictEqual(-1, [1, 2, 3].indexOf(0)); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /otel-vscode/src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | color: true 10 | }); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run(failures => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | console.error(err); 34 | e(err); 35 | } 36 | }); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /otel-vscode/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface CollectorPipeline { 2 | name: string; 3 | exporters: [string] | null; 4 | processors: [string] | null; 5 | receivers: [string] | null; 6 | } -------------------------------------------------------------------------------- /otel-vscode/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2020", 5 | "outDir": "out", 6 | "lib": [ 7 | "ES2020" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "strict": true /* enable all strict type-checking options */ 12 | /* Additional Checks */ 13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## otel-config-validator 2 | 3 | > 📣 This project is deprecated. For collector config validation, check out [OTelBin](https://otelbin.io) or, as of early 2024, the collector now natively supports validating configuration [using the `validate` command](https://opentelemetry.io/docs/collector/configuration/#location). 4 | 5 | Experimental OpenTelemetry Collector Configuraton Validator. Work-in-progress, currently supports usage: 6 | 7 | * In-browser (via WebAssembly), see Demo: [https://lightstep.github.io/otel-config-validator/](https://lightstep.github.io/otel-config-validator/) 8 | * CLI 9 | * Visual Studio Code Extension 10 | 11 | ### Development: Quick start (WebAssembly) 12 | 13 | ``` 14 | $ make 15 | 16 | # run local server 17 | $ go run cmd/server/main.go 18 | 19 | # open a browser to validate using an HTML form 20 | $ open http://127.0.0.1:8100 21 | ``` 22 | 23 | ### How it works 24 | 25 | Runs validation on partial subset of exporters, receivers, and processors are supported for now. See `components.go` for full list. 26 | 27 | In WebAssembly, some components are not supported because they cannot compile to wasm. 28 | 29 | ### CLI mode 30 | 31 | The validator can be built as a command line utility: 32 | 33 | ``` 34 | # build cli 35 | $ go build -o otel-config-validator ./cmd/cli/main.go 36 | 37 | #run cli against otel config file 38 | $ ./otel-config-validator -f /path/to/config 39 | ``` 40 | 41 | Output: 42 | 43 | ``` 44 | OpenTelemetry Collector Configuration file `test-adot.yml` is valid. 45 | 46 | 47 | Pipeline metrics: 48 | Receivers: [otlp] 49 | Processors: [] 50 | Exporters: [logging] 51 | Pipeline traces: 52 | Receivers: [otlp] 53 | Processors: [] 54 | Exporters: [awsxray] 55 | ``` 56 | -------------------------------------------------------------------------------- /test-adot.yml: -------------------------------------------------------------------------------- 1 | 2 | receivers: 3 | otlp: 4 | protocols: 5 | grpc: 6 | http: 7 | 8 | exporters: 9 | logging: 10 | awsxray: 11 | 12 | service: 13 | pipelines: 14 | traces: 15 | receivers: [otlp] 16 | exporters: [awsxray] 17 | metrics: 18 | receivers: [otlp] 19 | exporters: [logging] 20 | 21 | -------------------------------------------------------------------------------- /test.yml: -------------------------------------------------------------------------------- 1 | 2 | receivers: 3 | nop: 4 | nop/myreceiver: 5 | 6 | processors: 7 | nop: 8 | nop/myprocessor: 9 | 10 | exporters: 11 | nop: 12 | nop/myexporter: 13 | 14 | extensions: 15 | nop: 16 | nop/myextension: 17 | 18 | service: 19 | extensions: [nop] 20 | pipelines: 21 | traces: 22 | receivers: [nop] 23 | processors: [_nop] 24 | exporters: [nop] -------------------------------------------------------------------------------- /types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | 8 | "go.opentelemetry.io/collector/config" 9 | ) 10 | 11 | type PipelineOutput struct { 12 | Pipelines []*PipelineSummary `json:"pipelines"` 13 | Error string `json:"error"` 14 | IsValid bool `json:"valid"` 15 | Filename string `json:"filename"` 16 | } 17 | 18 | type PipelineSummary struct { 19 | PipelineName string `json:"name"` 20 | Receivers []string `json:"receivers"` 21 | Processors []string `json:"processors"` 22 | Exporters []string `json:"exporters"` 23 | } 24 | 25 | func (po PipelineOutput) JSONString() string { 26 | poJson, _ := json.Marshal(po) 27 | return string(poJson) 28 | } 29 | 30 | func (po PipelineOutput) String() string { 31 | var sb strings.Builder 32 | if po.IsValid { 33 | sb.WriteString(fmt.Sprintf("OpenTelemetry Collector Configuration file `%s` is valid.\n\n\n", po.Filename)) 34 | for _, p := range po.Pipelines { 35 | sb.WriteString(fmt.Sprintf("Pipeline %s:\n", p.PipelineName)) 36 | sb.WriteString(fmt.Sprintf(" Receivers: %s\n", p.Receivers)) 37 | sb.WriteString(fmt.Sprintf(" Processors: %s\n", p.Processors)) 38 | sb.WriteString(fmt.Sprintf(" Exporters: %s\n", p.Exporters)) 39 | } 40 | } else { 41 | sb.WriteString(fmt.Sprintf("OpenTelemetry Collector Configuration file `%s` is not valid.\n\n\n", po.Filename)) 42 | sb.WriteString(fmt.Sprintf("Error: %s\n", po.Error)) 43 | } 44 | return sb.String() 45 | } 46 | 47 | func NewPipelineOutput(valid bool, filename string, cfg *config.Config, err error) *PipelineOutput { 48 | po := PipelineOutput{} 49 | po.IsValid = valid 50 | if err != nil { 51 | po.Error = err.Error() 52 | } 53 | po.Filename = filename 54 | 55 | if cfg == nil { 56 | return &po 57 | } 58 | 59 | for id, v := range cfg.Pipelines { 60 | po.Pipelines = append(po.Pipelines, NewPipelineSummary(id.String(), v.Receivers, v.Processors, v.Exporters)) 61 | } 62 | return &po 63 | } 64 | 65 | func NewPipelineSummary(name string, r []config.ComponentID, p []config.ComponentID, e []config.ComponentID) *PipelineSummary { 66 | ps := PipelineSummary{} 67 | ps.PipelineName = name 68 | for _, v := range r { 69 | ps.Receivers = append(ps.Receivers, v.String()) 70 | } 71 | for _, v := range p { 72 | ps.Processors = append(ps.Processors, v.String()) 73 | } 74 | for _, v := range e { 75 | ps.Exporters = append(ps.Exporters, v.String()) 76 | } 77 | return &ps 78 | } 79 | -------------------------------------------------------------------------------- /validator/components.go: -------------------------------------------------------------------------------- 1 | //go:build !wasm 2 | // +build !wasm 3 | 4 | package validator 5 | 6 | import ( 7 | "go.opentelemetry.io/collector/component" 8 | "go.opentelemetry.io/collector/exporter/loggingexporter" 9 | "go.opentelemetry.io/collector/exporter/otlpexporter" 10 | "go.opentelemetry.io/collector/exporter/otlphttpexporter" 11 | 12 | "go.opentelemetry.io/collector/extension/zpagesextension" 13 | "go.opentelemetry.io/collector/processor/batchprocessor" 14 | "go.opentelemetry.io/collector/processor/memorylimiterprocessor" 15 | "go.opentelemetry.io/collector/receiver/otlpreceiver" 16 | 17 | "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/awsemfexporter" 18 | "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/awsxrayexporter" 19 | "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/prometheusexporter" 20 | "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/prometheusremotewriteexporter" 21 | 22 | "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter" 23 | "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/dynatraceexporter" 24 | "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/fileexporter" 25 | "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/logzioexporter" 26 | "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/sapmexporter" 27 | 28 | //"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/signalfxexporter" 29 | "github.com/open-telemetry/opentelemetry-collector-contrib/processor/attributesprocessor" 30 | "github.com/open-telemetry/opentelemetry-collector-contrib/processor/cumulativetodeltaprocessor" 31 | "github.com/open-telemetry/opentelemetry-collector-contrib/processor/deltatorateprocessor" 32 | "github.com/open-telemetry/opentelemetry-collector-contrib/processor/filterprocessor" 33 | "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricsgenerationprocessor" 34 | "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstransformprocessor" 35 | "github.com/open-telemetry/opentelemetry-collector-contrib/processor/probabilisticsamplerprocessor" 36 | "github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourceprocessor" 37 | "github.com/open-telemetry/opentelemetry-collector-contrib/processor/spanprocessor" 38 | "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/awsecscontainermetricsreceiver" 39 | "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/awsxrayreceiver" 40 | "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/statsdreceiver" 41 | "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/zipkinreceiver" 42 | ) 43 | 44 | // inspired by: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/80abcdd25de778d4546c679afba7906fb1639713/internal/components/components.go#L176 45 | func Components() (component.Factories, error) { 46 | var err error 47 | factories := component.Factories{} 48 | extensions := []component.ExtensionFactory{ 49 | zpagesextension.NewFactory(), 50 | } 51 | factories.Extensions, err = component.MakeExtensionFactoryMap(extensions...) 52 | if err != nil { 53 | return component.Factories{}, err 54 | } 55 | 56 | receivers := []component.ReceiverFactory{ 57 | // awscontainerinsightreceiver.NewFactory(), 58 | awsecscontainermetricsreceiver.NewFactory(), 59 | awsxrayreceiver.NewFactory(), 60 | // jaegerreceiver.NewFactory(), 61 | // prometheusreceiver.NewFactory(), 62 | statsdreceiver.NewFactory(), 63 | zipkinreceiver.NewFactory(), 64 | otlpreceiver.NewFactory(), 65 | } 66 | factories.Receivers, err = component.MakeReceiverFactoryMap(receivers...) 67 | if err != nil { 68 | return component.Factories{}, err 69 | } 70 | 71 | exporters := []component.ExporterFactory{ 72 | otlpexporter.NewFactory(), 73 | otlphttpexporter.NewFactory(), 74 | loggingexporter.NewFactory(), 75 | awsemfexporter.NewFactory(), 76 | awsxrayexporter.NewFactory(), 77 | datadogexporter.NewFactory(), 78 | dynatraceexporter.NewFactory(), 79 | fileexporter.NewFactory(), 80 | logzioexporter.NewFactory(), 81 | prometheusexporter.NewFactory(), 82 | prometheusremotewriteexporter.NewFactory(), 83 | sapmexporter.NewFactory(), 84 | //signalfxexporter.NewFactory(), 85 | } 86 | factories.Exporters, err = component.MakeExporterFactoryMap(exporters...) 87 | if err != nil { 88 | return component.Factories{}, err 89 | } 90 | 91 | processors := []component.ProcessorFactory{ 92 | attributesprocessor.NewFactory(), 93 | batchprocessor.NewFactory(), 94 | memorylimiterprocessor.NewFactory(), 95 | cumulativetodeltaprocessor.NewFactory(), 96 | deltatorateprocessor.NewFactory(), 97 | filterprocessor.NewFactory(), 98 | metricsgenerationprocessor.NewFactory(), 99 | metricstransformprocessor.NewFactory(), 100 | probabilisticsamplerprocessor.NewFactory(), 101 | // resourcedetectionprocessor.NewFactory(), 102 | resourceprocessor.NewFactory(), 103 | spanprocessor.NewFactory(), 104 | } 105 | factories.Processors, err = component.MakeProcessorFactoryMap(processors...) 106 | if err != nil { 107 | return component.Factories{}, err 108 | } 109 | 110 | return factories, nil 111 | } 112 | -------------------------------------------------------------------------------- /validator/components_wasm.go: -------------------------------------------------------------------------------- 1 | //go:build wasm 2 | // +build wasm 3 | 4 | package validator 5 | 6 | import ( 7 | "go.opentelemetry.io/collector/component" 8 | "go.opentelemetry.io/collector/exporter/loggingexporter" 9 | "go.opentelemetry.io/collector/exporter/otlpexporter" 10 | "go.opentelemetry.io/collector/exporter/otlphttpexporter" 11 | 12 | "go.opentelemetry.io/collector/extension/zpagesextension" 13 | "go.opentelemetry.io/collector/processor/batchprocessor" 14 | "go.opentelemetry.io/collector/processor/memorylimiterprocessor" 15 | "go.opentelemetry.io/collector/receiver/otlpreceiver" 16 | 17 | "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/awsemfexporter" 18 | "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/awsxrayexporter" 19 | 20 | //"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter" 21 | "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/dynatraceexporter" 22 | "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/fileexporter" 23 | "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/logzioexporter" 24 | "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/sapmexporter" 25 | //"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/signalfxexporter" 26 | "github.com/open-telemetry/opentelemetry-collector-contrib/processor/attributesprocessor" 27 | "github.com/open-telemetry/opentelemetry-collector-contrib/processor/cumulativetodeltaprocessor" 28 | "github.com/open-telemetry/opentelemetry-collector-contrib/processor/deltatorateprocessor" 29 | "github.com/open-telemetry/opentelemetry-collector-contrib/processor/filterprocessor" 30 | "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricsgenerationprocessor" 31 | "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstransformprocessor" 32 | "github.com/open-telemetry/opentelemetry-collector-contrib/processor/probabilisticsamplerprocessor" 33 | "github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourceprocessor" 34 | "github.com/open-telemetry/opentelemetry-collector-contrib/processor/spanprocessor" 35 | "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/awsecscontainermetricsreceiver" 36 | "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/awsxrayreceiver" 37 | "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/statsdreceiver" 38 | "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/zipkinreceiver" 39 | ) 40 | 41 | // inspired by: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/80abcdd25de778d4546c679afba7906fb1639713/internal/components/components.go#L176 42 | func Components() (component.Factories, error) { 43 | var err error 44 | factories := component.Factories{} 45 | extensions := []component.ExtensionFactory{ 46 | zpagesextension.NewFactory(), 47 | } 48 | factories.Extensions, err = component.MakeExtensionFactoryMap(extensions...) 49 | if err != nil { 50 | return component.Factories{}, err 51 | } 52 | 53 | receivers := []component.ReceiverFactory{ 54 | // awscontainerinsightreceiver.NewFactory(), 55 | awsecscontainermetricsreceiver.NewFactory(), 56 | awsxrayreceiver.NewFactory(), 57 | // jaegerreceiver.NewFactory(), 58 | // prometheusreceiver.NewFactory(), 59 | statsdreceiver.NewFactory(), 60 | zipkinreceiver.NewFactory(), 61 | otlpreceiver.NewFactory(), 62 | } 63 | factories.Receivers, err = component.MakeReceiverFactoryMap(receivers...) 64 | if err != nil { 65 | return component.Factories{}, err 66 | } 67 | 68 | exporters := []component.ExporterFactory{ 69 | otlpexporter.NewFactory(), 70 | otlphttpexporter.NewFactory(), 71 | loggingexporter.NewFactory(), 72 | awsemfexporter.NewFactory(), 73 | awsxrayexporter.NewFactory(), 74 | // datadogexporter.NewFactory(), 75 | dynatraceexporter.NewFactory(), 76 | fileexporter.NewFactory(), 77 | logzioexporter.NewFactory(), 78 | // prometheusexporter.NewFactory(), 79 | // prometheusremotewriteexporter.NewFactory(), 80 | sapmexporter.NewFactory(), 81 | //signalfxexporter.NewFactory(), 82 | } 83 | factories.Exporters, err = component.MakeExporterFactoryMap(exporters...) 84 | if err != nil { 85 | return component.Factories{}, err 86 | } 87 | 88 | processors := []component.ProcessorFactory{ 89 | attributesprocessor.NewFactory(), 90 | batchprocessor.NewFactory(), 91 | memorylimiterprocessor.NewFactory(), 92 | cumulativetodeltaprocessor.NewFactory(), 93 | deltatorateprocessor.NewFactory(), 94 | filterprocessor.NewFactory(), 95 | metricsgenerationprocessor.NewFactory(), 96 | metricstransformprocessor.NewFactory(), 97 | probabilisticsamplerprocessor.NewFactory(), 98 | // resourcedetectionprocessor.NewFactory(), 99 | resourceprocessor.NewFactory(), 100 | spanprocessor.NewFactory(), 101 | } 102 | factories.Processors, err = component.MakeProcessorFactoryMap(processors...) 103 | if err != nil { 104 | return component.Factories{}, err 105 | } 106 | 107 | return factories, nil 108 | } 109 | -------------------------------------------------------------------------------- /validator/validator.go: -------------------------------------------------------------------------------- 1 | package validator 2 | 3 | import ( 4 | "github.com/lightstep/otel-config-validator/configunmarshaler" 5 | 6 | "go.opentelemetry.io/collector/config" 7 | "go.opentelemetry.io/collector/confmap" 8 | "gopkg.in/yaml.v3" 9 | ) 10 | 11 | func IsValid(content []byte) (*config.Config, error) { 12 | var rawConf map[string]interface{} 13 | if err := yaml.Unmarshal(content, &rawConf); err != nil { 14 | return nil, err 15 | } 16 | 17 | conf := confmap.NewFromStringMap(rawConf) 18 | factories, err := Components() 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | cfg, err := configunmarshaler.New().Unmarshal(conf, factories) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | return cfg, cfg.Validate() 29 | } 30 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | var ( 4 | // ProviderVersion is set during the release process to the release version of the binary 5 | ValidatorVersion = "dev" 6 | ) 7 | --------------------------------------------------------------------------------