├── .github ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ ├── container.yml │ ├── integration.yml │ └── release.yml ├── .gitignore ├── .golangci.yml ├── Dockerfile ├── LICENSE ├── README.md ├── cmd └── p2 │ └── main.go ├── data.invalid.env ├── examples ├── dirmode │ ├── README.md │ ├── content.yaml │ └── root │ │ ├── VERSION │ │ ├── description.txt │ │ └── entrypoint.sh └── simple │ ├── content.yaml │ └── template.p2 ├── githooks └── pre-commit │ ├── 01-style.sh │ └── 02-lint.sh ├── go.mod ├── go.sum ├── lint ├── mage.go ├── magefile.go ├── pkg ├── entrypoint │ ├── custom_filters.go │ ├── data.invalid.env │ ├── entrypoint.go │ ├── entrypoint_test.go │ └── tests │ │ ├── data-invalid.env │ │ ├── data.base64.json │ │ ├── data.base64.out │ │ ├── data.base64.p2 │ │ ├── data.base64.test │ │ ├── data.env │ │ ├── data.env.1.test │ │ ├── data.env.2.test │ │ ├── data.env.3.test │ │ ├── data.env.4.test │ │ ├── data.gzip.json │ │ ├── data.gzip.out │ │ ├── data.gzip.p2 │ │ ├── data.gzip.test │ │ ├── data.indent.json │ │ ├── data.indent.out │ │ ├── data.indent.p2 │ │ ├── data.indent.test │ │ ├── data.json │ │ ├── data.json.1.test │ │ ├── data.json.2.test │ │ ├── data.json.3.test │ │ ├── data.json.4.test │ │ ├── data.make_dirs.json │ │ ├── data.make_dirs.out │ │ ├── data.make_dirs.p2 │ │ ├── data.make_dirs.test │ │ ├── data.out │ │ ├── data.out.overridden │ │ ├── data.p2 │ │ ├── data.replace.json │ │ ├── data.replace.out │ │ ├── data.replace.p2 │ │ ├── data.string_filters.json │ │ ├── data.string_filters.out │ │ ├── data.string_filters.p2 │ │ ├── data.string_filters.test │ │ ├── data.structured.json │ │ ├── data.structured.out │ │ ├── data.structured.p2 │ │ ├── data.structured.test │ │ ├── data.write_file.json │ │ ├── data.write_file.out │ │ ├── data.write_file.p2 │ │ ├── data.write_file.test │ │ ├── data.yml │ │ ├── data.yml.1.test │ │ ├── data.yml.2.test │ │ ├── data.yml.3.test │ │ ├── data.yml.4.test │ │ ├── directory-mode-filename-transform │ │ └── templates │ │ │ └── dir1 │ │ │ └── template1.tmpl.txt │ │ └── directory-mode │ │ ├── output │ │ └── .gitignore │ │ └── templates │ │ ├── dir1 │ │ ├── dir2 │ │ │ └── template2 │ │ └── template1 │ │ └── dir3 │ │ └── template3 ├── envutil │ ├── inputprocessors.go │ └── inputprocessors_test.go ├── errdefs │ └── errdefs.go ├── fileconsts │ └── fileconsts.go └── templating │ ├── engine.go │ ├── template_loader.go │ └── templating.go └── version └── version.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Set update schedule for GitHub Actions 2 | version: 2 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | # Check for updates to GitHub Actions every weekday 8 | interval: "daily" 9 | - package-ecosystem: "gomod" 10 | directory: "/" 11 | schedule: 12 | # Check for updates to GitHub Actions every weekday 13 | interval: "weekly" -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "main" ] 20 | schedule: 21 | - cron: '23 7 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'go' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v4 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | -------------------------------------------------------------------------------- /.github/workflows/container.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Container Build 3 | on: 4 | workflow_run: 5 | workflows: 6 | - Build and Test 7 | types: 8 | - completed 9 | branches: 10 | - main 11 | push: 12 | tags: 13 | - r* 14 | 15 | env: 16 | REGISTRY: ghcr.io 17 | IMAGE_NAME: ${{ github.repository }} 18 | 19 | jobs: 20 | container-build: 21 | runs-on: ubuntu-latest 22 | permissions: 23 | contents: read 24 | packages: write 25 | steps: 26 | - name: Checkout repository 27 | uses: actions/checkout@v4 28 | 29 | - name: Fetch tags 30 | run: git fetch --prune --unshallow --tags -f 31 | 32 | - name: Set up QEMU 33 | uses: docker/setup-qemu-action@v2 34 | 35 | - name: Set up Docker Buildx 36 | id: buildx 37 | uses: docker/setup-buildx-action@v3 38 | 39 | - name: Log in to the Container registry 40 | uses: docker/login-action@v2 41 | with: 42 | registry: ${{ env.REGISTRY }} 43 | username: ${{ github.actor }} 44 | password: ${{ secrets.GITHUB_TOKEN }} 45 | 46 | - name: Extract metadata (tags, labels) for Docker 47 | id: meta 48 | uses: docker/metadata-action@v4 49 | with: 50 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 51 | tags: | 52 | # set latest tag for default branch 53 | type=raw,value=latest,enable={{is_default_branch}} 54 | type=schedule 55 | type=semver,pattern={{version}} 56 | type=semver,pattern={{major}}.{{minor}} 57 | type=semver,pattern={{major}} 58 | type=ref,event=branch 59 | type=ref,event=pr 60 | type=sha 61 | type=sha,format=long 62 | 63 | - name: Build and push Docker image 64 | uses: docker/build-push-action@v5 65 | with: 66 | context: . 67 | platforms: linux/amd64,linux/arm64 68 | push: true 69 | tags: ${{ steps.meta.outputs.tags }} 70 | labels: ${{ steps.meta.outputs.labels }} 71 | 72 | -------------------------------------------------------------------------------- /.github/workflows/integration.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Build and Test 3 | "on": 4 | push: 5 | branches: 6 | - "*" 7 | tags: 8 | - r* 9 | pull_request: 10 | branches: 11 | - "*" 12 | workflow_call: 13 | jobs: 14 | style: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | - name: Setup Go 20 | uses: actions/setup-go@v4 21 | with: 22 | go-version: "1.23" 23 | - name: Check style 24 | run: go run mage.go style 25 | 26 | lint: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v2 31 | - name: Setup Go 32 | uses: actions/setup-go@v4 33 | with: 34 | go-version: "1.23" 35 | - name: Lint 36 | run: go run mage.go lint 37 | - name: Publish Linter Reports 38 | uses: mikepenz/action-junit-report@v4 39 | if: always() # always run even if the previous step fails 40 | with: 41 | report_paths: '.junit/*.xml' 42 | 43 | test: 44 | runs-on: ubuntu-latest 45 | steps: 46 | - name: Checkout 47 | uses: actions/checkout@v2 48 | - name: Setup Go 49 | uses: actions/setup-go@v4 50 | with: 51 | go-version: "1.23" 52 | - name: Test 53 | run: go run mage.go test 54 | - name: Merge Coverage Files 55 | run: go run mage.go coverage 56 | - name: Coveralls 57 | uses: shogo82148/actions-goveralls@v1 58 | with: 59 | path-to-profile: .cover.out 60 | 61 | build: 62 | runs-on: ubuntu-latest 63 | steps: 64 | - name: Checkout 65 | uses: actions/checkout@v2 66 | - name: Setup Go 67 | uses: actions/setup-go@v4 68 | with: 69 | go-version: "1.23" 70 | - name: Build 71 | run: go run mage.go binary 72 | 73 | # deploy: 74 | # runs-on: ubuntu-latest 75 | # needs: 76 | # - build 77 | # steps: 78 | # - name: Checkout 79 | # uses: actions/checkout@v2 80 | # with: 81 | # fetch-depth: 0 82 | # - name: Setup Go 83 | # uses: actions/setup-go@v2 84 | # - name: Build Release 85 | # run: make -j$(cat /proc/cpuinfo | grep processor | wc -l) release 86 | # - name: Release 87 | # uses: softprops/action-gh-release@v1 88 | # if: startsWith(github.ref, 'refs/tags/r') 89 | # env: 90 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 91 | # with: 92 | # files: | 93 | # callback-linux-arm.tar.gz 94 | # callback-linux-arm64.tar.gz 95 | # callback-linux-x86_64.tar.gz 96 | # callback-linux-i386.tar.gz 97 | # callback-windows-i386.zip 98 | # callback-windows-x86_64.zip 99 | # callback-darwin-x86_64.tar.gz 100 | # callback-darwin-arm64.tar.gz 101 | # callback-freebsd-x86_64.tar.gz -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release 3 | on: 4 | push: 5 | tags: 6 | - r* 7 | 8 | jobs: 9 | integration: 10 | uses: ./.github/workflows/integration.yml 11 | 12 | generate-release-matrix: 13 | runs-on: ubuntu-latest 14 | outputs: 15 | release-matrix: ${{ steps.generate-matrix.outputs.release-matrix }} 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | 20 | - name: Setup Go 21 | uses: actions/setup-go@v4 22 | with: 23 | go-version: "1.23" 24 | 25 | - name: Generate Release Matrix 26 | id: generate-matrix 27 | run: go run mage.go githubReleaseMatrix 28 | 29 | release-build: 30 | runs-on: ubuntu-latest 31 | needs: 32 | - integration 33 | - generate-release-matrix 34 | strategy: 35 | matrix: 36 | osarch: ${{ fromJson(needs.generate-release-matrix.outputs.release-matrix) }} 37 | max-parallel: 10 38 | steps: 39 | - name: Checkout 40 | uses: actions/checkout@v4 41 | 42 | - name: Fetch tags 43 | run: git fetch --prune --unshallow --tags -f 44 | 45 | - name: Setup Go 46 | uses: actions/setup-go@v4 47 | with: 48 | go-version: "1.23" 49 | 50 | - name: Release Build 51 | run: go run mage.go release ${{ matrix.osarch }} 52 | 53 | - uses: actions/upload-artifact@v3 54 | with: 55 | name: release 56 | path: release/* 57 | 58 | release: 59 | runs-on: ubuntu-latest 60 | needs: 61 | - release-build 62 | steps: 63 | - name: Download artifacts 64 | uses: actions/download-artifact@v3 65 | with: 66 | name: release 67 | path: release 68 | 69 | - name: Release 70 | uses: softprops/action-gh-release@v1 71 | env: 72 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 73 | with: 74 | files: | 75 | release/* -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /p2 2 | /p2-* 3 | **/tests/*.test 4 | *.orig 5 | /cover.out 6 | /cover.*.out 7 | /.idea 8 | /.vscode 9 | /coverage 10 | /pkg/entrypoint/tests/directory-mode*/output/ 11 | /pkg/entrypoint/tests/directory-mode*/*.tar 12 | /.bin 13 | /.junit 14 | /bin 15 | /.coverage 16 | 17 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | build-tags: 3 | - mage 4 | 5 | output: 6 | formats: junit-xml:.junit/linters.xml,colored-line-number 7 | 8 | linters: 9 | enable-all: true 10 | disable: 11 | # Doesn't work 12 | - promlinter 13 | - revive # Revisit in future 14 | # Not used 15 | - paralleltest 16 | - nlreturn 17 | - wsl 18 | - lll 19 | - gofumpt 20 | - gci 21 | - exhaustruct # ultimately too noisy for most coding 22 | - depguard 23 | - dupword 24 | # This one is just infuriating, produces a lot of warnings, and doesn't matter. 25 | - godot 26 | # These all stopped working suddenly due to a non-existent compilation error. 27 | # Retry them regularly. 28 | # - stylecheck 29 | # - exhaustive 30 | # - staticcheck 31 | # - gosimple 32 | # - govet 33 | # - unused 34 | - godox # noisy and counterproductive 35 | # Deprecated 36 | - ireturn 37 | - exportloopref # deprecated (since v1.60.2). Replaced by copyloopvar. 38 | issues: 39 | exclude-rules: 40 | - path: _test\.go 41 | linters: 42 | - dupl 43 | - funlen 44 | - varnamelen 45 | - path: magefile.go 46 | linters: 47 | - unparam 48 | exclude: 49 | - "ST1023: should omit type .*" 50 | 51 | severity: 52 | default-severity: error 53 | rules: 54 | - linters: 55 | - godox 56 | - funlen 57 | - nolintlint 58 | severity: info 59 | 60 | linters-settings: 61 | varnamelen: 62 | # The longest distance, in source lines, that is being considered a "small scope." (defaults to 5) 63 | # Variables used in at most this many lines will be ignored. 64 | max-distance: 5 65 | # The minimum length of a variable's name that is considered "long." (defaults to 3) 66 | # Variable names that are at least this long will be ignored. 67 | min-name-length: 3 68 | # Check method receivers. (defaults to false) 69 | check-receiver: false 70 | # Check named return values. (defaults to false) 71 | check-return: false 72 | # Check type parameters. (defaults to false) 73 | check-type-param: false 74 | # Ignore "ok" variables that hold the bool return value of a type assertion. (defaults to false) 75 | ignore-type-assert-ok: false 76 | # Ignore "ok" variables that hold the bool return value of a map index. (defaults to false) 77 | ignore-map-index-ok: false 78 | # Ignore "ok" variables that hold the bool return value of a channel receive. (defaults to false) 79 | ignore-chan-recv-ok: false 80 | # Optional list of variable names that should be ignored completely. (defaults to empty list) 81 | ignore-names: 82 | - err 83 | - ch # common channel var 84 | - l # common log var 85 | - k 86 | - v 87 | - c 88 | - ip 89 | - f # function 90 | - fn 91 | - st 92 | - e 93 | - in 94 | - wr 95 | - rd 96 | - b 97 | # Optional list of variable declarations that should be ignored completely. (defaults to empty list) 98 | # Entries must be in one of the following forms (see below for examples): 99 | # - for variables, parameters, named return values, method receivers, or type parameters: 100 | # ( can also be a pointer/slice/map/chan/...) 101 | # - for constants: const 102 | ignore-decls: 103 | - s string 104 | - w http.ResponseWriter 105 | - r *http.Request 106 | 107 | tagliatelle: 108 | # Check the struck tag name case. 109 | case: 110 | # Use the struct field name to check the name of the struct tag. 111 | # Default: false 112 | use-field-name: true 113 | rules: 114 | # Any struct tag type can be used. 115 | # Support string case: `camel`, `pascal`, `kebab`, `snake`, `goCamel`, `goPascal`, `goKebab`, `goSnake`, `upper`, `lower` 116 | json: snake 117 | yaml: snake 118 | xml: snake 119 | 120 | exhaustruct: 121 | # List of regular expressions to match struct packages and names. 122 | # If this list is empty, all structs are tested. 123 | # Default: [] 124 | include: [] 125 | # List of regular expressions to exclude struct packages and names from check. 126 | # Default: [] 127 | exclude: 128 | - 'net\.IPAddr$' 129 | - 'promhttp\.HandlerOpts$' 130 | - 'x509\.VerifyOptions$' 131 | - 'http\.Transport$' 132 | - 'http\.Request$' 133 | - 'http\.Client$' 134 | - 'net\.Dialer$' 135 | - 'http\.Server$' 136 | - 'tls\.Config$' 137 | 138 | gocritic: 139 | disabled-checks: 140 | - commentFormatting 141 | 142 | wrapcheck: 143 | ignoreSigRegexps: 144 | - func \(github\.com/labstack/echo/v4\.Context\) 145 | 146 | gomnd: 147 | ignored-functions: 148 | - 'math.*' 149 | - 'http.StatusText' 150 | - "strconv.Parse.*" 151 | 152 | perfsprint: 153 | # String concatenation frequently makes code harder to read. 154 | strconcat: false -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22 AS build 2 | 3 | RUN mkdir /build 4 | 5 | WORKDIR /build 6 | 7 | COPY ./ ./ 8 | 9 | RUN go run mage.go binary 10 | 11 | FROM scratch 12 | 13 | COPY --from=build /build/p2 /bin/p2 14 | 15 | ENV PATH=/bin:$PATH 16 | 17 | ENTRYPOINT ["p2"] 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # p2cli 2 | ![Build Status](https://github.com/wrouesnel/p2cli/actions/workflows/release.yml/badge.svg?branch=master) 3 | [![Coverage Status](https://coveralls.io/repos/github/wrouesnel/p2cli/badge.svg?branch=master)](https://coveralls.io/github/wrouesnel/p2cli?branch=master) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/wrouesnel/p2cli)](https://goreportcard.com/report/github.com/wrouesnel/p2cli) 5 | 6 | A command line tool for rendering pongo2 (jinja2-like) templates to stdout. 7 | 8 | The rendering library is [pongo2](https://github.com/flosch/pongo2). 9 | 10 | It is inspired (and pretty much a copy of) the j2cli utility for Python, but 11 | leveraging Golang's static compilation for easier use in Docker and other 12 | minimal environments. 13 | 14 | Note: We are currently updated to https://github.com/flosch/pongo2/commit/c84aecb5fa79a9c0feec284a7bf4f0536c6a6e99 15 | on the pongo2 main branch. This version notably introduces a new set variable 16 | functionality which allows implementing multi-parameter filters (used for 17 | `replace`). Check out the docs over there for more info. 18 | 19 | ## Usage 20 | p2 defaults to using the local environment variables as a data source. 21 | 22 | If `--format` is specified explicitely, then p2 will read that format from 23 | stdin (or the supplied given data file specified with `--input`). 24 | 25 | If only `--input` is specified, then it will guess the data type based on the 26 | file extension. 27 | 28 | Render template with environment variables (most useful for Docker): 29 | ``` 30 | p2 -t template.j2 31 | ``` 32 | 33 | Render a template with environment variables piped from stdin: 34 | ``` 35 | cat vars.env | p2 -t template.j2 36 | ``` 37 | 38 | Render a template with using a JSON source file: 39 | ``` 40 | p2 -t template.j2 -i source.json 41 | ``` 42 | 43 | Render a template using a YAML on stdin: 44 | ``` 45 | cat someYaml | p2 -t template.j2 -f yaml 46 | ``` 47 | 48 | Render a template using values from Kubernetes 49 | 50 | ### Advanced Usage 51 | 52 | #### Extra Built-In Filters 53 | 54 | * `indent` - output data with the given indent. Can be given either a string or number of spaces. 55 | * `replace` - replace strings. Usage: `{{ value | replace:["match", "replacement"] }}`. A third 56 | parameter can also be supplied to set the number of replacements. 57 | * `to_json` - outputs structured data as JSON. Supplying a parameter sets the indent. 58 | * `to_yaml` - outputs structured data as YAML. 59 | * `to_toml` - outputs structured data as TOML. Must be supplied a map. 60 | * `string` - convert input data to string (use with `from_base64`) 61 | * `bytes` - convert input data to bytes 62 | * `to_base64` - encode a string or bytes to base64 63 | * `from_base64` - decode a string from base64 to bytes 64 | * `to_gzip` - compress bytes with gzip (supply level as parameter, default 9) 65 | * `from_gzip` - decompress bytes with gzip 66 | 67 | #### Special Output Functions 68 | 69 | Several utility functions are provided to improve the configuration file 70 | templating experience: 71 | 72 | * `SetOwner` - try and set the owner of the output file to the supplied argument. 73 | * `SetGroup` - try and get the group of the output file to the supplied argument. 74 | * `SetMode` - try and set the mode of the output file to the supplied argument. 75 | 76 | Additionally, special variables are exposed under the key "p2": 77 | 78 | * `p2.OutputPath` - return the output path of the current template. 79 | * `p2.OutputName` - return the basename of the output path. 80 | * `p2.OutputDir` - return the directory of the current output path. 81 | 82 | #### Directory tree templating via `--directory-mode` 83 | 84 | Invoking `p2` with the `--directory-mode` option causes it to expect that the template file 85 | path is in fact a directory tree root which should be duplicated and copied to 86 | 1:1 to the output path, which also must be a directory. This is useful for 87 | templating large numbers of files in complex configurations using a single 88 | input source. 89 | 90 | In this mode, directives such as `write_file` execute relative to to the 91 | template file subpath they are found in - i.e. the working directory is 92 | changed to be the output directory location of the input template file. 93 | 94 | The `SetOwner`, `SetGroup`, and `SetMode` special filters exist principally 95 | to support this mode. 96 | 97 | #### `tar` file output mode 98 | 99 | This should generally be used with `--directory-mode` as without a filename 100 | the tar file is unlikely to be useful. 101 | 102 | In this mode, the `--output-path` parameter specifies the base prefix of files 103 | within the emitted `tar` file, and the `--tar` parameter specifies the name 104 | of a tar file to output. `--tar -` can be used to pipe the TAR file to stdout. 105 | 106 | #### Delete substrings in output filenames when `--directory-mode` enabled 107 | 108 | You can use the optional flag `--directory-mode-filename-substr-del` to delete 109 | substrings that are present in output filenames when running `p2` with 110 | `--directory-mode` enabled. 111 | 112 | This is useful for cases where your templates have asuffix e.g `.tmpl`, 113 | `.template` that you want removed once the template has been rendered. For 114 | example, the output file for a template named `mytemplate.tmpl.json` will 115 | become `mytemplate.json`. 116 | 117 | #### Side-effectful filters 118 | `p2` allows enabling a suite of non-standard pongo2 filters which have 119 | side-effects on the system. These filters add a certain amount of 120 | minimal scripting ability to `p2`, namely by allowing a single template 121 | to use filters which can perform operations like writing files and 122 | creating directories. 123 | 124 | These are __unsafe__ filters to use with uninspected templates, and so 125 | by default are disabled. They can be enabled on a per-filter basis with 126 | the `--enable-filters` flag. For template debugging purposes, they can 127 | also be enabled in no-op mode with `--enable-noops` which will allow 128 | all filters but disable their side-effects. 129 | 130 | #### Passing structured data in environment variable keys 131 | It is technically possible to store complex data in environment variables. `p2` 132 | supports this use-case (without commenting if it's a good idea). To use it, 133 | pass the name of the environment variable as the `--input` and specify 134 | `--use-env-key` and `--format` 135 | ``` 136 | p2 -t template.j2 -f json --use-env-key -i MY_ENV_VAR 137 | ``` 138 | 139 | #### Multiple file templating via `write_file` 140 | `p2` implements the custom `write_file` filter extension to pongo2. 141 | `write_file` takes a filename as an argument (which can itself be a 142 | templated value) and creates and outputs any input content to that 143 | filename (overwriting the destination file). 144 | 145 | This makes it possible to write a template which acts more like a 146 | script, and generates multiple output values. Example: 147 | 148 | `input.yml`: 149 | ```yaml 150 | users: 151 | - user: 152 | name: Mike 153 | content: This is Mike's content. 154 | - user: 155 | name: Sally 156 | content: This is Sally's content. 157 | - user: 158 | name: Greg 159 | content: This is Greg's content. 160 | ``` 161 | 162 | `template.p2`: 163 | ```Django 164 | {% macro user_content(content) %} 165 | {{content|safe}} 166 | {% endmacro %} 167 | 168 | {% for user in users %} 169 | ## {{user.name}}.txt output 170 | {% set filename = user.name|stringformat:"%s.txt" %} 171 | {{ user_content( user.content ) | write_file:filename }} 172 | ## 173 | {% endfor %} 174 | ``` 175 | 176 | Now executing the template: 177 | ```sh 178 | $ p2 -t template.p2 -i input.yml -f yaml --enable-filters write_file 179 | 180 | 181 | 182 | ## mike.txt output 183 | 184 | 185 | This is Mike's content. 186 | 187 | ## 188 | 189 | ## sally.txt output 190 | 191 | 192 | This is Sally's content. 193 | 194 | ## 195 | 196 | ## greg.txt output 197 | 198 | 199 | This is greg's content. 200 | 201 | ## 202 | 203 | $ ls 204 | greg.txt input.yml mike.txt sally.txt template.p2 205 | ``` 206 | 207 | We get the output, but we have also created a new set of files 208 | containing the content from our macro. 209 | 210 | Note that until pongo2 supports multiple filter arguments, the file 211 | output plugin creates files with the maximum possible umask of the user. 212 | 213 | #### Run p2 in docker 214 | ``` 215 | docker build . -t p2 216 | docker run -v $(pwd):/t -ti p2 -t /t/template.p2 -i /t/input.yml 217 | ``` 218 | 219 | ## Building 220 | 221 | It is recommended to build using the included Makefile. This correctly sets up 222 | Go to build a cgo-independent, statically linked binary. 223 | 224 | Note: users on platforms other than Linux will need to specify GOOS when 225 | building. 226 | -------------------------------------------------------------------------------- /cmd/p2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/samber/lo" 7 | "github.com/wrouesnel/p2cli/pkg/entrypoint" 8 | "github.com/wrouesnel/p2cli/pkg/envutil" 9 | ) 10 | 11 | func main() { 12 | env := lo.Must(envutil.FromEnvironment(os.Environ())) 13 | 14 | args := entrypoint.LaunchArgs{ 15 | StdIn: os.Stdin, 16 | StdOut: os.Stdout, 17 | StdErr: os.Stderr, 18 | Env: env, 19 | Args: os.Args[1:], 20 | } 21 | ret := entrypoint.Entrypoint(args) 22 | os.Exit(ret) 23 | } 24 | -------------------------------------------------------------------------------- /data.invalid.env: -------------------------------------------------------------------------------- 1 | a value 2 | a value 3 | -------------------------------------------------------------------------------- /examples/dirmode/README.md: -------------------------------------------------------------------------------- 1 | # directory-mode example 2 | 3 | Run this example with the following command: 4 | 5 | ```shell 6 | ./p2 -i content.yaml \ 7 | -t root \ 8 | -o out \ 9 | --directory-mode 10 | ``` 11 | 12 | It is also possible to use `--tar` mode to emit a tarfile bundle directly. 13 | In this mode, `-o` sets the prefix which output files are placed in the tarfile. 14 | 15 | ```shell 16 | ./p2 -i content.yaml \ 17 | -t root \ 18 | -o out \ 19 | --tar out.tar \ 20 | --directory-mode 21 | ``` 22 | 23 | -------------------------------------------------------------------------------- /examples/dirmode/content.yaml: -------------------------------------------------------------------------------- 1 | description: This is an example of directory mode. 2 | 3 | purpose: | 4 | Directory mode lets us consume a single source of truth input and template 5 | multiple files as output. It is useful for configuring docker container 6 | entrypoints from environment variables or JSON/YAML files like this. 7 | 8 | version: "0.1.1" 9 | 10 | subcommand: echo "this is a command" -------------------------------------------------------------------------------- /examples/dirmode/root/VERSION: -------------------------------------------------------------------------------- 1 | {{ version }}{{ "0644"|SetMode }} -------------------------------------------------------------------------------- /examples/dirmode/root/description.txt: -------------------------------------------------------------------------------- 1 | {{ description }} 2 | 3 | {{ purpose }} 4 | 5 | {{ "0644"|SetMode }} -------------------------------------------------------------------------------- /examples/dirmode/root/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Example of templating a script 3 | # Note the use of SetMode which ensures this file is emitted with the correct mode. 4 | {{ "0755"|SetMode }} 5 | exec {{ subcommand }} -------------------------------------------------------------------------------- /examples/simple/content.yaml: -------------------------------------------------------------------------------- 1 | name: MyName 2 | 3 | nested: 4 | value: XX 5 | 6 | listed_value: 7 | - 1 8 | - 2 9 | - 4 10 | - Outside -------------------------------------------------------------------------------- /examples/simple/template.p2: -------------------------------------------------------------------------------- 1 | This is a template for: {{ name }} 2 | 3 | A nested value can be output as: {{ nested.value }} 4 | 5 | The following list of values is joined sensibly: {{ listed_value | join:", " }} 6 | -------------------------------------------------------------------------------- /githooks/pre-commit/01-style.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | go run mage.go style || exit $? -------------------------------------------------------------------------------- /githooks/pre-commit/02-lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | go run mage.go lint || exit $? -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wrouesnel/p2cli 2 | 3 | go 1.22.0 4 | 5 | toolchain go1.23.0 6 | 7 | require ( 8 | github.com/pelletier/go-toml v1.9.5 9 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c 10 | gopkg.in/yaml.v2 v2.4.0 11 | ) 12 | 13 | require ( 14 | github.com/alecthomas/kong v1.6.1 15 | github.com/flosch/pongo2/v6 v6.0.1-0.20230411124213-c84aecb5fa79 16 | github.com/integralist/go-findroot v0.0.0-20160518114804-ac90681525dc 17 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 18 | github.com/magefile/mage v1.15.0 19 | github.com/mholt/archiver v3.1.1+incompatible 20 | github.com/pkg/errors v0.9.1 21 | github.com/rogpeppe/go-internal v1.13.1 22 | github.com/samber/lo v1.47.0 23 | go.uber.org/zap v1.27.0 24 | gopkg.in/yaml.v3 v3.0.1 25 | ) 26 | 27 | require ( 28 | github.com/dsnet/compress v0.0.1 // indirect 29 | github.com/flosch/pongo2 v0.0.0-20200913210552-0d938eb266f3 // indirect 30 | github.com/frankban/quicktest v1.14.3 // indirect 31 | github.com/golang/snappy v0.0.4 // indirect 32 | github.com/kr/pretty v0.3.0 // indirect 33 | github.com/kr/text v0.2.0 // indirect 34 | github.com/nwaples/rardecode v1.1.3 // indirect 35 | github.com/pierrec/lz4 v2.6.1+incompatible // indirect 36 | github.com/ulikunitz/xz v0.5.12 // indirect 37 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect 38 | go.uber.org/atomic v1.11.0 // indirect 39 | go.uber.org/multierr v1.11.0 // indirect 40 | golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect 41 | golang.org/x/mod v0.22.0 // indirect 42 | golang.org/x/text v0.21.0 // indirect 43 | ) 44 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/alecthomas/assert/v2 v2.1.0 h1:tbredtNcQnoSd3QBhQWI7QZ3XHOVkw1Moklp2ojoH/0= 2 | github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= 3 | github.com/alecthomas/kong v0.7.0 h1:YIjJUiR7AcmHxL87UlbPn0gyIGwl4+nYND0OQ4ojP7k= 4 | github.com/alecthomas/kong v0.7.0/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U= 5 | github.com/alecthomas/kong v1.6.1 h1:/7bVimARU3uxPD0hbryPE8qWrS3Oz3kPQoxA/H2NKG8= 6 | github.com/alecthomas/kong v1.6.1/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU= 7 | github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE= 8 | github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= 9 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 10 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 12 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= 14 | github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= 15 | github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= 16 | github.com/flosch/pongo2 v0.0.0-20200913210552-0d938eb266f3 h1:fmFk0Wt3bBxxwZnu48jqMdaOR/IZ4vdtJFuaFV8MpIE= 17 | github.com/flosch/pongo2 v0.0.0-20200913210552-0d938eb266f3/go.mod h1:bJWSKrZyQvfTnb2OudyUjurSG4/edverV7n82+K3JiM= 18 | github.com/flosch/pongo2/v6 v6.0.0 h1:lsGru8IAzHgIAw6H2m4PCyleO58I40ow6apih0WprMU= 19 | github.com/flosch/pongo2/v6 v6.0.0/go.mod h1:CuDpFm47R0uGGE7z13/tTlt1Y6zdxvr2RLT5LJhsHEU= 20 | github.com/flosch/pongo2/v6 v6.0.1-0.20230411124213-c84aecb5fa79 h1:t8V7Vr867xLh3FDoEmcqRhVeKn+mRXC0dS4AhIbNxko= 21 | github.com/flosch/pongo2/v6 v6.0.1-0.20230411124213-c84aecb5fa79/go.mod h1:hdFHt6Ygfap9bzf5cKFNw8q8nsuzjh0ONdE1texQckU= 22 | github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= 23 | github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= 24 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 25 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 26 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 27 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= 28 | github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= 29 | github.com/integralist/go-findroot v0.0.0-20160518114804-ac90681525dc h1:4IZpk3M4m6ypx0IlRoEyEyY1gAdicWLMQ0NcG/gBnnA= 30 | github.com/integralist/go-findroot v0.0.0-20160518114804-ac90681525dc/go.mod h1:UlaC6ndby46IJz9m/03cZPKKkR9ykeIVBBDE3UDBdJk= 31 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= 32 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 33 | github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 34 | github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 35 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 36 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 37 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 38 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 39 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 40 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 41 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 42 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 43 | github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= 44 | github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= 45 | github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU= 46 | github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU= 47 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 48 | github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= 49 | github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= 50 | github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= 51 | github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 52 | github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= 53 | github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 54 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 55 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 56 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 57 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 58 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 59 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 60 | github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 61 | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 62 | github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 63 | github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= 64 | github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= 65 | github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= 66 | github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= 67 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 68 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 69 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 70 | github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= 71 | github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= 72 | github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 73 | github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= 74 | github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 75 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= 76 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= 77 | go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= 78 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 79 | go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 80 | go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 81 | go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= 82 | go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= 83 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 84 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 85 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 86 | go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= 87 | go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= 88 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 89 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 90 | golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= 91 | golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= 92 | golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= 93 | golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= 94 | golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= 95 | golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 96 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 97 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 98 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 99 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 100 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 101 | gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 102 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 103 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 104 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 105 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 106 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 107 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 108 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 109 | -------------------------------------------------------------------------------- /lint: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec go run mage.go lint -------------------------------------------------------------------------------- /mage.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | 3 | package main 4 | 5 | import ( 6 | "os" 7 | 8 | "github.com/magefile/mage/mage" 9 | ) 10 | 11 | func main() { os.Exit(mage.Main()) } 12 | -------------------------------------------------------------------------------- /pkg/entrypoint/custom_filters.go: -------------------------------------------------------------------------------- 1 | package entrypoint 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/wrouesnel/p2cli/pkg/templating" 8 | 9 | "github.com/wrouesnel/p2cli/pkg/fileconsts" 10 | 11 | "github.com/flosch/pongo2/v6" 12 | ) 13 | 14 | // This noop filter is registered in place of custom filters which otherwise 15 | // passthru their input (our file filters). This allows debugging and testing 16 | // without running file operations. 17 | func filterNoopPassthru(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { 18 | return in, nil 19 | } 20 | 21 | // This filter writes the content of its input to the filename specified as its 22 | // argument. The templated content is returned verbatim. 23 | func filterWriteFile(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { 24 | if !in.IsString() { 25 | return nil, &pongo2.Error{ 26 | Sender: "filter:write_file", 27 | OrigError: templating.FilterError{Reason: "Filter input must be of type 'string'."}, 28 | } 29 | //return nil, &pongo2.Error{ 30 | // Sender: "filter:write_file", 31 | // ErrorMsg: "Filter input must be of type 'string'.", 32 | //} 33 | } 34 | 35 | if !param.IsString() { 36 | return nil, &pongo2.Error{ 37 | Sender: "filter:write_file", 38 | OrigError: templating.FilterError{Reason: "Filter parameter must be of type 'string'."}, 39 | } 40 | //return nil, &pongo2.Error{ 41 | // Sender: "filter:write_file", 42 | // ErrorMsg: "Filter parameter must be of type 'string'.", 43 | //} 44 | } 45 | 46 | f, err := os.OpenFile(param.String(), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.FileMode(fileconsts.OS_ALL_RWX)) 47 | if err != nil { 48 | return nil, &pongo2.Error{ 49 | Sender: "filter:write_file", 50 | Filename: "Could not open file for output: " + err.Error(), 51 | } 52 | //return nil, &pongo2.Error{ 53 | // Sender: "filter:write_file", 54 | // ErrorMsg: fmt.Sprintf("Could not open file for output: %s", err.Error()), 55 | //} 56 | } 57 | defer f.Close() 58 | 59 | _, werr := f.WriteString(in.String()) 60 | if werr != nil { 61 | return nil, &pongo2.Error{ 62 | Sender: "filter:write_file", 63 | OrigError: fmt.Errorf("could not write file for output: %w", werr), 64 | } 65 | //return nil, &pongo2.Error{ 66 | // Sender: "filter:write_file", 67 | // ErrorMsg: fmt.Sprintf("Could not write file for output: %s", werr.Error()), 68 | //} 69 | } 70 | 71 | return in, nil 72 | } 73 | 74 | // This filter makes a directory based on the value of its argument. It passes 75 | // through any content without alteration. This allows chaining with write-file. 76 | func filterMakeDirs(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { 77 | if !param.IsString() { 78 | return nil, &pongo2.Error{ 79 | Sender: "filter:make_dirs", 80 | OrigError: templating.FilterError{Reason: "filter parameter must be of type 'string'."}, 81 | } 82 | //return nil, &pongo2.Error{ 83 | // Sender: "filter:make_dirs", 84 | // ErrorMsg: "Filter parameter must be of type 'string'.", 85 | //} 86 | } 87 | 88 | err := os.MkdirAll(param.String(), os.FileMode(fileconsts.OS_ALL_RWX)) 89 | if err != nil { 90 | return nil, &pongo2.Error{ 91 | Sender: "filter:make_dirs", 92 | OrigError: fmt.Errorf("could not create directories: %s %w", in.String(), err), 93 | } 94 | //return nil, &pongo2.Error{ 95 | // Sender: "filter:make_dirs", 96 | // ErrorMsg: fmt.Sprintf("Could not create directories: %s %s", in.String(), err.Error()), 97 | //} 98 | } 99 | 100 | return in, nil 101 | } 102 | -------------------------------------------------------------------------------- /pkg/entrypoint/data.invalid.env: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /pkg/entrypoint/entrypoint.go: -------------------------------------------------------------------------------- 1 | /* 2 | A Golang replica of j2cli from Python. Designed for allowing easy templating 3 | of files using Jinja2-like syntax (from the Pongo2 engine). 4 | 5 | Extremely useful for building Docker files when you don't want to pull in all of 6 | python. 7 | */ 8 | 9 | package entrypoint 10 | 11 | import ( 12 | "archive/tar" 13 | "bufio" 14 | "bytes" 15 | "encoding/json" 16 | "fmt" 17 | "io" 18 | "os" 19 | "path" 20 | "path/filepath" 21 | "regexp" 22 | "strings" 23 | "time" 24 | 25 | "github.com/wrouesnel/p2cli/pkg/fileconsts" 26 | "github.com/wrouesnel/p2cli/version" 27 | 28 | "github.com/alecthomas/kong" 29 | "github.com/flosch/pongo2/v6" 30 | "github.com/pkg/errors" 31 | "github.com/samber/lo" 32 | "github.com/wrouesnel/p2cli/pkg/errdefs" 33 | 34 | "github.com/kballard/go-shellquote" 35 | "github.com/wrouesnel/p2cli/pkg/templating" 36 | 37 | "gopkg.in/yaml.v2" 38 | 39 | "go.uber.org/zap" 40 | ) 41 | 42 | // Copied from pongo2.context. 43 | var reIdentifiers = regexp.MustCompile("^[a-zA-Z0-9_]+$") 44 | 45 | // SupportedType is an enumeration of data types we support. 46 | type SupportedType int 47 | 48 | const ( 49 | // TypeUnknown is the default error type. 50 | TypeUnknown SupportedType = iota 51 | // TypeJSON is JSON. 52 | TypeJSON SupportedType = iota 53 | // TypeYAML is YAML. 54 | TypeYAML SupportedType = iota 55 | // TypeEnv is key=value pseudo environment files. 56 | TypeEnv SupportedType = iota 57 | ) 58 | 59 | // DataSource is an enumeration of the sources of input data we can take. 60 | type DataSource int 61 | 62 | const ( 63 | // SourceEnv means input comes from environment variables. 64 | SourceEnv DataSource = iota 65 | // SourceEnvKey means input comes from the value of a specific environment key. 66 | SourceEnvKey DataSource = iota 67 | // SourceStdin means input comes from stdin. 68 | SourceStdin DataSource = iota 69 | // SourceFile means input comes from a file. 70 | SourceFile DataSource = iota 71 | ) 72 | 73 | //nolint:gochecknoglobals 74 | var dataFormats = map[string]SupportedType{ 75 | "json": TypeJSON, 76 | "yaml": TypeYAML, 77 | "yml": TypeYAML, 78 | "env": TypeEnv, 79 | } 80 | 81 | const ( 82 | FormatAuto = "auto" 83 | ) 84 | 85 | type Options struct { 86 | Logging struct { 87 | Level string `default:"warning" help:"logging level"` 88 | Format string `default:"console" enum:"console,json" help:"logging format (${enum})"` 89 | } `embed:"" prefix:"logging."` 90 | 91 | DumpInputData bool `help:"Print Go serialization to stderr and then exit" name:"debug"` 92 | 93 | UseEnvKey bool `help:"Treat --input as an environment key name to read. This is equivalent to specifying --format=envkey"` 94 | Format string `default:"auto" enum:"auto,env,envkey,json,yml,yaml" help:"Input data format (may specify multiple values)" short:"f"` 95 | IncludeEnv bool `help:"Implicitly include environment variables in addition to any supplied data"` 96 | TemplateFile string `help:"Template file to process" name:"template" required:"" short:"t"` 97 | DataFile string `help:"Input data path. Leave blank for stdin." name:"input" short:"i"` 98 | OutputFile string `help:"Output file. Leave blank for stdout." name:"output" short:"o"` 99 | 100 | TarFile string `default:"" help:"Output content as a tar file with the given name or to stdout (-)" name:"tar"` 101 | 102 | CustomFilters string `help:"Enable custom P2 filters" name:"enable-filters"` 103 | CustomFilterNoops bool `help:"Enable all custom filters in no-op mode. Supercedes --enable-filters." name:"enable-noop-filters"` 104 | 105 | Autoescape bool `help:"Enable autoescaping"` 106 | 107 | DirectoryMode bool `help:"Treat template path as directory-tree, output path as target directory"` 108 | FilenameSubstrDel string `help:"Delete a given substring in the output filename (only applies to --directory-mode)" name:"directory-mode-filename-substr-del"` 109 | 110 | InputRootKey string `help:"If specified, the input will be placed under a common subkey rather then in the root context. Use this when the input may contain invalid root context names."` 111 | 112 | Version kong.VersionFlag `help:"Print the version and exit"` 113 | } 114 | 115 | // CustomFilterSpec is a map of custom filters p2 implements. These are gated 116 | // behind the --enable-filter command line option as they can have unexpected 117 | // or even unsafe behavior (i.e. templates gain the ability to make filesystem 118 | // modifications). Disabled filters are stubbed out to allow for debugging. 119 | type CustomFilterSpec struct { 120 | FilterFunc pongo2.FilterFunction 121 | NoopFunc pongo2.FilterFunction 122 | } 123 | 124 | //nolint:gochecknoglobals 125 | var customFilters = map[string]CustomFilterSpec{ 126 | "write_file": {filterWriteFile, filterNoopPassthru}, 127 | "make_dirs": {filterMakeDirs, filterNoopPassthru}, 128 | } 129 | 130 | func readRawInput(env map[string]string, stdIn io.Reader, name string, source DataSource) ([]byte, error) { 131 | logger := zap.L() 132 | var data []byte 133 | var err error 134 | //nolint:exhaustive 135 | switch source { 136 | case SourceStdin: 137 | // Read from stdin 138 | name = "-" 139 | data, err = io.ReadAll(stdIn) 140 | case SourceFile: 141 | // Read from file 142 | data, err = os.ReadFile(name) 143 | case SourceEnvKey: 144 | // Read from environment key 145 | data = []byte(env[name]) 146 | default: 147 | logger.Error("Invalid data source specified.", zap.String("filename", name)) 148 | return []byte{}, errors.Wrap(err, "readRawInput") 149 | } 150 | 151 | if err != nil { 152 | logger.Error("Could not read data", zap.Error(err), zap.String("filename", name)) 153 | return []byte{}, errors.Wrap(err, "readRawInput") 154 | } 155 | return data, nil 156 | } 157 | 158 | type LaunchArgs struct { 159 | StdIn io.Reader 160 | StdOut io.Writer 161 | StdErr io.Writer 162 | Env map[string]string 163 | Args []string 164 | } 165 | 166 | // Entrypoint implements the actual functionality of the program so it can be called inline from testing. 167 | // env is normally passed the environment variable array. 168 | // 169 | //nolint:funlen,gocognit,gocyclo,cyclop,maintidx 170 | func Entrypoint(args LaunchArgs) int { 171 | var err error 172 | options := Options{} 173 | 174 | deferredLogs := []string{} 175 | 176 | // Filter invalid environment variables. 177 | args.Env = lo.OmitBy(args.Env, func(key string, value string) bool { 178 | return !reIdentifiers.MatchString(key) 179 | }) 180 | 181 | // Command line parsing can now happen 182 | parser := lo.Must(kong.New(&options, kong.Description(version.Description), kong.Vars{ 183 | "version": version.Version, 184 | })) 185 | _, err = parser.Parse(args.Args) 186 | if err != nil { 187 | _, _ = fmt.Fprintf(args.StdErr, "Argument error: %s", err.Error()) 188 | return 1 189 | } 190 | 191 | // Initialize logging as soon as possible 192 | logConfig := zap.NewProductionConfig() 193 | if err := logConfig.Level.UnmarshalText([]byte(options.Logging.Level)); err != nil { 194 | deferredLogs = append(deferredLogs, err.Error()) 195 | } 196 | logConfig.Encoding = options.Logging.Format 197 | 198 | logger, err := logConfig.Build() 199 | if err != nil { 200 | // Error unhandled since this is a very early failure 201 | for _, line := range deferredLogs { 202 | _, _ = io.WriteString(args.StdErr, line) 203 | } 204 | _, _ = io.WriteString(args.StdErr, "Failure while building logger") 205 | return 1 206 | } 207 | 208 | // Install as the global logger 209 | zap.ReplaceGlobals(logger) 210 | 211 | //nolint:nestif 212 | if options.DirectoryMode { 213 | tst, _ := os.Stat(options.TemplateFile) 214 | if !tst.IsDir() { 215 | logger.Error("Template path must be a directory in directory mode", zap.String("template_file", options.TemplateFile)) 216 | return 1 217 | } 218 | 219 | ost, err := os.Stat(options.OutputFile) 220 | if err == nil { 221 | if !ost.IsDir() { 222 | logger.Error("Output path must be an existing directory in directory mode", zap.String("template_file", options.TemplateFile)) 223 | return 1 224 | } 225 | } else if options.TarFile == "" { 226 | // Allow non-existent output path if outputting to a tar file 227 | logger.Error("Error calling stat on output path", zap.Error(err)) 228 | return 1 229 | } 230 | } 231 | 232 | // Register custom filter functions. 233 | if options.CustomFilterNoops { 234 | for filter, spec := range customFilters { 235 | _ = pongo2.RegisterFilter(filter, spec.NoopFunc) 236 | } 237 | } else if options.CustomFilters != "" { 238 | for _, filter := range strings.Split(options.CustomFilters, ",") { 239 | spec, found := customFilters[filter] 240 | if !found { 241 | logger.Error("This version of p2 does not support the specified custom filter", zap.String("filter_name", filter)) 242 | return 1 243 | } 244 | 245 | _ = pongo2.RegisterFilter(filter, spec.FilterFunc) 246 | } 247 | } 248 | 249 | // filterSet is passed to executeTemplate so it can vary parameters within the filter space as it goes. 250 | filterSet := templating.FilterSet{OutputFileName: "", Chown: os.Chown, Chmod: os.Chmod} 251 | 252 | // Register the default custom filters. These are replaced each file execution later, but we 253 | // need the names in-scope here. 254 | _ = pongo2.RegisterFilter("SetOwner", filterSet.FilterSetOwner) 255 | _ = pongo2.RegisterFilter("SetGroup", filterSet.FilterSetGroup) 256 | _ = pongo2.RegisterFilter("SetMode", filterSet.FilterSetMode) 257 | 258 | // Standard suite of custom helpers 259 | _ = pongo2.RegisterFilter("indent", filterSet.FilterIndent) 260 | _ = pongo2.RegisterFilter("replace", filterSet.FilterReplace) 261 | 262 | _ = pongo2.RegisterFilter("to_json", filterSet.FilterToJSON) 263 | _ = pongo2.RegisterFilter("to_yaml", filterSet.FilterToYAML) 264 | _ = pongo2.RegisterFilter("to_toml", filterSet.FilterToTOML) 265 | 266 | _ = pongo2.RegisterFilter("to_base64", filterSet.FilterToBase64) 267 | _ = pongo2.RegisterFilter("from_base64", filterSet.FilterFromBase64) 268 | 269 | _ = pongo2.RegisterFilter("string", filterSet.FilterString) 270 | _ = pongo2.RegisterFilter("bytes", filterSet.FilterBytes) 271 | 272 | _ = pongo2.RegisterFilter("to_gzip", filterSet.FilterToGzip) 273 | _ = pongo2.RegisterFilter("from_gzip", filterSet.FilterFromGzip) 274 | 275 | // Determine mode of operations 276 | var fileFormat SupportedType 277 | inputSource := SourceEnv 278 | 279 | switch { 280 | case options.Format == FormatAuto && options.DataFile == "": 281 | fileFormat = TypeEnv 282 | inputSource = SourceEnv 283 | case options.Format == FormatAuto && options.DataFile != "": 284 | var ok bool 285 | fileFormat, ok = dataFormats[strings.TrimLeft(path.Ext(options.DataFile), ".")] 286 | if !ok { 287 | logger.Error("Unrecognized file extension. If the file is in a supported format, try specifying it explicitly.") 288 | return 1 289 | } 290 | inputSource = SourceFile 291 | case options.Format != "" && options.DataFile == "": 292 | var ok bool 293 | fileFormat, ok = dataFormats[options.Format] 294 | if !ok { 295 | logger.Error("Unsupported input format", zap.String("format", options.Format)) 296 | return 1 297 | } 298 | inputSource = SourceStdin 299 | default: 300 | var ok bool 301 | fileFormat, ok = dataFormats[options.Format] 302 | if !ok { 303 | logger.Error("Unsupported input format:", zap.String("format", options.Format)) 304 | return 1 305 | } 306 | inputSource = SourceFile 307 | } 308 | 309 | if options.UseEnvKey && options.DataFile == "" { 310 | logger.Error("--use-env-key is incompatible with stdin file input.") 311 | return 1 312 | } else if options.UseEnvKey { 313 | inputSource = SourceEnvKey 314 | } 315 | 316 | // Get the input context 317 | inputData := make(map[string]interface{}) 318 | 319 | switch fileFormat { 320 | case TypeEnv: 321 | if options.IncludeEnv { 322 | logger.Warn("--include-env has no effect when data source is already the environment") 323 | } 324 | err = func(inputData map[string]interface{}) error { 325 | //nolint:nestif 326 | if inputSource != SourceEnv { 327 | rawInput, err := readRawInput(args.Env, args.StdIn, options.DataFile, inputSource) 328 | if err != nil { 329 | return err 330 | } 331 | lineScanner := bufio.NewScanner(bytes.NewReader(rawInput)) 332 | for lineScanner.Scan() { 333 | keyval := lineScanner.Text() 334 | const expectedFragments = 2 335 | splitKeyVal := strings.SplitN(lineScanner.Text(), "=", expectedFragments) 336 | if len(splitKeyVal) != expectedFragments { 337 | return error(errdefs.EnvironmentVariablesError{ 338 | Reason: "Could not find an equals value to split on", 339 | RawEnvVar: keyval, 340 | }) 341 | } 342 | // File values should support sh-escaped strings, whereas the 343 | // raw environment will accept *anything* after the = sign. 344 | values, err := shellquote.Split(splitKeyVal[1]) 345 | if err != nil { 346 | return error(errdefs.EnvironmentVariablesError{ 347 | Reason: err.Error(), 348 | RawEnvVar: keyval, 349 | }) 350 | } 351 | 352 | // Detect if more than 1 values was parsed - this is invalid in 353 | // sourced files, and we don't want to try parsing shell arrays. 354 | if len(values) > 1 { 355 | return error(errdefs.EnvironmentVariablesError{ 356 | Reason: "Improperly escaped environment variable. p2 does not parse arrays.", 357 | RawEnvVar: keyval, 358 | }) 359 | } 360 | 361 | inputData[splitKeyVal[0]] = values[0] 362 | } 363 | } else { 364 | for k, v := range args.Env { 365 | inputData[k] = v 366 | } 367 | } 368 | return nil 369 | }(inputData) 370 | case TypeYAML: 371 | var rawInput []byte 372 | rawInput, err = readRawInput(args.Env, args.StdIn, options.DataFile, inputSource) 373 | if err != nil { 374 | return 1 375 | } 376 | err = yaml.Unmarshal(rawInput, &inputData) 377 | case TypeJSON: 378 | var rawInput []byte 379 | rawInput, err = readRawInput(args.Env, args.StdIn, options.DataFile, inputSource) 380 | if err != nil { 381 | return 1 382 | } 383 | err = json.Unmarshal(rawInput, &inputData) 384 | case TypeUnknown: 385 | logger.Error("Unknown input format.") 386 | return 1 387 | default: 388 | logger.Error("Unknown input format.") 389 | return 1 390 | } 391 | 392 | if err != nil { 393 | logger.Error("Error parsing input data:", zap.Error(err), zap.String("template", options.TemplateFile), zap.String("data", options.DataFile)) 394 | return 1 395 | } 396 | 397 | if options.IncludeEnv { 398 | logger.Info("Including environment variables") 399 | for k, v := range args.Env { 400 | inputData[k] = v 401 | } 402 | } 403 | 404 | if options.InputRootKey != "" { 405 | oldInputData := inputData 406 | inputData = make(map[string]interface{}) 407 | inputData[options.InputRootKey] = oldInputData 408 | } 409 | 410 | if options.DumpInputData { 411 | _, _ = fmt.Fprintln(args.StdErr, inputData) 412 | } 413 | 414 | if !options.Autoescape { 415 | pongo2.SetAutoescape(false) 416 | } 417 | 418 | // Load all templates and their relative paths 419 | templates := make(map[string]*templating.LoadedTemplate) 420 | inputMaps := make(map[string]string) 421 | 422 | rootDir := options.OutputFile 423 | if !options.DirectoryMode { 424 | rootDir, err = os.Getwd() 425 | if err != nil { 426 | logger.Error("Error getting working directory", zap.Error(err)) 427 | return 1 428 | } 429 | } 430 | 431 | rootDir, err = filepath.Abs(rootDir) 432 | if err != nil { 433 | logger.Error("Could not determine absolute path of root dir", zap.Error(err)) 434 | return 1 435 | } 436 | 437 | //nolint:nestif 438 | if options.DirectoryMode { 439 | err := filepath.Walk(options.TemplateFile, func(path string, info os.FileInfo, err error) error { 440 | if err != nil { 441 | logger.Error("Error walking directory tree", zap.Error(err)) 442 | return err 443 | } 444 | 445 | if info.IsDir() { 446 | return nil 447 | } 448 | 449 | relPath, err := filepath.Rel(options.TemplateFile, path) 450 | if err != nil { 451 | return errors.Wrap(err, "DirectoryMode") 452 | } 453 | 454 | logger.Debug("Template file", zap.String("template_file", relPath)) 455 | tmpl := templating.LoadTemplate(path) 456 | if tmpl == nil { 457 | logger.Error("Error loading template", zap.String("path", path)) 458 | return errors.New("Error loading template") 459 | } 460 | 461 | newRelPath := transformFileName(relPath, options) 462 | outputPath, err := filepath.Abs(filepath.Join(options.OutputFile, newRelPath)) 463 | if err != nil { 464 | logger.Error("Could not determine absolute path of output file", zap.String("output_file", options.OutputFile), zap.String("new_rel_path", newRelPath)) 465 | return errors.New("Error determining output path") 466 | } 467 | 468 | p2cliCtx := make(map[string]string) 469 | // Set the global p2 variables on the template sets 470 | p2cliCtx["OutputPath"] = outputPath 471 | p2cliCtx["OutputName"] = filepath.Base(outputPath) 472 | p2cliCtx["OutputDir"] = filepath.Dir(outputPath) 473 | p2cliCtx["OutputRelPath"], err = filepath.Rel(rootDir, outputPath) 474 | if err != nil { 475 | return fmt.Errorf("could not determine relative output path: %w", err) 476 | } 477 | p2cliCtx["OutputRelDir"], err = filepath.Rel(rootDir, filepath.Dir(outputPath)) 478 | if err != nil { 479 | return fmt.Errorf("could not determine relative output dir: %w", err) 480 | } 481 | 482 | ctx := make(pongo2.Context) 483 | ctx["p2"] = p2cliCtx 484 | templateSet := pongo2.NewSet(outputPath, pongo2.DefaultLoader) 485 | templateSet.Globals.Update(ctx) 486 | 487 | templates[outputPath] = tmpl 488 | inputMaps[outputPath] = path 489 | return nil 490 | }) 491 | if err != nil { 492 | logger.Error("Error while walking input directory path", zap.String("template_file", options.TemplateFile)) 493 | return 1 494 | } 495 | } else { 496 | // Just load the template as the output file 497 | tmpl := templating.LoadTemplate(options.TemplateFile) 498 | if tmpl == nil { 499 | logger.Error("Error loading template:", zap.String("template_file", options.TemplateFile)) 500 | return 1 501 | } 502 | 503 | outputPath := "" 504 | if options.OutputFile != "" { 505 | outputPath, err = filepath.Abs(options.OutputFile) 506 | if err != nil { 507 | logger.Error("Could not determine absolute path of output file", zap.Error(err)) 508 | return 1 509 | } 510 | } 511 | 512 | p2cliCtx := make(map[string]string) 513 | p2cliCtx["OutputPath"] = templating.StdOutVal 514 | p2cliCtx["OutputName"] = templating.StdOutVal 515 | p2cliCtx["OutputDir"] = rootDir 516 | p2cliCtx["OutputRelPath"] = templating.StdOutVal 517 | p2cliCtx["OutputRelDir"] = "." 518 | 519 | ctx := make(pongo2.Context) 520 | ctx["p2"] = p2cliCtx 521 | 522 | tmpl.TemplateSet.Globals.Update(ctx) 523 | 524 | ctx["p2"] = p2cliCtx 525 | 526 | templates[outputPath] = tmpl 527 | inputMaps[outputPath] = templating.StdOutVal 528 | } 529 | 530 | // Configure output path 531 | var templateEngine *templating.TemplateEngine 532 | switch { 533 | case options.TarFile != "": 534 | var fileOut io.Writer 535 | if options.TarFile == "-" { 536 | fileOut = args.StdOut 537 | } else { 538 | fileOut, err = os.OpenFile(options.TarFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(fileconsts.OS_ALL_RWX)) 539 | if err != nil { 540 | logger.Error("Error opening tar file for output", zap.Error(err)) 541 | return 1 542 | } 543 | } 544 | tarWriter := tar.NewWriter(fileOut) 545 | templateEngine = &templating.TemplateEngine{ 546 | PrepareOutput: func(inputData pongo2.Context, outputPath string) (io.Writer, func() error, error) { 547 | relPath, err := filepath.Rel(rootDir, outputPath) 548 | if err != nil { 549 | return nil, nil, fmt.Errorf("could not determine relative output path: %w", err) 550 | } 551 | 552 | // Setup a new header 553 | header := &tar.Header{ 554 | Typeflag: tar.TypeReg, 555 | Name: filepath.Join(options.OutputFile, relPath), 556 | //Linkname: "", 557 | Size: 0, 558 | Mode: fileconsts.OS_ALL_RWX, 559 | Uid: 0, 560 | Gid: 0, 561 | Uname: "", 562 | Gname: "", 563 | ModTime: time.Time{}, 564 | AccessTime: time.Time{}, 565 | ChangeTime: time.Time{}, 566 | //Devmajor: 0, 567 | //Devminor: 0, 568 | //Xattrs: nil, 569 | //PAXRecords: nil, 570 | //Format: 0, 571 | } 572 | 573 | // Modify filterSet so we receive the Chown/Chmod operations 574 | filterSet.Chown = func(name string, uid, gid int) error { 575 | if uid != -1 { 576 | header.Uid = uid 577 | } 578 | if gid != -1 { 579 | header.Gid = gid 580 | } 581 | return nil 582 | } 583 | filterSet.Chmod = func(name string, mode os.FileMode) error { 584 | header.Mode = int64(mode) 585 | return nil 586 | } 587 | 588 | // Setup a buffer for the output 589 | buf := new(bytes.Buffer) 590 | 591 | finalizer := func() error { 592 | header.Size = int64(buf.Len()) 593 | if err := tarWriter.WriteHeader(header); err != nil { 594 | return errors.Wrap(err, "entrypoint: write header for tar file failed") 595 | } 596 | if _, err := tarWriter.Write(buf.Bytes()); err != nil { 597 | return errors.Wrap(err, "entrypoint: write file body for tar file failed") 598 | } 599 | return nil 600 | } 601 | 602 | return buf, finalizer, nil 603 | }, 604 | } 605 | 606 | case options.DirectoryMode: 607 | for outputPath := range templates { 608 | if err := os.MkdirAll(filepath.Dir(outputPath), os.FileMode(fileconsts.OS_ALL_RWX)); err != nil { 609 | logger.Error("Error while creating directory for output") 610 | } 611 | } 612 | templateEngine = &templating.TemplateEngine{ 613 | PrepareOutput: func(inputData pongo2.Context, outputPath string) (io.Writer, func() error, error) { 614 | origWorkDir, err := os.Getwd() 615 | if err != nil { 616 | return nil, nil, errors.Wrap(err, "DirectoryMode") 617 | } 618 | 619 | if err := os.Chdir(filepath.Dir(outputPath)); err != nil { 620 | return nil, nil, fmt.Errorf("could not change to template output path directory: %w", err) 621 | } 622 | 623 | fileOut, err := os.OpenFile(outputPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(fileconsts.OS_ALL_RWX)) 624 | if err != nil { 625 | return nil, nil, errors.Wrap(err, "entrypoint: error opening output file for writing") 626 | } 627 | 628 | finalizer := func() error { 629 | if err := os.Chdir(origWorkDir); err != nil { 630 | return fmt.Errorf("could not change back to original working directory: %w", err) 631 | } 632 | 633 | if err := fileOut.Close(); err != nil { 634 | return errors.Wrap(err, "entrypoint: error closing file after writing") 635 | } 636 | return nil 637 | } 638 | 639 | return fileOut, finalizer, nil 640 | }, 641 | } 642 | 643 | case options.OutputFile != "-" && options.OutputFile != "": 644 | templateEngine = &templating.TemplateEngine{ 645 | PrepareOutput: func(inputData pongo2.Context, outputPath string) (io.Writer, func() error, error) { 646 | fileOut, err := os.OpenFile(outputPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(fileconsts.OS_ALL_RWX)) 647 | if err != nil { 648 | return nil, nil, errors.Wrap(err, "entrypoint: error opening output file for writing") 649 | } 650 | 651 | finalizer := func() error { 652 | if err := fileOut.Close(); err != nil { 653 | return errors.Wrap(err, "entrypoint: error closing file after writing") 654 | } 655 | return nil 656 | } 657 | 658 | return fileOut, finalizer, nil 659 | }, 660 | } 661 | 662 | case options.OutputFile == "-" || options.OutputFile == "": 663 | templateEngine = &templating.TemplateEngine{ 664 | PrepareOutput: func(inputData pongo2.Context, outputPath string) (io.Writer, func() error, error) { 665 | return args.StdOut, nil, nil 666 | }, 667 | } 668 | } 669 | 670 | failed := false 671 | for outputPath, tmpl := range templates { 672 | if err := templateEngine.ExecuteTemplate(&filterSet, tmpl, inputData, outputPath); err != nil { 673 | logger.Error("Failed to execute template", zap.Error(err), zap.String("template_path", inputMaps[outputPath]), zap.String("output_path", outputPath)) 674 | failed = true 675 | } 676 | } 677 | 678 | if failed { 679 | logger.Error("Errors encountered during template processing") 680 | return 1 681 | } 682 | 683 | return 0 684 | } 685 | 686 | // transformFileName applies modifications specified by the user to the resulting output filename 687 | // This function is only invoked in Directory Mode. 688 | func transformFileName(relPath string, options Options) string { 689 | filename := filepath.Base(relPath) 690 | transformedFileName := strings.ReplaceAll(filename, options.FilenameSubstrDel, "") 691 | return filepath.Join(filepath.Dir(relPath), transformedFileName) 692 | } 693 | -------------------------------------------------------------------------------- /pkg/entrypoint/entrypoint_test.go: -------------------------------------------------------------------------------- 1 | package entrypoint_test 2 | 3 | import ( 4 | "archive/tar" 5 | "fmt" 6 | "io" 7 | "os" 8 | "path" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/pkg/errors" 13 | 14 | "github.com/samber/lo" 15 | "github.com/wrouesnel/p2cli/pkg/entrypoint" 16 | "github.com/wrouesnel/p2cli/pkg/envutil" 17 | 18 | . "gopkg.in/check.v1" 19 | ) 20 | 21 | // Hook up gocheck into the "go test" runner. 22 | func Test(t *testing.T) { TestingT(t) } 23 | 24 | type p2Integration struct{} 25 | 26 | var _ = Suite(&p2Integration{}) 27 | 28 | func MustReadFile(filePath string) []byte { 29 | testResult, err := os.ReadFile(filePath) 30 | if err != nil { 31 | panic(err) 32 | } 33 | return testResult 34 | } 35 | 36 | // Open a file or panic the test. 37 | func MustOpenFile(filepath string) *os.File { 38 | f, err := os.OpenFile(filepath, os.O_RDONLY, os.FileMode(0777)) 39 | if err != nil { 40 | panic(err) 41 | } 42 | return f 43 | } 44 | 45 | // TestInputDataProducesIdenticalOutput tests basic input/output and extension 46 | // inference works. 47 | func (s *p2Integration) TestInputDataProducesIdenticalOutput(c *C) { 48 | // Template files 49 | const templateFile string = "tests/data.p2" 50 | expectedOutput := MustReadFile("tests/data.out") 51 | 52 | testDatas := []string{ 53 | "tests/data.env", 54 | "tests/data.json", 55 | "tests/data.yml", 56 | } 57 | 58 | entrypointArgs := entrypoint.LaunchArgs{ 59 | StdIn: os.Stdin, 60 | StdOut: os.Stdout, 61 | StdErr: os.Stderr, 62 | Env: lo.Must(envutil.FromEnvironment(os.Environ())), 63 | Args: []string{}, 64 | } 65 | 66 | for _, td := range testDatas { 67 | var exit int 68 | fileTofileOutput := fmt.Sprintf("%s.1.test", td) 69 | 70 | // Check reading and writing to a file works across data types 71 | entrypointArgs.Args = []string{"-i", td, "-t", templateFile, "-o", fileTofileOutput} 72 | exit = entrypoint.Entrypoint(entrypointArgs) 73 | c.Check(exit, Equals, 0) 74 | c.Check(MustReadFile(fileTofileOutput), DeepEquals, expectedOutput) 75 | 76 | // Check stdin to file works across data types 77 | stdinToFileOutput := fmt.Sprintf("%s.2.test", td) 78 | entrypointArgs.StdIn = MustOpenFile(td) 79 | entrypointArgs.Args = []string{"-f", strings.Split(td, ".")[1], "-t", templateFile, "-o", stdinToFileOutput} 80 | exit = entrypoint.Entrypoint(entrypointArgs) 81 | c.Check(exit, Equals, 0) 82 | c.Check(MustReadFile(stdinToFileOutput), DeepEquals, expectedOutput) 83 | 84 | // Check stdin to stdout works internally 85 | stdinToStdoutOutput := fmt.Sprintf("%s.3.test", td) 86 | entrypointArgs.StdIn = MustOpenFile(td) 87 | entrypointArgs.StdOut = lo.Must(os.OpenFile(stdinToStdoutOutput, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.FileMode(0777))) 88 | entrypointArgs.Args = []string{"-f", strings.Split(td, ".")[1], "-t", templateFile} 89 | exit = entrypoint.Entrypoint(entrypointArgs) 90 | c.Check(exit, Equals, 0) 91 | c.Check(MustReadFile(stdinToStdoutOutput), DeepEquals, expectedOutput) 92 | 93 | // Check we can read the environment files 94 | const EnvKey string = "P2_TEST_ENV_KEY" 95 | envkeyOutput := fmt.Sprintf("%s.4.test", td) 96 | entrypointArgs.Env[EnvKey] = string(MustReadFile(td)) 97 | entrypointArgs.Args = []string{"-f", strings.Split(td, ".")[1], "-t", templateFile, "--use-env-key", "-i", EnvKey, "-o", envkeyOutput} 98 | // Dump the data into an environment key 99 | exit = entrypoint.Entrypoint(entrypointArgs) 100 | c.Check(exit, Equals, 0) 101 | c.Check(MustReadFile(envkeyOutput), DeepEquals, expectedOutput) 102 | } 103 | } 104 | 105 | func (s *p2Integration) TestOnNoTemplateExitFail(c *C) { 106 | entrypointArgs := entrypoint.LaunchArgs{ 107 | StdIn: os.Stdin, 108 | StdOut: os.Stdout, 109 | StdErr: os.Stderr, 110 | Env: lo.Must(envutil.FromEnvironment(os.Environ())), 111 | Args: []string{"--template=\"\""}, 112 | } 113 | exit := entrypoint.Entrypoint(entrypointArgs) 114 | c.Check(exit, Not(Equals), 0, Commentf("Exit code for command line: %v", os.Args)) 115 | } 116 | 117 | func (s *p2Integration) TestIncludingEnvironmentVariablsOverridesDataVariables(c *C) { 118 | env := lo.Must(envutil.FromEnvironment(os.Environ())) 119 | env["simple_value1"] = "InTheEnvironment" 120 | env["simple_value2"] = "a value" 121 | 122 | // Template files 123 | const templateFile string = "tests/data.p2" 124 | expectedOutput := MustReadFile("tests/data.out") 125 | 126 | testDatas := []string{ 127 | "tests/data.env", 128 | "tests/data.json", 129 | "tests/data.yml", 130 | } 131 | 132 | for _, td := range testDatas { 133 | var exit int 134 | fileTofileOutput := fmt.Sprintf("%s.1.test", td) 135 | 136 | entrypointArgs := entrypoint.LaunchArgs{ 137 | StdIn: os.Stdin, 138 | StdOut: os.Stdout, 139 | StdErr: os.Stderr, 140 | Env: env, 141 | Args: []string{}, 142 | } 143 | 144 | // Check reading and writing to a file works across data types 145 | entrypointArgs.Args = []string{"-i", td, "-t", templateFile, "-o", fileTofileOutput} 146 | exit = entrypoint.Entrypoint(entrypointArgs) 147 | c.Check(exit, Equals, 0) 148 | c.Check(string(MustReadFile(fileTofileOutput)), Equals, string(expectedOutput)) 149 | 150 | // Check stdin to file works across data types 151 | stdinToFileOutput := fmt.Sprintf("%s.2.test", td) 152 | entrypointArgs.StdIn = MustOpenFile(td) 153 | entrypointArgs.Args = []string{"-t", templateFile, "-o", stdinToFileOutput} 154 | exit = entrypoint.Entrypoint(entrypointArgs) 155 | c.Check(exit, Equals, 0) 156 | c.Check(string(MustReadFile(stdinToFileOutput)), Equals, string(MustReadFile("tests/data.out.overridden"))) 157 | 158 | // Check stdin to stdout works internally 159 | stdinToStdoutOutput := fmt.Sprintf("%s.3.test", td) 160 | entrypointArgs.StdIn = MustOpenFile(td) 161 | entrypointArgs.StdOut = lo.Must(os.OpenFile(stdinToStdoutOutput, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.FileMode(0777))) 162 | entrypointArgs.Args = []string{"-f", strings.Split(td, ".")[1], "-t", templateFile} 163 | 164 | exit = entrypoint.Entrypoint(entrypointArgs) 165 | c.Check(exit, Equals, 0) 166 | c.Check(string(MustReadFile(stdinToStdoutOutput)), Equals, string(expectedOutput), Commentf("failed with %s", td)) 167 | } 168 | } 169 | 170 | // TestDebugCommandLineOptionsWork exercises the non-critical path command ine 171 | // options to ensure they operate without crashing. 172 | func (s *p2Integration) TestDebugCommandLineOptionsWork(c *C) { 173 | const templateFile string = "tests/data.p2" 174 | 175 | testDatas := []string{ 176 | "tests/data.env", 177 | "tests/data.json", 178 | "tests/data.yml", 179 | } 180 | 181 | entrypointArgs := entrypoint.LaunchArgs{ 182 | StdIn: os.Stdin, 183 | StdOut: os.Stdout, 184 | StdErr: os.Stderr, 185 | Env: lo.Must(envutil.FromEnvironment(os.Environ())), 186 | Args: []string{}, 187 | } 188 | 189 | // Test dump input data 190 | for _, td := range testDatas { 191 | entrypointArgs.Args = []string{"-t", templateFile, "-i", td, "--debug"} 192 | exit := entrypoint.Entrypoint(entrypointArgs) 193 | c.Check(exit, Equals, 0, Commentf("Exit code for input %s != 0", td)) 194 | } 195 | } 196 | 197 | func (s *p2Integration) TestIndentFilter(c *C) { 198 | { 199 | const templateFile string = "tests/data.indent.p2" 200 | const emptyData string = "tests/data.indent.json" 201 | 202 | // This test uses the write_file filter to produce its output. 203 | const outputFile string = "tests/data.indent.test" 204 | const expectedFile string = "tests/data.indent.out" 205 | 206 | entrypointArgs := entrypoint.LaunchArgs{ 207 | StdIn: os.Stdin, 208 | StdOut: os.Stdout, 209 | StdErr: os.Stderr, 210 | Env: lo.Must(envutil.FromEnvironment(os.Environ())), 211 | Args: []string{"-t", templateFile, "-i", emptyData, "-o", outputFile}, 212 | } 213 | 214 | exit := entrypoint.Entrypoint(entrypointArgs) 215 | c.Assert(exit, Equals, 0, Commentf("Exit code for input %s != 0", emptyData)) 216 | c.Check(string(MustReadFile(outputFile)), DeepEquals, string(MustReadFile(expectedFile))) 217 | } 218 | } 219 | 220 | func (s *p2Integration) TestReplaceFilter(c *C) { 221 | { 222 | const templateFile string = "tests/data.replace.p2" 223 | const emptyData string = "tests/data.replace.json" 224 | 225 | // This test uses the write_file filter to produce its output. 226 | const outputFile string = "tests/data.replace.test" 227 | const expectedFile string = "tests/data.replace.out" 228 | 229 | entrypointArgs := entrypoint.LaunchArgs{ 230 | StdIn: os.Stdin, 231 | StdOut: os.Stdout, 232 | StdErr: os.Stderr, 233 | Env: lo.Must(envutil.FromEnvironment(os.Environ())), 234 | Args: []string{"-t", templateFile, "-i", emptyData, "-o", outputFile}, 235 | } 236 | 237 | exit := entrypoint.Entrypoint(entrypointArgs) 238 | c.Assert(exit, Equals, 0, Commentf("Exit code for input %s != 0", emptyData)) 239 | c.Check(string(MustReadFile(outputFile)), DeepEquals, string(MustReadFile(expectedFile))) 240 | } 241 | } 242 | 243 | func (s *p2Integration) TestStructuredFilters(c *C) { 244 | const templateFile string = "tests/data.structured.p2" 245 | const emptyData string = "tests/data.structured.json" 246 | 247 | // This test uses the write_file filter to produce its output. 248 | const outputFile string = "tests/data.structured.test" 249 | const expectedFile string = "tests/data.structured.out" 250 | 251 | entrypointArgs := entrypoint.LaunchArgs{ 252 | StdIn: os.Stdin, 253 | StdOut: os.Stdout, 254 | StdErr: os.Stderr, 255 | Env: lo.Must(envutil.FromEnvironment(os.Environ())), 256 | Args: []string{"-t", templateFile, "-i", emptyData, "-o", outputFile}, 257 | } 258 | 259 | exit := entrypoint.Entrypoint(entrypointArgs) 260 | c.Assert(exit, Equals, 0, Commentf("Exit code for input %s != 0", emptyData)) 261 | c.Check(string(MustReadFile(outputFile)), DeepEquals, string(MustReadFile(expectedFile))) 262 | } 263 | 264 | func (s *p2Integration) TestBase64Filters(c *C) { 265 | const templateFile string = "tests/data.base64.p2" 266 | const emptyData string = "tests/data.base64.json" 267 | 268 | // This test uses the write_file filter to produce its output. 269 | const outputFile string = "tests/data.base64.test" 270 | const expectedFile string = "tests/data.base64.out" 271 | entrypointArgs := entrypoint.LaunchArgs{ 272 | StdIn: os.Stdin, 273 | StdOut: os.Stdout, 274 | StdErr: os.Stderr, 275 | Env: lo.Must(envutil.FromEnvironment(os.Environ())), 276 | Args: []string{"-t", templateFile, "-i", emptyData, "-o", outputFile}, 277 | } 278 | exit := entrypoint.Entrypoint(entrypointArgs) 279 | c.Assert(exit, Equals, 0, Commentf("Exit code for input %s != 0", emptyData)) 280 | c.Check(string(MustReadFile(outputFile)), DeepEquals, string(MustReadFile(expectedFile))) 281 | } 282 | 283 | func (s *p2Integration) TestStringFilters(c *C) { 284 | const templateFile string = "tests/data.string_filters.p2" 285 | const emptyData string = "tests/data.string_filters.json" 286 | 287 | // This test uses the write_file filter to produce its output. 288 | const outputFile string = "tests/data.string_filters.test" 289 | const expectedFile string = "tests/data.string_filters.out" 290 | entrypointArgs := entrypoint.LaunchArgs{ 291 | StdIn: os.Stdin, 292 | StdOut: os.Stdout, 293 | StdErr: os.Stderr, 294 | Env: lo.Must(envutil.FromEnvironment(os.Environ())), 295 | Args: []string{"-t", templateFile, "-i", emptyData, "-o", outputFile}, 296 | } 297 | exit := entrypoint.Entrypoint(entrypointArgs) 298 | c.Assert(exit, Equals, 0, Commentf("Exit code for input %s != 0", emptyData)) 299 | c.Check(string(MustReadFile(outputFile)), DeepEquals, string(MustReadFile(expectedFile))) 300 | } 301 | 302 | func (s *p2Integration) TestGzipFilters(c *C) { 303 | const templateFile string = "tests/data.gzip.p2" 304 | const emptyData string = "tests/data.gzip.json" 305 | 306 | // This test uses the write_file filter to produce its output. 307 | const outputFile string = "tests/data.gzip.test" 308 | const expectedFile string = "tests/data.gzip.out" 309 | entrypointArgs := entrypoint.LaunchArgs{ 310 | StdIn: os.Stdin, 311 | StdOut: os.Stdout, 312 | StdErr: os.Stderr, 313 | Env: lo.Must(envutil.FromEnvironment(os.Environ())), 314 | Args: []string{"-t", templateFile, "-i", emptyData, "-o", outputFile}, 315 | } 316 | exit := entrypoint.Entrypoint(entrypointArgs) 317 | c.Assert(exit, Equals, 0, Commentf("Exit code for input %s != 0", emptyData)) 318 | c.Check(string(MustReadFile(outputFile)), DeepEquals, string(MustReadFile(expectedFile))) 319 | } 320 | 321 | func (s *p2Integration) TestCustomFilters(c *C) { 322 | { 323 | const templateFile string = "tests/data.write_file.p2" 324 | const emptyData string = "tests/data.write_file.json" 325 | 326 | // This test uses the write_file filter to produce its output. 327 | const outputFile string = "tests/data.write_file.test" 328 | const expectedFile string = "tests/data.write_file.out" 329 | entrypointArgs := entrypoint.LaunchArgs{ 330 | StdIn: os.Stdin, 331 | StdOut: os.Stdout, 332 | StdErr: os.Stderr, 333 | Env: lo.Must(envutil.FromEnvironment(os.Environ())), 334 | Args: []string{"-t", templateFile, "-i", emptyData, "--enable-filters=write_file"}, 335 | } 336 | exit := entrypoint.Entrypoint(entrypointArgs) 337 | c.Assert(exit, Equals, 0, Commentf("Exit code for input %s != 0", emptyData)) 338 | c.Check(string(MustReadFile(outputFile)), DeepEquals, string(MustReadFile(expectedFile))) 339 | } 340 | // TestMakeDirsFilter tests that make dirs makes a named directory, and also 341 | // passes it's content through to write_file successfully. 342 | { 343 | const templateFile string = "tests/data.make_dirs.p2" 344 | const emptyData string = "tests/data.make_dirs.json" 345 | 346 | // This test uses the write_file filter to produce its output. 347 | const outputFile string = "tests/data.make_dirs.test" 348 | const expectedFile string = "tests/data.make_dirs.out" 349 | entrypointArgs := entrypoint.LaunchArgs{ 350 | StdIn: os.Stdin, 351 | StdOut: os.Stdout, 352 | StdErr: os.Stderr, 353 | Env: lo.Must(envutil.FromEnvironment(os.Environ())), 354 | Args: []string{"-t", templateFile, "-i", emptyData, "--enable-filters=make_dirs"}, 355 | } 356 | exit := entrypoint.Entrypoint(entrypointArgs) 357 | c.Assert(exit, Equals, 0, Commentf("Exit code for input %s != 0", emptyData)) 358 | c.Check(string(MustReadFile(outputFile)), DeepEquals, string(MustReadFile(expectedFile))) 359 | 360 | const createdDirectory string = "tests/make_dirs.test" 361 | st, err := os.Stat(createdDirectory) 362 | c.Assert(err, IsNil, Commentf("make_dirs didn't make a directory")) 363 | c.Assert(st.IsDir(), Equals, true, Commentf("didn't get a directory from make_dirs?")) 364 | // Remove the directory to avoid weird asseerts on subsequent runs 365 | os.RemoveAll(createdDirectory) 366 | } 367 | } 368 | 369 | func (s *p2Integration) TestDirectoryMode(c *C) { 370 | entrypointArgs := entrypoint.LaunchArgs{ 371 | StdIn: os.Stdin, 372 | StdOut: os.Stdout, 373 | StdErr: os.Stderr, 374 | Env: lo.Must(envutil.FromEnvironment(os.Environ())), 375 | Args: []string{"--directory-mode", "-t", "tests/directory-mode/templates", 376 | "-o", "tests/directory-mode/output"}, 377 | } 378 | 379 | exit := entrypoint.Entrypoint(entrypointArgs) 380 | c.Assert(exit, Equals, 0, Commentf("Exit code for directory mode != 0")) 381 | } 382 | 383 | func (s *p2Integration) TestTarFileDirectoryModeWithOutputPath(c *C) { 384 | const tarName = "tests/directory-mode/tar-outputpath.tar" 385 | 386 | entrypointArgs := entrypoint.LaunchArgs{ 387 | StdIn: os.Stdin, 388 | StdOut: os.Stdout, 389 | StdErr: os.Stderr, 390 | Env: lo.Must(envutil.FromEnvironment(os.Environ())), 391 | Args: []string{"--directory-mode", "-t", "tests/directory-mode/templates", 392 | "-o", "tests/directory-mode/output", "--tar", tarName}, 393 | } 394 | 395 | exit := entrypoint.Entrypoint(entrypointArgs) 396 | c.Assert(exit, Equals, 0, Commentf("Exit code for directory mode with --tar != 0")) 397 | 398 | tarFile := lo.Must(os.Open(tarName)) 399 | 400 | entries := map[string]struct{}{} 401 | 402 | tarReader := tar.NewReader(tarFile) 403 | for { 404 | header, err := tarReader.Next() 405 | if errors.Is(err, io.EOF) { 406 | break 407 | } 408 | entries[header.Name] = struct{}{} 409 | } 410 | 411 | for _, expected := range []string{"tests/directory-mode/output/dir1/dir2/template2", 412 | "tests/directory-mode/output/dir1/template1", 413 | "tests/directory-mode/output/dir3/template3"} { 414 | _, ok := entries[expected] 415 | c.Check(ok, Equals, true, Commentf("%s expected but not found in tar archive", expected)) 416 | } 417 | } 418 | 419 | func (s *p2Integration) TestTarFileDirectoryModeWithDefaultOutputPath(c *C) { 420 | const tarName = "tests/directory-mode/tar-default.tar" 421 | 422 | entrypointArgs := entrypoint.LaunchArgs{ 423 | StdIn: os.Stdin, 424 | StdOut: os.Stdout, 425 | StdErr: os.Stderr, 426 | Env: lo.Must(envutil.FromEnvironment(os.Environ())), 427 | Args: []string{"--directory-mode", "-t", "tests/directory-mode/templates", "--tar", tarName}, 428 | } 429 | 430 | exit := entrypoint.Entrypoint(entrypointArgs) 431 | c.Assert(exit, Equals, 0, Commentf("Exit code for directory mode with --tar != 0")) 432 | 433 | tarFile := lo.Must(os.Open(tarName)) 434 | 435 | entries := map[string]struct{}{} 436 | 437 | tarReader := tar.NewReader(tarFile) 438 | for { 439 | header, err := tarReader.Next() 440 | if errors.Is(err, io.EOF) { 441 | break 442 | } 443 | entries[header.Name] = struct{}{} 444 | } 445 | 446 | for _, expected := range []string{"dir1/dir2/template2", "dir1/template1", "dir3/template3"} { 447 | _, ok := entries[expected] 448 | c.Check(ok, Equals, true, Commentf("%s expected but not found in tar archive", expected)) 449 | } 450 | } 451 | 452 | func (s *p2Integration) TestFilenameSubstringDeleteForDirectoryMode(c *C) { 453 | testOutputDir := c.MkDir() 454 | 455 | entrypointArgs := entrypoint.LaunchArgs{ 456 | StdIn: os.Stdin, 457 | StdOut: os.Stdout, 458 | StdErr: os.Stderr, 459 | Env: lo.Must(envutil.FromEnvironment(os.Environ())), 460 | Args: []string{"--directory-mode", "--directory-mode-filename-substr-del", ".tmpl", "-t", "tests/directory-mode-filename-transform/templates", 461 | "-o", testOutputDir}, 462 | } 463 | 464 | exit := entrypoint.Entrypoint(entrypointArgs) 465 | c.Assert(exit, Equals, 0, Commentf("Exit code for deleting substring in filename when in directory mode != 0")) 466 | 467 | // Check output file exists 468 | expectedFilePath := path.Join(testOutputDir, "dir1/template1.txt") 469 | if _, err := os.Stat(expectedFilePath); err != nil { 470 | c.Logf("Expected file: %s does not exist\n", expectedFilePath) 471 | c.Fail() 472 | } 473 | } 474 | 475 | // TestInvalidEnvironmentVariables tests that invalid environment variables in the input still allow the the template 476 | // to be generated successfully. 477 | func (s *p2Integration) TestInvalidEnvironmentVariables(c *C) { 478 | entrypointArgs := entrypoint.LaunchArgs{ 479 | StdIn: os.Stdin, 480 | StdOut: os.Stdout, 481 | StdErr: os.Stderr, 482 | Env: lo.Must(envutil.FromEnvironment(os.Environ())), 483 | Args: []string{"-t", "tests/data.p2", "-o", "data.invalid.env"}, 484 | } 485 | 486 | entrypointArgs.Env["BASH_FUNC_x%%"] = "somevalue" 487 | 488 | // Check that invalid OS identifiers are filtered out. 489 | exit := entrypoint.Entrypoint(entrypointArgs) 490 | c.Check(exit, Equals, 0) 491 | c.Assert(exit, Equals, 0, Commentf("Exit code with invalid data in environment != 0")) 492 | } 493 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data-invalid.env: -------------------------------------------------------------------------------- 1 | simple_value1="a value" 2 | simple_value2=a\ value 3 | BASH_FUNC_govc-fys%%="a value" -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.base64.json: -------------------------------------------------------------------------------- 1 | { 2 | "encoded": "c29tZSB2YWx1ZQ==", 3 | "decoded": "some value" 4 | } -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.base64.out: -------------------------------------------------------------------------------- 1 | to_base64 2 | c29tZSB2YWx1ZQ== 3 | 4 | from_base64 5 | some value 6 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.base64.p2: -------------------------------------------------------------------------------- 1 | to_base64 2 | {{decoded|to_base64}} 3 | 4 | from_base64 5 | {{encoded|from_base64|string}} 6 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.base64.test: -------------------------------------------------------------------------------- 1 | to_base64 2 | c29tZSB2YWx1ZQ== 3 | 4 | from_base64 5 | some value 6 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.env: -------------------------------------------------------------------------------- 1 | simple_value1="a value" 2 | simple_value2=a\ value 3 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.env.1.test: -------------------------------------------------------------------------------- 1 | a value 2 | a value 3 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.env.2.test: -------------------------------------------------------------------------------- 1 | a value 2 | a value 3 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.env.3.test: -------------------------------------------------------------------------------- 1 | a value 2 | a value 3 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.env.4.test: -------------------------------------------------------------------------------- 1 | a value 2 | a value 3 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.gzip.json: -------------------------------------------------------------------------------- 1 | { 2 | "s": "text to convert a few times" 3 | } -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.gzip.out: -------------------------------------------------------------------------------- 1 | to_gzip / from_gzip 2 | text to convert a few times -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.gzip.p2: -------------------------------------------------------------------------------- 1 | to_gzip / from_gzip 2 | {{ s | bytes | to_gzip | from_gzip | string }} -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.gzip.test: -------------------------------------------------------------------------------- 1 | to_gzip / from_gzip 2 | text to convert a few times -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.indent.json: -------------------------------------------------------------------------------- 1 | { 2 | "cert": "-----BEGIN CERTIFICATE-----\nMIIFjTCCA3WgAwIBAgIRANOxciY0IzLc9AUoUSrsnGowDQYJKoZIhvcNAQELBQAw\nTzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\ncmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTYxMDA2MTU0MzU1\nWhcNMjExMDA2MTU0MzU1WjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg\nRW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDMwggEi\nMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc0wzwWuUuR7dyXTeDs2hjMOrX\nNSYZJeG9vjXxcJIvt7hLQQWrqZ41CFjssSrEaIcLo+N15Obzp2JxunmBYB/XkZqf\n89B4Z3HIaQ6Vkc/+5pnpYDxIzH7KTXcSJJ1HG1rrueweNwAcnKx7pwXqzkrrvUHl\nNpi5y/1tPJZo3yMqQpAMhnRnyH+lmrhSYRQTP2XpgofL2/oOVvaGifOFP5eGr7Dc\nGu9rDZUWfcQroGWymQQ2dYBrrErzG5BJeC+ilk8qICUpBMZ0wNAxzY8xOJUWuqgz\nuEPxsR/DMH+ieTETPS02+OP88jNquTkxxa/EjQ0dZBYzqvqEKbbUC8DYfcOTAgMB\nAAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBU\nBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIB\nFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBSo\nSmpjBH3duubRObemRWXv86jsoTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js\nLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEF\nBQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsG\nAQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYD\nVR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIB\nABnPdSA0LTqmRf/Q1eaM2jLonG4bQdEnqOJQ8nCqxOeTRrToEKtwT++36gTSlBGx\nA/5dut82jJQ2jxN8RI8L9QFXrWi4xXnA2EqA10yjHiR6H9cj6MFiOnb5In1eWsRM\nUM2v3e9tNsCAgBukPHAg1lQh07rvFKm/Bz9BCjaxorALINUfZ9DD64j2igLIxle2\nDPxW8dI/F2loHMjXZjqG8RkqZUdoxtID5+90FgsGIfkMpqgRS05f4zPbCEHqCXl1\neO5HyELTgcVlLXXQDgAWnRzut1hFJeczY1tjQQno6f6s+nMydLN26WuU4s3UYvOu\nOsUxRlJu7TSRHqDC3lSE5XggVkzdaPkuKGQbGpny+01/47hfXXNB7HntWNZ6N2Vw\np7G6OfY+YQrZwIaQmhrIqJZuigsrbe3W+gdn5ykE9+Ky0VgVUsfxo52mwFYs1JKY\n2PGDuWx8M6DlS6qQkvHaRUo0FMd8TsSlbF0/v965qGFKhSDeQoMpYnwcmQilRh/0\nayLThlHLN81gSkJjVrPI0Y8xCVPB4twb1PFUd2fPM3sA1tJ83sZ5v8vgFv2yofKR\nPB0t6JzUA81mSqM3kxl5e+IZwhYAyO0OTg3/fs8HqGTNKd9BqoUwSRBzp06JMg5b\nrUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt\n-----END CERTIFICATE-----\n" 3 | } -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.indent.out: -------------------------------------------------------------------------------- 1 | String Indent 2 | -----BEGIN CERTIFICATE----- 3 | MIIFjTCCA3WgAwIBAgIRANOxciY0IzLc9AUoUSrsnGowDQYJKoZIhvcNAQELBQAw 4 | TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh 5 | cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTYxMDA2MTU0MzU1 6 | WhcNMjExMDA2MTU0MzU1WjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg 7 | RW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDMwggEi 8 | MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc0wzwWuUuR7dyXTeDs2hjMOrX 9 | NSYZJeG9vjXxcJIvt7hLQQWrqZ41CFjssSrEaIcLo+N15Obzp2JxunmBYB/XkZqf 10 | 89B4Z3HIaQ6Vkc/+5pnpYDxIzH7KTXcSJJ1HG1rrueweNwAcnKx7pwXqzkrrvUHl 11 | Npi5y/1tPJZo3yMqQpAMhnRnyH+lmrhSYRQTP2XpgofL2/oOVvaGifOFP5eGr7Dc 12 | Gu9rDZUWfcQroGWymQQ2dYBrrErzG5BJeC+ilk8qICUpBMZ0wNAxzY8xOJUWuqgz 13 | uEPxsR/DMH+ieTETPS02+OP88jNquTkxxa/EjQ0dZBYzqvqEKbbUC8DYfcOTAgMB 14 | AAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBU 15 | BgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIB 16 | FiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBSo 17 | SmpjBH3duubRObemRWXv86jsoTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js 18 | LnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEF 19 | BQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsG 20 | AQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYD 21 | VR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIB 22 | ABnPdSA0LTqmRf/Q1eaM2jLonG4bQdEnqOJQ8nCqxOeTRrToEKtwT++36gTSlBGx 23 | A/5dut82jJQ2jxN8RI8L9QFXrWi4xXnA2EqA10yjHiR6H9cj6MFiOnb5In1eWsRM 24 | UM2v3e9tNsCAgBukPHAg1lQh07rvFKm/Bz9BCjaxorALINUfZ9DD64j2igLIxle2 25 | DPxW8dI/F2loHMjXZjqG8RkqZUdoxtID5+90FgsGIfkMpqgRS05f4zPbCEHqCXl1 26 | eO5HyELTgcVlLXXQDgAWnRzut1hFJeczY1tjQQno6f6s+nMydLN26WuU4s3UYvOu 27 | OsUxRlJu7TSRHqDC3lSE5XggVkzdaPkuKGQbGpny+01/47hfXXNB7HntWNZ6N2Vw 28 | p7G6OfY+YQrZwIaQmhrIqJZuigsrbe3W+gdn5ykE9+Ky0VgVUsfxo52mwFYs1JKY 29 | 2PGDuWx8M6DlS6qQkvHaRUo0FMd8TsSlbF0/v965qGFKhSDeQoMpYnwcmQilRh/0 30 | ayLThlHLN81gSkJjVrPI0Y8xCVPB4twb1PFUd2fPM3sA1tJ83sZ5v8vgFv2yofKR 31 | PB0t6JzUA81mSqM3kxl5e+IZwhYAyO0OTg3/fs8HqGTNKd9BqoUwSRBzp06JMg5b 32 | rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt 33 | -----END CERTIFICATE----- 34 | 35 | 36 | Number Indent 37 | -----BEGIN CERTIFICATE----- 38 | MIIFjTCCA3WgAwIBAgIRANOxciY0IzLc9AUoUSrsnGowDQYJKoZIhvcNAQELBQAw 39 | TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh 40 | cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTYxMDA2MTU0MzU1 41 | WhcNMjExMDA2MTU0MzU1WjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg 42 | RW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDMwggEi 43 | MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc0wzwWuUuR7dyXTeDs2hjMOrX 44 | NSYZJeG9vjXxcJIvt7hLQQWrqZ41CFjssSrEaIcLo+N15Obzp2JxunmBYB/XkZqf 45 | 89B4Z3HIaQ6Vkc/+5pnpYDxIzH7KTXcSJJ1HG1rrueweNwAcnKx7pwXqzkrrvUHl 46 | Npi5y/1tPJZo3yMqQpAMhnRnyH+lmrhSYRQTP2XpgofL2/oOVvaGifOFP5eGr7Dc 47 | Gu9rDZUWfcQroGWymQQ2dYBrrErzG5BJeC+ilk8qICUpBMZ0wNAxzY8xOJUWuqgz 48 | uEPxsR/DMH+ieTETPS02+OP88jNquTkxxa/EjQ0dZBYzqvqEKbbUC8DYfcOTAgMB 49 | AAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBU 50 | BgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIB 51 | FiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBSo 52 | SmpjBH3duubRObemRWXv86jsoTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js 53 | LnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEF 54 | BQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsG 55 | AQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYD 56 | VR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIB 57 | ABnPdSA0LTqmRf/Q1eaM2jLonG4bQdEnqOJQ8nCqxOeTRrToEKtwT++36gTSlBGx 58 | A/5dut82jJQ2jxN8RI8L9QFXrWi4xXnA2EqA10yjHiR6H9cj6MFiOnb5In1eWsRM 59 | UM2v3e9tNsCAgBukPHAg1lQh07rvFKm/Bz9BCjaxorALINUfZ9DD64j2igLIxle2 60 | DPxW8dI/F2loHMjXZjqG8RkqZUdoxtID5+90FgsGIfkMpqgRS05f4zPbCEHqCXl1 61 | eO5HyELTgcVlLXXQDgAWnRzut1hFJeczY1tjQQno6f6s+nMydLN26WuU4s3UYvOu 62 | OsUxRlJu7TSRHqDC3lSE5XggVkzdaPkuKGQbGpny+01/47hfXXNB7HntWNZ6N2Vw 63 | p7G6OfY+YQrZwIaQmhrIqJZuigsrbe3W+gdn5ykE9+Ky0VgVUsfxo52mwFYs1JKY 64 | 2PGDuWx8M6DlS6qQkvHaRUo0FMd8TsSlbF0/v965qGFKhSDeQoMpYnwcmQilRh/0 65 | ayLThlHLN81gSkJjVrPI0Y8xCVPB4twb1PFUd2fPM3sA1tJ83sZ5v8vgFv2yofKR 66 | PB0t6JzUA81mSqM3kxl5e+IZwhYAyO0OTg3/fs8HqGTNKd9BqoUwSRBzp06JMg5b 67 | rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt 68 | -----END CERTIFICATE----- 69 | 70 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.indent.p2: -------------------------------------------------------------------------------- 1 | String Indent 2 | {{cert|indent:" "}} 3 | 4 | Number Indent 5 | {{cert|indent:8}} 6 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.indent.test: -------------------------------------------------------------------------------- 1 | String Indent 2 | -----BEGIN CERTIFICATE----- 3 | MIIFjTCCA3WgAwIBAgIRANOxciY0IzLc9AUoUSrsnGowDQYJKoZIhvcNAQELBQAw 4 | TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh 5 | cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTYxMDA2MTU0MzU1 6 | WhcNMjExMDA2MTU0MzU1WjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg 7 | RW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDMwggEi 8 | MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc0wzwWuUuR7dyXTeDs2hjMOrX 9 | NSYZJeG9vjXxcJIvt7hLQQWrqZ41CFjssSrEaIcLo+N15Obzp2JxunmBYB/XkZqf 10 | 89B4Z3HIaQ6Vkc/+5pnpYDxIzH7KTXcSJJ1HG1rrueweNwAcnKx7pwXqzkrrvUHl 11 | Npi5y/1tPJZo3yMqQpAMhnRnyH+lmrhSYRQTP2XpgofL2/oOVvaGifOFP5eGr7Dc 12 | Gu9rDZUWfcQroGWymQQ2dYBrrErzG5BJeC+ilk8qICUpBMZ0wNAxzY8xOJUWuqgz 13 | uEPxsR/DMH+ieTETPS02+OP88jNquTkxxa/EjQ0dZBYzqvqEKbbUC8DYfcOTAgMB 14 | AAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBU 15 | BgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIB 16 | FiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBSo 17 | SmpjBH3duubRObemRWXv86jsoTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js 18 | LnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEF 19 | BQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsG 20 | AQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYD 21 | VR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIB 22 | ABnPdSA0LTqmRf/Q1eaM2jLonG4bQdEnqOJQ8nCqxOeTRrToEKtwT++36gTSlBGx 23 | A/5dut82jJQ2jxN8RI8L9QFXrWi4xXnA2EqA10yjHiR6H9cj6MFiOnb5In1eWsRM 24 | UM2v3e9tNsCAgBukPHAg1lQh07rvFKm/Bz9BCjaxorALINUfZ9DD64j2igLIxle2 25 | DPxW8dI/F2loHMjXZjqG8RkqZUdoxtID5+90FgsGIfkMpqgRS05f4zPbCEHqCXl1 26 | eO5HyELTgcVlLXXQDgAWnRzut1hFJeczY1tjQQno6f6s+nMydLN26WuU4s3UYvOu 27 | OsUxRlJu7TSRHqDC3lSE5XggVkzdaPkuKGQbGpny+01/47hfXXNB7HntWNZ6N2Vw 28 | p7G6OfY+YQrZwIaQmhrIqJZuigsrbe3W+gdn5ykE9+Ky0VgVUsfxo52mwFYs1JKY 29 | 2PGDuWx8M6DlS6qQkvHaRUo0FMd8TsSlbF0/v965qGFKhSDeQoMpYnwcmQilRh/0 30 | ayLThlHLN81gSkJjVrPI0Y8xCVPB4twb1PFUd2fPM3sA1tJ83sZ5v8vgFv2yofKR 31 | PB0t6JzUA81mSqM3kxl5e+IZwhYAyO0OTg3/fs8HqGTNKd9BqoUwSRBzp06JMg5b 32 | rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt 33 | -----END CERTIFICATE----- 34 | 35 | 36 | Number Indent 37 | -----BEGIN CERTIFICATE----- 38 | MIIFjTCCA3WgAwIBAgIRANOxciY0IzLc9AUoUSrsnGowDQYJKoZIhvcNAQELBQAw 39 | TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh 40 | cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTYxMDA2MTU0MzU1 41 | WhcNMjExMDA2MTU0MzU1WjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg 42 | RW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDMwggEi 43 | MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc0wzwWuUuR7dyXTeDs2hjMOrX 44 | NSYZJeG9vjXxcJIvt7hLQQWrqZ41CFjssSrEaIcLo+N15Obzp2JxunmBYB/XkZqf 45 | 89B4Z3HIaQ6Vkc/+5pnpYDxIzH7KTXcSJJ1HG1rrueweNwAcnKx7pwXqzkrrvUHl 46 | Npi5y/1tPJZo3yMqQpAMhnRnyH+lmrhSYRQTP2XpgofL2/oOVvaGifOFP5eGr7Dc 47 | Gu9rDZUWfcQroGWymQQ2dYBrrErzG5BJeC+ilk8qICUpBMZ0wNAxzY8xOJUWuqgz 48 | uEPxsR/DMH+ieTETPS02+OP88jNquTkxxa/EjQ0dZBYzqvqEKbbUC8DYfcOTAgMB 49 | AAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBU 50 | BgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIB 51 | FiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBSo 52 | SmpjBH3duubRObemRWXv86jsoTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js 53 | LnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEF 54 | BQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsG 55 | AQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYD 56 | VR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIB 57 | ABnPdSA0LTqmRf/Q1eaM2jLonG4bQdEnqOJQ8nCqxOeTRrToEKtwT++36gTSlBGx 58 | A/5dut82jJQ2jxN8RI8L9QFXrWi4xXnA2EqA10yjHiR6H9cj6MFiOnb5In1eWsRM 59 | UM2v3e9tNsCAgBukPHAg1lQh07rvFKm/Bz9BCjaxorALINUfZ9DD64j2igLIxle2 60 | DPxW8dI/F2loHMjXZjqG8RkqZUdoxtID5+90FgsGIfkMpqgRS05f4zPbCEHqCXl1 61 | eO5HyELTgcVlLXXQDgAWnRzut1hFJeczY1tjQQno6f6s+nMydLN26WuU4s3UYvOu 62 | OsUxRlJu7TSRHqDC3lSE5XggVkzdaPkuKGQbGpny+01/47hfXXNB7HntWNZ6N2Vw 63 | p7G6OfY+YQrZwIaQmhrIqJZuigsrbe3W+gdn5ykE9+Ky0VgVUsfxo52mwFYs1JKY 64 | 2PGDuWx8M6DlS6qQkvHaRUo0FMd8TsSlbF0/v965qGFKhSDeQoMpYnwcmQilRh/0 65 | ayLThlHLN81gSkJjVrPI0Y8xCVPB4twb1PFUd2fPM3sA1tJ83sZ5v8vgFv2yofKR 66 | PB0t6JzUA81mSqM3kxl5e+IZwhYAyO0OTg3/fs8HqGTNKd9BqoUwSRBzp06JMg5b 67 | rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt 68 | -----END CERTIFICATE----- 69 | 70 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "simple_value1" : "a value", 3 | "simple_value2" : "a value" 4 | } 5 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.json.1.test: -------------------------------------------------------------------------------- 1 | a value 2 | a value 3 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.json.2.test: -------------------------------------------------------------------------------- 1 | a value 2 | a value 3 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.json.3.test: -------------------------------------------------------------------------------- 1 | a value 2 | a value 3 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.json.4.test: -------------------------------------------------------------------------------- 1 | a value 2 | a value 3 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.make_dirs.json: -------------------------------------------------------------------------------- 1 | { 2 | "make_dirs_data" : "make_dirs\n" 3 | } 4 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.make_dirs.out: -------------------------------------------------------------------------------- 1 | make_dirs 2 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.make_dirs.p2: -------------------------------------------------------------------------------- 1 | {{ make_dirs_data | make_dirs:"tests/make_dirs.test" | write_file:"tests/data.make_dirs.test"}} 2 | 3 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.make_dirs.test: -------------------------------------------------------------------------------- 1 | make_dirs 2 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.out: -------------------------------------------------------------------------------- 1 | a value 2 | a value 3 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.out.overridden: -------------------------------------------------------------------------------- 1 | InTheEnvironment 2 | a value 3 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.p2: -------------------------------------------------------------------------------- 1 | {{simple_value1}} 2 | {{simple_value2}} 3 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.replace.json: -------------------------------------------------------------------------------- 1 | { 2 | "value": "This will have the spaces removed", 3 | "match": "This", 4 | "replace": "Hello", 5 | "count": 1 6 | } -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.replace.out: -------------------------------------------------------------------------------- 1 | String Replace 2 | Thiswillhavethespacesremoved 3 | 4 | String Replace with count 5 | Thiswill have the spaces removed -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.replace.p2: -------------------------------------------------------------------------------- 1 | String Replace 2 | {{ value | replace:[" ", ""] }} 3 | 4 | String Replace with count 5 | {{ value | replace:[" ", "", 1] }} -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.string_filters.json: -------------------------------------------------------------------------------- 1 | { 2 | "s": "text to convert a few times" 3 | } -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.string_filters.out: -------------------------------------------------------------------------------- 1 | string 2 | text to convert a few times 3 | 4 | bytes 5 | text to convert a few times -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.string_filters.p2: -------------------------------------------------------------------------------- 1 | string 2 | {{ s | string | bytes | string }} 3 | 4 | bytes 5 | {{ s | bytes | string | bytes | string }} -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.string_filters.test: -------------------------------------------------------------------------------- 1 | string 2 | text to convert a few times 3 | 4 | bytes 5 | text to convert a few times -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.structured.json: -------------------------------------------------------------------------------- 1 | { 2 | "complex_value": [ 3 | { 4 | "Id": "124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718", 5 | "Created": "2020-06-08T01:34:57.767752272Z", 6 | "Path": "/etc/confluent/docker/run", 7 | "Args": [], 8 | "State": { 9 | "Status": "exited", 10 | "Running": false, 11 | "Paused": false, 12 | "Restarting": false, 13 | "OOMKilled": false, 14 | "Dead": false, 15 | "Pid": 0, 16 | "ExitCode": 143, 17 | "Error": "", 18 | "StartedAt": "2020-06-08T01:34:58.170557665Z", 19 | "FinishedAt": "2020-06-09T07:27:55.135586596Z" 20 | }, 21 | "Image": "sha256:124ff6469e3d01eb72c58afa0668a5a19f7bf0318355e98847a4b4e28fcddbea", 22 | "ResolvConfPath": "/var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/resolv.conf", 23 | "HostnamePath": "/var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/hostname", 24 | "HostsPath": "/var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/hosts", 25 | "LogPath": "", 26 | "Name": "/kafka-single-node_zookeeper_1", 27 | "RestartCount": 0, 28 | "Driver": "aufs", 29 | "Platform": "linux", 30 | "MountLabel": "", 31 | "ProcessLabel": "", 32 | "AppArmorProfile": "docker-default", 33 | "ExecIDs": null, 34 | "HostConfig": { 35 | "Binds": [], 36 | "ContainerIDFile": "", 37 | "LogConfig": { 38 | "Type": "journald", 39 | "Config": {} 40 | }, 41 | "NetworkMode": "kafka-single-node_default", 42 | "PortBindings": {}, 43 | "RestartPolicy": { 44 | "Name": "", 45 | "MaximumRetryCount": 0 46 | }, 47 | "AutoRemove": false, 48 | "VolumeDriver": "", 49 | "VolumesFrom": [], 50 | "CapAdd": null, 51 | "CapDrop": null, 52 | "CgroupnsMode": "", 53 | "Dns": [], 54 | "DnsOptions": [], 55 | "DnsSearch": [], 56 | "ExtraHosts": null, 57 | "GroupAdd": null, 58 | "IpcMode": "shareable", 59 | "Cgroup": "", 60 | "Links": null, 61 | "OomScoreAdj": 0, 62 | "PidMode": "", 63 | "Privileged": false, 64 | "PublishAllPorts": false, 65 | "ReadonlyRootfs": false, 66 | "SecurityOpt": null, 67 | "UTSMode": "", 68 | "UsernsMode": "", 69 | "ShmSize": 67108864, 70 | "Runtime": "runc", 71 | "ConsoleSize": [ 72 | 0, 73 | 0 74 | ], 75 | "Isolation": "", 76 | "CpuShares": 0, 77 | "Memory": 0, 78 | "NanoCpus": 0, 79 | "CgroupParent": "", 80 | "BlkioWeight": 0, 81 | "BlkioWeightDevice": null, 82 | "BlkioDeviceReadBps": null, 83 | "BlkioDeviceWriteBps": null, 84 | "BlkioDeviceReadIOps": null, 85 | "BlkioDeviceWriteIOps": null, 86 | "CpuPeriod": 0, 87 | "CpuQuota": 0, 88 | "CpuRealtimePeriod": 0, 89 | "CpuRealtimeRuntime": 0, 90 | "CpusetCpus": "", 91 | "CpusetMems": "", 92 | "Devices": null, 93 | "DeviceCgroupRules": null, 94 | "DeviceRequests": null, 95 | "KernelMemory": 0, 96 | "KernelMemoryTCP": 0, 97 | "MemoryReservation": 0, 98 | "MemorySwap": 0, 99 | "MemorySwappiness": null, 100 | "OomKillDisable": false, 101 | "PidsLimit": null, 102 | "Ulimits": null, 103 | "CpuCount": 0, 104 | "CpuPercent": 0, 105 | "IOMaximumIOps": 0, 106 | "IOMaximumBandwidth": 0, 107 | "MaskedPaths": [ 108 | "/proc/asound", 109 | "/proc/acpi", 110 | "/proc/kcore", 111 | "/proc/keys", 112 | "/proc/latency_stats", 113 | "/proc/timer_list", 114 | "/proc/timer_stats", 115 | "/proc/sched_debug", 116 | "/proc/scsi", 117 | "/sys/firmware" 118 | ], 119 | "ReadonlyPaths": [ 120 | "/proc/bus", 121 | "/proc/fs", 122 | "/proc/irq", 123 | "/proc/sys", 124 | "/proc/sysrq-trigger" 125 | ] 126 | }, 127 | "GraphDriver": { 128 | "Data": null, 129 | "Name": "aufs" 130 | }, 131 | "Mounts": [ 132 | { 133 | "Type": "volume", 134 | "Name": "532ecadc4032756b0c19d7bbe959220fa50d9f8e88b4acd68abfd1def23907bb", 135 | "Source": "/var/lib/docker/volumes/532ecadc4032756b0c19d7bbe959220fa50d9f8e88b4acd68abfd1def23907bb/_data", 136 | "Destination": "/etc/zookeeper/secrets", 137 | "Driver": "local", 138 | "Mode": "", 139 | "RW": true, 140 | "Propagation": "" 141 | }, 142 | { 143 | "Type": "volume", 144 | "Name": "9e902e6c1edf9b8b0eb5c5abc7ccb0a6b1a0617dfcee375e2e150a7589edbe63", 145 | "Source": "/var/lib/docker/volumes/9e902e6c1edf9b8b0eb5c5abc7ccb0a6b1a0617dfcee375e2e150a7589edbe63/_data", 146 | "Destination": "/var/lib/zookeeper/data", 147 | "Driver": "local", 148 | "Mode": "", 149 | "RW": true, 150 | "Propagation": "" 151 | }, 152 | { 153 | "Type": "volume", 154 | "Name": "59c042dc9cc47be579f6f69c303ab1bef9048dc47619eb8d4b79b1df604b28f1", 155 | "Source": "/var/lib/docker/volumes/59c042dc9cc47be579f6f69c303ab1bef9048dc47619eb8d4b79b1df604b28f1/_data", 156 | "Destination": "/var/lib/zookeeper/log", 157 | "Driver": "local", 158 | "Mode": "", 159 | "RW": true, 160 | "Propagation": "" 161 | } 162 | ], 163 | "Config": { 164 | "Hostname": "124403b9f8c9", 165 | "Domainname": "", 166 | "User": "", 167 | "AttachStdin": false, 168 | "AttachStdout": false, 169 | "AttachStderr": false, 170 | "ExposedPorts": { 171 | "2181/tcp": {}, 172 | "2888/tcp": {}, 173 | "3888/tcp": {} 174 | }, 175 | "Tty": false, 176 | "OpenStdin": false, 177 | "StdinOnce": false, 178 | "Env": [ 179 | "ZOOKEEPER_CLIENT_PORT=2181", 180 | "ZOOKEEPER_TICK_TIME=2000", 181 | "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 182 | "ALLOW_UNSIGNED=false", 183 | "PYTHON_VERSION=2.7.9-1", 184 | "PYTHON_PIP_VERSION=8.1.2", 185 | "SCALA_VERSION=2.12", 186 | "KAFKA_VERSION=", 187 | "CONFLUENT_PLATFORM_LABEL=", 188 | "CONFLUENT_VERSION=5.5.0", 189 | "CONFLUENT_DEB_VERSION=1", 190 | "ZULU_OPENJDK_VERSION=8=8.38.0.13", 191 | "LANG=C.UTF-8", 192 | "CUB_CLASSPATH=/etc/confluent/docker/docker-utils.jar", 193 | "COMPONENT=zookeeper" 194 | ], 195 | "Cmd": [ 196 | "/etc/confluent/docker/run" 197 | ], 198 | "Image": "confluentinc/cp-zookeeper:latest", 199 | "Volumes": { 200 | "/etc/zookeeper/secrets": {}, 201 | "/var/lib/zookeeper/data": {}, 202 | "/var/lib/zookeeper/log": {} 203 | }, 204 | "WorkingDir": "", 205 | "Entrypoint": null, 206 | "OnBuild": null, 207 | "Labels": { 208 | "com.docker.compose.config-hash": "b6fa02ab96018cc522f057c093bf80ddefcb6e4e2077821b14cd0f6f1c2fd05b", 209 | "com.docker.compose.container-number": "1", 210 | "com.docker.compose.oneoff": "False", 211 | "com.docker.compose.project": "kafka-single-node", 212 | "com.docker.compose.service": "zookeeper", 213 | "com.docker.compose.version": "1.22.0", 214 | "io.confluent.docker": "true", 215 | "io.confluent.docker.build.number": "3", 216 | "io.confluent.docker.git.id": "9a5edfb", 217 | "io.confluent.docker.git.repo": "confluentinc/kafka-images", 218 | "maintainer": "partner-support@confluent.io" 219 | } 220 | }, 221 | "NetworkSettings": { 222 | "Bridge": "", 223 | "SandboxID": "ffdab23b3bb287542456d9cc7ece3d0662ea9a0f4b956bee7f94a0e7f26e51c1", 224 | "HairpinMode": false, 225 | "LinkLocalIPv6Address": "", 226 | "LinkLocalIPv6PrefixLen": 0, 227 | "Ports": {}, 228 | "SandboxKey": "/var/run/docker/netns/ffdab23b3bb2", 229 | "SecondaryIPAddresses": null, 230 | "SecondaryIPv6Addresses": null, 231 | "EndpointID": "", 232 | "Gateway": "", 233 | "GlobalIPv6Address": "", 234 | "GlobalIPv6PrefixLen": 0, 235 | "IPAddress": "", 236 | "IPPrefixLen": 0, 237 | "IPv6Gateway": "", 238 | "MacAddress": "", 239 | "Networks": { 240 | "kafka-single-node_default": { 241 | "IPAMConfig": null, 242 | "Links": null, 243 | "Aliases": [ 244 | "zookeeper", 245 | "124403b9f8c9" 246 | ], 247 | "NetworkID": "2f0899538ac85dcf98b46ddd30e241c36999e7b5b25ebe19b79066d214bd14f8", 248 | "EndpointID": "", 249 | "Gateway": "", 250 | "IPAddress": "", 251 | "IPPrefixLen": 0, 252 | "IPv6Gateway": "", 253 | "GlobalIPv6Address": "", 254 | "GlobalIPv6PrefixLen": 0, 255 | "MacAddress": "", 256 | "DriverOpts": null 257 | } 258 | } 259 | } 260 | } 261 | ] 262 | } -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.structured.out: -------------------------------------------------------------------------------- 1 | JSON 2 | [{"AppArmorProfile":"docker-default","Args":[],"Config":{"AttachStderr":false,"AttachStdin":false,"AttachStdout":false,"Cmd":["/etc/confluent/docker/run"],"Domainname":"","Entrypoint":null,"Env":["ZOOKEEPER_CLIENT_PORT=2181","ZOOKEEPER_TICK_TIME=2000","PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","ALLOW_UNSIGNED=false","PYTHON_VERSION=2.7.9-1","PYTHON_PIP_VERSION=8.1.2","SCALA_VERSION=2.12","KAFKA_VERSION=","CONFLUENT_PLATFORM_LABEL=","CONFLUENT_VERSION=5.5.0","CONFLUENT_DEB_VERSION=1","ZULU_OPENJDK_VERSION=8=8.38.0.13","LANG=C.UTF-8","CUB_CLASSPATH=/etc/confluent/docker/docker-utils.jar","COMPONENT=zookeeper"],"ExposedPorts":{"2181/tcp":{},"2888/tcp":{},"3888/tcp":{}},"Hostname":"124403b9f8c9","Image":"confluentinc/cp-zookeeper:latest","Labels":{"com.docker.compose.config-hash":"b6fa02ab96018cc522f057c093bf80ddefcb6e4e2077821b14cd0f6f1c2fd05b","com.docker.compose.container-number":"1","com.docker.compose.oneoff":"False","com.docker.compose.project":"kafka-single-node","com.docker.compose.service":"zookeeper","com.docker.compose.version":"1.22.0","io.confluent.docker":"true","io.confluent.docker.build.number":"3","io.confluent.docker.git.id":"9a5edfb","io.confluent.docker.git.repo":"confluentinc/kafka-images","maintainer":"partner-support@confluent.io"},"OnBuild":null,"OpenStdin":false,"StdinOnce":false,"Tty":false,"User":"","Volumes":{"/etc/zookeeper/secrets":{},"/var/lib/zookeeper/data":{},"/var/lib/zookeeper/log":{}},"WorkingDir":""},"Created":"2020-06-08T01:34:57.767752272Z","Driver":"aufs","ExecIDs":null,"GraphDriver":{"Data":null,"Name":"aufs"},"HostConfig":{"AutoRemove":false,"Binds":[],"BlkioDeviceReadBps":null,"BlkioDeviceReadIOps":null,"BlkioDeviceWriteBps":null,"BlkioDeviceWriteIOps":null,"BlkioWeight":0,"BlkioWeightDevice":null,"CapAdd":null,"CapDrop":null,"Cgroup":"","CgroupParent":"","CgroupnsMode":"","ConsoleSize":[0,0],"ContainerIDFile":"","CpuCount":0,"CpuPercent":0,"CpuPeriod":0,"CpuQuota":0,"CpuRealtimePeriod":0,"CpuRealtimeRuntime":0,"CpuShares":0,"CpusetCpus":"","CpusetMems":"","DeviceCgroupRules":null,"DeviceRequests":null,"Devices":null,"Dns":[],"DnsOptions":[],"DnsSearch":[],"ExtraHosts":null,"GroupAdd":null,"IOMaximumBandwidth":0,"IOMaximumIOps":0,"IpcMode":"shareable","Isolation":"","KernelMemory":0,"KernelMemoryTCP":0,"Links":null,"LogConfig":{"Config":{},"Type":"journald"},"MaskedPaths":["/proc/asound","/proc/acpi","/proc/kcore","/proc/keys","/proc/latency_stats","/proc/timer_list","/proc/timer_stats","/proc/sched_debug","/proc/scsi","/sys/firmware"],"Memory":0,"MemoryReservation":0,"MemorySwap":0,"MemorySwappiness":null,"NanoCpus":0,"NetworkMode":"kafka-single-node_default","OomKillDisable":false,"OomScoreAdj":0,"PidMode":"","PidsLimit":null,"PortBindings":{},"Privileged":false,"PublishAllPorts":false,"ReadonlyPaths":["/proc/bus","/proc/fs","/proc/irq","/proc/sys","/proc/sysrq-trigger"],"ReadonlyRootfs":false,"RestartPolicy":{"MaximumRetryCount":0,"Name":""},"Runtime":"runc","SecurityOpt":null,"ShmSize":67108864,"UTSMode":"","Ulimits":null,"UsernsMode":"","VolumeDriver":"","VolumesFrom":[]},"HostnamePath":"/var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/hostname","HostsPath":"/var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/hosts","Id":"124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718","Image":"sha256:124ff6469e3d01eb72c58afa0668a5a19f7bf0318355e98847a4b4e28fcddbea","LogPath":"","MountLabel":"","Mounts":[{"Destination":"/etc/zookeeper/secrets","Driver":"local","Mode":"","Name":"532ecadc4032756b0c19d7bbe959220fa50d9f8e88b4acd68abfd1def23907bb","Propagation":"","RW":true,"Source":"/var/lib/docker/volumes/532ecadc4032756b0c19d7bbe959220fa50d9f8e88b4acd68abfd1def23907bb/_data","Type":"volume"},{"Destination":"/var/lib/zookeeper/data","Driver":"local","Mode":"","Name":"9e902e6c1edf9b8b0eb5c5abc7ccb0a6b1a0617dfcee375e2e150a7589edbe63","Propagation":"","RW":true,"Source":"/var/lib/docker/volumes/9e902e6c1edf9b8b0eb5c5abc7ccb0a6b1a0617dfcee375e2e150a7589edbe63/_data","Type":"volume"},{"Destination":"/var/lib/zookeeper/log","Driver":"local","Mode":"","Name":"59c042dc9cc47be579f6f69c303ab1bef9048dc47619eb8d4b79b1df604b28f1","Propagation":"","RW":true,"Source":"/var/lib/docker/volumes/59c042dc9cc47be579f6f69c303ab1bef9048dc47619eb8d4b79b1df604b28f1/_data","Type":"volume"}],"Name":"/kafka-single-node_zookeeper_1","NetworkSettings":{"Bridge":"","EndpointID":"","Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"HairpinMode":false,"IPAddress":"","IPPrefixLen":0,"IPv6Gateway":"","LinkLocalIPv6Address":"","LinkLocalIPv6PrefixLen":0,"MacAddress":"","Networks":{"kafka-single-node_default":{"Aliases":["zookeeper","124403b9f8c9"],"DriverOpts":null,"EndpointID":"","Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"IPAMConfig":null,"IPAddress":"","IPPrefixLen":0,"IPv6Gateway":"","Links":null,"MacAddress":"","NetworkID":"2f0899538ac85dcf98b46ddd30e241c36999e7b5b25ebe19b79066d214bd14f8"}},"Ports":{},"SandboxID":"ffdab23b3bb287542456d9cc7ece3d0662ea9a0f4b956bee7f94a0e7f26e51c1","SandboxKey":"/var/run/docker/netns/ffdab23b3bb2","SecondaryIPAddresses":null,"SecondaryIPv6Addresses":null},"Path":"/etc/confluent/docker/run","Platform":"linux","ProcessLabel":"","ResolvConfPath":"/var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/resolv.conf","RestartCount":0,"State":{"Dead":false,"Error":"","ExitCode":143,"FinishedAt":"2020-06-09T07:27:55.135586596Z","OOMKilled":false,"Paused":false,"Pid":0,"Restarting":false,"Running":false,"StartedAt":"2020-06-08T01:34:58.170557665Z","Status":"exited"}}] 3 | 4 | JSON Indent 5 | [ 6 | { 7 | "AppArmorProfile": "docker-default", 8 | "Args": [], 9 | "Config": { 10 | "AttachStderr": false, 11 | "AttachStdin": false, 12 | "AttachStdout": false, 13 | "Cmd": [ 14 | "/etc/confluent/docker/run" 15 | ], 16 | "Domainname": "", 17 | "Entrypoint": null, 18 | "Env": [ 19 | "ZOOKEEPER_CLIENT_PORT=2181", 20 | "ZOOKEEPER_TICK_TIME=2000", 21 | "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 22 | "ALLOW_UNSIGNED=false", 23 | "PYTHON_VERSION=2.7.9-1", 24 | "PYTHON_PIP_VERSION=8.1.2", 25 | "SCALA_VERSION=2.12", 26 | "KAFKA_VERSION=", 27 | "CONFLUENT_PLATFORM_LABEL=", 28 | "CONFLUENT_VERSION=5.5.0", 29 | "CONFLUENT_DEB_VERSION=1", 30 | "ZULU_OPENJDK_VERSION=8=8.38.0.13", 31 | "LANG=C.UTF-8", 32 | "CUB_CLASSPATH=/etc/confluent/docker/docker-utils.jar", 33 | "COMPONENT=zookeeper" 34 | ], 35 | "ExposedPorts": { 36 | "2181/tcp": {}, 37 | "2888/tcp": {}, 38 | "3888/tcp": {} 39 | }, 40 | "Hostname": "124403b9f8c9", 41 | "Image": "confluentinc/cp-zookeeper:latest", 42 | "Labels": { 43 | "com.docker.compose.config-hash": "b6fa02ab96018cc522f057c093bf80ddefcb6e4e2077821b14cd0f6f1c2fd05b", 44 | "com.docker.compose.container-number": "1", 45 | "com.docker.compose.oneoff": "False", 46 | "com.docker.compose.project": "kafka-single-node", 47 | "com.docker.compose.service": "zookeeper", 48 | "com.docker.compose.version": "1.22.0", 49 | "io.confluent.docker": "true", 50 | "io.confluent.docker.build.number": "3", 51 | "io.confluent.docker.git.id": "9a5edfb", 52 | "io.confluent.docker.git.repo": "confluentinc/kafka-images", 53 | "maintainer": "partner-support@confluent.io" 54 | }, 55 | "OnBuild": null, 56 | "OpenStdin": false, 57 | "StdinOnce": false, 58 | "Tty": false, 59 | "User": "", 60 | "Volumes": { 61 | "/etc/zookeeper/secrets": {}, 62 | "/var/lib/zookeeper/data": {}, 63 | "/var/lib/zookeeper/log": {} 64 | }, 65 | "WorkingDir": "" 66 | }, 67 | "Created": "2020-06-08T01:34:57.767752272Z", 68 | "Driver": "aufs", 69 | "ExecIDs": null, 70 | "GraphDriver": { 71 | "Data": null, 72 | "Name": "aufs" 73 | }, 74 | "HostConfig": { 75 | "AutoRemove": false, 76 | "Binds": [], 77 | "BlkioDeviceReadBps": null, 78 | "BlkioDeviceReadIOps": null, 79 | "BlkioDeviceWriteBps": null, 80 | "BlkioDeviceWriteIOps": null, 81 | "BlkioWeight": 0, 82 | "BlkioWeightDevice": null, 83 | "CapAdd": null, 84 | "CapDrop": null, 85 | "Cgroup": "", 86 | "CgroupParent": "", 87 | "CgroupnsMode": "", 88 | "ConsoleSize": [ 89 | 0, 90 | 0 91 | ], 92 | "ContainerIDFile": "", 93 | "CpuCount": 0, 94 | "CpuPercent": 0, 95 | "CpuPeriod": 0, 96 | "CpuQuota": 0, 97 | "CpuRealtimePeriod": 0, 98 | "CpuRealtimeRuntime": 0, 99 | "CpuShares": 0, 100 | "CpusetCpus": "", 101 | "CpusetMems": "", 102 | "DeviceCgroupRules": null, 103 | "DeviceRequests": null, 104 | "Devices": null, 105 | "Dns": [], 106 | "DnsOptions": [], 107 | "DnsSearch": [], 108 | "ExtraHosts": null, 109 | "GroupAdd": null, 110 | "IOMaximumBandwidth": 0, 111 | "IOMaximumIOps": 0, 112 | "IpcMode": "shareable", 113 | "Isolation": "", 114 | "KernelMemory": 0, 115 | "KernelMemoryTCP": 0, 116 | "Links": null, 117 | "LogConfig": { 118 | "Config": {}, 119 | "Type": "journald" 120 | }, 121 | "MaskedPaths": [ 122 | "/proc/asound", 123 | "/proc/acpi", 124 | "/proc/kcore", 125 | "/proc/keys", 126 | "/proc/latency_stats", 127 | "/proc/timer_list", 128 | "/proc/timer_stats", 129 | "/proc/sched_debug", 130 | "/proc/scsi", 131 | "/sys/firmware" 132 | ], 133 | "Memory": 0, 134 | "MemoryReservation": 0, 135 | "MemorySwap": 0, 136 | "MemorySwappiness": null, 137 | "NanoCpus": 0, 138 | "NetworkMode": "kafka-single-node_default", 139 | "OomKillDisable": false, 140 | "OomScoreAdj": 0, 141 | "PidMode": "", 142 | "PidsLimit": null, 143 | "PortBindings": {}, 144 | "Privileged": false, 145 | "PublishAllPorts": false, 146 | "ReadonlyPaths": [ 147 | "/proc/bus", 148 | "/proc/fs", 149 | "/proc/irq", 150 | "/proc/sys", 151 | "/proc/sysrq-trigger" 152 | ], 153 | "ReadonlyRootfs": false, 154 | "RestartPolicy": { 155 | "MaximumRetryCount": 0, 156 | "Name": "" 157 | }, 158 | "Runtime": "runc", 159 | "SecurityOpt": null, 160 | "ShmSize": 67108864, 161 | "UTSMode": "", 162 | "Ulimits": null, 163 | "UsernsMode": "", 164 | "VolumeDriver": "", 165 | "VolumesFrom": [] 166 | }, 167 | "HostnamePath": "/var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/hostname", 168 | "HostsPath": "/var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/hosts", 169 | "Id": "124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718", 170 | "Image": "sha256:124ff6469e3d01eb72c58afa0668a5a19f7bf0318355e98847a4b4e28fcddbea", 171 | "LogPath": "", 172 | "MountLabel": "", 173 | "Mounts": [ 174 | { 175 | "Destination": "/etc/zookeeper/secrets", 176 | "Driver": "local", 177 | "Mode": "", 178 | "Name": "532ecadc4032756b0c19d7bbe959220fa50d9f8e88b4acd68abfd1def23907bb", 179 | "Propagation": "", 180 | "RW": true, 181 | "Source": "/var/lib/docker/volumes/532ecadc4032756b0c19d7bbe959220fa50d9f8e88b4acd68abfd1def23907bb/_data", 182 | "Type": "volume" 183 | }, 184 | { 185 | "Destination": "/var/lib/zookeeper/data", 186 | "Driver": "local", 187 | "Mode": "", 188 | "Name": "9e902e6c1edf9b8b0eb5c5abc7ccb0a6b1a0617dfcee375e2e150a7589edbe63", 189 | "Propagation": "", 190 | "RW": true, 191 | "Source": "/var/lib/docker/volumes/9e902e6c1edf9b8b0eb5c5abc7ccb0a6b1a0617dfcee375e2e150a7589edbe63/_data", 192 | "Type": "volume" 193 | }, 194 | { 195 | "Destination": "/var/lib/zookeeper/log", 196 | "Driver": "local", 197 | "Mode": "", 198 | "Name": "59c042dc9cc47be579f6f69c303ab1bef9048dc47619eb8d4b79b1df604b28f1", 199 | "Propagation": "", 200 | "RW": true, 201 | "Source": "/var/lib/docker/volumes/59c042dc9cc47be579f6f69c303ab1bef9048dc47619eb8d4b79b1df604b28f1/_data", 202 | "Type": "volume" 203 | } 204 | ], 205 | "Name": "/kafka-single-node_zookeeper_1", 206 | "NetworkSettings": { 207 | "Bridge": "", 208 | "EndpointID": "", 209 | "Gateway": "", 210 | "GlobalIPv6Address": "", 211 | "GlobalIPv6PrefixLen": 0, 212 | "HairpinMode": false, 213 | "IPAddress": "", 214 | "IPPrefixLen": 0, 215 | "IPv6Gateway": "", 216 | "LinkLocalIPv6Address": "", 217 | "LinkLocalIPv6PrefixLen": 0, 218 | "MacAddress": "", 219 | "Networks": { 220 | "kafka-single-node_default": { 221 | "Aliases": [ 222 | "zookeeper", 223 | "124403b9f8c9" 224 | ], 225 | "DriverOpts": null, 226 | "EndpointID": "", 227 | "Gateway": "", 228 | "GlobalIPv6Address": "", 229 | "GlobalIPv6PrefixLen": 0, 230 | "IPAMConfig": null, 231 | "IPAddress": "", 232 | "IPPrefixLen": 0, 233 | "IPv6Gateway": "", 234 | "Links": null, 235 | "MacAddress": "", 236 | "NetworkID": "2f0899538ac85dcf98b46ddd30e241c36999e7b5b25ebe19b79066d214bd14f8" 237 | } 238 | }, 239 | "Ports": {}, 240 | "SandboxID": "ffdab23b3bb287542456d9cc7ece3d0662ea9a0f4b956bee7f94a0e7f26e51c1", 241 | "SandboxKey": "/var/run/docker/netns/ffdab23b3bb2", 242 | "SecondaryIPAddresses": null, 243 | "SecondaryIPv6Addresses": null 244 | }, 245 | "Path": "/etc/confluent/docker/run", 246 | "Platform": "linux", 247 | "ProcessLabel": "", 248 | "ResolvConfPath": "/var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/resolv.conf", 249 | "RestartCount": 0, 250 | "State": { 251 | "Dead": false, 252 | "Error": "", 253 | "ExitCode": 143, 254 | "FinishedAt": "2020-06-09T07:27:55.135586596Z", 255 | "OOMKilled": false, 256 | "Paused": false, 257 | "Pid": 0, 258 | "Restarting": false, 259 | "Running": false, 260 | "StartedAt": "2020-06-08T01:34:58.170557665Z", 261 | "Status": "exited" 262 | } 263 | } 264 | ] 265 | 266 | YAML 267 | - AppArmorProfile: docker-default 268 | Args: [] 269 | Config: 270 | AttachStderr: false 271 | AttachStdin: false 272 | AttachStdout: false 273 | Cmd: 274 | - /etc/confluent/docker/run 275 | Domainname: "" 276 | Entrypoint: null 277 | Env: 278 | - ZOOKEEPER_CLIENT_PORT=2181 279 | - ZOOKEEPER_TICK_TIME=2000 280 | - PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 281 | - ALLOW_UNSIGNED=false 282 | - PYTHON_VERSION=2.7.9-1 283 | - PYTHON_PIP_VERSION=8.1.2 284 | - SCALA_VERSION=2.12 285 | - KAFKA_VERSION= 286 | - CONFLUENT_PLATFORM_LABEL= 287 | - CONFLUENT_VERSION=5.5.0 288 | - CONFLUENT_DEB_VERSION=1 289 | - ZULU_OPENJDK_VERSION=8=8.38.0.13 290 | - LANG=C.UTF-8 291 | - CUB_CLASSPATH=/etc/confluent/docker/docker-utils.jar 292 | - COMPONENT=zookeeper 293 | ExposedPorts: 294 | 2181/tcp: {} 295 | 2888/tcp: {} 296 | 3888/tcp: {} 297 | Hostname: 124403b9f8c9 298 | Image: confluentinc/cp-zookeeper:latest 299 | Labels: 300 | com.docker.compose.config-hash: b6fa02ab96018cc522f057c093bf80ddefcb6e4e2077821b14cd0f6f1c2fd05b 301 | com.docker.compose.container-number: "1" 302 | com.docker.compose.oneoff: "False" 303 | com.docker.compose.project: kafka-single-node 304 | com.docker.compose.service: zookeeper 305 | com.docker.compose.version: 1.22.0 306 | io.confluent.docker: "true" 307 | io.confluent.docker.build.number: "3" 308 | io.confluent.docker.git.id: 9a5edfb 309 | io.confluent.docker.git.repo: confluentinc/kafka-images 310 | maintainer: partner-support@confluent.io 311 | OnBuild: null 312 | OpenStdin: false 313 | StdinOnce: false 314 | Tty: false 315 | User: "" 316 | Volumes: 317 | /etc/zookeeper/secrets: {} 318 | /var/lib/zookeeper/data: {} 319 | /var/lib/zookeeper/log: {} 320 | WorkingDir: "" 321 | Created: "2020-06-08T01:34:57.767752272Z" 322 | Driver: aufs 323 | ExecIDs: null 324 | GraphDriver: 325 | Data: null 326 | Name: aufs 327 | HostConfig: 328 | AutoRemove: false 329 | Binds: [] 330 | BlkioDeviceReadBps: null 331 | BlkioDeviceReadIOps: null 332 | BlkioDeviceWriteBps: null 333 | BlkioDeviceWriteIOps: null 334 | BlkioWeight: 0 335 | BlkioWeightDevice: null 336 | CapAdd: null 337 | CapDrop: null 338 | Cgroup: "" 339 | CgroupParent: "" 340 | CgroupnsMode: "" 341 | ConsoleSize: 342 | - 0 343 | - 0 344 | ContainerIDFile: "" 345 | CpuCount: 0 346 | CpuPercent: 0 347 | CpuPeriod: 0 348 | CpuQuota: 0 349 | CpuRealtimePeriod: 0 350 | CpuRealtimeRuntime: 0 351 | CpuShares: 0 352 | CpusetCpus: "" 353 | CpusetMems: "" 354 | DeviceCgroupRules: null 355 | DeviceRequests: null 356 | Devices: null 357 | Dns: [] 358 | DnsOptions: [] 359 | DnsSearch: [] 360 | ExtraHosts: null 361 | GroupAdd: null 362 | IOMaximumBandwidth: 0 363 | IOMaximumIOps: 0 364 | IpcMode: shareable 365 | Isolation: "" 366 | KernelMemory: 0 367 | KernelMemoryTCP: 0 368 | Links: null 369 | LogConfig: 370 | Config: {} 371 | Type: journald 372 | MaskedPaths: 373 | - /proc/asound 374 | - /proc/acpi 375 | - /proc/kcore 376 | - /proc/keys 377 | - /proc/latency_stats 378 | - /proc/timer_list 379 | - /proc/timer_stats 380 | - /proc/sched_debug 381 | - /proc/scsi 382 | - /sys/firmware 383 | Memory: 0 384 | MemoryReservation: 0 385 | MemorySwap: 0 386 | MemorySwappiness: null 387 | NanoCpus: 0 388 | NetworkMode: kafka-single-node_default 389 | OomKillDisable: false 390 | OomScoreAdj: 0 391 | PidMode: "" 392 | PidsLimit: null 393 | PortBindings: {} 394 | Privileged: false 395 | PublishAllPorts: false 396 | ReadonlyPaths: 397 | - /proc/bus 398 | - /proc/fs 399 | - /proc/irq 400 | - /proc/sys 401 | - /proc/sysrq-trigger 402 | ReadonlyRootfs: false 403 | RestartPolicy: 404 | MaximumRetryCount: 0 405 | Name: "" 406 | Runtime: runc 407 | SecurityOpt: null 408 | ShmSize: 6.7108864e+07 409 | UTSMode: "" 410 | Ulimits: null 411 | UsernsMode: "" 412 | VolumeDriver: "" 413 | VolumesFrom: [] 414 | HostnamePath: /var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/hostname 415 | HostsPath: /var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/hosts 416 | Id: 124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718 417 | Image: sha256:124ff6469e3d01eb72c58afa0668a5a19f7bf0318355e98847a4b4e28fcddbea 418 | LogPath: "" 419 | MountLabel: "" 420 | Mounts: 421 | - Destination: /etc/zookeeper/secrets 422 | Driver: local 423 | Mode: "" 424 | Name: 532ecadc4032756b0c19d7bbe959220fa50d9f8e88b4acd68abfd1def23907bb 425 | Propagation: "" 426 | RW: true 427 | Source: /var/lib/docker/volumes/532ecadc4032756b0c19d7bbe959220fa50d9f8e88b4acd68abfd1def23907bb/_data 428 | Type: volume 429 | - Destination: /var/lib/zookeeper/data 430 | Driver: local 431 | Mode: "" 432 | Name: 9e902e6c1edf9b8b0eb5c5abc7ccb0a6b1a0617dfcee375e2e150a7589edbe63 433 | Propagation: "" 434 | RW: true 435 | Source: /var/lib/docker/volumes/9e902e6c1edf9b8b0eb5c5abc7ccb0a6b1a0617dfcee375e2e150a7589edbe63/_data 436 | Type: volume 437 | - Destination: /var/lib/zookeeper/log 438 | Driver: local 439 | Mode: "" 440 | Name: 59c042dc9cc47be579f6f69c303ab1bef9048dc47619eb8d4b79b1df604b28f1 441 | Propagation: "" 442 | RW: true 443 | Source: /var/lib/docker/volumes/59c042dc9cc47be579f6f69c303ab1bef9048dc47619eb8d4b79b1df604b28f1/_data 444 | Type: volume 445 | Name: /kafka-single-node_zookeeper_1 446 | NetworkSettings: 447 | Bridge: "" 448 | EndpointID: "" 449 | Gateway: "" 450 | GlobalIPv6Address: "" 451 | GlobalIPv6PrefixLen: 0 452 | HairpinMode: false 453 | IPAddress: "" 454 | IPPrefixLen: 0 455 | IPv6Gateway: "" 456 | LinkLocalIPv6Address: "" 457 | LinkLocalIPv6PrefixLen: 0 458 | MacAddress: "" 459 | Networks: 460 | kafka-single-node_default: 461 | Aliases: 462 | - zookeeper 463 | - 124403b9f8c9 464 | DriverOpts: null 465 | EndpointID: "" 466 | Gateway: "" 467 | GlobalIPv6Address: "" 468 | GlobalIPv6PrefixLen: 0 469 | IPAMConfig: null 470 | IPAddress: "" 471 | IPPrefixLen: 0 472 | IPv6Gateway: "" 473 | Links: null 474 | MacAddress: "" 475 | NetworkID: 2f0899538ac85dcf98b46ddd30e241c36999e7b5b25ebe19b79066d214bd14f8 476 | Ports: {} 477 | SandboxID: ffdab23b3bb287542456d9cc7ece3d0662ea9a0f4b956bee7f94a0e7f26e51c1 478 | SandboxKey: /var/run/docker/netns/ffdab23b3bb2 479 | SecondaryIPAddresses: null 480 | SecondaryIPv6Addresses: null 481 | Path: /etc/confluent/docker/run 482 | Platform: linux 483 | ProcessLabel: "" 484 | ResolvConfPath: /var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/resolv.conf 485 | RestartCount: 0 486 | State: 487 | Dead: false 488 | Error: "" 489 | ExitCode: 143 490 | FinishedAt: "2020-06-09T07:27:55.135586596Z" 491 | OOMKilled: false 492 | Paused: false 493 | Pid: 0 494 | Restarting: false 495 | Running: false 496 | StartedAt: "2020-06-08T01:34:58.170557665Z" 497 | Status: exited 498 | 499 | 500 | TOML 501 | AppArmorProfile = "docker-default" 502 | Args = [] 503 | Created = "2020-06-08T01:34:57.767752272Z" 504 | Driver = "aufs" 505 | HostnamePath = "/var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/hostname" 506 | HostsPath = "/var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/hosts" 507 | Id = "124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718" 508 | Image = "sha256:124ff6469e3d01eb72c58afa0668a5a19f7bf0318355e98847a4b4e28fcddbea" 509 | LogPath = "" 510 | MountLabel = "" 511 | Mounts = [{ Destination = "/etc/zookeeper/secrets", Driver = "local", Mode = "", Name = "532ecadc4032756b0c19d7bbe959220fa50d9f8e88b4acd68abfd1def23907bb", Propagation = "", RW = true, Source = "/var/lib/docker/volumes/532ecadc4032756b0c19d7bbe959220fa50d9f8e88b4acd68abfd1def23907bb/_data", Type = "volume" }, { Destination = "/var/lib/zookeeper/data", Driver = "local", Mode = "", Name = "9e902e6c1edf9b8b0eb5c5abc7ccb0a6b1a0617dfcee375e2e150a7589edbe63", Propagation = "", RW = true, Source = "/var/lib/docker/volumes/9e902e6c1edf9b8b0eb5c5abc7ccb0a6b1a0617dfcee375e2e150a7589edbe63/_data", Type = "volume" }, { Destination = "/var/lib/zookeeper/log", Driver = "local", Mode = "", Name = "59c042dc9cc47be579f6f69c303ab1bef9048dc47619eb8d4b79b1df604b28f1", Propagation = "", RW = true, Source = "/var/lib/docker/volumes/59c042dc9cc47be579f6f69c303ab1bef9048dc47619eb8d4b79b1df604b28f1/_data", Type = "volume" }] 512 | Name = "/kafka-single-node_zookeeper_1" 513 | Path = "/etc/confluent/docker/run" 514 | Platform = "linux" 515 | ProcessLabel = "" 516 | ResolvConfPath = "/var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/resolv.conf" 517 | RestartCount = 0.0 518 | 519 | [Config] 520 | AttachStderr = false 521 | AttachStdin = false 522 | AttachStdout = false 523 | Cmd = ["/etc/confluent/docker/run"] 524 | Domainname = "" 525 | Env = ["ZOOKEEPER_CLIENT_PORT=2181", "ZOOKEEPER_TICK_TIME=2000", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "ALLOW_UNSIGNED=false", "PYTHON_VERSION=2.7.9-1", "PYTHON_PIP_VERSION=8.1.2", "SCALA_VERSION=2.12", "KAFKA_VERSION=", "CONFLUENT_PLATFORM_LABEL=", "CONFLUENT_VERSION=5.5.0", "CONFLUENT_DEB_VERSION=1", "ZULU_OPENJDK_VERSION=8=8.38.0.13", "LANG=C.UTF-8", "CUB_CLASSPATH=/etc/confluent/docker/docker-utils.jar", "COMPONENT=zookeeper"] 526 | Hostname = "124403b9f8c9" 527 | Image = "confluentinc/cp-zookeeper:latest" 528 | OpenStdin = false 529 | StdinOnce = false 530 | Tty = false 531 | User = "" 532 | WorkingDir = "" 533 | 534 | [Config.ExposedPorts] 535 | 536 | [Config.ExposedPorts."2181/tcp"] 537 | 538 | [Config.ExposedPorts."2888/tcp"] 539 | 540 | [Config.ExposedPorts."3888/tcp"] 541 | 542 | [Config.Labels] 543 | "com.docker.compose.config-hash" = "b6fa02ab96018cc522f057c093bf80ddefcb6e4e2077821b14cd0f6f1c2fd05b" 544 | "com.docker.compose.container-number" = "1" 545 | "com.docker.compose.oneoff" = "False" 546 | "com.docker.compose.project" = "kafka-single-node" 547 | "com.docker.compose.service" = "zookeeper" 548 | "com.docker.compose.version" = "1.22.0" 549 | "io.confluent.docker" = "true" 550 | "io.confluent.docker.build.number" = "3" 551 | "io.confluent.docker.git.id" = "9a5edfb" 552 | "io.confluent.docker.git.repo" = "confluentinc/kafka-images" 553 | maintainer = "partner-support@confluent.io" 554 | 555 | [Config.Volumes] 556 | 557 | [Config.Volumes."/etc/zookeeper/secrets"] 558 | 559 | [Config.Volumes."/var/lib/zookeeper/data"] 560 | 561 | [Config.Volumes."/var/lib/zookeeper/log"] 562 | 563 | [GraphDriver] 564 | Name = "aufs" 565 | 566 | [HostConfig] 567 | AutoRemove = false 568 | Binds = [] 569 | BlkioWeight = 0.0 570 | Cgroup = "" 571 | CgroupParent = "" 572 | CgroupnsMode = "" 573 | ConsoleSize = [0.0, 0.0] 574 | ContainerIDFile = "" 575 | CpuCount = 0.0 576 | CpuPercent = 0.0 577 | CpuPeriod = 0.0 578 | CpuQuota = 0.0 579 | CpuRealtimePeriod = 0.0 580 | CpuRealtimeRuntime = 0.0 581 | CpuShares = 0.0 582 | CpusetCpus = "" 583 | CpusetMems = "" 584 | Dns = [] 585 | DnsOptions = [] 586 | DnsSearch = [] 587 | IOMaximumBandwidth = 0.0 588 | IOMaximumIOps = 0.0 589 | IpcMode = "shareable" 590 | Isolation = "" 591 | KernelMemory = 0.0 592 | KernelMemoryTCP = 0.0 593 | MaskedPaths = ["/proc/asound", "/proc/acpi", "/proc/kcore", "/proc/keys", "/proc/latency_stats", "/proc/timer_list", "/proc/timer_stats", "/proc/sched_debug", "/proc/scsi", "/sys/firmware"] 594 | Memory = 0.0 595 | MemoryReservation = 0.0 596 | MemorySwap = 0.0 597 | NanoCpus = 0.0 598 | NetworkMode = "kafka-single-node_default" 599 | OomKillDisable = false 600 | OomScoreAdj = 0.0 601 | PidMode = "" 602 | Privileged = false 603 | PublishAllPorts = false 604 | ReadonlyPaths = ["/proc/bus", "/proc/fs", "/proc/irq", "/proc/sys", "/proc/sysrq-trigger"] 605 | ReadonlyRootfs = false 606 | Runtime = "runc" 607 | ShmSize = 67108864.0 608 | UTSMode = "" 609 | UsernsMode = "" 610 | VolumeDriver = "" 611 | VolumesFrom = [] 612 | 613 | [HostConfig.LogConfig] 614 | Type = "journald" 615 | 616 | [HostConfig.LogConfig.Config] 617 | 618 | [HostConfig.PortBindings] 619 | 620 | [HostConfig.RestartPolicy] 621 | MaximumRetryCount = 0.0 622 | Name = "" 623 | 624 | [NetworkSettings] 625 | Bridge = "" 626 | EndpointID = "" 627 | Gateway = "" 628 | GlobalIPv6Address = "" 629 | GlobalIPv6PrefixLen = 0.0 630 | HairpinMode = false 631 | IPAddress = "" 632 | IPPrefixLen = 0.0 633 | IPv6Gateway = "" 634 | LinkLocalIPv6Address = "" 635 | LinkLocalIPv6PrefixLen = 0.0 636 | MacAddress = "" 637 | SandboxID = "ffdab23b3bb287542456d9cc7ece3d0662ea9a0f4b956bee7f94a0e7f26e51c1" 638 | SandboxKey = "/var/run/docker/netns/ffdab23b3bb2" 639 | 640 | [NetworkSettings.Networks] 641 | 642 | [NetworkSettings.Networks.kafka-single-node_default] 643 | Aliases = ["zookeeper", "124403b9f8c9"] 644 | EndpointID = "" 645 | Gateway = "" 646 | GlobalIPv6Address = "" 647 | GlobalIPv6PrefixLen = 0.0 648 | IPAddress = "" 649 | IPPrefixLen = 0.0 650 | IPv6Gateway = "" 651 | MacAddress = "" 652 | NetworkID = "2f0899538ac85dcf98b46ddd30e241c36999e7b5b25ebe19b79066d214bd14f8" 653 | 654 | [NetworkSettings.Ports] 655 | 656 | [State] 657 | Dead = false 658 | Error = "" 659 | ExitCode = 143.0 660 | FinishedAt = "2020-06-09T07:27:55.135586596Z" 661 | OOMKilled = false 662 | Paused = false 663 | Pid = 0.0 664 | Restarting = false 665 | Running = false 666 | StartedAt = "2020-06-08T01:34:58.170557665Z" 667 | Status = "exited" 668 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.structured.p2: -------------------------------------------------------------------------------- 1 | JSON 2 | {{complex_value|to_json}} 3 | 4 | JSON Indent 5 | {{complex_value|to_json:true}} 6 | 7 | YAML 8 | {{complex_value|to_yaml}} 9 | 10 | TOML 11 | {{complex_value|first|to_toml}} -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.structured.test: -------------------------------------------------------------------------------- 1 | JSON 2 | [{"AppArmorProfile":"docker-default","Args":[],"Config":{"AttachStderr":false,"AttachStdin":false,"AttachStdout":false,"Cmd":["/etc/confluent/docker/run"],"Domainname":"","Entrypoint":null,"Env":["ZOOKEEPER_CLIENT_PORT=2181","ZOOKEEPER_TICK_TIME=2000","PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","ALLOW_UNSIGNED=false","PYTHON_VERSION=2.7.9-1","PYTHON_PIP_VERSION=8.1.2","SCALA_VERSION=2.12","KAFKA_VERSION=","CONFLUENT_PLATFORM_LABEL=","CONFLUENT_VERSION=5.5.0","CONFLUENT_DEB_VERSION=1","ZULU_OPENJDK_VERSION=8=8.38.0.13","LANG=C.UTF-8","CUB_CLASSPATH=/etc/confluent/docker/docker-utils.jar","COMPONENT=zookeeper"],"ExposedPorts":{"2181/tcp":{},"2888/tcp":{},"3888/tcp":{}},"Hostname":"124403b9f8c9","Image":"confluentinc/cp-zookeeper:latest","Labels":{"com.docker.compose.config-hash":"b6fa02ab96018cc522f057c093bf80ddefcb6e4e2077821b14cd0f6f1c2fd05b","com.docker.compose.container-number":"1","com.docker.compose.oneoff":"False","com.docker.compose.project":"kafka-single-node","com.docker.compose.service":"zookeeper","com.docker.compose.version":"1.22.0","io.confluent.docker":"true","io.confluent.docker.build.number":"3","io.confluent.docker.git.id":"9a5edfb","io.confluent.docker.git.repo":"confluentinc/kafka-images","maintainer":"partner-support@confluent.io"},"OnBuild":null,"OpenStdin":false,"StdinOnce":false,"Tty":false,"User":"","Volumes":{"/etc/zookeeper/secrets":{},"/var/lib/zookeeper/data":{},"/var/lib/zookeeper/log":{}},"WorkingDir":""},"Created":"2020-06-08T01:34:57.767752272Z","Driver":"aufs","ExecIDs":null,"GraphDriver":{"Data":null,"Name":"aufs"},"HostConfig":{"AutoRemove":false,"Binds":[],"BlkioDeviceReadBps":null,"BlkioDeviceReadIOps":null,"BlkioDeviceWriteBps":null,"BlkioDeviceWriteIOps":null,"BlkioWeight":0,"BlkioWeightDevice":null,"CapAdd":null,"CapDrop":null,"Cgroup":"","CgroupParent":"","CgroupnsMode":"","ConsoleSize":[0,0],"ContainerIDFile":"","CpuCount":0,"CpuPercent":0,"CpuPeriod":0,"CpuQuota":0,"CpuRealtimePeriod":0,"CpuRealtimeRuntime":0,"CpuShares":0,"CpusetCpus":"","CpusetMems":"","DeviceCgroupRules":null,"DeviceRequests":null,"Devices":null,"Dns":[],"DnsOptions":[],"DnsSearch":[],"ExtraHosts":null,"GroupAdd":null,"IOMaximumBandwidth":0,"IOMaximumIOps":0,"IpcMode":"shareable","Isolation":"","KernelMemory":0,"KernelMemoryTCP":0,"Links":null,"LogConfig":{"Config":{},"Type":"journald"},"MaskedPaths":["/proc/asound","/proc/acpi","/proc/kcore","/proc/keys","/proc/latency_stats","/proc/timer_list","/proc/timer_stats","/proc/sched_debug","/proc/scsi","/sys/firmware"],"Memory":0,"MemoryReservation":0,"MemorySwap":0,"MemorySwappiness":null,"NanoCpus":0,"NetworkMode":"kafka-single-node_default","OomKillDisable":false,"OomScoreAdj":0,"PidMode":"","PidsLimit":null,"PortBindings":{},"Privileged":false,"PublishAllPorts":false,"ReadonlyPaths":["/proc/bus","/proc/fs","/proc/irq","/proc/sys","/proc/sysrq-trigger"],"ReadonlyRootfs":false,"RestartPolicy":{"MaximumRetryCount":0,"Name":""},"Runtime":"runc","SecurityOpt":null,"ShmSize":67108864,"UTSMode":"","Ulimits":null,"UsernsMode":"","VolumeDriver":"","VolumesFrom":[]},"HostnamePath":"/var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/hostname","HostsPath":"/var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/hosts","Id":"124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718","Image":"sha256:124ff6469e3d01eb72c58afa0668a5a19f7bf0318355e98847a4b4e28fcddbea","LogPath":"","MountLabel":"","Mounts":[{"Destination":"/etc/zookeeper/secrets","Driver":"local","Mode":"","Name":"532ecadc4032756b0c19d7bbe959220fa50d9f8e88b4acd68abfd1def23907bb","Propagation":"","RW":true,"Source":"/var/lib/docker/volumes/532ecadc4032756b0c19d7bbe959220fa50d9f8e88b4acd68abfd1def23907bb/_data","Type":"volume"},{"Destination":"/var/lib/zookeeper/data","Driver":"local","Mode":"","Name":"9e902e6c1edf9b8b0eb5c5abc7ccb0a6b1a0617dfcee375e2e150a7589edbe63","Propagation":"","RW":true,"Source":"/var/lib/docker/volumes/9e902e6c1edf9b8b0eb5c5abc7ccb0a6b1a0617dfcee375e2e150a7589edbe63/_data","Type":"volume"},{"Destination":"/var/lib/zookeeper/log","Driver":"local","Mode":"","Name":"59c042dc9cc47be579f6f69c303ab1bef9048dc47619eb8d4b79b1df604b28f1","Propagation":"","RW":true,"Source":"/var/lib/docker/volumes/59c042dc9cc47be579f6f69c303ab1bef9048dc47619eb8d4b79b1df604b28f1/_data","Type":"volume"}],"Name":"/kafka-single-node_zookeeper_1","NetworkSettings":{"Bridge":"","EndpointID":"","Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"HairpinMode":false,"IPAddress":"","IPPrefixLen":0,"IPv6Gateway":"","LinkLocalIPv6Address":"","LinkLocalIPv6PrefixLen":0,"MacAddress":"","Networks":{"kafka-single-node_default":{"Aliases":["zookeeper","124403b9f8c9"],"DriverOpts":null,"EndpointID":"","Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"IPAMConfig":null,"IPAddress":"","IPPrefixLen":0,"IPv6Gateway":"","Links":null,"MacAddress":"","NetworkID":"2f0899538ac85dcf98b46ddd30e241c36999e7b5b25ebe19b79066d214bd14f8"}},"Ports":{},"SandboxID":"ffdab23b3bb287542456d9cc7ece3d0662ea9a0f4b956bee7f94a0e7f26e51c1","SandboxKey":"/var/run/docker/netns/ffdab23b3bb2","SecondaryIPAddresses":null,"SecondaryIPv6Addresses":null},"Path":"/etc/confluent/docker/run","Platform":"linux","ProcessLabel":"","ResolvConfPath":"/var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/resolv.conf","RestartCount":0,"State":{"Dead":false,"Error":"","ExitCode":143,"FinishedAt":"2020-06-09T07:27:55.135586596Z","OOMKilled":false,"Paused":false,"Pid":0,"Restarting":false,"Running":false,"StartedAt":"2020-06-08T01:34:58.170557665Z","Status":"exited"}}] 3 | 4 | JSON Indent 5 | [ 6 | { 7 | "AppArmorProfile": "docker-default", 8 | "Args": [], 9 | "Config": { 10 | "AttachStderr": false, 11 | "AttachStdin": false, 12 | "AttachStdout": false, 13 | "Cmd": [ 14 | "/etc/confluent/docker/run" 15 | ], 16 | "Domainname": "", 17 | "Entrypoint": null, 18 | "Env": [ 19 | "ZOOKEEPER_CLIENT_PORT=2181", 20 | "ZOOKEEPER_TICK_TIME=2000", 21 | "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 22 | "ALLOW_UNSIGNED=false", 23 | "PYTHON_VERSION=2.7.9-1", 24 | "PYTHON_PIP_VERSION=8.1.2", 25 | "SCALA_VERSION=2.12", 26 | "KAFKA_VERSION=", 27 | "CONFLUENT_PLATFORM_LABEL=", 28 | "CONFLUENT_VERSION=5.5.0", 29 | "CONFLUENT_DEB_VERSION=1", 30 | "ZULU_OPENJDK_VERSION=8=8.38.0.13", 31 | "LANG=C.UTF-8", 32 | "CUB_CLASSPATH=/etc/confluent/docker/docker-utils.jar", 33 | "COMPONENT=zookeeper" 34 | ], 35 | "ExposedPorts": { 36 | "2181/tcp": {}, 37 | "2888/tcp": {}, 38 | "3888/tcp": {} 39 | }, 40 | "Hostname": "124403b9f8c9", 41 | "Image": "confluentinc/cp-zookeeper:latest", 42 | "Labels": { 43 | "com.docker.compose.config-hash": "b6fa02ab96018cc522f057c093bf80ddefcb6e4e2077821b14cd0f6f1c2fd05b", 44 | "com.docker.compose.container-number": "1", 45 | "com.docker.compose.oneoff": "False", 46 | "com.docker.compose.project": "kafka-single-node", 47 | "com.docker.compose.service": "zookeeper", 48 | "com.docker.compose.version": "1.22.0", 49 | "io.confluent.docker": "true", 50 | "io.confluent.docker.build.number": "3", 51 | "io.confluent.docker.git.id": "9a5edfb", 52 | "io.confluent.docker.git.repo": "confluentinc/kafka-images", 53 | "maintainer": "partner-support@confluent.io" 54 | }, 55 | "OnBuild": null, 56 | "OpenStdin": false, 57 | "StdinOnce": false, 58 | "Tty": false, 59 | "User": "", 60 | "Volumes": { 61 | "/etc/zookeeper/secrets": {}, 62 | "/var/lib/zookeeper/data": {}, 63 | "/var/lib/zookeeper/log": {} 64 | }, 65 | "WorkingDir": "" 66 | }, 67 | "Created": "2020-06-08T01:34:57.767752272Z", 68 | "Driver": "aufs", 69 | "ExecIDs": null, 70 | "GraphDriver": { 71 | "Data": null, 72 | "Name": "aufs" 73 | }, 74 | "HostConfig": { 75 | "AutoRemove": false, 76 | "Binds": [], 77 | "BlkioDeviceReadBps": null, 78 | "BlkioDeviceReadIOps": null, 79 | "BlkioDeviceWriteBps": null, 80 | "BlkioDeviceWriteIOps": null, 81 | "BlkioWeight": 0, 82 | "BlkioWeightDevice": null, 83 | "CapAdd": null, 84 | "CapDrop": null, 85 | "Cgroup": "", 86 | "CgroupParent": "", 87 | "CgroupnsMode": "", 88 | "ConsoleSize": [ 89 | 0, 90 | 0 91 | ], 92 | "ContainerIDFile": "", 93 | "CpuCount": 0, 94 | "CpuPercent": 0, 95 | "CpuPeriod": 0, 96 | "CpuQuota": 0, 97 | "CpuRealtimePeriod": 0, 98 | "CpuRealtimeRuntime": 0, 99 | "CpuShares": 0, 100 | "CpusetCpus": "", 101 | "CpusetMems": "", 102 | "DeviceCgroupRules": null, 103 | "DeviceRequests": null, 104 | "Devices": null, 105 | "Dns": [], 106 | "DnsOptions": [], 107 | "DnsSearch": [], 108 | "ExtraHosts": null, 109 | "GroupAdd": null, 110 | "IOMaximumBandwidth": 0, 111 | "IOMaximumIOps": 0, 112 | "IpcMode": "shareable", 113 | "Isolation": "", 114 | "KernelMemory": 0, 115 | "KernelMemoryTCP": 0, 116 | "Links": null, 117 | "LogConfig": { 118 | "Config": {}, 119 | "Type": "journald" 120 | }, 121 | "MaskedPaths": [ 122 | "/proc/asound", 123 | "/proc/acpi", 124 | "/proc/kcore", 125 | "/proc/keys", 126 | "/proc/latency_stats", 127 | "/proc/timer_list", 128 | "/proc/timer_stats", 129 | "/proc/sched_debug", 130 | "/proc/scsi", 131 | "/sys/firmware" 132 | ], 133 | "Memory": 0, 134 | "MemoryReservation": 0, 135 | "MemorySwap": 0, 136 | "MemorySwappiness": null, 137 | "NanoCpus": 0, 138 | "NetworkMode": "kafka-single-node_default", 139 | "OomKillDisable": false, 140 | "OomScoreAdj": 0, 141 | "PidMode": "", 142 | "PidsLimit": null, 143 | "PortBindings": {}, 144 | "Privileged": false, 145 | "PublishAllPorts": false, 146 | "ReadonlyPaths": [ 147 | "/proc/bus", 148 | "/proc/fs", 149 | "/proc/irq", 150 | "/proc/sys", 151 | "/proc/sysrq-trigger" 152 | ], 153 | "ReadonlyRootfs": false, 154 | "RestartPolicy": { 155 | "MaximumRetryCount": 0, 156 | "Name": "" 157 | }, 158 | "Runtime": "runc", 159 | "SecurityOpt": null, 160 | "ShmSize": 67108864, 161 | "UTSMode": "", 162 | "Ulimits": null, 163 | "UsernsMode": "", 164 | "VolumeDriver": "", 165 | "VolumesFrom": [] 166 | }, 167 | "HostnamePath": "/var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/hostname", 168 | "HostsPath": "/var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/hosts", 169 | "Id": "124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718", 170 | "Image": "sha256:124ff6469e3d01eb72c58afa0668a5a19f7bf0318355e98847a4b4e28fcddbea", 171 | "LogPath": "", 172 | "MountLabel": "", 173 | "Mounts": [ 174 | { 175 | "Destination": "/etc/zookeeper/secrets", 176 | "Driver": "local", 177 | "Mode": "", 178 | "Name": "532ecadc4032756b0c19d7bbe959220fa50d9f8e88b4acd68abfd1def23907bb", 179 | "Propagation": "", 180 | "RW": true, 181 | "Source": "/var/lib/docker/volumes/532ecadc4032756b0c19d7bbe959220fa50d9f8e88b4acd68abfd1def23907bb/_data", 182 | "Type": "volume" 183 | }, 184 | { 185 | "Destination": "/var/lib/zookeeper/data", 186 | "Driver": "local", 187 | "Mode": "", 188 | "Name": "9e902e6c1edf9b8b0eb5c5abc7ccb0a6b1a0617dfcee375e2e150a7589edbe63", 189 | "Propagation": "", 190 | "RW": true, 191 | "Source": "/var/lib/docker/volumes/9e902e6c1edf9b8b0eb5c5abc7ccb0a6b1a0617dfcee375e2e150a7589edbe63/_data", 192 | "Type": "volume" 193 | }, 194 | { 195 | "Destination": "/var/lib/zookeeper/log", 196 | "Driver": "local", 197 | "Mode": "", 198 | "Name": "59c042dc9cc47be579f6f69c303ab1bef9048dc47619eb8d4b79b1df604b28f1", 199 | "Propagation": "", 200 | "RW": true, 201 | "Source": "/var/lib/docker/volumes/59c042dc9cc47be579f6f69c303ab1bef9048dc47619eb8d4b79b1df604b28f1/_data", 202 | "Type": "volume" 203 | } 204 | ], 205 | "Name": "/kafka-single-node_zookeeper_1", 206 | "NetworkSettings": { 207 | "Bridge": "", 208 | "EndpointID": "", 209 | "Gateway": "", 210 | "GlobalIPv6Address": "", 211 | "GlobalIPv6PrefixLen": 0, 212 | "HairpinMode": false, 213 | "IPAddress": "", 214 | "IPPrefixLen": 0, 215 | "IPv6Gateway": "", 216 | "LinkLocalIPv6Address": "", 217 | "LinkLocalIPv6PrefixLen": 0, 218 | "MacAddress": "", 219 | "Networks": { 220 | "kafka-single-node_default": { 221 | "Aliases": [ 222 | "zookeeper", 223 | "124403b9f8c9" 224 | ], 225 | "DriverOpts": null, 226 | "EndpointID": "", 227 | "Gateway": "", 228 | "GlobalIPv6Address": "", 229 | "GlobalIPv6PrefixLen": 0, 230 | "IPAMConfig": null, 231 | "IPAddress": "", 232 | "IPPrefixLen": 0, 233 | "IPv6Gateway": "", 234 | "Links": null, 235 | "MacAddress": "", 236 | "NetworkID": "2f0899538ac85dcf98b46ddd30e241c36999e7b5b25ebe19b79066d214bd14f8" 237 | } 238 | }, 239 | "Ports": {}, 240 | "SandboxID": "ffdab23b3bb287542456d9cc7ece3d0662ea9a0f4b956bee7f94a0e7f26e51c1", 241 | "SandboxKey": "/var/run/docker/netns/ffdab23b3bb2", 242 | "SecondaryIPAddresses": null, 243 | "SecondaryIPv6Addresses": null 244 | }, 245 | "Path": "/etc/confluent/docker/run", 246 | "Platform": "linux", 247 | "ProcessLabel": "", 248 | "ResolvConfPath": "/var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/resolv.conf", 249 | "RestartCount": 0, 250 | "State": { 251 | "Dead": false, 252 | "Error": "", 253 | "ExitCode": 143, 254 | "FinishedAt": "2020-06-09T07:27:55.135586596Z", 255 | "OOMKilled": false, 256 | "Paused": false, 257 | "Pid": 0, 258 | "Restarting": false, 259 | "Running": false, 260 | "StartedAt": "2020-06-08T01:34:58.170557665Z", 261 | "Status": "exited" 262 | } 263 | } 264 | ] 265 | 266 | YAML 267 | - AppArmorProfile: docker-default 268 | Args: [] 269 | Config: 270 | AttachStderr: false 271 | AttachStdin: false 272 | AttachStdout: false 273 | Cmd: 274 | - /etc/confluent/docker/run 275 | Domainname: "" 276 | Entrypoint: null 277 | Env: 278 | - ZOOKEEPER_CLIENT_PORT=2181 279 | - ZOOKEEPER_TICK_TIME=2000 280 | - PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 281 | - ALLOW_UNSIGNED=false 282 | - PYTHON_VERSION=2.7.9-1 283 | - PYTHON_PIP_VERSION=8.1.2 284 | - SCALA_VERSION=2.12 285 | - KAFKA_VERSION= 286 | - CONFLUENT_PLATFORM_LABEL= 287 | - CONFLUENT_VERSION=5.5.0 288 | - CONFLUENT_DEB_VERSION=1 289 | - ZULU_OPENJDK_VERSION=8=8.38.0.13 290 | - LANG=C.UTF-8 291 | - CUB_CLASSPATH=/etc/confluent/docker/docker-utils.jar 292 | - COMPONENT=zookeeper 293 | ExposedPorts: 294 | 2181/tcp: {} 295 | 2888/tcp: {} 296 | 3888/tcp: {} 297 | Hostname: 124403b9f8c9 298 | Image: confluentinc/cp-zookeeper:latest 299 | Labels: 300 | com.docker.compose.config-hash: b6fa02ab96018cc522f057c093bf80ddefcb6e4e2077821b14cd0f6f1c2fd05b 301 | com.docker.compose.container-number: "1" 302 | com.docker.compose.oneoff: "False" 303 | com.docker.compose.project: kafka-single-node 304 | com.docker.compose.service: zookeeper 305 | com.docker.compose.version: 1.22.0 306 | io.confluent.docker: "true" 307 | io.confluent.docker.build.number: "3" 308 | io.confluent.docker.git.id: 9a5edfb 309 | io.confluent.docker.git.repo: confluentinc/kafka-images 310 | maintainer: partner-support@confluent.io 311 | OnBuild: null 312 | OpenStdin: false 313 | StdinOnce: false 314 | Tty: false 315 | User: "" 316 | Volumes: 317 | /etc/zookeeper/secrets: {} 318 | /var/lib/zookeeper/data: {} 319 | /var/lib/zookeeper/log: {} 320 | WorkingDir: "" 321 | Created: "2020-06-08T01:34:57.767752272Z" 322 | Driver: aufs 323 | ExecIDs: null 324 | GraphDriver: 325 | Data: null 326 | Name: aufs 327 | HostConfig: 328 | AutoRemove: false 329 | Binds: [] 330 | BlkioDeviceReadBps: null 331 | BlkioDeviceReadIOps: null 332 | BlkioDeviceWriteBps: null 333 | BlkioDeviceWriteIOps: null 334 | BlkioWeight: 0 335 | BlkioWeightDevice: null 336 | CapAdd: null 337 | CapDrop: null 338 | Cgroup: "" 339 | CgroupParent: "" 340 | CgroupnsMode: "" 341 | ConsoleSize: 342 | - 0 343 | - 0 344 | ContainerIDFile: "" 345 | CpuCount: 0 346 | CpuPercent: 0 347 | CpuPeriod: 0 348 | CpuQuota: 0 349 | CpuRealtimePeriod: 0 350 | CpuRealtimeRuntime: 0 351 | CpuShares: 0 352 | CpusetCpus: "" 353 | CpusetMems: "" 354 | DeviceCgroupRules: null 355 | DeviceRequests: null 356 | Devices: null 357 | Dns: [] 358 | DnsOptions: [] 359 | DnsSearch: [] 360 | ExtraHosts: null 361 | GroupAdd: null 362 | IOMaximumBandwidth: 0 363 | IOMaximumIOps: 0 364 | IpcMode: shareable 365 | Isolation: "" 366 | KernelMemory: 0 367 | KernelMemoryTCP: 0 368 | Links: null 369 | LogConfig: 370 | Config: {} 371 | Type: journald 372 | MaskedPaths: 373 | - /proc/asound 374 | - /proc/acpi 375 | - /proc/kcore 376 | - /proc/keys 377 | - /proc/latency_stats 378 | - /proc/timer_list 379 | - /proc/timer_stats 380 | - /proc/sched_debug 381 | - /proc/scsi 382 | - /sys/firmware 383 | Memory: 0 384 | MemoryReservation: 0 385 | MemorySwap: 0 386 | MemorySwappiness: null 387 | NanoCpus: 0 388 | NetworkMode: kafka-single-node_default 389 | OomKillDisable: false 390 | OomScoreAdj: 0 391 | PidMode: "" 392 | PidsLimit: null 393 | PortBindings: {} 394 | Privileged: false 395 | PublishAllPorts: false 396 | ReadonlyPaths: 397 | - /proc/bus 398 | - /proc/fs 399 | - /proc/irq 400 | - /proc/sys 401 | - /proc/sysrq-trigger 402 | ReadonlyRootfs: false 403 | RestartPolicy: 404 | MaximumRetryCount: 0 405 | Name: "" 406 | Runtime: runc 407 | SecurityOpt: null 408 | ShmSize: 6.7108864e+07 409 | UTSMode: "" 410 | Ulimits: null 411 | UsernsMode: "" 412 | VolumeDriver: "" 413 | VolumesFrom: [] 414 | HostnamePath: /var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/hostname 415 | HostsPath: /var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/hosts 416 | Id: 124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718 417 | Image: sha256:124ff6469e3d01eb72c58afa0668a5a19f7bf0318355e98847a4b4e28fcddbea 418 | LogPath: "" 419 | MountLabel: "" 420 | Mounts: 421 | - Destination: /etc/zookeeper/secrets 422 | Driver: local 423 | Mode: "" 424 | Name: 532ecadc4032756b0c19d7bbe959220fa50d9f8e88b4acd68abfd1def23907bb 425 | Propagation: "" 426 | RW: true 427 | Source: /var/lib/docker/volumes/532ecadc4032756b0c19d7bbe959220fa50d9f8e88b4acd68abfd1def23907bb/_data 428 | Type: volume 429 | - Destination: /var/lib/zookeeper/data 430 | Driver: local 431 | Mode: "" 432 | Name: 9e902e6c1edf9b8b0eb5c5abc7ccb0a6b1a0617dfcee375e2e150a7589edbe63 433 | Propagation: "" 434 | RW: true 435 | Source: /var/lib/docker/volumes/9e902e6c1edf9b8b0eb5c5abc7ccb0a6b1a0617dfcee375e2e150a7589edbe63/_data 436 | Type: volume 437 | - Destination: /var/lib/zookeeper/log 438 | Driver: local 439 | Mode: "" 440 | Name: 59c042dc9cc47be579f6f69c303ab1bef9048dc47619eb8d4b79b1df604b28f1 441 | Propagation: "" 442 | RW: true 443 | Source: /var/lib/docker/volumes/59c042dc9cc47be579f6f69c303ab1bef9048dc47619eb8d4b79b1df604b28f1/_data 444 | Type: volume 445 | Name: /kafka-single-node_zookeeper_1 446 | NetworkSettings: 447 | Bridge: "" 448 | EndpointID: "" 449 | Gateway: "" 450 | GlobalIPv6Address: "" 451 | GlobalIPv6PrefixLen: 0 452 | HairpinMode: false 453 | IPAddress: "" 454 | IPPrefixLen: 0 455 | IPv6Gateway: "" 456 | LinkLocalIPv6Address: "" 457 | LinkLocalIPv6PrefixLen: 0 458 | MacAddress: "" 459 | Networks: 460 | kafka-single-node_default: 461 | Aliases: 462 | - zookeeper 463 | - 124403b9f8c9 464 | DriverOpts: null 465 | EndpointID: "" 466 | Gateway: "" 467 | GlobalIPv6Address: "" 468 | GlobalIPv6PrefixLen: 0 469 | IPAMConfig: null 470 | IPAddress: "" 471 | IPPrefixLen: 0 472 | IPv6Gateway: "" 473 | Links: null 474 | MacAddress: "" 475 | NetworkID: 2f0899538ac85dcf98b46ddd30e241c36999e7b5b25ebe19b79066d214bd14f8 476 | Ports: {} 477 | SandboxID: ffdab23b3bb287542456d9cc7ece3d0662ea9a0f4b956bee7f94a0e7f26e51c1 478 | SandboxKey: /var/run/docker/netns/ffdab23b3bb2 479 | SecondaryIPAddresses: null 480 | SecondaryIPv6Addresses: null 481 | Path: /etc/confluent/docker/run 482 | Platform: linux 483 | ProcessLabel: "" 484 | ResolvConfPath: /var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/resolv.conf 485 | RestartCount: 0 486 | State: 487 | Dead: false 488 | Error: "" 489 | ExitCode: 143 490 | FinishedAt: "2020-06-09T07:27:55.135586596Z" 491 | OOMKilled: false 492 | Paused: false 493 | Pid: 0 494 | Restarting: false 495 | Running: false 496 | StartedAt: "2020-06-08T01:34:58.170557665Z" 497 | Status: exited 498 | 499 | 500 | TOML 501 | AppArmorProfile = "docker-default" 502 | Args = [] 503 | Created = "2020-06-08T01:34:57.767752272Z" 504 | Driver = "aufs" 505 | HostnamePath = "/var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/hostname" 506 | HostsPath = "/var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/hosts" 507 | Id = "124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718" 508 | Image = "sha256:124ff6469e3d01eb72c58afa0668a5a19f7bf0318355e98847a4b4e28fcddbea" 509 | LogPath = "" 510 | MountLabel = "" 511 | Mounts = [{ Destination = "/etc/zookeeper/secrets", Driver = "local", Mode = "", Name = "532ecadc4032756b0c19d7bbe959220fa50d9f8e88b4acd68abfd1def23907bb", Propagation = "", RW = true, Source = "/var/lib/docker/volumes/532ecadc4032756b0c19d7bbe959220fa50d9f8e88b4acd68abfd1def23907bb/_data", Type = "volume" }, { Destination = "/var/lib/zookeeper/data", Driver = "local", Mode = "", Name = "9e902e6c1edf9b8b0eb5c5abc7ccb0a6b1a0617dfcee375e2e150a7589edbe63", Propagation = "", RW = true, Source = "/var/lib/docker/volumes/9e902e6c1edf9b8b0eb5c5abc7ccb0a6b1a0617dfcee375e2e150a7589edbe63/_data", Type = "volume" }, { Destination = "/var/lib/zookeeper/log", Driver = "local", Mode = "", Name = "59c042dc9cc47be579f6f69c303ab1bef9048dc47619eb8d4b79b1df604b28f1", Propagation = "", RW = true, Source = "/var/lib/docker/volumes/59c042dc9cc47be579f6f69c303ab1bef9048dc47619eb8d4b79b1df604b28f1/_data", Type = "volume" }] 512 | Name = "/kafka-single-node_zookeeper_1" 513 | Path = "/etc/confluent/docker/run" 514 | Platform = "linux" 515 | ProcessLabel = "" 516 | ResolvConfPath = "/var/lib/docker/containers/124403b9f8c91168f8ac5dc95794abcfa02e3cda83d490109b1b90c46fa7b718/resolv.conf" 517 | RestartCount = 0.0 518 | 519 | [Config] 520 | AttachStderr = false 521 | AttachStdin = false 522 | AttachStdout = false 523 | Cmd = ["/etc/confluent/docker/run"] 524 | Domainname = "" 525 | Env = ["ZOOKEEPER_CLIENT_PORT=2181", "ZOOKEEPER_TICK_TIME=2000", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "ALLOW_UNSIGNED=false", "PYTHON_VERSION=2.7.9-1", "PYTHON_PIP_VERSION=8.1.2", "SCALA_VERSION=2.12", "KAFKA_VERSION=", "CONFLUENT_PLATFORM_LABEL=", "CONFLUENT_VERSION=5.5.0", "CONFLUENT_DEB_VERSION=1", "ZULU_OPENJDK_VERSION=8=8.38.0.13", "LANG=C.UTF-8", "CUB_CLASSPATH=/etc/confluent/docker/docker-utils.jar", "COMPONENT=zookeeper"] 526 | Hostname = "124403b9f8c9" 527 | Image = "confluentinc/cp-zookeeper:latest" 528 | OpenStdin = false 529 | StdinOnce = false 530 | Tty = false 531 | User = "" 532 | WorkingDir = "" 533 | 534 | [Config.ExposedPorts] 535 | 536 | [Config.ExposedPorts."2181/tcp"] 537 | 538 | [Config.ExposedPorts."2888/tcp"] 539 | 540 | [Config.ExposedPorts."3888/tcp"] 541 | 542 | [Config.Labels] 543 | "com.docker.compose.config-hash" = "b6fa02ab96018cc522f057c093bf80ddefcb6e4e2077821b14cd0f6f1c2fd05b" 544 | "com.docker.compose.container-number" = "1" 545 | "com.docker.compose.oneoff" = "False" 546 | "com.docker.compose.project" = "kafka-single-node" 547 | "com.docker.compose.service" = "zookeeper" 548 | "com.docker.compose.version" = "1.22.0" 549 | "io.confluent.docker" = "true" 550 | "io.confluent.docker.build.number" = "3" 551 | "io.confluent.docker.git.id" = "9a5edfb" 552 | "io.confluent.docker.git.repo" = "confluentinc/kafka-images" 553 | maintainer = "partner-support@confluent.io" 554 | 555 | [Config.Volumes] 556 | 557 | [Config.Volumes."/etc/zookeeper/secrets"] 558 | 559 | [Config.Volumes."/var/lib/zookeeper/data"] 560 | 561 | [Config.Volumes."/var/lib/zookeeper/log"] 562 | 563 | [GraphDriver] 564 | Name = "aufs" 565 | 566 | [HostConfig] 567 | AutoRemove = false 568 | Binds = [] 569 | BlkioWeight = 0.0 570 | Cgroup = "" 571 | CgroupParent = "" 572 | CgroupnsMode = "" 573 | ConsoleSize = [0.0, 0.0] 574 | ContainerIDFile = "" 575 | CpuCount = 0.0 576 | CpuPercent = 0.0 577 | CpuPeriod = 0.0 578 | CpuQuota = 0.0 579 | CpuRealtimePeriod = 0.0 580 | CpuRealtimeRuntime = 0.0 581 | CpuShares = 0.0 582 | CpusetCpus = "" 583 | CpusetMems = "" 584 | Dns = [] 585 | DnsOptions = [] 586 | DnsSearch = [] 587 | IOMaximumBandwidth = 0.0 588 | IOMaximumIOps = 0.0 589 | IpcMode = "shareable" 590 | Isolation = "" 591 | KernelMemory = 0.0 592 | KernelMemoryTCP = 0.0 593 | MaskedPaths = ["/proc/asound", "/proc/acpi", "/proc/kcore", "/proc/keys", "/proc/latency_stats", "/proc/timer_list", "/proc/timer_stats", "/proc/sched_debug", "/proc/scsi", "/sys/firmware"] 594 | Memory = 0.0 595 | MemoryReservation = 0.0 596 | MemorySwap = 0.0 597 | NanoCpus = 0.0 598 | NetworkMode = "kafka-single-node_default" 599 | OomKillDisable = false 600 | OomScoreAdj = 0.0 601 | PidMode = "" 602 | Privileged = false 603 | PublishAllPorts = false 604 | ReadonlyPaths = ["/proc/bus", "/proc/fs", "/proc/irq", "/proc/sys", "/proc/sysrq-trigger"] 605 | ReadonlyRootfs = false 606 | Runtime = "runc" 607 | ShmSize = 67108864.0 608 | UTSMode = "" 609 | UsernsMode = "" 610 | VolumeDriver = "" 611 | VolumesFrom = [] 612 | 613 | [HostConfig.LogConfig] 614 | Type = "journald" 615 | 616 | [HostConfig.LogConfig.Config] 617 | 618 | [HostConfig.PortBindings] 619 | 620 | [HostConfig.RestartPolicy] 621 | MaximumRetryCount = 0.0 622 | Name = "" 623 | 624 | [NetworkSettings] 625 | Bridge = "" 626 | EndpointID = "" 627 | Gateway = "" 628 | GlobalIPv6Address = "" 629 | GlobalIPv6PrefixLen = 0.0 630 | HairpinMode = false 631 | IPAddress = "" 632 | IPPrefixLen = 0.0 633 | IPv6Gateway = "" 634 | LinkLocalIPv6Address = "" 635 | LinkLocalIPv6PrefixLen = 0.0 636 | MacAddress = "" 637 | SandboxID = "ffdab23b3bb287542456d9cc7ece3d0662ea9a0f4b956bee7f94a0e7f26e51c1" 638 | SandboxKey = "/var/run/docker/netns/ffdab23b3bb2" 639 | 640 | [NetworkSettings.Networks] 641 | 642 | [NetworkSettings.Networks.kafka-single-node_default] 643 | Aliases = ["zookeeper", "124403b9f8c9"] 644 | EndpointID = "" 645 | Gateway = "" 646 | GlobalIPv6Address = "" 647 | GlobalIPv6PrefixLen = 0.0 648 | IPAddress = "" 649 | IPPrefixLen = 0.0 650 | IPv6Gateway = "" 651 | MacAddress = "" 652 | NetworkID = "2f0899538ac85dcf98b46ddd30e241c36999e7b5b25ebe19b79066d214bd14f8" 653 | 654 | [NetworkSettings.Ports] 655 | 656 | [State] 657 | Dead = false 658 | Error = "" 659 | ExitCode = 143.0 660 | FinishedAt = "2020-06-09T07:27:55.135586596Z" 661 | OOMKilled = false 662 | Paused = false 663 | Pid = 0.0 664 | Restarting = false 665 | Running = false 666 | StartedAt = "2020-06-08T01:34:58.170557665Z" 667 | Status = "exited" 668 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.write_file.json: -------------------------------------------------------------------------------- 1 | { 2 | "write_file_data" : "write_file\n" 3 | } 4 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.write_file.out: -------------------------------------------------------------------------------- 1 | write_file 2 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.write_file.p2: -------------------------------------------------------------------------------- 1 | {{ write_file_data | write_file:"tests/data.write_file.test"}} 2 | 3 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.write_file.test: -------------------------------------------------------------------------------- 1 | write_file 2 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.yml: -------------------------------------------------------------------------------- 1 | simple_value1: a value 2 | simple_value2: a value 3 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.yml.1.test: -------------------------------------------------------------------------------- 1 | a value 2 | a value 3 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.yml.2.test: -------------------------------------------------------------------------------- 1 | a value 2 | a value 3 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.yml.3.test: -------------------------------------------------------------------------------- 1 | a value 2 | a value 3 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/data.yml.4.test: -------------------------------------------------------------------------------- 1 | a value 2 | a value 3 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/directory-mode-filename-transform/templates/dir1/template1.tmpl.txt: -------------------------------------------------------------------------------- 1 | # OutputPath: {{ p2.OutputPath }} 2 | # OutputRelPath: {{ p2.OutputRelPath }} 3 | # OutputName: {{ p2.OutputName }} 4 | # OutputDir: {{ p2.OutputDir }} 5 | # OutputRelPath: {{ p2.OutputRelPath }} -------------------------------------------------------------------------------- /pkg/entrypoint/tests/directory-mode/output/.gitignore: -------------------------------------------------------------------------------- 1 | dir1 2 | !/.gitignore 3 | 4 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/directory-mode/templates/dir1/dir2/template2: -------------------------------------------------------------------------------- 1 | # OutputPath: {{ p2.OutputPath }} 2 | # OutputRelPath: {{ p2.OutputRelPath }} 3 | # OutputName: {{ p2.OutputName }} 4 | # OutputDir: {{ p2.OutputDir }} 5 | # OutputRelPath: {{ p2.OutputRelPath }} 6 | -------------------------------------------------------------------------------- /pkg/entrypoint/tests/directory-mode/templates/dir1/template1: -------------------------------------------------------------------------------- 1 | # OutputPath: {{ p2.OutputPath }} 2 | # OutputRelPath: {{ p2.OutputRelPath }} 3 | # OutputName: {{ p2.OutputName }} 4 | # OutputDir: {{ p2.OutputDir }} 5 | # OutputRelPath: {{ p2.OutputRelPath }} 6 | {{ "0640"|SetMode }} -------------------------------------------------------------------------------- /pkg/entrypoint/tests/directory-mode/templates/dir3/template3: -------------------------------------------------------------------------------- 1 | # OutputPath: {{ p2.OutputPath }} 2 | # OutputRelPath: {{ p2.OutputRelPath }} 3 | # OutputName: {{ p2.OutputName }} 4 | # OutputDir: {{ p2.OutputDir }} 5 | # OutputRelPath: {{ p2.OutputRelPath }} 6 | -------------------------------------------------------------------------------- /pkg/envutil/inputprocessors.go: -------------------------------------------------------------------------------- 1 | package envutil 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | 7 | "github.com/wrouesnel/p2cli/pkg/errdefs" 8 | ) 9 | 10 | // FromEnvironment consumes the environment and outputs a valid input data field into the 11 | // supplied map. 12 | func FromEnvironment(env []string) (map[string]string, error) { 13 | results := map[string]string{} 14 | 15 | if env == nil { 16 | env = os.Environ() 17 | } 18 | 19 | const expectedArgs = 2 20 | 21 | for _, keyval := range env { 22 | splitKeyVal := strings.SplitN(keyval, "=", expectedArgs) 23 | if len(splitKeyVal) != expectedArgs { 24 | return results, error(errdefs.EnvironmentVariablesError{ 25 | Reason: "Could not find an equals value to split on", 26 | RawEnvVar: keyval, 27 | }) 28 | } 29 | results[splitKeyVal[0]] = splitKeyVal[1] 30 | } 31 | 32 | return results, nil 33 | } 34 | -------------------------------------------------------------------------------- /pkg/envutil/inputprocessors_test.go: -------------------------------------------------------------------------------- 1 | package envutil_test 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/wrouesnel/p2cli/pkg/envutil" 8 | 9 | . "gopkg.in/check.v1" 10 | ) 11 | 12 | // Hook up gocheck into the "go test" runner. 13 | func Test(t *testing.T) { TestingT(t) } 14 | 15 | type testSuite struct{} 16 | 17 | var _ = Suite(&testSuite{}) 18 | 19 | func (s *testSuite) TestFromEnvironment(c *C) { 20 | result, _ := envutil.FromEnvironment([]string{"TESTKEY=1"}) 21 | c.Check(result["TESTKEY"], Equals, "1") 22 | } 23 | 24 | func (s *testSuite) TestFromEnvironmentUsingEnvironment(c *C) { 25 | os.Setenv("TESTKEY", "1") 26 | result, _ := envutil.FromEnvironment(nil) 27 | os.Unsetenv("TESTKEY") 28 | c.Check(result["TESTKEY"], Equals, "1") 29 | } 30 | -------------------------------------------------------------------------------- /pkg/errdefs/errdefs.go: -------------------------------------------------------------------------------- 1 | package errdefs 2 | 3 | import "fmt" 4 | 5 | // EnvironmentVariablesError is raised when an environment variable is improperly formatted. 6 | type EnvironmentVariablesError struct { 7 | Reason string 8 | RawEnvVar string 9 | } 10 | 11 | // Error implements error. 12 | func (eev EnvironmentVariablesError) Error() string { 13 | return fmt.Sprintf("%s: %s", eev.Reason, eev.RawEnvVar) 14 | } 15 | -------------------------------------------------------------------------------- /pkg/fileconsts/fileconsts.go: -------------------------------------------------------------------------------- 1 | package fileconsts 2 | 3 | //nolint:revive,stylecheck 4 | const ( 5 | OS_READ = 04 6 | OS_WRITE = 02 7 | OS_EX = 01 8 | OS_USER_SHIFT = 6 9 | OS_GROUP_SHIFT = 3 10 | OS_OTH_SHIFT = 0 11 | 12 | OS_USER_R = OS_READ << OS_USER_SHIFT 13 | OS_USER_W = OS_WRITE << OS_USER_SHIFT 14 | OS_USER_X = OS_EX << OS_USER_SHIFT 15 | OS_USER_RW = OS_USER_R | OS_USER_W 16 | OS_USER_RWX = OS_USER_RW | OS_USER_X 17 | 18 | OS_GROUP_R = OS_READ << OS_GROUP_SHIFT 19 | OS_GROUP_W = OS_WRITE << OS_GROUP_SHIFT 20 | OS_GROUP_X = OS_EX << OS_GROUP_SHIFT 21 | OS_GROUP_RW = OS_GROUP_R | OS_GROUP_W 22 | OS_GROUP_RWX = OS_GROUP_RW | OS_GROUP_X 23 | 24 | OS_OTH_R = OS_READ << OS_OTH_SHIFT 25 | OS_OTH_W = OS_WRITE << OS_OTH_SHIFT 26 | OS_OTH_X = OS_EX << OS_OTH_SHIFT 27 | OS_OTH_RW = OS_OTH_R | OS_OTH_W 28 | OS_OTH_RWX = OS_OTH_RW | OS_OTH_X 29 | 30 | OS_ALL_R = OS_USER_R | OS_GROUP_R | OS_OTH_R 31 | OS_ALL_W = OS_USER_W | OS_GROUP_W | OS_OTH_W 32 | OS_ALL_X = OS_USER_X | OS_GROUP_X | OS_OTH_X 33 | OS_ALL_RW = OS_ALL_R | OS_ALL_W 34 | OS_ALL_RWX = OS_ALL_RW | OS_ALL_X 35 | ) 36 | -------------------------------------------------------------------------------- /pkg/templating/engine.go: -------------------------------------------------------------------------------- 1 | package templating 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/flosch/pongo2/v6" 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | type TemplateEngine struct { 11 | // PrepareOutput is invoked before the engine writes a file and must return a writer 12 | // which directs the byte output to correct location, and a finalizer function which 13 | // can be called to finish the write operation. 14 | PrepareOutput func(inputData pongo2.Context, outputPath string) (io.Writer, func() error, error) 15 | } 16 | 17 | func (te *TemplateEngine) ExecuteTemplate(filterSet *FilterSet, tmpl *LoadedTemplate, 18 | inputData pongo2.Context, outputPath string) error { 19 | outputWriter, finalizer, err := te.PrepareOutput(inputData, outputPath) 20 | if err != nil { 21 | return errors.Wrap(err, "ExecuteTemplate") 22 | } 23 | 24 | ctx := make(pongo2.Context) 25 | ctx.Update(inputData) 26 | 27 | // Parallelism risk! This is a really hacky way to implement this functionality, 28 | // and creates all sorts of problems if this is ever used concurrently. We need 29 | // changes to Pongo2 (ideally per templateset filters) in order to avoid this. 30 | filterSet.OutputFileName = outputPath 31 | if err := tmpl.Template.ExecuteWriter(ctx, outputWriter); err != nil { 32 | return errors.Wrap(err, "ExecuteTemplate template error") 33 | } 34 | 35 | if finalizer != nil { 36 | if err := finalizer(); err != nil { 37 | return errors.Wrap(err, "ExecuteTemplate finalizer error") 38 | } 39 | } 40 | 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /pkg/templating/template_loader.go: -------------------------------------------------------------------------------- 1 | package templating 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/flosch/pongo2/v6" 7 | "go.uber.org/zap" 8 | ) 9 | 10 | type LoadedTemplate struct { 11 | Template *pongo2.Template 12 | TemplateSet *pongo2.TemplateSet 13 | } 14 | 15 | func LoadTemplate(templatePath string) *LoadedTemplate { 16 | logger := zap.L() 17 | // Load template 18 | templateBytes, err := os.ReadFile(templatePath) 19 | if err != nil { 20 | logger.Error("Could not read template file", zap.Error(err)) 21 | return nil 22 | } 23 | 24 | templateString := string(templateBytes) 25 | 26 | templateSet := pongo2.NewSet(templatePath, pongo2.DefaultLoader) 27 | 28 | // Load the template to parse it and get it into the cache. 29 | tmpl, err := templateSet.FromString(templateString) 30 | if err != nil { 31 | logger.Error("Could not template file", zap.Error(err), zap.String("template", templatePath)) 32 | return nil 33 | } 34 | 35 | return &LoadedTemplate{ 36 | Template: tmpl, 37 | TemplateSet: templateSet, 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /pkg/templating/templating.go: -------------------------------------------------------------------------------- 1 | package templating 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "encoding/base64" 7 | "encoding/json" 8 | "fmt" 9 | "io" 10 | "os" 11 | "os/user" 12 | "strconv" 13 | "strings" 14 | 15 | "github.com/flosch/pongo2/v6" 16 | "github.com/pelletier/go-toml" 17 | "gopkg.in/yaml.v2" 18 | ) 19 | 20 | // Directory Mode filters are special filters which are activated during directory mode processing. They do things 21 | // like set file permissions and ownership on the output file from the template file perspective. 22 | 23 | const StdOutVal = "" 24 | 25 | type FilterError struct { 26 | Reason string 27 | } 28 | 29 | func (e FilterError) Error() string { 30 | return e.Reason 31 | } 32 | 33 | // FilterSet implements filter-returning functions which can support context information such as the 34 | // name of the output file. 35 | type FilterSet struct { 36 | OutputFileName string 37 | Chown func(name string, uid, gid int) error 38 | Chmod func(name string, mode os.FileMode) error 39 | } 40 | 41 | func (fs *FilterSet) FilterSetOwner(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { 42 | if fs.OutputFileName == StdOutVal { 43 | return nil, nil 44 | } 45 | 46 | var uid int 47 | switch { 48 | case in.IsInteger(): 49 | uid = in.Integer() 50 | case in.IsString(): 51 | userData, err := user.Lookup(in.String()) 52 | if err != nil { 53 | return nil, &pongo2.Error{ 54 | Sender: "filter:SetOwner", 55 | OrigError: err, 56 | } 57 | } 58 | uidraw, err := strconv.ParseInt(userData.Uid, 10, 64) 59 | if err != nil { 60 | return nil, &pongo2.Error{ 61 | Sender: "filter:SetOwner", 62 | OrigError: fmt.Errorf("cannot convert UID value to int: %v %w", userData.Uid, err), 63 | } 64 | } 65 | uid = int(uidraw) 66 | default: 67 | return nil, &pongo2.Error{ 68 | Sender: "filter:SetOwner", 69 | OrigError: FilterError{Reason: "filter input must be of type 'string' or 'integer'."}, 70 | } 71 | } 72 | 73 | if err := fs.Chown(fs.OutputFileName, uid, -1); err != nil { 74 | return nil, &pongo2.Error{ 75 | Sender: "filter:SetOwner", 76 | OrigError: err, 77 | } 78 | } 79 | return pongo2.AsValue(""), nil 80 | } 81 | 82 | func (fs *FilterSet) FilterSetGroup(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { 83 | if fs.OutputFileName == StdOutVal { 84 | return nil, nil 85 | } 86 | 87 | var gid int 88 | switch { 89 | case in.IsInteger(): 90 | gid = in.Integer() 91 | case in.IsString(): 92 | userData, err := user.LookupGroup(in.String()) 93 | if err != nil { 94 | return nil, &pongo2.Error{ 95 | Sender: "filter:SetGroup", 96 | OrigError: err, 97 | } 98 | } 99 | gidraw, err := strconv.ParseInt(userData.Gid, 10, 64) 100 | if err != nil { 101 | return nil, &pongo2.Error{ 102 | Sender: "filter:SetGroup", 103 | OrigError: fmt.Errorf("cannot convert UID value to int: %v %w", userData.Gid, err), 104 | } 105 | } 106 | gid = int(gidraw) 107 | default: 108 | return nil, &pongo2.Error{ 109 | Sender: "filter:SetGroup", 110 | OrigError: FilterError{Reason: "filter input must be of type 'string' or 'integer'."}, 111 | } 112 | } 113 | 114 | if err := os.Chown(fs.OutputFileName, -1, gid); err != nil { 115 | return nil, &pongo2.Error{ 116 | Sender: "filter:SetGroup", 117 | OrigError: err, 118 | } 119 | } 120 | return pongo2.AsValue(""), nil 121 | } 122 | 123 | func (fs *FilterSet) FilterSetMode(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { 124 | if fs.OutputFileName == StdOutVal { 125 | return nil, nil 126 | } 127 | 128 | var mode os.FileMode 129 | 130 | if !in.IsString() { 131 | return nil, &pongo2.Error{ 132 | Sender: "filter:SetMode", 133 | OrigError: FilterError{Reason: "filter input must be of type 'string' in octal format."}, 134 | } 135 | } 136 | 137 | strmode := in.String() 138 | intmode, err := strconv.ParseUint(strmode, 8, 32) 139 | if err != nil { 140 | return nil, &pongo2.Error{ 141 | Sender: "filter:SetMode", 142 | OrigError: err, 143 | } 144 | } 145 | 146 | mode = os.FileMode(intmode) 147 | 148 | if err := fs.Chmod(fs.OutputFileName, mode); err != nil { 149 | return nil, &pongo2.Error{ 150 | Sender: "filter:SetMode", 151 | OrigError: err, 152 | } 153 | } 154 | return pongo2.AsValue(""), nil 155 | } 156 | 157 | func (fs *FilterSet) FilterIndent(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { 158 | if !in.IsString() { 159 | return nil, &pongo2.Error{ 160 | Sender: "filter:Indent", 161 | OrigError: FilterError{Reason: "filter input must be of type 'string'."}, 162 | } 163 | } 164 | 165 | var indent string 166 | switch { 167 | case param.IsString(): 168 | indent = param.String() 169 | case param.IsInteger(): 170 | indent = strings.Repeat(" ", param.Integer()) 171 | default: 172 | return nil, &pongo2.Error{ 173 | Sender: "filter:Indent", 174 | OrigError: FilterError{Reason: "filter param must be of type 'string'."}, 175 | } 176 | } 177 | 178 | input := in.String() 179 | 180 | splitStr := strings.Split(input, "\n") 181 | for idx, v := range splitStr { 182 | splitStr[idx] = fmt.Sprintf("%s%s", indent, v) 183 | } 184 | return pongo2.AsValue(strings.Join(splitStr, "\n")), nil 185 | } 186 | 187 | //nolint:mnd 188 | func (fs *FilterSet) FilterReplace(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { 189 | if !in.IsString() { 190 | return nil, &pongo2.Error{ 191 | Sender: "filter:Replace", 192 | OrigError: FilterError{Reason: "filter input must be of type 'string'."}, 193 | } 194 | } 195 | 196 | if !param.CanSlice() { 197 | return nil, &pongo2.Error{ 198 | Sender: "filter:Replace", 199 | OrigError: FilterError{Reason: "filter param must be of type 'slice'."}, 200 | } 201 | } 202 | 203 | if param.Len() != 2 && param.Len() != 3 { 204 | return nil, &pongo2.Error{ 205 | Sender: "filter:Replace", 206 | OrigError: FilterError{Reason: "filter param must be of type 'slice' containing 2 strings or 2 strings and int."}, 207 | } 208 | } 209 | 210 | var matchParam *pongo2.Value 211 | var replaceParam *pongo2.Value 212 | var countParam *pongo2.Value 213 | 214 | matchParam = param.Index(0) 215 | replaceParam = param.Index(1) 216 | 217 | //nolint:mnd 218 | if param.Len() == 3 { 219 | countParam = param.Index(2) 220 | } 221 | 222 | // For some reason, IsString fails with our set args. But it's probably 223 | // fine to ignore that for now. 224 | //if !matchParam.IsString() { 225 | // return nil, &pongo2.Error{ 226 | // Sender: "filter:Replace", 227 | // OrigError: FilterError{Reason: "element 0 of filter param must be a string"}, 228 | // } 229 | //} 230 | // 231 | //if !replaceParam.IsString() { 232 | // return nil, &pongo2.Error{ 233 | // Sender: "filter:Replace", 234 | // OrigError: FilterError{Reason: "element 1 of filter param must be a string"}, 235 | // } 236 | //} 237 | // 238 | //if countParam != nil { 239 | // if !countParam.IsInteger() { 240 | // return nil, &pongo2.Error{ 241 | // Sender: "filter:Replace", 242 | // OrigError: FilterError{Reason: "element 2 of filter param must be an integer"}, 243 | // } 244 | // } 245 | //} 246 | 247 | match := matchParam.String() 248 | replace := replaceParam.String() 249 | 250 | input := in.String() 251 | 252 | var result string 253 | if countParam == nil { 254 | result = strings.ReplaceAll(input, match, replace) 255 | } else { 256 | // Set notation returns strings 257 | countStr := countParam.String() 258 | count, err := strconv.ParseInt(countStr, 10, 64) 259 | if err != nil { 260 | return nil, &pongo2.Error{ 261 | Sender: "filter:Replace", 262 | OrigError: FilterError{Reason: "element 2 of filter param must be an integer"}, 263 | } 264 | } 265 | 266 | result = strings.Replace(input, match, replace, int(count)) 267 | } 268 | 269 | return pongo2.AsValue(result), nil 270 | } 271 | 272 | func (fs *FilterSet) FilterToJSON(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { 273 | intf := in.Interface() 274 | 275 | useIndent := true 276 | indent := "" 277 | switch { 278 | case param.IsInteger(): 279 | indent = strings.Repeat(" ", param.Integer()) 280 | case param.IsBool(): 281 | indent = " " 282 | case param.IsString(): 283 | indent = param.String() 284 | default: 285 | // We will not be using the indent 286 | useIndent = false 287 | } 288 | 289 | var b []byte 290 | var err error 291 | if useIndent { 292 | b, err = json.MarshalIndent(intf, "", indent) 293 | } else { 294 | b, err = json.Marshal(intf) 295 | } 296 | 297 | if err != nil { 298 | return nil, &pongo2.Error{ 299 | Sender: "filter:ToJson", 300 | OrigError: err, 301 | } 302 | } 303 | 304 | return pongo2.AsValue(string(b)), nil 305 | } 306 | 307 | func (fs *FilterSet) FilterToYAML(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { 308 | intf := in.Interface() 309 | 310 | b, err := yaml.Marshal(intf) 311 | if err != nil { 312 | return nil, &pongo2.Error{ 313 | Sender: "filter:ToJson", 314 | OrigError: err, 315 | } 316 | } 317 | return pongo2.AsValue(string(b)), nil 318 | } 319 | 320 | func (fs *FilterSet) FilterToTOML(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { 321 | intf := in.Interface() 322 | 323 | b, err := toml.Marshal(intf) 324 | if err != nil { 325 | return nil, &pongo2.Error{ 326 | Sender: "filter:ToToml", 327 | OrigError: err, 328 | } 329 | } 330 | return pongo2.AsValue(string(b)), nil 331 | } 332 | 333 | func (fs *FilterSet) FilterToBase64(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { 334 | if in.IsString() { 335 | // encode string 336 | return pongo2.AsValue(base64.StdEncoding.EncodeToString([]byte(in.String()))), nil 337 | } 338 | 339 | intf := in.Interface() 340 | b, ok := intf.([]byte) 341 | if !ok { 342 | return nil, &pongo2.Error{ 343 | Sender: "filter:toBase64", 344 | OrigError: FilterError{Reason: "filter requires a []byte or string input"}, 345 | } 346 | } 347 | 348 | // encode bytes 349 | return pongo2.AsValue(base64.StdEncoding.EncodeToString(b)), nil 350 | } 351 | 352 | func (fs *FilterSet) FilterFromBase64(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { 353 | if !in.IsString() { 354 | return nil, &pongo2.Error{ 355 | Sender: "filter:FromBase64", 356 | OrigError: FilterError{Reason: "filter input must be of type 'string'."}, 357 | } 358 | } 359 | 360 | output, err := base64.StdEncoding.DecodeString(in.String()) 361 | if err != nil { 362 | return nil, &pongo2.Error{ 363 | Sender: "filter:FromBase64", 364 | OrigError: err, 365 | } 366 | } 367 | 368 | // decode as bytes 369 | return pongo2.AsValue(output), nil 370 | } 371 | 372 | func (fs *FilterSet) FilterString(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { 373 | if in.IsString() { 374 | return pongo2.AsValue(in.String()), nil 375 | } 376 | 377 | intf := in.Interface() 378 | 379 | byteData, ok := intf.([]byte) 380 | if !ok { 381 | return nil, &pongo2.Error{ 382 | Sender: "filter:string", 383 | OrigError: FilterError{Reason: "filter requires a []byte or string input"}, 384 | } 385 | } 386 | return pongo2.AsValue(string(byteData)), nil 387 | } 388 | 389 | func (fs *FilterSet) FilterBytes(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { 390 | if in.IsString() { 391 | return pongo2.AsValue([]byte(in.String())), nil 392 | } 393 | 394 | intf := in.Interface() 395 | b, ok := intf.([]byte) 396 | 397 | if !ok { 398 | return nil, &pongo2.Error{ 399 | Sender: "filter:string", 400 | OrigError: FilterError{Reason: "filter requires a []byte or string input"}, 401 | } 402 | } 403 | 404 | return pongo2.AsValue(b), nil 405 | } 406 | 407 | func (fs *FilterSet) FilterToGzip(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { 408 | level := 9 409 | if param.IsInteger() { 410 | level = param.Integer() 411 | } 412 | 413 | intf := in.Interface() 414 | b, ok := intf.([]byte) 415 | 416 | if !ok { 417 | return nil, &pongo2.Error{ 418 | Sender: "filter:to_gzip", 419 | OrigError: FilterError{Reason: "filter requires a []byte input"}, 420 | } 421 | } 422 | 423 | buf := bytes.NewBuffer(nil) 424 | wr, err := gzip.NewWriterLevel(buf, level) 425 | if err != nil { 426 | return nil, &pongo2.Error{ 427 | Sender: "filter:to_gzip", 428 | OrigError: err, 429 | } 430 | } 431 | 432 | if _, err := wr.Write(b); err != nil { 433 | return nil, &pongo2.Error{ 434 | Sender: "filter:to_gzip", 435 | OrigError: err, 436 | } 437 | } 438 | 439 | err = wr.Close() 440 | if err != nil { 441 | return nil, &pongo2.Error{ 442 | Sender: "filter:to_gzip", 443 | OrigError: err, 444 | } 445 | } 446 | 447 | return pongo2.AsValue(buf.Bytes()), nil 448 | } 449 | 450 | func (fs *FilterSet) FilterFromGzip(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { 451 | intf := in.Interface() 452 | b, ok := intf.([]byte) 453 | 454 | if !ok { 455 | return nil, &pongo2.Error{ 456 | Sender: "filter:from_gzip", 457 | OrigError: FilterError{Reason: "filter requires a []byte input"}, 458 | } 459 | } 460 | 461 | rd, err := gzip.NewReader(bytes.NewReader(b)) 462 | if err != nil { 463 | return nil, &pongo2.Error{ 464 | Sender: "filter:from_gzip", 465 | OrigError: err, 466 | } 467 | } 468 | 469 | output, err := io.ReadAll(rd) 470 | if err != nil { 471 | return nil, &pongo2.Error{ 472 | Sender: "filter:from_gzip", 473 | OrigError: err, 474 | } 475 | } 476 | 477 | return pongo2.AsValue(output), nil 478 | } 479 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | // Version is populated by the build system. 4 | // 5 | //nolint:gochecknoglobals 6 | var Version = "development" 7 | 8 | const Description = "Pongo2 based command line templating tool" 9 | --------------------------------------------------------------------------------