diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..12ed4ff --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,6 @@ +FROM puppet/pdk:latest + +# [Optional] Uncomment this section to install additional packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends + diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..f1a55dc --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,23 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.140.1/containers/puppet +{ + "name": "Puppet Development Kit (Community)", + "dockerFile": "Dockerfile", + + // Set *default* container specific settings.json values on container create. + "settings": { + "terminal.integrated.shell.linux": "/bin/bash" + }, + + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "puppet.puppet-vscode", + "rebornix.Ruby" + ] + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "pdk --version", +} diff --git a/.github/workflows/auto_release.yml b/.github/workflows/auto_release.yml new file mode 100644 index 0000000..e028483 --- /dev/null +++ b/.github/workflows/auto_release.yml @@ -0,0 +1,84 @@ +name: "Auto release" + +on: + workflow_dispatch: + +env: + HONEYCOMB_WRITEKEY: 7f3c63a70eecc61d635917de46bea4e6 + HONEYCOMB_DATASET: litmus tests + CHANGELOG_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + auto_release: + name: "Automatic release prep" + runs-on: ubuntu-20.04 + + steps: + - name: "Honeycomb: Start recording" + uses: puppetlabs/kvrhdn-gha-buildevents@pdk-templates-v1 + with: + apikey: ${{ env.HONEYCOMB_WRITEKEY }} + dataset: ${{ env.HONEYCOMB_DATASET }} + job-status: ${{ job.status }} + + - name: "Honeycomb: start first step" + run: | + echo STEP_ID="auto-release" >> $GITHUB_ENV + echo STEP_START=$(date +%s) >> $GITHUB_ENV + + - name: "Checkout Source" + if: ${{ github.repository_owner == 'puppetlabs' }} + uses: actions/checkout@v2 + with: + fetch-depth: 0 + persist-credentials: false + + - name: "PDK Release prep" + uses: docker://puppet/iac_release:ci + with: + args: 'release prep --force' + env: + CHANGELOG_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: "Get Version" + if: ${{ github.repository_owner == 'puppetlabs' }} + id: gv + run: | + echo "::set-output name=ver::$(jq --raw-output .version metadata.json)" + + - name: "Commit changes" + if: ${{ github.repository_owner == 'puppetlabs' }} + run: | + git config --local user.email "${{ github.repository_owner }}@users.noreply.github.com" + git config --local user.name "GitHub Action" + git add . + git commit -m "Release prep v${{ steps.gv.outputs.ver }}" + + - name: Create Pull Request + id: cpr + uses: puppetlabs/peter-evans-create-pull-request@v3 + if: ${{ github.repository_owner == 'puppetlabs' }} + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: "Release prep v${{ steps.gv.outputs.ver }}" + branch: "release-prep" + delete-branch: true + title: "Release prep v${{ steps.gv.outputs.ver }}" + body: | + Automated release-prep through [pdk-templates](https://github.com/puppetlabs/pdk-templates/blob/main/moduleroot/.github/workflows/auto_release.yml.erb) from commit ${{ github.sha }}. + Please verify before merging: + - [ ] last [nightly](https://github.com/${{ github.repository }}/actions/workflows/nightly.yml) run is green + - [ ] [Changelog](https://github.com/${{ github.repository }}/blob/release-prep/CHANGELOG.md) is readable and has no unlabeled pull requests + - [ ] Ensure the [changelog](https://github.com/${{ github.repository }}/blob/release-prep/CHANGELOG.md) version and [metadata](https://github.com/${{ github.repository }}/blob/release-prep/metadata.json) version match + labels: "maintenance" + + - name: PR outputs + if: ${{ github.repository_owner == 'puppetlabs' }} + run: | + echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}" + echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}" + + - name: "Honeycomb: Record finish step" + if: ${{ always() }} + run: | + buildevents step $TRACE_ID $STEP_ID $STEP_START 'Finished auto release workflow' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1509f6e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,47 @@ +name: "Publish module" + +on: + workflow_dispatch: + +jobs: + create-github-release: + name: Deploy GitHub Release + runs-on: ubuntu-20.04 + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + ref: ${{ github.ref }} + clean: true + fetch-depth: 0 + - name: Get Version + id: gv + run: | + echo "::set-output name=ver::$(jq --raw-output .version metadata.json)" + - name: Create Release + uses: actions/create-release@v1 + id: create_release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: "v${{ steps.gv.outputs.ver }}" + draft: false + prerelease: false + + deploy-forge: + name: Deploy to Forge + runs-on: ubuntu-20.04 + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + ref: ${{ github.ref }} + clean: true + - name: "PDK Build" + uses: docker://puppet/pdk:nightly + with: + args: 'build' + - name: "Push to Forge" + uses: docker://puppet/pdk:nightly + with: + args: 'release publish --forge-token ${{ secrets.FORGE_API_KEY }} --force' diff --git a/.gitignore b/.gitignore index 62a28c8..988dcbb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,29 +1,28 @@ .git/ .*.sw[op] .metadata .yardoc .yardwarns *.iml /.bundle/ /.idea/ /.vagrant/ /coverage/ /bin/ /doc/ /Gemfile.local /Gemfile.lock /junit/ /log/ /pkg/ /spec/fixtures/manifests/ /spec/fixtures/modules/ /tmp/ /vendor/ /convert_report.txt /update_report.txt .DS_Store .project .envrc /inventory.yaml -.rspec -appveyor.yml +/spec/fixtures/litmus_inventory.yaml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 81e6d76..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,41 +0,0 @@ ---- -stages: - - syntax - - unit - -cache: - paths: - - vendor/bundle - -before_script: - - bundle -v - - rm Gemfile.lock || true - - gem update --system $RUBYGEMS_VERSION - - gem --version - - bundle -v - - bundle install --without system_tests --path vendor/bundle --jobs $(nproc) - -syntax lint metadata_lint check:symlinks check:git_ignore check:dot_underscore check:test_file rubocop-Ruby 2.5.3-Puppet ~> 6: - stage: syntax - image: ruby:2.5.3 - script: - - bundle exec rake syntax lint metadata_lint check:symlinks check:git_ignore check:dot_underscore check:test_file rubocop - variables: - PUPPET_GEM_VERSION: '~> 6' - -parallel_spec-Ruby 2.5.3-Puppet ~> 6: - stage: unit - image: ruby:2.5.3 - script: - - bundle exec rake parallel_spec - variables: - PUPPET_GEM_VERSION: '~> 6' - -parallel_spec-Ruby 2.4.5-Puppet ~> 5: - stage: unit - image: ruby:2.4.5 - script: - - bundle exec rake parallel_spec - variables: - PUPPET_GEM_VERSION: '~> 5' - diff --git a/.pdkignore b/.pdkignore index e6215cd..c538bea 100644 --- a/.pdkignore +++ b/.pdkignore @@ -1,42 +1,47 @@ .git/ .*.sw[op] .metadata .yardoc .yardwarns *.iml /.bundle/ /.idea/ /.vagrant/ /coverage/ /bin/ /doc/ /Gemfile.local /Gemfile.lock /junit/ /log/ /pkg/ /spec/fixtures/manifests/ /spec/fixtures/modules/ /tmp/ /vendor/ /convert_report.txt /update_report.txt .DS_Store .project .envrc /inventory.yaml +/spec/fixtures/litmus_inventory.yaml /appveyor.yml +/.editorconfig /.fixtures.yml /Gemfile /.gitattributes /.gitignore /.gitlab-ci.yml /.pdkignore +/.puppet-lint.rc /Rakefile /rakelib/ /.rspec /.rubocop.yml /.travis.yml /.yardopts /spec/ /.vscode/ +/.sync.yml +/.devcontainer/ diff --git a/.rubocop.yml b/.rubocop.yml index 937bda2..93dce85 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,135 +1,521 @@ --- require: +- rubocop-performance - rubocop-rspec -- rubocop-i18n AllCops: DisplayCopNames: true - TargetRubyVersion: '2.1' + TargetRubyVersion: '2.4' Include: - - "./**/*.rb" + - "**/*.rb" Exclude: - bin/* - ".vendor/**/*" - "**/Gemfile" - "**/Rakefile" - pkg/**/* - spec/fixtures/**/* - vendor/**/* - "**/Puppetfile" - "**/Vagrantfile" - "**/Guardfile" -Metrics/LineLength: +Layout/LineLength: Description: People have wide screens, use them. Max: 200 -GetText: - Enabled: false -GetText/DecorateString: - Description: We don't want to decorate test output. - Exclude: - - spec/**/* - Enabled: false RSpec/BeforeAfterAll: Description: Beware of using after(:all) as it may cause state to leak between tests. A necessary evil in acceptance testing. Exclude: - spec/acceptance/**/*.rb RSpec/HookArgument: Description: Prefer explicit :each argument, matching existing module's style EnforcedStyle: each +RSpec/DescribeSymbol: + Exclude: + - spec/unit/facter/**/*.rb Style/BlockDelimiters: Description: Prefer braces for chaining. Mostly an aesthetical choice. Better to be consistent then. EnforcedStyle: braces_for_chaining Style/ClassAndModuleChildren: Description: Compact style reduces the required amount of indentation. EnforcedStyle: compact Style/EmptyElse: Description: Enforce against empty else clauses, but allow `nil` for clarity. EnforcedStyle: empty Style/FormatString: Description: Following the main puppet project's style, prefer the % format format. EnforcedStyle: percent Style/FormatStringToken: Description: Following the main puppet project's style, prefer the simpler template tokens over annotated ones. EnforcedStyle: template Style/Lambda: Description: Prefer the keyword for easier discoverability. EnforcedStyle: literal Style/RegexpLiteral: Description: Community preference. See https://github.com/voxpupuli/modulesync_config/issues/168 EnforcedStyle: percent_r Style/TernaryParentheses: Description: Checks for use of parentheses around ternary conditions. Enforce parentheses on complex expressions for better readability, but seriously consider breaking it up. EnforcedStyle: require_parentheses_when_complex Style/TrailingCommaInArguments: Description: Prefer always trailing comma on multiline argument lists. This makes diffs, and re-ordering nicer. EnforcedStyleForMultiline: comma -Style/TrailingCommaInLiteral: +Style/TrailingCommaInArrayLiteral: Description: Prefer always trailing comma on multiline literals. This makes diffs, and re-ordering nicer. EnforcedStyleForMultiline: comma Style/SymbolArray: Description: Using percent style obscures symbolic intent of array's contents. EnforcedStyle: brackets RSpec/InstanceVariable: Enabled: false RSpec/MessageSpies: EnforcedStyle: receive Style/Documentation: Exclude: - lib/puppet/parser/functions/**/* - spec/**/* Style/WordArray: EnforcedStyle: brackets +Performance/AncestorsInclude: + Enabled: true +Performance/BigDecimalWithNumericArgument: + Enabled: true +Performance/BlockGivenWithExplicitBlock: + Enabled: true +Performance/CaseWhenSplat: + Enabled: true +Performance/ConstantRegexp: + Enabled: true +Performance/MethodObjectAsBlock: + Enabled: true +Performance/RedundantSortBlock: + Enabled: true +Performance/RedundantStringChars: + Enabled: true +Performance/ReverseFirst: + Enabled: true +Performance/SortReverse: + Enabled: true +Performance/Squeeze: + Enabled: true +Performance/StringInclude: + Enabled: true +Performance/Sum: + Enabled: true Style/CollectionMethods: Enabled: true Style/MethodCalledOnDoEndBlock: Enabled: true Style/StringMethods: Enabled: true -GetText/DecorateFunctionMessage: +Bundler/InsecureProtocolSource: + Enabled: false +Gemspec/DuplicatedAssignment: + Enabled: false +Gemspec/OrderedDependencies: + Enabled: false +Gemspec/RequiredRubyVersion: + Enabled: false +Gemspec/RubyVersionGlobalsUsage: + Enabled: false +Layout/ArgumentAlignment: + Enabled: false +Layout/BeginEndAlignment: + Enabled: false +Layout/ClosingHeredocIndentation: Enabled: false -GetText/DecorateStringFormattingUsingInterpolation: +Layout/EmptyComment: Enabled: false -GetText/DecorateStringFormattingUsingPercent: +Layout/EmptyLineAfterGuardClause: + Enabled: false +Layout/EmptyLinesAroundArguments: + Enabled: false +Layout/EmptyLinesAroundAttributeAccessor: Enabled: false Layout/EndOfLine: Enabled: false -Layout/IndentHeredoc: +Layout/FirstArgumentIndentation: + Enabled: false +Layout/HashAlignment: + Enabled: false +Layout/HeredocIndentation: + Enabled: false +Layout/LeadingEmptyLines: + Enabled: false +Layout/SpaceAroundMethodCallOperator: + Enabled: false +Layout/SpaceInsideArrayLiteralBrackets: + Enabled: false +Layout/SpaceInsideReferenceBrackets: + Enabled: false +Lint/BigDecimalNew: + Enabled: false +Lint/BooleanSymbol: + Enabled: false +Lint/ConstantDefinitionInBlock: + Enabled: false +Lint/DeprecatedOpenSSLConstant: + Enabled: false +Lint/DisjunctiveAssignmentInConstructor: + Enabled: false +Lint/DuplicateElsifCondition: + Enabled: false +Lint/DuplicateRequire: + Enabled: false +Lint/DuplicateRescueException: + Enabled: false +Lint/EmptyConditionalBody: + Enabled: false +Lint/EmptyFile: + Enabled: false +Lint/ErbNewArguments: + Enabled: false +Lint/FloatComparison: + Enabled: false +Lint/HashCompareByIdentity: + Enabled: false +Lint/IdentityComparison: + Enabled: false +Lint/InterpolationCheck: + Enabled: false +Lint/MissingCopEnableDirective: + Enabled: false +Lint/MixedRegexpCaptureTypes: + Enabled: false +Lint/NestedPercentLiteral: + Enabled: false +Lint/NonDeterministicRequireOrder: + Enabled: false +Lint/OrderedMagicComments: + Enabled: false +Lint/OutOfRangeRegexpRef: + Enabled: false +Lint/RaiseException: + Enabled: false +Lint/RedundantCopEnableDirective: + Enabled: false +Lint/RedundantRequireStatement: + Enabled: false +Lint/RedundantSafeNavigation: + Enabled: false +Lint/RedundantWithIndex: + Enabled: false +Lint/RedundantWithObject: + Enabled: false +Lint/RegexpAsCondition: + Enabled: false +Lint/ReturnInVoidContext: + Enabled: false +Lint/SafeNavigationConsistency: + Enabled: false +Lint/SafeNavigationWithEmpty: + Enabled: false +Lint/SelfAssignment: + Enabled: false +Lint/SendWithMixinArgument: + Enabled: false +Lint/ShadowedArgument: + Enabled: false +Lint/StructNewOverride: + Enabled: false +Lint/ToJSON: + Enabled: false +Lint/TopLevelReturnWithArgument: + Enabled: false +Lint/TrailingCommaInAttributeDeclaration: + Enabled: false +Lint/UnreachableLoop: + Enabled: false +Lint/UriEscapeUnescape: + Enabled: false +Lint/UriRegexp: + Enabled: false +Lint/UselessMethodDefinition: + Enabled: false +Lint/UselessTimes: Enabled: false Metrics/AbcSize: Enabled: false Metrics/BlockLength: Enabled: false +Metrics/BlockNesting: + Enabled: false Metrics/ClassLength: Enabled: false Metrics/CyclomaticComplexity: Enabled: false Metrics/MethodLength: Enabled: false Metrics/ModuleLength: Enabled: false Metrics/ParameterLists: Enabled: false Metrics/PerceivedComplexity: Enabled: false +Migration/DepartmentName: + Enabled: false +Naming/AccessorMethodName: + Enabled: false +Naming/BlockParameterName: + Enabled: false +Naming/HeredocDelimiterCase: + Enabled: false +Naming/HeredocDelimiterNaming: + Enabled: false +Naming/MemoizedInstanceVariableName: + Enabled: false +Naming/MethodParameterName: + Enabled: false +Naming/RescuedExceptionsVariableName: + Enabled: false +Naming/VariableNumber: + Enabled: false +Performance/BindCall: + Enabled: false +Performance/DeletePrefix: + Enabled: false +Performance/DeleteSuffix: + Enabled: false +Performance/InefficientHashSearch: + Enabled: false +Performance/UnfreezeString: + Enabled: false +Performance/UriDefaultParser: + Enabled: false +RSpec/Be: + Enabled: false +RSpec/Capybara/CurrentPathExpectation: + Enabled: false +RSpec/Capybara/FeatureMethods: + Enabled: false +RSpec/Capybara/VisibilityMatcher: + Enabled: false +RSpec/ContextMethod: + Enabled: false +RSpec/ContextWording: + Enabled: false RSpec/DescribeClass: Enabled: false +RSpec/EmptyHook: + Enabled: false +RSpec/EmptyLineAfterExample: + Enabled: false +RSpec/EmptyLineAfterExampleGroup: + Enabled: false +RSpec/EmptyLineAfterHook: + Enabled: false RSpec/ExampleLength: Enabled: false -RSpec/MessageExpectation: +RSpec/ExampleWithoutDescription: + Enabled: false +RSpec/ExpectChange: + Enabled: false +RSpec/ExpectInHook: + Enabled: false +RSpec/FactoryBot/AttributeDefinedStatically: + Enabled: false +RSpec/FactoryBot/CreateList: + Enabled: false +RSpec/FactoryBot/FactoryClassName: + Enabled: false +RSpec/HooksBeforeExamples: + Enabled: false +RSpec/ImplicitBlockExpectation: + Enabled: false +RSpec/ImplicitSubject: + Enabled: false +RSpec/LeakyConstantDeclaration: + Enabled: false +RSpec/LetBeforeExamples: + Enabled: false +RSpec/MissingExampleGroupArgument: Enabled: false RSpec/MultipleExpectations: Enabled: false +RSpec/MultipleMemoizedHelpers: + Enabled: false +RSpec/MultipleSubjects: + Enabled: false RSpec/NestedGroups: Enabled: false +RSpec/PredicateMatcher: + Enabled: false +RSpec/ReceiveCounts: + Enabled: false +RSpec/ReceiveNever: + Enabled: false +RSpec/RepeatedExampleGroupBody: + Enabled: false +RSpec/RepeatedExampleGroupDescription: + Enabled: false +RSpec/RepeatedIncludeExample: + Enabled: false +RSpec/ReturnFromStub: + Enabled: false +RSpec/SharedExamples: + Enabled: false +RSpec/StubbedMock: + Enabled: false +RSpec/UnspecifiedException: + Enabled: false +RSpec/VariableDefinition: + Enabled: false +RSpec/VoidExpect: + Enabled: false +RSpec/Yield: + Enabled: false +Security/Open: + Enabled: false +Style/AccessModifierDeclarations: + Enabled: false +Style/AccessorGrouping: + Enabled: false Style/AsciiComments: Enabled: false +Style/BisectedAttrAccessor: + Enabled: false +Style/CaseLikeIf: + Enabled: false +Style/ClassEqualityComparison: + Enabled: false +Style/ColonMethodDefinition: + Enabled: false +Style/CombinableLoops: + Enabled: false +Style/CommentedKeyword: + Enabled: false +Style/Dir: + Enabled: false +Style/DoubleCopDisableDirective: + Enabled: false +Style/EmptyBlockParameter: + Enabled: false +Style/EmptyLambdaParameter: + Enabled: false +Style/Encoding: + Enabled: false +Style/EvalWithLocation: + Enabled: false +Style/ExpandPathArguments: + Enabled: false +Style/ExplicitBlockArgument: + Enabled: false +Style/ExponentialNotation: + Enabled: false +Style/FloatDivision: + Enabled: false +Style/FrozenStringLiteralComment: + Enabled: false +Style/GlobalStdStream: + Enabled: false +Style/HashAsLastArrayItem: + Enabled: false +Style/HashLikeCase: + Enabled: false +Style/HashTransformKeys: + Enabled: false +Style/HashTransformValues: + Enabled: false Style/IfUnlessModifier: Enabled: false +Style/KeywordParametersOrder: + Enabled: false +Style/MinMax: + Enabled: false +Style/MixinUsage: + Enabled: false +Style/MultilineWhenThen: + Enabled: false +Style/NegatedUnless: + Enabled: false +Style/NumericPredicate: + Enabled: false +Style/OptionalBooleanParameter: + Enabled: false +Style/OrAssignment: + Enabled: false +Style/RandomWithOffset: + Enabled: false +Style/RedundantAssignment: + Enabled: false +Style/RedundantCondition: + Enabled: false +Style/RedundantConditional: + Enabled: false +Style/RedundantFetchBlock: + Enabled: false +Style/RedundantFileExtensionInRequire: + Enabled: false +Style/RedundantRegexpCharacterClass: + Enabled: false +Style/RedundantRegexpEscape: + Enabled: false +Style/RedundantSelfAssignment: + Enabled: false +Style/RedundantSort: + Enabled: false +Style/RescueStandardError: + Enabled: false +Style/SingleArgumentDig: + Enabled: false +Style/SlicingWithRange: + Enabled: false +Style/SoleNestedConditional: + Enabled: false +Style/StderrPuts: + Enabled: false +Style/StringConcatenation: + Enabled: false +Style/Strip: + Enabled: false Style/SymbolProc: Enabled: false +Style/TrailingBodyOnClass: + Enabled: false +Style/TrailingBodyOnMethodDefinition: + Enabled: false +Style/TrailingBodyOnModule: + Enabled: false +Style/TrailingCommaInHashLiteral: + Enabled: false +Style/TrailingMethodEndStatement: + Enabled: false +Style/UnpackFirst: + Enabled: false +Lint/DuplicateBranch: + Enabled: false +Lint/DuplicateRegexpCharacterClassElement: + Enabled: false +Lint/EmptyBlock: + Enabled: false +Lint/EmptyClass: + Enabled: false +Lint/NoReturnInBeginEndBlocks: + Enabled: false +Lint/ToEnumArguments: + Enabled: false +Lint/UnexpectedBlockArity: + Enabled: false +Lint/UnmodifiedReduceAccumulator: + Enabled: false +Performance/CollectionLiteralInLoop: + Enabled: false +Style/ArgumentsForwarding: + Enabled: false +Style/CollectionCompact: + Enabled: false +Style/DocumentDynamicEvalDefinition: + Enabled: false +Style/NegatedIfElseCondition: + Enabled: false +Style/NilLambda: + Enabled: false +Style/RedundantArgument: + Enabled: false +Style/SwapValues: + Enabled: false diff --git a/.sync.yml b/.sync.yml index 1492961..6bf1277 100644 --- a/.sync.yml +++ b/.sync.yml @@ -1,49 +1,37 @@ --- +.rubocop.yml: + default_configs: + RSpec/InstanceVariable: + Enabled: false Gemfile: - required: - ':system_tests': - - gem: 'puppet-module-posix-system-r#{minor_version}' - platforms: ruby - - gem: 'puppet-module-win-system-r#{minor_version}' - platforms: - - mswin - - mingw - - x64_mingw + optional: + ":development": - gem: beaker - version: '~> 3.34' + version: '~> 4.30' from_env: BEAKER_VERSION - gem: beaker-abs from_env: BEAKER_ABS_VERSION - version: '~> 0.5' + version: '~> 0.9' - gem: beaker-pe - gem: beaker-hostgenerator from_env: BEAKER_HOSTGENERATOR_VERSION - gem: beaker-rspec from_env: BEAKER_RSPEC_VERSION - gem: beaker-puppet from_env: BEAKER_PUPPET_VERSION - version: '~> 0.15' - ':development': - - gem: puppet-strings - optional: - ':development': - - gem: 'github_changelog_generator' - git: 'https://github.com/skywinder/github-changelog-generator' - ref: '20ee04ba1234e9e83eb2ffb5056e23d641c7a018' - condition: "Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.2.2')" - -spec/spec_helper.rb: - mock_with: ':rspec' - -.rubocop.yml: - default_configs: - RSpec/InstanceVariable: - Enabled: false - -.gitignore: - required: - - '.rspec' - - 'appveyor.yml' + version: '~> 1.22' + - gem: github_changelog_generator + - gem: beaker-module_install_helper + - gem: beaker-puppet_install_helper + - gem: nokogiri -Rakefile: - changelog_version_tag_pattern: '%s' +appveyor.yml: + delete: true +.travis.yml: + delete: true +.github/workflows/auto_release.yml: + unmanaged: false +.github/workflows/release.yml: + unmanaged: false +.gitlab-ci.yml: + delete: true diff --git a/Gemfile b/Gemfile index 5c87918..26f0def 100644 --- a/Gemfile +++ b/Gemfile @@ -1,96 +1,72 @@ source ENV['GEM_SOURCE'] || 'https://rubygems.org' def location_for(place_or_version, fake_version = nil) git_url_regex = %r{\A(?(https?|git)[:@][^#]*)(#(?.*))?} file_url_regex = %r{\Afile:\/\/(?.*)} if place_or_version && (git_url = place_or_version.match(git_url_regex)) [fake_version, { git: git_url[:url], branch: git_url[:branch], require: false }].compact elsif place_or_version && (file_url = place_or_version.match(file_url_regex)) ['>= 0', { path: File.expand_path(file_url[:path]), require: false }] else [place_or_version, { require: false }] end end ruby_version_segments = Gem::Version.new(RUBY_VERSION.dup).segments minor_version = ruby_version_segments[0..1].join('.') group :development do - gem "parallel_tests", '>= 2.14.1', '< 2.14.3', require: false - gem "metadata-json-lint", '>= 2.0.2', '< 3.0.0', require: false - gem "rspec-puppet-facts", '~> 1.10.0', require: false - gem "rspec_junit_formatter", '~> 0.2', require: false - gem "rubocop", '~> 0.49.0', require: false - gem "rubocop-rspec", '~> 1.16.0', require: false - gem "rubocop-i18n", '~> 1.2.0', require: false - gem "puppetlabs_spec_helper", '>= 2.9.0', '< 3.0.0', require: false - gem "fast_gettext", '1.1.0', require: false if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.1.0') - gem "fast_gettext", require: false if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.1.0') - gem "json_pure", '<= 2.0.1', require: false if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.0.0') - gem "json", '= 1.8.1', require: false if Gem::Version.new(RUBY_VERSION.dup) == Gem::Version.new('2.1.9') - gem "json", '= 2.0.4', require: false if Gem::Requirement.create('~> 2.4.2').satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) - gem "json", '= 2.1.0', require: false if Gem::Requirement.create(['>= 2.5.0', '< 2.7.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) - gem "rb-readline", '= 0.5.5', require: false, platforms: [:mswin, :mingw, :x64_mingw] - gem "puppet-module-posix-default-r#{minor_version}", '~> 0.3', require: false, platforms: [:ruby] - gem "puppet-module-win-default-r#{minor_version}", '~> 0.3', require: false, platforms: [:mswin, :mingw, :x64_mingw] - gem "puppet-strings", require: false - gem "github_changelog_generator", require: false, git: 'https://github.com/skywinder/github-changelog-generator', ref: '20ee04ba1234e9e83eb2ffb5056e23d641c7a018' if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.2.2') -end - -group :system_tests do - gem "puppet-module-posix-system-r#{minor_version}", '~> 0.5', require: false, platforms: [:ruby] - gem "puppet-module-win-system-r#{minor_version}", '~> 0.5', require: false, platforms: [:mswin, :mingw, :x64_mingw] - gem "beaker", *location_for(ENV['BEAKER_VERSION'] || '~> 4') - gem "beaker-abs", *location_for(ENV['BEAKER_ABS_VERSION'] || '~> 0.5') + gem "json", '= 2.0.4', require: false if Gem::Requirement.create('~> 2.4.2').satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) + gem "json", '= 2.1.0', require: false if Gem::Requirement.create(['>= 2.5.0', '< 2.7.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) + gem "json", '= 2.3.0', require: false if Gem::Requirement.create(['>= 2.7.0', '< 2.8.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) + gem "puppet-module-posix-default-r#{minor_version}", '~> 1.0', require: false, platforms: [:ruby] + gem "puppet-module-posix-dev-r#{minor_version}", '~> 1.0', require: false, platforms: [:ruby] + gem "puppet-module-win-default-r#{minor_version}", '~> 1.0', require: false, platforms: [:mswin, :mingw, :x64_mingw] + gem "puppet-module-win-dev-r#{minor_version}", '~> 1.0', require: false, platforms: [:mswin, :mingw, :x64_mingw] + gem "beaker", *location_for(ENV['BEAKER_VERSION'] || '~> 4.30') + gem "beaker-abs", *location_for(ENV['BEAKER_ABS_VERSION'] || '~> 0.9') gem "beaker-pe", require: false gem "beaker-hostgenerator" gem "beaker-rspec" - gem "beaker-puppet", *location_for(ENV['BEAKER_PUPPET_VERSION'] || '~> 1.0') + gem "beaker-puppet", *location_for(ENV['BEAKER_PUPPET_VERSION'] || '~> 1.22') + gem "github_changelog_generator", require: false + gem "beaker-module_install_helper", require: false + gem "beaker-puppet_install_helper", require: false + gem "nokogiri", require: false end - -group :release do - gem "puppet-blacksmith", '~> 3.4', require: false - gem "pdk", '~> 2.0', platforms: [:ruby] +group :system_tests do + gem "puppet-module-posix-system-r#{minor_version}", '~> 1.0', require: false, platforms: [:ruby] + gem "puppet-module-win-system-r#{minor_version}", '~> 1.0', require: false, platforms: [:mswin, :mingw, :x64_mingw] end puppet_version = ENV['PUPPET_GEM_VERSION'] facter_version = ENV['FACTER_GEM_VERSION'] hiera_version = ENV['HIERA_GEM_VERSION'] gems = {} gems['puppet'] = location_for(puppet_version) # If facter or hiera versions have been specified via the environment # variables gems['facter'] = location_for(facter_version) if facter_version gems['hiera'] = location_for(hiera_version) if hiera_version -if Gem.win_platform? && puppet_version =~ %r{^(file:///|git://)} - # If we're using a Puppet gem on Windows which handles its own win32-xxx gem - # dependencies (>= 3.5.0), set the maximum versions (see PUP-6445). - gems['win32-dir'] = ['<= 0.4.9', require: false] - gems['win32-eventlog'] = ['<= 0.6.5', require: false] - gems['win32-process'] = ['<= 0.7.5', require: false] - gems['win32-security'] = ['<= 0.2.5', require: false] - gems['win32-service'] = ['0.8.8', require: false] -end - gems.each do |gem_name, gem_params| gem gem_name, *gem_params end # Evaluate Gemfile.local and ~/.gemfile if they exist extra_gemfiles = [ "#{__FILE__}.local", File.join(Dir.home, '.gemfile'), ] extra_gemfiles.each do |gemfile| if File.file?(gemfile) && File.readable?(gemfile) eval(File.read(gemfile), binding) end end # vim: syntax=ruby diff --git a/REFERENCE.md b/REFERENCE.md index c29d161..157b3f1 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -1,253 +1,287 @@ # Reference + -## Resource types +## Table of Contents -* [`zfs`](#zfs): Manage zfs. Create destroy and set properties on zfs instances. **Autorequires:** If Puppet is managing the zpool at the root of this zfs in -* [`zpool`](#zpool): Manage zpools. Create and delete zpools. The provider WILL NOT SYNC, only report differences. Supports vdevs with mirrors, raidz, logs and s +### Resource types -## Resource types +* [`zfs`](#zfs): Manage zfs. Create destroy and set properties on zfs instances. +* [`zpool`](#zpool): Manage zpools. Create and delete zpools. The provider WILL NOT SYNC, only report differences. -### zfs +## Resource types -Manage zfs. Create destroy and set properties on zfs instances. +### `zfs` **Autorequires:** If Puppet is managing the zpool at the root of this zfs instance, the zfs resource will autorequire it. If Puppet is managing any parent zfs instances, the zfs resource will autorequire them. #### Examples ##### Using zfs. ```puppet zfs { 'tstpool': ensure => present, } ``` #### Properties The following properties are available in the `zfs` type. -##### `ensure` - -Valid values: present, absent - -The basic property that the resource should be in. - -Default value: present - ##### `aclinherit` The aclinherit property. Valid values are `discard`, `noallow`, `restricted`, `passthrough`, `passthrough-x`. ##### `aclmode` The aclmode property. Valid values are `discard`, `groupmask`, `passthrough`. ##### `acltype` The acltype propery. Valid values are 'noacl' and 'posixacl'. Only supported on Linux. ##### `atime` The atime property. Valid values are `on`, `off`. ##### `canmount` The canmount property. Valid values are `on`, `off`, `noauto`. ##### `checksum` The checksum property. Valid values are `on`, `off`, `fletcher2`, `fletcher4`, `sha256`. ##### `compression` The compression property. Valid values are `on`, `off`, `lzjb`, `gzip`, `gzip-[1-9]`, `zle`. ##### `copies` The copies property. Valid values are `1`, `2`, `3`. ##### `dedup` The dedup property. Valid values are `on`, `off`. ##### `devices` The devices property. Valid values are `on`, `off`. +##### `ensure` + +Valid values: `present`, `absent` + +The basic property that the resource should be in. + +Default value: `present` + ##### `exec` The exec property. Valid values are `on`, `off`. ##### `logbias` The logbias property. Valid values are `latency`, `throughput`. ##### `mountpoint` The mountpoint property. Valid values are ``, `legacy`, `none`. ##### `nbmand` The nbmand property. Valid values are `on`, `off`. +##### `overlay` + +The overlay property. Valid values are `on`, `off`. + ##### `primarycache` The primarycache property. Valid values are `all`, `none`, `metadata`. ##### `quota` The quota property. Valid values are ``, `none`. ##### `readonly` The readonly property. Valid values are `on`, `off`. ##### `recordsize` The recordsize property. Valid values are powers of two between 512 and 128k. ##### `refquota` The refquota property. Valid values are ``, `none`. ##### `refreservation` The refreservation property. Valid values are ``, `none`. ##### `reservation` The reservation property. Valid values are ``, `none`. ##### `secondarycache` The secondarycache property. Valid values are `all`, `none`, `metadata`. ##### `setuid` The setuid property. Valid values are `on`, `off`. ##### `shareiscsi` The shareiscsi property. Valid values are `on`, `off`, `type=`. ##### `sharenfs` The sharenfs property. Valid values are `on`, `off`, share(1M) options ##### `sharesmb` The sharesmb property. Valid values are `on`, `off`, sharemgr(1M) options ##### `snapdir` The snapdir property. Valid values are `hidden`, `visible`. ##### `sync` The sync property. Valid values are `standard`, `always`, `disabled`. ##### `version` The version property. Valid values are `1`, `2`, `3`, `4`, `current`. ##### `volsize` The volsize property. Valid values are `` ##### `vscan` The vscan property. Valid values are `on`, `off`. ##### `xattr` The xattr property. Valid values are `on`, `off`. ##### `zoned` The zoned property. Valid values are `on`, `off`. #### Parameters The following parameters are available in the `zfs` type. -##### `name` +* [`name`](#name) +* [`provider`](#provider) + +##### `name` namevar The full name for this filesystem (including the zpool). -### zpool +##### `provider` + +The specific backend to use for this `zfs` resource. You will seldom need to specify this --- Puppet will usually +discover the appropriate provider for your platform. -Manage zpools. Create and delete zpools. The provider WILL NOT SYNC, only report differences. +### `zpool` -Supports vdevs with mirrors, raidz, logs and spares. +Supports vdevs with mirrors, raidz, logs, spares, and cache. #### Examples ##### Using zpool. ```puppet zpool { 'tstpool': ensure => present, - disk => '/ztstpool/dsk', + disk => '/ztstpool/dsk', } ``` #### Properties The following properties are available in the `zpool` type. -##### `ensure` +##### `ashift` -Valid values: present, absent +The Alignment Shift for the vdevs in the given pool. -The basic property that the resource should be in. +##### `autoexpand` -Default value: present +The autoexpand setting for the given pool. Valid values are `on` or `off` + +##### `cache` + +Cache disks for this pool. ##### `disk` The disk(s) for this pool. Can be an array or a space separated string. -Use disk/device names as displayed by "zpool status". On Linux/ZOL, use -full device pathes as displayed by "zpool status -P". + +##### `ensure` + +Valid values: `present`, `absent` + +The basic property that the resource should be in. + +Default value: `present` + +##### `failmode` + +The failmode setting for the given pool. Valid values are `wait`, `continue` or `panic` + +##### `log` + +Log disks for this pool. This type does not currently support mirroring of log disks. ##### `mirror` List of all the devices to mirror for this pool. Each mirror should be a space separated string: mirror => [\"disk1 disk2\", \"disk3 disk4\"], ##### `raidz` List of all the devices to raid for this pool. Should be an array of space separated strings: raidz => [\"disk1 disk2\", \"disk3 disk4\"], ##### `spare` Spare disk(s) for this pool. -##### `log` - -Log disks for this pool. This type does not currently support mirroring of log disks. - #### Parameters The following parameters are available in the `zpool` type. -##### `pool` +* [`pool`](#pool) +* [`provider`](#provider) +* [`raid_parity`](#raid_parity) + +##### `pool` namevar The name for this pool. -##### `raid_parity` +##### `provider` + +The specific backend to use for this `zpool` resource. You will seldom need to specify this --- Puppet will usually +discover the appropriate provider for your platform. + +##### `raid_parity` Determines parity when using the `raidz` parameter. diff --git a/Rakefile b/Rakefile index 7f91a3f..2906c15 100644 --- a/Rakefile +++ b/Rakefile @@ -1,111 +1,88 @@ +# frozen_string_literal: true + +require 'bundler' require 'puppet_litmus/rake_tasks' if Bundler.rubygems.find_name('puppet_litmus').any? require 'puppetlabs_spec_helper/rake_tasks' require 'puppet-syntax/tasks/puppet-syntax' require 'puppet_blacksmith/rake_tasks' if Bundler.rubygems.find_name('puppet-blacksmith').any? require 'github_changelog_generator/task' if Bundler.rubygems.find_name('github_changelog_generator').any? require 'puppet-strings/tasks' if Bundler.rubygems.find_name('puppet-strings').any? def changelog_user return unless Rake.application.top_level_tasks.include? "changelog" returnVal = nil || JSON.load(File.read('metadata.json'))['author'] raise "unable to find the changelog_user in .sync.yml, or the author in metadata.json" if returnVal.nil? puts "GitHubChangelogGenerator user:#{returnVal}" returnVal end def changelog_project return unless Rake.application.top_level_tasks.include? "changelog" returnVal = nil returnVal ||= begin metadata_source = JSON.load(File.read('metadata.json'))['source'] metadata_source_match = metadata_source && metadata_source.match(%r{.*\/([^\/]*?)(?:\.git)?\Z}) metadata_source_match && metadata_source_match[1] end raise "unable to find the changelog_project in .sync.yml or calculate it from the source in metadata.json" if returnVal.nil? puts "GitHubChangelogGenerator project:#{returnVal}" returnVal end def changelog_future_release return unless Rake.application.top_level_tasks.include? "changelog" - returnVal = "%s" % JSON.load(File.read('metadata.json'))['version'] + returnVal = "v%s" % JSON.load(File.read('metadata.json'))['version'] raise "unable to find the future_release (version) in metadata.json" if returnVal.nil? puts "GitHubChangelogGenerator future_release:#{returnVal}" returnVal end PuppetLint.configuration.send('disable_relative') if Bundler.rubygems.find_name('github_changelog_generator').any? GitHubChangelogGenerator::RakeTask.new :changelog do |config| raise "Set CHANGELOG_GITHUB_TOKEN environment variable eg 'export CHANGELOG_GITHUB_TOKEN=valid_token_here'" if Rake.application.top_level_tasks.include? "changelog" and ENV['CHANGELOG_GITHUB_TOKEN'].nil? config.user = "#{changelog_user}" config.project = "#{changelog_project}" config.future_release = "#{changelog_future_release}" config.exclude_labels = ['maintenance'] config.header = "# Change log\n\nAll notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org)." config.add_pr_wo_labels = true config.issues = false - config.merge_prefix = "### UNCATEGORIZED PRS; GO LABEL THEM" + config.merge_prefix = "### UNCATEGORIZED PRS; LABEL THEM ON GITHUB" config.configure_sections = { "Changed" => { "prefix" => "### Changed", "labels" => ["backwards-incompatible"], }, "Added" => { "prefix" => "### Added", - "labels" => ["feature", "enhancement"], + "labels" => ["enhancement", "feature"], }, "Fixed" => { "prefix" => "### Fixed", - "labels" => ["bugfix"], + "labels" => ["bug", "documentation", "bugfix"], }, } end else desc 'Generate a Changelog from GitHub' task :changelog do raise <= Gem::Version.new('2.2.2')" + version: '~> 1.15' + condition: "Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.3.0')" EOM end end -desc "verify that commit messages match CONTRIBUTING.md requirements" -task(:commits) do - # This rake task looks at the summary from every commit from this branch not - # in the branch targeted for a PR. - commit_range = 'HEAD^..HEAD' - puts "Checking commits #{commit_range}" - %x{git log --no-merges --pretty=%s #{commit_range}}.each_line do |commit_summary| - # This regex tests for the currently supported commit summary tokens. - # The exception tries to explain it in more full. - if /^\((maint|packaging|doc|docs|modules-\d+)\)|revert/i.match(commit_summary).nil? - raise "\n\n\n\tThis commit summary didn't match CONTRIBUTING.md guidelines:\n" \ - "\n\t\t#{commit_summary}\n" \ - "\tThe commit summary (i.e. the first line of the commit message) should start with one of:\n" \ - "\t\t(MODULES-) # this is most common and should be a ticket at tickets.puppet.com\n" \ - "\t\t(docs)\n" \ - "\t\t(docs)(DOCUMENT-)\n" \ - "\t\t(packaging)\n" - "\t\t(maint)\n" \ - "\n\tThis test for the commit summary is case-insensitive.\n\n\n" - else - puts "#{commit_summary}" - end - puts "...passed" - end -end diff --git a/lib/puppet/provider/zpool/zpool.rb b/lib/puppet/provider/zpool/zpool.rb index 7d27be3..5d2554a 100644 --- a/lib/puppet/provider/zpool/zpool.rb +++ b/lib/puppet/provider/zpool/zpool.rb @@ -1,195 +1,193 @@ Puppet::Type.type(:zpool).provide(:zpool) do desc 'Provider for zpool.' commands zpool: 'zpool' # NAME SIZE ALLOC FREE CAP HEALTH ALTROOT def self.instances zpool(:list, '-H').split("\n").map do |line| name, _size, _alloc, _free, _cap, _health, _altroot = line.split(%r{\s+}) new(name: name, ensure: :present) end end def get_zpool_property(prop) zpool(:get, prop, @resource[:name]).split("\n").reverse.map { |line| name, _property, value, _source = line.split("\s") value if name == @resource[:name] }.shift end def process_zpool_data(pool_array) if pool_array == [] return Hash.new(:absent) end # get the name and get rid of it pool = {} pool[:pool] = pool_array[0] pool_array.shift tmp = [] # order matters here :( pool_array.reverse_each do |value| sym = nil case value when 'spares' sym = :spare when 'logs' sym = :log when 'cache' sym = :cache when %r{^mirror|^raidz1|^raidz2} sym = (value =~ %r{^mirror}) ? :mirror : :raidz - pool[:raid_parity] = 'raidz2' if value =~ %r{^raidz2} + pool[:raid_parity] = 'raidz2' if %r{^raidz2}.match?(value) else # get full drive name if the value is a partition (Linux only) tmp << if Facter.value(:kernel) == 'Linux' && value =~ %r{/dev/(:?[a-z]+1|disk/by-id/.+-part1)$} execute("lsblk -p -no pkname #{value}").chomp else value end sym = :disk if value == pool_array.first end if sym pool[sym] = (pool[sym]) ? pool[sym].unshift(tmp.reverse.join(' ')) : [tmp.reverse.join(' ')] tmp.clear end end pool end - # rubocop:disable Style/AccessorMethodName - # rubocop:disable Style/NumericPredicate def get_pool_data # https://docs.oracle.com/cd/E19082-01/817-2271/gbcve/index.html # we could also use zpool iostat -v mypool for a (little bit) cleaner output zpool_opts = case Facter.value(:kernel) # use full device names ("-P") on Linux/ZOL to prevent # mismatches between creation and display paths: when 'Linux' '-P' else '' end out = execute("zpool status #{zpool_opts} #{@resource[:pool]}", failonfail: false, combine: false) zpool_data = out.lines.select { |line| line.index("\t") == 0 }.map { |l| l.strip.split("\s")[0] } zpool_data.shift zpool_data end def current_pool @current_pool = process_zpool_data(get_pool_data) unless defined?(@current_pool) && @current_pool @current_pool end def flush @current_pool = nil end # Adds log and spare def build_named(name) prop = @resource[name.to_sym] if prop [name] + prop.map { |p| p.split(' ') }.flatten else [] end end # query for parity and set the right string def raidzarity (@resource[:raid_parity]) ? @resource[:raid_parity] : 'raidz1' end # handle mirror or raid def handle_multi_arrays(prefix, array) array.map { |a| [prefix] + a.split(' ') }.flatten end # builds up the vdevs for create command def build_vdevs disk = @resource[:disk] mirror = @resource[:mirror] raidz = @resource[:raidz] if disk disk.map { |d| d.split(' ') }.flatten elsif mirror handle_multi_arrays('mirror', mirror) elsif raidz handle_multi_arrays(raidzarity, raidz) end end def add_pool_properties properties = [] [:ashift, :autoexpand, :failmode].each do |property| if (value = @resource[property]) && value != '' properties << '-o' << "#{property}=#{value}" end end properties end def create zpool(*([:create] + add_pool_properties + [@resource[:pool]] + build_vdevs + build_named('spare') + build_named('log') + build_named('cache'))) end def destroy zpool :destroy, @resource[:pool] end def exists? if current_pool[:pool] == :absent false else true end end [:disk, :mirror, :raidz, :log, :spare, :cache].each do |field| define_method(field) do current_pool[field] end # rubocop:disable Style/SignalException define_method(field.to_s + '=') do |should| fail "zpool #{field} can't be changed. should be #{should}, currently is #{current_pool[field]}" end end [:autoexpand, :failmode].each do |field| define_method(field) do get_zpool_property(field) end define_method(field.to_s + '=') do |should| zpool(:set, "#{field}=#{should}", @resource[:name]) end end # Borrow the code from the ZFS provider here so that we catch and return '-' # as ashift is linux only. # see lib/puppet/provider/zfs/zfs.rb PARAMETER_UNSET_OR_NOT_AVAILABLE = '-'.freeze unless defined? PARAMETER_UNSET_OR_NOT_AVAILABLE define_method(:ashift) do begin get_zpool_property(:ashift) rescue PARAMETER_UNSET_OR_NOT_AVAILABLE end end define_method('ashift=') do |should| begin zpool(:set, "ashift=#{should}", @resource[:name]) rescue PARAMETER_UNSET_OR_NOT_AVAILABLE end end end diff --git a/lib/puppet/type/zfs.rb b/lib/puppet/type/zfs.rb index b2cd48a..7ea6430 100644 --- a/lib/puppet/type/zfs.rb +++ b/lib/puppet/type/zfs.rb @@ -1,174 +1,174 @@ # Manage zfs. Create destroy and set properties on zfs instances. module Puppet Type.newtype(:zfs) do desc <<-DESC - Manage zfs. Create destroy and set properties on zfs instances. + @summary Manage zfs. Create destroy and set properties on zfs instances. **Autorequires:** If Puppet is managing the zpool at the root of this zfs instance, the zfs resource will autorequire it. If Puppet is managing any parent zfs instances, the zfs resource will autorequire them. @example Using zfs. zfs { 'tstpool': ensure => present, } DESC ensurable newparam(:name) do desc 'The full name for this filesystem (including the zpool).' end newproperty(:aclinherit) do desc 'The aclinherit property. Valid values are `discard`, `noallow`, `restricted`, `passthrough`, `passthrough-x`.' end newproperty(:aclmode) do desc 'The aclmode property. Valid values are `discard`, `groupmask`, `passthrough`.' end newproperty(:acltype) do desc "The acltype propery. Valid values are 'noacl' and 'posixacl'. Only supported on Linux." end newproperty(:atime) do desc 'The atime property. Valid values are `on`, `off`.' end newproperty(:canmount) do desc 'The canmount property. Valid values are `on`, `off`, `noauto`.' end newproperty(:checksum) do desc 'The checksum property. Valid values are `on`, `off`, `fletcher2`, `fletcher4`, `sha256`.' end newproperty(:compression) do desc 'The compression property. Valid values are `on`, `off`, `lzjb`, `gzip`, `gzip-[1-9]`, `zle`.' end newproperty(:copies) do desc 'The copies property. Valid values are `1`, `2`, `3`.' end newproperty(:dedup) do desc 'The dedup property. Valid values are `on`, `off`.' end newproperty(:devices) do desc 'The devices property. Valid values are `on`, `off`.' end newproperty(:exec) do desc 'The exec property. Valid values are `on`, `off`.' end newproperty(:logbias) do desc 'The logbias property. Valid values are `latency`, `throughput`.' end newproperty(:mountpoint) do desc 'The mountpoint property. Valid values are ``, `legacy`, `none`.' end newproperty(:nbmand) do desc 'The nbmand property. Valid values are `on`, `off`.' end newproperty(:overlay) do desc 'The overlay property. Valid values are `on`, `off`.' validate do |_value| raise Puppet::Error _('This property is only supported on Linux') unless Facter.value(:kernel) == 'Linux' end end newproperty(:primarycache) do desc 'The primarycache property. Valid values are `all`, `none`, `metadata`.' end newproperty(:quota) do desc 'The quota property. Valid values are ``, `none`.' end newproperty(:readonly) do desc 'The readonly property. Valid values are `on`, `off`.' end newproperty(:recordsize) do desc 'The recordsize property. Valid values are powers of two between 512 and 128k.' end newproperty(:refquota) do desc 'The refquota property. Valid values are ``, `none`.' end newproperty(:refreservation) do desc 'The refreservation property. Valid values are ``, `none`.' end newproperty(:reservation) do desc 'The reservation property. Valid values are ``, `none`.' end newproperty(:secondarycache) do desc 'The secondarycache property. Valid values are `all`, `none`, `metadata`.' end newproperty(:setuid) do desc 'The setuid property. Valid values are `on`, `off`.' end newproperty(:shareiscsi) do desc 'The shareiscsi property. Valid values are `on`, `off`, `type=`.' end newproperty(:sharenfs) do desc 'The sharenfs property. Valid values are `on`, `off`, share(1M) options' end newproperty(:sharesmb) do desc 'The sharesmb property. Valid values are `on`, `off`, sharemgr(1M) options' end newproperty(:snapdir) do desc 'The snapdir property. Valid values are `hidden`, `visible`.' end newproperty(:sync) do desc 'The sync property. Valid values are `standard`, `always`, `disabled`.' end newproperty(:version) do desc 'The version property. Valid values are `1`, `2`, `3`, `4`, `current`.' end newproperty(:volsize) do desc 'The volsize property. Valid values are ``' end newproperty(:vscan) do desc 'The vscan property. Valid values are `on`, `off`.' end newproperty(:xattr) do desc 'The xattr property. Valid values are `on`, `off`.' end newproperty(:zoned) do desc 'The zoned property. Valid values are `on`, `off`.' end autorequire(:zpool) do # strip the zpool off the zfs name and autorequire it [@parameters[:name].value.split('/')[0]] end autorequire(:zfs) do # slice and dice, we want all the zfs before this one names = @parameters[:name].value.split('/') names.slice(1..-2).reduce([]) { |a, v| a << "#{a.last}/#{v}" }.map { |fs| names[0] + fs } end end end diff --git a/lib/puppet/type/zpool.rb b/lib/puppet/type/zpool.rb index 2047e9f..955ab1d 100644 --- a/lib/puppet/type/zpool.rb +++ b/lib/puppet/type/zpool.rb @@ -1,127 +1,127 @@ # ZPool type module Puppet # Puppet::Property class Property # VDev class class VDev < Property # @param array the array to be flattened and sorted # @return [Array] returns a flattened and sorted array def flatten_and_sort(array) array = [array] unless array.is_a? Array array.map { |a| a.split(' ') }.flatten.sort end # @param is the current state of the object # @return [Boolean] if the resource is in sync with what it should be def insync?(is) return @should == [:absent] if is == :absent flatten_and_sort(is) == flatten_and_sort(@should) end end # MultiVDev class class MultiVDev < VDev # @param is the current state of the object # @return [Boolean] if the resource is in sync with what it should be def insync?(is) return @should == [:absent] if is == :absent return false unless is.length == @should.length is.each_with_index { |list, i| return false unless flatten_and_sort(list) == flatten_and_sort(@should[i]) } # if we made it this far we are in sync true end end end Type.newtype(:zpool) do desc <<-DESC - Manage zpools. Create and delete zpools. The provider WILL NOT SYNC, only report differences. + @summary Manage zpools. Create and delete zpools. The provider WILL NOT SYNC, only report differences. Supports vdevs with mirrors, raidz, logs, spares, and cache. @example Using zpool. zpool { 'tstpool': ensure => present, - disk => '/ztstpool/dsk', + disk => '/ztstpool/dsk', } DESC ensurable newproperty(:disk, array_matching: :all, parent: Puppet::Property::VDev) do desc 'The disk(s) for this pool. Can be an array or a space separated string.' end newproperty(:mirror, array_matching: :all, parent: Puppet::Property::MultiVDev) do desc "List of all the devices to mirror for this pool. Each mirror should be a space separated string: mirror => [\"disk1 disk2\", \"disk3 disk4\"], " validate do |value| raise ArgumentError, _('mirror names must be provided as string separated, not a comma-separated list') if value.include?(',') end end newproperty(:raidz, array_matching: :all, parent: Puppet::Property::MultiVDev) do desc "List of all the devices to raid for this pool. Should be an array of space separated strings: raidz => [\"disk1 disk2\", \"disk3 disk4\"], " validate do |value| raise ArgumentError, _('raid names must be provided as string separated, not a comma-separated list') if value.include?(',') end end newproperty(:spare, array_matching: :all, parent: Puppet::Property::VDev) do desc 'Spare disk(s) for this pool.' end newproperty(:log, array_matching: :all, parent: Puppet::Property::VDev) do desc 'Log disks for this pool. This type does not currently support mirroring of log disks.' end newproperty(:cache, array_matching: :all, parent: Puppet::Property::VDev) do desc 'Cache disks for this pool.' end newproperty(:ashift) do desc 'The Alignment Shift for the vdevs in the given pool.' validate do |_value| raise Puppet::Error _('This property is only supported on Linux') unless Facter.value(:kernel) == 'Linux' end end newproperty(:autoexpand) do desc 'The autoexpand setting for the given pool. Valid values are `on` or `off`' end newproperty(:failmode) do desc 'The failmode setting for the given pool. Valid values are `wait`, `continue` or `panic`' end newparam(:pool) do desc 'The name for this pool.' isnamevar end newparam(:raid_parity) do desc 'Determines parity when using the `raidz` parameter.' end validate do has_should = [:disk, :mirror, :raidz].select { |prop| should(prop) } raise _('You cannot specify %{multiple_props} on this type (only one)') % { multiple_props: has_should.join(' and ') } if has_should.length > 1 end end end diff --git a/metadata.json b/metadata.json index e469480..6764f25 100644 --- a/metadata.json +++ b/metadata.json @@ -1,27 +1,27 @@ { "name": "puppetlabs-zfs_core", "version": "1.2.0", "author": "puppetlabs", "summary": "Manage zfs. Create destroy and set properties on zfs instances.", "license": "Apache-2.0", "source": "https://github.com/puppetlabs/puppetlabs-zfs_core", "project_page": "https://github.com/puppetlabs/puppetlabs-zfs_core", "issues_url": "https://tickets.puppetlabs.com/projects/MODULES", "dependencies": [ ], "operatingsystem_support": [ { "operatingsystem": "Solaris" } ], "requirements": [ { "name": "puppet", "version_requirement": ">= 6.0.0 < 8.0.0" } ], - "pdk-version": "1.14.0", - "template-url": "https://github.com/puppetlabs/pdk-templates#1.14.0", - "template-ref": "1.14.0-0-g1bf3a4e" + "pdk-version": "2.2.0", + "template-url": "https://github.com/puppetlabs/pdk-templates#2.2.0", + "template-ref": "tags/2.2.0-0-g2381db6" } diff --git a/rakelib/commits.rake b/rakelib/commits.rake new file mode 100644 index 0000000..42eb209 --- /dev/null +++ b/rakelib/commits.rake @@ -0,0 +1,26 @@ +desc "verify that commit messages match CONTRIBUTING.md requirements" +task(:commits) do + # This rake task looks at the summary from every commit from this branch not + # in the branch targeted for a PR. + commit_range = 'HEAD^..HEAD' + puts "Checking commits #{commit_range}" + %x{git log --no-merges --pretty=%s #{commit_range}}.each_line do |commit_summary| + # This regex tests for the currently supported commit summary tokens. + # The exception tries to explain it in more full. + if /^Release prep|\((maint|packaging|doc|docs|modules-\d+)\)|revert/i.match(commit_summary).nil? + raise "\n\n\n\tThis commit summary didn't match CONTRIBUTING.md guidelines:\n" \ + "\n\t\t#{commit_summary}\n" \ + "\tThe commit summary (i.e. the first line of the commit message) should start with one of:\n" \ + "\t\t(MODULES-) # this is most common and should be a ticket at tickets.puppet.com\n" \ + "\t\t(docs)\n" \ + "\t\t(docs)(DOCUMENT-)\n" \ + "\t\t(packaging)\n" + "\t\t(maint)\n" \ + "\t\tRelease prep v\n" \ + "\n\tThis test for the commit summary is case-insensitive.\n\n\n" + else + puts "#{commit_summary}" + end + puts "...passed" + end +end diff --git a/spec/acceptance/lib/solaris_util.rb b/spec/acceptance/lib/solaris_util.rb index 89e36db..1adee9b 100644 --- a/spec/acceptance/lib/solaris_util.rb +++ b/spec/acceptance/lib/solaris_util.rb @@ -1,29 +1,29 @@ OPTS = { poolpath: '/ztstpool', pool: 'tstpool', fs: 'tstfs' }.freeze def zfs_clean(agent, o = {}) o = OPTS.merge(o) - on agent, 'zfs destroy -f -r %s/%s ||:' % [o[:pool], o[:fs]] - on agent, 'zpool destroy -f %s ||:' % o[:pool] - on agent, 'rm -rf %s ||:' % o[:poolpath] + on agent, "zfs destroy -f -r #{o[:pool]}/#{o[:fs]} ||:" + on agent, "zpool destroy -f #{o[:pool]} ||:" + on agent, "rm -rf #{o[:poolpath]} ||:" end def zfs_setup(agent, o = {}) o = OPTS.merge(o) - on agent, 'mkdir -p %s/mnt' % o[:poolpath] - on agent, 'mkdir -p %s/mnt2' % o[:poolpath] - on agent, 'mkfile 64m %s/dsk' % o[:poolpath] - on agent, 'zpool create %s %s/dsk' % [o[:pool], o[:poolpath]] + on agent, "mkdir -p #{o[:poolpath]}/mnt" + on agent, "mkdir -p #{o[:poolpath]}/mnt2" + on agent, "mkfile 64m #{o[:poolpath]}/dsk" + on agent, "zpool create #{o[:pool]} #{o[:poolpath]}/dsk" end def zpool_clean(agent, o = {}) o = OPTS.merge(o) - on agent, 'zpool destroy -f %s ||:' % o[:pool] - on agent, 'rm -rf %s ||:' % o[:poolpath] + on agent, "zpool destroy -f #{o[:pool]} ||:" + on agent, "rm -rf #{o[:poolpath]} ||:" end def zpool_setup(agent, o = {}) o = OPTS.merge(o) - on agent, 'mkdir -p %s/mnt||:' % o[:poolpath] - on agent, 'mkfile 100m %s/dsk1 %s/dsk2 %s/dsk3 %s/dsk5 ||:' % ([o[:poolpath]] * 4) - on agent, 'mkfile 50m %s/dsk4 ||:' % o[:poolpath] + on agent, "mkdir -p #{o[:poolpath]}/mnt||:" + on agent, "mkfile 100m #{o[:poolpath]}/dsk1 #{o[:poolpath]}/dsk2 #{o[:poolpath]}/dsk3 #{o[:poolpath]}/dsk5 ||:" + on agent, "mkfile 50m #{o[:poolpath]}/dsk4 ||:" end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 012cbac..9b1fa6f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,58 +1,73 @@ +# frozen_string_literal: true + RSpec.configure do |c| c.mock_with :rspec end require 'puppetlabs_spec_helper/module_spec_helper' require 'rspec-puppet-facts' require 'spec_helper_local' if File.file?(File.join(File.dirname(__FILE__), 'spec_helper_local.rb')) include RspecPuppetFacts default_facts = { puppetversion: Puppet.version, facterversion: Facter.version, } default_fact_files = [ File.expand_path(File.join(File.dirname(__FILE__), 'default_facts.yml')), File.expand_path(File.join(File.dirname(__FILE__), 'default_module_facts.yml')), ] default_fact_files.each do |f| next unless File.exist?(f) && File.readable?(f) && File.size?(f) begin default_facts.merge!(YAML.safe_load(File.read(f), [], [], true)) rescue => e RSpec.configuration.reporter.message "WARNING: Unable to load #{f}: #{e}" end end # read default_facts and merge them over what is provided by facterdb default_facts.each do |fact, value| add_custom_fact fact, value end RSpec.configure do |c| c.default_facts = default_facts c.before :each do # set to strictest setting for testing # by default Puppet runs at warning level Puppet.settings[:strict] = :warning + Puppet.settings[:strict_variables] = true end c.filter_run_excluding(bolt: true) unless ENV['GEM_BOLT'] c.after(:suite) do end + + # Filter backtrace noise + backtrace_exclusion_patterns = [ + %r{spec_helper}, + %r{gems}, + ] + + if c.respond_to?(:backtrace_exclusion_patterns) + c.backtrace_exclusion_patterns = backtrace_exclusion_patterns + elsif c.respond_to?(:backtrace_clean_patterns) + c.backtrace_clean_patterns = backtrace_exclusion_patterns + end end # Ensures that a module is defined # @param module_name Name of the module def ensure_module_defined(module_name) module_name.split('::').reduce(Object) do |last_module, next_module| last_module.const_set(next_module, Module.new) unless last_module.const_defined?(next_module, false) last_module.const_get(next_module, false) end end # 'spec_overrides' from sync.yml will appear below this line diff --git a/spec/unit/provider/zfs/zfs_spec.rb b/spec/unit/provider/zfs/zfs_spec.rb index 266e146..e8cefa1 100644 --- a/spec/unit/provider/zfs/zfs_spec.rb +++ b/spec/unit/provider/zfs/zfs_spec.rb @@ -1,166 +1,166 @@ require 'spec_helper' describe Puppet::Type.type(:zfs).provider(:zfs) do let(:name) { 'myzfs' } let(:zfs) { '/usr/sbin/zfs' } let(:resource) do Puppet::Type.type(:zfs).new(name: name, provider: :zfs) end let(:provider) { resource.provider } before(:each) do allow(provider.class).to receive(:which).with('zfs') { zfs } allow(Facter).to receive(:value).with(:kernel).and_return('Linux') end context '.instances' do it 'has an instances method' do expect(provider.class).to respond_to(:instances) end it 'lists instances' do allow(provider.class).to receive(:zfs).with(:list, '-H') { File.read(my_fixture('zfs-list.out')) } instances = provider.class.instances.map { |p| { name: p.get(:name), ensure: p.get(:ensure) } } expect(instances.size).to eq(2) expect(instances[0]).to eq(name: 'rpool', ensure: :present) expect(instances[1]).to eq(name: 'rpool/ROOT', ensure: :present) end end context '#add_properties' do it 'returns an array of properties' do resource[:mountpoint] = '/foo' expect(provider.add_properties).to eq(['-o', 'mountpoint=/foo']) end it 'returns an empty array' do expect(provider.add_properties).to eq([]) end end context '#create' do it 'executes zfs create' do expect(provider).to receive(:zfs).with(:create, name) provider.create end Puppet::Type.type(:zfs).validproperties.each do |prop| next if [:ensure, :volsize].include?(prop) - it "should include property #{prop}" do + it "includes property #{prop}" do resource[prop] = prop expect(provider).to receive(:zfs).with(:create, '-o', "#{prop}=#{prop}", name) provider.create end end it 'uses -V for the volsize property' do resource[:volsize] = '10' expect(provider).to receive(:zfs).with(:create, '-V', '10', name) provider.create end end context '#destroy' do it 'executes zfs destroy' do expect(provider).to receive(:zfs).with(:destroy, name) provider.destroy end end context '#exists?' do it 'returns true if the resource exists' do # return stuff because we have to slice and dice it expect(provider).to receive(:zfs).with(:list, name) expect(provider).to be_exists end it "returns false if returned values don't match the name" do expect(provider).to receive(:zfs).with(:list, name).and_raise(Puppet::ExecutionFailure, 'Failed') expect(provider).not_to be_exists end end describe 'zfs properties' do [:aclinherit, :aclmode, :atime, :canmount, :checksum, :compression, :copies, :dedup, :devices, :exec, :logbias, :mountpoint, :nbmand, :overlay, :primarycache, :quota, :readonly, :recordsize, :refquota, :refreservation, :reservation, :secondarycache, :setuid, :shareiscsi, :sharenfs, :sharesmb, :snapdir, :version, :volsize, :vscan, :xattr].each do |prop| - it "should get #{prop}" do + it "gets #{prop}" do expect(provider).to receive(:zfs).with(:get, '-H', '-o', 'value', prop, name).and_return("value\n") expect(provider.send(prop)).to eq('value') end - it "should set #{prop}=value" do + it "sets #{prop}=value" do expect(provider).to receive(:zfs).with(:set, "#{prop}=value", name) provider.send("#{prop}=", 'value') end end end describe 'zoned' do context 'on FreeBSD' do before(:each) do allow(Facter).to receive(:value).with(:operatingsystem).and_return('FreeBSD') end it "gets 'jailed' property" do expect(provider).to receive(:zfs).with(:get, '-H', '-o', 'value', :jailed, name).and_return("value\n") expect(provider.send('zoned')).to eq('value') end it 'sets jalied=value' do expect(provider).to receive(:zfs).with(:set, 'jailed=value', name) provider.send('zoned=', 'value') end end context 'when not running FreeBSD' do before(:each) do allow(Facter).to receive(:value).with(:operatingsystem).and_return('Solaris') end it "gets 'zoned' property" do expect(provider).to receive(:zfs).with(:get, '-H', '-o', 'value', :zoned, name).and_return("value\n") expect(provider.send('zoned')).to eq('value') end it 'sets zoned=value' do expect(provider).to receive(:zfs).with(:set, 'zoned=value', name) provider.send('zoned=', 'value') end end end describe 'acltype' do context 'when available' do it "gets 'acltype' property" do expect(provider).to receive(:zfs).with(:get, '-H', '-o', 'value', :acltype, name).and_return("value\n") expect(provider.send('acltype')).to eq('value') end it 'sets acltype=value' do expect(provider).to receive(:zfs).with(:set, 'acltype=value', name) provider.send('acltype=', 'value') end end context 'when not available' do it "gets '-' for the acltype property" do expect(provider).to receive(:zfs).with(:get, '-H', '-o', 'value', :acltype, name).and_raise(RuntimeError, 'not valid') expect(provider.send('acltype')).to eq('-') end it 'does not error out when trying to set acltype' do expect(provider).to receive(:zfs).with(:set, 'acltype=value', name).and_raise(RuntimeError, 'not valid') expect { provider.send('acltype=', 'value') }.not_to raise_error end end end end diff --git a/spec/unit/provider/zpool/zpool_spec.rb b/spec/unit/provider/zpool/zpool_spec.rb index 416d989..f773707 100644 --- a/spec/unit/provider/zpool/zpool_spec.rb +++ b/spec/unit/provider/zpool/zpool_spec.rb @@ -1,329 +1,329 @@ require 'spec_helper' describe Puppet::Type.type(:zpool).provider(:zpool) do let(:name) { 'mypool' } let(:zpool) { '/usr/sbin/zpool' } let(:resource) do Puppet::Type.type(:zpool).new(name: name, provider: :zpool) end let(:provider) { resource.provider } before(:each) do allow(provider.class).to receive(:which).with('zpool') { zpool } allow(Facter).to receive(:value).with(:kernel).and_return('Linux') end context '#current_pool' do it 'calls process_zpool_data with the result of get_pool_data only once' do allow(provider).to receive(:get_pool_data).and_return(['foo', 'disk']) allow(provider).to receive(:process_zpool_data).with(['foo', 'disk']) { 'stuff' } expect(provider).to receive(:process_zpool_data).with(['foo', 'disk']).once provider.current_pool provider.current_pool end end describe 'self.instances' do it 'has an instances method' do expect(provider.class).to respond_to(:instances) end it 'lists instances' do allow(provider.class).to receive(:zpool).with(:list, '-H') { File.read(my_fixture('zpool-list.out')) } instances = provider.class.instances.map { |p| { name: p.get(:name), ensure: p.get(:ensure) } } expect(instances.size).to eq(2) expect(instances[0]).to eq(name: 'rpool', ensure: :present) expect(instances[1]).to eq(name: 'mypool', ensure: :present) end end context '#flush' do it 'reloads the pool' do allow(provider).to receive(:get_pool_data) allow(provider).to receive(:process_zpool_data).and_return('stuff') expect(provider).to receive(:process_zpool_data).twice provider.current_pool provider.flush provider.current_pool end end context '#process_zpool_data' do let(:zpool_data) { ['foo', 'disk'] } describe 'when there is no data' do it 'returns a hash with ensure=>:absent' do expect(provider.process_zpool_data([])[:ensure]).to eq(:absent) end end describe 'when there are full path disks on Linux' do it 'munges partitions into disk names' do allow(provider).to receive(:execute).with('lsblk -p -no pkname /dev/sdc1').and_return('/dev/sdc') allow(provider).to receive(:execute).with('lsblk -p -no pkname /dev/disk/by-id/disk_serial-0:0-part1').and_return('/dev/disk/by-id/disk_serial-0:0') zpool_data = ['foo', '/dev/sdc1', '/dev/disk/by-id/disk_serial-0:0-part1'] expect(provider.process_zpool_data(zpool_data)[:disk]).to eq(['/dev/sdc /dev/disk/by-id/disk_serial-0:0']) end end describe 'when there is a spare' do it 'adds the spare disk to the hash' do zpool_data.concat ['spares', 'spare_disk'] expect(provider.process_zpool_data(zpool_data)[:spare]).to eq(['spare_disk']) end end describe 'when there are two spares' do it 'adds the spare disk to the hash as a single string' do zpool_data.concat ['spares', 'spare_disk', 'spare_disk2'] expect(provider.process_zpool_data(zpool_data)[:spare]).to eq(['spare_disk spare_disk2']) end end describe 'when there is a log' do it 'adds the log disk to the hash' do zpool_data.concat ['logs', 'log_disk'] expect(provider.process_zpool_data(zpool_data)[:log]).to eq(['log_disk']) end end describe 'when there are two logs' do it 'adds the log disks to the hash as a single string' do zpool_data.concat ['logs', 'log_disk', 'log_disk2'] expect(provider.process_zpool_data(zpool_data)[:log]).to eq(['log_disk log_disk2']) end end describe 'when there is a cache' do it 'adds the cache disk to the hash' do zpool_data.concat ['cache', 'cache_disk'] expect(provider.process_zpool_data(zpool_data)[:cache]).to eq(['cache_disk']) end end describe 'when there are two caches' do it 'adds the cache disks to the hash as a single string' do zpool_data.concat ['cache', 'cache_disk', 'cache_disk2'] expect(provider.process_zpool_data(zpool_data)[:cache]).to eq(['cache_disk cache_disk2']) end end describe 'when the vdev is a single mirror' do it 'calls create_multi_array with mirror' do zpool_data = ['mirrorpool', 'mirror', 'disk1', 'disk2'] expect(provider.process_zpool_data(zpool_data)[:mirror]).to eq(['disk1 disk2']) end end describe 'when the vdev is a single mirror on solaris 10u9 or later' do it 'calls create_multi_array with mirror' do zpool_data = ['mirrorpool', 'mirror-0', 'disk1', 'disk2'] expect(provider.process_zpool_data(zpool_data)[:mirror]).to eq(['disk1 disk2']) end end describe 'when the vdev is a double mirror' do it 'calls create_multi_array with mirror' do zpool_data = ['mirrorpool', 'mirror', 'disk1', 'disk2', 'mirror', 'disk3', 'disk4'] expect(provider.process_zpool_data(zpool_data)[:mirror]).to eq(['disk1 disk2', 'disk3 disk4']) end end describe 'when the vdev is a double mirror on solaris 10u9 or later' do it 'calls create_multi_array with mirror' do zpool_data = ['mirrorpool', 'mirror-0', 'disk1', 'disk2', 'mirror-1', 'disk3', 'disk4'] expect(provider.process_zpool_data(zpool_data)[:mirror]).to eq(['disk1 disk2', 'disk3 disk4']) end end describe 'when the vdev is a raidz1' do it 'calls create_multi_array with raidz1' do zpool_data = ['mirrorpool', 'raidz1', 'disk1', 'disk2'] expect(provider.process_zpool_data(zpool_data)[:raidz]).to eq(['disk1 disk2']) end end describe 'when the vdev is a raidz1 on solaris 10u9 or later' do it 'calls create_multi_array with raidz1' do zpool_data = ['mirrorpool', 'raidz1-0', 'disk1', 'disk2'] expect(provider.process_zpool_data(zpool_data)[:raidz]).to eq(['disk1 disk2']) end end describe 'when the vdev is a raidz2' do it 'calls create_multi_array with raidz2 and set the raid_parity' do zpool_data = ['mirrorpool', 'raidz2', 'disk1', 'disk2'] pool = provider.process_zpool_data(zpool_data) expect(pool[:raidz]).to eq(['disk1 disk2']) expect(pool[:raid_parity]).to eq('raidz2') end end describe 'when the vdev is a raidz2 on solaris 10u9 or later' do it 'calls create_multi_array with raidz2 and set the raid_parity' do zpool_data = ['mirrorpool', 'raidz2-0', 'disk1', 'disk2'] pool = provider.process_zpool_data(zpool_data) expect(pool[:raidz]).to eq(['disk1 disk2']) expect(pool[:raid_parity]).to eq('raidz2') end end end describe 'when calling the getters and setters for configurable options' do [:autoexpand, :failmode].each do |field| - it "should get the #{field} value from the pool" do + it "gets the #{field} value from the pool" do allow(provider).to receive(:zpool).with(:get, field, name).and_return("NAME PROPERTY VALUE SOURCE\n#{name} #{field} value local") expect(provider.send(field)).to eq('value') end - it "should set #{field}=value" do + it "sets #{field}=value" do expect(provider).to receive(:zpool).with(:set, "#{field}=value", name) provider.send("#{field}=", 'value') end end end describe 'when calling the getters and setters for ashift' do context 'when available' do it "gets 'ashift' property" do expect(provider).to receive(:zpool).with(:get, :ashift, name).and_return("NAME PROPERTY VALUE SOURCE\n#{name} ashift value local") expect(provider.send('ashift')).to eq('value') end it 'sets ashift=value' do expect(provider).to receive(:zpool).with(:set, 'ashift=value', name) provider.send('ashift=', 'value') end end context 'when not available' do it "gets '-' for the ashift property" do expect(provider).to receive(:zpool).with(:get, :ashift, name).and_raise(RuntimeError, 'not valid') expect(provider.send('ashift')).to eq('-') end it 'does not error out when trying to set ashift' do expect(provider).to receive(:zpool).with(:set, 'ashift=value', name).and_raise(RuntimeError, 'not valid') expect { provider.send('ashift=', 'value') }.not_to raise_error end end end describe 'when calling the getters and setters' do [:disk, :mirror, :raidz, :log, :spare, :cache].each do |field| describe "when calling #{field}" do - it "should get the #{field} value from the current_pool hash" do + it "gets the #{field} value from the current_pool hash" do pool_hash = {} pool_hash[field] = 'value' allow(provider).to receive(:current_pool) { pool_hash } expect(provider.send(field)).to eq('value') end end describe "when setting the #{field}" do - it "should fail if readonly #{field} values change" do + it "fails if readonly #{field} values change" do allow(provider).to receive(:current_pool) { Hash.new('currentvalue') } expect { provider.send((field.to_s + '=').to_sym, 'shouldvalue') }.to raise_error(Puppet::Error, %r{can\'t be changed}) end end end end context '#create' do context 'when creating disks for a zpool' do before(:each) do resource[:disk] = 'disk1' end it 'calls create with the build_vdevs value' do expect(provider).to receive(:zpool).with(:create, name, 'disk1') provider.create end it "calls create with the 'spares' and 'log' values" do resource[:spare] = ['value1'] resource[:log] = ['value2'] resource[:cache] = ['value3'] expect(provider).to receive(:zpool).with(:create, name, 'disk1', 'spare', 'value1', 'log', 'value2', 'cache', 'value3') provider.create end end context 'when creating mirrors for a zpool' do it "executes 'create' for a single group of mirrored devices" do resource[:mirror] = ['disk1 disk2'] expect(provider).to receive(:zpool).with(:create, name, 'mirror', 'disk1', 'disk2') provider.create end it "repeats the 'mirror' keyword between groups of mirrored devices" do resource[:mirror] = ['disk1 disk2', 'disk3 disk4'] expect(provider).to receive(:zpool).with(:create, name, 'mirror', 'disk1', 'disk2', 'mirror', 'disk3', 'disk4') provider.create end end describe 'when creating raidz for a zpool' do it "executes 'create' for a single raidz group" do resource[:raidz] = ['disk1 disk2'] expect(provider).to receive(:zpool).with(:create, name, 'raidz1', 'disk1', 'disk2') provider.create end it "execute 'create' for a single raidz2 group" do resource[:raidz] = ['disk1 disk2'] resource[:raid_parity] = 'raidz2' expect(provider).to receive(:zpool).with(:create, name, 'raidz2', 'disk1', 'disk2') provider.create end it "repeats the 'raidz1' keyword between each group of raidz devices" do resource[:raidz] = ['disk1 disk2', 'disk3 disk4'] expect(provider).to receive(:zpool).with(:create, name, 'raidz1', 'disk1', 'disk2', 'raidz1', 'disk3', 'disk4') provider.create end end describe 'when creating a zpool with options' do before(:each) do resource[:disk] = 'disk1' end [:ashift, :autoexpand, :failmode].each do |field| - it "should include field #{field}" do + it "includes field #{field}" do resource[field] = field expect(provider).to receive(:zpool).with(:create, '-o', "#{field}=#{field}", name, 'disk1') provider.create end end end end context '#delete' do it 'calls zpool with destroy and the pool name' do expect(provider).to receive(:zpool).with(:destroy, name) provider.destroy end end context '#exists?' do it 'gets the current pool' do allow(provider).to receive(:current_pool).and_return(pool: 'somepool') expect(provider).to receive(:current_pool) provider.exists? end it 'returns false if the current_pool is absent' do allow(provider).to receive(:current_pool).and_return(pool: :absent) expect(provider).to receive(:current_pool) expect(provider).not_to be_exists end it 'returns true if the current_pool has values' do allow(provider).to receive(:current_pool).and_return(pool: name) expect(provider).to receive(:current_pool) expect(provider).to be_exists end end end diff --git a/spec/unit/type/zfs_spec.rb b/spec/unit/type/zfs_spec.rb index 4f85757..13afbbf 100644 --- a/spec/unit/type/zfs_spec.rb +++ b/spec/unit/type/zfs_spec.rb @@ -1,43 +1,43 @@ require 'spec_helper' describe Puppet::Type.type(:zfs) do properties = [:ensure, :mountpoint, :compression, :copies, :overlay, :quota, :reservation, :sharenfs, :snapdir, :sync] properties.each do |property| - it "should have a #{property} property" do + it "has a #{property} property" do expect(described_class.attrclass(property).ancestors).to be_include(Puppet::Property) end end parameters = [:name] parameters.each do |parameter| - it "should have a #{parameter} parameter" do + it "has a #{parameter} parameter" do expect(described_class.attrclass(parameter).ancestors).to be_include(Puppet::Parameter) end end it 'autorequires the containing zfs and the zpool' do zfs_provider = instance_double 'provider' allow(zfs_provider).to receive(:name).and_return(:zfs) allow(described_class).to receive(:defaultprovider) { zfs_provider } zpool_provider = instance_double 'provider' allow(zpool_provider).to receive(:name).and_return(:zpool) allow(Puppet::Type.type(:zpool)).to receive(:defaultprovider) { zpool_provider } foo_pool = Puppet::Type.type(:zpool).new(name: 'foo') foo_bar_zfs = described_class.new(name: 'foo/bar') foo_bar_baz_zfs = described_class.new(name: 'foo/bar/baz') foo_bar_baz_buz_zfs = described_class.new(name: 'foo/bar/baz/buz') Puppet::Resource::Catalog.new :testing do |conf| [foo_pool, foo_bar_zfs, foo_bar_baz_zfs, foo_bar_baz_buz_zfs].each { |resource| conf.add_resource resource } end req = foo_bar_baz_buz_zfs.autorequire.map { |edge| edge.source.ref } [foo_pool.ref, foo_bar_zfs.ref, foo_bar_baz_zfs.ref].each { |ref| expect(req.include?(ref)).to eq(true) } end end diff --git a/spec/unit/type/zpool_spec.rb b/spec/unit/type/zpool_spec.rb index d63f8d7..9860355 100644 --- a/spec/unit/type/zpool_spec.rb +++ b/spec/unit/type/zpool_spec.rb @@ -1,107 +1,107 @@ require 'spec_helper' describe 'zpool' do describe Puppet::Type.type(:zpool) do properties = [:ensure, :disk, :mirror, :raidz, :spare, :log, :autoexpand, :failmode, :ashift, :cache] properties.each do |property| - it "should have a #{property} property" do + it "has a #{property} property" do expect(described_class.attrclass(property).ancestors).to be_include(Puppet::Property) end end parameters = [:pool, :raid_parity] parameters.each do |parameter| - it "should have a #{parameter} parameter" do + it "has a #{parameter} parameter" do expect(described_class.attrclass(parameter).ancestors).to be_include(Puppet::Parameter) end end end describe Puppet::Property::VDev do let(:resource) { instance_double('resource', :[]= => nil, :property => nil) } let(:vdev) do described_class.new( resource: resource, ) end before(:each) do described_class.initvars end it 'is insync if the devices are the same' do vdev.should = ['dev1 dev2'] expect(vdev).to be_safe_insync(['dev2 dev1']) end it 'is out of sync if the devices are not the same' do vdev.should = ['dev1 dev3'] expect(vdev).not_to be_safe_insync(['dev2 dev1']) end it 'is insync if the devices are the same and the should values are comma separated' do vdev.should = ['dev1', 'dev2'] expect(vdev).to be_safe_insync(['dev2 dev1']) end it 'is out of sync if the device is absent and should has a value' do vdev.should = ['dev1', 'dev2'] expect(vdev).not_to be_safe_insync(:absent) end it 'is insync if the device is absent and should is absent' do vdev.should = [:absent] expect(vdev).to be_safe_insync(:absent) end end describe Puppet::Property::MultiVDev do let(:resource) { instance_double('resource', :[]= => nil, :property => nil) } let(:multi_vdev) do described_class.new( resource: resource, ) end before(:each) do described_class.initvars end it 'is insync if the devices are the same' do multi_vdev.should = ['dev1 dev2'] expect(multi_vdev).to be_safe_insync(['dev2 dev1']) end it 'is out of sync if the devices are not the same' do multi_vdev.should = ['dev1 dev3'] expect(multi_vdev).not_to be_safe_insync(['dev2 dev1']) end it 'is out of sync if the device is absent and should has a value' do multi_vdev.should = ['dev1', 'dev2'] expect(multi_vdev).not_to be_safe_insync(:absent) end it 'is insync if the device is absent and should is absent' do multi_vdev.should = [:absent] expect(multi_vdev).to be_safe_insync(:absent) end describe 'when there are multiple lists of devices' do it 'is in sync if each group has the same devices' do multi_vdev.should = ['dev1 dev2', 'dev3 dev4'] expect(multi_vdev).to be_safe_insync(['dev2 dev1', 'dev3 dev4']) end it 'is out of sync if any group has the different devices' do multi_vdev.should = ['dev1 devX', 'dev3 dev4'] expect(multi_vdev).not_to be_safe_insync(['dev2 dev1', 'dev3 dev4']) end it 'is out of sync if devices are in the wrong group' do multi_vdev.should = ['dev1 dev2', 'dev3 dev4'] expect(multi_vdev).not_to be_safe_insync(['dev2 dev3', 'dev1 dev4']) end end end end