Reading in variables from a file in Ruby - ruby

Is there a way to read in a file of environment variables?
In bash I have a file env.sh that I can use
env.sh
foo="bar"
bash file
set -a
source env.sh
This would allow me to just use foo as if I had delcared it in the ruby script.
Also is it there a way to make sure that this file is unreadable so that passwords could be stored in this file?

It sounds like you should provide a file example for the user/admin to modify for their personal environment, and then populate the environment from that, while avoiding, perhaps, having that file with the sensitive information in a repository. Note: per file security is going to be addressed by where the file is located and your operating system, and server software.
If this is the case, then you can provide a file that holds a template of the kind of things that you would require from the administrator/user of the program you are configuring.
Ruby has the ENV constant that acts like a Hash and holds the environment of the shell you are using.
As an example, there is a file called environment.rb.sample that gets shared with anyone, publicly. It has instructions and holds the template that users can modify freely, with instructions to copy the file to environment.rb. The sample file looks like this:
# environment.rb.sample
# Copy this file to environment.rb and change the name and password to your credentials
ENV['temp_user_name'] = 'Foo Bar'
ENV['temp_password'] = 'Dazz Kezz
The file is then copied to this, perhaps:
# environment.rb
ENV['temp_user_name'] = 'Joe Admin'
ENV['temp_password'] = 'Super Secure Password'
The file that loads this and uses it is just a Ruby file that is freely modified by the user/administrator of the software, and looks like this and is also shared publicly.
# load_environment
require './environment'
puts ENV['temp_user_name']
puts ENV['temp_password']
This loads the file and uses the ENV that is a globally scoped constant for the application.
The file permissions are then managed by the user/administrator of the system and secured like any other sensitive information on their system. The sensitive file should also be listed in the repository's ignore mechanism. It should never be made public.

Yes, there is, and if for some bizzare, arcane reason you must use it, it's eval:
WARNING: Never use this on a user-supplied file
And, unless you have a very, very specific need, don't use it in production code.
eval(File.read("name_of_var_file"), binding)
If what you're really trying to do is write a configuration file, use YAML. A file like this:
config.yaml:
foo: "bar"
Can be accessed like this:
require 'yaml'
conf = YAML.loads(File.read("config.yaml"))
conf['foo'] #=> 'bar'
This is secure and manageable, not to mention standard practice.
As for making the file inaccessible, that is an operating system level problem that can't be solved without information on the environment, OS, setup, etc.

The purpose of a local variable is to be used temporally within a method definition or a block. Using it outside of such environments, particularly across files defeats the purpose of it. You should not need to do it, and Ruby does not have a simple way to do it.
If you are using variables correctly, and want to share variables between files, that should be other types of variables such as instance, class, or global variables. Or, for the purpose of setting environments, you should be using constants. Among them, global variables and constants can be written in a file, loaded in a different file, and be used.
file-a.rb
$foo = 1
FOO = 2
file-b.rb
load "file-a.rb"
$foo # => 1
FOO # => 2
As for instance and class variables, they belong to a class or an instance of it, so they should be defined in such environment. And you can reopen the same class within a different file, and load it in another file.
file-a.rb
class Bar
##foo = 1
def initialize; #foo = 2 end
end
file-b.rb
load "file-a.rb"
Bar.class_variable_get("##foo") # => 1
Bar.new.instance_variable_get("#foo") # => 2

Related

Fetch variable from yaml in puppet manifest

I'm doing one project for puppet, however currently stuck in one logic.
Thus, want to know can we fetch variable from .yaml, .json or plain text file in puppet manifest file.
For example,
My puppet manifest want to create user but the variable exist in the .yaml or any configuration file, hence need to fetch the varibale from the outside file. The puppet manifest also can do looping if it exist multiple users in .yaml file.
I read about hiera but let say we are not using hiera is there any possible way.
There are a number of ways you can do this using a combination of built-in and stdlib functions, at least for YAML and JSON.
Using the built-in file function and the parseyaml or parsejson stdlib functions:
Create a file at mymodule/files/myfile.yaml:
▶ cat files/myfile.yaml
---
foo: bar
Then in your manifests read it into a string and parse it:
$myhash = parseyaml(file('mymodule/myfile.yaml'))
notice($myhash)
That will output:
Notice: Scope(Class[mymodule]): {foo => bar}
Or, using the loadyaml or loadjson stdlib functions:
$myhash = loadyaml('/etc/puppet/data/myfile.yaml')
notice($myhash)
The problem with that approach is that you need to know the path to file on the Puppet master. Or, you could use a Puppet 6 deferred function and read the data from a file on the agent node.
(Whether or not you should do this is another matter entirely - hint: answer is you almost certainly should be using Hiera - but that isn't the question you asked.)

Is there a way to force a required file to be reloaded in Ruby?

Yes, I know I can just use load instead of require. But that is not a good solution for my use case:
When the app boots, it requires a config file. Each environment has its own config. The config sets constants.
When the app boots, only one environment is required. However, during testing, it loads config files multiple times to make sure there are no syntax errors.
In the testing environment, the same config file may be loaded more than once. But I don't want to change the require to load because every time the a spec runs, it reloads the config. This should be done via require, because if the config has already been loaded, it raises already initialized constant warnings.
The cleanest solution I can see is to manually reset the require flag for the config file after any config spec.
Is there a way to do that in Ruby?
Edit: adding code.
When the app boots it calls the init file:
init.rb:
require "./config/environments/#{ ENV[ 'RACK_ENV' ]}.rb"
config/environments/test.rb:
APP_SETTING = :foo
config/environments/production.rb:
APP_SETTING = :bar
spec/models/config.rb: # It's not a model spec...
describe 'Config' do
specify do
load './config/environments/test.rb'
end
specify do
load './config/environments/production.rb'
end
Yes it can be done. You must know the path to the files that you want to reload. There is a special variable $LOADED_FEATURES which stores what has been loaded, and is used by require to decide whether to load a file when it is requested again.
Here I am assuming that the files you want to re-require all have the unique path /myapp/config/ in their name. But hopefully you can see that this would work for any rule about the path name you can code.
$LOADED_FEATURES.reject! { |path| path =~ /\/myapp\/config\// }
And that's it . . .
Some caveats:
require does not store or follow any kind of dependency tree, to know what it "should" have loaded. So you need to ensure the full chain of requires starting with the require command you run in the spec to re-load the config, and including everything you need to be loaded, is covered by the removed paths.
This will not unload class definitions or constants, but simply re-load the files. In fact that is literally what require does, it just calls load internally. So all the warning messages about re-defining constants will also need to be handled by un-defining the constants you expect to see defined in the files.
There is probably a design of your config and specs that avoids the need to do this.
if you really want to do this, here's one approach that doesn't leak into your test process. Fork a process for every config file you want to test, communicate the status back to the test process via IO.pipe and fail/succeed the test based on the result.
You can go as crazy as you want with the stuff you send down the pipe...
Here's some quick and dirty example to show you what I mean.
a config
# foo.rb
FOO = "from foo"
another config
# bar.rb
FOO = "from bar"
some faulty config
# witherror.rb
asdf
and your "test"
# yourtest.rb
def load_config(writer, config_file)
fork do
begin
require_relative config_file
writer.write "success: #{FOO}\n"
rescue
writer.write "fail: #{$!.message}\n"
end
writer.close
exit # maybe this is even enough to NOT make it run your other tests...
end
end
rd, writer = IO.pipe
load_config(writer, "foo.rb")
load_config(writer, "bar.rb")
load_config(writer, "witherror.rb")
writer.close
puts rd.read
puts rd.read
puts rd.read
puts FOO
The output is:
success: from foo
success: from bar
fail: undefined local variable or method `asdf' for main:Object
yourtest.rb:24:in `<main>': uninitialized constant FOO (NameError)
as you can see, the FOO constant doesn't leak into your test process etc.
Of course you're only through half way because there's more to it like, making sure only one process runs the test etc.
Frankly, I don't think this is a good idea, no matter what approach you chose because you'll open a can of worms and imho there's no really clean way to do this.

Using require to use file with hash

I am trying to refer to a hash written in another file, and tried:
require './filewithhash' #this file has hash
puts name_hash['somename'] #just trying access by the index
and got undefined local variable or method error.
The problem is that when requiring file in ruby, local variables are out of scope. Therefore any local variables defined in required file will be not available later on.
Solutions:
Make your program object oriented and add this hash as a field in some class
Make your variable constant (like this: NAME_HASH)
Make your variable global (like this: $name_hash)
Make your variable an instance variable (like this: #name_hash)
If program is very simple, I would choose option 3. However if it's suppose to grow, 1 is your best choice.
Requires in Ruby don't just inline the file like in PHP; each file has its own scope. That is, the code in the file will execute in a scope that means that all local variables are all constrained to that file's scope, and will not be visible outside of it.
In order to execute a file in the current scope, you're going to have to eval it with the current binding:
file = "file_with_hash.rb"
File.open(file) {|f| eval f.read, binding, file }
This is a bad idea and you shouldn't do it unless you know what you're doing and why. Instead, you should expose that hash as a member of a constant (which transcends the file scope):
# foo.rb
module Foo
PROGRAMMER_WORDS = {foo: 1, bar: 2, baz: 3}
...
end
# another file
require 'foo'
Foo::PROGRAMMER_WORDS # => {:foo=>1, :bar=>2, :baz=>3}

How to load a ruby file in to IRB?

I have a file: options.rb
I open IRB and type:
require './options.rb'
#=> true
Try to call a variable in the options file such as key (yes this variable is there and the file is saved)
NameError: undefined local variable or method `key' for main:Object
from (irb):2
Why is this not working? By the way also tried to load the file as: irb -r ./options.rb
UPDATE
Also tried to do load './options.rb' which does return #=> true but this also does not work.
From the require docs:
Any constants or globals within the loaded source file will be available in the calling program’s global namespace. However, local variables will not be propagated to the loading environment.
So if in options.rb you have something like:
key = something
(i.e. key is a local in the file) then it will not be available in irb. If you make it a global (e.g. $key = 'something') or a constant (e.g. KEY = 'something') it should be available.
If you do not like global variables (as matt suggested) you might also make it an instance variable of the object irb is running on (an instance of Object available through self as ruby always has an object it is operating on) you may also assign
#key='value'
in your file which will give you access to #key in your irb-session afterwards. This will work with either require as with load, but require will only load the file if it has not already done so while load will always execute the code in the file and thus will end up overwriting the contents of your variable if it has been changed in the mean time.
Ruby is a interpreted language, so for the interpreter to notice your declarations you need to actually 'run' them, the corresponding command in irb is
load './options.rb'

what ruby features are used in chef recipes?

I just started using chef and don't know much about ruby.
I have problems understanding the language-syntax used in recipes.
Say, I create a directory in a cookbook in recipes/default.rb like:
directory "/home/test/mydir" do
owner "test"
mode "0755"
action :create
recursive true
end
I assume this is part of a valid ruby script. What do lines like owner "test" mean? Is this a function call, a variable assignment or something else entirely?
Chef is written in Ruby and makes an extensive use of Ruby ability to design custom DSL. Almost every chef configuration file is written with a Ruby-based DSL.
This means that in order to use chef effectively you should be familiar with the basic of Ruby syntax including
Grammar
Data types (the main difference compared to other languages are Symbols)
Blocks
You don't need to know a lot about metaprogramming in Ruby.
The case of the code you posted is an excellent example of a Ruby based DSL. Let me explain it a little bit.
# Call the method directory passing the path and a block
# containing some code to be evaluated
directory "/home/test/mydir" do
# chown the directory to the test user
owner "test"
# set the permissions to 0555
mode "0755"
# create the directory if it does not exists
action :create
# equivalent of -p flag in the mkdir
recursive true
end
Blocks are a convenient way to specify a group of operations (in this case create, set permissions, etc) to be evaluated in a single context (in this case in the context of that path).
Let's break it down.
directory "/home/test/mydir" do
...
end
You are just calling a global method defined by Chef called directory, passing one argument "/home/test/mydir", and a block (everything between the do and end).
This block is probably excecuted in a special scope created by Chef in which all of the options (owner, mode, action, etc.) are method.

Resources