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.251.1';
11             use strict;
12 2     2   6326 use warnings;
  2         5  
  2         48  
13 2     2   8 use 5.020;
  2         4  
  2         43  
14 2     2   32 use Mouse ;
  2         7  
15 2     2   8 use Log::Log4perl qw(get_logger :levels);
  2         4  
  2         12  
16 2     2   793 use Path::Tiny 0.086;
  2         4  
  2         13  
17 2     2   214  
  2         29  
  2         133  
18             extends 'Config::Model::Backend::Any';
19             with 'Config::Model::Backend::Systemd::Layers';
20              
21             use feature qw/postderef signatures/;
22 2     2   12 no warnings qw/experimental::postderef experimental::signatures/;
  2         3  
  2         182  
23 2     2   11  
  2         3  
  2         2526  
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 88 my $ba = $self->instance->backend_arg;
42             if (not $ba) {
43 26         88 Config::Model::Exception::User->throw(
44 26 50       316 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         74  
51             ## no critic (Subroutines::ProhibitBuiltinHomonyms)
52             my $app = $self->instance->application;
53              
54 21     21 1 2015675 if ($app =~ /file/) {
  21         50  
  21         133  
  21         42  
55 21         97 return $self->read_systemd_files(@args);
56             }
57 21 50       367 else {
58 0         0 return $self->read_systemd_units(@args);
59             }
60             }
61 21         93  
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 55 # file_path => './my_test/etc/foo/foo.conf'
  21         52  
  21         122  
  21         37  
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         72 objet => $self->node,
102             error => "Missing systemd unit to work on. This must be passed as 3rd argument to cme",
103 21         236 );
104 21 50       83 }
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     170 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       95  
129 21         130 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     277 my $dir = $root_path->child($layer);
135             next unless $dir->is_dir;
136             $self->config_dir($dir);
137              
138 21         122 foreach my $file ($dir->children($filter) ) {
139 95         12175 my $file_name = $file->basename();
140 95 100       2593 my $unit_name = $file->basename($filter);
141 20         402 $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         77 $logger->trace( "unit $file_name from $file (layered mode) does not match $select_unit");
144 28         8188 next;
145 28         675 }
146 28         686 my ($unit_type) = ($file =~ $filter);
147 28 100 66     618 $logger->debug( "registering unit $unit_type name $unit_name from $file (layered mode))");
148 8         24 # force config_dir during init
149 8         67 $self->node->load(step => qq!$unit_type:"$unit_name"!, check => $args{check} ) ;
150             }
151 20         118 }
152 20         168  
153             my $dir = $root_path->child($args{config_dir});
154 20         330  
155             if (not $dir->is_dir) {
156             $logger->debug("skipping missing directory $dir");
157             return 1 ;
158 21         18006 }
159              
160 21 100       787 $self->config_dir($dir);
161 3         46 my $found = 0;
162 3         60 foreach my $file ($dir->children($filter) ) {
163             my ($unit_type,$dot_d) = ($file =~ $filter);
164             my $file_name = $file->basename();
165 18         445 my $unit_name = $file->basename($filter);
166 18         49  
167 18         74 next if ($select_unit ne '*' and $file_name !~ /$select_unit/);
168 23         2375 $logger->trace( "checking $file against $select_unit");
169 23         202  
170 23         557 if ($file->realpath eq '/dev/null') {
171             $logger->warn("unit $unit_type name $unit_name from $file is disabled");
172 23 100 66     718 $self->node->load(step => qq!$unit_type:"$unit_name" disable=1!, check => $args{check} ) ;
173 19         75 }
174             elsif ($dot_d and $file->child('override.conf')->exists) {
175 19 100 66     257 $logger->warn("registering unit $unit_type name $unit_name from override file");
    100          
176 1         189 $self->node->load(step => qq!$unit_type:"$unit_name"!, check => $args{check} ) ;
177 1         50 }
178             else {
179             $logger->warn("registering unit $unit_type name $unit_name from $file");
180 8         1678 $self->node->load(step => qq!$unit_type:"$unit_name"!, check => $args{check} ) ;
181 8         97 }
182             $found++;
183             }
184 10         2053  
185 10         241 if (not $found) {
186             # no service exists, let's create them.
187 19         39150 $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       150 $service_name //= $select_unit;
191             foreach my $unit_type (@to_create) {
192 2         10 $logger->debug("registering unit $unit_type name $service_name from scratch");
193 2         30 $self->node->load(step => qq!$unit_type:"$service_name"!, check => $args{check} ) ;
194 2 100       10 }
195 2   33     8 }
196 2         5 return 1 ;
197 3         615 }
198 3         34  
199             # args are:
200             # root => './my_test', # fake root directory, userd for tests
201 18         3330 # 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 3609974 # check => yes|no|skip
  12         33  
  12         51  
  12         24  
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       70  
214 12 100       304 my $select_unit = $self->get_backend_arg;
215              
216 5   33     79 # delete files for non-existing elements (deleted services)
217 5         31 foreach my $file ($dir->children($filter) ) {
218 5 50       162 my ($unit_type) = ($file =~ $filter);
219             my $unit_name = $file->basename($filter);
220 5         99  
221             next if ($select_unit ne '*' and $unit_name !~ /$select_unit/);
222              
223 5         45 my $unit_collection = $self->node->fetch_element($unit_type);
224 4         656 if (not $unit_collection->defined($unit_name)) {
225 4         50 $user_logger->warn("removing file $file of deleted service");
226             $file->remove;
227 4 100 66     370 }
228             }
229 3         22  
230 3 50       348 return 1;
231 0         0 }
232 0         0  
233             no Mouse ;
234             __PACKAGE__->meta->make_immutable ;
235              
236 5         235 1;
237              
238             # ABSTRACT: R/W backend for systemd configurations files
239 2     2   31  
  2         4  
  2         10  
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.251.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