install-osrf.sh: set ejabberd host correctly during registration on prod
[sitka/sitka-tools.git] / deployment / integrity-checker.pl
1 #!/usr/bin/perl
2 use strict;
3 use warnings;
4 # On Ubuntu, you'll want the following packages:
5 # - libconfig-simple-perl
6 # - libgit-repository-perl
7 # - libdate-manip-perl
8 use Config::Simple;
9 use File::Find;
10 use File::stat;
11 use Date::Manip qw/ParseDate UnixDate/;
12 use Time::localtime;
13 use Git::Repository;
14 use Git::Repository::Command;
15 use Getopt::Long;
16 use Data::Dumper;
17
18 my ($help, $config_file, $repo_path, $all, $check_files, $hash_file, $since, $git_output, $deployed_output);
19 my $branch = 'HEAD';
20 my $remote = 'origin';
21 my @components;
22
23 GetOptions(
24     'help'              => \$help,            # show help message and exit
25     'config=s'          => \$config_file,     # INI file for path mappings
26     'repo=s'            => \$repo_path,       # location of git repo
27     'branch=s'          => \$branch,          # git branch/head to check against (defaults to 'HEAD')
28     'remote=s'          => \$remote,          # git remote to pull from (defaults to 'origin')
29     'component=s'       => \@components,      # parts of EG to be checked (each component is a block in the INI file)
30     'all'               => \$all,             # check all components specified in config (overrides --component)
31     'check-files'       => \$check_files,     # check deployed files
32     'hash-file=s'       => \$hash_file,       # file containing git hashes (overrides --repo)
33     'since=s'           => \$since,           # check only files modified since this time
34     'print-git-hashes=s'      => \$git_output,      # output file for git hashes (optional)
35     'print-deployed-hashes=s' => \$deployed_output  # output file for hashes of deployed files (optional)
36 );
37
38 if ($help) {
39     print <<"HELP";
40 USAGE:
41     $0 --config pathmap.ini --repo /path/to/evergreen.git [ --component perl [ --component tt2 ] | --all ] --check-files [ --since <date> ]
42     $0 --config pathmap.ini --repo /path/to/evergreen.git [ --component perl [ --component tt2 ... ] | --all ] --print-git-hashes <git-hashes.txt>
43     $0 --config pathmap.ini --repo /path/to/evergreen.git [ --component perl [ --component tt2 ... ] | --all ] --print-deployed-hashes <deployed-hashes.txt>
44
45 OPTIONS:
46     --help
47         Show help message and exit.
48     --config
49         Location of INI file for path mappings.
50     --repo
51         Location of git repo (overridden by --hash-file).
52     --branch
53         Git branch (head) to check against (defaults to HEAD).
54     --remote
55         Git remote to pull from (defaults to origin).
56     --component
57         Parts of EG to be checked (each component is a block in the config file).
58         You can use this option multiple times: --component perl --component web
59     --all
60         Check all components specified in config file (overrides --component).
61     --check-files
62         Get file hashes from git, then check deployed files to see if they match.
63     --hash-file
64         File containing file hashes from git.  If you use this option, you don't
65         need to specify a git repo using the --repo option.
66         Use case: pull hashes from git once, copy the resulting file to multiple
67         servers, then check the deployed code against the file instead of pulling
68         hashes from git individually on each server.
69     --since
70         Only calculate hashes if file has been modified since the specified time.
71     --print-git-hashes
72         Get file hashes from git repo and print/append to the specified file.
73         Can be used with --check-files and --print-deployed-hashes.
74     --print-deployed-hashes
75         Print git-like hashes for deployed files.
76         Can be used with --check-files and --print-git-hashes.
77
78 HELP
79     exit;
80 }
81
82 # specify all possible components (--all option);
83 @components = split('\n', `./access_pathmap.pl --config $config_file`) if ($all);
84
85 if ($git_output) {
86     open (GITOUTPUT, '>>', $git_output) or die "Could not open $git_output: $!\n";
87 }
88 if ($deployed_output) {
89     open (DEPLOYEDOUTPUT, '>>', $deployed_output) or die "Could not open $deployed_output: $!\n";
90 }
91
92 my %git_hashes;
93
94 # optionally read in git hashes from file
95 if ($hash_file) {
96     open (HASHFILE, '<', $hash_file) or die "Could not open $hash_file: $!\n";
97     while (<HASHFILE>) {
98         chomp;
99         my ($hash, $file) = split(/\s+/, $_, 2);
100         $git_hashes{$file} = $hash;
101     }
102     close HASHFILE;
103 }
104
105 foreach my $component (@components) {
106     my @paths = split('\n', `./access_pathmap.pl --config $config_file --component $component`);
107
108     # if no hash file was supplied, grab git hashes from repo
109     if (!$hash_file) {
110
111         # load git repo
112         die "No repo specified\n" unless ($repo_path);
113         $repo_path =~ s|/$||;
114         $repo_path = "$repo_path/.git" unless ($repo_path =~ /\.git$/);
115         my $repo = Git::Repository->new( git_dir => $repo_path ) or die "Could not load git repo $repo_path: $!\n";
116
117         # ensure git repo is up-to-date
118         if ($branch ne 'HEAD') {
119             $repo->run( 'pull' => $remote );
120             $repo->run( 'checkout' => $branch ); # TODO: is this necessary?
121         }
122
123         # get hashes from git
124         foreach my $srcpath (@paths) {
125             # use git-ls-tree to traverse the file tree starting at $srcpath
126             # e.g. `git ls-tree -r HEAD Open-ILS/src/perlmods/lib`
127             my @tree = $repo->run( 'ls-tree' => '-r', $branch, $srcpath );
128             foreach my $file (@tree) {
129                 my ($mode, $type, $hash, $filename) = split(/\s+/, $file, 4);
130                 $git_hashes{$filename} = $hash;
131                 print GITOUTPUT $hash, "\t", $filename, "\n" if ($git_output);
132             }
133         }
134     }
135
136     # check deployed files
137     if ($check_files || $deployed_output) {
138         foreach my $srcpath (@paths) {
139             my $destpath = `./access_pathmap.pl --config $config_file --component $component --srcpath $srcpath`;
140
141             # for each file in the destination path, push the file's absolute path to @files;
142             # output will include symlinked files, but will not include directories
143             # clear @files for each time through loop
144             my @files = ();
145             find( { wanted => sub { push @files, $_ if -f }, follow => 1, no_chdir => 1 }, $destpath );
146
147             foreach my $file (@files) {
148                 if ($since) {
149                     # convert $since to seconds since epoch
150                     my $since_ts = UnixDate($since, '%s');
151                     # get $file modification time as seconds since epoch
152                     my $file_ts = ctime(stat($file)->mtime);
153
154                     next unless $file_ts > $since_ts;
155                 }
156
157                 # you can calculate what the git hash would be
158                 # for any file using `git hash-object <file>`;
159                 # you don't even need to be in a git repo to run it!
160                 my $hash = Git::Repository->run( 'hash-object', $file );
161
162                 if ($check_files) {
163                     my $srcfile = $file;
164                     $srcfile =~ s|^$destpath|$srcpath|;
165
166                     if (!$git_hashes{$srcfile}) {
167                         print "untracked\t$file\n";
168                     } elsif ($git_hashes{$srcfile} ne $hash) {
169                         print "modified\t$file\n";
170                     }
171                 }
172                 print DEPLOYEDOUTPUT $hash, "\t", $file, "\n" if ($deployed_output);
173
174             }
175         }
176     }
177 }
178
179 close (GITOUTPUT) if ($git_output);
180 close (DEPLOYEDOUTPUT) if ($deployed_output);
181