├── .adr-dir ├── .dependency_decisions.yml ├── .fixtures.yml ├── .github ├── ISSUE_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── ci.yml │ ├── nightly.yml │ ├── release.yml │ ├── release_prep.yml │ ├── sync_private.yml │ ├── workflow-restarter-test.yml │ └── workflow-restarter.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── .rubocop_todo.yml ├── CHANGELOG.md ├── CODEOWNERS ├── CONTRIBUTING.md ├── Gemfile ├── LICENSE ├── NOTICE ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── docs ├── adr │ └── 0001-restart-pdk-nightly-on-acceptance-test-failures.md ├── customizing_module_config.md ├── pdk-workflow.png ├── pdk.ditamap ├── pdk.md ├── pdk_building_module_packages.md ├── pdk_converting_modules.md ├── pdk_creating_modules.md ├── pdk_install.md ├── pdk_known_issues.md ├── pdk_overview.md ├── pdk_reference.md ├── pdk_release_notes.md ├── pdk_testing.md ├── pdk_troubleshooting.md ├── pdk_updating_modules.md ├── pdk_upgrading.md └── release_notes_pdk.md ├── exe └── pdk ├── ext ├── PuppetDevelopmentKit │ ├── PuppetDevelopmentKit.psd1.erb │ └── PuppetDevelopmentKit.psm1 ├── PuppetDevelopmentKitBeta │ ├── PuppetDevelopmentKitBeta.psd1.erb │ └── PuppetDevelopmentKitBeta.psm1.erb ├── build_defaults.yml └── rubocop.rb ├── lib ├── pdk.rb └── pdk │ ├── answer_file.rb │ ├── bolt.rb │ ├── cli.rb │ ├── cli │ ├── build.rb │ ├── bundle.rb │ ├── console.rb │ ├── convert.rb │ ├── env.rb │ ├── errors.rb │ ├── exec.rb │ ├── exec │ │ ├── command.rb │ │ └── interactive_command.rb │ ├── exec_group.rb │ ├── get.rb │ ├── get │ │ └── config.rb │ ├── new.rb │ ├── new │ │ ├── class.rb │ │ ├── defined_type.rb │ │ ├── fact.rb │ │ ├── function.rb │ │ ├── module.rb │ │ ├── provider.rb │ │ ├── task.rb │ │ ├── test.rb │ │ └── transport.rb │ ├── release.rb │ ├── release │ │ ├── prep.rb │ │ └── publish.rb │ ├── remove.rb │ ├── remove │ │ └── config.rb │ ├── set.rb │ ├── set │ │ └── config.rb │ ├── test.rb │ ├── test │ │ └── unit.rb │ ├── update.rb │ ├── util.rb │ ├── util │ │ ├── command_redirector.rb │ │ ├── interview.rb │ │ ├── option_normalizer.rb │ │ ├── option_validator.rb │ │ ├── spinner.rb │ │ └── update_manager_printer.rb │ └── validate.rb │ ├── config.rb │ ├── config │ ├── errors.rb │ ├── ini_file.rb │ ├── ini_file_setting.rb │ ├── json.rb │ ├── json_schema_namespace.rb │ ├── json_schema_setting.rb │ ├── json_with_schema.rb │ ├── namespace.rb │ ├── setting.rb │ ├── task_schema.json │ ├── validator.rb │ ├── yaml.rb │ └── yaml_with_schema.rb │ ├── context.rb │ ├── context │ ├── control_repo.rb │ ├── module.rb │ └── none.rb │ ├── control_repo.rb │ ├── generate.rb │ ├── generate │ ├── defined_type.rb │ ├── fact.rb │ ├── function.rb │ ├── module.rb │ ├── provider.rb │ ├── puppet_class.rb │ ├── puppet_object.rb │ ├── task.rb │ └── transport.rb │ ├── logger.rb │ ├── module.rb │ ├── module │ ├── convert.rb │ ├── metadata.rb │ ├── release.rb │ ├── update.rb │ └── update_manager.rb │ ├── report.rb │ ├── report │ └── event.rb │ ├── template.rb │ ├── template │ ├── fetcher.rb │ ├── fetcher │ │ ├── git.rb │ │ └── local.rb │ ├── renderer.rb │ ├── renderer │ │ ├── v1.rb │ │ └── v1 │ │ │ ├── legacy_template_dir.rb │ │ │ ├── renderer.rb │ │ │ └── template_file.rb │ └── template_dir.rb │ ├── tests │ └── unit.rb │ ├── util.rb │ ├── util │ ├── bundler.rb │ ├── changelog_generator.rb │ ├── env.rb │ ├── filesystem.rb │ ├── git.rb │ ├── json_finder.rb │ ├── puppet_strings.rb │ ├── puppet_version.rb │ ├── ruby_version.rb │ ├── template_uri.rb │ ├── vendored_file.rb │ ├── version.rb │ ├── windows.rb │ └── windows │ │ ├── api_types.rb │ │ ├── file.rb │ │ ├── process.rb │ │ └── string.rb │ ├── validate.rb │ ├── validate │ ├── control_repo │ │ ├── control_repo_validator_group.rb │ │ └── environment_conf_validator.rb │ ├── external_command_validator.rb │ ├── internal_ruby_validator.rb │ ├── invokable_validator.rb │ ├── metadata │ │ ├── metadata_json_lint_validator.rb │ │ ├── metadata_syntax_validator.rb │ │ └── metadata_validator_group.rb │ ├── puppet │ │ ├── puppet_epp_validator.rb │ │ ├── puppet_lint_validator.rb │ │ ├── puppet_plan_syntax_validator.rb │ │ ├── puppet_syntax_validator.rb │ │ └── puppet_validator_group.rb │ ├── ruby │ │ ├── ruby_rubocop_validator.rb │ │ └── ruby_validator_group.rb │ ├── tasks │ │ ├── tasks_metadata_lint_validator.rb │ │ ├── tasks_name_validator.rb │ │ └── tasks_validator_group.rb │ ├── validator.rb │ ├── validator_group.rb │ └── yaml │ │ ├── yaml_syntax_validator.rb │ │ └── yaml_validator_group.rb │ └── version.rb ├── package-testing ├── .gitignore ├── Gemfile ├── Rakefile ├── config │ └── options.rb ├── lib │ ├── helper.rb │ ├── pdk │ │ └── pdk_helper.rb │ └── testenv.rb └── spec │ ├── package │ ├── add_gem_to_module_spec.rb │ ├── airgapped_usage_spec.rb │ ├── pdk_configuration_spec.rb │ ├── pdk_help_spec.rb │ ├── support │ │ ├── install_pdk.rb │ │ ├── serverspec_monkeypatch.rb │ │ └── spec_utils.rb │ ├── unit_test_a_new_module_spec.rb │ ├── unprivileged_user_spec.rb │ ├── update_module_spec.rb │ ├── validate_a_new_module_spec.rb │ └── version_selection_spec.rb │ └── spec_helper_package.rb ├── pdk.gemspec ├── rakelib ├── command_spec.rake └── test_pdk_as_library.rake ├── reference └── validate │ └── REFERENCE.md └── spec ├── acceptance ├── build_spec.rb ├── bundle_spec.rb ├── convert_spec.rb ├── get_config_spec.rb ├── get_spec.rb ├── new_class_spec.rb ├── new_defined_type_spec.rb ├── new_fact_spec.rb ├── new_function_spec.rb ├── new_module_spec.rb ├── new_provider_spec.rb ├── new_test_spec.rb ├── new_transport_spec.rb ├── remove_config_spec.rb ├── remove_spec.rb ├── report_spec.rb ├── set_config_spec.rb ├── set_spec.rb ├── support │ ├── contain_valid_junit_xml.rb │ ├── have_junit_testcase.rb │ ├── have_junit_testsuite.rb │ ├── have_no_output.rb │ ├── have_xpath.rb │ ├── in_a_new_module.rb │ ├── it_requires_running_in_a_module.rb │ ├── with_a_fake_answer_file.rb │ └── with_a_fake_tty.rb ├── template_ref_spec.rb ├── test_unit_spec.rb ├── update_spec.rb ├── validate_all_spec.rb ├── validate_metadata_spec.rb ├── validate_puppet_spec.rb ├── validate_ruby_spec.rb ├── validate_tasks_spec.rb └── version_changer_spec.rb ├── fixtures ├── JUnit.xsd ├── control_repo │ ├── Puppetfile │ ├── data │ │ └── .gitkeep │ ├── environment.conf │ └── site │ │ ├── profile │ │ └── .gitkeep │ │ └── role │ │ └── .gitkeep ├── module_gemfile ├── module_gemfile_lockfile ├── module_root │ └── lib │ │ └── facter │ │ └── .gitkeep ├── pe_versions.json └── puppet_module │ ├── manifests │ └── .gitkeep │ └── metadata.json ├── spec_helper.rb ├── spec_helper_acceptance.rb ├── support ├── exit_with_status.rb ├── file_based_namespaces.rb ├── hash_with_defaults_including.rb ├── it_accepts_epp_targets.rb ├── it_accepts_metadata_json_targets.rb ├── it_accepts_pp_targets.rb ├── mock_configuration.rb ├── packaged_install.rb ├── run_outside_module.rb ├── valid_in_context.rb └── validators.rb └── unit ├── pdk ├── bolt_spec.rb ├── cli │ ├── build_spec.rb │ ├── bundle_spec.rb │ ├── console_spec.rb │ ├── convert_spec.rb │ ├── env_spec.rb │ ├── errors_spec.rb │ ├── exec │ │ ├── command_spec.rb │ │ └── interactive_command_spec.rb │ ├── exec_spec.rb │ ├── new │ │ ├── class_spec.rb │ │ ├── defined_type_spec.rb │ │ ├── module_spec.rb │ │ ├── task_spec.rb │ │ └── test_spec.rb │ ├── new_spec.rb │ ├── release │ │ ├── prep_spec.rb │ │ └── publish_spec.rb │ ├── release_spec.rb │ ├── remove │ │ └── config_spec.rb │ ├── set │ │ └── config_spec.rb │ ├── test │ │ └── unit_spec.rb │ ├── test_spec.rb │ ├── update_spec.rb │ ├── util │ │ ├── command_redirector_spec.rb │ │ ├── interview_spec.rb │ │ ├── option_normalizer_spec.rb │ │ ├── option_validator_spec.rb │ │ └── update_manager_printer_spec.rb │ ├── util_spec.rb │ └── validate_spec.rb ├── cli_spec.rb ├── config │ ├── ini_file_setting_spec.rb │ ├── ini_file_spec.rb │ ├── json_schema_namespace_spec.rb │ ├── json_schema_setting_spec.rb │ ├── json_spec.rb │ ├── json_with_schema_spec.rb │ ├── namespace_spec.rb │ ├── schema_files_spec.rb │ ├── setting_spec.rb │ ├── yaml_spec.rb │ └── yaml_with_schema_spec.rb ├── config_spec.rb ├── context │ ├── control_repo_spec.rb │ ├── module_spec.rb │ └── none_spec.rb ├── context_spec.rb ├── control_repo_spec.rb ├── generate │ ├── defined_type_spec.rb │ ├── module_spec.rb │ ├── provider_spec.rb │ ├── puppet_class_spec.rb │ ├── puppet_object_spec.rb │ ├── task_spec.rb │ └── transport_spec.rb ├── logger_spec.rb ├── module │ ├── convert_spec.rb │ ├── metadata_spec.rb │ ├── release_spec.rb │ ├── update_manager_spec.rb │ └── update_spec.rb ├── report │ └── event_spec.rb ├── report_spec.rb ├── template │ ├── fetcher │ │ ├── git_spec.rb │ │ └── local_spec.rb │ ├── fetcher_spec.rb │ ├── renderer │ │ ├── v1 │ │ │ ├── renderer_spec.rb │ │ │ └── template_file_spec.rb │ │ └── v1_spec.rb │ ├── renderer_spec.rb │ └── template_dir_spec.rb ├── template_spec.rb ├── test │ └── unit_spec.rb ├── util │ ├── bundler_spec.rb │ ├── changelog_generator_spec.rb │ ├── env_spec.rb │ ├── filesystem_spec.rb │ ├── git_spec.rb │ ├── puppet_strings_spec.rb │ ├── puppet_version_spec.rb │ ├── ruby_version_spec.rb │ ├── template_uri_spec.rb │ ├── vendored_file_spec.rb │ └── version_spec.rb ├── util_spec.rb ├── validate │ ├── control_repo │ │ ├── control_repo_validator_group_spec.rb │ │ └── environment_conf_validator_spec.rb │ ├── external_command_validator_spec.rb │ ├── internal_ruby_validator_spec.rb │ ├── invokable_validator_spec.rb │ ├── metadata │ │ ├── metadata_json_lint_validator_spec.rb │ │ ├── metadata_syntax_validator_spec.rb │ │ └── metadata_validator_group_spec.rb │ ├── puppet │ │ ├── puppet_epp_validator_spec.rb │ │ ├── puppet_lint_validator_spec.rb │ │ ├── puppet_plan_syntax_validator_spec.rb │ │ ├── puppet_syntax_validator_spec.rb │ │ └── puppet_validator_group_spec.rb │ ├── ruby │ │ ├── ruby_rubocop_validator_spec.rb │ │ └── ruby_validator_group_spec.rb │ ├── tasks │ │ ├── tasks_metadata_lint_validator_spec.rb │ │ ├── tasks_name_validator_spec.rb │ │ └── tasks_validator_group_spec.rb │ ├── validator_group_spec.rb │ ├── validator_spec.rb │ └── yaml │ │ ├── yaml_syntax_validator_spec.rb │ │ └── yaml_validator_group_spec.rb ├── validate_spec.rb └── version_spec.rb └── pdk_spec.rb /.adr-dir: -------------------------------------------------------------------------------- 1 | docs/adr 2 | -------------------------------------------------------------------------------- /.fixtures.yml: -------------------------------------------------------------------------------- 1 | fixtures: 2 | repositories: 3 | provision: 'https://github.com/puppetlabs/provision.git' 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | --- 2 | labels: needs-triage 3 | --- 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve the PDK 4 | title: '' 5 | labels: bug, needs-triage 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 | The simplest sequence of steps you have discovered which triggers the bug. 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Additional context** 20 | - Your PDK installation method (native packages or via Rubygems) 21 | - (If via Rubygems, please include your Ruby version `ruby -v`) 22 | - Your PDK version (`pdk --version`) 23 | - Your operating system / platform 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: 'Feature requests should be raised as a ''Feature Request'' Discussion here: 4 | https://github.com/puppetlabs/pdk/discussions/categories/feature-request ' 5 | title: '' 6 | labels: '' 7 | assignees: '' 8 | 9 | --- 10 | 11 | **Feature requests should be opened as a ['Feature Request' Discussion](https://github.com/puppetlabs/pdk/discussions/categories/feature-request) in the [Discussions](https://github.com/puppetlabs/pdk/discussions) area.** 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # raise PRs for gem updates 4 | - package-ecosystem: bundler 5 | directory: "/" 6 | schedule: 7 | interval: daily 8 | time: "13:00" 9 | open-pull-requests-limit: 10 10 | 11 | # Maintain dependencies for GitHub Actions 12 | - package-ecosystem: github-actions 13 | directory: "/" 14 | schedule: 15 | interval: daily 16 | time: "13:00" 17 | open-pull-requests-limit: 10 18 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | Provide a detailed description of all the changes present in this pull request. 3 | 4 | ## Additional Context 5 | Add any additional context about the problem here. 6 | - [ ] Root cause and the steps to reproduce. (If applicable) 7 | - [ ] Thought process behind the implementation. 8 | 9 | ## Related Issues (if any) 10 | Mention any related issues or pull requests. 11 | 12 | ## Checklist 13 | - [ ] 🟢 Spec tests. 14 | - [ ] 🟢 Acceptance tests. 15 | - [ ] Manually verified. 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "ci" 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | pull_request: 8 | branches: 9 | - "main" 10 | workflow_dispatch: 11 | 12 | env: 13 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 14 | 15 | jobs: 16 | spec: 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | ruby_version: 21 | - "3.1" 22 | include: 23 | - puppet_gem_version: "~> 8.0" 24 | ruby_version: "3.1" 25 | name: "spec (ruby ${{ matrix.ruby_version }})" 26 | uses: "puppetlabs/cat-github-actions/.github/workflows/gem_ci.yml@main" 27 | secrets: inherit 28 | with: 29 | rake_task: 'spec:coverage' 30 | ruby_version: ${{ matrix.ruby_version }} 31 | puppet_gem_version: ${{ matrix.puppet_gem_version }} 32 | # This line enables CI shellcheck (reviewdog) to be run on the repository 33 | run_shellcheck: true 34 | 35 | acceptance: 36 | needs: "spec" 37 | strategy: 38 | fail-fast: false 39 | matrix: 40 | os: 41 | - "ubuntu-latest" 42 | - "windows-2019" 43 | ruby_version: 44 | - "3.1" 45 | include: 46 | - puppet_gem_version: "~> 8.0" 47 | ruby_version: "3.1" 48 | name: "acceptance (ruby ${{ matrix.ruby_version }} | ${{ matrix.os }})" 49 | uses: "puppetlabs/cat-github-actions/.github/workflows/gem_acceptance.yml@main" 50 | secrets: inherit 51 | with: 52 | ruby_version: ${{ matrix.ruby_version }} 53 | puppet_version: ${{ matrix.puppet_gem_version }} 54 | rake_task: 'acceptance:local' 55 | runs_on: ${{ matrix.os }} 56 | -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | name: "nightly" 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | workflow_dispatch: 7 | 8 | jobs: 9 | 10 | spec: 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | ruby_version: 15 | - "3.1" 16 | include: 17 | - puppet_gem_version: "~> 8.0" 18 | ruby_version: "3.1" 19 | name: "spec (ruby ${{ matrix.ruby_version }})" 20 | uses: "puppetlabs/cat-github-actions/.github/workflows/gem_ci.yml@main" 21 | secrets: "inherit" 22 | with: 23 | rake_task: 'spec:coverage' 24 | ruby_version: ${{ matrix.ruby_version }} 25 | puppet_gem_version: ${{ matrix.puppet_gem_version }} 26 | 27 | acceptance: 28 | needs: "spec" 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | os: 33 | - "ubuntu-latest" 34 | - "windows-2019" 35 | ruby_version: 36 | - "3.1" 37 | include: 38 | - puppet_gem_version: "~> 8.0" 39 | ruby_version: "3.1" 40 | name: "acceptance (ruby ${{ matrix.ruby_version }} | ${{ matrix.os }})" 41 | uses: "puppetlabs/cat-github-actions/.github/workflows/gem_acceptance.yml@main" 42 | secrets: "inherit" 43 | with: 44 | ruby_version: ${{ matrix.ruby_version }} 45 | puppet_version: ${{ matrix.puppet_gem_version }} 46 | rake_task: 'acceptance:local' 47 | runs_on: ${{ matrix.os }} 48 | 49 | on-failure-workflow-restarter-proxy: 50 | # (1) run this job after the "acceptance" job and... 51 | needs: [acceptance] 52 | # (2) continue ONLY IF "acceptance" fails 53 | if: always() && needs.acceptance.result == 'failure' 54 | runs-on: ubuntu-latest 55 | steps: 56 | # (3) checkout this repository in order to "see" the following custom action 57 | - name: Checkout repository 58 | uses: actions/checkout@v4 59 | 60 | # (4) "use" the custom action to retrigger the failed "acceptance job" above 61 | # NOTE: pass the SOURCE_GITHUB_TOKEN to the custom action because (a) it must have 62 | # this to trigger the reusable workflow that restarts the failed job; and 63 | # (b) custom actions do not have access to the calling workflow's secrets 64 | - name: Trigger reusable workflow 65 | uses: "puppetlabs/cat-github-actions/.github/actions/workflow-restarter-proxy@main" 66 | env: 67 | SOURCE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 68 | with: 69 | repository: ${{ github.repository }} 70 | run_id: ${{ github.run_id }} 71 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: "release" 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | target: 7 | description: "The target for the release. This can be a commit sha or a branch." 8 | required: false 9 | default: "main" 10 | 11 | jobs: 12 | release: 13 | uses: "puppetlabs/cat-github-actions/.github/workflows/gem_release.yml@main" 14 | with: 15 | target: "${{ github.event.inputs.target }}" 16 | secrets: "inherit" 17 | -------------------------------------------------------------------------------- /.github/workflows/release_prep.yml: -------------------------------------------------------------------------------- 1 | name: "release prep" 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | target: 7 | description: "The target for the release. This can be a commit sha or a branch." 8 | required: false 9 | default: "main" 10 | version: 11 | description: "Version of gem to be released." 12 | required: true 13 | 14 | jobs: 15 | release_prep: 16 | uses: "puppetlabs/cat-github-actions/.github/workflows/gem_release_prep.yml@main" 17 | with: 18 | target: "${{ github.event.inputs.target }}" 19 | version: "${{ github.events.inputs.version }}" 20 | secrets: "inherit" 21 | -------------------------------------------------------------------------------- /.github/workflows/workflow-restarter-test.yml: -------------------------------------------------------------------------------- 1 | name: Workflow Restarter TEST 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | fail: 7 | description: > 8 | For (acceptance, unit) jobs: 9 | 'true' = (fail, succeed) and 10 | 'false' = (succeed, fail) 11 | required: true 12 | default: 'true' 13 | env: 14 | SOURCE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 15 | 16 | jobs: 17 | unit: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Check outcome 21 | run: | 22 | if [ "${{ github.event.inputs.fail }}" = "true" ]; then 23 | echo "'unit' job succeeded" 24 | exit 0 25 | else 26 | echo "'unit' job failed" 27 | exit 1 28 | fi 29 | acceptance: 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Check outcome 33 | run: | 34 | if [ "${{ github.event.inputs.fail }}" = "true" ]; then 35 | echo "'acceptance' job failed" 36 | exit 1 37 | else 38 | echo "'acceptance' job succeeded" 39 | exit 0 40 | fi 41 | 42 | on-failure-workflow-restarter-proxy: 43 | # (1) run this job after the "acceptance" job and... 44 | needs: [acceptance, unit] 45 | # (2) continue ONLY IF "acceptance" fails 46 | if: always() && needs.acceptance.result == 'failure' || needs.unit.result == 'failure' 47 | runs-on: ubuntu-latest 48 | steps: 49 | # (3) checkout this repository in order to "see" the following custom action 50 | - name: Checkout repository 51 | uses: actions/checkout@v4 52 | 53 | # (4) "use" the custom action to retrigger the failed "acceptance job" above 54 | # NOTE: pass the SOURCE_GITHUB_TOKEN to the custom action because (a) it must have 55 | # this to trigger the reusable workflow that restarts the failed job; and 56 | # (b) custom actions do not have access to the calling workflow's secrets 57 | - name: Trigger reusable workflow 58 | uses: "puppetlabs/cat-github-actions/.github/actions/workflow-restarter-proxy@main" 59 | env: 60 | SOURCE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 61 | with: 62 | repository: ${{ github.repository }} 63 | run_id: ${{ github.run_id }} 64 | -------------------------------------------------------------------------------- /.github/workflows/workflow-restarter.yml: -------------------------------------------------------------------------------- 1 | # target-repo/.github/workflows/call-reusable-workflow.yml 2 | name: Workflow Restarter 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | repo: 7 | description: "GitHub repository name." 8 | required: true 9 | type: string 10 | run_id: 11 | description: "The ID of the workflow run to rerun." 12 | required: true 13 | type: string 14 | retries: 15 | description: "The number of times to retry the workflow run." 16 | required: false 17 | type: string 18 | default: "3" 19 | 20 | jobs: 21 | call-reusable-workflow: 22 | uses: "puppetlabs/cat-github-actions/.github/workflows/workflow-restarter.yml@main" 23 | with: 24 | repo: ${{ inputs.repo }} 25 | run_id: ${{ inputs.run_id }} 26 | retries: ${{ inputs.retries }} 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | *.swp 4 | /.config 5 | /coverage/ 6 | /inventory.yaml 7 | /InstalledFiles 8 | /pkg/ 9 | /spec/fixtures/ 10 | /spec/reports/ 11 | /spec/examples.txt 12 | /test/tmp/ 13 | /test/version_tmp/ 14 | /tmp/ 15 | 16 | # Used by dotenv library to load environment variables. 17 | # .env 18 | 19 | ## Specific to RubyMotion: 20 | .dat* 21 | .repl_history 22 | build/ 23 | *.bridgesupport 24 | build-iPhoneOS/ 25 | build-iPhoneSimulator/ 26 | 27 | ## Specific to RubyMotion (use of CocoaPods): 28 | # 29 | # We recommend against adding the Pods directory to your .gitignore. However 30 | # you should judge for yourself, the pros and cons are mentioned at: 31 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 32 | # 33 | # vendor/Pods/ 34 | 35 | ## Documentation cache and generated files: 36 | /.yardoc/ 37 | /_yardoc/ 38 | /doc/ 39 | /rdoc/ 40 | 41 | ## Environment normalization: 42 | /.bundle/ 43 | /vendor/bundle 44 | /lib/bundler/man/ 45 | 46 | # for a library or gem, you might want to ignore these files since the code is 47 | # intended to run in multiple environments; otherwise, check them in: 48 | # Gemfile.lock 49 | .ruby-version 50 | # .ruby-gemset 51 | 52 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 53 | .rvmrc 54 | 55 | # bundler defaults 56 | /.bundle/ 57 | /.yardoc 58 | /Gemfile.lock 59 | /_yardoc/ 60 | /coverage/ 61 | /doc/ 62 | /pkg/ 63 | /spec/reports/ 64 | /tmp/ 65 | /vendor/gems/ 66 | Gemfile.local 67 | 68 | # ignore bundler generated binstubs, but keep setup/console scripts 69 | /bin/* 70 | !/bin/setup 71 | !/bin/console 72 | 73 | # beaker output directories 74 | /log/ 75 | /repo-config/ 76 | /acceptance_hosts.yml 77 | /junit/ 78 | /archive/ 79 | /sut-files.tgz 80 | 81 | # Puppet packaging files 82 | /ext/packaging/ 83 | 84 | # generated artifacts from rake tasks 85 | /output 86 | modules 87 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Setting ownership to the tooling team 2 | * @puppetlabs/devx 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in pdk.gemspec 4 | gemspec 5 | 6 | group :development do 7 | gem 'ruby-prof' 8 | gem 'yard' 9 | 10 | gem 'fuubar' 11 | gem 'pry' 12 | gem 'pry-stack_explorer' 13 | end 14 | 15 | group :test do 16 | gem 'parallel' 17 | gem 'parallel_tests' 18 | gem 'rake' 19 | gem 'rexml' 20 | gem 'rspec', '~> 3.0' 21 | gem 'rubocop', '~> 1.70.0', require: false 22 | gem 'rubocop-performance', '~> 1.22.1', require: false 23 | gem 'rubocop-rspec', '~> 3.1.0', require: false 24 | gem 'simplecov-console' 25 | 26 | # Temporary exclusion required as these versions are currently broken for us 27 | gem 'rubocop-factory_bot', '!= 2.26.0', require: false 28 | gem 'rubocop-rspec_rails', '!= 2.29.0', require: false 29 | end 30 | 31 | group :acceptance do 32 | gem 'minitar-cli' 33 | gem 'rspec-xsd' 34 | gem 'serverspec' 35 | end 36 | 37 | group :acceptance_ci do 38 | gem 'puppetlabs_spec_helper', '~> 7.0', require: false 39 | end 40 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Puppet Development Kit 2 | 3 | Copyright (C) 2017 Puppet, Inc. 4 | 5 | Puppet Labs can be contacted at: info@puppetlabs.com 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'bundler/setup' 4 | require 'pdk' 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | require 'pry' 10 | Pry.start 11 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /docs/pdk-workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/pdk/9c9acab177d14a94f113f81b75220cb215133f3a/docs/pdk-workflow.png -------------------------------------------------------------------------------- /docs/pdk.ditamap: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | pdk 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /docs/pdk_building_module_packages.md: -------------------------------------------------------------------------------- 1 | # Building module packages 2 | 3 | Before you can upload and publish your module to the Forge, build an uploadable 4 | module package. 5 | 6 | The `pdk build` command performs a series of checks on your module and builds a 7 | `tar.gz` package so that you can upload your module to the Forge. To learn more 8 | about publishing your module to the Forge, see the documentation about 9 | [publishing your 10 | module](https://www.puppet.com/docs/puppet/latest/modules_publishing.html). 11 | 12 | When you run the `pdk build` command, PDK checks your module metadata, looks for 13 | any symlinks, and excludes from the package any files listed in the `.gitignore` 14 | or `.pdkignore` files. If PDK finds any issues with metadata or symlinks, it 15 | prompts you to fix these issues. 16 | 17 | By default, the `.pdkignore` file contains a list of commonly ignored files, 18 | such as temporary files. This file is located in the module's main directory. To 19 | add or remove files to this list, define them in the module's `.sync.yml` file 20 | and run `pdk update` on your module. 21 | 22 | PDK prompts you for confirmation before building the package. It writes module 23 | packages to the module's `pkg` directory, but you can specify a different 24 | directory if you prefer. PDK names module packages with the convention 25 | `forgeusername-modulename-version.tar.gz`. 26 | 27 | ## Build a module 28 | 29 | Build a module package with the `pdk build` command so that you can upload your 30 | module to the Forge. 31 | 32 | 1. From the command line, change into the module's directory with `cd 33 | ` 34 | 35 | 2. Run `pdk build` and respond to any prompts. 36 | 37 | To change the behavior of the build command, add option flags to the 38 | command. For example, to create the package to a custom location, run `pdk 39 | build --target-dir=` . For a complete list of command options and 40 | usage information, see the PDK [command reference](pdk_reference.md). 41 | 42 | 43 | **Result:** 44 | 45 | PDK builds a package with the naming convention 46 | `forgeusername-modulename-version.tar.gz` to the `pkg` directory of the module. 47 | 48 | ### What to do next: 49 | 50 | You can now upload your module to the Forge. 51 | 52 | -------------------------------------------------------------------------------- /docs/pdk_release_notes.md: -------------------------------------------------------------------------------- 1 | # Release notes 2 | 3 | These release notes contain important information about Puppet Development Kit 4 | (PDK). 5 | 6 | This release incorporates new features, enhancements, and resolved issues from 7 | all previous releases. If you're upgrading from an earlier version of PDK,check 8 | the release notes for any interim versions for details about additional 9 | improvements in this release over your current release. Update your PDK 10 | installation with every new release. 11 | 12 | -------------------------------------------------------------------------------- /docs/pdk_troubleshooting.md: -------------------------------------------------------------------------------- 1 | # PDK troubleshooting 2 | 3 | If you are encountering trouble with PDK, check for these common issues. 4 | 5 | ## PDK not in ZShell PATH on Mac OS X 6 | 7 | With ZShell on Mac OS X, PDK is not automatically added to the PATH. To fix 8 | this, add the PATH by adding the line `eval (/usr/libexec/path_helper -s)` to 9 | the ZShell resource file (`~/.zshrc`). 10 | 11 | ## PDK failing to pull from custom git server 12 | 13 | If a `fatal: unable to access...SSL certificate problem: self signed certificate` error occurs during PDK usage, it indicates that the PDK is trying to download a module from a source that isn't trusted. For example, given a `.fixtures.yml` that references an untrusted self-hosted git repository server `git.self.hosted` 14 | 15 | ```yaml 16 | # .fixtures.yml 17 | --- 18 | fixtures: 19 | forge_modules: 20 | nginx: "puppet/nginx" 21 | repositories: 22 | mymodule: 'https://git.self.hosted/companyxyz/mymodule.git' 23 | ``` 24 | 25 | then running `pdk test unit` will throw an error something like: 26 | 27 | ```bash 28 | root@seattle:~/modules/tester# pdk test unit 29 | pdk (INFO): Using Ruby 3.2.2 30 | pdk (INFO): Using Puppet 8.1.0 31 | [✖] Preparing to run the unit tests. 32 | [✔] Cleaning up after running unit tests. 33 | pdk (ERROR): The spec_prep rake task failed with the following error(s): 34 | 35 | Cloning into 'spec/fixtures/modules/mymodule'... 36 | fatal: unable to access 'https://git.self.hosted/companyxyz/mymodule.git/': SSL certificate problem: self signed certificate 37 | # terminated with exception (report_on_exception is true): 38 | ... 39 | ... 40 | ``` 41 | 42 | To resolve this issue, first create a backup of the existing PDK certificates file, which lives `/opt/puppetlabs/pdk/ssl/cert.pem` on linux machines and `C:\Program Files\Puppet Labs\DevelopmentKit\ssl\cert.pem` on windows. 43 | 44 | Then do the following 45 | 46 | * Obtain the chain of trust certificates from the self-hosted server. For example, `printf '' | openssl s_client -connect git.self.hosted:443 -showcerts` will return metadata including the trust certificates for the `git.self.hosted:443` server. 47 | * Append the trust certificates to the end of the `cert.pem` ensuring the pdk "trusts" the new self-hosted git server. 48 | 49 | **NOTE:** There is a known issue [Upgrading the pdk over-writes the pdk's cert.pem](pdk_known_issues.md#upgrading-the-pdk-over-writes-the-pdks-certpem). Therefore, any custom certificates will need to be added again after upgrading the pdk. 50 | -------------------------------------------------------------------------------- /docs/pdk_updating_modules.md: -------------------------------------------------------------------------------- 1 | # Updating modules with changes to the template 2 | 3 | To keep your module's configuration current with changes to either the PDK 4 | default template or your own custom template, use the `pdk update` command. 5 | 6 | The `pdk update` function updates your module based on the template you used 7 | when you created or converted your module. If there have been any changes to 8 | that template PDK updates your module to incorporate them. 9 | 10 | If you used a custom template, you can update whenever you know there is a 11 | change in your template. If you didn't specify any custom template, you created 12 | or converted your module using the default PDK template and can update when new 13 | versions of PDK release. 14 | 15 | When you run the `update` command, PDK displays a summary of the files that will 16 | change during converstion and prompts you to either continue or cancel the 17 | update. Either way, PDK generates a detailed change report, `update_report.txt`, 18 | in the top directory of the module. This report is replaced by an updated 19 | version every time you run the `update` command. 20 | 21 | You can check for template changes by running `update` with the `--noop` option, 22 | which runs the command in "no operation" mode. This option shows what changes 23 | would be made, but doesn't actually make them. 24 | 25 | **Important:** The default PDK template URL changed in PDK version 1.3.0. If you 26 | created your module with a PDK version earlier than 1.3.0, update your PDK 27 | version and run `pdk convert` on your old module to bring it up to date. 28 | 29 | **Related information** 30 | - [Converting modules](pdk_converting_modules.md) 31 | 32 | -------------------------------------------------------------------------------- /exe/pdk: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'pdk/cli' 4 | 5 | begin 6 | PDK::CLI.run(ARGV) 7 | rescue Interrupt 8 | warn "\nAborted!" 9 | exit 1 10 | end 11 | -------------------------------------------------------------------------------- /ext/PuppetDevelopmentKit/PuppetDevelopmentKit.psd1.erb: -------------------------------------------------------------------------------- 1 | @{ 2 | ModuleToProcess = 'PuppetDevelopmentKit.psm1' 3 | ModuleVersion = '<%= Gem::Version.new(PDK::VERSION).release %>' 4 | GUID = 'bfe70e90-1802-4f6b-b4a0-f627d53f593f' 5 | Author = "Puppet, Inc" 6 | CompanyName = "Puppet, Inc" 7 | Copyright = '(c) <%= Time.new.year %> Puppet, Inc. All rights reserved' 8 | FunctionsToExport = @('pdk') 9 | CmdletsToExport = @() 10 | VariablesToExport = @() 11 | AliasesToExport = @() 12 | PrivateData = @{ 13 | PSData = @{ 14 | # Tags = @() 15 | LicenseUri = 'https://github.com/puppetlabs/pdk/blob/main/LICENSE' 16 | ProjectUri = 'https://github.com/puppetlabs/pdk' 17 | # IconUri = '' 18 | ReleaseNotes = 'https://github.com/puppetlabs/pdk/blob/main/CHANGELOG.md' 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ext/PuppetDevelopmentKit/PuppetDevelopmentKit.psm1: -------------------------------------------------------------------------------- 1 | $fso = New-Object -ComObject Scripting.FileSystemObject 2 | 3 | $env:DEVKIT_BASEDIR = (Get-ItemProperty -Path "HKLM:\Software\Puppet Labs\DevelopmentKit").RememberedInstallDir64 4 | # Windows API GetShortPathName requires inline C#, so use COM instead 5 | $env:DEVKIT_BASEDIR = $fso.GetFolder($env:DEVKIT_BASEDIR).ShortPath 6 | $env:RUBY_DIR = "$($env:DEVKIT_BASEDIR)\private\ruby\2.4.5" 7 | $env:SSL_CERT_FILE = "$($env:DEVKIT_BASEDIR)\ssl\cert.pem" 8 | $env:SSL_CERT_DIR = "$($env:DEVKIT_BASEDIR)\ssl\certs" 9 | 10 | function pdk { 11 | if ($Host.Name -eq 'Windows PowerShell ISE Host') { 12 | Write-Error ("The Puppet Development Kit cannot be run in the Windows PowerShell ISE.`n" + ` 13 | "Open a new Windows PowerShell Console, or 'Start-Process PowerShell', and use PDK within this new console.`n" + ` 14 | "For more information see https://puppet.com/docs/pdk/latest/pdk_known_issues.html and https://devblogs.microsoft.com/powershell/console-application-non-support-in-the-ise.") 15 | return 16 | } 17 | if ($env:ConEmuANSI -eq 'ON') { 18 | &$env:RUBY_DIR\bin\ruby -S -- $env:RUBY_DIR\bin\pdk $args 19 | } else { 20 | &$env:DEVKIT_BASEDIR\private\tools\bin\ansicon.exe $env:RUBY_DIR\bin\ruby -S -- $env:RUBY_DIR\bin\pdk $args 21 | } 22 | } 23 | 24 | Export-ModuleMember -Function pdk -Variable * 25 | -------------------------------------------------------------------------------- /ext/PuppetDevelopmentKitBeta/PuppetDevelopmentKitBeta.psd1.erb: -------------------------------------------------------------------------------- 1 | @{ 2 | ModuleToProcess = 'PuppetDevelopmentKitBeta.psm1' 3 | ModuleVersion = '0.0.1-beta' 4 | GUID = '8b45f6ef-3990-4a86-a2e1-9f66a9ba55f6' 5 | Author = "Puppet, Inc" 6 | CompanyName = "Puppet, Inc" 7 | Copyright = '(c) <%= Time.new.year %> Puppet, Inc. All rights reserved' 8 | FunctionsToExport = @('PENDING') 9 | CmdletsToExport = @() 10 | VariablesToExport = @() 11 | AliasesToExport = @() 12 | PrivateData = @{ 13 | PSData = @{ 14 | # Tags = @() 15 | LicenseUri = 'https://github.com/puppetlabs/pdk/blob/main/LICENSE' 16 | ProjectUri = 'https://github.com/puppetlabs/pdk' 17 | IconUri = 'https://cdn.rawgit.com/puppetlabs/puppet-chocolatey-packages/main/icons/puppet.png' 18 | ReleaseNotes = 'https://github.com/puppetlabs/pdk/blob/main/CHANGELOG.md' 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ext/build_defaults.yml: -------------------------------------------------------------------------------- 1 | --- 2 | packaging_url: 'git://github.com/puppetlabs/packaging.git --branch=main' 3 | packaging_repo: 'packaging' 4 | packager: 'puppetlabs' 5 | -------------------------------------------------------------------------------- /lib/pdk.rb: -------------------------------------------------------------------------------- 1 | module PDK 2 | autoload :AnswerFile, 'pdk/answer_file' 3 | autoload :Bolt, 'pdk/bolt' 4 | autoload :Config, 'pdk/config' 5 | autoload :Context, 'pdk/context' 6 | autoload :ControlRepo, 'pdk/control_repo' 7 | autoload :Generate, 'pdk/generate' 8 | autoload :Logger, 'pdk/logger' 9 | autoload :Module, 'pdk/module' 10 | autoload :Report, 'pdk/report' 11 | autoload :Template, 'pdk/template' 12 | autoload :TEMPLATE_REF, 'pdk/version' 13 | autoload :Util, 'pdk/util' 14 | autoload :Validate, 'pdk/validate' 15 | autoload :VERSION, 'pdk/version' 16 | 17 | # TODO: Refactor backend code to not raise CLI errors or use CLI util 18 | # methods. 19 | module CLI 20 | autoload :ExitWithError, 'pdk/cli/errors' 21 | autoload :FatalError, 'pdk/cli/errors' 22 | autoload :Util, 'pdk/cli/util' 23 | autoload :Exec, 'pdk/cli/exec' 24 | autoload :ExecGroup, 'pdk/cli/exec_group' 25 | end 26 | 27 | module Test 28 | autoload :Unit, 'pdk/tests/unit' 29 | end 30 | 31 | def self.logger 32 | @logger ||= PDK::Logger.new 33 | end 34 | 35 | def self.config 36 | return @config unless @config.nil? 37 | 38 | options = {} 39 | options['user.module_defaults.path'] = PDK::Util::Env['PDK_ANSWER_FILE'] unless PDK::Util::Env['PDK_ANSWER_FILE'].nil? 40 | @config = PDK::Config.new(options) 41 | end 42 | 43 | def self.context 44 | @context ||= PDK::Context.create(Dir.pwd) 45 | end 46 | 47 | def self.available_feature_flags 48 | @available_feature_flags ||= ['controlrepo'].freeze 49 | end 50 | 51 | def self.requested_feature_flags 52 | @requested_feature_flags ||= (PDK::Util::Env['PDK_FEATURE_FLAGS'] || '').split(',').map(&:strip) 53 | end 54 | 55 | def self.feature_flag?(flagname) 56 | return false unless available_feature_flags.include?(flagname) 57 | 58 | requested_feature_flags.include?(flagname) 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/pdk/answer_file.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | class AnswerFile 5 | # Determine the default path to the answer file. 6 | # 7 | # @return [String] The path on disk to the default answer file. 8 | def self.default_answer_file_path 9 | PDK::Util::Filesystem.expand_path(File.join(PDK::Util.cachedir, 'answers.json')) 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/pdk/bolt.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module Bolt 5 | # Returns true or false depending on if any of the common files and directories in 6 | # a Bolt Project are found in the specified directory. If a directory is not specified, 7 | # the current working directory is used. 8 | # 9 | # @see https://puppet.com/docs/bolt/latest/bolt_project_directories.html 10 | # 11 | # @return [boolean] True if any bolt specific files or directories are present 12 | # 13 | def bolt_project_root?(path = Dir.pwd) 14 | return true if File.basename(path) == 'Boltdir' && PDK::Util::Filesystem.directory?(path) 15 | 16 | PDK::Util::Filesystem.file?(File.join(path, 'bolt.yaml')) 17 | end 18 | module_function :bolt_project_root? 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/pdk/cli/bundle.rb: -------------------------------------------------------------------------------- 1 | module PDK 2 | module CLI 3 | @bundle_cmd = @base_cmd.define_command do 4 | name 'bundle' 5 | usage 'bundle [bundler_options]' 6 | summary 'Command pass-through to bundler' 7 | description <<~EOF 8 | For advanced users, pdk bundle runs arbitrary commands in the bundler environment that pdk manages. 9 | Careless use of this command can lead to errors that pdk can't help recover from. 10 | EOF 11 | skip_option_parsing 12 | 13 | run do |_opts, args, _cmd| 14 | require 'pdk/util/bundler' 15 | 16 | PDK::CLI::Util.ensure_in_module!( 17 | message: '`pdk bundle` can only be run from inside a valid module directory.' 18 | ) 19 | 20 | PDK::CLI::Util.validate_puppet_version_opts({}) 21 | 22 | screen_view_name = ['bundle'] 23 | screen_view_name << args[0] if args.size >= 1 24 | screen_view_name << args[1] if args.size >= 2 && args[0] == 'exec' 25 | 26 | # Ensure that the correct Ruby is activated before running command. 27 | puppet_env = PDK::CLI::Util.puppet_from_opts_or_env({}) 28 | PDK::Util::RubyVersion.use(puppet_env[:ruby_version]) 29 | 30 | gemfile_env = PDK::Util::Bundler::BundleHelper.gemfile_env(puppet_env[:gemset]) 31 | 32 | require 'pdk/cli/exec' 33 | require 'pdk/cli/exec/interactive_command' 34 | 35 | command = PDK::CLI::Exec::InteractiveCommand.new(PDK::CLI::Exec.bundle_bin, *args).tap do |c| 36 | c.context = :pwd 37 | c.update_environment(gemfile_env) 38 | end 39 | 40 | result = command.execute! 41 | 42 | exit result[:exit_code] 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/pdk/cli/convert.rb: -------------------------------------------------------------------------------- 1 | module PDK 2 | module CLI 3 | @convert_cmd = @base_cmd.define_command do 4 | name 'convert' 5 | usage 'convert [options]' 6 | summary 'Convert an existing module to be compatible with the PDK.' 7 | 8 | PDK::CLI.template_url_option(self) 9 | PDK::CLI.template_ref_option(self) 10 | PDK::CLI.skip_interview_option(self) 11 | PDK::CLI.full_interview_option(self) 12 | flag nil, :noop, 'Do not convert the module, just output what would be done.' 13 | flag nil, :force, 'Convert the module automatically, with no prompts.' 14 | flag nil, :'add-tests', 'Add any missing tests while converting the module.' 15 | flag nil, :'default-template', 'Convert the module to use the default PDK template.' 16 | 17 | run do |opts, _args, _cmd| 18 | # Write the context information to the debug log 19 | PDK.context.to_debug_log 20 | 21 | unless PDK.context.is_a?(PDK::Context::Module) || PDK.context.is_a?(PDK::Context::ControlRepo) 22 | raise PDK::CLI::ExitWithError, '`pdk convert` can only be run from inside a valid module directory.' 23 | end 24 | 25 | raise PDK::CLI::ExitWithError, 'You can not specify --noop and --force when converting a module' if opts[:noop] && opts[:force] 26 | 27 | if opts[:'default-template'] 28 | raise PDK::CLI::ExitWithError, 'You can not specify --template-url and --default-template.' if opts[:'template-url'] 29 | 30 | opts[:'template-url'] = PDK::Util::TemplateURI.default_template_addressable_uri.to_s 31 | PDK.config.set(['user', 'module_defaults', 'template-url'], nil) 32 | end 33 | 34 | PDK::CLI::Util.validate_template_opts(opts) 35 | 36 | if opts[:'skip-interview'] && opts[:'full-interview'] 37 | PDK.logger.info 'Ignoring --full-interview and continuing with --skip-interview.' 38 | opts[:'full-interview'] = false 39 | end 40 | 41 | if opts[:force] && opts[:'full-interview'] 42 | PDK.logger.info 'Ignoring --full-interview and continuing with --force.' 43 | opts[:'full-interview'] = false 44 | end 45 | 46 | PDK::Module::Convert.invoke(PDK.context.root_path, opts) 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/pdk/cli/env.rb: -------------------------------------------------------------------------------- 1 | module PDK 2 | module CLI 3 | @env_cmd = @base_cmd.define_command do 4 | name 'env' 5 | usage 'env' 6 | summary '(Experimental) Output environment variables for specific Puppet context' 7 | description <<~EOF 8 | [experimental] Aids in setting a CLI context for a specified version of Puppet by outputting export commands for necessary environment variables. 9 | EOF 10 | 11 | PDK::CLI.puppet_version_options(self) 12 | PDK::CLI.puppet_dev_option(self) 13 | 14 | run do |opts, _args, _cmd| 15 | require 'pdk/util' 16 | require 'pdk/util/ruby_version' 17 | 18 | PDK::CLI::Util.validate_puppet_version_opts(opts) 19 | 20 | # Ensure that the correct Ruby is activated before running command. 21 | puppet_env = PDK::CLI::Util.puppet_from_opts_or_env(opts) 22 | PDK::Util::RubyVersion.use(puppet_env[:ruby_version]) 23 | 24 | resolved_env = { 25 | 'PDK_RESOLVED_PUPPET_VERSION' => puppet_env[:gemset][:puppet], 26 | 'PDK_RESOLVED_RUBY_VERSION' => puppet_env[:ruby_version] 27 | } 28 | 29 | resolved_env['GEM_HOME'] = PDK::Util::RubyVersion.gem_home 30 | gem_path = PDK::Util::RubyVersion.gem_path 31 | resolved_env['GEM_PATH'] = gem_path.empty? ? resolved_env['GEM_HOME'] : gem_path 32 | 33 | # Make sure invocation of Ruby prefers our private installation. 34 | package_binpath = PDK::Util.package_install? ? File.join(PDK::Util.pdk_package_basedir, 'bin') : nil 35 | 36 | resolved_env['PATH'] = [ 37 | PDK::Util::RubyVersion.bin_path, 38 | File.join(resolved_env['GEM_HOME'], 'bin'), 39 | PDK::Util::RubyVersion.gem_paths_raw.map { |gem_path_raw| File.join(gem_path_raw, 'bin') }, 40 | package_binpath, 41 | PDK::Util::Env['PATH'] 42 | ].compact.flatten.join(File::PATH_SEPARATOR) 43 | 44 | resolved_env.each do |var, val| 45 | puts "export #{var}=\"#{val}\"" 46 | end 47 | exit 0 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/pdk/cli/errors.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module CLI 5 | class FatalError < StandardError 6 | attr_reader :exit_code 7 | 8 | def initialize(msg = 'An unexpected error has occurred. Try running the command again with --debug', opts = {}) 9 | @exit_code = opts.fetch(:exit_code, 1) 10 | super(msg) 11 | end 12 | end 13 | 14 | class ExitWithError < StandardError 15 | attr_reader :exit_code, :log_level 16 | 17 | def initialize(msg, opts = {}) 18 | @exit_code = opts.fetch(:exit_code, 1) 19 | @log_level = opts.fetch(:log_level, :error) 20 | super(msg) 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pdk/cli/get.rb: -------------------------------------------------------------------------------- 1 | module PDK 2 | module CLI 3 | @get_cmd = @base_cmd.define_command do 4 | name 'get' 5 | usage 'get [subcommand] [options]' 6 | summary 'Retrieve information about the PDK or current project.' 7 | default_subcommand 'help' 8 | 9 | run do |_opts, args, _cmd| 10 | if args == ['help'] 11 | PDK::CLI.run(['get', '--help']) 12 | exit 0 13 | end 14 | 15 | PDK::CLI.run(['get', 'help']) if args.empty? 16 | end 17 | end 18 | @get_cmd.add_command Cri::Command.new_basic_help 19 | end 20 | end 21 | 22 | require 'pdk/cli/get/config' 23 | -------------------------------------------------------------------------------- /lib/pdk/cli/get/config.rb: -------------------------------------------------------------------------------- 1 | module PDK 2 | module CLI 3 | @get_config_cmd = @get_cmd.define_command do 4 | name 'config' 5 | usage 'config [name]' 6 | summary 'Retrieve the configuration for . If not specified, retrieve all configuration settings' 7 | 8 | run do |_opts, args, _cmd| 9 | item_name = args[0] 10 | resolved_config = PDK.config.resolve(item_name) 11 | # If the user wanted to know a setting but it doesn't exist, raise an error 12 | if resolved_config.empty? && !item_name.nil? 13 | PDK.logger.error(format("Configuration item '%{name}' does not exist", name: item_name)) 14 | exit 1 15 | end 16 | # If the user requested a setting and it's the only one resolved, then just output the value 17 | if resolved_config.count == 1 && resolved_config.keys[0] == item_name 18 | puts format('%{value}', value: resolved_config.values[0]) 19 | exit 0 20 | end 21 | # Otherwise just output everything 22 | resolved_config.keys.sort.each { |key| puts format('%{name}=%{value}', name: key, value: resolved_config[key]) } 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/pdk/cli/new.rb: -------------------------------------------------------------------------------- 1 | module PDK 2 | module CLI 3 | @new_cmd = @base_cmd.define_command do 4 | name 'new' 5 | usage 'new [options]' 6 | summary 'create a new module, etc.' 7 | description 'Creates a new using relevant options.' 8 | default_subcommand 'help' 9 | end 10 | 11 | @new_cmd.add_command Cri::Command.new_basic_help 12 | end 13 | end 14 | 15 | require 'pdk/cli/new/class' 16 | require 'pdk/cli/new/defined_type' 17 | require 'pdk/cli/new/module' 18 | require 'pdk/cli/new/provider' 19 | require 'pdk/cli/new/task' 20 | require 'pdk/cli/new/test' 21 | require 'pdk/cli/new/transport' 22 | require 'pdk/cli/new/fact' 23 | require 'pdk/cli/new/function' 24 | -------------------------------------------------------------------------------- /lib/pdk/cli/new/class.rb: -------------------------------------------------------------------------------- 1 | module PDK 2 | module CLI 3 | @new_class_cmd = @new_cmd.define_command do 4 | name 'class' 5 | usage 'class [options] ' 6 | summary 'Create a new class named using given options' 7 | 8 | run do |opts, args, _cmd| 9 | require 'pdk/generate/puppet_class' 10 | 11 | PDK::CLI::Util.ensure_in_module!( 12 | message: 'Classes can only be created from inside a valid module directory.', 13 | log_level: :info 14 | ) 15 | 16 | class_name = args[0] 17 | 18 | if class_name.nil? || class_name.empty? 19 | puts command.help 20 | exit 1 21 | end 22 | 23 | raise PDK::CLI::ExitWithError, format("'%{name}' is not a valid class name", name: class_name) unless Util::OptionValidator.valid_class_name?(class_name) 24 | 25 | updates = PDK::Generate::PuppetClass.new(PDK.context, class_name, opts).run 26 | PDK::CLI::Util::UpdateManagerPrinter.print_summary(updates, tense: :past) 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/pdk/cli/new/defined_type.rb: -------------------------------------------------------------------------------- 1 | module PDK 2 | module CLI 3 | @new_define_cmd = @new_cmd.define_command do 4 | name 'defined_type' 5 | usage 'defined_type [options] ' 6 | summary 'Create a new defined type named using given options' 7 | 8 | run do |opts, args, _cmd| 9 | PDK::CLI::Util.ensure_in_module!( 10 | message: 'Defined types can only be created from inside a valid module directory.', 11 | log_level: :info 12 | ) 13 | 14 | defined_type_name = args[0] 15 | 16 | if defined_type_name.nil? || defined_type_name.empty? 17 | puts command.help 18 | exit 1 19 | end 20 | 21 | raise PDK::CLI::ExitWithError, format("'%{name}' is not a valid defined type name", name: defined_type_name) unless Util::OptionValidator.valid_defined_type_name?(defined_type_name) 22 | 23 | require 'pdk/generate/defined_type' 24 | 25 | updates = PDK::Generate::DefinedType.new(PDK.context, defined_type_name, opts).run 26 | PDK::CLI::Util::UpdateManagerPrinter.print_summary(updates, tense: :past) 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/pdk/cli/new/fact.rb: -------------------------------------------------------------------------------- 1 | module PDK 2 | module CLI 3 | @new_fact_cmd = @new_cmd.define_command do 4 | name 'fact' 5 | usage 'fact [options] ' 6 | summary 'Create a new custom fact named using given options' 7 | 8 | run do |opts, args, _cmd| 9 | PDK::CLI::Util.ensure_in_module! 10 | 11 | fact_name = args[0] 12 | 13 | if fact_name.nil? || fact_name.empty? 14 | puts command.help 15 | exit 1 16 | end 17 | 18 | raise PDK::CLI::ExitWithError, format("'%{name}' is not a valid fact name", name: fact_name) unless Util::OptionValidator.valid_fact_name?(fact_name) 19 | 20 | require 'pdk/generate/fact' 21 | 22 | updates = PDK::Generate::Fact.new(PDK.context, fact_name, opts).run 23 | PDK::CLI::Util::UpdateManagerPrinter.print_summary(updates, tense: :past) 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/pdk/cli/new/function.rb: -------------------------------------------------------------------------------- 1 | module PDK 2 | module CLI 3 | @new_function_cmd = @new_cmd.define_command do 4 | name 'function' 5 | usage 'function [options] ' 6 | summary 'Create a new function named using given options' 7 | option :t, :type, 'The function type, (native or v4)', argument: :required, default: 'native' 8 | 9 | run do |opts, args, _cmd| 10 | PDK::CLI::Util.ensure_in_module! 11 | 12 | function_name = args[0] 13 | 14 | if function_name.nil? || function_name.empty? 15 | puts command.help 16 | exit 1 17 | end 18 | 19 | raise PDK::CLI::ExitWithError, format("'%{name}' is not a valid function name", name: function_name) unless Util::OptionValidator.valid_function_name?(function_name) 20 | 21 | require 'pdk/generate/function' 22 | updates = PDK::Generate::Function.new(PDK.context, function_name, opts).run 23 | PDK::CLI::Util::UpdateManagerPrinter.print_summary(updates, tense: :past) 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/pdk/cli/new/module.rb: -------------------------------------------------------------------------------- 1 | module PDK 2 | module CLI 3 | @new_module_cmd = @new_cmd.define_command do 4 | name 'module' 5 | usage 'module [options] [module_name] [target_dir]' 6 | summary 'Create a new module named [module_name] using given options' 7 | 8 | PDK::CLI.template_url_option(self) 9 | PDK::CLI.template_ref_option(self) 10 | PDK::CLI.skip_interview_option(self) 11 | PDK::CLI.full_interview_option(self) 12 | 13 | option nil, 'license', 'Specifies the license this module is written under. ' \ 14 | "This should be a identifier from https://spdx.org/licenses/. Common values are 'Apache-2.0', 'MIT', or 'proprietary'.", argument: :required 15 | option nil, 'skip-bundle-install', 'Do not automatically run `bundle install` after creating the module.', hidden: true 16 | 17 | run do |opts, args, _cmd| 18 | require 'pdk/generate/module' 19 | 20 | module_name = args[0] 21 | target_dir = args[1] 22 | 23 | PDK::CLI::Util.validate_template_opts(opts) 24 | 25 | if opts[:'skip-interview'] && opts[:'full-interview'] 26 | PDK.logger.info 'Ignoring --full-interview and continuing with --skip-interview.' 27 | opts[:'full-interview'] = false 28 | end 29 | 30 | if module_name.nil? || module_name.empty? 31 | if opts[:'skip-interview'] 32 | raise PDK::CLI::ExitWithError, 33 | 'You must specify a module name on the command line when running ' \ 34 | 'with --skip-interview.' 35 | end 36 | else 37 | module_name_parts = module_name.split('-', 2) 38 | if module_name_parts.size > 1 39 | opts[:username] = module_name_parts[0] 40 | opts[:module_name] = module_name_parts[1] 41 | else 42 | opts[:module_name] = module_name 43 | end 44 | opts[:target_dir] = target_dir.nil? ? opts[:module_name] : target_dir 45 | end 46 | 47 | PDK.logger.info(format('Creating new module: %{modname}', modname: module_name)) 48 | PDK::Generate::Module.invoke(opts) 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/pdk/cli/new/provider.rb: -------------------------------------------------------------------------------- 1 | module PDK 2 | module CLI 3 | @new_provider_cmd = @new_cmd.define_command do 4 | name 'provider' 5 | usage 'provider [options] ' 6 | summary '[experimental] Create a new ruby provider named using given options' 7 | 8 | run do |opts, args, _cmd| 9 | PDK::CLI::Util.ensure_in_module! 10 | 11 | provider_name = args[0] 12 | 13 | if provider_name.nil? || provider_name.empty? 14 | puts command.help 15 | exit 1 16 | end 17 | 18 | raise PDK::CLI::ExitWithError, format("'%{name}' is not a valid provider name", name: provider_name) unless Util::OptionValidator.valid_provider_name?(provider_name) 19 | 20 | require 'pdk/generate/provider' 21 | 22 | updates = PDK::Generate::Provider.new(PDK.context, provider_name, opts).run 23 | PDK::CLI::Util::UpdateManagerPrinter.print_summary(updates, tense: :past) 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/pdk/cli/new/task.rb: -------------------------------------------------------------------------------- 1 | module PDK 2 | module CLI 3 | @new_task_cmd = @new_cmd.define_command do 4 | name 'task' 5 | usage 'task [options] ' 6 | summary 'Create a new task named using given options' 7 | 8 | option nil, :description, 'A short description of the purpose of the task', argument: :required 9 | 10 | run do |opts, args, _cmd| 11 | require 'pdk/generate/task' 12 | 13 | PDK::CLI::Util.ensure_in_module!( 14 | message: 'Tasks can only be created from inside a valid module directory.', 15 | log_level: :info 16 | ) 17 | 18 | task_name = args[0] 19 | 20 | if task_name.nil? || task_name.empty? 21 | puts command.help 22 | exit 1 23 | end 24 | 25 | raise PDK::CLI::ExitWithError, format("'%{name}' is not a valid task name", name: task_name) unless Util::OptionValidator.valid_task_name?(task_name) 26 | 27 | updates = PDK::Generate::Task.new(PDK.context, task_name, opts).run 28 | PDK::CLI::Util::UpdateManagerPrinter.print_summary(updates, tense: :past) 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/pdk/cli/new/test.rb: -------------------------------------------------------------------------------- 1 | module PDK 2 | module CLI 3 | @new_define_cmd = @new_cmd.define_command do 4 | name 'test' 5 | usage 'test [options] ' 6 | summary 'Create a new test for the object named ' 7 | flag :u, :unit, 'Create a new unit test.' 8 | PDK::CLI.puppet_version_options(self) 9 | PDK::CLI.puppet_dev_option(self) 10 | 11 | run do |opts, args, _cmd| 12 | require 'pdk/util/puppet_strings' 13 | require 'pdk/util/bundler' 14 | 15 | PDK::CLI::Util.validate_puppet_version_opts(opts) 16 | PDK::CLI::Util.ensure_in_module!( 17 | message: 'Tests can only be created from inside a valid module directory.', 18 | log_level: :info 19 | ) 20 | 21 | object_name = args[0] 22 | 23 | if object_name.nil? || object_name.empty? 24 | puts command.help 25 | exit 1 26 | end 27 | 28 | unless opts[:unit] 29 | # At a future time, we'll replace this conditional with an interactive 30 | # question to choose the test type. 31 | PDK.logger.info 'Test type not specified, assuming unit.' 32 | opts[:unit] = true 33 | end 34 | 35 | puppet_env = PDK::CLI::Util.puppet_from_opts_or_env(opts) 36 | PDK::Util::RubyVersion.use(puppet_env[:ruby_version]) 37 | PDK::Util::Bundler.ensure_bundle!(puppet_env[:gemset]) 38 | 39 | begin 40 | generator, obj = PDK::Util::PuppetStrings.find_object(object_name) 41 | 42 | updates = generator.new(PDK.context, obj['name'], opts.merge(spec_only: true)).run 43 | PDK::CLI::Util::UpdateManagerPrinter.print_summary(updates, tense: :past) 44 | rescue PDK::Util::PuppetStrings::NoObjectError 45 | raise PDK::CLI::ExitWithError, format('Unable to find anything called "%{object}" to generate unit tests for.', object: object_name) 46 | rescue PDK::Util::PuppetStrings::NoGeneratorError => e 47 | raise PDK::CLI::ExitWithError, format('PDK does not support generating unit tests for "%{object_type}" objects.', object_type: e.message) 48 | end 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/pdk/cli/new/transport.rb: -------------------------------------------------------------------------------- 1 | module PDK 2 | module CLI 3 | @new_transport_cmd = @new_cmd.define_command do 4 | name 'transport' 5 | usage 'transport [options] ' 6 | summary '[experimental] Create a new ruby transport named using given options' 7 | 8 | run do |opts, args, _cmd| 9 | PDK::CLI::Util.ensure_in_module! 10 | 11 | transport_name = args[0] 12 | 13 | if transport_name.nil? || transport_name.empty? 14 | puts command.help 15 | exit 1 16 | end 17 | 18 | raise PDK::CLI::ExitWithError, format("'%{name}' is not a valid transport name", name: transport_name) unless Util::OptionValidator.valid_transport_name?(transport_name) 19 | 20 | require 'pdk/generate/transport' 21 | 22 | updates = PDK::Generate::Transport.new(PDK.context, transport_name, opts).run 23 | PDK::CLI::Util::UpdateManagerPrinter.print_summary(updates, tense: :past) 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/pdk/cli/release/prep.rb: -------------------------------------------------------------------------------- 1 | require 'pdk/cli/release' 2 | 3 | module PDK 4 | module CLI 5 | @release_prep_cmd = @release_cmd.define_command do 6 | name 'prep' 7 | usage 'prep [options]' 8 | summary '(Experimental) Performs all the pre-release checks to ensure module is ready to be released' 9 | 10 | flag nil, :force, 'Prepare the module automatically, with no prompts.' 11 | flag nil, :'skip-validation', 'Skips the module validation check.' 12 | flag nil, :'skip-changelog', 'Skips the automatic changelog generation.' 13 | flag nil, :'skip-dependency', 'Skips the module dependency check.' 14 | flag nil, :'skip-documentation', 'Skips the documentation update.' 15 | 16 | option nil, :version, 'Update the module to the specified version prior to release. When not specified, the new version will be computed from the Changelog where possible.', 17 | argument: :required 18 | 19 | run do |opts, _args, cmd| 20 | # Make sure build is being run in a valid module directory with a metadata.json 21 | PDK::CLI::Util.ensure_in_module!( 22 | message: "`pdk release #{cmd.name}` can only be run from inside a valid module with a metadata.json.", 23 | log_level: :info 24 | ) 25 | 26 | opts[:'skip-build'] = true 27 | opts[:'skip-publish'] = true 28 | 29 | Release.prepare_interview(opts) unless opts[:force] 30 | 31 | release = PDK::Module::Release.new(nil, opts) 32 | 33 | Release.module_compatibility_checks!(release, opts) 34 | 35 | release.run 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/pdk/cli/release/publish.rb: -------------------------------------------------------------------------------- 1 | require 'pdk/cli/release' 2 | 3 | module PDK 4 | module CLI 5 | @release_publish_cmd = @release_cmd.define_command do 6 | name 'publish' 7 | usage 'publish [options] ' 8 | summary '(Experimental) Publishes the module to the Forge.' 9 | 10 | flag nil, :force, 'Publish the module automatically, with no prompts.' 11 | 12 | option nil, :'forge-upload-url', 'Set forge upload url path.', 13 | argument: :required, default: 'https://forgeapi.puppetlabs.com/v3/releases' 14 | 15 | option nil, :'forge-token', 'Set Forge API token (you may also set via environment variable PDK_FORGE_TOKEN)', argument: :required 16 | 17 | run do |opts, _args, cmd| 18 | # Make sure build is being run in a valid module directory with a metadata.json 19 | PDK::CLI::Util.ensure_in_module!( 20 | message: "`pdk release #{cmd.name}` can only be run from inside a valid module with a metadata.json.", 21 | log_level: :info 22 | ) 23 | 24 | opts[:'skip-validation'] = true 25 | opts[:'skip-changelog'] = true 26 | opts[:'skip-dependency'] = true 27 | opts[:'skip-documentation'] = true 28 | opts[:'skip-build'] = true 29 | opts[:'skip-versionset'] = true 30 | opts[:force] = true unless PDK::CLI::Util.interactive? 31 | opts[:'forge-token'] ||= PDK::Util::Env['PDK_FORGE_TOKEN'] 32 | 33 | if opts[:'forge-token'].nil? || opts[:'forge-token'].empty? 34 | PDK.logger.error 'You must supply a Forge API token either via `--forge-token` option or PDK_FORGE_TOKEN environment variable.' 35 | exit 1 36 | end 37 | 38 | Release.prepare_publish_interview(TTY::Prompt.new(help_color: :cyan), opts) unless opts[:force] 39 | 40 | release = PDK::Module::Release.new(nil, opts) 41 | 42 | release.run 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/pdk/cli/remove.rb: -------------------------------------------------------------------------------- 1 | module PDK 2 | module CLI 3 | @remove_cmd = @base_cmd.define_command do 4 | name 'remove' 5 | usage 'remove [subcommand] [options]' 6 | summary 'Remove or delete information about the PDK or current project.' 7 | default_subcommand 'help' 8 | 9 | run do |_opts, args, _cmd| 10 | if args == ['help'] 11 | PDK::CLI.run(['remove', '--help']) 12 | exit 0 13 | end 14 | 15 | PDK::CLI.run(['remove', 'help']) if args.empty? 16 | end 17 | end 18 | @remove_cmd.add_command Cri::Command.new_basic_help 19 | end 20 | end 21 | 22 | require 'pdk/cli/remove/config' 23 | -------------------------------------------------------------------------------- /lib/pdk/cli/set.rb: -------------------------------------------------------------------------------- 1 | module PDK 2 | module CLI 3 | @set_cmd = @base_cmd.define_command do 4 | name 'set' 5 | usage 'set [subcommand] [options]' 6 | summary 'Set or update information about the PDK or current project.' 7 | default_subcommand 'help' 8 | 9 | run do |_opts, args, _cmd| 10 | if args == ['help'] 11 | PDK::CLI.run(['set', '--help']) 12 | exit 0 13 | end 14 | 15 | PDK::CLI.run(['set', 'help']) if args.empty? 16 | end 17 | end 18 | @set_cmd.add_command Cri::Command.new_basic_help 19 | end 20 | end 21 | 22 | require 'pdk/cli/set/config' 23 | -------------------------------------------------------------------------------- /lib/pdk/cli/test.rb: -------------------------------------------------------------------------------- 1 | module PDK 2 | module CLI 3 | @test_cmd = @base_cmd.define_command do 4 | name 'test' 5 | usage 'test [subcommand] [options]' 6 | summary 'Run tests.' 7 | default_subcommand 'help' 8 | end 9 | @test_cmd.add_command Cri::Command.new_basic_help 10 | end 11 | end 12 | 13 | require 'pdk/cli/test/unit' 14 | -------------------------------------------------------------------------------- /lib/pdk/cli/update.rb: -------------------------------------------------------------------------------- 1 | module PDK 2 | module CLI 3 | @update_cmd = @base_cmd.define_command do 4 | name 'update' 5 | usage 'update [options]' 6 | summary 'Update a module that has been created by or converted for use by PDK.' 7 | 8 | flag nil, :noop, 'Do not update the module, just output what would be done.' 9 | flag nil, :force, 'Update the module automatically, with no prompts.' 10 | 11 | PDK::CLI.template_ref_option(self) 12 | 13 | run do |opts, _args, _cmd| 14 | # Write the context information to the debug log 15 | PDK.context.to_debug_log 16 | 17 | unless PDK.context.is_a?(PDK::Context::Module) || PDK.context.is_a?(PDK::Context::ControlRepo) 18 | raise PDK::CLI::ExitWithError, '`pdk update` can only be run from inside a valid module directory.' 19 | end 20 | 21 | raise PDK::CLI::ExitWithError, 'This module does not appear to be PDK compatible. To make the module compatible with PDK, run `pdk convert`.' unless PDK::Util.module_pdk_compatible? 22 | 23 | raise PDK::CLI::ExitWithError, 'You can not specify --noop and --force when updating a module' if opts[:noop] && opts[:force] 24 | 25 | if Gem::Version.new(PDK::VERSION) < Gem::Version.new(PDK::Util.module_pdk_version) 26 | PDK.logger.warn "This module has been updated to PDK #{PDK::Util.module_pdk_version} which is newer than your PDK version (#{PDK::VERSION}), proceed with caution!" 27 | 28 | unless opts[:force] 29 | raise PDK::CLI::ExitWithError, 30 | 'Please update your PDK installation and try again. ' \ 31 | 'You may also use the --force flag to override this and ' \ 32 | 'continue at your own risk.' 33 | end 34 | end 35 | 36 | updater = PDK::Module::Update.new(PDK.context.root_path, opts) 37 | 38 | if updater.pinned_to_puppetlabs_template_tag? 39 | PDK.logger.info format('This module is currently pinned to version %{current_version} ' \ 40 | 'of the default template. If you would like to update your ' \ 41 | 'module to the latest version of the template, please run `pdk ' \ 42 | 'update --template-ref %{new_version}`.', current_version: updater.template_uri.uri_fragment, new_version: PDK::TEMPLATE_REF) 43 | end 44 | 45 | updater.run 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/pdk/cli/util/command_redirector.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | require 'tty/prompt' 3 | 4 | module PDK 5 | module CLI 6 | module Util 7 | class CommandRedirector < TTY::Prompt::AnswersCollector 8 | attr_accessor :command 9 | 10 | # Override the initialize method because the original one 11 | # doesn't work with Ruby 3. 12 | # rubocop:disable Lint/MissingSuper 13 | def initialize(prompt, options = {}) 14 | @prompt = prompt 15 | @answers = options.fetch(:answers) { {} } 16 | end 17 | # rubocop:enable Lint/MissingSuper 18 | 19 | def pastel 20 | @pastel ||= Pastel.new 21 | end 22 | 23 | def target_command(cmd) 24 | @command = cmd 25 | end 26 | 27 | def run 28 | @prompt.puts "Did you mean '#{pastel.bold(@command)}'?" 29 | @prompt.yes?('-->') 30 | rescue PDK::CLI::Util::Interview::READER::InputInterrupt 31 | nil 32 | end 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/pdk/cli/util/option_normalizer.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module CLI 5 | module Util 6 | class OptionNormalizer 7 | def self.comma_separated_list_to_array(list, _options = {}) 8 | raise 'Error: expected comma separated list' unless OptionValidator.comma_separated_list?(list) 9 | 10 | list.split(',').compact 11 | end 12 | 13 | # Parse one or more format:target pairs into report format 14 | # specifications. 15 | # 16 | # Each specification is a Hash with two values: 17 | # :method => The name of the method to call on the PDK::Report object 18 | # to render the report. 19 | # :target => The target to write the report to. This can be either an 20 | # IO object that implements #write, or a String filename 21 | # that will be opened for writing. 22 | # 23 | # If the target given is "stdout" or "stderr", this will convert those 24 | # strings into the appropriate IO object. 25 | # 26 | # @return [ArrayObject}>] An array of one or more report 27 | # format specifications 28 | def self.report_formats(formats) 29 | formats.map do |f| 30 | format, target = f.split(':', 2) 31 | 32 | begin 33 | OptionValidator.enum(format, PDK::Report.formats) 34 | rescue ArgumentError 35 | raise PDK::CLI::ExitWithError, format("'%{name}' is not a valid report format (%{valid})", name: format, valid: PDK::Report.formats.join(', ')) 36 | end 37 | 38 | case target 39 | when 'stdout' 40 | target = $stdout 41 | when 'stderr' 42 | target = $stderr 43 | when nil 44 | target = PDK::Report.default_target 45 | end 46 | 47 | { method: :"write_#{format}", target: } 48 | end 49 | end 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/pdk/cli/util/spinner.rb: -------------------------------------------------------------------------------- 1 | require 'tty-spinner' 2 | 3 | # Replace the built-in tty check in tty-spinner with our own implementation 4 | # that allows us to mock the behaviour during acceptance tests. 5 | module TTY 6 | class Spinner 7 | def tty? 8 | require 'pdk/cli/util' 9 | 10 | PDK::CLI::Util.interactive? 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/pdk/config/errors.rb: -------------------------------------------------------------------------------- 1 | module PDK 2 | class Config 3 | class LoadError < StandardError; end 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/pdk/config/ini_file_setting.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | class Config 5 | class IniFileSetting < PDK::Config::Setting 6 | # Initialises the PDK::Config::JSONSchemaSetting object. 7 | # 8 | # @see PDK::Config::Setting.initialize 9 | def initialize(_name, namespace, initial_value = nil) 10 | raise 'The IniFileSetting object can only be created within the IniFile Namespace' unless namespace.is_a?(PDK::Config::IniFile) 11 | 12 | super 13 | validate!(initial_value) unless initial_value.nil? 14 | end 15 | 16 | # Verifies that the new setting value is valid in an Ini File 17 | # 18 | # @see PDK::Config::Setting.validate! 19 | def validate!(value) 20 | # We're very restrictive here. Realistically Ini files only have string types 21 | return if value.nil? || value.is_a?(String) || value.is_a?(Integer) 22 | 23 | # The only other valid-ish type is a Hash 24 | raise ArgumentError, format('The setting %{key} may only be a String or Integer, not %{class}', key: qualified_name, class: value.class) unless value.is_a?(Hash) 25 | 26 | # Any hashes can only have a single String/Integer value 27 | value.each do |child_name, child_value| 28 | next if child_value.nil? || child_value.is_a?(String) || child_value.is_a?(Integer) 29 | 30 | raise ArgumentError, format('The setting %{key} may only be a String or Integer, not %{class}', key: "#{qualified_name}.#{child_name}", class: child_value.class) 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/pdk/config/json.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | class Config 5 | class JSON < Namespace 6 | # Parses a JSON document. 7 | # 8 | # @see PDK::Config::Namespace.parse_file 9 | def parse_file(filename) 10 | raise unless block_given? 11 | 12 | data = load_data(filename) 13 | return if data.nil? || data.empty? 14 | 15 | require 'json' 16 | 17 | data = ::JSON.parse(data) 18 | return if data.nil? || data.empty? 19 | 20 | data.each { |k, v| yield k, PDK::Config::Setting.new(k, self, v) } 21 | rescue ::JSON::ParserError => e 22 | raise PDK::Config::LoadError, e.message 23 | end 24 | 25 | # Serializes object data into a JSON string. 26 | # 27 | # @see PDK::Config::Namespace.serialize_data 28 | def serialize_data(data) 29 | require 'json' 30 | 31 | ::JSON.pretty_generate(data) 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/pdk/config/json_schema_setting.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | class Config 5 | class JSONSchemaSetting < PDK::Config::Setting 6 | # Initialises the PDK::Config::JSONSchemaSetting object. 7 | # 8 | # @see PDK::Config::Setting.initialize 9 | def initialize(_name, namespace, _initial_value) 10 | raise 'The JSONSchemaSetting object can only be created within the JSONSchemaNamespace' unless namespace.is_a?(PDK::Config::JSONSchemaNamespace) 11 | 12 | super 13 | end 14 | 15 | # Verifies that the new setting value is valid by calling the JSON schema validator on 16 | # a hash which includes the new setting 17 | # 18 | # @see PDK::Config::Setting.validate! 19 | def validate!(value) 20 | # Get the existing namespace data 21 | new_document = namespace.to_h 22 | # ... set the new value 23 | new_document[@name] = value 24 | begin 25 | # ... add validate it 26 | namespace.validate_document!(new_document) 27 | rescue ::JSON::Schema::ValidationError => e 28 | raise ArgumentError, format('%{key} %{message}', key: qualified_name, message: e.message) 29 | end 30 | end 31 | 32 | # Evaluate the default setting, firstly from the JSON schema and then 33 | # from any other default evaluators in the settings chain. 34 | # 35 | # @see PDK::Config::Setting.default 36 | # 37 | # @return [Object, nil] the result of evaluating the block given to 38 | # {#default_to}, or `nil` if the setting has no default. 39 | def default 40 | # Return the default from the schema document if it exists 41 | if namespace.schema_property_names.include?(@name) 42 | prop_schema = namespace.schema['properties'][@name] 43 | return prop_schema['default'] unless prop_schema['default'].nil? 44 | end 45 | # ... otherwise call the settings chain default 46 | # and if that doesn't exist, just return nil 47 | @previous_setting&.default 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/pdk/config/json_with_schema.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | class Config 5 | class JSONWithSchema < JSONSchemaNamespace 6 | # Parses a JSON document with a schema. 7 | # 8 | # @see PDK::Config::Namespace.parse_file 9 | def parse_file(filename) 10 | raise unless block_given? 11 | 12 | data = load_data(filename) 13 | data = '{}' if data.nil? || data.empty? 14 | require 'json' 15 | 16 | @raw_data = ::JSON.parse(data) 17 | @raw_data = {} if @raw_data.nil? 18 | 19 | begin 20 | # Ensure the parsed document is actually valid 21 | validate_document!(@raw_data) 22 | rescue ::JSON::Schema::ValidationError => e 23 | raise PDK::Config::LoadError, format('The configuration file %{filename} is not valid: %{message}', filename:, message: e.message) 24 | end 25 | 26 | schema_property_names.each do |key| 27 | yield key, PDK::Config::JSONSchemaSetting.new(key, self, @raw_data[key]) 28 | end 29 | 30 | # Remove all of the "known" settings from the schema and 31 | # we're left with the settings that we don't manage. 32 | self.unmanaged_settings = @raw_data.except(*schema_property_names) 33 | rescue ::JSON::ParserError => e 34 | raise PDK::Config::LoadError, e.message 35 | end 36 | 37 | # Serializes object data into a JSON string. 38 | # 39 | # @see PDK::Config::Namespace.serialize_data 40 | def serialize_data(data) 41 | require 'json' 42 | 43 | ::JSON.pretty_generate(data) 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/pdk/config/validator.rb: -------------------------------------------------------------------------------- 1 | module PDK 2 | class Config 3 | # A collection of predefined validators for use with {PDK::Config::Value}. 4 | # 5 | # @example 6 | # value :enabled do 7 | # validate PDK::Config::Validator.boolean 8 | # end 9 | module Validator 10 | # @return [Hash{Symbol => [Proc,String]}] a {PDK::Config::Value} 11 | # validator that ensures that the value is either a TrueClass or 12 | # FalseClass. 13 | def self.boolean 14 | { 15 | proc: ->(value) { [true, false].include?(value) }, 16 | message: 'must be a boolean: true or false' 17 | } 18 | end 19 | 20 | # @return [Hash{Symbol => [Proc,String]}] a {PDK::Config::Value} 21 | # validator that ensures that the value is a String that matches the 22 | # regex for a version 4 UUID. 23 | def self.uuid 24 | { 25 | proc: ->(value) { value.match(/\A\h{8}(?:-\h{4}){3}-\h{12}\z/) }, 26 | message: 'must be a version 4 UUID' 27 | } 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/pdk/config/yaml.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | class Config 5 | # Parses a YAML document. 6 | # 7 | # @see PDK::Config::Namespace.parse_file 8 | class YAML < Namespace 9 | def parse_file(filename) 10 | raise unless block_given? 11 | 12 | data = load_data(filename) 13 | return if data.nil? || data.empty? 14 | 15 | require 'yaml' 16 | 17 | data = if Gem::Version.new(Psych::VERSION) >= Gem::Version.new('3.1.0.pre1') 18 | ::YAML.safe_load(data, permitted_classes: [Symbol], permitted_symbols: [], aliases: true) 19 | else 20 | ::YAML.safe_load(data, [Symbol], [], true) 21 | end 22 | return if data.nil? 23 | 24 | data.each { |k, v| yield k, PDK::Config::Setting.new(k, self, v) } 25 | rescue Psych::SyntaxError => e 26 | raise PDK::Config::LoadError, format('Syntax error when loading %{file}: %{error}', file: filename, error: "#{e.problem} #{e.context}") 27 | rescue Psych::DisallowedClass => e 28 | raise PDK::Config::LoadError, format('Unsupported class in %{file}: %{error}', file: filename, error: e.message) 29 | end 30 | 31 | # Serializes object data into a YAML string. 32 | # 33 | # @see PDK::Config::Namespace.serialize_data 34 | def serialize_data(data) 35 | require 'yaml' 36 | 37 | ::YAML.dump(data) 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/pdk/config/yaml_with_schema.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | class Config 5 | # Parses a YAML document with a JSON schema. 6 | # 7 | # @see PDK::Config::Namespace.parse_file 8 | class YAMLWithSchema < JSONSchemaNamespace 9 | def parse_file(filename) 10 | raise unless block_given? 11 | 12 | data = load_data(filename) 13 | data = '' if data.nil? 14 | require 'yaml' 15 | require 'json-schema' 16 | 17 | @raw_data = ::YAML.safe_load(data, [Symbol], [], true) 18 | @raw_data = {} if @raw_data.nil? 19 | 20 | begin 21 | # Ensure the parsed document is actually valid 22 | validate_document!(@raw_data) 23 | rescue ::JSON::Schema::ValidationError => e 24 | raise PDK::Config::LoadError, format('The configuration file %{filename} is not valid: %{message}', filename:, message: e.message) 25 | end 26 | 27 | require 'pdk/config/json_schema_setting' 28 | 29 | schema_property_names.each do |key| 30 | yield key, PDK::Config::JSONSchemaSetting.new(key, self, @raw_data[key]) 31 | end 32 | 33 | # Remove all of the "known" settings from the schema and 34 | # we're left with the settings that we don't manage. 35 | self.unmanaged_settings = @raw_data.except(*schema_property_names) 36 | rescue Psych::SyntaxError => e 37 | raise PDK::Config::LoadError, format('Syntax error when loading %{file}: %{error}', file: filename, error: "#{e.problem} #{e.context}") 38 | rescue Psych::DisallowedClass => e 39 | raise PDK::Config::LoadError, format('Unsupported class in %{file}: %{error}', file: filename, error: e.message) 40 | end 41 | 42 | # Serializes object data into a YAML string. 43 | # 44 | # @see PDK::Config::Namespace.serialize_data 45 | def serialize_data(data) 46 | require 'yaml' 47 | ::YAML.dump(data) 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/pdk/context/module.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module Context 5 | # Represents a context for a Puppet Module 6 | class Module < PDK::Context::AbstractContext 7 | # @param module_root [String] The root path for the module. 8 | # @param context_path [String] The path where this context was created from e.g. Dir.pwd 9 | # @see PDK::Context::AbstractContext 10 | def initialize(module_root, context_path) 11 | super(context_path) 12 | @root_path = module_root 13 | end 14 | 15 | # @see PDK::Context::AbstractContext.pdk_compatible? 16 | def pdk_compatible? 17 | PDK::Util.module_pdk_compatible?(root_path) 18 | end 19 | 20 | # :nocov: 21 | # @see PDK::Context::AbstractContext.display_name 22 | def display_name 23 | 'a Puppet Module context' 24 | end 25 | # :nocov: 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/pdk/context/none.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module Context 5 | # Represents a context which the PDK does not know. For example 6 | # an empty directory 7 | class None < PDK::Context::AbstractContext 8 | # :nocov: 9 | # @see PDK::Context::AbstractContext.display_name 10 | def display_name 11 | 'an unknown context' 12 | end 13 | # :nocov: 14 | 15 | # @see PDK::Context::AbstractContext.parent_context 16 | def parent_context 17 | # An unknown context has no parent 18 | nil 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/pdk/generate.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module Generate 5 | autoload :DefinedType, 'pdk/generate/defined_type' 6 | autoload :Module, 'pdk/generate/module' 7 | autoload :Provider, 'pdk/generate/provider' 8 | autoload :PuppetClass, 'pdk/generate/puppet_class' 9 | autoload :PuppetObject, 'pdk/generate/puppet_object' 10 | autoload :Task, 'pdk/generate/task' 11 | autoload :Transport, 'pdk/generate/transport' 12 | 13 | def generators 14 | @generators ||= [ 15 | PDK::Generate::DefinedType, 16 | PDK::Generate::Provider, 17 | PDK::Generate::PuppetClass, 18 | PDK::Generate::Task, 19 | PDK::Generate::Transport 20 | ].freeze 21 | end 22 | module_function :generators 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pdk/generate/defined_type.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module Generate 5 | class DefinedType < PuppetObject 6 | PUPPET_STRINGS_TYPE = 'defined_types'.freeze 7 | 8 | def initialize(*_args) 9 | super 10 | object_name_parts = @object_name.split('::') 11 | 12 | @object_name = if object_name_parts.first == module_name 13 | object_name 14 | else 15 | [module_name, object_name].join('::') 16 | end 17 | end 18 | 19 | def friendly_name 20 | 'Defined Type'.freeze 21 | end 22 | 23 | def template_files 24 | # Calculate the defined type tests name 25 | define_name_parts = object_name.split('::') 26 | # drop the module name if the object name contains multiple parts 27 | define_name_parts.delete_at(0) if define_name_parts.length > 1 28 | files = { 'defined_type_spec.erb' => "#{File.join('spec', 'defines', *define_name_parts)}_spec.rb" } 29 | return files if spec_only? 30 | 31 | define_name_parts = object_name.split('::')[1..] 32 | define_name_parts << 'init' if define_name_parts.empty? 33 | files['defined_type.erb'] = "#{File.join('manifests', *define_name_parts)}.pp" 34 | 35 | files 36 | end 37 | 38 | def template_data 39 | { name: object_name } 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/pdk/generate/fact.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module Generate 5 | class Fact < PuppetObject 6 | def friendly_name 7 | 'Custom Fact'.freeze 8 | end 9 | 10 | def template_files 11 | files = { 12 | 'fact_spec.erb' => "#{File.join('spec', 'unit', 'facter', object_name)}_spec.rb" 13 | } 14 | return files if spec_only? 15 | 16 | files.merge( 17 | 'fact.erb' => "#{File.join('lib', 'facter', object_name)}.rb" 18 | ) 19 | end 20 | 21 | def template_data 22 | { name: object_name } 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/pdk/generate/function.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module Generate 5 | class Function < PuppetObject 6 | def initialize(*_args) 7 | super 8 | object_name_parts = @object_name.split('::') 9 | 10 | @object_name = if object_name_parts.first == module_name 11 | object_name 12 | else 13 | [module_name, object_name].join('::') 14 | end 15 | end 16 | 17 | def friendly_name 18 | 'Function'.freeze 19 | end 20 | 21 | def template_files 22 | # Calculate the function tests name 23 | func_name_parts = object_name.split('::') 24 | # Drop the module name if the object name contains multiple parts 25 | func_name_parts.delete_at(0) if func_name_parts.length > 1 26 | files = { 27 | File.join('functions', 'function_spec.erb') => "#{File.join('spec', 'functions', *func_name_parts)}_spec.rb" 28 | } 29 | return files if spec_only? 30 | 31 | func_name_parts = object_name.split('::')[1..] 32 | template_file = File.join('functions', "#{options[:type]}_function.erb") 33 | 34 | files[template_file] = if options[:type].eql?('v4') 35 | "#{File.join('lib', 'puppet', 'functions', module_name, *func_name_parts)}.rb" 36 | else 37 | "#{File.join('functions', *func_name_parts)}.pp" 38 | end 39 | files 40 | end 41 | 42 | def template_data 43 | func_name = object_name.split('::').last 44 | namespace = object_name.split('::')[0...-1].join('::') 45 | { name: object_name, func_name:, namespace: } 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/pdk/generate/provider.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module Generate 5 | class Provider < PuppetObject 6 | def friendly_name 7 | 'Resource API Provider'.freeze 8 | end 9 | 10 | def template_files 11 | files = { 12 | 'provider_spec.erb' => "#{File.join('spec', 'unit', 'puppet', 'provider', object_name, object_name)}_spec.rb", 13 | 'provider_type_spec.erb' => "#{File.join('spec', 'unit', 'puppet', 'type', object_name)}_spec.rb" 14 | } 15 | return files if spec_only? 16 | 17 | files.merge( 18 | 'provider.erb' => "#{File.join('lib', 'puppet', 'provider', object_name, object_name)}.rb", 19 | 'provider_type.erb' => "#{File.join('lib', 'puppet', 'type', object_name)}.rb" 20 | ) 21 | end 22 | 23 | def template_data 24 | { name: object_name, 25 | provider_class: class_name_from_object_name(object_name) } 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/pdk/generate/puppet_class.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module Generate 5 | class PuppetClass < PuppetObject 6 | PUPPET_STRINGS_TYPE = 'puppet_classes'.freeze 7 | 8 | def initialize(*_args) 9 | super 10 | object_name_parts = @object_name.split('::') 11 | 12 | @object_name = if object_name_parts.first == module_name 13 | object_name 14 | else 15 | [module_name, object_name].join('::') 16 | end 17 | end 18 | 19 | def friendly_name 20 | 'Puppet Class'.freeze 21 | end 22 | 23 | def template_files 24 | # Calculate the class tests name 25 | class_name_parts = object_name.split('::') 26 | # Drop the module name if the object name contains multiple parts 27 | class_name_parts.delete_at(0) if class_name_parts.length > 1 28 | files = { 'class_spec.erb' => "#{File.join('spec', 'classes', *class_name_parts)}_spec.rb" } 29 | return files if spec_only? 30 | 31 | class_name_parts = object_name.split('::')[1..] 32 | class_name_parts << 'init' if class_name_parts.empty? 33 | files['class.erb'] = "#{File.join('manifests', *class_name_parts)}.pp" 34 | 35 | files 36 | end 37 | 38 | def template_data 39 | { name: object_name } 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/pdk/generate/task.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module Generate 5 | class Task < PuppetObject 6 | def friendly_name 7 | 'Task' 8 | end 9 | 10 | def template_files 11 | return {} if spec_only? 12 | 13 | { 14 | 'task.erb' => File.join('tasks', "#{task_name}.sh") 15 | } 16 | end 17 | 18 | def template_data 19 | { 20 | name: object_name 21 | } 22 | end 23 | 24 | # Checks that the task has not already been defined with a different 25 | # extension. 26 | # 27 | # @raise [PDK::CLI::ExitWithError] if files with the same name as the 28 | # task exist in the /tasks/ directory 29 | def check_preconditions 30 | super 31 | 32 | error = "A task named '%{name}' already exists in this module; defined in %{file}" 33 | allowed_extensions = ['.md', '.conf'] 34 | 35 | PDK::Util::Filesystem.glob(File.join(context.root_path, 'tasks', "#{task_name}.*")).each do |file| 36 | next if allowed_extensions.include?(File.extname(file)) 37 | 38 | raise PDK::CLI::ExitWithError, format(error, name: task_name, file:) 39 | end 40 | end 41 | 42 | def non_template_files 43 | task_metadata_file = File.join('tasks', "#{task_name}.json") 44 | { task_metadata_file => JSON.pretty_generate(task_metadata) } 45 | end 46 | 47 | private 48 | 49 | # Calculates the file name of the task files ('init' if the task has the 50 | # same name as the module, otherwise use the specified task name). 51 | # 52 | # @return [String] the base name of the file(s) for the task. 53 | # 54 | # @api private 55 | def task_name 56 | object_name == module_name ? 'init' : object_name 57 | end 58 | 59 | def task_metadata 60 | { 61 | puppet_task_version: 1, 62 | supports_noop: false, 63 | description: options.fetch(:description, 'A short description of this task'), 64 | parameters: {} 65 | } 66 | end 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/pdk/generate/transport.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module Generate 5 | class Transport < PuppetObject 6 | def friendly_name 7 | 'Resource API Transport'.freeze 8 | end 9 | 10 | def template_files 11 | # NOTE: Due to how the V1 templates work, the names of the source template files may be mismatched to 12 | # their destination, e.g. transport_type.erb is really a transport schema 13 | files = { 14 | 'transport_spec.erb' => "#{File.join('spec', 'unit', 'puppet', 'transport', object_name)}_spec.rb", 15 | 'transport_type_spec.erb' => "#{File.join('spec', 'unit', 'puppet', 'transport', 'schema', object_name)}_spec.rb" 16 | } 17 | return files if spec_only? 18 | 19 | files.merge( 20 | 'transport.erb' => "#{File.join('lib', 'puppet', 'transport', object_name)}.rb", 21 | 'transport_device.erb' => File.join('lib', 'puppet', 'util', 'network_device', object_name, 'device.rb'), 22 | 'transport_type.erb' => "#{File.join('lib', 'puppet', 'transport', 'schema', object_name)}.rb" 23 | ) 24 | end 25 | 26 | def template_data 27 | { 28 | name: object_name, 29 | transport_class: class_name_from_object_name(object_name) 30 | } 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/pdk/logger.rb: -------------------------------------------------------------------------------- 1 | require 'logger' 2 | require 'pdk' 3 | 4 | module PDK 5 | class Logger < ::Logger 6 | WRAP_COLUMN_LIMIT = 78 7 | 8 | def initialize 9 | super($stderr) 10 | @sent_messages = {} 11 | 12 | # TODO: Decide on output format. 13 | self.formatter = proc do |severity, _datetime, _progname, msg| 14 | prefix = "pdk (#{severity}): " 15 | if msg.is_a?(Hash) 16 | if msg.fetch(:wrap, false) 17 | wrap_pattern = /(.{1,#{WRAP_COLUMN_LIMIT - prefix.length}})(\s+|\Z)/ 18 | "#{prefix}#{msg[:text].gsub(wrap_pattern, "\\1\n#{' ' * prefix.length}")}\n" 19 | else 20 | "#{prefix}#{msg[:text]}\n" 21 | end 22 | else 23 | "#{prefix}#{msg}\n" 24 | end 25 | end 26 | 27 | self.level = ::Logger::INFO 28 | end 29 | 30 | def warn_once(*args) 31 | hash = args.inspect.hash 32 | return if (@sent_messages[::Logger::WARN] ||= {}).key?(hash) 33 | 34 | @sent_messages[::Logger::WARN][hash] = true 35 | warn(*args) 36 | end 37 | 38 | def enable_debug_output 39 | self.level = ::Logger::DEBUG 40 | end 41 | 42 | def debug? 43 | level == ::Logger::DEBUG 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/pdk/module.rb: -------------------------------------------------------------------------------- 1 | module PDK 2 | module Module 3 | autoload :Build, 'pdk/module/build' 4 | autoload :Convert, 'pdk/module/convert' 5 | autoload :Metadata, 'pdk/module/metadata' 6 | autoload :Release, 'pdk/module/release' 7 | autoload :UpdateManager, 'pdk/module/update_manager' 8 | autoload :Update, 'pdk/module/update' 9 | 10 | DEFAULT_IGNORED = [ 11 | '/pkg/', 12 | '~*', 13 | '/coverage', 14 | '/checksums.json', 15 | '/REVISION', 16 | '/spec/fixtures/modules/', 17 | '/vendor/', 18 | '.DS_Store' 19 | ].freeze 20 | 21 | def default_ignored_pathspec(ignore_dotfiles = true) 22 | require 'pathspec' 23 | 24 | PathSpec.new(DEFAULT_IGNORED).tap do |ps| 25 | ps.add('.*') if ignore_dotfiles 26 | end 27 | end 28 | module_function :default_ignored_pathspec 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/pdk/template.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module Template 5 | autoload :Fetcher, 'pdk/template/fetcher' 6 | autoload :Renderer, 'pdk/template/renderer' 7 | autoload :TemplateDir, 'pdk/template/template_dir' 8 | 9 | MODULE_TEMPLATE_TYPE = :module_template 10 | 11 | # Creates a TemplateDir object with the path or URL to the template 12 | # and the block of code to run to be run while the template is available. 13 | # 14 | # The template directory is only guaranteed to be available on disk 15 | # within the scope of the block passed to this method. 16 | # 17 | # @param uri [PDK::Util::TemplateURI] The path to a directory to use as the 18 | # template or a URI to a git repository. 19 | # 20 | # @param context [PDK::Context::AbstractContext] The context in which the template will render to 21 | # 22 | # @yieldparam self [PDK::Template::TemplateDir] The initialised object with 23 | # the template available on disk. 24 | # 25 | # @example Using a git repository as a template 26 | # PDK::Template.with('https://github.com/puppetlabs/pdk-templates') do |t| 27 | # t.render_module('module, PDK.context) do |filename, content, status| 28 | # File.open(filename, 'w') do |file| 29 | # ... 30 | # end 31 | # end 32 | # end 33 | # 34 | # @raise [ArgumentError] If no block is given to this method. 35 | # @raise [PDK::CLI::FatalError] 36 | # @raise [ArgumentError] 37 | # 38 | # @api public 39 | def self.with(uri, context) 40 | raise ArgumentError, format('%{class_name}.with must be passed a block.', class_name: name) unless block_given? 41 | raise ArgumentError, format('%{class_name}.with must be passed a PDK::Util::TemplateURI, got a %{uri_type}', uri_type: uri.class, class_name: name) unless uri.is_a? PDK::Util::TemplateURI 42 | 43 | Fetcher.with(uri) do |fetcher| 44 | template_dir = TemplateDir.instance(uri, fetcher.path, context) 45 | template_dir.metadata = fetcher.metadata 46 | 47 | yield template_dir 48 | end 49 | nil 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/pdk/template/fetcher/local.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module Template 5 | module Fetcher 6 | class Local < PDK::Template::Fetcher::AbstractFetcher 7 | # Whether the passed uri is fetchable. This is a catch-all and all URIs 8 | # are considered on-disk already. 9 | # 10 | # @see PDK::Template::Fetcher.instance 11 | # @return [Boolean] 12 | def self.fetchable?(_uri, _options = {}) 13 | true 14 | end 15 | 16 | # @see PDK::Template::Fetcher::AbstractTemplateFetcher.fetch! 17 | def fetch! 18 | return if fetched 19 | 20 | super 21 | 22 | @path = uri.shell_path 23 | @temporary = false 24 | @metadata['template-url'] = uri.bare_uri 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/pdk/template/renderer/v1.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module Template 5 | module Renderer 6 | module V1 7 | autoload :LegacyTemplateDir, 'pdk/template/renderer/v1/legacy_template_dir' 8 | autoload :Renderer, 'pdk/template/renderer/v1/renderer' 9 | autoload :TemplateFile, 'pdk/template/renderer/v1/template_file' 10 | 11 | # Whether the template directory and context are valid for the V1 renderer 12 | # @see PDK::Template::Renderer.instance 13 | def self.compatible?(template_root, _context) 14 | ['moduleroot', 'moduleroot_init'].all? { |dir| PDK::Util::Filesystem.directory?(File.join(template_root, dir)) } 15 | end 16 | 17 | # Creates an instance of the V1 Renderer 18 | # @see PDK::Template::Renderer.instance 19 | def self.instance(template_root, template_uri, context) 20 | PDK::Template::Renderer::V1::Renderer.new(template_root, template_uri, context) 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/pdk/util/env.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | require 'forwardable' 3 | 4 | module PDK 5 | module Util 6 | class Env 7 | class WindowsEnv 8 | extend Forwardable 9 | 10 | # Note, these delegators may not have case insensitive keys 11 | def_delegators :env_hash, :fetch, :select, :reject 12 | 13 | def []=(key, value) 14 | PDK::Util::Windows::Process.set_environment_variable(key, value) 15 | end 16 | 17 | def key?(key) 18 | !env_hash.keys.find { |item| key.casecmp(item).zero? }.nil? 19 | end 20 | 21 | def [](key) 22 | env_hash.each do |item, value| 23 | next unless key.casecmp(item).zero? 24 | 25 | return value 26 | end 27 | nil 28 | end 29 | 30 | private 31 | 32 | def env_hash 33 | PDK::Util::Windows::Process.environment_hash 34 | end 35 | end 36 | 37 | class << self 38 | extend Forwardable 39 | 40 | def_delegators :implementation, :key?, :[], :[]=, :fetch, :select, :reject 41 | 42 | def implementation 43 | @implementation ||= Gem.win_platform? ? WindowsEnv.new : ENV 44 | end 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/pdk/util/version.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module Util 5 | module Version 6 | def self.version_string 7 | require 'pdk/version' 8 | 9 | "#{PDK::VERSION} #{pdk_ref}".strip.freeze 10 | end 11 | 12 | def self.pdk_ref 13 | ref = "#{pkg_sha} #{git_ref}".strip 14 | ref.empty? ? nil : "(#{ref})" 15 | end 16 | 17 | def self.pkg_sha 18 | if version_file && PDK::Util::Filesystem.exist?(version_file) 19 | ver = PDK::Util::Filesystem.read_file(version_file) 20 | sha = ver.strip.split('.')[5] unless ver.nil? 21 | end 22 | 23 | sha 24 | end 25 | 26 | def self.git_ref 27 | require 'pdk/util/git' 28 | source_git_dir = File.join(PDK::Util::Filesystem.expand_path('../../..', File.dirname(__FILE__)), '.git') 29 | 30 | return unless PDK::Util::Filesystem.directory?(source_git_dir) 31 | 32 | PDK::Util::Git.describe(source_git_dir) 33 | end 34 | 35 | def self.version_file 36 | require 'pdk/util' 37 | 38 | # FIXME: this gets called a LOT and doesn't currently get cached 39 | PDK::Util.find_upwards('PDK_VERSION', File.dirname(__FILE__)) 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/pdk/util/windows.rb: -------------------------------------------------------------------------------- 1 | module PDK 2 | module Util 3 | module Windows 4 | WIN32_FALSE = 0 5 | module File; end 6 | 7 | if Gem.win_platform? 8 | require 'pdk/util/windows/api_types' 9 | require 'pdk/util/windows/string' 10 | require 'pdk/util/windows/file' 11 | require 'pdk/util/windows/process' 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/pdk/util/windows/file.rb: -------------------------------------------------------------------------------- 1 | require 'pdk/util/windows' 2 | 3 | module PDK 4 | module Util 5 | module Windows 6 | module File 7 | require 'ffi' 8 | extend FFI::Library 9 | extend PDK::Util::Windows::String 10 | 11 | def get_long_pathname(path) 12 | converted = '' 13 | FFI::Pointer.from_string_to_wide_string(path) do |path_ptr| 14 | # includes terminating NULL 15 | buffer_size = GetLongPathNameW(path_ptr, FFI::Pointer::NULL, 0) 16 | FFI::MemoryPointer.new(:wchar, buffer_size) do |converted_ptr| 17 | raise 'Failed to call GetLongPathName' if GetLongPathNameW(path_ptr, converted_ptr, buffer_size) == PDK::Util::Windows::WIN32_FALSE 18 | 19 | converted = converted_ptr.read_wide_string(buffer_size - 1) 20 | end 21 | end 22 | 23 | converted 24 | end 25 | module_function :get_long_pathname 26 | 27 | ffi_convention :stdcall 28 | 29 | # https://msdn.microsoft.com/en-us/library/windows/desktop/aa364980(v=vs.85).aspx 30 | # DWORD WINAPI GetLongPathName( 31 | # _In_ LPCTSTR lpszShortPath, 32 | # _Out_ LPTSTR lpszLongPath, 33 | # _In_ DWORD cchBuffer 34 | # ); 35 | ffi_lib :kernel32 36 | attach_function :GetLongPathNameW, [:lpcwstr, :lpwstr, :dword], :dword 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/pdk/util/windows/string.rb: -------------------------------------------------------------------------------- 1 | require 'pdk/util/windows' 2 | 3 | module PDK 4 | module Util 5 | module Windows 6 | module String 7 | def wide_string(str) 8 | # if given a nil string, assume caller wants to pass a nil pointer to win32 9 | return if str.nil? 10 | 11 | # ruby (< 2.1) does not respect multibyte terminators, so it is possible 12 | # for a string to contain a single trailing null byte, followed by garbage 13 | # causing buffer overruns. 14 | # 15 | # See http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?revision=41920&view=revision 16 | newstr = str + "\0".encode(str.encoding) 17 | newstr.encode!('UTF-16LE') 18 | end 19 | module_function :wide_string 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/pdk/validate/control_repo/control_repo_validator_group.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module Validate 5 | module ControlRepo 6 | class ControlRepoValidatorGroup < ValidatorGroup 7 | def name 8 | 'control-repo' 9 | end 10 | 11 | def valid_in_context? 12 | context.is_a?(PDK::Context::ControlRepo) 13 | end 14 | 15 | def validators 16 | [ 17 | EnvironmentConfValidator 18 | ].freeze 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/pdk/validate/metadata/metadata_syntax_validator.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module Validate 5 | module Metadata 6 | class MetadataSyntaxValidator < InternalRubyValidator 7 | def name 8 | 'metadata-syntax' 9 | end 10 | 11 | def pattern 12 | contextual_pattern(['metadata.json', 'tasks/*.json']) 13 | end 14 | 15 | def spinner_text 16 | format('Checking metadata syntax (%{patterns}).', patterns: pattern.join(' ')) 17 | end 18 | 19 | def invoke(report) 20 | super 21 | ensure 22 | JSON.parser = JSON::Ext::Parser if defined?(JSON::Ext::Parser) 23 | end 24 | 25 | def validate_target(report, target) 26 | unless PDK::Util::Filesystem.readable?(target) 27 | report.add_event( 28 | file: target, 29 | source: name, 30 | state: :failure, 31 | severity: 'error', 32 | message: 'Could not be read.' 33 | ) 34 | return 1 35 | end 36 | 37 | begin 38 | JSON.parse(PDK::Util::Filesystem.read_file(target)) 39 | 40 | report.add_event( 41 | file: target, 42 | source: name, 43 | state: :passed, 44 | severity: 'ok' 45 | ) 46 | 0 47 | rescue JSON::ParserError => e 48 | # Because the message contains a raw segment of the file, we use 49 | # String#dump here to unescape any escape characters like newlines. 50 | sane_message = e.message.dump 51 | 52 | report.add_event( 53 | file: target, 54 | source: name, 55 | state: :failure, 56 | severity: 'error', 57 | message: sane_message 58 | ) 59 | 1 60 | end 61 | end 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/pdk/validate/metadata/metadata_validator_group.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module Validate 5 | module Metadata 6 | class MetadataValidatorGroup < ValidatorGroup 7 | def name 8 | 'metadata' 9 | end 10 | 11 | def validators 12 | [ 13 | MetadataSyntaxValidator, 14 | MetadataJSONLintValidator 15 | ].freeze 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/pdk/validate/puppet/puppet_lint_validator.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module Validate 5 | module Puppet 6 | class PuppetLintValidator < ExternalCommandValidator 7 | def name 8 | 'puppet-lint' 9 | end 10 | 11 | def cmd 12 | 'puppet-lint' 13 | end 14 | 15 | def pattern 16 | contextual_pattern('**/*.pp') 17 | end 18 | 19 | def spinner_text_for_targets(_targets) 20 | format('Checking Puppet manifest style (%{pattern}).', pattern: pattern.join(' ')) 21 | end 22 | 23 | def parse_options(targets) 24 | cmd_options = ['--json', '--relative'] 25 | 26 | cmd_options << '--fix' if options[:auto_correct] 27 | 28 | cmd_options.concat(targets) 29 | end 30 | 31 | def parse_output(report, result, targets) 32 | begin 33 | json_data = JSON.parse(result[:stdout]).flatten 34 | rescue JSON::ParserError 35 | raise PDK::Validate::ParseOutputError, result[:stdout] 36 | end 37 | 38 | # puppet-lint does not include files without problems in its JSON 39 | # output, so we need to go through the list of targets and add passing 40 | # events to the report for any target not listed in the JSON output. 41 | targets.reject { |target| json_data.any? { |j| j['path'] == target } }.each do |target| 42 | report.add_event( 43 | file: target, 44 | source: name, 45 | severity: 'ok', 46 | state: :passed 47 | ) 48 | end 49 | 50 | json_data.each do |offense| 51 | report.add_event({ 52 | file: offense['path'], 53 | source: name, 54 | line: offense['line'], 55 | column: offense['column'], 56 | message: offense['message'], 57 | test: offense['check'], 58 | severity: offense['kind'] == 'fixed' ? 'corrected' : offense['kind'], 59 | state: :failure 60 | }) 61 | end 62 | end 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/pdk/validate/puppet/puppet_plan_syntax_validator.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module Validate 5 | module Puppet 6 | class PuppetPlanSyntaxValidator < PuppetSyntaxValidator 7 | def name 8 | 'puppet-plan-syntax' 9 | end 10 | 11 | def pattern 12 | contextual_pattern('plans/**/*.pp') 13 | end 14 | 15 | def pattern_ignore; end 16 | 17 | def spinner_text_for_targets(_targets) 18 | format('Checking Puppet plan syntax (%{pattern}).', pattern: pattern.join(' ')) 19 | end 20 | 21 | def parse_options(targets) 22 | # Due to PDK-1266 we need to run `puppet parser validate` with an empty 23 | # modulepath. On *nix, Ruby treats `/dev/null` as an empty directory 24 | # however it doesn't do so with `NUL` on Windows. The workaround for 25 | # this to ensure consistent behaviour is to create an empty temporary 26 | # directory and use that as the modulepath. 27 | ['parser', 'validate', '--tasks', '--config', null_file, '--modulepath', validate_tmpdir].concat(targets) 28 | end 29 | 30 | def validate_tmpdir 31 | require 'tmpdir' 32 | 33 | @validate_tmpdir ||= Dir.mktmpdir('puppet-plan-parser-validate') 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/pdk/validate/puppet/puppet_validator_group.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module Validate 5 | module Puppet 6 | class PuppetValidatorGroup < ValidatorGroup 7 | def name 8 | 'puppet' 9 | end 10 | 11 | def validators 12 | [ 13 | PuppetSyntaxValidator, 14 | PuppetPlanSyntaxValidator, 15 | PuppetLintValidator, 16 | PuppetEPPValidator 17 | ].freeze 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/pdk/validate/ruby/ruby_rubocop_validator.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module Validate 5 | module Ruby 6 | class RubyRubocopValidator < ExternalCommandValidator 7 | def allow_empty_targets? 8 | true 9 | end 10 | 11 | def name 12 | 'rubocop' 13 | end 14 | 15 | def cmd 16 | 'rubocop' 17 | end 18 | 19 | def pattern 20 | if context.is_a?(PDK::Context::ControlRepo) 21 | ['Puppetfile', '**/**.rb'] 22 | else 23 | '**/**.rb' 24 | end 25 | end 26 | 27 | def spinner_text_for_targets(_targets) 28 | format('Checking Ruby code style (%{pattern}).', pattern:) 29 | end 30 | 31 | def parse_options(targets) 32 | cmd_options = ['--format', 'json'] 33 | 34 | cmd_options << '--auto-correct' if options[:auto_correct] 35 | 36 | cmd_options.concat(targets) 37 | end 38 | 39 | def parse_output(report, result, _targets) 40 | return if result[:stdout].empty? 41 | 42 | begin 43 | json_data = JSON.parse(result[:stdout]) 44 | rescue JSON::ParserError 45 | raise PDK::Validate::ParseOutputError, result[:stdout] 46 | end 47 | 48 | return unless json_data.key?('files') 49 | 50 | json_data['files'].each do |file_info| 51 | next unless file_info.key?('offenses') 52 | 53 | result = { 54 | file: file_info['path'], 55 | source: 'rubocop' 56 | } 57 | 58 | if file_info['offenses'].empty? 59 | report.add_event(result.merge(state: :passed, severity: :ok)) 60 | else 61 | file_info['offenses'].each do |offense| 62 | report.add_event( 63 | result.merge( 64 | line: offense['location']['line'], 65 | column: offense['location']['column'], 66 | message: offense['message'], 67 | severity: offense['corrected'] ? 'corrected' : offense['severity'], 68 | test: offense['cop_name'], 69 | state: :failure 70 | ) 71 | ) 72 | end 73 | end 74 | end 75 | end 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /lib/pdk/validate/ruby/ruby_validator_group.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module Validate 5 | module Ruby 6 | class RubyValidatorGroup < ValidatorGroup 7 | def name 8 | 'ruby' 9 | end 10 | 11 | def validators 12 | [ 13 | RubyRubocopValidator 14 | ].freeze 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/pdk/validate/tasks/tasks_name_validator.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module Validate 5 | module Tasks 6 | class TasksNameValidator < InternalRubyValidator 7 | INVALID_TASK_MSG = 'Invalid task name. Task names must start with a lowercase letter and can only contain lowercase letters, numbers, and underscores.'.freeze 8 | 9 | def name 10 | 'task-name' 11 | end 12 | 13 | def pattern 14 | contextual_pattern('tasks/**/*') 15 | end 16 | 17 | def spinner_text 18 | format('Checking task names (%{pattern}).', pattern: pattern.join(' ')) 19 | end 20 | 21 | def validate_target(report, target) 22 | task_name = File.basename(target, File.extname(target)) 23 | if PDK::CLI::Util::OptionValidator.valid_task_name?(task_name) 24 | report.add_event( 25 | file: target, 26 | source: name, 27 | state: :passed, 28 | severity: 'ok' 29 | ) 30 | 0 31 | else 32 | report.add_event( 33 | file: target, 34 | source: name, 35 | state: :failure, 36 | severity: 'error', 37 | message: INVALID_TASK_MSG 38 | ) 39 | 1 40 | end 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/pdk/validate/tasks/tasks_validator_group.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module Validate 5 | module Tasks 6 | class TasksValidatorGroup < ValidatorGroup 7 | def name 8 | 'tasks' 9 | end 10 | 11 | def validators 12 | [ 13 | TasksNameValidator, 14 | TasksMetadataLintValidator 15 | ].freeze 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/pdk/validate/yaml/yaml_validator_group.rb: -------------------------------------------------------------------------------- 1 | require 'pdk' 2 | 3 | module PDK 4 | module Validate 5 | module YAML 6 | class YAMLValidatorGroup < ValidatorGroup 7 | def name 8 | 'yaml' 9 | end 10 | 11 | def validators 12 | [ 13 | YAMLSyntaxValidator 14 | ].freeze 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/pdk/version.rb: -------------------------------------------------------------------------------- 1 | module PDK 2 | VERSION = '3.4.0'.freeze 3 | TEMPLATE_REF = '3.4.0.3'.freeze 4 | end 5 | -------------------------------------------------------------------------------- /package-testing/.gitignore: -------------------------------------------------------------------------------- 1 | # bundler items 2 | /.bundle/ 3 | /vendor/ 4 | /Gemfile.lock 5 | 6 | # beaker output directories 7 | /tmp/ 8 | /log/ 9 | /repo-config/ 10 | /acceptance_hosts.yml 11 | /junit/ 12 | /archive/ 13 | /sut-files.tgz 14 | -------------------------------------------------------------------------------- /package-testing/Gemfile: -------------------------------------------------------------------------------- 1 | source ENV.fetch('GEM_SOURCE', nil) || 'https://rubygems.org' 2 | 3 | gem 'beaker', '~> 4.39' 4 | gem 'beaker-abs', '~> 0.11.0' 5 | gem 'beaker-docker', '~> 2' 6 | gem 'beaker-hostgenerator', '~> 2.11.0' 7 | gem 'beaker-puppet', '= 1.29.0' 8 | gem 'beaker-rspec', '= 7.1.0' 9 | gem 'beaker-vmpooler', '= 1.4.0' 10 | gem 'nokogiri', '~> 1.13.6' 11 | gem 'rake' 12 | 13 | # net-ping has a implicit dependency on win32-security 14 | gem 'win32-security', require: false if File::ALT_SEPARATOR 15 | 16 | group :development do 17 | gem 'pry' 18 | gem 'pry-stack_explorer' 19 | end 20 | -------------------------------------------------------------------------------- /package-testing/config/options.rb: -------------------------------------------------------------------------------- 1 | { 2 | ssh: { 3 | keys: ['~/.ssh/id_rsa-acceptance'], 4 | verify_host_key: :never 5 | }, 6 | preserve_hosts: 'onfail', 7 | provision: 'true' 8 | } 9 | -------------------------------------------------------------------------------- /package-testing/lib/helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH << File.expand_path(__dir__) 2 | -------------------------------------------------------------------------------- /package-testing/lib/pdk/pdk_helper.rb: -------------------------------------------------------------------------------- 1 | def install_dir(host) 2 | if host.platform.include?('windows') 3 | '/cygdrive/c/Program\ Files/Puppet\ Labs/DevelopmentKit' 4 | else 5 | '/opt/puppetlabs/pdk' 6 | end 7 | end 8 | 9 | def pdk_git_bin_dir(host) 10 | if host.platform.include?('windows') 11 | "#{install_dir(host)}/private/git/mingw64/bin" 12 | else 13 | "#{install_dir(host)}/private/git/bin" 14 | end 15 | end 16 | 17 | # Common way to just invoke 'pdk' on each platform 18 | def pdk_command(host, command, env = {}) 19 | env ||= {} 20 | env_str = '' 21 | 22 | if host.platform.include?('windows') 23 | env.each do |var, val| 24 | env_str += "\\$env:#{var}='#{val}'; " 25 | end 26 | 27 | # Pass the command to powershell and exit powershell with pdk's exit code 28 | "powershell -Command \"#{env_str.tr('"', '\"').strip} pdk #{command.tr('"', '\"')}; exit $LASTEXITCODE\"" 29 | else 30 | env.each do |var, val| 31 | env_str += "#{var}=#{val} " 32 | end 33 | 34 | "/bin/bash -lc \"#{env_str} pdk #{command}\"" 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /package-testing/lib/testenv.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper_package' 2 | 3 | RSpec.configure do |c| 4 | c.after(:suite) do 5 | puts "\nTarget host successfully provisioned with PDK:" 6 | puts "\n#{hosts.first.hostname}\n" 7 | end 8 | end 9 | 10 | describe 'validate PDK was successfully installed' do 11 | describe command('pdk --version') do 12 | its(:exit_status) { is_expected.to eq(0) } 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /package-testing/spec/package/add_gem_to_module_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper_package' 2 | 3 | describe 'C100545 - Generate a module, add a gem to it, and validate it' do 4 | module_name = 'c100545_module' 5 | 6 | describe command("pdk new module #{module_name} --skip-interview --skip-interview --template-url=https://github.com/puppetlabs/pdk-templates --template-ref=main") do 7 | its(:exit_status) { is_expected.to eq(0) } 8 | end 9 | 10 | context 'when a new gem dependency has been added to the Gemfile' do 11 | before(:all) do 12 | shell("echo \"gem 'bolt'\" >> #{File.join(module_name, 'Gemfile')}") 13 | end 14 | 15 | describe command('pdk validate') do 16 | let(:cwd) { module_name } 17 | 18 | its(:exit_status) { is_expected.to eq(0) } 19 | end 20 | 21 | describe file(File.join(module_name, 'Gemfile.lock')) do 22 | it { is_expected.to exist } 23 | 24 | describe 'the content of the file' do 25 | subject { super().content } 26 | 27 | it 'differs from the vendored lockfile' do 28 | vendored_lockfile = File.join(install_dir, 'share', 'cache', 'Gemfile.lock') 29 | expect(subject).not_to eq(file(vendored_lockfile).content) 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /package-testing/spec/package/airgapped_usage_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper_package' 2 | 3 | describe 'Basic usage in an air-gapped environment' do 4 | module_name = 'airgapped_module' 5 | 6 | context 'with rubygems.org access disabled' do 7 | before(:all) do 8 | shell("cp #{hosts_file} #{hosts_file}.bak") 9 | shell("echo \"127.0.0.1 rubygems.org\" >> #{hosts_file}") 10 | end 11 | 12 | after(:all) do 13 | shell("cp #{hosts_file}.bak #{hosts_file}") 14 | end 15 | 16 | context 'when creating a new module' do 17 | describe command("pdk new module #{module_name} --skip-interview") do 18 | its(:exit_status) { is_expected.to eq(0) } 19 | end 20 | 21 | describe file(File.join(module_name, 'metadata.json')) do 22 | it { is_expected.to be_file } 23 | 24 | its(:content_as_json) do 25 | is_expected.to include('template-url' => a_string_matching(/pdk-templates#main/)) 26 | end 27 | end 28 | end 29 | 30 | # If this test fails with a mismatch between the expected and actual Gemfile.lock content, check that the following 31 | # steps are up to date: 32 | # - Ensure that the pdk-templates main has been given an anotated (it must be annotated) tag with the version number, 33 | # if between releases add a fourth number to it, i.e. 3.2.0.3 34 | # - Ensure that the pdk version.rb is pointing to this tag 35 | # https://github.com/puppetlabs/pdk/blob/main/lib/pdk/version.rb 36 | # - Ensure that the pdk-vanagon template pin is up to date with the pdk-templates main commit 37 | context 'when validating the module' do 38 | context "with puppet #{PDK_VERSION[:latest][:major]}" do 39 | let(:ruby_version) { ruby_for_puppet(PDK_VERSION[:latest][:major]) } 40 | 41 | describe command("pdk validate --puppet-version=#{PDK_VERSION[:latest][:major]}") do 42 | let(:cwd) { module_name } 43 | 44 | its(:exit_status) { is_expected.to eq(0) } 45 | end 46 | 47 | describe file(File.join(module_name, 'Gemfile.lock')) do 48 | it { is_expected.to be_file } 49 | 50 | describe 'the content of the file' do 51 | subject { super().content.gsub(/^DEPENDENCIES.+?\n\n/m, '') } 52 | 53 | it 'is identical to the vendored lockfile' do 54 | vendored_lockfile = File.join(install_dir, 'share', 'cache', "Gemfile-#{ruby_version}.lock") 55 | 56 | expect(subject).to eq(file(vendored_lockfile).content.gsub(/^DEPENDENCIES.+?\n\n/m, '')) 57 | end 58 | end 59 | end 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /package-testing/spec/package/pdk_help_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper_package' 2 | 3 | describe 'C100022 - pdk --help' do 4 | describe command('pdk --help') do 5 | its(:exit_status) { is_expected.to eq(0) } 6 | its(:stdout) { is_expected.to match(/NAME.*USAGE.*DESCRIPTION.*COMMANDS.*OPTIONS/m) } 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /package-testing/spec/package/support/serverspec_monkeypatch.rb: -------------------------------------------------------------------------------- 1 | require 'beaker-rspec' 2 | 3 | module Serverspec 4 | module Type 5 | class Command 6 | def run 7 | command_result 8 | end 9 | end 10 | end 11 | end 12 | 13 | option_keys = Specinfra::Configuration.singleton_class.const_get(:VALID_OPTIONS_KEYS).dup 14 | option_keys << :cwd 15 | option_keys << :run_as 16 | 17 | Specinfra::Configuration.singleton_class.send(:remove_const, :VALID_OPTIONS_KEYS) # rubocop:disable RSpec/RemoveConst 18 | Specinfra::Configuration.singleton_class.const_set(:VALID_OPTIONS_KEYS, option_keys.freeze) 19 | RSpec.configuration.add_setting :cwd 20 | RSpec.configuration.add_setting :run_as 21 | 22 | module Specinfra 23 | module Backend 24 | class BeakerCygwin 25 | old_create_script = instance_method(:create_script) 26 | 27 | define_method(:create_script) do |cmd| 28 | prepend_env(old_create_script.bind_call(self, cmd)) 29 | end 30 | 31 | def prepend_env(script) 32 | cmd = [] 33 | 34 | cmd << %(Set-Location -Path "#{get_config(:cwd)}") if get_config(:cwd) 35 | (get_config(:env) || {}).each do |k, v| 36 | cmd << %($env:#{k} = "#{v}") 37 | end 38 | cmd << script 39 | 40 | cmd.join("\n") 41 | end 42 | end 43 | end 44 | end 45 | 46 | module Specinfra 47 | module Backend 48 | class BeakerExec 49 | old_build_command = instance_method(:build_command) 50 | 51 | define_method(:build_command) do |cmd| 52 | if get_config(:cwd) 53 | cmd = cmd.shelljoin if cmd.is_a? Array 54 | cmd = "cd #{get_config(:cwd)} && #{cmd}" 55 | end 56 | 57 | prepend_env(old_build_command.bind_call(self, cmd)) 58 | end 59 | 60 | def unescape(string) 61 | JSON.parse(%(["#{string}"])).first 62 | end 63 | 64 | def prepend_env(cmd) 65 | _, orig_env, orig_cmd = cmd.match(/\A(?:env (.+) )?(\S+(?: -i)?(?: -l)? -c .+)\Z/).to_a 66 | 67 | env = [orig_env].compact 68 | (get_config(:env) || {}).each do |k, v| 69 | env << %(#{k}="#{v}") 70 | end 71 | 72 | command = if env.empty? 73 | orig_cmd 74 | else 75 | "env #{env.join(' ')} #{orig_cmd}" 76 | end 77 | 78 | output = if get_config(:run_as) 79 | "su -l #{get_config(:run_as)} -c #{command.shellescape}" 80 | else 81 | command 82 | end 83 | 84 | $stderr.puts(output) if ENV.key?('BEAKER_debug') 85 | output 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /package-testing/spec/package/support/spec_utils.rb: -------------------------------------------------------------------------------- 1 | module SpecUtils 2 | def install_dir(cygpath = false) 3 | if windows_node? 4 | if cygpath 5 | '/cygdrive/c/Program\ Files/Puppet\ Labs/DevelopmentKit' 6 | else 7 | 'C:/Program Files/Puppet Labs/DevelopmentKit' 8 | end 9 | else 10 | '/opt/puppetlabs/pdk' 11 | end 12 | end 13 | 14 | def home_dir(cygpath = false) 15 | if windows_node? 16 | cygpath ? '/home/Administrator' : 'c:/cygwin64/home/Administrator' 17 | else 18 | get_working_node.external_copy_base 19 | end 20 | end 21 | 22 | def windows_node? 23 | get_working_node.platform.include?('windows') 24 | end 25 | module_function :windows_node? 26 | 27 | def git_bin 28 | path = File.join(install_dir, 'private', 'git') 29 | windows_node? ? "& '#{File.join(path, 'cmd', 'git.exe')}'" : File.join(path, 'bin', 'git') 30 | end 31 | 32 | def hosts_file 33 | if windows_node? 34 | '/cygdrive/c/Windows/System32/Drivers/etc/hosts' 35 | else 36 | '/etc/hosts' 37 | end 38 | end 39 | 40 | def ruby_cache_dir 41 | File.join(install_dir(true), 'private', 'ruby') 42 | end 43 | 44 | def latest_ruby 45 | installed_rubies = shell("cd #{ruby_cache_dir}; ls -dr *").stdout.split 46 | installed_rubies[0] 47 | end 48 | 49 | def ruby_for_puppet(pupver) 50 | ruby_pattern = case pupver 51 | when /^4/ then '2.1.*' 52 | when /^5/ then '2.4.*' 53 | when /^6/ then '2.5.*' 54 | when /^7/ then '2.7.*' 55 | when /^8/ then '3.2.*' 56 | end 57 | 58 | return unless ruby_pattern 59 | 60 | shell("cd #{ruby_cache_dir}; ls -dr #{ruby_pattern}").stdout.split.first 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /package-testing/spec/package/unit_test_a_new_module_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper_package' 2 | 3 | describe 'Generate a module for unit testing' do 4 | module_name = 'unit_test_module' 5 | 6 | context 'when creating a new module and new class' do 7 | describe command("pdk new module #{module_name} --skip-interview") do 8 | its(:exit_status) { is_expected.to eq(0) } 9 | end 10 | 11 | describe command("pdk new class #{module_name}") do 12 | let(:cwd) { module_name } 13 | 14 | its(:exit_status) { is_expected.to eq(0) } 15 | end 16 | 17 | describe command('pdk new defined_type test_define') do 18 | let(:cwd) { module_name } 19 | 20 | its(:exit_status) { is_expected.to eq(0) } 21 | end 22 | end 23 | 24 | context 'when unit testing' do 25 | describe command('pdk test unit') do 26 | let(:cwd) { module_name } 27 | 28 | its(:exit_status) { is_expected.to eq(0) } 29 | its(:stdout) { is_expected.to match(/[1-9]\d* examples.*0 failures/im) } 30 | end 31 | end 32 | 33 | context 'when unit testing in parallel' do 34 | # Parallel tests gem is currently broken on Windows. 35 | describe command('pdk test unit --parallel'), unless: windows_node? do 36 | let(:cwd) { module_name } 37 | 38 | its(:exit_status) { is_expected.to eq(0) } 39 | its(:stdout) { is_expected.to match(/[1-9]\d* examples.*0 failures/im) } 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /package-testing/spec/package/unprivileged_user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper_package' 2 | 3 | describe 'Running PDK as an unprivileged user' do 4 | module_name = 'unprivileged_user' 5 | 6 | before(:all) do 7 | hosts.each do |host| 8 | next if host.platform.include?('windows') 9 | 10 | host.user_present('testuser') 11 | 12 | case host.platform 13 | when /osx/ 14 | on(host, 'createhomedir -c -u testuser') 15 | else 16 | on(host, 'getent passwd testuser') do |result| 17 | _, _, uid, gid, _, homedir, = result.stdout.strip.split(':') 18 | on(host, "mkdir #{homedir} && chown #{uid}:#{gid} #{homedir}") unless directory_exists_on(host, homedir) 19 | end 20 | end 21 | end 22 | end 23 | 24 | let(:run_as) { 'testuser' } 25 | 26 | context 'when creating a new module and new class', unless: windows_node? do 27 | describe command('whoami') do 28 | its(:stdout) { is_expected.to contain('testuser') } 29 | end 30 | 31 | describe command("pdk new module #{module_name} --skip-interview --template-url=https://github.com/puppetlabs/pdk-templates --template-ref=main") do 32 | its(:exit_status) { is_expected.to eq(0) } 33 | end 34 | 35 | describe command("pdk new class #{module_name}") do 36 | let(:cwd) { module_name } 37 | 38 | its(:exit_status) { is_expected.to eq(0) } 39 | end 40 | 41 | describe command('pdk new defined_type test_define') do 42 | let(:cwd) { module_name } 43 | 44 | its(:exit_status) { is_expected.to eq(0) } 45 | end 46 | end 47 | 48 | context 'when unit testing', unless: windows_node? do 49 | describe command('pdk test unit') do 50 | let(:cwd) { module_name } 51 | 52 | its(:exit_status) { is_expected.to eq(0) } 53 | its(:stdout) { is_expected.to match(/[1-9]\d* examples.*0 failures/im) } 54 | end 55 | end 56 | 57 | context 'when unit testing in parallel', unless: windows_node? do 58 | describe command('pdk test unit --parallel') do 59 | let(:cwd) { module_name } 60 | 61 | its(:exit_status) { is_expected.to eq(0) } 62 | its(:stdout) { is_expected.to match(/[1-9]\d* examples.*0 failures/im) } 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /package-testing/spec/package/validate_a_new_module_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper_package' 2 | 3 | describe 'C100321 - Generate a module and validate it (i.e. ensure bundle install works)' do 4 | module_name = 'c100321_module' 5 | 6 | context 'when creating a new module' do 7 | describe command("pdk new module #{module_name} --skip-interview") do 8 | its(:exit_status) { is_expected.to eq(0) } 9 | end 10 | 11 | describe file(File.join(module_name, 'metadata.json')) do 12 | it { is_expected.to be_file } 13 | 14 | its(:content_as_json) do 15 | is_expected.to include('template-url' => a_string_matching(/pdk-templates#main/)) 16 | end 17 | end 18 | end 19 | 20 | # If this test fails with a mismatch between the expected and actual Gemfile.lock content, check that the following 21 | # steps are up to date: 22 | # - Ensure that the pdk-templates main has been given an anotated (it must be annotated) tag with the version number, 23 | # if between releases add a fourth number to it, i.e. 3.2.0.3 24 | # - Ensure that the pdk version.rb is pointing to this tag 25 | # https://github.com/puppetlabs/pdk/blob/main/lib/pdk/version.rb 26 | # - Ensure that the pdk-vanagon template pin is up to date with the pdk-templates main commit 27 | context 'when validating the module' do 28 | context "with puppet #{PDK_VERSION[:latest][:major]}" do 29 | let(:ruby_version) { ruby_for_puppet(PDK_VERSION[:latest][:major]) } 30 | 31 | describe command("pdk validate --puppet-version=#{PDK_VERSION[:latest][:major]}") do 32 | let(:cwd) { module_name } 33 | 34 | its(:exit_status) { is_expected.to eq(0) } 35 | end 36 | 37 | describe file(File.join(module_name, 'Gemfile.lock')) do 38 | it { is_expected.to be_file } 39 | 40 | describe 'the content of the file' do 41 | subject { super().content.gsub(/^DEPENDENCIES.+?\n\n/m, '') } 42 | 43 | it 'is identical to the vendored lockfile' do 44 | vendored_lockfile = File.join(install_dir, 'share', 'cache', "Gemfile-#{ruby_version}.lock") 45 | 46 | expect(subject).to eq(file(vendored_lockfile).content.gsub(/^DEPENDENCIES.+?\n\n/m, '')) 47 | end 48 | end 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /package-testing/spec/package/version_selection_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper_package' 2 | 3 | describe 'Test puppet & ruby version selection' do 4 | module_name = 'version_selection' 5 | test_cases = [ 6 | { envvar: 'PDK_PUPPET_VERSION', expected_puppet: PDK_VERSION[:latest][:major], expected_ruby: PDK_VERSION[:latest][:ruby] } 7 | ] 8 | 9 | before(:all) do 10 | command('pdk new module version_selection --skip-interview').run 11 | end 12 | 13 | test_cases.each do |test_case| 14 | context "Select Puppet #{test_case[:expected_puppet]}" do 15 | let(:env) { { test_case[:envvar] => test_case[:expected_puppet] } } 16 | let(:cwd) { module_name } 17 | 18 | let(:expected_puppet) { Regexp.escape(test_case[:expected_puppet]) } 19 | let(:expected_ruby) { Regexp.escape(test_case[:expected_ruby]) } 20 | 21 | describe command('rm Gemfile.lock; pdk bundle update --local') do 22 | its(:exit_status) { is_expected.to eq(0) } 23 | end 24 | 25 | describe command('pdk bundle exec puppet --version') do 26 | its(:exit_status) { is_expected.to eq(0) } 27 | its(:stderr) { is_expected.to match(/using puppet (#{expected_puppet}\.\d+\.\d+)/im) } 28 | its(:stdout) { is_expected.to match(/^(#{expected_puppet}\.\d+\.\d+)*/im) } 29 | end 30 | 31 | describe command('pdk bundle exec ruby --version') do 32 | its(:exit_status) { is_expected.to eq(0) } 33 | its(:stderr) { is_expected.to match(/using ruby #{expected_ruby}*/im) } 34 | its(:stdout) { is_expected.to match(/^(#{expected_ruby})*/im) } 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /package-testing/spec/spec_helper_package.rb: -------------------------------------------------------------------------------- 1 | require 'beaker-rspec' 2 | require 'beaker-puppet' 3 | 4 | Dir['./spec/package/support/*.rb'].each { |f| require f } 5 | 6 | include SpecUtils # rubocop:disable Style/MixinUsage 7 | 8 | bin_path = SpecUtils.windows_node? ? 'bin$PATH' : 'bin:$PATH' 9 | set :path, "#{SpecUtils.install_dir}/#{bin_path}" 10 | 11 | # IMPORTANT: The following block should be updated with the version of ruby that is included within the newest 12 | # Puppet release for each major version. If you are running integration testing prior to a release and its 13 | # failing, verify that the following versions are correct. 14 | # Duplicates of this are found within spec_helper.rb and spec_helper_acceptance.rb and should be updated simultaneously. 15 | PDK_VERSION = { 16 | latest: { 17 | full: '8.6.0', 18 | major: '8', 19 | ruby: '3.2.*' 20 | }, 21 | lts: { 22 | full: '8.6.0', 23 | major: '8', 24 | ruby: '3.2.*' 25 | } 26 | }.freeze 27 | 28 | RSpec.configure do |c| 29 | c.include SpecUtils 30 | c.extend SpecUtils 31 | 32 | c.before(:suite) do 33 | hosts.each do |host| 34 | PackageHelpers.install_pdk_on(host) 35 | end 36 | end 37 | 38 | # rubocop:disable RSpec/BeforeAfterAll 39 | c.before(:all) do 40 | RSpec.configuration.logger.log_level = :warn 41 | end 42 | 43 | c.after(:all) do 44 | RSpec.configuration.logger.log_level = :verbose 45 | end 46 | # rubocop:enable RSpec/BeforeAfterAll 47 | 48 | c.after do 49 | cmd = if windows_node? 50 | command('rm -Recurse -Force $env:LOCALAPPDATA/PDK/Cache/ruby') 51 | else 52 | command('rm -rf ~/.pdk/cache/ruby') 53 | end 54 | 55 | # clear out any cached gems 56 | cmd.run 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /pdk.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('lib', __dir__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require 'pdk/version' 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = 'pdk' 7 | spec.version = PDK::VERSION 8 | spec.authors = ['Puppet, Inc.'] 9 | spec.email = ['pdk-maintainers@puppet.com'] 10 | 11 | spec.summary = 'A key part of the Puppet Development Kit, the shortest path to better modules' 12 | spec.description = 'A CLI to facilitate easy, unified development workflows for Puppet modules.' 13 | spec.homepage = 'https://github.com/puppetlabs/pdk' 14 | 15 | spec.files = Dir['CHANGELOG.md', 'README.md', 'LICENSE', 'lib/**/*', 'exe/**/*'] 16 | spec.bindir = 'exe' 17 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 18 | spec.require_paths = ['lib'] 19 | 20 | spec.required_ruby_version = '>= 3.1.0' 21 | 22 | # PDK Rubygems 23 | spec.add_dependency 'ffi', '>= 1.15.5', '< 2.0.0' 24 | spec.add_dependency 'minitar', '~> 0.8' 25 | 26 | # Bundler 27 | spec.add_dependency 'bundler', '>= 2.1.0', '< 3.0.0' 28 | 29 | # Cri and deps 30 | spec.add_dependency 'cri', '~> 2.15.11' 31 | 32 | # Childprocess and deps 33 | spec.add_dependency 'childprocess', '~> 5.0' 34 | spec.add_dependency 'hitimes', '2.0.0' 35 | 36 | ## root tty gems 37 | spec.add_dependency 'tty-prompt', '~> 0.23' 38 | spec.add_dependency 'tty-spinner', '~> 0.9' 39 | spec.add_dependency 'tty-which', '~> 0.5' 40 | 41 | # json-schema and deps 42 | spec.add_dependency 'json-schema', '~> 5.0' 43 | 44 | #  PDK build 45 | spec.add_dependency 'puppet-modulebuilder', '~> 1.0' 46 | 47 | # Other deps 48 | spec.add_dependency 'deep_merge', '~> 1.2.2' 49 | spec.add_dependency 'diff-lcs', '>= 1.5.0' 50 | spec.add_dependency 'pathspec', '~> 1.1' 51 | spec.add_dependency 'puppet_forge', '~> 5.0' 52 | 53 | spec.metadata['rubygems_mfa_required'] = 'true' 54 | end 55 | -------------------------------------------------------------------------------- /rakelib/test_pdk_as_library.rake: -------------------------------------------------------------------------------- 1 | require 'English' 2 | 3 | task :test_pdk_as_library do 4 | spec_dir = File.expand_path(File.join(__dir__, '..', 'spec')) 5 | 6 | Dir[File.join(spec_dir, 'unit', '**', '*_spec.rb')].each do |spec_file| 7 | system("bundle exec rspec #{spec_file}") 8 | 9 | raise unless $CHILD_STATUS.success? 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/acceptance/get_config_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper_acceptance' 2 | require 'fileutils' 3 | 4 | describe 'pdk get config' do 5 | include_context 'with a fake TTY' 6 | 7 | context 'when run outside of a module' do 8 | describe command('pdk get config') do 9 | its(:exit_status) { is_expected.to eq 0 } 10 | # This setting should appear in all pdk versions 11 | its(:stdout) { is_expected.to match(/user.pdk_feature_flags.available=/) } 12 | its(:stderr) { is_expected.to have_no_output } 13 | end 14 | 15 | describe command('pdk get config user.pdk_feature_flags.available') do 16 | its(:exit_status) { is_expected.to eq 0 } 17 | # This setting, and only, this setting should appear in output 18 | its(:stdout) { is_expected.to match('["controlrepo"]') } 19 | its(:stderr) { is_expected.to have_no_output } 20 | end 21 | 22 | describe command('pdk get config user.pdk_feature_flags') do 23 | its(:exit_status) { is_expected.to eq 0 } 24 | # There should be two configuration items returned 25 | its(:stdout) { expect(is_expected.target.split("\n").count).to eq(2) } 26 | 27 | its(:stdout) do 28 | result = is_expected.target.split("\n").sort 29 | expect(result[0]).to match('user.pdk_feature_flags.available=["controlrepo"]') 30 | expect(result[1]).to match(/user.pdk_feature_flags.requested=.+/) 31 | end 32 | 33 | its(:stderr) { is_expected.to have_no_output } 34 | end 35 | 36 | describe command('pdk get config does.not.exist') do 37 | its(:exit_status) { is_expected.not_to eq(0) } 38 | its(:stdout) { is_expected.to have_no_output } 39 | its(:stderr) { is_expected.to match(/does\.not\.exist/) } 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/acceptance/get_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper_acceptance' 2 | require 'fileutils' 3 | 4 | describe 'pdk get' do 5 | include_context 'with a fake TTY' 6 | 7 | context 'when run outside of a module' do 8 | describe command('pdk get') do 9 | its(:exit_status) { is_expected.to eq 0 } 10 | # Should show the command help 11 | its(:stdout) { is_expected.to match(/pdk get \[subcommand\] \[options\]/) } 12 | its(:stderr) { is_expected.to have_no_output } 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/acceptance/new_fact_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper_acceptance' 2 | 3 | describe 'pdk new fact', :module_command do 4 | shared_examples 'it creates a fact' do |_options| 5 | context 'in a new module' do 6 | include_context 'in a new module', 'new_fact' 7 | 8 | context 'when creating the fact' do 9 | describe command('pdk new fact new_fact') do 10 | its(:exit_status) { is_expected.to eq 0 } 11 | its(:stdout) { is_expected.to match(/Files added/) } 12 | its(:stdout) { is_expected.to match(/#{File.join('lib', 'facter', 'new_fact.rb')}/) } 13 | its(:stdout) { is_expected.to match(/#{File.join('spec', 'unit', 'facter', 'new_fact_spec.rb')}/) } 14 | its(:stderr) { is_expected.to have_no_output } 15 | 16 | it_behaves_like 'it creates a fact', 17 | name: 'new_fact', 18 | file: File.join('lib', 'facter', 'new_fact.rb'), 19 | spec: File.join('spec', 'unit', 'facter', 'new_fact_spec.rb') 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/acceptance/new_function_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper_acceptance' 2 | 3 | describe 'pdk new function', :module_command do 4 | shared_examples 'it creates a function' do |_options| 5 | context 'in a new module' do 6 | include_context 'in a new module', 'new_function' 7 | context 'when creating the function' do 8 | describe command('pdk new function -t v4 abs') do 9 | its(:exit_status) { is_expected.to eq 0 } 10 | its(:stdout) { is_expected.to match(/Files added/) } 11 | its(:stdout) { is_expected.to match(/#{File.join('lib', 'puppet', 'functions', 'abs.rb')}/) } 12 | its(:stdout) { is_expected.to match(/#{File.join('dspec', 'functions', 'abs_spec.rb')}/) } 13 | its(:stderr) { is_expected.to have_no_output } 14 | 15 | it_behaves_like 'it creates a function', 16 | name: 'abs', 17 | file: File.join('lib', 'puppet', 'functions', 'abs.rb'), 18 | spec: File.join('spec', 'functions', 'abs_spec.rb') 19 | end 20 | 21 | describe command('pdk new function -t native abs') do 22 | its(:exit_status) { is_expected.to eq 0 } 23 | its(:stdout) { is_expected.to match(/Files added/) } 24 | its(:stdout) { is_expected.to match(/#{File.join('functions', 'abs.pp')}/) } 25 | its(:stdout) { is_expected.to match(/#{File.join('spec', 'functions', 'abs_spec.rb')}/) } 26 | its(:stderr) { is_expected.to have_no_output } 27 | 28 | it_behaves_like 'it creates a function', 29 | name: 'abs', 30 | file: File.join('functions', 'abs.rb'), 31 | spec: File.join('spec', 'functions', 'abs_spec.rb') 32 | end 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/acceptance/new_module_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper_acceptance' 2 | require 'pdk/version' 3 | 4 | describe 'pdk new module' do 5 | context 'when the --skip-interview option is used' do 6 | after(:all) do 7 | FileUtils.rm_rf('new_module') 8 | end 9 | 10 | describe command('pdk new module new_module --skip-interview') do 11 | its(:exit_status) { is_expected.to eq 0 } 12 | its(:stderr) { is_expected.to match(/Creating new module: new_module/) } 13 | its(:stderr) { is_expected.not_to match(/WARN|ERR/) } 14 | its(:stdout) { is_expected.to have_no_output } 15 | 16 | describe file('new_module') do 17 | it { is_expected.to be_directory } 18 | end 19 | 20 | describe file(File.join('new_module', 'metadata.json')) do 21 | it { is_expected.to be_file } 22 | 23 | its(:content_as_json) do 24 | is_expected.to include( 25 | 'name' => match(/-new_module/), 26 | 'template-ref' => match(%r{(main-)|(^(tags/)?(\d+)\.(\d+)\.(\d+))}), 27 | 'operatingsystem_support' => include( 28 | 'operatingsystem' => 'Debian', 29 | 'operatingsystemrelease' => ['10', '11', '12'] 30 | ) 31 | ) 32 | end 33 | end 34 | 35 | describe file(File.join('new_module', 'README.md')) do 36 | it { is_expected.to be_file } 37 | it { is_expected.to contain(/# new_module/i) } 38 | end 39 | 40 | describe file(File.join('new_module', 'CHANGELOG.md')) do 41 | it { is_expected.to be_file } 42 | it { is_expected.to contain(/## Release 0.1.0/i) } 43 | end 44 | 45 | eol_check = '(Get-Content .\new_module\spec\spec_helper.rb -Delimiter [String].Empty) -Match "`r`n"' 46 | describe command(eol_check), if: Gem.win_platform? do 47 | its(:stdout) { is_expected.to match(/\AFalse$/) } 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/acceptance/new_test_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper_acceptance' 2 | 3 | describe 'pdk new test', :module_command do 4 | context 'in a new module' do 5 | include_context 'in a new module', 'new_unit_test' 6 | 7 | before(:all) do 8 | File.open(File.join('manifests', 'init.pp'), 'w') do |fd| 9 | fd.puts 'class new_unit_test {}' 10 | end 11 | 12 | File.open(File.join('manifests', 'def_type.pp'), 'w') do |fd| 13 | fd.puts 'define new_unit_test::def_type() {}' 14 | end 15 | end 16 | 17 | context 'when creating a test for the main class' do 18 | describe command('pdk new test --unit new_unit_test') do 19 | its(:exit_status) { is_expected.to eq 0 } 20 | its(:stdout) { is_expected.to match(/#{File.join('spec', 'classes', 'new_unit_test_spec.rb')}/) } 21 | its(:stderr) { is_expected.to match(/Using Ruby/) } 22 | its(:stderr) { is_expected.to match(/Using Puppet/) } 23 | 24 | describe file(File.join('spec', 'classes', 'new_unit_test_spec.rb')) do 25 | it { is_expected.to be_file } 26 | 27 | its(:content) do 28 | is_expected.to match(/describe 'new_unit_test' do/) 29 | end 30 | end 31 | end 32 | end 33 | 34 | context 'when creating a test for a defined type' do 35 | describe command('pdk new test def_type') do 36 | its(:exit_status) { is_expected.to eq 0 } 37 | its(:stdout) { is_expected.to match(/#{File.join('spec', 'defines', 'def_type_spec.rb')}/) } 38 | its(:stderr) { is_expected.to match(/Using Ruby/) } 39 | its(:stderr) { is_expected.to match(/Using Puppet/) } 40 | 41 | describe file(File.join('spec', 'defines', 'def_type_spec.rb')) do 42 | it { is_expected.to be_file } 43 | 44 | its(:content) do 45 | is_expected.to match(/describe 'new_unit_test::def_type' do/) 46 | end 47 | end 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/acceptance/remove_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper_acceptance' 2 | require 'fileutils' 3 | 4 | describe 'pdk set' do 5 | include_context 'with a fake TTY' 6 | 7 | context 'when run outside of a module' do 8 | describe command('pdk set') do 9 | its(:exit_status) { is_expected.to eq 0 } 10 | # Should show the command help 11 | its(:stdout) { is_expected.to match(/pdk set \[subcommand\] \[options\]/) } 12 | its(:stderr) { is_expected.to have_no_output } 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/acceptance/set_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper_acceptance' 2 | require 'fileutils' 3 | 4 | describe 'pdk set' do 5 | include_context 'with a fake TTY' 6 | 7 | context 'when run outside of a module' do 8 | describe command('pdk set') do 9 | its(:exit_status) { is_expected.to eq 0 } 10 | # Should show the command help 11 | its(:stdout) { is_expected.to match(/pdk set \[subcommand\] \[options\]/) } 12 | its(:stderr) { is_expected.to have_no_output } 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/acceptance/support/contain_valid_junit_xml.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/xsd' 2 | 3 | module RSpec 4 | module XSD 5 | class Matcher 6 | attr_reader :errors 7 | end 8 | end 9 | end 10 | 11 | RSpec::Matchers.define :contain_valid_junit_xml do 12 | match do |text| 13 | xsd = File.join(RSpec.configuration.fixtures_path, 'JUnit.xsd') 14 | @matcher = RSpec::XSD::Matcher.new(xsd, nil) 15 | @matcher.matches?(text) 16 | end 17 | 18 | description do 19 | 'contain valid JUnit XML' 20 | end 21 | 22 | failure_message do 23 | "expected that it would contain valid JUnit XML\r\n\r\n#{@matcher.errors.join("\r\n")}" 24 | end 25 | 26 | failure_message_when_negated do 27 | 'expected that it would not contain valid JUnit XML' 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/acceptance/support/have_junit_testcase.rb: -------------------------------------------------------------------------------- 1 | require 'rexml/document' 2 | 3 | RSpec::Matchers.define :have_junit_testcase do 4 | match do |xml_text| 5 | document = REXML::Document.new(xml_text) 6 | 7 | return false if document.root.nil? 8 | 9 | testcases = document.elements.to_a("/testsuites/testsuite[@name=\"#{@testsuite_name}\"]/testcase") 10 | 11 | if @expected_attributes 12 | testcases.select! do |testcase| 13 | @expected_attributes.all? do |attribute, expected_value| 14 | values_match?(expected_value, testcase.attribute(attribute).value) 15 | end 16 | end 17 | end 18 | 19 | case @status 20 | when :pass 21 | testcases.reject!(&:has_elements?) 22 | when :skip 23 | testcases.reject! do |testcase| 24 | testcase.elements.to_a('skipped').empty? 25 | end 26 | when :fail 27 | testcases.reject! do |testcase| 28 | testcase.elements.to_a('failure').empty? 29 | end 30 | 31 | unless @failure_attributes.nil? 32 | testcases.select! do |testcase| 33 | @failure_attributes.all? do |attribute, expected_value| 34 | failure_element = testcase.elements.to_a('failure').first 35 | 36 | values_match?(expected_value, failure_element.attribute(attribute).value) 37 | end 38 | end 39 | end 40 | end 41 | 42 | !testcases.empty? 43 | end 44 | 45 | chain :with_attributes do |attributes| 46 | @expected_attributes = attributes 47 | end 48 | 49 | chain :in_testsuite do |testsuite_name| 50 | @testsuite_name = testsuite_name 51 | end 52 | 53 | chain :that_passed do 54 | @status = :pass 55 | end 56 | 57 | chain :that_was_skipped do 58 | @status = :skip 59 | end 60 | 61 | chain :that_failed do |attributes = nil| 62 | @status = :fail 63 | @failure_attributes = attributes 64 | end 65 | 66 | failure_message do |body| 67 | "expected to find a JUnit testcase#{chained_method_clause_sentences} in:\n#{body}" 68 | end 69 | 70 | failure_message_when_negated do |body| 71 | "expected not to find a JUnit testcase#{chained_method_clause_sentences} in:\n#{body}" 72 | end 73 | 74 | description do 75 | "have a JUnit testcase#{chained_method_clause_sentences}" 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /spec/acceptance/support/have_junit_testsuite.rb: -------------------------------------------------------------------------------- 1 | require 'rexml/document' 2 | 3 | RSpec::Matchers.define :have_junit_testsuite do |testsuite_name| 4 | match do |xml_text| 5 | document = REXML::Document.new(xml_text) 6 | 7 | return false if document.root.nil? 8 | 9 | testsuites = document.elements.to_a("/testsuites/testsuite[@name=\"#{testsuite_name}\"]") 10 | 11 | if @expected_attributes 12 | testsuites.select! do |testsuite| 13 | @expected_attributes.all? do |attribute, expected_value| 14 | actual_value = case attribute 15 | when 'tests', 'failures', 'errors', 'skipped' 16 | testsuite.attribute(attribute).value.to_i 17 | else 18 | testsuite.attribute(attribute).value 19 | end 20 | values_match?(expected_value, actual_value) 21 | end 22 | end 23 | end 24 | 25 | !testsuites.empty? 26 | end 27 | 28 | chain :with_attributes do |attributes| 29 | @expected_attributes = attributes 30 | end 31 | 32 | failure_message do |body| 33 | "expected to find a JUnit testsuite named '#{testsuite_name}'#{chained_method_clause_sentences} in:\n#{body}" 34 | end 35 | 36 | failure_message_when_negated do |body| 37 | "expected not to find a JUnit testsuite named '#{testsuite_name}'#{chained_method_clause_sentences} in:\n#{body}" 38 | end 39 | 40 | description do 41 | "have a JUnit testsuite named '#{testsuite_name}'#{chained_method_clause_sentences}" 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/acceptance/support/have_no_output.rb: -------------------------------------------------------------------------------- 1 | RSpec::Matchers.define :have_no_output do 2 | match do |text| 3 | values_match?(/\A\Z/, text) 4 | end 5 | 6 | diffable 7 | end 8 | -------------------------------------------------------------------------------- /spec/acceptance/support/have_xpath.rb: -------------------------------------------------------------------------------- 1 | require 'rexml/document' 2 | 3 | RSpec::Matchers.define :have_xpath do |path| 4 | match do |xml_text| 5 | doc = REXML::Document.new(xml_text) 6 | nodes = doc.elements.to_a(path) 7 | 8 | if @expected_text 9 | nodes.select! do |node| 10 | values_match?(@expected_text, node.text) 11 | end 12 | end 13 | 14 | if @expected_attributes 15 | nodes.reject! do |node| 16 | retval = false 17 | 18 | @expected_attributes.each do |key, value| 19 | retval = true unless values_match?(value, node.attributes[key]) 20 | end 21 | 22 | retval 23 | end 24 | end 25 | 26 | !nodes.empty? 27 | end 28 | 29 | chain :with_text do |text| 30 | @expected_text = text 31 | end 32 | 33 | chain :with_attributes do |attributes| 34 | @expected_attributes = attributes 35 | end 36 | 37 | failure_message do |body| 38 | "expected to find an XML tag matching XPath '#{path}'#{chained_method_clause_sentences} in:\n#{body}" 39 | end 40 | 41 | failure_message_when_negated do |body| 42 | "expected not to find an XML tag matching XPath '#{path}'#{chained_method_clause_sentences} in:\n#{body}" 43 | end 44 | 45 | description do 46 | "have an XML tag matching XPath '#{path}'#{chained_method_clause_sentences}" 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/acceptance/support/in_a_new_module.rb: -------------------------------------------------------------------------------- 1 | require 'open3' 2 | 3 | shared_context 'in a new module' do |name, options = {}| 4 | before(:all) do 5 | default_template = RSpec.configuration.template_dir 6 | default_template = "file:///#{default_template}" unless Gem.win_platform? 7 | template = options.fetch(:template, default_template) 8 | argv = [ 9 | 'pdk', 'new', 'module', name, 10 | '--skip-interview', 11 | '--template-url', template 12 | ] 13 | env = { 14 | 'PDK_ANSWER_FILE' => File.join(Dir.pwd, "#{name}_answers.json"), 15 | 'PDK_PUPPET_VERSION' => ENV.fetch('PDK_PUPPET_VERSION', PDK_VERSION[:lts][:full]) 16 | } 17 | 18 | output, status = Open3.capture2e(env, *argv) 19 | 20 | raise "Failed to create test module:\n#{output}" unless status.success? 21 | 22 | Dir.chdir(name) 23 | end 24 | 25 | after(:all) do 26 | Dir.chdir('..') 27 | FileUtils.rm_rf(name) 28 | FileUtils.rm_f("#{name}_answers.json") 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/acceptance/support/it_requires_running_in_a_module.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | 3 | RSpec.shared_examples('it requires running from inside a module', :module_command) do 4 | context 'when run outside of a module' do 5 | before(:all) do 6 | Dir.mkdir('empty_test_dir') 7 | Dir.chdir('empty_test_dir') 8 | end 9 | 10 | after(:all) do 11 | Dir.chdir('..') 12 | FileUtils.rm_rf('empty_test_dir') 13 | end 14 | 15 | describe command(top_level_description) do 16 | its(:exit_status) { is_expected.not_to eq(0) } 17 | its(:stderr) { is_expected.to match(/a valid module/i) } 18 | its(:stdout) { is_expected.to have_no_output } 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/acceptance/support/with_a_fake_answer_file.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_context 'with a fake answer file' do |initial_content = nil| 2 | before(:all) do 3 | fake_answer_file = Tempfile.new('mock_answers.json') 4 | unless initial_content.nil? 5 | require 'json' 6 | fake_answer_file.binmode 7 | fake_answer_file.write(JSON.pretty_generate(initial_content)) 8 | end 9 | fake_answer_file.close 10 | ENV['PDK_ANSWER_FILE'] = fake_answer_file.path 11 | end 12 | 13 | after(:all) do 14 | FileUtils.rm_f(ENV.fetch('PDK_ANSWER_FILE', nil)) # Need actual file calls here 15 | ENV.delete('PDK_ANSWER_FILE') 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/acceptance/support/with_a_fake_tty.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_context 'with a fake TTY' do 2 | around do |example| 3 | ENV['PDK_FRONTEND'] = 'INTERACTIVE' 4 | example.run 5 | ENV.delete('PDK_FRONTEND') 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/acceptance/template_ref_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper_acceptance' 2 | 3 | describe 'Specifying a template-ref' do 4 | after(:all) do 5 | # We may or may not be in the foo module directory. If we are, go back one directory. 6 | # This can happen if you only run a subset of tests in this file. 7 | Dir.chdir('..') if Dir.pwd.end_with?('foo') 8 | FileUtils.rm_rf('foo') 9 | FileUtils.rm('foo_answers.json') 10 | end 11 | 12 | context 'when creating a new module' do 13 | create_cmd = [ 14 | 'pdk', 'new', 'module', 'foo', 15 | '--skip-interview', 16 | '--template-url', 'https://github.com/puppetlabs/pdk-templates', 17 | '--template-ref', '3.0.0' 18 | ] 19 | 20 | around do |example| 21 | old_answer_file = ENV.fetch('PDK_ANSWER_FILE', nil) 22 | ENV['PDK_ANSWER_FILE'] = 'foo_answers.json' 23 | example.run 24 | ENV['PDK_ANSWER_FILE'] = old_answer_file 25 | end 26 | 27 | describe command(create_cmd.join(' ')) do 28 | its(:exit_status) { is_expected.to eq(0) } 29 | its(:stderr) { is_expected.to match(/creating new module: foo/i) } 30 | its(:stderr) { is_expected.not_to match(/WARN|ERR/) } 31 | its(:stdout) { is_expected.to match(/\A\Z/) } 32 | 33 | describe file('foo/metadata.json') do 34 | it { is_expected.to be_file } 35 | 36 | its(:content_as_json) do 37 | is_expected.to include('template-ref' => match(/3\.0\.0/)) 38 | end 39 | end 40 | end 41 | 42 | context 'and then updating the module to a specific ref' do 43 | before(:all) { Dir.chdir('foo') } 44 | 45 | describe command('pdk update --template-ref 3.2.0 --force') do 46 | its(:exit_status) { is_expected.to eq(0) } 47 | 48 | describe file('metadata.json') do 49 | its(:content_as_json) do 50 | is_expected.to include('template-ref' => match(/3\.2\.0/)) 51 | end 52 | end 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /spec/fixtures/control_repo/Puppetfile: -------------------------------------------------------------------------------- 1 | forge 'https://forge.puppetlabs.com/' 2 | 3 | # Modules from the Puppet Forge 4 | mod 'puppetlabs-stdlib', '1.0.0' 5 | -------------------------------------------------------------------------------- /spec/fixtures/control_repo/data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/pdk/9c9acab177d14a94f113f81b75220cb215133f3a/spec/fixtures/control_repo/data/.gitkeep -------------------------------------------------------------------------------- /spec/fixtures/control_repo/environment.conf: -------------------------------------------------------------------------------- 1 | modulepath = modules:site:$basemodulepath 2 | environment_timeout = 0 3 | -------------------------------------------------------------------------------- /spec/fixtures/control_repo/site/profile/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/pdk/9c9acab177d14a94f113f81b75220cb215133f3a/spec/fixtures/control_repo/site/profile/.gitkeep -------------------------------------------------------------------------------- /spec/fixtures/control_repo/site/role/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/pdk/9c9acab177d14a94f113f81b75220cb215133f3a/spec/fixtures/control_repo/site/role/.gitkeep -------------------------------------------------------------------------------- /spec/fixtures/module_root/lib/facter/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/pdk/9c9acab177d14a94f113f81b75220cb215133f3a/spec/fixtures/module_root/lib/facter/.gitkeep -------------------------------------------------------------------------------- /spec/fixtures/puppet_module/manifests/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/pdk/9c9acab177d14a94f113f81b75220cb215133f3a/spec/fixtures/puppet_module/manifests/.gitkeep -------------------------------------------------------------------------------- /spec/fixtures/puppet_module/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "testfixture-valid", 3 | "version": "0.1.0", 4 | "author": "testfixture", 5 | "summary": "Skeleton module test fixture", 6 | "license": "Apache-2.0", 7 | "source": "http://localhost", 8 | "dependencies": [] 9 | } 10 | -------------------------------------------------------------------------------- /spec/support/exit_with_status.rb: -------------------------------------------------------------------------------- 1 | RSpec::Matchers.define(:exit_with_status) do |expected_status| 2 | supports_block_expectations 3 | 4 | match do |block| 5 | expectation_passed = false 6 | 7 | begin 8 | block.call 9 | rescue SystemExit => e 10 | expectation_passed = values_match?(expected_status, e.status) 11 | rescue StandardError 12 | nil 13 | end 14 | 15 | expectation_passed 16 | end 17 | end 18 | 19 | RSpec::Matchers.define(:exit_zero) do 20 | supports_block_expectations 21 | 22 | match do |block| 23 | expect { block.call }.to exit_with_status(0) 24 | end 25 | end 26 | 27 | RSpec::Matchers.define(:exit_nonzero) do 28 | supports_block_expectations 29 | 30 | match do |block| 31 | expect { block.call }.to exit_with_status(be_nonzero) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/support/hash_with_defaults_including.rb: -------------------------------------------------------------------------------- 1 | # Similar to RSpec’s built-in #hash_including, but does not require the keys of 2 | # `expected` to be present in `actual` -- what matters is that for all keys in 3 | # `expected`, the value in `actual` and `expected` is the same. 4 | # 5 | # hash = Hash.new { |hash, key| 9000 } 6 | # moo(hash) 7 | # 8 | # This passes: 9 | # 10 | # expect(something) 11 | # .to receive(:moo) 12 | # .with(hash_with_defaults_including(stuff: 9000)) 13 | # 14 | # This does not pass: 15 | # 16 | # expect(something) 17 | # .to receive(:moo) 18 | # .with(hash_including(stuff: 9000)) 19 | RSpec::Matchers.define :hash_with_defaults_including do |expected| 20 | include RSpec::Matchers::Composable 21 | 22 | match do |actual| 23 | expected.keys.all? do |key| 24 | values_match?(expected[key], actual[key]) 25 | end 26 | end 27 | 28 | description do 29 | "hash_with_defaults_including(#{expected.inspect})" 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/support/it_accepts_epp_targets.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples_for 'it accepts .epp targets' do 2 | describe '.pattern' do 3 | it 'matches EPP files' do 4 | expect(described_class.pattern).to include('**/*.epp') 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/support/it_accepts_metadata_json_targets.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples_for 'it accepts metadata.json targets' do 2 | describe '.pattern' do 3 | it 'matches metadata.json in the root' do 4 | expect(described_class.pattern).to include('metadata.json') 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/support/it_accepts_pp_targets.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples_for 'it accepts .pp targets' do 2 | describe '.pattern' do 3 | it 'matches manifest files' do 4 | expect(described_class.pattern).to include('**/*.pp') 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/support/mock_configuration.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_context 'mock configuration' do 2 | let(:default_answer_file_content) { nil } 3 | let(:system_answers_content) { nil } 4 | let(:user_config_content) { nil } 5 | let(:system_config_content) { nil } 6 | 7 | let(:new_config) do 8 | PDK::Config.new.tap do |item| 9 | item.user_config.read_only! 10 | item.system_config.read_only! 11 | item.project_config.read_only! 12 | end 13 | end 14 | 15 | before do 16 | # The PDK.config method memoizes, so create a new read only config object every time 17 | allow(PDK).to receive(:config).and_return(new_config) 18 | 19 | # Mock any configuration file read/writes 20 | # Note - That we don't yet know what the project config filename/path will be so it's not mockable. 21 | [ 22 | { file: PDK::AnswerFile.default_answer_file_path, content: default_answer_file_content }, 23 | { file: PDK::Config.system_answers_path, content: system_answers_content }, 24 | { file: PDK::Config.user_config_path, content: user_config_content }, 25 | { file: PDK::Config.system_config_path, content: system_config_content } 26 | ].each do |item| 27 | # If the content is nil then mock a missing file, otherwise mock a read-able file 28 | if item[:content].nil? 29 | allow(PDK::Util::Filesystem).to receive(:file?).with(PDK::Util::Filesystem.expand_path(item[:file])).and_return(false) 30 | allow(PDK::Util::Filesystem).to receive(:read_file).with(PDK::Util::Filesystem.expand_path(item[:file])).and_raise('Mock configuration file does not exist') 31 | else 32 | allow(PDK::Util::Filesystem).to receive(:file?).with(PDK::Util::Filesystem.expand_path(item[:file])).and_return(true) 33 | allow(PDK::Util::Filesystem).to receive(:read_file).with(PDK::Util::Filesystem.expand_path(item[:file])).and_return(item[:content]) 34 | end 35 | allow(PDK::Util::Filesystem).to receive(:write_file).with(PDK::Util::Filesystem.expand_path(item[:file]), anything) 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/support/packaged_install.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_context 'packaged install' do 2 | let(:package_cachedir) { '/package/share/cache' } 3 | 4 | before do 5 | allow(PDK::Util).to receive_messages(package_install?: true, package_cachedir:) 6 | allow(PDK::Util::Filesystem).to receive(:file?).with(/PDK_VERSION/).and_return(true) 7 | allow(PDK::Util::Filesystem).to receive(:exist?).with(/bundle(\.bat)?$/).and_return(true) 8 | allow(PDK::Util::RubyVersion).to receive_messages(versions: { '2.4.4' => '2.4.0' }, default_ruby_version: '2.4.4') 9 | end 10 | end 11 | 12 | RSpec.shared_context 'not packaged install' do 13 | before do 14 | allow(PDK::Util).to receive(:package_install?).and_return(false) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/support/run_outside_module.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_context 'run outside module' do 2 | let(:mock_context) { PDK::Context::None.new(nil) } 3 | 4 | before do 5 | msg = 'must be run from inside a valid module (no metadata.json found)' 6 | allow(PDK::CLI::Util).to receive(:ensure_in_module!).with(any_args).and_raise(PDK::CLI::ExitWithError, msg) 7 | allow(PDK).to receive(:context).and_return(mock_context) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/support/valid_in_context.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples_for 'only valid in specified PDK contexts' do |*context_class| 2 | describe '.valid_in_context?' do 3 | [ 4 | PDK::Context::None.new(nil), 5 | PDK::Context::Module.new(nil, nil), 6 | PDK::Context::ControlRepo.new(nil, nil) 7 | ].each do |pdk_context| 8 | context "in #{pdk_context.display_name}" do 9 | subject { described_class.new(pdk_context, {}).valid_in_context? } 10 | 11 | if context_class.include?(pdk_context.class) 12 | it { is_expected.to be(true) } 13 | else 14 | it { is_expected.to be(false) } 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/unit/pdk/bolt_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/util' 3 | 4 | describe PDK::Bolt do 5 | describe '.bolt_project_root?' do 6 | # We use NUL here because that should never be a valid directory name. But it will work with RSpec mocking. 7 | let(:test_path) { '\x00path/test' } 8 | 9 | before do 10 | allow(PDK::Util::Filesystem).to receive(:file?).and_call_original 11 | end 12 | 13 | # Directories which indicate a bolt project 14 | ['Boltdir'].each do |testcase| 15 | it "detects the directory #{testcase} as being the root of a bolt project" do 16 | path = File.join(test_path, testcase) 17 | allow(PDK::Util::Filesystem).to receive(:directory?).with(path).and_return(true) 18 | expect(described_class.bolt_project_root?(path)).to be(true) 19 | end 20 | end 21 | 22 | # Directories which do not indicate a bolt project 23 | ['boltdir', 'Boltdir/something'].each do |testcase| 24 | it "detects the directory #{testcase} as not being the root of a bolt project" do 25 | path = File.join(test_path, testcase) 26 | allow(PDK::Util::Filesystem).to receive(:directory?).with(path).and_return(true) 27 | expect(described_class.bolt_project_root?(path)).to be(false) 28 | end 29 | end 30 | 31 | # Files which indicate a bolt project 32 | ['bolt.yaml'].each do |testcase| 33 | it "detects ./#{testcase} as being in the root of a bolt project" do 34 | allow(PDK::Util::Filesystem).to receive(:file?).with(File.join(test_path, testcase)).and_return(true) 35 | expect(described_class.bolt_project_root?(test_path)).to be(true) 36 | end 37 | end 38 | 39 | # Files which do not indicate a bolt project 40 | ['Puppetfile', 'environment.conf', 'metadata.json'].each do |testcase| 41 | it "detects ./#{testcase} as not being in the root of a bolt project" do 42 | allow(PDK::Util::Filesystem).to receive(:file?).with(File.join(test_path, testcase)).and_return(true) 43 | expect(described_class.bolt_project_root?(test_path)).to be(false) 44 | end 45 | end 46 | 47 | it 'uses the current directory if a directory is not specified' do 48 | expect(PDK::Util::Filesystem).to receive(:file?) { |path| expect(path).to start_with(Dir.pwd) }.at_least(:once) 49 | described_class.bolt_project_root? 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/unit/pdk/cli/bundle_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/cli' 3 | 4 | describe 'Running `pdk bundle`' do 5 | let(:command_args) { ['bundle'] } 6 | let(:command_result) { { exit_code: 0 } } 7 | 8 | context 'when it calls bundler successfully' do 9 | before do 10 | mock_command = instance_double( 11 | PDK::CLI::Exec::InteractiveCommand, 12 | :context= => true, 13 | :update_environment => true, 14 | :execute! => command_result 15 | ) 16 | allow(PDK::CLI::Exec::InteractiveCommand).to receive(:new) 17 | .with(PDK::CLI::Exec.bundle_bin, *(command_args[1..] || [])) 18 | .and_return(mock_command) 19 | 20 | allow(PDK::Util).to receive(:module_root) 21 | .and_return(File.join('path', 'to', 'test', 'module')) 22 | allow(PDK::Module::Metadata).to receive(:from_file) 23 | .with('metadata.json').and_return({}) 24 | allow(PDK::CLI::Util).to receive(:puppet_from_opts_or_env) 25 | .and_return(ruby_version: '2.4.3', gemset: { puppet: '5.4.0' }) 26 | allow(PDK::Util::RubyVersion).to receive(:use) 27 | end 28 | 29 | context 'and called with no arguments' do 30 | it 'runs without an error' do 31 | expect do 32 | PDK::CLI.run(command_args) 33 | end.to exit_zero 34 | end 35 | end 36 | 37 | context 'and called with a bundler subcommand that is not "exec"' do 38 | let(:command_args) { super() + ['config', 'something'] } 39 | 40 | it 'runs without an error' do 41 | expect do 42 | PDK::CLI.run(command_args) 43 | end.to exit_zero 44 | end 45 | end 46 | 47 | context 'and called with the "exec" bundler subcommand' do 48 | let(:command_args) { super() + ['exec', 'rspec', 'some/path'] } 49 | 50 | it 'runs without an error' do 51 | expect do 52 | PDK::CLI.run(command_args) 53 | end.to exit_zero 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /spec/unit/pdk/cli/env_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/cli' 3 | 4 | describe 'Running `pdk env`' do 5 | let(:command_args) { ['env'] } 6 | let(:command_result) { { exit_code: 0 } } 7 | 8 | context 'when it calls env successfully' do 9 | after do 10 | expect do 11 | PDK::CLI.run(command_args) 12 | end.to exit_zero 13 | end 14 | 15 | before do 16 | allow(PDK::Util::RubyVersion).to receive_messages(gem_home: '/opt/puppetlabs/pdk/share/cache/ruby/2.4.0', gem_path: '/opt/puppetlabs/pdk/private/ruby/2.4.3/lib', 17 | bin_path: '/opt/puppetlabs/pdk/private/ruby/2.4.3/bin', gem_paths_raw: ['/opt/puppetlabs/pdk/private/ruby/2.4.3/lib']) 18 | allow(PDK::Util::Env).to receive(:[]).and_call_original 19 | allow(PDK::Util::Env).to receive(:[]).with('PATH').and_return('/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin') 20 | 21 | allow(PDK::CLI::Util).to receive(:puppet_from_opts_or_env) 22 | .and_return(ruby_version: '2.4.3', gemset: { puppet: '5.4.0' }) 23 | allow(PDK::Util::RubyVersion).to receive(:use) 24 | end 25 | 26 | context 'and called with no arguments' do 27 | it 'outputs export commands for environment variables' do 28 | output_regexes = [ 29 | /export PDK_RESOLVED_PUPPET_VERSION="\d\.\d+\.\d+"/, 30 | /export PDK_RESOLVED_RUBY_VERSION="\d\.\d+\.\d+"/, 31 | /export GEM_HOME=.*/, 32 | /export GEM_PATH=.*/, 33 | /export PATH=.*/ 34 | ] 35 | 36 | output_regexes.each do |regex| 37 | expect($stdout).to receive(:puts).with(a_string_matching(regex)) 38 | end 39 | end 40 | end 41 | 42 | context 'and called with a puppet version' do 43 | let(:command_args) { super() + ['--puppet-version=6'] } 44 | 45 | it 'outputs export commands for environment variables' do 46 | output_regexes = [ 47 | /export PDK_RESOLVED_PUPPET_VERSION="\d\.\d+\.\d+"/, 48 | /export PDK_RESOLVED_RUBY_VERSION="\d\.\d+\.\d+"/, 49 | /export GEM_HOME=.*/, 50 | /export GEM_PATH=.*/, 51 | /export PATH=.*/ 52 | ] 53 | 54 | output_regexes.each do |regex| 55 | expect($stdout).to receive(:puts).with(a_string_matching(regex)) 56 | end 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /spec/unit/pdk/cli/errors_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/cli/errors' 3 | 4 | describe PDK::CLI::FatalError do # rubocop:disable RSpec/MultipleDescribes 5 | subject(:error) { described_class.new } 6 | 7 | it 'has a default error message' do 8 | expect(error.message).to match(/an unexpected error has occurred/i) 9 | end 10 | 11 | it 'has a default non-zero exit code' do 12 | expect(error.exit_code).to be_an(Integer) 13 | expect(error.exit_code).not_to be_zero 14 | end 15 | 16 | context 'when provided a custom error message' do 17 | subject(:error) { described_class.new('test message') } 18 | 19 | it 'uses the custom error message' do 20 | expect(error.message).to eq('test message') 21 | end 22 | 23 | context 'and a custom exit code' do 24 | subject(:error) { described_class.new('test message', exit_code: 2) } 25 | 26 | it 'uses the custom exit code' do 27 | expect(error.exit_code).to eq(2) 28 | end 29 | end 30 | end 31 | end 32 | 33 | describe PDK::CLI::ExitWithError do 34 | subject(:error) { described_class.new(message, options) } 35 | 36 | let(:message) { 'test message' } 37 | let(:options) { {} } 38 | 39 | it 'uses the provided error message' do 40 | expect(error.message).to eq(message) 41 | end 42 | 43 | it 'has a default non-zero exit code' do 44 | expect(error.exit_code).to be_an(Integer) 45 | expect(error.exit_code).not_to be_zero 46 | end 47 | 48 | it 'has a default log level of error' do 49 | expect(error.log_level).to eq(:error) 50 | end 51 | 52 | context 'when provided with a custom exit code' do 53 | let(:options) { { exit_code: 2 } } 54 | 55 | it 'uses the custom exit code' do 56 | expect(error.exit_code).to eq(2) 57 | end 58 | end 59 | 60 | context 'when provided with a custom log level' do 61 | let(:options) { { log_level: :info } } 62 | 63 | it 'uses the custom log level' do 64 | expect(error.log_level).to eq(:info) 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec/unit/pdk/cli/exec_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/cli/exec' 3 | 4 | describe PDK::CLI::Exec do 5 | describe '#try_vendored_bin' do 6 | context 'when installed as gem' do 7 | before { allow(PDK::Util).to receive(:package_install?).with(no_args).and_return(false) } 8 | 9 | it 'returns the fallback' do 10 | expect(described_class.try_vendored_bin('something', 'fallback')).to eq 'fallback' 11 | end 12 | end 13 | 14 | context 'when installed as package' do 15 | before do 16 | allow(PDK::Util).to receive(:package_install?).with(no_args).and_return(true) 17 | allow(PDK::Util).to receive(:pdk_package_basedir).with(no_args).and_return('/foo') 18 | end 19 | 20 | context 'when the file exists in the package' do 21 | before do 22 | allow(PDK::Util::Filesystem).to receive(:exist?).with('/foo/private/bin/bar').and_return(true) 23 | end 24 | 25 | it 'returns the full path' do 26 | expect(described_class.try_vendored_bin('private/bin/bar', 'fallback')).to eq '/foo/private/bin/bar' 27 | end 28 | end 29 | 30 | context 'when the file is not in the package' do 31 | before do 32 | allow(PDK::Util::Filesystem).to receive(:exist?).with('/foo/private/bin/bar').and_return(false) 33 | end 34 | 35 | it 'returns the full path' do 36 | expect(described_class.try_vendored_bin('private/bin/bar', 'fallback')).to eq 'fallback' 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/unit/pdk/cli/new/class_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/cli' 3 | 4 | describe 'PDK::CLI new class' do 5 | let(:help_text) { a_string_matching(/^USAGE\s+pdk new class/m) } 6 | 7 | before do 8 | # Stop printing out the result 9 | allow(PDK::CLI::Util::UpdateManagerPrinter).to receive(:print_summary) 10 | end 11 | 12 | context 'when not run from inside a module' do 13 | include_context 'run outside module' 14 | 15 | it 'exits with an error' do 16 | expect(logger).to receive(:error).with(a_string_matching(/must be run from inside a valid module/)) 17 | 18 | expect { PDK::CLI.run(['new', 'class', 'test_class']) }.to exit_nonzero 19 | end 20 | end 21 | 22 | context 'when run from inside a module' do 23 | let(:root_dir) { '/path/to/test/module' } 24 | 25 | before do 26 | allow(PDK::Util).to receive(:module_root).and_return(root_dir) 27 | end 28 | 29 | context 'and not provided with a class name' do 30 | it 'exits non-zero and prints the `pdk new class` help' do 31 | expect { PDK::CLI.run(['new', 'class']) }.to exit_nonzero.and output(help_text).to_stdout 32 | end 33 | end 34 | 35 | context 'and provided an empty string as the class name' do 36 | it 'exits non-zero and prints the `pdk new class` help' do 37 | expect { PDK::CLI.run(['new', 'class', '']) }.to exit_nonzero.and output(help_text).to_stdout 38 | end 39 | end 40 | 41 | context 'and provided an invalid class name' do 42 | it 'exits with an error' do 43 | expect(logger).to receive(:error).with(a_string_matching(/'test-class' is not a valid class name/)) 44 | 45 | expect { PDK::CLI.run(['new', 'class', 'test-class']) }.to exit_nonzero 46 | end 47 | end 48 | 49 | context 'and provided a valid class name' do 50 | let(:generator) { instance_double(PDK::Generate::PuppetClass, run: true) } 51 | 52 | after do 53 | PDK::CLI.run(['new', 'class', 'test_class']) 54 | end 55 | 56 | it 'generates the class' do 57 | expect(PDK::Generate::PuppetClass).to receive(:new).with(anything, 'test_class', instance_of(Hash)).and_return(generator) 58 | expect(generator).to receive(:run) 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/unit/pdk/cli/new_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/cli' 3 | 4 | describe 'Running `pdk new`' do 5 | subject { PDK::CLI.instance_variable_get(:@new_cmd) } 6 | 7 | context 'when no arguments or options are provided' do 8 | it do 9 | expect do 10 | PDK::CLI.run(['new']) 11 | end.to output(/^USAGE\s+pdk new/m).to_stdout 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/unit/pdk/cli/release/prep_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/cli' 3 | 4 | describe 'PDK::CLI release prep' do 5 | let(:help_text) { a_string_matching(/^USAGE\s+pdk release prep/m) } 6 | let(:cli_args) { ['release', 'prep'] } 7 | 8 | context 'when not run from inside a module' do 9 | include_context 'run outside module' 10 | 11 | it 'exits with an error' do 12 | expect(logger).to receive(:error).with(a_string_matching(/must be run from inside a valid module/)) 13 | 14 | expect { PDK::CLI.run(cli_args) }.to exit_nonzero 15 | end 16 | end 17 | 18 | context 'when run from inside a module' do 19 | let(:release_object) do 20 | instance_double( 21 | PDK::Module::Release, 22 | pdk_compatible?: true, 23 | module_metadata: mock_metadata_obj, 24 | run: nil 25 | ) 26 | end 27 | 28 | let(:mock_metadata_obj) do 29 | instance_double( 30 | PDK::Module::Metadata, 31 | forge_ready?: true 32 | ) 33 | end 34 | 35 | before do 36 | allow(PDK::CLI::Util).to receive(:ensure_in_module!).and_return(nil) 37 | allow(PDK::Module::Release).to receive(:new).and_return(release_object) 38 | allow(PDK::Util).to receive(:exit_process).and_raise('exit_process mock should not be called') 39 | end 40 | 41 | it 'calls PDK::Module::Release.run' do 42 | expect(release_object).to receive(:run) 43 | 44 | expect { PDK::CLI.run(cli_args.push('--force')) }.not_to raise_error 45 | end 46 | 47 | it 'skips building and publishing' do 48 | expect(PDK::Module::Release).to receive(:new).with(Object, hash_including('skip-build': true, 'skip-publish': true)) 49 | 50 | expect { PDK::CLI.run(cli_args.push('--force')) }.not_to raise_error 51 | end 52 | 53 | it 'does not start an interview when --force is used' do 54 | expect(PDK::CLI::Util::Interview).not_to receive(:new) 55 | 56 | PDK::CLI.run(cli_args.push('--force')) 57 | end 58 | 59 | it 'calls PDK::CLI::Release.module_compatibility_checks!' do 60 | expect(PDK::CLI::Release).to receive(:module_compatibility_checks!).and_return(nil) 61 | 62 | expect { PDK::CLI.run(cli_args.push('--force')) }.not_to raise_error 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/unit/pdk/cli/test_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/cli' 3 | 4 | describe 'Running `pdk test`' do 5 | subject { PDK::CLI.instance_variable_get(:@test_cmd) } 6 | 7 | it { is_expected.not_to be_nil } 8 | 9 | context 'when no arguments or options are provided' do 10 | it do 11 | expect do 12 | PDK::CLI.run(['test']) 13 | end.to output(/^USAGE\s+pdk test/m).to_stdout 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/unit/pdk/cli/util/command_redirector_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/cli/util/command_redirector' 3 | 4 | describe PDK::CLI::Util::CommandRedirector do 5 | subject(:command_redirector) do 6 | described_class.new(prompt, {}) 7 | end 8 | 9 | let(:prompt) { instance_double(TTY::Prompt) } 10 | 11 | let(:command) { 'foo' } 12 | 13 | it 'initially has no target command' do 14 | expect(command_redirector.command).to be_nil 15 | end 16 | 17 | it 'sets the target' do 18 | command_redirector.target_command(command) 19 | expect(command_redirector.command).to eq(command) 20 | end 21 | 22 | it 'prints a query when run' do 23 | command_redirector.target_command('foo') 24 | expect(prompt).to receive(:puts).with(a_string_matching(/Did you mean.*foo.*?/i)) 25 | expect(prompt).to receive(:yes?).with('-->') 26 | command_redirector.run 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/unit/pdk/cli/util/interview_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/cli/util/interview' 3 | 4 | describe 'Module interview' do 5 | subject(:interview) { PDK::CLI::Util::Interview } 6 | 7 | it 'initially has 0 questions' do 8 | expect(interview.new({}, {}).num_questions).to eq(0) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/unit/pdk/config/ini_file_setting_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/config/ini_file_setting' 3 | 4 | describe PDK::Config::IniFileSetting do 5 | subject(:setting) { described_class.new('spec_setting', namespace, initial_value) } 6 | 7 | let(:initial_value) { nil } 8 | 9 | let(:namespace) { PDK::Config::IniFile.new('spec') } 10 | 11 | context 'when not in an Ini File Namespace' do 12 | let(:namespace) { PDK::Config::Namespace.new } 13 | 14 | it 'raises' do 15 | expect { setting }.to raise_error(/IniFile Namespace/) 16 | end 17 | end 18 | 19 | context 'with invalid initial value' do 20 | let(:initial_value) { ['abc', '123'] } 21 | 22 | it 'raises' do 23 | expect { setting }.to raise_error(ArgumentError, /spec_setting/) 24 | end 25 | end 26 | 27 | RSpec.shared_examples 'a setting validator' do |value_type_name, value| 28 | it "validates #{value_type_name}" do 29 | expect { setting.validate!(value) }.not_to raise_error 30 | end 31 | 32 | it "validates #{value_type_name} in a simple hash" do 33 | expect { setting.validate!('foo' => value) }.not_to raise_error 34 | end 35 | end 36 | 37 | RSpec.shared_examples 'an error raising validator' do |value_type_name, value| 38 | it "raises for #{value_type_name}" do 39 | expect { setting.validate!(value) }.to raise_error(ArgumentError, /spec_setting/) 40 | end 41 | end 42 | 43 | describe '#validate!' do 44 | context 'with valid values' do 45 | include_examples 'a setting validator', 'String', 'value' 46 | include_examples 'a setting validator', 'Nil', nil 47 | include_examples 'a setting validator', 'Integer', 1 48 | end 49 | 50 | context 'with invalid values' do 51 | include_examples 'an error raising validator', 'Symbol', :value 52 | include_examples 'an error raising validator', 'Array', ['abc', '123'] 53 | include_examples 'an error raising validator', 'Nested hash', 'foo' => { 'bar' => 'baz' } 54 | include_examples 'an error raising validator', 'Float', 1.0 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /spec/unit/pdk/config/json_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'tempfile' 3 | require 'pdk/config/json' 4 | 5 | describe PDK::Config::JSON do 6 | subject(:json_config) { described_class.new(file: tempfile) } 7 | 8 | let(:tempfile) do 9 | file = Tempfile.new('test') 10 | file.path 11 | end 12 | 13 | it_behaves_like 'a file based namespace', "{\n \"foo\": \"bar\"\n}", 'foo' => 'bar' 14 | 15 | it_behaves_like 'a file based namespace without a schema' 16 | 17 | it_behaves_like 'a json file based namespace' 18 | end 19 | -------------------------------------------------------------------------------- /spec/unit/pdk/config/json_with_schema_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/config/json_with_schema' 3 | require 'tempfile' 4 | 5 | # Note that the JSON Schema Gem is too unreliable for testing right now. 6 | # For the moment, all tests are skipped here. 7 | describe PDK::Config::JSONWithSchema, :skip do 8 | subject(:json_config) { described_class.new(file: tempfile, schema_file: temp_schema_file) } 9 | 10 | let(:tempfile) do 11 | file = Tempfile.new('test') 12 | file.write(data) 13 | file.close 14 | file.path 15 | end 16 | let(:data) { nil } 17 | let(:temp_schema_file) do 18 | file = Tempfile.new('schema') 19 | file.write(schema_data) 20 | file.close 21 | file.path 22 | end 23 | 24 | let(:schema_data) do 25 | <<-SCHEMA 26 | { 27 | "definitions": {}, 28 | "$schema": "http://json-schema.org/draft-06/schema#", 29 | "$id": "http://puppet.com/schema/does_not_exist.json", 30 | "type": "object", 31 | "title": "A Schema", 32 | "properties": { 33 | "foo": { 34 | "$id": "#/properties/foo", 35 | "title": "A property" 36 | } 37 | } 38 | } 39 | SCHEMA 40 | end 41 | 42 | it_behaves_like 'a file based namespace', "{\n \"foo\": \"bar\"\n}", 'foo' => 'bar' 43 | 44 | it_behaves_like 'a file based namespace with a schema', "{\n\"extra_setting\": \"extra_value\",\n\"foo\": \"oldvalue\"\n}\n" 45 | 46 | it_behaves_like 'a json file based namespace' 47 | 48 | it 'inherits from JSONSchemaNamespace' do 49 | expect(json_config).to be_a(PDK::Config::JSONSchemaNamespace) 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/unit/pdk/config/schema_files_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/config' 3 | require 'json-schema' 4 | 5 | describe 'PDK::Config Schema Files' do 6 | PDK::Util::Filesystem.glob(File.join(PDK::Config.json_schemas_path, '*_schema.json')).each do |schema_path| 7 | describe File.basename(schema_path) do 8 | # rubocop:disable PDK/FileOpen 9 | subject(:schema) { JSON.parse(File.open(schema_path, 'rb:UTF-8').read) } 10 | # rubocop:enable PDK/FileOpen 11 | 12 | it 'is a valid JSON schema document' do 13 | # The Schema Document specifies which schema version it uses 14 | expect(schema['$schema']).to match(/draft-\d+/) 15 | metaschema = JSON::Validator.validator_for_name(schema['$schema']).metaschema 16 | 17 | expect(JSON::Validator.validate(metaschema, schema)).to be true 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/unit/pdk/config/yaml_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/config/yaml' 3 | 4 | describe PDK::Config::YAML do 5 | subject(:yaml_config) { described_class.new(file: tempfile) } 6 | 7 | let(:tempfile) do 8 | file = Tempfile.new('test') 9 | file.write(data) 10 | file.close 11 | file.path 12 | end 13 | let(:data) { nil } 14 | 15 | it_behaves_like 'a file based namespace', "---\nfoo: bar\n", 'foo' => 'bar' 16 | 17 | it_behaves_like 'a file based namespace without a schema' 18 | 19 | it_behaves_like 'a yaml file based namespace' 20 | end 21 | -------------------------------------------------------------------------------- /spec/unit/pdk/config/yaml_with_schema_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/config/yaml_with_schema' 3 | require 'tempfile' 4 | 5 | # Note that the JSON Schema Gem is too unreliable for testing right now. 6 | # For the moment, all tests are skipped here. 7 | describe PDK::Config::YAMLWithSchema, :skip do 8 | subject(:yaml_config) { described_class.new(file: tempfile, schema_file: temp_schema_file) } 9 | 10 | let(:tempfile) do 11 | file = Tempfile.new('test') 12 | file.write(data) 13 | file.close 14 | file.path 15 | end 16 | let(:data) { nil } 17 | let(:temp_schema_file) do 18 | file = Tempfile.new('schema') 19 | file.write(schema_data) 20 | file.close 21 | file.path 22 | end 23 | 24 | let(:schema_data) do 25 | <<-SCHEMA 26 | { 27 | "definitions": {}, 28 | "$schema": "http://json-schema.org/draft-06/schema#", 29 | "$id": "http://puppet.com/schema/does_not_exist.json", 30 | "type": "object", 31 | "title": "A Schema", 32 | "properties": { 33 | "foo": { 34 | "$id": "#/properties/foo", 35 | "title": "A property" 36 | } 37 | } 38 | } 39 | SCHEMA 40 | end 41 | 42 | it_behaves_like 'a file based namespace', "---\nfoo: bar\n", { 'foo' => 'bar' }, true 43 | 44 | it_behaves_like 'a file based namespace with a schema', "---\nextra_setting: \"extra value\"\nfoo: oldvalue" 45 | 46 | it_behaves_like 'a yaml file based namespace' 47 | 48 | it 'inherits from JSONSchemaNamespace' do 49 | expect(yaml_config).to be_a(PDK::Config::JSONSchemaNamespace) 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/unit/pdk/context/control_repo_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/context' 3 | 4 | describe PDK::Context::ControlRepo do 5 | subject(:context) { described_class.new(repo_root, nil) } 6 | 7 | let(:repo_root) { File.join(FIXTURES_DIR, 'control_repo') } 8 | let(:expected_module_paths) { ['modules', 'site', '$basemodulepath'] } 9 | let(:module_paths_in_fixture) { ['site'] } 10 | 11 | it 'subclasses PDK::Context::AbstractContext' do 12 | expect(context).is_a?(PDK::Context::AbstractContext) 13 | end 14 | 15 | it 'remembers the repo root' do 16 | expect(context.root_path).to eq(repo_root) 17 | end 18 | 19 | it 'is PDK compatible' do 20 | expect(context.pdk_compatible?).to be(true) 21 | end 22 | 23 | describe '.module_paths' do 24 | it 'returns an array of paths' do 25 | expect(context.module_paths).to eq(expected_module_paths) 26 | end 27 | 28 | it 'is memoized' do 29 | expect(context).to receive(:environment_conf).and_call_original # rubocop:disable RSpec/SubjectStub We are still calling the original so this is fine 30 | 31 | context.module_paths 32 | context.module_paths 33 | context.module_paths 34 | end 35 | end 36 | 37 | describe '.actualized_module_paths' do 38 | it 'returns absolute paths that exist on disk' do 39 | expect(context.actualized_module_paths).to eq(module_paths_in_fixture) 40 | end 41 | 42 | it 'is memoized' do 43 | expect(context).to receive(:module_paths).and_call_original # rubocop:disable RSpec/SubjectStub We are still calling the original so this is fine 44 | 45 | context.actualized_module_paths 46 | context.actualized_module_paths 47 | context.actualized_module_paths 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/unit/pdk/context/module_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/context' 3 | 4 | describe PDK::Context::Module do 5 | subject(:context) { described_class.new(module_root, nil) } 6 | 7 | let(:module_root) { File.join(FIXTURES_DIR, 'puppet_module') } 8 | 9 | it 'subclasses PDK::Context::AbstractContext' do 10 | expect(context).is_a?(PDK::Context::AbstractContext) 11 | end 12 | 13 | it 'remembers the module root' do 14 | expect(context.root_path).to eq(module_root) 15 | end 16 | 17 | describe '.pdk_compatible?' do 18 | it 'calls PDK::Util to determine compatibility' do 19 | expect(PDK::Util).to receive(:module_pdk_compatible?).with(context.root_path).and_return(true) 20 | expect(context.pdk_compatible?).to be(true) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/unit/pdk/context/none_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/context' 3 | 4 | describe PDK::Context::None do 5 | subject(:context) { described_class.new(nil) } 6 | 7 | it 'subclasses PDK::Context::AbstractContext' do 8 | expect(context).is_a?(PDK::Context::AbstractContext) 9 | end 10 | 11 | it 'has no parent context' do 12 | expect(context.parent_context).to be_nil 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/unit/pdk/generate/provider_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/generate/provider' 3 | 4 | describe PDK::Generate::Provider do 5 | subject(:generator) { described_class.new(context, given_name, options) } 6 | 7 | let(:context) { PDK::Context::Module.new(module_dir, module_dir) } 8 | let(:module_dir) { '/tmp/test_module' } 9 | let(:options) { {} } 10 | let(:given_name) { 'test_provider' } 11 | 12 | it 'inherits from PuppetObject' do 13 | expect(generator).to be_a(PDK::Generate::PuppetObject) 14 | end 15 | 16 | describe '#template_files' do 17 | let(:given_class_name) { module_name } 18 | 19 | context 'when spec_only is true' do 20 | let(:options) { { spec_only: true } } 21 | 22 | it 'only returns spec files' do 23 | expect(generator.template_files.keys).to eq(['provider_spec.erb', 'provider_type_spec.erb']) 24 | end 25 | end 26 | 27 | context 'when spec_only is false' do 28 | let(:options) { { spec_only: false } } 29 | 30 | it 'returns all files' do 31 | expect(generator.template_files.keys).to eq(['provider_spec.erb', 'provider_type_spec.erb', 'provider.erb', 'provider_type.erb']) 32 | end 33 | end 34 | end 35 | 36 | # TODO: Write some tests!! 37 | end 38 | -------------------------------------------------------------------------------- /spec/unit/pdk/generate/transport_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/generate/transport' 3 | 4 | describe PDK::Generate::Transport do 5 | subject(:generator) { described_class.new(context, given_name, options) } 6 | 7 | let(:context) { PDK::Context::Module.new(module_dir, module_dir) } 8 | let(:module_dir) { '/tmp/test_module' } 9 | let(:options) { {} } 10 | let(:given_name) { 'test_transport' } 11 | 12 | it 'inherits from PuppetObject' do 13 | expect(generator).to be_a(PDK::Generate::PuppetObject) 14 | end 15 | 16 | describe '#template_files' do 17 | let(:given_class_name) { module_name } 18 | 19 | context 'when spec_only is true' do 20 | let(:options) { { spec_only: true } } 21 | 22 | it 'only returns spec files' do 23 | expect(generator.template_files.keys).to eq(['transport_spec.erb', 'transport_type_spec.erb']) 24 | end 25 | end 26 | 27 | context 'when spec_only is false' do 28 | let(:options) { { spec_only: false } } 29 | 30 | it 'returns all files' do 31 | expect(generator.template_files.keys).to eq(['transport_spec.erb', 'transport_type_spec.erb', 'transport.erb', 'transport_device.erb', 'transport_type.erb']) 32 | end 33 | end 34 | end 35 | 36 | # TODO: Write some tests!! 37 | end 38 | -------------------------------------------------------------------------------- /spec/unit/pdk/logger_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/logger' 3 | 4 | describe PDK::Logger do 5 | subject(:pdk_logger) { described_class.new } 6 | 7 | context 'by default' do 8 | it 'prints info messages to stdout' do 9 | expect($stderr).to receive(:write).with(a_string_matching(/test message/)) 10 | 11 | pdk_logger.info('test message') 12 | end 13 | 14 | it 'does not print debug messages to stdout' do 15 | expect($stderr).not_to receive(:write).with(anything) 16 | 17 | pdk_logger.debug('test message') 18 | end 19 | 20 | it { is_expected.to have_attributes(debug?: false) } 21 | end 22 | 23 | context 'with debug output enabled' do 24 | before do 25 | pdk_logger.enable_debug_output 26 | end 27 | 28 | it 'prints debug messages to stdout' do 29 | expect($stderr).to receive(:write).with(a_string_matching(/test debug message/)) 30 | 31 | pdk_logger.debug('test debug message') 32 | end 33 | 34 | it { is_expected.to have_attributes(debug?: true) } 35 | end 36 | 37 | describe '#warn_once' do 38 | it 'only sends each message once' do 39 | expect($stderr).to receive(:write).with("pdk (WARN): message 1\n").once 40 | 41 | pdk_logger.warn_once('message 1') 42 | pdk_logger.warn_once('message 1') 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/unit/pdk/template/fetcher/local_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/template/fetcher/local' 3 | 4 | describe PDK::Template::Fetcher::Local do 5 | subject(:fetcher) { described_class.new(template_uri, pdk_context) } 6 | 7 | let(:template_path) { '/some/path' } 8 | let(:template_uri) { PDK::Util::TemplateURI.new(template_path) } 9 | let(:pdk_context) { PDK::Context::None.new(nil) } 10 | 11 | describe '.fetchable?' do 12 | it 'is always fetchable' do 13 | expect(described_class.fetchable?(nil, nil)).to be true 14 | end 15 | end 16 | 17 | describe '.fetch!' do 18 | it 'is not temporary' do 19 | fetcher.fetch! 20 | expect(fetcher.temporary).to be false 21 | end 22 | 23 | it 'uses the path from the uri' do 24 | fetcher.fetch! 25 | expect(fetcher.path).to eq(template_path) 26 | end 27 | 28 | it 'sets template-url in the metadata' do 29 | fetcher.fetch! 30 | expect(fetcher.metadata).to include('template-url' => template_path) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/unit/pdk/template/renderer/v1_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/template/renderer/v1' 3 | 4 | describe PDK::Template::Renderer::V1 do 5 | let(:template_root) { '/some/path' } 6 | let(:template_uri) { PDK::Util::TemplateURI.new(template_root) } 7 | let(:pdk_context) { PDK::Context::None.new(nil) } 8 | 9 | describe '.compatible?' do 10 | subject(:compatible) { described_class.compatible?(template_root, pdk_context) } 11 | 12 | context 'when all module template directories exist' do 13 | before do 14 | allow(PDK::Util::Filesystem).to receive(:directory?).with('/some/path/moduleroot').and_return(true) 15 | allow(PDK::Util::Filesystem).to receive(:directory?).with('/some/path/moduleroot_init').and_return(true) 16 | end 17 | 18 | it 'is compatible' do 19 | expect(compatible).to be true 20 | end 21 | end 22 | 23 | context 'when only some of module template directories exist' do 24 | before do 25 | allow(PDK::Util::Filesystem).to receive(:directory?).with('/some/path/moduleroot').and_return(true) 26 | allow(PDK::Util::Filesystem).to receive(:directory?).with('/some/path/moduleroot_init').and_return(false) 27 | end 28 | 29 | it 'is not compatible' do 30 | expect(compatible).to be false 31 | end 32 | end 33 | end 34 | 35 | describe '.instance' do 36 | it 'creates a PDK::Template::Renderer::V1::Renderer object' do 37 | expect(described_class.instance(template_root, template_uri, pdk_context)).to be_a(described_class::Renderer) 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/unit/pdk/template/renderer_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/template/renderer' 3 | 4 | describe PDK::Template::Renderer do 5 | let(:template_path) { '/some/path' } 6 | let(:template_uri) { PDK::Util::TemplateURI.new(template_path) } 7 | let(:pdk_context) { PDK::Context::None.new(nil) } 8 | 9 | describe '.instance' do 10 | subject(:instance) { described_class.instance(template_uri, template_path, pdk_context) } 11 | 12 | context 'given an original template directory' do 13 | before do 14 | allow(described_class::V1).to receive(:compatible?).and_return(true) 15 | end 16 | 17 | it 'creates a version 1 renderer' do 18 | expect(instance).to be_a(described_class::V1::Renderer) 19 | end 20 | end 21 | 22 | context 'given a template that has no appropriate renderer' do 23 | before do 24 | allow(described_class::V1).to receive(:compatible?).and_return(false) 25 | end 26 | 27 | it 'creates a Local Fetcher object' do 28 | expect(instance).to be_nil 29 | end 30 | end 31 | end 32 | 33 | describe PDK::Template::Renderer::AbstractRenderer do 34 | subject(:renderer) { described_class.new(template_path, template_uri, pdk_context) } 35 | 36 | it 'responds to template_root' do 37 | expect(renderer.template_root).to eq(template_path) 38 | end 39 | 40 | it 'responds to template_uri' do 41 | expect(renderer.template_uri).to eq(template_uri) 42 | end 43 | 44 | it 'responds to context' do 45 | expect(renderer.context).to eq(pdk_context) 46 | end 47 | 48 | it 'responds to has_single_item?' do 49 | expect(renderer.has_single_item?(nil)).to be false 50 | end 51 | 52 | it 'responds to render' do 53 | expect(renderer.render(nil, nil, nil)).to be_nil 54 | end 55 | 56 | it 'responds to render_single_item' do 57 | expect(renderer.render_single_item(nil, nil)).to be_nil 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /spec/unit/pdk/template/template_dir_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/template/template_dir' 3 | 4 | describe PDK::Template::TemplateDir do 5 | subject(:template_dir) { described_class.new(template_uri, template_path, pdk_context, renderer) } 6 | 7 | let(:template_uri) { PDK::Util::TemplateURI.new(PDK::Util::TemplateURI::PDK_TEMPLATE_URL) } 8 | let(:template_path) { '/some/path' } 9 | let(:pdk_context) { PDK::Context::None.new(nil) } 10 | let(:renderer) { instance_double(PDK::Template::Renderer::AbstractRenderer) } 11 | 12 | describe '#instance' do 13 | it 'creates a TemplateDir object' do 14 | expect(described_class.instance(template_uri, template_path, pdk_context, renderer)).to be_a(PDK::Template::TemplateDir) # rubocop:disable RSpec/DescribedClass No, this is correct 15 | end 16 | end 17 | 18 | context 'when not passed a renderer' do 19 | subject(:template_dir) { described_class.new(template_uri, template_path, pdk_context) } 20 | 21 | it 'tries to create a renderer' do 22 | expect(PDK::Template::Renderer).to receive(:instance).with(template_uri, template_path, pdk_context).and_return(renderer) 23 | 24 | template_dir 25 | end 26 | 27 | context 'when a renderer could not be found' do 28 | before do 29 | expect(PDK::Template::Renderer).to receive(:instance).with(template_uri, template_path, pdk_context).and_return(nil) 30 | end 31 | 32 | it 'raises a RuntimeError' do 33 | expect { template_dir }.to raise_error(RuntimeError, /Could not find a compatible/) 34 | end 35 | end 36 | end 37 | 38 | it 'has a uri method' do 39 | expect(template_dir.uri).to be(template_uri) 40 | end 41 | 42 | it 'has a path method' do 43 | expect(template_dir.path).to be(template_path) 44 | end 45 | 46 | it 'has a metadata method' do 47 | expect(template_dir.metadata).to eq({}) 48 | end 49 | 50 | it 'delegates a render method' do 51 | expect(renderer).to receive(:render) 52 | template_dir.render(nil, nil, nil) 53 | end 54 | 55 | it 'delegates a render_single_item method' do 56 | expect(renderer).to receive(:render_single_item) 57 | template_dir.render_single_item(nil, nil) 58 | end 59 | 60 | it 'delegates a has_single_item? method' do 61 | expect(renderer).to receive(:has_single_item?) 62 | template_dir.has_single_item?(nil) 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/unit/pdk/template_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/template' 3 | 4 | describe PDK::Template do 5 | let(:pdk_context) { PDK::Context::None.new(nil) } 6 | let(:template_uri) { PDK::Util::TemplateURI.new(PDK::Util::TemplateURI::PDK_TEMPLATE_URL) } 7 | 8 | describe '.with' do 9 | context 'when not passed a block' do 10 | it 'raises an ArgumentError' do 11 | expect do 12 | described_class.with(template_uri, pdk_context) 13 | end.to raise_error(ArgumentError, /must be passed a block/i) 14 | end 15 | end 16 | 17 | context 'when not initialized with a PDK::Util::TemplateURI' do 18 | let(:template_uri) { 'string uri' } 19 | 20 | it 'raises an ArgumentError' do 21 | expect do 22 | described_class.with(template_uri, pdk_context) {} 23 | end.to raise_error(ArgumentError, /must be passed a PDK::Util::TemplateURI/i) 24 | end 25 | end 26 | 27 | context 'when initialized correctly' do 28 | let(:fetcher) { described_class::Fetcher::AbstractFetcher.new(template_uri, {}) } 29 | let(:template_dir) do 30 | described_class::TemplateDir.new( 31 | template_uri, 32 | nil, 33 | pdk_context, 34 | instance_double(described_class::Renderer::AbstractRenderer) 35 | ) 36 | end 37 | 38 | before do 39 | expect(described_class::Fetcher).to receive(:with).with(template_uri).and_yield(fetcher) 40 | allow(described_class::TemplateDir).to receive(:instance).with(template_uri, anything, pdk_context).and_return(template_dir) 41 | end 42 | 43 | it 'fetches remote templates' do 44 | described_class.with(template_uri, pdk_context) {} 45 | end 46 | 47 | it 'yields a PDK::Template::TemplateDir' do 48 | expect { |b| described_class.with(template_uri, pdk_context, &b) }.to yield_with_args(template_dir) 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/unit/pdk/util/env_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/util/env' 3 | require 'securerandom' 4 | 5 | def on_windows 6 | Gem.win_platform? 7 | end 8 | 9 | describe PDK::Util::Env do 10 | before do 11 | ENV[env_name] = env_val 12 | end 13 | 14 | after do 15 | ENV.delete(env_name) 16 | end 17 | 18 | let(:env_name) { "#{SecureRandom.hex(10)}ABCabc" } 19 | let(:env_val) { 'PDK::Util::Env test value' } 20 | let(:upcase_name) { env_name.upcase } 21 | let(:downcase_name) { env_name.upcase } 22 | 23 | describe '[]' do 24 | it 'is case insensitive on Windows platform', if: on_windows do 25 | expect(described_class[env_name]).to eq(env_val) 26 | expect(described_class[downcase_name]).to eq(env_val) 27 | expect(described_class[upcase_name]).to eq(env_val) 28 | end 29 | 30 | it 'is case sensitive on non-Windows platform', unless: on_windows do 31 | expect(described_class[env_name]).to eq(env_val) 32 | expect(described_class[downcase_name]).to be_nil 33 | expect(described_class[upcase_name]).to be_nil 34 | end 35 | end 36 | 37 | describe '[]=' do 38 | let(:new_val) { 'New PDK::Util::Env test value' } 39 | 40 | before do 41 | # Order is important here. 42 | ENV.delete(upcase_name) 43 | ENV[env_name] = env_val 44 | expect(described_class[env_name]).to eq(env_val) 45 | end 46 | 47 | after do 48 | ENV.delete(upcase_name) 49 | end 50 | 51 | it 'is case insensitive on Windows platform', if: on_windows do 52 | described_class[upcase_name] = new_val 53 | expect(described_class[env_name]).to eq(new_val) 54 | expect(described_class[upcase_name]).to eq(new_val) 55 | end 56 | 57 | it 'is case sensitive on non-Windows platform', unless: on_windows do 58 | described_class[upcase_name] = new_val 59 | expect(described_class[env_name]).to eq(env_val) 60 | expect(described_class[upcase_name]).to eq(new_val) 61 | end 62 | end 63 | 64 | describe '.key?' do 65 | let(:new_val) { 'New PDK::Util::Env test value' } 66 | 67 | it 'is case insensitive on Windows platform', if: on_windows do 68 | expect(described_class.key?(env_name)).to be true 69 | expect(described_class.key?(downcase_name)).to be true 70 | expect(described_class.key?(upcase_name)).to be true 71 | end 72 | 73 | it 'is case sensitive on non-Windows platform', unless: on_windows do 74 | expect(described_class.key?(env_name)).to be true 75 | expect(described_class.key?(downcase_name)).to be false 76 | expect(described_class.key?(upcase_name)).to be false 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /spec/unit/pdk/util/version_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/util/version' 3 | 4 | describe PDK::Util::Version do 5 | context 'Getting the version_string' do 6 | subject(:version_string) { described_class.version_string } 7 | 8 | it { is_expected.not_to be_nil } 9 | end 10 | 11 | context 'when running from a checkout' do 12 | before do 13 | allow(PDK::Util).to receive(:find_upwards).and_return('/tmp/package/PDK_VERSION') 14 | allow(PDK::Util::Filesystem).to receive(:exist?).with('/tmp/package/PDK_VERSION').and_return(false) 15 | allow(PDK::Util::Filesystem).to receive(:directory?).with(/.git\Z/).and_return(true) 16 | 17 | result = instance_double(Hash) 18 | allow(result).to receive(:[]).with(:stdout).and_return('git_hash') 19 | allow(result).to receive(:[]).with(:exit_code).and_return(0) 20 | 21 | allow(PDK::Util::Git).to receive(:git).with('--git-dir', /.git\Z/, 'describe', '--all', '--long', '--always').and_return(result) 22 | end 23 | 24 | describe '#git_ref' do 25 | it { expect(described_class.git_ref).to eq 'git_hash' } 26 | end 27 | 28 | describe '#pkg_sha' do 29 | it { expect(described_class.pkg_sha).to be_nil } 30 | end 31 | end 32 | 33 | context 'when running from a package' do 34 | before do 35 | allow(PDK::Util).to receive(:find_upwards).and_return('/tmp/package/PDK_VERSION') 36 | allow(PDK::Util::Filesystem).to receive(:exist?).with('/tmp/package/PDK_VERSION').and_return(true) 37 | allow(PDK::Util::Filesystem).to receive(:read_file).with('/tmp/package/PDK_VERSION').and_return('0.1.2.3.4.pkg_hash') 38 | allow(PDK::Util::Filesystem).to receive(:directory?).with(/.git\Z/).and_return(false) 39 | expect(PDK::CLI::Exec).not_to receive(:git) 40 | end 41 | 42 | describe '#git_ref' do 43 | it { expect(described_class.git_ref).to be_nil } 44 | end 45 | 46 | describe '#pkg_sha' do 47 | it { expect(described_class.pkg_sha).to eq 'pkg_hash' } 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/unit/pdk/validate/control_repo/control_repo_validator_group_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/validate/control_repo/control_repo_validator_group' 3 | 4 | describe PDK::Validate::ControlRepo::ControlRepoValidatorGroup do 5 | subject(:validator) { described_class.new(validator_context, validator_options) } 6 | 7 | let(:validator_context) { nil } 8 | let(:validator_options) { {} } 9 | 10 | describe '.name' do 11 | subject { validator.name } 12 | 13 | it { is_expected.to eq('control-repo') } 14 | end 15 | 16 | it_behaves_like 'only valid in specified PDK contexts', PDK::Context::ControlRepo 17 | 18 | describe '.validators' do 19 | subject { validator.validators } 20 | 21 | it { is_expected.not_to be_empty } 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/unit/pdk/validate/metadata/metadata_validator_group_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/validate/metadata/metadata_validator_group' 3 | 4 | describe PDK::Validate::Metadata::MetadataValidatorGroup do 5 | describe '.name' do 6 | subject { described_class.new.name } 7 | 8 | it { is_expected.to eq('metadata') } 9 | end 10 | 11 | describe '.validators' do 12 | subject { described_class.new.validators } 13 | 14 | it { is_expected.not_to be_empty } 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/unit/pdk/validate/puppet/puppet_validator_group_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/validate/puppet/puppet_validator_group' 3 | 4 | describe PDK::Validate::Puppet::PuppetValidatorGroup do 5 | describe '.name' do 6 | subject { described_class.new.name } 7 | 8 | it { is_expected.to eq('puppet') } 9 | end 10 | 11 | describe '.validators' do 12 | subject { described_class.new.validators } 13 | 14 | it { is_expected.not_to be_empty } 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/unit/pdk/validate/ruby/ruby_validator_group_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/validate/ruby/ruby_validator_group' 3 | 4 | describe PDK::Validate::Ruby::RubyValidatorGroup do 5 | describe '.name' do 6 | subject { described_class.new.name } 7 | 8 | it { is_expected.to eq('ruby') } 9 | end 10 | 11 | describe '.validators' do 12 | subject { described_class.new.validators } 13 | 14 | it { is_expected.not_to be_empty } 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/unit/pdk/validate/tasks/tasks_validator_group_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/validate/tasks/tasks_validator_group' 3 | 4 | describe PDK::Validate::Tasks::TasksValidatorGroup do 5 | describe '.name' do 6 | subject { described_class.new.name } 7 | 8 | it { is_expected.to eq('tasks') } 9 | end 10 | 11 | describe '.validators' do 12 | subject { described_class.new.validators } 13 | 14 | it { is_expected.not_to be_empty } 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/unit/pdk/validate/yaml/yaml_validator_group_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/validate/yaml/yaml_validator_group' 3 | 4 | describe PDK::Validate::YAML::YAMLValidatorGroup do 5 | describe '.name' do 6 | subject { described_class.new.name } 7 | 8 | it { is_expected.to eq('yaml') } 9 | end 10 | 11 | describe '.validators' do 12 | subject { described_class.new.validators } 13 | 14 | it { is_expected.not_to be_empty } 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/unit/pdk/version_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pdk/version' 3 | 4 | describe 'PDK version string' do 5 | it 'has major minor and patch numbers' do 6 | expect(PDK::VERSION).to match(/^[0-9]+\.[0-9]+\.[0-9]+/) 7 | end 8 | end 9 | --------------------------------------------------------------------------------