├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── config.yml ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── acceptance.yml │ ├── codeql-analysis.yml │ ├── codequality.yml │ ├── installation.yml │ ├── lint.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── CHANGELOG.md ├── CODEOWNERS ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── RELEASE.md ├── acceptance ├── build │ ├── build_test.go │ └── testdata │ │ ├── good-project │ │ ├── content │ │ │ ├── empty.txt │ │ │ └── goodfile.txt.tmpl │ │ └── pct-config.yml │ │ ├── no-content-dir-project │ │ └── pct-config.yml │ │ └── no-pct-config-project │ │ └── content │ │ └── empty.txt ├── install │ ├── install_test.go │ └── testdata │ │ ├── additional-project.tar.gz │ │ ├── good-project.tar.gz │ │ ├── invalid-gz-project.tar.gz │ │ └── invalid-tar-project.tar.gz ├── new │ └── new_test.go └── testutils │ └── testutils.go ├── build.ps1 ├── build.sh ├── cmd ├── build │ ├── build.go │ └── build_test.go ├── completion │ └── completion.go ├── explain │ └── explain.go ├── install │ ├── install.go │ └── install_test.go ├── new │ ├── new.go │ └── new_test.go ├── root │ └── root.go └── version │ └── version.go ├── docs.ps1 ├── docs.sh ├── docs ├── _resources │ ├── completion_setup.gif │ ├── install_and_export_path.gif │ ├── new_class.gif │ ├── new_ghactions.gif │ ├── new_info.gif │ └── new_module.gif ├── md │ ├── content │ │ ├── about.md │ │ ├── contact │ │ │ ├── _index.md │ │ │ └── contact.md │ │ ├── install.md │ │ ├── quick-start.md │ │ └── usage │ │ │ ├── _index.md │ │ │ ├── templates-getting-started.md │ │ │ ├── templates-overriding.md │ │ │ ├── templates-sharing.md │ │ │ └── templates-writing.md │ ├── go.mod │ └── md.go └── terminalizer │ ├── step1.yml │ ├── step2.yml │ ├── step3.yml │ └── step4.yml ├── go.mod ├── go.sum ├── internal └── pkg │ ├── pct │ ├── pct.go │ ├── pct_test.go │ └── testdata │ │ ├── examples │ │ └── puppetlabs │ │ │ ├── full-project │ │ │ └── 0.1.0 │ │ │ │ ├── content │ │ │ │ └── metadata.json.tmpl │ │ │ │ └── pct-config.yml │ │ │ ├── good-project-override │ │ │ └── pct.yml │ │ │ ├── good-project │ │ │ └── 0.1.0 │ │ │ │ ├── content │ │ │ │ ├── empty.txt │ │ │ │ └── goodfile.txt.tmpl │ │ │ │ └── pct-config.yml │ │ │ └── replace-thing │ │ │ └── 0.1.0 │ │ │ ├── content │ │ │ └── {{pct_name}}.txt.tmpl │ │ │ └── pct-config.yml │ │ └── good-project.tar.gz │ └── pct_config_processor │ ├── pct_config_processor.go │ └── pct_config_processor_test.go ├── main.go ├── pkg ├── build │ ├── build.go │ └── build_test.go ├── config_processor │ └── config_processor.go ├── docs │ └── docs.go ├── exec_runner │ └── exec_runner.go ├── gzip │ ├── good-project.tar │ ├── gunzip.go │ ├── gunzip_test.go │ ├── gzip.go │ ├── gzip_test.go │ └── testdata │ │ └── examples │ │ ├── good-project.tar │ │ └── good-project.tar.gz ├── httpclient │ └── httpclient.go ├── install │ ├── install.go │ └── install_test.go ├── mock │ ├── build.go │ ├── exec_runner.go │ ├── fileinfo.go │ ├── gunzip.go │ ├── gzip.go │ ├── httpclient.go │ ├── install.go │ ├── install_config.go │ ├── os_utils.go │ ├── tar.go │ └── utilsHelper.go ├── tar │ ├── tar.go │ ├── tar_test.go │ └── testdata │ │ └── examples │ │ ├── full-project │ │ ├── content │ │ │ └── metadata.json.tmpl │ │ └── pct-config.yml │ │ └── good-project.tar ├── telemetry │ ├── telemetry.go │ └── telemetry_noop.go └── utils │ ├── os_utils.go │ ├── utils.go │ ├── utilsHelper.go │ └── utils_test.go └── scripts ├── install.ps1 └── install.sh /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = tab 9 | indent_size = 2 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | charset = utf-8 13 | 14 | [*.go,go.mod,go.sum] 15 | indent_style = tab 16 | 17 | [*.md] 18 | indent_style = tab 19 | trim_trailing_whitespace = false 20 | 21 | [*.{yml,yaml,json}] 22 | indent_style = space 23 | indent_size = 2 24 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.go text eol=lf 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "(BUG) " 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Run the '...' command 16 | 2. Do other thing 17 | 3. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots or logs** 23 | If applicable, add screenshots or logs to help explain your problem. 24 | 25 | **What versions are you running?:** 26 | - OS: [e.g. Windows 10] 27 | - PCT Version [e.g. 0.1.0] 28 | 29 | **Additional context** 30 | Add any other context about the problem here. 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 📢 Feedback 4 | about: Have an idea for a new feature? Open a discussion and let's talk about it! 5 | url: https://github.com/puppetlabs/pct/discussions/new 6 | - name: ❔ Ask a question about Puppet Content Templates 7 | about: Please check out the community forum for discussions about the Puppet Content Templates. 8 | url: https://github.com/puppetlabs/pct/discussions/new 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" # See documentation for possible values 4 | directory: "/" # Location of package manifests 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /.github/workflows/acceptance.yml: -------------------------------------------------------------------------------- 1 | name: Acceptance 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | 7 | env: 8 | go_version: 1.18 9 | 10 | jobs: 11 | build: 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: [ubuntu-latest, windows-latest, macos-latest] 16 | runs-on: ${{ matrix.os }} 17 | env: 18 | GOPATH: ${{ github.workspace }} 19 | TEST_ACCEPTANCE: true 20 | TMP: ${{ github.workspace }}/tmp 21 | TEMP: ${{ github.workspace }}/tmp 22 | defaults: 23 | run: 24 | working-directory: ${{ env.GOPATH }}/src/github.com/${{ github.repository }} 25 | steps: 26 | - uses: actions/checkout@v2 27 | with: 28 | path: ${{ env.GOPATH }}/src/github.com/${{ github.repository }} 29 | - name: Set up Go 30 | uses: actions/setup-go@v2 31 | with: 32 | go-version: ${{ env.go_version }} 33 | - name: Build nix 34 | run: | 35 | go install github.com/goreleaser/goreleaser@latest 36 | ./build.sh 37 | if: runner.os != 'Windows' 38 | env: 39 | HONEYCOMB_API_KEY: ${{ secrets.HONEYCOMB_API_KEY_DEV }} 40 | HONEYCOMB_DATASET: pct_dev 41 | - name: Build Windows 42 | run: | 43 | go install github.com/goreleaser/goreleaser@latest 44 | ./build.ps1 45 | if: runner.os == 'Windows' 46 | env: 47 | HONEYCOMB_API_KEY: ${{ secrets.HONEYCOMB_API_KEY_DEV }} 48 | HONEYCOMB_DATASET: pct_dev 49 | - name: Test 50 | run: go test -v ./acceptance/... 51 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | schedule: 9 | - cron: '44 9 * * 4' 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | permissions: 16 | actions: read 17 | contents: read 18 | security-events: write 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | language: [ 'go' ] 24 | 25 | steps: 26 | - name: Checkout repository 27 | uses: actions/checkout@v2 28 | 29 | - name: Initialize CodeQL 30 | uses: github/codeql-action/init@v1 31 | with: 32 | languages: ${{ matrix.language }} 33 | 34 | - name: Autobuild 35 | uses: github/codeql-action/autobuild@v1 36 | 37 | - name: Perform CodeQL Analysis 38 | uses: github/codeql-action/analyze@v1 39 | -------------------------------------------------------------------------------- /.github/workflows/codequality.yml: -------------------------------------------------------------------------------- 1 | name: codequality 2 | on: [pull_request] 3 | 4 | env: 5 | go_version: 1.18 6 | 7 | jobs: 8 | security: 9 | name: gosec, Inspects source code for security problems 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Check out code 13 | uses: actions/checkout@v3 14 | - name: Snyk Scan for vulnerabilities in dependencies 15 | uses: snyk/actions/golang@master 16 | env: 17 | SNYK_TOKEN: ${{ secrets.SNYK_FOSS_KEY }} 18 | with: 19 | command: monitor 20 | - name: Set up Go 21 | uses: actions/setup-go@v2 22 | with: 23 | go-version: ${{ env.go_version }} 24 | - run: | 25 | go install github.com/securego/gosec/v2/cmd/gosec@v2.12.0 26 | gosec -exclude-dir=testutils -exclude-dir=pkg/mock ./... 27 | format: 28 | name: fmt, makes sure there are no formatting issues 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Set up Go 32 | uses: actions/setup-go@v2 33 | with: 34 | go-version: ${{ env.go_version }} 35 | - name: Check out code 36 | uses: actions/checkout@v1 37 | - name: Run fmt 38 | run: make format 39 | mod_tidy: 40 | name: go mod tidy, makes sure are dependencies are cool 41 | runs-on: ubuntu-latest 42 | steps: 43 | - name: Set up Go 44 | uses: actions/setup-go@v2 45 | with: 46 | go-version: ${{ env.go_version }} 47 | - name: Check out code 48 | uses: actions/checkout@v1 49 | - name: Run go mod tidy 50 | run: make tidy 51 | -------------------------------------------------------------------------------- /.github/workflows/installation.yml: -------------------------------------------------------------------------------- 1 | name: Installation Tests 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | 7 | jobs: 8 | install_test: 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | os: [ubuntu-latest, windows-latest, macos-latest] 13 | runs-on: ${{ matrix.os }} 14 | env: 15 | PCT_INSTALL_DEBUG: true 16 | steps: 17 | - uses: actions/checkout@v2 18 | with: 19 | fetch-depth: 0 20 | - name: Get Latest Tag (Windows) 21 | id: latest_tag 22 | if: runner.os == 'Windows' 23 | run: | 24 | $TagVersion = git tag --list | 25 | Where-Object { $_ -match '^\d+\.\d+\.\d+$' } | 26 | Sort-Object -Descending | 27 | Select-Object -First 1 28 | echo "::set-output name=tag::$TagVersion" 29 | - name: Install PCT (Windows) 30 | if: runner.os == 'Windows' 31 | shell: pwsh 32 | run: | 33 | . .\scripts\install.ps1; Install-Pct 34 | - name: Validate install (Windows) 35 | if: runner.os == 'Windows' 36 | run: | 37 | $HomeDir = Get-Item ~ | Select-Object -ExpandProperty FullName 38 | $PctPath = "${HomeDir}\.puppetlabs\pct\pct.exe" 39 | $verInfo = & $PctPath --version | 40 | Select-Object -First 1 | 41 | ForEach-Object { $_ -split " " } | 42 | Select-Object -Skip 1 -First 2 43 | if (& $PctPath --version | Out-String -Stream | Select-String -Pattern '${{ steps.latest_tag.outputs.tag }}') { 44 | exit 0 45 | } else { 46 | exit 1 47 | } 48 | - name: Install PCT (Unix) 49 | if: runner.os != 'Windows' 50 | run: ./scripts/install.sh 51 | - name: Validate install (Unix) 52 | if: runner.os != 'Windows' 53 | run: $HOME/.puppetlabs/pct/pct --version | grep "pct $(git tag | tail -n 1)" 54 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | 7 | jobs: 8 | golangci: 9 | name: lint 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: golangci-lint 14 | uses: golangci/golangci-lint-action@v2 15 | with: 16 | version: v1.45.2 17 | only-new-issues: true 18 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*.*.*' 7 | 8 | env: 9 | go_version: 1.18 10 | 11 | jobs: 12 | goreleaser: 13 | runs-on: ubuntu-latest 14 | env: 15 | WORKINGDIR: ${{ github.workspace }} 16 | steps: 17 | - 18 | name: Checkout pct 19 | uses: actions/checkout@v2 20 | with: 21 | fetch-depth: 0 22 | - 23 | name: Checkout PCT Templates 24 | uses: actions/checkout@v2 25 | with: 26 | repository: puppetlabs/baker-round 27 | fetch-depth: 1 28 | path: templates 29 | - 30 | name: Set up Go 31 | uses: actions/setup-go@v2 32 | with: 33 | go-version: ${{ env.go_version }} 34 | - 35 | name: Run GoReleaser 36 | uses: goreleaser/goreleaser-action@v2 37 | with: 38 | version: latest 39 | args: release --rm-dist 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | HONEYCOMB_API_KEY: ${{ secrets.HONEYCOMB_API_KEY_PROD }} 43 | HONEYCOMB_DATASET: pct 44 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | 7 | env: 8 | go_version: 1.18 9 | 10 | jobs: 11 | build: 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: [ubuntu-latest, windows-latest, macos-latest] 16 | runs-on: ${{ matrix.os }} 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - name: Set up Go 21 | uses: actions/setup-go@v2 22 | with: 23 | go-version: ${{ env.go_version }} 24 | 25 | - name: Build 26 | run: go build -v 27 | 28 | - name: Test 29 | run: go test -v ./... 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | dist/ 17 | templates/ 18 | 19 | .DS_Store 20 | 21 | .idea/ 22 | 23 | ./pdk 24 | ./pct 25 | 26 | # Documentation site 27 | docs/site 28 | 29 | .hugo_build.lock 30 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # Inspired by https://github.com/golangci/golangci-lint/blob/54bfbb9a52299b6ecbdde9365282aa40a5561bf1/.golangci.yml#L1 2 | linters-settings: 3 | linters: 4 | disable-all: true 5 | enable: 6 | - bodyclose 7 | - deadcode 8 | - depguard 9 | - dogsled 10 | - dupl 11 | - errcheck 12 | - exportloopref 13 | - exhaustive 14 | - funlen 15 | - gochecknoinits 16 | - goconst 17 | - gocritic 18 | - gocyclo 19 | - gofmt 20 | - goimports 21 | - golint 22 | - gomnd 23 | - goprintffuncname 24 | - gosec 25 | - gosimple 26 | - govet 27 | - ineffassign 28 | - lll 29 | - misspell 30 | - nakedret 31 | - noctx 32 | - nolintlint 33 | - rowserrcheck 34 | - staticcheck 35 | - structcheck 36 | - stylecheck 37 | - typecheck 38 | - unconvert 39 | - unparam 40 | - unused 41 | - varcheck 42 | - whitespace 43 | 44 | goconst: 45 | min-len: 2 46 | min-occurrences: 2 47 | gocritic: 48 | enabled-tags: 49 | - diagnostic 50 | - experimental 51 | - opinionated 52 | - performance 53 | - style 54 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: pct 2 | 3 | release: 4 | name_template: "PCT {{.Version}}" 5 | prerelease: auto 6 | 7 | before: 8 | hooks: 9 | - go mod tidy 10 | - go fmt ./... 11 | 12 | builds: 13 | - binary: pct 14 | id: pct 15 | env: 16 | - CGO_ENABLED=0 17 | goos: 18 | - linux 19 | - windows 20 | - darwin 21 | goarch: 22 | - amd64 23 | - arm 24 | - arm64 25 | asmflags: 26 | - all=-trimpath={{.Env.WORKINGDIR}} 27 | gcflags: 28 | - all=-trimpath={{.Env.WORKINGDIR}} 29 | ldflags: 30 | - -s -w -X main.version={{.Version}} -X main.commit={{.ShortCommit}} -X main.date={{.CommitDate}} -X main.honeycomb_api_key={{.Env.HONEYCOMB_API_KEY}} -X main.honeycomb_dataset={{.Env.HONEYCOMB_DATASET}} 31 | mod_timestamp: '{{ .CommitTimestamp }}' 32 | tags: 33 | - telemetry 34 | - binary: pct 35 | id: notel_pct 36 | env: 37 | - CGO_ENABLED=0 38 | goos: 39 | - linux 40 | - windows 41 | - darwin 42 | goarch: 43 | - amd64 44 | - arm 45 | - arm64 46 | asmflags: 47 | - all=-trimpath={{.Env.WORKINGDIR}} 48 | gcflags: 49 | - all=-trimpath={{.Env.WORKINGDIR}} 50 | ldflags: 51 | - -s -w -X main.version={{.Version}} -X main.commit={{.ShortCommit}} -X main.date={{.CommitDate}} -X main.honeycomb_api_key={{.Env.HONEYCOMB_API_KEY}} -X main.honeycomb_dataset={{.Env.HONEYCOMB_DATASET}} 52 | mod_timestamp: '{{ .CommitTimestamp }}' 53 | 54 | archives: 55 | - name_template: "pct_{{ tolower .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}" 56 | id: pct 57 | builds: 58 | - pct 59 | replacements: 60 | darwin: Darwin 61 | linux: Linux 62 | windows: Windows 63 | 386: i386 64 | amd64: x86_64 65 | wrap_in_directory: false 66 | format_overrides: 67 | - goos: windows 68 | format: zip 69 | files: 70 | - templates/**/* 71 | - name_template: "notel_pct_{{ tolower .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}" 72 | id: notel_pct 73 | builds: 74 | - notel_pct 75 | replacements: 76 | darwin: Darwin 77 | linux: Linux 78 | windows: Windows 79 | 386: i386 80 | amd64: x86_64 81 | wrap_in_directory: false 82 | format_overrides: 83 | - goos: windows 84 | format: zip 85 | files: 86 | - templates/**/* 87 | 88 | 89 | checksum: 90 | name_template: 'checksums.txt' 91 | 92 | snapshot: 93 | name_template: "{{ .Tag }}-{{.ShortCommit}}" 94 | 95 | changelog: 96 | sort: asc 97 | filters: 98 | exclude: 99 | - '^docs:' 100 | - '^test:' 101 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "golang.go" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Debug (With Telemetry): pct new --list", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "debug", 12 | "program": "${workspaceFolder}", 13 | "args": [ 14 | "new", 15 | "--list", 16 | "--templatepath", 17 | "${input:pct_template_path}" 18 | ], 19 | "buildFlags": "-tags='telemetry' -ldflags='-X main.honeycomb_api_key=${input:honeycomb_api_key} -X main.honeycomb_dataset=pct_dev'", 20 | }, 21 | { 22 | "name": "Debug (No Telemetry): pct new --list", 23 | "type": "go", 24 | "request": "launch", 25 | "mode": "debug", 26 | "program": "${workspaceFolder}", 27 | "args": [ 28 | "new", 29 | "--list", 30 | "--templatepath", 31 | "${input:pct_template_path}" 32 | ], 33 | }, 34 | { 35 | "name": "Debug (With Telemetry): pct new puppetlabs/bolt-plan", 36 | "type": "go", 37 | "request": "launch", 38 | "mode": "debug", 39 | "program": "${workspaceFolder}", 40 | "args": [ 41 | "new", 42 | "puppetlabs/bolt-plan", 43 | "--templatepath", 44 | "${input:pct_template_path}" 45 | ], 46 | "buildFlags": "-tags='telemetry' -ldflags='-X main.honeycomb_api_key=${input:honeycomb_api_key} -X main.honeycomb_dataset=pct_dev'", 47 | }, 48 | { 49 | "name": "Debug (No Telemetry): pct new puppetlabs/bolt-plan", 50 | "type": "go", 51 | "request": "launch", 52 | "mode": "debug", 53 | "program": "${workspaceFolder}", 54 | "args": [ 55 | "new", 56 | "puppetlabs/bolt-plan", 57 | "--templatepath", 58 | "${input:pct_template_path}" 59 | ], 60 | }, 61 | ], 62 | "inputs": [ 63 | { 64 | "id": "pct_template_path", 65 | "description": "The path to the folder containing PCT Templates", 66 | "type": "promptString", 67 | "default": "${workspaceFolder}/../baker-round" 68 | }, 69 | { 70 | "id": "honeycomb_api_key", 71 | "description": "The API Key for sending traces to Honeycomb", 72 | "type": "promptString", 73 | } 74 | ] 75 | } 76 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.formatOnSaveMode": "modifications", 4 | 5 | "files.encoding": "utf8", 6 | "files.eol": "\n", 7 | "files.insertFinalNewline": true, 8 | "files.trimFinalNewlines": true, 9 | "files.trimTrailingWhitespace": true, 10 | 11 | "[go]": { 12 | "editor.insertSpaces": false, 13 | "editor.formatOnSave": true, 14 | "editor.codeActionsOnSave": { 15 | "source.fixAll": true, 16 | "source.organizeImports": true 17 | } 18 | }, 19 | 20 | "go.lintTool":"golangci-lint", 21 | "go.lintFlags": [ 22 | "--config=${workspaceFolder}/.golangci.yml", 23 | "--fast" 24 | ], 25 | "go.testOnSave": true, 26 | "go.lintOnSave": "file", 27 | "go.coverOnSave": false, 28 | "go.coverageOptions": "showBothCoveredAndUncoveredCode", 29 | "go.coverOnTestPackage": true, 30 | "go.coverOnSingleTest": true, 31 | "go.delveConfig": { 32 | "dlvLoadConfig": { 33 | "maxStringLen": 1024, 34 | }, 35 | "apiVersion": 2, 36 | }, 37 | } 38 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Change Log" 3 | description: "List of changes made across the different versions of PCT." 4 | --- 5 | 6 | # Changelog 7 | All notable changes to this project will be documented in this file. 8 | 9 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 10 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 11 | 12 | ## [Unreleased] 13 | 14 | ### Added 15 | 16 | - [(GH-342)](https://github.com/puppetlabs/pct/issues/342) The `build` package as a genericized public package for turning packages with a config file and content folder into `tar.gz` files. 17 | 18 | ### Changed 19 | 20 | - [(GH-342)](https://github.com/puppetlabs/pct/issues/342) Improved the messaging for `build` failures to point to the full path of the config being processed. 21 | 22 | ### Fixed 23 | 24 | - [(GH-285)](https://github.com/puppetlabs/pct/issues/285) Ensure running PCT without arguments does not fail unexpectedly. 25 | - [(GH-287)](https://github.com/puppetlabs/pct/issues/287) Ensure a misconfigured telemetry binary fails early and cleanly. 26 | - [(GH-312)](https://github.com/puppetlabs/pct/issues/312) Ensure that the format flag correctly autocompletes valid format options. 27 | 28 | ## [0.5.0] 29 | ### Added 30 | 31 | - [(GH-222)](https://github.com/puppetlabs/pct/issues/222) Telemetry to the binary, which will report the operating system type and architecture when a command is run; the implementation allows for two binaries: one with telemetry configured and enabled, and one _without_ the telemetry included at all. 32 | - [(GH-223)](https://github.com/puppetlabs/pct/issues/223) Added hashed machine uuid generation and included in the telemetry; this will report a universally unique machine ID for each node running PCT and reporting telemetry. 33 | - [(GH-136)](https://github.com/puppetlabs/pct/issues/136) Added `--git-uri` flag to the `pct install` command for installation of templates from remote repositories. 34 | 35 | ## [0.4.0] 36 | 37 | ### Changed 38 | 39 | - The Puppet Content templates shipped in 0.4.0 and the handling of templates in 0.4.0 is _not_ backward compatible with templates which do not have `id`, `author`, AND `version` defined in their metadata 40 | 41 | ### Added 42 | 43 | - [(GH-183)](https://github.com/puppetlabs/pct/issues/183) `pct new` handles namespaced templates 44 | - [(GH-184)](https://github.com/puppetlabs/pct/issues/184) `pct install` works against remote `tar.gz` files 45 | - [(GH-185)](https://github.com/puppetlabs/pct/issues/185) `pct build` validates pct-config.yml 46 | - [(GH-167)](https://github.com/puppetlabs/pct/issues/167) Implement `pct install` CLI command 47 | - [(TEMPLATES-17)](https://github.com/puppetlabs/baker-round/issues/17) Ensure `puppet-content-template` includes the author key in the scaffolded config file 48 | - [(TEMPLATES-18)](https://github.com/puppetlabs/baker-round/issues/18) Ensure all default templates have their author set to `puppetlabs` 49 | 50 | ## [0.3.0] 51 | 52 | - [(GH-144)](https://github.com/puppetlabs/pct/issues/144) Implement `pct build` CLI command 53 | 54 | ### Removed 55 | 56 | - [(GH-172)](https://github.com/puppetlabs/pct/issues/172) Removal of PDKShell commands 57 | 58 | ## [0.2.0] 59 | 60 | ### Added 61 | 62 | - [(GH-83)](https://github.com/puppetlabs/pct/issues/83) Allow for workspace configuration overrides 63 | - [(GH-107](https://github.com/puppetlabs/pct/issues/107) Initialize zerolog via cobra.OnInitialize method 64 | 65 | ### Fixed 66 | 67 | - [(GH-15)](https://github.com/puppetlabs/pct/issues/15) Unset necessary env vars in pdkshell 68 | - [(GH-125)](https://github.com/puppetlabs/pct/issues/125) Fail on errors, quote arguments 69 | - [(GH-125)](https://github.com/puppetlabs/pct/issues/125) Fix `$ver` bug in download script 70 | 71 | ## [0.1.0] 72 | 73 | ### Added 74 | 75 | - [(GH-67)](https://github.com/puppetlabs/pct/issues/67) Add installation scripts for PCT 76 | 77 | ### Fixed 78 | 79 | - [(GH-64)](https://github.com/puppetlabs/pct/issues/64) Strip pct from command name 80 | - [(GH-65)](https://github.com/puppetlabs/pct/issues/65) Allow deployment of empty files 81 | - [(GH-14)](https://github.com/puppetlabs/pct/issues/14) Return the exit code from the PDK when executed by the wrapper 82 | 83 | ## [0.1.0-pre] 84 | 85 | ### Added 86 | 87 | - [(GH-2)](https://github.com/puppetlabs/pct/issues/2) Created Puppet Content Templates package and modified pdk new to use PCT 88 | - [(GH-7)](https://github.com/puppetlabs/pct/issues/7) Added wrapper to all existing PDK commands 89 | 90 | ### Fixed 91 | 92 | - [(GH-29)](https://github.com/puppetlabs/pct/issues/29) Error if template not found 93 | 94 | [Unreleased]: https://github.com/puppetlabs/pct/compare/0.4.0..main 95 | [0.5.0]: https://github.com/puppetlabs/pct/releases/tag/0.5.0 96 | [0.4.0]: https://github.com/puppetlabs/pct/releases/tag/0.4.0 97 | [0.3.0]: https://github.com/puppetlabs/pct/releases/tag/0.3.0 98 | [0.2.0]: https://github.com/puppetlabs/pct/releases/tag/0.2.0 99 | [0.1.0]: https://github.com/puppetlabs/pct/releases/tag/0.1.0 100 | [0.1.0-pre]: https://github.com/puppetlabs/pct/releases/tag/0.1.0-pre 101 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @puppetlabs/modules 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Contributing" 3 | draft: false 4 | --- 5 | 6 | # Contributing 7 | 8 | Hi! Thanks for your interest in contributing to the Puppet Development Kit! 9 | 10 | Community contributions are essential for keeping Puppet great. We simply can't access the huge number of platforms and myriad configurations for running Puppet. We want to keep it as easy as possible to contribute changes that get things working in your environment. There are a few guidelines that we need contributors to follow so that we can have a chance of keeping on top of things. 11 | 12 | We accept pull requests for bug fixes and features where we've discussed the approach in an issue and given the go-ahead for a community member to work on it. We'd also love to hear about ideas for new features as Github Discussion. 13 | 14 | Please do: 15 | 16 | - Open an issue if things aren't working as expected. 17 | - Open a Github Discussion to propose a significant change. 18 | - Open a pull request to fix a bug. 19 | - Open a pull request to fix documentation about a command. 20 | - Open a pull request for any issue labelled help wanted or good first issue. 21 | 22 | ## Building the project 23 | 24 | Prerequisites: 25 | 26 | - Go 1.16+ 27 | 28 | To build the PDK, run the following command: 29 | 30 | > These build scripts produce a binary for the current platform only. These scripts are meant for local testing. We use a Github Action to release this product. 31 | 32 | ```bash 33 | > # on nix 34 | > ./build.sh 35 | ``` 36 | 37 | ```powershell 38 | # on windows 39 | > ./build.ps1 40 | ``` 41 | 42 | To run the new binary: 43 | 44 | ```bash 45 | > ./pct [command] 46 | ``` 47 | 48 | To test the PCT command, run the following command. 49 | 50 | ```bash 51 | > go test ./... 52 | ``` 53 | 54 | To run the acceptance tests, ensure that you have built the PCT binary using the build scripts, then: 55 | 56 | ```bash 57 | TEST_ACCEPTANCE=true go test -count=1 -v github.com/puppetlabs/pct/acceptance 58 | ``` 59 | 60 | ## Running the project 61 | 62 | ```bash 63 | > go run main.go 64 | ``` 65 | 66 | ## Building Cross Platform binaries 67 | 68 | Prerequisites: 69 | 70 | - Go 1.16+ 71 | - goreleaser 0.16+ 72 | 73 | To build the PCT for more than your current platform, use [`GoReleaser`](https://goreleaser.com/quick-start/#dry-run): 74 | 75 | ```bash 76 | > goreleaser --snapshot --skip-publish --rm-dist 77 | ``` 78 | 79 | This will ouput a set of binaries in the `dist` folder. 80 | 81 | ## Submitting a Pull Request 82 | 83 | 1. Create a new branch `git checkout -b my-branch-name` 84 | 1. Make you change, add tests, and ensure tests pass 85 | 1. Make sure your commit messages are in the proper format. If the commit addresses an issue filed in the project, start the first line of the commit with the prefix `GH-` and the issue number in parentheses `(GH-111)`. After leave a detailed explanation of your change, so a person in the future can understand what your work does. 86 | 1. Submit a pull request (i.e. using the Github commandline: `gh pr create --web`) 87 | 88 | ## Making Trivial Changes 89 | 90 | For changes of a trivial nature, it is not always necessary to create a new ticket. In this case, it is appropriate to start the first line of a commit with one of (docs), (maint), or (packaging) instead of a ticket number. 91 | 92 | ## Additional Resources 93 | 94 | * [Puppet community guidelines](https://puppet.com/community/community-guidelines) 95 | * [Contributor License Agreement](http://cla.puppet.com/) 96 | * [General GitHub documentation](https://help.github.com/) 97 | * [GitHub pull request documentation](https://help.github.com/articles/creating-a-pull-request/) 98 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | quality: format lint sec tidy 2 | 3 | # Run go mod tidy and check go.sum is unchanged 4 | PHONY+= tidy 5 | tidy: 6 | @echo "🔘 Checking that go mod tidy does not make a change..." 7 | @cp go.sum go.sum.bak 8 | @go mod tidy 9 | @diff go.sum go.sum.bak && rm go.sum.bak || (echo "🔴 go mod tidy would make a change, exiting"; exit 1) 10 | @echo "✅ Checking go mod tidy complete" 11 | 12 | # Format go code and error if any changes are made 13 | PHONY+= format 14 | format: 15 | @echo "🔘 Checking that go fmt does not make any changes..." 16 | @test -z $$(go fmt ./...) || (echo "🔴 go fmt would make a change, exiting"; exit 1) 17 | @echo "✅ Checking go fmt complete" 18 | 19 | PHONY+= lint 20 | lint: $(GOPATH)/bin/golangci-lint 21 | @echo "🔘 Linting $(1) (`date '+%H:%M:%S'`)" 22 | @lint=`golint ./...`; \ 23 | if [ "$$lint" != "" ]; \ 24 | then echo "🔴 Lint found by golint"; echo "$$lint"; exit 1;\ 25 | fi 26 | @lint=`go vet ./...`; \ 27 | if [ "$$lint" != "" ]; \ 28 | then echo "🔴 Lint found by go vet"; echo "$$lint"; exit 1;\ 29 | fi 30 | @lint=`golangci-lint run`; \ 31 | if [ "$$lint" != "" ]; \ 32 | then echo "🔴 Lint found by golangci-lint"; echo "$$lint"; exit 1;\ 33 | fi 34 | @echo "✅ Lint-free (`date '+%H:%M:%S'`)" 35 | 36 | PHONY+= sec 37 | sec: $(GOPATH)/bin/gosec 38 | @echo "🔘 Checking for security problems ... (`date '+%H:%M:%S'`)" 39 | @sec=`gosec -exclude-dir=testutils -quiet ./...`; \ 40 | if [ "$$sec" != "" ]; \ 41 | then echo "🔴 Problems found"; echo "$$sec"; exit 1;\ 42 | else echo "✅ No problems found (`date '+%H:%M:%S'`)"; \ 43 | fi 44 | 45 | default: quality 46 | 47 | .PHONY: $(PHONY) 48 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Releasing a New Version 2 | 3 | We use [`goreleaser`](https://github.com/goreleaser/goreleaser) to release new packages. 4 | Once the commit we want to release at, is tagged, the [release workflow](https://github.com/puppetlabs/pct/blob/main/.github/workflows/release.yml) executes, which will: 5 | - Generate the build artefacts 6 | - Create a new [release](https://github.com/puppetlabs/pct/releases) in Github 7 | 8 | ## Create and merge release prep PR 9 | 10 | - Create a new branch from `puppetlabs/main` called `maint/main/release_prep_` (e.g. `maint/main/release_prep_0.5.0`) 11 | - Ensure the `CHANGELOG` is up-to-date and contains entries for any user visible new features / bugfixes. This should have been added as part of each ticket's work, but sometimes things are missed: 12 | 13 | >> Compare the changes between `main` and the latest release tag: 14 | >> 15 | >> For example: 16 | 17 | - Rename `[Unreleased]` to the version we are releasing and create a new `[Unreleased]` header at the top of the `CHANGELOG`: 18 | 19 | ```md 20 | ## [Unreleased] 21 | 22 | ## [0.5.0] 23 | 24 | - [(GH-123)](https://github.com/puppetlabs/pct/issues/123) New feature in 0.5.0 25 | - [(GH-567)](https://github.com/puppetlabs/pct/issues/567) Bug fix in 0.5.0 26 | ``` 27 | 28 | - Update the links at the bottom of the `CHANGELOG` with the new release version and update the `[Unreleased]` tag to compare from the version we're releasing now against `main`: 29 | 30 | ```md 31 | [Unreleased]: https://github.com/puppetlabs/pct/compare/0.5.0..main 32 | [0.5.0]: https://github.com/puppetlabs/pct/releases/tag/0.5.0 33 | [0.4.0]: https://github.com/puppetlabs/pct/releases/tag/0.4.0 34 | ... 35 | ``` 36 | 37 | - Add and commit these changes 38 | - Create a PR against `main`: 39 | - Tag: `maintenance` 40 | - Wait for the tests to pass 41 | - Request a colleage to review and merge 42 | 43 | ## Tag merge commit 44 | 45 | After the release prep PR has been merged, perform a `git fetch` and `git pull` and ensure you are on the merged commit of the release prep that has just landed in [`puppetlabs:main`](https://github.com/puppetlabs/pct/commits/main). 46 | 47 | Tag the merged commit and push to `puppetlabs`: 48 | 49 | ```sh 50 | git tag -a -m "PCT " 51 | git push 52 | ``` 53 | 54 | For example, assuming: 55 | - **Locally configured remote repo name for `puppetlabs`:** `origin` 56 | - **Version:** `0.5.0` 57 | 58 | ```sh 59 | git tag -a 0.5.0 -m "PCT 0.5.0" 60 | git push origin 0.5.0 61 | ``` 62 | 63 | This should trigger the [release worfklow](https://github.com/puppetlabs/pct/actions/workflows/release.yml) to perform the release. 64 | Ensure the workflow completes, then move on to the final steps. 65 | 66 | ## Perform post release installation tests 67 | 68 | - Ensure that there is a new release for the version we tagged in [Releases](https://github.com/puppetlabs/pct/releases), with: 69 | - The correct version 70 | - The expected number of build artefacts 71 | - Perform a quick test locally to ensure that the [installation instructions in the README](https://github.com/puppetlabs/pct/blob/main/README.md#installing) work and that the latest version is installed on your local system / test system 72 | - Repeat the above steps for the [Telemetry free version](https://github.com/puppetlabs/pct/blob/main/README.md#installing-telemetry-free-version) 73 | -------------------------------------------------------------------------------- /acceptance/build/build_test.go: -------------------------------------------------------------------------------- 1 | package build_test 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | "testing" 7 | 8 | "github.com/puppetlabs/pct/acceptance/testutils" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | const APP = "pct" 13 | 14 | func Test_PctBuild_Outputs_TarGz(t *testing.T) { 15 | testutils.SkipAcceptanceTest(t) 16 | testutils.SetAppName(APP) 17 | 18 | templateName := "good-project" 19 | 20 | sourceDir, _ := filepath.Abs("../../acceptance/build/testdata") 21 | templateDir := filepath.Join(sourceDir, templateName) 22 | wd := testutils.GetTmpDir(t) 23 | 24 | cmd := fmt.Sprintf("build --sourcedir %v --targetdir %v", templateDir, wd) 25 | stdout, stderr, exitCode := testutils.RunAppCommand(cmd, "") 26 | 27 | expectedOutputFilePath := filepath.Join(wd, fmt.Sprintf("%v.tar.gz", templateName)) 28 | 29 | assert.Contains(t, stdout, fmt.Sprintf("Packaged template output to %v", expectedOutputFilePath)) 30 | assert.Equal(t, "", stderr) 31 | assert.Equal(t, 0, exitCode) 32 | assert.FileExists(t, expectedOutputFilePath) 33 | } 34 | 35 | func Test_PctBuild_With_NoTargetDir_Outputs_TarGz(t *testing.T) { 36 | testutils.SkipAcceptanceTest(t) 37 | testutils.SetAppName(APP) 38 | 39 | templateName := "good-project" 40 | 41 | sourceDir, _ := filepath.Abs("../../acceptance/build/testdata") 42 | templateDir := filepath.Join(sourceDir, templateName) 43 | wd := testutils.GetTmpDir(t) 44 | 45 | cmd := fmt.Sprintf("build --sourcedir %v", templateDir) 46 | stdout, stderr, exitCode := testutils.RunAppCommand(cmd, wd) 47 | 48 | expectedOutputFilePath := filepath.Join(wd, "pkg", fmt.Sprintf("%v.tar.gz", templateName)) 49 | 50 | assert.Contains(t, stdout, fmt.Sprintf("Packaged template output to %v", expectedOutputFilePath)) 51 | assert.Equal(t, "", stderr) 52 | assert.Equal(t, 0, exitCode) 53 | assert.FileExists(t, expectedOutputFilePath) 54 | } 55 | 56 | func Test_PctBuild_With_EmptySourceDir_Errors(t *testing.T) { 57 | testutils.SkipAcceptanceTest(t) 58 | testutils.SetAppName(APP) 59 | 60 | templateName := "no-project-here" 61 | 62 | sourceDir, _ := filepath.Abs("../../acceptance/build/testdata") 63 | templateDir := filepath.Join(sourceDir, templateName) 64 | 65 | cmd := fmt.Sprintf("build --sourcedir %v", templateDir) 66 | stdout, stderr, exitCode := testutils.RunAppCommand(cmd, "") 67 | 68 | assert.Contains(t, stdout, fmt.Sprintf("No project directory at %v", templateDir)) 69 | assert.Equal(t, "exit status 1", stderr) 70 | assert.Equal(t, 1, exitCode) 71 | } 72 | 73 | func Test_PctBuild_With_NoPctConfig_Errors(t *testing.T) { 74 | testutils.SkipAcceptanceTest(t) 75 | testutils.SetAppName(APP) 76 | 77 | templateName := "no-pct-config-project" 78 | 79 | sourceDir, _ := filepath.Abs("../../acceptance/build/testdata") 80 | templateDir := filepath.Join(sourceDir, templateName) 81 | 82 | cmd := fmt.Sprintf("build --sourcedir %v", templateDir) 83 | stdout, stderr, exitCode := testutils.RunAppCommand(cmd, "") 84 | 85 | assert.Contains(t, stdout, fmt.Sprintf("No 'pct-config.yml' found in %v", templateDir)) 86 | assert.Equal(t, "exit status 1", stderr) 87 | assert.Equal(t, 1, exitCode) 88 | } 89 | 90 | func Test_PctBuild_With_NoContentDir_Errors(t *testing.T) { 91 | testutils.SkipAcceptanceTest(t) 92 | testutils.SetAppName(APP) 93 | 94 | templateName := "no-content-dir-project" 95 | 96 | sourceDir, _ := filepath.Abs("../../acceptance/build/testdata") 97 | templateDir := filepath.Join(sourceDir, templateName) 98 | 99 | cmd := fmt.Sprintf("build --sourcedir %v", templateDir) 100 | stdout, stderr, exitCode := testutils.RunAppCommand(cmd, "") 101 | 102 | assert.Contains(t, stdout, fmt.Sprintf("No 'content' dir found in %v", templateDir)) 103 | assert.Equal(t, "exit status 1", stderr) 104 | assert.Equal(t, 1, exitCode) 105 | } 106 | -------------------------------------------------------------------------------- /acceptance/build/testdata/good-project/content/empty.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs-toy-chest/pct/a56eff24b5cf6a2be33c6707bb90e8d06e620a05/acceptance/build/testdata/good-project/content/empty.txt -------------------------------------------------------------------------------- /acceptance/build/testdata/good-project/content/goodfile.txt.tmpl: -------------------------------------------------------------------------------- 1 | This is {{.example_data}} data -------------------------------------------------------------------------------- /acceptance/build/testdata/good-project/pct-config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | template: 3 | id: good-project 4 | author: puppetlabs 5 | type: project 6 | display: Good Project 7 | version: 0.1.0 8 | url: https://github.com/puppetlabs/pct-good-project 9 | 10 | puppet_module: 11 | # author: "james" 12 | license: "Apache-2.0" 13 | version: "0.1.0" 14 | summary: "A New Puppet Module" 15 | -------------------------------------------------------------------------------- /acceptance/build/testdata/no-content-dir-project/pct-config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | template: 3 | id: good-project 4 | author: puppetlabs 5 | type: project 6 | display: Good Project 7 | version: 0.1.0 8 | url: https://github.com/puppetlabs/pct-good-project 9 | 10 | puppet_module: 11 | # author: "james" 12 | license: "Apache-2.0" 13 | version: "0.1.0" 14 | summary: "A New Puppet Module" 15 | -------------------------------------------------------------------------------- /acceptance/build/testdata/no-pct-config-project/content/empty.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs-toy-chest/pct/a56eff24b5cf6a2be33c6707bb90e8d06e620a05/acceptance/build/testdata/no-pct-config-project/content/empty.txt -------------------------------------------------------------------------------- /acceptance/install/testdata/additional-project.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs-toy-chest/pct/a56eff24b5cf6a2be33c6707bb90e8d06e620a05/acceptance/install/testdata/additional-project.tar.gz -------------------------------------------------------------------------------- /acceptance/install/testdata/good-project.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs-toy-chest/pct/a56eff24b5cf6a2be33c6707bb90e8d06e620a05/acceptance/install/testdata/good-project.tar.gz -------------------------------------------------------------------------------- /acceptance/install/testdata/invalid-gz-project.tar.gz: -------------------------------------------------------------------------------- 1 | This is not a valid tar.gz 2 | -------------------------------------------------------------------------------- /acceptance/install/testdata/invalid-tar-project.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs-toy-chest/pct/a56eff24b5cf6a2be33c6707bb90e8d06e620a05/acceptance/install/testdata/invalid-tar-project.tar.gz -------------------------------------------------------------------------------- /acceptance/new/new_test.go: -------------------------------------------------------------------------------- 1 | package new_test 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "os" 7 | "path/filepath" 8 | "testing" 9 | 10 | "github.com/puppetlabs/pct/acceptance/testutils" 11 | "github.com/rs/zerolog" 12 | "github.com/rs/zerolog/log" 13 | "gopkg.in/yaml.v2" 14 | 15 | "github.com/stretchr/testify/assert" 16 | ) 17 | 18 | const APP = "pct" 19 | 20 | var templatePath string 21 | 22 | func TestMain(m *testing.M) { 23 | log.Logger = zerolog.New(ioutil.Discard).With().Timestamp().Logger() 24 | 25 | templatePath, _ = filepath.Abs("../../internal/pkg/pct/testdata/examples") 26 | 27 | os.Exit(m.Run()) 28 | } 29 | 30 | func TestPctNew(t *testing.T) { 31 | testutils.SkipAcceptanceTest(t) 32 | testutils.SetAppName(APP) 33 | 34 | stdout, stderr, exitCode := testutils.RunAppCommand("new", "") 35 | assert.Contains(t, stdout, "DISPLAYNAME") 36 | assert.Contains(t, stdout, "AUTHOR") 37 | assert.Contains(t, stdout, "NAME") 38 | assert.Contains(t, stdout, "TYPE") 39 | assert.Equal(t, "", stderr) 40 | assert.Equal(t, 0, exitCode) 41 | } 42 | 43 | func TestPctNewUnknownTag(t *testing.T) { 44 | testutils.SkipAcceptanceTest(t) 45 | testutils.SetAppName(APP) 46 | 47 | stdout, stderr, exitCode := testutils.RunAppCommand("new --foo", "") 48 | assert.Contains(t, stdout, "unknown flag: --foo") 49 | assert.Equal(t, "exit status 1", stderr) 50 | assert.Equal(t, 1, exitCode) 51 | } 52 | 53 | func TestPctNewTemplatePath(t *testing.T) { 54 | testutils.SkipAcceptanceTest(t) 55 | testutils.SetAppName(APP) 56 | 57 | stdout, stderr, exitCode := testutils.RunAppCommand("new --templatepath "+templatePath, "") 58 | assert.Contains(t, stdout, "DISPLAYNAME") 59 | assert.Contains(t, stdout, "NAME") 60 | assert.Contains(t, stdout, "TYPE") 61 | assert.Contains(t, stdout, "full-project") 62 | assert.Equal(t, "", stderr) 63 | assert.Equal(t, 0, exitCode) 64 | } 65 | 66 | func TestPctNewUnknownTemplate(t *testing.T) { 67 | testutils.SkipAcceptanceTest(t) 68 | testutils.SetAppName(APP) 69 | 70 | stdout, stderr, exitCode := testutils.RunAppCommand("new foo/bar", "") 71 | assert.Contains(t, stdout, "Error: Couldn't find an installed template that matches 'foo/bar'") 72 | assert.Equal(t, "exit status 1", stderr) 73 | assert.Equal(t, 1, exitCode) 74 | } 75 | 76 | func TestPctNewAuthorNoId(t *testing.T) { 77 | testutils.SkipAcceptanceTest(t) 78 | testutils.SetAppName(APP) 79 | 80 | stdout, stderr, exitCode := testutils.RunAppCommand("new puppetlabs", "") 81 | assert.Contains(t, stdout, "Error: Selected template must be in AUTHOR/ID format") 82 | assert.Equal(t, "exit status 1", stderr) 83 | assert.Equal(t, 1, exitCode) 84 | } 85 | 86 | func TestPctNewKnownTemplate(t *testing.T) { 87 | testutils.SkipAcceptanceTest(t) 88 | testutils.SetAppName(APP) 89 | 90 | stdout, stderr, exitCode := testutils.RunAppCommand("new puppetlabs/full-project --templatepath "+templatePath, os.TempDir()) 91 | assert.Contains(t, stdout, "Deployed:") 92 | assert.Equal(t, "", stderr) 93 | assert.Equal(t, 0, exitCode) 94 | } 95 | 96 | func TestPctNewInfo(t *testing.T) { 97 | testutils.SkipAcceptanceTest(t) 98 | testutils.SetAppName(APP) 99 | 100 | stdout, stderr, exitCode := testutils.RunAppCommand("new --info puppetlabs/full-project --templatepath "+templatePath, os.TempDir()) 101 | 102 | expectedYaml := `puppet_module: 103 | license: Apache-2.0 104 | summary: A New Puppet Module 105 | version: 0.1.0` 106 | 107 | var output map[string]interface{} 108 | var expected map[string]interface{} 109 | 110 | err := yaml.Unmarshal([]byte(stdout), &output) 111 | if err != nil { 112 | assert.Fail(t, "returned data is not YAML") 113 | } 114 | 115 | err = yaml.Unmarshal([]byte(expectedYaml), &expected) 116 | if err != nil { 117 | assert.Fail(t, "expected data is not YAML") 118 | } 119 | 120 | assert.Equal(t, expected, output) 121 | assert.Equal(t, "", stderr) 122 | assert.Equal(t, 0, exitCode) 123 | } 124 | 125 | func TestPctNewInfoJson(t *testing.T) { 126 | testutils.SkipAcceptanceTest(t) 127 | testutils.SetAppName(APP) 128 | 129 | stdout, stderr, exitCode := testutils.RunAppCommand("new --info puppetlabs/full-project --format json --templatepath "+templatePath, os.TempDir()) 130 | 131 | expectedJson := `{ 132 | "puppet_module": { 133 | "license": "Apache-2.0", 134 | "version": "0.1.0", 135 | "summary": "A New Puppet Module" 136 | } 137 | }` 138 | 139 | var output map[string]interface{} 140 | var expected map[string]interface{} 141 | 142 | err := json.Unmarshal([]byte(stdout), &output) 143 | if err != nil { 144 | assert.Fail(t, "returned data is not JSON") 145 | } 146 | 147 | err = json.Unmarshal([]byte(expectedJson), &expected) 148 | if err != nil { 149 | assert.Fail(t, "expected data is not JSON") 150 | } 151 | 152 | assert.Equal(t, expected, output) 153 | assert.Equal(t, "", stderr) 154 | assert.Equal(t, 0, exitCode) 155 | } 156 | -------------------------------------------------------------------------------- /acceptance/testutils/testutils.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "path/filepath" 8 | "runtime" 9 | "strings" 10 | "syscall" 11 | "testing" 12 | 13 | "github.com/rs/zerolog/log" 14 | ) 15 | 16 | var app string 17 | 18 | func SetAppName(appName string) { 19 | app = appName 20 | } 21 | 22 | func SkipAcceptanceTest(t *testing.T) { 23 | if _, present := os.LookupEnv("TEST_ACCEPTANCE"); !present { 24 | t.Skip("Skipping, Acceptance test") 25 | } 26 | } 27 | 28 | // Run Command takes a command to execute and the directory in which to execute the command. 29 | // if wd is and empty string it will default to the current working directory 30 | func RunCommand(cmdString string, wd string) (stdout string, stderr string, exitCode int) { 31 | cmds := strings.Split(cmdString, " ") 32 | 33 | cmds = toolArgsAsSingleArg(cmds) // Remove when GH-52 is resolved 34 | 35 | cmd := exec.Command(cmds[0], cmds[1:]...) // #nosec // used only for testing 36 | if wd != "" { 37 | cmd.Dir = wd 38 | } 39 | out, err := cmd.CombinedOutput() 40 | exitCode = 0 41 | 42 | if err != nil { 43 | stderr = err.Error() 44 | // todo: double check that error statuss work on Windows 45 | if msg, ok := err.(*exec.ExitError); ok { // there is error code 46 | exitCode = msg.Sys().(syscall.WaitStatus).ExitStatus() 47 | } 48 | } 49 | 50 | stdout = string(out) 51 | 52 | return stdout, stderr, exitCode 53 | } 54 | 55 | // Wraps RunCommand for App calls, locating the correct binary for the executing OS 56 | func RunAppCommand(cmdString string, wd string) (stdout string, stderr string, exitCode int) { 57 | // where is app built for this current OS? 58 | postfix := "" 59 | if runtime.GOOS == "windows" { 60 | postfix = ".exe" 61 | } 62 | 63 | goArch := runtime.GOARCH 64 | if goArch == "amd64" { 65 | goArch = fmt.Sprintf("%s_v1", runtime.GOARCH) 66 | } 67 | 68 | appPath := fmt.Sprintf("../../dist/%s_%s_%s/%s%s", app, runtime.GOOS, goArch, app, postfix) 69 | absPath, err := filepath.Abs(appPath) 70 | 71 | if err != nil { 72 | log.Error().Msgf("Unable to run create path for %s: %s", appPath, err.Error()) 73 | } 74 | 75 | log.Debug().Msgf("Testing Command: %s %s", app, cmdString) 76 | 77 | executeString := fmt.Sprintf("%s %s", absPath, cmdString) 78 | 79 | return RunCommand(executeString, wd) 80 | } 81 | 82 | // On macOS systems, the `TempDir` func in the `testing` package will 83 | // potentially return the symlink to the dir, rather than the actual 84 | // path (`/private/folders/...` vs `/var/private/folders/...`). 85 | func GetTmpDir(t *testing.T) string { 86 | dirName := t.TempDir() 87 | tmpDir, err := filepath.EvalSymlinks(dirName) 88 | 89 | if err != nil { 90 | panic("Could not create temp dir for test") 91 | } 92 | 93 | return tmpDir 94 | } 95 | 96 | // Remove when GH-52 is resolved 97 | // This function assumes that '--toolArgs' is the final argument passed to the app 98 | func toolArgsAsSingleArg(cmds []string) []string { 99 | toolArgsFragmentIndex := 0 100 | var reassembled strings.Builder 101 | 102 | for i, arg := range cmds { 103 | if strings.HasPrefix(arg, "--toolArgs=") { 104 | toolArgsFragmentIndex = i 105 | } 106 | if toolArgsFragmentIndex > 0 { 107 | reassembled.WriteString(fmt.Sprintf("%s ", arg)) 108 | } 109 | } 110 | 111 | if toolArgsFragmentIndex > 0 { 112 | var cmdAndArgs []string 113 | cmdAndArgs = append(cmdAndArgs, cmds[0]) 114 | cmdAndArgs = append(cmdAndArgs, cmds[1:toolArgsFragmentIndex]...) 115 | cmdAndArgs = append(cmdAndArgs, reassembled.String()) 116 | return cmdAndArgs 117 | } 118 | 119 | return cmds 120 | } 121 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | 3 | [CmdletBinding()] 4 | param ( 5 | [Parameter()] 6 | [ValidateSet('build', 'quick', 'package')] 7 | [string] 8 | $Target = 'build' 9 | ) 10 | $Env:WORKINGDIR = $PSScriptRoot 11 | 12 | $arch = go env GOHOSTARCH 13 | $platform = go env GOHOSTOS 14 | $binPath = Join-Path $PSScriptRoot "dist" "pct_${platform}_${arch}" 15 | $binPath2 = Join-Path $PSScriptRoot "dist" "notel_pct_${platform}_${arch}" 16 | 17 | $amd64 = go env GOAMD64 18 | if ($amd64) { 19 | $binPath = "${binPath}_${amd64}" 20 | $binPath2 = "${binPath2}_${amd64}" 21 | } 22 | 23 | switch ($Target) { 24 | 'build' { 25 | # Set goreleaser to build for current platform only 26 | # Add environment variables for honeycomb if not already loaded 27 | if (!(Test-Path ENV:\HONEYCOMB_API_KEY)) { 28 | $ENV:HONEYCOMB_API_KEY = 'not_set' 29 | } 30 | if (!(Test-Path ENV:\HONEYCOMB_DATASET)) { 31 | $ENV:HONEYCOMB_DATASET = 'not_set' 32 | } 33 | goreleaser build --snapshot --rm-dist --single-target 34 | git clone -b main --depth 1 --single-branch https://github.com/puppetlabs/baker-round (Join-Path $binPath "templates") 35 | Get-ChildItem -Path (Join-Path $binPath "templates") 36 | Copy-Item (Join-Path $binPath "templates") -Destination (Join-Path $binPath2 "templates") -Recurse 37 | } 38 | 'quick' { 39 | If ($Env:OS -match '^Windows') { 40 | go build -o "$binPath/pct.exe" -tags telemetry 41 | go build -o "$binPath2/pct.exe" 42 | } else { 43 | go build -o "$binPath/pct" -tags telemetry 44 | go build -o "$binPath2/pct" 45 | } 46 | } 47 | 'package' { 48 | git clone -b main --depth 1 --single-branch https://github.com/puppetlabs/baker-round "templates" 49 | goreleaser --skip-publish --snapshot --rm-dist 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export WORKINGDIR=$(pwd) 4 | 5 | target=${1:-build} 6 | arch=$(go env GOHOSTARCH) 7 | platform=$(go env GOHOSTOS) 8 | binPath="$(pwd)/dist/pct_${platform}_${arch}" 9 | binPath2="$(pwd)/dist/notel_pct_${platform}_${arch}" 10 | 11 | amd64=$(go env GOAMD64) 12 | if [[ ! -z "${amd64+z}" ]]; then 13 | binPath="${binPath}_${amd64}" 14 | binPath2="${binPath2}_${amd64}" 15 | fi 16 | 17 | if [ "$target" == "build" ]; then 18 | # Set goreleaser to build for current platform only 19 | if [ -z "${HONEYCOMB_API_KEY}" ]; then 20 | export HONEYCOMB_API_KEY="not_set" 21 | fi 22 | if [ -z "${HONEYCOMB_DATASET}" ]; then 23 | export HONEYCOMB_DATASET="not_set" 24 | fi 25 | goreleaser build --snapshot --rm-dist --single-target 26 | git clone -b main --depth 1 --single-branch https://github.com/puppetlabs/baker-round "$binPath/templates" 27 | cp -r "$binPath/templates" "$binPath2/templates" 28 | elif [ "$target" == "quick" ]; then 29 | go build -o ${binPath}/pct -tags telemetry 30 | go build -o ${binPath2}/pct 31 | elif [ "$target" == "package" ]; then 32 | git clone -b main --depth 1 --single-branch https://github.com/puppetlabs/baker-round "templates" 33 | goreleaser --skip-publish --snapshot --rm-dist 34 | fi 35 | -------------------------------------------------------------------------------- /cmd/build/build.go: -------------------------------------------------------------------------------- 1 | package build 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/puppetlabs/pct/pkg/build" 9 | "github.com/rs/zerolog/log" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | type BuildCommand struct { 14 | SourceDir string 15 | TargetDir string 16 | ProjectType string 17 | Builder build.BuilderI 18 | } 19 | 20 | type BuildCommandI interface { 21 | CreateCommand() *cobra.Command 22 | } 23 | 24 | func (bc *BuildCommand) CreateCommand() *cobra.Command { 25 | tmp := &cobra.Command{ 26 | Use: "build [flags]", 27 | Short: fmt.Sprintf("Builds a package from the %s project", bc.ProjectType), 28 | Long: fmt.Sprintf("Builds a package from the %s project. Assumes the current working directory is the template you wish to package", bc.ProjectType), 29 | PreRunE: bc.preExecute, 30 | RunE: bc.execute, 31 | } 32 | 33 | tmp.Flags().StringVar(&bc.SourceDir, "sourcedir", "", fmt.Sprintf("The %s project directory you wish to package up", bc.ProjectType)) 34 | tmp.Flags().StringVar(&bc.TargetDir, "targetdir", "", fmt.Sprintf("The target directory where you want the packaged %s project to be output to", bc.ProjectType)) 35 | 36 | return tmp 37 | } 38 | 39 | func (bc *BuildCommand) preExecute(cmd *cobra.Command, args []string) error { 40 | 41 | wd, err := os.Getwd() 42 | log.Trace().Msgf("WD: %v", wd) 43 | 44 | if (bc.SourceDir == "" || bc.TargetDir == "") && err != nil { 45 | return err 46 | } 47 | 48 | if bc.SourceDir == "" { 49 | bc.SourceDir = wd 50 | } 51 | 52 | bc.SourceDir = filepath.Clean(bc.SourceDir) 53 | 54 | if bc.TargetDir == "" { 55 | bc.TargetDir = filepath.Join(wd, "pkg") 56 | } 57 | 58 | return nil 59 | } 60 | 61 | func (bc *BuildCommand) execute(cmd *cobra.Command, args []string) error { 62 | gzipArchiveFilePath, err := bc.Builder.Build(bc.SourceDir, bc.TargetDir) 63 | 64 | if err != nil { 65 | return fmt.Errorf("`sourcedir` is not a valid %s project: %s", bc.ProjectType, err.Error()) 66 | } 67 | log.Info().Msgf("Packaged %s output to %v", bc.ProjectType, gzipArchiveFilePath) 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /cmd/build/build_test.go: -------------------------------------------------------------------------------- 1 | package build_test 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "os" 7 | "path/filepath" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | 12 | "github.com/puppetlabs/pct/cmd/build" 13 | "github.com/puppetlabs/pct/pkg/mock" 14 | ) 15 | 16 | func TestCreateBuildCommand(t *testing.T) { 17 | wd, _ := os.Getwd() 18 | defaultSourceDir := wd 19 | defaultTargetDir := filepath.Join(wd, "pkg") 20 | 21 | tests := []struct { 22 | name string 23 | args []string 24 | expectedSourceDir string 25 | expectedTargetDir string 26 | expectedErrorMatch string 27 | }{ 28 | { 29 | name: "executes without error when no flags passed", 30 | args: []string{}, 31 | expectedSourceDir: defaultSourceDir, 32 | expectedTargetDir: defaultTargetDir, 33 | }, 34 | { 35 | name: "executes with error for invalid flag", 36 | args: []string{"--foo"}, 37 | expectedErrorMatch: "unknown flag: --foo", 38 | }, 39 | { 40 | name: "uses sourcedir, targetdir when passed in", 41 | args: []string{"--sourcedir", "/path/to/template", "--targetdir", "/path/to/output"}, 42 | expectedSourceDir: filepath.Clean("/path/to/template"), 43 | expectedTargetDir: "/path/to/output", 44 | }, 45 | { 46 | name: "Sets correct defaults if sourcedir and targetdir undefined", 47 | args: []string{}, 48 | expectedSourceDir: defaultSourceDir, 49 | expectedTargetDir: defaultTargetDir, 50 | }, 51 | { 52 | name: "Cleans sourcedir of initial ./", 53 | args: []string{"--sourcedir", "./path/to/template"}, 54 | expectedSourceDir: filepath.Clean("path/to/template"), 55 | expectedTargetDir: defaultTargetDir, 56 | }, 57 | } 58 | for _, tt := range tests { 59 | t.Run(tt.name, func(t *testing.T) { 60 | cmd := build.BuildCommand{ 61 | ProjectType: "template", 62 | Builder: &mock.Builder{ 63 | ProjectName: "my-project", 64 | ExpectedSourceDir: tt.expectedSourceDir, 65 | ExpectedTargetDir: tt.expectedTargetDir, 66 | }, 67 | } 68 | buildCmd := cmd.CreateCommand() 69 | 70 | b := bytes.NewBufferString("") 71 | buildCmd.SetOut(b) 72 | buildCmd.SetErr(b) 73 | 74 | buildCmd.SetArgs(tt.args) 75 | err := buildCmd.Execute() 76 | 77 | if err != nil { 78 | if tt.expectedErrorMatch == "" { 79 | t.Errorf("Unexpected error when none wanted: %v", err) 80 | return 81 | } else { 82 | out, _ := ioutil.ReadAll(b) 83 | assert.Regexp(t, tt.expectedErrorMatch, string(out)) 84 | } 85 | } else if tt.expectedErrorMatch != "" { 86 | t.Errorf("Expected error '%s'but none raised", err) 87 | } 88 | }) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /cmd/completion/completion.go: -------------------------------------------------------------------------------- 1 | package completion 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/puppetlabs/pct/pkg/telemetry" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | func CreateCompletionCommand() *cobra.Command { 12 | tmp := &cobra.Command{ 13 | Use: "completion", 14 | Short: "Generate shell completions for the chosen shell", 15 | Long: `To load completions: 16 | 17 | Bash: 18 | 19 | $ source <(pct completion bash) 20 | 21 | # To load completions for each session, execute once: 22 | # Linux: 23 | $ pct completion bash > /etc/bash_completion.d/pct 24 | # macOS: 25 | $ pct completion bash > /usr/local/etc/bash_completion.d/pct 26 | 27 | Zsh: 28 | 29 | # If shell completion is not already enabled in your environment, 30 | # you will need to enable it. You can execute the following once: 31 | 32 | $ echo "autoload -U compinit; compinit" >> ~/.zshrc 33 | 34 | # To load completions for each session, execute once: 35 | $ pct completion zsh > "${fpath[1]}/_pct" 36 | 37 | # You will need to start a new shell for this setup to take effect. 38 | 39 | fish: 40 | 41 | $ pct completion fish | source 42 | 43 | # To load completions for each session, execute once: 44 | $ pct completion fish > ~/.config/fish/completions/pct.fish 45 | 46 | PowerShell: 47 | 48 | PS> pct completion powershell | Out-String | Invoke-Expression 49 | 50 | # To load completions for every new session, run: 51 | PS> pct completion powershell > pct.ps1 52 | # and source this file from your PowerShell profile.`, 53 | ValidArgs: []string{"bash", "fish", "pwsh", "zsh"}, 54 | Args: cobra.ExactValidArgs(1), 55 | Run: func(cmd *cobra.Command, args []string) { 56 | _, span := telemetry.NewSpan(cmd.Context(), "completion") 57 | defer telemetry.EndSpan(span) 58 | telemetry.AddStringSpanAttribute(span, "name", "completion") 59 | 60 | var err error 61 | switch args[0] { 62 | case "bash": 63 | err = cmd.Root().GenBashCompletion(os.Stdout) 64 | case "fish": 65 | err = cmd.Root().GenFishCompletion(os.Stdout, true) 66 | case "pwsh": 67 | err = cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout) 68 | case "zsh": 69 | err = cmd.Root().GenZshCompletion(os.Stdout) 70 | default: 71 | log.Printf("unsupported shell type %q", args[0]) 72 | } 73 | 74 | if err != nil { 75 | log.Fatal(err) 76 | } 77 | }, 78 | } 79 | return tmp 80 | } 81 | -------------------------------------------------------------------------------- /cmd/explain/explain.go: -------------------------------------------------------------------------------- 1 | package explain 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/puppetlabs/pct/docs/md" 7 | "github.com/puppetlabs/pct/pkg/docs" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var ( 12 | docsApi *docs.Docs 13 | listTopics bool 14 | format string 15 | tag string 16 | category string 17 | topic string 18 | // Possibly implement later to enable context aware filtering for tags/categories 19 | // depending on which is filtered first? 20 | // filteredDocs []docs.MarkdownDoc 21 | ) 22 | 23 | func CreateCommand() *cobra.Command { 24 | cmd := &cobra.Command{ 25 | Use: "explain", 26 | Short: "Present documentation about topics", 27 | Long: "Present documentation about various topics, including...", 28 | Args: validateArgCount, 29 | ValidArgsFunction: flagCompletion, 30 | PreRun: preExecute, 31 | RunE: execute, 32 | } 33 | 34 | dfs := md.GetDocsFS() 35 | docsApi = &docs.Docs{ 36 | DocsFileSystem: &dfs, 37 | } 38 | 39 | cmd.Flags().SortFlags = false 40 | cmd.Flags().BoolVarP(&listTopics, "list", "l", false, "list available topics") 41 | 42 | cmd.Flags().StringVarP(&format, "format", "f", "human", "display output in human or json format") 43 | err := cmd.RegisterFlagCompletionFunc("format", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 44 | return []string{"table", "json"}, cobra.ShellCompDirectiveNoFileComp 45 | }) 46 | cobra.CheckErr(err) 47 | 48 | cmd.Flags().StringVarP(&tag, "tag", "t", "", "filter available topics by tag") 49 | err = cmd.RegisterFlagCompletionFunc("tag", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 50 | if docsApi.ParsedDocsCache == nil { 51 | preExecute(cmd, args) 52 | } 53 | return docsApi.ListTags(docsApi.ParsedDocsCache), cobra.ShellCompDirectiveNoFileComp 54 | }) 55 | cobra.CheckErr(err) 56 | 57 | cmd.Flags().StringVarP(&category, "category", "c", "", "filter available topics by category") 58 | err = cmd.RegisterFlagCompletionFunc("category", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 59 | if docsApi.ParsedDocsCache == nil { 60 | preExecute(cmd, args) 61 | } 62 | return docsApi.ListCategories(docsApi.ParsedDocsCache), cobra.ShellCompDirectiveNoFileComp 63 | }) 64 | cobra.CheckErr(err) 65 | 66 | return cmd 67 | } 68 | 69 | func preExecute(cmd *cobra.Command, args []string) { 70 | docsApi.FindAndParse("content") 71 | } 72 | 73 | func validateArgCount(cmd *cobra.Command, args []string) error { 74 | // show available topics if user runs `pct explain` 75 | if len(args) == 0 && !listTopics { 76 | listTopics = true 77 | } 78 | 79 | if len(args) == 1 { 80 | if category != "" || tag != "" { 81 | return fmt.Errorf("Specify a topic *or* search by tag/category") 82 | } 83 | topic = args[0] 84 | } else if len(args) > 1 { 85 | return fmt.Errorf("Specify only one topic to explain") 86 | } 87 | 88 | return nil 89 | } 90 | 91 | func flagCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 92 | if docsApi.ParsedDocsCache == nil { 93 | preExecute(cmd, args) 94 | } 95 | if len(args) != 0 { 96 | return nil, cobra.ShellCompDirectiveNoFileComp 97 | } 98 | 99 | return docsApi.CompleteTitle(docsApi.ParsedDocsCache, toComplete), cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp 100 | } 101 | 102 | func execute(cmd *cobra.Command, args []string) error { 103 | docs := docsApi.ParsedDocsCache 104 | if listTopics { 105 | if format == "" { 106 | format = "table" 107 | } 108 | if category != "" { 109 | docs = docsApi.FilterByCategory(category, docs) 110 | } 111 | if tag != "" { 112 | docs = docsApi.FilterByTag(tag, docs) 113 | } 114 | // If there's only one match, should we render the matching doc? 115 | docsApi.FormatFrontMatter(format, docs) 116 | } else if topic != "" { 117 | doc, err := docsApi.SelectDocument(topic, docsApi.ParsedDocsCache) 118 | if err != nil { 119 | return err 120 | } 121 | output, err := docsApi.RenderDocument(doc) 122 | if err != nil { 123 | return err 124 | } 125 | fmt.Print(output) 126 | // If --online, open in browser and do not display 127 | // Should we have a --scroll mode? 128 | } 129 | return nil 130 | } 131 | -------------------------------------------------------------------------------- /cmd/install/install.go: -------------------------------------------------------------------------------- 1 | package install 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/afero" 7 | 8 | "github.com/puppetlabs/pct/pkg/install" 9 | "github.com/puppetlabs/pct/pkg/telemetry" 10 | "github.com/puppetlabs/pct/pkg/utils" 11 | "github.com/rs/zerolog/log" 12 | "github.com/spf13/cobra" 13 | "github.com/spf13/viper" 14 | ) 15 | 16 | type InstallCommand struct { 17 | TemplatePkgPath string 18 | InstallPath string 19 | Force bool 20 | PctInstaller install.InstallerI 21 | GitUri string 22 | AFS *afero.Afero 23 | } 24 | 25 | type InstallCommandI interface { 26 | CreateCommand() *cobra.Command 27 | } 28 | 29 | func (ic *InstallCommand) CreateCommand() *cobra.Command { 30 | tmp := &cobra.Command{ 31 | Use: "install [flags]", 32 | Short: "Installs a template package (in tar.gz format)", 33 | Long: `Installs a template package (in tar.gz format) to the default or specified template path`, 34 | PreRunE: ic.preExecute, 35 | RunE: ic.executeInstall, 36 | } 37 | tmp.Flags().StringVar(&ic.InstallPath, "templatepath", "", "location of installed templates") 38 | err := viper.BindPFlag("templatepath", tmp.Flags().Lookup("templatepath")) 39 | tmp.Flags().BoolVarP(&ic.Force, "force", "f", false, "Forces the install of a template without error, if it already exists. ") 40 | tmp.Flags().StringVar(&ic.GitUri, "git-uri", "", "Installs a template package from a remote git repository.") 41 | 42 | cobra.CheckErr(err) 43 | 44 | return tmp 45 | } 46 | 47 | func (ic *InstallCommand) executeInstall(cmd *cobra.Command, args []string) error { 48 | _, span := telemetry.NewSpan(cmd.Context(), "install") 49 | defer telemetry.EndSpan(span) 50 | telemetry.AddStringSpanAttribute(span, "name", "install") 51 | 52 | templateInstallationPath := "" 53 | var err error = nil 54 | if ic.GitUri != "" { // For cloning a template 55 | // Create temp folder 56 | tempDir, dirErr := ic.AFS.TempDir("", "") 57 | defer func() { 58 | dirErr := ic.AFS.Remove(tempDir) 59 | if dirErr != nil { 60 | log.Error().Msgf("Failed to remove temp dir: %v", dirErr) 61 | } 62 | }() 63 | if dirErr != nil { 64 | return fmt.Errorf("Could not create tempdir to clone template to: %v", err) 65 | } 66 | templateInstallationPath, err = ic.PctInstaller.InstallClone(ic.GitUri, ic.InstallPath, tempDir, ic.Force) 67 | } else { // For downloading and/or locally installing a template 68 | templateInstallationPath, err = ic.PctInstaller.Install(ic.TemplatePkgPath, ic.InstallPath, ic.Force) 69 | } 70 | 71 | if err != nil { 72 | return err 73 | } 74 | log.Info().Msgf("Template installed to %v", templateInstallationPath) 75 | return nil 76 | } 77 | 78 | func (ic *InstallCommand) setInstallPath() error { 79 | if ic.InstallPath == "" { 80 | ic.InstallPath = viper.GetString("templatepath") 81 | if ic.InstallPath == "" { 82 | defaultTemplatePath, err := utils.GetDefaultTemplatePath() 83 | if err != nil { 84 | return fmt.Errorf("Could not determine location to install template: %v", err) 85 | } 86 | ic.InstallPath = defaultTemplatePath 87 | } 88 | } 89 | return nil 90 | } 91 | 92 | func (ic *InstallCommand) preExecute(cmd *cobra.Command, args []string) error { 93 | if len(args) < 1 { 94 | if ic.GitUri != "" { 95 | return ic.setInstallPath() 96 | } 97 | return fmt.Errorf("Path to template package (tar.gz) should be first argument") 98 | } 99 | 100 | if len(args) == 1 { 101 | ic.TemplatePkgPath = args[0] 102 | return ic.setInstallPath() 103 | } 104 | 105 | if len(args) > 1 { 106 | return fmt.Errorf("Incorrect number of arguments; path to template package (tar.gz) should be first argument") 107 | } 108 | 109 | return nil 110 | } 111 | -------------------------------------------------------------------------------- /cmd/install/install_test.go: -------------------------------------------------------------------------------- 1 | package install_test 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "testing" 7 | 8 | "github.com/spf13/afero" 9 | 10 | "github.com/puppetlabs/pct/cmd/install" 11 | "github.com/puppetlabs/pct/pkg/mock" 12 | "github.com/spf13/viper" 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | func TestCreateinstallCommand(t *testing.T) { 17 | tests := []struct { 18 | name string 19 | args []string 20 | expectError bool 21 | expectedTemplatePkgPath string 22 | expectedTargetDir string 23 | viperTemplatePath string 24 | expectedOutput string 25 | expectedGitUri string 26 | }{ 27 | { 28 | name: "Should error when no args provided", 29 | args: []string{}, 30 | expectError: true, 31 | expectedOutput: "Path to template package (tar.gz) should be first argument", 32 | }, 33 | { 34 | name: "Should error when > 1 arg provided", 35 | args: []string{"first/arg", "second/undeed/arg"}, 36 | expectError: true, 37 | expectedOutput: "Incorrect number of arguments; path to template package (tar.gz) should be first argument", 38 | }, 39 | { 40 | name: "Sets TemplatePkgPath to passed arg and InstallPath to default template dir", 41 | args: []string{"/path/to/my-cool-template.tar.gz"}, 42 | expectError: false, 43 | expectedTemplatePkgPath: "/path/to/my-cool-template.tar.gz", 44 | expectedTargetDir: "/the/default/location/for/templates", 45 | viperTemplatePath: "/the/default/location/for/templates", 46 | }, 47 | { 48 | name: "Sets TemplatePkgPath and InstallPath to passed args", 49 | args: []string{"/path/to/my-cool-template.tar.gz", "--templatepath", "/a/new/place/for/templates"}, 50 | expectError: false, 51 | expectedTemplatePkgPath: "/path/to/my-cool-template.tar.gz", 52 | expectedTargetDir: "/a/new/place/for/templates", 53 | viperTemplatePath: "/the/default/location/for/templates", 54 | }, 55 | { 56 | name: "Sets GitUri to passed arg and InstallPath to default template dir", 57 | args: []string{"--git-uri", "https://github.com/puppetlabs/pct-test-template-01.git"}, 58 | viperTemplatePath: "/the/default/location/for/templates", 59 | expectError: false, 60 | expectedTargetDir: "/the/default/location/for/templates", 61 | expectedGitUri: "https://github.com/puppetlabs/pct-test-template-01.git", 62 | }, 63 | { 64 | name: "Sets GitUri and InstallPath to passed args", 65 | args: []string{"--git-uri", "https://github.com/puppetlabs/pct-test-template-01.git", "--templatepath", "/a/new/place/for/templates"}, 66 | viperTemplatePath: "/the/default/location/for/templates", 67 | expectError: false, 68 | expectedTargetDir: "/a/new/place/for/templates", 69 | expectedGitUri: "https://github.com/puppetlabs/pct-test-template-01.git", 70 | }, 71 | } 72 | 73 | for _, tt := range tests { 74 | t.Run(tt.name, func(t *testing.T) { 75 | fs := afero.NewMemMapFs() 76 | viper.SetDefault("templatepath", tt.viperTemplatePath) 77 | cmd := install.InstallCommand{ 78 | PctInstaller: &mock.PctInstaller{ 79 | ExpectedTemplatePkg: tt.expectedTemplatePkgPath, 80 | ExpectedTargetDir: tt.expectedTargetDir, 81 | ExpectedGitUri: tt.expectedGitUri, 82 | }, 83 | AFS: &afero.Afero{Fs: fs}, 84 | } 85 | installCmd := cmd.CreateCommand() 86 | 87 | b := bytes.NewBufferString("") 88 | installCmd.SetOutput(b) 89 | 90 | installCmd.SetArgs(tt.args) 91 | err := installCmd.Execute() 92 | 93 | if (err != nil) != tt.expectError { 94 | t.Errorf("executeTestUnit() error = %v, wantErr %v", err, tt.expectError) 95 | return 96 | } 97 | 98 | if (err != nil) && tt.expectError { 99 | out, _ := ioutil.ReadAll(b) 100 | assert.Contains(t, string(out), tt.expectedOutput) 101 | } 102 | 103 | }) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /cmd/new/new.go: -------------------------------------------------------------------------------- 1 | package new 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | "strings" 7 | 8 | "github.com/puppetlabs/pct/internal/pkg/pct" 9 | "github.com/puppetlabs/pct/pkg/telemetry" 10 | "github.com/puppetlabs/pct/pkg/utils" 11 | 12 | "github.com/rs/zerolog/log" 13 | "github.com/spf13/afero" 14 | "github.com/spf13/cobra" 15 | "github.com/spf13/viper" 16 | ) 17 | 18 | var ( 19 | localTemplatePath string 20 | format string 21 | selectedTemplate string 22 | selectedTemplateDirPath string 23 | selectedTemplateInfo string 24 | listTemplates bool 25 | targetName string 26 | targetOutput string 27 | pctApi *pct.Pct 28 | cachedTemplates []pct.PuppetContentTemplate 29 | ) 30 | 31 | func CreateCommand() *cobra.Command { 32 | tmp := &cobra.Command{ 33 | Use: "new