File Coverage

blib/lib/IO/K8s/Role/Routable.pm
Criterion Covered Total %
statement 70 103 67.9
branch 19 50 38.0
condition 26 52 50.0
subroutine 5 5 100.0
pod 0 4 0.0
total 120 214 56.0


line stmt bran cond sub pod time code
1             package IO::K8s::Role::Routable;
2             # ABSTRACT: Role for building HTTP/gRPC routing rules
3             our $VERSION = '1.008';
4 9     9   6222 use Moo::Role;
  9         25  
  9         89  
5              
6             requires '_route_format';
7              
8             sub add_hostname {
9 4     4 0 575424 my ($self, @hostnames) = @_;
10 4         18 my $format = $self->_route_format;
11              
12 4 100       15 if ($format eq 'gateway') {
    50          
    0          
13 3   100     49 my $spec = $self->spec // {};
14 3   50     30 my $existing = $spec->{hostnames} // [];
15 3         7 push @$existing, @hostnames;
16 3         6 $spec->{hostnames} = $existing;
17 3         36 $self->spec($spec);
18             } elsif ($format eq 'traefik') {
19             # Traefik uses match rules like Host(`example.com`)
20             # We add a route with the host match
21 1   50     17 my $spec = $self->spec // {};
22 1   50     12 my $routes = $spec->{routes} //= [];
23 1         2 my $hosts = join ', ', map { "Host(`$_`)" } @hostnames;
  2         7  
24 1         5 push @$routes, { match => $hosts, kind => 'Rule', services => [] };
25 1         13 $self->spec($spec);
26             } elsif ($format eq 'ingress') {
27 0         0 my $spec = $self->spec;
28 0 0       0 unless ($spec) {
29 0         0 require IO::K8s::Api::Networking::V1::IngressSpec;
30 0         0 $spec = IO::K8s::Api::Networking::V1::IngressSpec->new;
31 0         0 $self->spec($spec);
32             }
33 0   0     0 my $rules = $spec->rules // [];
34 0         0 for my $hostname (@hostnames) {
35 0         0 push @$rules, IO::K8s::Api::Networking::V1::IngressRule->new(
36             host => $hostname,
37             );
38             }
39 0         0 $spec->rules($rules);
40             }
41 4         88 return $self;
42             }
43              
44             sub add_backend {
45 6     6 0 7441 my ($self, $name, %opts) = @_;
46 6         17 my $format = $self->_route_format;
47              
48 6 100       20 if ($format eq 'gateway') {
    50          
    0          
49 4   50     47 my $spec = $self->spec // {};
50 4   100     28 my $rules = $spec->{rules} //= [{}];
51 4         5 my $rule = $rules->[-1];
52 4   100     11 my $backends = $rule->{backendRefs} //= [];
53             push @$backends, {
54             name => $name,
55             $opts{port} ? (port => $opts{port}) : (),
56 4 50       16 $opts{weight} ? (weight => $opts{weight}) : (),
    100          
57             };
58 4         44 $self->spec($spec);
59             } elsif ($format eq 'traefik') {
60 2   50     26 my $spec = $self->spec // {};
61 2   50     17 my $routes = $spec->{routes} //= [{}];
62 2         3 my $route = $routes->[-1];
63 2   50     3 my $services = $route->{services} //= [];
64             push @$services, {
65             name => $name,
66             $opts{port} ? (port => $opts{port}) : (),
67 2 50       8 $opts{weight} ? (weight => $opts{weight}) : (),
    50          
68             };
69 2         22 $self->spec($spec);
70             } elsif ($format eq 'ingress') {
71             # For Ingress, add to the last rule's paths
72 0         0 my $spec = $self->spec;
73 0 0       0 unless ($spec) {
74 0         0 require IO::K8s::Api::Networking::V1::IngressSpec;
75 0         0 $spec = IO::K8s::Api::Networking::V1::IngressSpec->new;
76 0         0 $self->spec($spec);
77             }
78             $spec->defaultBackend(IO::K8s::Api::Networking::V1::IngressBackend->new(
79             service => IO::K8s::Api::Networking::V1::IngressServiceBackend->new(
80             name => $name,
81             port => IO::K8s::Api::Networking::V1::ServiceBackendPort->new(
82             number => $opts{port},
83 0         0 ),
84             ),
85             ));
86             }
87 6         95 return $self;
88             }
89              
90             sub add_path_match {
91 2     2 0 13840 my ($self, $path, %opts) = @_;
92 2   50     6 my $type = $opts{type} // 'Prefix';
93 2         8 my $format = $self->_route_format;
94              
95 2 100       7 if ($format eq 'gateway') {
    50          
    0          
96 1   50     17 my $spec = $self->spec // {};
97 1   50     11 my $rules = $spec->{rules} //= [{}];
98 1         2 my $rule = $rules->[-1];
99 1   50     5 my $matches = $rule->{matches} //= [];
100 1         4 push @$matches, {
101             path => { type => $type, value => $path },
102             };
103 1         39 $self->spec($spec);
104             } elsif ($format eq 'traefik') {
105 1   50     14 my $spec = $self->spec // {};
106 1   50     8 my $routes = $spec->{routes} //= [{}];
107 1         3 my $route = $routes->[-1];
108 1 50       3 if ($type eq 'Prefix') {
    0          
    0          
109 1         3 $route->{match} = "PathPrefix(`$path`)";
110             } elsif ($type eq 'Exact') {
111 0         0 $route->{match} = "Path(`$path`)";
112             } elsif ($type eq 'Regex') {
113 0         0 $route->{match} = "PathRegexp(`$path`)";
114             }
115 1         12 $self->spec($spec);
116             } elsif ($format eq 'ingress') {
117 0         0 my $spec = $self->spec;
118 0 0       0 unless ($spec) {
119 0         0 require IO::K8s::Api::Networking::V1::IngressSpec;
120 0         0 $spec = IO::K8s::Api::Networking::V1::IngressSpec->new;
121 0         0 $self->spec($spec);
122             }
123 0   0     0 my $rules = $spec->rules // [];
124             # Add path to the last rule, or create new one
125 0 0       0 my $rule = @$rules ? $rules->[-1] : IO::K8s::Api::Networking::V1::IngressRule->new;
126 0 0       0 push @$rules, $rule unless @$rules;
127 0         0 my $http = $rule->http;
128 0 0       0 unless ($http) {
129 0         0 $http = IO::K8s::Api::Networking::V1::HTTPIngressRuleValue->new(paths => []);
130 0         0 $rule->http($http);
131             }
132 0   0     0 my $paths = $http->paths // [];
133 0         0 push @$paths, IO::K8s::Api::Networking::V1::HTTPIngressPath->new(
134             path => $path,
135             pathType => $type,
136             );
137 0         0 $http->paths($paths);
138 0         0 $spec->rules($rules);
139             }
140 2         114 return $self;
141             }
142              
143             sub add_header_match {
144 2     2 0 7736 my ($self, $header, $value) = @_;
145 2         9 my $format = $self->_route_format;
146              
147 2 100       40 if ($format eq 'gateway') {
    50          
148 1   50     14 my $spec = $self->spec // {};
149 1   50     11 my $rules = $spec->{rules} //= [{}];
150 1         3 my $rule = $rules->[-1];
151 1   50     12 my $matches = $rule->{matches} //= [{}];
152 1         2 my $match = $matches->[-1];
153 1   50     5 my $headers = $match->{headers} //= [];
154 1         3 push @$headers, { name => $header, value => $value };
155 1         14 $self->spec($spec);
156             } elsif ($format eq 'traefik') {
157 1   50     18 my $spec = $self->spec // {};
158 1   50     7 my $routes = $spec->{routes} //= [{}];
159 1         2 my $route = $routes->[-1];
160 1   50     3 my $existing = $route->{match} // '';
161 1         2 my $header_match = "Header(`$header`, `$value`)";
162 1 50       3 $route->{match} = $existing ? "$existing && $header_match" : $header_match;
163 1         12 $self->spec($spec);
164             }
165             # Ingress doesn't support header matching natively
166 2         36 return $self;
167             }
168              
169             1;
170              
171             __END__