use strict;
use warnings;
use Net::SMTP;
# to install Net::SMTP moudle package, run cpan command in command line
# in the shell type install Net::SMTP
sub send_mail
####################################################################################################
#
# SUBROUTINE : send_mail
#
# PURPOSE : Send an email.
#
# INPUT(S) : smtp_server - Simple Mail Transfer Protocol server
# to - Recipient address
# from - Sender address
# subject - Subject
# body - Reference to an array containing the message body
#
# OUTPUT(S) : 0 - success
# 1 - failure
#
####################################################################################################
{
my $from = 'home.yosef#gmail.com';
my $to = 'home.yosef#gmail.com';
my $data = "A simple test message from Perl script\n";
my $subject = "Hello World from Perl script";
smtp_server => "smtp.gmail.com";
# Connect to the SMTP server
my $smtp = Net::SMTP->new($smtp_server);
# If connection is successful, send mail
if ($smtp) {
# Establish to/from
$smtp->mail($from);
$smtp->to($to);
# Start data transfer
$smtp->data();
# Send the header
$smtp->datasend("To: $to\n");
$smtp->datasend("From: $from\n");
$smtp->datasend("Subject: $subject\n");
$smtp->datasend("\n");
# Send the body
$smtp->datasend(#body);
# End data transfer
$smtp->dataend();
# Close the SMTP connection
$smtp->quit();
# If connection fails return with error
}
else {
# Print warning
warn "WARNING: Failed to connect to $smtp_server: $!";
return 1;
}
return 0;
}
# Define the message body
my #message_body = "Hello World! from Perl script\n";
push #message_body, "Add another line!\n";
# Send the email!
send_mail(
smtp_server => <smtp_server_name>,
to => <to_address>,
from => <from_address>,
subject => 'This is a mail from Perl script',
body => \#message_body,
);
I have Windows installed with Strawberry Perl. When I run this script from the command line I get errors and I donn't know what is wrong.
These are the errors:
Global symbol "$smtp_server" requires explicit package name (did you forget to declare "my $smtp_server"?) at windows_send_mail_with_SMTP.pl line 40.
Global symbol "#body" requires explicit package name (did you forget to declare "my #body"?) at windows_send_mail_with_SMTP.pl line 59.
Global symbol "$smtp_server" requires explicit package name (did you forget to declare "my $smtp_server"?) at windows_send_mail_with_SMTP.pl line 71.
Execution of windows_send_mail_with_SMTP.pl aborted due to compilation errors.
Well I think it's pretty clear from the messages. You haven't declared (or defined) either $smtp_server or #body
This line doesn't do anything useful
smtp_server => "smtp.gmail.com"
The => operator is just a comma that happens to put quotes around its first parameter, so it's the same as
"smtp_server", "smtp.gmail.com"
which just evaluates two strings and discards them
Since you have already successfully defined four other variables on the lines before, I don't understand why you chose to do something different here
As for #body, you never declare it or put anything into it. As the error message says,
did you forget to declare "my #body"?
Related
I have a recipe that iterates a hash containing SQL scripts in an each method and -- in case the script changed from the previous run -- the cookbook_file resource notifies the execute resource to run.
The issue is that it seems it always runs the execute using the last element of the hash.
Following the attributes file
default['sql_scripts_dir'] = 'C:\\DBScripts'
default['script_runner']['scripts'] = [
{ 'name' => 'test', 'hostname' => 'local' },
{ 'name' => 'test2', 'hostname' => 'local' },
{ 'name' => 'test3', 'hostname' => 'local' },
{ 'name' => 'test4', 'hostname' => 'local' },
]
And the recipe
directory node['sql_scripts_dir'] do
recursive true
end
node['script_runner']['scripts'].each do |script|
cookbook_file "#{node['sql_scripts_dir']}\\#{script['name']}.sql" do
source "#{script['name']}.sql"
action :create
notifies :run, 'execute[Create_scripts]', :immediately
end
execute 'Create_scripts' do
command "sqlcmd -S \"#{script['hostname']}\" -i \"#{node['sql_scripts_dir']}\\#{script['name']}.sql\""
action :nothing
end
end
And it produces the following output:
Recipe: test_runner::default
* directory[C:\DBScripts] action create
- create new directory C:\DBScripts
* cookbook_file[C:\DBScripts\test.sql] action create
- create new file C:\DBScripts\test.sql
- update content in file C:\DBScripts\test.sql from none to 8c40f1
--- C:\DBScripts\test.sql 2020-07-30 17:30:30.959220400 +0000
+++ C:\DBScripts/chef-test20200730-1500-11bz3an.sql 2020-07-30 17:30:30.959220400 +0000
## -1 +1,2 ##
+select ##version
* execute[Create_scripts] action run
================================================================================
Error executing action `run` on resource 'execute[Create_scripts]'
================================================================================
Mixlib::ShellOut::ShellCommandFailed
------------------------------------
Expected process to exit with [0], but received '1'
---- Begin output of sqlcmd -S "local" -i "C:\DBScripts\test4.sql" ----
STDOUT:
STDERR: Sqlcmd: 'C:\DBScripts\test4.sql': Invalid filename.
---- End output of sqlcmd -S "local" -i "C:\DBScripts\test4.sql" ----
Ran sqlcmd -S "local" -i "C:\DBScripts\test4.sql" returned 1
The expected behavior is that the recipe runs sequentially the 4 scripts in the example instead of running just the last one. What am I missing for getting it done?
You are creating 4 nearly identical resources all named execute[Create_scripts] and when the notification fires from the first cookbook_file resource being updated it finds the last one of them to be notified and runs against test4 (no matter which cookbook_file resource updates).
The fix is to use string interpolation to change the name of the execute resources to be unique and to notify based on that unique name:
directory node['sql_scripts_dir'] do
recursive true
end
node['script_runner']['scripts'].each do |script|
cookbook_file "#{node['sql_scripts_dir']}\\#{script['name']}.sql" do
source "#{script['name']}.sql"
action :create
notifies :run, "execute[create #{script['name']} scripts]", :immediately
end
execute "create #{script['name']} scripts" do
command "sqlcmd -S \"#{script['hostname']}\" -i \"#{node['sql_scripts_dir']}\\#{script['name']}.sql\""
action :nothing
end
end
Note that this is a manifestation of the same issues behind the old CHEF-3694 warning message where what would happen is that all the four execute resources would be merged into one resource via "resource cloning" with the properties of the subsequent resource being "last-writer-wins".
In Chef 13 this was changed to remove resource cloning and the warning, and in most circumstances having two resources named the same thing in the resource collection is totally harmless -- until you try to notify one of those resources. The resource notification system should really warn in this situation rather than silently picking the last resource that matches (but between notifications, subscribes, lazy resolution and now unified_mode that code is very complicated and you only want it to be firing under exactly the right conditions).
I am trying to create a custom fact I can use as the value for a class parameter in a hiera yaml file.
I am using the openstack/puppet-keystone module and I want to use fernet-keys.
According to the comments in the module I can use this parameter.
# [*fernet_keys*]
# (Optional) Hash of Keystone fernet keys
# If you enable this parameter, make sure enable_fernet_setup is set to True.
# Example of valid value:
# fernet_keys:
# /etc/keystone/fernet-keys/0:
# content: c_aJfy6At9y-toNS9SF1NQMTSkSzQ-OBYeYulTqKsWU=
# /etc/keystone/fernet-keys/1:
# content: zx0hNG7CStxFz5KXZRsf7sE4lju0dLYvXdGDIKGcd7k=
# Puppet will create a file per key in $fernet_key_repository.
# Note: defaults to false so keystone-manage fernet_setup will be executed.
# Otherwise Puppet will manage keys with File resource.
# Defaults to false
So wrote this custom fact ...
[root#puppetmaster modules]# cat keystone_fernet/lib/facter/fernet_keys.rb
Facter.add(:fernet_keys) do
setcode do
fernet_keys = {}
puts ( 'Debug keyrepo is /etc/keystone/fernet-keys' )
Dir.glob('/etc/keystone/fernet-keys/*').each do |fernet_file|
data = File.read(fernet_file)
if data
content = {}
puts ( "Debug Key file #{fernet_file} contains #{data}" )
fernet_keys[fernet_file] = { 'content' => data }
end
end
fernet_keys
end
end
Then in my keystone.yaml file I have this line:
keystone::fernet_keys: '%{::fernet_keys}'
But when I run puppet agent -t on my node I get this error:
Error: Could not retrieve catalog from remote server: Error 500 on SERVER: Server Error: Evaluation Error: Error while evaluating a Function Call, "{\"/etc/keystone/fernet-keys/1\"=>{\"content\"=>\"xxxxxxxxxxxxxxxxxxxx=\"}, \"/etc/keystone/fernet-keys/0\"=>{\"content\"=>\"xxxxxxxxxxxxxxxxxxxx=\"}}" is not a Hash. It looks to be a String at /etc/puppetlabs/code/environments/production/modules/keystone/manifests/init.pp:1144:7 on node mgmt-01
I had assumed that I had formatted the hash correctly because facter -p fernet_keys output this on the agent:
{
/etc/keystone/fernet-keys/1 => {
content => "xxxxxxxxxxxxxxxxxxxx="
},
/etc/keystone/fernet-keys/0 => {
content => "xxxxxxxxxxxxxxxxxxxx="
}
}
The code in the keystone module looks like this (with line numbers)
1142
1143 if $fernet_keys {
1144 validate_hash($fernet_keys)
1145 create_resources('file', $fernet_keys, {
1146 'owner' => $keystone_user,
1147 'group' => $keystone_group,
1148 'subscribe' => 'Anchor[keystone::install::end]',
1149 }
1150 )
1151 } else {
Puppet does not necessarily think your fact value is a string -- it might do, if the client is set to stringify facts, but that's actually beside the point. The bottom line is that Hiera interpolation tokens don't work the way you think. Specifically:
Hiera can interpolate values of any of Puppet’s data types, but the
value will be converted to a string.
(Emphasis added.)
I am using the msutter DSC module for puppet. While reading through the source code, I come across code like this (in dsc_configuration_provider.rb):
def create
Puppet.debug "\n" + ps_script_content('set')
output = powershell(ps_script_content('set'))
Puppet.debug output
end
What file defines the powershell function or method? Is it a ruby builtin? A puppet builtin? Inherited from a class? I know that it is being used to send text to powershell as a command and gathering results, but I need to see the source code to understand how to improve its error logging for my purposes, because certain powershell errors are being swallowed and no warnings are being printed to the Puppet log.
These lines in file dsc_provider_helpers.rb may be relevant:
provider.commands :powershell =>
if File.exists?("#{ENV['SYSTEMROOT']}\\sysnative\\WindowsPowershell\\v1.0\\powershell.exe")
"#{ENV['SYSTEMROOT']}\\sysnative\\WindowsPowershell\\v1.0\\powershell.exe"
elsif File.exists?("#{ENV['SYSTEMROOT']}\\system32\\WindowsPowershell\\v1.0\\powershell.exe")
"#{ENV['SYSTEMROOT']}\\system32\\WindowsPowershell\\v1.0\\powershell.exe"
else
'powershell.exe'
end
Surely this defines where the Powershell executable is located, but gives no indication how it is called and how its return value is derived. Are stdout and stderr combined? Am I given the text output or just the error code? etc.
This is core Puppet logic. When a provider has a command, like
commands :powershell => some binary
That is hooked up as a function powershell(*args).
You can see it with other providers like Chocolatey:
commands :chocolatey => chocolatey_command
def self.chocolatey_command
if Puppet::Util::Platform.windows?
# must determine how to get to params in ruby
#default_location = $chocolatey::params::install_location || ENV['ALLUSERSPROFILE'] + '\chocolatey'
chocopath = ENV['ChocolateyInstall'] ||
('C:\Chocolatey' if File.directory?('C:\Chocolatey')) ||
('C:\ProgramData\chocolatey' if File.directory?('C:\ProgramData\chocolatey')) ||
"#{ENV['ALLUSERSPROFILE']}\chocolatey"
chocopath += '\bin\choco.exe'
else
chocopath = 'choco.exe'
end
chocopath
end
Then other locations can just call chocolatey like a function with args:
chocolatey(*args)
I must migrate my perlscripts to a new machine but DBD::CSV would not work as I expect.
I got ActiveState perl 5, version 16, subversion 3 (v5.16.3) built for MSWin32-x86-multi-thread
DBD-CSV is Version 0.41 and everyting I try results in:
#################################################################
DBD::CSV::db do failed:
Execution ERROR: -d D:\TEST/D:\TEST: No such file or directory at D:/Programme/Perl/site/lib/DBI/DBD/SqlEngine.pm line 1503.
called from D:\TEST\CSVTest.pl at 7.
#################################################################
even at the simplest create TestTable
#################################################################
use DBI;
$dbh = DBI->connect ("dbi:CSV:");
my $table ="TestTable";
$dbh->do ("CREATE TABLE $table (id INTEGER, name CHAR (64))");
#################################################################
Any suggestion?
Update
Not the real answer, but too much for an comment the uncommented $DBDIR is the way i had excpeted to work. All the commented versions of $DBDIR I tried, but all created an foo.csv in same directory as the script (C:\Temp). I want to use some other Directory as the Script is located any sugestion?
use DBI;
my $DBDIR='C:\Temp\CSV-DB';
# my $DBDIR="C:\\Temp\\CSV-DB";
# my $DBDIR='C:\Temp\CSV-DB\\';
# my $DBDIR='..\CSV-DB';
# my $DBDIR="..\\CSV-DB";
# my $DBDIR='CSV-DB';
# my $DBDIR='\CSV-DB';
# my $DBDIR='\\CSV-DB';
# my $DBDIR='.\CSV-DB';
# my $DBDIR='/CSV-DB';
# my $DBDIR='./CSV-DB';
$dbh = DBI->connect ("dbi:CSV:", undef, undef, {
f_dir => [$DBDIR],
#f_dir_search => [$DBDIR],
f_ext => ".csv/r",
RaiseError => 1,
}) or die "Cannot connect: $DBI::errstr";
# Simple statements
$dbh->do ("CREATE TABLE foo (id INTEGER, name CHAR (10))");
##############################################################
I had this problem on my windows PC.
I found that it occurred when DBI was not the first module to be used.
By putting "use DBI;" as the first module the problem has disappeared.
I haven't tried to determine exactly which module caused the problem. I'm using CGI, Archive::Zip, POSIX, etc..
Anyone out there know of a graceful way to install the "Image::OCR::Tesseract" module on Windows? The module fails to install on Windows via CPAN due to a *NIX only module dependency called "LEOCHARRE::CLI". This module does not seem to be required to run "Image::OCR::Tesseract" itself.
I've managed to get the module working by first manually installing the dependency modules listed in the makefile.pl (except for "LEOCHARRE::CLI") and then by moving the module file to the correct directory structure under "C:\Perl\site\lib\Image\OCR". The final part of getting it to work was to alter the section of code that calls the ImageMagick and Tesseract executables from the command line to put quotes around the program names when the executables are called by module.
This works, but I'd really feel better about doing a PPM or CPAN install on a production system from a repo that works on Windows.
Never mind, I got it, though I can't decide what is the better solution.
To get the installer to work on Windows via the traditional "perl makefile.pl, make, make test, make install" routine requires an edit to the Makefile.pl script, including the missing Windows install module (Devel::AssertOS::MSWin32), and patch to AssertEXE.pm to use "File::Which" rather than the built in shell "which" command that Windows lacks. All this still requires that The "Image::OCR::Tesseract" be patched to put quotes around program names when executing "convert" and "tesseract" from the command line.
Given the number of steps involved to make the installer work on Windows, and the fact the module does not create a binary component for the module to link to, I'd say the best option for installing and getting the Tesseract module working on windows would be to first install the following binary packages:
ImageMagick
Link
Tesseract
http://code.google.com/p/tesseract-ocr/downloads/list
Next, locate your Perl module directory - on my system it is "C:\Perl\site\lib". Create a folder "Image", if you don't have one. Next, open the Image folder and create a folder called "OCR". Open the OCR folder. At this point, your path should be something along the lines of "C:\Perl\site\lib\Image\OCR". Create a new text file called "Tesseract.pm", and copy in the following content...
package Image::OCR::Tesseract;
use strict;
use Carp;
use Cwd;
use String::ShellQuote 'shell_quote';
use Exporter;
use vars qw(#EXPORT_OK #ISA $VERSION $DEBUG $WHICH_TESSERACT $WHICH_CONVERT %EXPORT_TAGS #TRASH);
#ISA = qw(Exporter);
#EXPORT_OK = qw(get_ocr get_hocr _tesseract convert_8bpp_tif tesseract);
$VERSION = sprintf "%d.%02d", q$Revision: 1.24 $ =~ /(\d+)/g;
%EXPORT_TAGS = ( all => \#EXPORT_OK );
BEGIN {
use File::Which 'which';
$WHICH_TESSERACT = which('tesseract');
$WHICH_CONVERT = which('convert');
if($^O=~m/MSWin/) {
$WHICH_TESSERACT='"'.$WHICH_TESSERACT.'"';
$WHICH_CONVERT='"'.$WHICH_CONVERT.'"';
}
$WHICH_TESSERACT or die("Is tesseract installed? Cannot find bin path to tesseract.");
$WHICH_CONVERT or die("Is convert installed? Cannot find bin path to convert.");
}
END {
scalar #TRASH or return;
if ( $DEBUG ){
print STDERR "Debug on, these are trash files:\n".join("\n",#TRASH) ;
}
else {
unlink #TRASH;
}
}
sub DEBUG { Carp::cluck("Image::OCR::Tesseract::DEBUG() deprecated") }
sub get_hocr {
my ($abs_image,$abs_tmp_dir,$lang)= #_;
-f $abs_image or croak("$abs_image is not a file on disk");
my $hocr="hocr";
if(defined $abs_tmp_dir){
-d $abs_tmp_dir or die("tmp dir arg $abs_tmp_dir not a dir on disk.");
$abs_image=~/([^\/]+)$/ or die("cant match filename in path arg '$abs_image'");
my $abs_copy = "$abs_tmp_dir/$1";
# TODO, what if source and dest are same, i want it to die
require File::Copy;
File::Copy::copy($abs_image, $abs_copy)
or die("cant make copy of $abs_image to $abs_copy, $!");
# change the image to get ocr from to be the copy
$abs_image = $abs_copy;
# since it's a copy. erase that on exit
push #TRASH, $abs_image;
}
my $tmp_tif = convert_8bpp_tif($abs_image);
push #TRASH, $tmp_tif; # for later delete
_tesseract($tmp_tif,$lang,$hocr) || '';
}
sub get_ocr {
my ($abs_image,$abs_tmp_dir,$lang)= #_;
-f $abs_image or croak("$abs_image is not a file on disk");
if(defined $abs_tmp_dir){
-d $abs_tmp_dir or die("tmp dir arg $abs_tmp_dir not a dir on disk.");
$abs_image=~/([^\/]+)$/ or die("cant match filename in path arg '$abs_image'");
my $abs_copy = "$abs_tmp_dir/$1";
# TODO, what if source and dest are same, i want it to die
require File::Copy;
File::Copy::copy($abs_image, $abs_copy)
or die("cant make copy of $abs_image to $abs_copy, $!");
# change the image to get ocr from to be the copy
$abs_image = $abs_copy;
# since it's a copy. erase that on exit
push #TRASH, $abs_image;
}
my $tmp_tif = convert_8bpp_tif($abs_image);
push #TRASH, $tmp_tif; # for later delete
_tesseract($tmp_tif,$lang) || '';
}
sub convert_8bpp_tif {
my ($abs_img,$abs_out) = (shift,shift);
defined $abs_img or die('missing image arg');
$abs_out ||= $abs_img.'.tmp.'.time().(int rand(9000)).'.tif';
my #arg = ( $WHICH_CONVERT, $abs_img, '-compress','none','+matte', $abs_out );
#die (join(" ", #arg));
system(#arg) == 0 or die("convert $abs_img error.. $?");
$DEBUG and warn("made $abs_out 8bpp tiff.");
$abs_out;
}
# people expect tesseract to automatically convert
*tesseract = \&_tesseract;
sub _tesseract {
my ($abs_image,$lang,$hocr) = #_;
defined $abs_image or croak('missing image path arg');
$abs_image=~/\.tif+$/i or warn("Are you sure '$abs_image' is a tif image? This operation may fail.");
#my #arg = (
# $WHICH_TESSERACT, shell_quote($abs_image), shell_quote($abs_image),
# (defined $lang and ('-l', $lang) ), '2>/dev/null'
#);
my $cmd =
( sprintf '%s %s %s',
$WHICH_TESSERACT,
shell_quote($abs_image),
shell_quote($abs_image)
) .
( defined $lang ? " -l $lang" : '' ) .
( defined $hocr ? " hocr" : '' ) .
" 2>/dev/null";
$DEBUG and warn "command: $cmd";
system($cmd); # hard to check ==0
my $txt = $abs_image.($hocr?".html":".txt");
unless( -f $txt ){
Carp::cluck("no text output for image '$abs_image'. (No text file '$txt' found on disk)");
return;
}
$DEBUG and warn "Found text file '$txt'";
my $content = (_slurp($txt) || '');
$DEBUG and warn("content length of text in '$txt' from image '$abs_image' is ". length $content );
push #TRASH, $txt;
$content;
}
sub _slurp {
my $abs = shift;
open(FILE,'<', $abs) or die("can't open file for reading '$abs', $!");
local $/;
my $txt = <FILE>;
close FILE;
$txt;
}
1;
__END__
#sub _force_imgtype {
# my $img = shift;
# my $type = shift;
# my $delete_original = shift;
# $delete_original ||=0;
#
#
# if($img=~/\.$type$/i){
# return $img;
# }
#
# my $img_out= $img;
# $img_out=~s/\.\w{1,5}$/\.$type/ or die("cant get file ext for $img");
#
#
#
#}
Save and close. Close the command line session and open a new one if you've had one open from before you did the ImageMagick and Tesseract binary installs. Test the module with the following script:
use Image::OCR::Tesseract;
my $image = 'SomeImageFileThatContainsText.jpg';
my $text = Image::OCR::Tesseract::get_ocr($image);
print "Text...\n";
print $text."\n";
print "Normal Exit\n";
exit;
That's it. Messy, I know, but there's no good way around the fact that the module installer really needs to be updated to support Windows (and other) systems even though the actual module code almost runs without modification. Really, if Tesseract and ImageMagick were installed to paths without spaces then the "Image::OCR::Tesseract" module code would not need any changes, but this minor tweak lets the supporting executables be installed anywhere, including the default locations.