Rex-1.16.1/0000755000175000017500000000000015032177050011360 5ustar00ferkiferkiRex-1.16.1/share/0000755000175000017500000000000015032177050012462 5ustar00ferkiferkiRex-1.16.1/share/rex-tab-completion.bash0000644000175000017500000000356315032177050017041 0ustar00ferkiferki# bash completion for rex _rex() { local cur prev COMPREPLY=() _get_comp_words_by_ref -n : cur prev if [[ "$cur" == -* ]]; then COMPREPLY=( $(compgen -W '$(_parse_help "$1" -h)' -- "$cur") ) return fi case "$prev" in -f) _filedir ;; -H) if [ -f Rexfile ]; then hosts=( $(rex -Ty 2>/dev/null | perl -MYAML -MList::Util=uniq -E 'my $groups = Load(join "", <>)->{groups}; say $_->{name} for uniq sort map { @{ $groups->{$_} } } keys %$groups') ) COMPREPLY=( $( compgen -W '${hosts[@]}' -- "$cur" ) ) || _known_hosts_real -a "$cur" fi ;; -E) if [ -f Rexfile ]; then envs=( $(rex -Ty 2>/dev/null | perl -MYAML -e 'my $envs = Load(join "", <>)->{envs}; print "$_\n" for @$envs;') ) COMPREPLY=( $( compgen -W '${envs[@]}' -- "$cur" ) ) fi ;; -G) if [ -f Rexfile ]; then groups=( $(rex -Ty 2>/dev/null | perl -MYAML -e 'my $groups = Load(join "", <>)->{groups}; print "$_\n" for keys %$groups;') ) COMPREPLY=( $( compgen -W '${groups[@]}' -- "$cur" ) ) fi ;; *) if [ -f Rexfile ]; then tasks=( $(rex -Ty 2>/dev/null | perl -MYAML -E 'my $tasks = Load(join "", <>)->{tasks}; say $_ for @$tasks;') ) COMPREPLY=( $( compgen -W '${tasks[@]}' -- "$cur" ) ) fi ;; esac _rex_fix_colon_reply return 0 } && complete -F _rex rex _rex_fix_colon_reply() { local colprefs i colprefs=${cur%"${cur##*:}"} i=${#COMPREPLY[*]} while [ $((--i)) -ge 0 ]; do COMPREPLY[$i]=${COMPREPLY[$i]#"$colprefs"} done } # Local variables: # mode: shell-script # indent-tabs-mode: nil # End: # ex: ts=4 sw=4 et filetype=sh Rex-1.16.1/share/rex-tab-completion.zsh0000644000175000017500000000532415032177050016725 0ustar00ferkiferki#compdef rex # zsh completions for 'rex' # initially generated with http://github.com/RobSis/zsh-completion-generator # complete groups, environments, hosts and tasks (default) local curcontext="$curcontext" state state_descr line context ret=1 typeset -A opt_args _hostgroups(){ groups=(${(f)"$(rex -Ty 2>/dev/null| perl -MYAML -e 'my $groups = Load(join "", <>)->{groups}; print "$_\n" for keys %$groups;')"}) _wanted hostgroups expl "available host groups" compadd -a groups } _envs(){ envs=(${(f)"$(rex -Ty 2>/dev/null| perl -MYAML -e 'my $envs = Load(join "", <>)->{envs}; print "$_\n" for @$envs;')"}) _wanted environments expl "available environments" compadd -a envs } # return hosts managed by rex or any other host availabe via zsh's _hosts function _rex_hosts() { rexhosts=(${(f)"$(rex -Ty 2>/dev/null| perl -MYAML -MList::Util=uniq -E 'my $groups = Load(join "", <>)->{groups}; say $_->{name} for uniq sort map { @{ $groups->{$_} } } keys %$groups')"}) _wanted hosts expl "rex managed hosts" compadd -a rexhosts || _hosts } local arguments arguments=( '-b[run batch]' '-e[run the given code fragment]' '-E[execute a task on the given environment]:environments:_envs' '-G[|-g Execute a task on the given server groups]:hosts group:_hostgroups' '-H[execute a task on the given hosts (space delimited)]:host:_rex_hosts' '-z[execute a task on hosts from this commands output]' '-K[public key file for the ssh connection]' '-P[private key file for the ssh connection]' '-p[password for the ssh connection]' '-u[username for the ssh connection]' '-d[show debug output]' '-ddd[show more debug output (includes profiling output)]' '-m[monochrome output: no colors]' '-o[output format]' '-q[quiet mode: no log output]' '-qw[quiet mode: only output warnings and errors]' '-Q[really quiet: output nothing]' '-T[list tasks]' '-Ta[List all tasks, including hidden]' '-Tm[list tasks in machine-readable format]' '-Tv[list tasks verbosely]' '-Ty[list tasks in YAML format]' '-c[turn cache ON]' '-C[turn cache OFF]' '-f[use this file instead of Rexfile]:filename:_files' '-F[force: disregard lock file]' '-h[display this help message]' '-M[load this module instead of Rexfile]' '-O[pass additional options, like CMDB path]' '-s[use sudo for every command]' '-S[password for sudo]' '-t[number of threads to use (aka parallelism param)]' '-v[display (R)?ex version]' '*:options:->vary' ) _arguments -C -s -A "*" $arguments && ret=0 case "$state" in vary) local optsfile optsfile='Rexfile' if [[ -e $optsfile ]]; then tasks=(${(f)"$(rex -Ty 2>/dev/null| perl -MYAML -E 'my $tasks = Load(join "", <>)->{tasks}; say $_ for @$tasks;')"}) _wanted tasks expl "available tasks" compadd -a tasks fi ;; esac return $ret Rex-1.16.1/xt/0000755000175000017500000000000015032177050012013 5ustar00ferkiferkiRex-1.16.1/xt/release/0000755000175000017500000000000015032177050013433 5ustar00ferkiferkiRex-1.16.1/xt/release/cpan-changes.t0000644000175000017500000000034615032177050016152 0ustar00ferkiferkiuse strict; use warnings; # this test was generated with Dist::Zilla::Plugin::Test::CPAN::Changes 0.013 use Test::More 0.96 tests => 1; use Test::CPAN::Changes; subtest 'changes_ok' => sub { changes_file_ok('ChangeLog'); }; Rex-1.16.1/xt/release/kwalitee.t0000644000175000017500000000027515032177050015431 0ustar00ferkiferki# this test was generated with Dist::Zilla::Plugin::Test::Kwalitee 2.12 use strict; use warnings; use Test::More 0.88; use Test::Kwalitee 1.21 'kwalitee_ok'; kwalitee_ok(); done_testing; Rex-1.16.1/xt/author/0000755000175000017500000000000015032177050013315 5ustar00ferkiferkiRex-1.16.1/xt/author/critic-progressive.t0000755000175000017500000000126315032177050017332 0ustar00ferkiferki#!/usr/bin/env perl use v5.14.4; use warnings; our $VERSION = '9999.99.99_99'; # VERSION use File::Spec; use Test::More; plan skip_all => 'these tests are for testing by the author' unless $ENV{AUTHOR_TESTING}; use Test::Perl::Critic::Progressive qw(progressive_critic_ok set_history_file); if ( $ENV{CI} ) { my $history_dir = File::Spec->catfile( File::Spec->tmpdir(), 'cache' ); my $history_file = File::Spec->catfile( $history_dir, '.perlcritic-history' ); mkdir $history_dir; set_history_file($history_file); } my @files = qw(xt/author/critic-progressive.t xt/author/perltidy.t); my @directories = qw(lib bin t); progressive_critic_ok( @files, @directories ); Rex-1.16.1/xt/author/minimum-version.t0000644000175000017500000000015315032177050016637 0ustar00ferkiferkiuse strict; use warnings; use Test::More; use Test::MinimumVersion; all_minimum_version_ok( qq{5.14.4} ); Rex-1.16.1/xt/author/pod-syntax.t0000644000175000017500000000025215032177050015607 0ustar00ferkiferki#!perl # This file was automatically generated by Dist::Zilla::Plugin::PodSyntaxTests. use strict; use warnings; use Test::More; use Test::Pod 1.41; all_pod_files_ok(); Rex-1.16.1/xt/author/perltidy.t0000755000175000017500000000120015032177050015332 0ustar00ferkiferki#!/usr/bin/env perl use v5.14.4; use warnings; our $VERSION = '9999.99.99_99'; # VERSION use Test::More; use Test::PerlTidy; plan skip_all => 'these tests are for testing by the author' unless $ENV{AUTHOR_TESTING}; plan tests => 2; subtest 'files in bin', sub { my @bin_files = glob('bin/*'); plan tests => scalar @bin_files; for my $file (@bin_files) { ok( Test::PerlTidy::is_file_tidy($file), "$file is tidy" ); } }; subtest 'standard files', sub { run_tests( exclude => [ 'Makefile.PL', '.build/', 'blib/', 'misc/', 'local/', qr{xt/author/(?!perltidy\.t$)}, qr{xt/release/}, ], ); }; Rex-1.16.1/CONTRIBUTING.md0000644000175000017500000003613115032177050013615 0ustar00ferkiferki# Contributing guide Thank you for considering to contribute to Rex! Volunteers like you make this free and open source project possible. We consider it important to ensure everyone involved can make an efficient use of the time and effort they choose to invest. This guide aims to streamline communication around contributions by documenting the most frequently discussed aspects. While most sections focus on the code contributions we typically receive, we encourage and welcome other types of contributions too. For example: - improving documentation - testing with different setups - donating (access to) hardware - submitting and triaging bug reports - sharing your use cases and experience with Rex - spreading the word via blogs, talks, and social media - starring the project on [GitHub](https://github.com/RexOps/Rex) - adding Rex to your favorites on [MetaCPAN](https://metacpan.org/pod/Rex) - listing Rex as part of your stack on [StackShare](https://stackshare.io/rex) ## Project guidelines We took many decisions around Rex since the first commit in 2010. We found some of the ideas coming up more often than others, and decided to share them here (in no particular order). ### Getting help and support If you need help with questions like “How do I…?” and “Why does it…?”, please start a discussion via one of our [support](https://www.rexify.org/support/index.html) channels. We list options there for both community and commercial support. ### Discussions, issues, and pull requests In short, we follow these steps in that order: discuss first, track next, implement last. 1. Discuss: first we discuss the relevant details of any change proposal idea, and decide on next steps. 1. Track: when we decide to work on a change proposal, we open an issue to track progress, and design or refine potential solutions. 1. Implement: we open pull requests as a last step to propose and review a specific implementation of the given idea. Please use the templates we provide to streamline our collaboration in typical situations. ### Making exceptions We may choose and accept skipping one or more of the steps in exceptional cases, as long as the reasoning makes sense and gets well documented. When we find ourselves repeating the same exception, we will document it here as a standard case instead. When you feel our workflow, guidelines, or templates does not fit your situation, please consider that a strong indicator to [contact us](https://www.rexify.org/support/index.html) early, and we’ll help figuring out the best course of action. ### Rex core vs extending Rex Strictly speaking, we consider the following the core competency of Rex: execute commands and manage files, by defining tasks and orchestrating their execution. Rex gained lots of other capabilities over time, and historically many of them landed in core as well. This includes the capability to extend Rex without changing the core (or only minimally), for example when adding support to: - new operating systems - new shell types - new virtualization methods - new cloud providers We strongly encourage to add such new capabilities via their own extension modules outside the core. In case of questions or concerns, see also the “Common scenarios” section, or [contact us](https://www.rexify.org/support/index.html). ### Supported operating systems We expect Rex to _run_ on operating systems wherever Perl can run. This includes Linux, BSDs, Mac OS X, Windows, Solaris, and possibly others. We also expect Rex to _manage_ endpoints running different operating systems. This primarily means Unix-like systems for now. We welcome patches and guides about how to support Rex both to run on and manage even more operating systems. In general, we aim to support Rex to run on and manage actively maintained platforms only. See also the “Deprecation policy” section for more details. ### Supported Perl versions Rex aims to run even on older Perl versions up to 10 years old. Currently this means 5.14.4. On top of the supported minimum version of Perl, we aim to support the latest versions of all minor stable Perl 5 releases since then. That makes the full list the following: - 5.14.4 - 5.16.3 - 5.18.4 - 5.20.3 - 5.22.4 - 5.24.4 - 5.26.3 - 5.28.3 - 5.30.3 - 5.32.1 - 5.34.1 - 5.36.1 - 5.38.2 - 5.40.2 - 5.42.0 ### Backwards compatibility We aim to remain backwards compatible within major versions of Rex, as implied by following [Dotted Semantic Versioning](https://metacpan.org/pod/Version::Dotted::Semantic#Dotted-Semantic-Versioning-Specification). To introduce new features while staying backwards compatible, Rex also uses [feature flags](https://metacpan.org/pod/Rex#FEATURE-FLAGS). This makes it possible to selectively opt-in to or opt-out from new features, depending on the needs of the given use case. Feature flags named after the minor releases of Rex (for example `1.4`) enable the collection of preferred settings for the given Rex version. In general, we strive to add new feature flags in pairs for opting in and out. ### Deprecation policy We may drop features and code paths by following a planned deprecation procedure. Adequate warnings and an ample transition period should precede any such change. If a supported operating system gets deprecated, we may try to keep it supported while it doesn't cause any problems. As soon as a retired operating system starts causing bugs, or gets in the way of progress, we consider it a candidate to drop from support. In other words, while we may choose to stay lazy to keep the support around, we also don’t plan to put active effort into maintaining compatibility with something which got abandoned by its own creators. In case you depend on a deprecated feature, or must use or manage a retired operating system, please consider contributing your solutions, or asking for community or commercial [support](https://www.rexify.org/support/index.html) for your use case. ### Code layout We use the latest version of [Perl::Tidy](https://metacpan.org/pod/Perl::Tidy) to format the Rex codebase according to the rules described in `.perltidyrc`. We expect all contributions to follow the same formatting rules, and we also actively test for it. To avoid unnecessary “tidy only” commits in contributions, we recommend to integrate a formatting step directly into your workflow. For example via a git pre-commit hook, a shortcut in the editor, or as an automatic action upon saving. Importantly, we emphasize the importance of a consistent layout throughout the codebase over any specific formatting rule. Since we include `.perltidyrc` in the repo, consider it as subject of contributions too. ### Code quality We use [Test::Perl::Critic::Progressive](https://metacpan.org/pod/Test::Perl::Critic::Progressive) to make sure every change follows the code quality rules in `.perlcriticrc`, and no contribution introduces any violations accidentally. The `dist.ini` file contains the list of perlcritic policy modules we use. Since we include both `.perlcriticrc` and `dist.ini` in the repo, consider it as subject of contributions too. In fact, we welcome improving both our policies and the codebase in this regard. ### Tests should pass Rex has two major test suites: - the tests included with the code, exercising different modules and features - the historical tests in the [RexOps/rex-build](https://github.com/RexOps/rex-build) repo, making sure Rex can manage actual VMs running different operating systems In general, we expect contributions to include their own tests for the proposed changes, while ensuring that previous tests still pass. To make it easier to follow the guidelines of this document, we strive to have corresponding tests for them as well. ### Have fun We find it highly important to have fun while using Rex and contributing to it. If you have a different experience, then please let us know, as that probably indicates a bug somewhere. ## Development tools We use [Git](https://www.git-scm.com) for version control of the source code, and host Rex code in a [GitHub repository](https://github.com/RexOps/Rex). We recommend to use a separate Perl environment for development, like the ones you managed by [Perlbrew](https://perlbrew.pl/) or similar tools. The code and examples use [cpanm](https://metacpan.org/pod/App::cpanminus) to install modules from CPAN. We also use [Dist::Zilla](https://metacpan.org/pod/Dist::Zilla) as an authoring tool. [Perltidy](https://metacpan.org/pod/distribution/Perl-Tidy/bin/perltidy) takes care of maintaining a consistent source code formatting. [Perlcritic](https://metacpan.org/pod/distribution/Perl-Critic/bin/perlcritic) makes sure the codebase follows best practices and adheres to our minimum quality requirements. ## Dependencies After cloning the source code, run the following commands to install dependencies: dzil authordeps | cpanm dzil listdeps | cpanm While some optional dependencies may not support your platform, use this command to install these as well: dzil listdeps --suggests | cpanm Install the remaining dependencies specific to the operating system, and Rex itself with: dzil install To only install the dependencies specific to the operating system, run one of these commands: - Windows: `cpanm Net::SSH2` - non-Windows: `cpanm Net::OpenSSH Net::SFTP::Foreign IO::Pty` When considering new dependencies, we prefer: - Perl core modules - CPAN modules already packaged by the operating systems distributing Rex - CPAN modules supporting the same platforms Rex itself supports We welcome and encourage contributions aimed at reducing the amount of Rex dependencies, for example by: - removing unused dependencies - consolidating similar dependencies into a single one - replacing external dependencies with Perl code modules ## Testing Run the test suite included with the source code of Rex with `prove`: prove --lib --recurse t/ Extended, author, and release tests may need further dependencies. Install these with: dzil listdeps --author --missing | cpanm Then run those tests with: dzil test --all As an important step before modifying the codebase, please run the progressive perlcritic tests on the default branch: rm -f xt/author/.perlcritic-history AUTHOR_TESTING=1 prove --lib xt/author/critic-progressive.t The first run should succeed, and records the current state. This serves as baseline to compare later runs against. Please see the [Test::Perl::Critic::Progressive NOTES](https://metacpan.org/pod/Test::Perl::Critic::Progressive#NOTES) for more details. ## Git workflow We prefer receiving contributions by [forking](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-forks) the repository on GitHub, and then sending [pull requests](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests) against the default branch of the repository. If you have to use a different way, please [contact us](https://www.rexify.org/support/index.html) first to discuss alternative options. Please read and follow the “Discussions, issues, and pull requests” section before investing effort in a pull request. We recommend to use feature [branches](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-branches) when working on contributions. This helps keeping the commits related to the change together, while separating them from the main line of development. We consider a single commit ideal when it: - represents a single logical change ([atomic](https://en.wikipedia.org/wiki/Atomic_commit#Atomic_commit_convention)) - has a useful commit message - has the expected test suite result We expect commit messages to follow the guidelines collected in the [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/) article, and we also test for most of those expectations. We generally recommend to: - follow a test-driven approach: add new breaking tests on a first commit, then change the code to fix them on a follow up commit, then refactoring on follow-up commits as needed - simplify making larger changes: refactor on a separate commit first to make later changes simpler on smaller commits - use atomic commits: use separate commits in a single pull request to separate the logical steps, and to help understanding the series of changes during review - share your ideas and ask for feedback early: open [draft pull requests](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests#draft-pull-requests) - keep your branch up-to-date: [rebase](https://docs.github.com/en/github/using-git/about-git-rebase) your feature branch on top of our default branch whenever we made new commits while you work - keep a tidy history: use follow-up/cleanup commits on the same PR, and [squash or fixup related commits](https://docs.github.com/en/github/using-git/about-git-rebase) together in the feature branch, while keeping commits atomic (in other words, no need for “tidy only”, “fix typo”, and merge commits, while avoiding to squash everything into a single commit) - use force push on pull request branches: work on your contributions until they pass automated tests and review, and keep the history tidy there to prepare for merging ## Common scenarios ### Add support to manage new operating systems Allowing Rex to manage a new operating system requires the following steps: 1. Teach rex about how to detect the given operating system - add a way to `Rex::Hardware::Host::get_operating_system()` to detect the given operating system - add a new `is_myos()` function to `Rex::Commands::Gather` 1. Let Rex choose the proper package and service management modules for the given operating system - add support in `Rex::Service` and `Rex::Pkg` 1. Add new service and package management modules specific to the given operating system - add `Rex::Service::MyOS` - add `Rex::Pkg::MyOS` While the first two steps must currently go into Rex core, please consider publishing the `Rex::Service::MyOS` and `Rex::Pkg::MyOS` modules as a separate distribution. ### Add support for new virtualization methods Follow these steps to add support for a new virtualization method called `MyVirt`: - create the top-level `Rex::Virtualization::MyVirt` module which includes the constructor, and the documentation - create submodules for each virtualization command, e.g. `Rex::Virtualization::MyVirt::info` - implement the logic of the given command as the `execute` method Please consider publishing the `Rex::Virtualization::MyVirt` module as a separate distribution. ## Contribute to this guide If you think some of the information in this document got outdated, lacks clarity, or has bugs, please propose changes to it too. ## Useful resources - [Rex website](https://www.rexify.org) - [MetaCPAN](https://metacpan.org/pod/Rex) - [GitHub](https://github.com/RexOps/Rex) - [Discussions](https://github.com/RexOps/Rex/discussions) - [Issue tracker](https://github.com/RexOps/Rex/issues) - [Google Groups](https://groups.google.com/forum/#!forum/rex-users) - [StackShare](https://stackshare.io/rex) - [Matrix](https://matrix.to/#/#rexops:matrix.org) - [IRC](https://webchat.oftc.net/?channels=rexops) Rex-1.16.1/.perlcriticrc0000644000175000017500000000077315032177050014055 0ustar00ferkiferkiseverity = brutal theme = -freenode [-CodeLayout::RequireTidyCode] [CodeLayout::RequireTrailingCommaAtNewline] except_function_calls = 1 [Subroutines::ProhibitUnusedPrivateSubroutines] private_name_regex = _(?!_)\w+ [Subroutines::ProtectPrivateSubs] private_name_regex = _(?!_)\w+ [TooMuchCode::ProhibitDuplicateLiteral] allowlist = 'MSWin32' [-ValuesAndExpressions::ProhibitConstantPragma] [-ValuesAndExpressions::ProhibitVersionStrings] [Variables::ProhibitPackageVars] add_packages = Rex::Logger Rex-1.16.1/CONTRIBUTORS0000644000175000017500000000530415032177050013242 0ustar00ferkiferki # REX CONTRIBUTORS # This is the (likely incomplete) list of people who have helped make this distribution what it is, either via code contributions, patches, bug reports, help with troubleshooting, etc. A huge 'thank you' to all of them. * A Happy User * Alexander Karelas * Alexander Romanenko * Alexandr Ciornii * Alex Mestiashvili * Ali Polatel * alx542 * Anders Ossowicki * Andrej Zverev * Andrew Solomon * Andy Beverley * Arnold Bechtoldt * Boris Däppen * Brian Manning * Cameron Daniel * Chris Steigmeier * Christophe Wolfhugel * Crimson Thompson * Daniel Bäurer * Daniel Cesario * Daniel Dico * Denis Silakov * Dmitry Kopytov * Dominik Schulz * E. Choroba * Eduardo J * Eivin Giske Skaaren * elisdg * Elmer Quintanilla * Elvin Aslanov * Eric Johnson * Erik Huelsmann * Ferenc Erki * Franky Van Liedekerke * Fran Rodriguez * Gabor Szabo * Graham Todd * gregor herrmann * Harm Müller * Hayato Imai * Hiroaki Nakamura * Hiroki Matsuo * iblinder * Ilya Pavlov * James D Bearden * Jan Gehring * Jean Charles Passard * Jean-Marie Renouard * Jeen Lee * Jens Berthold * Joachim Bargsten * John Karr * Jonathan Delgado * Jon Gentle * Joris DE POOTER * Jose Luis Martinez * Jose Luis Perez Diez * Kasim Tuman * Keedi Kim * Ken Crowell * Kent Fredric * Kirill Babikhin * labbeduddel * Leah Neukirchen * LeMerP * Mario Domgoergen * Max E. Aubrey * Mitch Broadhead * Nathan Abu * Naveed Massjouni * necrophcodr * Nicolas Leclercq * Nigel Gregoire * Nikolay A. Fetisov * Nils Domrose * okaoka * Oleg Hardt * Olivier Cherrier * Orange * Paco Esteban * Patrick Lauer * Pavel Timofeev * perlancar * Peter H. Ezetta * Peter Manthey * petersonchen * Pierrick DINTRAT * Piotr Karbowski * Prajithp * Randy Lauen * Renée Bäcker * Robert Abraham * Robert Rothenberg * Roy Storey * Samuele Tognini * Sascha Askani * Sascha Guenther * Simon Bertrang * Solène Rapenne * Stephane Benoit * Steve Dondley * Sven Dowideit * Tamas Molnar * Tianon Gravi * Tokuhiro Matsuno * Tomohiro Hosaka * Ural Khasanov * Volker Kroll * Walery Wysotsky * Yanick Champoux * Yegor Korablev * Zane C. Bowers-Hadley * Сергей Романов * 范野人 * 饶琛琳 * Cuong Manh Le * David Golovan * Dominik Danter * Ilya Evseev * Niklas Larsson * Qiao Liu * Renato CRON * Peter Jankovics Rex-1.16.1/Makefile.PL0000644000175000017500000001650215032177050013336 0ustar00ferkiferki# This Makefile.PL for Rex was generated by # Dist::Zilla::Plugin::MakeMaker::Awesome 0.49. # Don't edit it but the dist.ini and plugins used to construct it. use strict; use warnings; use 5.014004; use ExtUtils::MakeMaker 7.1101; if ( $^O eq 'MSWin32' ) { my ( undef, $major, $minor ) = Win32::GetOSVersion(); if ( $major < 6 ) { die 'OS unsupported'; } elsif ( $major == 6 && $minor < 2 ) { die 'OS unsupported'; } } use File::ShareDir::Install; $File::ShareDir::Install::INCLUDE_DOTFILES = 1; $File::ShareDir::Install::INCLUDE_DOTDIRS = 1; install_share dist => "share"; my %WriteMakefileArgs = ( "ABSTRACT" => "the friendly automation framework", "AUTHOR" => "Jan Gehring ", "CONFIGURE_REQUIRES" => { "CPAN::Meta::Requirements" => "2.120620", "ExtUtils::MakeMaker" => "7.1101", "File::ShareDir::Install" => "0.06", "Module::Metadata" => 0 }, "DISTNAME" => "Rex", "EXE_FILES" => [ "bin/rex", "bin/rexify" ], "LICENSE" => "apache", "MIN_PERL_VERSION" => "5.014004", "NAME" => "Rex", "PREREQ_PM" => { "AWS::Signature4" => 0, "Carp" => 0, "Cwd" => 0, "Data::Dumper" => 0, "Data::Validate::IP" => 0, "Devel::Caller" => 0, "Digest::HMAC_SHA1" => 0, "Digest::MD5" => 0, "English" => 0, "Exporter" => 0, "Fcntl" => 0, "File::Basename" => 0, "File::Spec" => 0, "File::Spec::Unix" => 0, "File::Spec::Win32" => 0, "FindBin" => 0, "HTTP::Request" => 0, "HTTP::Request::Common" => 0, "Hash::Merge" => 0, "IO::File" => 0, "IO::Select" => 0, "IO::Socket" => 0, "IPC::Open3" => 0, "JSON::MaybeXS" => 0, "LWP::UserAgent" => 0, "List::Util" => "1.45", "MIME::Base64" => 0, "Module::Load::Conditional" => 0, "Net::OpenSSH::ShellQuoter" => 0, "POSIX" => 0, "Scalar::Util" => 0, "Sort::Naturally" => 0, "Storable" => 0, "Symbol" => 0, "Term::ANSIColor" => 0, "Term::ReadKey" => 0, "Test::Builder::Module" => 0, "Text::Glob" => 0, "Text::Wrap" => ">= 0, != 2023.0509", "Time::HiRes" => 0, "UNIVERSAL" => 0, "URI" => 0, "URI::QueryParam" => 0, "XML::Simple" => 0, "YAML" => ">= 0, != 1.25", "attributes" => 0, "base" => 0, "constant" => 0, "if" => 0, "lib" => 0, "overload" => 0, "strict" => 0, "vars" => 0, "version" => 0, "warnings" => 0 }, "TEST_REQUIRES" => { "File::Find" => 0, "File::Temp" => 0, "String::Escape" => 0, "Sub::Override" => 0, "Test::Deep" => 0, "Test::Exception" => 0, "Test::More" => 0, "Test::Output" => 0, "Test::UseAllModules" => 0, "Test::Warnings" => "0.033", "autodie" => 0, "utf8" => 0 }, "VERSION" => "1.16.1", "test" => { "TESTS" => "t/*.t t/commands/*.t t/hardware/*.t t/issue/*.t t/scm/*.t" } ); my %FallbackPrereqs = ( "AWS::Signature4" => 0, "Carp" => 0, "Cwd" => 0, "Data::Dumper" => 0, "Data::Validate::IP" => 0, "Devel::Caller" => 0, "Digest::HMAC_SHA1" => 0, "Digest::MD5" => 0, "English" => 0, "Exporter" => 0, "Fcntl" => 0, "File::Basename" => 0, "File::Find" => 0, "File::Spec" => 0, "File::Spec::Unix" => 0, "File::Spec::Win32" => 0, "File::Temp" => 0, "FindBin" => 0, "HTTP::Request" => 0, "HTTP::Request::Common" => 0, "Hash::Merge" => 0, "IO::File" => 0, "IO::Select" => 0, "IO::Socket" => 0, "IPC::Open3" => 0, "JSON::MaybeXS" => 0, "LWP::UserAgent" => 0, "List::Util" => "1.45", "MIME::Base64" => 0, "Module::Load::Conditional" => 0, "Net::OpenSSH::ShellQuoter" => 0, "POSIX" => 0, "Scalar::Util" => 0, "Sort::Naturally" => 0, "Storable" => 0, "String::Escape" => 0, "Sub::Override" => 0, "Symbol" => 0, "Term::ANSIColor" => 0, "Term::ReadKey" => 0, "Test::Builder::Module" => 0, "Test::Deep" => 0, "Test::Exception" => 0, "Test::More" => 0, "Test::Output" => 0, "Test::UseAllModules" => 0, "Test::Warnings" => "0.033", "Text::Glob" => 0, "Text::Wrap" => ">= 0, != 2023.0509", "Time::HiRes" => 0, "UNIVERSAL" => 0, "URI" => 0, "URI::QueryParam" => 0, "XML::Simple" => 0, "YAML" => ">= 0, != 1.25", "attributes" => 0, "autodie" => 0, "base" => 0, "constant" => 0, "if" => 0, "lib" => 0, "overload" => 0, "strict" => 0, "utf8" => 0, "vars" => 0, "version" => 0, "warnings" => 0 ); # inserted by Dist::Zilla::Plugin::DynamicPrereqs 0.040 requires('IPC::Shareable') if has_module('IPC::Shareable') || prompt('install Support custom output formatters? [y/N]', 'N') =~ /^y/i; requires('DBI') if has_module('DBI') || prompt('install Support database tasks and describing host groups in a database? [y/N]', 'N') =~ /^y/i; requires('XML::LibXML') if has_module('XML::LibXML') || prompt('install Support describing host groups in XML format? [y/N]', 'N') =~ /^y/i; requires('Expect') if has_module('Expect') || prompt('install Support rsync tasks? [y/N]', 'N') =~ /^y/i; unless ( eval { ExtUtils::MakeMaker->VERSION('6.63_03') } ) { delete $WriteMakefileArgs{TEST_REQUIRES}; delete $WriteMakefileArgs{BUILD_REQUIRES}; $WriteMakefileArgs{PREREQ_PM} = \%FallbackPrereqs; } delete $WriteMakefileArgs{CONFIGURE_REQUIRES} unless eval { ExtUtils::MakeMaker->VERSION(6.52) }; if ( $^O !~ /MSWin/i ) { $WriteMakefileArgs{PREREQ_PM}{'IO::Pty'} = $FallbackPrereqs{'IO::Pty'} = '0'; $WriteMakefileArgs{PREREQ_PM}{'Net::OpenSSH'} = $FallbackPrereqs{'Net::OpenSSH'} = '0'; $WriteMakefileArgs{PREREQ_PM}{'Net::SFTP::Foreign'} = $FallbackPrereqs{'Net::SFTP::Foreign'} = '0'; } if ( $^O =~ /MSWin/i ) { $WriteMakefileArgs{PREREQ_PM}{'Net::SSH2'} = $FallbackPrereqs{'Net::SSH2'} = '0'; $WriteMakefileArgs{PREREQ_PM}{'Win32::Console::ANSI'} = $FallbackPrereqs{'Win32::Console::ANSI'} = '0'; } if ( $^O eq 'darwin' ) { $WriteMakefileArgs{PREREQ_PM}{'IO::Pty'} = $FallbackPrereqs{'IO::Pty'} = '!= 1.18'; } WriteMakefile(%WriteMakefileArgs); { package MY; use File::ShareDir::Install qw(postamble); } # inserted by Dist::Zilla::Plugin::DynamicPrereqs 0.040 sub _add_prereq { my ($mm_key, $module, $version_or_range) = @_; $version_or_range ||= 0; warn "$module already exists in $mm_key (at version $WriteMakefileArgs{$mm_key}{$module}) -- need to do a sane metamerge!" if exists $WriteMakefileArgs{$mm_key}{$module} and $WriteMakefileArgs{$mm_key}{$module} ne '0' and $WriteMakefileArgs{$mm_key}{$module} ne $version_or_range; warn "$module already exists in FallbackPrereqs (at version $FallbackPrereqs{$module}) -- need to do a sane metamerge!" if exists $FallbackPrereqs{$module} and $FallbackPrereqs{$module} ne '0' and $FallbackPrereqs{$module} ne $version_or_range; $WriteMakefileArgs{$mm_key}{$module} = $FallbackPrereqs{$module} = $version_or_range; return; } sub has_module { my ($module, $version_or_range) = @_; require Module::Metadata; my $mmd = Module::Metadata->new_from_module($module); return undef if not $mmd; return $mmd->version($module) if not defined $version_or_range; require CPAN::Meta::Requirements; my $req = CPAN::Meta::Requirements->new; $req->add_string_requirement($module => $version_or_range); return 1 if $req->accepts_module($module => $mmd->version($module)); return 0; } sub requires { goto &runtime_requires } sub runtime_requires { my ($module, $version_or_range) = @_; _add_prereq(PREREQ_PM => $module, $version_or_range); } Rex-1.16.1/.perltidyrc0000644000175000017500000000021115032177050013534 0ustar00ferkiferki--ignore-side-comment-lengths --indent-columns=2 --minimum-space-to-comment=1 --no-outdent-long-quotes --static-side-comments --trim-pod Rex-1.16.1/lib/0000755000175000017500000000000015032177050012126 5ustar00ferkiferkiRex-1.16.1/lib/Rex/0000755000175000017500000000000015032177050012664 5ustar00ferkiferkiRex-1.16.1/lib/Rex/Virtualization/0000755000175000017500000000000015032177050015710 5ustar00ferkiferkiRex-1.16.1/lib/Rex/Virtualization/LibVirt/0000755000175000017500000000000015032177050017263 5ustar00ferkiferkiRex-1.16.1/lib/Rex/Virtualization/LibVirt/vncdisplay.pm0000644000175000017500000000140015032177050021770 0ustar00ferkiferki# # (c) Jan Gehring # package Rex::Virtualization::LibVirt::vncdisplay; use v5.14.4; use warnings; our $VERSION = '1.16.1'; # VERSION use Rex::Logger; use Rex::Helper::Run; use XML::Simple; use Data::Dumper; sub execute { my ( $class, $vmname ) = @_; my $virt_settings = Rex::Config->get("virtualization"); chomp( my $uri = ref($virt_settings) ? $virt_settings->{connect} : i_run "virsh uri" ); unless ($vmname) { die("You have to define the vm name!"); } Rex::Logger::debug("Getting info of domain: $vmname"); my $xml; my @vncdisplay = i_run "virsh -c $uri vncdisplay '$vmname'", fail_ok => 1; if ( $? != 0 ) { die("Error running virsh vncdisplay '$vmname'"); } return shift @vncdisplay; } 1; Rex-1.16.1/lib/Rex/Virtualization/LibVirt/hypervisor.pm0000644000175000017500000000407615032177050022042 0ustar00ferkiferki# # (c) Jan Gehring # package Rex::Virtualization::LibVirt::hypervisor; use v5.14.4; use warnings; our $VERSION = '1.16.1'; # VERSION use Rex::Logger; use Rex::Helper::Run; use XML::Simple; use Data::Dumper; sub execute { my ( $class, $arg1, %opt ) = @_; my $virt_settings = Rex::Config->get("virtualization"); chomp( my $uri = ref($virt_settings) ? $virt_settings->{connect} : i_run "virsh uri" ); unless ($arg1) { die("You have to define the vm name!"); } my ( $xml, @dominfo ); if ( $arg1 eq 'capabilities' ) { @dominfo = i_run "virsh -c $uri capabilities", fail_ok => 1; if ( $? != 0 ) { die("Error running virsh capabilities"); } my $xs = XML::Simple->new(); $xml = $xs->XMLin( join( "", @dominfo ), KeepRoot => 1, KeyAttr => 1, ForceContent => 1 ); } else { Rex::Logger::debug("Unknown action $arg1"); die("Unknown action $arg1"); } my %ret = (); my ( $k, $v ); if ( ref( $xml->{'capabilities'}->{'guest'} ) ne "ARRAY" ) { $xml->{'capabilities'}->{'guest'} = [ $xml->{'capabilities'}->{'guest'} ]; } for my $line ( @{ $xml->{'capabilities'}->{'guest'} } ) { next if ( $line->{'arch'}->{'name'} ne "x86_64" ); $ret{ $line->{'arch'}->{'name'} } = 'true' if defined( $line->{'arch'}->{'name'} ); $ret{'emulator'} = $line->{'arch'}->{'emulator'}->{'content'} if defined( $line->{'arch'}->{'emulator'}->{'content'} ); $ret{'loader'} = $line->{'arch'}->{'loader'}->{'content'} if defined( $line->{'arch'}->{'loader'}->{'content'} ); $ret{ $line->{'os_type'}->{'content'} } = 'true' if defined( $line->{'os_type'}->{'content'} ); if ( defined( $line->{'arch'}->{'domain'} ) && ref( $line->{'arch'}->{'domain'} ) eq 'ARRAY' ) { for ( @{ $line->{'arch'}->{'domain'} } ) { $ret{ $_->{'type'} } = 'true'; } } else { $ret{ $line->{'arch'}->{'domain'}->{'type'} } = 'true' if defined( $line->{'arch'}->{'domain'}->{'type'} ); } } return \%ret; } 1; Rex-1.16.1/lib/Rex/Virtualization/LibVirt/guestinfo.pm0000644000175000017500000000706515032177050021634 0ustar00ferkiferki# # (c) Jan Gehring # package Rex::Virtualization::LibVirt::guestinfo; use v5.14.4; use warnings; our $VERSION = '1.16.1'; # VERSION use Data::Dumper; use Rex::Logger; use Rex::Helper::Run; use Rex::Helper::Path; use Rex::Virtualization::LibVirt::iflist; use Rex::Commands::Gather; use Rex::Virtualization::LibVirt::info; use Rex::Interface::File; use Rex::Interface::Exec; use JSON::MaybeXS; require Rex::Commands::Run; sub execute { my ( $class, $vmname ) = @_; unless ($vmname) { die("You have to define the vm name!"); } Rex::Logger::debug("Getting info of guest: $vmname"); my $info = Rex::Virtualization::LibVirt::info->execute($vmname); if ( $info->{State} eq "shut off" ) { return {}; } my @ifaces; if ( Rex::Config::get_use_rex_kvm_agent && exists $info->{has_kvm_agent_on_port} && $info->{has_kvm_agent_on_port} ) { my $got_ip = 0; while ( $got_ip == 0 ) { my $fh = Rex::Interface::File->create(); my $rnd_file = get_tmp_file; my $content = q| use strict; use warnings; unlink $0; use IO::Socket::INET; my $sock; while(! $sock) { $sock = IO::Socket::INET->new(PeerHost => '127.0.0.1', PeerPort => $ARGV[0], Proto => 'tcp'); sleep 1; } my $got_info = 0; my $try = 0; while($got_info == 0) { eval { local $SIG{ALRM} = sub { die; }; alarm 3; if($try >= 10) { exit 1; } $try++; print $sock "GET /network/devices\n"; my $line = <$sock>; $line = <$sock>; $line =~ s/[\r\n]//gms; if($line =~ m/^\{"networkdevices/) { print "$line\n"; $got_info++; } alarm 0; }; } |; $fh->open( ">", $rnd_file ); $fh->write($content); $fh->close; my $exec = Rex::Interface::Exec->create(); my ($data) = $exec->exec("perl $rnd_file $info->{has_kvm_agent_on_port}"); if ( $? == 1 ) { # can't connect to agent $info->{has_kvm_agent_on_port} = 0; last; } my $ref = decode_json($data); delete $ref->{networkconfiguration}->{lo}; for my $net ( keys %{ $ref->{networkconfiguration} } ) { push @ifaces, { device => $net, %{ $ref->{networkconfiguration}->{$net} }, }; } if ( $ifaces[0]->{ip} ) { $got_ip++; } else { @ifaces = (); sleep 1; } } } if ( !$info->{has_kvm_agent_on_port} ) { my $ifs = Rex::Virtualization::LibVirt::iflist->execute($vmname); my $got_ip = 0; # search arp in different locations. # sometimes there is not PATH for normal users to /sbin commands. # and also arp can be in different locations. my $command = Rex::Commands::Run::can_run( "/sbin/arp", "/usr/sbin/arp", "arp" ); while ( $got_ip < scalar( keys %{$ifs} ) ) { my %arp = map { my @x = ( $_ =~ m/\(([^\)]+)\) at ([^\s]+)\s/ ); ( $x[1], $x[0] ) } i_run "$command -an", fail_ok => 1; for my $if ( keys %{$ifs} ) { if ( exists $arp{ $ifs->{$if}->{mac} } && $arp{ $ifs->{$if}->{mac} } ) { $got_ip++; push @ifaces, { device => $if, ip => $arp{ $ifs->{$if}->{mac} }, %{ $ifs->{$if} } }; } } sleep 1; } } return { network => \@ifaces, }; } 1; Rex-1.16.1/lib/Rex/Virtualization/LibVirt/shutdown.pm0000644000175000017500000000131115032177050021470 0ustar00ferkiferki# # (c) Jan Gehring # package Rex::Virtualization::LibVirt::shutdown; use v5.14.4; use warnings; our $VERSION = '1.16.1'; # VERSION use Rex::Logger; use Rex::Helper::Run; sub execute { my ( $class, $arg1, %opt ) = @_; my $virt_settings = Rex::Config->get("virtualization"); chomp( my $uri = ref($virt_settings) ? $virt_settings->{connect} : i_run "virsh uri" ); unless ($arg1) { die("You have to define the vm name!"); } my $dom = $arg1; Rex::Logger::debug("shutdown domain: $dom"); unless ($dom) { die("VM $dom not found."); } i_run "virsh -c $uri shutdown '$dom'", fail_ok => 1; if ( $? != 0 ) { die("Error shutdown vm $dom"); } } 1; Rex-1.16.1/lib/Rex/Virtualization/LibVirt/dumpxml.pm0000644000175000017500000000136415032177050021313 0ustar00ferkiferki# # (c) Jan Gehring # package Rex::Virtualization::LibVirt::dumpxml; use v5.14.4; use warnings; our $VERSION = '1.16.1'; # VERSION use Rex::Logger; use Rex::Helper::Run; use XML::Simple; use Data::Dumper; sub execute { my ( $class, $vmname ) = @_; my $virt_settings = Rex::Config->get("virtualization"); chomp( my $uri = ref($virt_settings) ? $virt_settings->{connect} : i_run "virsh uri" ); unless ($vmname) { die("You have to define the vm name!"); } Rex::Logger::debug("Getting dumpxml of domain: $vmname"); my $xml; my $dumpxml = i_run "virsh -c $uri dumpxml '$vmname'", fail_ok => 1; if ( $? != 0 ) { die("Error running virsh dumpxml '$vmname'"); } return XMLin($dumpxml); } 1; Rex-1.16.1/lib/Rex/Virtualization/LibVirt/destroy.pm0000644000175000017500000000152415032177050021314 0ustar00ferkiferki# # (c) Jan Gehring # package Rex::Virtualization::LibVirt::destroy; use v5.14.4; use warnings; our $VERSION = '1.16.1'; # VERSION use Rex::Logger; use Rex::Helper::Run; sub execute { my ( $class, $arg1, %opt ) = @_; my $virt_settings = Rex::Config->get("virtualization"); chomp( my $uri = ref($virt_settings) ? $virt_settings->{connect} : i_run "virsh uri" ); unless ($arg1) { die("You have to define the vm name!"); } my $dom = $arg1; Rex::Logger::debug("destroying domain: $dom"); unless ($dom) { die("VM $dom not found."); } # virsh must distinguish between a not-running VM and failed to destroy via exit code! my $out = i_run "virsh -c $uri destroy '$dom' 2>&1", fail_ok => 1; if ( $? != 0 && $out !~ /domain is not running/ ) { die("Error destroying vm $dom"); } } 1; Rex-1.16.1/lib/Rex/Virtualization/LibVirt/blklist.pm0000644000175000017500000000272015032177050021266 0ustar00ferkiferki# # (c) Jan Gehring # package Rex::Virtualization::LibVirt::blklist; use v5.14.4; use warnings; our $VERSION = '1.16.1'; # VERSION use Rex::Logger; use Rex::Helper::Run; use Data::Dumper; sub execute { shift; my $vmname = shift; my %options = @_; my $virt_settings = Rex::Config->get("virtualization"); chomp( my $uri = ref($virt_settings) ? $virt_settings->{connect} : i_run "virsh uri" ); unless ($vmname) { die("You have to define the vm name!"); } Rex::Logger::debug("Getting block list of domain: $vmname"); my @blklist = i_run "virsh -c $uri domblklist '$vmname' --details", fail_ok => 1; if ( $? != 0 ) { die("Error running virsh domblklist '$vmname'"); } my %ret = (); my ( $k, $v ); shift @blklist; shift @blklist; for my $line (@blklist) { my ( $type, $device, $target, $source ) = split( /\s+/, $line ); $ret{$target} = { type => $type, device => $device, source => $source }; } if (%options) { if ( $options{details} ) { my $unit = $options{unit} || 1; for my $target ( keys %ret ) { my @infos = i_run "virsh -c $uri domblkinfo '$vmname' '$target' 2>/dev/null", fail_ok => 1; if ( $? == 0 ) { for my $line (@infos) { my ( $k, $v ) = split( /:\s+/, $line ); $ret{$target}->{$k} = $v / $unit; } } } } } return \%ret; } 1; Rex-1.16.1/lib/Rex/Virtualization/LibVirt/status.pm0000644000175000017500000000062715032177050021151 0ustar00ferkiferki# # (c) Jan Gehring # package Rex::Virtualization::LibVirt::status; use v5.14.4; use warnings; our $VERSION = '1.16.1'; # VERSION use Rex::Virtualization::LibVirt::info; sub execute { my ( $class, $arg1, %opt ) = @_; my $info = Rex::Virtualization::LibVirt::info->execute($arg1); if ( $info->{State} eq "shut off" ) { return "stopped"; } return "running"; } 1; Rex-1.16.1/lib/Rex/Virtualization/LibVirt/reboot.pm0000644000175000017500000000130715032177050021114 0ustar00ferkiferki# # (c) Jan Gehring # package Rex::Virtualization::LibVirt::reboot; use v5.14.4; use warnings; our $VERSION = '1.16.1'; # VERSION use Rex::Logger; use Rex::Helper::Run; sub execute { my ( $class, $arg1, %opt ) = @_; my $virt_settings = Rex::Config->get("virtualization"); chomp( my $uri = ref($virt_settings) ? $virt_settings->{connect} : i_run "virsh uri" ); unless ($arg1) { die("You have to define the vm name!"); } my $dom = $arg1; Rex::Logger::debug("rebooting domain: $dom"); unless ($dom) { die("VM $dom not found."); } i_run "virsh -c $uri reboot '$dom'", fail_ok => 1; if ( $? != 0 ) { die("Error rebooting vm $dom"); } } 1; Rex-1.16.1/lib/Rex/Virtualization/LibVirt/option.pm0000644000175000017500000000176115032177050021136 0ustar00ferkiferki# # (c) Jan Gehring # package Rex::Virtualization::LibVirt::option; use v5.14.4; use warnings; our $VERSION = '1.16.1'; # VERSION use Rex::Logger; use Rex::Helper::Run; my $FUNC_MAP = { max_memory => "setmaxmem", memory => "setmem", }; sub execute { my ( $class, $arg1, %opt ) = @_; my $virt_settings = Rex::Config->get("virtualization"); chomp( my $uri = ref($virt_settings) ? $virt_settings->{connect} : i_run "virsh uri" ); unless ($arg1) { die("You have to define the vm name!"); } my $dom = $arg1; Rex::Logger::debug("setting some options for: $dom"); for my $opt ( keys %opt ) { my $val = $opt{$opt}; unless ( exists $FUNC_MAP->{$opt} ) { Rex::Logger::info("$opt can't be set right now."); next; } my $func = $FUNC_MAP->{$opt}; i_run "virsh -c $uri $func '$dom' '$val'", fail_ok => 1; if ( $? != 0 ) { Rex::Logger::info( "Error setting $opt to $val on $dom ($@)", "warn" ); } } } 1; Rex-1.16.1/lib/Rex/Virtualization/LibVirt/import.pm0000644000175000017500000000610215032177050021132 0ustar00ferkiferki# # (c) Jan Gehring # package Rex::Virtualization::LibVirt::import; use v5.14.4; use warnings; our $VERSION = '1.16.1'; # VERSION use Rex::Logger; use Rex::Helper::Run; use Rex::Commands::Fs; use File::Basename; use Rex::Virtualization::LibVirt::create; use Data::Dumper; # # %opt = (cpus => 2, memory => 512) # sub execute { my ( $class, $arg1, %opt ) = @_; unless ($arg1) { die("You have to define the vm name!"); } my $dom = $arg1; Rex::Logger::debug( "importing: $dom -> " . $opt{file} ); my $cwd = i_run "pwd"; chomp $cwd; my $dir = dirname $opt{file}; my ( undef, undef, $suffix ) = fileparse( $opt{file}, qr{\.[^.]*} ); $opt{storage_path} = $cwd . '/storage' unless ( $opt{storage_path} ); my $file = $opt{storage_path} . '/' . $dom . $suffix; i_run "mkdir -p $opt{storage_path}"; my $format = "qcow2"; my @serial_devices; if ( exists $opt{serial_devices} ) { @serial_devices = @{ $opt{serial_devices} }; } if ( $opt{file} =~ m/\.ova$/ ) { Rex::Logger::debug("Importing ova file. Try to convert with qemu-img"); $file =~ s/\.[a-z]+$//; my @vmdk = grep { m/\.vmdk$/ } i_run "tar -C '$dir' -vxf '$opt{file}'"; Rex::Logger::debug("converting '$cwd/tmp/$vmdk[0]' -> '$file.qcow2'"); i_run "qemu-img convert -O qcow2 '$cwd/tmp/$vmdk[0]' '$file.qcow2'", fail_ok => 1; if ( $? != 0 ) { Rex::Logger::info( "Can't import and convert $opt{file}. You qemu-img version seems not " . " to support this format.", "warn" ); die("Error importing VM $opt{file}"); } $file = "$file.qcow2"; } else { Rex::Logger::debug("Importing kvm compatible file."); Rex::Logger::debug("Copying $opt{file} -> $file"); cp $opt{file}, $file; if ( $file =~ m/\.gz$/ ) { Rex::Logger::debug("Extracting gzip'ed file $file"); i_run "gunzip -q -f '$file'"; $file =~ s/\.gz$//; } } my ($format_out) = grep { m/^file format:/ } i_run "qemu-img info '$file'", fail_ok => 1; if ( $format_out =~ m/^file format: (.*)$/i ) { $format = $1; } my @network = values %{ $opt{__network} }; if ( scalar @network == 0 ) { # create default network push @network, { type => "network", network => "default", }; } for (@network) { $_->{type} ||= "network"; $_->{type} = "bridge" if ( $_->{type} && $_->{type} eq "bridged" ); $_->{type} = "network" if ( $_->{type} eq "nat" ); if ( $_->{type} eq "network" && !exists $_->{network} ) { $_->{network} = "default"; } } Rex::Virtualization::LibVirt::create->execute( $dom, storage => [ { file => "$file", dev => "vda", driver_type => $format, }, ], network => \@network, serial_devices => \@serial_devices, memory => $opt{memory}, cpus => $opt{cpus}, ); if ( exists $opt{__forward_port} ) { # currently not supported Rex::Logger::info( "Port-forwarding is currently not supported for KVM boxes.", "warn" ); } } 1; Rex-1.16.1/lib/Rex/Virtualization/LibVirt/iflist.pm0000644000175000017500000000161015032177050021111 0ustar00ferkiferki# # (c) Jan Gehring # package Rex::Virtualization::LibVirt::iflist; use v5.14.4; use warnings; our $VERSION = '1.16.1'; # VERSION use Rex::Logger; use Data::Dumper; use Rex::Virtualization::LibVirt::dumpxml; sub execute { shift; my $vmname = shift; my %options = @_; unless ($vmname) { die("You have to define the vm name!"); } my $ref = Rex::Virtualization::LibVirt::dumpxml->execute($vmname); my $interfaces = $ref->{devices}->{interface}; if ( ref $interfaces ne "ARRAY" ) { $interfaces = [$interfaces]; } my %ret = (); my $iface_num = 0; for my $iface ( @{$interfaces} ) { $ret{"vnet$iface_num"} = { type => $iface->{model}->{type}, source => $iface->{source}->{network}, model => $iface->{model}->{type}, mac => $iface->{mac}->{address}, }; $iface_num++; } return \%ret; } 1; Rex-1.16.1/lib/Rex/Virtualization/LibVirt/delete.pm0000644000175000017500000000130315032177050021060 0ustar00ferkiferki# # (c) Jan Gehring # package Rex::Virtualization::LibVirt::delete; use v5.14.4; use warnings; our $VERSION = '1.16.1'; # VERSION use Rex::Logger; use Rex::Helper::Run; sub execute { my ( $class, $arg1 ) = @_; my $virt_settings = Rex::Config->get("virtualization"); chomp( my $uri = ref($virt_settings) ? $virt_settings->{connect} : i_run "virsh uri" ); unless ($arg1) { die("You have to define the vm name!"); } my $dom = $arg1; Rex::Logger::debug("deleting domain: $dom"); unless ($dom) { die("VM $dom not found."); } i_run "virsh -c $uri undefine '$dom'", fail_ok => 1; if ( $? != 0 ) { die("Error destroying vm $dom"); } } 1; Rex-1.16.1/lib/Rex/Virtualization/LibVirt/create.pm0000644000175000017500000003534215032177050021073 0ustar00ferkiferki# # (c) Jan Gehring # package Rex::Virtualization::LibVirt::create; use v5.14.4; use warnings; our $VERSION = '1.16.1'; # VERSION use Rex::Logger; use Rex::Commands::Gather; use Rex::Hardware; use Rex::Commands::Fs; use Rex::Commands::Run; use Rex::Helper::Run; use Rex::Commands::File; use Rex::File::Parser::Data; use Rex::Template; use Rex::Helper::Path; use XML::Simple; use Rex::Virtualization::LibVirt::hypervisor; use Data::Dumper; my $QEMU_IMG; if ( can_run("qemu-img") ) { $QEMU_IMG = "qemu-img"; } elsif ( can_run("qemu-img-xen") ) { $QEMU_IMG = "qemu-img-xen"; } # read __DATA__ into an array my @data = ; sub execute { my ( $class, $name, %opt ) = @_; my $virt_settings = Rex::Config->get("virtualization"); chomp( my $uri = ref($virt_settings) ? $virt_settings->{connect} : i_run "virsh uri" ); my $opts = \%opt; $opts->{"name"} = $name; unless ($opts) { die("You have to define the create options!"); } ## detect the hypervisor caps my $hypervisor = Rex::Virtualization::LibVirt::hypervisor->execute('capabilities'); my $virt_type = "unknown"; _set_defaults( $opts, $hypervisor ); if ( exists $hypervisor->{"kvm"} ) { $virt_type = "kvm"; } elsif ( exists $hypervisor->{"xen"} ) { $virt_type = "xen-" . $opts->{"type"}; } else { die("Hypervisor not supported."); } my $fp = Rex::File::Parser::Data->new( data => \@data ); my $create_xml = $fp->read("create-${virt_type}.xml"); my $template = Rex::Template->new; my $parsed_template = $template->parse( $create_xml, $opts ); Rex::Logger::debug($parsed_template); ## create storage devices for ( @{ $opts->{'storage'} } ) { if ( !exists $_->{"template"} && $_->{"size"} && $_->{"type"} eq "file" ) { my $size = $_->{'size'}; if ( !is_file( $_->{"file"} ) ) { Rex::Logger::debug("creating storage disk: \"$_->{file}\""); i_run "$QEMU_IMG create -f $_->{driver_type} '$_->{file}' $size", fail_ok => 1; if ( $? != 0 ) { die("Error creating storage disk: $_->{'file'}"); } } else { Rex::Logger::info("$_->{file} already exists. Using this."); } } elsif ( $_->{'template'} && $_->{'type'} eq "file" ) { Rex::Logger::info( "building domain: \"$opts->{'name'}\" from template: \"$_->{'template'}\"" ); Rex::Logger::info("Please wait ..."); i_run "$QEMU_IMG convert -f raw '$_->{template}' -O '$_->{driver_type}' '$_->{file}'", fail_ok => 1; if ( $? != 0 ) { die( "Error building domain: \"$opts->{'name'}\" from template: \"$_->{'template'}\"\n Template doesn't exist or the qemu-img binary is missing" ); } } else { Rex::Logger::info("$_->{file} already exists. Using this."); } } Rex::Logger::info("Creating domain: \"$opts->{'name'}\""); $parsed_template =~ s/[\n\r]//gms; my $file_name = get_tmp_file; file "$file_name", content => $parsed_template; i_run "virsh -c $uri define '$file_name'", fail_ok => 1; if ( $? != 0 ) { die("Error defining vm $opts->{name}"); } unlink($file_name); return; } sub _set_defaults { my ( $opts, $hyper ) = @_; if ( !exists $opts->{"name"} ) { die("You have to give a name."); } if ( !exists $opts->{"storage"} ) { die("You have to add at least one storage disk."); } if ( !exists $opts->{"type"} ) { if ( exists $opts->{"os"} && exists $opts->{"os"}->{"kernel"} && !exists $hyper->{"kvm"} ) { $opts->{"type"} = "pvm"; } else { $opts->{"type"} = "hvm"; } } if ( !$opts->{"memory"} ) { $opts->{"memory"} = 512 * 1024; } if ( !$opts->{"cpus"} ) { $opts->{"cpus"} = 1; } if ( !exists $opts->{"clock"} ) { $opts->{"clock"} = "utc"; } if ( !exists $opts->{"arch"} ) { if ( exists $hyper->{"x86_64"} ) { $opts->{"arch"} = "x86_64"; } else { $opts->{"arch"} = "i686"; } } if ( !exists $opts->{"boot"} ) { $opts->{"boot"} = "hd"; } if ( !exists $opts->{"emulator"} ) { $opts->{"emulator"} = $hyper->{"emulator"}; if ( operating_system_is("Debian") && exists $hyper->{"xen"} ) { # fix for debian, because virsh capabilities don't give the correct # emulator. $opts->{"emulator"} = "/usr/lib/xen-4.0/bin/qemu-dm"; } } if ( exists $hyper->{"loader"} && !exists $opts->{"loader"} ) { $opts->{"loader"} = $hyper->{"loader"}; } if ( !exists $opts->{"on_poweroff"} ) { $opts->{"on_poweroff"} = "destroy"; } if ( !exists $opts->{"on_reboot"} ) { $opts->{"on_reboot"} = "restart"; } if ( !exists $opts->{"on_crash"} ) { $opts->{"on_crash"} = "restart"; } if ( exists $hyper->{"xen"} && $opts->{"type"} eq "pvm" ) { if ( !exists $opts->{"os"}->{"type"} ) { $opts->{"os"}->{"type"} = "linux"; } if ( !exists $opts->{"os"}->{"kernel"} ) { my %hw = Rex::Hardware->get(qw/ Kernel /); if ( is_redhat() ) { $opts->{"os"}->{"kernel"} = "/boot/vmlinuz-" . $hw{"Kernel"}->{"kernelrelease"}; } else { $opts->{"os"}->{"kernel"} = "/boot/vmlinuz-" . $hw{"Kernel"}->{"kernelrelease"}; } } if ( !exists $opts->{"os"}->{"initrd"} ) { my %hw = Rex::Hardware->get(qw/ Kernel /); if ( is_redhat() ) { $opts->{"os"}->{"initrd"} = "/boot/initrd-" . $hw{"Kernel"}->{"kernelrelease"} . ".img"; } else { $opts->{"os"}->{"initrd"} = "/boot/initrd.img-" . $hw{"Kernel"}->{"kernelrelease"}; } } if ( !exists $opts->{"os"}->{"cmdline"} ) { my @root_store = grep { $_->{"is_root"} && $_->{"is_root"} == 1 } @{ $opts->{"storage"} }; $opts->{"os"}->{"cmdline"} = "root=/dev/" . $root_store[0]->{"dev"} . " ro"; } } _set_storage_defaults( $opts, $hyper ); _set_network_defaults( $opts, $hyper ); } sub _set_storage_defaults { my ( $opts, $hyper ) = @_; my $store_letter = "a"; for my $store ( @{ $opts->{"storage"} } ) { if ( !exists $store->{"type"} ) { $store->{"type"} = "file"; } if ( !exists $store->{"driver_type"} ) { $store->{"driver_type"} = "raw"; } if ( !exists $store->{"size"} && $store->{"type"} eq "file" ) { if ( $store->{"file"} =~ m/swap/ ) { $store->{"size"} = "1G"; } else { $store->{"size"} = "10G"; } } if ( exists $store->{"file"} && $store->{"file"} =~ m/\.iso$/ && !exists $store->{"device"} ) { $store->{"device"} = "cdrom"; } if ( !exists $store->{"device"} ) { $store->{"device"} = "disk"; } if ( !exists $store->{"dev"} && $store->{"device"} eq "cdrom" ) { $store->{"dev"} = "hdc"; } if ( !exists $store->{"dev"} ) { if ( exists $hyper->{"kvm"} ) { $store->{"dev"} = "vd${store_letter}"; } else { $store->{"dev"} = "hd${store_letter}"; } } if ( !exists $store->{"bus"} ) { if ( exists $hyper->{"kvm"} && $store->{"device"} eq "disk" ) { $store->{"bus"} = "virtio"; } else { $store->{"bus"} = "ide"; } } if ( exists $hyper->{"kvm"} ) { if ( $store->{"bus"} eq "virtio" && !exists $store->{"address"} ) { $store->{"address"} = { type => "pci", domain => "0x0000", bus => "0x00", slot => "0x05", function => "0x0", }; } elsif ( $store->{"bus"} eq "ide" && !exists $store->{"address"} ) { $store->{"address"} = { type => "drive", controller => 0, bus => 1, unit => 0, }; } } if ( $store->{"device"} eq "cdrom" ) { $store->{"readonly"} = 1; } if ( is_redhat() ) { if ( !exists $store->{"aio"} ) { $store->{"aio"} = 1; } } $store_letter++; } } sub _set_network_defaults { my ( $opts, $hyper ) = @_; if ( !exists $opts->{"network"} ) { $opts->{"network"} = [ { type => "bridge", bridge => "virbr0", }, ]; } my $slot = 10; for my $netdev ( @{ $opts->{"network"} } ) { if ( !exists $netdev->{"type"} ) { $netdev->{"type"} = "bridge"; } if ( !exists $netdev->{"bridge"} ) { $netdev->{"bridge"} = "virbr0"; } if ( exists $hyper->{"kvm"} ) { if ( !exists $netdev->{"model"} ) { $netdev->{"model"} = "virtio"; } if ( !exists $netdev->{"address"} ) { $netdev->{"address"} = { type => "pci", domain => "0x0000", bus => "0x00", slot => "0x" . sprintf( '%02i', $slot ), function => "0x0", }; $slot++; } } } } 1; __DATA__ @create-kvm.xml <%= $::name %> <%= $::memory %> <%= $::memory %> <% if(exists $::cpu->{mode}) { %> <% } %> <%= $::cpus %> hvm <%= $::on_poweroff %> <%= $::on_reboot %> <%= $::on_crash %> <%= $::emulator %> <% for my $disk (@{$::storage}) { %> <% if ($disk->{type} eq "file") { %> <% } elsif ($disk->{file} eq "block") { %> <% } %> <% if(exists $disk->{readonly}) { %> <% } %>
{address}}) { %> <%= $key %>="<%= $disk->{address}->{$key} %>" <% } %> /> <% } %>
<% for my $netdev (@{$::network}) { %> <% if(exists $netdev->{mac}) { %> <% } %> <% if($netdev->{type} =~ m/^bridge/) { %> <% } elsif($netdev->{type} eq "network") { %> <% } %>
{address}}) { %> <%= $key %>="<%= $netdev->{address}->{$key} %>" <% } %> /> <% } %> <% my $serial_i = 1; %> <% for my $serial (@{ $serial_devices }) { %> <% if($serial->{type} eq "tcp") { %> <% } %> <% $serial_i++; %> <% } %>