diff --git a/.rubocop.yml b/.rubocop.yml index 858882d..33c33fa 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,137 +1,534 @@ --- 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 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/Caller: + Enabled: true +Performance/CaseWhenSplat: + Enabled: true +Performance/Casecmp: + Enabled: true +Performance/CollectionLiteralInLoop: + Enabled: true +Performance/CompareWithBlock: + Enabled: true +Performance/ConstantRegexp: + Enabled: true +Performance/Count: + Enabled: true +Performance/Detect: + Enabled: true +Performance/DoubleStartEndWith: + Enabled: true +Performance/EndWith: + Enabled: true +Performance/FixedSize: + Enabled: true +Performance/FlatMap: + Enabled: true +Performance/MethodObjectAsBlock: + Enabled: true +Performance/RangeInclude: + Enabled: true +Performance/RedundantBlockCall: + Enabled: true +Performance/RedundantMatch: + Enabled: true +Performance/RedundantMerge: + Enabled: true +Performance/RedundantSortBlock: + Enabled: true +Performance/RedundantStringChars: + Enabled: true +Performance/RegexpMatch: + Enabled: true +Performance/ReverseEach: + Enabled: true +Performance/ReverseFirst: + Enabled: true +Performance/Size: + Enabled: true +Performance/SortReverse: + Enabled: true +Performance/Squeeze: + Enabled: true +Performance/StartWith: + Enabled: true +Performance/StringInclude: + Enabled: true +Performance/StringReplacement: + Enabled: true +Performance/Sum: + Enabled: true +Performance/TimesMap: + 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 -GetText/DecorateStringFormattingUsingInterpolation: +Gemspec/RequiredRubyVersion: Enabled: false -GetText/DecorateStringFormattingUsingPercent: +Gemspec/RubyVersionGlobalsUsage: + Enabled: false +Layout/ArgumentAlignment: + Enabled: false +Layout/BeginEndAlignment: + Enabled: false +Layout/ClosingHeredocIndentation: + Enabled: false +Layout/EmptyComment: + Enabled: false +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/DuplicateBranch: + Enabled: false +Lint/DuplicateElsifCondition: + Enabled: false +Lint/DuplicateRegexpCharacterClassElement: + Enabled: false +Lint/DuplicateRequire: + Enabled: false +Lint/DuplicateRescueException: + Enabled: false +Lint/EmptyBlock: + Enabled: false +Lint/EmptyClass: + 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/NoReturnInBeginEndBlocks: + 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/ToEnumArguments: + Enabled: false +Lint/ToJSON: + Enabled: false +Lint/TopLevelReturnWithArgument: + Enabled: false +Lint/TrailingCommaInAttributeDeclaration: + Enabled: false +Lint/UnexpectedBlockArity: + Enabled: false +Lint/UnmodifiedReduceAccumulator: + 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/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/BlockParameterName: + Enabled: false +Naming/HeredocDelimiterCase: + Enabled: false +Naming/HeredocDelimiterNaming: + Enabled: false +Naming/MemoizedInstanceVariableName: + Enabled: false +Naming/MethodParameterName: + Enabled: false +Naming/RescuedExceptionsVariableName: + 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/ArgumentsForwarding: + Enabled: false Style/AsciiComments: Enabled: false +Style/BisectedAttrAccessor: + Enabled: false +Style/CaseLikeIf: + Enabled: false +Style/ClassEqualityComparison: + Enabled: false +Style/CollectionCompact: + Enabled: false +Style/ColonMethodDefinition: + Enabled: false +Style/CombinableLoops: + Enabled: false +Style/CommentedKeyword: + Enabled: false +Style/Dir: + Enabled: false +Style/DocumentDynamicEvalDefinition: + 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/NegatedIfElseCondition: + Enabled: false +Style/NegatedUnless: + Enabled: false +Style/NilLambda: + Enabled: false +Style/NumericPredicate: + Enabled: false +Style/OptionalBooleanParameter: + Enabled: false +Style/OrAssignment: + Enabled: false +Style/RandomWithOffset: + Enabled: false +Style/RedundantArgument: + 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/SwapValues: + 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 diff --git a/.sync.yml b/.sync.yml index 5e3a009..0f4cf95 100644 --- a/.sync.yml +++ b/.sync.yml @@ -1,56 +1,53 @@ --- ".gitlab-ci.yml": delete: true ".yardopts": optional: - "--output-dir docs/" appveyor.yml: delete: true Gemfile: optional: ":development": - gem: github_changelog_generator - git: https://github.com/skywinder/github-changelog-generator - ref: 20ee04ba1234e9e83eb2ffb5056e23d641c7a018 - condition: Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.2.2') spec/spec_helper.rb: mock_with: ":rspec" coverage_report: true ".travis.yml": global_env: - HONEYCOMB_WRITEKEY="7f3c63a70eecc61d635917de46bea4e6",HONEYCOMB_DATASET="litmus tests" deploy_to_forge: enabled: false branches: - release user: puppet secure: "" use_litmus: true litmus: provision_list: - ---travis_el - travis_deb - travis_el7 complex: - collection: puppet_collection: - puppet6 provision_list: - travis_ub_6 - collection: puppet_collection: - puppet5 provision_list: - travis_ub_5 simplecov: true notifications: slack: secure: XnFGMHjVwgUY0Dtxvent3sa6m7yAF7HH3kRsJ39Oje95cechyfuIIDKX80NBLRS/Zpjm5wCEA6l90hgZMw5H8WmqPyo1LrQGzBsk22abh/XI6y/+N1TKSavgegcf+NnU4w30QO4FNiQCoO8krJHoOPqcIpf/azrPPSWVimMovAE= .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 ccfcb6f..e96c68f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,124 +1,118 @@ --- 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_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 - 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: XnFGMHjVwgUY0Dtxvent3sa6m7yAF7HH3kRsJ39Oje95cechyfuIIDKX80NBLRS/Zpjm5wCEA6l90hgZMw5H8WmqPyo1LrQGzBsk22abh/XI6y/+N1TKSavgegcf+NnU4w30QO4FNiQCoO8krJHoOPqcIpf/azrPPSWVimMovAE= deploy: provider: puppetforge username: puppet password: secure: "" on: tags: true all_branches: true condition: "$DEPLOY_TO_FORGE = yes" diff --git a/Gemfile b/Gemfile index b6b25af..ae2b430 100644 --- a/Gemfile +++ b/Gemfile @@ -1,74 +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 "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-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/lib/facter/vcsrepo_svn_ver.rb b/lib/facter/vcsrepo_svn_ver.rb index 9fe3047..53541d6 100644 --- a/lib/facter/vcsrepo_svn_ver.rb +++ b/lib/facter/vcsrepo_svn_ver.rb @@ -1,18 +1,20 @@ +# frozen_string_literal: true + Facter.add(:vcsrepo_svn_ver) do setcode do begin if Facter.value(:operatingsystem) == 'Darwin' && !File.directory?(Facter::Core::Execution.execute('xcode-select -p')) '' else version = Facter::Core::Execution.execute('svn --version --quiet') if Gem::Version.new(version) > Gem::Version.new('0.0.1') version else '' end end rescue StandardError '' end end end diff --git a/lib/puppet/provider/vcsrepo.rb b/lib/puppet/provider/vcsrepo.rb index 2d2f6e0..9793264 100644 --- a/lib/puppet/provider/vcsrepo.rb +++ b/lib/puppet/provider/vcsrepo.rb @@ -1,58 +1,60 @@ +# frozen_string_literal: true + require 'tmpdir' require 'digest/md5' require 'fileutils' # Abstract class Puppet::Provider::Vcsrepo < Puppet::Provider def check_force return unless path_exists? && !path_empty? raise Puppet::Error, 'Path %s exists and is not the desired repository.' % @resource.value(:path) unless @resource.value(:force) notice 'Removing %s to replace with desired repository.' % @resource.value(:path) destroy end private def set_ownership owner = @resource.value(:owner) || nil group = @resource.value(:group) || nil excludes = @resource.value(:excludes) || nil if excludes.nil? || excludes.empty? FileUtils.chown_R(owner, group, @resource.value(:path)) else FileUtils.chown(owner, group, files) end end def files excludes = @resource.value(:excludes) path = @resource.value(:path) Dir["#{path}/**/*"].reject { |f| excludes.any? { |p| f.start_with?("#{path}/#{p}") } } end def path_exists? File.directory?(@resource.value(:path)) end def path_empty? # Path is empty if the only entries are '.' and '..' d = Dir.new(@resource.value(:path)) d.read # should return '.' d.read # should return '..' d.read.nil? end - # Note: We don't rely on Dir.chdir's behavior of automatically returning the + # NOTE: We don't rely on Dir.chdir's behavior of automatically returning the # value of the last statement -- for easier stubbing. def at_path #:nodoc: value = nil Dir.chdir(@resource.value(:path)) do value = yield end value end def tempdir @tempdir ||= File.join(Dir.tmpdir, 'vcsrepo-' + Digest::MD5.hexdigest(@resource.value(:path))) end end diff --git a/lib/puppet/provider/vcsrepo/bzr.rb b/lib/puppet/provider/vcsrepo/bzr.rb index ded7d5e..86d5086 100644 --- a/lib/puppet/provider/vcsrepo/bzr.rb +++ b/lib/puppet/provider/vcsrepo/bzr.rb @@ -1,108 +1,110 @@ +# frozen_string_literal: true + require File.join(File.dirname(__FILE__), '..', 'vcsrepo') Puppet::Type.type(:vcsrepo).provide(:bzr, parent: Puppet::Provider::Vcsrepo) do desc 'Supports Bazaar repositories' commands bzr: 'bzr' has_features :reference_tracking def create check_force if !@resource.value(:source) create_repository(@resource.value(:path)) else clone_repository(@resource.value(:revision)) end end def working_copy_exists? return false unless File.directory?(@resource.value(:path)) begin bzr('status', @resource.value(:path)) - return true + true rescue Puppet::ExecutionFailure - return false + false end end def exists? working_copy_exists? end def destroy FileUtils.rm_rf(@resource.value(:path)) end def revision at_path do current_revid = bzr('version-info')[%r{^revision-id:\s+(\S+)}, 1] desired = @resource.value(:revision) begin desired_revid = bzr('revision-info', desired).strip.split(%r{\s+}).last rescue Puppet::ExecutionFailure # Possible revid available during update (but definitely not current) desired_revid = nil end if current_revid == desired_revid desired else current_revid end end end def revision=(desired) at_path do begin bzr('update', '-r', desired) rescue Puppet::ExecutionFailure bzr('update', '-r', desired, ':parent') end end update_owner end def source at_path do bzr('info')[%r{^\s+parent branch:\s+(\S+?)$}m, 1] end end def source=(_desired) create # recreate end def latest at_path do bzr('version-info', ':parent')[%r{^revision-id:\s+(\S+)}, 1] end end def latest? at_path do return revision == latest end end private def create_repository(path) bzr('init', path) update_owner end def clone_repository(revision) args = ['branch'] if revision args.push('-r', revision) end args.push(@resource.value(:source), @resource.value(:path)) bzr(*args) update_owner end def update_owner set_ownership if @resource.value(:owner) || @resource.value(:group) end end diff --git a/lib/puppet/provider/vcsrepo/cvs.rb b/lib/puppet/provider/vcsrepo/cvs.rb index d9358f2..3b0064f 100644 --- a/lib/puppet/provider/vcsrepo/cvs.rb +++ b/lib/puppet/provider/vcsrepo/cvs.rb @@ -1,155 +1,157 @@ +# frozen_string_literal: true + require File.join(File.dirname(__FILE__), '..', 'vcsrepo') Puppet::Type.type(:vcsrepo).provide(:cvs, parent: Puppet::Provider::Vcsrepo) do desc 'Supports CVS repositories/workspaces' commands cvs: 'cvs' has_features :gzip_compression, :reference_tracking, :modules, :cvs_rsh, :user def create check_force if !@resource.value(:source) create_repository(@resource.value(:path)) else checkout_repository end update_owner end def exist? working_copy_exists? end def working_copy_exists? if @resource.value(:source) directory = File.join(@resource.value(:path), 'CVS') return false unless File.directory?(directory) begin at_path { runcvs('-nq', 'status', '-l') } - return true + true rescue Puppet::ExecutionFailure - return false + false end else directory = File.join(@resource.value(:path), 'CVSROOT') return false unless File.directory?(directory) config = File.join(@resource.value(:path), 'CVSROOT', 'config,v') return false unless File.exist?(config) true end end def destroy FileUtils.rm_rf(@resource.value(:path)) end def latest? Puppet.debug "Checking for updates because 'ensure => latest'" at_path do # We cannot use -P to prune empty dirs, otherwise # CVS would report those as "missing", regardless # if they have contents or updates. is_current = (runcvs('-nq', 'update', '-d').strip == '') unless is_current then Puppet.debug "There are updates available on the checkout's current branch/tag." end return is_current end end def latest # CVS does not have a conecpt like commit-IDs or change # sets, so we can only have the current branch name (or the # requested one, if that differs) as the "latest" revision. should = @resource.value(:revision) current = revision (should != current) ? should : current end def revision unless @rev if File.exist?(tag_file) contents = File.read(tag_file).strip - # Note: Doesn't differentiate between N and T entries + # NOTE: Doesn't differentiate between N and T entries @rev = contents[1..-1] else @rev = 'HEAD' end Puppet.debug "Checkout is on branch/tag '#{@rev}'" end @rev end def revision=(desired) at_path do runcvs('update', '-dr', desired, '.') update_owner @rev = desired end end def source File.read(File.join(@resource.value(:path), 'CVS', 'Root')).chomp end def source=(_desired) create # recreate end def module File.read(File.join(@resource.value(:path), 'CVS', 'Repository')).chomp end def module=(_desired) create # recreate end private def tag_file File.join(@resource.value(:path), 'CVS', 'Tag') end def checkout_repository dirname, basename = File.split(@resource.value(:path)) Dir.chdir(dirname) do args = ['-d', @resource.value(:source)] if @resource.value(:compression) args.push('-z', @resource.value(:compression)) end args.push('checkout') if @resource.value(:revision) args.push('-r', @resource.value(:revision)) end args.push('-d', basename, module_name) runcvs(*args) end end # If no module is provided, use '.', the root of the repo def module_name @resource.value(:module) || '.' end def create_repository(path) runcvs('-d', path, 'init') end def update_owner set_ownership if @resource.value(:owner) || @resource.value(:group) end def runcvs(*args) if @resource.value(:cvs_rsh) Puppet.debug 'Using CVS_RSH = ' + @resource.value(:cvs_rsh) e = { CVS_RSH: @resource.value(:cvs_rsh) } else e = {} end if @resource.value(:user) && @resource.value(:user) != Facter['id'].value Puppet.debug 'Running as user ' + @resource.value(:user) Puppet::Util::Execution.execute([:cvs, *args], uid: @resource.value(:user), custom_environment: e, combine: true, failonfail: true) else Puppet::Util::Execution.execute([:cvs, *args], custom_environment: e, combine: true, failonfail: true) end end end diff --git a/lib/puppet/provider/vcsrepo/dummy.rb b/lib/puppet/provider/vcsrepo/dummy.rb index 14bc527..34a83d0 100644 --- a/lib/puppet/provider/vcsrepo/dummy.rb +++ b/lib/puppet/provider/vcsrepo/dummy.rb @@ -1,17 +1,19 @@ +# frozen_string_literal: true + require File.join(File.dirname(__FILE__), '..', 'vcsrepo') Puppet::Type.type(:vcsrepo).provide(:dummy, parent: Puppet::Provider::Vcsrepo) do desc 'Dummy default provider' defaultfor feature: :posix defaultfor operatingsystem: :windows def working_copy_exists? providers = begin @resource.class.providers.map { |x| x.to_s }.sort.reject { |x| x == 'dummy' }.join(', ') rescue StandardError 'none' end raise("vcsrepo resource must have a provider, available: #{providers}") end end diff --git a/lib/puppet/provider/vcsrepo/git.rb b/lib/puppet/provider/vcsrepo/git.rb index 504c5ba..764edc4 100644 --- a/lib/puppet/provider/vcsrepo/git.rb +++ b/lib/puppet/provider/vcsrepo/git.rb @@ -1,608 +1,610 @@ +# frozen_string_literal: true + require File.join(File.dirname(__FILE__), '..', 'vcsrepo') Puppet::Type.type(:vcsrepo).provide(:git, parent: Puppet::Provider::Vcsrepo) do desc 'Supports Git repositories' has_command(:git, 'git') do environment('HOME' => ENV['HOME']) end has_features :bare_repositories, :reference_tracking, :ssh_identity, :multiple_remotes, :user, :depth, :branch, :submodules def create check_force if @resource.value(:revision) && ensure_bare_or_mirror? raise("Cannot set a revision (#{@resource.value(:revision)}) on a bare repository") end if !@resource.value(:source) if @resource.value(:ensure) == :mirror raise('Cannot init repository with mirror option, try bare instead') end init_repository else clone_repository(default_url, @resource.value(:path)) update_remotes(@resource.value(:source)) set_mirror if @resource.value(:ensure) == :mirror && @resource.value(:source).is_a?(Hash) if @resource.value(:revision) checkout end if !ensure_bare_or_mirror? && @resource.value(:submodules) == :true update_submodules end end update_owner_and_excludes end def destroy FileUtils.rm_rf(@resource.value(:path)) end # Checks to see if the current revision is equal to the revision on the # remote (whether on a branch, tag, or reference) # # @return [Boolean] Returns true if the repo is on the latest revision def latest? revision == latest_revision end # Just gives the `should` value that we should be setting the repo to if # latest? returns false # # @return [String] Returns the target sha/tag/branch def latest if !@resource.value(:revision) && (branch = on_branch?) branch else @resource.value(:revision) end end # Get the current revision of the repo (tag/branch/sha) # # @return [String] Returns the branch/tag if the current sha matches the # remote; otherwise returns the current sha. def revision # HEAD is the default, but lets just be explicit here. get_revision('HEAD') end # Is passed the desired reference, whether a tag, rev, or branch. Should # handle transitions from a rev/branch/tag to a rev/branch/tag. Detached # heads should be treated like bare revisions. # # @param [String] desired The desired revision to which the repo should be # set. def revision=(desired) # just checkout tags and shas; fetch has already happened so they should be updated. checkout(desired) # branches require more work. if local_branch_revision?(desired) # reset instead of pull to avoid merge conflicts. assuming remote is # updated and authoritative. # TODO might be worthwhile to have an allow_local_changes param to decide # whether to reset or pull when we're ensuring latest. if @resource.value(:source) at_path { git_with_identity('reset', '--hard', "#{@resource.value(:remote)}/#{desired}") } else at_path { git_with_identity('reset', '--hard', desired.to_s) } end end # TODO: Would this ever reach here if it is bare? if !ensure_bare_or_mirror? && @resource.value(:submodules) == :true update_submodules end update_owner_and_excludes end def bare_exists? bare_git_config_exists? && !working_copy_exists? end def ensure_bare_or_mirror? [:bare, :mirror].include? @resource.value(:ensure) end # If :source is set to a hash (for supporting multiple remotes), # we search for the URL for :remote. If it doesn't exist, # we throw an error. If :source is just a string, we use that # value for the default URL. def default_url return @resource.value(:source) unless @resource.value(:source).is_a?(Hash) return @resource.value(:source)[@resource.value(:remote)] if @resource.value(:source).key?(@resource.value(:remote)) raise("You must specify the URL for remote '#{@resource.value(:remote)}' in the :source hash") end def working_copy_exists? # NOTE: a change in the `default_url` will tell the type that this repo # doesn't exist (i.e. it triggers a "not the same repository" error). # Thus, changing the `source` property from a string to a string (which # changes the origin url), or if the @resource.value(:remote)'s url is # changed, the provider will require force. return false unless File.directory?(File.join(@resource.value(:path), '.git')) at_path do if @resource.value(:source) begin return git('config', '--get', "remote.#{@resource.value(:remote)}.url").chomp == default_url rescue Puppet::ExecutionFailure return false end else begin git('status') return true rescue Puppet::ExecutionFailure return false end end end end def exists? working_copy_exists? || bare_exists? end def remove_remote(remote) at_path do git_with_identity('remote', 'remove', remote) end end def update_remote_url(remote_name, remote_url) current = git_with_identity('config', '-l') return if remote_url.nil? # Check if remote exists at all, regardless of URL. # If remote doesn't exist, add it if !current.include? "remote.#{remote_name}.url" git_with_identity('remote', 'add', remote_name, remote_url) - return true + true # If remote exists, but URL doesn't match, update URL elsif !current.include? "remote.#{remote_name}.url=#{remote_url}" git_with_identity('remote', 'set-url', remote_name, remote_url) - return true + true else - return false + false end end def source at_path do remotes = git('remote').split("\n") return git('config', '--get', "remote.#{remotes[0]}.url").chomp if remotes.size == 1 Hash[remotes.map do |remote| [remote, git('config', '--get', "remote.#{remote}.url").chomp] end] end end def source=(desired) # NOTE: a change in the `default_url` will tell the type that this repo # doesn't exist (i.e. it triggers a "not the same repository" error). # Thus, a change from a string to a string (which changes the origin url), # or if the @resource.value(:remote)'s url is changed, the provider will # require force, without ever reaching this block. The recreation is # duplicated here in case something changes in the `working_copy_exists?` # logic. current = source if current.is_a?(Hash) current.each_key do |remote| remove_remote(remote) if desired.is_a?(Hash) && !desired.key?(remote) remove_remote(remote) if desired.is_a?(String) && remote != @resource.value(:remote) end end if current.is_a?(String) && desired.is_a?(String) create # recreate else update_remotes(desired) end end def update_remotes(remotes) do_update = false # If supplied source is a hash of remote name and remote url pairs, then # we loop around the hash. Otherwise, we assume single url specified # in source property if remotes.is_a?(Hash) remotes.keys.sort.each do |remote_name| remote_url = remotes[remote_name] at_path { do_update |= update_remote_url(remote_name, remote_url) } end else at_path { do_update |= update_remote_url(@resource.value(:remote), remotes) } end # If at least one remote was added or updated, then we must # call the 'git remote update' command at_path { git_with_identity('remote', 'update') } if do_update == true end def update_references at_path do git_with_identity('fetch', @resource.value(:remote)) git_with_identity('fetch', '--tags', @resource.value(:remote)) update_owner_and_excludes end end # Convert working copy to bare # # Moves: # /.git # to: # / # and sets core.bare=true, and calls `set_mirror` if appropriate def convert_working_copy_to_bare return unless working_copy_exists? && !bare_exists? notice 'Converting working copy repository to bare repository' FileUtils.mv(File.join(@resource.value(:path), '.git'), tempdir) FileUtils.rm_rf(@resource.value(:path)) FileUtils.mv(tempdir, @resource.value(:path)) at_path do git('config', '--local', '--bool', 'core.bare', 'true') return unless @resource.value(:ensure) == :mirror raise('Cannot have empty repository that is also a mirror.') unless @resource.value(:source) set_mirror end end # Convert bare to working copy # # Moves: # / # to: # /.git # and sets core.bare=false, and calls `set_no_mirror` if appropriate def convert_bare_to_working_copy notice 'Converting bare repository to working copy repository' FileUtils.mv(@resource.value(:path), tempdir) FileUtils.mkdir(@resource.value(:path)) FileUtils.mv(tempdir, File.join(@resource.value(:path), '.git')) if commits? at_path do git('config', '--local', '--bool', 'core.bare', 'false') reset('HEAD') git_with_identity('checkout', '--force') update_owner_and_excludes end end set_no_mirror if mirror? end def mirror? at_path do begin git('config', '--get-regexp', 'remote\..*\.mirror') return true rescue Puppet::ExecutionFailure return false end end end def set_mirror at_path do if @resource.value(:source).is_a?(String) git('config', "remote.#{@resource.value(:remote)}.mirror", 'true') else @resource.value(:source).each_key do |remote| git('config', "remote.#{remote}.mirror", 'true') end end end end def set_no_mirror at_path do if @resource.value(:source).is_a?(String) begin git('config', '--unset', "remote.#{@resource.value(:remote)}.mirror") rescue Puppet::ExecutionFailure next end else @resource.value(:source).each_key do |remote| begin git('config', '--unset', "remote.#{remote}.mirror") rescue Puppet::ExecutionFailure next end end end end end private # @!visibility private def bare_git_config_exists? return false unless File.exist?(File.join(@resource.value(:path), 'config')) begin at_path { git('config', '--list', '--file', 'config') } - return true + true rescue Puppet::ExecutionFailure - return false + false end end # @!visibility private def clone_repository(source, path) args = ['clone'] if @resource.value(:depth) && @resource.value(:depth).to_i > 0 args.push('--depth', @resource.value(:depth).to_s) if @resource.value(:revision) && !@resource.value(:branch) args.push('--branch', @resource.value(:revision).to_s) end end if @resource.value(:branch) args.push('--branch', @resource.value(:branch).to_s) end case @resource.value(:ensure) when :bare then args << '--bare' when :mirror then args << '--mirror' end if @resource.value(:remote) != 'origin' args.push('--origin', @resource.value(:remote)) end if !working_copy_exists? args.push(source, path) Dir.chdir('/') do git_with_identity(*args) end else notice 'Repo has already been cloned' end end # @!visibility private def init_repository if @resource.value(:ensure) == :bare && working_copy_exists? convert_working_copy_to_bare elsif @resource.value(:ensure) == :present && bare_exists? convert_bare_to_working_copy else # normal init FileUtils.mkdir(@resource.value(:path)) FileUtils.chown(@resource.value(:user), nil, @resource.value(:path)) if @resource.value(:user) args = ['init'] if @resource.value(:ensure) == :bare args << '--bare' end at_path do git_with_identity(*args) end end end # @!visibility private def commits? at_path do begin commits = git_with_identity('rev-list', '--all', '--count').to_i rescue Puppet::ExecutionFailure commits = 0 end return commits > 0 end end # Will checkout a rev/branch/tag using the locally cached versions. Does not # handle upstream branch changes # @!visibility private def checkout(revision = @resource.value(:revision)) keep_local_changes = @resource.value(:keep_local_changes) stash if keep_local_changes == :true if !local_branch_revision?(revision) && remote_branch_revision?(revision) # non-locally existant branches (perhaps switching to a branch that has never been checked out) at_path { git_with_identity('checkout', '--force', '-b', revision, '--track', "#{@resource.value(:remote)}/#{revision}") } else # tags, locally existant branches (perhaps outdated), and shas at_path { git_with_identity('checkout', '--force', revision) } end unstash if keep_local_changes == :true end # @!visibility private def reset(desired) at_path do git_with_identity('reset', '--hard', desired) end end # @!visibility private def update_submodules at_path do git_with_identity('submodule', 'update', '--init', '--recursive') end end # Determins if the branch exists at the upstream but has not yet been locally committed # @!visibility private def remote_branch_revision?(revision = @resource.value(:revision)) # git < 1.6 returns '#{@resource.value(:remote)}/#{revision}' # git 1.6+ returns 'remotes/#{@resource.value(:remote)}/#{revision}' branch = at_path { branches.grep %r{(remotes/)?#{@resource.value(:remote)}/#{revision}$} } branch unless branch.empty? end # Determins if the branch is already cached locally # @!visibility private def local_branch_revision?(revision = @resource.value(:revision)) at_path { branches.include?(revision) } end # @!visibility private def tag_revision?(revision = @resource.value(:revision)) at_path { tags.include?(revision) } end # @!visibility private def branches at_path { git_with_identity('branch', '--no-color', '-a') }.tr('*', ' ').split(%r{\n}).map { |line| line.strip } end # git < 2.4 returns 'detached from' # git 2.4+ returns 'HEAD detached at' # @!visibility private def on_branch? at_path do matches = git_with_identity('branch', '--no-color', '-a').match %r{\*\s+(.*)} - matches[1] unless matches[1] =~ %r{(\(detached from|\(HEAD detached at|\(no branch)} + matches[1] unless %r{(\(detached from|\(HEAD detached at|\(no branch)}.match?(matches[1]) end end # @!visibility private def tags at_path { git_with_identity('tag', '-l') }.split(%r{\n}).map { |line| line.strip } end # @!visibility private def set_excludes # Excludes may be an Array or a String. at_path do open('.git/info/exclude', 'w') do |f| if @resource.value(:excludes).respond_to?(:each) @resource.value(:excludes).each { |ex| f.puts ex } else f.puts @resource.value(:excludes) end end end end # @!visibility private def stash at_path { git_with_identity('stash', 'save') } end # @!visibility private def unstash at_path { git_with_identity('stash', 'pop') } end # Finds the latest revision or sha of the current branch if on a branch, or # of HEAD otherwise. # @note Calls create which can forcibly destroy and re-clone the repo if # force => true # @see get_revision # # @!visibility private # @return [String] Returns the output of get_revision def latest_revision # TODO: Why is create called here anyway? create if @resource.value(:force) && working_copy_exists? create unless working_copy_exists? branch = on_branch? return get_revision("#{@resource.value(:remote)}/#{branch}") if branch get_revision end # Returns the current revision given if the revision is a tag or branch and # matches the current sha. If the current sha does not match the sha of a tag # or branch, then it will just return the sha (ie, is not in sync) # # @!visibility private # # @param [String] rev The revision of which to check if it is current # @return [String] Returns the tag/branch of the current repo if it's up to # date; otherwise returns the sha of the requested revision. def get_revision(rev = 'HEAD') unless @resource.value(:source) status = at_path { git_with_identity('status') } is_it_new = status =~ %r{Initial commit|No commits yet} if is_it_new status =~ %r{On branch (.*)} branch = Regexp.last_match(1) return branch end end current = at_path { git_with_identity('rev-parse', rev).strip } if @resource.value(:revision) == current # if already pointed at desired revision, it must be a SHA, so just return it return current end if @resource.value(:source) update_references end if @resource.value(:revision) canonical = if tag_revision? # git-rev-parse will give you the hash of the tag object itself rather # than the commit it points to by default. Using tag^0 will return the # actual commit. at_path { git_with_identity('rev-parse', "#{@resource.value(:revision)}^0").strip } elsif local_branch_revision? at_path { git_with_identity('rev-parse', @resource.value(:revision)).strip } elsif remote_branch_revision? at_path { git_with_identity('rev-parse', "#{@resource.value(:remote)}/#{@resource.value(:revision)}").strip } else # look for a sha (could match invalid shas) at_path { git_with_identity('rev-parse', '--revs-only', @resource.value(:revision)).strip } end raise("#{@resource.value(:revision)} is not a local or remote ref") if canonical.nil? || canonical.empty? current = @resource.value(:revision) if current == canonical end current end # @!visibility private def update_owner_and_excludes if @resource.value(:owner) || @resource.value(:group) set_ownership end set_excludes if @resource.value(:excludes) end def git_version git('--version').match(%r{[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?})[0] end # @!visibility private def git_with_identity(*args) if @resource.value(:trust_server_cert) == :true git_ver = git_version git_ver_err = "Can't set sslVerify to false, the -c parameter is not supported in Git #{git_ver}. Please install Git 1.7.2 or higher." return raise(git_ver_err) unless Gem::Version.new(git_ver) >= Gem::Version.new('1.7.2') args.unshift('-c', 'http.sslVerify=false') end if @resource.value(:identity) Tempfile.open('git-helper', Puppet[:statedir]) do |f| f.puts '#!/bin/sh' f.puts 'SSH_AUTH_SOCKET=' f.puts 'export SSH_AUTH_SOCKET' f.puts 'exec ssh -oStrictHostKeyChecking=no -oPasswordAuthentication=no -oKbdInteractiveAuthentication=no ' \ "-oChallengeResponseAuthentication=no -oConnectTimeout=120 -i #{@resource.value(:identity)} $*" f.close FileUtils.chmod(0o755, f.path) env_git_ssh_save = ENV['GIT_SSH'] env_git_ssh_command_save = ENV['GIT_SSH_COMMAND'] ENV['GIT_SSH'] = f.path ENV['GIT_SSH_COMMAND'] = nil # Unset GIT_SSH_COMMAND environment variable ret = git(*args) ENV['GIT_SSH'] = env_git_ssh_save ENV['GIT_SSH_COMMAND'] = env_git_ssh_command_save return ret end elsif @resource.value(:user) && @resource.value(:user) != Facter['id'].value env = Etc.getpwnam(@resource.value(:user)) Puppet::Util::Execution.execute("git #{args.join(' ')}", uid: @resource.value(:user), failonfail: true, custom_environment: { 'HOME' => env['dir'] }, combine: true) else git(*args) end end end diff --git a/lib/puppet/provider/vcsrepo/hg.rb b/lib/puppet/provider/vcsrepo/hg.rb index 3df61b0..1b20773 100644 --- a/lib/puppet/provider/vcsrepo/hg.rb +++ b/lib/puppet/provider/vcsrepo/hg.rb @@ -1,153 +1,155 @@ +# frozen_string_literal: true + require File.join(File.dirname(__FILE__), '..', 'vcsrepo') Puppet::Type.type(:vcsrepo).provide(:hg, parent: Puppet::Provider::Vcsrepo) do desc 'Supports Mercurial repositories' commands hg: 'hg' has_features :reference_tracking, :ssh_identity, :user, :basic_auth def create check_force if !@resource.value(:source) create_repository(@resource.value(:path)) else clone_repository(@resource.value(:revision)) end update_owner end def working_copy_exists? return false unless File.directory?(@resource.value(:path)) begin hg_wrapper('status', @resource.value(:path)) - return true + true rescue Puppet::ExecutionFailure - return false + false end end def exists? working_copy_exists? end def destroy FileUtils.rm_rf(@resource.value(:path)) end def latest? at_path do return revision == latest end end def latest at_path do begin hg_wrapper('incoming', '--branch', '.', '--newest-first', '--limit', '1', remote: true)[%r{^changeset:\s+(?:-?\d+):(\S+)}m, 1] rescue Puppet::ExecutionFailure # If there are no new changesets, return the current nodeid revision end end end def revision at_path do current = hg_wrapper('parents')[%r{^changeset:\s+(?:-?\d+):(\S+)}m, 1] desired = @resource.value(:revision) if desired # Return the tag name if it maps to the current nodeid mapped = hg_wrapper('tags')[%r{^#{Regexp.quote(desired)}\s+\d+:(\S+)}m, 1] if current == mapped desired else current end else current end end end def revision=(desired) at_path do begin hg_wrapper('pull', remote: true) rescue StandardError next end begin hg_wrapper('merge') rescue Puppet::ExecutionFailure next # If there's nothing to merge, just skip end hg_wrapper('update', '--clean', '-r', desired) end update_owner end def source at_path do hg_wrapper('paths')[%r{^default = (.*)}, 1] end end def source=(_desired) create # recreate end private def create_repository(path) hg_wrapper('init', path) end def clone_repository(revision) args = ['clone'] if revision args.push('-u', revision) end args.push(@resource.value(:source), @resource.value(:path)) args.push(remote: true) hg_wrapper(*args) end def update_owner set_ownership if @resource.value(:owner) || @resource.value(:group) end def sensitive? (@resource.parameters.key?(:basic_auth_password) && @resource.parameters[:basic_auth_password].sensitive) ? true : false # Check if there is a sensitive parameter end def hg_wrapper(*args) options = { remote: false } if !args.empty? && args[-1].is_a?(Hash) options.merge!(args.pop) end if @resource.value(:basic_auth_username) && @resource.value(:basic_auth_password) args += [ '--config', "auth.x.prefix=#{@resource.value(:source)}", '--config', "auth.x.username=#{@resource.value(:basic_auth_username)}", '--config', "auth.x.password=#{sensitive? ? @resource.value(:basic_auth_password).unwrap : @resource.value(:basic_auth_password)}", '--config', 'auth.x.schemes=http https' ] end if options[:remote] && @resource.value(:identity) args += ['--ssh', "ssh -oStrictHostKeyChecking=no -oPasswordAuthentication=no -oKbdInteractiveAuthentication=no -oChallengeResponseAuthentication=no -i #{@resource.value(:identity)}"] end args.map! { |a| (a =~ %r{\s}) ? "'#{a}'" : a } # Adds quotes to arguments with whitespaces. if @resource.value(:user) && @resource.value(:user) != Facter['id'].value Puppet::Util::Execution.execute("hg #{args.join(' ')}", uid: @resource.value(:user), failonfail: true, combine: true, sensitive: sensitive?) else Puppet::Util::Execution.execute("hg #{args.join(' ')}", sensitive: sensitive?) end end end diff --git a/lib/puppet/provider/vcsrepo/p4.rb b/lib/puppet/provider/vcsrepo/p4.rb index ff821cc..8b213a1 100644 --- a/lib/puppet/provider/vcsrepo/p4.rb +++ b/lib/puppet/provider/vcsrepo/p4.rb @@ -1,284 +1,286 @@ +# frozen_string_literal: true + require File.join(File.dirname(__FILE__), '..', 'vcsrepo') Puppet::Type.type(:vcsrepo).provide(:p4, parent: Puppet::Provider::Vcsrepo) do desc 'Supports Perforce depots' has_features :filesystem_types, :reference_tracking, :p4config def create check_force # create or update client create_client(client_name) # if source provided, sync client source = @resource.value(:source) if source revision = @resource.value(:revision) sync_client(source, revision) end update_owner end def working_copy_exists? # Check if the server is there, or raise error p4(['info'], marshal: false) # Check if workspace is setup args = ['where'] args.push(@resource.value(:path) + '/...') hash = p4(args, raise: false) (hash['code'] != 'error') end def exists? working_copy_exists? end def destroy args = ['client'] args.push('-d', '-f') args.push(client_name) p4(args) FileUtils.rm_rf(@resource.value(:path)) end def latest? rev = revision if rev (rev >= latest) else true end end def latest args = ['changes'] args.push('-m1', @resource.value(:source)) hash = p4(args) hash['change'].to_i end def revision args = ['cstat'] args.push(@resource.value(:source)) hash = p4(args, marshal: false) hash = marshal_cstat(hash) revision = 0 if hash && hash['code'] != 'error' hash['data'].each do |c| if c['status'] == 'have' change = c['change'].to_i revision = change if change > revision end end end revision end def revision=(desired) sync_client(@resource.value(:source), desired) update_owner end def source args = ['where'] args.push(@resource.value(:path) + '/...') hash = p4(args, raise: false) hash['depotFile'] end def source=(_desired) create # recreate end private def update_owner set_ownership if @resource.value(:owner) || @resource.value(:group) end # Sync the client workspace files to head or specified revision. # Params: # +source+:: Depot path to sync # +revision+:: Perforce change list to sync to (optional) def sync_client(source, revision) Puppet.debug "Syncing: #{source}" args = ['sync'] if revision args.push(source + "@#{revision}") else args.push(source) end p4(args) end # Returns the name of the Perforce client workspace def client_name p4config = @resource.value(:p4config) # default (generated) client name path = @resource.value(:path) host = Facter.value('hostname') default = 'puppet-' + Digest::MD5.hexdigest(path + host) # check config for client name set_client = nil if p4config && File.file?(p4config) open(p4config) do |f| m = f.grep(%r{^P4CLIENT=}).pop p = %r{^P4CLIENT=(.*)$} set_client = p.match(m)[1] if m end end set_client || ENV['P4CLIENT'] || default end # Create (or update) a client workspace spec. # If a client name is not provided then a hash based on the path is used. # Params: # +client+:: Name of client workspace # +path+:: The Root location of the Perforce client workspace def create_client(client) Puppet.debug "Creating client: #{client}" # fetch client spec hash = parse_client(client) hash['Root'] = @resource.value(:path) hash['Description'] = 'Generated by Puppet VCSrepo' # check is source is a Stream source = @resource.value(:source) if source parts = source.split(%r{/}) if parts && parts.length >= 4 source = '//' + parts[2] + '/' + parts[3] streams = p4(['streams', source], raise: false) if streams['code'] == 'stat' hash['Stream'] = streams['Stream'] notice 'Streams' + streams['Stream'].inspect end end end # save client spec save_client(hash) end # Fetches a client workspace spec from Perforce and returns a hash map representation. # Params: # +client+:: name of the client workspace def parse_client(client) args = ['client'] args.push('-o', client) hash = p4(args) hash end # Saves the client workspace spec from the given hash # Params: # +hash+:: hash map of client spec def save_client(hash) spec = '' view = "\nView:\n" hash.keys.sort.each do |k| v = hash[k] next if k == 'code' - if k.to_s =~ %r{View} + if %r{View}.match?(k.to_s) view += "\t#{v}\n" else spec += "#{k}: #{v}\n" end end spec += view args = ['client'] args.push('-i') p4(args, input: spec, marshal: false) end # Sets Perforce Configuration environment. # P4CLIENT generated, but overwitten if defined in config. def config p4config = @resource.value(:p4config) cfg = {} cfg.store 'P4CONFIG', p4config if p4config cfg.store 'P4CLIENT', client_name cfg end def p4(args, options = {}) # Merge custom options with defaults opts = { raise: true, # Raise errors marshal: true, # Marshal output }.merge(options) cmd = ['p4'] cmd.push '-R' if opts[:marshal] cmd.push args cmd_str = cmd.respond_to?(:join) ? cmd.join(' ') : cmd Puppet.debug "environment: #{config}" Puppet.debug "command: #{cmd_str}" hash = {} Open3.popen3(config, cmd_str) do |i, o, e, t| # Send input stream if provided if opts[:input] Puppet.debug "input:\n" + opts[:input] i.write opts[:input] i.close end if opts[:marshal] hash = Marshal.dump(o) else hash['data'] = o.read end # Raise errors, Perforce or Exec if opts[:raise] && !e.eof && t.value != 0 raise Puppet::Error, "\nP4: #{e.read}" end if opts[:raise] && hash['code'] == 'error' && t.value != 0 raise Puppet::Error, "\nP4: #{hash['data']}" end end Puppet.debug "hash: #{hash}\n" hash end # helper method as cstat does not Marshal def marshal_cstat(hash) data = hash['data'] code = 'error' list = [] change = {} data.each_line do |l| p = %r{^\.\.\. (.*) (.*)$} m = p.match(l) next unless m change[m[1]] = m[2] next unless m[1] == 'status' code = 'stat' list.push change change = {} end hash = {} hash.store 'code', code hash.store 'data', list hash end end diff --git a/lib/puppet/provider/vcsrepo/svn.rb b/lib/puppet/provider/vcsrepo/svn.rb index 4d73883..69b73b9 100644 --- a/lib/puppet/provider/vcsrepo/svn.rb +++ b/lib/puppet/provider/vcsrepo/svn.rb @@ -1,308 +1,312 @@ +# frozen_string_literal: true + require File.join(File.dirname(__FILE__), '..', 'vcsrepo') Puppet::Type.type(:vcsrepo).provide(:svn, parent: Puppet::Provider::Vcsrepo) do desc 'Supports Subversion repositories' commands svn: 'svn', svnadmin: 'svnadmin', svnlook: 'svnlook' has_features :filesystem_types, :reference_tracking, :basic_auth, :configuration, :conflict, :depth, :include_paths def create check_force if !@resource.value(:source) if @resource.value(:includes) raise Puppet::Error, 'Specifying include paths on a nonexistent repo.' end create_repository(@resource.value(:path)) else if @resource.value(:basic_auth_username) && !@resource.value(:basic_auth_password) raise("You must specify the HTTP basic authentication password for user '#{@resource.value(:basic_auth_username)}'") end if !@resource.value(:basic_auth_username) && @resource.value(:basic_auth_password) raise('You must specify the HTTP basic authentication username') end if @resource.value(:basic_auth_username) && @resource.value(:basic_auth_password) - if @resource.value(:basic_auth_password).to_s =~ %r{[\u007B-\u00BF\u02B0-\u037F\u2000-\u2BFF]} + if %r{[\u007B-\u00BF\u02B0-\u037F\u2000-\u2BFF]}.match?(@resource.value(:basic_auth_password).to_s) raise('The password can not contain non-ASCII characters') end end checkout_repository(@resource.value(:source), @resource.value(:path), @resource.value(:revision), @resource.value(:depth)) end if @resource.value(:includes) validate_version update_includes(@resource.value(:includes)) end update_owner end def working_copy_exists? return false unless File.directory?(@resource.value(:path)) if @resource.value(:source) begin svn_wrapper('info', @resource.value(:path)) - return true + true rescue Puppet::ExecutionFailure => detail - if detail.message =~ %r{This client is too old} + if %r{This client is too old}.match?(detail.message) raise Puppet::Error, detail.message end - return false + false end else begin svnlook('uuid', @resource.value(:path)) - return true + true rescue Puppet::ExecutionFailure - return false + false end end end def exists? working_copy_exists? end def destroy FileUtils.rm_rf(@resource.value(:path)) end def latest? at_path do (revision >= latest) && (@resource.value(:source) == source) end end def buildargs args = ['--non-interactive'] if @resource.value(:basic_auth_username) && @resource.value(:basic_auth_password) args.push('--username', @resource.value(:basic_auth_username)) args.push('--password', sensitive? ? @resource.value(:basic_auth_password).unwrap : @resource.value(:basic_auth_password)) args.push('--no-auth-cache') end if @resource.value(:configuration) args.push('--config-dir', @resource.value(:configuration)) end if @resource.value(:trust_server_cert) != :false args.push('--trust-server-cert') end args end def latest args = buildargs.push('info', '-r', 'HEAD') at_path do svn_wrapper(*args)[%r{^Revision:\s+(\d+)}m, 1] end end def source args = buildargs.push('info') at_path do svn_wrapper(*args)[%r{^URL:\s+(\S+)}m, 1] end end def source=(desired) args = buildargs.push('switch') if @resource.value(:force) args.push('--force') end if @resource.value(:revision) args.push('-r', @resource.value(:revision)) end if @resource.value(:conflict) args.push('--accept', @resource.value(:conflict)) end args.push(desired) at_path do svn_wrapper(*args) end update_owner end def revision args = buildargs.push('info') at_path do svn_wrapper(*args)[%r{^Revision:\s+(\d+)}m, 1] end end def revision=(desired) args = if @resource.value(:source) buildargs.push('switch', '-r', desired, @resource.value(:source)) else buildargs.push('update', '-r', desired) end if @resource.value(:force) args.push('--force') end if @resource.value(:conflict) args.push('--accept', @resource.value(:conflict)) end at_path do svn_wrapper(*args) end update_owner end def includes return nil if Gem::Version.new(return_svn_client_version) < Gem::Version.new('1.6.0') get_includes('.') end def includes=(desired) validate_version exists = includes old_paths = exists - desired new_paths = desired - exists # Remove paths that are no longer specified old_paths.each { |path| delete_include(path) } update_includes(new_paths) end private def svn_wrapper(*args) Puppet::Util::Execution.execute("svn #{args.join(' ')}", sensitive: sensitive?) end def sensitive? (@resource.parameters.key?(:basic_auth_password) && @resource.parameters[:basic_auth_password].sensitive) ? true : false # Check if there is a sensitive parameter end + SKIP_DIRS = ['.', '..', '.svn'].freeze + def get_includes(directory) at_path do args = buildargs.push('info', directory) if svn_wrapper(*args)[%r{^Depth:\s+(\w+)}m, 1] != 'empty' return directory[2..-1].gsub(File::SEPARATOR, '/') end Dir.entries(directory).map { |entry| - next if ['.', '..', '.svn'].include?(entry) + next if SKIP_DIRS.include?(entry) entry = File.join(directory, entry) if File.directory?(entry) get_includes(entry) elsif File.file?(entry) entry[2..-1].gsub(File::SEPARATOR, '/') end }.flatten.compact! end end def delete_include(path) at_path do # svn version 1.6 has an incorrect implementation of the `exclude` # parameter to `--set-depth`; it doesn't handle files, only # directories. I know, I rolled my eyes, too. svn_ver = return_svn_client_version if Gem::Version.new(svn_ver) < Gem::Version.new('1.7.0') && !File.directory?(path) # In the non-happy case, we delete the file, and check if the only # thing left in that directory is the .svn folder. If that's the case, # the loop below will take care of excluding the parent directory, and # we're back to a happy case. But, if that's not the case, we need to # fire off a warning telling the user the path can't be excluded. Puppet.debug "Vcsrepo[#{@resource.name}]: Need to handle #{path} removal specially" File.delete(path) - if Dir.entries(File.dirname(path)).sort != ['.', '..', '.svn'] + if Dir.entries(File.dirname(path)).sort != SKIP_DIRS Puppet.warning "Unable to exclude #{path} from Vcsrepo[#{@resource.name}]; update to subversion >= 1.7" end else Puppet.debug "Vcsrepo[#{@resource.name}]: Can remove #{path} directly using svn" args = buildargs.push('update', '--set-depth', 'exclude', path) svn_wrapper(*args) end # Keep walking up the parent directories of this include until we find # a non-empty folder, excluding as we go. while (path = path.rpartition(File::SEPARATOR)[0]) != '' entries = Dir.entries(path).sort - break if entries != ['.', '..'] && entries != ['.', '..', '.svn'] + break if entries != ['.', '..'] && entries != SKIP_DIRS args = buildargs.push('update', '--set-depth', 'exclude', path) svn_wrapper(*args) end end end def checkout_repository(source, path, revision, depth) args = buildargs.push('checkout') if revision args.push('-r', revision) end if @resource.value(:includes) # Make root checked out at empty depth to provide sparse directories args.push('--depth', 'empty') elsif depth args.push('--depth', depth) end args.push(source, path) svn_wrapper(*args) end def create_repository(path) args = ['create'] if @resource.value(:fstype) args.push('--fs-type', @resource.value(:fstype)) end args << path svnadmin(*args) end def update_owner set_ownership if @resource.value(:owner) || @resource.value(:group) end def update_includes(paths) at_path do args = buildargs.push('update') args.push('--depth', 'empty') if @resource.value(:revision) args.push('-r', @resource.value(:revision)) end parents = paths.map { |path| File.dirname(path) } parents = make_include_paths(parents) args.push(*parents) svn_wrapper(*args) args = buildargs.push('update') if @resource.value(:revision) args.push('-r', @resource.value(:revision)) end if @resource.value(:depth) args.push('--depth', @resource.value(:depth)) end args.push(*paths) svn_wrapper(*args) end end def make_include_paths(includes) includes.map { |inc| prefix = nil inc.split('/').map do |path| prefix = [prefix, path].compact.join('/') end }.flatten end def return_svn_client_version Facter.value('vcsrepo_svn_ver').dup end def validate_version svn_ver = return_svn_client_version raise "Includes option is not available for SVN versions < 1.6. Version installed: #{svn_ver}" if Gem::Version.new(svn_ver) < Gem::Version.new('1.6.0') end end diff --git a/lib/puppet/type/vcsrepo.rb b/lib/puppet/type/vcsrepo.rb index 7f869a0..23d1649 100644 --- a/lib/puppet/type/vcsrepo.rb +++ b/lib/puppet/type/vcsrepo.rb @@ -1,316 +1,318 @@ +# frozen_string_literal: true + require 'pathname' require 'puppet/parameter/boolean' Puppet::Type.newtype(:vcsrepo) do desc 'A local version control repository' feature :gzip_compression, 'The provider supports explicit GZip compression levels' feature :basic_auth, 'The provider supports HTTP Basic Authentication' feature :bare_repositories, "The provider differentiates between bare repositories and those with working copies", methods: [:bare_exists?, :working_copy_exists?] feature :filesystem_types, 'The provider supports different filesystem types' feature :reference_tracking, "The provider supports tracking revision references that can change over time (eg, some VCS tags and branch names)" feature :ssh_identity, 'The provider supports a configurable SSH identity file' feature :user, 'The provider can run as a different user' feature :modules, 'The repository contains modules that can be chosen of' feature :multiple_remotes, 'The repository tracks multiple remote repositories' feature :configuration, 'The configuration directory to use' feature :cvs_rsh, 'The provider understands the CVS_RSH environment variable' feature :depth, 'The provider can do shallow clones or set scope limit' feature :branch, 'The name of the branch' feature :p4config, 'The provider understands Perforce Configuration' feature :submodules, 'The repository contains submodules which can be optionally initialized' feature :conflict, 'The provider supports automatic conflict resolution' feature :include_paths, 'The provider supports checking out only specific paths' feature :keep_local_changes, 'The provider supports keeping local changes on files tracked by the repository when changing revision' ensurable do desc 'Ensure the version control repository.' attr_accessor :latest def insync?(is) @should ||= [] case should when :present return true unless [:absent, :purged, :held].include?(is) when :latest return true if is == :latest - return false + false when :bare - return is == :bare + is == :bare when :mirror - return is == :mirror + is == :mirror when :absent - return is == :absent + is == :absent end end newvalue :present do if !provider.exists? provider.create elsif provider.class.feature?(:bare_repositories) && provider.bare_exists? provider.convert_bare_to_working_copy end end newvalue :bare, required_features: [:bare_repositories] do if !provider.exists? provider.create elsif provider.working_copy_exists? provider.convert_working_copy_to_bare elsif provider.mirror? provider.set_no_mirror end end newvalue :mirror, required_features: [:bare_repositories] do if !provider.exists? provider.create elsif provider.working_copy_exists? provider.convert_working_copy_to_bare elsif !provider.mirror? provider.set_mirror end end newvalue :absent do provider.destroy end newvalue :latest, required_features: [:reference_tracking] do if provider.exists? && !@resource.value(:force) if provider.class.feature?(:bare_repositories) && provider.bare_exists? provider.convert_bare_to_working_copy end if provider.respond_to?(:update_references) provider.update_references end reference = if provider.respond_to?(:latest?) provider.latest || provider.revision else resource.value(:revision) || provider.revision end notice "Updating to latest '#{reference}' revision" provider.revision = reference else notice 'Creating repository from latest' provider.create end end def retrieve prov = @resource.provider raise Puppet::Error, 'Could not find provider' unless prov if prov.working_copy_exists? (@should.include?(:latest) && prov.latest?) ? :latest : :present elsif prov.class.feature?(:bare_repositories) && prov.bare_exists? if prov.mirror? :mirror else :bare end else :absent end end end newparam :path do desc 'Absolute path to repository' isnamevar validate do |value| path = Pathname.new(value) unless path.absolute? raise ArgumentError, "Path must be absolute: #{path}" end end end newproperty :source do desc 'The source URI for the repository' # Tolerate versions/providers that strip/add trailing slashes def insync?(is) # unwrap @should should = @should[0] return true if is == should begin if should[-1] == '/' return true if is == should[0..-2] elsif is[-1] == '/' return true if is[0..-2] == should end rescue StandardError return end false end end newparam :fstype, required_features: [:filesystem_types] do desc 'Filesystem type' end newproperty :revision do desc 'The revision of the repository' newvalue(%r{^\S+$}) end newparam :owner do desc 'The user/uid that owns the repository files' end newparam :group do desc 'The group/gid that owns the repository files' end newparam :user do desc 'The user to run for repository operations' end newparam :excludes do desc "Local paths which shouldn't be tracked by the repository" end newproperty :includes, required_features: [:include_paths], array_matching: :all do desc 'Paths to be included from the repository' def insync?(is) if is.is_a?(Array) && @should.is_a?(Array) is.sort == @should.sort else is == @should end end validate do |path| raise Puppet::Error, "Include path '#{path}' starts with a '/'; remove it" if path[0..0] == '/' super(path) end end newparam(:force, boolean: true, parent: Puppet::Parameter::Boolean) do desc 'Force repository creation, destroying any files on the path in the process.' defaultto false end newparam :compression, required_features: [:gzip_compression] do desc 'Compression level' validate do |amount| unless Integer(amount).between?(0, 6) raise ArgumentError, "Unsupported compression level: #{amount} (expected 0-6)" end end end newparam :basic_auth_username, required_features: [:basic_auth] do desc 'HTTP Basic Auth username' end newparam :basic_auth_password, required_features: [:basic_auth] do desc 'HTTP Basic Auth password' end newparam :identity, required_features: [:ssh_identity] do desc 'SSH identity file' end newproperty :module, required_features: [:modules] do desc 'The repository module to manage' end newparam :remote, required_features: [:multiple_remotes] do desc 'The remote repository to track' defaultto 'origin' end newparam :configuration, required_features: [:configuration] do desc 'The configuration directory to use' end newparam :cvs_rsh, required_features: [:cvs_rsh] do desc 'The value to be used for the CVS_RSH environment variable.' end newparam :depth, required_features: [:depth] do desc 'The value to be used to do a shallow clone.' end newparam :branch, required_features: [:branch] do desc 'The name of the branch to clone.' end newparam :p4config, required_features: [:p4config] do desc 'The Perforce P4CONFIG environment.' end newparam :submodules, required_features: [:submodules] do desc 'Initialize and update each submodule in the repository.' newvalues(true, false) defaultto true end newparam :conflict do desc 'The action to take if conflicts exist between repository and working copy' end newparam :trust_server_cert do desc 'Trust server certificate' newvalues(true, false) defaultto :false end newparam :keep_local_changes do desc 'Keep local changes on files tracked by the repository when changing revision' newvalues(true, false) defaultto :false end autorequire(:package) do ['git', 'git-core', 'mercurial', 'subversion'] end private - def set_sensitive_parameters(sensitive_parameters) # rubocop:disable Style/AccessorMethodName + def set_sensitive_parameters(sensitive_parameters) # rubocop:disable Naming/AccessorMethodName if sensitive_parameters.include?(:basic_auth_password) sensitive_parameters.delete(:basic_auth_password) parameter(:basic_auth_password).sensitive = true end super(sensitive_parameters) end end diff --git a/metadata.json b/metadata.json index 626e39f..822aef8 100644 --- a/metadata.json +++ b/metadata.json @@ -1,82 +1,82 @@ { "name": "puppetlabs-vcsrepo", "version": "3.1.1", "author": "puppetlabs", "summary": "Puppet module providing a type to manage repositories from various version control systems", "license": "GPL-2.0+", "source": "https://github.com/puppetlabs/puppetlabs-vcsrepo", "project_page": "https://github.com/puppetlabs/puppetlabs-vcsrepo", "issues_url": "https://tickets.puppetlabs.com/browse/MODULES", "dependencies": [ ], "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 SP1", "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" } ], "template-url": "https://github.com/puppetlabs/pdk-templates#main", - "template-ref": "heads/main-0-g24afa8d", + "template-ref": "heads/main-0-g4543421", "pdk-version": "1.18.1" } diff --git a/spec/acceptance/clone_repo_spec.rb b/spec/acceptance/clone_repo_spec.rb index 31b202e..926667f 100644 --- a/spec/acceptance/clone_repo_spec.rb +++ b/spec/acceptance/clone_repo_spec.rb @@ -1,552 +1,554 @@ +# frozen_string_literal: true + require 'spec_helper_acceptance' tmpdir = '/tmp/vcsrepo' describe 'clones a remote repo' do before(:all) do my_root = File.expand_path(File.join(File.dirname(__FILE__), '..')) run_shell("rm -rf #{tmpdir}") bolt_upload_file("#{my_root}/acceptance/files", tmpdir, 'create_git_repo.sh') run_shell("cd #{tmpdir} && ./create_git_repo.sh") end after(:all) do run_shell("rm -rf #{tmpdir}/testrepo") run_shell("rm -rf #{tmpdir}/testrepo_mirror_repo") end context 'with get the current master HEAD' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo": ensure => present, provider => git, source => "file://#{tmpdir}/testrepo.git", } MANIFEST it 'clones a repo' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/testrepo/.git") do it { is_expected.to be_directory } end describe file("#{tmpdir}/testrepo/.git/HEAD") do it { is_expected.to contain 'ref: refs/heads/master' } end end context 'with using a https source on github', unless: only_supports_weak_encryption do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/httpstestrepo": ensure => present, provider => git, source => "https://github.com/puppetlabs/puppetlabs-vcsrepo.git", } MANIFEST it 'clones a repo' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/httpstestrepo/.git") do it { is_expected.to be_directory } end describe file("#{tmpdir}/httpstestrepo/.git/HEAD") do it { is_expected.to contain 'ref: refs/heads/main' } end end context 'with using a commit SHA' do let(:sha) do run_shell("git --git-dir=#{tmpdir}/testrepo.git rev-list HEAD | tail -1").stdout.chomp end after(:all) do run_shell("rm -rf #{tmpdir}/testrepo_sha") end it 'clones a repo' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_sha": ensure => present, provider => git, source => "file://#{tmpdir}/testrepo.git", revision => "#{sha}", } MANIFEST # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/testrepo_sha/.git") do it { is_expected.to be_directory } end describe file("#{tmpdir}/testrepo_sha/.git/HEAD") do it { is_expected.to contain sha } end end context 'with using a tag' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_tag": ensure => present, provider => git, source => "file://#{tmpdir}/testrepo.git", revision => '0.0.2', } MANIFEST it 'clones a repo' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/testrepo_tag/.git") do it { is_expected.to be_directory } end it 'has the tag as the HEAD' do run_shell("git --git-dir=#{tmpdir}/testrepo_tag/.git name-rev HEAD | grep '0.0.2'") end end context 'with using a branch name' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_branch": ensure => present, provider => git, source => "file://#{tmpdir}/testrepo.git", revision => 'a_branch', } MANIFEST it 'clones a repo' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/testrepo_branch/.git") do it { is_expected.to be_directory } end describe file("#{tmpdir}/testrepo_branch/.git/HEAD") do it { is_expected.to contain 'ref: refs/heads/a_branch' } end end context 'with ensure latest with branch specified' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_latest": ensure => latest, provider => git, source => "file://#{tmpdir}/testrepo.git", revision => 'a_branch', } MANIFEST it 'clones a repo' do # Run it twice and test for idempotency idempotent_apply(pp) end it 'verifies the HEAD commit SHA on remote and local match' do remote_commit = run_shell("git ls-remote file://#{tmpdir}/testrepo_latest HEAD | head -1").stdout local_commit = run_shell("git --git-dir=#{tmpdir}/testrepo_latest/.git rev-parse HEAD").stdout.chomp expect(remote_commit).to include(local_commit) end end context 'with ensure latest with branch unspecified' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_latest": ensure => latest, provider => git, source => "file://#{tmpdir}/testrepo.git", } MANIFEST it 'clones a repo' do # Run it twice and test for idempotency idempotent_apply(pp) end it 'verifies the HEAD commit SHA on remote and local match' do remote_commit = run_shell("git ls-remote file://#{tmpdir}/testrepo_latest HEAD | head -1").stdout local_commit = run_shell("git --git-dir=#{tmpdir}/testrepo_latest/.git rev-parse HEAD").stdout.chomp expect(remote_commit).to include(local_commit) end end context 'with with shallow clone' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_shallow": ensure => present, provider => git, source => "file://#{tmpdir}/testrepo.git", depth => '1', } MANIFEST it 'does a shallow clone' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/testrepo_shallow/.git/shallow") do it { is_expected.to be_file } end end context 'with path is not empty and not a repository' do before(:all) do run_shell("mkdir #{tmpdir}/not_a_repo", acceptable_exit_codes: [0, 1]) run_shell("touch #{tmpdir}/not_a_repo/file1.txt", acceptable_exit_codes: [0, 1]) end pp = <<-MANIFEST vcsrepo { "#{tmpdir}/not_a_repo": ensure => present, provider => git source => "file://#{tmpdir}/testrepo.git", } MANIFEST it 'raises an exception' do apply_manifest(pp, expect_failures: true) end end context 'with with an owner' do pp = <<-MANIFEST user { 'vagrant': ensure => present, } MANIFEST apply_manifest(pp, catch_failures: true) pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_owner": ensure => present, provider => git, source => "file://#{tmpdir}/testrepo.git", owner => 'vagrant', } MANIFEST it 'clones a repo' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/testrepo_owner") do it { is_expected.to be_directory } it { is_expected.to be_owned_by 'vagrant' } end end context 'with with a group' do pp = <<-MANIFEST group { 'vagrant': ensure => present, } MANIFEST apply_manifest(pp, catch_failures: true) pp = <<-MANIFEST vcsrepo { "/#{tmpdir}/testrepo_group": ensure => present, provider => git, source => "file://#{tmpdir}/testrepo.git", group => 'vagrant', } MANIFEST it 'clones a repo' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/testrepo_group") do it { is_expected.to be_directory } it { is_expected.to be_grouped_into 'vagrant' } end end context 'with with excludes' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_excludes": ensure => present, provider => git, source => "file://#{tmpdir}/testrepo.git", excludes => ['exclude1.txt', 'exclude2.txt'], } MANIFEST it 'clones a repo' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/testrepo_excludes/.git/info/exclude") do subject { super().content } it { is_expected.to match %r{exclude1.txt} } end describe file("#{tmpdir}/testrepo_excludes/.git/info/exclude") do subject { super().content } it { is_expected.to match %r{exclude2.txt} } end end context 'with with force' do before(:all) do run_shell("mkdir -p #{tmpdir}/testrepo_force/folder") run_shell("touch #{tmpdir}/testrepo_force/temp.txt") end it 'applies the manifest' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_force": ensure => present, provider => git, source => "file://#{tmpdir}/testrepo.git", force => true, } MANIFEST # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/testrepo_force/folder") do it { is_expected.not_to be_directory } end describe file("#{tmpdir}/testrepo_force/temp.txt") do it { is_expected.not_to be_file } end describe file("#{tmpdir}/testrepo_force/.git") do it { is_expected.to be_directory } end context 'with and noop' do before(:all) do run_shell("mkdir #{tmpdir}/testrepo_already_exists") run_shell("cd #{tmpdir}/testrepo_already_exists && git init") run_shell("cd #{tmpdir}/testrepo_already_exists && touch a && git add a && git commit -m 'a'") end after(:all) do run_shell("rm -rf #{tmpdir}/testrepo_already_exists") end pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_already_exists": ensure => present, source => "file://#{tmpdir}/testrepo.git", provider => git, force => true, noop => true, } MANIFEST it 'applies the manifest' do apply_manifest(pp, catch_changes: true) end end end context 'with as a user' do before(:all) do run_shell("chmod 707 #{tmpdir}") pp = <<-MANIFEST group { 'testuser': ensure => present, } user { 'testuser': ensure => present, groups => 'testuser', } MANIFEST apply_manifest(pp, catch_failures: true) end pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_user": ensure => present, provider => git, source => "file://#{tmpdir}/testrepo.git", user => 'testuser', } MANIFEST it 'applies the manifest' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/testrepo_user") do it { is_expected.to be_directory } it { is_expected.to be_owned_by 'testuser' } end describe file("#{tmpdir}/testrepo_user") do it { is_expected.to be_directory } it { is_expected.to be_grouped_into 'testuser' } end after(:all) do pp = 'user { "testuser": ensure => absent }' apply_manifest(pp, catch_failures: true) end end context 'with non-origin remote name' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_remote": ensure => present, provider => git, source => "file://#{tmpdir}/testrepo.git", remote => 'testorigin', } MANIFEST it 'applies the manifest' do # Run it twice and test for idempotency idempotent_apply(pp) end it 'remote name is "testorigin"' do run_shell("git --git-dir=#{tmpdir}/testrepo_remote/.git remote | grep 'testorigin'") end end context 'with as a user with ssh - includes special characters' do before(:all) do # create user pp = <<-MANIFEST group { 'testuser-ssh': ensure => present, } user { 'testuser-ssh': ensure => present, groups => 'testuser-ssh', managehome => true, } MANIFEST apply_manifest(pp, catch_failures: true) # create ssh keys run_shell('mkdir -p /home/testuser-ssh/.ssh') run_shell('ssh-keygen -q -t rsa -f /home/testuser-ssh/.ssh/id_rsa -N ""') # copy public key to authorized_keys run_shell('cat /home/testuser-ssh/.ssh/id_rsa.pub > /home/testuser-ssh/.ssh/authorized_keys') run_shell('echo -e "Host localhost\n\tStrictHostKeyChecking no\n" > /home/testuser-ssh/.ssh/config') run_shell('chown -R testuser-ssh:testuser-ssh /home/testuser-ssh/.ssh') end pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_user_ssh": ensure => present, provider => git, source => "git+ssh://testuser-ssh@localhost#{tmpdir}/testrepo.git", user => 'testuser-ssh', } MANIFEST it 'applies the manifest' do # Run it twice and test for idempotency idempotent_apply(pp) end after(:all) do pp = <<-MANIFEST user { 'testuser-ssh': ensure => absent, managehome => true, } MANIFEST sleep 10 apply_manifest(pp, catch_failures: true) end end context 'with using an identity file' do before(:all) do # create user pp = <<-MANIFEST user { 'testuser-ssh': ensure => present, managehome => true, } MANIFEST apply_manifest(pp, catch_failures: true) # create ssh keys run_shell('mkdir -p /home/testuser-ssh/.ssh') run_shell('ssh-keygen -q -t rsa -f /home/testuser-ssh/.ssh/id_rsa -N ""') # copy public key to authorized_keys run_shell('cat /home/testuser-ssh/.ssh/id_rsa.pub > /home/testuser-ssh/.ssh/authorized_keys') run_shell('echo -e "Host localhost\n\tStrictHostKeyChecking no\n" > /home/testuser-ssh/.ssh/config') run_shell('chown -R testuser-ssh:testuser-ssh /home/testuser-ssh/.ssh') end pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_user_ssh_id": ensure => present, provider => git, source => "testuser-ssh@localhost:#{tmpdir}/testrepo.git", identity => '/home/testuser-ssh/.ssh/id_rsa', } MANIFEST it 'applies the manifest' do # Run it twice and test for idempotency idempotent_apply(pp) end end context 'with bare repo' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_bare_repo": ensure => bare, provider => git, source => "file://#{tmpdir}/testrepo.git", } MANIFEST it 'creates a bare repo' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/testrepo_bare_repo/config") do it { is_expected.to contain 'bare = true' } end describe file("#{tmpdir}/testrepo_bare_repo/.git") do it { is_expected.not_to be_directory } end describe file("#{tmpdir}/testrepo_bare_repo/HEAD") do it { is_expected.to contain 'ref: refs/heads/master' } end end context 'with mirror repo' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_mirror_repo": ensure => mirror, provider => git, source => "file://#{tmpdir}/testrepo.git", } MANIFEST it 'creates a mirror repo' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/testrepo_mirror_repo/config") do it { is_expected.to contain 'bare = true' } it { is_expected.to contain 'mirror = true' } end describe file("#{tmpdir}/testrepo_mirror_repo/.git") do it { is_expected.not_to be_directory } end describe file("#{tmpdir}/testrepo_mirror_repo/HEAD") do it { is_expected.to contain 'ref: refs/heads/master' } end end end diff --git a/spec/acceptance/create_repo_spec.rb b/spec/acceptance/create_repo_spec.rb index 945c2cf..c2a9618 100644 --- a/spec/acceptance/create_repo_spec.rb +++ b/spec/acceptance/create_repo_spec.rb @@ -1,98 +1,100 @@ +# frozen_string_literal: true + require 'spec_helper_acceptance' tmpdir = '/tmp/vcsrepo' describe 'create a repo' do context 'with without a source' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_blank_repo": ensure => present, provider => git, } MANIFEST it 'creates a blank repo' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/testrepo_blank_repo/") do it 'has zero files' do run_shell("ls -1 #{tmpdir}/testrepo_blank_repo | wc -l") do |r| expect(r.stdout).to match(%r{^0\n$}) end end end describe file("#{tmpdir}/testrepo_blank_repo/.git") do it { is_expected.to be_directory } end end context 'with no source but revision provided' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_blank_with_revision_repo": ensure => present, provider => git, revision => 'master' } MANIFEST it 'does not fail (MODULES-2125)' do # Run it twice and test for idempotency idempotent_apply(pp) end end context 'with bare repo' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_bare_repo": ensure => bare, provider => git, } MANIFEST it 'creates a bare repo' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/testrepo_bare_repo/config") do it { is_expected.to contain 'bare = true' } end describe file("#{tmpdir}/testrepo_bare_repo/.git") do it { is_expected.not_to be_directory } end end context 'with bare repo with a revision' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_bare_repo_rev": ensure => bare, provider => git, revision => 'master', } MANIFEST it 'does not create a bare repo when a revision is defined' do apply_manifest(pp, expect_failures: true) end describe file("#{tmpdir}/testrepo_bare_repo_rev") do it { is_expected.not_to be_directory } end end context 'with mirror repo' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_mirror_repo": ensure => mirror, provider => git, } MANIFEST it 'does not create a mirror repo' do apply_manifest(pp, expect_failures: true) end describe file("#{tmpdir}/testrepo_mirror_repo") do it { is_expected.not_to be_directory } end end end diff --git a/spec/acceptance/multiple_remotes_spec.rb b/spec/acceptance/multiple_remotes_spec.rb index 4c56d2b..7a8f2ef 100644 --- a/spec/acceptance/multiple_remotes_spec.rb +++ b/spec/acceptance/multiple_remotes_spec.rb @@ -1,65 +1,67 @@ +# frozen_string_literal: true + require 'spec_helper_acceptance' tmpdir = '/tmp/vcsrepo' describe 'clones a remote repo', unless: only_supports_weak_encryption do before(:all) do File.expand_path(File.join(File.dirname(__FILE__), '..')) run_shell("mkdir -p #{tmpdir}") # win test end after(:all) do run_shell("rm -rf #{tmpdir}/vcsrepo") end context 'with clone with single remote' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/vcsrepo": ensure => present, provider => git, source => "https://github.com/puppetlabs/puppetlabs-vcsrepo.git", } MANIFEST it 'clones from default remote' do apply_manifest(pp, catch_failures: true) end it 'git config output should contain the remote' do run_shell("/usr/bin/git config -l -f #{tmpdir}/vcsrepo/.git/config") do |r| expect(r.stdout).to match(%r{remote.origin.url=https://github.com/puppetlabs/puppetlabs-vcsrepo.git}) end end after(:all) do run_shell("rm -rf #{tmpdir}/vcsrepo") end end context 'with clone with multiple remotes' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/vcsrepo": ensure => present, provider => git, source => {"origin" => "https://github.com/puppetlabs/puppetlabs-vcsrepo.git", "test1" => "https://github.com/puppetlabs/puppetlabs-vcsrepo.git"}, } MANIFEST it 'clones from default remote and adds 2 remotes to config file' do idempotent_apply(pp) end it 'git config output should contain the remotes - origin' do run_shell("/usr/bin/git config -l -f #{tmpdir}/vcsrepo/.git/config") do |r| expect(r.stdout).to match(%r{remote.origin.url=https://github.com/puppetlabs/puppetlabs-vcsrepo.git}) end end it 'git config output should contain the remotes - test1' do run_shell("/usr/bin/git config -l -f #{tmpdir}/vcsrepo/.git/config") do |r| expect(r.stdout).to match(%r{remote.test1.url=https://github.com/puppetlabs/puppetlabs-vcsrepo.git}) end end after(:all) do run_shell("rm -rf #{tmpdir}/vcsrepo") end end end diff --git a/spec/acceptance/remove_repo_spec.rb b/spec/acceptance/remove_repo_spec.rb index b32e471..46b4cd6 100644 --- a/spec/acceptance/remove_repo_spec.rb +++ b/spec/acceptance/remove_repo_spec.rb @@ -1,48 +1,50 @@ +# frozen_string_literal: true + require 'spec_helper_acceptance' tmpdir = '/tmp/vcsrepo' describe 'remove a repo' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_deleted": ensure => present, provider => git, } MANIFEST it 'creates a blank repo' do apply_manifest(pp, catch_failures: true) end pp_noop_remove = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_deleted": ensure => absent, provider => git, force => true, } MANIFEST context 'when ran with noop' do it 'does not remove a repo' do apply_manifest(pp_noop_remove, catch_failures: true, noop: true, verbose: false) end describe file("#{tmpdir}/testrepo_deleted") do it { is_expected.to be_directory } end end pp_remove = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo_deleted": ensure => absent, provider => git, } MANIFEST context 'when ran without noop' do it 'removes a repo' do apply_manifest(pp_remove, catch_failures: true) end describe file("#{tmpdir}/testrepo_deleted") do it { is_expected.not_to be_directory } end end end diff --git a/spec/acceptance/revision_spec.rb b/spec/acceptance/revision_spec.rb index 368d249..9ea548a 100644 --- a/spec/acceptance/revision_spec.rb +++ b/spec/acceptance/revision_spec.rb @@ -1,87 +1,89 @@ +# frozen_string_literal: true + require 'spec_helper_acceptance' tmpdir = '/tmp/vcsrepo' describe 'changing revision' do before(:all) do # Create testrepo.git my_root = File.expand_path(File.join(File.dirname(__FILE__), '..')) bolt_upload_file("#{my_root}/acceptance/files", tmpdir, 'create_git_repo.sh') run_shell("cd #{tmpdir} && ./create_git_repo.sh") # Configure testrepo.git as upstream of testrepo pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo": ensure => present, provider => git, revision => 'a_branch', source => "file://#{tmpdir}/testrepo.git", } MANIFEST apply_manifest(pp, catch_failures: true) end after(:all) do run_shell("rm -rf #{tmpdir}/testrepo.git") end shared_examples 'switch to branch/tag/sha' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo": ensure => latest, provider => git, revision => 'a_branch', source => "file://#{tmpdir}/testrepo.git", } MANIFEST it 'pulls the new branch commits' do idempotent_apply(pp) end pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo": ensure => latest, provider => git, revision => '0.0.3', source => "file://#{tmpdir}/testrepo.git", } MANIFEST it 'checks out the tag' do idempotent_apply(pp) end it 'checks out the sha' do sha = run_shell("cd #{tmpdir}/testrepo && git rev-parse origin/master").stdout.chomp pp = <<-MANIFEST vcsrepo { "#{tmpdir}/testrepo": ensure => latest, provider => git, revision => '#{sha}', source => "file://#{tmpdir}/testrepo.git", } MANIFEST idempotent_apply(pp) end end context 'when on branch' do before :each do run_shell("cd #{tmpdir}/testrepo && git checkout a_branch") run_shell("cd #{tmpdir}/testrepo && git reset --hard 0.0.2") end it_behaves_like 'switch to branch/tag/sha' end context 'when on tag' do before :each do run_shell("cd #{tmpdir}/testrepo && git checkout 0.0.1") end it_behaves_like 'switch to branch/tag/sha' end context 'when on detached head' do before :each do run_shell("cd #{tmpdir}/testrepo && git checkout 0.0.2") run_shell("cd #{tmpdir}/testrepo && git checkout HEAD~1") end it_behaves_like 'switch to branch/tag/sha' end end diff --git a/spec/acceptance/svn_paths_spec.rb b/spec/acceptance/svn_paths_spec.rb index 55eae05..ec18c71 100644 --- a/spec/acceptance/svn_paths_spec.rb +++ b/spec/acceptance/svn_paths_spec.rb @@ -1,268 +1,268 @@ +# frozen_string_literal: true + require 'spec_helper_acceptance' tmpdir = '/tmp/vcsrepo' describe 'subversion :includes tests on SVN version >= 1.7', unless: ( # rubocop:disable RSpec/MultipleDescribes : The # test's on this page must be kept seperate as they are for different operating systems. (os[:family] == 'redhat' && os[:release].start_with?('5', '6')) || (os[:family] == 'sles') -) do - + ) do before(:all) do run_shell("mkdir -p #{tmpdir}") # win test end after(:all) do run_shell("rm -rf #{tmpdir}/svnrepo") end context 'with include paths' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/svnrepo": ensure => present, provider => svn, includes => ['difftools/README', 'obsolete-notes',], source => "http://svn.apache.org/repos/asf/subversion/developer-resources", revision => 1000000, } MANIFEST it 'can checkout specific paths from svn' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/svnrepo/difftools") do it { is_expected.to be_directory } end describe file("#{tmpdir}/svnrepo/difftools/README") do its(:md5sum) { is_expected.to eq '540241e9d5d4740d0ef3d27c3074cf93' } end describe file("#{tmpdir}/svnrepo/difftools/pics") do it { is_expected.not_to exist } end describe file("#{tmpdir}/svnrepo/obsolete-notes") do it { is_expected.to be_directory } end describe file("#{tmpdir}/svnrepo/obsolete-notes/draft-korn-vcdiff-01.txt") do its(:md5sum) { is_expected.to eq '37019f808e1af64864853a67526cfe19' } end describe file("#{tmpdir}/svnrepo/obsolete-notes/vcdiff-karlnotes") do its(:md5sum) { is_expected.to eq '26e23ff6a156de14aebd1099e23ac2d8' } end describe file("#{tmpdir}/svnrepo/guis") do it { is_expected.not_to exist } end end context 'with add include paths' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/svnrepo": ensure => present, provider => svn, includes => ['difftools/README', 'obsolete-notes', 'guis/pics', 'difftools/pics/README'], source => "http://svn.apache.org/repos/asf/subversion/developer-resources", revision => 1000000, } MANIFEST it 'can add paths to includes' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/svnrepo/guis/pics/README") do its(:md5sum) { is_expected.to eq '62bdc9180684042fe764d89c9beda40f' } end describe file("#{tmpdir}/svnrepo/difftools/pics/README") do its(:md5sum) { is_expected.to eq 'bad02dfc3cb96bf5cadd59bf4fe3e00e' } end end context 'with remove include paths' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/svnrepo": ensure => present, provider => svn, includes => ['difftools/pics/README', 'obsolete-notes',], source => "http://svn.apache.org/repos/asf/subversion/developer-resources", revision => 1000000, } MANIFEST it 'can remove paths (and empty parent directories) from includes' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/svnrepo/guis/pics/README") do it { is_expected.not_to exist } end describe file("#{tmpdir}/svnrepo/guis/pics") do it { is_expected.not_to exist } end describe file("#{tmpdir}/svnrepo/guis") do it { is_expected.not_to exist } end describe file("#{tmpdir}/svnrepo/difftools/pics/README") do its(:md5sum) { is_expected.to eq 'bad02dfc3cb96bf5cadd59bf4fe3e00e' } end describe file("#{tmpdir}/svnrepo/difftools/README") do it { is_expected.not_to exist } end end context 'with changing revisions' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/svnrepo": ensure => present, provider => svn, includes => ['difftools/README', 'obsolete-notes',], source => "http://svn.apache.org/repos/asf/subversion/developer-resources", revision => 1700000, } MANIFEST it 'can change revisions' do # Run it twice and test for idempotency idempotent_apply(pp) end it 'svn info svnrepo' do run_shell("svn info #{tmpdir}/svnrepo") do |r| expect(r.stdout).to match(%r{.*Revision: 1700000.*}) end end it 'svn info svnrepo/difftools/README' do run_shell("svn info #{tmpdir}/svnrepo/difftools/README") do |r| expect(r.stdout).to match(%r{.*Revision: 1700000.*}) end end end end describe 'subversion :includes tests on SVN version == 1.6', if: ( (os[:family] == 'redhat' && os[:release].start_with?('5', '6')) -) do - + ) do after(:all) do run_shell("rm -rf #{tmpdir}/svnrepo") end context 'with include paths' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/svnrepo": ensure => present, provider => svn, includes => ['difftools/README', 'obsolete-notes',], source => "http://svn.apache.org/repos/asf/subversion/developer-resources", revision => 1000000, } MANIFEST it 'can checkout specific paths from svn' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/svnrepo/difftools") do it { is_expected.to be_directory } end describe file("#{tmpdir}/svnrepo/difftools/README") do its(:md5sum) { is_expected.to eq '540241e9d5d4740d0ef3d27c3074cf93' } end describe file("#{tmpdir}/svnrepo/difftools/pics") do it { is_expected.not_to exist } end describe file("#{tmpdir}/svnrepo/obsolete-notes") do it { is_expected.to be_directory } end describe file("#{tmpdir}/svnrepo/obsolete-notes/draft-korn-vcdiff-01.txt") do its(:md5sum) { is_expected.to eq '37019f808e1af64864853a67526cfe19' } end describe file("#{tmpdir}/svnrepo/obsolete-notes/vcdiff-karlnotes") do its(:md5sum) { is_expected.to eq '26e23ff6a156de14aebd1099e23ac2d8' } end describe file("#{tmpdir}/svnrepo/guis") do it { is_expected.not_to exist } end end context 'with add include paths' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/svnrepo": ensure => present, provider => svn, includes => ['difftools/README', 'obsolete-notes', 'guis/pics', 'difftools/pics/README'], source => "http://svn.apache.org/repos/asf/subversion/developer-resources", revision => 1000000, } MANIFEST it 'can add paths to includes' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/svnrepo/guis/pics/README") do its(:md5sum) { is_expected.to eq '62bdc9180684042fe764d89c9beda40f' } end describe file("#{tmpdir}/svnrepo/difftools/pics/README") do its(:md5sum) { is_expected.to eq 'bad02dfc3cb96bf5cadd59bf4fe3e00e' } end end context 'with remove include paths' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/svnrepo": ensure => present, provider => svn, includes => ['difftools/pics/README', 'obsolete-notes',], source => "http://svn.apache.org/repos/asf/subversion/developer-resources", revision => 1000000, } MANIFEST it 'can remove directory paths (and empty parent directories) from includes, but not files with siblings' do apply_manifest(pp, catch_failures: true) end describe file("#{tmpdir}/svnrepo/guis/pics/README") do it { is_expected.not_to exist } end describe file("#{tmpdir}/svnrepo/guis/pics") do it { is_expected.not_to exist } end describe file("#{tmpdir}/svnrepo/guis") do it { is_expected.not_to exist } end describe file("#{tmpdir}/svnrepo/difftools/pics/README") do its(:md5sum) { is_expected.to eq 'bad02dfc3cb96bf5cadd59bf4fe3e00e' } end describe file("#{tmpdir}/svnrepo/difftools/README") do its(:md5sum) { is_expected.to eq '540241e9d5d4740d0ef3d27c3074cf93' } end end context 'with changing revisions' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/svnrepo": ensure => present, provider => svn, includes => ['difftools/README', 'obsolete-notes',], source => "http://svn.apache.org/repos/asf/subversion/developer-resources", revision => 1700000, } MANIFEST it 'can change revisions' do # Run it twice and test for idempotency idempotent_apply(pp) end it 'svn info of svnrepo' do run_shell("svn info #{tmpdir}/svnrepo") do |r| expect(r.stdout).to match(%r{.*Revision: 1700000.*}) end end it 'svn info of svnrepo/difftools/README' do run_shell("svn info #{tmpdir}/svnrepo/difftools/README") do |r| expect(r.stdout).to match(%r{.*Revision: 1700000.*}) end end end end diff --git a/spec/acceptance/svn_spec.rb b/spec/acceptance/svn_spec.rb index ffb13e1..b75d2f8 100644 --- a/spec/acceptance/svn_spec.rb +++ b/spec/acceptance/svn_spec.rb @@ -1,132 +1,134 @@ +# frozen_string_literal: true + require 'spec_helper_acceptance' tmpdir = '/tmp/vcsrepo' describe 'subversion tests' do before(:each) do run_shell("mkdir -p #{tmpdir}") # win test end context 'with plain checkout' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/svnrepo": ensure => present, provider => svn, source => "http://svn.apache.org/repos/asf/subversion/svn-logos", } MANIFEST it 'can checkout svn' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/svnrepo/.svn") do it { is_expected.to be_directory } end describe file("#{tmpdir}/svnrepo/images/tyrus-svn2.png") do its(:md5sum) { is_expected.to eq '6b20cbc4a793913190d1548faad1ae80' } end after(:all) do run_shell("rm -rf #{tmpdir}/svnrepo") end end context 'with handles revisions' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/svnrepo": ensure => present, provider => svn, source => "http://svn.apache.org/repos/asf/subversion/developer-resources", revision => 1000000, } MANIFEST it 'can checkout a specific revision of svn' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/svnrepo/.svn") do it { is_expected.to be_directory } end it 'svn info svnrepo' do run_shell("svn info #{tmpdir}/svnrepo") do |r| expect(r.stdout).to match(%r{.*Revision: 1000000.*}) end end describe file("#{tmpdir}/svnrepo/difftools/README") do its(:md5sum) { is_expected.to eq '540241e9d5d4740d0ef3d27c3074cf93' } end end context 'with handles revisions' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/svnrepo": ensure => present, provider => svn, source => "http://svn.apache.org/repos/asf/subversion/developer-resources", revision => 1700000, } MANIFEST it 'can switch revisions' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/svnrepo/.svn") do it { is_expected.to be_directory } end it 'svn info svnrepo' do run_shell("svn info #{tmpdir}/svnrepo") do |r| expect(r.stdout).to match(%r{.*Revision: 1700000.*}) end end after(:all) do run_shell("rm -rf #{tmpdir}/svnrepo") end end context 'with switching sources' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/svnrepo": ensure => present, provider => svn, source => "http://svn.apache.org/repos/asf/subversion/tags/1.9.0", } MANIFEST it 'can checkout tag=1.9.0' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/svnrepo/.svn") do it { is_expected.to be_directory } end describe file("#{tmpdir}/svnrepo/STATUS") do its(:md5sum) { is_expected.to eq '286708a30aea43d78bc2b11f3ac57fff' } end end context 'with switching sources' do pp = <<-MANIFEST vcsrepo { "#{tmpdir}/svnrepo": ensure => present, provider => svn, source => "http://svn.apache.org/repos/asf/subversion/tags/1.9.4", } MANIFEST it 'can switch to tag=1.9.4' do # Run it twice and test for idempotency idempotent_apply(pp) end describe file("#{tmpdir}/svnrepo/.svn") do it { is_expected.to be_directory } end describe file("#{tmpdir}/svnrepo/STATUS") do its(:md5sum) { is_expected.to eq '7f072a1c0e2ba37ca058f65e554de95e' } end end end diff --git a/spec/spec_helper_acceptance_local.rb b/spec/spec_helper_acceptance_local.rb index 024508e..cfd8282 100644 --- a/spec/spec_helper_acceptance_local.rb +++ b/spec/spec_helper_acceptance_local.rb @@ -1,52 +1,54 @@ +# frozen_string_literal: true + require 'singleton' class LitmusHelper include Singleton include PuppetLitmus end RSpec.configure do |c| # Readable test descriptions c.formatter = :documentation # Configure all nodes in nodeset c.before :suite do case os[:family] when 'redhat' - if os[:release][0] =~ %r{5} + if %r{5}.match?(os[:release][0]) LitmusHelper.instance.run_shell('which git', expect_failures: true) LitmusHelper.instance.run_shell('rpm -ivh http://repository.it4i.cz/mirrors/repoforge/redhat/el5/en/x86_64/rpmforge/RPMS/rpmforge-release-0.5.3-1.el5.rf.x86_64.rpm', expect_failures: true) LitmusHelper.instance.run_shell('yum install -y git') end pp = <<-PP package { 'git': ensure => present, } package { 'subversion': ensure => present, } PP LitmusHelper.instance.apply_manifest(pp) when %r{(ubuntu|[dD]ebian|sles)} pp = <<-PP package { 'git-core': ensure => present, } package { 'subversion': ensure => present, } PP LitmusHelper.instance.apply_manifest(pp) else unless run_bolt_task('package', 'action' => 'status', 'name' => 'git') puts 'Git package is required for this module' exit end unless run_bolt_task('package', 'action' => 'status', 'name' => 'subversion') puts 'Subversion package is required for this module' exit end end LitmusHelper.instance.run_shell('git config --global user.email "root@localhost"') LitmusHelper.instance.run_shell('git config --global user.name "root"') end end # git with 3.18 changes the maximum enabled TLS protocol version, older OSes will fail these tests def only_supports_weak_encryption return_val = (os[:family] == 'redhat' && os[:release].start_with?('5', '6') || (os[:family] == 'sles' && os[:release].start_with?('11'))) return_val end diff --git a/spec/spec_helper_local.rb b/spec/spec_helper_local.rb index b74c7f3..b7d1730 100644 --- a/spec/spec_helper_local.rb +++ b/spec/spec_helper_local.rb @@ -1,36 +1,38 @@ +# frozen_string_literal: true + 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 require 'support/filesystem_helpers' require 'support/fixture_helpers' RSpec.configure do |c| c.include FilesystemHelpers c.include FixtureHelpers end diff --git a/spec/support/filesystem_helpers.rb b/spec/support/filesystem_helpers.rb index d6d6015..979517d 100644 --- a/spec/support/filesystem_helpers.rb +++ b/spec/support/filesystem_helpers.rb @@ -1,18 +1,20 @@ +# frozen_string_literal: true + # filesystem_helpers.rb module FilesystemHelpers def expect_chdir(path = resource.value(:path)) expect(Dir).to receive(:chdir).with(path).at_least(:once).and_yield end def expect_mkdir(path = resource.value(:path)) expect(Dir).to receive(:mkdir).with(path).once end def expect_rm_rf(path = resource.value(:path)) expect(FileUtils).to receive(:rm_rf).with(path) end def expect_directory?(returns = true, path = resource.value(:path)) expect(File).to receive(:directory?).with(path).and_return(returns) end end diff --git a/spec/support/fixture_helpers.rb b/spec/support/fixture_helpers.rb index b2d6642..2aa3341 100644 --- a/spec/support/fixture_helpers.rb +++ b/spec/support/fixture_helpers.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + # fixture_helpers.b module FixtureHelpers def fixture(name, ext = '.txt') File.read(File.join(File.dirname(__FILE__), '..', 'fixtures', name.to_s + ext)) end end diff --git a/spec/unit/facter/vcsrepo_svn_ver_spec.rb b/spec/unit/facter/vcsrepo_svn_ver_spec.rb index 0b48327..5616f34 100644 --- a/spec/unit/facter/vcsrepo_svn_ver_spec.rb +++ b/spec/unit/facter/vcsrepo_svn_ver_spec.rb @@ -1,21 +1,23 @@ +# frozen_string_literal: true + require 'spec_helper' describe Facter::Util::Fact do # rubocop:disable RSpec/FilePath before(:each) do Facter.clear end describe 'vcsrepo_svn_ver' do context 'with valid value' do before :each do allow(Facter.fact(:operatingsystem)).to receive(:value).and_return('OpenBSD') allow(Facter::Core::Execution).to receive(:execute) .with('svn --version --quiet') .and_return('1.7.23') end it { expect(Facter.fact(:vcsrepo_svn_ver).value).to eq('1.7.23') } end end end diff --git a/spec/unit/puppet/provider/vcsrepo/bzr_spec.rb b/spec/unit/puppet/provider/vcsrepo/bzr_spec.rb index 23014ff..5f16ef2 100644 --- a/spec/unit/puppet/provider/vcsrepo/bzr_spec.rb +++ b/spec/unit/puppet/provider/vcsrepo/bzr_spec.rb @@ -1,121 +1,123 @@ +# frozen_string_literal: true + require 'spec_helper' describe Puppet::Type.type(:vcsrepo).provider(:bzr) do let(:resource) do Puppet::Type.type(:vcsrepo).new(name: 'test', ensure: :present, provider: :bzr, revision: '2634', source: 'lp:do', path: '/tmp/test') end let(:provider) { resource.provider } before :each do allow(Puppet::Util).to receive(:which).with('bzr').and_return('/usr/bin/bzr') end describe 'creating' do context 'with defaults' do it "executes 'bzr clone -r' with the revision" do expect(provider).to receive(:bzr).with('branch', '-r', resource.value(:revision), resource.value(:source), resource.value(:path)) provider.create end end context 'without revision' do it "justs execute 'bzr clone' without a revision" do resource.delete(:revision) expect(provider).to receive(:bzr).with('branch', resource.value(:source), resource.value(:path)) provider.create end end context 'without source' do it "executes 'bzr init'" do resource.delete(:source) expect(provider).to receive(:bzr).with('init', resource.value(:path)) provider.create end end end describe 'destroying' do it 'removes the directory' do provider.destroy end end describe 'checking existence' do it 'executes bzr status on the path' do expect(File).to receive(:directory?).with(resource.value(:path)).and_return(true) expect(provider).to receive(:bzr).with('status', resource[:path]) provider.exists? end end describe 'checking the revision property' do before(:each) do expect_chdir allow(provider).to receive(:bzr).with('version-info').and_return(File.read(fixtures('bzr_version_info.txt'))) end let(:current_revid) { 'menesis@pov.lt-20100309191856-4wmfqzc803fj300x' } context 'when given a non-revid as the resource revision and its revid is not different than the current revid' do it 'and_return the ref' do resource[:revision] = '2634' expect(provider).to receive(:bzr).with('revision-info', '2634').and_return("2634 menesis@pov.lt-20100309191856-4wmfqzc803fj300x\n") expect(provider.revision).to eq(resource.value(:revision)) end end context 'when given a non-revid as the resource revision and its revid is different than the current revid' do it 'and_return the current revid' do resource[:revision] = '2636' expect(provider).to receive(:bzr).with('revision-info', resource.value(:revision)).and_return("2635 foo\n") expect(provider.revision).to eq(current_revid) end end context 'when given a revid as the resource revision and it is the same as the current revid' do it 'and_return it' do resource[:revision] = 'menesis@pov.lt-20100309191856-4wmfqzc803fj300x' expect(provider).to receive(:bzr).with('revision-info', resource.value(:revision)).and_return("1234 #{resource.value(:revision)}\n") expect(provider.revision).to eq(resource.value(:revision)) end end context 'when given a revid as the resource revision and it is not the same as the current revid' do it 'and_return the current revid' do resource[:revision] = 'menesis@pov.lt-20100309191856-4wmfqzc803fj300y' expect(provider).to receive(:bzr).with('revision-info', resource.value(:revision)).and_return("2636 foo\n") expect(provider.revision).to eq(current_revid) end end end describe 'setting the revision property' do it "uses 'bzr update -r' with the revision" do expect(Dir).to receive(:chdir).with('/tmp/test').once.and_yield expect(provider).to receive(:bzr).with('update', '-r', 'somerev') provider.revision = 'somerev' end end describe 'checking the source property' do it "uses 'bzr info'" do expect_chdir resource[:source] = 'http://bazaar.launchpad.net/~bzr-pqm/bzr/bzr.dev/' expect(provider).to receive(:bzr).with('info').and_return(' parent branch: http://bazaar.launchpad.net/~bzr-pqm/bzr/bzr.dev/') expect(provider.source).to eq(resource.value(:source)) end end describe 'setting the source property' do it "calls 'create'" do resource[:source] = 'http://bazaar.launchpad.net/~bzr-pqm/bzr/bzr.dev/' expect(provider).to receive(:create) provider.source = resource.value(:source) end end end diff --git a/spec/unit/puppet/provider/vcsrepo/cvs_spec.rb b/spec/unit/puppet/provider/vcsrepo/cvs_spec.rb index 4285820..3f981bb 100644 --- a/spec/unit/puppet/provider/vcsrepo/cvs_spec.rb +++ b/spec/unit/puppet/provider/vcsrepo/cvs_spec.rb @@ -1,161 +1,163 @@ +# frozen_string_literal: true + require 'spec_helper' describe Puppet::Type.type(:vcsrepo).provider(:cvs) do let(:resource) do Puppet::Type.type(:vcsrepo).new(name: 'test', ensure: :present, provider: :cvs, revision: '2634', source: ':pserver:anonymous@cvs.sv.gnu.org:/sources/cvs/', path: '/tmp/test') end let(:provider) { resource.provider } before :each do allow(Puppet::Util).to receive(:which).with('cvs').and_return('/usr/bin/cvs') end describe 'creating' do context 'with a source' do it "executes 'cvs checkout'" do resource[:source] = ':ext:source@example.com:/foo/bar' resource[:revision] = 'an-unimportant-value' expect_chdir('/tmp') expect(Puppet::Util::Execution).to receive(:execute).with([:cvs, '-d', resource.value(:source), 'checkout', '-r', 'an-unimportant-value', '-d', 'test', '.'], custom_environment: {}, combine: true, failonfail: true) provider.create end it "executes 'cvs checkout' as user 'muppet'" do resource[:source] = ':ext:source@example.com:/foo/bar' resource[:revision] = 'an-unimportant-value' resource[:user] = 'muppet' expect_chdir('/tmp') expect(Puppet::Util::Execution).to receive(:execute).with([:cvs, '-d', resource.value(:source), 'checkout', '-r', 'an-unimportant-value', '-d', 'test', '.'], uid: 'muppet', custom_environment: {}, combine: true, failonfail: true) provider.create end it "justs execute 'cvs checkout' without a revision" do resource[:source] = ':ext:source@example.com:/foo/bar' resource.delete(:revision) expect(Puppet::Util::Execution).to receive(:execute).with([:cvs, '-d', resource.value(:source), 'checkout', '-d', File.basename(resource.value(:path)), '.'], custom_environment: {}, combine: true, failonfail: true) provider.create end end context 'with a compression' do it "justs execute 'cvs checkout' without a revision" do resource[:source] = ':ext:source@example.com:/foo/bar' resource[:compression] = '3' resource.delete(:revision) expect(Puppet::Util::Execution).to receive(:execute).with([:cvs, '-d', resource.value(:source), '-z', '3', 'checkout', '-d', File.basename(resource.value(:path)), '.'], custom_environment: {}, combine: true, failonfail: true) provider.create end end context 'when a source is not given' do it "executes 'cvs init'" do resource.delete(:source) expect(Puppet::Util::Execution).to receive(:execute).with([:cvs, '-d', resource.value(:path), 'init'], custom_environment: {}, combine: true, failonfail: true) provider.create end end end describe 'destroying' do it 'removes the directory' do provider.destroy end end describe 'checking existence' do context 'with a source value' do it "runs 'cvs status'" do resource[:source] = ':ext:source@example.com:/foo/bar' expect(File).to receive(:directory?).with(File.join(resource.value(:path), 'CVS')).and_return(true) expect_chdir expect(Puppet::Util::Execution).to receive(:execute).with([:cvs, '-nq', 'status', '-l'], custom_environment: {}, combine: true, failonfail: true) provider.exist? end end context 'without a source value' do it 'checks for the CVSROOT directory and config file' do resource.delete(:source) expect(File).to receive(:directory?).with(File.join(resource.value(:path), 'CVSROOT')).and_return(true) expect(File).to receive(:exist?).with(File.join(resource.value(:path), 'CVSROOT', 'config,v')).and_return(true) provider.exist? end end end describe 'checking the revision property' do let(:tag_file) { File.join(resource.value(:path), 'CVS', 'Tag') } context 'when CVS/Tag exists' do let(:tag) { 'TAG' } before(:each) do allow(File).to receive(:exist?).with(tag_file).and_return(true) end it 'reads CVS/Tag' do expect(File).to receive(:read).with(tag_file).and_return("T#{tag}") expect(provider.revision).to eq(tag) end end context 'when CVS/Tag does not exist' do before(:each) do allow(File).to receive(:exist?).with(tag_file).and_return(false) end it 'assumes HEAD' do expect(provider.revision).to eq('HEAD') end end end describe 'when setting the revision property' do let(:tag) { 'SOMETAG' } it "uses 'cvs update -dr'" do expect_chdir expect(Puppet::Util::Execution).to receive(:execute).with([:cvs, 'update', '-dr', tag, '.'], custom_environment: {}, combine: true, failonfail: true) provider.revision = tag end end describe 'checking the source property' do it "reads the contents of file 'CVS/Root'" do expect(File).to receive(:read).with(File.join(resource.value(:path), 'CVS', 'Root')) .and_return(':pserver:anonymous@cvs.sv.gnu.org:/sources/cvs/') expect(provider.source).to eq(resource.value(:source)) end end describe 'setting the source property' do it "calls 'create'" do expect(provider).to receive(:create) provider.source = resource.value(:source) end end describe 'checking the module property' do before(:each) do resource[:module] = 'ccvs' end it "reads the contents of file 'CVS/Repository'" do expect(File).to receive(:read).with(File.join(resource.value(:path), 'CVS', 'Repository')) .and_return('ccvs') expect(provider.module).to eq(resource.value(:module)) end end describe 'setting the module property' do it "calls 'create'" do expect(provider).to receive(:create) provider.module = resource.value(:module) end end end diff --git a/spec/unit/puppet/provider/vcsrepo/git_spec.rb b/spec/unit/puppet/provider/vcsrepo/git_spec.rb index 905dff0..6ea27ef 100644 --- a/spec/unit/puppet/provider/vcsrepo/git_spec.rb +++ b/spec/unit/puppet/provider/vcsrepo/git_spec.rb @@ -1,588 +1,590 @@ +# frozen_string_literal: true + require 'spec_helper' describe Puppet::Type.type(:vcsrepo).provider(:git) do def branch_a_list(include_branch = nil?) < 'git://git@foo.com/bar.git', 'other' => 'git://git@foo.com/baz.git' } resource.delete(:revision) expect(Dir).to receive(:chdir).with('/').once.and_yield expect(provider).to receive(:git).with('clone', '--mirror', resource.value(:source)['origin'], resource.value(:path)) expect(provider).to receive(:update_remotes) expect_chdir expect(provider).to receive(:git).with('config', 'remote.origin.mirror', 'true') expect(provider).to receive(:git).with('config', 'remote.other.mirror', 'true') provider.create end end end context 'when with an ensure of mirror - when the path is a working copy repository' do it 'clones overtop it using force' do resource[:force] = true expect(Dir).to receive(:chdir).with('/').once.and_yield expect(Dir).to receive(:chdir).with('/tmp/test').at_least(:once).and_yield expect(provider).to receive(:path_exists?).and_return(true) expect(provider).to receive(:path_empty?).and_return(false) provider.destroy expect(provider).to receive(:git).with('clone', resource.value(:source), resource.value(:path)) expect(provider).to receive(:update_submodules) expect(provider).to receive(:update_remote_url).with('origin', resource.value(:source)).and_return false expect(provider).to receive(:git).with('branch', '--no-color', '-a').and_return(branch_a_list(resource.value(:revision))) expect(provider).to receive(:git).with('checkout', '--force', resource.value(:revision)) provider.create end end context 'when with an ensure of mirror - when the path is not empty and not a repository' do it 'raises an exception' do expect(provider).to receive(:path_exists?).and_return(true) expect(provider).to receive(:path_empty?).and_return(false) expect { provider.create }.to raise_error(Puppet::Error) end end context 'when converting repo type' do context 'when with working copy to bare' do it 'converts the repo' do resource[:ensure] = :bare expect(provider).to receive(:working_copy_exists?).and_return(true) expect(provider).to receive(:bare_exists?).and_return(false) expect(FileUtils).to receive(:mv).and_return(true) expect(FileUtils).to receive(:rm_rf).and_return(true) expect(FileUtils).to receive(:mv).and_return(true) expect_chdir expect(provider).to receive(:git).with('config', '--local', '--bool', 'core.bare', 'true') provider.instance_eval { convert_working_copy_to_bare } end end context 'when with working copy to mirror' do it 'converts the repo' do resource[:ensure] = :mirror expect(provider).to receive(:working_copy_exists?).and_return(true) expect(provider).to receive(:bare_exists?).and_return(false) expect(FileUtils).to receive(:mv).and_return(true) expect(FileUtils).to receive(:rm_rf).and_return(true) expect(FileUtils).to receive(:mv).and_return(true) expect_chdir expect(provider).to receive(:git).with('config', '--local', '--bool', 'core.bare', 'true') expect(provider).to receive(:git).with('config', 'remote.origin.mirror', 'true') provider.instance_eval { convert_working_copy_to_bare } end end context 'when with bare copy to working copy' do it 'converts the repo' do expect(FileUtils).to receive(:mv).and_return(true) expect(FileUtils).to receive(:mkdir).and_return(true) expect(FileUtils).to receive(:mv).and_return(true) expect_chdir expect(provider).to receive(:commits?).and_return(true) # If you forget to stub these out you lose 3 hours of rspec work. expect(provider).to receive(:git) .with('config', '--local', '--bool', 'core.bare', 'false').and_return(true) expect(provider).to receive(:reset).with('HEAD').and_return(true) expect(provider).to receive(:git_with_identity).with('checkout', '--force').and_return(true) expect(provider).to receive(:update_owner_and_excludes).and_return(true) expect(provider).to receive(:mirror?).and_return(false) provider.instance_eval { convert_bare_to_working_copy } end end context 'when with mirror to working copy' do it 'converts the repo' do expect(FileUtils).to receive(:mv).and_return(true) expect(FileUtils).to receive(:mkdir).and_return(true) expect(FileUtils).to receive(:mv).and_return(true) expect_chdir expect(provider).to receive(:commits?).and_return(true) expect(provider).to receive(:git) .with('config', '--local', '--bool', 'core.bare', 'false').and_return(true) expect(provider).to receive(:reset).with('HEAD').and_return(true) expect(provider).to receive(:git_with_identity).with('checkout', '--force').and_return(true) expect(provider).to receive(:update_owner_and_excludes).and_return(true) expect(provider).to receive(:git).with('config', '--unset', 'remote.origin.mirror') expect(provider).to receive(:mirror?).and_return(true) provider.instance_eval { convert_bare_to_working_copy } end end end context 'when destroying' do it 'removes the directory' do expect_rm_rf provider.destroy end end context 'when checking the revision property' do before(:each) do expect_chdir('/tmp/test') resource[:source] = 'http://example.com' allow(provider).to receive(:git).with('config', 'remote.origin.url').and_return('') allow(provider).to receive(:git).with('fetch', 'origin') # FIXME allow(provider).to receive(:git).with('fetch', '--tags', 'origin') allow(provider).to receive(:git).with('rev-parse', 'HEAD').and_return('currentsha') allow(provider).to receive(:git).with('tag', '-l').and_return('Hello') end context 'when its a SHA and is not different than the current SHA' do it 'and_return the current SHA' do resource[:revision] = 'currentsha' allow(provider).to receive(:git).with('branch', '--no-color', '-a').and_return(branch_a_list) expect(provider).to receive(:git).with('rev-parse', '--revs-only', resource.value(:revision)).never expect(provider).to receive(:update_references).never expect(provider.revision).to eq(resource.value(:revision)) end end context 'when its a SHA and is different than the current SHA' do it 'and_return the current SHA' do resource[:revision] = 'othersha' allow(provider).to receive(:git).with('branch', '--no-color', '-a').and_return(branch_a_list) expect(provider).to receive(:git).with('rev-parse', '--revs-only', resource.value(:revision)).and_return('othersha') expect(provider).to receive(:update_references) expect(provider.revision).to eq('currentsha') end end context 'when its a local branch and is not different than the current SHA' do it 'and_return the ref' do resource[:revision] = 'localbranch' allow(provider).to receive(:git).with('branch', '--no-color', '-a').and_return(branch_a_list('localbranch')) expect(provider).to receive(:git).with('rev-parse', resource.value(:revision)).and_return('currentsha') expect(provider).to receive(:update_references) expect(provider.revision).to eq(resource.value(:revision)) end end context 'when its a local branch and is different than the current SHA' do it 'and_return the current SHA' do resource[:revision] = 'localbranch' allow(provider).to receive(:git).with('branch', '--no-color', '-a').and_return(branch_a_list('localbranch')) expect(provider).to receive(:git).with('rev-parse', resource.value(:revision)).and_return('othersha') expect(provider).to receive(:update_references) expect(provider.revision).to eq('currentsha') end end context 'when its a ref to a remote head' do it 'and_return the ref' do resource[:revision] = 'remotebranch' allow(provider).to receive(:git).with('branch', '--no-color', '-a').and_return(" remotes/origin/#{resource.value(:revision)}") expect(provider).to receive(:git).with('rev-parse', "origin/#{resource.value(:revision)}").and_return('currentsha') expect(provider).to receive(:update_references) expect(provider.revision).to eq(resource.value(:revision)) end end context 'when its a ref to non existant remote head' do it 'fails' do resource[:revision] = 'remotebranch' allow(provider).to receive(:git).with('branch', '--no-color', '-a').and_return(branch_a_list) expect(provider).to receive(:git).with('rev-parse', '--revs-only', resource.value(:revision)).and_return('') expect(provider).to receive(:update_references) expect { provider.revision }.to raise_error(RuntimeError, %r{not a local or remote ref$}) end end context "when there's no source" do it 'and_return the revision' do resource[:revision] = 'localbranch' resource.delete(:source) allow(provider).to receive(:git).with('branch', '--no-color', '-a').and_return(branch_a_list('localbranch')) expect(provider).to receive(:update_references).never expect(provider).to receive(:git).with('status') expect(provider).to receive(:git).with('rev-parse', resource.value(:revision)).and_return('currentsha') expect(provider.revision).to eq(resource.value(:revision)) end end end context 'when setting the revision property' do before(:each) do expect_chdir end context "when it's an existing local branch" do it "uses 'git fetch' and 'git reset'" do resource[:revision] = 'feature/foo' expect(provider).to receive(:update_submodules) expect(provider).to receive(:git).with('branch', '--no-color', '-a').at_least(:once).and_return(branch_a_list(resource.value(:revision))) expect(provider).to receive(:git).with('checkout', '--force', resource.value(:revision)) expect(provider).to receive(:git).with('reset', '--hard', "origin/#{resource.value(:revision)}") provider.revision = resource.value(:revision) end end context "when it's a remote branch" do it "uses 'git fetch' and 'git reset'" do resource[:revision] = 'only/remote' expect(provider).to receive(:update_submodules) expect(provider).to receive(:git).with('branch', '--no-color', '-a').at_least(:once).and_return(resource.value(:revision)) expect(provider).to receive(:git).with('checkout', '--force', resource.value(:revision)) expect(provider).to receive(:git).with('reset', '--hard', "origin/#{resource.value(:revision)}") provider.revision = resource.value(:revision) end end context "when it's a commit or tag" do it "uses 'git fetch' and 'git reset'" do resource[:revision] = 'a-commit-or-tag' expect(provider).to receive(:git).with('branch', '--no-color', '-a').once.and_return(fixture(:git_branch_a)) expect(provider).to receive(:git).with('checkout', '--force', resource.value(:revision)) expect(provider).to receive(:git).with('branch', '--no-color', '-a').and_return(fixture(:git_branch_a)) expect(provider).to receive(:git).with('branch', '--no-color', '-a').and_return(fixture(:git_branch_a)) expect(provider).to receive(:git).with('submodule', 'update', '--init', '--recursive') provider.revision = resource.value(:revision) end end context 'when ignoring local changes' do it "uses 'git stash'" do resource[:revision] = 'a-commit-or-tag' resource[:keep_local_changes] = true expect(provider).to receive(:git).with('stash', 'save') expect(provider).to receive(:git).with('branch', '--no-color', '-a').once.and_return(fixture(:git_branch_a)) expect(provider).to receive(:git).with('checkout', '--force', resource.value(:revision)) expect(provider).to receive(:git).with('branch', '--no-color', '-a').and_return(fixture(:git_branch_a)) expect(provider).to receive(:git).with('branch', '--no-color', '-a').and_return(fixture(:git_branch_a)) expect(provider).to receive(:git).with('submodule', 'update', '--init', '--recursive') expect(provider).to receive(:git).with('stash', 'pop') provider.revision = resource.value(:revision) end end end context 'when checking the source property' do before(:each) do expect_chdir('/tmp/test') allow(provider).to receive(:git).with('config', 'remote.origin.url').and_return('') allow(provider).to receive(:git).with('fetch', 'origin') # FIXME allow(provider).to receive(:git).with('fetch', '--tags', 'origin') allow(provider).to receive(:git).with('rev-parse', 'HEAD').and_return('currentsha') allow(provider).to receive(:git).with('branch', '--no-color', '-a').and_return(branch_a_list(resource.value(:revision))) allow(provider).to receive(:git).with('tag', '-l').and_return('Hello') end context "when there's a single remote 'origin'" do it 'and_return the URL for the remote' do resource[:source] = 'http://example.com' expect(provider).to receive(:git).with('remote').and_return("origin\n") expect(provider).to receive(:git).with('config', '--get', 'remote.origin.url').and_return('http://example.com') expect(provider.source).to eq(resource.value(:source)) end end context "when there's more than one remote" do it 'and_return the remotes as a hash' do resource[:source] = { 'origin' => 'git://git@foo.com/bar.git', 'other' => 'git://git@foo.com/baz.git' } expect(provider).to receive(:git).with('remote').and_return("origin\nother\n") expect(provider).to receive(:git).with('config', '--get', 'remote.origin.url').and_return('git://git@foo.com/bar.git') expect(provider).to receive(:git).with('config', '--get', 'remote.other.url').and_return('git://git@foo.com/baz.git') expect(provider.source).to eq(resource.value(:source)) end end end context 'when updating remotes' do context 'when with string to string' do it 'fails' do resource[:source] = 'git://git@foo.com/bar.git' resource[:force] = false expect(provider).to receive(:source).and_return('git://git@foo.com/foo.git') expect(provider).to receive(:path_exists?).and_return(true) expect(provider).to receive(:path_empty?).and_return(false) expect { provider.source = resource.value(:source) }.to raise_error(Puppet::Error) end end context 'when with hash to hash' do it 'adds any new remotes, update any existing remotes, remove deleted remotes' do expect_chdir resource[:source] = { 'origin' => 'git://git@foo.com/bar.git', 'new_remote' => 'git://git@foo.com/baz.git' } expect(provider).to receive(:source).and_return( 'origin' => 'git://git@foo.com/foo.git', 'old_remote' => 'git://git@foo.com/old.git', ) expect(provider).to receive(:git).once.with('config', '-l').and_return("remote.old_remote.url=git://git@foo.com/old.git\n", "remote.origin.url=git://git@foo.com/foo.git\n") expect(provider).to receive(:git).with('remote', 'remove', 'old_remote') expect(provider).to receive(:git).with('remote', 'set-url', 'origin', 'git://git@foo.com/bar.git') expect(provider).to receive(:git).with('remote', 'add', 'new_remote', 'git://git@foo.com/baz.git') expect(provider).to receive(:git).with('remote', 'update') provider.source = resource.value(:source) end end context 'when with string to hash' do it 'adds any new remotes, update origin remote' do expect_chdir resource[:source] = { 'origin' => 'git://git@foo.com/bar.git', 'new_remote' => 'git://git@foo.com/baz.git' } expect(provider).to receive(:source).and_return('git://git@foo.com/foo.git') expect(provider).to receive(:git).at_least(:once).with('config', '-l').and_return("remote.origin.url=git://git@foo.com/foo.git\n") expect(provider).to receive(:git).with('remote', 'set-url', 'origin', 'git://git@foo.com/bar.git') expect(provider).to receive(:git).with('remote', 'add', 'new_remote', 'git://git@foo.com/baz.git') expect(provider).to receive(:git).with('remote', 'update') provider.source = resource.value(:source) end end context 'when with hash to string' do it 'updates origin remote, remove deleted remotes' do expect_chdir resource[:source] = 'git://git@foo.com/baz.git' expect(provider).to receive(:source).and_return( 'origin' => 'git://git@foo.com/foo.git', 'old_remote' => 'git://git@foo.com/old.git', ) expect(provider).to receive(:git).with('remote', 'remove', 'old_remote') expect(provider).to receive(:git).with('config', '-l').at_most(:twice).and_return("remote.origin.url=git://git@foo.com/foo.git\n", "remote.other.url=git://git@foo.com/bar.git\n") expect(provider).to receive(:git).with('remote', 'set-url', 'origin', 'git://git@foo.com/baz.git') expect(provider).to receive(:git).with('remote', 'update') provider.source = resource.value(:source) end end end # rubocop:enable RSpec/ExampleLength context 'when updating references' do it "uses 'git fetch --tags'" do resource.delete(:source) expect_chdir expect(provider).to receive(:git).with('fetch', 'origin') expect(provider).to receive(:git).with('fetch', '--tags', 'origin') provider.update_references end end describe 'latest?' do context 'when true' do it do expect(provider).to receive(:revision).and_return('testrev') expect(provider).to receive(:latest_revision).and_return('testrev') expect(provider).to be_latest end end context 'when false' do it do expect(provider).to receive(:revision).and_return('master') expect(provider).to receive(:latest_revision).and_return('testrev') expect(provider).not_to be_latest end end end describe 'trust_server_cert' do context 'when true' do before :each do resource[:trust_server_cert] = true end it 'raises error with git 1.7.0' do allow(provider).to receive(:git).with('--version').and_return '1.7.0' expect { provider.create }.to raise_error RuntimeError, %r{Can't set sslVerify to false} end it 'compiles with git 2.13.0' do resource[:revision] = 'only/remote' expect(Dir).to receive(:chdir).with('/').once.and_yield expect(Dir).to receive(:chdir).with('/tmp/test').at_least(:once).and_yield expect(provider).to receive(:git).with('-c', 'http.sslVerify=false', 'clone', resource.value(:source), resource.value(:path)) expect(provider).to receive(:update_submodules) expect(provider).to receive(:update_remote_url).with('origin', resource.value(:source)).and_return false expect(provider).to receive(:git).with('-c', 'http.sslVerify=false', 'branch', '--no-color', '-a').and_return(branch_a_list(resource.value(:revision))) expect(provider).to receive(:git).with('-c', 'http.sslVerify=false', 'checkout', '--force', resource.value(:revision)) allow(provider).to receive(:git).with('--version').and_return '2.13.0' expect { provider.create }.not_to raise_error end end end context 'owner' do it 'without excludes run FileUtils.chown_R' do resource[:owner] = 'john' expect_chdir expect(FileUtils).to receive(:chown_R).with('john', nil, '/tmp/test') expect(provider).to receive(:git).with('fetch', 'origin') expect(provider).to receive(:git).with('fetch', '--tags', 'origin') provider.update_references end it 'with excludes run filtered chown_R' do resource[:owner] = 'john' resource[:excludes] = ['bzr', 'cvs', 'hg', 'p4', 'svn'] expect_chdir filtered_files = ['git/a', 'git/b'] expect(provider).to receive(:files).and_return(filtered_files) expect(FileUtils).to receive(:chown).with('john', nil, filtered_files) expect(provider).to receive(:set_excludes) expect(provider).to receive(:git).with('fetch', 'origin') expect(provider).to receive(:git).with('fetch', '--tags', 'origin') provider.update_references end end end diff --git a/spec/unit/puppet/provider/vcsrepo/hg_spec.rb b/spec/unit/puppet/provider/vcsrepo/hg_spec.rb index b0ebb0b..1ddbe75 100644 --- a/spec/unit/puppet/provider/vcsrepo/hg_spec.rb +++ b/spec/unit/puppet/provider/vcsrepo/hg_spec.rb @@ -1,163 +1,165 @@ +# frozen_string_literal: true + require 'spec_helper' describe Puppet::Type.type(:vcsrepo).provider(:hg) do let(:resource) do Puppet::Type.type(:vcsrepo).new(name: 'test', ensure: :present, provider: :hg, path: '/tmp/vcsrepo') end let(:provider) { resource.provider } before :each do allow(Puppet::Util).to receive(:which).with('hg').and_return('/usr/bin/hg') end describe 'creating' do context 'with source and revision' do it "executes 'hg clone -u' with the revision" do resource[:source] = 'something' resource[:revision] = '1' expect(Puppet::Util::Execution).to receive(:execute).with("hg clone -u #{resource.value(:revision)} #{resource.value(:source)} #{resource.value(:path)}", sensitive: false) provider.create end end context 'without revision' do it "justs execute 'hg clone' without a revision" do resource[:source] = 'something' expect(Puppet::Util::Execution).to receive(:execute).with("hg clone #{resource.value(:source)} #{resource.value(:path)}", sensitive: false) provider.create end end context 'when a source is not given' do it "executes 'hg init'" do expect(Puppet::Util::Execution).to receive(:execute).with("hg init #{resource.value(:path)}", sensitive: false) provider.create end end context 'when basic auth is used' do it "executes 'hg clone'" do resource[:source] = 'something' resource[:basic_auth_username] = 'user' resource[:basic_auth_password] = 'pass' command = "hg clone #{resource.value(:source)} #{resource.value(:path)} --config auth.x.prefix=#{resource.value(:source)} "\ "--config auth.x.username=#{resource.value(:basic_auth_username)} --config auth.x.password=#{resource.value(:basic_auth_password)} "\ "--config 'auth.x.schemes=http https'"\ expect(Puppet::Util::Execution).to receive(:execute).with(command, sensitive: false) provider.create end end context 'when basic auth is used with Sensitive basic_auth_password' do let(:resource) do Puppet::Type.type(:vcsrepo).new(name: 'test', ensure: :present, provider: :hg, path: '/tmp/vcsrepo', source: 'something', sensitive_parameters: [:basic_auth_password], basic_auth_username: 'user', basic_auth_password: Puppet::Pops::Types::PSensitiveType::Sensitive.new('pass')) end it "executes 'hg clone'" do command = "hg clone #{resource.value(:source)} #{resource.value(:path)} --config auth.x.prefix=#{resource.value(:source)} "\ "--config auth.x.username=#{resource.value(:basic_auth_username)} --config auth.x.password=#{resource.value(:basic_auth_password).unwrap} "\ "--config 'auth.x.schemes=http https'"\ expect(Puppet::Util::Execution).to receive(:execute).with(command, sensitive: true) provider.create end end end describe 'destroying' do it 'removes the directory' do expect_rm_rf provider.destroy end end describe 'checking existence' do it 'checks for the directory' do expect_directory?(true, resource.value(:path)) expect(Puppet::Util::Execution).to receive(:execute).with("hg status #{resource.value(:path)}", sensitive: false) provider.exists? end end describe 'checking the revision property' do before(:each) do expect_chdir end context 'when given a non-SHA as the resource revision' do before(:each) do allow(Puppet::Util::Execution).to receive(:execute).with('hg parents', sensitive: false).and_return(fixture(:hg_parents)) allow(Puppet::Util::Execution).to receive(:execute).with('hg tags', sensitive: false).and_return(fixture(:hg_tags)) end it 'when its sha is not different from the current SHA it and_return the ref' do resource[:revision] = '0.6' expect(provider.revision).to eq('0.6') end it 'when its SHA is different than the current SHA it and_return the current SHA' do resource[:revision] = '0.5.3' expect(provider.revision).to eq('34e6012c783a') end end context 'when given a SHA as the resource revision' do before(:each) do allow(Puppet::Util::Execution).to receive(:execute).with('hg parents', sensitive: false).and_return(fixture(:hg_parents)) end it 'when it is the same as the current SHA it and_return it' do resource[:revision] = '34e6012c783a' expect(Puppet::Util::Execution).to receive(:execute).with('hg tags', sensitive: false).and_return(fixture(:hg_tags)) expect(provider.revision).to eq(resource.value(:revision)) end it 'when it is not the same as the current SHA it and_return the current SHA' do resource[:revision] = 'not-the-same' expect(Puppet::Util::Execution).to receive(:execute).with('hg tags', sensitive: false).and_return(fixture(:hg_tags)) expect(provider.revision).to eq('34e6012c783a') end end end describe 'setting the revision property' do let(:revision) { '6aa99e9b3ab1' } it "uses 'hg update ---clean -r'" do expect_chdir expect(provider).to receive(:hg_wrapper).with('pull', remote: true) expect(provider).to receive(:hg_wrapper).with('merge') expect(provider).to receive(:hg_wrapper).with('update', '--clean', '-r', revision) provider.revision = revision end end describe 'checking the source property' do it 'and_return the default path' do resource[:source] = 'http://selenic.com/hg' expect_chdir expect(provider).to receive(:hg_wrapper).with('paths').and_return('default = http://selenic.com/hg') expect(provider.source).to eq(resource.value(:source)) end end describe 'setting the source property' do it "calls 'create'" do resource[:source] = 'some-example' expect(provider).to receive(:create) provider.source = resource.value(:source) end end end diff --git a/spec/unit/puppet/provider/vcsrepo/p4_spec.rb b/spec/unit/puppet/provider/vcsrepo/p4_spec.rb index 606e12e..c74816e 100644 --- a/spec/unit/puppet/provider/vcsrepo/p4_spec.rb +++ b/spec/unit/puppet/provider/vcsrepo/p4_spec.rb @@ -1,97 +1,99 @@ +# frozen_string_literal: true + require 'spec_helper' describe Puppet::Type.type(:vcsrepo).provider(:p4) do let(:resource) do Puppet::Type.type(:vcsrepo).new(name: 'test', ensure: :present, provider: :p4, path: '/tmp/vcsrepo') end let(:provider) { resource.provider } before :each do allow(Puppet::Util).to receive(:which).with('p4').and_return('/usr/local/bin/p4') end spec = { input: "Description: Generated by Puppet VCSrepo\nRoot: /tmp/vcsrepo\n\nView:\n", marshal: false, } describe 'creating' do context 'with source and revision' do it "executes 'p4 sync' with the revision" do resource[:source] = 'something' resource[:revision] = '1' ENV['P4CLIENT'] = 'client_ws1' expect(provider).to receive(:p4).with(['client', '-o', 'client_ws1']).and_return({}) expect(provider).to receive(:p4).with(['client', '-i'], spec) expect(provider).to receive(:p4).with(['sync', resource.value(:source) + '@' + resource.value(:revision)]) provider.create end end context 'without revision' do it "justs execute 'p4 sync' without a revision" do resource[:source] = 'something' ENV['P4CLIENT'] = 'client_ws2' expect(provider).to receive(:p4).with(['client', '-o', 'client_ws2']).and_return({}) expect(provider).to receive(:p4).with(['client', '-i'], spec) expect(provider).to receive(:p4).with(['sync', resource.value(:source)]) provider.create end end context 'when a client and source are not given' do it "executes 'p4 client'" do ENV['P4CLIENT'] = nil path = resource.value(:path) host = Facter.value('hostname') default = 'puppet-' + Digest::MD5.hexdigest(path + host) expect(provider).to receive(:p4).with(['client', '-o', default]).and_return({}) expect(provider).to receive(:p4).with(['client', '-i'], spec) provider.create end end end describe 'destroying' do it 'removes the directory' do ENV['P4CLIENT'] = 'test_client' expect(provider).to receive(:p4).with(['client', '-d', '-f', 'test_client']) expect_rm_rf provider.destroy end end describe 'checking existence' do it 'checks for the directory' do expect(provider).to receive(:p4).with(['info'], marshal: false).and_return({}) expect(provider).to receive(:p4).with(['where', resource.value(:path) + '/...'], raise: false).and_return({}) provider.exists? end end describe 'checking the source property' do it "runs 'p4 where'" do resource[:source] = '//public/something' expect(provider).to receive(:p4).with(['where', resource.value(:path) + '/...'], raise: false).and_return('depotFile' => '//public/something') expect(provider.source).to eq(resource.value(:source)) end end describe 'setting the source property' do it "calls 'create'" do resource[:source] = '//public/something' expect(provider).to receive(:create) provider.source = resource.value(:source) end end end diff --git a/spec/unit/puppet/provider/vcsrepo/svn_spec.rb b/spec/unit/puppet/provider/vcsrepo/svn_spec.rb index ebf20bf..c598820 100644 --- a/spec/unit/puppet/provider/vcsrepo/svn_spec.rb +++ b/spec/unit/puppet/provider/vcsrepo/svn_spec.rb @@ -1,324 +1,326 @@ +# frozen_string_literal: true + require 'spec_helper' describe Puppet::Type.type(:vcsrepo).provider(:svn) do let(:resource) do Puppet::Type.type(:vcsrepo).new(name: 'test', ensure: :present, provider: :svn, path: '/tmp/vcsrepo') end let(:provider) { resource.provider } let(:test_paths) { ['path1/file1', 'path2/nested/deep/file2'] } let(:test_paths_parents) { ['path1', 'path2', 'path2/nested', 'path2/nested/deep'] } before :each do allow(Puppet::Util).to receive(:which).with('svn').and_return('/usr/bin/svn') end describe 'creation/checkout' do context 'with source and revision' do it "executes 'svn checkout' with a revision" do resource[:source] = 'exists' resource[:revision] = '1' expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'checkout', '-r', resource.value(:revision), resource.value(:source), resource.value(:path)) provider.create end end context 'with source' do it "justs execute 'svn checkout' without a revision" do resource[:source] = 'exists' expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'checkout', resource.value(:source), resource.value(:path)) provider.create end end context 'with fstype' do it "executes 'svnadmin create' with an '--fs-type' option" do resource[:fstype] = 'ext4' expect(provider).to receive(:svnadmin).with('create', '--fs-type', resource.value(:fstype), resource.value(:path)) provider.create end end context 'without fstype' do it "executes 'svnadmin create' without an '--fs-type' option" do expect(provider).to receive(:svnadmin).with('create', resource.value(:path)) provider.create end end context 'with depth' do it "executes 'svn checkout' with a depth" do resource[:source] = 'exists' resource[:depth] = 'infinity' expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'checkout', '--depth', 'infinity', resource.value(:source), resource.value(:path)) provider.create end end context 'with trust_server_cert' do it "executes 'svn checkout' without a trust-server-cert" do resource[:source] = 'exists' resource[:trust_server_cert] = false expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'checkout', resource.value(:source), resource.value(:path)) provider.create end it "executes 'svn checkout' with a trust-server-cert" do resource[:source] = 'exists' resource[:trust_server_cert] = true expect(provider).to receive(:svn_wrapper).with('--non-interactive', '--trust-server-cert', 'checkout', resource.value(:source), resource.value(:path)) provider.create end end context 'with specific include paths' do it 'raises an error when trying to make a repo' do resource[:includes] = test_paths expect { provider.create }.to raise_error(Puppet::Error, %r{Specifying include paths on a nonexistent repo.}) end it 'performs a sparse checkout' do resource[:source] = 'exists' resource[:includes] = test_paths expect(Dir).to receive(:chdir).with('/tmp/vcsrepo').once.and_yield expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'checkout', '--depth', 'empty', resource.value(:source), resource.value(:path)) expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'update', '--depth', 'empty', *test_paths_parents) expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'update', *resource[:includes]) provider.create end it 'performs a sparse checkout at a specific revision' do resource[:source] = 'exists' resource[:revision] = 1 resource[:includes] = test_paths expect(Dir).to receive(:chdir).with('/tmp/vcsrepo').once.and_yield expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'checkout', '-r', resource.value(:revision), '--depth', 'empty', resource.value(:source), resource.value(:path)) expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'update', '--depth', 'empty', '-r', resource.value(:revision), *test_paths_parents) expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'update', '-r', resource.value(:revision), *resource[:includes]) provider.create end it 'performs a sparse checkout with a specific depth' do resource[:source] = 'exists' resource[:depth] = 'files' resource[:includes] = test_paths expect(Dir).to receive(:chdir).with('/tmp/vcsrepo').once.and_yield expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'checkout', '--depth', 'empty', resource.value(:source), resource.value(:path)) expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'update', '--depth', 'empty', *test_paths_parents) expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'update', '--depth', resource.value(:depth), *resource[:includes]) provider.create end it 'performs a sparse checkout at a specific depth and revision' do resource[:source] = 'exists' resource[:revision] = 1 resource[:depth] = 'files' resource[:includes] = test_paths expect(Dir).to receive(:chdir).with('/tmp/vcsrepo').once.and_yield expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'checkout', '-r', resource.value(:revision), '--depth', 'empty', resource.value(:source), resource.value(:path)) expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'update', '--depth', 'empty', '-r', resource.value(:revision), *test_paths_parents) expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'update', '-r', resource.value(:revision), '--depth', resource.value(:depth), *resource[:includes]) provider.create end end end describe 'destroying' do it 'removes the directory' do expect_rm_rf provider.destroy end end describe 'checking existence' do it "runs `svn info` on the path when there's a source" do resource[:source] = 'dummy' expect_directory?(true, resource.value(:path)) expect(provider).to receive(:svn_wrapper).with('info', resource[:path]) provider.exists? end it "runs `svnlook uuid` on the path when there's no source" do expect_directory?(true, resource.value(:path)) expect(provider).to receive(:svnlook).with('uuid', resource[:path]) provider.exists? end end describe 'checking the revision property' do before(:each) do allow(provider).to receive(:svn_wrapper).with('--non-interactive', 'info').and_return(fixture(:svn_info)) end it "uses 'svn info'" do expect_chdir expect(provider.revision).to eq('4') # From 'Revision', not 'Last Changed Rev' end end describe 'setting the revision property' do let(:revision) { '30' } context 'with conflict' do it "uses 'svn update'" do resource[:conflict] = 'theirs-full' expect_chdir expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'update', '-r', revision, '--accept', resource.value(:conflict)) provider.revision = revision end end context 'without conflict' do it "uses 'svn update'" do expect_chdir expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'update', '-r', revision) provider.revision = revision end end end describe 'setting the revision property and repo source' do let(:revision) { '30' } context 'with conflict' do it "uses 'svn switch'" do resource[:source] = 'an-unimportant-value' resource[:conflict] = 'theirs-full' expect_chdir expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'switch', '-r', revision, 'an-unimportant-value', '--accept', resource.value(:conflict)) provider.revision = revision end end context 'without conflict' do it "uses 'svn switch' - variation one" do resource[:source] = 'an-unimportant-value' expect_chdir expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'switch', '-r', revision, 'an-unimportant-value') provider.revision = revision end it "uses 'svn switch' - variation two" do resource[:source] = 'an-unimportant-value' resource[:revision] = '30' expect_chdir expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'switch', '-r', resource.value(:revision), 'an-unimportant-value') provider.source = resource.value(:source) end end end describe 'checking the source property' do before(:each) do allow(provider).to receive(:svn_wrapper).with('--non-interactive', 'info').and_return(fixture(:svn_info)) end it "uses 'svn info'" do expect_chdir expect(provider.source).to eq('http://example.com/svn/trunk') # From URL end end describe 'checking the basic_auth properties' do context 'when basic_auth_username is set and basic_auth_password is not set' do it 'fails' do resource[:source] = 'an-unimportant-value' resource[:basic_auth_username] = 'dummy_user' expect { provider.create }.to raise_error RuntimeError, %r{you must specify the HTTP basic authentication password.+}i end end context 'when basic_auth_username is not set and basic_auth_password is set' do it 'fails' do resource[:source] = 'an-unimportant-value' resource[:basic_auth_password] = 'dummy_pass' expect { provider.create }.to raise_error RuntimeError, %r{you must specify the HTTP .+username.*}i end end context 'when basic_auth_password is Sensitive' do let(:resource) do Puppet::Type.type(:vcsrepo).new(name: 'test', ensure: :present, provider: :svn, path: '/tmp/vcsrepo', source: 'an-unimportant-value', sensitive_parameters: [:basic_auth_password], basic_auth_username: 'dummy_user', basic_auth_password: Puppet::Pops::Types::PSensitiveType::Sensitive.new('dummy_pass')) end it 'works' do expect(provider).to receive(:svn_wrapper).with('--non-interactive', '--username', resource.value(:basic_auth_username), '--password', resource.value(:basic_auth_password).unwrap, '--no-auth-cache', 'checkout', resource.value(:source), resource.value(:path)) provider.create end end context 'when basic_auth_password contains non-ASCII characters' do it 'fails' do resource[:source] = 'an-important-value' resource[:basic_auth_username] = 'dummy_user' resource[:basic_auth_password] = 'ÙöØÓqþBÐh¦¹XH8«' expect { provider.create }.to raise_error RuntimeError, %r{The password can not contain non-ASCII characters} end end context 'when basic_auth_password contains only ASCII characters' do it 'works' do resource[:source] = 'an-important-value' resource[:basic_auth_username] = 'dummy_user' resource[:basic_auth_password] = 'dummy_pass' provider.create end end end describe 'setting the source property' do context 'with conflict' do it "uses 'svn switch'" do resource[:source] = 'http://example.com/svn/tags/1.0' resource[:conflict] = 'theirs-full' expect_chdir expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'switch', '--accept', resource.value(:conflict), resource.value(:source)) provider.source = resource.value(:source) end end context 'without conflict' do it "uses 'svn switch'" do resource[:source] = 'http://example.com/svn/tags/1.0' expect_chdir expect(provider).to receive(:svn_wrapper).with('--non-interactive', 'switch', resource.value(:source)) provider.source = resource.value(:source) end end end end diff --git a/spec/unit/puppet/type/vcsrepo_spec.rb b/spec/unit/puppet/type/vcsrepo_spec.rb index cbf60da..d6aba6d 100755 --- a/spec/unit/puppet/type/vcsrepo_spec.rb +++ b/spec/unit/puppet/type/vcsrepo_spec.rb @@ -1,151 +1,153 @@ #! /usr/bin/env ruby +# frozen_string_literal: true + require 'spec_helper' describe Puppet::Type.type(:vcsrepo) do before :each do allow(Puppet::Type.type(:vcsrepo)).to receive(:defaultprovider).and_return(providerclass) end let(:providerclass) do described_class.provide(:fake_vcsrepo_provider) do attr_accessor :property_hash def create; end def destroy; end def exists? get(:ensure) != :absent end mk_resource_methods has_features :include_paths end end let(:provider) do providerclass.new(name: 'fake-vcs') end let(:resource) do described_class.new(name: '/repo', ensure: :present, source: 'http://example.com/repo/', provider: provider) end let(:ensureprop) do resource.property(:ensure) end let(:sourceprop) do resource.property(:source) end properties = [:ensure, :source] properties.each do |property| - it "should have a #{property} property" do + it "has a #{property} property" do expect(described_class.attrclass(property).ancestors).to be_include(Puppet::Property) end end parameters = [:ensure] parameters.each do |parameter| - it "should have a #{parameter} parameter" do + it "has a #{parameter} parameter" do expect(described_class.attrclass(parameter).ancestors).to be_include(Puppet::Parameter) end end describe "with an include path that starts with a '/'" do it 'raises a Puppet::ResourceError error' do expect { resource[:includes] = ['/path1/file1', '/path2/file2'] }.to raise_error(Puppet::ResourceError, %r{Include path '.*' starts with a '/'}) end end describe 'when using a provider that adds/removes a trailing / to the source' do it 'stays in sync when it leaves it as-is' do sourceprop.should = 'http://example.com/repo/' expect(sourceprop.safe_insync?('http://example.com/repo/')).to eq(true) end it 'stays in sync when it adds a slash' do sourceprop.should = 'http://example.com/repo' expect(sourceprop.safe_insync?('http://example.com/repo/')).to eq(true) end it 'stays in sync when it removes a slash' do sourceprop.should = 'http://example.com/repo/' expect(sourceprop.safe_insync?('http://example.com/repo')).to eq(true) end it 'is out of sync with a different source' do sourceprop.should = 'http://example.com/repo/asdf' expect(sourceprop.safe_insync?('http://example.com/repo')).to eq(false) end end describe 'default resource with required params' do it 'has a valid name parameter' do expect(resource[:name]).to eq('/repo') end it 'has ensure set to present' do expect(resource[:ensure]).to eq(:present) end it 'has path set to /repo' do expect(resource[:path]).to eq('/repo') end defaults = { owner: nil, group: nil, user: nil, revision: nil, } defaults.each_pair do |param, value| - it "should have #{param} parameter set to #{value}" do + it "has #{param} parameter set to #{value}" do expect(resource[param]).to eq(value) end end end describe 'when changing the ensure' do it 'is in sync if it is :absent and should be :absent' do ensureprop.should = :absent expect(ensureprop.safe_insync?(:absent)).to eq(true) end it 'is in sync if it is :present and should be :present' do ensureprop.should = :present expect(ensureprop.safe_insync?(:present)).to eq(true) end it 'is out of sync if it is :absent and should be :present' do ensureprop.should = :present expect(ensureprop.safe_insync?(:absent)).not_to eq(true) end it 'is out of sync if it is :present and should be :absent' do ensureprop.should = :absent expect(ensureprop.safe_insync?(:present)).not_to eq(true) end end describe 'when running the type it should autorequire packages' do let(:catalog) { Puppet::Resource::Catalog.new } let(:resource) { described_class.new(name: '/foo', provider: provider) } before :each do ['git', 'git-core', 'mercurial', 'subversion'].each do |pkg| catalog.add_resource(Puppet::Type.type(:package).new(name: pkg)) end end it 'requires package packages' do catalog.add_resource(resource) req = resource.autorequire expect(req.size).to eq(4) end end end