diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 4e115c9..4021da7 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -1,217 +1,217 @@ name: "nightly" on: schedule: - cron: '0 0 * * *' env: HONEYCOMB_WRITEKEY: 7f3c63a70eecc61d635917de46bea4e6 HONEYCOMB_DATASET: litmus tests jobs: setup_matrix: name: "Setup Test Matrix" runs-on: ubuntu-20.04 outputs: matrix: ${{ steps.get-matrix.outputs.matrix }} steps: - name: "Honeycomb: Start recording" - uses: kvrhdn/gha-buildevents@v1.0.2 + uses: kvrhdn/gha-buildevents@5be4636b81803713c94d7cb7e3a4b85d759df112 # pin@v1.0.2 with: apikey: ${{ env.HONEYCOMB_WRITEKEY }} dataset: ${{ env.HONEYCOMB_DATASET }} job-status: ${{ job.status }} - name: "Honeycomb: Start first step" run: | echo STEP_ID=0 >> $GITHUB_ENV echo STEP_START=$(date +%s) >> $GITHUB_ENV - name: Checkout Source uses: actions/checkout@v2 if: ${{ github.repository_owner == 'puppetlabs' }} - name: Activate Ruby 2.7 uses: actions/setup-ruby@v1 if: ${{ github.repository_owner == 'puppetlabs' }} with: ruby-version: "2.7" - name: Cache gems uses: actions/cache@v2 if: ${{ github.repository_owner == 'puppetlabs' }} with: path: vendor/gems key: ${{ runner.os }}-${{ github.event_name }}-${{ hashFiles('**/Gemfile') }} restore-keys: | ${{ runner.os }}-${{ github.event_name }}- ${{ runner.os }}- - name: Install gems if: ${{ github.repository_owner == 'puppetlabs' }} run: | buildevents cmd $TRACE_ID $STEP_ID 'bundle config path vendor/gems' -- bundle config path vendor/gems buildevents cmd $TRACE_ID $STEP_ID 'bundle config jobs 8' -- bundle config jobs 8 buildevents cmd $TRACE_ID $STEP_ID 'bundle config retry 3' -- bundle config retry 3 buildevents cmd $TRACE_ID $STEP_ID 'bundle install' -- bundle install buildevents cmd $TRACE_ID $STEP_ID 'bundle clean' -- bundle clean - name: Setup Acceptance Test Matrix id: get-matrix if: ${{ github.repository_owner == 'puppetlabs' }} run: | if [ '${{ github.repository_owner }}' == 'puppetlabs' ]; then buildevents cmd $TRACE_ID $STEP_ID matrix_from_metadata -- bundle exec matrix_from_metadata else echo "::set-output name=matrix::{}" fi - name: "Honeycomb: Record setup time" if: ${{ always() }} run: | buildevents step $TRACE_ID $STEP_ID $STEP_START 'Setup Test Matrix' Acceptance: needs: - setup_matrix runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: ${{fromJson(needs.setup_matrix.outputs.matrix)}} env: BUILDEVENT_FILE: '../buildevents.txt' steps: - run: | echo 'platform=${{ matrix.platform }}' >> $BUILDEVENT_FILE echo 'collection=${{ matrix.collection }}' >> $BUILDEVENT_FILE - name: "Honeycomb: Start recording" - uses: kvrhdn/gha-buildevents@v1.0.2 + uses: kvrhdn/gha-buildevents@5be4636b81803713c94d7cb7e3a4b85d759df112 # pin@v1.0.2 with: apikey: ${{ env.HONEYCOMB_WRITEKEY }} dataset: ${{ env.HONEYCOMB_DATASET }} job-status: ${{ job.status }} matrix-key: ${{ matrix.platform }}-${{ matrix.collection }} - name: "Honeycomb: start first step" run: | echo STEP_ID=${{ matrix.platform }}-${{ matrix.collection }}-1 >> $GITHUB_ENV echo STEP_START=$(date +%s) >> $GITHUB_ENV - name: Checkout Source uses: actions/checkout@v2 - name: Activate Ruby 2.7 uses: actions/setup-ruby@v1 with: ruby-version: "2.7" - name: Cache gems uses: actions/cache@v2 with: path: vendor/gems key: ${{ runner.os }}-${{ github.event_name }}-${{ hashFiles('**/Gemfile') }} restore-keys: | ${{ runner.os }}-${{ github.event_name }}- ${{ runner.os }}- - name: "Honeycomb: Record cache setup time" if: ${{ always() }} run: | buildevents step $TRACE_ID $STEP_ID $STEP_START 'Cache retrieval' echo STEP_ID=${{ matrix.platform }}-${{ matrix.collection }}-2 >> $GITHUB_ENV echo STEP_START=$(date +%s) >> $GITHUB_ENV - name: Bundler Setup run: | buildevents cmd $TRACE_ID $STEP_ID 'bundle config path vendor/gems' -- bundle config path vendor/gems buildevents cmd $TRACE_ID $STEP_ID 'bundle config jobs 8' -- bundle config jobs 8 buildevents cmd $TRACE_ID $STEP_ID 'bundle config retry 3' -- bundle config retry 3 buildevents cmd $TRACE_ID $STEP_ID 'bundle install' -- bundle install buildevents cmd $TRACE_ID $STEP_ID 'bundle clean' -- bundle clean echo ::group::bundler environment buildevents cmd $TRACE_ID $STEP_ID 'bundle env' -- bundle env echo ::endgroup:: - name: "Honeycomb: Record Bundler Setup time" if: ${{ always() }} run: | buildevents step $TRACE_ID $STEP_ID $STEP_START 'Bundler Setup' echo STEP_ID=${{ matrix.platform }}-${{ matrix.collection }}-3 >> $GITHUB_ENV echo STEP_START=$(date +%s) >> $GITHUB_ENV - name: Provision test environment run: | buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:provision ${{ matrix.platform }}' -- bundle exec rake 'litmus:provision[provision::provision_service,${{ matrix.platform }}]' echo ::group::=== REQUEST === cat request.json || true echo echo ::endgroup:: echo ::group::=== INVENTORY === sed -e 's/password: .*/password: "[redacted]"/' < inventory.yaml || true echo ::endgroup:: - name: Install agent run: | buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:install_agent ${{ matrix.collection }}' -- bundle exec rake 'litmus:install_agent[${{ matrix.collection }}]' - name: Install module run: | buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:install_module' -- bundle exec rake 'litmus:install_module' - name: "Honeycomb: Record deployment times" if: ${{ always() }} run: | echo ::group::honeycomb step buildevents step $TRACE_ID $STEP_ID $STEP_START 'Deploy test system' echo STEP_ID=${{ matrix.platform }}-${{ matrix.collection }}-4 >> $GITHUB_ENV echo STEP_START=$(date +%s) >> $GITHUB_ENV echo ::endgroup:: - name: Run acceptance tests run: | buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:acceptance:parallel' -- bundle exec rake 'litmus:acceptance:parallel' - name: "Honeycomb: Record acceptance testing times" if: ${{ always() }} run: | buildevents step $TRACE_ID $STEP_ID $STEP_START 'Run acceptance tests' echo STEP_ID=${{ matrix.platform }}-${{ matrix.collection }}-5 >> $GITHUB_ENV echo STEP_START=$(date +%s) >> $GITHUB_ENV - name: Remove test environment if: ${{ always() }} run: | if [ -f inventory.yaml ]; then buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:tear_down' -- bundle exec rake 'litmus:tear_down' echo ::group::=== REQUEST === cat request.json || true echo echo ::endgroup:: fi - name: "Honeycomb: Record removal times" if: ${{ always() }} run: | buildevents step $TRACE_ID $STEP_ID $STEP_START 'Remove test environment' slack-workflow-status: if: always() name: Post Workflow Status To Slack needs: - Acceptance runs-on: ubuntu-20.04 steps: - name: Slack Workflow Notification - uses: Gamesight/slack-workflow-status@master + uses: Gamesight/slack-workflow-status@88ee95b73b4669825883ddf22747966204663e58 # pin@master with: # Required Input repo_token: ${{ secrets.GITHUB_TOKEN }} slack_webhook_url: ${{ secrets.SLACK_WEBHOOK }} # Optional Input channel: '#team-ia-bots' name: 'GABot' diff --git a/.github/workflows/pr_test.yml b/.github/workflows/pr_test.yml index b8f49c7..2b5ab1f 100644 --- a/.github/workflows/pr_test.yml +++ b/.github/workflows/pr_test.yml @@ -1,198 +1,198 @@ name: "PR Testing" on: [pull_request] env: HONEYCOMB_WRITEKEY: 7f3c63a70eecc61d635917de46bea4e6 HONEYCOMB_DATASET: litmus tests jobs: setup_matrix: name: "Setup Test Matrix" runs-on: ubuntu-20.04 outputs: matrix: ${{ steps.get-matrix.outputs.matrix }} steps: - name: "Honeycomb: Start recording" - uses: kvrhdn/gha-buildevents@v1.0.2 + uses: kvrhdn/gha-buildevents@5be4636b81803713c94d7cb7e3a4b85d759df112 # pin@v1.0.2 with: apikey: ${{ env.HONEYCOMB_WRITEKEY }} dataset: ${{ env.HONEYCOMB_DATASET }} job-status: ${{ job.status }} - name: "Honeycomb: Start first step" run: | echo STEP_ID=0 >> $GITHUB_ENV echo STEP_START=$(date +%s) >> $GITHUB_ENV - name: Checkout Source uses: actions/checkout@v2 if: ${{ github.repository_owner == 'puppetlabs' }} - name: Activate Ruby 2.7 uses: actions/setup-ruby@v1 if: ${{ github.repository_owner == 'puppetlabs' }} with: ruby-version: "2.7" - name: Cache gems uses: actions/cache@v2 if: ${{ github.repository_owner == 'puppetlabs' }} with: path: vendor/gems key: ${{ runner.os }}-${{ github.event_name }}-${{ hashFiles('**/Gemfile') }} restore-keys: | ${{ runner.os }}-${{ github.event_name }}- ${{ runner.os }}- - name: Install gems if: ${{ github.repository_owner == 'puppetlabs' }} run: | buildevents cmd $TRACE_ID $STEP_ID 'bundle config path vendor/gems' -- bundle config path vendor/gems buildevents cmd $TRACE_ID $STEP_ID 'bundle config jobs 8' -- bundle config jobs 8 buildevents cmd $TRACE_ID $STEP_ID 'bundle config retry 3' -- bundle config retry 3 buildevents cmd $TRACE_ID $STEP_ID 'bundle install' -- bundle install buildevents cmd $TRACE_ID $STEP_ID 'bundle clean' -- bundle clean - name: Setup Acceptance Test Matrix id: get-matrix if: ${{ github.repository_owner == 'puppetlabs' }} run: | if [ '${{ github.repository_owner }}' == 'puppetlabs' ]; then buildevents cmd $TRACE_ID $STEP_ID matrix_from_metadata -- bundle exec matrix_from_metadata else echo "::set-output name=matrix::{}" fi - name: "Honeycomb: Record setup time" if: ${{ always() }} run: | buildevents step $TRACE_ID $STEP_ID $STEP_START 'Setup Test Matrix' Acceptance: needs: - setup_matrix runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: ${{fromJson(needs.setup_matrix.outputs.matrix)}} env: BUILDEVENT_FILE: '../buildevents.txt' steps: - run: | echo 'platform=${{ matrix.platform }}' >> $BUILDEVENT_FILE echo 'collection=${{ matrix.collection }}' >> $BUILDEVENT_FILE - name: "Honeycomb: Start recording" - uses: kvrhdn/gha-buildevents@v1.0.2 + uses: kvrhdn/gha-buildevents@5be4636b81803713c94d7cb7e3a4b85d759df112 # pin@v1.0.2 with: apikey: ${{ env.HONEYCOMB_WRITEKEY }} dataset: ${{ env.HONEYCOMB_DATASET }} job-status: ${{ job.status }} matrix-key: ${{ matrix.platform }}-${{ matrix.collection }} - name: "Honeycomb: start first step" run: | echo STEP_ID=${{ matrix.platform }}-${{ matrix.collection }}-1 >> $GITHUB_ENV echo STEP_START=$(date +%s) >> $GITHUB_ENV - name: Checkout Source uses: actions/checkout@v2 - name: Activate Ruby 2.7 uses: actions/setup-ruby@v1 with: ruby-version: "2.7" - name: Cache gems uses: actions/cache@v2 with: path: vendor/gems key: ${{ runner.os }}-${{ github.event_name }}-${{ hashFiles('**/Gemfile') }} restore-keys: | ${{ runner.os }}-${{ github.event_name }}- ${{ runner.os }}- - name: "Honeycomb: Record cache setup time" if: ${{ always() }} run: | buildevents step $TRACE_ID $STEP_ID $STEP_START 'Cache retrieval' echo STEP_ID=${{ matrix.platform }}-${{ matrix.collection }}-2 >> $GITHUB_ENV echo STEP_START=$(date +%s) >> $GITHUB_ENV - name: Bundler Setup run: | buildevents cmd $TRACE_ID $STEP_ID 'bundle config path vendor/gems' -- bundle config path vendor/gems buildevents cmd $TRACE_ID $STEP_ID 'bundle config jobs 8' -- bundle config jobs 8 buildevents cmd $TRACE_ID $STEP_ID 'bundle config retry 3' -- bundle config retry 3 buildevents cmd $TRACE_ID $STEP_ID 'bundle install' -- bundle install buildevents cmd $TRACE_ID $STEP_ID 'bundle clean' -- bundle clean echo ::group::bundler environment buildevents cmd $TRACE_ID $STEP_ID 'bundle env' -- bundle env echo ::endgroup:: - name: "Honeycomb: Record Bundler Setup time" if: ${{ always() }} run: | buildevents step $TRACE_ID $STEP_ID $STEP_START 'Bundler Setup' echo STEP_ID=${{ matrix.platform }}-${{ matrix.collection }}-3 >> $GITHUB_ENV echo STEP_START=$(date +%s) >> $GITHUB_ENV - name: Provision test environment run: | buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:provision ${{ matrix.platform }}' -- bundle exec rake 'litmus:provision[provision::provision_service,${{ matrix.platform }}]' echo ::group::=== REQUEST === cat request.json || true echo echo ::endgroup:: echo ::group::=== INVENTORY === sed -e 's/password: .*/password: "[redacted]"/' < inventory.yaml || true echo ::endgroup:: - name: Install agent run: | buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:install_agent ${{ matrix.collection }}' -- bundle exec rake 'litmus:install_agent[${{ matrix.collection }}]' - name: Install module run: | buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:install_module' -- bundle exec rake 'litmus:install_module' - name: "Honeycomb: Record deployment times" if: ${{ always() }} run: | echo ::group::honeycomb step buildevents step $TRACE_ID $STEP_ID $STEP_START 'Deploy test system' echo STEP_ID=${{ matrix.platform }}-${{ matrix.collection }}-4 >> $GITHUB_ENV echo STEP_START=$(date +%s) >> $GITHUB_ENV echo ::endgroup:: - name: Run acceptance tests run: | buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:acceptance:parallel' -- bundle exec rake 'litmus:acceptance:parallel' - name: "Honeycomb: Record acceptance testing times" if: ${{ always() }} run: | buildevents step $TRACE_ID $STEP_ID $STEP_START 'Run acceptance tests' echo STEP_ID=${{ matrix.platform }}-${{ matrix.collection }}-5 >> $GITHUB_ENV echo STEP_START=$(date +%s) >> $GITHUB_ENV - name: Remove test environment if: ${{ always() }} run: | if [ -f inventory.yaml ]; then buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:tear_down' -- bundle exec rake 'litmus:tear_down' echo ::group::=== REQUEST === cat request.json || true echo echo ::endgroup:: fi - name: "Honeycomb: Record removal times" if: ${{ always() }} run: | buildevents step $TRACE_ID $STEP_ID $STEP_START 'Remove test environment' diff --git a/.rubocop.yml b/.rubocop.yml index b7c972b..3aaa696 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,138 +1,512 @@ --- 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 Style/BlockDelimiters: Description: Prefer braces for chaining. Mostly an aesthetical choice. Better to be consistent then. EnforcedStyle: braces_for_chaining -Style/BracesAroundHashParameters: - Description: Braces are required by Ruby 2.7. Cop removed from RuboCop v0.80.0. - See https://github.com/rubocop-hq/rubocop/pull/7643 - Enabled: false 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 -inherit_from: ".rubocop_todo.yml" 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 +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/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/.rubocop_todo.yml b/.rubocop_todo.yml deleted file mode 100644 index 57dd86c..0000000 --- a/.rubocop_todo.yml +++ /dev/null @@ -1,2 +0,0 @@ -GetText/DecorateString: - Enabled: false diff --git a/.sync.yml b/.sync.yml index 36d2725..fbd2371 100644 --- a/.sync.yml +++ b/.sync.yml @@ -1,65 +1,51 @@ --- ".gitlab-ci.yml": delete: true -".rubocop.yml": - default_configs: - inherit_from: ".rubocop_todo.yml" - require: - - rubocop-i18n - - rubocop-rspec ".travis.yml": - global_env: + global_env: - HONEYCOMB_WRITEKEY="7f3c63a70eecc61d635917de46bea4e6",HONEYCOMB_DATASET="litmus tests" deploy_to_forge: enabled: false branches: - release use_litmus: true litmus: provision_list: - ---travis_el - travis_deb - travis_el7 - travis_el8 complex: - collection: puppet_collection: - puppet6 provision_list: - travis_ub_6 - collection: puppet_collection: - puppet5 provision_list: - travis_ub_5 simplecov: true notifications: slack: secure: XpBD602OXRZHSTDylzzx/OqpfThEJPbx0PLhXctWuES4GpW1EHWnyPgrliNOaJOh0Zb7qMrdaKWLOltfqPT5IanPd0XF7GbT8RrNeLTmLXqvHmC6dDqWxnvFvdSrGwqpj7s7Dbwl79nmszONRj1OlolPmJgY/2kGw88c71biaas= appveyor.yml: delete: true Gemfile: optional: ":development": - - gem: puppet-lint-i18n - 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') - - gem: puppet-resource_api -Rakefile: - requires: - - puppet_pot_generator/rake_tasks spec/spec_helper.rb: spec_overrides: - require 'spec_helper_local' coverage_report: true "  changelog_user": puppetlabs .gitpod.Dockerfile: unmanaged: false .gitpod.yml: unmanaged: false .github/workflows/nightly.yml: unmanaged: false .github/workflows/pr_test.yml: unmanaged: false diff --git a/.travis.yml b/.travis.yml index 4c9a2ff..066fafb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,137 +1,129 @@ --- os: linux dist: xenial language: ruby cache: bundler before_install: - bundle -v - rm -f Gemfile.lock - "# Update system gems if requested. This is useful to temporarily workaround troubles in the test runner" - "# See https://github.com/puppetlabs/pdk-templates/commit/705154d5c437796b821691b707156e1b056d244f for an example of how this was used" - "# Ignore exit code of SIGPIPE'd yes to not fail with shell's pipefail set" - '[ -z "$RUBYGEMS_VERSION" ] || (yes || true) | gem update --system $RUBYGEMS_VERSION' - gem --version - bundle -v script: - 'SIMPLECOV=yes bundle exec rake $CHECK' bundler_args: --without system_tests rvm: - 2.5.7 env: global: - HONEYCOMB_WRITEKEY="7f3c63a70eecc61d635917de46bea4e6",HONEYCOMB_DATASET="litmus tests" stages: - static - spec - acceptance jobs: fast_finish: true include: - - + - bundler_args: --with system_tests before_script: - "bundle exec rake 'litmus:provision_list[travis_ub_6]'" - "bundle exec rake 'litmus:install_agent[puppet6]'" - "bundle exec rake litmus:install_module" - bundler_args: env: PLATFORMS=travis_ub_6_puppet6 rvm: 2.5.7 script: ["travis_wait 45 bundle exec rake litmus:acceptance:parallel"] services: docker stage: acceptance - - + - bundler_args: --with system_tests before_script: - "bundle exec rake 'litmus:provision_list[travis_ub_5]'" - "bundle exec rake 'litmus:install_agent[puppet5]'" - "bundle exec rake litmus:install_module" - bundler_args: env: PLATFORMS=travis_ub_5_puppet5 rvm: 2.5.7 script: ["travis_wait 45 bundle exec rake litmus:acceptance:parallel"] services: docker stage: acceptance - - + - bundler_args: --with system_tests before_script: - "bundle exec rake 'litmus:provision_list[travis_deb]'" - "bundle exec rake 'litmus:install_agent[puppet5]'" - "bundle exec rake litmus:install_module" - bundler_args: env: PLATFORMS=travis_deb_puppet5 rvm: 2.5.7 script: ["travis_wait 45 bundle exec rake litmus:acceptance:parallel"] services: docker stage: acceptance - - + - bundler_args: --with system_tests before_script: - "bundle exec rake 'litmus:provision_list[travis_el7]'" - "bundle exec rake 'litmus:install_agent[puppet5]'" - "bundle exec rake litmus:install_module" - bundler_args: env: PLATFORMS=travis_el7_puppet5 rvm: 2.5.7 script: ["travis_wait 45 bundle exec rake litmus:acceptance:parallel"] services: docker stage: acceptance - - + - bundler_args: --with system_tests before_script: - "bundle exec rake 'litmus:provision_list[travis_el8]'" - "bundle exec rake 'litmus:install_agent[puppet5]'" - "bundle exec rake litmus:install_module" - bundler_args: env: PLATFORMS=travis_el8_puppet5 rvm: 2.5.7 script: ["travis_wait 45 bundle exec rake litmus:acceptance:parallel"] services: docker stage: acceptance - - + - bundler_args: --with system_tests before_script: - "bundle exec rake 'litmus:provision_list[travis_deb]'" - "bundle exec rake 'litmus:install_agent[puppet6]'" - "bundle exec rake litmus:install_module" - bundler_args: env: PLATFORMS=travis_deb_puppet6 rvm: 2.5.7 script: ["travis_wait 45 bundle exec rake litmus:acceptance:parallel"] services: docker stage: acceptance - - + - bundler_args: --with system_tests before_script: - "bundle exec rake 'litmus:provision_list[travis_el7]'" - "bundle exec rake 'litmus:install_agent[puppet6]'" - "bundle exec rake litmus:install_module" - bundler_args: env: PLATFORMS=travis_el7_puppet6 rvm: 2.5.7 script: ["travis_wait 45 bundle exec rake litmus:acceptance:parallel"] services: docker stage: acceptance - - + - bundler_args: --with system_tests before_script: - "bundle exec rake 'litmus:provision_list[travis_el8]'" - "bundle exec rake 'litmus:install_agent[puppet6]'" - "bundle exec rake litmus:install_module" - bundler_args: env: PLATFORMS=travis_el8_puppet6 rvm: 2.5.7 script: ["travis_wait 45 bundle exec rake litmus:acceptance:parallel"] services: docker stage: acceptance - env: CHECK="check:symlinks check:git_ignore check:dot_underscore check:test_file rubocop syntax lint metadata_lint" stage: static - env: PUPPET_GEM_VERSION="~> 5.0" CHECK=parallel_spec rvm: 2.4.5 stage: spec - env: PUPPET_GEM_VERSION="~> 6.0" CHECK=parallel_spec rvm: 2.5.7 stage: spec branches: only: - main - /^v\d/ - release notifications: email: false slack: secure: XpBD602OXRZHSTDylzzx/OqpfThEJPbx0PLhXctWuES4GpW1EHWnyPgrliNOaJOh0Zb7qMrdaKWLOltfqPT5IanPd0XF7GbT8RrNeLTmLXqvHmC6dDqWxnvFvdSrGwqpj7s7Dbwl79nmszONRj1OlolPmJgY/2kGw88c71biaas= diff --git a/Gemfile b/Gemfile index d6b5c1e..ae2b430 100644 --- a/Gemfile +++ b/Gemfile @@ -1,76 +1,73 @@ 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 "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 "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 "rb-readline", '= 0.5.5', require: false, platforms: [:mswin, :mingw, :x64_mingw] - gem "puppet-module-posix-default-r#{minor_version}", '~> 0.4', require: false, platforms: [:ruby] - gem "puppet-module-posix-dev-r#{minor_version}", '~> 0.4', require: false, platforms: [:ruby] - gem "puppet-module-win-default-r#{minor_version}", '~> 0.4', require: false, platforms: [:mswin, :mingw, :x64_mingw] - gem "puppet-module-win-dev-r#{minor_version}", '~> 0.4', require: false, platforms: [:mswin, :mingw, :x64_mingw] - gem "puppet-lint-i18n", 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') - gem "puppet-resource_api", require: false + 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 "github_changelog_generator", require: false +end +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/Rakefile b/Rakefile index f8ec754..0a5093b 100644 --- a/Rakefile +++ b/Rakefile @@ -1,88 +1,87 @@ # frozen_string_literal: true 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? -require 'puppet_pot_generator/rake_tasks' 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 = "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; LABEL THEM ON GITHUB" config.configure_sections = { "Changed" => { "prefix" => "### Changed", "labels" => ["backwards-incompatible"], }, "Added" => { "prefix" => "### Added", "labels" => ["enhancement", "feature"], }, "Fixed" => { "prefix" => "### Fixed", "labels" => ["bug", "documentation", "bugfix"], }, } end else desc 'Generate a Changelog from GitHub' task :changelog do raise < 1.15' condition: "Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.3.0')" EOM end end diff --git a/lib/facter/mysql_server_id.rb b/lib/facter/mysql_server_id.rb index caeafd2..ec8cd99 100644 --- a/lib/facter/mysql_server_id.rb +++ b/lib/facter/mysql_server_id.rb @@ -1,22 +1,24 @@ +# frozen_string_literal: true + def mysql_id_get # Convert the existing mac to an integer macval = Facter.value(:macaddress).delete(':').to_i(16) # Valid range is from 1 - 4294967295 for replication hosts. # We can not guarantee a fully unique value, this reduces the # full mac value down to into that number space. # # The -1/+1 ensures that we keep above 1 if we get unlucky # enough to hit a mac address that evenly divides. (macval % (4_294_967_295 - 1)) + 1 end Facter.add('mysql_server_id') do setcode do begin mysql_id_get rescue nil end end end diff --git a/lib/facter/mysql_version.rb b/lib/facter/mysql_version.rb index 73e14ee..f79b2af 100644 --- a/lib/facter/mysql_version.rb +++ b/lib/facter/mysql_version.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + Facter.add('mysql_version') do confine { Facter::Core::Execution.which('mysql') } setcode do mysql_ver = Facter::Util::Resolution.exec('mysql --version') mysql_ver.match(%r{\d+\.\d+\.\d+})[0] if mysql_ver end end diff --git a/lib/facter/mysqld_version.rb b/lib/facter/mysqld_version.rb index a0e67ea..6548f5b 100644 --- a/lib/facter/mysqld_version.rb +++ b/lib/facter/mysqld_version.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + Facter.add('mysqld_version') do confine { Facter::Core::Execution.which('mysqld') } setcode do Facter::Util::Resolution.exec('mysqld --no-defaults -V 2>/dev/null') end end diff --git a/lib/puppet/functions/mysql/normalise_and_deepmerge.rb b/lib/puppet/functions/mysql/normalise_and_deepmerge.rb index 70c03f5..c2f5880 100644 --- a/lib/puppet/functions/mysql/normalise_and_deepmerge.rb +++ b/lib/puppet/functions/mysql/normalise_and_deepmerge.rb @@ -1,67 +1,69 @@ +# frozen_string_literal: true + # @summary Recursively merges two or more hashes together, normalises keys with differing use of dashesh and underscores, # then returns the resulting hash. # # @example # $hash1 = {'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } } # $hash2 = {'two' => 'dos', 'three' => { 'five' => 5 } } # $merged_hash = mysql::normalise_and_deepmerge($hash1, $hash2) # # The resulting hash is equivalent to: # # $merged_hash = { 'one' => 1, 'two' => 'dos', 'three' => { 'four' => 4, 'five' => 5 } } # # - When there is a duplicate key that is a hash, they are recursively merged. # - When there is a duplicate key that is not a hash, the key in the rightmost hash will "win." # - When there are conficting uses of dashes and underscores in two keys (which mysql would otherwise equate), the rightmost style will win. # Puppet::Functions.create_function(:'mysql::normalise_and_deepmerge') do def normalise_and_deepmerge(*args) if args.length < 2 raise Puppet::ParseError, _('mysql::normalise_and_deepmerge(): wrong number of arguments (%{args_length}; must be at least 2)') % { args_length: args.length } end result = {} args.each do |arg| next if arg.is_a?(String) && arg.empty? # empty string is synonym for puppet's undef # If the argument was not a hash, skip it. unless arg.is_a?(Hash) raise Puppet::ParseError, _('mysql::normalise_and_deepmerge: unexpected argument type %{arg_class}, only expects hash arguments.') % { args_class: args.class } end # We need to make a copy of the hash since it is frozen by puppet current = deep_copy(arg) # Now we have to traverse our hash assigning our non-hash values # to the matching keys in our result while following our hash values # and repeating the process. overlay(result, current) end result end def normalized?(hash, key) return true if hash.key?(key) - return false unless key =~ %r{-|_} + return false unless %r{-|_}.match?(key) other_key = key.include?('-') ? key.tr('-', '_') : key.tr('_', '-') return false unless hash.key?(other_key) hash[key] = hash.delete(other_key) true end def overlay(hash1, hash2) hash2.each do |key, value| if normalized?(hash1, key) && value.is_a?(Hash) && hash1[key].is_a?(Hash) overlay(hash1[key], value) else hash1[key] = value end end end def deep_copy(inputhash) return inputhash unless inputhash.is_a? Hash hash = {} inputhash.each do |k, v| hash.store(k, deep_copy(v)) end hash end end diff --git a/lib/puppet/functions/mysql/password.rb b/lib/puppet/functions/mysql/password.rb index 4a8db82..8cb9e55 100644 --- a/lib/puppet/functions/mysql/password.rb +++ b/lib/puppet/functions/mysql/password.rb @@ -1,22 +1,24 @@ +# frozen_string_literal: true + require 'digest/sha1' # @summary # Hash a string as mysql's "PASSWORD()" function would do it # Puppet::Functions.create_function(:'mysql::password') do # @param password # Plain text password. # # @return hash # The mysql password hash from the clear text password. # dispatch :password do required_param 'String', :password return_type 'String' end def password(password) return '' if password.empty? - return password if password =~ %r{\*[A-F0-9]{40}$} + return password if %r{\*[A-F0-9]{40}$}.match?(password) '*' + Digest::SHA1.hexdigest(Digest::SHA1.digest(password)).upcase end end diff --git a/lib/puppet/functions/mysql/strip_hash.rb b/lib/puppet/functions/mysql/strip_hash.rb index a978ae8..00b9cc2 100644 --- a/lib/puppet/functions/mysql/strip_hash.rb +++ b/lib/puppet/functions/mysql/strip_hash.rb @@ -1,22 +1,24 @@ +# frozen_string_literal: true + # @summary # When given a hash this function strips out all blank entries. # Puppet::Functions.create_function(:'mysql::strip_hash') do # @param hash # Hash to be stripped # # @return hash # The given hash with all blank entries removed # dispatch :strip_hash do required_param 'Hash', :hash return_type 'Hash' end def strip_hash(hash) # Filter out all the top level blanks. hash.reject { |_k, v| v == '' }.each do |_k, v| v.reject! { |_ki, vi| vi == '' } if v.is_a?(Hash) end end end diff --git a/lib/puppet/functions/mysql_password.rb b/lib/puppet/functions/mysql_password.rb index d2ac76d..2353f04 100644 --- a/lib/puppet/functions/mysql_password.rb +++ b/lib/puppet/functions/mysql_password.rb @@ -1,17 +1,19 @@ +# frozen_string_literal: true + # @summary DEPRECATED. Use the namespaced function [`mysql::password`](#mysqlpassword) instead. Puppet::Functions.create_function(:mysql_password) do # @param password # Plain text password. # # @return # The mysql password hash from the 4.x function mysql::password. dispatch :mysql_password do required_param 'String', :password return_type 'String' end def mysql_password(password) call_function('deprecation', 'mysql_password', "This method has been deprecated, please use the namespaced version 'mysql::password' instead.") call_function('mysql::password', password) end end diff --git a/lib/puppet/provider/mysql.rb b/lib/puppet/provider/mysql.rb index dd261ab..2c572bb 100644 --- a/lib/puppet/provider/mysql.rb +++ b/lib/puppet/provider/mysql.rb @@ -1,175 +1,177 @@ +# frozen_string_literal: true + # Puppet provider for mysql class Puppet::Provider::Mysql < Puppet::Provider # Without initvars commands won't work. initvars # Make sure we find mysql commands on CentOS and FreeBSD ENV['PATH'] = ENV['PATH'] + ':/usr/libexec:/usr/local/libexec:/usr/local/bin' ENV['LD_LIBRARY_PATH'] = [ ENV['LD_LIBRARY_PATH'], '/usr/lib', '/usr/lib64', '/opt/rh/rh-mysql56/root/usr/lib', '/opt/rh/rh-mysql56/root/usr/lib64', '/opt/rh/rh-mysql57/root/usr/lib', '/opt/rh/rh-mysql57/root/usr/lib64', '/opt/rh/rh-mysql80/root/usr/lib', '/opt/rh/rh-mysql80/root/usr/lib64', '/opt/rh/rh-mariadb100/root/usr/lib', '/opt/rh/rh-mariadb100/root/usr/lib64', '/opt/rh/rh-mariadb101/root/usr/lib', '/opt/rh/rh-mariadb101/root/usr/lib64', '/opt/rh/rh-mariadb102/root/usr/lib', '/opt/rh/rh-mariadb102/root/usr/lib64', '/opt/rh/rh-mariadb103/root/usr/lib', '/opt/rh/rh-mariadb103/root/usr/lib64', '/opt/rh/mysql55/root/usr/lib', '/opt/rh/mysql55/root/usr/lib64', '/opt/rh/mariadb55/root/usr/lib', '/opt/rh/mariadb55/root/usr/lib64', '/usr/mysql/5.5/lib', '/usr/mysql/5.5/lib64', '/usr/mysql/5.6/lib', '/usr/mysql/5.6/lib64', '/usr/mysql/5.7/lib', '/usr/mysql/5.7/lib64', ].join(':') # rubocop:disable Style/HashSyntax commands :mysql_raw => 'mysql' commands :mysqld => 'mysqld' commands :mysqladmin => 'mysqladmin' # rubocop:enable Style/HashSyntax # Optional defaults file def self.defaults_file "--defaults-extra-file=#{Facter.value(:root_home)}/.my.cnf" if File.file?("#{Facter.value(:root_home)}/.my.cnf") end def self.mysqld_type # find the mysql "dialect" like mariadb / mysql etc. mysqld_version_string.scan(%r{mariadb}i) { return 'mariadb' } mysqld_version_string.scan(%r{\s\(percona}i) { return 'percona' } 'mysql' end def mysqld_type self.class.mysqld_type end def self.mysqld_version_string # As the possibility of the mysqld being remote we need to allow the version string to be overridden, # this can be done by facter.value as seen below. In the case that it has not been set and the facter # value is nil we use the mysql -v command to ensure we report the correct version of mysql for later use cases. @mysqld_version_string ||= Facter.value(:mysqld_version) || mysqld('-V') end def mysqld_version_string self.class.mysqld_version_string end def self.mysqld_version - # note: be prepared for '5.7.6-rc-log' etc results + # NOTE: be prepared for '5.7.6-rc-log' etc results # versioncmp detects 5.7.6-log to be newer then 5.7.6 # this is why we need the trimming. - mysqld_version_string.scan(%r{\d+\.\d+\.\d+}).first unless mysqld_version_string.nil? + mysqld_version_string&.scan(%r{\d+\.\d+\.\d+})&.first end def mysqld_version self.class.mysqld_version end def self.newer_than(forks_versions) - forks_versions.keys.include?(mysqld_type) && Puppet::Util::Package.versioncmp(mysqld_version, forks_versions[mysqld_type]) >= 0 + forks_versions.key?(mysqld_type) && Puppet::Util::Package.versioncmp(mysqld_version, forks_versions[mysqld_type]) >= 0 end def newer_than(forks_versions) self.class.newer_than(forks_versions) end def self.older_than(forks_versions) - forks_versions.keys.include?(mysqld_type) && Puppet::Util::Package.versioncmp(mysqld_version, forks_versions[mysqld_type]) < 0 + forks_versions.key?(mysqld_type) && Puppet::Util::Package.versioncmp(mysqld_version, forks_versions[mysqld_type]) < 0 end def older_than(forks_versions) self.class.older_than(forks_versions) end def defaults_file self.class.defaults_file end def self.mysql_caller(text_of_sql, type) if type.eql? 'system' if File.file?("#{Facter.value(:root_home)}/.mylogin.cnf") ENV['MYSQL_TEST_LOGIN_FILE'] = "#{Facter.value(:root_home)}/.mylogin.cnf" mysql_raw([system_database, '-e', text_of_sql].flatten.compact).scrub else mysql_raw([defaults_file, system_database, '-e', text_of_sql].flatten.compact).scrub end elsif type.eql? 'regular' if File.file?("#{Facter.value(:root_home)}/.mylogin.cnf") ENV['MYSQL_TEST_LOGIN_FILE'] = "#{Facter.value(:root_home)}/.mylogin.cnf" mysql_raw(['-NBe', text_of_sql].flatten.compact).scrub else mysql_raw([defaults_file, '-NBe', text_of_sql].flatten.compact).scrub end else raise Puppet::Error, _("#mysql_caller: Unrecognised type '%{type}'" % { type: type }) end end def self.users mysql_caller("SELECT CONCAT(User, '@',Host) AS User FROM mysql.user", 'regular').split("\n") end # Optional parameter to run a statement on the MySQL system database. def self.system_database '--database=mysql' end def system_database self.class.system_database end # Take root@localhost and munge it to 'root'@'localhost' # Take root@id123@localhost and munge it to 'root@id123'@'localhost' def self.cmd_user(user) "'#{user.reverse.sub('@', "'@'").reverse}'" end # Take root.* and return ON `root`.* def self.cmd_table(table) table_string = '' # We can't escape *.* so special case this. - table_string << if table == '*.*' + table_string += if table == '*.*' '*.*' # Special case also for FUNCTIONs and PROCEDUREs elsif table.start_with?('FUNCTION ', 'PROCEDURE ') table.sub(%r{^(FUNCTION|PROCEDURE) (.*)(\..*)}, '\1 `\2`\3') else table.sub(%r{^(.*)(\..*)}, '`\1`\2') end table_string end def self.cmd_privs(privileges) return 'ALL PRIVILEGES' if privileges.include?('ALL') priv_string = '' privileges.each do |priv| - priv_string << "#{priv}, " + priv_string += "#{priv}, " end # Remove trailing , from the last element. priv_string.sub(%r{, $}, '') end # Take in potential options and build up a query string with them. def self.cmd_options(options) option_string = '' options.each do |opt| - option_string << ' WITH GRANT OPTION' if opt == 'GRANT' + option_string += ' WITH GRANT OPTION' if opt == 'GRANT' end option_string end end diff --git a/lib/puppet/provider/mysql_database/mysql.rb b/lib/puppet/provider/mysql_database/mysql.rb index bbbf216..04d95bf 100644 --- a/lib/puppet/provider/mysql_database/mysql.rb +++ b/lib/puppet/provider/mysql_database/mysql.rb @@ -1,65 +1,67 @@ +# frozen_string_literal: true + require File.expand_path(File.join(File.dirname(__FILE__), '..', 'mysql')) Puppet::Type.type(:mysql_database).provide(:mysql, parent: Puppet::Provider::Mysql) do desc 'Manages MySQL databases.' commands mysql_raw: 'mysql' def self.instances mysql_caller('show databases', 'regular').split("\n").map do |name| attributes = {} mysql_caller(["show variables like '%_database'", name], 'regular').split("\n").each do |line| k, v = line.split(%r{\s}) attributes[k] = v end new(name: name, ensure: :present, charset: attributes['character_set_database'], collate: attributes['collation_database']) end end # We iterate over each mysql_database entry in the catalog and compare it against # the contents of the property_hash generated by self.instances def self.prefetch(resources) databases = instances - resources.keys.each do |database| + resources.each_key do |database| provider = databases.find { |db| db.name == database } resources[database].provider = provider if provider end end def create self.class.mysql_caller("create database if not exists `#{@resource[:name]}` character set `#{@resource[:charset]}` collate `#{@resource[:collate]}`", 'regular') @property_hash[:ensure] = :present @property_hash[:charset] = @resource[:charset] @property_hash[:collate] = @resource[:collate] exists? ? (return true) : (return false) end def destroy self.class.mysql_caller("drop database if exists `#{@resource[:name]}`", 'regular') @property_hash.clear exists? ? (return false) : (return true) end def exists? @property_hash[:ensure] == :present || false end mk_resource_methods def charset=(value) self.class.mysql_caller("alter database `#{resource[:name]}` CHARACTER SET #{value}", 'regular') @property_hash[:charset] = value (charset == value) ? (return true) : (return false) end def collate=(value) self.class.mysql_caller("alter database `#{resource[:name]}` COLLATE #{value}", 'regular') @property_hash[:collate] = value (collate == value) ? (return true) : (return false) end end diff --git a/lib/puppet/provider/mysql_datadir/mysql.rb b/lib/puppet/provider/mysql_datadir/mysql.rb index 88b8fa3..31cae9d 100644 --- a/lib/puppet/provider/mysql_datadir/mysql.rb +++ b/lib/puppet/provider/mysql_datadir/mysql.rb @@ -1,98 +1,100 @@ +# frozen_string_literal: true + require File.expand_path(File.join(File.dirname(__FILE__), '..', 'mysql')) Puppet::Type.type(:mysql_datadir).provide(:mysql, parent: Puppet::Provider::Mysql) do desc 'manage data directories for mysql instances' initvars # Make sure we find mysqld on CentOS and mysql_install_db on Gentoo and Solaris 11 ENV['PATH'] = [ ENV['PATH'], '/usr/libexec', '/usr/share/mysql/scripts', '/opt/rh/rh-mysql80/root/usr/bin', '/opt/rh/rh-mysql80/root/usr/libexec', '/opt/rh/rh-mysql57/root/usr/bin', '/opt/rh/rh-mysql57/root/usr/libexec', '/opt/rh/rh-mysql56/root/usr/bin', '/opt/rh/rh-mysql56/root/usr/libexec', '/opt/rh/rh-mariadb101/root/usr/bin', '/opt/rh/rh-mariadb101/root/usr/libexec', '/opt/rh/rh-mariadb100/root/usr/bin', '/opt/rh/rh-mariadb100/root/usr/libexec', '/opt/rh/rh-mariadb102/root/usr/bin', '/opt/rh/rh-mariadb102/root/usr/libexec', '/opt/rh/rh-mariadb103/root/usr/bin', '/opt/rh/rh-mariadb103/root/usr/libexec', '/opt/rh/mysql55/root/usr/bin', '/opt/rh/mysql55/root/usr/libexec', '/opt/rh/mariadb55/root/usr/bin', '/opt/rh/mariadb55/root/usr/libexec', '/usr/mysql/5.5/bin', '/usr/mysql/5.6/bin', '/usr/mysql/5.7/bin', ].join(':') commands mysqld: 'mysqld' optional_commands mysql_install_db: 'mysql_install_db' # rubocop:disable Lint/UselessAssignment def create name = @resource[:name] insecure = @resource.value(:insecure) || true defaults_extra_file = @resource.value(:defaults_extra_file) user = @resource.value(:user) || 'mysql' basedir = @resource.value(:basedir) datadir = @resource.value(:datadir) || @resource[:name] log_error = @resource.value(:log_error) || '/var/tmp/mysqld_initialize.log' # rubocop:enable Lint/UselessAssignment unless defaults_extra_file.nil? unless File.exist?(defaults_extra_file) raise ArgumentError, _('Defaults-extra-file %{file} is missing.') % { file: defaults_extra_file } end defaults_extra_file = "--defaults-extra-file=#{defaults_extra_file}" end initialize = if insecure == true '--initialize-insecure' else '--initialize' end opts = [defaults_extra_file] ['basedir', 'datadir', 'user'].each do |opt| val = eval(opt) # rubocop:disable Security/Eval opts << "--#{opt}=#{val}" unless val.nil? end if mysqld_version.nil? debug("Installing MySQL data directory with mysql_install_db #{opts.compact.join(' ')}") mysql_install_db(opts.compact) elsif newer_than('mysql' => '5.7.6', 'percona' => '5.7.6') opts << "--log-error=#{log_error}" opts << initialize.to_s debug("Initializing MySQL data directory >= 5.7.6 with mysqld: #{opts.compact.join(' ')}") mysqld(opts.compact) else debug("Installing MySQL data directory with mysql_install_db #{opts.compact.join(' ')}") mysql_install_db(opts.compact) end exists? end def destroy name = @resource[:name] # rubocop:disable Lint/UselessAssignment raise ArgumentError, _('ERROR: `Resource` can not be removed.') end def exists? datadir = @resource[:datadir] File.directory?("#{datadir}/mysql") && (Dir.entries("#{datadir}/mysql") - ['.', '..']).any? end ## ## MySQL datadir properties ## # Generates method for all properties of the property_hash mk_resource_methods end diff --git a/lib/puppet/provider/mysql_grant/mysql.rb b/lib/puppet/provider/mysql_grant/mysql.rb index 8829033..d26b0fb 100644 --- a/lib/puppet/provider/mysql_grant/mysql.rb +++ b/lib/puppet/provider/mysql_grant/mysql.rb @@ -1,176 +1,178 @@ +# frozen_string_literal: true + require File.expand_path(File.join(File.dirname(__FILE__), '..', 'mysql')) Puppet::Type.type(:mysql_grant).provide(:mysql, parent: Puppet::Provider::Mysql) do desc 'Set grants for users in MySQL.' commands mysql_raw: 'mysql' def self.instances instances = [] users.map do |user| user_string = cmd_user(user) query = "SHOW GRANTS FOR #{user_string};" begin grants = mysql_caller(query, 'regular') rescue Puppet::ExecutionFailure => e # Silently ignore users with no grants. Can happen e.g. if user is # defined with fqdn and server is run with skip-name-resolve. Example: # Default root user created by mysql_install_db on a host with fqdn # of myhost.mydomain.my: root@myhost.mydomain.my, when MySQL is started # with --skip-name-resolve. - next if e.inspect =~ %r{There is no such grant defined for user} + next if %r{There is no such grant defined for user}.match?(e.inspect) raise Puppet::Error, _('#mysql had an error -> %{inspect}') % { inspect: e.inspect } end # Once we have the list of grants generate entries for each. grants.each_line do |grant| # Match the munges we do in the type. munged_grant = grant.delete("'").delete('`').delete('"') # Matching: GRANT (SELECT, UPDATE) PRIVILEGES ON (*.*) TO ('root')@('127.0.0.1') (WITH GRANT OPTION) next unless match = munged_grant.match(%r{^GRANT\s(.+)\sON\s(.+)\sTO\s(.*)@(.*?)(\s.*)?$}) # rubocop:disable Lint/AssignmentInCondition privileges, table, user, host, rest = match.captures table.gsub!('\\\\', '\\') # split on ',' if it is not a non-'('-containing string followed by a # closing parenthesis ')'-char - e.g. only split comma separated elements not in # parentheses stripped_privileges = privileges.strip.split(%r{\s*,\s*(?![^(]*\))}).map do |priv| # split and sort the column_privileges in the parentheses and rejoin if priv.include?('(') type, col = priv.strip.split(%r{\s+|\b}, 2) type.upcase + ' (' + col.slice(1...-1).strip.split(%r{\s*,\s*}).sort.join(', ') + ')' else # Once we split privileges up on the , we need to make sure we # shortern ALL PRIVILEGES to just all. (priv == 'ALL PRIVILEGES') ? 'ALL' : priv.strip end end # Same here, but to remove OPTION leaving just GRANT. - options = if rest =~ %r{WITH\sGRANT\sOPTION} + options = if %r{WITH\sGRANT\sOPTION}.match?(rest) ['GRANT'] else ['NONE'] end # fix double backslash that MySQL prints, so resources match table.gsub!('\\\\', '\\') # We need to return an array of instances so capture these instances << new( name: "#{user}@#{host}/#{table}", ensure: :present, privileges: stripped_privileges.sort, table: table, user: "#{user}@#{host}", options: options, ) end end instances end def self.prefetch(resources) users = instances - resources.keys.each do |name| + resources.each_key do |name| if provider = users.find { |user| user.name == name } # rubocop:disable Lint/AssignmentInCondition resources[name].provider = provider end end end def grant(user, table, privileges, options) user_string = self.class.cmd_user(user) priv_string = self.class.cmd_privs(privileges) table_string = privileges.include?('PROXY') ? self.class.cmd_user(table) : self.class.cmd_table(table) query = "GRANT #{priv_string}" - query << " ON #{table_string}" - query << " TO #{user_string}" - query << self.class.cmd_options(options) unless options.nil? + query += " ON #{table_string}" + query += " TO #{user_string}" + query += self.class.cmd_options(options) unless options.nil? self.class.mysql_caller(query, 'system') end def create grant(@resource[:user], @resource[:table], @resource[:privileges], @resource[:options]) @property_hash[:ensure] = :present @property_hash[:table] = @resource[:table] @property_hash[:user] = @resource[:user] @property_hash[:options] = @resource[:options] if @resource[:options] @property_hash[:privileges] = @resource[:privileges] exists? ? (return true) : (return false) end def revoke(user, table, revoke_privileges = ['ALL']) user_string = self.class.cmd_user(user) table_string = revoke_privileges.include?('PROXY') ? self.class.cmd_user(table) : self.class.cmd_table(table) priv_string = self.class.cmd_privs(revoke_privileges) # revoke grant option needs to be a extra query, because # "REVOKE ALL PRIVILEGES, GRANT OPTION [..]" is only valid mysql syntax # if no ON clause is used. # It hast to be executed before "REVOKE ALL [..]" since a GRANT has to # exist to be executed successfully if revoke_privileges.include?('ALL') && !revoke_privileges.include?('PROXY') query = "REVOKE GRANT OPTION ON #{table_string} FROM #{user_string}" self.class.mysql_caller(query, 'system') end query = "REVOKE #{priv_string} ON #{table_string} FROM #{user_string}" self.class.mysql_caller(query, 'system') end def destroy # if the user was dropped, it'll have been removed from the user hash # as the grants are already removed by the DROP statement if self.class.users.include? @property_hash[:user] if @property_hash[:privileges].include?('PROXY') revoke(@property_hash[:user], @property_hash[:table], @property_hash[:privileges]) else revoke(@property_hash[:user], @property_hash[:table]) end end @property_hash.clear exists? ? (return false) : (return true) end def exists? @property_hash[:ensure] == :present || false end def flush @property_hash.clear self.class.mysql_caller('FLUSH PRIVILEGES', 'regular') end mk_resource_methods def diff_privileges(privileges_old, privileges_new) diff = { revoke: [], grant: [] } if privileges_old.include? 'ALL' diff[:revoke] = privileges_old diff[:grant] = privileges_new elsif privileges_new.include? 'ALL' diff[:grant] = privileges_new else diff[:revoke] = privileges_old - privileges_new diff[:grant] = privileges_new - privileges_old end diff end def privileges=(privileges) diff = diff_privileges(@property_hash[:privileges], privileges) unless diff[:revoke].empty? revoke(@property_hash[:user], @property_hash[:table], diff[:revoke]) end unless diff[:grant].empty? grant(@property_hash[:user], @property_hash[:table], diff[:grant], @property_hash[:options]) end @property_hash[:privileges] = privileges self.privileges end def options=(options) revoke(@property_hash[:user], @property_hash[:table]) grant(@property_hash[:user], @property_hash[:table], @property_hash[:privileges], options) @property_hash[:options] = options self.options end end diff --git a/lib/puppet/provider/mysql_login_path/inifile.rb b/lib/puppet/provider/mysql_login_path/inifile.rb index d556bd0..dde4237 100644 --- a/lib/puppet/provider/mysql_login_path/inifile.rb +++ b/lib/puppet/provider/mysql_login_path/inifile.rb @@ -1,632 +1,643 @@ # encoding: UTF-8 +# frozen_string_literal: true # See: https://github.com/puppetlabs/puppet/blob/main/lib/puppet/util/inifile.rb # This class represents the INI file and can be used to parse, modify, # and write INI files. class Puppet::Provider::MysqlLoginPath::IniFile < Puppet::Provider include Enumerable class Error < StandardError; end # VERSION = '3.0.0' # Public: Open an INI file and load the contents. # # filename - The name of the file as a String # opts - The Hash of options (default: {}) # :comment - String containing the comment character(s) # :parameter - String used to separate parameter and value # :encoding - Encoding String for reading / writing # :default - The String name of the default global section # # Examples # # IniFile.load('file.ini') # #=> IniFile instance # # IniFile.load('does/not/exist.ini') # #=> nil # # Returns an IniFile instance or nil if the file could not be opened. def self.load(filename, opts = {}) return unless File.file? filename new(opts.merge(filename: filename)) end # Get and set the filename attr_accessor :filename # Get and set the encoding attr_accessor :encoding # Public: Create a new INI file from the given set of options. If :content # is provided then it will be used to populate the INI file. If a :filename # is provided then the contents of the file will be parsed and stored in the # INI file. If neither the :content or :filename is provided then an empty # INI file is created. # # opts - The Hash of options (default: {}) # :content - The String/Hash containing the INI contents # :comment - String containing the comment character(s) # :parameter - String used to separate parameter and value # :encoding - Encoding String for reading / writing # :default - The String name of the default global section # :filename - The filename as a String # # Examples # # IniFile.new # #=> an empty IniFile instance # # IniFile.new( :content => "[global]\nfoo=bar" ) # #=> an IniFile instance # # IniFile.new( :filename => 'file.ini', :encoding => 'UTF-8' ) # #=> an IniFile instance # # IniFile.new( :content => "[global]\nfoo=bar", :comment => '#' ) # #=> an IniFile instance # def initialize(opts = {}) + super + @comment = opts.fetch(:comment, ';#') @param = opts.fetch(:parameter, '=') @encoding = opts.fetch(:encoding, nil) @default = opts.fetch(:default, 'global') @filename = opts.fetch(:filename, nil) content = opts.fetch(:content, nil) @ini = Hash.new { |h, k| h[k] = {} } if content.is_a?(Hash) then merge!(content) elsif content then parse(content) elsif @filename then read end end # Public: Write the contents of this IniFile to the file system. If left # unspecified, the currently configured filename and encoding will be used. # Otherwise the filename and encoding can be specified in the options hash. # # opts - The default options Hash # :filename - The filename as a String # :encoding - The encoding as a String # # Returns this IniFile instance. def write(opts = {}) filename = opts.fetch(:filename, @filename) encoding = opts.fetch(:encoding, @encoding) mode = encoding ? "w:#{encoding}" : 'w' File.open(filename, mode) do |f| @ini.each do |section, hash| f.puts "[#{section}]" hash.each { |param, val| f.puts "#{param} #{@param} #{escape_value val}" } f.puts end end self end alias save write # Public: Read the contents of the INI file from the file system and replace # and set the state of this IniFile instance. If left unspecified the # currently configured filename and encoding will be used when reading from # the file system. Otherwise the filename and encoding can be specified in # the options hash. # # opts - The default options Hash # :filename - The filename as a String # :encoding - The encoding as a String # # Returns this IniFile instance if the read was successful; nil is returned # if the file could not be read. def read(opts = {}) filename = opts.fetch(:filename, @filename) encoding = opts.fetch(:encoding, @encoding) return unless File.file? filename mode = encoding ? "r:#{encoding}" : 'r' File.open(filename, mode) { |fd| parse fd } self end alias restore read # Returns this IniFile converted to a String. def to_s s = [] @ini.each do |section, hash| s << "[#{section}]" hash.each { |param, val| s << "#{param} #{@param} #{escape_value val}" } s << '' end s.join("\n") end # Returns this IniFile converted to a Hash. def to_h @ini.dup end # Public: Creates a copy of this inifile with the entries from the # other_inifile merged into the copy. # # other - The other IniFile. # # Returns a new IniFile. def merge(other) dup.merge!(other) end # Public: Merges other_inifile into this inifile, overwriting existing # entries. Useful for having a system inifile with user overridable settings # elsewhere. # # other - The other IniFile. # # Returns this IniFile. def merge!(other) return self if other.nil? my_keys = @ini.keys other_keys = case other when IniFile other.instance_variable_get(:@ini).keys when Hash other.keys else raise Error, "cannot merge contents from '#{other.class.name}'" end (my_keys & other_keys).each do |key| case other[key] when Hash @ini[key].merge!(other[key]) when nil nil else raise Error, "cannot merge section #{key.inspect} - unsupported type: #{other[key].class.name}" end end (other_keys - my_keys).each do |key| @ini[key] = case other[key] when Hash other[key].dup when nil {} else raise Error, "cannot merge section #{key.inspect} - unsupported type: #{other[key].class.name}" end end self end # Public: Yield each INI file section, parameter, and value in turn to the # given block. # # block - The block that will be iterated by the each method. The block will # be passed the current section and the parameter/value pair. # # Examples # # inifile.each do |section, parameter, value| # puts "#{parameter} = #{value} [in section - #{section}]" # end # # Returns this IniFile. def each return unless block_given? @ini.each do |section, hash| hash.each do |param, val| yield section, param, val end end self end # Public: Yield each section in turn to the given block. # # block - The block that will be iterated by the each method. The block will # be passed the current section as a Hash. # # Examples # # inifile.each_section do |section| # puts section.inspect # end # # Returns this IniFile. def each_section return unless block_given? @ini.each_key { |section| yield section } self end # Public: Remove a section identified by name from the IniFile. # # section - The section name as a String. # # Returns the deleted section Hash. def delete_section(section) @ini.delete section.to_s end # Public: Get the section Hash by name. If the section does not exist, then # it will be created. # # section - The section name as a String. # # Examples # # inifile['global'] # #=> global section Hash # # Returns the Hash of parameter/value pairs for this section. def [](section) return nil if section.nil? @ini[section.to_s] end # Public: Set the section to a hash of parameter/value pairs. # # section - The section name as a String. # value - The Hash of parameter/value pairs. # # Examples # # inifile['tenderloin'] = { 'gritty' => 'yes' } # #=> { 'gritty' => 'yes' } # # Returns the value Hash. def []=(section, value) @ini[section.to_s] = value end # Public: Create a Hash containing only those INI file sections whose names # match the given regular expression. # # regex - The Regexp used to match section names. # # Examples # # inifile.match(/^tree_/) # #=> Hash of matching sections # # Return a Hash containing only those sections that match the given regular # expression. def match(regex) @ini.dup.delete_if { |section, _| section !~ regex } end # Public: Check to see if the IniFile contains the section. # # section - The section name as a String. # # Returns true if the section exists in the IniFile. def section?(section) @ini.key? section.to_s end # Returns an Array of section names contained in this IniFile. def sections @ini.keys end # Public: Freeze the state of this IniFile object. Any attempts to change # the object will raise an error. # # Returns this IniFile. def freeze super @ini.each_value { |h| h.freeze } @ini.freeze self end # Public: Mark this IniFile as tainted -- this will traverse each section # marking each as tainted. # # Returns this IniFile. def taint super @ini.each_value { |h| h.taint } @ini.taint self end # Public: Produces a duplicate of this IniFile. The duplicate is independent # of the original -- i.e. the duplicate can be modified without changing the # original. The tainted state of the original is copied to the duplicate. # # Returns a new IniFile. def dup other = super other.instance_variable_set(:@ini, Hash.new { |h, k| h[k] = {} }) @ini.each_pair { |s, h| other[s].merge! h } other.taint if tainted? other end # Public: Produces a duplicate of this IniFile. The duplicate is independent # of the original -- i.e. the duplicate can be modified without changing the # original. The tainted state and the frozen state of the original is copied # to the duplicate. # # Returns a new IniFile. def clone other = dup other.freeze if frozen? other end # Public: Compare this IniFile to some other IniFile. For two INI files to # be equivalent, they must have the same sections with the same parameter / # value pairs in each section. # # other - The other IniFile. # # Returns true if the INI files are equivalent and false if they differ. def eql?(other) return true if equal? other return false unless other.instance_of? self.class @ini == other.instance_variable_get(:@ini) end alias == eql? # Escape special characters. # # value - The String value to escape. # # Returns the escaped value. def escape_value(value) value = value.to_s.dup value.gsub!(%r{\\([0nrt])}, '\\\\\1') value.gsub!(%r{\n}, '\n') value.gsub!(%r{\r}, '\r') value.gsub!(%r{\t}, '\t') value.gsub!(%r{\0}, '\0') value end # Parse the given content and store the information in this IniFile # instance. All data will be cleared out and replaced with the information # read from the content. # # content - A String or a file descriptor (must respond to `each_line`) # # Returns this IniFile. def parse(content) parser = Parser.new(@ini, @param, @comment, @default) parser.parse(content) self end # The IniFile::Parser has the responsibility of reading the contents of an # .ini file and storing that information into a ruby Hash. The object being # parsed must respond to `each_line` - this includes Strings and any IO # object. class Parser attr_writer :section attr_accessor :property attr_accessor :value # Create a new IniFile::Parser that can be used to parse the contents of # an .ini file. # # hash - The Hash where parsed information will be stored # param - String used to separate parameter and value # comment - String containing the comment character(s) # default - The String name of the default global section # def initialize(hash, param, comment, default) @hash = hash @default = default comment = comment.to_s.empty? ? '\\z' : "\\s*(?:[#{comment}].*)?\\z" @section_regexp = %r{\A\s*\[([^\]]+)\]#{comment}} @ignore_regexp = %r{\A#{comment}} @property_regexp = %r{\A(.*?)(? true # "false" --> false # "" --> nil # "42" --> 42 # "3.14" --> 3.14 # "foo" --> "foo" # # Returns the typecast value. def typecast(value) case value when %r{\Atrue\z}i then true when %r{\Afalse\z}i then false when %r{\A\s*\z}i then nil else begin begin Integer(value) - rescue - Float(value) + rescue + Float(value) end rescue unescape_value(value) end end end # Unescape special characters found in the value string. This will convert # escaped null, tab, carriage return, newline, and backslash into their # literal equivalents. # # value - The String value to unescape. # # Returns the unescaped value. def unescape_value(value) value = value.to_s value.gsub!(%r{\\[0nrt\\]}) do |char| case char when '\0' then "\0" when '\n' then "\n" when '\r' then "\r" when '\t' then "\t" when '\\\\' then '\\' end end value end end end # IniFile diff --git a/lib/puppet/provider/mysql_login_path/mysql_login_path.rb b/lib/puppet/provider/mysql_login_path/mysql_login_path.rb index 452aa74..a068fa4 100644 --- a/lib/puppet/provider/mysql_login_path/mysql_login_path.rb +++ b/lib/puppet/provider/mysql_login_path/mysql_login_path.rb @@ -1,166 +1,166 @@ # frozen_string_literal: true require File.expand_path(File.join(File.dirname(__FILE__), 'inifile')) require File.expand_path(File.join(File.dirname(__FILE__), 'sensitive')) require 'puppet/resource_api/simple_provider' require 'puppet/util/execution' require 'puppet/util/suidmanager' require 'open3' require 'pty' require 'expect' require 'fileutils' require 'English' # Implementation for the mysql_login_path type using the Resource API. class Puppet::Provider::MysqlLoginPath::MysqlLoginPath < Puppet::ResourceApi::SimpleProvider def get_homedir(_context, uid) result = Puppet::Util::Execution.execute(['/usr/bin/getent', 'passwd', uid], failonfail: true) result.split(':')[5] end def mysql_config_editor_set_cmd(context, uid, password = nil, *args) args.unshift('/usr/bin/mysql_config_editor') homedir = get_homedir(context, uid) login_file_path = "#{homedir}/.mylogin.cnf" if args.is_a?(Array) command = args.flatten.map(&:to_s) command_str = command.join(' ') elsif args.is_a?(String) command_str = command end begin Puppet::Util::SUIDManager.asuser(uid) do FileUtils.touch login_file_path FileUtils.chmod 0o600, login_file_path end PTY.spawn({ 'HOME' => homedir }, command_str) do |input, output, _pid| if password input.expect(%r{Enter password:}) output.puts password end end rescue => e raise Puppet::ExecutionFailure, _( "Execution of '%{str}' returned %{exit_status}: %{output}", ) % { str: command_str, exit_status: $CHILD_STATUS.exitstatus, output: e.message, } end end def mysql_config_editor_cmd(context, uid, *args) args.unshift('/usr/bin/mysql_config_editor') homedir = get_homedir(context, uid) Puppet::Util::Execution.execute( args, failonfail: true, uid: uid, custom_environment: { 'HOME' => homedir }, ) end def my_print_defaults_cmd(context, uid, *args) args.unshift('/usr/bin/my_print_defaults') homedir = get_homedir(context, uid) Puppet::Util::Execution.execute( args, failonfail: true, uid: uid, custom_environment: { 'HOME' => homedir }, ) end def get_password(context, uid, name) result = '' output = my_print_defaults_cmd(context, uid, '-s', name) output.split("\n").each do |line| - if line =~ %r{\-\-password} + if %r{\-\-password}.match?(line) result = line.sub(%r{\-\-password=}, '') end end result end def save_login_path(context, name, should) uid = name.fetch(:owner) args = ['set', '--skip-warn'] args.push('-G', should[:name].to_s) if should[:name] args.push('-h', should[:host].to_s) if should[:host] args.push('-u', should[:user].to_s) if should[:user] args.push('-S', should[:socket].to_s) if should[:socket] args.push('-P', should[:port].to_s) if should[:port] args.push('-p') if should[:password] && extract_pw(should[:password]) password = (should[:password] && extract_pw(should[:password])) ? extract_pw(should[:password]) : nil mysql_config_editor_set_cmd(context, uid, password, args) end def delete_login_path(context, name) login_path = name.fetch(:name) uid = name.fetch(:owner) mysql_config_editor_cmd(context, uid, 'remove', '-G', login_path) end def gen_pw(pw) Puppet::Provider::MysqlLoginPath::Sensitive.new(pw) end def extract_pw(sensitive) sensitive.unwrap end def list_login_paths(context, uid) result = [] output = mysql_config_editor_cmd(context, uid, 'print', '--all') ini = Puppet::Provider::MysqlLoginPath::IniFile.new(content: output) ini.each_section do |section| result.push(ensure: 'present', name: section, owner: uid.to_s, title: section + '-' + uid.to_s, host: ini[section]['host'].nil? ? nil : ini[section]['host'], user: ini[section]['user'].nil? ? nil : ini[section]['user'], password: ini[section]['password'].nil? ? nil : gen_pw(get_password(context, uid, section)), socket: ini[section]['socket'].nil? ? nil : ini[section]['socket'], port: ini[section]['port'].nil? ? nil : ini[section]['port']) end result end def get(context, name) result = [] owner = name.empty? ? ['root'] : name.map { |item| item[:owner] }.compact.uniq owner.each do |uid| login_paths = list_login_paths(context, uid) result += login_paths end result end def create(context, name, should) save_login_path(context, name, should) end def update(context, name, should) delete_login_path(context, name) save_login_path(context, name, should) end def delete(context, name) delete_login_path(context, name) end def canonicalize(_context, resources) resources.each do |r| if r.key?(:password) && r[:password].is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive) r[:password] = gen_pw(extract_pw(r[:password])) end end end end diff --git a/lib/puppet/provider/mysql_login_path/sensitive.rb b/lib/puppet/provider/mysql_login_path/sensitive.rb index 4876504..1c026ac 100644 --- a/lib/puppet/provider/mysql_login_path/sensitive.rb +++ b/lib/puppet/provider/mysql_login_path/sensitive.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + # A Puppet Language type that makes the Sensitive Type comparable # class Puppet::Provider::MysqlLoginPath::Sensitive < Puppet::Pops::Types::PSensitiveType::Sensitive def ==(other) return true if other.is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive) && unwrap == other.unwrap end end diff --git a/lib/puppet/provider/mysql_plugin/mysql.rb b/lib/puppet/provider/mysql_plugin/mysql.rb index 7a7ed9f..0a331f4 100644 --- a/lib/puppet/provider/mysql_plugin/mysql.rb +++ b/lib/puppet/provider/mysql_plugin/mysql.rb @@ -1,51 +1,53 @@ +# frozen_string_literal: true + require File.expand_path(File.join(File.dirname(__FILE__), '..', 'mysql')) Puppet::Type.type(:mysql_plugin).provide(:mysql, parent: Puppet::Provider::Mysql) do desc 'Manages MySQL plugins.' commands mysql_raw: 'mysql' def self.instances mysql_caller('show plugins', 'regular').split("\n").map do |line| name, _status, _type, library, _license = line.split(%r{\t}) new(name: name, ensure: :present, soname: library) end end # We iterate over each mysql_plugin entry in the catalog and compare it against # the contents of the property_hash generated by self.instances def self.prefetch(resources) plugins = instances - resources.keys.each do |plugin| + resources.each_key do |plugin| if provider = plugins.find { |pl| pl.name == plugin } # rubocop:disable Lint/AssignmentInCondition resources[plugin].provider = provider end end end def create # Use plugin_name.so as soname if it's not specified. This won't work on windows as # there it should be plugin_name.dll @resource[:soname].nil? ? (soname = @resource[:name] + '.so') : (soname = @resource[:soname]) self.class.mysql_caller("install plugin #{@resource[:name]} soname '#{soname}'", 'regular') @property_hash[:ensure] = :present @property_hash[:soname] = @resource[:soname] exists? ? (return true) : (return false) end def destroy self.class.mysql_caller("uninstall plugin #{@resource[:name]}", 'regular') @property_hash.clear exists? ? (return false) : (return true) end def exists? @property_hash[:ensure] == :present || false end mk_resource_methods end diff --git a/lib/puppet/provider/mysql_user/mysql.rb b/lib/puppet/provider/mysql_user/mysql.rb index 2699495..30d0dd2 100644 --- a/lib/puppet/provider/mysql_user/mysql.rb +++ b/lib/puppet/provider/mysql_user/mysql.rb @@ -1,255 +1,257 @@ +# frozen_string_literal: true + require File.expand_path(File.join(File.dirname(__FILE__), '..', 'mysql')) Puppet::Type.type(:mysql_user).provide(:mysql, parent: Puppet::Provider::Mysql) do desc 'manage users for a mysql database.' commands mysql_raw: 'mysql' # Build a property_hash containing all the discovered information about MySQL # users. def self.instances users = mysql_caller("SELECT CONCAT(User, '@',Host) AS User FROM mysql.user", 'regular').split("\n") # To reduce the number of calls to MySQL we collect all the properties in # one big swoop. users.map do |name| if mysqld_version.nil? ## Default ... - # rubocop:disable Metrics/LineLength + # rubocop:disable Layout/LineLength query = "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{name}'" elsif newer_than('mysql' => '5.7.6', 'percona' => '5.7.6') query = "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, AUTHENTICATION_STRING, PLUGIN FROM mysql.user WHERE CONCAT(user, '@', host) = '#{name}'" elsif newer_than('mariadb' => '10.1.21') query = "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD, PLUGIN, AUTHENTICATION_STRING FROM mysql.user WHERE CONCAT(user, '@', host) = '#{name}'" else query = "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{name}'" end @max_user_connections, @max_connections_per_hour, @max_queries_per_hour, @max_updates_per_hour, ssl_type, ssl_cipher, x509_issuer, x509_subject, @password, @plugin, @authentication_string = mysql_caller(query, 'regular').chomp.split(%r{\t}) @tls_options = parse_tls_options(ssl_type, ssl_cipher, x509_issuer, x509_subject) if newer_than('mariadb' => '10.1.21') && @plugin == 'ed25519' # Some auth plugins (e.g. ed25519) use authentication_string # to store password hash or auth information @password = @authentication_string elsif (newer_than('mariadb' => '10.2.16') && older_than('mariadb' => '10.2.19')) || (newer_than('mariadb' => '10.3.8') && older_than('mariadb' => '10.3.11')) # Old mariadb 10.2 or 10.3 store password hash in authentication_string # https://jira.mariadb.org/browse/MDEV-16238 https://jira.mariadb.org/browse/MDEV-16774 @password = @authentication_string end - # rubocop:enable Metrics/LineLength + # rubocop:enable Layout/LineLength new(name: name, ensure: :present, password_hash: @password, plugin: @plugin, max_user_connections: @max_user_connections, max_connections_per_hour: @max_connections_per_hour, max_queries_per_hour: @max_queries_per_hour, max_updates_per_hour: @max_updates_per_hour, tls_options: @tls_options) end end # We iterate over each mysql_user entry in the catalog and compare it against # the contents of the property_hash generated by self.instances def self.prefetch(resources) users = instances # rubocop:disable Lint/AssignmentInCondition - resources.keys.each do |name| + resources.each_key do |name| if provider = users.find { |user| user.name == name } resources[name].provider = provider end end # rubocop:enable Lint/AssignmentInCondition end def create # (MODULES-3539) Allow @ in username merged_name = @resource[:name].reverse.sub('@', "'@'").reverse password_hash = @resource.value(:password_hash) plugin = @resource.value(:plugin) max_user_connections = @resource.value(:max_user_connections) || 0 max_connections_per_hour = @resource.value(:max_connections_per_hour) || 0 max_queries_per_hour = @resource.value(:max_queries_per_hour) || 0 max_updates_per_hour = @resource.value(:max_updates_per_hour) || 0 tls_options = @resource.value(:tls_options) || ['NONE'] # Use CREATE USER to be compatible with NO_AUTO_CREATE_USER sql_mode # This is also required if you want to specify a authentication plugin if !plugin.nil? if !password_hash.nil? self.class.mysql_caller("CREATE USER '#{merged_name}' IDENTIFIED WITH '#{plugin}' AS '#{password_hash}'", 'system') else self.class.mysql_caller("CREATE USER '#{merged_name}' IDENTIFIED WITH '#{plugin}'", 'system') end @property_hash[:ensure] = :present @property_hash[:plugin] = plugin elsif newer_than('mysql' => '5.7.6', 'percona' => '5.7.6', 'mariadb' => '10.1.3') self.class.mysql_caller("CREATE USER IF NOT EXISTS '#{merged_name}' IDENTIFIED WITH 'mysql_native_password' AS '#{password_hash}'", 'system') @property_hash[:ensure] = :present @property_hash[:password_hash] = password_hash else self.class.mysql_caller("CREATE USER '#{merged_name}' IDENTIFIED BY PASSWORD '#{password_hash}'", 'system') @property_hash[:ensure] = :present @property_hash[:password_hash] = password_hash end - # rubocop:disable Metrics/LineLength + # rubocop:disable Layout/LineLength if newer_than('mysql' => '5.7.6', 'percona' => '5.7.6') self.class.mysql_caller("ALTER USER IF EXISTS '#{merged_name}' WITH MAX_USER_CONNECTIONS #{max_user_connections} MAX_CONNECTIONS_PER_HOUR #{max_connections_per_hour} MAX_QUERIES_PER_HOUR #{max_queries_per_hour} MAX_UPDATES_PER_HOUR #{max_updates_per_hour}", 'system') else self.class.mysql_caller("GRANT USAGE ON *.* TO '#{merged_name}' WITH MAX_USER_CONNECTIONS #{max_user_connections} MAX_CONNECTIONS_PER_HOUR #{max_connections_per_hour} MAX_QUERIES_PER_HOUR #{max_queries_per_hour} MAX_UPDATES_PER_HOUR #{max_updates_per_hour}", 'system') end - # rubocop:enable Metrics/LineLength + # rubocop:enable Layout/LineLength @property_hash[:max_user_connections] = max_user_connections @property_hash[:max_connections_per_hour] = max_connections_per_hour @property_hash[:max_queries_per_hour] = max_queries_per_hour @property_hash[:max_updates_per_hour] = max_updates_per_hour merged_tls_options = tls_options.join(' AND ') if newer_than('mysql' => '5.7.6', 'percona' => '5.7.6', 'mariadb' => '10.2.0') self.class.mysql_caller("ALTER USER '#{merged_name}' REQUIRE #{merged_tls_options}", 'system') else self.class.mysql_caller("GRANT USAGE ON *.* TO '#{merged_name}' REQUIRE #{merged_tls_options}", 'system') end @property_hash[:tls_options] = tls_options exists? ? (return true) : (return false) end def destroy # (MODULES-3539) Allow @ in username merged_name = @resource[:name].reverse.sub('@', "'@'").reverse if_exists = if newer_than('mysql' => '5.7', 'percona' => '5.7', 'mariadb' => '10.1.3') 'IF EXISTS ' else '' end self.class.mysql_caller("DROP USER #{if_exists}'#{merged_name}'", 'system') @property_hash.clear exists? ? (return false) : (return true) end def exists? @property_hash[:ensure] == :present || false end ## ## MySQL user properties ## # Generates method for all properties of the property_hash mk_resource_methods def password_hash=(string) merged_name = self.class.cmd_user(@resource[:name]) plugin = @resource.value(:plugin) # We have a fact for the mysql version ... if mysqld_version.nil? # default ... if mysqld_version does not work self.class.mysql_caller("SET PASSWORD FOR #{merged_name} = '#{string}'", 'system') elsif newer_than('mariadb' => '10.1.21') && plugin == 'ed25519' raise ArgumentError, _('ed25519 hash should be 43 bytes long.') unless string.length == 43 # ALTER USER statement is only available upstream starting 10.2 # https://mariadb.com/kb/en/mariadb-1020-release-notes/ if newer_than('mariadb' => '10.2.0') sql = "ALTER USER #{merged_name} IDENTIFIED WITH ed25519 AS '#{string}'" else concat_name = @resource[:name] sql = "UPDATE mysql.user SET password = '', plugin = 'ed25519'" - sql << ", authentication_string = '#{string}'" - sql << " where CONCAT(user, '@', host) = '#{concat_name}'; FLUSH PRIVILEGES" + sql += ", authentication_string = '#{string}'" + sql += " where CONCAT(user, '@', host) = '#{concat_name}'; FLUSH PRIVILEGES" end self.class.mysql_caller(sql, 'system') elsif newer_than('mysql' => '5.7.6', 'percona' => '5.7.6', 'mariadb' => '10.2.0') - raise ArgumentError, _('Only mysql_native_password (*ABCD...XXX) hashes are supported.') unless string =~ %r{^\*|^$} + raise ArgumentError, _('Only mysql_native_password (*ABCD...XXX) hashes are supported.') unless %r{^\*|^$}.match?(string) self.class.mysql_caller("ALTER USER #{merged_name} IDENTIFIED WITH mysql_native_password AS '#{string}'", 'system') else self.class.mysql_caller("SET PASSWORD FOR #{merged_name} = '#{string}'", 'system') end (password_hash == string) ? (return true) : (return false) end def max_user_connections=(int) merged_name = self.class.cmd_user(@resource[:name]) self.class.mysql_caller("GRANT USAGE ON *.* TO #{merged_name} WITH MAX_USER_CONNECTIONS #{int}", 'system').chomp (max_user_connections == int) ? (return true) : (return false) end def max_connections_per_hour=(int) merged_name = self.class.cmd_user(@resource[:name]) self.class.mysql_caller("GRANT USAGE ON *.* TO #{merged_name} WITH MAX_CONNECTIONS_PER_HOUR #{int}", 'system').chomp (max_connections_per_hour == int) ? (return true) : (return false) end def max_queries_per_hour=(int) merged_name = self.class.cmd_user(@resource[:name]) self.class.mysql_caller("GRANT USAGE ON *.* TO #{merged_name} WITH MAX_QUERIES_PER_HOUR #{int}", 'system').chomp (max_queries_per_hour == int) ? (return true) : (return false) end def max_updates_per_hour=(int) merged_name = self.class.cmd_user(@resource[:name]) self.class.mysql_caller("GRANT USAGE ON *.* TO #{merged_name} WITH MAX_UPDATES_PER_HOUR #{int}", 'system').chomp (max_updates_per_hour == int) ? (return true) : (return false) end def plugin=(string) merged_name = self.class.cmd_user(@resource[:name]) if newer_than('mariadb' => '10.1.21') && string == 'ed25519' if newer_than('mariadb' => '10.2.0') sql = "ALTER USER #{merged_name} IDENTIFIED WITH '#{string}' AS '#{@resource[:password_hash]}'" else concat_name = @resource[:name] sql = "UPDATE mysql.user SET password = '', plugin = '#{string}'" - sql << ", authentication_string = '#{@resource[:password_hash]}'" - sql << " where CONCAT(user, '@', host) = '#{concat_name}'; FLUSH PRIVILEGES" + sql += ", authentication_string = '#{@resource[:password_hash]}'" + sql += " where CONCAT(user, '@', host) = '#{concat_name}'; FLUSH PRIVILEGES" end elsif newer_than('mysql' => '5.7.6', 'percona' => '5.7.6', 'mariadb' => '10.2.0') sql = "ALTER USER #{merged_name} IDENTIFIED WITH '#{string}'" - sql << " AS '#{@resource[:password_hash]}'" if string == 'mysql_native_password' + sql += " AS '#{@resource[:password_hash]}'" if string == 'mysql_native_password' else # See https://bugs.mysql.com/bug.php?id=67449 sql = "UPDATE mysql.user SET plugin = '#{string}'" - sql << ((string == 'mysql_native_password') ? ", password = '#{@resource[:password_hash]}'" : ", password = ''") - sql << " WHERE CONCAT(user, '@', host) = '#{@resource[:name]}'" + sql += ((string == 'mysql_native_password') ? ", password = '#{@resource[:password_hash]}'" : ", password = ''") + sql += " WHERE CONCAT(user, '@', host) = '#{@resource[:name]}'" end self.class.mysql_caller(sql, 'system') (plugin == string) ? (return true) : (return false) end def tls_options=(array) merged_name = self.class.cmd_user(@resource[:name]) merged_tls_options = array.join(' AND ') if newer_than('mysql' => '5.7.6', 'percona' => '5.7.6', 'mariadb' => '10.2.0') self.class.mysql_caller("ALTER USER #{merged_name} REQUIRE #{merged_tls_options}", 'system') else self.class.mysql_caller("GRANT USAGE ON *.* TO #{merged_name} REQUIRE #{merged_tls_options}", 'system') end (tls_options == array) ? (return true) : (return false) end def self.parse_tls_options(ssl_type, ssl_cipher, x509_issuer, x509_subject) if ssl_type == 'ANY' ['SSL'] elsif ssl_type == 'X509' ['X509'] elsif ssl_type == 'SPECIFIED' options = [] options << "CIPHER '#{ssl_cipher}'" if !ssl_cipher.nil? && !ssl_cipher.empty? options << "ISSUER '#{x509_issuer}'" if !x509_issuer.nil? && !x509_issuer.empty? options << "SUBJECT '#{x509_subject}'" if !x509_subject.nil? && !x509_subject.empty? options else ['NONE'] end end end diff --git a/lib/puppet/type/mysql_database.rb b/lib/puppet/type/mysql_database.rb index 817f042..916caf6 100644 --- a/lib/puppet/type/mysql_database.rb +++ b/lib/puppet/type/mysql_database.rb @@ -1,29 +1,31 @@ +# frozen_string_literal: true + Puppet::Type.newtype(:mysql_database) do @doc = <<-PUPPET @summary Manage a MySQL database. @api private PUPPET ensurable autorequire(:file) { '/root/.my.cnf' } autorequire(:class) { 'mysql::server' } newparam(:name, namevar: true) do desc 'The name of the MySQL database to manage.' end newproperty(:charset) do desc 'The CHARACTER SET setting for the database' defaultto :utf8 newvalue(%r{^\S+$}) end newproperty(:collate) do desc 'The COLLATE setting for the database' defaultto :utf8_general_ci newvalue(%r{^\S+$}) end end diff --git a/lib/puppet/type/mysql_datadir.rb b/lib/puppet/type/mysql_datadir.rb index 7945f4e..5f8e009 100644 --- a/lib/puppet/type/mysql_datadir.rb +++ b/lib/puppet/type/mysql_datadir.rb @@ -1,39 +1,41 @@ +# frozen_string_literal: true + Puppet::Type.newtype(:mysql_datadir) do @doc = <<-PUPPET @summary Manage MySQL datadirs with mysql_install_db OR mysqld (5.7.6 and above). @api private PUPPET ensurable autorequire(:package) { 'mysql-server' } newparam(:datadir, namevar: true) do desc 'The datadir name' end newparam(:basedir) do desc 'The basedir name, default /usr.' newvalues(%r{^/}) end newparam(:user) do desc 'The user for the directory default mysql (name, not uid).' end newparam(:defaults_extra_file) do desc 'MySQL defaults-extra-file with absolute path (*.cnf).' newvalues(%r{^/.*\.cnf$}) end newparam(:insecure, boolean: true) do desc 'Insecure initialization (needed for 5.7.6++).' end newparam(:log_error) do desc 'The path to the mysqld error log file (used with the --log-error option)' newvalues(%r{^/}) end end diff --git a/lib/puppet/type/mysql_grant.rb b/lib/puppet/type/mysql_grant.rb index bcc8441..abaa332 100644 --- a/lib/puppet/type/mysql_grant.rb +++ b/lib/puppet/type/mysql_grant.rb @@ -1,119 +1,121 @@ +# frozen_string_literal: true + Puppet::Type.newtype(:mysql_grant) do @doc = <<-PUPPET @summary Manage a MySQL user's rights. PUPPET ensurable autorequire(:file) { '/root/.my.cnf' } autorequire(:mysql_user) { self[:user] } def initialize(*args) super # Forcibly munge any privilege with 'ALL' in the array to exist of just # 'ALL'. This can't be done in the munge in the property as that iterates # over the array and there's no way to replace the entire array before it's # returned to the provider. - if self[:ensure] == :present && Array(self[:privileges]).count > 1 && self[:privileges].to_s.include?('ALL') + if self[:ensure] == :present && Array(self[:privileges]).size > 1 && self[:privileges].to_s.include?('ALL') self[:privileges] = 'ALL' end # Sort the privileges array in order to ensure the comparision in the provider # self.instances method match. Otherwise this causes it to keep resetting the # privileges. # rubocop:disable Style/MultilineBlockChain self[:privileges] = Array(self[:privileges]).map { |priv| # split and sort the column_privileges in the parentheses and rejoin if priv.include?('(') type, col = priv.strip.split(%r{\s+|\b}, 2) type.upcase + ' (' + col.slice(1...-1).strip.split(%r{\s*,\s*}).sort.join(', ') + ')' else priv.strip.upcase end - }.uniq.reject { |k| k == 'GRANT' || k == 'GRANT OPTION' }.sort! + }.uniq.reject { |k| ['GRANT', 'GRANT OPTION'].include?(k) }.sort! end # rubocop:enable Style/MultilineBlockChain validate do raise(_('mysql_grant: `privileges` `parameter` is required.')) if self[:ensure] == :present && self[:privileges].nil? - raise(_('mysql_grant: `privileges` `parameter`: PROXY can only be specified by itself.')) if Array(self[:privileges]).count > 1 && Array(self[:privileges]).include?('PROXY') + raise(_('mysql_grant: `privileges` `parameter`: PROXY can only be specified by itself.')) if Array(self[:privileges]).size > 1 && Array(self[:privileges]).include?('PROXY') raise(_('mysql_grant: `table` `parameter` is required.')) if self[:ensure] == :present && self[:table].nil? raise(_('mysql_grant: `user` `parameter` is required.')) if self[:ensure] == :present && self[:user].nil? if self[:user] && self[:table] raise(_('mysql_grant: `name` `parameter` must match user@host/table format.')) if self[:name] != "#{self[:user]}/#{self[:table]}" end end newparam(:name, namevar: true) do desc 'Name to describe the grant.' munge do |value| value.delete("'") end end newproperty(:privileges, array_matching: :all) do desc 'Privileges for user' validate do |value| mysql_version = Facter.value(:mysql_version) if value =~ %r{proxy}i && Puppet::Util::Package.versioncmp(mysql_version, '5.5.0') < 0 raise(ArgumentError, _('mysql_grant: PROXY user not supported on mysql versions < 5.5.0. Current version %{version}.') % { version: mysql_version }) end end end newproperty(:table) do desc 'Table to apply privileges to.' validate do |value| if Array(@resource[:privileges]).include?('PROXY') && !%r{^[0-9a-zA-Z$_]*@[\w%\.:\-\/]*$}.match(value) raise(ArgumentError, _('mysql_grant: `table` `property` for PROXY should be specified as proxy_user@proxy_host.')) end end munge do |value| value.delete('`') end newvalues(%r{.*\..*}, %r{^[0-9a-zA-Z$_]*@[\w%\.:\-/]*$}) end newproperty(:user) do desc 'User to operate on.' validate do |value| # http://dev.mysql.com/doc/refman/5.5/en/identifiers.html # If at least one special char is used, string must be quoted # http://stackoverflow.com/questions/8055727/negating-a-backreference-in-regular-expressions/8057827#8057827 # rubocop:disable Lint/AssignmentInCondition # rubocop:disable Lint/UselessAssignment if matches = %r{^(['`"])((?!\1).)*\1@([\w%\.:\-/]+)$}.match(value) user_part = matches[2] host_part = matches[3] elsif matches = %r{^([0-9a-zA-Z$_]*)@([\w%\.:\-/]+)$}.match(value) user_part = matches[1] host_part = matches[2] elsif matches = %r{^((?!['`"]).*[^0-9a-zA-Z$_].*)@(.+)$}.match(value) user_part = matches[1] host_part = matches[2] else raise(ArgumentError, _('mysql_grant: Invalid database user %{user}.') % { user: value }) end # rubocop:enable Lint/AssignmentInCondition # rubocop:enable Lint/UselessAssignment mysql_version = Facter.value(:mysql_version) unless mysql_version.nil? raise(ArgumentError, _('mysql_grant: MySQL usernames are limited to a maximum of 16 characters.')) if Puppet::Util::Package.versioncmp(mysql_version, '5.7.8') < 0 && user_part.size > 16 raise(ArgumentError, _('mysql_grant: MySQL usernames are limited to a maximum of 32 characters.')) if Puppet::Util::Package.versioncmp(mysql_version, '10.0.0') < 0 && user_part.size > 32 raise(ArgumentError, _('mysql_grant: MySQL usernames are limited to a maximum of 80 characters.')) if Puppet::Util::Package.versioncmp(mysql_version, '10.0.0') > 0 && user_part.size > 80 end end munge do |value| matches = %r{^((['`"]?).*\2)@(.+)$}.match(value) "#{matches[1]}@#{matches[3].downcase}" end end newproperty(:options, array_matching: :all) do desc 'Options to grant.' end end diff --git a/lib/puppet/type/mysql_plugin.rb b/lib/puppet/type/mysql_plugin.rb index 433146e..b52539b 100644 --- a/lib/puppet/type/mysql_plugin.rb +++ b/lib/puppet/type/mysql_plugin.rb @@ -1,25 +1,27 @@ +# frozen_string_literal: true + Puppet::Type.newtype(:mysql_plugin) do @doc = <<-PUPPET @summary Manage MySQL plugins. @example mysql_plugin { 'some_plugin': soname => 'some_pluginlib.so', } PUPPET ensurable autorequire(:file) { '/root/.my.cnf' } newparam(:name, namevar: true) do desc 'The name of the MySQL plugin to manage.' end newproperty(:soname) do desc 'The name of the library' newvalue(%r{^\w+\.\w+$}) end end diff --git a/lib/puppet/type/mysql_user.rb b/lib/puppet/type/mysql_user.rb index e008375..dafb9b2 100644 --- a/lib/puppet/type/mysql_user.rb +++ b/lib/puppet/type/mysql_user.rb @@ -1,118 +1,120 @@ +# frozen_string_literal: true + # This has to be a separate type to enable collecting Puppet::Type.newtype(:mysql_user) do @doc = <<-PUPPET @summary Manage a MySQL user. This includes management of users password as well as privileges. PUPPET ensurable autorequire(:file) { '/root/.my.cnf' } autorequire(:class) { 'mysql::server' } newparam(:name, namevar: true) do desc "The name of the user. This uses the 'username@hostname' or username@hostname." validate do |value| # http://dev.mysql.com/doc/refman/5.5/en/identifiers.html # If at least one special char is used, string must be quoted # http://stackoverflow.com/questions/8055727/negating-a-backreference-in-regular-expressions/8057827#8057827 mysql_version = Facter.value(:mysql_version) # rubocop:disable Lint/AssignmentInCondition # rubocop:disable Lint/UselessAssignment if matches = %r{^(['`"])((?:(?!\1).)*)\1@([\w%\.:\-/]+)$}.match(value) user_part = matches[2] host_part = matches[3] elsif matches = %r{^([0-9a-zA-Z$_]*)@([\w%\.:\-/]+)$}.match(value) user_part = matches[1] host_part = matches[2] elsif matches = %r{^((?!['`"]).*[^0-9a-zA-Z$_].*)@(.+)$}.match(value) user_part = matches[1] host_part = matches[2] else raise ArgumentError, _('Invalid database user %{user}.') % { user: value } end # rubocop:enable Lint/AssignmentInCondition # rubocop:enable Lint/UselessAssignment unless mysql_version.nil? raise(ArgumentError, _('MySQL usernames are limited to a maximum of 16 characters.')) if Puppet::Util::Package.versioncmp(mysql_version, '5.7.8') < 0 && user_part.size > 16 raise(ArgumentError, _('MySQL usernames are limited to a maximum of 32 characters.')) if Puppet::Util::Package.versioncmp(mysql_version, '10.0.0') < 0 && user_part.size > 32 raise(ArgumentError, _('MySQL usernames are limited to a maximum of 80 characters.')) if Puppet::Util::Package.versioncmp(mysql_version, '10.0.0') > 0 && user_part.size > 80 end end munge do |value| matches = %r{^((['`"]?).*\2)@(.+)$}.match(value) "#{matches[1]}@#{matches[3].downcase}" end end newproperty(:password_hash) do desc 'The password hash of the user. Use mysql::password() for creating such a hash.' newvalue(%r{\w*}) def change_to_s(currentvalue, _newvalue) (currentvalue == :absent) ? 'created password' : 'changed password' end - # rubocop:disable Style/PredicateName + # rubocop:disable Naming/PredicateName def is_to_s(_currentvalue) '[old password hash redacted]' end - # rubocop:enable Style/PredicateName + # rubocop:enable Naming/PredicateName def should_to_s(_newvalue) '[new password hash redacted]' end end newproperty(:plugin) do desc 'The authentication plugin of the user.' newvalue(%r{\w+}) end newproperty(:max_user_connections) do desc 'Max concurrent connections for the user. 0 means no (or global) limit.' newvalue(%r{\d+}) end newproperty(:max_connections_per_hour) do desc 'Max connections per hour for the user. 0 means no (or global) limit.' newvalue(%r{\d+}) end newproperty(:max_queries_per_hour) do desc 'Max queries per hour for the user. 0 means no (or global) limit.' newvalue(%r{\d+}) end newproperty(:max_updates_per_hour) do desc 'Max updates per hour for the user. 0 means no (or global) limit.' newvalue(%r{\d+}) end newproperty(:tls_options, array_matching: :all) do desc 'Options to that set the TLS-related REQUIRE attributes for the user.' validate do |value| value = [value] unless value.is_a?(Array) if value.include?('NONE') || value.include?('SSL') || value.include?('X509') if value.length > 1 raise(ArgumentError, _('`tls_options` `property`: The values NONE, SSL and X509 cannot be used with other options, you may only pick one of them.')) end else value.each do |opt| o = opt.match(%r{^(CIPHER|ISSUER|SUBJECT)}i) raise(ArgumentError, _('Invalid tls option %{option}.') % { option: o }) unless o end end end def insync?(is) # The current value may be nil and we don't # want to call sort on it so make sure we have arrays if is.is_a?(Array) && @should.is_a?(Array) is.sort == @should.sort else is == @should end end end end diff --git a/metadata.json b/metadata.json index 50bc4e9..cb6754d 100644 --- a/metadata.json +++ b/metadata.json @@ -1,94 +1,94 @@ { "name": "puppetlabs-mysql", "version": "10.9.1", "author": "puppetlabs", "summary": "Installs, configures, and manages the MySQL service.", "license": "Apache-2.0", "source": "git://github.com/puppetlabs/puppetlabs-mysql", "project_page": "http://github.com/puppetlabs/puppetlabs-mysql", "issues_url": "https://tickets.puppetlabs.com/browse/MODULES", "dependencies": [ { "name": "puppetlabs/stdlib", "version_requirement": ">= 3.2.0 < 7.0.0" }, { "name": "puppetlabs/translate", "version_requirement": ">= 1.0.0 < 3.0.0" }, { "name": "puppetlabs/resource_api", "version_requirement": ">= 1.0.0 < 2.0.0" } ], "operatingsystem_support": [ { "operatingsystem": "RedHat", "operatingsystemrelease": [ "5", "6", "7", "8" ] }, { "operatingsystem": "CentOS", "operatingsystemrelease": [ "5", "6", "7", "8" ] }, { "operatingsystem": "OracleLinux", "operatingsystemrelease": [ "5", "6", "7" ] }, { "operatingsystem": "Scientific", "operatingsystemrelease": [ "6", "7" ] }, { "operatingsystem": "SLES", "operatingsystemrelease": [ "11", "12", "15" ] }, { "operatingsystem": "Debian", "operatingsystemrelease": [ "8", "9", "10" ] }, { "operatingsystem": "Ubuntu", "operatingsystemrelease": [ "14.04", "16.04", "18.04", "20.04" ] } ], "requirements": [ { "name": "puppet", "version_requirement": ">= 5.5.10 < 8.0.0" } ], "description": "MySQL module", "template-url": "https://github.com/puppetlabs/pdk-templates#main", - "template-ref": "heads/main-0-g874030e", + "template-ref": "heads/main-0-g4543421", "pdk-version": "1.18.1" } diff --git a/spec/acceptance/mysql_backup_spec.rb b/spec/acceptance/mysql_backup_spec.rb index 3c4e153..bdc73d9 100644 --- a/spec/acceptance/mysql_backup_spec.rb +++ b/spec/acceptance/mysql_backup_spec.rb @@ -1,366 +1,364 @@ +# frozen_string_literal: true + require 'spec_helper_acceptance' describe 'mysql::server::backup class' do context 'should work with no errors' do pp = <<-MANIFEST class { 'mysql::server': root_password => 'password' } mysql::db { [ 'backup1', 'backup2' ]: user => 'backup', password => 'secret', } class { 'mysql::server::backup': backupuser => 'myuser', backuppassword => 'mypassword', backupdir => '/tmp/backups', backupcompress => true, postscript => [ 'rm -rf /var/tmp/mysqlbackups', 'rm -f /var/tmp/mysqlbackups.done', 'cp -r /tmp/backups /var/tmp/mysqlbackups', 'touch /var/tmp/mysqlbackups.done', ], execpath => '/usr/bin:/usr/sbin:/bin:/sbin:/opt/zimbra/bin', } MANIFEST it 'when configuring mysql backups' do idempotent_apply(pp) end end describe 'mysqlbackup.sh', if: Gem::Version.new(mysql_version) < Gem::Version.new('5.7.0') do before(:all) do pre_run end it 'runs mysqlbackup.sh with no errors' do run_shell('/usr/local/sbin/mysqlbackup.sh') do |r| expect(r.stderr).to eq('') end end it 'dumps all databases to single file' do run_shell('ls -l /tmp/backups/mysql_backup_*-*.sql.bz2 | wc -l') do |r| expect(r.stdout).to match(%r{1}) expect(r.exit_code).to be_zero end end context 'should create one file per database per run' do it 'executes mysqlbackup.sh a second time' do run_shell('sleep 1') run_shell('/usr/local/sbin/mysqlbackup.sh') end it 'creates at least one backup tarball' do run_shell('ls -l /tmp/backups/mysql_backup_*-*.sql.bz2 | wc -l') do |r| expect(r.stdout).to match(%r{2}) expect(r.exit_code).to be_zero end end end end - # rubocop:enable RSpec/MultipleExpectations, RSpec/ExampleLength -end -context 'with one file per database' do - context 'should work with no errors' do - pp = <<-MANIFEST + context 'with one file per database' do + context 'should work with no errors' do + pp = <<-MANIFEST class { 'mysql::server': root_password => 'password' } mysql::db { [ 'backup1', 'backup2' ]: user => 'backup', password => 'secret', } class { 'mysql::server::backup': backupuser => 'myuser', backuppassword => 'mypassword', backupdir => '/tmp/backups', backupcompress => true, file_per_database => true, postscript => [ 'rm -rf /var/tmp/mysqlbackups', 'rm -f /var/tmp/mysqlbackups.done', 'cp -r /tmp/backups /var/tmp/mysqlbackups', 'touch /var/tmp/mysqlbackups.done', ], execpath => '/usr/bin:/usr/sbin:/bin:/sbin:/opt/zimbra/bin', } MANIFEST - it 'when configuring mysql backups' do - idempotent_apply(pp) + it 'when configuring mysql backups' do + idempotent_apply(pp) + end end - end - describe 'mysqlbackup.sh', if: Gem::Version.new(mysql_version) < Gem::Version.new('5.7.0') do - before(:all) do - pre_run - end + describe 'mysqlbackup.sh', if: Gem::Version.new(mysql_version) < Gem::Version.new('5.7.0') do + before(:all) do + pre_run + end - it 'runs mysqlbackup.sh with no errors without root credentials' do - run_shell('HOME=/tmp/dontreadrootcredentials /usr/local/sbin/mysqlbackup.sh') do |r| - expect(r.stderr).to eq('') + it 'runs mysqlbackup.sh with no errors without root credentials' do + run_shell('HOME=/tmp/dontreadrootcredentials /usr/local/sbin/mysqlbackup.sh') do |r| + expect(r.stderr).to eq('') + end end - end - it 'creates one file per database' do - ['backup1', 'backup2'].each do |database| - run_shell("ls -l /tmp/backups/mysql_backup_#{database}_*-*.sql.bz2 | wc -l") do |r| - expect(r.stdout).to match(%r{1}) - expect(r.exit_code).to be_zero + it 'creates one file per database' do + ['backup1', 'backup2'].each do |database| + run_shell("ls -l /tmp/backups/mysql_backup_#{database}_*-*.sql.bz2 | wc -l") do |r| + expect(r.stdout).to match(%r{1}) + expect(r.exit_code).to be_zero + end end end - end - it 'executes mysqlbackup.sh a second time' do - run_shell('sleep 1') - run_shell('HOME=/tmp/dontreadrootcredentials /usr/local/sbin/mysqlbackup.sh') - end + it 'executes mysqlbackup.sh a second time' do + run_shell('sleep 1') + run_shell('HOME=/tmp/dontreadrootcredentials /usr/local/sbin/mysqlbackup.sh') + end - it 'has one file per database per run' do - ['backup1', 'backup2'].each do |database| - run_shell("ls -l /tmp/backups/mysql_backup_#{database}_*-*.sql.bz2 | wc -l") do |r| - expect(r.stdout).to match(%r{2}) - expect(r.exit_code).to be_zero + it 'has one file per database per run' do + ['backup1', 'backup2'].each do |database| + run_shell("ls -l /tmp/backups/mysql_backup_#{database}_*-*.sql.bz2 | wc -l") do |r| + expect(r.stdout).to match(%r{2}) + expect(r.exit_code).to be_zero + end end end end - # rubocop:enable RSpec/MultipleExpectations, RSpec/ExampleLength end -end -context 'with xtrabackup enabled' do - context 'should work with no errors', if: ((os[:family] == 'debian' && os[:release].to_i >= 8) || (os[:family] == 'ubuntu' && os[:release] =~ %r{^16\.04|^18\.04}) || (os[:family] == 'redhat' && os[:release].to_i > 6)) do # rubocop:disable Metrics/LineLength - pp = <<-MANIFEST + context 'with xtrabackup enabled' do + context 'should work with no errors', if: ((os[:family] == 'debian' && os[:release].to_i >= 8) || (os[:family] == 'ubuntu' && os[:release] =~ %r{^16\.04|^18\.04}) || (os[:family] == 'redhat' && os[:release].to_i > 6)) do # rubocop:disable Layout/LineLength + pp = <<-MANIFEST class { 'mysql::server': root_password => 'password' } mysql::db { [ 'backup1', 'backup2' ]: user => 'backup', password => 'secret', } case $facts['os']['family'] { /Debian/: { if versioncmp($::operatingsystemmajrelease, '8') >= 0 { $source_url = "http://repo.percona.com/apt/percona-release_1.0-22.generic_all.deb" } else { $source_url = "http://repo.percona.com/apt/percona-release_latest.${facts['os']['distro']['codename']}_all.deb" } file { '/tmp/percona-release_latest.deb': ensure => present, source => $source_url, } ensure_packages('gnupg') ensure_packages('gnupg2') ensure_packages('curl') ensure_packages('percona-release',{ ensure => present, provider => 'dpkg', source => '/tmp/percona-release_latest.deb', notify => Exec['apt-get update'], }) exec { 'apt-get update': path => '/usr/bin:/usr/sbin:/bin:/sbin', refreshonly => true, } } /RedHat/: { # RHEL/CentOS 5 is no longer supported by Percona, but older versions # of the repository are still available. if versioncmp($::operatingsystemmajrelease, '6') >= 0 { $percona_url = 'http://repo.percona.com/yum/percona-release-latest.noarch.rpm' $epel_url = "https://download.fedoraproject.org/pub/epel/epel-release-latest-${facts['os']['release']['major']}.noarch.rpm" } else { $percona_url = 'http://repo.percona.com/yum/release/5/os/noarch/percona-release-0.1-3.noarch.rpm' $epel_url = 'https://archives.fedoraproject.org/pub/archive/epel/epel-release-latest-5.noarch.rpm' } ensure_packages('percona-release',{ ensure => present, provider => 'rpm', source => $percona_url, }) ensure_packages('epel-release',{ ensure => present, provider => 'rpm', source => $epel_url, }) if ($facts['os']['name'] == 'Scientific') { # $releasever resolves to '6.10' instead of '6' which breaks Percona repos file { '/etc/yum/vars/releasever': ensure => present, content => '6', } } } default: { } } class { 'mysql::server::backup': backupuser => 'myuser', backuppassword => 'mypassword', backupdir => '/tmp/xtrabackups', provider => 'xtrabackup', execpath => '/usr/bin:/usr/sbin:/bin:/sbin:/opt/zimbra/bin', } MANIFEST - it 'when configuring mysql backup' do - idempotent_apply(pp) + it 'when configuring mysql backup' do + idempotent_apply(pp) + end end - end - describe 'xtrabackup.sh', if: Gem::Version.new(mysql_version) < Gem::Version.new('5.7.0') && ((os[:family] == 'debian' && os[:release].to_i >= 8) || (os[:family] == 'ubuntu' && os[:release] =~ %r{^16\.04|^18\.04}) || (os[:family] == 'redhat' && os[:release].to_i > 6)) do # rubocop:disable Metrics/LineLength - before(:all) do - pre_run - end + describe 'xtrabackup.sh', if: Gem::Version.new(mysql_version) < Gem::Version.new('5.7.0') && ((os[:family] == 'debian' && os[:release].to_i >= 8) || (os[:family] == 'ubuntu' && os[:release] =~ %r{^16\.04|^18\.04}) || (os[:family] == 'redhat' && os[:release].to_i > 6)) do # rubocop:disable Layout/LineLength + before(:all) do + pre_run + end - it 'runs xtrabackup.sh full backup with no errors' do - run_shell('/usr/local/sbin/xtrabackup.sh --target-dir=/tmp/xtrabackups/$(date +%F)_full --backup 2>&1 | tee /tmp/xtrabackup_full.log') do |r| - expect(r.exit_code).to be_zero + it 'runs xtrabackup.sh full backup with no errors' do + run_shell('/usr/local/sbin/xtrabackup.sh --target-dir=/tmp/xtrabackups/$(date +%F)_full --backup 2>&1 | tee /tmp/xtrabackup_full.log') do |r| + expect(r.exit_code).to be_zero + end end - end - it 'xtrabackup reports success for the full backup' do - # NOTE: Once support for CentOS 6 is dropped, we should check for "completed OK" instead. - run_shell('grep "xtrabackup: Transaction log of lsn" /tmp/xtrabackup_full.log') do |r| - expect(r.exit_code).to be_zero + it 'xtrabackup reports success for the full backup' do + # NOTE: Once support for CentOS 6 is dropped, we should check for "completed OK" instead. + run_shell('grep "xtrabackup: Transaction log of lsn" /tmp/xtrabackup_full.log') do |r| + expect(r.exit_code).to be_zero + end end - end - it 'creates a subdirectory for the full backup' do - run_shell('find /tmp/xtrabackups -mindepth 1 -maxdepth 1 -type d -name $(date +%Y)\*full | wc -l') do |r| - expect(r.stdout).to match(%r{1}) - expect(r.exit_code).to be_zero + it 'creates a subdirectory for the full backup' do + run_shell('find /tmp/xtrabackups -mindepth 1 -maxdepth 1 -type d -name $(date +%Y)\*full | wc -l') do |r| + expect(r.stdout).to match(%r{1}) + expect(r.exit_code).to be_zero + end end - end - it 'runs xtrabackup.sh incremental backup with no errors' do - run_shell('sleep 1') - run_shell('/usr/local/sbin/xtrabackup.sh --incremental-basedir=/tmp/xtrabackups/$(date +%F)_full --target-dir=/tmp/xtrabackups/$(date +%F_%H-%M-%S) --backup 2>&1 | tee /tmp/xtrabackup_inc.log') do |r| # rubocop:disable Metrics/LineLength - expect(r.exit_code).to be_zero + it 'runs xtrabackup.sh incremental backup with no errors' do + run_shell('sleep 1') + run_shell('/usr/local/sbin/xtrabackup.sh --incremental-basedir=/tmp/xtrabackups/$(date +%F)_full --target-dir=/tmp/xtrabackups/$(date +%F_%H-%M-%S) --backup 2>&1 | tee /tmp/xtrabackup_inc.log') do |r| # rubocop:disable Layout/LineLength + expect(r.exit_code).to be_zero + end end - end - it 'xtrabackup reports success for the incremental backup' do - # NOTE: Once support for CentOS 6 is dropped, we should check for "completed OK" instead. - run_shell('grep "xtrabackup: Transaction log of lsn" /tmp/xtrabackup_inc.log') do |r| - expect(r.exit_code).to be_zero + it 'xtrabackup reports success for the incremental backup' do + # NOTE: Once support for CentOS 6 is dropped, we should check for "completed OK" instead. + run_shell('grep "xtrabackup: Transaction log of lsn" /tmp/xtrabackup_inc.log') do |r| + expect(r.exit_code).to be_zero + end end - end - it 'creates a new subdirectory for each backup' do - run_shell('find /tmp/xtrabackups -mindepth 1 -maxdepth 1 -type d -name $(date +%Y)\* | wc -l') do |r| - expect(r.stdout).to match(%r{2}) - expect(r.exit_code).to be_zero + it 'creates a new subdirectory for each backup' do + run_shell('find /tmp/xtrabackups -mindepth 1 -maxdepth 1 -type d -name $(date +%Y)\* | wc -l') do |r| + expect(r.stdout).to match(%r{2}) + expect(r.exit_code).to be_zero + end end end end - # rubocop:enable RSpec/MultipleExpectations, RSpec/ExampleLength -end -context 'with xtrabackup enabled and incremental backups disabled' do - context 'should work with no errors', if: ((os[:family] == 'debian' && os[:release].to_i >= 8) || (os[:family] == 'ubuntu' && os[:release] =~ %r{^16\.04|^18\.04}) || (os[:family] == 'redhat' && os[:release].to_i > 6)) do # rubocop:disable Metrics/LineLength - pp = <<-MANIFEST + context 'with xtrabackup enabled and incremental backups disabled' do + context 'should work with no errors', if: ((os[:family] == 'debian' && os[:release].to_i >= 8) || (os[:family] == 'ubuntu' && os[:release] =~ %r{^16\.04|^18\.04}) || (os[:family] == 'redhat' && os[:release].to_i > 6)) do # rubocop:disable Layout/LineLength + pp = <<-MANIFEST class { 'mysql::server': root_password => 'password' } mysql::db { [ 'backup1', 'backup2' ]: user => 'backup', password => 'secret', } case $facts['os']['family'] { /Debian/: { if versioncmp($::operatingsystemmajrelease, '8') >= 0 { $source_url = "http://repo.percona.com/apt/percona-release_1.0-22.generic_all.deb" } else { $source_url = "http://repo.percona.com/apt/percona-release_latest.${facts['os']['distro']['codename']}_all.deb" } file { '/tmp/percona-release_latest.deb': ensure => present, source => $source_url, } ensure_packages('gnupg') ensure_packages('gnupg2') ensure_packages('percona-release',{ ensure => present, provider => 'dpkg', source => '/tmp/percona-release_latest.deb', notify => Exec['apt-get update'], }) exec { 'apt-get update': path => '/usr/bin:/usr/sbin:/bin:/sbin', refreshonly => true, } } /RedHat/: { # RHEL/CentOS 5 is no longer supported by Percona, but older versions # of the repository are still available. if versioncmp($::operatingsystemmajrelease, '6') >= 0 { $percona_url = 'http://repo.percona.com/yum/percona-release-latest.noarch.rpm' $epel_url = "https://download.fedoraproject.org/pub/epel/epel-release-latest-${facts['os']['release']['major']}.noarch.rpm" } else { $percona_url = 'http://repo.percona.com/yum/release/5/os/noarch/percona-release-0.1-3.noarch.rpm' $epel_url = 'https://archives.fedoraproject.org/pub/archive/epel/epel-release-latest-5.noarch.rpm' } ensure_packages('percona-release',{ ensure => present, provider => 'rpm', source => $percona_url, }) ensure_packages('epel-release',{ ensure => present, provider => 'rpm', source => $epel_url, }) if ($facts['os']['name'] == 'Scientific') { # $releasever resolves to '6.10' instead of '6' which breaks Percona repos file { '/etc/yum/vars/releasever': ensure => present, content => '6', } } } default: { } } class { 'mysql::server::backup': backupuser => 'myuser', backuppassword => 'mypassword', backupdir => '/tmp/xtrabackups', provider => 'xtrabackup', incremental_backups => false, execpath => '/usr/bin:/usr/sbin:/bin:/sbin:/opt/zimbra/bin', } MANIFEST - it 'when configuring mysql backup' do - idempotent_apply(pp) + it 'when configuring mysql backup' do + idempotent_apply(pp) + end end - end - describe 'xtrabackup.sh', if: Gem::Version.new(mysql_version) < Gem::Version.new('5.7.0') && ((os[:family] == 'debian' && os[:release].to_i >= 8) || (os[:family] == 'ubuntu' && os[:release] =~ %r{^16\.04|^18\.04}) || (os[:family] == 'redhat' && os[:release].to_i > 6)) do # rubocop:disable Metrics/LineLength - before(:all) do - pre_run - end + describe 'xtrabackup.sh', if: Gem::Version.new(mysql_version) < Gem::Version.new('5.7.0') && ((os[:family] == 'debian' && os[:release].to_i >= 8) || (os[:family] == 'ubuntu' && os[:release] =~ %r{^16\.04|^18\.04}) || (os[:family] == 'redhat' && os[:release].to_i > 6)) do # rubocop:disable Layout/LineLength + before(:all) do + pre_run + end - it 'runs xtrabackup.sh with no errors' do - run_shell('/usr/local/sbin/xtrabackup.sh --target-dir=/tmp/xtrabackups/$(date +%F_%H-%M-%S) --backup 2>&1 | tee /tmp/xtrabackup.log') do |r| - expect(r.exit_code).to be_zero + it 'runs xtrabackup.sh with no errors' do + run_shell('/usr/local/sbin/xtrabackup.sh --target-dir=/tmp/xtrabackups/$(date +%F_%H-%M-%S) --backup 2>&1 | tee /tmp/xtrabackup.log') do |r| + expect(r.exit_code).to be_zero + end end - end - it 'xtrabackup reports success for the backup' do - # NOTE: Once support for CentOS 6 is dropped, we should check for "completed OK" instead. - run_shell('grep "xtrabackup: Transaction log of lsn" /tmp/xtrabackup.log') do |r| - expect(r.exit_code).to be_zero + it 'xtrabackup reports success for the backup' do + # NOTE: Once support for CentOS 6 is dropped, we should check for "completed OK" instead. + run_shell('grep "xtrabackup: Transaction log of lsn" /tmp/xtrabackup.log') do |r| + expect(r.exit_code).to be_zero + end end end end - # rubocop:enable RSpec/MultipleExpectations, RSpec/ExampleLength end diff --git a/spec/acceptance/mysql_db_spec.rb b/spec/acceptance/mysql_db_spec.rb index 6fccaf7..15930a6 100644 --- a/spec/acceptance/mysql_db_spec.rb +++ b/spec/acceptance/mysql_db_spec.rb @@ -1,81 +1,83 @@ +# frozen_string_literal: true + require 'spec_helper_acceptance' describe 'mysql::db define' do describe 'creating a database' do let(:pp) do <<-MANIFEST class { 'mysql::server': root_password => 'password', service_enabled => 'true', service_manage => 'true', } mysql::db { 'spec1': user => 'root1', password => 'password', } MANIFEST end it 'behaves idempotently' do idempotent_apply(pp) end it 'Checking exit code and stdout' do result = run_shell("mysql -e 'show databases;'") expect(result.exit_code).to eq 0 expect(result.stdout).to match %r{^spec1$} end end describe 'creating a database with post-sql' do let(:pp) do <<-MANIFEST class { 'mysql::server': override_options => { 'root_password' => 'password' } } file { '/tmp/spec.sql': ensure => file, content => 'CREATE TABLE table1 (id int);', before => Mysql::Db['spec2'], } mysql::db { 'spec2': user => 'root1', password => 'password', sql => '/tmp/spec.sql', } MANIFEST end it 'behaves idempotently' do idempotent_apply(pp) end it 'Checking exit code and stdout' do result = run_shell("mysql -e 'show tables;' spec2") expect(result.exit_code).to eq 0 expect(result.stdout).to match %r{^table1$} end end describe 'creating a database with dbname parameter' do let(:check_command) { ' | grep realdb' } let(:pp) do <<-MANIFEST class { 'mysql::server': override_options => { 'root_password' => 'password' } } mysql::db { 'spec1': user => 'root1', password => 'password', dbname => 'realdb', } MANIFEST end it 'behaves idempotently' do idempotent_apply(pp) end it 'Checking exit code and stdout' do result = run_shell("mysql -e 'show databases;'") expect(result.exit_code).to eq 0 expect(result.stdout).to match %r{^realdb$} end end end diff --git a/spec/acceptance/mysql_mariadb_spec.rb b/spec/acceptance/mysql_mariadb_spec.rb index f70a5ba..1b628b1 100644 --- a/spec/acceptance/mysql_mariadb_spec.rb +++ b/spec/acceptance/mysql_mariadb_spec.rb @@ -1,44 +1,46 @@ +# frozen_string_literal: true + require 'spec_helper_acceptance' describe 'mysql server class', if: ((os[:family] == 'debian' && os[:release].to_i > 8) || (os[:family] == 'redhat' && os[:release].to_i > 6)) do describe 'mariadb' do let(:pp) do <<-MANIFEST $osname = $facts['os']['name'].downcase yumrepo {'mariadb': baseurl => "http://yum.mariadb.org/10.4/$osname${facts['os']['release']['major']}-aarch64/", gpgkey => 'https://yum.mariadb.org/RPM-GPG-KEY-MariaDB', descr => "MariaDB 10.4", enabled => 1, gpgcheck => 1, }-> class { '::mysql::server': require => Yumrepo['mariadb'], package_name => 'mariadb-server', service_name => 'mariadb', root_password => 'strongpassword', remove_default_accounts => true, managed_dirs => ['/var/log','/var/run/mysql'], override_options => { mysqld => { log-error => '/var/log/mariadb.log', pid-file => '/var/run/mysql/mysqld.pid', }, mysqld_safe => { log-error => '/var/log/mariadb.log', }, }, } MANIFEST end it 'apply manifest' do apply_manifest(pp) end it 'mariadb connection' do result = run_shell('mysql --user="root" --password="strongpassword" -e "status"') expect(result.stdout).to match(%r{MariaDB}) expect(result.stderr).to be_empty end end end diff --git a/spec/acceptance/mysql_server_spec.rb b/spec/acceptance/mysql_server_spec.rb index ec8a53a..3248a83 100644 --- a/spec/acceptance/mysql_server_spec.rb +++ b/spec/acceptance/mysql_server_spec.rb @@ -1,84 +1,86 @@ +# frozen_string_literal: true + require 'spec_helper_acceptance' describe 'mysql class' do describe 'advanced config' do let(:pp) do <<-MANIFEST class { 'mysql::server': manage_config_file => 'true', override_options => { 'mysqld' => { 'key_buffer_size' => '32M' }}, package_ensure => 'present', purge_conf_dir => 'true', remove_default_accounts => 'true', restart => 'true', root_group => 'root', root_password => 'test', service_enabled => 'true', service_manage => 'true', users => { 'someuser@localhost' => { ensure => 'present', max_connections_per_hour => '0', max_queries_per_hour => '0', max_updates_per_hour => '0', max_user_connections => '0', password_hash => '*F3A2A51A9B0F2BE2468926B4132313728C250DBF', }}, grants => { 'someuser@localhost/somedb.*' => { ensure => 'present', options => ['GRANT'], privileges => ['SELECT', 'INSERT', 'UPDATE', 'DELETE'], table => 'somedb.*', user => 'someuser@localhost', }, }, databases => { 'somedb' => { ensure => 'present', charset => 'utf8', }, } } MANIFEST end it 'behaves idempotently' do idempotent_apply(pp) end describe 'override_options' do let(:pp) do <<-MANIFEST class { '::mysql::server': override_options => { 'mysqld' => { 'log-bin' => '/var/log/mariadb/mariadb-bin.log',} } } MANIFEST end it 'can be set' do export_locales apply_manifest(pp, catch_failures: true) do |r| expect(r.stderr).to be_empty end end end end describe 'syslog configuration' do let(:pp) do <<-MANIFEST class { 'mysql::server': override_options => { 'mysqld' => { 'log-error' => undef }, 'mysqld_safe' => { 'log-error' => false, 'syslog' => true }}, } MANIFEST end it 'behaves idempotently' do idempotent_apply(pp) end end end diff --git a/spec/acceptance/mysql_task_spec.rb b/spec/acceptance/mysql_task_spec.rb index 7cc227f..0b082bc 100644 --- a/spec/acceptance/mysql_task_spec.rb +++ b/spec/acceptance/mysql_task_spec.rb @@ -1,24 +1,26 @@ +# frozen_string_literal: true + # run a test task require 'spec_helper_acceptance' describe 'mysql tasks', if: os[:family] != 'sles' do describe 'execute some sql' do pp = <<-MANIFEST class { 'mysql::server': root_password => 'password' } mysql::db { 'spec1': user => 'root1', password => 'password', } MANIFEST it 'sets up a mysql instance' do apply_manifest(pp, catch_failures: true) end it 'execute arbitary sql' do result = run_bolt_task('mysql::sql', 'sql' => 'show databases;', 'password' => 'password') expect(result.stdout).to contain(%r{information_schema}) expect(result.stdout).to contain(%r{spec1}) end end end diff --git a/spec/acceptance/types/mysql_database_spec.rb b/spec/acceptance/types/mysql_database_spec.rb index 87490a8..03579fe 100644 --- a/spec/acceptance/types/mysql_database_spec.rb +++ b/spec/acceptance/types/mysql_database_spec.rb @@ -1,60 +1,62 @@ +# frozen_string_literal: true + require 'spec_helper_acceptance' describe 'mysql_database' do describe 'setup' do pp = <<-MANIFEST class { 'mysql::server': } MANIFEST it 'works with no errors' do apply_manifest(pp, catch_failures: true) end end describe 'creating database' do pp = <<-MANIFEST mysql_database { 'spec_db': ensure => present, } MANIFEST it 'works without errors' do apply_manifest(pp, catch_failures: true) end it 'finds the database #stdout' do run_shell("mysql -NBe \"SHOW DATABASES LIKE 'spec_db'\"") do |r| expect(r.stdout).to match(%r{^spec_db$}) expect(r.stderr).to be_empty end end end describe 'charset and collate' do pp = <<-MANIFEST mysql_database { 'spec_latin1': charset => 'latin1', collate => 'latin1_swedish_ci', } mysql_database { 'spec_utf8': charset => 'utf8', collate => 'utf8_general_ci', } MANIFEST it 'creates two db of different types idempotently' do idempotent_apply(pp) end it 'finds latin1 db #stdout' do run_shell("mysql -NBe \"SHOW VARIABLES LIKE '%_database'\" spec_latin1") do |r| expect(r.stdout).to match(%r{^character_set_database\tlatin1\ncollation_database\tlatin1_swedish_ci$}) expect(r.stderr).to be_empty end end it 'finds utf8 db #stdout' do run_shell("mysql -NBe \"SHOW VARIABLES LIKE '%_database'\" spec_utf8") do |r| expect(r.stdout).to match(%r{^character_set_database\tutf8\ncollation_database\tutf8_general_ci$}) expect(r.stderr).to be_empty end end end end diff --git a/spec/acceptance/types/mysql_grant_spec.rb b/spec/acceptance/types/mysql_grant_spec.rb index 84a141f..76face4 100644 --- a/spec/acceptance/types/mysql_grant_spec.rb +++ b/spec/acceptance/types/mysql_grant_spec.rb @@ -1,739 +1,741 @@ +# frozen_string_literal: true + require 'spec_helper_acceptance' describe 'mysql_grant' do before(:all) do pp = <<-MANIFEST class { 'mysql::server': root_password => 'password', } MANIFEST apply_manifest(pp, catch_failures: true) end describe 'missing privileges for user' do pp = <<-MANIFEST mysql_user { 'test1@tester': ensure => present, } mysql_grant { 'test1@tester/test.*': ensure => 'present', table => 'test.*', user => 'test1@tester', require => Mysql_user['test1@tester'], } MANIFEST it 'fails' do result = apply_manifest(pp, expect_failures: true) expect(result.stderr).to contain(%r{`privileges` `parameter` is required}) end it 'does not find the user' do result = run_shell('mysql -NBe "SHOW GRANTS FOR test1@tester"', expect_failures: true) expect(result.stderr).to contain(%r{There is no such grant defined for user 'test1' on host 'tester'}) end end describe 'missing table for user' do pp = <<-MANIFEST mysql_user { 'atest@tester': ensure => present, } mysql_grant { 'atest@tester/test.*': ensure => 'present', user => 'atest@tester', privileges => ['ALL'], require => Mysql_user['atest@tester'], } MANIFEST it 'fails' do apply_manifest(pp, expect_failures: true) end it 'does not find the user' do result = run_shell('mysql -NBe "SHOW GRANTS FOR atest@tester"', expect_failures: true) expect(result.stderr).to contain(%r{There is no such grant defined for user 'atest' on host 'tester'}) end end describe 'adding privileges' do pp = <<-MANIFEST mysql_user { 'test2@tester': ensure => present, } mysql_grant { 'test2@tester/test.*': ensure => 'present', table => 'test.*', user => 'test2@tester', privileges => ['SELECT', 'UPDATE'], require => Mysql_user['test2@tester'], } MANIFEST it 'works without errors' do apply_manifest(pp, catch_failures: true) end it 'finds the user #stdout' do result = run_shell('mysql -NBe "SHOW GRANTS FOR test2@tester"') expect(result.stdout).to contain(%r{GRANT SELECT, UPDATE.*TO ['|`]test2['|`]@['|`]tester['|`]}) expect(result.stderr).to be_empty end end describe 'adding privileges with special character in name' do pp = <<-MANIFEST mysql_user { 'test-2@tester': ensure => present, } mysql_grant { 'test-2@tester/test.*': ensure => 'present', table => 'test.*', user => 'test-2@tester', privileges => ['SELECT', 'UPDATE'], require => Mysql_user['test-2@tester'], } MANIFEST it 'works without errors' do apply_manifest(pp, catch_failures: true) end it 'finds the user #stdout' do result = run_shell("mysql -NBe \"SHOW GRANTS FOR 'test-2'@tester\"") expect(result.stdout).to contain(%r{GRANT SELECT, UPDATE.*TO ['|`]test-2['|`]@['|`]tester['|`]}) expect(result.stderr).to be_empty end end describe 'adding option' do pp = <<-MANIFEST mysql_user { 'test3@tester': ensure => present, } mysql_grant { 'test3@tester/test.*': ensure => 'present', table => 'test.*', user => 'test3@tester', options => ['GRANT'], privileges => ['SELECT', 'UPDATE'], require => Mysql_user['test3@tester'], } MANIFEST it 'works without errors' do apply_manifest(pp, catch_failures: true) end it 'finds the user #stdout' do result = run_shell('mysql -NBe "SHOW GRANTS FOR test3@tester"') expect(result.stdout).to contain(%r{GRANT SELECT, UPDATE ON `test`.* TO ['|`]test3['|`]@['|`]tester['|`] WITH GRANT OPTION$}) expect(result.stderr).to be_empty end end describe 'adding all privileges without table' do pp = <<-MANIFEST mysql_user { 'test4@tester': ensure => present, } mysql_grant { 'test4@tester/test.*': ensure => 'present', user => 'test4@tester', options => ['GRANT'], privileges => ['SELECT', 'UPDATE', 'ALL'], require => Mysql_user['test4@tester'], } MANIFEST it 'fails' do result = apply_manifest(pp, expect_failures: true) expect(result.stderr).to contain(%r{`table` `parameter` is required.}) end end describe 'adding all privileges' do pp = <<-MANIFEST mysql_user { 'test4@tester': ensure => present, } mysql_grant { 'test4@tester/test.*': ensure => 'present', table => 'test.*', user => 'test4@tester', options => ['GRANT'], privileges => ['SELECT', 'UPDATE', 'ALL'], require => Mysql_user['test4@tester'], } MANIFEST it 'onlies try to apply ALL' do apply_manifest(pp, catch_failures: true) end it 'finds the user #stdout' do result = run_shell('mysql -NBe "SHOW GRANTS FOR test4@tester"') expect(result.stdout).to contain(%r{GRANT ALL PRIVILEGES ON `test`.* TO ['|`]test4['|`]@['|`]tester['|`] WITH GRANT OPTION}) expect(result.stderr).to be_empty end end # Test combinations of user@host to ensure all cases work. describe 'short hostname' do pp = <<-MANIFEST mysql_user { 'test@short': ensure => present, } mysql_grant { 'test@short/test.*': ensure => 'present', table => 'test.*', user => 'test@short', privileges => 'ALL', require => Mysql_user['test@short'], } mysql_user { 'test@long.hostname.com': ensure => present, } mysql_grant { 'test@long.hostname.com/test.*': ensure => 'present', table => 'test.*', user => 'test@long.hostname.com', privileges => 'ALL', require => Mysql_user['test@long.hostname.com'], } mysql_user { 'test@192.168.5.6': ensure => present, } mysql_grant { 'test@192.168.5.6/test.*': ensure => 'present', table => 'test.*', user => 'test@192.168.5.6', privileges => 'ALL', require => Mysql_user['test@192.168.5.6'], } mysql_user { 'test@2607:f0d0:1002:0051:0000:0000:0000:0004': ensure => present, } mysql_grant { 'test@2607:f0d0:1002:0051:0000:0000:0000:0004/test.*': ensure => 'present', table => 'test.*', user => 'test@2607:f0d0:1002:0051:0000:0000:0000:0004', privileges => 'ALL', require => Mysql_user['test@2607:f0d0:1002:0051:0000:0000:0000:0004'], } mysql_user { 'test@::1/128': ensure => present, } mysql_grant { 'test@::1/128/test.*': ensure => 'present', table => 'test.*', user => 'test@::1/128', privileges => 'ALL', require => Mysql_user['test@::1/128'], } MANIFEST it 'applies' do apply_manifest(pp, catch_failures: true) end it 'finds short hostname #stdout' do result = run_shell('mysql -NBe "SHOW GRANTS FOR test@short"') expect(result.stdout).to contain(%r{GRANT ALL PRIVILEGES ON ['|`]test['|`].* TO ['|`]test['|`]@['|`]short['|`]}) expect(result.stderr).to be_empty end it 'finds long hostname #stdout' do run_shell("mysql -NBe \"SHOW GRANTS FOR 'test'@'long.hostname.com'\"") do |r| expect(r.stdout).to match(%r{GRANT ALL PRIVILEGES ON ['|`]test['|`].* TO ['|`]test['|`]@['|`]long.hostname.com['|`]}) expect(r.stderr).to be_empty end end it 'finds ipv4 #stdout' do run_shell("mysql -NBe \"SHOW GRANTS FOR 'test'@'192.168.5.6'\"") do |r| expect(r.stdout).to match(%r{GRANT ALL PRIVILEGES ON ['|`]test['|`].* TO ['|`]test['|`]@['|`]192.168.5.6['|`]}) expect(r.stderr).to be_empty end end it 'finds ipv6 #stdout' do run_shell("mysql -NBe \"SHOW GRANTS FOR 'test'@'2607:f0d0:1002:0051:0000:0000:0000:0004'\"") do |r| expect(r.stdout).to match(%r{GRANT ALL PRIVILEGES ON ['|`]test['|`].* TO ['|`]test['|`]@['|`]2607:f0d0:1002:0051:0000:0000:0000:0004['|`]}) expect(r.stderr).to be_empty end end it 'finds short ipv6 #stdout' do run_shell("mysql -NBe \"SHOW GRANTS FOR 'test'@'::1/128'\"") do |r| expect(r.stdout).to match(%r{GRANT ALL PRIVILEGES ON ['|`]test['|`].* TO ['|`]test['|`]@['|`]::1\/128['|`]}) expect(r.stderr).to be_empty end end end # On Ubuntu 20.04 'ALL' now returns as the sum of it's constitute parts and so require a specific test describe 'ALL privilege on newer MySQL versions', if: os[:family] == 'ubuntu' && os[:release] =~ %r{^20\.04} do pp_one = <<-MANIFEST mysql_user { 'all@localhost': ensure => present, } mysql_grant { 'all@localhost/*.*': user => 'all@localhost', privileges => ['ALL'], table => '*.*', require => Mysql_user['all@localhost'], } MANIFEST it "create ['ALL'] privs" do apply_manifest(pp_one, catch_failures: true) end pp_two = <<-MANIFEST mysql_user { 'all@localhost': ensure => present, } mysql_grant { 'all@localhost/*.*': user => 'all@localhost', privileges => ['ALTER', 'ALTER ROUTINE', 'CREATE', 'CREATE ROLE', 'CREATE ROUTINE', 'CREATE TABLESPACE', 'CREATE TEMPORARY TABLES', 'CREATE USER', 'CREATE VIEW', 'DELETE', 'DROP', 'DROP ROLE', 'EVENT', 'EXECUTE', 'FILE', 'INDEX', 'INSERT', 'LOCK TABLES', 'PROCESS', 'REFERENCES', 'RELOAD', 'REPLICATION CLIENT', 'REPLICATION SLAVE', 'SELECT', 'SHOW DATABASES', 'SHOW VIEW', 'SHUTDOWN', 'SUPER', 'TRIGGER', 'UPDATE'], table => '*.*', require => Mysql_user['all@localhost'], } MANIFEST it "create ['ALL'] constitute parts privs" do apply_manifest(pp_two, catch_changes: true) end end describe 'complex test' do # On Ubuntu 20.04 'ALL' now returns as the sum of it's constitute parts and so is no longer idempotent when set privileges = if os[:family] == 'ubuntu' && os[:release] =~ %r{^20\.04} "['SELECT', 'INSERT', 'UPDATE']" else "['ALL']" end pp = <<-MANIFEST $dbSubnet = '10.10.10.%' mysql_database { 'foo': ensure => present, } exec { 'mysql-create-table': command => '/usr/bin/mysql -NBe "CREATE TABLE foo.bar (name VARCHAR(20))"', environment => "HOME=${::root_home}", unless => '/usr/bin/mysql -NBe "SELECT 1 FROM foo.bar LIMIT 1;"', require => Mysql_database['foo'], } Mysql_grant { ensure => present, options => ['GRANT'], privileges => #{privileges}, table => '*.*', require => [ Mysql_database['foo'], Exec['mysql-create-table'] ], } mysql_user { "user1@${dbSubnet}": ensure => present, } mysql_grant { "user1@${dbSubnet}/*.*": user => "user1@${dbSubnet}", require => Mysql_user["user1@${dbSubnet}"], } mysql_user { "user2@${dbSubnet}": ensure => present, } mysql_grant { "user2@${dbSubnet}/foo.bar": privileges => ['SELECT', 'INSERT', 'UPDATE'], user => "user2@${dbSubnet}", table => 'foo.bar', require => Mysql_user["user2@${dbSubnet}"], } mysql_user { "user3@${dbSubnet}": ensure => present, } mysql_grant { "user3@${dbSubnet}/foo.*": privileges => ['SELECT', 'INSERT', 'UPDATE'], user => "user3@${dbSubnet}", table => 'foo.*', require => Mysql_user["user3@${dbSubnet}"], } mysql_user { 'web@%': ensure => present, } mysql_grant { 'web@%/*.*': user => 'web@%', require => Mysql_user['web@%'], } mysql_user { "web@${dbSubnet}": ensure => present, } mysql_grant { "web@${dbSubnet}/*.*": user => "web@${dbSubnet}", require => Mysql_user["web@${dbSubnet}"], } mysql_user { "web@${fqdn}": ensure => present, } mysql_grant { "web@${fqdn}/*.*": user => "web@${fqdn}", require => Mysql_user["web@${fqdn}"], } mysql_user { 'web@localhost': ensure => present, } mysql_grant { 'web@localhost/*.*': user => 'web@localhost', require => Mysql_user['web@localhost'], } MANIFEST it 'setup mysql::server' do idempotent_apply(pp) end end describe 'lower case privileges' do pp_one = <<-MANIFEST mysql_user { 'lowercase@localhost': ensure => present, } mysql_grant { 'lowercase@localhost/*.*': user => 'lowercase@localhost', privileges => ['SELECT', 'INSERT', 'UPDATE'], table => '*.*', require => Mysql_user['lowercase@localhost'], } MANIFEST it "create ['SELECT', 'INSERT', 'UPDATE'] privs" do apply_manifest(pp_one, catch_failures: true) end pp_two = <<-MANIFEST mysql_user { 'lowercase@localhost': ensure => present, } mysql_grant { 'lowercase@localhost/*.*': user => 'lowercase@localhost', privileges => ['select', 'insert', 'update'], table => '*.*', require => Mysql_user['lowercase@localhost'], } MANIFEST it "create lowercase ['select', 'insert', 'update'] privs" do apply_manifest(pp_two, catch_changes: true) end end describe 'adding procedure privileges' do pp = <<-MANIFEST exec { 'simpleproc-create': command => 'mysql --user="root" --password="password" --database=mysql --delimiter="//" -NBe "CREATE PROCEDURE simpleproc (OUT param1 INT) BEGIN SELECT COUNT(*) INTO param1 FROM t; end//"', path => '/usr/bin/', before => Mysql_user['test2@tester'], } mysql_user { 'test2@tester': ensure => present, } mysql_grant { 'test2@tester/PROCEDURE mysql.simpleproc': ensure => 'present', table => 'PROCEDURE mysql.simpleproc', user => 'test2@tester', privileges => ['EXECUTE'], require => Mysql_user['test2@tester'], } MANIFEST it 'works without errors' do apply_manifest(pp, catch_failures: true) end it 'finds the user #stdout' do result = run_shell('mysql -NBe "SHOW GRANTS FOR test2@tester"') expect(result.stdout).to match(%r{GRANT EXECUTE ON PROCEDURE `mysql`.`simpleproc` TO ['|`]test2['|`]@['|`]tester['|`]}) expect(result.stderr).to be_empty end end describe 'adding function privileges' do it 'works without errors' do pp = <<-EOS exec { 'simplefunc-create': command => '/usr/bin/mysql --user="root" --password="password" --database=mysql -NBe "CREATE FUNCTION simplefunc (s CHAR(20)) RETURNS CHAR(50) DETERMINISTIC RETURN CONCAT(\\'Hello, \\', s, \\'!\\')"', before => Mysql_user['test3@tester'], } mysql_user { 'test3@tester': ensure => 'present', } mysql_grant { 'test3@tester/FUNCTION mysql.simplefunc': ensure => 'present', table => 'FUNCTION mysql.simplefunc', user => 'test3@tester', privileges => ['EXECUTE'], require => Mysql_user['test3@tester'], } EOS apply_manifest(pp, catch_failures: true) end # rubocop:enable RSpec/ExampleLength it 'finds the user' do result = run_shell('mysql -NBe "SHOW GRANTS FOR test3@tester"') expect(result.stdout).to match(%r{GRANT EXECUTE ON FUNCTION `mysql`.`simplefunc` TO ['|`]test3['|`]@['|`]tester['|`]}) expect(result.stderr).to be_empty end # rubocop:enable RSpec/MultipleExpectations end describe 'proxy privilieges' do pre_run describe 'adding proxy privileges', if: Gem::Version.new(mysql_version) > Gem::Version.new('5.5.0') do pp = <<-MANIFEST mysql_user { 'proxy1@tester': ensure => present, } mysql_grant { 'proxy1@tester/proxy_user@proxy_host': ensure => 'present', table => 'proxy_user@proxy_host', user => 'proxy1@tester', privileges => ['PROXY'], require => Mysql_user['proxy1@tester'], } MANIFEST it 'works without errors when version greater than 5.5.0' do apply_manifest(pp, catch_failures: true) end it 'finds the user #stdout' do run_shell('mysql -NBe "SHOW GRANTS FOR proxy1@tester"') do |r| expect(r.stdout).to match(%r{GRANT PROXY ON 'proxy_user'@'proxy_host' TO ['|`]proxy1['|`]@['|`]tester['|`]}) expect(r.stderr).to be_empty end end end describe 'removing proxy privileges', if: Gem::Version.new(mysql_version) > Gem::Version.new('5.5.0') do pp = <<-MANIFEST mysql_user { 'proxy1@tester': ensure => present, } mysql_grant { 'proxy1@tester/proxy_user@proxy_host': ensure => 'absent', table => 'proxy_user@proxy_host', user => 'proxy1@tester', privileges => ['PROXY'], require => Mysql_user['proxy1@tester'], } MANIFEST it 'works without errors' do apply_manifest(pp, catch_failures: true) end it 'finds the user #stdout' do run_shell('mysql -NBe "SHOW GRANTS FOR proxy1@tester"') do |r| expect(r.stdout).not_to match(%r{GRANT PROXY ON 'proxy_user'@'proxy_host' TO ['|`]proxy1['|`]@['|`]tester['|`]}) expect(r.stderr).to be_empty end end end describe 'adding proxy privileges with other privileges', if: Gem::Version.new(mysql_version) > Gem::Version.new('5.5.0') do pp = <<-MANIFEST mysql_user { 'proxy2@tester': ensure => present, } mysql_grant { 'proxy2@tester/proxy_user@proxy_host': ensure => 'present', table => 'proxy_user@proxy_host', user => 'proxy2@tester', privileges => ['PROXY', 'SELECT'], require => Mysql_user['proxy2@tester'], } MANIFEST it 'fails' do result = apply_manifest(pp, expect_failures: true) expect(result.stderr).to match(%r{`privileges` `parameter`: PROXY can only be specified by itself}) end it 'does not find the user' do result = run_shell('mysql -NBe "SHOW GRANTS FOR proxy2@tester"', expect_failures: true) expect(result.stderr).to match(%r{There is no such grant defined for user 'proxy2' on host 'tester'}) end end describe 'adding proxy privileges with mysql version less than 5.5.0', unless: Gem::Version.new(mysql_version) > Gem::Version.new('5.5.0') do pp = <<-MANIFEST mysql_user { 'proxy3@tester': ensure => present, } mysql_grant { 'proxy3@tester/proxy_user@proxy_host': ensure => 'present', table => 'proxy_user@proxy_host', user => 'proxy3@tester', privileges => ['PROXY', 'SELECT'], require => Mysql_user['proxy3@tester'], } MANIFEST it 'fails' do result = apply_manifest(pp, expect_failures: true) expect(result.stderr).to match(%r{PROXY user not supported on mysql versions < 5\.5\.0}i) end it 'does not find the user' do result = run_shell('mysql -NBe "SHOW GRANTS FOR proxy2@tester"', expect_failures: true) expect(result.stderr).to match(%r{There is no such grant defined for user 'proxy2' on host 'tester'}) end end describe 'adding proxy privileges with invalid proxy user', if: Gem::Version.new(mysql_version) > Gem::Version.new('5.5.0') do pp = <<-MANIFEST mysql_user { 'proxy3@tester': ensure => present, } mysql_grant { 'proxy3@tester/invalid_proxy_user': ensure => 'present', table => 'invalid_proxy_user', user => 'proxy3@tester', privileges => ['PROXY'], require => Mysql_user['proxy3@tester'], } MANIFEST it 'fails' do result = apply_manifest(pp, expect_failures: true) expect(result.stderr).to match(%r{`table` `property` for PROXY should be specified as proxy_user@proxy_host.}) end it 'does not find the user' do result = run_shell('mysql -NBe "SHOW GRANTS FOR proxy3@tester"', expect_failures: true) expect(result.stderr).to contain(%r{There is no such grant defined for user 'proxy3' on host 'tester'}) end end end describe 'grants with skip-name-resolve specified' do pp_one = <<-MANIFEST class { 'mysql::server': override_options => { 'mysqld' => {'skip-name-resolve' => true} }, restart => true, } MANIFEST it 'setup mysql::server' do apply_manifest(pp_one, catch_failures: true) end pp_two = <<-MANIFEST mysql_user { 'test@fqdn.com': ensure => present, } mysql_grant { 'test@fqdn.com/test.*': ensure => 'present', table => 'test.*', user => 'test@fqdn.com', privileges => 'ALL', require => Mysql_user['test@fqdn.com'], } mysql_user { 'test@192.168.5.7': ensure => present, } mysql_grant { 'test@192.168.5.7/test.*': ensure => 'present', table => 'test.*', user => 'test@192.168.5.7', privileges => 'ALL', require => Mysql_user['test@192.168.5.7'], } MANIFEST it 'applies' do apply_manifest(pp_two, catch_failures: true) end it 'fails with fqdn' do pre_run unless Gem::Version.new(mysql_version) > Gem::Version.new('5.7.0') result = run_shell('mysql -NBe "SHOW GRANTS FOR test@fqdn.com"', expect_failures: true) expect(result.stderr).to contain(%r{There is no such grant defined for user 'test' on host 'fqdn.com'}) end end it 'finds ipv4 #stdout' do run_shell("mysql -NBe \"SHOW GRANTS FOR 'test'@'192.168.5.7'\"") do |r| expect(r.stdout).to match(%r{GRANT ALL PRIVILEGES ON `test`.* TO ['|`]test['|`]@['|`]192.168.5.7['|`]}) expect(r.stderr).to be_empty end end pp_three = <<-MANIFEST mysql_user { 'test@fqdn.com': ensure => present, } mysql_grant { 'test@fqdn.com/test.*': ensure => 'present', table => 'test.*', user => 'test@fqdn.com', privileges => 'ALL', require => Mysql_user['test@fqdn.com'], } MANIFEST it 'fails to execute while applying' do mysql_cmd = run_shell('which mysql').stdout.chomp run_shell("mv #{mysql_cmd} #{mysql_cmd}.bak") result = apply_manifest(pp_three, expect_failures: true) expect(result.stderr).to match(%r{Could not find a suitable provider for mysql_grant}) run_shell("mv #{mysql_cmd}.bak #{mysql_cmd}") end pp_four = <<-MANIFEST class { 'mysql::server': restart => true, } MANIFEST it 'reset mysql::server config' do apply_manifest(pp_four, catch_failures: true) end end describe 'adding privileges to specific table' do # Using puppet_apply as a helper pp_one = <<-MANIFEST class { 'mysql::server': override_options => { 'root_password' => 'password' } } MANIFEST it 'setup mysql server' do apply_manifest(pp_one, catch_failures: true) end pp_two = <<-MANIFEST mysql_user { 'test@localhost': ensure => present, } mysql_grant { 'test@localhost/grant_spec_db.grant_spec_table_doesnt_exist': user => 'test@localhost', privileges => ['SELECT'], table => 'grant_spec_db.grant_spec_table_doesnt_exist', require => Mysql_user['test@localhost'], } MANIFEST it 'creates grant on missing table will fail' do result = apply_manifest(pp_two, expect_failures: true) expect(result.stderr).to match(%r{Table 'grant_spec_db\.grant_spec_table_doesnt_exist' doesn't exist}) end pp_three = <<-MANIFEST file { '/tmp/grant_spec_table.sql': ensure => file, content => 'CREATE TABLE grant_spec_table (id int);', before => Mysql::Db['grant_spec_db'], } mysql::db { 'grant_spec_db': user => 'root1', password => 'password', sql => '/tmp/grant_spec_table.sql', } MANIFEST it 'creates table' do apply_manifest(pp_three, catch_failures: true) end it 'has the table' do result = run_shell("mysql -e 'show tables;' grant_spec_db|grep grant_spec_table") expect(result.exit_code).to be_zero end end end diff --git a/spec/acceptance/types/mysql_login_path_spec.rb b/spec/acceptance/types/mysql_login_path_spec.rb index f07aed4..cd4f17c 100644 --- a/spec/acceptance/types/mysql_login_path_spec.rb +++ b/spec/acceptance/types/mysql_login_path_spec.rb @@ -1,266 +1,268 @@ +# frozen_string_literal: true + require 'spec_helper_acceptance' mysql_version = '5.6' support_bin_dir = '/root/mysql_login_path' if os[:family] == 'redhat' && os[:release].to_i == 8 mysql_version = '8.0' elsif os[:family] == 'debian' && os[:release] =~ %r{9|10} mysql_version = '8.0' elsif os[:family] == 'ubuntu' && os[:release] =~ %r{16\.04|18\.04} mysql_version = '5.7' end describe 'mysql_login_path', unless: ("#{os[:family]}-#{os[:release].to_i}" =~ %r{redhat\-5|suse}) do before(:all) do run_shell("rm -rf #{support_bin_dir}") bolt_upload_file('spec/support/mysql_login_path', support_bin_dir) run_shell("cp #{support_bin_dir}/mysql-#{mysql_version}/my_print_defaults /usr/bin/.") run_shell("cp #{support_bin_dir}/mysql-#{mysql_version}/mysql_config_editor /usr/bin/.") end after(:all) do pp_cleanup = <<-MANIFEST user { 'loginpath_test': ensure => absent, } file { '/root/.mylogin.cnf': ensure => absent, } MANIFEST apply_manifest(pp_cleanup, catch_failures: true) run_shell("rm -rf #{support_bin_dir}") end describe 'setup' do pp = <<-MANIFEST if versioncmp($::puppetversion, '6.0.0') < 0 { include resource_api } user { 'loginpath_test': ensure => present, managehome => true, } MANIFEST it 'works with no errors' do apply_manifest(pp, catch_failures: true) end it 'finds mysql_config_editor binary for the provider' do run_shell('mysql_config_editor -V') do |r| expect(r.stdout).to match(%r{Ver.*#{mysql_version}.*x86_64}) end end it 'finds my_print_defaults binary for the provider' do run_shell('my_print_defaults -V') do |r| expect(r.exit_status).to eq(0) end end end context 'for user root' do describe 'add login path' do pp = <<-MANIFEST mysql_login_path { 'local_socket': owner => root, host => 'localhost', user => 'root', password => Sensitive('secure'), socket => '/var/run/mysql/mysql.sock', ensure => present, } mysql_login_path { 'local_tcp': owner => root, host => '127.0.0.1', user => 'network', password => Sensitive('more_secure'), port => 3306, ensure => present, } MANIFEST it 'works without errors' do apply_manifest(pp, catch_failures: true) end it 'finds the login path #stdout' do run_shell('mysql_config_editor print --all') do |r| expect(r.stdout).to match(%r{^\[local_socket\]\n}) expect(r.stdout).to match(%r{host = localhost\n}) expect(r.stdout).to match(%r{user = root\n}) expect(r.stdout).to match(%r{socket = /var/run/mysql/mysql.sock\n}) expect(r.stdout).to match(%r{^\[local_tcp\]\n}) expect(r.stdout).to match(%r{host = 127.0.0.1\n}) expect(r.stdout).to match(%r{user = network\n}) expect(r.stdout).to match(%r{port = 3306\n}) expect(r.stderr).to be_empty end end it 'finds the login path password #stdout' do run_shell('my_print_defaults -s local_socket') do |r| expect(r.stdout).to match(%r{--password=secure\n}) end run_shell('my_print_defaults -s local_tcp') do |r| expect(r.stdout).to match(%r{--password=more_secure\n}) end end end describe 'update login path' do pp = <<-MANIFEST mysql_login_path { 'local_tcp-root': owner => root, host => '10.0.0.1', user => 'network2', password => Sensitive('Fort_kn0X'), port => 3307, ensure => present, } MANIFEST pp2 = <<-MANIFEST mysql_login_path { 'local_tcp-root': ensure => present, host => '192.168.0.1' } MANIFEST it 'works without errors' do apply_manifest(pp, catch_failures: true) end it 'finds the login path #stdout' do run_shell('mysql_config_editor print -G local_tcp') do |r| expect(r.stdout).to match(%r{^\[local_tcp\]\n}) expect(r.stdout).to match(%r{host = 10.0.0.1\n}) expect(r.stdout).to match(%r{user = network2\n}) expect(r.stdout).to match(%r{port = 3307\n}) expect(r.stderr).to be_empty end end it 'finds the login path password #stdout' do run_shell('my_print_defaults -s local_tcp') do |r| expect(r.stdout).to match(%r{--password=Fort_kn0X\n}) end end it 'applies idempotent' do idempotent_apply(pp) end it 'removes values' do apply_manifest(pp2, catch_failures: true) end it 'ensure values are removed #stdout' do run_shell('mysql_config_editor print -G local_tcp') do |r| expect(r.stdout).to match(%r{^\[local_tcp\]\n}) expect(r.stdout).to match(%r{host = 192.168.0.1\n}) expect(r.stdout).not_to match(%r{host = 10.0.0.1\n}) expect(r.stdout).not_to match(%r{user = network2\n}) expect(r.stdout).not_to match(%r{port = 3307\n}) expect(r.stderr).to be_empty end end it 'ensure password removed from the login path #stdout' do run_shell('my_print_defaults -s local_tcp') do |r| expect(r.stdout).not_to match(%r{--password=Fort_kn0X\n}) end end end describe 'delete login path' do pp = <<-MANIFEST mysql_login_path { 'local_socket': owner => root, ensure => absent, } mysql_login_path { 'local_tcp-root': ensure => absent, } MANIFEST it 'works without errors' do apply_manifest(pp, catch_failures: true) end it 'finds the login path #stdout' do run_shell('mysql_config_editor print --all') do |r| expect(r.stdout).not_to match(%r{^\[local_socket\]\n}) expect(r.stdout).not_to match(%r{^\[local_tcp\]\n}) expect(r.stderr).to be_empty end end end end context 'for user loginpath_test' do describe 'add login path' do pp = <<-MANIFEST mysql_login_path { 'local_tcp': owner => loginpath_test, host => '10.0.0.2', user => 'other', password => Sensitive('sensitive'), port => 3306, ensure => present, } MANIFEST it 'works without errors' do apply_manifest(pp, catch_failures: true) end it 'finds the login path #stdout' do run_shell('MYSQL_TEST_LOGIN_FILE=/home/loginpath_test/.mylogin.cnf mysql_config_editor print -G local_tcp') do |r| expect(r.stdout).to match(%r{^\[local_tcp\]\n}) expect(r.stdout).to match(%r{host = 10.0.0.2\n}) expect(r.stdout).to match(%r{user = other\n}) expect(r.stdout).to match(%r{port = 3306\n}) expect(r.stderr).to be_empty end end it 'finds the login path password #stdout' do run_shell('MYSQL_TEST_LOGIN_FILE=/home/loginpath_test/.mylogin.cnf my_print_defaults print -s local_tcp') do |r| expect(r.stdout).to match(%r{--password=sensitive\n}) end end end describe 'update login path' do pp = <<-MANIFEST mysql_login_path { 'local_tcp-loginpath_test': host => '10.0.0.3', user => 'other2', password => Sensitive('password'), port => 3307, ensure => present, } MANIFEST it 'works without errors' do apply_manifest(pp, catch_failures: true) end it 'finds the login path #stdout' do run_shell('MYSQL_TEST_LOGIN_FILE=/home/loginpath_test/.mylogin.cnf mysql_config_editor print -G local_tcp') do |r| expect(r.stdout).to match(%r{^\[local_tcp\]\n}) expect(r.stdout).to match(%r{host = 10.0.0.3\n}) expect(r.stdout).to match(%r{user = other2\n}) expect(r.stdout).to match(%r{port = 3307\n}) expect(r.stderr).to be_empty end end it 'finds the login path password #stdout' do run_shell('MYSQL_TEST_LOGIN_FILE=/home/loginpath_test/.mylogin.cnf my_print_defaults -s local_tcp') do |r| expect(r.stdout).to match(%r{--password=password\n}) end end end describe 'delete login path' do pp = <<-MANIFEST mysql_login_path { 'local_tcp': owner => loginpath_test, ensure => absent, } MANIFEST it 'works without errors' do apply_manifest(pp, catch_failures: true) end it 'finds the login path #stdout' do run_shell('MYSQL_TEST_LOGIN_FILE=/home/loginpath_test/.mylogin.cnf mysql_config_editor print --all') do |r| expect(r.stdout).not_to match(%r{^\[local_tcp\]\n}) expect(r.stderr).to be_empty end end end end end diff --git a/spec/acceptance/types/mysql_plugin_spec.rb b/spec/acceptance/types/mysql_plugin_spec.rb index d4766ec..7780241 100644 --- a/spec/acceptance/types/mysql_plugin_spec.rb +++ b/spec/acceptance/types/mysql_plugin_spec.rb @@ -1,63 +1,65 @@ +# frozen_string_literal: true + require 'spec_helper_acceptance' # Different operating systems (and therefore different versions/forks # of mysql) have varying levels of support for plugins and have # different plugins available. Choose a plugin that works or don't try # to test plugins if not available. if os[:family] == 'redhat' if os[:release].to_i == 5 plugin = nil # Plugins not supported on mysql on RHEL 5 elsif os[:release].to_i == 6 plugin = 'example' plugin_lib = 'ha_example.so' elsif os[:release].to_i == 7 plugin = 'pam' plugin_lib = 'auth_pam.so' end elsif os[:family] == 'debian' if os[:family] == 'ubuntu' - if os[:release] =~ %r{^16\.04|^18\.04} + if %r{^16\.04|^18\.04}.match?(os[:release]) # On Xenial running 5.7.12, the example plugin does not appear to be available. plugin = 'validate_password' plugin_lib = 'validate_password.so' else plugin = 'example' plugin_lib = 'ha_example.so' end end elsif os[:family] == 'suse' plugin = nil # Plugin library path is broken on Suse http://lists.opensuse.org/opensuse-bugs/2013-08/msg01123.html end describe 'mysql_plugin' do if plugin # if plugins are supported describe 'setup' do it 'works with no errors' do pp = <<-MANIFEST class { 'mysql::server': } MANIFEST apply_manifest(pp, catch_failures: true) end end describe 'load plugin' do pp = <<-MANIFEST mysql_plugin { #{plugin}: ensure => present, soname => '#{plugin_lib}', } MANIFEST it 'works without errors' do apply_manifest(pp, catch_failures: true) end it 'finds the plugin #stdout' do run_shell("mysql -NBe \"select plugin_name from information_schema.plugins where plugin_name='#{plugin}'\"") do |r| expect(r.stdout).to match(%r{^#{plugin}$}i) expect(r.stderr).to be_empty end end end end end diff --git a/spec/acceptance/types/mysql_user_spec.rb b/spec/acceptance/types/mysql_user_spec.rb index 72fc45c..f809a1b 100644 --- a/spec/acceptance/types/mysql_user_spec.rb +++ b/spec/acceptance/types/mysql_user_spec.rb @@ -1,253 +1,255 @@ +# frozen_string_literal: true + require 'spec_helper_acceptance' describe 'mysql_user' do describe 'setup' do pp_one = <<-MANIFEST $ed25519_opts = versioncmp($facts['mysql_version'], '10.1.21') >= 0 ? { true => { restart => true, override_options => { 'mysqld' => { 'plugin_load_add' => 'auth_ed25519' } }, }, false => {} } class { 'mysql::server': * => $ed25519_opts } MANIFEST it 'works with no errors' do apply_manifest(pp_one, catch_failures: true) end end context 'using ashp@localhost' do describe 'adding user' do pp_two = <<-MANIFEST mysql_user { 'ashp@localhost': password_hash => '*F9A8E96790775D196D12F53BCC88B8048FF62ED5', } MANIFEST it 'works without errors' do apply_manifest(pp_two, catch_failures: true) end it 'finds the user #stdout' do run_shell("mysql -NBe \"select '1' from mysql.user where CONCAT(user, '@', host) = 'ashp@localhost'\"") do |r| expect(r.stdout).to match(%r{^1$}) expect(r.stderr).to be_empty end end it 'has no SSL options #stdout' do run_shell("mysql -NBe \"select SSL_TYPE from mysql.user where CONCAT(user, '@', host) = 'ashp@localhost'\"") do |r| expect(r.stdout).to match(%r{^\s*$}) expect(r.stderr).to be_empty end end end describe 'changing authentication plugin', if: (Gem::Version.new(mysql_version) > Gem::Version.new('5.5.0') && os[:release] !~ %r{^16\.04}) do it 'works without errors', if: (os[:family] != 'sles' && os[:release].to_i == 15) do pp = <<-EOS mysql_user { 'ashp@localhost': plugin => 'auth_socket', } EOS idempotent_apply(pp) end it 'has the correct plugin', if: (os[:family] != 'sles' && os[:release].to_i == 15) do run_shell("mysql -NBe \"select plugin from mysql.user where CONCAT(user, '@', host) = 'ashp@localhost'\"") do |r| expect(r.stdout.rstrip).to eq('auth_socket') expect(r.stderr).to be_empty end end it 'does not have a password', if: (os[:family] != 'sles' && os[:release].to_i == 15) do pre_run table = if Gem::Version.new(mysql_version) > Gem::Version.new('5.7.0') 'authentication_string' else 'password' end run_shell("mysql -NBe \"select #{table} from mysql.user where CONCAT(user, '@', host) = 'ashp@localhost'\"") do |r| expect(r.stdout.rstrip).to be_empty expect(r.stderr).to be_empty end end end describe 'using ed25519 authentication plugin', if: Gem::Version.new(mysql_version) > Gem::Version.new('10.1.21') do it 'works without errors' do pp = <<-EOS mysql_user { 'ashp@localhost': plugin => 'ed25519', password_hash => 'z0pjExBYbzbupUByZRrQvC6kRCcE8n/tC7kUdUD11fU', } EOS idempotent_apply(pp) end it 'has the correct plugin' do run_shell("mysql -NBe \"select plugin from mysql.user where CONCAT(user, '@', host) = 'ashp@localhost'\"") do |r| expect(r.stdout.rstrip).to eq('ed25519') expect(r.stderr).to be_empty end end end # rubocop:enable RSpec/ExampleLength, RSpec/MultipleExpectations end context 'using ashp-dash@localhost' do describe 'adding user' do pp_three = <<-MANIFEST mysql_user { 'ashp-dash@localhost': password_hash => '*F9A8E96790775D196D12F53BCC88B8048FF62ED5', } MANIFEST it 'works without errors' do apply_manifest(pp_three, catch_failures: true) end it 'finds the user #stdout' do run_shell("mysql -NBe \"select '1' from mysql.user where CONCAT(user, '@', host) = 'ashp-dash@localhost'\"") do |r| expect(r.stdout).to match(%r{^1$}) expect(r.stderr).to be_empty end end end end context 'using ashp@LocalHost' do describe 'adding user' do pp_four = <<-MANIFEST mysql_user { 'ashp@LocalHost': password_hash => '*F9A8E96790775D196D12F53BCC88B8048FF62ED5', } MANIFEST it 'works without errors' do apply_manifest(pp_four, catch_failures: true) end it 'finds the user #stdout' do run_shell("mysql -NBe \"select '1' from mysql.user where CONCAT(user, '@', host) = 'ashp@localhost'\"") do |r| expect(r.stdout).to match(%r{^1$}) expect(r.stderr).to be_empty end end end end context 'using resource should throw no errors' do describe 'find users' do it do result = run_shell('puppet resource mysql_user') expect(result.stdout).not_to match(%r{Error:}) expect(result.stdout).not_to match(%r{must be properly quoted, invalid character:}) end end end context 'using user-w-ssl@localhost with SSL' do describe 'adding user' do pp_five = <<-MANIFEST mysql_user { 'user-w-ssl@localhost': password_hash => '*F9A8E96790775D196D12F53BCC88B8048FF62ED5', tls_options => ['SSL'], } MANIFEST it 'works without errors' do apply_manifest(pp_five, catch_failures: true) end it 'finds the user #stdout' do run_shell("mysql -NBe \"select '1' from mysql.user where CONCAT(user, '@', host) = 'user-w-ssl@localhost'\"") do |r| expect(r.stdout).to match(%r{^1$}) expect(r.stderr).to be_empty end end it 'shows correct ssl_type #stdout' do run_shell("mysql -NBe \"select SSL_TYPE from mysql.user where CONCAT(user, '@', host) = 'user-w-ssl@localhost'\"") do |r| expect(r.stdout).to match(%r{^ANY$}) expect(r.stderr).to be_empty end end end end context 'using user-w-x509@localhost with X509' do describe 'adding user' do pp_six = <<-MANIFEST mysql_user { 'user-w-x509@localhost': password_hash => '*F9A8E96790775D196D12F53BCC88B8048FF62ED5', tls_options => ['X509'], } MANIFEST it 'works without errors' do apply_manifest(pp_six, catch_failures: true) end it 'finds the user #stdout' do run_shell("mysql -NBe \"select '1' from mysql.user where CONCAT(user, '@', host) = 'user-w-x509@localhost'\"") do |r| expect(r.stdout).to match(%r{^1$}) expect(r.stderr).to be_empty end end it 'shows correct ssl_type #stdout' do run_shell("mysql -NBe \"select SSL_TYPE from mysql.user where CONCAT(user, '@', host) = 'user-w-x509@localhost'\"") do |r| expect(r.stdout).to match(%r{^X509$}) expect(r.stderr).to be_empty end end end end context 'using user-w-subject@localhost with ISSUER and SUBJECT' do describe 'adding user' do it 'works without errors' do pp = <<-MANIFEST mysql_user { 'user-w-subject@localhost': tls_options => [ "SUBJECT '/OU=MySQL Users/CN=username'", "ISSUER '/CN=Certificate Authority'", "CIPHER 'EDH-RSA-DES-CBC3-SHA'", ], } MANIFEST idempotent_apply(pp) end it 'finds the user #stdout' do run_shell("mysql -NBe \"select '1' from mysql.user where CONCAT(user, '@', host) = 'user-w-subject@localhost'\"") do |r| expect(r.stdout).to match(%r{^1$}) expect(r.stderr).to be_empty end end it 'shows correct ssl_type #stdout' do run_shell("mysql -NBe \"select SSL_TYPE from mysql.user where CONCAT(user, '@', host) = 'user-w-subject@localhost'\"") do |r| expect(r.stdout).to match(%r{^SPECIFIED$}) expect(r.stderr).to be_empty end end it 'shows correct x509_issuer #stdout' do run_shell("mysql -NBe \"select X509_ISSUER from mysql.user where CONCAT(user, '@', host) = 'user-w-subject@localhost'\"") do |r| expect(r.stdout).to match(%r{^/CN=Certificate Authority$}) expect(r.stderr).to be_empty end end it 'shows correct x509_subject #stdout' do run_shell("mysql -NBe \"select X509_SUBJECT from mysql.user where CONCAT(user, '@', host) = 'user-w-subject@localhost'\"") do |r| expect(r.stdout).to match(%r{^/OU=MySQL Users/CN=username$}) expect(r.stderr).to be_empty end end it 'shows correct ssl_cipher #stdout' do run_shell("mysql -NBe \"select SSL_CIPHER from mysql.user where CONCAT(user, '@', host) = 'user-w-subject@localhost'\"") do |r| expect(r.stdout).to match(%r{^EDH-RSA-DES-CBC3-SHA$}) expect(r.stderr).to be_empty end end end end end diff --git a/spec/classes/graceful_failures_spec.rb b/spec/classes/graceful_failures_spec.rb index 22a677b..6380e00 100644 --- a/spec/classes/graceful_failures_spec.rb +++ b/spec/classes/graceful_failures_spec.rb @@ -1,16 +1,18 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'mysql::server' do context 'on an unsupported OS' do let(:facts) do { osfamily: 'UNSUPPORTED', operatingsystem: 'UNSUPPORTED', } end it 'gracefully fails' do is_expected.to compile.and_raise_error(%r{Unsupported platform:}) end end end diff --git a/spec/classes/mycnf_template_spec.rb b/spec/classes/mycnf_template_spec.rb index dc64191..d15514b 100644 --- a/spec/classes/mycnf_template_spec.rb +++ b/spec/classes/mycnf_template_spec.rb @@ -1,164 +1,166 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'mysql::server' do on_supported_os.each do |os, facts| context "my.cnf template - on #{os}" do let(:facts) do facts.merge(root_home: '/root') end context 'normal entry' do let(:params) { { override_options: { 'mysqld' => { 'socket' => '/var/lib/mysql/mysql.sock' } } } } it do is_expected.to contain_file('mysql-config-file').with(mode: '0644', selinux_ignore_defaults: true).with_content(%r{socket = \/var\/lib\/mysql\/mysql.sock}) end end describe 'array entry' do let(:params) { { override_options: { 'mysqld' => { 'replicate-do-db' => ['base1', 'base2'] } } } } it do is_expected.to contain_file('mysql-config-file').with_content( %r{.*replicate-do-db = base1\nreplicate-do-db = base2.*}, ) end end describe 'skip-name-resolve set to an empty string' do let(:params) { { override_options: { 'mysqld' => { 'skip-name-resolve' => '' } } } } it { is_expected.to contain_file('mysql-config-file').with_content(%r{^skip-name-resolve$}) } end describe 'ssl set to true' do let(:params) { { override_options: { 'mysqld' => { 'ssl' => true } } } } it { is_expected.to contain_file('mysql-config-file').with_content(%r{ssl}) } it { is_expected.to contain_file('mysql-config-file').without_content(%r{ssl = true}) } end describe 'ssl set to false' do let(:params) { { override_options: { 'mysqld' => { 'ssl' => false } } } } it { is_expected.to contain_file('mysql-config-file').with_content(%r{ssl = false}) } end # ssl-disable (and ssl) are special cased within mysql. describe 'possibility of disabling ssl completely' do let(:params) { { override_options: { 'mysqld' => { 'ssl' => true, 'ssl-disable' => true } } } } it { is_expected.to contain_file('mysql-config-file').without_content(%r{ssl = true}) } end describe 'a non ssl option set to true' do let(:params) { { override_options: { 'mysqld' => { 'test' => true } } } } it { is_expected.to contain_file('mysql-config-file').with_content(%r{^test$}) } it { is_expected.to contain_file('mysql-config-file').without_content(%r{test = true}) } end context 'with includedir' do let(:params) { { includedir: '/etc/my.cnf.d' } } it 'makes the directory' do is_expected.to contain_file('/etc/my.cnf.d').with(ensure: :directory, mode: '0755') end it { is_expected.to contain_file('mysql-config-file').with_content(%r{!includedir}) } end context 'without includedir' do let(:params) { { includedir: '' } } it 'shouldnt contain the directory' do is_expected.not_to contain_file('mysql-config-file').with(ensure: :directory, mode: '0755') end it { is_expected.to contain_file('mysql-config-file').without_content(%r{!includedir}) } end context 'with file mode 0644' do let(:params) { { 'config_file_mode' => '0644' } } it do is_expected.to contain_file('mysql-config-file').with(mode: '0644') end end context 'with file mode 0664' do let(:params) { { 'config_file_mode' => '0664' } } it do is_expected.to contain_file('mysql-config-file').with(mode: '0664') end end context 'with file mode 0660' do let(:params) { { 'config_file_mode' => '0660' } } it do is_expected.to contain_file('mysql-config-file').with(mode: '0660') end end context 'with file mode 0641' do let(:params) { { 'config_file_mode' => '0641' } } it do is_expected.to contain_file('mysql-config-file').with(mode: '0641') end end context 'with file mode 0610' do let(:params) { { 'config_file_mode' => '0610' } } it do is_expected.to contain_file('mysql-config-file').with(mode: '0610') end end context 'with file 0600' do let(:params) { { 'config_file_mode' => '0600' } } it do is_expected.to contain_file('mysql-config-file').with(mode: '0600') end end context 'user owner 12345' do let(:params) { { 'mycnf_owner' => '12345' } } it do is_expected.to contain_file('mysql-config-file').with( owner: '12345', ) end end context 'group owner 12345' do let(:params) { { 'mycnf_group' => '12345' } } it do is_expected.to contain_file('mysql-config-file').with( group: '12345', ) end end context 'user and group owner 12345' do let(:params) { { 'mycnf_owner' => '12345', 'mycnf_group' => '12345' } } it do is_expected.to contain_file('mysql-config-file').with( owner: '12345', group: '12345', ) end end end end end diff --git a/spec/classes/mysql_backup_mysqldump_spec.rb b/spec/classes/mysql_backup_mysqldump_spec.rb index b5bb0e8..b9dfbcf 100644 --- a/spec/classes/mysql_backup_mysqldump_spec.rb +++ b/spec/classes/mysql_backup_mysqldump_spec.rb @@ -1,56 +1,58 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'mysql::backup::mysqldump' do on_supported_os.each do |os, facts| context "on #{os}" do let(:pre_condition) do <<-EOF class { 'mysql::server': } EOF end let(:facts) do facts.merge(root_home: '/root') end let(:default_params) do { 'backupuser' => 'testuser', 'backuppassword' => 'testpass', 'backupdir' => '/tmp/mysql-backup', 'backuprotate' => '25', 'delete_before_dump' => true, 'execpath' => '/usr/bin:/usr/sbin:/bin:/sbin:/opt/zimbra/bin', 'maxallowedpacket' => '1M' } end context 'with time included' do let(:params) do { time: [23, 59, 30, 12, 6] }.merge(default_params) end it { is_expected.to contain_cron('mysql-backup').with( hour: 23, minute: 59, monthday: 30, month: 12, weekday: 6, ) } end context 'with defaults' do let(:params) { default_params } it { is_expected.to contain_cron('mysql-backup').with( command: '/usr/local/sbin/mysqlbackup.sh', ensure: 'present', hour: 23, minute: 5, ) } end end end # rubocop:enable RSpec/NestedGroups end diff --git a/spec/classes/mysql_backup_xtrabackup_spec.rb b/spec/classes/mysql_backup_xtrabackup_spec.rb index a4daa2f..6caf49a 100644 --- a/spec/classes/mysql_backup_xtrabackup_spec.rb +++ b/spec/classes/mysql_backup_xtrabackup_spec.rb @@ -1,232 +1,234 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'mysql::backup::xtrabackup' do on_supported_os.each do |os, facts| context "on #{os}" do let(:pre_condition) do <<-EOF class { 'mysql::server': } EOF end let(:facts) do facts.merge(root_home: '/root') end let(:default_params) do { 'backupdir' => '/tmp' } end context 'with defaults' do let(:params) do default_params end it 'contains the wrapper script' do is_expected.to contain_file('xtrabackup.sh').with_content( %r{(\n*^xtrabackup\s+.*\$@)}, ) end package = if facts[:osfamily] == 'RedHat' if Puppet::Util::Package.versioncmp(facts[:operatingsystemmajrelease], '8') >= 0 'percona-xtrabackup-24' elsif Puppet::Util::Package.versioncmp(facts[:operatingsystemmajrelease], '7') >= 0 'percona-xtrabackup' else 'percona-xtrabackup-20' end elsif facts[:operatingsystem] == 'Debian' 'percona-xtrabackup-24' elsif facts[:operatingsystem] == 'Ubuntu' if Puppet::Util::Package.versioncmp(facts[:operatingsystemmajrelease], '16') >= 0 'percona-xtrabackup' else 'percona-xtrabackup-24' end elsif facts[:osfamily] == 'Suse' 'xtrabackup' else 'percona-xtrabackup' end it 'contains the weekly cronjob' do is_expected.to contain_cron('xtrabackup-weekly') .with( ensure: 'present', command: '/usr/local/sbin/xtrabackup.sh --target-dir=/tmp/$(date +\%F)_full --backup', user: 'root', hour: '23', minute: '5', weekday: '0', ) .that_requires("Package[#{package}]") end it 'contains the daily cronjob for weekdays 1-6' do dateformat = case facts[:osfamily] when 'FreeBSD', 'OpenBSD' '$(date -v-sun +\%F)_full' else '$(date -d "last sunday" +\%F)_full' end is_expected.to contain_cron('xtrabackup-daily') .with( ensure: 'present', command: "/usr/local/sbin/xtrabackup.sh --incremental-basedir=/tmp/#{dateformat} --target-dir=/tmp/$(date +\\\%F_\\\%H-\\\%M-\\\%S) --backup", user: 'root', hour: '23', minute: '5', weekday: '1-6', ) .that_requires("Package[#{package}]") end end context 'with backupuser and backuppassword' do let(:params) do { backupuser: 'backupuser', backuppassword: 'backuppassword' }.merge(default_params) end it 'contains the defined mysql user' do is_expected.to contain_mysql_user('backupuser@localhost') .with( ensure: 'present', password_hash: '*4110E08DF51E70A4BA1D4E33A84205E38CF3FE58', ) .that_requires('Class[mysql::server::root_password]') is_expected.to contain_mysql_grant('backupuser@localhost/*.*') .with( ensure: 'present', user: 'backupuser@localhost', table: '*.*', privileges: ['RELOAD', 'PROCESS', 'LOCK TABLES', 'REPLICATION CLIENT'], ) .that_requires('Mysql_user[backupuser@localhost]') end end context 'with additional cron args' do let(:params) do { additional_cron_args: '--backup --skip-ssl' }.merge(default_params) end package = if facts[:osfamily] == 'RedHat' if Puppet::Util::Package.versioncmp(facts[:operatingsystemmajrelease], '8') >= 0 'percona-xtrabackup-24' elsif Puppet::Util::Package.versioncmp(facts[:operatingsystemmajrelease], '7') >= 0 'percona-xtrabackup' else 'percona-xtrabackup-20' end elsif facts[:operatingsystem] == 'Debian' 'percona-xtrabackup-24' elsif facts[:operatingsystem] == 'Ubuntu' if Puppet::Util::Package.versioncmp(facts[:operatingsystemmajrelease], '16') >= 0 'percona-xtrabackup' else 'percona-xtrabackup-24' end elsif facts[:osfamily] == 'Suse' 'xtrabackup' else 'percona-xtrabackup' end dateformat = case facts[:osfamily] when 'FreeBSD', 'OpenBSD' '$(date -v-sun +\%F)_full' else '$(date -d "last sunday" +\%F)_full' end it 'contains the weekly cronjob' do is_expected.to contain_cron('xtrabackup-weekly') .with( ensure: 'present', command: '/usr/local/sbin/xtrabackup.sh --target-dir=/tmp/$(date +\%F)_full --backup --skip-ssl', user: 'root', hour: '23', minute: '5', weekday: '0', ) .that_requires("Package[#{package}]") end it 'contains the daily cronjob for weekdays 1-6' do is_expected.to contain_cron('xtrabackup-daily') .with( ensure: 'present', command: "/usr/local/sbin/xtrabackup.sh --incremental-basedir=/tmp/#{dateformat} --target-dir=/tmp/$(date +\\\%F_\\\%H-\\\%M-\\\%S) --backup --skip-ssl", user: 'root', hour: '23', minute: '5', weekday: '1-6', ) .that_requires("Package[#{package}]") end end context 'with deactivated incremental backups' do let(:params) do { incremental_backups: false }.merge(default_params) end it 'not contains the weekly cronjob' do is_expected.not_to contain_cron('xtrabackup-weekly') end it 'contains the daily cronjob with all weekdays' do is_expected.to contain_cron('xtrabackup-daily').with( ensure: 'present', command: '/usr/local/sbin/xtrabackup.sh --target-dir=/tmp/$(date +\%F_\%H-\%M-\%S) --backup', user: 'root', hour: '23', minute: '5', weekday: '*', ) end end context 'with prescript defined' do let(:params) do { prescript: ['rsync -a /tmp backup01.local-lan:', 'rsync -a /tmp backup02.local-lan:'] }.merge(default_params) end it 'contains the prescript' do is_expected.to contain_file('xtrabackup.sh').with_content( %r{.*rsync -a \/tmp backup01.local-lan:\n\nrsync -a \/tmp backup02.local-lan:.*}, ) end end context 'with postscript defined' do let(:params) do { postscript: ['rsync -a /tmp backup01.local-lan:', 'rsync -a /tmp backup02.local-lan:'] }.merge(default_params) end it 'contains the prostscript' do is_expected.to contain_file('xtrabackup.sh').with_content( %r{.*rsync -a \/tmp backup01.local-lan:\n\nrsync -a \/tmp backup02.local-lan:.*}, ) end end context 'with mariabackup' do let(:params) do { backupmethod: 'mariabackup' }.merge(default_params) end it 'contain the mariabackup executor' do is_expected.to contain_file('xtrabackup.sh').with_content( %r{(\n*^mariabackup\s+.*\$@)}, ) end end end end # rubocop:enable RSpec/NestedGroups end diff --git a/spec/classes/mysql_bindings_spec.rb b/spec/classes/mysql_bindings_spec.rb index 1c2283b..ba0dc26 100644 --- a/spec/classes/mysql_bindings_spec.rb +++ b/spec/classes/mysql_bindings_spec.rb @@ -1,33 +1,35 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'mysql::bindings' do on_supported_os.each do |os, facts| next if facts[:osfamily] == 'Archlinux' context "on #{os}" do let(:facts) do facts.merge(root_home: '/root') end let(:params) do { 'java_enable' => true, 'perl_enable' => true, 'php_enable' => true, 'python_enable' => true, 'ruby_enable' => true, 'client_dev' => true, 'daemon_dev' => true, 'client_dev_package_name' => 'libmysqlclient-devel', 'daemon_dev_package_name' => 'mysql-devel', } end it { is_expected.to contain_package('mysql-connector-java') } it { is_expected.to contain_package('perl_mysql') } it { is_expected.to contain_package('python-mysqldb') } it { is_expected.to contain_package('ruby_mysql') } it { is_expected.to contain_package('mysql-client_dev') } it { is_expected.to contain_package('mysql-daemon_dev') } end end end diff --git a/spec/classes/mysql_client_spec.rb b/spec/classes/mysql_client_spec.rb index b44b3a2..18c1450 100644 --- a/spec/classes/mysql_client_spec.rb +++ b/spec/classes/mysql_client_spec.rb @@ -1,51 +1,53 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'mysql::client' do on_supported_os.each do |os, facts| context "on #{os}" do let(:facts) do facts.merge(root_home: '/root') end context 'with defaults' do it { is_expected.not_to contain_class('mysql::bindings') } it { is_expected.to contain_package('mysql_client') } end context 'with bindings enabled' do let(:params) { { bindings_enable: true } } it { is_expected.to contain_class('mysql::bindings') } it { is_expected.to contain_package('mysql_client') } end context 'with package_manage set to true' do let(:params) { { package_manage: true } } it { is_expected.to contain_package('mysql_client') } end context 'with package_manage set to false' do let(:params) { { package_manage: false } } it { is_expected.not_to contain_package('mysql_client') } end context 'with package provider' do let(:params) do { package_provider: 'dpkg', package_source: '/somewhere', } end it do is_expected.to contain_package('mysql_client').with( provider: 'dpkg', source: '/somewhere', ) end end end end end diff --git a/spec/classes/mysql_server_account_security_spec.rb b/spec/classes/mysql_server_account_security_spec.rb index 5b5e6a7..3750f5c 100644 --- a/spec/classes/mysql_server_account_security_spec.rb +++ b/spec/classes/mysql_server_account_security_spec.rb @@ -1,83 +1,85 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'mysql::server::account_security' do on_supported_os.each do |os, facts| context "on #{os}" do let(:pre_condition) do <<-EOF anchor {'mysql::server::end': } EOF end context 'with fqdn==myhost.mydomain' do let(:facts) do facts.merge(root_home: '/root', fqdn: 'myhost.mydomain', hostname: 'myhost') end ['root@myhost.mydomain', 'root@127.0.0.1', 'root@::1', '@myhost.mydomain', '@localhost', '@%'].each do |user| - it "removes Mysql_User[#{user}]" do # rubocop:disable RSpec/RepeatedExample + it "removes Mysql_User[#{user}]" do # rubocop:disable RSpec/RepeatedExample,RSpec/RepeatedDescription is_expected.to contain_mysql_user(user).with_ensure('absent') end end # When the hostname doesn't match the fqdn we also remove these. # We don't need to test the inverse as when they match they are # covered by the above list. ['root@myhost', '@myhost'].each do |user| - it "removes Mysql_User[#{user}]" do # rubocop:disable RSpec/RepeatedExample + it "removes Mysql_User[#{user}]" do # rubocop:disable RSpec/RepeatedExample,RSpec/RepeatedDescription is_expected.to contain_mysql_user(user).with_ensure('absent') end end it 'removes Mysql_database[test]' do is_expected.to contain_mysql_database('test').with_ensure('absent') end end context 'with fqdn==localhost' do let(:facts) do facts.merge(root_home: '/root', fqdn: 'localhost', hostname: 'localhost') end ['root@127.0.0.1', 'root@::1', '@localhost', 'root@localhost.localdomain', '@localhost.localdomain', '@%'].each do |user| it "removes Mysql_User[#{user}] for fqdn==localhost" do is_expected.to contain_mysql_user(user).with_ensure('absent') end end end context 'with fqdn==localhost.localdomain' do let(:facts) do facts.merge(root_home: '/root', fqdn: 'localhost.localdomain', hostname: 'localhost') end ['root@127.0.0.1', 'root@::1', '@localhost', 'root@localhost.localdomain', '@localhost.localdomain', '@%'].each do |user| it "removes Mysql_User[#{user}] for fqdn==localhost.localdomain" do is_expected.to contain_mysql_user(user).with_ensure('absent') end end end end end end diff --git a/spec/classes/mysql_server_backup_spec.rb b/spec/classes/mysql_server_backup_spec.rb index 02c9fa9..217f1bc 100644 --- a/spec/classes/mysql_server_backup_spec.rb +++ b/spec/classes/mysql_server_backup_spec.rb @@ -1,388 +1,390 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'mysql::server::backup' do on_supported_os.each do |os, facts| context "on #{os}" do let(:pre_condition) do <<-EOF class { 'mysql::server': } EOF end let(:facts) do facts.merge(root_home: '/root') end let(:default_params) do { 'backupuser' => 'testuser', 'backuppassword' => 'testpass', 'backupdir' => '/tmp/mysql-backup', 'backuprotate' => '25', 'delete_before_dump' => true, 'execpath' => '/usr/bin:/usr/sbin:/bin:/sbin:/opt/zimbra/bin', 'maxallowedpacket' => '1M' } end context 'standard conditions' do let(:params) { default_params } # Cannot use that_requires here, doesn't work on classes. it { is_expected.to contain_mysql_user('testuser@localhost').with( require: 'Class[Mysql::Server::Root_password]', ) } it { is_expected.to contain_mysql_grant('testuser@localhost/*.*').with( privileges: ['SELECT', 'RELOAD', 'LOCK TABLES', 'SHOW VIEW', 'PROCESS'], ).that_requires('Mysql_user[testuser@localhost]') } context 'with triggers included' do let(:params) do { include_triggers: true }.merge(default_params) end it { is_expected.to contain_mysql_grant('testuser@localhost/*.*').with( privileges: ['SELECT', 'RELOAD', 'LOCK TABLES', 'SHOW VIEW', 'PROCESS', 'TRIGGER'], ).that_requires('Mysql_user[testuser@localhost]') } end it { is_expected.to contain_cron('mysql-backup').with( command: '/usr/local/sbin/mysqlbackup.sh', ensure: 'present', ) } it { is_expected.to contain_file('mysqlbackup.sh').with( path: '/usr/local/sbin/mysqlbackup.sh', ensure: 'present', ) } it { is_expected.to contain_file('/tmp/mysql-backup').with( ensure: 'directory', ) } it 'has compression by default' do is_expected.to contain_file('mysqlbackup.sh').with_content( %r{bzcat -zc}, ) end it 'skips backing up events table by default' do is_expected.to contain_file('mysqlbackup.sh').with_content( %r{ADDITIONAL_OPTIONS="--ignore-table=mysql.event"}, ) end it 'does not mention triggers by default because file_per_database is false' do is_expected.to contain_file('mysqlbackup.sh').without_content( %r{.*triggers.*}, ) end it 'does not mention routines by default because file_per_database is false' do is_expected.to contain_file('mysqlbackup.sh').without_content( %r{.*routines.*}, ) end it 'has 25 days of rotation' do # MySQL counts from 0 is_expected.to contain_file('mysqlbackup.sh').with_content(%r{.*ROTATE=24.*}) end it 'has a standard PATH' do is_expected.to contain_file('mysqlbackup.sh').with_content(%r{PATH=/usr/bin:/usr/sbin:/bin:/sbin:/opt/zimbra/bin}) end end context 'with delete after dump' do let(:custom_params) do { 'delete_before_dump' => false, } end let(:params) do default_params.merge!(custom_params) end it { is_expected.to contain_file('mysqlbackup.sh').with_content(%r{touch /tmp/mysqlbackup_success}) } end context 'with delete after dump and custom success file path' do let(:custom_params) do { 'delete_before_dump' => false, 'backup_success_file_path' => '/opt/mysqlbackup_success', } end let(:params) do default_params.merge!(custom_params) end it { is_expected.to contain_file('mysqlbackup.sh').with_content(%r{touch /opt/mysqlbackup_success}) } end context 'custom ownership and mode for backupdir' do let(:params) do { backupdirmode: '0750', backupdirowner: 'testuser', backupdirgroup: 'testgrp' }.merge(default_params) end it { is_expected.to contain_file('/tmp/mysql-backup').with( ensure: 'directory', mode: '0750', owner: 'testuser', group: 'testgrp', ) } end context 'with compression disabled' do let(:params) do { backupcompress: false }.merge(default_params) end it { is_expected.to contain_file('mysqlbackup.sh').with( path: '/usr/local/sbin/mysqlbackup.sh', ensure: 'present', ) } it 'is able to disable compression' do is_expected.to contain_file('mysqlbackup.sh').without_content( %r{.*bzcat -zc.*}, ) end end context 'with mysql.events backedup' do let(:params) do { ignore_events: false }.merge(default_params) end it { is_expected.to contain_file('mysqlbackup.sh').with( path: '/usr/local/sbin/mysqlbackup.sh', ensure: 'present', ) } it 'is able to backup events table' do is_expected.to contain_file('mysqlbackup.sh').with_content( %r{ADDITIONAL_OPTIONS="--events"}, ) end end context 'with database list specified' do let(:params) do { backupdatabases: ['mysql'] }.merge(default_params) end it { is_expected.to contain_file('mysqlbackup.sh').with( path: '/usr/local/sbin/mysqlbackup.sh', ensure: 'present', ) } it 'has a backup file for each database' do is_expected.to contain_file('mysqlbackup.sh').with_content( %r{mysql | bzcat -zc \${DIR}\\\${PREFIX}mysql_`date'}, ) end it 'skips backup triggers by default' do is_expected.to contain_file('mysqlbackup.sh').with_content( %r{ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --skip-triggers"}, ) end it 'skips backing up routines by default' do is_expected.to contain_file('mysqlbackup.sh').with_content( %r{ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --skip-routines"}, ) end context 'with include_triggers set to true' do let(:params) do default_params.merge(backupdatabases: ['mysql'], include_triggers: true) end it 'backups triggers when asked' do is_expected.to contain_file('mysqlbackup.sh').with_content( %r{ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --triggers"}, ) end end context 'with include_triggers set to false' do let(:params) do default_params.merge(backupdatabases: ['mysql'], include_triggers: false) end it 'skips backing up triggers when asked to skip' do is_expected.to contain_file('mysqlbackup.sh').with_content( %r{ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --skip-triggers"}, ) end end context 'with include_routines set to true' do let(:params) do default_params.merge(backupdatabases: ['mysql'], include_routines: true) end it 'backups routines when asked' do is_expected.to contain_file('mysqlbackup.sh').with_content( %r{ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --routines"}, ) end end context 'with include_routines set to false' do let(:params) do default_params.merge(backupdatabases: ['mysql'], include_triggers: true) end it 'skips backing up routines when asked to skip' do is_expected.to contain_file('mysqlbackup.sh').with_content( %r{ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --skip-routines"}, ) end end end context 'with file per database' do let(:params) do default_params.merge(file_per_database: true) end it 'loops through backup all databases' do is_expected.to contain_file('mysqlbackup.sh').with_content(%r{.*SHOW DATABASES.*}) end context 'with compression disabled' do let(:params) do default_params.merge(file_per_database: true, backupcompress: false) end it 'loops through backup all databases without compression #show databases' do is_expected.to contain_file('mysqlbackup.sh').with_content(%r{.*SHOW DATABASES.*}) end it 'loops through backup all databases without compression #bzcat' do is_expected.to contain_file('mysqlbackup.sh').without_content(%r{.*bzcat -zc.*}) end end it 'skips backup triggers by default' do is_expected.to contain_file('mysqlbackup.sh').with_content( %r{ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --skip-triggers"}, ) end it 'skips backing up routines by default' do is_expected.to contain_file('mysqlbackup.sh').with_content( %r{ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --skip-routines"}, ) end context 'with include_triggers set to true' do let(:params) do default_params.merge(file_per_database: true, include_triggers: true) end it 'backups triggers when asked' do is_expected.to contain_file('mysqlbackup.sh').with_content( %r{ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --triggers"}, ) end end context 'with include_triggers set to false' do let(:params) do default_params.merge(file_per_database: true, include_triggers: false) end it 'skips backing up triggers when asked to skip' do is_expected.to contain_file('mysqlbackup.sh').with_content( %r{ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --skip-triggers"}, ) end end context 'with include_routines set to true' do let(:params) do default_params.merge(file_per_database: true, include_routines: true) end it 'backups routines when asked' do is_expected.to contain_file('mysqlbackup.sh').with_content( %r{ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --routines"}, ) end end context 'with include_routines set to false' do let(:params) do default_params.merge(file_per_database: true, include_triggers: true) end it 'skips backing up routines when asked to skip' do is_expected.to contain_file('mysqlbackup.sh').with_content( %r{ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --skip-routines"}, ) end end end context 'with postscript' do let(:params) do default_params.merge(postscript: 'rsync -a /tmp backup01.local-lan:') end it 'is add postscript' do is_expected.to contain_file('mysqlbackup.sh').with_content( %r{rsync -a \/tmp backup01.local-lan:}, ) end end context 'with postscripts' do let(:params) do default_params.merge(postscript: [ 'rsync -a /tmp backup01.local-lan:', 'rsync -a /tmp backup02.local-lan:', ]) end it 'is add postscript' do is_expected.to contain_file('mysqlbackup.sh').with_content( %r{.*rsync -a \/tmp backup01.local-lan:\n\nrsync -a \/tmp backup02.local-lan:.*}, ) end end end end # rubocop:enable RSpec/NestedGroups end diff --git a/spec/classes/mysql_server_monitor_spec.rb b/spec/classes/mysql_server_monitor_spec.rb index 1a79ad7..da5115f 100644 --- a/spec/classes/mysql_server_monitor_spec.rb +++ b/spec/classes/mysql_server_monitor_spec.rb @@ -1,36 +1,38 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'mysql::server::monitor' do on_supported_os.each do |os, facts| context "on #{os}" do let(:facts) do facts.merge(root_home: '/root') end let :pre_condition do "include 'mysql::server'" end let :default_params do { mysql_monitor_username: 'monitoruser', mysql_monitor_password: 'monitorpass', mysql_monitor_hostname: 'monitorhost', } end let :params do default_params end it { is_expected.to contain_mysql_user('monitoruser@monitorhost') } it { is_expected.to contain_mysql_grant('monitoruser@monitorhost/*.*').with( ensure: 'present', user: 'monitoruser@monitorhost', table: '*.*', privileges: ['PROCESS', 'SUPER'], require: 'Mysql_user[monitoruser@monitorhost]' ) } end end end diff --git a/spec/classes/mysql_server_mysqltuner_spec.rb b/spec/classes/mysql_server_mysqltuner_spec.rb index ec31f1b..0c118ee 100644 --- a/spec/classes/mysql_server_mysqltuner_spec.rb +++ b/spec/classes/mysql_server_mysqltuner_spec.rb @@ -1,35 +1,37 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'mysql::server::mysqltuner' do context 'ensure => present' do it { is_expected.to compile } it { is_expected.to contain_file('/usr/local/bin/mysqltuner') } end context 'ensure => absent' do let(:params) { { ensure: 'absent' } } it { is_expected.to compile } it { is_expected.to contain_file('/usr/local/bin/mysqltuner').with(ensure: 'absent') } end context 'custom version' do let(:params) { { version: 'v1.2.0' } } it { is_expected.to compile } it { is_expected.to contain_file('/usr/local/bin/mysqltuner') } end context 'custom source' do let(:params) { { source: '/tmp/foo' } } it { is_expected.to compile } it { is_expected.to contain_file('/usr/local/bin/mysqltuner') } end end diff --git a/spec/classes/mysql_server_spec.rb b/spec/classes/mysql_server_spec.rb index 066140b..3edcd57 100644 --- a/spec/classes/mysql_server_spec.rb +++ b/spec/classes/mysql_server_spec.rb @@ -1,296 +1,298 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'mysql::server' do on_supported_os.each do |os, facts| context "on #{os}" do let(:facts) do facts.merge(root_home: '/root') end context 'with defaults' do it { is_expected.to contain_class('mysql::server::install') } it { is_expected.to contain_class('mysql::server::config') } it { is_expected.to contain_class('mysql::server::service') } it { is_expected.to contain_class('mysql::server::root_password') } it { is_expected.to contain_class('mysql::server::providers') } end context 'with remove_default_accounts set' do let(:params) { { remove_default_accounts: true } } it { is_expected.to contain_class('mysql::server::account_security') } end context 'when not managing config file' do let(:params) { { manage_config_file: false } } it { is_expected.to compile.with_all_deps } end context 'when not managing the service' do let(:params) { { service_manage: false } } it { is_expected.to compile.with_all_deps } it { is_expected.not_to contain_service('mysqld') } end context 'configuration options' do context 'when specifying both $override_options and $options' do let(:params) do { override_options: { 'mysqld' => { 'datadir' => '/tmp' } }, options: { 'mysqld' => { 'max_allowed_packet' => '12M' } }, } end it { is_expected.to compile.and_raise_error(%r{You can't specify \$options and \$override_options simultaneously, see the README section 'Customize server options'!}) } end context 'when specifying $options' do let(:params) do { options: { 'mysqld' => { 'datadir' => '/tmp' } }, } end it { is_expected.to compile.with_all_deps } it { is_expected.to contain_mysql_datadir('/tmp') } it { is_expected.not_to contain_mysql_bind_addr('127.0.0.1') } end end context 'mysql::server::install' do it 'contains the package by default' do is_expected.to contain_package('mysql-server').with(ensure: :present) end context 'with package_manage set to true' do let(:params) { { package_manage: true } } it { is_expected.to contain_package('mysql-server') } end context 'with package_manage set to false' do let(:params) { { package_manage: false } } it { is_expected.not_to contain_package('mysql-server') } end context 'with datadir overridden' do let(:params) { { override_options: { 'mysqld' => { 'datadir' => '/tmp' } } } } it { is_expected.to contain_mysql_datadir('/tmp') } end context 'with package provider' do let(:params) do { package_provider: 'dpkg', package_source: '/somewhere', } end it do is_expected.to contain_package('mysql-server').with( provider: 'dpkg', source: '/somewhere', ) end end end context 'mysql::server::service' do context 'with defaults' do it { is_expected.to contain_service('mysqld') } end context 'with package_manage set to true' do let(:params) { { package_manage: true } } it { is_expected.to contain_service('mysqld').that_requires('Package[mysql-server]') } end context 'with package_manage set to false' do let(:params) { { package_manage: false } } it { is_expected.to contain_service('mysqld') } it { is_expected.not_to contain_service('mysqld').that_requires('Package[mysql-server]') } end context 'service_enabled set to false' do let(:params) { { service_enabled: false } } it do is_expected.to contain_service('mysqld').with(ensure: :stopped) end context 'with package_manage set to true' do let(:params) { { package_manage: true } } it { is_expected.to contain_package('mysql-server') } end context 'with package_manage set to false' do let(:params) { { package_manage: false } } it { is_expected.not_to contain_package('mysql-server') } end context 'with datadir overridden' do let(:params) { { override_options: { 'mysqld' => { 'datadir' => '/tmp' } } } } it { is_expected.to contain_mysql_datadir('/tmp') } end end context 'with log-error overridden' do let(:params) { { override_options: { 'mysqld' => { 'log-error' => '/tmp/error.log' } } } } it { is_expected.to contain_file('/tmp/error.log') } end context 'default bind-address' do it { is_expected.to contain_file('mysql-config-file').with_content(%r{^bind-address = 127.0.0.1}) } end context 'with defined bind-address' do let(:params) { { override_options: { 'mysqld' => { 'bind-address' => '1.1.1.1' } } } } it { is_expected.to contain_file('mysql-config-file').with_content(%r{^bind-address = 1.1.1.1}) } end context 'without bind-address' do let(:params) { { override_options: { 'mysqld' => { 'bind-address' => :undef } } } } it { is_expected.to contain_file('mysql-config-file').without_content(%r{^bind-address}) } end end context 'mysql::server::root_password' do describe 'when defaults' do it { is_expected.to contain_exec('remove install pass').with( command: 'mysqladmin -u root --password=$(grep -o \'[^ ]\\+$\' /.mysql_secret) password \'\' && rm -f /.mysql_secret', onlyif: 'test -f /.mysql_secret', path: '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin', ) } it { is_expected.not_to contain_mysql_user('root@localhost') } it { is_expected.not_to contain_file('/root/.my.cnf') } end describe 'when root_password set' do let(:params) { { root_password: 'SET' } } it { is_expected.to contain_mysql_user('root@localhost') } if Puppet.version.to_f >= 3.0 it { is_expected.to contain_file('/root/.my.cnf').with(show_diff: false).that_requires('Mysql_user[root@localhost]') } else it { is_expected.to contain_file('/root/.my.cnf').that_requires('Mysql_user[root@localhost]') } end end describe 'when root_password set, create_root_user set to false' do let(:params) { { root_password: 'SET', create_root_user: false } } it { is_expected.not_to contain_mysql_user('root@localhost') } if Puppet.version.to_f >= 3.0 it { is_expected.to contain_file('/root/.my.cnf').with(show_diff: false) } else it { is_expected.to contain_file('/root/.my.cnf') } end end describe 'when root_password set, create_root_my_cnf set to false' do let(:params) { { root_password: 'SET', create_root_my_cnf: false } } it { is_expected.to contain_mysql_user('root@localhost') } it { is_expected.not_to contain_file('/root/.my.cnf') } end describe 'when root_password set, create_root_user and create_root_my_cnf set to false' do let(:params) { { root_password: 'SET', create_root_user: false, create_root_my_cnf: false } } it { is_expected.not_to contain_mysql_user('root@localhost') } it { is_expected.not_to contain_file('/root/.my.cnf') } end describe 'when install_secret_file set to /root/.mysql_secret' do let(:params) { { install_secret_file: '/root/.mysql_secret' } } it { is_expected.to contain_exec('remove install pass').with( command: 'mysqladmin -u root --password=$(grep -o \'[^ ]\\+$\' /root/.mysql_secret) password \'\' && rm -f /root/.mysql_secret', onlyif: 'test -f /root/.mysql_secret', ) } end end context 'mysql::server::providers' do describe 'with users' do let(:params) do { users: { 'foo@localhost' => { 'max_connections_per_hour' => '1', 'max_queries_per_hour' => '2', 'max_updates_per_hour' => '3', 'max_user_connections' => '4', 'password_hash' => '*F3A2A51A9B0F2BE2468926B4132313728C250DBF', }, 'foo2@localhost' => {}, } } end it { is_expected.to contain_mysql_user('foo@localhost').with( max_connections_per_hour: '1', max_queries_per_hour: '2', max_updates_per_hour: '3', max_user_connections: '4', password_hash: '*F3A2A51A9B0F2BE2468926B4132313728C250DBF' ) } it { is_expected.to contain_mysql_user('foo2@localhost').with( max_connections_per_hour: nil, max_queries_per_hour: nil, max_updates_per_hour: nil, max_user_connections: nil, password_hash: nil ) } end describe 'with grants' do let(:params) do { grants: { 'foo@localhost/somedb.*' => { 'user' => 'foo@localhost', 'table' => 'somedb.*', 'privileges' => ['SELECT', 'UPDATE'], 'options' => ['GRANT'], }, 'foo2@localhost/*.*' => { 'user' => 'foo2@localhost', 'table' => '*.*', 'privileges' => ['SELECT'], }, } } end it { is_expected.to contain_mysql_grant('foo@localhost/somedb.*').with( user: 'foo@localhost', table: 'somedb.*', privileges: ['SELECT', 'UPDATE'], options: ['GRANT'] ) } it { is_expected.to contain_mysql_grant('foo2@localhost/*.*').with( user: 'foo2@localhost', table: '*.*', privileges: ['SELECT'], options: nil ) } end describe 'with databases' do let(:params) do { databases: { 'somedb' => { 'charset' => 'latin1', 'collate' => 'latin1', }, 'somedb2' => {}, } } end it { is_expected.to contain_mysql_database('somedb').with( charset: 'latin1', collate: 'latin1', ) } it { is_expected.to contain_mysql_database('somedb2') } end end end end # rubocop:enable RSpec/NestedGroups end diff --git a/spec/defines/mysql_db_spec.rb b/spec/defines/mysql_db_spec.rb index 8c175bf..688e7b7 100644 --- a/spec/defines/mysql_db_spec.rb +++ b/spec/defines/mysql_db_spec.rb @@ -1,82 +1,84 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'mysql::db', type: :define do on_supported_os.each do |os, facts| context "on #{os}" do let(:facts) do facts.merge(root_home: '/root') end let(:title) { 'test_db' } let(:params) do { 'user' => 'testuser', 'password' => 'testpass', 'mysql_exec_path' => '' } end it 'does not notify the import sql exec if no sql script was provided' do is_expected.to contain_mysql_database('test_db').without_notify end it 'subscribes to database if sql script is given' do params['sql'] = 'test_sql' is_expected.to contain_mysql_database('test_db') is_expected.to contain_exec('test_db-import').with_subscribe('Mysql_database[test_db]') end it 'onlies import sql script on creation if not enforcing' do params.merge!('sql' => 'test_sql', 'enforce_sql' => false) is_expected.to contain_exec('test_db-import').with_refreshonly(true) end it 'imports sql script on creation' do params.merge!('sql' => 'test_sql', 'enforce_sql' => true) # ' if enforcing #refreshonly' is_expected.to contain_exec('test_db-import').with_refreshonly(false) # 'if enforcing #command' is_expected.to contain_exec('test_db-import').with_command('cat test_sql | mysql test_db') end it 'imports sql script with custom command on creation ' do params.merge!('sql' => 'test_sql', 'enforce_sql' => true, 'import_cat_cmd' => 'zcat') # if enforcing #refreshonly is_expected.to contain_exec('test_db-import').with_refreshonly(false) # if enforcing #command is_expected.to contain_exec('test_db-import').with_command('zcat test_sql | mysql test_db') end it 'imports sql scripts when more than one is specified' do params['sql'] = ['test_sql', 'test_2_sql'] is_expected.to contain_exec('test_db-import').with_command('cat test_sql test_2_sql | mysql test_db') end it 'does not create database' do params.merge!('ensure' => 'absent', 'host' => 'localhost') is_expected.to contain_mysql_database('test_db').with_ensure('absent') is_expected.to contain_mysql_user('testuser@localhost').with_ensure('absent') end it 'creates with an appropriate collate and charset' do params.merge!('charset' => 'utf8', 'collate' => 'utf8_danish_ci') is_expected.to contain_mysql_database('test_db').with('charset' => 'utf8', 'collate' => 'utf8_danish_ci') end it 'uses dbname parameter as database name instead of name' do params['dbname'] = 'real_db' is_expected.to contain_mysql_database('real_db') end it 'uses tls_options for user when set' do params['tls_options'] = ['SSL'] is_expected.to contain_mysql_user('testuser@localhost').with_tls_options(['SSL']) end it 'uses grant_options for grant when set' do params['grant_options'] = ['GRANT'] is_expected.to contain_mysql_grant('testuser@localhost/test_db.*').with_options(['GRANT']) end end end end diff --git a/spec/functions/mysql_normalise_and_deepmerge_spec.rb b/spec/functions/mysql_normalise_and_deepmerge_spec.rb index d1c94f8..67cabfa 100644 --- a/spec/functions/mysql_normalise_and_deepmerge_spec.rb +++ b/spec/functions/mysql_normalise_and_deepmerge_spec.rb @@ -1,92 +1,94 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'mysql::normalise_and_deepmerge' do it 'exists' do is_expected.not_to eq(nil) end it 'throws error with no arguments' do is_expected.to run.with_params.and_raise_error(Puppet::ParseError) end it 'throws error with only one argument' do is_expected.to run.with_params('one' => 1).and_raise_error(Puppet::ParseError) end it 'accepts empty strings as puppet undef' do is_expected.to run.with_params({}, '') end # rubocop:disable RSpec/NamedSubject index_values = ['one', 'two', 'three'] expected_values_one = ['1', '2', '2'] it 'merge two hashes' do new_hash = subject.execute({ 'one' => '1', 'two' => '1' }, 'two' => '2', 'three' => '2') index_values.each_with_index do |index, expected| expect(new_hash[index]).to eq(expected_values_one[expected]) end end it 'merges multiple hashes' do hash = subject.execute({ 'one' => 1 }, { 'one' => '2' }, 'one' => '3') expect(hash['one']).to eq('3') end it 'accepts empty hashes' do is_expected.to run.with_params({}, {}, {}).and_return({}) end expected_values_two = [1, 2, 'four' => 4] it 'merges subhashes' do hash = subject.execute({ 'one' => 1 }, 'two' => 2, 'three' => { 'four' => 4 }) index_values.each_with_index do |index, expected| expect(hash[index]).to eq(expected_values_two[expected]) end end it 'appends to subhashes' do hash = subject.execute({ 'one' => { 'two' => 2 } }, 'one' => { 'three' => 3 }) expect(hash['one']).to eq('two' => 2, 'three' => 3) end expected_values_three = [1, 'dos', { 'four' => 4, 'five' => 5 }] it 'appends to subhashes 2' do hash = subject.execute({ 'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } }, 'two' => 'dos', 'three' => { 'five' => 5 }) index_values.each_with_index do |index, expected| expect(hash[index]).to eq(expected_values_three[expected]) end end index_values_two = ['key1', 'key2'] expected_values_four = [{ 'a' => 1, 'b' => 99 }, 'c' => 3] it 'appends to subhashes 3' do hash = subject.execute({ 'key1' => { 'a' => 1, 'b' => 2 }, 'key2' => { 'c' => 3 } }, 'key1' => { 'b' => 99 }) index_values_two.each_with_index do |index, expected| expect(hash[index]).to eq(expected_values_four[expected]) end end it 'equates keys mod dash and underscore #value' do hash = subject.execute({ 'a-b-c' => 1 }, 'a_b_c' => 10) expect(hash['a_b_c']).to eq(10) end it 'equates keys mod dash and underscore #not' do hash = subject.execute({ 'a-b-c' => 1 }, 'a_b_c' => 10) expect(hash).not_to have_key('a-b-c') end index_values_three = ['a_b_c', 'b-c-d'] expected_values_five = [10, { 'e-f-g' => 3, 'c_d_e' => 12 }] index_values_error = ['a-b-c', 'b_c_d'] index_values_three.each_with_index do |index, expected| it 'keeps style of the last when keys are equal mod dash and underscore #value' do hash = subject.execute({ 'a-b-c' => 1, 'b_c_d' => { 'c-d-e' => 2, 'e-f-g' => 3 } }, 'a_b_c' => 10, 'b-c-d' => { 'c_d_e' => 12 }) expect(hash[index]).to eq(expected_values_five[expected]) end it 'keeps style of the last when keys are equal mod dash and underscore #not' do hash = subject.execute({ 'a-b-c' => 1, 'b_c_d' => { 'c-d-e' => 2, 'e-f-g' => 3 } }, 'a_b_c' => 10, 'b-c-d' => { 'c_d_e' => 12 }) expect(hash).not_to have_key(index_values_error[expected]) end end # rubocop:enable RSpec/NamedSubject end diff --git a/spec/functions/mysql_password_spec.rb b/spec/functions/mysql_password_spec.rb index a1dfffc..dbe4441 100644 --- a/spec/functions/mysql_password_spec.rb +++ b/spec/functions/mysql_password_spec.rb @@ -1,41 +1,43 @@ +# frozen_string_literal: true + require 'spec_helper' shared_examples 'mysql::password function' do it 'exists' do is_expected.not_to eq(nil) end it 'raises a ArgumentError if there is less than 1 arguments' do is_expected.to run.with_params.and_raise_error(ArgumentError) end it 'raises a ArgumentError if there is more than 1 arguments' do is_expected.to run.with_params('foo', 'bar').and_raise_error(ArgumentError) end it 'converts password into a hash' do is_expected.to run.with_params('password').and_return('*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19') end it 'password should be String' do is_expected.to run.with_params(123).and_raise_error(ArgumentError) end it 'converts an empty password into a empty string' do is_expected.to run.with_params('').and_return('') end it 'does not convert a password that is already a hash' do is_expected.to run.with_params('*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19').and_return('*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19') end end describe 'mysql::password' do it_behaves_like 'mysql::password function' describe 'non-namespaced shim' do describe 'mysql_password', type: :puppet_function do it_behaves_like 'mysql::password function' end end end diff --git a/spec/functions/mysql_strip_hash_spec.rb b/spec/functions/mysql_strip_hash_spec.rb index be67046..1bc60b6 100644 --- a/spec/functions/mysql_strip_hash_spec.rb +++ b/spec/functions/mysql_strip_hash_spec.rb @@ -1,27 +1,29 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'mysql::strip_hash' do it 'exists' do is_expected.not_to eq(nil) end it 'raises a ArgumentError if there is less than 1 arguments' do is_expected.to run.with_params.and_raise_error(ArgumentError) end it 'raises a ArgumentError if there is more than 1 arguments' do is_expected.to run.with_params({ 'foo' => 1 }, 'bar' => 2).and_raise_error(ArgumentError) end it 'raises a ArgumentError if argument is not a hash' do is_expected.to run.with_params('foo').and_raise_error(ArgumentError) end it 'passes a hash without blanks through' do is_expected.to run.with_params('one' => 1, 'two' => 2, 'three' => 3).and_return('one' => 1, 'two' => 2, 'three' => 3) end it 'removes blank hash elements' do is_expected.to run.with_params('one' => 1, 'two' => '', 'three' => nil, 'four' => 4).and_return('one' => 1, 'three' => nil, 'four' => 4) end end diff --git a/spec/spec_helper_local.rb b/spec/spec_helper_local.rb index 0a0c2d7..937b686 100644 --- a/spec/spec_helper_local.rb +++ b/spec/spec_helper_local.rb @@ -1,31 +1,33 @@ +# frozen_string_literal: true + require 'rspec-puppet-facts' include RspecPuppetFacts if ENV['COVERAGE'] == 'yes' require 'simplecov' require 'simplecov-console' require 'codecov' SimpleCov.formatters = [ SimpleCov::Formatter::HTMLFormatter, SimpleCov::Formatter::Console, SimpleCov::Formatter::Codecov, ] SimpleCov.start do track_files 'lib/**/*.rb' add_filter '/spec' # do not track vendored files add_filter '/vendor' add_filter '/.vendor' # do not track gitignored files # this adds about 4 seconds to the coverage check # this could definitely be optimized add_filter do |f| # system returns true if exit status is 0, which with git-check-ignore means file is ignored system("git check-ignore --quiet #{f.filename}") end end end diff --git a/spec/unit/facter/mysql_server_id_spec.rb b/spec/unit/facter/mysql_server_id_spec.rb index aab8c52..f09de60 100644 --- a/spec/unit/facter/mysql_server_id_spec.rb +++ b/spec/unit/facter/mysql_server_id_spec.rb @@ -1,36 +1,38 @@ +# frozen_string_literal: true + require 'spec_helper' describe Facter::Util::Fact.to_s do before(:each) do Facter.clear end describe 'mysql_server_id' do context "igalic's laptop" do before :each do Facter.fact(:macaddress).stubs(:value).returns('3c:97:0e:69:fb:e1') end it do Facter.fact(:mysql_server_id).value.to_s.should == '241857808' end end context 'node with lo only' do before :each do Facter.fact(:macaddress).stubs(:value).returns('00:00:00:00:00:00') end it do Facter.fact(:mysql_server_id).value.to_s.should == '1' end end context 'test nil case' do before :each do Facter.fact(:macaddress).stubs(:value).returns(nil) end it do Facter.fact(:mysql_server_id).value.to_s.should == '' end end end end diff --git a/spec/unit/facter/mysql_version_spec.rb b/spec/unit/facter/mysql_version_spec.rb index 07ddbc6..abcc6fc 100644 --- a/spec/unit/facter/mysql_version_spec.rb +++ b/spec/unit/facter/mysql_version_spec.rb @@ -1,19 +1,21 @@ +# frozen_string_literal: true + require 'spec_helper' describe Facter::Util::Fact.to_s do before(:each) do Facter.clear end describe 'mysql_version' do context 'with value' do before :each do Facter::Core::Execution.stubs(:which).returns('fake_mysql_path') Facter::Util::Resolution.stubs(:exec).with('mysql --version').returns('mysql Ver 14.12 Distrib 5.0.95, for redhat-linux-gnu (x86_64) using readline 5.1') end it { expect(Facter.fact(:mysql_version).value).to eq('5.0.95') } end end end diff --git a/spec/unit/facter/mysqld_version_spec.rb b/spec/unit/facter/mysqld_version_spec.rb index 3d38e3c..2be7108 100644 --- a/spec/unit/facter/mysqld_version_spec.rb +++ b/spec/unit/facter/mysqld_version_spec.rb @@ -1,19 +1,21 @@ +# frozen_string_literal: true + require 'spec_helper' describe Facter::Util::Fact.to_s do before(:each) do Facter.clear end describe 'mysqld_version' do context 'with value' do before :each do Facter::Core::Execution.stubs(:which).with('mysqld').returns('/usr/sbin/mysqld') Facter::Util::Resolution.stubs(:exec).with('mysqld --no-defaults -V 2>/dev/null').returns('mysqld Ver 5.5.49-37.9 for Linux on x86_64 (Percona Server (GPL), Release 37.9, Revision efa0073)') end it { expect(Facter.fact(:mysqld_version).value).to eq('mysqld Ver 5.5.49-37.9 for Linux on x86_64 (Percona Server (GPL), Release 37.9, Revision efa0073)') } end end end diff --git a/spec/unit/puppet/provider/mysql_database/mysql_spec.rb b/spec/unit/puppet/provider/mysql_database/mysql_spec.rb index 4e29f33..5147398 100644 --- a/spec/unit/puppet/provider/mysql_database/mysql_spec.rb +++ b/spec/unit/puppet/provider/mysql_database/mysql_spec.rb @@ -1,112 +1,114 @@ +# frozen_string_literal: true + require 'spec_helper' describe Puppet::Type.type(:mysql_database).provider(:mysql) do let(:defaults_file) { '--defaults-extra-file=/root/.my.cnf' } let(:parsed_databases) { ['information_schema', 'mydb', 'mysql', 'performance_schema', 'test'] } let(:provider) { resource.provider } let(:instance) { provider.class.instances.first } let(:resource) do Puppet::Type.type(:mysql_database).new( ensure: :present, charset: 'latin1', collate: 'latin1_swedish_ci', name: 'new_database', provider: described_class.name ) end let(:raw_databases) do <<-SQL_OUTPUT information_schema mydb mysql performance_schema test SQL_OUTPUT # rubocop:enable Layout/IndentHeredoc end before :each do Facter.stubs(:value).with(:root_home).returns('/root') Puppet::Util.stubs(:which).with('mysql').returns('/usr/bin/mysql') File.stubs(:file?).with('/root/.my.cnf').returns(true) provider.class.stubs(:mysql_caller).with('show databases', 'regular').returns('new_database') - provider.class.stubs(:mysql_caller).with(["show variables like '%_database'", 'new_database'], 'regular').returns("character_set_database latin1\ncollation_database latin1_swedish_ci\nskip_show_database OFF") # rubocop:disable Metrics/LineLength + provider.class.stubs(:mysql_caller).with(["show variables like '%_database'", 'new_database'], 'regular').returns("character_set_database latin1\ncollation_database latin1_swedish_ci\nskip_show_database OFF") # rubocop:disable Layout/LineLength end describe 'self.instances' do it 'returns an array of databases' do provider.class.stubs(:mysql_caller).with('show databases', 'regular').returns(raw_databases) raw_databases.each_line do |db| - provider.class.stubs(:mysql_caller).with(["show variables like '%_database'", db.chomp], 'regular').returns("character_set_database latin1\ncollation_database latin1_swedish_ci\nskip_show_database OFF") # rubocop:disable Metrics/LineLength + provider.class.stubs(:mysql_caller).with(["show variables like '%_database'", db.chomp], 'regular').returns("character_set_database latin1\ncollation_database latin1_swedish_ci\nskip_show_database OFF") # rubocop:disable Layout/LineLength end databases = provider.class.instances.map { |x| x.name } expect(parsed_databases).to match_array(databases) end end describe 'self.prefetch' do it 'exists' do provider.class.instances provider.class.prefetch({}) end end describe 'create' do it 'makes a database' do provider.class.expects(:mysql_caller).with("create database if not exists `#{resource[:name]}` character set `#{resource[:charset]}` collate `#{resource[:collate]}`", 'regular') provider.expects(:exists?).returns(true) expect(provider.create).to be_truthy end end describe 'destroy' do it 'removes a database if present' do provider.class.expects(:mysql_caller).with("drop database if exists `#{resource[:name]}`", 'regular') provider.expects(:exists?).returns(false) expect(provider.destroy).to be_truthy end end describe 'exists?' do it 'checks if database exists' do expect(instance).to be_exists end end describe 'self.defaults_file' do it 'sets --defaults-extra-file' do File.stubs(:file?).with('/root/.my.cnf').returns(true) expect(provider.defaults_file).to eq '--defaults-extra-file=/root/.my.cnf' end it 'fails if file missing' do File.stubs(:file?).with('/root/.my.cnf').returns(false) expect(provider.defaults_file).to be_nil end end describe 'charset' do it 'returns a charset' do expect(instance.charset).to eq('latin1') end end describe 'charset=' do it 'changes the charset' do provider.class.expects(:mysql_caller).with("alter database `#{resource[:name]}` CHARACTER SET blah", 'regular').returns('0') provider.charset = 'blah' end end describe 'collate' do it 'returns a collate' do expect(instance.collate).to eq('latin1_swedish_ci') end end describe 'collate=' do it 'changes the collate' do provider.class.expects(:mysql_caller).with("alter database `#{resource[:name]}` COLLATE blah", 'regular').returns('0') provider.collate = 'blah' end end end diff --git a/spec/unit/puppet/provider/mysql_plugin/mysql_spec.rb b/spec/unit/puppet/provider/mysql_plugin/mysql_spec.rb index 5dea0f7..2a4bad6 100644 --- a/spec/unit/puppet/provider/mysql_plugin/mysql_spec.rb +++ b/spec/unit/puppet/provider/mysql_plugin/mysql_spec.rb @@ -1,68 +1,70 @@ +# frozen_string_literal: true + require 'spec_helper' describe Puppet::Type.type(:mysql_plugin).provider(:mysql) do let(:defaults_file) { '--defaults-extra-file=/root/.my.cnf' } let(:provider) { resource.provider } let(:instance) { provider.class.instances.first } let(:resource) do Puppet::Type.type(:mysql_plugin).new( ensure: :present, soname: 'auth_socket.so', name: 'auth_socket', provider: described_class.name, ) end before :each do Facter.stubs(:value).with(:root_home).returns('/root') Puppet::Util.stubs(:which).with('mysql').returns('/usr/bin/mysql') File.stubs(:file?).with('/root/.my.cnf').returns(true) provider.class.stubs(:mysql_caller).with('show plugins', 'regular').returns('auth_socket ACTIVE AUTHENTICATION auth_socket.so GPL') end describe 'self.prefetch' do it 'exists' do provider.class.instances provider.class.prefetch({}) end end describe 'create' do it 'loads a plugin' do provider.class.expects(:mysql_caller).with("install plugin #{resource[:name]} soname '#{resource[:soname]}'", 'regular') provider.expects(:exists?).returns(true) expect(provider.create).to be_truthy end end describe 'destroy' do it 'unloads a plugin if present' do provider.class.expects(:mysql_caller).with("uninstall plugin #{resource[:name]}", 'regular') provider.expects(:exists?).returns(false) expect(provider.destroy).to be_truthy end end describe 'exists?' do it 'checks if plugin exists' do expect(instance).to be_exists end end describe 'self.defaults_file' do it 'sets --defaults-extra-file' do File.stubs(:file?).with('/root/.my.cnf').returns(true) expect(provider.defaults_file).to eq '--defaults-extra-file=/root/.my.cnf' end it 'fails if file missing' do File.stubs(:file?).with('/root/.my.cnf').returns(false) expect(provider.defaults_file).to be_nil end end describe 'soname' do it 'returns a soname' do expect(instance.soname).to eq('auth_socket.so') end end end diff --git a/spec/unit/puppet/provider/mysql_user/mysql_spec.rb b/spec/unit/puppet/provider/mysql_user/mysql_spec.rb index 5c8d1dc..ba72cc1 100644 --- a/spec/unit/puppet/provider/mysql_user/mysql_spec.rb +++ b/spec/unit/puppet/provider/mysql_user/mysql_spec.rb @@ -1,495 +1,497 @@ +# frozen_string_literal: true + require 'spec_helper' describe Puppet::Type.type(:mysql_user).provider(:mysql) do # Output of mysqld -V mysql_version_string_hash = { 'mysql-5.5' => { version: '5.5.46', string: '/usr/sbin/mysqld Ver 5.5.46-log for Linux on x86_64 (MySQL Community Server (GPL))', mysql_type: 'mysql', }, 'mysql-5.6' => { version: '5.6.27', string: '/usr/sbin/mysqld Ver 5.6.27 for Linux on x86_64 (MySQL Community Server (GPL))', mysql_type: 'mysql', }, 'mysql-5.7.1' => { version: '5.7.1', string: '/usr/sbin/mysqld Ver 5.7.1 for Linux on x86_64 (MySQL Community Server (GPL))', mysql_type: 'mysql', }, 'mysql-5.7.6' => { version: '5.7.8', string: '/usr/sbin/mysqld Ver 5.7.8-rc for Linux on x86_64 (MySQL Community Server (GPL))', mysql_type: 'mysql', }, 'mariadb-10.0' => { version: '10.0.21', string: '/usr/sbin/mysqld Ver 10.0.21-MariaDB for Linux on x86_64 (MariaDB Server)', mysql_type: 'mariadb', }, 'mariadb-10.0-deb8' => { version: '10.0.23', string: '/usr/sbin/mysqld (mysqld 10.0.23-MariaDB-0+deb8u1)', mysql_type: 'mariadb', }, 'mariadb-10.1.44' => { version: '10.1.44', string: '/usr/sbin/mysqld (mysqld 10.1.44-MariaDB-1~bionic)', mysql_type: 'mariadb', }, 'mariadb-10.3.22' => { version: '10.3.22', string: '/usr/sbin/mysqld (mysqld 10.3.22-MariaDB-0+deb10u1)', mysql_type: 'mariadb', }, 'percona-5.5' => { version: '5.5.39', string: 'mysqld Ver 5.5.39-36.0-55 for Linux on x86_64 (Percona XtraDB Cluster (GPL), Release rel36.0, Revision 824, WSREP version 25.11, wsrep_25.11.r4023)', mysql_type: 'percona', }, } let(:defaults_file) { '--defaults-extra-file=/root/.my.cnf' } let(:system_database) { '--database=mysql' } let(:newhash) { '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5' } let(:raw_users) do <<-SQL_OUTPUT root@127.0.0.1 root@::1 @localhost debian-sys-maint@localhost root@localhost usvn_user@localhost @vagrant-ubuntu-raring-64 SQL_OUTPUT # rubocop:enable Layout/IndentHeredoc end let(:parsed_users) { ['root@127.0.0.1', 'root@::1', '@localhost', 'debian-sys-maint@localhost', 'root@localhost', 'usvn_user@localhost', '@vagrant-ubuntu-raring-64'] } let(:provider) { resource.provider } let(:instance) { provider.class.instances.first } let(:resource) do Puppet::Type.type(:mysql_user).new( ensure: :present, password_hash: '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4', name: 'joe@localhost', max_user_connections: '10', max_connections_per_hour: '10', max_queries_per_hour: '10', max_updates_per_hour: '10', provider: described_class.name, ) end before :each do # Set up the stubs for an instances call. Facter.stubs(:value).with(:root_home).returns('/root') Facter.stubs(:value).with(:mysql_version).returns('5.6.24') provider.class.instance_variable_set(:@mysqld_version_string, '5.6.24') Puppet::Util.stubs(:which).with('mysql').returns('/usr/bin/mysql') Puppet::Util.stubs(:which).with('mysqld').returns('/usr/sbin/mysqld') File.stubs(:file?).with('/root/.my.cnf').returns(true) provider.class.stubs(:mysql_caller).with("SELECT CONCAT(User, '@',Host) AS User FROM mysql.user", 'regular').returns('joe@localhost') - provider.class.stubs(:mysql_caller).with("SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = 'joe@localhost'", 'regular').returns('10 10 10 10 *6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4') # rubocop:disable Metrics/LineLength + provider.class.stubs(:mysql_caller).with("SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = 'joe@localhost'", 'regular').returns('10 10 10 10 *6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4') # rubocop:disable Layout/LineLength end describe 'self.instances' do it 'returns an array of users MySQL 5.5' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.5'][:string]) provider.class.stubs(:mysql_caller).with("SELECT CONCAT(User, '@',Host) AS User FROM mysql.user", 'regular').returns(raw_users) - parsed_users.each { |user| provider.class.stubs(:mysql_caller).with("SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'", 'regular').returns('10 10 10 10 ') } # rubocop:disable Metrics/LineLength + parsed_users.each { |user| provider.class.stubs(:mysql_caller).with("SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'", 'regular').returns('10 10 10 10 ') } # rubocop:disable Layout/LineLength usernames = provider.class.instances.map { |x| x.name } expect(parsed_users).to match_array(usernames) end it 'returns an array of users MySQL 5.6' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.6'][:string]) provider.class.stubs(:mysql_caller).with("SELECT CONCAT(User, '@',Host) AS User FROM mysql.user", 'regular').returns(raw_users) - parsed_users.each { |user| provider.class.stubs(:mysql_caller).with("SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'", 'regular').returns('10 10 10 10 ') } # rubocop:disable Metrics/LineLength + parsed_users.each { |user| provider.class.stubs(:mysql_caller).with("SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'", 'regular').returns('10 10 10 10 ') } # rubocop:disable Layout/LineLength usernames = provider.class.instances.map { |x| x.name } expect(parsed_users).to match_array(usernames) end it 'returns an array of users MySQL >= 5.7.0 < 5.7.6' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.7.1'][:string]) provider.class.stubs(:mysql_caller).with("SELECT CONCAT(User, '@',Host) AS User FROM mysql.user", 'regular').returns(raw_users) - parsed_users.each { |user| provider.class.stubs(:mysql_caller).with("SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'", 'regular').returns('10 10 10 10 ') } # rubocop:disable Metrics/LineLength + parsed_users.each { |user| provider.class.stubs(:mysql_caller).with("SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'", 'regular').returns('10 10 10 10 ') } # rubocop:disable Layout/LineLength usernames = provider.class.instances.map { |x| x.name } expect(parsed_users).to match_array(usernames) end it 'returns an array of users MySQL >= 5.7.6' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.7.6'][:string]) provider.class.stubs(:mysql_caller).with("SELECT CONCAT(User, '@',Host) AS User FROM mysql.user", 'regular').returns(raw_users) - parsed_users.each { |user| provider.class.stubs(:mysql_caller).with("SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, AUTHENTICATION_STRING, PLUGIN FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'", 'regular').returns('10 10 10 10 ') } # rubocop:disable Metrics/LineLength + parsed_users.each { |user| provider.class.stubs(:mysql_caller).with("SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, AUTHENTICATION_STRING, PLUGIN FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'", 'regular').returns('10 10 10 10 ') } # rubocop:disable Layout/LineLength usernames = provider.class.instances.map { |x| x.name } expect(parsed_users).to match_array(usernames) end it 'returns an array of users mariadb 10.0' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mariadb-10.0'][:string]) provider.class.stubs(:mysql_caller).with("SELECT CONCAT(User, '@',Host) AS User FROM mysql.user", 'regular').returns(raw_users) - parsed_users.each { |user| provider.class.stubs(:mysql_caller).with("SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'", 'regular').returns('10 10 10 10 ') } # rubocop:disable Metrics/LineLength + parsed_users.each { |user| provider.class.stubs(:mysql_caller).with("SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'", 'regular').returns('10 10 10 10 ') } # rubocop:disable Layout/LineLength usernames = provider.class.instances.map { |x| x.name } expect(parsed_users).to match_array(usernames) end it 'returns an array of users mariadb >= 10.1.21' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mariadb-10.1.44'][:string]) provider.class.stubs(:mysql_caller).with("SELECT CONCAT(User, '@',Host) AS User FROM mysql.user", 'regular').returns(raw_users) - parsed_users.each { |user| provider.class.stubs(:mysql_caller).with("SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD, PLUGIN, AUTHENTICATION_STRING FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'", 'regular').returns('10 10 10 10 ') } # rubocop:disable Metrics/LineLength + parsed_users.each { |user| provider.class.stubs(:mysql_caller).with("SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD, PLUGIN, AUTHENTICATION_STRING FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'", 'regular').returns('10 10 10 10 ') } # rubocop:disable Layout/LineLength usernames = provider.class.instances.map { |x| x.name } expect(parsed_users).to match_array(usernames) end it 'returns an array of users percona 5.5' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['percona-5.5'][:string]) provider.class.stubs(:mysql_caller).with("SELECT CONCAT(User, '@',Host) AS User FROM mysql.user", 'regular').returns(raw_users) - parsed_users.each { |user| provider.class.stubs(:mysql_caller).with("SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'", 'regular').returns('10 10 10 10 ') } # rubocop:disable Metrics/LineLength + parsed_users.each { |user| provider.class.stubs(:mysql_caller).with("SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'", 'regular').returns('10 10 10 10 ') } # rubocop:disable Layout/LineLength usernames = provider.class.instances.map { |x| x.name } expect(parsed_users).to match_array(usernames) end end describe 'mysql version and type detection' do mysql_version_string_hash.each do |_name, line| version = line[:version] string = line[:string] mysql_type = line[:mysql_type] it "detects version '#{version}'" do provider.class.instance_variable_set(:@mysqld_version_string, string) expect(provider.mysqld_version).to eq(version) end it "detects type '#{mysql_type}'" do provider.class.instance_variable_set(:@mysqld_version_string, string) expect(provider.mysqld_type).to eq(mysql_type) end end end describe 'self.prefetch' do it 'exists' do provider.class.instances provider.class.prefetch({}) end end describe 'create' do it 'makes a user' do provider.class.expects(:mysql_caller).with("CREATE USER 'joe'@'localhost' IDENTIFIED BY PASSWORD '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4'", 'system') - provider.class.expects(:mysql_caller).with("GRANT USAGE ON *.* TO 'joe'@'localhost' WITH MAX_USER_CONNECTIONS 10 MAX_CONNECTIONS_PER_HOUR 10 MAX_QUERIES_PER_HOUR 10 MAX_UPDATES_PER_HOUR 10", 'system') # rubocop:disable Metrics/LineLength + provider.class.expects(:mysql_caller).with("GRANT USAGE ON *.* TO 'joe'@'localhost' WITH MAX_USER_CONNECTIONS 10 MAX_CONNECTIONS_PER_HOUR 10 MAX_QUERIES_PER_HOUR 10 MAX_UPDATES_PER_HOUR 10", 'system') # rubocop:disable Layout/LineLength provider.class.expects(:mysql_caller).with("GRANT USAGE ON *.* TO 'joe'@'localhost' REQUIRE NONE", 'system') provider.expects(:exists?).returns(true) expect(provider.create).to be_truthy end it 'creates a user using IF NOT EXISTS' do provider.class.instance_variable_set(:@mysqld_version_string, '5.7.6') - provider.class.expects(:mysql_caller).with("CREATE USER IF NOT EXISTS 'joe'@'localhost' IDENTIFIED WITH 'mysql_native_password' AS '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4'", 'system') # rubocop:disable Metrics/LineLength - provider.class.expects(:mysql_caller).with("ALTER USER IF EXISTS 'joe'@'localhost' WITH MAX_USER_CONNECTIONS 10 MAX_CONNECTIONS_PER_HOUR 10 MAX_QUERIES_PER_HOUR 10 MAX_UPDATES_PER_HOUR 10", 'system') # rubocop:disable Metrics/LineLength + provider.class.expects(:mysql_caller).with("CREATE USER IF NOT EXISTS 'joe'@'localhost' IDENTIFIED WITH 'mysql_native_password' AS '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4'", 'system') + provider.class.expects(:mysql_caller).with("ALTER USER IF EXISTS 'joe'@'localhost' WITH MAX_USER_CONNECTIONS 10 MAX_CONNECTIONS_PER_HOUR 10 MAX_QUERIES_PER_HOUR 10 MAX_UPDATES_PER_HOUR 10", 'system') # rubocop:disable Layout/LineLength provider.class.expects(:mysql_caller).with("ALTER USER 'joe'@'localhost' REQUIRE NONE", 'system') provider.expects(:exists?).returns(true) expect(provider.create).to be_truthy end end describe 'destroy' do it 'removes a user if present' do provider.class.expects(:mysql_caller).with("DROP USER 'joe'@'localhost'", 'system') provider.expects(:exists?).returns(false) expect(provider.destroy).to be_truthy end it 'removes a user using IF EXISTS' do provider.class.instance_variable_set(:@mysqld_version_string, '5.7.1') provider.class.expects(:mysql_caller).with("DROP USER IF EXISTS 'joe'@'localhost'", 'system') expect(provider.destroy).to be_truthy end end describe 'exists?' do it 'checks if user exists' do expect(instance).to be_exists end end describe 'self.mysqld_version' do it 'uses the mysqld_version fact if unset' do provider.class.instance_variable_set(:@mysqld_version_string, nil) Facter.stubs(:value).with(:mysqld_version).returns('5.6.24') expect(provider.mysqld_version).to eq '5.6.24' end it 'returns 5.7.6 for "mysqld Ver 5.7.6 for Linux on x86_64 (MySQL Community Server (GPL))"' do provider.class.instance_variable_set(:@mysqld_version_string, 'mysqld Ver 5.7.6 for Linux on x86_64 (MySQL Community Server (GPL))') expect(provider.mysqld_version).to eq '5.7.6' end it 'returns 5.7.6 for "mysqld Ver 5.7.6-rc for Linux on x86_64 (MySQL Community Server (GPL))"' do provider.class.instance_variable_set(:@mysqld_version_string, 'mysqld Ver 5.7.6-rc for Linux on x86_64 (MySQL Community Server (GPL))') expect(provider.mysqld_version).to eq '5.7.6' end it 'detects >= 5.7.6 for 5.7.7-log' do provider.class.instance_variable_set(:@mysqld_version_string, 'mysqld Ver 5.7.7-log for Linux on x86_64 (MySQL Community Server (GPL))') expect(Puppet::Util::Package.versioncmp(provider.mysqld_version, '5.7.6')).to be >= 0 end it 'detects < 5.7.6 for 5.7.5-log' do provider.class.instance_variable_set(:@mysqld_version_string, 'mysqld Ver 5.7.5-log for Linux on x86_64 (MySQL Community Server (GPL))') expect(Puppet::Util::Package.versioncmp(provider.mysqld_version, '5.7.6')).to be < 0 end end describe 'self.defaults_file' do it 'sets --defaults-extra-file' do File.stubs(:file?).with('/root/.my.cnf').returns(true) expect(provider.defaults_file).to eq '--defaults-extra-file=/root/.my.cnf' end it 'fails if file missing' do File.expects(:file?).with('/root/.my.cnf').returns(false) expect(provider.defaults_file).to be_nil end end describe 'password_hash' do it 'returns a hash' do expect(instance.password_hash).to eq('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4') end end describe 'password_hash=' do it 'changes the hash mysql 5.5' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.5'][:string]) provider.class.expects(:mysql_caller).with("SET PASSWORD FOR 'joe'@'localhost' = '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5'", 'system').returns('0') provider.expects(:password_hash).returns('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') provider.password_hash = '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5' end it 'changes the hash mysql 5.6' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.6'][:string]) provider.class.expects(:mysql_caller).with("SET PASSWORD FOR 'joe'@'localhost' = '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5'", 'system').returns('0') provider.expects(:password_hash).returns('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') provider.password_hash = '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5' end it 'changes the hash mysql < 5.7.6' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.7.1'][:string]) provider.class.expects(:mysql_caller).with("SET PASSWORD FOR 'joe'@'localhost' = '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5'", 'system').returns('0') provider.expects(:password_hash).returns('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') provider.password_hash = '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5' end it 'changes the hash MySQL >= 5.7.6' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.7.6'][:string]) - provider.class.expects(:mysql_caller).with("ALTER USER 'joe'@'localhost' IDENTIFIED WITH mysql_native_password AS '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5'", 'system').returns('0') # rubocop:disable Metrics/LineLength + provider.class.expects(:mysql_caller).with("ALTER USER 'joe'@'localhost' IDENTIFIED WITH mysql_native_password AS '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5'", 'system').returns('0') provider.expects(:password_hash).returns('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') provider.password_hash = '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5' end it 'changes the hash mariadb-10.0' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mariadb-10.0'][:string]) provider.class.expects(:mysql_caller).with("SET PASSWORD FOR 'joe'@'localhost' = '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5'", 'system').returns('0') provider.expects(:password_hash).returns('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') provider.password_hash = '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5' end it 'changes the hash to an ed25519 hash mariadb >= 10.1.21 and < 10.2.0' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mariadb-10.1.44'][:string]) resource.stubs(:value).with(:plugin).returns('ed25519') - provider.class.expects(:mysql_caller).with("UPDATE mysql.user SET password = '', plugin = 'ed25519', authentication_string = 'z0pjExBYbzbupUByZRrQvC6kRCcE8n/tC7kUdUD11fU' where CONCAT(user, '@', host) = 'joe@localhost'; FLUSH PRIVILEGES", 'system').returns('0') # rubocop:disable Metrics/LineLength + provider.class.expects(:mysql_caller).with("UPDATE mysql.user SET password = '', plugin = 'ed25519', authentication_string = 'z0pjExBYbzbupUByZRrQvC6kRCcE8n/tC7kUdUD11fU' where CONCAT(user, '@', host) = 'joe@localhost'; FLUSH PRIVILEGES", 'system').returns('0') # rubocop:disable Layout/LineLength provider.expects(:password_hash).returns('z0pjExBYbzbupUByZRrQvC6kRCcE8n/tC7kUdUD11fU') provider.password_hash = 'z0pjExBYbzbupUByZRrQvC6kRCcE8n/tC7kUdUD11fU' end it 'changes the hash to an ed25519 hash mariadb >= 10.2.0' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mariadb-10.3.22'][:string]) resource.stubs(:value).with(:plugin).returns('ed25519') - provider.class.expects(:mysql_caller).with("ALTER USER 'joe'@'localhost' IDENTIFIED WITH ed25519 AS 'z0pjExBYbzbupUByZRrQvC6kRCcE8n/tC7kUdUD11fU'", 'system').returns('0') # rubocop:disable Metrics/LineLength + provider.class.expects(:mysql_caller).with("ALTER USER 'joe'@'localhost' IDENTIFIED WITH ed25519 AS 'z0pjExBYbzbupUByZRrQvC6kRCcE8n/tC7kUdUD11fU'", 'system').returns('0') provider.expects(:password_hash).returns('z0pjExBYbzbupUByZRrQvC6kRCcE8n/tC7kUdUD11fU') provider.password_hash = 'z0pjExBYbzbupUByZRrQvC6kRCcE8n/tC7kUdUD11fU' end it 'changes the hash to an invalid ed25519 hash mariadb >= 10.1.21' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mariadb-10.1.44'][:string]) resource.stubs(:value).with(:plugin).returns('ed25519') expect { provider.password_hash = 'invalid' }.to raise_error(ArgumentError, 'ed25519 hash should be 43 bytes long.') end it 'changes the hash percona-5.5' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['percona-5.5'][:string]) provider.class.expects(:mysql_caller).with("SET PASSWORD FOR 'joe'@'localhost' = '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5'", 'system').returns('0') provider.expects(:password_hash).returns('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') provider.password_hash = '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5' end end describe 'plugin=' do context 'auth_socket' do context 'MySQL < 5.7.6' do it 'changes the authentication plugin' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.7.1'][:string]) provider.class.expects(:mysql_caller).with("UPDATE mysql.user SET plugin = 'auth_socket', password = '' WHERE CONCAT(user, '@', host) = 'joe@localhost'", 'system').returns('0') provider.expects(:plugin).returns('auth_socket') provider.plugin = 'auth_socket' end end context 'MySQL >= 5.7.6' do it 'changes the authentication plugin' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.7.6'][:string]) provider.class.expects(:mysql_caller).with("ALTER USER 'joe'@'localhost' IDENTIFIED WITH 'auth_socket'", 'system').returns('0') provider.expects(:plugin).returns('auth_socket') provider.plugin = 'auth_socket' end end end context 'mysql_native_password' do context 'MySQL < 5.7.6' do it 'changes the authentication plugin' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.7.1'][:string]) - provider.class.expects(:mysql_caller).with("UPDATE mysql.user SET plugin = 'mysql_native_password', password = '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4' WHERE CONCAT(user, '@', host) = 'joe@localhost'", 'system').returns('0') # rubocop:disable Metrics/LineLength + provider.class.expects(:mysql_caller).with("UPDATE mysql.user SET plugin = 'mysql_native_password', password = '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4' WHERE CONCAT(user, '@', host) = 'joe@localhost'", 'system').returns('0') # rubocop:disable Layout/LineLength provider.expects(:plugin).returns('mysql_native_password') provider.plugin = 'mysql_native_password' end end context 'MySQL >= 5.7.6' do it 'changes the authentication plugin' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.7.6'][:string]) - provider.class.expects(:mysql_caller).with("ALTER USER 'joe'@'localhost' IDENTIFIED WITH 'mysql_native_password' AS '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4'", 'system').returns('0') # rubocop:disable Metrics/LineLength + provider.class.expects(:mysql_caller).with("ALTER USER 'joe'@'localhost' IDENTIFIED WITH 'mysql_native_password' AS '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4'", 'system').returns('0') provider.expects(:plugin).returns('mysql_native_password') provider.plugin = 'mysql_native_password' end end end context 'ed25519' do context 'mariadb >= 10.1.21 and < 10.2.0' do it 'changes the authentication plugin' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mariadb-10.1.44'][:string]) resource.stubs('[]').with(:name).returns('joe@localhost') resource.stubs('[]').with(:password_hash).returns('z0pjExBYbzbupUByZRrQvC6kRCcE8n/tC7kUdUD11fU') - provider.class.expects(:mysql_caller).with("UPDATE mysql.user SET password = '', plugin = 'ed25519', authentication_string = 'z0pjExBYbzbupUByZRrQvC6kRCcE8n/tC7kUdUD11fU' where CONCAT(user, '@', host) = 'joe@localhost'; FLUSH PRIVILEGES", 'system').returns('0') # rubocop:disable Metrics/LineLength + provider.class.expects(:mysql_caller).with("UPDATE mysql.user SET password = '', plugin = 'ed25519', authentication_string = 'z0pjExBYbzbupUByZRrQvC6kRCcE8n/tC7kUdUD11fU' where CONCAT(user, '@', host) = 'joe@localhost'; FLUSH PRIVILEGES", 'system').returns('0') # rubocop:disable Layout/LineLength provider.expects(:plugin).returns('ed25519') provider.plugin = 'ed25519' end end context 'mariadb >= 10.2.0' do it 'changes the authentication plugin' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mariadb-10.3.22'][:string]) resource.stubs('[]').with(:name).returns('joe@localhost') resource.stubs('[]').with(:password_hash).returns('z0pjExBYbzbupUByZRrQvC6kRCcE8n/tC7kUdUD11fU') - provider.class.expects(:mysql_caller).with("ALTER USER 'joe'@'localhost' IDENTIFIED WITH 'ed25519' AS 'z0pjExBYbzbupUByZRrQvC6kRCcE8n/tC7kUdUD11fU'", 'system').returns('0') # rubocop:disable Metrics/LineLength + provider.class.expects(:mysql_caller).with("ALTER USER 'joe'@'localhost' IDENTIFIED WITH 'ed25519' AS 'z0pjExBYbzbupUByZRrQvC6kRCcE8n/tC7kUdUD11fU'", 'system').returns('0') provider.expects(:plugin).returns('ed25519') provider.plugin = 'ed25519' end end end # rubocop:enable RSpec/NestedGroups end describe 'tls_options=' do it 'adds SSL option grant in mysql 5.5' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.5'][:string]) provider.class.expects(:mysql_caller).with("GRANT USAGE ON *.* TO 'joe'@'localhost' REQUIRE NONE", 'system').returns('0') provider.expects(:tls_options).returns(['NONE']) provider.tls_options = ['NONE'] end it 'adds SSL option grant in mysql 5.6' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.6'][:string]) provider.class.expects(:mysql_caller).with("GRANT USAGE ON *.* TO 'joe'@'localhost' REQUIRE NONE", 'system').returns('0') provider.expects(:tls_options).returns(['NONE']) provider.tls_options = ['NONE'] end it 'adds SSL option grant in mysql < 5.7.6' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.7.1'][:string]) provider.class.expects(:mysql_caller).with("GRANT USAGE ON *.* TO 'joe'@'localhost' REQUIRE NONE", 'system').returns('0') provider.expects(:tls_options).returns(['NONE']) provider.tls_options = ['NONE'] end it 'adds SSL option grant in mysql >= 5.7.6' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.7.6'][:string]) provider.class.expects(:mysql_caller).with("ALTER USER 'joe'@'localhost' REQUIRE NONE", 'system').returns('0') provider.expects(:tls_options).returns(['NONE']) provider.tls_options = ['NONE'] end it 'adds SSL option grant in mariadb-10.0' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mariadb-10.0'][:string]) provider.class.expects(:mysql_caller).with("GRANT USAGE ON *.* TO 'joe'@'localhost' REQUIRE NONE", 'system').returns('0') provider.expects(:tls_options).returns(['NONE']) provider.tls_options = ['NONE'] end end describe 'tls_options=required' do it 'adds mTLS option grant in mysql 5.5' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.5'][:string]) provider.class.expects(:mysql_caller).with("GRANT USAGE ON *.* TO 'joe'@'localhost' REQUIRE ISSUER '/CN=Certificate Authority' AND SUBJECT '/OU=MySQL Users/CN=Username'", 'system').returns('0') provider.expects(:tls_options).returns(['ISSUER \'/CN=Certificate Authority\'', 'SUBJECT \'/OU=MySQL Users/CN=Username\'']) provider.tls_options = ['ISSUER \'/CN=Certificate Authority\'', 'SUBJECT \'/OU=MySQL Users/CN=Username\''] end it 'adds mTLS option grant in mysql 5.6' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.6'][:string]) provider.class.expects(:mysql_caller).with("GRANT USAGE ON *.* TO 'joe'@'localhost' REQUIRE ISSUER '/CN=Certificate Authority' AND SUBJECT '/OU=MySQL Users/CN=Username'", 'system').returns('0') provider.expects(:tls_options).returns(['ISSUER \'/CN=Certificate Authority\'', 'SUBJECT \'/OU=MySQL Users/CN=Username\'']) provider.tls_options = ['ISSUER \'/CN=Certificate Authority\'', 'SUBJECT \'/OU=MySQL Users/CN=Username\''] end it 'adds mTLS option grant in mysql < 5.7.6' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.7.1'][:string]) provider.class.expects(:mysql_caller).with("GRANT USAGE ON *.* TO 'joe'@'localhost' REQUIRE ISSUER '/CN=Certificate Authority' AND SUBJECT '/OU=MySQL Users/CN=Username'", 'system').returns('0') provider.expects(:tls_options).returns(['ISSUER \'/CN=Certificate Authority\'', 'SUBJECT \'/OU=MySQL Users/CN=Username\'']) provider.tls_options = ['ISSUER \'/CN=Certificate Authority\'', 'SUBJECT \'/OU=MySQL Users/CN=Username\''] end it 'adds mTLS option grant in mysql >= 5.7.6' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.7.6'][:string]) provider.class.expects(:mysql_caller).with("ALTER USER 'joe'@'localhost' REQUIRE ISSUER '/CN=Certificate Authority' AND SUBJECT '/OU=MySQL Users/CN=Username'", 'system').returns('0') provider.expects(:tls_options).returns(['ISSUER \'/CN=Certificate Authority\'', 'SUBJECT \'/OU=MySQL Users/CN=Username\'']) provider.tls_options = ['ISSUER \'/CN=Certificate Authority\'', 'SUBJECT \'/OU=MySQL Users/CN=Username\''] end it 'adds mTLS option grant in mariadb-10.0' do provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mariadb-10.0'][:string]) provider.class.expects(:mysql_caller).with("GRANT USAGE ON *.* TO 'joe'@'localhost' REQUIRE ISSUER '/CN=Certificate Authority' AND SUBJECT '/OU=MySQL Users/CN=Username'", 'system').returns('0') provider.expects(:tls_options).returns(['ISSUER \'/CN=Certificate Authority\'', 'SUBJECT \'/OU=MySQL Users/CN=Username\'']) provider.tls_options = ['ISSUER \'/CN=Certificate Authority\'', 'SUBJECT \'/OU=MySQL Users/CN=Username\''] end end ['max_user_connections', 'max_connections_per_hour', 'max_queries_per_hour', 'max_updates_per_hour'].each do |property| describe property do it "returns #{property}" do expect(instance.send(property.to_s.to_sym)).to eq('10') end end describe "#{property}=" do it "changes #{property}" do provider.class.expects(:mysql_caller).with("GRANT USAGE ON *.* TO 'joe'@'localhost' WITH #{property.upcase} 42", 'system').returns('0') provider.expects(property.to_sym).returns('42') provider.send("#{property}=".to_sym, '42') end end end end diff --git a/spec/unit/puppet/type/mysql_database_spec.rb b/spec/unit/puppet/type/mysql_database_spec.rb index 9594d0b..b6a9d78 100644 --- a/spec/unit/puppet/type/mysql_database_spec.rb +++ b/spec/unit/puppet/type/mysql_database_spec.rb @@ -1,25 +1,27 @@ +# frozen_string_literal: true + require 'puppet' require 'puppet/type/mysql_database' describe Puppet::Type.type(:mysql_database) do let(:user) { Puppet::Type.type(:mysql_database).new(name: 'test', charset: 'utf8', collate: 'utf8_blah_ci') } it 'accepts a database name' do expect(user[:name]).to eq('test') end it 'accepts a charset' do user[:charset] = 'latin1' expect(user[:charset]).to eq('latin1') end it 'accepts a collate' do user[:collate] = 'latin1_swedish_ci' expect(user[:collate]).to eq('latin1_swedish_ci') end it 'requires a name' do expect { Puppet::Type.type(:mysql_database).new({}) }.to raise_error(Puppet::Error, 'Title or name must be provided') end end diff --git a/spec/unit/puppet/type/mysql_grant_spec.rb b/spec/unit/puppet/type/mysql_grant_spec.rb index 5af6383..602c22a 100644 --- a/spec/unit/puppet/type/mysql_grant_spec.rb +++ b/spec/unit/puppet/type/mysql_grant_spec.rb @@ -1,102 +1,104 @@ +# frozen_string_literal: true + require 'puppet' require 'puppet/type/mysql_grant' require 'spec_helper' describe Puppet::Type.type(:mysql_grant) do let(:user) { Puppet::Type.type(:mysql_grant).new(name: 'foo@localhost/*.*', privileges: ['ALL'], table: ['*.*'], user: 'foo@localhost') } it 'accepts a grant name' do expect(user[:name]).to eq('foo@localhost/*.*') end it 'accepts ALL privileges' do user[:privileges] = 'ALL' expect(user[:privileges]).to eq(['ALL']) end context 'PROXY privilege with mysql greater than or equal to 5.5.0' do before :each do Facter.stubs(:value).with(:mysql_version).returns('5.5.0') end it 'does not raise error' do user[:privileges] = 'PROXY' user[:table] = 'proxy_user@proxy_host' expect(user[:privileges]).to eq(['PROXY']) end end context 'PROXY privilege with mysql greater than or equal to 5.4.0' do before :each do Facter.stubs(:value).with(:mysql_version).returns('5.4.0') end it 'raises error' do expect { user[:privileges] = 'PROXY' }.to raise_error(Puppet::ResourceError, %r{PROXY user not supported on mysql versions < 5.5.0}) end end it 'accepts a table' do user[:table] = '*.*' expect(user[:table]).to eq('*.*') end it 'accepts @ for table' do user[:table] = '@' expect(user[:table]).to eq('@') end it 'accepts proxy user for table' do user[:table] = 'proxy_user@proxy_host' expect(user[:table]).to eq('proxy_user@proxy_host') end it 'accepts a user' do user[:user] = 'foo@localhost' expect(user[:user]).to eq('foo@localhost') end it 'requires a name' do expect { Puppet::Type.type(:mysql_grant).new({}) }.to raise_error(Puppet::Error, 'Title or name must be provided') end it 'requires the name to match the user and table #general' do expect { Puppet::Type.type(:mysql_grant).new(name: 'foo@localhost/*.*', privileges: ['ALL'], table: ['*.*'], user: 'foo@localhost') }.not_to raise_error end it 'requires the name to match the user and table #specific' do expect { Puppet::Type.type(:mysql_grant).new(name: 'foo', privileges: ['ALL'], table: ['*.*'], user: 'foo@localhost') }.to raise_error %r{mysql_grant: `name` `parameter` must match user@host\/table format} end describe 'it should munge privileges' do it 'to just ALL' do user = Puppet::Type.type(:mysql_grant).new( name: 'foo@localhost/*.*', table: ['*.*'], user: 'foo@localhost', privileges: ['ALL'] ) expect(user[:privileges]).to eq(['ALL']) end it 'to upcase and ordered' do user = Puppet::Type.type(:mysql_grant).new( name: 'foo@localhost/*.*', table: ['*.*'], user: 'foo@localhost', privileges: ['select', 'Insert'] ) expect(user[:privileges]).to eq(['INSERT', 'SELECT']) end it 'ordered including column privileges' do user = Puppet::Type.type(:mysql_grant).new( name: 'foo@localhost/*.*', table: ['*.*'], user: 'foo@localhost', privileges: ['SELECT(Host,Address)', 'Insert'] ) expect(user[:privileges]).to eq(['INSERT', 'SELECT (Address, Host)']) end end end diff --git a/spec/unit/puppet/type/mysql_plugin_spec.rb b/spec/unit/puppet/type/mysql_plugin_spec.rb index d89be25..ce23c98 100644 --- a/spec/unit/puppet/type/mysql_plugin_spec.rb +++ b/spec/unit/puppet/type/mysql_plugin_spec.rb @@ -1,20 +1,22 @@ +# frozen_string_literal: true + require 'puppet' require 'puppet/type/mysql_plugin' describe Puppet::Type.type(:mysql_plugin) do let(:plugin) { Puppet::Type.type(:mysql_plugin).new(name: 'test', soname: 'test.so') } it 'accepts a plugin name' do expect(plugin[:name]).to eq('test') end it 'accepts a library name' do plugin[:soname] = 'test.so' expect(plugin[:soname]).to eq('test.so') end it 'requires a name' do expect { Puppet::Type.type(:mysql_plugin).new({}) }.to raise_error(Puppet::Error, 'Title or name must be provided') end end diff --git a/spec/unit/puppet/type/mysql_user_spec.rb b/spec/unit/puppet/type/mysql_user_spec.rb index cdc66b8..09e1ada 100644 --- a/spec/unit/puppet/type/mysql_user_spec.rb +++ b/spec/unit/puppet/type/mysql_user_spec.rb @@ -1,136 +1,138 @@ +# frozen_string_literal: true + require 'puppet' require 'puppet/type/mysql_user' require 'spec_helper' describe Puppet::Type.type(:mysql_user) do context 'On MySQL 5.x' do before :each do Facter.stubs(:value).with(:mysql_version).returns('5.6.24') end it 'fails with a long user name' do expect { Puppet::Type.type(:mysql_user).new(name: '12345678901234567@localhost', password_hash: 'pass') }.to raise_error %r{MySQL usernames are limited to a maximum of 16 characters} end end context 'On MariaDB 10.0.0+' do let(:user) { Puppet::Type.type(:mysql_user).new(name: '12345678901234567@localhost', password_hash: 'pass') } before :each do Facter.stubs(:value).with(:mysql_version).returns('10.0.19') end it 'succeeds with a long user name on MariaDB' do expect(user[:name]).to eq('12345678901234567@localhost') end end it 'requires a name' do expect { Puppet::Type.type(:mysql_user).new({}) }.to raise_error(Puppet::Error, 'Title or name must be provided') end context 'using foo@localhost' do let(:user) { Puppet::Type.type(:mysql_user).new(name: 'foo@localhost', password_hash: 'pass') } it 'accepts a user name' do expect(user[:name]).to eq('foo@localhost') end it 'accepts a password' do user[:password_hash] = 'foo' expect(user[:password_hash]).to eq('foo') end it 'accepts an empty password' do user[:password_hash] = '' expect(user[:password_hash]).to eq('') end end context 'using foo@LocalHost' do let(:user) { Puppet::Type.type(:mysql_user).new(name: 'foo@LocalHost', password_hash: 'pass') } it 'lowercases the user name' do expect(user[:name]).to eq('foo@localhost') end end context 'using foo@192.168.1.0/255.255.255.0' do let(:user) { Puppet::Type.type(:mysql_user).new(name: 'foo@192.168.1.0/255.255.255.0', password_hash: 'pass') } it 'creates the user with the netmask' do expect(user[:name]).to eq('foo@192.168.1.0/255.255.255.0') end end context 'using allo_wed$char@localhost' do let(:user) { Puppet::Type.type(:mysql_user).new(name: 'allo_wed$char@localhost', password_hash: 'pass') } it 'accepts a user name' do expect(user[:name]).to eq('allo_wed$char@localhost') end end context 'ensure the default \'debian-sys-main\'@localhost user can be parsed' do let(:user) { Puppet::Type.type(:mysql_user).new(name: '\'debian-sys-maint\'@localhost', password_hash: 'pass') } it 'accepts a user name' do expect(user[:name]).to eq('\'debian-sys-maint\'@localhost') end end context 'using a quoted 16 char username' do let(:user) { Puppet::Type.type(:mysql_user).new(name: '"debian-sys-maint"@localhost', password_hash: 'pass') } it 'accepts a user name' do expect(user[:name]).to eq('"debian-sys-maint"@localhost') end end context 'using a quoted username that is too long ' do before :each do Facter.stubs(:value).with(:mysql_version).returns('5.6.24') end it 'fails with a size error' do expect { Puppet::Type.type(:mysql_user).new(name: '"debian-sys-maint2"@localhost', password_hash: 'pass') }.to raise_error %r{MySQL usernames are limited to a maximum of 16 characters} end end context 'using `speci!al#`@localhost' do let(:user) { Puppet::Type.type(:mysql_user).new(name: '`speci!al#`@localhost', password_hash: 'pass') } it 'accepts a quoted user name with special chatracters' do expect(user[:name]).to eq('`speci!al#`@localhost') end end context 'using in-valid@localhost' do let(:user) { Puppet::Type.type(:mysql_user).new(name: 'in-valid@localhost', password_hash: 'pass') } it 'accepts a user name with special chatracters' do expect(user[:name]).to eq('in-valid@localhost') end end context 'using "misquoted@localhost' do it 'fails with a misquoted username is used' do expect { Puppet::Type.type(:mysql_user).new(name: '"misquoted@localhost', password_hash: 'pass') }.to raise_error %r{Invalid database user "misquoted@localhost} end end context 'using invalid options' do it 'fails with an invalid option' do expect { Puppet::Type.type(:mysql_user).new(name: 'misquoted@localhost', password_hash: 'pass', tls_options: ['SOMETHING_ELSE']) }.to raise_error %r{Invalid tls option} end end end diff --git a/tasks/export.rb b/tasks/export.rb index e3f44d0..6bf3e83 100755 --- a/tasks/export.rb +++ b/tasks/export.rb @@ -1,30 +1,32 @@ #!/opt/puppetlabs/puppet/bin/ruby +# frozen_string_literal: true + require 'json' require 'open3' require 'puppet' def get(file, database, user, password) cmd_string = 'mysqldump' cmd_string << " --databases #{database}" unless database.nil? cmd_string << " --user=#{user}" unless user.nil? cmd_string << " --password=#{password}" unless password.nil? cmd_string << " > #{file}" unless file.nil? stdout, stderr, status = Open3.capture3(cmd_string) raise Puppet::Error, _("stderr: '%{stderr}'" % { stderr: stderr }) if status != 0 { status: stdout.strip } end params = JSON.parse(STDIN.read) database = params['database'] user = params['user'] password = params['password'] file = params['file'] begin result = get(file, database, user, password) puts result.to_json exit 0 rescue Puppet::Error => e puts({ status: 'failure', error: e.message }.to_json) exit 1 end diff --git a/tasks/sql.rb b/tasks/sql.rb index 53c9d01..13e3234 100755 --- a/tasks/sql.rb +++ b/tasks/sql.rb @@ -1,29 +1,31 @@ #!/opt/puppetlabs/puppet/bin/ruby +# frozen_string_literal: true + require 'json' require 'open3' require 'puppet' def get(sql, database, user, password) cmd = ['mysql', '-e', "#{sql} "] cmd << "--database=#{database}" unless database.nil? cmd << "--user=#{user}" unless user.nil? cmd << "--password=#{password}" unless password.nil? stdout, stderr, status = Open3.capture3(*cmd) raise Puppet::Error, _("stderr: '%{stderr}'" % { stderr: stderr }) if status != 0 { status: stdout.strip } end params = JSON.parse(STDIN.read) database = params['database'] user = params['user'] password = params['password'] sql = params['sql'] begin result = get(sql, database, user, password) puts result.to_json exit 0 rescue Puppet::Error => e puts({ status: 'failure', error: e.message }.to_json) exit 1 end