File: Systemd.pm

package info (click to toggle)
libconfig-model-systemd-perl 0.257.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,084 kB
  • sloc: perl: 13,439; makefile: 12
file content (251 lines) | stat: -rw-r--r-- 8,201 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
package Config::Model::Backend::Systemd ;

use strict;
use warnings;
use 5.020;
use Mouse ;
use Log::Log4perl qw(get_logger :levels);
use Path::Tiny 0.086;

extends 'Config::Model::Backend::Any';
with 'Config::Model::Backend::Systemd::Layers';

use feature qw/postderef signatures/;
no warnings qw/experimental::postderef experimental::signatures/;

my $logger = get_logger("Backend::Systemd");
my $user_logger = get_logger("User");

has config_dir => (
    is => 'rw',
    isa => 'Path::Tiny'
);

has 'annotation' => ( is => 'ro', isa => 'Bool', default => 1 );

# TODO: accepts other systemd suffixes
my @service_types = qw/service socket timer/;
my $joined_types = join('|', @service_types);
my $filter = qr/\.($joined_types)(\.d)?$/;

sub get_backend_arg {
    my $self = shift ;

    my $ba = $self->instance->backend_arg;
    if (not $ba) {
        Config::Model::Exception::User->throw(
            objet => $self->node,
            error => "Missing systemd unit to work on. This may be passed as 3rd argument to cme",
        );
    }
    return $ba;
}

## no critic (Subroutines::ProhibitBuiltinHomonyms)
sub read ($self, @args) {
    my $app = $self->instance->application;

    if ($app =~ /file/) {
        return $self->read_systemd_files(@args);
    }
    else {
        return $self->read_systemd_units(@args);
    }
}

sub read_systemd_files ($self, %args) {
    # args are:
    # root       => './my_test',  # fake root directory, used for tests
    # config_dir => /etc/foo',    # absolute path
    # config_file       => 'foo.conf',   # file name
    # file_path  => './my_test/etc/foo/foo.conf'
    # check      => yes|no|skip

    #use Tk::ObjScanner; Tk::ObjScanner::scan_object(\%args) ;
    my $file = $args{file_path};
    if (not $file) {
        Config::Model::Exception::User->throw(
            objet => $self->node,
            error => "Missing systemd file to work on. This may be passed as 3rd argument to cme",
        );
    }

    $user_logger->info( "Loading unit file '$file'");
    my ($service_name, $unit_type) =  split /\./, path($file)->basename;

    my @to_create = $unit_type ? ($unit_type) : @service_types;
    foreach my $unit_type (@to_create) {
        $logger->debug("registering unit $unit_type name $service_name from file name");
        $self->node->load(step => qq!$unit_type:"$service_name"!, check => $args{check} ) ;
    }
    return 1;
}

sub read_systemd_units ($self, %args) {
    # args are:
    # root       => './my_test',  # fake root directory, used for tests
    # config_dir => /etc/foo',    # absolute path
    # config_file       => 'foo.conf',   # file name
    # file_path  => './my_test/etc/foo/foo.conf'
    # check      => yes|no|skip

    my $app = $self->instance->application;

    my $select_unit = $self->get_backend_arg;
    if (not $select_unit) {
        Config::Model::Exception::User->throw(
            objet => $self->node,
            error => "Missing systemd unit to work on. This must be passed as 3rd argument to cme",
        );
    }

    if ($app ne 'systemd-user' and $select_unit =~ $filter) {
        my $unit_type = $1;
        if ($app eq 'systemd') {
            Config::Model::Exception::User->throw(
                objet => $self->node,
                error => "With 'systemd' app, unit name should not specify '$unit_type'. "
                ." Use 'systemd-$unit_type' app if you want to act only on $select_unit",
            );
        }
        elsif ($app ne 'systemd-$unit_type') {
            Config::Model::Exception::User->throw(
                objet => $self->node,
                error => "Unit name $select_unit does not match app $app"
            );
        }
    }

    if ($select_unit ne '*') {
        $user_logger->info( "Loading unit matching '$select_unit'");
    } else {
        $user_logger->info("Loading all units...")
    }

    my $root_path = $args{root} || path('/');

    # load layers. layered mode is handled by Unit backend. Only a hash
    # key is created here, so layered mode does not matter
    foreach my $layer ($self->default_directories) {
        my $dir = $root_path->child($layer);
        next unless $dir->is_dir;
        $self->config_dir($dir);

        foreach my $file ($dir->children($filter) ) {
            my $file_name = $file->basename();
            my $unit_name = $file->basename($filter);
            $logger->trace( "checking unit $file_name from $file (layered mode) against $select_unit");
            if ($select_unit ne '*' and $file_name !~ /$select_unit/) {
                $logger->trace( "unit $file_name from $file (layered mode) does not match $select_unit");
                next;
            }
            my ($unit_type) = ($file =~ $filter);
            $logger->debug( "registering unit $unit_type name $unit_name from $file (layered mode))");
            # force config_dir during init
            $self->node->load(step => qq!$unit_type:"$unit_name"!, check => $args{check} ) ;
        }
    }

    my $dir = $root_path->child($args{config_dir});

    if (not $dir->is_dir) {
        $logger->debug("skipping missing directory $dir");
        return 1 ;
    }

    $self->config_dir($dir);
    my $found = 0;
    foreach my $file ($dir->children($filter) ) {
        $logger->trace("Scanning unit dir $dir with filter $filter");
        my ($unit_type,$dot_d) = ($file =~ $filter);
        my $file_name = $file->basename();
        my $unit_name = $file->basename($filter);

        next if ($select_unit ne '*' and $file_name !~ /$select_unit/);
        $logger->trace( "checking $file against $select_unit");

        if ($file->realpath eq '/dev/null') {
            $logger->info("unit $unit_type name $unit_name from $file is disabled");
            $self->node->load(step => qq!$unit_type:"$unit_name" disable=1!, check => $args{check} ) ;
        }
        elsif ($dot_d and $file->child('override.conf')->exists) {
            $logger->info("registering unit $unit_type name $unit_name from override file");
            $self->node->load(step => qq!$unit_type:"$unit_name"!, check => $args{check} ) ;
        }
        else {
            $logger->info("registering unit $unit_type name $unit_name from $file");
            $self->node->load(step => qq!$unit_type:"$unit_name"!, check => $args{check} ) ;
        }
        $found++;
    }

    if (not $found) {
        # no service exists, let's create them.
        $user_logger->info( "No unit '$select_unit' found in $dir, creating one...");
        my ($service_name, $unit_type) =  split /\./, $select_unit;
        my @to_create = $unit_type ? ($unit_type) : @service_types;
        $service_name //= $select_unit;
        foreach my $unit_type (@to_create) {
            $logger->debug("registering unit $unit_type name $service_name from scratch");
            $self->node->load(step => qq!$unit_type:"$service_name"!, check => $args{check} ) ;
        }
    }
    return 1 ;
}

sub write ($self, %args) {
    # args are:
    # root       => './my_test',  # fake root directory, userd for tests
    # config_dir => /etc/foo',    # absolute path
    # file       => 'foo.conf',   # file name
    # file_path  => './my_test/etc/foo/foo.conf'
    # check      => yes|no|skip

    # file write is handled by Unit backend
    return 1;
}

no Mouse ;
__PACKAGE__->meta->make_immutable ;

1;

# ABSTRACT: R/W backend for systemd configurations files

__END__

=pod

=head1 SYNOPSIS

 # in systemd model
 rw_config => {
     'backend' => 'Systemd'
 }

=head1 DESCRIPTION

Config::Model::Backend::Systemd provides a plugin class to enable
L<Config::Model> to read and write systemd configuration files. This
class inherits L<Config::Model::Backend::Any> is designed to be used
by L<Config::Model::BackendMgr>.

=head1 Methods

=head2 read

This method scans systemd default directory and systemd config
directory to create all units in L<Config::Model> tree. The actual
configuration parameters are read by
L<Config::Model::Backend::Systemd::Unit>.

=head2 write

This method is a bit of a misnomer. It deletes configuration files of
deleted service.

The actual configuration parameters are written by
L<Config::Model::Backend::Systemd::Unit>.

=cut