File Coverage

blib/lib/Config/Model/Backend/Systemd.pm
Criterion Covered Total %
statement 108 132 81.8
branch 28 44 63.6
condition 13 24 54.1
subroutine 13 14 92.8
pod 2 5 40.0
total 164 219 74.8


line stmt bran cond sub pod time code
1             #
2             # This file is part of Config-Model-Systemd
3             #
4             # This software is Copyright (c) 2008-2022 by Dominique Dumont.
5             #
6             # This is free software, licensed under:
7             #
8             # The GNU Lesser General Public License, Version 2.1, February 1999
9             #
10             $Config::Model::Backend::Systemd::VERSION = '0.252.1';
11             use strict;
12 2     2   9196 use warnings;
  2         6  
  2         74  
13 2     2   12 use 5.020;
  2         5  
  2         53  
14 2     2   70 use Mouse ;
  2         11  
15 2     2   13 use Log::Log4perl qw(get_logger :levels);
  2         4  
  2         20  
16 2     2   1300 use Path::Tiny 0.086;
  2         6  
  2         19  
17 2     2   322  
  2         41  
  2         189  
18             extends 'Config::Model::Backend::Any';
19             with 'Config::Model::Backend::Systemd::Layers';
20              
21             use feature qw/postderef signatures/;
22 2     2   17 no warnings qw/experimental::postderef experimental::signatures/;
  2         5  
  2         264  
23 2     2   13  
  2         8  
  2         3775  
24             my $logger = get_logger("Backend::Systemd");
25             my $user_logger = get_logger("User");
26              
27             has config_dir => (
28             is => 'rw',
29             isa => 'Path::Tiny'
30             );
31              
32             has 'annotation' => ( is => 'ro', isa => 'Bool', default => 1 );
33              
34             # TODO: accepts other systemd suffixes
35             my @service_types = qw/service socket/;
36             my $joined_types = join('|', @service_types);
37             my $filter = qr/\.($joined_types)(\.d)?$/;
38              
39             my $self = shift ;
40              
41 26     26 0 76 my $ba = $self->instance->backend_arg;
42             if (not $ba) {
43 26         127 Config::Model::Exception::User->throw(
44 26 50       306 objet => $self->node,
45 0         0 error => "Missing systemd unit to work on. This may be passed as 3rd argument to cme",
46             );
47             }
48             return $ba;
49             }
50 26         99  
51             ## no critic (Subroutines::ProhibitBuiltinHomonyms)
52             my $app = $self->instance->application;
53              
54 21     21 1 2673721 if ($app =~ /file/) {
  21         84  
  21         141  
  21         72  
55 21         149 return $self->read_systemd_files(@args);
56             }
57 21 50       356 else {
58 0         0 return $self->read_systemd_units(@args);
59             }
60             }
61 21         123  
62             # args are:
63             # root => './my_test', # fake root directory, used for tests
64             # config_dir => /etc/foo', # absolute path
65 0     0 0 0 # config_file => 'foo.conf', # file name
  0         0  
  0         0  
  0         0  
66             # file_path => './my_test/etc/foo/foo.conf'
67             # check => yes|no|skip
68              
69             #use Tk::ObjScanner; Tk::ObjScanner::scan_object(\%args) ;
70             my $file = $args{file_path};
71             if (not $file) {
72             Config::Model::Exception::User->throw(
73             objet => $self->node,
74 0         0 error => "Missing systemd file to work on. This may be passed as 3rd argument to cme",
75 0 0       0 );
76 0         0 }
77              
78             $user_logger->warn( "Loading unit file '$file'");
79             my ($service_name, $unit_type) = split /\./, path($file)->basename;
80              
81             my @to_create = $unit_type ? ($unit_type) : @service_types;
82 0         0 foreach my $unit_type (@to_create) {
83 0         0 $logger->debug("registering unit $unit_type name $service_name from file name");
84             $self->node->load(step => qq!$unit_type:"$service_name"!, check => $args{check} ) ;
85 0 0       0 }
86 0         0 return 1;
87 0         0 }
88 0         0  
89             # args are:
90 0         0 # root => './my_test', # fake root directory, used for tests
91             # config_dir => /etc/foo', # absolute path
92             # config_file => 'foo.conf', # file name
93 21     21 0 77 # file_path => './my_test/etc/foo/foo.conf'
  21         54  
  21         172  
  21         57  
94             # check => yes|no|skip
95              
96             my $app = $self->instance->application;
97              
98             my $select_unit = $self->get_backend_arg;
99             if (not $select_unit) {
100             Config::Model::Exception::User->throw(
101 21         103 objet => $self->node,
102             error => "Missing systemd unit to work on. This must be passed as 3rd argument to cme",
103 21         215 );
104 21 50       96 }
105 0         0  
106             if ($app ne 'systemd-user' and $select_unit =~ $filter) {
107             my $unit_type = $1;
108             if ($app eq 'systemd') {
109             Config::Model::Exception::User->throw(
110             objet => $self->node,
111 21 50 66     244 error => "With 'systemd' app, unit name should not specify '$unit_type'. "
112 0         0 ." Use 'systemd-$unit_type' app if you want to act only on $select_unit",
113 0 0       0 );
    0          
114 0         0 }
115             elsif ($app ne 'systemd-$unit_type') {
116             Config::Model::Exception::User->throw(
117             objet => $self->node,
118             error => "Unit name $select_unit does not match app $app"
119             );
120             }
121 0         0 }
122              
123             if ($select_unit ne '*') {
124             $user_logger->warn( "Loading unit matching '$select_unit'");
125             } else {
126             $user_logger->warn("Loading all units...")
127             }
128 21 50       90  
129 21         169 my $root_path = $args{root} || path('/');
130              
131 0         0 # load layers. layered mode is handled by Unit backend. Only a hash
132             # key is created here, so layered mode does not matter
133             foreach my $layer ($self->default_directories) {
134 21   33     425 my $dir = $root_path->child($layer);
135             next unless $dir->is_dir;
136             $self->config_dir($dir);
137              
138 21         137 foreach my $file ($dir->children($filter) ) {
139 95         16968 my $file_name = $file->basename();
140 95 100       3623 my $unit_name = $file->basename($filter);
141 20         573 $logger->trace( "checking unit $file_name from $file (layered mode) against $select_unit");
142             if ($select_unit ne '*' and $file_name !~ /$select_unit/) {
143 20         116 $logger->trace( "unit $file_name from $file (layered mode) does not match $select_unit");
144 28         11489 next;
145 28         840 }
146 28         922 my ($unit_type) = ($file =~ $filter);
147 28 100 66     738 $logger->debug( "registering unit $unit_type name $unit_name from $file (layered mode))");
148 8         28 # force config_dir during init
149 8         96 $self->node->load(step => qq!$unit_type:"$unit_name"!, check => $args{check} ) ;
150             }
151 20         112 }
152 20         239  
153             my $dir = $root_path->child($args{config_dir});
154 20         409  
155             if (not $dir->is_dir) {
156             $logger->debug("skipping missing directory $dir");
157             return 1 ;
158 21         22572 }
159              
160 21 100       935 $self->config_dir($dir);
161 3         86 my $found = 0;
162 3         89 foreach my $file ($dir->children($filter) ) {
163             my ($unit_type,$dot_d) = ($file =~ $filter);
164             my $file_name = $file->basename();
165 18         612 my $unit_name = $file->basename($filter);
166 18         79  
167 18         85 next if ($select_unit ne '*' and $file_name !~ /$select_unit/);
168 23         2592 $logger->trace( "checking $file against $select_unit");
169 23         269  
170 23         666 if ($file->realpath eq '/dev/null') {
171             $logger->warn("unit $unit_type name $unit_name from $file is disabled");
172 23 100 66     1077 $self->node->load(step => qq!$unit_type:"$unit_name" disable=1!, check => $args{check} ) ;
173 19         98 }
174             elsif ($dot_d and $file->child('override.conf')->exists) {
175 19 100 66     330 $logger->warn("registering unit $unit_type name $unit_name from override file");
    100          
176 1         256 $self->node->load(step => qq!$unit_type:"$unit_name"!, check => $args{check} ) ;
177 1         22 }
178             else {
179             $logger->warn("registering unit $unit_type name $unit_name from $file");
180 8         2335 $self->node->load(step => qq!$unit_type:"$unit_name"!, check => $args{check} ) ;
181 8         140 }
182             $found++;
183             }
184 10         2890  
185 10         248 if (not $found) {
186             # no service exists, let's create them.
187 19         47548 $user_logger->warn( "No unit '$select_unit' found in $dir, creating one...");
188             my ($service_name, $unit_type) = split /\./, $select_unit;
189             my @to_create = $unit_type ? ($unit_type) : @service_types;
190 18 100       210 $service_name //= $select_unit;
191             foreach my $unit_type (@to_create) {
192 2         15 $logger->debug("registering unit $unit_type name $service_name from scratch");
193 2         41 $self->node->load(step => qq!$unit_type:"$service_name"!, check => $args{check} ) ;
194 2 100       13 }
195 2   33     8 }
196 2         8 return 1 ;
197 3         762 }
198 3         46  
199             # args are:
200             # root => './my_test', # fake root directory, userd for tests
201 18         4225 # config_dir => /etc/foo', # absolute path
202             # file => 'foo.conf', # file name
203             # file_path => './my_test/etc/foo/foo.conf'
204 12     12 1 4885955 # check => yes|no|skip
  12         41  
  12         78  
  12         40  
205              
206             # file write is handled by Unit backend
207             return 1 if $self->instance->application =~ /file/;
208             return 1 if $self->instance->application eq 'systemd';
209              
210             my $root_path = $args{root} || path('/');
211             my $dir = $args{root}->path($args{config_dir});
212             die "Unknown directory $dir" unless $dir->is_dir;
213 12 50       102  
214 12 100       256 my $select_unit = $self->get_backend_arg;
215              
216 5   33     92 # delete files for non-existing elements (deleted services)
217 5         38 foreach my $file ($dir->children($filter) ) {
218 5 50       239 my ($unit_type) = ($file =~ $filter);
219             my $unit_name = $file->basename($filter);
220 5         171  
221             next if ($select_unit ne '*' and $unit_name !~ /$select_unit/);
222              
223 5         34 my $unit_collection = $self->node->fetch_element($unit_type);
224 4         892 if (not $unit_collection->defined($unit_name)) {
225 4         64 $user_logger->warn("removing file $file of deleted service");
226             $file->remove;
227 4 100 66     419 }
228             }
229 3         109  
230 3 50       303 return 1;
231 0         0 }
232 0         0  
233             no Mouse ;
234             __PACKAGE__->meta->make_immutable ;
235              
236 5         239 1;
237              
238             # ABSTRACT: R/W backend for systemd configurations files
239 2     2   39  
  2         6  
  2         12  
240              
241             =pod
242              
243             =encoding UTF-8
244              
245             =head1 NAME
246              
247             Config::Model::Backend::Systemd - R/W backend for systemd configurations files
248              
249             =head1 VERSION
250              
251             version 0.252.1
252              
253             =head1 SYNOPSIS
254              
255             # in systemd model
256             rw_config => {
257             'backend' => 'Systemd'
258             }
259              
260             =head1 DESCRIPTION
261              
262             Config::Model::Backend::Systemd provides a plugin class to enable
263             L<Config::Model> to read and write systemd configuration files. This
264             class inherits L<Config::Model::Backend::Any> is designed to be used
265             by L<Config::Model::BackendMgr>.
266              
267             =head1 Methods
268              
269             =head2 read
270              
271             This method scans systemd default directory and systemd config
272             directory to create all units in L<Config::Model> tree. The actual
273             configuration parameters are read by
274             L<Config::Model::Backend::Systemd::Unit>.
275              
276             =head2 write
277              
278             This method is a bit of a misnomer. It deletes configuration files of
279             deleted service.
280              
281             The actual configuration parameters are written by
282             L<Config::Model::Backend::Systemd::Unit>.
283              
284             =head1 AUTHOR
285              
286             Dominique Dumont
287              
288             =head1 COPYRIGHT AND LICENSE
289              
290             This software is Copyright (c) 2008-2022 by Dominique Dumont.
291              
292             This is free software, licensed under:
293              
294             The GNU Lesser General Public License, Version 2.1, February 1999
295              
296             =cut