Is any reliable method available for including a conditional in a Vagrantfile, for use in Vagrant, to select method of folder syncing based on provider?
Presently, I have included the following line:
config.vm.synced_folder "proj", "/srv", type: "9p", accessmode: "mapped"
However, the following represent a few possible improvements:
Use type "9p" only if the provider is libvirt, otherwise use some other, particular method.
As above, but use whatever method is default for the provider, if the provider is not libvirt.
As above, but test not whether the provider is libvirt, but simply whether support is available for the type "9p".
Select a method globally, rather than as the type parameter, for all separate invocations of synced_folder.
Apply also conditionally the value for accessmode, based on one of the tests given above.
Might anyone offer recipes for making the Vagrantfile more robust and portable, through any of the above suggestions that may be feasible, given the capabilities of the tools?
I'm no expert on vagrant or libvirt, but maybe this article Writing a general multi-provider Vagrantfile, in particular testing for (in your case) libvirt. Here's the relevant part of the sample config in the article that might point you in the right direction,
Vagrant.configure(2) do |config|
config.vm.provider :libvirt do |libvirt|
config.vm.box_url = "https://download.fedoraproject.org/pub/fedora/linux/releases/22/Cloud/x86_64/Images/Fedora-Cloud-Base-Vagrant-22-20150521.x86_64.vagrant-libvirt.box"
end if Vagrant.has_plugin?('vagrant-libvirt')
end
I'm not sure why the conditional is after the end in that article, I think the following is equivalent and clearer,
Vagrant.configure(2) do |config|
if Vagrant.has_plugin?("vagrant-libvirt")
#...
else obviously also useful for the non-libvirt scenario.
If I'm misunderstanding the question or going off in totally the wrong direction please comment accordingly so I can delete or modify my answer.
Related
I have come accross the idea to use pragmas to make PLC code as easy to re-configure from machine to machine depending on what a particular machine requires (they are very similar, but still require to some work to make for every new project). The machine as a whole can contain N units, then you would just remove certain parts if new project does not need it. My idea was:
VAR_GLOBAL
{attribute 'machinePart1'}
{attribute 'machinePart2'}
//{attribute 'machinePart3'} Lets say machine does not use attribute machinePart3
machineParts : BOOL; // dummy variable to add attributes
END_VAR
In the PLC code, I would then use conditional pragmas for code parts that require or don't require certain parts - you would determin which attribute exists by commenting out unnecessary ones.
In the PLC the parts of the machine that are not present would then be disregarded like this:
{IF hasattribute (variable: machineParts, 'machinePart3')}
//this code will not be executed
{END_IF}
{IF hasattribute (variable: machineParts, 'machinePart2')}
//this code will be executed
{END_IF}
{IF hasattribute (variable: machineParts, 'machinePart1')}
//this code will be executed
{END_IF}
So, my question is - is this a valid way to go about making a PLC code as easy to configure as possible? Does it affect efficency/CPU time? I havent looked deep into this, but I would assume the code is compiled, and for whatever conditional pragma the result is false, the code isn't complied and thus not present on the PLC itself when downloaded.
I would apply this to structures for machine parts and PLC code exectuion/function block instance creation.
Beckhoff already provides a solution for your requirements which is called variant management:
https://infosys.beckhoff.com/english.php?content=../content/1033/variant_management/6325752587.html&id=
This does not affect performance as the compiled code only includes the variant that you have chosen.
Jakob is right, there is already a variant management for the project level. And the link describes all necessary steps. The only thing to add here is a little advice from someone who has already fallen on his face with it several times. Be carefull with or rather just don't use the group feature (<=TC4024.20). The behaviour is really strange and very error-prone, from my point of view.
I have a wrapper cookbook that uses resources from other cookbooks, but they have different constraints. I tried defining my metadata.rb as follow :
name 'yp_linko'
maintainer 'The Authors'
maintainer_email 'devops#yp.ca'
license 'all_rights'
description 'Installs/Configures yp_linko'
long_description 'Installs/Configures yp_linko'
version '1.3.4'
depends 'apt'
if chef_version '<= 12' then
depends 'ypg_tomcat'
else
depends 'yp_tomcat'
end
This didn't work as chef grabbed both cookbooks during convergence. I tried a couple different syntax (only_if, unless, etc...) and none worked. Anyone has ideas to work around this issue?
TL;DR
You're using a constraint from the Chef DSL in a way that it's not meant to be used. You need to branch based on a Boolean expression, not a metadata constraint. I provide a couple of ways to do that using String#to_f as examples (not recommended if you care about patch levels in semantic versioning), as well as the more accurate but often-overlooked Gem::Version.
Don't Use a Constraint for Branching
if chef_version '<= 12'
You're trying to use a constraint from the DSL. This constraint has a specific purpose: to declare the chef-client versions supported by the cookbook, not to provide logical branching. Without looking at the underlying Chef code for the DSL, I'd say it's unlikely that the expression is feeding your if-then expression the way you expect. Setting aside for the moment whether this is the right pattern to follow at all, you can try grabbing the current version of your Chef tools in a couple of different ways:
Branching on the chef-client version, likely cast as a Float (it's normally a String). For instance:
if Chef::VERSION.to_f <= 12
Get the node value from ohai with something like:
if node['chef_packages']['chef']['version'].to_f <= 12
Parse the value directly from the client, e.g.:
depends %x(chef-client --version).split[1].to_f <= 12 ? 'ypg_tomcat' : 'yp_tomcat'
However, in all cases, you're going to have to deal with the fact that you're being passed a String containing semantic versioning, not a Float or Integer. So, you'll have to figure out how you really want to parse the information, which is potentially error-prone (see below for a trick using Gem::Version, though). In any case, once you've parsed it the way you want, you can match it using a comparison operator to get the branching behavior you want.
Better Options
Rather than trying to make the metadata constraint hold business logic, you should probably move the data out to an attribute. Consider an attribute such as node['yp_linko']['tomcat_cookbook'], which you can set based on some other detectable node value other than semantic versioning.
Another approach would be to declare both cookbooks a dependency, and then include the one you want inside a recipe in your yp_linko cookbook. For example, assuming you haven't declared incompatible chef-client versions in the Tomcat cookbooks:
# metadata.rb
depends 'yp_tomcat'
depends 'ypg_tomcat'
# default.rb
if Chef::VERSION <= Gem::Version.new(12)
include ypg_tomcat::default
else
include yp_tomcat::default
end
And finally, you should consider whether it really makes sense to be running different versions of your Chef clients within the infrastructure in the first place. There may be a business need to do that, but it's the real problem you're actually trying to solve for. Branching in your cookbooks is an X/Y solution to an infrastructure issue. It seems likely that you'd have other cookbooks with similar issues, so it's at least worth considering whether it makes more sense to get all your clients on the same version rather than solving the problem at the cookbook level.
I'm fixing a cookbook for redis, with 2 distinct recipes + a default one.
On the default recipe I add the a PPA repository and do the apt-get update stuff.
What I would like to do is be able to persist on the node, only the attributes for the corresponding enabled recipes:
If the node includes "server" recipe, then store the defaults from attributes/server.rb
If the node includes "client" recipe, then store the defaults from attributes/client.rb
The documentation isn't clear enough to whether the above will happen or it will store both, despite the recipes inclusion.
What i've seen people suggesting is to move the node.default[...] definitions that are specific to a recipe only and does not fit on "default" recipe to the recipe itself. I believe that this is non logical.
What is the "best-practice" to achieve this separation?
The key quote from Chef's documentation is: "When a cookbook is run against a node, the attributes contained in all attribute files are evaluated in the context of the node object." (emphasis mine). You can only achieve what you're talking about by moving to using node.(scope) in the recipes.
For thing you need in the client recipe
node[:redis][:client][:foo] = "bar"
For things you need in the server recipe
node[:redis][:server][:foo] = "bar"
Out of curiosity, which redis recipe are you using?
Let's assume a script needs access a directory, say /some/where/abc on an "arbitrary" OS. There are a couple options to build the path in Ruby:
File.join('', 'some', 'where', 'abc')
File.absolute_path("some#{File::SEPARATOR}where#{File::SEPARATOR}abc", File::SEPARATOR)
Pathname in the standard API
I believe the first solution is clear enough, but idiomatic. In my experience, some code reviews ask for a comment to explain what it does...
The Question
Is there a better way to build an absolute path is Ruby, where better means "does the job and speaks for itself"?
What I would pick up if I was doing a code review is that on Windows /tmp is not necessarily the best place to create a temporary directory, and also the initial '', argument is perhaps not obvious to the casual reviewed that it creates <nothing>/tmp/abc. Therefore, I would recommend this code:
File.join(Dir.tmpdir(), 'abc')
See Ruby-doc for an explanation.
UPDATE
If we expand the problem to a more generic solution that does not involve using tmpdir(), I cannot see a way round using the initial '' idiom (hack?). On Linux this is not too much of a problem, perhaps, but on Windows with multiple drive letters it will be. Furthermore, there does not appear to be a Ruby API or gem for iterating the mount points.
Therefore, my recommendation would be to delegate the mount point definition to a configuration option that might be '/' for Linux, 'z:/' for Windows, and smb://domain;user#my.file.server.com/mountpoint for a Samba share, then use File.join(ProjectConfig::MOUNT_POINT, 'some', 'where', 'abc').
File#join is THE canonical way to build a portable path in Ruby. I'm wondering who is doing the review. Perhaps Ruby is new to your organization.
I agree with #ChrisHeald that referring to the documentation is the best way to explain the code to a reviewer.
I'm used to making calls such as:
new_count.should eql(10)
on variables, but how can I do something similar with a class method such as File.directory?(my_path)?
Every combination of File.should be_directory(my_path) that I've tried leads to a method missing, as Ruby tries to find "be_directory" on my current object, rather than matching it against File.
I know I can turn it around and write
File.directory?(my_path).should == true
but that gives a really poor message when it fails.
Any ideas?
Hmm, maybe I have an idea.
File is part of Ruby proper, so it may have elements written in C. Some of Ruby's meta-programming tools break down when dealing with classes imported from C, that could explain Rspec's failure to make .should behave as expected.
If that's true, there is no real solution here. I'd suggest using the MockFS library:
http://mockfs.rubyforge.org/
This downside to MockFS is using it everywhere you'd normally use File, Dir and FileUtils:
require 'mockfs'
def move_log
MockFS.file_utils.mv( '/var/log/httpd/access_log', '/home/francis/logs/' )
end
The upside, especially if your code is file-intensive, is the ability to spec really complex scenarios out, and have them run without actually touching the slow filesystem. Everything happens in memory. Faster, more complete specs.
Hope this helps, Good luck!
I'm not sure why be_directory wouldn't work for you. What version of rspec are you using? You can also use rspec's predicate_matchers method, when a predicate exists, but it doesn't read nicely as be_predicate.
Here's what I tried:
describe File, "looking for a directory" do
it "should be directory" do
File.should be_directory("foo")
end
predicate_matchers[:find_the_directory_named] = :directory?
it "should find directory" do
File.should find_the_directory_named("foo")
end
end
And that gave me the following output (run with spec -fs spec.rb):
File looking for a directory
- should be directory
- should find directory
Finished in 0.004895 seconds
2 examples, 0 failures