Is it possible to include common-tests in serverspec? - ruby

I'm using serverspec to carry out remote testing of servers.
I've a number of different tests, which all work fine:
`-- spec
|-- builder.example.org.uk
\ host_spec.rb
|-- chat.example.org.uk
\ host_spec.rb
|-- docker.example.org.uk
\ host_spec.rb
\-- git.example.org.uk
\ host_spec.rb
However each host-test has a lot of duplication, because I want to ensure that each host has sshd running, for example.
I've tried several different ways of creating spec/common_tests.rb but each time fails. For example adding spec/common.rb:
describe command("lsb_release -d") do
its(:stdout) { should match /wheezy/ }
end
Then in spec/chat.example.org.uk/host_spec.rb:
require 'common'
However this seems to suddenly want to connect to a different host, and fail:
shelob ~ $ bundle exec rake spec:ssh.example.org.uk
/usr/bin/ruby1.9.1 -S rspec spec/ssh.example.org.uk/host_spec.rb
F.....................
Failures:
1) Command "lsb_release -d" stdout
On host `ssh.example.org.uk`
Failure/Error: Unable to find matching line from backtrace
SocketError: getaddrinfo: Name or service not known
So my question is twofold:
Is it possible to include common tests from an external file?
If so how do I accomplish this?

I'm not sure whether your example has a typo, as it seems to do exactly what you want. You're running bundle exec rake spec:ssh.example.org.uk and it's running against ssh.example.org.uk.
The serverspec documentation suggests another way to run shared specs. Instead of organising your files by host, you should organise them by role. For instance:
`-- spec
|-- app
| `-- ruby_spec.rb
|-- base
| `-- users_and_groups_spec.rb
|-- db
| `-- mysql_spec.rb
|-- proxy
| `-- nginx_spec.rb
`-- spec_helper.rb
Then, in your Rakefile, you map your hosts to roles:
hosts = [{name: 'www.example.org.uk', roles: %w(base app)},
{name: 'db.example.org.uk', roles: %w(base db)}]
You can then provide a ServerSpecTask that runs commands by setting the host address as an environment variable, by overriding RSpec's spec_command method:
class ServerspecTask < RSpec::Core::RakeTask
attr_accessor :target
def spec_command
cmd = super
"env TARGET_HOST=#{target} #{cmd}"
end
end
namespace :serverspec do
hosts.each do |host|
desc "Run serverspec to #{host[:name]}"
ServerspecTask.new(host[:name].to_sym) do |t|
t.target = host[:name]
t.pattern = 'spec/{' + host[:roles].join(',') + '}/*_spec.rb'
end
end
end
And then finally, update your spec_helper.rb to read that environment variable and use it as the host:
RSpec.configure do |c|
c.host = ENV['TARGET_HOST']
options = Net::SSH::Config.for(c.host)
user = options[:user] || Etc.getlogin
c.ssh = Net::SSH.start(c.host, user, options)
c.os = backend.check_os
end

Related

Why isn't "srb tc" finding the "expect" and "eq" methods for my RSpec tests?

I'm trying out Sorbet in an experimental open-source project (ruby_crystal_codemod). I can't figure out how to get the type-checking to work with some RSpec tests in a nested test project. When I run srb tc, I am seeing some type-checking errors like this:
spec/src/example_class_annotated_spec.rb:6: Method it does not exist on T.class_of(<root>) https://srb.help/7003
6 | it 'should add #foo and #bar' do
7 | instance = ExampleClass.new(2, 3, 4)
8 | expect(instance.add).to eq 5
9 | end
spec/src/example_class_annotated_spec.rb:8: Method expect does not exist on T.class_of(<root>) https://srb.help/7003
8 | expect(instance.add).to eq 5
^^^^^^^^^^^^^^^^^^^^
https://github.com/sorbet/sorbet/tree/67cd17f5168252fdec1ad04839b31fdda8bc6155/rbi/core/kernel.rbi#L2662: Did you mean: Kernel#exec?
2662 | def exec(*args); end
^^^^^^^^^^^^^^^
spec/src/example_class_annotated_spec.rb:8: Method eq does not exist on T.class_of(<root>) https://srb.help/7003
8 | expect(instance.add).to eq 5
^^^^
# etc.
Here is the source directory for the nested project on GitHub.
spec/src/example_class_annotated_spec.rb
spec/spec_helper.rb
src/example_class_annotated.rb
You should be able to run the following commands to reproduce the type error:
cd /tmp
git clone https://github.com/DocSpring/ruby_crystal_codemod.git
cd ruby_crystal_codemod
git checkout sorbet-rspec-type-checking-error
cd spec/fixtures/rspec_project/
bundle install
bundle exec srb tc
You should see these type errors:
spec/src/example_class_annotated_spec.rb:6: Method it does not exist on T.class_of(<root>) https://srb.help/7003
6 | it 'should add #foo and #bar' do
7 | instance = ExampleClass.new(2, 3, 4)
8 | expect(instance.add).to eq 5
9 | end
# etc.
Is there something wrong with the RBI files at spec/fixtures/rspec_project/sorbet/rbi/gems/rspec-core.rbi, etc.?
Maybe using tapioca to generate the type definitions would help https://github.com/Shopify/tapioca

Questions about Pipeline check out code from Git

My pipeline includes two steps: first is to check code from git and another is the do the build.
See my pipeline as below:
stages {
stage('Sync code from GitLab'){
steps {
echo "Step 1: Sync code"
ws(dir: '/home/tOmMy/jks_node/workspace/test') {
checkout([$class: 'GitSCM', branches: [[name: '*/release_br']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'xxx', url: 'yyy']]])
sh '''
pwd
ls -lt
'''
}
}
}
stage('Build'){
steps {
echo "Step 2: Build"
...
}
}
}
However, when the Jenkins checkout code from git, I saw many directories:
[tOmMy#ubuntu workspace]$ tree
.
|-- xx
| `-- yy
| |-- zz
| |-- zz#2
| |-- zz#2#tmp
| `-- zz#tmp
|-- test
|-- test#2
|-- test#2#tmp
`-- test#tmp
Where xx, yy, zz is the folder and job name in my Jenkins' server.
The questions are:
1. which folder does the Jenkins checkout the source code? I compare test, test#2, xx/yy/zz, xx/yy/zz2, all of them have the same files. Why does jenkins check out code to such many places?
How can I get the actual Jenkins' checkout found in stage('Build')?
Usually a declarative pipeline(if you are reading it from a repository) already check out the code itself in the "Checkout SCM" inside the job/build workspace folder.
If you allocate in another workspace (command ws) and check the code again you have 2 versions.
You should only do the build the checkout is already done in normal pipelines (if you use the jenkinsfile from a repo)

Explode a single file script into a project with proper directory layout

The problem
Suppose that I have written a lengthy script
in some language "lang",
and now want to convert this single-file script
into a directory tree with a project consisting of many files. I want to insert some kind of separators and file-paths into this file, and process it in some way so that in the end I obtain:
a proper project directory layout (sth. like this),
build-definition file,
readme's,
separate subdirectories for main/src and test/src etc.
For example, given the following script (pseudocode):
// required dependencies, should be moved
// into the build definition build.foo
require "org.foo" % "foo-core" % "1.2.3"
require "org.bar" % "bar-gui" % "3.2.1"
// A longer comment that should be converted
// into a text file and moved into a 'notes'
// subdirectory
/*
#README
Another lengthy comment that should go into
a readme.md
*/
/** A class that should
* go to src/main/lang/proj/A.lang
*/
class A {
def a = "foo"
}
/** Another class
* that should go to src/main/lang/proj/B.lang
*/
class B {
def b = "bar"
}
/** Some tests,
* should end up in
* src/test/lang/proj/MyTest.lang
#Test def testFoo() {
assert(2 + 2 == 5)
}
and assuming that I can insert arbitrary separators, commands, escape-sequences and file paths into this file, I would like to obtain the following project:
project/
|-- build.txt
|-- notes
| `-- note_01.txt
|-- readme.md
`-- src
|-- main
| `-- lang
| `-- proj
| |-- A.lang
| `-- B.lang
`-- test
`-- lang
`-- proj
`-- MySpec.lang
Edit:
What follows is a less-sophisticated version of my own answer below
What I've tried
Here is one naive way to do it:
Convert the original script into a bash script by prepending #!/bin/bash
split the source code into HEREDOCS
insert package declarations where necessary
add bunch of mkdir -p and cd between the HEREDOC-pieces
cat the HEREDOC pieces into appropriately named files
test the script on empty directories until it works as expected
For the above script, it might look somehow like this:
#!/bin/bash
mkdir project
cd project
cat <<'EOF' > build.txt
// required dependencies, should be moved
// into the build definition build.foo
require "org.foo" % "foo-core" % "1.2.3"
require "org.bar" % "bar-gui" % "3.2.1"
EOF
mkdir notes
cd notes
cat <<'EOF' > note_01.txt
// A longer comment that should be converted
// into a text file and moved into a 'notes'
// subdirectory
EOF
cd ..
cat <<'EOF' > readme.md
/*
#README
Another lengthy comment that should go into
a readme.md
*/
EOF
mkdir -p src/main/lang/proj
cd src/main/lang/proj
cat <<'EOF' > A.lang
package proj
/** A class
* that should go to src/main/lang/proj/A.lang
*/
class A {
def a = "foo"
}
EOF
cat <<'EOF' > B.lang
package proj
/** Another class
* that should go to src/main/lang/proj/B.lang
*/
class B {
def b = "bar"
}
EOF
cd ../../..
mkdir -p test/lang/proj
cd test/lang/proj
cat <<'EOF' > MySpec.lang
package proj
/** Some tests,
* should end up in
* src/test/lang/proj/MyTest.lang
#Test def testFoo() {
// this should end up in test
assert(2 + 2 == 5)
}
EOF
cd ../../..
What's wrong with this approach
It does generate the correct tree, but this approach seems rather error-prone:
it's too easy to cd ../../.. to the wrong nesting level
too easy to mkdir with a wrong name, and then fail to cd into it.
There is no way to handle the entire tree construction as a single
transaction, that is, if something fails later in the script,
there is no simple way to clean up the mess generated before
the error occurred.
I certainly could try to make it a bit less brittle by defining
special functions that mkdir and cd in one go, and
then wrap invocations of those functions together with cats into
(mkdirAndCd d ; cat) etc.
But it just doesn't feel quite right. Isn't there a much simpler
way to do it? Could one somehow combine the standard bash/linux utilities
into a tiny & very restricted domain specific language for
generating directory trees with text files? Maybe some newer version of split where one could specify where to split and where to put the pieces?
Related questions:
mkdir and touch in single command
The reverse of tree - reconstruct file and directory structure from text file contents?
Other interesting proposals that don't seem to work:
Use tar. That would mean that one would have to convert the text file manually into a valid tar-archive. While a tar archive indeed is a single plain-text file, its internal format does not look like the most comfortable DSL for such a simple task. It was never intended to be used by humans directly in that way.
Similar argument holds for shar. Since shar uses the bash itself to extract the archive, my above proposal is, in principle, a manually generated shar-archive in a very uncommon format, therefore shar seems to share all the drawbacks with the above proposal. I'd rather prefer something more restricted, that allows to do fewer things, but provides more guarantees about the quality of the outcome.
Maybe I should emphasize again that I don't have a tree to begin with, so there is nothing to compress. I have only the single script file and a rough idea of what the tree should look like in the end.
Seems to me that you are trying to write a custom parser. Provided that all blocks mentioned by you are ended by double line endings, this could help you
#!/bin/bash
gawk 'BEGIN{RS="\n\n([/][*]|[/]{2,2})"}
{
if ($0 ~ /#README/){
system("echo -e \"\nThis is a Readme.md\n--------\n" $0 "\"")
}else if ($0 ~ /class /){
system("echo -e \"\nThis is a class\n---------\n/*" $0 "\"")
}else if ($0 ~ /require /){
system("echo -e \"\nthis is a conf\n-----------\n" $0 "\"")
}else if($0 ~ /[/]{2,2}.*\n[/]{2,2}/){
system("echo -e \"\nthis is a note\n-----------\n" $0 "\"")
}
}' your_script.lang
The key part is the record separator RS that splits block of code that start with '\n\n//' or '\n\n/*'.
Instead of echo -e you could write custom scripts for each type of block.
Please note that the record separator will not be present on $0 so you have to add the missing characters, as in the /class / example above.
The output of the above code is
this is a conf
-----------
// required dependencies, should be moved
// into the build definition build.foo
require org.foo % foo-core % 1.2.3
require org.bar % bar-gui % 3.2.1
this is a note
-----------
A longer comment that should be converted
// into a text file and moved into a 'notes'
// subdirectory
This is a Readme.md
--------
#README
Another lengthy comment that should go into
a readme.md
*/
This is a class
---------
/** A class that should
* go to src/main/lang/proj/A.lang
*/
class A {
def a = foo
}
This is a class
---------
/** Another class
* that should go to src/main/lang/proj/B.lang
*/
class B {
def b = bar
}
About your concerns:
it's too easy to cd ../../.. to the wrong nesting level
-> define a variable with root path and cd to it.
too easy to mkdir with a wrong name, and then fail to cd into it.
-> define variables with directory names and check if they already exists.
path1=src/main/lang/some
if [ -d $path1 ]; then
do_something
fi
There is no way to handle the entire tree construction as a single transaction ...
-> write to file paths of every NEW directory/file that you create and use it to revert if necessary.
(my own answer)
Consider the following definition of a tiny embedded domain specific language for defining directory trees with text files:
#!/bin/bash
enter() {
local subdir="$1"
if [ ! -d "$subdir" ]
then
mkdir -p "$subdir"
fi
pushd "$subdir" > /dev/null
}
leave() {
popd > /dev/null
}
save() {
local fileName="$1"
cut -d'|' -f2- > "$fileName"
}
The enter command creates a directory if necessary, and cds into this directory, it works with arbitrary relative paths. The save command saves the text content of a here-document to file. The leave command changes to previous directory.
When a file is saved, the margin (empty space followed by '|') is stripped from each line. This is to ensure that the indentation of the script does not interfere with the indentation of the written files.
If these definitions are sourced, then the tree-generation script can be written as follows:
#!/bin/bash
source explode.sh
mkdir project
cd project
enter "src"
enter "main/lang/proj"
save "A.lang" <<'____EOF'
|package proj
|
|/** A totally useful class
| * that should go to src/main/lang/proj/A.lang
| */
|class A {
| def a = "foo"
|}
____EOF
save "B.lang" <<'____EOF'
|package proj
|/** Another very useful class
| * that should go to src/main/lang/proj/B.lang
| */
|class B {
| def b = "bar"
|}
____EOF
leave
enter "test/lang/proj"
save "MyTest.lang" <<'____EOF'
|package proj
|
|/** A test that should end up in
| * src/test/lang/proj/MyTest.lang
|#Test def testFoo() {
| assert(2 + 2 == 5)
|}
____EOF
leave
leave
save "build.txt" <<'EOF'
|require "org.foo" % "foo-core" % "1.2.3"
|require "org.bar" % "bar-gui" % "3.2.1"
EOF
enter "notes"
save "note_01.txt" <<'__EOF'
|A longer comment that should be converted
|into a text file and moved into a 'notes'
|subdirectory. This is a very long comment
|about the purpose of the project. Blah
|blah blah.
__EOF
leave
save "README.md" <<'EOF'
|#README
|
|This is a readme file for my awesome project.
|It ends with this line. Bye.
EOF
When executed, the script generates the following directory tree:
project/
├── build.txt
├── notes
│   └── note_01.txt
├── README.md
└── src
├── main
│   └── lang
│   └── proj
│   ├── A.lang
│   └── B.lang
└── test
└── lang
└── proj
└── MyTest.lang
The bash-script mirrors the tree structure very closely, and it's impossible to mess up the cd ../../../../../.. commands. It still lacks various desirable properties though (not transactional, no dry-run capability).

Custom fact with home directorys as domains for puppet

I'm trying to generate a custom fact called domains.
the idea is to list all the directories within /home but remove some default directory's such as centos, ec2-user, myadmin.
I'm using bash as I don't know ruby. so far my script outputs the list into a txt file which it then cats the answer for factors. but it is treated as one long answer and not multiple like an array?
My script is as follows:
#!/bin/bash
ls -m /home/ | sed -e 's/, /,/g' | tr -d '\n' > /tmp/domains.txt
cat /tmp/domains.txt | awk '{gsub("it_support,", "");print}'| awk '{gsub("ec2-user,", "");print}'| awk '{gsub("myadmin,", "");print}'| awk '{gsub("nginx", "");print}'| awk '{gsub("lost+found,", "");print}' > /tmp/domains1.txt
echo "domains={$(cat /tmp/domains1.txt)}"
exit
Foremans sees my domains as
facts.domains = "{domain1,domain2,domain3,domain4,lost+found,}"
I also need to remove lost+found, some how.
Any help or advice would be appreciated
Kevin
I'm also not familiar with ruby, but I have an idea for some workaround:
Please look at the following example about returning an array of network interfaces. Now to create domain_array fact use the following code:
Facter.add(:domain_array) do
setcode do
domains = Facter.value(:domains)
domain_array = domains.split(',')
domain_array
end
end
You can put a parser function to do this. Parser functions go inside:
modules/<modulename>/lib/puppet/parser/functions/getdomain.rb
Note: Parser function will compile only in the puppet master. See below for a custom fact that will run on the agent.
getdomain.rb can contain something like the following for your purpose:
module Puppet::Parser::Functions
newfunction(:getdomain, :type => :rvalue) do |args|
dnames=Array.new
Dir.foreach("/home/") do |d|
# Avoid listing directories starts with . or ..
if !d.start_with?('.') then
# You can put more names inside the [...] that you want to avoid
dnames.push(d) unless ['lost+found','centos'].include?(d)
end
end
domainlist=dnames.join(',')
return domainlist
end
end
You can call it from a manifest and assign to a variable:
$myhomedomains=getdomain()
$myhomedomains should return something similar to this : user1,user2,user3
.......
For a custom fact with similar code. You can put it in :
modules/<modulename>/lib/facter/getdomain.rb
Content of getdomain.rb :
Facter.add(:getdomain) do
setcode do
dnames=Array.new
Dir.foreach("/home/") do |d|
# Avoid listing directories starts with . or ..
if !d.start_with?('.') then
# You can put more names inside the [...] that you want to avoid
dnames.push(d) unless ['lost+found','centos'].include?(d)
end
end
getdomain=dnames.join(',')
getdomain
end
end
You can call the getdomain fact in any manifest, for example, calling it from the same module's init.pp :
notify { "$::getdomain" : }
will result in something similar :
Notice: /Stage[main]/Testmodule/Notify[user1,user2,user3]

How can I write spec tests for puppet facts?

I have the following rb script which generate puppet facts according to the packages installed and which seems to be working ok in my puppet environment:
begin
pack = Facter::Core::Execution.execute('rpm -qa | grep ^ts')
packages = pack.split("\n")
packagehash = Hash.new
packages.each do |f|
packagehash[f.split("-")[0]] = f.split("-")[1] + ("-") + f.split("-")[2].split(".")[0]
end
rescue
end
begin
unless packagehash.empty?
packagehash.each_pair do |k,v|
Facter.add("bs_rpm_#{k}") {
setcode { "#{v}" }
}
end
end
rescue
end
I wrote the following spec which runs a small dummy test to see if my rspec env in general is ok:
require 'spec_helper'
describe 'bs package spec' do
before do
Facter.fact(:kernel).stubs(:value).returns("windows")
end
it "should run windows" do
Facter.fact(:kernel).value.should == "windows"
end
it "should create new facts" do
Facter::Core::Execution.stubs(:execute).with('rpm -qa | grep ^ts').returns('ts3_hostt01-1.0.0-34.x86_64\n')
Facter.fact(:bs_rpm_ts3_hostt01).value.should == "1.0.0-34"
end
end
But then when running rake spec I get the following error:
[dan#kyvltvm00022 bs_master]$ rake spec
/home/dan/.rvm/rubies/ruby-2.1.0/bin/ruby -S rspec spec/unit/facter/bs_package_spec.rb --color
.F
Failures:
1) bs package spec should create new facts
Failure/Error: Facter::Core::Execution.stubs(:execute).with('rpm -qa | grep ^ts').returns('ts3_hostt01-1.0.0-34.x86_64\n')
NameError:
uninitialized constant Facter::Core
# ./spec/unit/facter/bs_package_spec.rb:13:in `block (2 levels) in <top (required)>'
Finished in 0.00692 seconds
2 examples, 1 failure
Failed examples:
rspec ./spec/unit/facter/bs_package_spec.rb:12 # bs package spec should create new facts
/home/dan/.rvm/rubies/ruby-2.1.0/bin/ruby -S rspec spec/unit/facter/bs_package_spec.rb --color failed
[dan#kyvltvm00022 bs_master]$ exit
shell returned 1
[dan#kyvltvm00022 bs_master]$
What am I doing wrong or might be missing that is not loading Facter::Core ?? My spec_helper looks like this:
[dan#kyvltvm00022 bs_master]$ cat spec/spec_helper.rb
dir = File.expand_path(File.dirname(__FILE__))
$LOAD_PATH.unshift File.join(dir, 'lib')
# Don't want puppet getting the command line arguments for rake or autotest
ARGV.clear
require 'puppet'
require 'facter'
require 'mocha'
gem 'rspec', '>=2.0.0'
require 'rspec/expectations'
require 'puppetlabs_spec_helper/puppet_spec_helper'
RSpec.configure do |config|
# FIXME REVISIT - We may want to delegate to Facter like we do in
# Puppet::PuppetSpecInitializer.initialize_via_testhelper(config) because
# this behavior is a duplication of the spec_helper in Facter.
config.before :each do
# Ensure that we don't accidentally cache facts and environment between
# test cases. This requires each example group to explicitly load the
# facts being exercised with something like
# Facter.collection.loader.load(:ipaddress)
Facter::Util::Loader.any_instance.stubs(:load_all)
Facter.clear
Facter.clear_messages
end
end
[dan#kyvltvm00022 bs_master]$
[UPDATE]
After checking my test system I noticed the facter gem was missing core so I updated the code and test as follows:
pack is now:
pack = Facter::Util::Resolution.exec('rpm -qa | grep ^ts')
and the stub in my test now is:
Facter::Util::Resolution.stubs(:exec).with('rpm -qa | grep ^ts').returns('ts3_hostt01-1.0.0-34.x86_64\n')
And the result now is this:
[dan#kyvltvm00022 bs_master]$ rake spec
/home/dan/.rvm/rubies/ruby-2.1.0/bin/ruby -S rspec spec/unit/facter/bs_package_spec.rb --color
.F
Failures:
1) bs package spec should create new facts
Failure/Error: Facter.fact(:bs_rpm_ts3_hostt01).value.should == "1.0.0-34"
NoMethodError:
undefined method `value' for nil:NilClass
# ./spec/unit/facter/bs_package_spec.rb:14:in `block (2 levels) in <top (required)>'
Finished in 0.00747 seconds
2 examples, 1 failure
Failed examples:
rspec ./spec/unit/facter/bs_package_spec.rb:12 # bs package spec should create new facts
/home/dan/.rvm/rubies/ruby-2.1.0/bin/ruby -S rspec spec/unit/facter/bs_package_spec.rb --color failed
[dan#kyvltvm00022 bs_master]$
What am i doing wrong in my test?
Looking through the facter code, this is the file you're requiring:
https://github.com/puppetlabs/facter/blob/master/lib/facter.rb
It doesn't require core/execution itself... without digging too much further in could you just try this in your spec_helper:
require 'facter/core/execution'

Resources