Deployed 5c278732 to v0.20.0 with MkDocs 1.5.3 and mike 2.0.0

This commit is contained in:
github-actions[bot] 2025-11-14 09:25:09 +00:00
parent 89cb0f13d2
commit 001a9fe1b6
521 changed files with 480271 additions and 0 deletions

3152
v0.20.0/404.html Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

3403
v0.20.0/LICENSE/index.html Normal file

File diff suppressed because it is too large Load Diff

265
v0.20.0/api/webhook.yaml Normal file
View File

@ -0,0 +1,265 @@
---
openapi: "3.0.0"
info:
version: v0.15.0
title: External DNS Webhook Server
description: >-
Implements the external DNS webhook endpoints.
contact:
url: https://github.com/kubernetes-sigs/external-dns
license:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0.html
tags:
- name: initialization
description: Endpoints for initial negotiation.
- name: listing
description: Endpoints to get listings of DNS records.
- name: update
description: Endpoints to update DNS records.
servers:
- url: http://localhost:8888
description: Server url for a Kubernetes deployment.
paths:
/:
get:
summary: >-
Initialisation and negotiates headers and returns domain
filter.
description: |
Initialisation and negotiates headers and returns domain
filter.
operationId: negotiate
tags: [initialization]
responses:
'200':
description: |
The list of domains this DNS provider serves.
content:
application/external.dns.webhook+json;version=1:
schema:
$ref: '#/components/schemas/filters'
example:
filters:
- example.com
'500':
description: |
Negotiation failed.
/records:
get:
summary: Returns the current records.
description: |
Get the current records from the DNS provider and return them.
operationId: getRecords
tags: [listing]
responses:
'200':
description: |
Provided the list of DNS records successfully.
content:
application/external.dns.webhook+json;version=1:
schema:
$ref: '#/components/schemas/endpoints'
example:
- dnsName: "test.example.com"
recordTTL: 10
recordType: 'A'
targets:
- "1.2.3.4"
'500':
description: |
Failed to provide the list of DNS records.
post:
summary: Applies the changes.
description: |
Set the records in the DNS provider based on those supplied here.
operationId: setRecords
tags: [update]
requestBody:
description: |
This is the list of changes that need to be applied. There are
four lists of endpoints. The `create` and `delete` lists are lists
of records to create and delete respectively. The `updateOld` and
`updateNew` lists are paired. For each entry there's the old version
of the record and a new version of the record.
required: true
content:
application/external.dns.webhook+json;version=1:
schema:
$ref: '#/components/schemas/changes'
example:
create:
- dnsName: "test.example.com"
recordTTL: 10
recordType: 'A'
responses:
'204':
description: |
Changes were accepted.
'500':
description: |
Changes were not accepted.
/adjustendpoints:
post:
summary: Executes the AdjustEndpoints method.
description: |
Adjusts the records in the provider based on those supplied here.
operationId: adjustRecords
tags: [update]
requestBody:
description: |
This is the list of changes to be applied.
required: true
content:
application/external.dns.webhook+json;version=1:
schema:
$ref: '#/components/schemas/endpoints'
example:
- dnsName: "test.example.com"
recordTTL: 10
recordType: 'A'
targets:
- "1.2.3.4"
responses:
'200':
description: |
Adjustments were accepted.
content:
application/external.dns.webhook+json;version=1:
schema:
$ref: '#/components/schemas/endpoints'
example:
- dnsName: "test.example.com"
recordTTL: 0
recordType: 'A'
targets:
- "1.2.3.4"
'500':
description: |
Adjustments were not accepted.
components:
schemas:
filters:
description: |
external-dns will only create DNS records for host names (specified in ingress objects and services with the external-dns annotation) related to zones that match filters. They can set in external-dns deployment manifest.
type: object
properties:
filters:
type: array
items:
type: string
example: "foo.example.com"
example:
- ".example.com"
example:
filters:
- ".example.com"
- ".example.org"
endpoints:
description: |
This is a list of DNS records.
type: array
items:
$ref: '#/components/schemas/endpoint'
example:
- dnsName: foo.example.com
recordType: A
recordTTL: 60
endpoint:
description: |
This is a DNS record.
type: object
properties:
dnsName:
type: string
example: "foo.example.org"
targets:
$ref: '#/components/schemas/targets'
recordType:
type: string
example: "CNAME"
setIdentifier:
type: string
example: "v1"
recordTTL:
type: integer
format: int64
example: 60
labels:
type: object
additionalProperties:
type: string
example: "foo"
example:
foo: bar
providerSpecific:
type: array
items:
$ref: '#/components/schemas/providerSpecificProperty'
example:
- name: foo
value: bar
example:
dnsName: foo.example.com
recordType: A
recordTTL: 60
targets:
description: |
This is the list of targets that this DNS record points to.
So for an A record it will be a list of IP addresses.
type: array
items:
type: string
example: "::1"
example:
- "1.2.3.4"
- "test.example.org"
providerSpecificProperty:
description: |
Allows provider to pass property specific to their implementation.
type: object
properties:
name:
type: string
example: foo
value:
type: string
example: bar
example:
name: foo
value: bar
changes:
description: |
This is the list of changes send by `external-dns` that need to
be applied. There are four lists of endpoints. The `create`
and `delete` lists are lists of records to create and delete
respectively. The `updateOld` and `updateNew` lists are paired.
For each entry there's the old version of the record and a new
version of the record.
type: object
properties:
create:
$ref: '#/components/schemas/endpoints'
updateOld:
$ref: '#/components/schemas/endpoints'
updateNew:
$ref: '#/components/schemas/endpoints'
delete:
$ref: '#/components/schemas/endpoints'
example:
create:
- dnsName: foo.example.com
recordType: A
recordTTL: 60
delete:
- dnsName: foo.example.org
recordType: CNAME

4
v0.20.0/apis/OWNERS Normal file
View File

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- apis

17
v0.20.0/apis/api.go Normal file
View File

@ -0,0 +1,17 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apis

View File

@ -0,0 +1,20 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package v1alpha1 contains API Schema definitions for the externaldns.k8s.io v1alpha1 API group
// +kubebuilder:object:generate=true
// +groupName=externaldns.k8s.io
package v1alpha1

View File

@ -0,0 +1,62 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/external-dns/endpoint"
)
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// DNSEndpoint is a contract that a user-specified CRD must implement to be used as a source for external-dns.
// The user-specified CRD should also have the status sub-resource.
// +k8s:openapi-gen=true
// +groupName=externaldns.k8s.io
// +kubebuilder:resource:path=dnsendpoints
// +kubebuilder:subresource:status
// +kubebuilder:metadata:annotations="api-approved.kubernetes.io=https://github.com/kubernetes-sigs/external-dns/pull/2007"
// +versionName=v1alpha1
type DNSEndpoint struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec DNSEndpointSpec `json:"spec,omitempty"`
Status DNSEndpointStatus `json:"status,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// DNSEndpointList is a list of DNSEndpoint objects
type DNSEndpointList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []DNSEndpoint `json:"items"`
}
// DNSEndpointSpec defines the desired state of DNSEndpoint
type DNSEndpointSpec struct {
Endpoints []*endpoint.Endpoint `json:"endpoints,omitempty"`
}
// DNSEndpointStatus defines the observed state of DNSEndpoint
type DNSEndpointStatus struct {
// The generation observed by the external-dns controller.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
}

View File

@ -0,0 +1,40 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package v1alpha1 contains API Schema definitions for the externaldns.k8s.io v1alpha1 API group
// +kubebuilder:object:generate=true
// +groupName=externaldns.k8s.io
package v1alpha1
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/scheme"
)
var (
// GroupVersion is group version used to register these objects
GroupVersion = schema.GroupVersion{Group: "externaldns.k8s.io", Version: "v1alpha1"}
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
// AddToScheme adds the types in this group-version to the given scheme.
AddToScheme = SchemeBuilder.AddToScheme
)
func init() {
SchemeBuilder.Register(&DNSEndpoint{}, &DNSEndpointList{})
}

View File

@ -0,0 +1,110 @@
//go:build !ignore_autogenerated
// Code generated by controller-gen. DO NOT EDIT.
package v1alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/external-dns/endpoint"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DNSEndpoint) DeepCopyInto(out *DNSEndpoint) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
out.Status = in.Status
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSEndpoint.
func (in *DNSEndpoint) DeepCopy() *DNSEndpoint {
if in == nil {
return nil
}
out := new(DNSEndpoint)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *DNSEndpoint) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DNSEndpointList) DeepCopyInto(out *DNSEndpointList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]DNSEndpoint, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSEndpointList.
func (in *DNSEndpointList) DeepCopy() *DNSEndpointList {
if in == nil {
return nil
}
out := new(DNSEndpointList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *DNSEndpointList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DNSEndpointSpec) DeepCopyInto(out *DNSEndpointSpec) {
*out = *in
if in.Endpoints != nil {
in, out := &in.Endpoints, &out.Endpoints
*out = make([]*endpoint.Endpoint, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(endpoint.Endpoint)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSEndpointSpec.
func (in *DNSEndpointSpec) DeepCopy() *DNSEndpointSpec {
if in == nil {
return nil
}
out := new(DNSEndpointSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DNSEndpointStatus) DeepCopyInto(out *DNSEndpointStatus) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSEndpointStatus.
func (in *DNSEndpointStatus) DeepCopy() *DNSEndpointStatus {
if in == nil {
return nil
}
out := new(DNSEndpointStatus)
in.DeepCopyInto(out)
return out
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,18 @@
/*!
* Lunr languages, `Danish` language
* https://github.com/MihaiValentin/lunr-languages
*
* Copyright 2014, Mihai Valentin
* http://www.mozilla.org/MPL/
*/
/*!
* based on
* Snowball JavaScript Library v0.3
* http://code.google.com/p/urim/
* http://snowball.tartarus.org/
*
* Copyright 2010, Oleg Mazko
* http://www.mozilla.org/MPL/
*/
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.da=function(){this.pipeline.reset(),this.pipeline.add(e.da.trimmer,e.da.stopWordFilter,e.da.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.da.stemmer))},e.da.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA--",e.da.trimmer=e.trimmerSupport.generateTrimmer(e.da.wordCharacters),e.Pipeline.registerFunction(e.da.trimmer,"trimmer-da"),e.da.stemmer=function(){var r=e.stemmerSupport.Among,i=e.stemmerSupport.SnowballProgram,n=new function(){function e(){var e,r=f.cursor+3;if(d=f.limit,0<=r&&r<=f.limit){for(a=r;;){if(e=f.cursor,f.in_grouping(w,97,248)){f.cursor=e;break}if(f.cursor=e,e>=f.limit)return;f.cursor++}for(;!f.out_grouping(w,97,248);){if(f.cursor>=f.limit)return;f.cursor++}d=f.cursor,d<a&&(d=a)}}function n(){var e,r;if(f.cursor>=d&&(r=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,e=f.find_among_b(c,32),f.limit_backward=r,e))switch(f.bra=f.cursor,e){case 1:f.slice_del();break;case 2:f.in_grouping_b(p,97,229)&&f.slice_del()}}function t(){var e,r=f.limit-f.cursor;f.cursor>=d&&(e=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,f.find_among_b(l,4)?(f.bra=f.cursor,f.limit_backward=e,f.cursor=f.limit-r,f.cursor>f.limit_backward&&(f.cursor--,f.bra=f.cursor,f.slice_del())):f.limit_backward=e)}function s(){var e,r,i,n=f.limit-f.cursor;if(f.ket=f.cursor,f.eq_s_b(2,"st")&&(f.bra=f.cursor,f.eq_s_b(2,"ig")&&f.slice_del()),f.cursor=f.limit-n,f.cursor>=d&&(r=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,e=f.find_among_b(m,5),f.limit_backward=r,e))switch(f.bra=f.cursor,e){case 1:f.slice_del(),i=f.limit-f.cursor,t(),f.cursor=f.limit-i;break;case 2:f.slice_from("løs")}}function o(){var e;f.cursor>=d&&(e=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,f.out_grouping_b(w,97,248)?(f.bra=f.cursor,u=f.slice_to(u),f.limit_backward=e,f.eq_v_b(u)&&f.slice_del()):f.limit_backward=e)}var a,d,u,c=[new r("hed",-1,1),new r("ethed",0,1),new r("ered",-1,1),new r("e",-1,1),new r("erede",3,1),new r("ende",3,1),new r("erende",5,1),new r("ene",3,1),new r("erne",3,1),new r("ere",3,1),new r("en",-1,1),new r("heden",10,1),new r("eren",10,1),new r("er",-1,1),new r("heder",13,1),new r("erer",13,1),new r("s",-1,2),new r("heds",16,1),new r("es",16,1),new r("endes",18,1),new r("erendes",19,1),new r("enes",18,1),new r("ernes",18,1),new r("eres",18,1),new r("ens",16,1),new r("hedens",24,1),new r("erens",24,1),new r("ers",16,1),new r("ets",16,1),new r("erets",28,1),new r("et",-1,1),new r("eret",30,1)],l=[new r("gd",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1)],m=[new r("ig",-1,1),new r("lig",0,1),new r("elig",1,1),new r("els",-1,1),new r("løst",-1,2)],w=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],p=[239,254,42,3,0,0,0,0,0,0,0,0,0,0,0,0,16],f=new i;this.setCurrent=function(e){f.setCurrent(e)},this.getCurrent=function(){return f.getCurrent()},this.stem=function(){var r=f.cursor;return e(),f.limit_backward=r,f.cursor=f.limit,n(),f.cursor=f.limit,t(),f.cursor=f.limit,s(),f.cursor=f.limit,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.da.stemmer,"stemmer-da"),e.da.stopWordFilter=e.generateStopWordFilter("ad af alle alt anden at blev blive bliver da de dem den denne der deres det dette dig din disse dog du efter eller en end er et for fra ham han hans har havde have hende hendes her hos hun hvad hvis hvor i ikke ind jeg jer jo kunne man mange med meget men mig min mine mit mod ned noget nogle nu når og også om op os over på selv sig sin sine sit skal skulle som sådan thi til ud under var vi vil ville vor være været".split(" ")),e.Pipeline.registerFunction(e.da.stopWordFilter,"stopWordFilter-da")}});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.hi=function(){this.pipeline.reset(),this.pipeline.add(e.hi.trimmer,e.hi.stopWordFilter,e.hi.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.hi.stemmer))},e.hi.wordCharacters="ऀ-ःऄ-एऐ-टठ-यर-िी-ॏॐ-य़ॠ-९॰-ॿa-zA-Z--0-9-",e.hi.trimmer=e.trimmerSupport.generateTrimmer(e.hi.wordCharacters),e.Pipeline.registerFunction(e.hi.trimmer,"trimmer-hi"),e.hi.stopWordFilter=e.generateStopWordFilter("अत अपना अपनी अपने अभी अंदर आदि आप इत्यादि इन इनका इन्हीं इन्हें इन्हों इस इसका इसकी इसके इसमें इसी इसे उन उनका उनकी उनके उनको उन्हीं उन्हें उन्हों उस उसके उसी उसे एक एवं एस ऐसे और कई कर करता करते करना करने करें कहते कहा का काफ़ी कि कितना किन्हें किन्हों किया किर किस किसी किसे की कुछ कुल के को कोई कौन कौनसा गया घर जब जहाँ जा जितना जिन जिन्हें जिन्हों जिस जिसे जीधर जैसा जैसे जो तक तब तरह तिन तिन्हें तिन्हों तिस तिसे तो था थी थे दबारा दिया दुसरा दूसरे दो द्वारा न नके नहीं ना निहायत नीचे ने पर पहले पूरा पे फिर बनी बही बहुत बाद बाला बिलकुल भी भीतर मगर मानो मे में यदि यह यहाँ यही या यिह ये रखें रहा रहे ऱ्वासा लिए लिये लेकिन व वग़ैरह वर्ग वह वहाँ वहीं वाले वुह वे वो सकता सकते सबसे सभी साथ साबुत साभ सारा से सो संग ही हुआ हुई हुए है हैं हो होता होती होते होना होने".split(" ")),e.hi.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var r=e.wordcut;r.init(),e.hi.tokenizer=function(i){if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(r){return isLunr2?new e.Token(r.toLowerCase()):r.toLowerCase()});var t=i.toString().toLowerCase().replace(/^\s+/,"");return r.cut(t).split("|")},e.Pipeline.registerFunction(e.hi.stemmer,"stemmer-hi"),e.Pipeline.registerFunction(e.hi.stopWordFilter,"stopWordFilter-hi")}});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.hy=function(){this.pipeline.reset(),this.pipeline.add(e.hy.trimmer,e.hy.stopWordFilter)},e.hy.wordCharacters="[A-Za-z԰-֏ff-ﭏ]",e.hy.trimmer=e.trimmerSupport.generateTrimmer(e.hy.wordCharacters),e.Pipeline.registerFunction(e.hy.trimmer,"trimmer-hy"),e.hy.stopWordFilter=e.generateStopWordFilter("դու և եք էիր էիք հետո նաև նրանք որը վրա է որ պիտի են այս մեջ ն իր ու ի այդ որոնք այն կամ էր մի ես համար այլ իսկ էին ենք հետ ին թ էինք մենք նրա նա դուք եմ էի ըստ որպես ում".split(" ")),e.Pipeline.registerFunction(e.hy.stopWordFilter,"stopWordFilter-hy"),e.hy.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}(),e.Pipeline.registerFunction(e.hy.stemmer,"stemmer-hy")}});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r="2"==e.version[0];e.ja=function(){this.pipeline.reset(),this.pipeline.add(e.ja.trimmer,e.ja.stopWordFilter,e.ja.stemmer),r?this.tokenizer=e.ja.tokenizer:(e.tokenizer&&(e.tokenizer=e.ja.tokenizer),this.tokenizerFn&&(this.tokenizerFn=e.ja.tokenizer))};var t=new e.TinySegmenter;e.ja.tokenizer=function(i){var n,o,s,p,a,u,m,l,c,f;if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(t){return r?new e.Token(t.toLowerCase()):t.toLowerCase()});for(o=i.toString().toLowerCase().replace(/^\s+/,""),n=o.length-1;n>=0;n--)if(/\S/.test(o.charAt(n))){o=o.substring(0,n+1);break}for(a=[],s=o.length,c=0,l=0;c<=s;c++)if(u=o.charAt(c),m=c-l,u.match(/\s/)||c==s){if(m>0)for(p=t.segment(o.slice(l,c)).filter(function(e){return!!e}),f=l,n=0;n<p.length;n++)r?a.push(new e.Token(p[n],{position:[f,p[n].length],index:a.length})):a.push(p[n]),f+=p[n].length;l=c+1}return a},e.ja.stemmer=function(){return function(e){return e}}(),e.Pipeline.registerFunction(e.ja.stemmer,"stemmer-ja"),e.ja.wordCharacters="一二三四五六七八九十百千万億兆一-龠々〆ヵヶぁ-んァ-ヴーア-ン゙a-zA-Z--0-9-",e.ja.trimmer=e.trimmerSupport.generateTrimmer(e.ja.wordCharacters),e.Pipeline.registerFunction(e.ja.trimmer,"trimmer-ja"),e.ja.stopWordFilter=e.generateStopWordFilter("これ それ あれ この その あの ここ そこ あそこ こちら どこ だれ なに なん 何 私 貴方 貴方方 我々 私達 あの人 あのかた 彼女 彼 です あります おります います は が の に を で え から まで より も どの と し それで しかし".split(" ")),e.Pipeline.registerFunction(e.ja.stopWordFilter,"stopWordFilter-ja"),e.jp=e.ja,e.Pipeline.registerFunction(e.jp.stemmer,"stemmer-jp"),e.Pipeline.registerFunction(e.jp.trimmer,"trimmer-jp"),e.Pipeline.registerFunction(e.jp.stopWordFilter,"stopWordFilter-jp")}});

View File

@ -0,0 +1 @@
module.exports=require("./lunr.ja");

View File

@ -0,0 +1 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.kn=function(){this.pipeline.reset(),this.pipeline.add(e.kn.trimmer,e.kn.stopWordFilter,e.kn.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.kn.stemmer))},e.kn.wordCharacters="ಀ-಄ಅ-ಔಕ-ಹಾ-ೌ಼-ಽೕ-ೖೝ-ೞೠ-ೡೢ-ೣ೤೥೦-೯ೱ-ೳ",e.kn.trimmer=e.trimmerSupport.generateTrimmer(e.kn.wordCharacters),e.Pipeline.registerFunction(e.kn.trimmer,"trimmer-kn"),e.kn.stopWordFilter=e.generateStopWordFilter("ಮತ್ತು ಈ ಒಂದು ರಲ್ಲಿ ಹಾಗೂ ಎಂದು ಅಥವಾ ಇದು ರ ಅವರು ಎಂಬ ಮೇಲೆ ಅವರ ತನ್ನ ಆದರೆ ತಮ್ಮ ನಂತರ ಮೂಲಕ ಹೆಚ್ಚು ನ ಆ ಕೆಲವು ಅನೇಕ ಎರಡು ಹಾಗು ಪ್ರಮುಖ ಇದನ್ನು ಇದರ ಸುಮಾರು ಅದರ ಅದು ಮೊದಲ ಬಗ್ಗೆ ನಲ್ಲಿ ರಂದು ಇತರ ಅತ್ಯಂತ ಹೆಚ್ಚಿನ ಸಹ ಸಾಮಾನ್ಯವಾಗಿ ನೇ ಹಲವಾರು ಹೊಸ ದಿ ಕಡಿಮೆ ಯಾವುದೇ ಹೊಂದಿದೆ ದೊಡ್ಡ ಅನ್ನು ಇವರು ಪ್ರಕಾರ ಇದೆ ಮಾತ್ರ ಕೂಡ ಇಲ್ಲಿ ಎಲ್ಲಾ ವಿವಿಧ ಅದನ್ನು ಹಲವು ರಿಂದ ಕೇವಲ ದ ದಕ್ಷಿಣ ಗೆ ಅವನ ಅತಿ ನೆಯ ಬಹಳ ಕೆಲಸ ಎಲ್ಲ ಪ್ರತಿ ಇತ್ಯಾದಿ ಇವು ಬೇರೆ ಹೀಗೆ ನಡುವೆ ಇದಕ್ಕೆ ಎಸ್ ಇವರ ಮೊದಲು ಶ್ರೀ ಮಾಡುವ ಇದರಲ್ಲಿ ರೀತಿಯ ಮಾಡಿದ ಕಾಲ ಅಲ್ಲಿ ಮಾಡಲು ಅದೇ ಈಗ ಅವು ಗಳು ಎ ಎಂಬುದು ಅವನು ಅಂದರೆ ಅವರಿಗೆ ಇರುವ ವಿಶೇಷ ಮುಂದೆ ಅವುಗಳ ಮುಂತಾದ ಮೂಲ ಬಿ ಮೀ ಒಂದೇ ಇನ್ನೂ ಹೆಚ್ಚಾಗಿ ಮಾಡಿ ಅವರನ್ನು ಇದೇ ಯ ರೀತಿಯಲ್ಲಿ ಜೊತೆ ಅದರಲ್ಲಿ ಮಾಡಿದರು ನಡೆದ ಆಗ ಮತ್ತೆ ಪೂರ್ವ ಆತ ಬಂದ ಯಾವ ಒಟ್ಟು ಇತರೆ ಹಿಂದೆ ಪ್ರಮಾಣದ ಗಳನ್ನು ಕುರಿತು ಯು ಆದ್ದರಿಂದ ಅಲ್ಲದೆ ನಗರದ ಮೇಲಿನ ಏಕೆಂದರೆ ರಷ್ಟು ಎಂಬುದನ್ನು ಬಾರಿ ಎಂದರೆ ಹಿಂದಿನ ಆದರೂ ಆದ ಸಂಬಂಧಿಸಿದ ಮತ್ತೊಂದು ಸಿ ಆತನ ".split(" ")),e.kn.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var r=e.wordcut;r.init(),e.kn.tokenizer=function(t){if(!arguments.length||null==t||void 0==t)return[];if(Array.isArray(t))return t.map(function(r){return isLunr2?new e.Token(r.toLowerCase()):r.toLowerCase()});var n=t.toString().toLowerCase().replace(/^\s+/,"");return r.cut(n).split("|")},e.Pipeline.registerFunction(e.kn.stemmer,"stemmer-kn"),e.Pipeline.registerFunction(e.kn.stopWordFilter,"stopWordFilter-kn")}});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
!function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(e.lunr)}(this,function(){return function(e){e.multiLanguage=function(){for(var t=Array.prototype.slice.call(arguments),i=t.join("-"),r="",n=[],s=[],p=0;p<t.length;++p)"en"==t[p]?(r+="\\w",n.unshift(e.stopWordFilter),n.push(e.stemmer),s.push(e.stemmer)):(r+=e[t[p]].wordCharacters,e[t[p]].stopWordFilter&&n.unshift(e[t[p]].stopWordFilter),e[t[p]].stemmer&&(n.push(e[t[p]].stemmer),s.push(e[t[p]].stemmer)));var o=e.trimmerSupport.generateTrimmer(r);return e.Pipeline.registerFunction(o,"lunr-multi-trimmer-"+i),n.unshift(o),function(){this.pipeline.reset(),this.pipeline.add.apply(this.pipeline,n),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add.apply(this.searchPipeline,s))}}}});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,18 @@
/*!
* Lunr languages, `Norwegian` language
* https://github.com/MihaiValentin/lunr-languages
*
* Copyright 2014, Mihai Valentin
* http://www.mozilla.org/MPL/
*/
/*!
* based on
* Snowball JavaScript Library v0.3
* http://code.google.com/p/urim/
* http://snowball.tartarus.org/
*
* Copyright 2010, Oleg Mazko
* http://www.mozilla.org/MPL/
*/
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.no=function(){this.pipeline.reset(),this.pipeline.add(e.no.trimmer,e.no.stopWordFilter,e.no.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.no.stemmer))},e.no.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA--",e.no.trimmer=e.trimmerSupport.generateTrimmer(e.no.wordCharacters),e.Pipeline.registerFunction(e.no.trimmer,"trimmer-no"),e.no.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,i=new function(){function e(){var e,r=w.cursor+3;if(a=w.limit,0<=r||r<=w.limit){for(s=r;;){if(e=w.cursor,w.in_grouping(d,97,248)){w.cursor=e;break}if(e>=w.limit)return;w.cursor=e+1}for(;!w.out_grouping(d,97,248);){if(w.cursor>=w.limit)return;w.cursor++}a=w.cursor,a<s&&(a=s)}}function i(){var e,r,n;if(w.cursor>=a&&(r=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,e=w.find_among_b(m,29),w.limit_backward=r,e))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:n=w.limit-w.cursor,w.in_grouping_b(c,98,122)?w.slice_del():(w.cursor=w.limit-n,w.eq_s_b(1,"k")&&w.out_grouping_b(d,97,248)&&w.slice_del());break;case 3:w.slice_from("er")}}function t(){var e,r=w.limit-w.cursor;w.cursor>=a&&(e=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,w.find_among_b(u,2)?(w.bra=w.cursor,w.limit_backward=e,w.cursor=w.limit-r,w.cursor>w.limit_backward&&(w.cursor--,w.bra=w.cursor,w.slice_del())):w.limit_backward=e)}function o(){var e,r;w.cursor>=a&&(r=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,e=w.find_among_b(l,11),e?(w.bra=w.cursor,w.limit_backward=r,1==e&&w.slice_del()):w.limit_backward=r)}var s,a,m=[new r("a",-1,1),new r("e",-1,1),new r("ede",1,1),new r("ande",1,1),new r("ende",1,1),new r("ane",1,1),new r("ene",1,1),new r("hetene",6,1),new r("erte",1,3),new r("en",-1,1),new r("heten",9,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",12,1),new r("s",-1,2),new r("as",14,1),new r("es",14,1),new r("edes",16,1),new r("endes",16,1),new r("enes",16,1),new r("hetenes",19,1),new r("ens",14,1),new r("hetens",21,1),new r("ers",14,1),new r("ets",14,1),new r("et",-1,1),new r("het",25,1),new r("ert",-1,3),new r("ast",-1,1)],u=[new r("dt",-1,-1),new r("vt",-1,-1)],l=[new r("leg",-1,1),new r("eleg",0,1),new r("ig",-1,1),new r("eig",2,1),new r("lig",2,1),new r("elig",4,1),new r("els",-1,1),new r("lov",-1,1),new r("elov",7,1),new r("slov",7,1),new r("hetslov",9,1)],d=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],c=[119,125,149,1],w=new n;this.setCurrent=function(e){w.setCurrent(e)},this.getCurrent=function(){return w.getCurrent()},this.stem=function(){var r=w.cursor;return e(),w.limit_backward=r,w.cursor=w.limit,i(),w.cursor=w.limit,t(),w.cursor=w.limit,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.no.stemmer,"stemmer-no"),e.no.stopWordFilter=e.generateStopWordFilter("alle at av bare begge ble blei bli blir blitt både båe da de deg dei deim deira deires dem den denne der dere deres det dette di din disse ditt du dykk dykkar då eg ein eit eitt eller elles en enn er et ett etter for fordi fra før ha hadde han hans har hennar henne hennes her hjå ho hoe honom hoss hossen hun hva hvem hver hvilke hvilken hvis hvor hvordan hvorfor i ikke ikkje ikkje ingen ingi inkje inn inni ja jeg kan kom korleis korso kun kunne kva kvar kvarhelst kven kvi kvifor man mange me med medan meg meget mellom men mi min mine mitt mot mykje ned no noe noen noka noko nokon nokor nokre nå når og også om opp oss over på samme seg selv si si sia sidan siden sin sine sitt sjøl skal skulle slik so som som somme somt så sånn til um upp ut uten var vart varte ved vere verte vi vil ville vore vors vort vår være være vært å".split(" ")),e.Pipeline.registerFunction(e.no.stopWordFilter,"stopWordFilter-no")}});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.sa=function(){this.pipeline.reset(),this.pipeline.add(e.sa.trimmer,e.sa.stopWordFilter,e.sa.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.sa.stemmer))},e.sa.wordCharacters="ऀ-ःऄ-एऐ-टठ-यर-िी-ॏॐ-य़ॠ-९॰-ॿ꣠-꣱ꣲ-ꣷ꣸-ꣻ꣼-ꣽꣾ-ꣿᆰ0-ᆰ9",e.sa.trimmer=e.trimmerSupport.generateTrimmer(e.sa.wordCharacters),e.Pipeline.registerFunction(e.sa.trimmer,"trimmer-sa"),e.sa.stopWordFilter=e.generateStopWordFilter('तथा अयम्‌ एकम्‌ इत्यस्मिन्‌ तथा तत्‌ वा अयम्‌ इत्यस्य ते आहूत उपरि तेषाम्‌ किन्तु तेषाम्‌ तदा इत्यनेन अधिकः इत्यस्य तत्‌ केचन बहवः द्वि तथा महत्वपूर्णः अयम्‌ अस्य विषये अयं अस्ति तत्‌ प्रथमः विषये इत्युपरि इत्युपरि इतर अधिकतमः अधिकः अपि सामान्यतया ठ इतरेतर नूतनम्‌ द न्यूनम्‌ कश्चित्‌ वा विशालः द सः अस्ति तदनुसारम् तत्र अस्ति केवलम्‌ अपि अत्र सर्वे विविधाः तत्‌ बहवः यतः इदानीम्‌ द दक्षिण इत्यस्मै तस्य उपरि नथ अतीव कार्यम्‌ सर्वे एकैकम्‌ इत्यादि। एते सन्ति उत इत्थम्‌ मध्ये एतदर्थं . स कस्य प्रथमः श्री. करोति अस्मिन् प्रकारः निर्मिता कालः तत्र कर्तुं समान अधुना ते सन्ति स एकः अस्ति सः अर्थात् तेषां कृते . स्थितम् विशेषः अग्रिम तेषाम्‌ समान स्रोतः ख म समान इदानीमपि अधिकतया करोतु ते समान इत्यस्य वीथी सह यस्मिन् कृतवान्‌ धृतः तदा पुनः पूर्वं सः आगतः किम्‌ कुल इतर पुरा मात्रा स विषये उ अतएव अपि नगरस्य उपरि यतः प्रतिशतं कतरः कालः साधनानि भूत तथापि जात सम्बन्धि अन्यत्‌ ग अतः अस्माकं स्वकीयाः अस्माकं इदानीं अन्तः इत्यादयः भवन्तः इत्यादयः एते एताः तस्य अस्य इदम् एते तेषां तेषां तेषां तान् तेषां तेषां तेषां समानः सः एकः च तादृशाः बहवः अन्ये च वदन्ति यत् कियत् कस्मै कस्मै यस्मै यस्मै यस्मै यस्मै न अतिनीचः किन्तु प्रथमं सम्पूर्णतया ततः चिरकालानन्तरं पुस्तकं सम्पूर्णतया अन्तः किन्तु अत्र वा इह इव श्रद्धाय अवशिष्यते परन्तु अन्ये वर्गाः सन्ति ते सन्ति शक्नुवन्ति सर्वे मिलित्वा सर्वे एकत्र"'.split(" ")),e.sa.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var r=e.wordcut;r.init(),e.sa.tokenizer=function(t){if(!arguments.length||null==t||void 0==t)return[];if(Array.isArray(t))return t.map(function(r){return isLunr2?new e.Token(r.toLowerCase()):r.toLowerCase()});var i=t.toString().toLowerCase().replace(/^\s+/,"");return r.cut(i).split("|")},e.Pipeline.registerFunction(e.sa.stemmer,"stemmer-sa"),e.Pipeline.registerFunction(e.sa.stopWordFilter,"stopWordFilter-sa")}});

View File

@ -0,0 +1 @@
!function(r,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(r.lunr)}(this,function(){return function(r){r.stemmerSupport={Among:function(r,t,i,s){if(this.toCharArray=function(r){for(var t=r.length,i=new Array(t),s=0;s<t;s++)i[s]=r.charCodeAt(s);return i},!r&&""!=r||!t&&0!=t||!i)throw"Bad Among initialisation: s:"+r+", substring_i: "+t+", result: "+i;this.s_size=r.length,this.s=this.toCharArray(r),this.substring_i=t,this.result=i,this.method=s},SnowballProgram:function(){var r;return{bra:0,ket:0,limit:0,cursor:0,limit_backward:0,setCurrent:function(t){r=t,this.cursor=0,this.limit=t.length,this.limit_backward=0,this.bra=this.cursor,this.ket=this.limit},getCurrent:function(){var t=r;return r=null,t},in_grouping:function(t,i,s){if(this.cursor<this.limit){var e=r.charCodeAt(this.cursor);if(e<=s&&e>=i&&(e-=i,t[e>>3]&1<<(7&e)))return this.cursor++,!0}return!1},in_grouping_b:function(t,i,s){if(this.cursor>this.limit_backward){var e=r.charCodeAt(this.cursor-1);if(e<=s&&e>=i&&(e-=i,t[e>>3]&1<<(7&e)))return this.cursor--,!0}return!1},out_grouping:function(t,i,s){if(this.cursor<this.limit){var e=r.charCodeAt(this.cursor);if(e>s||e<i)return this.cursor++,!0;if(e-=i,!(t[e>>3]&1<<(7&e)))return this.cursor++,!0}return!1},out_grouping_b:function(t,i,s){if(this.cursor>this.limit_backward){var e=r.charCodeAt(this.cursor-1);if(e>s||e<i)return this.cursor--,!0;if(e-=i,!(t[e>>3]&1<<(7&e)))return this.cursor--,!0}return!1},eq_s:function(t,i){if(this.limit-this.cursor<t)return!1;for(var s=0;s<t;s++)if(r.charCodeAt(this.cursor+s)!=i.charCodeAt(s))return!1;return this.cursor+=t,!0},eq_s_b:function(t,i){if(this.cursor-this.limit_backward<t)return!1;for(var s=0;s<t;s++)if(r.charCodeAt(this.cursor-t+s)!=i.charCodeAt(s))return!1;return this.cursor-=t,!0},find_among:function(t,i){for(var s=0,e=i,n=this.cursor,u=this.limit,o=0,h=0,c=!1;;){for(var a=s+(e-s>>1),f=0,l=o<h?o:h,_=t[a],m=l;m<_.s_size;m++){if(n+l==u){f=-1;break}if(f=r.charCodeAt(n+l)-_.s[m])break;l++}if(f<0?(e=a,h=l):(s=a,o=l),e-s<=1){if(s>0||e==s||c)break;c=!0}}for(;;){var _=t[s];if(o>=_.s_size){if(this.cursor=n+_.s_size,!_.method)return _.result;var b=_.method();if(this.cursor=n+_.s_size,b)return _.result}if((s=_.substring_i)<0)return 0}},find_among_b:function(t,i){for(var s=0,e=i,n=this.cursor,u=this.limit_backward,o=0,h=0,c=!1;;){for(var a=s+(e-s>>1),f=0,l=o<h?o:h,_=t[a],m=_.s_size-1-l;m>=0;m--){if(n-l==u){f=-1;break}if(f=r.charCodeAt(n-1-l)-_.s[m])break;l++}if(f<0?(e=a,h=l):(s=a,o=l),e-s<=1){if(s>0||e==s||c)break;c=!0}}for(;;){var _=t[s];if(o>=_.s_size){if(this.cursor=n-_.s_size,!_.method)return _.result;var b=_.method();if(this.cursor=n-_.s_size,b)return _.result}if((s=_.substring_i)<0)return 0}},replace_s:function(t,i,s){var e=s.length-(i-t),n=r.substring(0,t),u=r.substring(i);return r=n+s+u,this.limit+=e,this.cursor>=i?this.cursor+=e:this.cursor>t&&(this.cursor=t),e},slice_check:function(){if(this.bra<0||this.bra>this.ket||this.ket>this.limit||this.limit>r.length)throw"faulty slice operation"},slice_from:function(r){this.slice_check(),this.replace_s(this.bra,this.ket,r)},slice_del:function(){this.slice_from("")},insert:function(r,t,i){var s=this.replace_s(r,t,i);r<=this.bra&&(this.bra+=s),r<=this.ket&&(this.ket+=s)},slice_to:function(){return this.slice_check(),r.substring(this.bra,this.ket)},eq_v_b:function(r){return this.eq_s_b(r.length,r)}}}},r.trimmerSupport={generateTrimmer:function(r){var t=new RegExp("^[^"+r+"]+"),i=new RegExp("[^"+r+"]+$");return function(r){return"function"==typeof r.update?r.update(function(r){return r.replace(t,"").replace(i,"")}):r.replace(t,"").replace(i,"")}}}}});

View File

@ -0,0 +1,18 @@
/*!
* Lunr languages, `Swedish` language
* https://github.com/MihaiValentin/lunr-languages
*
* Copyright 2014, Mihai Valentin
* http://www.mozilla.org/MPL/
*/
/*!
* based on
* Snowball JavaScript Library v0.3
* http://code.google.com/p/urim/
* http://snowball.tartarus.org/
*
* Copyright 2010, Oleg Mazko
* http://www.mozilla.org/MPL/
*/
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.sv=function(){this.pipeline.reset(),this.pipeline.add(e.sv.trimmer,e.sv.stopWordFilter,e.sv.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.sv.stemmer))},e.sv.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA--",e.sv.trimmer=e.trimmerSupport.generateTrimmer(e.sv.wordCharacters),e.Pipeline.registerFunction(e.sv.trimmer,"trimmer-sv"),e.sv.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,t=new function(){function e(){var e,r=w.cursor+3;if(o=w.limit,0<=r||r<=w.limit){for(a=r;;){if(e=w.cursor,w.in_grouping(l,97,246)){w.cursor=e;break}if(w.cursor=e,w.cursor>=w.limit)return;w.cursor++}for(;!w.out_grouping(l,97,246);){if(w.cursor>=w.limit)return;w.cursor++}o=w.cursor,o<a&&(o=a)}}function t(){var e,r=w.limit_backward;if(w.cursor>=o&&(w.limit_backward=o,w.cursor=w.limit,w.ket=w.cursor,e=w.find_among_b(u,37),w.limit_backward=r,e))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:w.in_grouping_b(d,98,121)&&w.slice_del()}}function i(){var e=w.limit_backward;w.cursor>=o&&(w.limit_backward=o,w.cursor=w.limit,w.find_among_b(c,7)&&(w.cursor=w.limit,w.ket=w.cursor,w.cursor>w.limit_backward&&(w.bra=--w.cursor,w.slice_del())),w.limit_backward=e)}function s(){var e,r;if(w.cursor>=o){if(r=w.limit_backward,w.limit_backward=o,w.cursor=w.limit,w.ket=w.cursor,e=w.find_among_b(m,5))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:w.slice_from("lös");break;case 3:w.slice_from("full")}w.limit_backward=r}}var a,o,u=[new r("a",-1,1),new r("arna",0,1),new r("erna",0,1),new r("heterna",2,1),new r("orna",0,1),new r("ad",-1,1),new r("e",-1,1),new r("ade",6,1),new r("ande",6,1),new r("arne",6,1),new r("are",6,1),new r("aste",6,1),new r("en",-1,1),new r("anden",12,1),new r("aren",12,1),new r("heten",12,1),new r("ern",-1,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",18,1),new r("or",-1,1),new r("s",-1,2),new r("as",21,1),new r("arnas",22,1),new r("ernas",22,1),new r("ornas",22,1),new r("es",21,1),new r("ades",26,1),new r("andes",26,1),new r("ens",21,1),new r("arens",29,1),new r("hetens",29,1),new r("erns",21,1),new r("at",-1,1),new r("andet",-1,1),new r("het",-1,1),new r("ast",-1,1)],c=[new r("dd",-1,-1),new r("gd",-1,-1),new r("nn",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1),new r("tt",-1,-1)],m=[new r("ig",-1,1),new r("lig",0,1),new r("els",-1,1),new r("fullt",-1,3),new r("löst",-1,2)],l=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,24,0,32],d=[119,127,149],w=new n;this.setCurrent=function(e){w.setCurrent(e)},this.getCurrent=function(){return w.getCurrent()},this.stem=function(){var r=w.cursor;return e(),w.limit_backward=r,w.cursor=w.limit,t(),w.cursor=w.limit,i(),w.cursor=w.limit,s(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return t.setCurrent(e),t.stem(),t.getCurrent()}):(t.setCurrent(e),t.stem(),t.getCurrent())}}(),e.Pipeline.registerFunction(e.sv.stemmer,"stemmer-sv"),e.sv.stopWordFilter=e.generateStopWordFilter("alla allt att av blev bli blir blivit de dem den denna deras dess dessa det detta dig din dina ditt du där då efter ej eller en er era ert ett från för ha hade han hans har henne hennes hon honom hur här i icke ingen inom inte jag ju kan kunde man med mellan men mig min mina mitt mot mycket ni nu när någon något några och om oss på samma sedan sig sin sina sitta själv skulle som så sådan sådana sådant till under upp ut utan vad var vara varför varit varje vars vart vem vi vid vilka vilkas vilken vilket vår våra vårt än är åt över".split(" ")),e.Pipeline.registerFunction(e.sv.stopWordFilter,"stopWordFilter-sv")}});

View File

@ -0,0 +1 @@
!function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.ta=function(){this.pipeline.reset(),this.pipeline.add(e.ta.trimmer,e.ta.stopWordFilter,e.ta.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ta.stemmer))},e.ta.wordCharacters="஀-உஊ-ஏஐ-ஙச-ட஠-னப-யர-ஹ஺-ிீ-௉ொ-௏ௐ-௙௚-௟௠-௩௪-௯௰-௹௺-௿a-zA-Z--0-9-",e.ta.trimmer=e.trimmerSupport.generateTrimmer(e.ta.wordCharacters),e.Pipeline.registerFunction(e.ta.trimmer,"trimmer-ta"),e.ta.stopWordFilter=e.generateStopWordFilter("அங்கு அங்கே அது அதை அந்த அவர் அவர்கள் அவள் அவன் அவை ஆக ஆகவே ஆகையால் ஆதலால் ஆதலினால் ஆனாலும் ஆனால் இங்கு இங்கே இது இதை இந்த இப்படி இவர் இவர்கள் இவள் இவன் இவை இவ்வளவு உனக்கு உனது உன் உன்னால் எங்கு எங்கே எது எதை எந்த எப்படி எவர் எவர்கள் எவள் எவன் எவை எவ்வளவு எனக்கு எனது எனவே என் என்ன என்னால் ஏது ஏன் தனது தன்னால் தானே தான் நாங்கள் நாம் நான் நீ நீங்கள்".split(" ")),e.ta.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var t=e.wordcut;t.init(),e.ta.tokenizer=function(r){if(!arguments.length||null==r||void 0==r)return[];if(Array.isArray(r))return r.map(function(t){return isLunr2?new e.Token(t.toLowerCase()):t.toLowerCase()});var i=r.toString().toLowerCase().replace(/^\s+/,"");return t.cut(i).split("|")},e.Pipeline.registerFunction(e.ta.stemmer,"stemmer-ta"),e.Pipeline.registerFunction(e.ta.stopWordFilter,"stopWordFilter-ta")}});

View File

@ -0,0 +1 @@
!function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.te=function(){this.pipeline.reset(),this.pipeline.add(e.te.trimmer,e.te.stopWordFilter,e.te.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.te.stemmer))},e.te.wordCharacters="ఀ-ఄఅ-ఔక-హా-ౌౕ-ౖౘ-ౚౠ-ౡౢ-ౣ౦-౯౸-౿఼ఽ్ౝ౷౤౥",e.te.trimmer=e.trimmerSupport.generateTrimmer(e.te.wordCharacters),e.Pipeline.registerFunction(e.te.trimmer,"trimmer-te"),e.te.stopWordFilter=e.generateStopWordFilter("అందరూ అందుబాటులో అడగండి అడగడం అడ్డంగా అనుగుణంగా అనుమతించు అనుమతిస్తుంది అయితే ఇప్పటికే ఉన్నారు ఎక్కడైనా ఎప్పుడు ఎవరైనా ఎవరో ఏ ఏదైనా ఏమైనప్పటికి ఒక ఒకరు కనిపిస్తాయి కాదు కూడా గా గురించి చుట్టూ చేయగలిగింది తగిన తర్వాత దాదాపు దూరంగా నిజంగా పై ప్రకారం ప్రక్కన మధ్య మరియు మరొక మళ్ళీ మాత్రమే మెచ్చుకో వద్ద వెంట వేరుగా వ్యతిరేకంగా సంబంధం".split(" ")),e.te.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var t=e.wordcut;t.init(),e.te.tokenizer=function(r){if(!arguments.length||null==r||void 0==r)return[];if(Array.isArray(r))return r.map(function(t){return isLunr2?new e.Token(t.toLowerCase()):t.toLowerCase()});var i=r.toString().toLowerCase().replace(/^\s+/,"");return t.cut(i).split("|")},e.Pipeline.registerFunction(e.te.stemmer,"stemmer-te"),e.Pipeline.registerFunction(e.te.stopWordFilter,"stopWordFilter-te")}});

View File

@ -0,0 +1 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r="2"==e.version[0];e.th=function(){this.pipeline.reset(),this.pipeline.add(e.th.trimmer),r?this.tokenizer=e.th.tokenizer:(e.tokenizer&&(e.tokenizer=e.th.tokenizer),this.tokenizerFn&&(this.tokenizerFn=e.th.tokenizer))},e.th.wordCharacters="[฀-๿]",e.th.trimmer=e.trimmerSupport.generateTrimmer(e.th.wordCharacters),e.Pipeline.registerFunction(e.th.trimmer,"trimmer-th");var t=e.wordcut;t.init(),e.th.tokenizer=function(i){if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(t){return r?new e.Token(t):t});var n=i.toString().replace(/^\s+/,"");return t.cut(n).split("|")}}});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.vi=function(){this.pipeline.reset(),this.pipeline.add(e.vi.stopWordFilter,e.vi.trimmer)},e.vi.wordCharacters="[A-Za-ẓ̀͐́͑̉̃̓ÂâÊêÔôĂ-ăĐ-đƠ-ơƯ-ư]",e.vi.trimmer=e.trimmerSupport.generateTrimmer(e.vi.wordCharacters),e.Pipeline.registerFunction(e.vi.trimmer,"trimmer-vi"),e.vi.stopWordFilter=e.generateStopWordFilter("là cái nhưng mà".split(" "))}});

View File

@ -0,0 +1 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r(require("@node-rs/jieba")):r()(e.lunr)}(this,function(e){return function(r,t){if(void 0===r)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===r.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var i="2"==r.version[0];r.zh=function(){this.pipeline.reset(),this.pipeline.add(r.zh.trimmer,r.zh.stopWordFilter,r.zh.stemmer),i?this.tokenizer=r.zh.tokenizer:(r.tokenizer&&(r.tokenizer=r.zh.tokenizer),this.tokenizerFn&&(this.tokenizerFn=r.zh.tokenizer))},r.zh.tokenizer=function(n){if(!arguments.length||null==n||void 0==n)return[];if(Array.isArray(n))return n.map(function(e){return i?new r.Token(e.toLowerCase()):e.toLowerCase()});t&&e.load(t);var o=n.toString().trim().toLowerCase(),s=[];e.cut(o,!0).forEach(function(e){s=s.concat(e.split(" "))}),s=s.filter(function(e){return!!e});var u=0;return s.map(function(e,t){if(i){var n=o.indexOf(e,u),s={};return s.position=[n,e.length],s.index=t,u=n,new r.Token(e,s)}return e})},r.zh.wordCharacters="\\w一-龥",r.zh.trimmer=r.trimmerSupport.generateTrimmer(r.zh.wordCharacters),r.Pipeline.registerFunction(r.zh.trimmer,"trimmer-zh"),r.zh.stemmer=function(){return function(e){return e}}(),r.Pipeline.registerFunction(r.zh.stemmer,"stemmer-zh"),r.zh.stopWordFilter=r.generateStopWordFilter("的 一 不 在 人 有 是 为 為 以 于 於 上 他 而 后 後 之 来 來 及 了 因 下 可 到 由 这 這 与 與 也 此 但 并 並 个 個 其 已 无 無 小 我 们 們 起 最 再 今 去 好 只 又 或 很 亦 某 把 那 你 乃 它 吧 被 比 别 趁 当 當 从 從 得 打 凡 儿 兒 尔 爾 该 該 各 给 給 跟 和 何 还 還 即 几 幾 既 看 据 據 距 靠 啦 另 么 麽 每 嘛 拿 哪 您 凭 憑 且 却 卻 让 讓 仍 啥 如 若 使 谁 誰 虽 雖 随 隨 同 所 她 哇 嗡 往 些 向 沿 哟 喲 用 咱 则 則 怎 曾 至 致 着 著 诸 諸 自".split(" ")),r.Pipeline.registerFunction(r.zh.stopWordFilter,"stopWordFilter-zh")}});

View File

@ -0,0 +1,206 @@
/**
* export the module via AMD, CommonJS or as a browser global
* Export code from https://github.com/umdjs/umd/blob/master/returnExports.js
*/
;(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(factory)
} else if (typeof exports === 'object') {
/**
* Node. Does not work with strict CommonJS, but
* only CommonJS-like environments that support module.exports,
* like Node.
*/
module.exports = factory()
} else {
// Browser globals (root is window)
factory()(root.lunr);
}
}(this, function () {
/**
* Just return a value to define the module export.
* This example returns an object, but the module
* can return a function as the exported value.
*/
return function(lunr) {
// TinySegmenter 0.1 -- Super compact Japanese tokenizer in Javascript
// (c) 2008 Taku Kudo <taku@chasen.org>
// TinySegmenter is freely distributable under the terms of a new BSD licence.
// For details, see http://chasen.org/~taku/software/TinySegmenter/LICENCE.txt
function TinySegmenter() {
var patterns = {
"[一二三四五六七八九十百千万億兆]":"M",
"[一-龠々〆ヵヶ]":"H",
"[ぁ-ん]":"I",
"[ァ-ヴーア-ン゙ー]":"K",
"[a-zA-Z--]":"A",
"[0-9-]":"N"
}
this.chartype_ = [];
for (var i in patterns) {
var regexp = new RegExp(i);
this.chartype_.push([regexp, patterns[i]]);
}
this.BIAS__ = -332
this.BC1__ = {"HH":6,"II":2461,"KH":406,"OH":-1378};
this.BC2__ = {"AA":-3267,"AI":2744,"AN":-878,"HH":-4070,"HM":-1711,"HN":4012,"HO":3761,"IA":1327,"IH":-1184,"II":-1332,"IK":1721,"IO":5492,"KI":3831,"KK":-8741,"MH":-3132,"MK":3334,"OO":-2920};
this.BC3__ = {"HH":996,"HI":626,"HK":-721,"HN":-1307,"HO":-836,"IH":-301,"KK":2762,"MK":1079,"MM":4034,"OA":-1652,"OH":266};
this.BP1__ = {"BB":295,"OB":304,"OO":-125,"UB":352};
this.BP2__ = {"BO":60,"OO":-1762};
this.BQ1__ = {"BHH":1150,"BHM":1521,"BII":-1158,"BIM":886,"BMH":1208,"BNH":449,"BOH":-91,"BOO":-2597,"OHI":451,"OIH":-296,"OKA":1851,"OKH":-1020,"OKK":904,"OOO":2965};
this.BQ2__ = {"BHH":118,"BHI":-1159,"BHM":466,"BIH":-919,"BKK":-1720,"BKO":864,"OHH":-1139,"OHM":-181,"OIH":153,"UHI":-1146};
this.BQ3__ = {"BHH":-792,"BHI":2664,"BII":-299,"BKI":419,"BMH":937,"BMM":8335,"BNN":998,"BOH":775,"OHH":2174,"OHM":439,"OII":280,"OKH":1798,"OKI":-793,"OKO":-2242,"OMH":-2402,"OOO":11699};
this.BQ4__ = {"BHH":-3895,"BIH":3761,"BII":-4654,"BIK":1348,"BKK":-1806,"BMI":-3385,"BOO":-12396,"OAH":926,"OHH":266,"OHK":-2036,"ONN":-973};
this.BW1__ = {",と":660,",同":727,"B1あ":1404,"B1同":542,"、と":660,"、同":727,"」と":1682,"あっ":1505,"いう":1743,"いっ":-2055,"いる":672,"うし":-4817,"うん":665,"から":3472,"がら":600,"こう":-790,"こと":2083,"こん":-1262,"さら":-4143,"さん":4573,"した":2641,"して":1104,"すで":-3399,"そこ":1977,"それ":-871,"たち":1122,"ため":601,"った":3463,"つい":-802,"てい":805,"てき":1249,"でき":1127,"です":3445,"では":844,"とい":-4915,"とみ":1922,"どこ":3887,"ない":5713,"なっ":3015,"など":7379,"なん":-1113,"にし":2468,"には":1498,"にも":1671,"に対":-912,"の一":-501,"の中":741,"ませ":2448,"まで":1711,"まま":2600,"まる":-2155,"やむ":-1947,"よっ":-2565,"れた":2369,"れで":-913,"をし":1860,"を見":731,"亡く":-1886,"京都":2558,"取り":-2784,"大き":-2604,"大阪":1497,"平方":-2314,"引き":-1336,"日本":-195,"本当":-2423,"毎日":-2113,"目指":-724,"B1あ":1404,"B1同":542,"」と":1682};
this.BW2__ = {"..":-11822,"11":-669,"――":-5730,"":-13175,"いう":-1609,"うか":2490,"かし":-1350,"かも":-602,"から":-7194,"かれ":4612,"がい":853,"がら":-3198,"きた":1941,"くな":-1597,"こと":-8392,"この":-4193,"させ":4533,"され":13168,"さん":-3977,"しい":-1819,"しか":-545,"した":5078,"して":972,"しな":939,"その":-3744,"たい":-1253,"たた":-662,"ただ":-3857,"たち":-786,"たと":1224,"たは":-939,"った":4589,"って":1647,"っと":-2094,"てい":6144,"てき":3640,"てく":2551,"ては":-3110,"ても":-3065,"でい":2666,"でき":-1528,"でし":-3828,"です":-4761,"でも":-4203,"とい":1890,"とこ":-1746,"とと":-2279,"との":720,"とみ":5168,"とも":-3941,"ない":-2488,"なが":-1313,"など":-6509,"なの":2614,"なん":3099,"にお":-1615,"にし":2748,"にな":2454,"によ":-7236,"に対":-14943,"に従":-4688,"に関":-11388,"のか":2093,"ので":-7059,"のに":-6041,"のの":-6125,"はい":1073,"はが":-1033,"はず":-2532,"ばれ":1813,"まし":-1316,"まで":-6621,"まれ":5409,"めて":-3153,"もい":2230,"もの":-10713,"らか":-944,"らし":-1611,"らに":-1897,"りし":651,"りま":1620,"れた":4270,"れて":849,"れば":4114,"ろう":6067,"われ":7901,"を通":-11877,"んだ":728,"んな":-4115,"一人":602,"一方":-1375,"一日":970,"一部":-1051,"上が":-4479,"会社":-1116,"出て":2163,"分の":-7758,"同党":970,"同日":-913,"大阪":-2471,"委員":-1250,"少な":-1050,"年度":-8669,"年間":-1626,"府県":-2363,"手権":-1982,"新聞":-4066,"日新":-722,"日本":-7068,"日米":3372,"曜日":-601,"朝鮮":-2355,"本人":-2697,"東京":-1543,"然と":-1384,"社会":-1276,"立て":-990,"第に":-1612,"米国":-4268,"":-669};
this.BW3__ = {"あた":-2194,"あり":719,"ある":3846,"い.":-1185,"い。":-1185,"いい":5308,"いえ":2079,"いく":3029,"いた":2056,"いっ":1883,"いる":5600,"いわ":1527,"うち":1117,"うと":4798,"えと":1454,"か.":2857,"か。":2857,"かけ":-743,"かっ":-4098,"かに":-669,"から":6520,"かり":-2670,"が,":1816,"が、":1816,"がき":-4855,"がけ":-1127,"がっ":-913,"がら":-4977,"がり":-2064,"きた":1645,"けど":1374,"こと":7397,"この":1542,"ころ":-2757,"さい":-714,"さを":976,"し,":1557,"し、":1557,"しい":-3714,"した":3562,"して":1449,"しな":2608,"しま":1200,"す.":-1310,"す。":-1310,"する":6521,"ず,":3426,"ず、":3426,"ずに":841,"そう":428,"た.":8875,"た。":8875,"たい":-594,"たの":812,"たり":-1183,"たる":-853,"だ.":4098,"だ。":4098,"だっ":1004,"った":-4748,"って":300,"てい":6240,"てお":855,"ても":302,"です":1437,"でに":-1482,"では":2295,"とう":-1387,"とし":2266,"との":541,"とも":-3543,"どう":4664,"ない":1796,"なく":-903,"など":2135,"に,":-1021,"に、":-1021,"にし":1771,"にな":1906,"には":2644,"の,":-724,"の、":-724,"の子":-1000,"は,":1337,"は、":1337,"べき":2181,"まし":1113,"ます":6943,"まっ":-1549,"まで":6154,"まれ":-793,"らし":1479,"られ":6820,"るる":3818,"れ,":854,"れ、":854,"れた":1850,"れて":1375,"れば":-3246,"れる":1091,"われ":-605,"んだ":606,"んで":798,"カ月":990,"会議":860,"入り":1232,"大会":2217,"始め":1681,"市":965,"新聞":-5055,"日,":974,"日、":974,"社会":2024,"カ月":990};
this.TC1__ = {"AAA":1093,"HHH":1029,"HHM":580,"HII":998,"HOH":-390,"HOM":-331,"IHI":1169,"IOH":-142,"IOI":-1015,"IOM":467,"MMH":187,"OOI":-1832};
this.TC2__ = {"HHO":2088,"HII":-1023,"HMM":-1154,"IHI":-1965,"KKH":703,"OII":-2649};
this.TC3__ = {"AAA":-294,"HHH":346,"HHI":-341,"HII":-1088,"HIK":731,"HOH":-1486,"IHH":128,"IHI":-3041,"IHO":-1935,"IIH":-825,"IIM":-1035,"IOI":-542,"KHH":-1216,"KKA":491,"KKH":-1217,"KOK":-1009,"MHH":-2694,"MHM":-457,"MHO":123,"MMH":-471,"NNH":-1689,"NNO":662,"OHO":-3393};
this.TC4__ = {"HHH":-203,"HHI":1344,"HHK":365,"HHM":-122,"HHN":182,"HHO":669,"HIH":804,"HII":679,"HOH":446,"IHH":695,"IHO":-2324,"IIH":321,"III":1497,"IIO":656,"IOO":54,"KAK":4845,"KKA":3386,"KKK":3065,"MHH":-405,"MHI":201,"MMH":-241,"MMM":661,"MOM":841};
this.TQ1__ = {"BHHH":-227,"BHHI":316,"BHIH":-132,"BIHH":60,"BIII":1595,"BNHH":-744,"BOHH":225,"BOOO":-908,"OAKK":482,"OHHH":281,"OHIH":249,"OIHI":200,"OIIH":-68};
this.TQ2__ = {"BIHH":-1401,"BIII":-1033,"BKAK":-543,"BOOO":-5591};
this.TQ3__ = {"BHHH":478,"BHHM":-1073,"BHIH":222,"BHII":-504,"BIIH":-116,"BIII":-105,"BMHI":-863,"BMHM":-464,"BOMH":620,"OHHH":346,"OHHI":1729,"OHII":997,"OHMH":481,"OIHH":623,"OIIH":1344,"OKAK":2792,"OKHH":587,"OKKA":679,"OOHH":110,"OOII":-685};
this.TQ4__ = {"BHHH":-721,"BHHM":-3604,"BHII":-966,"BIIH":-607,"BIII":-2181,"OAAA":-2763,"OAKK":180,"OHHH":-294,"OHHI":2446,"OHHO":480,"OHIH":-1573,"OIHH":1935,"OIHI":-493,"OIIH":626,"OIII":-4007,"OKAK":-8156};
this.TW1__ = {"につい":-4681,"東京都":2026};
this.TW2__ = {"ある程":-2049,"いった":-1256,"ころが":-2434,"しょう":3873,"その後":-4430,"だって":-1049,"ていた":1833,"として":-4657,"ともに":-4517,"もので":1882,"一気に":-792,"初めて":-1512,"同時に":-8097,"大きな":-1255,"対して":-2721,"社会党":-3216};
this.TW3__ = {"いただ":-1734,"してい":1314,"として":-4314,"につい":-5483,"にとっ":-5989,"に当た":-6247,"ので,":-727,"ので、":-727,"のもの":-600,"れから":-3752,"十二月":-2287};
this.TW4__ = {"いう.":8576,"いう。":8576,"からな":-2348,"してい":2958,"たが,":1516,"たが、":1516,"ている":1538,"という":1349,"ました":5543,"ません":1097,"ようと":-4258,"よると":5865};
this.UC1__ = {"A":484,"K":93,"M":645,"O":-505};
this.UC2__ = {"A":819,"H":1059,"I":409,"M":3987,"N":5775,"O":646};
this.UC3__ = {"A":-1370,"I":2311};
this.UC4__ = {"A":-2643,"H":1809,"I":-1032,"K":-3450,"M":3565,"N":3876,"O":6646};
this.UC5__ = {"H":313,"I":-1238,"K":-799,"M":539,"O":-831};
this.UC6__ = {"H":-506,"I":-253,"K":87,"M":247,"O":-387};
this.UP1__ = {"O":-214};
this.UP2__ = {"B":69,"O":935};
this.UP3__ = {"B":189};
this.UQ1__ = {"BH":21,"BI":-12,"BK":-99,"BN":142,"BO":-56,"OH":-95,"OI":477,"OK":410,"OO":-2422};
this.UQ2__ = {"BH":216,"BI":113,"OK":1759};
this.UQ3__ = {"BA":-479,"BH":42,"BI":1913,"BK":-7198,"BM":3160,"BN":6427,"BO":14761,"OI":-827,"ON":-3212};
this.UW1__ = {",":156,"、":156,"「":-463,"あ":-941,"う":-127,"が":-553,"き":121,"こ":505,"で":-201,"と":-547,"ど":-123,"に":-789,"の":-185,"は":-847,"も":-466,"や":-470,"よ":182,"ら":-292,"り":208,"れ":169,"を":-446,"ん":-137,"・":-135,"主":-402,"京":-268,"区":-912,"午":871,"国":-460,"大":561,"委":729,"市":-411,"日":-141,"理":361,"生":-408,"県":-386,"都":-718,"「":-463,"・":-135};
this.UW2__ = {",":-829,"、":-829,"":892,"「":-645,"」":3145,"あ":-538,"い":505,"う":134,"お":-502,"か":1454,"が":-856,"く":-412,"こ":1141,"さ":878,"ざ":540,"し":1529,"す":-675,"せ":300,"そ":-1011,"た":188,"だ":1837,"つ":-949,"て":-291,"で":-268,"と":-981,"ど":1273,"な":1063,"に":-1764,"の":130,"は":-409,"ひ":-1273,"べ":1261,"ま":600,"も":-1263,"や":-402,"よ":1639,"り":-579,"る":-694,"れ":571,"を":-2516,"ん":2095,"ア":-587,"カ":306,"キ":568,"ッ":831,"三":-758,"不":-2150,"世":-302,"中":-968,"主":-861,"事":492,"人":-123,"会":978,"保":362,"入":548,"初":-3025,"副":-1566,"北":-3414,"区":-422,"大":-1769,"天":-865,"太":-483,"子":-1519,"学":760,"実":1023,"小":-2009,"市":-813,"年":-1060,"強":1067,"手":-1519,"揺":-1033,"政":1522,"文":-1355,"新":-1682,"日":-1815,"明":-1462,"最":-630,"朝":-1843,"本":-1650,"東":-931,"果":-665,"次":-2378,"民":-180,"気":-1740,"理":752,"発":529,"目":-1584,"相":-242,"県":-1165,"立":-763,"第":810,"米":509,"自":-1353,"行":838,"西":-744,"見":-3874,"調":1010,"議":1198,"込":3041,"開":1758,"間":-1257,"「":-645,"」":3145,"ッ":831,"ア":-587,"カ":306,"キ":568};
this.UW3__ = {",":4889,"1":-800,"":-1723,"、":4889,"々":-2311,"":5827,"」":2670,"〓":-3573,"あ":-2696,"い":1006,"う":2342,"え":1983,"お":-4864,"か":-1163,"が":3271,"く":1004,"け":388,"げ":401,"こ":-3552,"ご":-3116,"さ":-1058,"し":-395,"す":584,"せ":3685,"そ":-5228,"た":842,"ち":-521,"っ":-1444,"つ":-1081,"て":6167,"で":2318,"と":1691,"ど":-899,"な":-2788,"に":2745,"の":4056,"は":4555,"ひ":-2171,"ふ":-1798,"へ":1199,"ほ":-5516,"ま":-4384,"み":-120,"め":1205,"も":2323,"や":-788,"よ":-202,"ら":727,"り":649,"る":5905,"れ":2773,"わ":-1207,"を":6620,"ん":-518,"ア":551,"グ":1319,"ス":874,"ッ":-1350,"ト":521,"ム":1109,"ル":1591,"ロ":2201,"ン":278,"・":-3794,"一":-1619,"下":-1759,"世":-2087,"両":3815,"中":653,"主":-758,"予":-1193,"二":974,"人":2742,"今":792,"他":1889,"以":-1368,"低":811,"何":4265,"作":-361,"保":-2439,"元":4858,"党":3593,"全":1574,"公":-3030,"六":755,"共":-1880,"円":5807,"再":3095,"分":457,"初":2475,"別":1129,"前":2286,"副":4437,"力":365,"動":-949,"務":-1872,"化":1327,"北":-1038,"区":4646,"千":-2309,"午":-783,"協":-1006,"口":483,"右":1233,"各":3588,"合":-241,"同":3906,"和":-837,"員":4513,"国":642,"型":1389,"場":1219,"外":-241,"妻":2016,"学":-1356,"安":-423,"実":-1008,"家":1078,"小":-513,"少":-3102,"州":1155,"市":3197,"平":-1804,"年":2416,"広":-1030,"府":1605,"度":1452,"建":-2352,"当":-3885,"得":1905,"思":-1291,"性":1822,"戸":-488,"指":-3973,"政":-2013,"教":-1479,"数":3222,"文":-1489,"新":1764,"日":2099,"旧":5792,"昨":-661,"時":-1248,"曜":-951,"最":-937,"月":4125,"期":360,"李":3094,"村":364,"東":-805,"核":5156,"森":2438,"業":484,"氏":2613,"民":-1694,"決":-1073,"法":1868,"海":-495,"無":979,"物":461,"特":-3850,"生":-273,"用":914,"町":1215,"的":7313,"直":-1835,"省":792,"県":6293,"知":-1528,"私":4231,"税":401,"立":-960,"第":1201,"米":7767,"系":3066,"約":3663,"級":1384,"統":-4229,"総":1163,"線":1255,"者":6457,"能":725,"自":-2869,"英":785,"見":1044,"調":-562,"財":-733,"費":1777,"車":1835,"軍":1375,"込":-1504,"通":-1136,"選":-681,"郎":1026,"郡":4404,"部":1200,"金":2163,"長":421,"開":-1432,"間":1302,"関":-1282,"雨":2009,"電":-1045,"非":2066,"駅":1620,"":-800,"」":2670,"・":-3794,"ッ":-1350,"ア":551,"グ":1319,"ス":874,"ト":521,"ム":1109,"ル":1591,"ロ":2201,"ン":278};
this.UW4__ = {",":3930,".":3508,"―":-4841,"、":3930,"。":3508,"":4999,"「":1895,"」":3798,"〓":-5156,"あ":4752,"い":-3435,"う":-640,"え":-2514,"お":2405,"か":530,"が":6006,"き":-4482,"ぎ":-3821,"く":-3788,"け":-4376,"げ":-4734,"こ":2255,"ご":1979,"さ":2864,"し":-843,"じ":-2506,"す":-731,"ず":1251,"せ":181,"そ":4091,"た":5034,"だ":5408,"ち":-3654,"っ":-5882,"つ":-1659,"て":3994,"で":7410,"と":4547,"な":5433,"に":6499,"ぬ":1853,"ね":1413,"の":7396,"は":8578,"ば":1940,"ひ":4249,"び":-4134,"ふ":1345,"へ":6665,"べ":-744,"ほ":1464,"ま":1051,"み":-2082,"む":-882,"め":-5046,"も":4169,"ゃ":-2666,"や":2795,"ょ":-1544,"よ":3351,"ら":-2922,"り":-9726,"る":-14896,"れ":-2613,"ろ":-4570,"わ":-1783,"を":13150,"ん":-2352,"カ":2145,"コ":1789,"セ":1287,"ッ":-724,"ト":-403,"メ":-1635,"ラ":-881,"リ":-541,"ル":-856,"ン":-3637,"・":-4371,"ー":-11870,"一":-2069,"中":2210,"予":782,"事":-190,"井":-1768,"人":1036,"以":544,"会":950,"体":-1286,"作":530,"側":4292,"先":601,"党":-2006,"共":-1212,"内":584,"円":788,"初":1347,"前":1623,"副":3879,"力":-302,"動":-740,"務":-2715,"化":776,"区":4517,"協":1013,"参":1555,"合":-1834,"和":-681,"員":-910,"器":-851,"回":1500,"国":-619,"園":-1200,"地":866,"場":-1410,"塁":-2094,"士":-1413,"多":1067,"大":571,"子":-4802,"学":-1397,"定":-1057,"寺":-809,"小":1910,"屋":-1328,"山":-1500,"島":-2056,"川":-2667,"市":2771,"年":374,"庁":-4556,"後":456,"性":553,"感":916,"所":-1566,"支":856,"改":787,"政":2182,"教":704,"文":522,"方":-856,"日":1798,"時":1829,"最":845,"月":-9066,"木":-485,"来":-442,"校":-360,"業":-1043,"氏":5388,"民":-2716,"気":-910,"沢":-939,"済":-543,"物":-735,"率":672,"球":-1267,"生":-1286,"産":-1101,"田":-2900,"町":1826,"的":2586,"目":922,"省":-3485,"県":2997,"空":-867,"立":-2112,"第":788,"米":2937,"系":786,"約":2171,"経":1146,"統":-1169,"総":940,"線":-994,"署":749,"者":2145,"能":-730,"般":-852,"行":-792,"規":792,"警":-1184,"議":-244,"谷":-1000,"賞":730,"車":-1481,"軍":1158,"輪":-1433,"込":-3370,"近":929,"道":-1291,"選":2596,"郎":-4866,"都":1192,"野":-1100,"銀":-2213,"長":357,"間":-2344,"院":-2297,"際":-2604,"電":-878,"領":-1659,"題":-792,"館":-1984,"首":1749,"高":2120,"「":1895,"」":3798,"・":-4371,"ッ":-724,"ー":-11870,"カ":2145,"コ":1789,"セ":1287,"ト":-403,"メ":-1635,"ラ":-881,"リ":-541,"ル":-856,"ン":-3637};
this.UW5__ = {",":465,".":-299,"1":-514,"E2":-32768,"]":-2762,"、":465,"。":-299,"「":363,"あ":1655,"い":331,"う":-503,"え":1199,"お":527,"か":647,"が":-421,"き":1624,"ぎ":1971,"く":312,"げ":-983,"さ":-1537,"し":-1371,"す":-852,"だ":-1186,"ち":1093,"っ":52,"つ":921,"て":-18,"で":-850,"と":-127,"ど":1682,"な":-787,"に":-1224,"の":-635,"は":-578,"べ":1001,"み":502,"め":865,"ゃ":3350,"ょ":854,"り":-208,"る":429,"れ":504,"わ":419,"を":-1264,"ん":327,"イ":241,"ル":451,"ン":-343,"中":-871,"京":722,"会":-1153,"党":-654,"務":3519,"区":-901,"告":848,"員":2104,"大":-1296,"学":-548,"定":1785,"嵐":-1304,"市":-2991,"席":921,"年":1763,"思":872,"所":-814,"挙":1618,"新":-1682,"日":218,"月":-4353,"査":932,"格":1356,"機":-1508,"氏":-1347,"田":240,"町":-3912,"的":-3149,"相":1319,"省":-1052,"県":-4003,"研":-997,"社":-278,"空":-813,"統":1955,"者":-2233,"表":663,"語":-1073,"議":1219,"選":-1018,"郎":-368,"長":786,"間":1191,"題":2368,"館":-689,"":-514,"":-32768,"「":363,"イ":241,"ル":451,"ン":-343};
this.UW6__ = {",":227,".":808,"1":-270,"E1":306,"、":227,"。":808,"あ":-307,"う":189,"か":241,"が":-73,"く":-121,"こ":-200,"じ":1782,"す":383,"た":-428,"っ":573,"て":-1014,"で":101,"と":-105,"な":-253,"に":-149,"の":-417,"は":-236,"も":-206,"り":187,"る":-135,"を":195,"ル":-673,"ン":-496,"一":-277,"中":201,"件":-800,"会":624,"前":302,"区":1792,"員":-1212,"委":798,"学":-960,"市":887,"広":-695,"後":535,"業":-697,"相":753,"社":-507,"福":974,"空":-822,"者":1811,"連":463,"郎":1082,"":-270,"":306,"ル":-673,"ン":-496};
return this;
}
TinySegmenter.prototype.ctype_ = function(str) {
for (var i in this.chartype_) {
if (str.match(this.chartype_[i][0])) {
return this.chartype_[i][1];
}
}
return "O";
}
TinySegmenter.prototype.ts_ = function(v) {
if (v) { return v; }
return 0;
}
TinySegmenter.prototype.segment = function(input) {
if (input == null || input == undefined || input == "") {
return [];
}
var result = [];
var seg = ["B3","B2","B1"];
var ctype = ["O","O","O"];
var o = input.split("");
for (i = 0; i < o.length; ++i) {
seg.push(o[i]);
ctype.push(this.ctype_(o[i]))
}
seg.push("E1");
seg.push("E2");
seg.push("E3");
ctype.push("O");
ctype.push("O");
ctype.push("O");
var word = seg[3];
var p1 = "U";
var p2 = "U";
var p3 = "U";
for (var i = 4; i < seg.length - 3; ++i) {
var score = this.BIAS__;
var w1 = seg[i-3];
var w2 = seg[i-2];
var w3 = seg[i-1];
var w4 = seg[i];
var w5 = seg[i+1];
var w6 = seg[i+2];
var c1 = ctype[i-3];
var c2 = ctype[i-2];
var c3 = ctype[i-1];
var c4 = ctype[i];
var c5 = ctype[i+1];
var c6 = ctype[i+2];
score += this.ts_(this.UP1__[p1]);
score += this.ts_(this.UP2__[p2]);
score += this.ts_(this.UP3__[p3]);
score += this.ts_(this.BP1__[p1 + p2]);
score += this.ts_(this.BP2__[p2 + p3]);
score += this.ts_(this.UW1__[w1]);
score += this.ts_(this.UW2__[w2]);
score += this.ts_(this.UW3__[w3]);
score += this.ts_(this.UW4__[w4]);
score += this.ts_(this.UW5__[w5]);
score += this.ts_(this.UW6__[w6]);
score += this.ts_(this.BW1__[w2 + w3]);
score += this.ts_(this.BW2__[w3 + w4]);
score += this.ts_(this.BW3__[w4 + w5]);
score += this.ts_(this.TW1__[w1 + w2 + w3]);
score += this.ts_(this.TW2__[w2 + w3 + w4]);
score += this.ts_(this.TW3__[w3 + w4 + w5]);
score += this.ts_(this.TW4__[w4 + w5 + w6]);
score += this.ts_(this.UC1__[c1]);
score += this.ts_(this.UC2__[c2]);
score += this.ts_(this.UC3__[c3]);
score += this.ts_(this.UC4__[c4]);
score += this.ts_(this.UC5__[c5]);
score += this.ts_(this.UC6__[c6]);
score += this.ts_(this.BC1__[c2 + c3]);
score += this.ts_(this.BC2__[c3 + c4]);
score += this.ts_(this.BC3__[c4 + c5]);
score += this.ts_(this.TC1__[c1 + c2 + c3]);
score += this.ts_(this.TC2__[c2 + c3 + c4]);
score += this.ts_(this.TC3__[c3 + c4 + c5]);
score += this.ts_(this.TC4__[c4 + c5 + c6]);
// score += this.ts_(this.TC5__[c4 + c5 + c6]);
score += this.ts_(this.UQ1__[p1 + c1]);
score += this.ts_(this.UQ2__[p2 + c2]);
score += this.ts_(this.UQ3__[p3 + c3]);
score += this.ts_(this.BQ1__[p2 + c2 + c3]);
score += this.ts_(this.BQ2__[p2 + c3 + c4]);
score += this.ts_(this.BQ3__[p3 + c2 + c3]);
score += this.ts_(this.BQ4__[p3 + c3 + c4]);
score += this.ts_(this.TQ1__[p2 + c1 + c2 + c3]);
score += this.ts_(this.TQ2__[p2 + c2 + c3 + c4]);
score += this.ts_(this.TQ3__[p3 + c1 + c2 + c3]);
score += this.ts_(this.TQ4__[p3 + c2 + c3 + c4]);
var p = "O";
if (score > 0) {
result.push(word);
word = "";
p = "B";
}
p1 = p2;
p2 = p3;
p3 = p;
word += seg[i];
}
result.push(word);
return result;
}
lunr.TinySegmenter = TinySegmenter;
};
}));

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"version":3,"sources":["src/templates/assets/stylesheets/palette/_scheme.scss","../../../../src/templates/assets/stylesheets/palette.scss","src/templates/assets/stylesheets/palette/_accent.scss","src/templates/assets/stylesheets/palette/_primary.scss","src/templates/assets/stylesheets/utilities/_break.scss"],"names":[],"mappings":"AA2BA,cAGE,6BAME,sDAAA,CACA,6DAAA,CACA,+DAAA,CACA,gEAAA,CACA,mDAAA,CACA,6DAAA,CACA,+DAAA,CACA,gEAAA,CAGA,mDAAA,CACA,gDAAA,CAGA,0BAAA,CACA,mCAAA,CAGA,iCAAA,CACA,kCAAA,CACA,mCAAA,CACA,mCAAA,CACA,kCAAA,CACA,iCAAA,CACA,+CAAA,CACA,6DAAA,CACA,gEAAA,CACA,4DAAA,CACA,4DAAA,CACA,6DAAA,CAGA,6CAAA,CAGA,+CAAA,CAGA,uDAAA,CACA,6DAAA,CACA,2DAAA,CAGA,iCAAA,CAGA,yDAAA,CACA,iEAAA,CAGA,mDAAA,CACA,mDAAA,CAGA,qDAAA,CACA,uDAAA,CAGA,8DAAA,CAKA,8DAAA,CAKA,0DAAA,CAvEA,iBCeF,CD6DE,kHAEE,YC3DJ,CDkFE,yDACE,4BChFJ,CD+EE,2DACE,4BC7EJ,CD4EE,gEACE,4BC1EJ,CDyEE,2DACE,4BCvEJ,CDsEE,yDACE,4BCpEJ,CDmEE,0DACE,4BCjEJ,CDgEE,gEACE,4BC9DJ,CD6DE,0DACE,4BC3DJ,CD0DE,2OACE,4BC/CJ,CDsDA,+FAGE,iCCpDF,CACF,CC/CE,2BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCD2CN,CCrDE,4BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDkDN,CC5DE,8BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDyDN,CCnEE,mCACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDgEN,CC1EE,8BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDuEN,CCjFE,4BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCD8EN,CCxFE,kCACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDqFN,CC/FE,4BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCD4FN,CCtGE,4BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDmGN,CC7GE,6BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCD0GN,CCpHE,mCACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDiHN,CC3HE,4BACE,4BAAA,CACA,2CAAA,CAIE,8BAAA,CACA,qCD2HN,CClIE,8BACE,4BAAA,CACA,2CAAA,CAIE,8BAAA,CACA,qCDkIN,CCzIE,6BACE,yBAAA,CACA,2CAAA,CAIE,8BAAA,CACA,qCDyIN,CChJE,8BACE,4BAAA,CACA,2CAAA,CAIE,8BAAA,CACA,qCDgJN,CCvJE,mCACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDoJN,CEzJE,4BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFsJN,CEjKE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCF8JN,CEzKE,+BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFsKN,CEjLE,oCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCF8KN,CEzLE,+BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFsLN,CEjME,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCF8LN,CEzME,mCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFsMN,CEjNE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCF8MN,CEzNE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFsNN,CEjOE,8BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCF8NN,CEzOE,oCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFsON,CEjPE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAIE,+BAAA,CACA,sCFiPN,CEzPE,+BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAIE,+BAAA,CACA,sCFyPN,CEjQE,8BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAIE,+BAAA,CACA,sCFiQN,CEzQE,+BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAIE,+BAAA,CACA,sCFyQN,CEjRE,oCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCF8QN,CEzRE,8BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFsRN,CEjSE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCAAA,CAKA,4BF0RN,CE1SE,kCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCAAA,CAKA,4BFmSN,CEpRE,sEACE,4BFuRJ,CExRE,+DACE,4BF2RJ,CE5RE,iEACE,4BF+RJ,CEhSE,gEACE,4BFmSJ,CEpSE,iEACE,4BFuSJ,CE9RA,8BACE,mDAAA,CACA,4DAAA,CACA,0DAAA,CACA,oDAAA,CACA,2DAAA,CAGA,4BF+RF,CE5RE,yCACE,+BF8RJ,CE3RI,kDAEE,0CAAA,CACA,sCAAA,CAFA,mCF+RN,CG3MI,mCD1EA,+CACE,8CFwRJ,CErRI,qDACE,8CFuRN,CElRE,iEACE,mCFoRJ,CACF,CGtNI,sCDvDA,uCACE,oCFgRJ,CACF,CEvQA,8BACE,kDAAA,CACA,4DAAA,CACA,wDAAA,CACA,oDAAA,CACA,6DAAA,CAGA,4BFwQF,CErQE,yCACE,+BFuQJ,CEpQI,kDAEE,0CAAA,CACA,sCAAA,CAFA,mCFwQN,CEjQE,yCACE,6CFmQJ,CG5NI,0CDhCA,8CACE,gDF+PJ,CACF,CGjOI,0CDvBA,iFACE,6CF2PJ,CACF,CGzPI,sCDKA,uCACE,6CFuPJ,CACF","file":"palette.css"}

6
v0.20.0/charts/OWNERS Normal file
View File

@ -0,0 +1,6 @@
labels:
- chart
approvers:
- stevehipwell
reviewers:
- stevehipwell

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
apiVersion: v2
name: external-dns
description: ExternalDNS synchronizes exposed Kubernetes Services and Ingresses with DNS providers.
type: application
version: 1.19.0
appVersion: 0.19.0
keywords:
- kubernetes
- k8s
- externaldns
- external-dns
- dns
- service
- ingress
- gateway
home: https://github.com/kubernetes-sigs/external-dns/
icon: https://github.com/kubernetes-sigs/external-dns/raw/master/docs/img/external-dns.png
sources:
- https://github.com/kubernetes-sigs/external-dns/
maintainers:
- name: stevehipwell
email: steve.hipwell@gmail.com
annotations: {}

View File

@ -0,0 +1,94 @@
{{ template "chart.header" . }}
{{ template "chart.deprecationWarning" . }}
{{ template "chart.badgesSection" . }}
{{ template "chart.description" . }}
{{ template "chart.homepageLine" . }}
{{ template "chart.maintainersSection" . }}
{{ template "chart.sourcesSection" . }}
## Installing the Chart
Before you can install the chart you will need to add the `external-dns` repo to [Helm](https://helm.sh/).
```shell
helm repo add external-dns https://kubernetes-sigs.github.io/external-dns/
```
After you've installed the repo you can install the chart.
```shell
helm upgrade --install {{ template "chart.name" . }} external-dns/{{ template "chart.name" . }} --version {{ template "chart.version" . }}
```
## Providers
> Legacy support of setting `provider: <name>` is deprecated.
Configuring the _ExternalDNS_ provider should be done via the `provider.name` value with provider specific configuration being set via the `provider.<name>.<key>` values, where supported, and the `extraArgs` value.
See [documentation](https://kubernetes-sigs.github.io/external-dns/#new-providers) for more info on available providers and tutorials.
### Providers with Specific Configuration Support
| Provider | Supported |
|------------------------|------------|
| `webhook` | ✅ |
### Other Providers
For set up for a specific provider using the Helm chart, see the following links:
* [AWS](https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/aws.md#using-helm-with-oidc)
* [akamai-edgedns](https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/akamai-edgedns.md#using-helm)
* [cloudflare](https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/cloudflare.md#using-helm)
* [digitalocean](https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/digitalocean.md#using-helm)
* [godaddy](https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/godaddy.md#using-helm)
* [ns1](https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/ns1.md#using-helm)
* [plural](https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/plural.md#using-helm)
## Namespaced Scoped Installation
external-dns supports running on a namespaced only scope, too.
If `namespaced=true` is defined, the helm chart will setup `Roles` and `RoleBindings` instead `ClusterRoles` and `ClusterRoleBindings`.
### Limited Supported
Not all sources are supported in namespaced scope, since some sources depends on cluster-wide resources.
For example: Source `node` isn't supported, since `kind: Node` has scope `Cluster`.
Sources like `istio-virtualservice` only work, if all resources like `Gateway` and `VirtualService` are present in the same
namespaces as `external-dns`.
The annotation `external-dns.alpha.kubernetes.io/endpoints-type: NodeExternalIP` is not supported.
If `namespaced` is set to `true`, please ensure that `sources` my only contains supported sources (Default: `service,ingress`).
### Support Matrix
| Source | Supported | Infos |
|------------------------|------------|------------------------|
| `ingress` | ✅ | |
| `istio-gateway` | ✅ | |
| `istio-virtualservice` | ✅ | |
| `crd` | ✅ | |
| `kong-tcpingress` | ✅ | |
| `openshift-route` | ✅ | |
| `skipper-routegroup` | ✅ | |
| `gloo-proxy` | ✅ | |
| `contour-httpproxy` | ✅ | |
| `service` | ⚠️️ | NodePort not supported |
| `node` | ❌ | |
| `pod` | ❌ | |
{{ template "chart.requirementsSection" . }}
{{ template "chart.valuesSection" . }}
----------------------------------------------
Autogenerated from chart metadata using [helm-docs](https://github.com/norwoodj/helm-docs/).

View File

@ -0,0 +1,44 @@
labelFilter: foo=bar
managedRecordTypes: []
provider:
name: inmemory
affinity:
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
topologyKey: "kubernetes.io/hostname"
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- test
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: "kubernetes.io/hostname"
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
topologyKey: "kubernetes.io/hostname"
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: "kubernetes.io/hostname"
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- test
topologySpreadConstraints:
- maxSkew: 1
topologyKey: "topology.kubernetes.io/zone"
whenUnsatisfiable: "ScheduleAnyway"
serviceAccount:
annotations:
notTemplated/version: "v1.2.3"
justValueTemplated/version: "{{ .Chart.Version }}"
"{{ .Chart.Name }}/chart": "{{ .Chart.Version }}"
booleanVersion: "true"
number: "1"

View File

@ -0,0 +1,96 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
api-approved.kubernetes.io: https://github.com/kubernetes-sigs/external-dns/pull/2007
name: dnsendpoints.externaldns.k8s.io
spec:
group: externaldns.k8s.io
names:
kind: DNSEndpoint
listKind: DNSEndpointList
plural: dnsendpoints
singular: dnsendpoint
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: |-
DNSEndpoint is a contract that a user-specified CRD must implement to be used as a source for external-dns.
The user-specified CRD should also have the status sub-resource.
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: DNSEndpointSpec defines the desired state of DNSEndpoint
properties:
endpoints:
items:
description: Endpoint is a high-level way of a connection between a service and an IP
properties:
dnsName:
description: The hostname of the DNS record
type: string
labels:
additionalProperties:
type: string
description: Labels stores labels defined for the Endpoint
type: object
providerSpecific:
description: ProviderSpecific stores provider specific config
items:
description: ProviderSpecificProperty holds the name and value of a configuration which is specific to individual DNS providers
properties:
name:
type: string
value:
type: string
type: object
type: array
recordTTL:
description: TTL for the record
format: int64
type: integer
recordType:
description: RecordType type of record, e.g. CNAME, A, AAAA, SRV, TXT etc
type: string
setIdentifier:
description: Identifier to distinguish multiple records with the same name and type (e.g. Route53 records with routing policies other than 'simple')
type: string
targets:
description: The targets the DNS record points to
items:
type: string
type: array
type: object
type: array
type: object
status:
description: DNSEndpointStatus defines the observed state of DNSEndpoint
properties:
observedGeneration:
description: The generation observed by the external-dns controller.
format: int64
type: integer
type: object
type: object
served: true
storage: true
subresources:
status: {}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
# Custom values for schema creation.
# This is a YAML-formatted file.
# Declare variables to be passed into your schema.
resources:
requests:
cpu: 200m
memory: 128Mi
limits:
cpu: 200m
memory: 128Mi
provider:
webhook:
requests:
cpu: 200m
memory: 128Mi
limits:
cpu: 300m
memory: 200Mi

View File

@ -0,0 +1,18 @@
***********************************************************************
* External DNS *
***********************************************************************
Chart version: {{ .Chart.Version }}
App version: {{ .Chart.AppVersion }}
Image tag: {{ include "external-dns.image" . }}
***********************************************************************
{{- if eq (typeOf .Values.provider) "string" }}
🚧 DEPRECATIONS 🚧
The following features, functions, or methods are deprecated and no longer recommended for use.
{{/* The deprecation message for legacy 'provider: name'. */}}
{{- if eq (typeOf .Values.provider) "string" -}}
❗❗❗ DEPRECATED ❗❗❗ The legacy 'provider: <name>' configuration is in use. Support will be removed in future releases.
{{- end -}}
{{- end }}

View File

@ -0,0 +1,114 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "external-dns.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "external-dns.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "external-dns.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "external-dns.labels" -}}
helm.sh/chart: {{ include "external-dns.chart" . }}
{{ include "external-dns.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- with .Values.commonLabels }}
{{ toYaml . }}
{{- end }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "external-dns.selectorLabels" -}}
app.kubernetes.io/name: {{ include "external-dns.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "external-dns.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "external-dns.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
{{/*
The image to use
*/}}
{{- define "external-dns.image" -}}
{{- printf "%s:%s" .Values.image.repository (default (printf "v%s" .Chart.AppVersion) .Values.image.tag) }}
{{- end }}
{{/*
Provider name, Keeps backward compatibility on provider
TODO: line eq (typeOf .Values.provider) "string" to be removed in future releases
*/}}
{{- define "external-dns.providerName" -}}
{{- if eq (typeOf .Values.provider) "string" }}
{{- .Values.provider }}
{{- else }}
{{- .Values.provider.name }}
{{- end }}
{{- end }}
{{/*
The image to use for optional webhook sidecar
*/}}
{{- define "external-dns.webhookImage" -}}
{{- with .image }}
{{- if or (empty .repository) (empty .tag) }}
{{- fail "ERROR: webhook provider needs an image repository and a tag" }}
{{- end }}
{{- printf "%s:%s" .repository .tag }}
{{- end }}
{{- end }}
{{/*
The pod affinity default label Selector
*/}}
{{- define "external-dns.labelSelector" -}}
labelSelector:
matchLabels:
{{ include "external-dns.selectorLabels" . | nindent 4 }}
{{- end }}
{{/*
Check if any Gateway API sources are enabled
*/}}
{{- define "external-dns.hasGatewaySources" -}}
{{- if or (has "gateway-httproute" .Values.sources) (has "gateway-grpcroute" .Values.sources) (has "gateway-tlsroute" .Values.sources) (has "gateway-tcproute" .Values.sources) (has "gateway-udproute" .Values.sources) -}}
true
{{- end -}}
{{- end }}

View File

@ -0,0 +1,163 @@
{{- if .Values.rbac.create -}}
apiVersion: rbac.authorization.k8s.io/v1
kind: {{ .Values.namespaced | ternary "Role" "ClusterRole" }}
metadata:
name: {{ template "external-dns.fullname" . }}
labels:
{{- include "external-dns.labels" . | nindent 4 }}
rules:
{{- if and (not .Values.namespaced) (or (has "node" .Values.sources) (has "pod" .Values.sources) (has "service" .Values.sources) (has "contour-httpproxy" .Values.sources) (has "gloo-proxy" .Values.sources) (has "openshift-route" .Values.sources) (has "skipper-routegroup" .Values.sources)) }}
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list","watch"]
{{- end }}
{{- if or (has "pod" .Values.sources) (has "service" .Values.sources) (has "contour-httpproxy" .Values.sources) (has "gloo-proxy" .Values.sources) (has "openshift-route" .Values.sources) (has "skipper-routegroup" .Values.sources) }}
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","watch","list"]
{{- end }}
{{- if or (has "service" .Values.sources) (has "contour-httpproxy" .Values.sources) (has "gloo-proxy" .Values.sources) (has "istio-gateway" .Values.sources) (has "istio-virtualservice" .Values.sources) (has "openshift-route" .Values.sources) (has "skipper-routegroup" .Values.sources) }}
- apiGroups: [""]
resources: ["services"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "service" .Values.sources }}
- apiGroups: ["discovery.k8s.io"]
resources: ["endpointslices"]
verbs: ["get","watch","list"]
{{- end }}
{{- if or (has "ingress" .Values.sources) (has "istio-gateway" .Values.sources) (has "istio-virtualservice" .Values.sources) (has "contour-httpproxy" .Values.sources) (has "openshift-route" .Values.sources) (has "skipper-routegroup" .Values.sources) }}
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
{{- end }}
{{- if or (has "istio-gateway" .Values.sources) (has "istio-virtualservice" .Values.sources) }}
- apiGroups: ["networking.istio.io"]
resources: ["gateways"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "istio-virtualservice" .Values.sources }}
- apiGroups: ["networking.istio.io"]
resources: ["virtualservices"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "ambassador-host" .Values.sources }}
- apiGroups: ["getambassador.io"]
resources: ["hosts","ingresses"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "contour-httpproxy" .Values.sources }}
- apiGroups: ["projectcontour.io"]
resources: ["httpproxies"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "crd" .Values.sources }}
- apiGroups: ["externaldns.k8s.io"]
resources: ["dnsendpoints"]
verbs: ["get","watch","list"]
- apiGroups: ["externaldns.k8s.io"]
resources: ["dnsendpoints/status"]
verbs: ["*"]
{{- end }}
{{- if include "external-dns.hasGatewaySources" . }}
{{- if or (not .Values.namespaced) (and .Values.namespaced (not .Values.gatewayNamespace)) }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways"]
verbs: ["get","watch","list"]
{{- end }}
{{- if not .Values.namespaced }}
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get","watch","list"]
{{- end }}
{{- end }}
{{- if has "gateway-httproute" .Values.sources }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["httproutes"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "gateway-grpcroute" .Values.sources }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["grpcroutes"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "gateway-tlsroute" .Values.sources }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["tlsroutes"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "gateway-tcproute" .Values.sources }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["tcproutes"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "gateway-udproute" .Values.sources }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["udproutes"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "gloo-proxy" .Values.sources }}
- apiGroups: ["gloo.solo.io","gateway.solo.io"]
resources: ["proxies","virtualservices"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "kong-tcpingress" .Values.sources }}
- apiGroups: ["configuration.konghq.com"]
resources: ["tcpingresses"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "traefik-proxy" .Values.sources }}
- apiGroups: ["traefik.containo.us", "traefik.io"]
resources: ["ingressroutes", "ingressroutetcps", "ingressrouteudps"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "openshift-route" .Values.sources }}
- apiGroups: ["route.openshift.io"]
resources: ["routes"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "skipper-routegroup" .Values.sources }}
- apiGroups: ["zalando.org"]
resources: ["routegroups"]
verbs: ["get","watch","list"]
- apiGroups: ["zalando.org"]
resources: ["routegroups/status"]
verbs: ["patch","update"]
{{- end }}
{{- if or (has "f5-virtualserver" .Values.sources) (has "f5-transportserver" .Values.sources) }}
- apiGroups: ["cis.f5.com"]
resources: ["virtualservers", "transportservers"]
verbs: ["get","watch","list"]
{{- end }}
{{- with .Values.rbac.additionalPermissions }}
{{- toYaml . | nindent 2 }}
{{- end }}
{{- if and .Values.rbac.create .Values.namespaced (include "external-dns.hasGatewaySources" .) }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ template "external-dns.fullname" . }}-namespaces
labels:
{{- include "external-dns.labels" . | nindent 4 }}
rules:
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get","watch","list"]
{{- if .Values.gatewayNamespace }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ template "external-dns.fullname" . }}-gateway
namespace: {{ .Values.gatewayNamespace }}
labels:
{{- include "external-dns.labels" . | nindent 4 }}
rules:
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways"]
verbs: ["get","watch","list"]
{{- end }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,51 @@
{{- if .Values.rbac.create -}}
apiVersion: rbac.authorization.k8s.io/v1
kind: {{ .Values.namespaced | ternary "RoleBinding" "ClusterRoleBinding" }}
metadata:
name: {{ printf "%s-viewer" (include "external-dns.fullname" .) }}
labels:
{{- include "external-dns.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: {{ .Values.namespaced | ternary "Role" "ClusterRole" }}
name: {{ template "external-dns.fullname" . }}
subjects:
- kind: ServiceAccount
name: {{ template "external-dns.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- if and .Values.rbac.create .Values.namespaced (include "external-dns.hasGatewaySources" .) }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ template "external-dns.fullname" . }}-namespaces
labels:
{{- include "external-dns.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ template "external-dns.fullname" . }}-namespaces
subjects:
- kind: ServiceAccount
name: {{ template "external-dns.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- if .Values.gatewayNamespace }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ template "external-dns.fullname" . }}-gateway
namespace: {{ .Values.gatewayNamespace }}
labels:
{{- include "external-dns.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ template "external-dns.fullname" . }}-gateway
subjects:
- kind: ServiceAccount
name: {{ template "external-dns.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- end }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,304 @@
{{- $defaultSelector := (include "external-dns.labelSelector" $ ) | fromYaml -}}
{{- $providerName := tpl (include "external-dns.providerName" .) $ }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "external-dns.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "external-dns.labels" . | nindent 4 }}
{{- with .Values.deploymentAnnotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
replicas: 1
selector:
matchLabels:
{{- include "external-dns.selectorLabels" . | nindent 6 }}
strategy:
{{- toYaml .Values.deploymentStrategy | nindent 4 }}
{{- if not (has (quote .Values.revisionHistoryLimit) (list "" (quote ""))) }}
revisionHistoryLimit: {{ .Values.revisionHistoryLimit | int64 }}
{{- end }}
template:
metadata:
labels:
{{- include "external-dns.selectorLabels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if or .Values.secretConfiguration.enabled .Values.podAnnotations }}
annotations:
{{- if .Values.secretConfiguration.enabled }}
checksum/secret: {{ tpl (toYaml .Values.secretConfiguration.data) . | sha256sum }}
{{- end }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
spec:
{{- if not (quote .Values.automountServiceAccountToken | empty) }}
automountServiceAccountToken: {{ .Values.automountServiceAccountToken }}
{{- end }}
{{- with (default .Values.global.imagePullSecrets .Values.imagePullSecrets) }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "external-dns.serviceAccountName" . }}
{{- with .Values.shareProcessNamespace }}
shareProcessNamespace: {{ . }}
{{- end }}
{{- with .Values.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.priorityClassName }}
priorityClassName: {{ . | quote }}
{{- end }}
{{- with .Values.terminationGracePeriodSeconds }}
terminationGracePeriodSeconds: {{ . }}
{{- end }}
{{- with .Values.dnsPolicy }}
dnsPolicy: {{ . }}
{{- end }}
{{- with .Values.dnsConfig }}
dnsConfig:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.initContainers }}
initContainers:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
{{- with .Values.extraContainers }}
{{- toYaml . | nindent 8 }}
{{- end }}
- name: external-dns
{{- with .Values.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
image: {{ include "external-dns.image" . }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
{{- with .Values.env }}
env:
{{- toYaml . | nindent 12 }}
{{- end }}
args:
- --log-level={{ .Values.logLevel }}
- --log-format={{ .Values.logFormat }}
- --interval={{ .Values.interval }}
{{- if .Values.triggerLoopOnEvent }}
- --events
{{- end }}
{{- range .Values.sources }}
- --source={{ . }}
{{- end }}
- --policy={{ .Values.policy }}
- --registry={{ .Values.registry }}
{{- if .Values.txtOwnerId }}
- --txt-owner-id={{ .Values.txtOwnerId }}
{{- end }}
{{- if and .Values.txtPrefix .Values.txtSuffix }}
{{- fail (printf "'txtPrefix' and 'txtSuffix' are mutually exclusive") }}
{{- end }}
{{- if .Values.txtPrefix }}
- --txt-prefix={{ .Values.txtPrefix }}
{{- else if .Values.txtSuffix }}
- --txt-suffix={{ .Values.txtSuffix }}
{{- end }}
{{- if .Values.namespaced }}
- --namespace={{ .Release.Namespace }}
{{- end }}
{{- if .Values.gatewayNamespace }}
- --gateway-namespace={{ .Values.gatewayNamespace }}
{{- end }}
{{- range .Values.domainFilters }}
- --domain-filter={{ . }}
{{- end }}
{{- range .Values.excludeDomains }}
- --exclude-domains={{ . }}
{{- end }}
{{- if .Values.labelFilter }}
- --label-filter={{ .Values.labelFilter }}
{{- end }}
{{- if .Values.annotationFilter }}
- --annotation-filter={{ .Values.annotationFilter }}
{{- end }}
{{- if .Values.annotationPrefix }}
- --annotation-prefix={{ .Values.annotationPrefix }}
{{- end }}
{{- range .Values.managedRecordTypes }}
- --managed-record-types={{ . }}
{{- end }}
- --provider={{ $providerName }}
{{- if kindIs "map" .Values.extraArgs }}
{{- range $key, $value := .Values.extraArgs }}
{{- if not (kindIs "invalid" $value) }}
{{- if kindIs "slice" $value }}
{{- range $value }}
- --{{ $key }}={{ tpl (. | toString) $ }}
{{- end }}
{{- else }}
- --{{ $key }}={{ tpl ($value | toString) $ }}
{{- end }}
{{- else }}
- --{{ $key }}
{{- end }}
{{- end }}
{{- end }}
{{- if kindIs "slice" .Values.extraArgs }}
{{- range .Values.extraArgs }}
- {{ tpl . $ }}
{{- end }}
{{- end }}
ports:
- name: http
protocol: TCP
containerPort: 7979
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
{{- if or .Values.secretConfiguration.enabled .Values.extraVolumeMounts }}
volumeMounts:
{{- if .Values.secretConfiguration.enabled }}
- name: secrets
mountPath: {{ tpl .Values.secretConfiguration.mountPath $ }}
{{- with .Values.secretConfiguration.subPath }}
subPath: {{ tpl . $ }}
{{- end }}
{{- end }}
{{- with .Values.extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- end }}
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if eq $providerName "webhook" }}
{{- with .Values.provider.webhook }}
- name: webhook
image: {{ include "external-dns.webhookImage" . }}
imagePullPolicy: {{ .image.pullPolicy }}
{{- with .env }}
env:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .args }}
args:
{{- toYaml . | nindent 12 }}
{{- end }}
ports:
- name: http-webhook
protocol: TCP
containerPort: 8080
livenessProbe:
{{- toYaml .livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .readinessProbe | nindent 12 }}
{{- if .extraVolumeMounts }}
volumeMounts:
{{- with .extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- end }}
{{- with .resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- end }}
{{- end }}
{{- if or .Values.secretConfiguration.enabled .Values.extraVolumes }}
volumes:
{{- if .Values.secretConfiguration.enabled }}
- name: secrets
secret:
secretName: {{ include "external-dns.fullname" . }}
{{- end }}
{{- with .Values.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- with .nodeAffinity }}
nodeAffinity:
{{- toYaml . | nindent 10 }}
{{- end }}
{{- with .podAffinity }}
podAffinity:
{{- with .preferredDuringSchedulingIgnoredDuringExecution }}
preferredDuringSchedulingIgnoredDuringExecution:
{{- range . }}
- podAffinityTerm:
{{- if dig "podAffinityTerm" "labelSelector" nil . }}
{{- toYaml .podAffinityTerm | nindent 16 }}
{{- else }}
{{- (merge $defaultSelector .podAffinityTerm) | toYaml | nindent 16 }}
{{- end }}
weight: {{ .weight }}
{{- end }}
{{- end }}
{{- with .requiredDuringSchedulingIgnoredDuringExecution }}
requiredDuringSchedulingIgnoredDuringExecution:
{{- range . }}
{{- if dig "labelSelector" nil . }}
- {{ toYaml . | indent 16 | trim }}
{{- else }}
- {{ (merge $defaultSelector .) | toYaml | indent 16 | trim }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{- with .podAntiAffinity }}
podAntiAffinity:
{{- with .preferredDuringSchedulingIgnoredDuringExecution }}
preferredDuringSchedulingIgnoredDuringExecution:
{{- range . }}
- podAffinityTerm:
{{- if dig "podAffinityTerm" "labelSelector" nil . }}
{{- toYaml .podAffinityTerm | nindent 16 }}
{{- else }}
{{- (merge $defaultSelector .podAffinityTerm) | toYaml | nindent 16 }}
{{- end }}
weight: {{ .weight }}
{{- end }}
{{- end }}
{{- with .requiredDuringSchedulingIgnoredDuringExecution }}
requiredDuringSchedulingIgnoredDuringExecution:
{{- range . }}
{{- if dig "labelSelector" nil . }}
- {{ toYaml . | indent 16 | trim }}
{{- else }}
- {{ (merge $defaultSelector .) | toYaml | indent 16 | trim }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{- with .Values.topologySpreadConstraints }}
topologySpreadConstraints:
{{- range . }}
- {{ toYaml . | nindent 10 | trim }}
{{- if not (hasKey . "labelSelector") }}
labelSelector:
matchLabels:
{{- include "external-dns.selectorLabels" $ | nindent 12 }}
{{- end }}
{{- end }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@ -0,0 +1,13 @@
{{- if .Values.secretConfiguration.enabled }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "external-dns.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "external-dns.labels" . | nindent 4 }}
data:
{{- range $key, $value := .Values.secretConfiguration.data }}
{{ $key }}: {{ tpl $value $ | b64enc | quote }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,36 @@
{{- $providerName := include "external-dns.providerName" . }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "external-dns.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "external-dns.labels" . | nindent 4 }}
{{- with .Values.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- with .Values.service.ipFamilies }}
ipFamilies:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.service.ipFamilyPolicy }}
ipFamilyPolicy: {{ . }}
{{- end }}
type: ClusterIP
selector:
{{- include "external-dns.selectorLabels" . | nindent 4 }}
ports:
- name: http
port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
{{- if eq $providerName "webhook" }}
{{- with .Values.provider.webhook.service }}
- name: http-webhook
port: {{ .port }}
targetPort: http-webhook
protocol: TCP
{{- end }}
{{- end }}

View File

@ -0,0 +1,19 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "external-dns.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "external-dns.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- range $k, $v := . }}
{{- printf "%s: %s" (toYaml (tpl $k $)) (toYaml (tpl $v $)) | nindent 4 }}
{{- end }}
{{- end }}
automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }}
{{- end }}

View File

@ -0,0 +1,86 @@
{{- if .Values.serviceMonitor.enabled -}}
{{- $providerName := include "external-dns.providerName" . }}
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ include "external-dns.fullname" . }}
namespace: {{ default .Release.Namespace .Values.serviceMonitor.namespace }}
{{- with .Values.serviceMonitor.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
labels:
{{- include "external-dns.labels" . | nindent 4 }}
{{- with .Values.serviceMonitor.additionalLabels }}
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
jobLabel: app.kubernetes.io/instance
namespaceSelector:
matchNames:
- {{ .Release.Namespace }}
selector:
matchLabels:
{{- include "external-dns.selectorLabels" . | nindent 6 }}
endpoints:
- port: http
path: /metrics
{{- with .Values.serviceMonitor.interval }}
interval: {{ . }}
{{- end }}
{{- with .Values.serviceMonitor.scheme }}
scheme: {{ . }}
{{- end }}
{{- with .Values.serviceMonitor.bearerTokenFile }}
bearerTokenFile: {{ . }}
{{- end }}
{{- with .Values.serviceMonitor.tlsConfig }}
tlsConfig:
{{- toYaml .| nindent 8 }}
{{- end }}
{{- with .Values.serviceMonitor.scrapeTimeout }}
scrapeTimeout: {{ . }}
{{- end }}
{{- with .Values.serviceMonitor.metricRelabelings }}
metricRelabelings:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.serviceMonitor.relabelings }}
relabelings:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if eq $providerName "webhook" }}
{{- with .Values.provider.webhook.serviceMonitor }}
- port: http-webhook
path: /metrics
{{- with .interval }}
interval: {{ . }}
{{- end }}
{{- with .scheme }}
scheme: {{ . }}
{{- end }}
{{- with .bearerTokenFile }}
bearerTokenFile: {{ . }}
{{- end }}
{{- with .tlsConfig }}
tlsConfig:
{{- toYaml .| nindent 8 }}
{{- end }}
{{- with .scrapeTimeout }}
scrapeTimeout: {{ . }}
{{- end }}
{{- with .metricRelabelings }}
metricRelabelings:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .relabelings }}
relabelings:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
{{- end }}
{{- with .Values.serviceMonitor.targetLabels }}
targetLabels:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,29 @@
suite: All resources contains metadata
templates:
- "*.yaml"
release:
name: external-dns
chart:
version: "1.15.0"
appVersion: "0.15.0"
tests:
- it: "should contain labels metadata in each template"
set:
secretConfiguration:
enabled: true
mountPath: "/etc/kubernetes/"
serviceMonitor:
enabled: true
asserts:
- exists:
path: metadata.labels
- isNotEmpty:
path: metadata.labels
- equal:
path: metadata.labels
value:
helm.sh/chart: external-dns-1.15.0
app.kubernetes.io/name: external-dns
app.kubernetes.io/instance: external-dns
app.kubernetes.io/version: "0.15.0"
app.kubernetes.io/managed-by: Helm

View File

@ -0,0 +1,81 @@
suite: Deployment configuration
templates:
- deployment.yaml
release:
namespace: default
tests:
- it: should provide expected defaults
asserts:
- isKind:
of: Deployment
- hasDocuments:
count: 1
- equal:
path: spec.replicas
value: 1
- equal:
path: spec.strategy.type
value: Recreate
- equal:
path: metadata.namespace
value: default
- equal:
path: spec.template.spec.automountServiceAccountToken
value: true
- it: should provide expected defaults for securityContext
asserts:
- isSubset:
path: spec.template.spec.containers[?(@.name == "external-dns")]
content:
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
privileged: false
readOnlyRootFilesystem: true
runAsGroup: 65532
runAsNonRoot: true
runAsUser: 65532
- it: should provide expected defaults for liveness and readiness
asserts:
- isSubset:
path: spec.template.spec.containers[?(@.name == "external-dns")]
content:
readinessProbe:
failureThreshold: 6
httpGet:
path: /healthz
port: http
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 5
livenessProbe:
failureThreshold: 2
httpGet:
path: /healthz
port: http
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 5
- it: should have service account set
release:
name: test
asserts:
- equal:
path: spec.template.spec.serviceAccountName
value: test-external-dns
- it: should have not be able to change replicas when specified via values
set:
deployment:
replicas: 3
asserts:
- equal:
path: spec.replicas
value: 1

View File

@ -0,0 +1,204 @@
suite: Deployment flags configurations
templates:
- deployment.yaml
release:
namespace: default
tests:
- it: should provide expected default flags
asserts:
- exists :
path: spec.template.spec.containers[?(@.name == "external-dns")]
- equal :
path: spec.template.spec.containers[?(@.name == "external-dns")].args
value:
- --log-level=info
- --log-format=text
- --interval=1m
- --source=service
- --source=ingress
- --policy=upsert-only
- --registry=txt
- --provider=aws
- it: should configure 'txtSuffix' when not empty with 'txtPrefix' empty
set:
txtPrefix: ""
txtSuffix: "test-suffix"
asserts:
- exists :
path: spec.template.spec.containers[?(@.name == "external-dns")]
- equal :
path: spec.template.spec.containers[?(@.name == "external-dns")].args
value:
- --log-level=info
- --log-format=text
- --interval=1m
- --source=service
- --source=ingress
- --policy=upsert-only
- --registry=txt
- --txt-suffix=test-suffix
- --provider=aws
- notContains:
path: spec.template.spec.containers[?(@.name == "external-dns")].args
content: "--txt-prefix=test-prefix"
- contains:
path: spec.template.spec.containers[?(@.name == "external-dns")].args
content: "--txt-suffix=test-suffix"
- it: should configure 'txtSuffix' when set and 'txtPrefix' is not present
set:
txtSuffix: "custom-suffix"
asserts:
- exists :
path: spec.template.spec.containers[?(@.name == "external-dns")]
- contains:
path: spec.template.spec.containers[?(@.name == "external-dns")].args
content: "--txt-suffix=custom-suffix"
- it: should be able configure multiple sources
set:
sources:
- fake
- crd
asserts:
- contains:
path: spec.template.spec.containers[?(@.name == "external-dns")].args
content: "--source=fake"
- contains:
path: spec.template.spec.containers[?(@.name == "external-dns")].args
content: "--source=crd"
- it: should be able to configure in single namespace
set:
namespaced: true
asserts:
- contains:
path: spec.template.spec.containers[?(@.name == "external-dns")].args
content: "--namespace=default"
- it: should manage multiple zones with 'extraArgs'
set:
extraArgs:
- --zone-id-filter=/hostedzone/Z00001
- --zone-id-filter=/hostedzone/Z00002
- --zone-id-filter=/hostedzone/Z00003
- --zone-id-filter=/hostedzone/Z00004
- --zone-id-filter=/hostedzone/Z00005
asserts:
- equal:
path: spec.template.spec.containers[?(@.name == "external-dns")].args
value:
- --log-level=info
- --log-format=text
- --interval=1m
- --source=service
- --source=ingress
- --policy=upsert-only
- --registry=txt
- --provider=aws
- --zone-id-filter=/hostedzone/Z00001
- --zone-id-filter=/hostedzone/Z00002
- --zone-id-filter=/hostedzone/Z00003
- --zone-id-filter=/hostedzone/Z00004
- --zone-id-filter=/hostedzone/Z00005
- it: should allow 'extraArgs' to be a slice
set:
extraArgs:
- --extraArgA=valueA
- --extraArgB=valueB
- --extraArgC=valueC-1
- --extraArgC=valueC-2
asserts:
- equal:
path: spec.template.spec.containers[?(@.name == "external-dns")].args
value:
- --log-level=info
- --log-format=text
- --interval=1m
- --source=service
- --source=ingress
- --policy=upsert-only
- --registry=txt
- --provider=aws
- --extraArgA=valueA
- --extraArgB=valueB
- --extraArgC=valueC-1
- --extraArgC=valueC-2
- it: should allow 'extraArgs' to be a map with its entries potentially being slices (lists) themselves
set:
extraArgs:
extraArgA: valueA
extraArgB: valueB
extraArgC:
- valueC-1
- valueC-2
asserts:
- equal:
path: spec.template.spec.containers[?(@.name == "external-dns")].args
value:
- --log-level=info
- --log-format=text
- --interval=1m
- --source=service
- --source=ingress
- --policy=upsert-only
- --registry=txt
- --provider=aws
- --extraArgA=valueA
- --extraArgB=valueB
- --extraArgC=valueC-1
- --extraArgC=valueC-2
- it: should throw error when txtPrefix and txtSuffix are set
set:
txtPrefix: "test-prefix"
txtSuffix: "test-suffix"
asserts:
- failedTemplate:
errorMessage: "'txtPrefix' and 'txtSuffix' are mutually exclusive"
- it: should configure custom annotation prefix
set:
annotationPrefix: "custom.io/"
asserts:
- contains:
path: spec.template.spec.containers[?(@.name == "external-dns")].args
content: "--annotation-prefix=custom.io/"
- it: should not include annotation-prefix flag when not set
set:
annotationPrefix: null
asserts:
- notContains:
path: spec.template.spec.containers[?(@.name == "external-dns")].args
content: "--annotation-prefix"
- it: should configure annotation prefix with other flags
set:
annotationPrefix: "internal.company.io/"
provider:
name: cloudflare
sources:
- service
- ingress
asserts:
- contains:
path: spec.template.spec.containers[?(@.name == "external-dns")].args
content: "--annotation-prefix=internal.company.io/"
- contains:
path: spec.template.spec.containers[?(@.name == "external-dns")].args
content: "--provider=cloudflare"
- contains:
path: spec.template.spec.containers[?(@.name == "external-dns")].args
content: "--source=service"
- contains:
path: spec.template.spec.containers[?(@.name == "external-dns")].args
content: "--source=ingress"

View File

@ -0,0 +1,118 @@
suite: Deployment scheduling configuration
templates:
- deployment.yaml
release:
namespace: default
tests:
- it: should not provide defaults for affinities or tolerations
asserts:
- isKind:
of: Deployment
- notExists:
path: spec.template.spec.tolerations
- notExists:
path: spec.template.spec.affinity
- it: should provide support for custom affinities
set:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- zoneA
- zoneB
asserts:
- equal:
path: spec.template.spec.affinity
value:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- zoneA
- zoneB
- it: should provide support for configuring node and pod affinities
set:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- test-pod-scheduling-config
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: datadog-agent
operator: In
values:
- standard
- key: iam/scope
operator: In
values:
- namespace
asserts:
- equal:
path: spec.template.spec.affinity
value:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: datadog-agent
operator: In
values:
- standard
- key: iam/scope
operator: In
values:
- namespace
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- test-pod-scheduling-config
topologyKey: kubernetes.io/hostname
- it: should provide support for configuring tolerations
set:
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoSchedule"
- key: iam/scope
operator: Equal
value: namespace
effect: NoExecute
asserts:
- notExists:
path: spec.template.spec.affinity
- exists:
path: spec.template.spec.tolerations
- equal:
path: spec.template.spec.tolerations
value:
- effect: NoSchedule
key: key1
operator: Equal
value: value1
- effect: NoExecute
key: iam/scope
operator: Equal
value: namespace

View File

@ -0,0 +1,64 @@
suite: Test json value schema validation
templates:
- templates/*
release:
name: notes
namespace: apps-notes
tests:
- it: should not throw an error when legacy provider is a string
set:
provider: legacy
asserts:
- notFailedTemplate: {}
- it: should not throw an error when provider is an object
set:
provider:
name: aws
webhook: {}
asserts:
- notFailedTemplate: {}
- it: should not throw an error when 'enabled' value is provided
set:
enabled: true
asserts:
- notFailedTemplate: {}
- it: should throw an error when 'enabled' value is not a bool or nil
set:
enabled: "abrakadabra"
asserts:
- failedTemplate:
errorPattern: "got string, want null or boolean"
- it: should fail if provider is null
set:
provider: null
asserts:
- failedTemplate:
errorPattern: 'executing "external-dns.providerName" at <.Values.provider.name>: nil pointer evaluating interface {}.name'
- it: should not fail if provider block is empty
set:
provider: {}
asserts:
- notFailedTemplate: {}
- it: should not fail when labelFilter has condition
set:
labelFilter: "mydomain.io/enable-dns-record in (true)"
asserts:
- notFailedTemplate: {}
- it: should not fail when livenessProbe is null
set:
livenessProbe: null
asserts:
- notFailedTemplate: {}
- it: should not fail when readinessProbe is null
set:
readinessProbe: null
asserts:
- notFailedTemplate: {}

View File

@ -0,0 +1,26 @@
suite: Test NOTES.txt deprecations
templates:
- templates/NOTES.txt
release:
name: notes
namespace: apps-notes
tests:
- it: should output a deprecation message when legacy provider configuration is in use
set:
provider: legacy
asserts:
- matchRegexRaw:
pattern: "🚧 DEPRECATIONS 🚧"
- matchRegexRaw:
pattern: |
❗❗❗ DEPRECATED ❗❗❗\sThe legacy 'provider: <name>' configuration is in use. Support will be removed in future releases.
- it: should not output a deprecation message when supported provider configuration is in use
set:
provider:
name: non-legacy-config
asserts:
- notMatchRegexRaw:
pattern: "🚧 DEPRECATIONS 🚧"
- notMatchRegexRaw:
pattern: ❗❗❗ DEPRECATED ❗❗❗

View File

@ -0,0 +1,21 @@
suite: NOTES.txt output
templates:
- NOTES.txt
chart:
appVersion: 0.15.0
version: 1.15.0
tests:
- it: should display message
set:
rbac:
namespaced: true
asserts:
- equalRaw:
value: |
***********************************************************************
* External DNS *
***********************************************************************
Chart version: 1.15.0
App version: 0.15.0
Image tag: registry.k8s.io/external-dns/external-dns:v0.15.0
***********************************************************************

View File

@ -0,0 +1,522 @@
suite: RBAC configuration
templates:
- clusterrole.yaml
- clusterrolebinding.yaml
- serviceaccount.yaml
release:
name: rbac
tests:
- it: should create default RBAC related objects
asserts:
- isKind:
of: ClusterRole
template: clusterrole.yaml
- equal:
path: metadata.name
value: rbac-external-dns
template: clusterrole.yaml
- isKind:
of: ClusterRoleBinding
template: clusterrolebinding.yaml
- equal:
path: metadata.name
value: rbac-external-dns-viewer
template: clusterrolebinding.yaml
- isKind:
of: ServiceAccount
template: serviceaccount.yaml
- equal:
path: metadata.name
value: rbac-external-dns
template: serviceaccount.yaml
- it: should create default RBAC rules
asserts:
- template: clusterrole.yaml
equal:
path: rules
value:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list", "watch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
- apiGroups: [""]
resources: ["services"]
verbs: ["get","watch","list"]
- apiGroups: ["discovery.k8s.io"]
resources: ["endpointslices"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- it: should create no RBAC rules when no sources are set
set:
sources: []
asserts:
- template: clusterrole.yaml
equal:
path: rules
value: null
- it: should create default RBAC rules for 'ingresses' with source 'ingress'
set:
sources:
- ingress
asserts:
- template: clusterrole.yaml
equal:
path: rules
value:
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- it: should create default RBAC rules for 'nodes', 'pods', 'services' and 'endpointslices' with source 'service'
set:
sources:
- service
asserts:
- template: clusterrole.yaml
equal:
path: rules
value:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list", "watch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
- apiGroups: [""]
resources: ["services"]
verbs: ["get","watch","list"]
- apiGroups: ["discovery.k8s.io"]
resources: ["endpointslices"]
verbs: ["get","watch","list"]
- it: should create default RBAC rules for 'ambassador-host'
set:
sources:
- ambassador-host
asserts:
- template: clusterrole.yaml
equal:
path: rules
value:
- apiGroups: ["getambassador.io"]
resources: ["hosts","ingresses"]
verbs: ["get","watch","list"]
- it: should create default RBAC rules for 'crd' and 'traefik-proxy'
set:
sources:
- crd
- traefik-proxy
asserts:
- template: clusterrole.yaml
equal:
path: rules
value:
- apiGroups: ["externaldns.k8s.io"]
resources: ["dnsendpoints"]
verbs: ["get","watch","list"]
- apiGroups: ["externaldns.k8s.io"]
resources: ["dnsendpoints/status"]
verbs: ["*"]
- apiGroups: ["traefik.containo.us", "traefik.io"]
resources: ["ingressroutes", "ingressroutetcps", "ingressrouteudps"]
verbs: ["get","watch","list"]
- it: should create default RBAC rules for 'f5' when 'f5-virtualserver' is set
set:
sources:
- f5-virtualserver
asserts:
- template: clusterrole.yaml
equal:
path: rules
value:
- apiGroups: ["cis.f5.com"]
resources: ["virtualservers", "transportservers"]
verbs: ["get","watch","list"]
- it: should create default RBAC rules for 'f5' when 'f5-transportserver' is set
set:
sources:
- f5-transportserver
asserts:
- template: clusterrole.yaml
equal:
path: rules
value:
- apiGroups: ["cis.f5.com"]
resources: ["virtualservers", "transportservers"]
verbs: ["get","watch","list"]
- it: should create default RBAC rules for 'gateway-api' with source 'gateway-httproute'
set:
sources:
- gateway-httproute
asserts:
- template: clusterrole.yaml
equal:
path: rules
value:
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get","watch","list"]
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["httproutes"]
verbs: ["get","watch","list"]
- it: should create default RBAC rules for 'gateway-api' with sources 'tlsroute,tcproute,udproute'
set:
sources:
- gateway-tlsroute
- gateway-tcproute
- gateway-udproute
asserts:
- template: clusterrole.yaml
equal:
path: rules
value:
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get","watch","list"]
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["tlsroutes"]
verbs: ["get","watch","list"]
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["tcproutes"]
verbs: ["get","watch","list"]
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["udproutes"]
verbs: ["get","watch","list"]
- it: should create Role instead of ClusterRole when namespaced is true
set:
namespaced: true
sources:
- service
asserts:
- isKind:
of: Role
template: clusterrole.yaml
- equal:
path: metadata.name
value: rbac-external-dns
template: clusterrole.yaml
- it: should create RoleBinding instead of ClusterRoleBinding when namespaced is true
set:
namespaced: true
sources:
- service
asserts:
- isKind:
of: RoleBinding
template: clusterrolebinding.yaml
- equal:
path: metadata.name
value: rbac-external-dns-viewer
template: clusterrolebinding.yaml
- it: should create all required resources when namespaced=true and gatewayNamespace is specified
set:
namespaced: true
gatewayNamespace: gateway-ns
sources:
- gateway-httproute
asserts:
# Should have: main Role + ClusterRole for namespaces + Gateway Role
- hasDocuments:
count: 3
template: clusterrole.yaml
- hasDocuments:
count: 3
template: clusterrolebinding.yaml
# Main role should exist and contain route permissions but NOT gateway permissions
- isKind:
of: Role
documentSelector:
path: metadata.name
value: rbac-external-dns
template: clusterrole.yaml
- contains:
path: rules
content:
apiGroups: ["gateway.networking.k8s.io"]
resources: ["httproutes"]
verbs: ["get","watch","list"]
documentSelector:
path: metadata.name
value: rbac-external-dns
template: clusterrole.yaml
- notContains:
path: rules
content:
apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways"]
verbs: ["get","watch","list"]
documentSelector:
path: metadata.name
value: rbac-external-dns
template: clusterrole.yaml
# ClusterRole for namespaces should exist
- isKind:
of: ClusterRole
documentSelector:
path: metadata.name
value: rbac-external-dns-namespaces
template: clusterrole.yaml
# Gateway role should exist and have gateway permissions only
- isKind:
of: Role
documentSelector:
path: metadata.name
value: rbac-external-dns-gateway
template: clusterrole.yaml
- equal:
path: metadata.namespace
value: gateway-ns
documentSelector:
path: metadata.name
value: rbac-external-dns-gateway
template: clusterrole.yaml
- contains:
path: rules
content:
apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways"]
verbs: ["get","watch","list"]
documentSelector:
path: metadata.name
value: rbac-external-dns-gateway
template: clusterrole.yaml
- it: should create main Role with gateway permissions and ClusterRole for namespaces when namespaced=true and gatewayNamespace is not set
set:
namespaced: true
sources:
- gateway-httproute
asserts:
# Should have: main Role + ClusterRole for namespaces
- hasDocuments:
count: 2
template: clusterrole.yaml
- hasDocuments:
count: 2
template: clusterrolebinding.yaml
# Main Role should exist and contain gateway permissions
- isKind:
of: Role
documentSelector:
path: metadata.name
value: rbac-external-dns
template: clusterrole.yaml
- contains:
path: rules
content:
apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways"]
verbs: ["get","watch","list"]
documentSelector:
path: metadata.name
value: rbac-external-dns
template: clusterrole.yaml
- contains:
path: rules
content:
apiGroups: ["gateway.networking.k8s.io"]
resources: ["httproutes"]
verbs: ["get","watch","list"]
documentSelector:
path: metadata.name
value: rbac-external-dns
template: clusterrole.yaml
# ClusterRole for namespaces should exist
- isKind:
of: ClusterRole
documentSelector:
path: metadata.name
value: rbac-external-dns-namespaces
template: clusterrole.yaml
- contains:
path: rules
content:
apiGroups: [""]
resources: ["namespaces"]
verbs: ["get","watch","list"]
documentSelector:
path: metadata.name
value: rbac-external-dns-namespaces
template: clusterrole.yaml
- it: should create ClusterRole with all permissions when namespaced=false and gateway sources exist
set:
namespaced: false
sources:
- gateway-httproute
asserts:
- hasDocuments:
count: 1
template: clusterrole.yaml
- hasDocuments:
count: 1
template: clusterrolebinding.yaml
- isKind:
of: ClusterRole
template: clusterrole.yaml
- contains:
path: rules
content:
apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways"]
verbs: ["get","watch","list"]
template: clusterrole.yaml
- contains:
path: rules
content:
apiGroups: [""]
resources: ["namespaces"]
verbs: ["get","watch","list"]
template: clusterrole.yaml
- it: should create only ClusterRole when namespaced=false and no gateway sources
set:
namespaced: false
sources:
- service
- ingress
asserts:
- hasDocuments:
count: 1
template: clusterrole.yaml
- hasDocuments:
count: 1
template: clusterrolebinding.yaml
- isKind:
of: ClusterRole
template: clusterrole.yaml
- notContains:
path: rules
content:
apiGroups: ["gateway.networking.k8s.io"]
template: clusterrole.yaml
- it: should create only Role when namespaced=true and no gateway sources
set:
namespaced: true
sources:
- service
- ingress
asserts:
- hasDocuments:
count: 1
template: clusterrole.yaml
- hasDocuments:
count: 1
template: clusterrolebinding.yaml
- isKind:
of: Role
template: clusterrole.yaml
- isKind:
of: RoleBinding
template: clusterrolebinding.yaml
- it: should create only Role when namespaced=true for istio sources
set:
namespaced: true
sources:
- istio-virtualservice
- istio-gateway
asserts:
- isKind:
of: Role
template: clusterrole.yaml
- isKind:
of: RoleBinding
template: clusterrolebinding.yaml
- equal:
path: rules
value:
- apiGroups: [""]
resources: ["services"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- apiGroups: ["networking.istio.io"]
resources: ["gateways"]
verbs: ["get","watch","list"]
- apiGroups: ["networking.istio.io"]
resources: ["virtualservices"]
verbs: ["get","watch","list"]
template: clusterrole.yaml
- it: should create only ClusterRole for istio-gateway with ingress permissions
set:
namespaced: false
sources:
- istio-gateway
asserts:
- isKind:
of: ClusterRole
template: clusterrole.yaml
- isKind:
of: ClusterRoleBinding
template: clusterrolebinding.yaml
- equal:
path: rules
value:
- apiGroups: [""]
resources: ["services"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- apiGroups: ["networking.istio.io"]
resources: ["gateways"]
verbs: ["get","watch","list"]
template: clusterrole.yaml
- it: should create only ClusterRole for istio-virtualservice with ingress permissions required
set:
namespaced: false
sources:
- istio-virtualservice
asserts:
- isKind:
of: ClusterRole
template: clusterrole.yaml
- isKind:
of: ClusterRoleBinding
template: clusterrolebinding.yaml
- equal:
path: rules
value:
- apiGroups: [""]
resources: ["services"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- apiGroups: ["networking.istio.io"]
resources: ["gateways"]
verbs: ["get","watch","list"]
- apiGroups: ["networking.istio.io"]
resources: ["virtualservices"]
verbs: ["get","watch","list"]
template: clusterrole.yaml

View File

@ -0,0 +1,42 @@
suite: Secret configuration
templates:
- "secret.yaml"
- "deployment.yaml"
tests:
- it: should not provide secret by default
template: "secret.yaml"
asserts:
- hasDocuments:
count: 0
- it: should configure Azure secret
release:
name: under-test
set:
secretConfiguration:
enabled: true
mountPath: "/etc/kubernetes/"
data:
azure.json: |
{
"tenantId": "00000000-0000-0000-0000-000000000000",
"subscriptionId": "00000000-0000-0000-0000-000000000000",
"resourceGroup": "networking",
"useWorkloadIdentityExtension": true
}
asserts:
- hasDocuments:
count: 1
- exists:
path: data["azure.json"]
template: "secret.yaml"
- exists:
path: spec.template.spec.volumes
template: "deployment.yaml"
- equal:
path: spec.template.spec.volumes
value:
- name: secrets
secret:
secretName: under-test-external-dns
template: "deployment.yaml"

View File

@ -0,0 +1,61 @@
suite: ServiceAccount configuration
templates:
- serviceaccount.yaml
release:
namespace: default
tests:
- it: should provide a single service account by default
asserts:
- isKind:
of: ServiceAccount
- hasDocuments:
count: 1
- equal:
path: automountServiceAccountToken
value: true
- it: should provide a way to disable service account
set:
serviceAccount:
create: false
asserts:
- hasDocuments:
count: 0
- it: should support annotations without template variables
set:
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789:role/external-dns-role
eks.amazonaws.com/sts-regional-endpoints: "true"
asserts:
- hasDocuments:
count: 1
- equal:
path: metadata.annotations
value:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789:role/external-dns-role
eks.amazonaws.com/sts-regional-endpoints: "true"
- it: should support annotations with template variables
release:
name: v1
set:
account: 9876-dynamic-account-id
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::{{ .Values.account }}:role/external-dns-role
eks.amazonaws.com/sts-regional-endpoints: "true"
tags.k8s.io/service: service-{{ include "external-dns.fullname" . }}
asserts:
- hasDocuments:
count: 1
- equal:
path: metadata.annotations
value:
eks.amazonaws.com/role-arn: arn:aws:iam::9876-dynamic-account-id:role/external-dns-role
eks.amazonaws.com/sts-regional-endpoints: "true"
tags.k8s.io/service: service-v1-external-dns
- isType:
path: metadata.annotations["eks.amazonaws.com/sts-regional-endpoints"]
type: string

View File

@ -0,0 +1,880 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"affinity": {
"description": "Affinity settings for `Pod` [scheduling](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/). If an explicit label selector is not provided for pod affinity or pod anti-affinity one will be created from the pod selector labels.",
"type": "object"
},
"annotationFilter": {
"description": "Filter resources queried for endpoints by annotation selector.",
"type": [
"string",
"null"
]
},
"annotationPrefix": {
"description": "Annotation prefix for external-dns annotations (useful for split horizon DNS with multiple instances).",
"type": [
"string",
"null"
]
},
"automountServiceAccountToken": {
"description": "Set this to `false` to [opt out of API credential automounting](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#opt-out-of-api-credential-automounting) for the `Pod`.",
"type": "boolean"
},
"commonLabels": {
"description": "Labels to add to all chart resources.",
"type": "object"
},
"deploymentAnnotations": {
"description": "Annotations to add to the `Deployment`.",
"type": "object"
},
"deploymentStrategy": {
"description": "[Deployment Strategy](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy).",
"type": "object",
"properties": {
"type": {
"default": "Recreate",
"type": "string",
"enum": [
"Recreate",
"RollingUpdate"
]
}
},
"additionalProperties": true
},
"dnsConfig": {
"description": "[DNS config](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-dns-config) for the pod, if not set the default will be used.",
"type": [
"object",
"null"
]
},
"dnsPolicy": {
"description": "[DNS policy](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy) for the pod, if not set the default will be used.",
"type": [
"string",
"null"
]
},
"domainFilters": {
"description": "Limit possible target zones by domain suffixes.",
"type": "array"
},
"enabled": {
"description": "No effect - reserved for use in sub-charting",
"type": [
"boolean",
"null"
]
},
"env": {
"description": "[Environment variables](https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) for the `external-dns` container.",
"type": "array"
},
"excludeDomains": {
"description": "Intentionally exclude domains from being managed.",
"type": "array"
},
"extraArgs": {
"description": "Extra arguments to provide to _ExternalDNS_. An array or map can be used, with maps allowing for value overrides; maps also support slice values to use the same arg multiple times.",
"type": [
"array",
"null",
"object"
],
"uniqueItems": true,
"items": {
"type": "string"
}
},
"extraContainers": {
"description": "Extra containers to add to the `Deployment`.",
"type": "array"
},
"extraVolumeMounts": {
"description": "Extra [volume mounts](https://kubernetes.io/docs/concepts/storage/volumes/) for the `external-dns` container.",
"type": "array"
},
"extraVolumes": {
"description": "Extra [volumes](https://kubernetes.io/docs/concepts/storage/volumes/) for the `Pod`.",
"type": "array"
},
"fullnameOverride": {
"description": "Override the full name of the chart.",
"type": [
"string",
"null"
]
},
"gatewayNamespace": {
"description": "_Gateway API_ gateway namespace to watch.",
"type": [
"string",
"null"
]
},
"global": {
"type": "object",
"properties": {
"imagePullSecrets": {
"description": "Global image pull secrets.",
"type": "array",
"items": {
"type": "object"
}
}
}
},
"image": {
"type": "object",
"properties": {
"pullPolicy": {
"description": "Image pull policy for the `external-dns` container.",
"type": "string",
"enum": [
"IfNotPresent",
"Always"
]
},
"repository": {
"description": "Image repository for the `external-dns` container.",
"type": "string"
},
"tag": {
"description": "Image tag for the `external-dns` container, this will default to `.Chart.AppVersion` if not set.",
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
},
"imagePullSecrets": {
"description": "Image pull secrets.",
"type": "array",
"items": {
"type": "object"
}
},
"initContainers": {
"description": "[Init containers](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) to add to the `Pod` definition.",
"type": "array"
},
"interval": {
"description": "Interval for DNS updates.",
"type": "string"
},
"labelFilter": {
"description": "Filter resources queried for endpoints by label selector.",
"type": [
"string",
"null"
]
},
"livenessProbe": {
"description": "[Liveness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) configuration for the `external-dns` container.",
"type": "object",
"properties": {
"failureThreshold": {
"type": "integer"
},
"httpGet": {
"type": "object",
"properties": {
"path": {
"type": "string"
},
"port": {
"type": "string"
}
}
},
"initialDelaySeconds": {
"type": "integer"
},
"periodSeconds": {
"type": "integer"
},
"successThreshold": {
"type": "integer"
},
"timeoutSeconds": {
"type": "integer"
}
}
},
"logFormat": {
"description": "Log format.",
"default": "text",
"type": "string",
"enum": [
"text",
"json"
]
},
"logLevel": {
"description": "Log level.",
"default": "info",
"type": "string",
"enum": [
"panic",
"debug",
"info",
"warning",
"error",
"fatal"
]
},
"managedRecordTypes": {
"description": "Record types to manage (default: A, AAAA, CNAME)",
"type": [
"array",
"null"
],
"uniqueItems": true,
"items": {
"type": "string"
}
},
"nameOverride": {
"description": "Override the name of the chart.",
"type": [
"string",
"null"
]
},
"namespaced": {
"description": "if `true`, _ExternalDNS_ will run in a namespaced scope (`Role`` and `Rolebinding`` will be namespaced too).",
"type": "boolean"
},
"nodeSelector": {
"description": "Node labels to match for `Pod` [scheduling](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/).",
"type": "object"
},
"podAnnotations": {
"description": "Annotations to add to the `Pod`.",
"type": "object"
},
"podLabels": {
"description": "Labels to add to the `Pod`.",
"type": "object"
},
"podSecurityContext": {
"description": "[Pod security context](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#podsecuritycontext-v1-core), this supports full customisation.",
"type": "object",
"properties": {
"fsGroup": {
"type": "integer"
},
"runAsNonRoot": {
"type": "boolean"
},
"seccompProfile": {
"type": "object",
"properties": {
"type": {
"type": "string"
}
}
}
}
},
"policy": {
"description": "How DNS records are synchronized between sources and providers; available values are `create-only`, `sync`, \u0026 `upsert-only`.",
"default": "upsert-only",
"type": "string",
"enum": [
"create-only",
"sync",
"upsert-only"
]
},
"priorityClassName": {
"description": "Priority class name for the `Pod`.",
"type": [
"string",
"null"
]
},
"provider": {
"type": [
"object",
"string"
],
"properties": {
"name": {
"description": "_ExternalDNS_ provider name; for the available providers and how to configure them see [README](https://github.com/kubernetes-sigs/external-dns/blob/master/charts/external-dns/README.md#providers).",
"type": "string"
},
"webhook": {
"type": "object",
"properties": {
"args": {
"description": "Extra arguments to provide for the `webhook` container.",
"type": "array"
},
"env": {
"description": "[Environment variables](https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) for the `webhook` container.",
"type": "array"
},
"extraVolumeMounts": {
"description": "Extra [volume mounts](https://kubernetes.io/docs/concepts/storage/volumes/) for the `webhook` container.",
"type": "array"
},
"image": {
"type": "object",
"properties": {
"pullPolicy": {
"description": "Image pull policy for the `webhook` container.",
"type": "string"
},
"repository": {
"description": "Image repository for the `webhook` container.",
"type": [
"string",
"null"
]
},
"tag": {
"description": "Image tag for the `webhook` container.",
"type": [
"string",
"null"
]
}
}
},
"limits": {
"type": "object",
"properties": {
"cpu": {
"type": "string"
},
"memory": {
"type": "string"
}
}
},
"livenessProbe": {
"description": "[Liveness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) configuration for the `external-dns` container.",
"type": "object",
"properties": {
"failureThreshold": {
"type": [
"integer",
"null"
]
},
"httpGet": {
"type": "object",
"properties": {
"path": {
"type": [
"string",
"null"
]
},
"port": {
"default": "string",
"type": [
"integer",
"string"
]
}
}
},
"initialDelaySeconds": {
"type": [
"integer",
"null"
]
},
"periodSeconds": {
"type": [
"integer",
"null"
]
},
"successThreshold": {
"type": [
"integer",
"null"
]
},
"timeoutSeconds": {
"type": [
"integer",
"null"
]
}
}
},
"readinessProbe": {
"description": "[Readiness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) configuration for the `webhook` container.",
"type": "object",
"properties": {
"failureThreshold": {
"type": [
"integer",
"null"
]
},
"httpGet": {
"type": "object",
"properties": {
"path": {
"type": [
"string",
"null"
]
},
"port": {
"default": "string",
"type": [
"integer",
"string"
]
}
}
},
"initialDelaySeconds": {
"type": [
"integer",
"null"
]
},
"periodSeconds": {
"type": [
"integer",
"null"
]
},
"successThreshold": {
"type": [
"integer",
"null"
]
},
"timeoutSeconds": {
"type": [
"integer",
"null"
]
}
}
},
"requests": {
"type": "object",
"properties": {
"cpu": {
"type": "string"
},
"memory": {
"type": "string"
}
}
},
"resources": {
"description": "[Resources](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) for the `webhook` container.",
"type": "object"
},
"securityContext": {
"description": "[Pod security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container) for the `webhook` container.",
"type": "object"
},
"service": {
"type": "object",
"properties": {
"port": {
"description": "Webhook exposed HTTP port for the service.",
"type": "integer"
}
}
},
"serviceMonitor": {
"description": "Optional [Service Monitor](https://prometheus-operator.dev/docs/operator/design/#servicemonitor) configuration for the `webhook` container.",
"type": "object",
"properties": {
"bearerTokenFile": {
"type": "null"
},
"interval": {
"type": "null"
},
"metricRelabelings": {
"type": "array"
},
"relabelings": {
"type": "array"
},
"scheme": {
"type": "null"
},
"scrapeTimeout": {
"type": "null"
},
"tlsConfig": {
"type": "object"
}
}
}
}
}
}
},
"rbac": {
"type": "object",
"properties": {
"additionalPermissions": {
"description": "Additional rules to add to the `ClusterRole`.",
"type": "array"
},
"create": {
"description": "If `true`, create a `ClusterRole` \u0026 `ClusterRoleBinding` with access to the Kubernetes API.",
"type": "boolean"
}
},
"additionalProperties": true
},
"readinessProbe": {
"description": "[Readiness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) configuration for the `external-dns` container.",
"type": "object",
"properties": {
"failureThreshold": {
"type": "integer"
},
"httpGet": {
"type": "object",
"properties": {
"path": {
"type": "string"
},
"port": {
"type": "string"
}
}
},
"initialDelaySeconds": {
"type": "integer"
},
"periodSeconds": {
"type": "integer"
},
"successThreshold": {
"type": "integer"
},
"timeoutSeconds": {
"type": "integer"
}
}
},
"registry": {
"description": "Specify the registry for storing ownership and labels. Valid values are `txt`, `aws-sd`, `dynamodb` \u0026 `noop`.",
"default": "txt",
"type": "string",
"enum": [
"txt",
"aws-sd",
"dynamodb",
"noop"
]
},
"resources": {
"description": "[Resources](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) for the `external-dns` container.",
"type": "object",
"properties": {
"limits": {
"type": "object",
"properties": {
"cpu": {
"type": "string"
},
"memory": {
"type": "string"
}
}
},
"requests": {
"type": "object",
"properties": {
"cpu": {
"type": "string"
},
"memory": {
"type": "string"
}
}
}
}
},
"revisionHistoryLimit": {
"description": "Specify the number of old `ReplicaSets` to retain to allow rollback of the `Deployment``.",
"type": [
"integer",
"null"
],
"minimum": 0
},
"secretConfiguration": {
"type": "object",
"properties": {
"data": {
"description": "`Secret` data.",
"type": "object"
},
"enabled": {
"description": "If `true`, create a `Secret` to store sensitive provider configuration (**DEPRECATED**).",
"type": "boolean"
},
"mountPath": {
"description": "Mount path for the `Secret`, this can be templated.",
"type": [
"string",
"null"
]
},
"subPath": {
"description": "Sub-path for mounting the `Secret`, this can be templated.",
"type": [
"string",
"null"
]
}
}
},
"securityContext": {
"description": "[Security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container) for the `external-dns` container.",
"type": "object",
"properties": {
"allowPrivilegeEscalation": {
"type": "boolean"
},
"capabilities": {
"type": "object",
"properties": {
"drop": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"privileged": {
"type": "boolean"
},
"readOnlyRootFilesystem": {
"type": "boolean"
},
"runAsGroup": {
"type": "integer"
},
"runAsNonRoot": {
"type": "boolean"
},
"runAsUser": {
"type": "integer"
}
}
},
"service": {
"type": "object",
"properties": {
"annotations": {
"description": "Service annotations.",
"type": "object"
},
"ipFamilies": {
"description": "Service IP families (e.g. IPv4 and/or IPv6).",
"type": [
"array",
"null"
],
"maxItems": 2,
"minItems": 0,
"uniqueItems": true,
"items": {
"type": "string",
"enum": [
"IPv4",
"IPv6"
]
}
},
"ipFamilyPolicy": {
"description": "Service IP family policy.",
"type": [
"string",
"null"
],
"enum": [
"SingleStack",
"PreferDualStack",
"RequireDualStack",
null
]
},
"port": {
"description": "Service HTTP port.",
"default": 7979,
"type": "integer",
"minimum": 0
}
}
},
"serviceAccount": {
"type": "object",
"properties": {
"annotations": {
"description": "Annotations to add to the service account. Templates are allowed in both the key and the value. Example: `example.com/annotation/{{ .Values.nameOverride }}: {{ .Values.nameOverride }}`",
"type": "object"
},
"automountServiceAccountToken": {
"description": "Set this to `false` to [opt out of API credential automounting](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#opt-out-of-api-credential-automounting) for the `ServiceAccount`.",
"type": "boolean"
},
"create": {
"description": "If `true`, create a new `ServiceAccount`.",
"type": "boolean"
},
"labels": {
"description": "Labels to add to the service account.",
"type": "object"
},
"name": {
"description": "If this is set and `serviceAccount.create` is `true` this will be used for the created `ServiceAccount` name, if set and `serviceAccount.create` is `false` then this will define an existing `ServiceAccount` to use.",
"type": [
"string",
"null"
]
}
}
},
"serviceMonitor": {
"type": "object",
"properties": {
"additionalLabels": {
"description": "Additional labels for the `ServiceMonitor`.",
"type": "object"
},
"annotations": {
"description": "Annotations to add to the `ServiceMonitor`.",
"type": "object"
},
"bearerTokenFile": {
"description": "Provide a bearer token file for the `ServiceMonitor`.",
"type": [
"string",
"null"
]
},
"enabled": {
"description": "If `true`, create a `ServiceMonitor` resource to support the _Prometheus Operator_.",
"type": "boolean"
},
"interval": {
"description": "If set override the _Prometheus_ default interval.",
"type": [
"string",
"null"
]
},
"metricRelabelings": {
"description": "[Metric relabel configs](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs) to apply to samples before ingestion.",
"type": "array"
},
"namespace": {
"description": "If set create the `ServiceMonitor` in an alternate namespace.",
"type": [
"string",
"null"
]
},
"relabelings": {
"description": "[Relabel configs](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config) to apply to samples before ingestion.",
"type": "array"
},
"scheme": {
"description": "If set overrides the _Prometheus_ default scheme.",
"type": [
"string",
"null"
]
},
"scrapeTimeout": {
"description": "If set override the _Prometheus_ default scrape timeout.",
"type": [
"string",
"null"
]
},
"targetLabels": {
"description": "Provide target labels for the `ServiceMonitor`.",
"type": "array"
},
"tlsConfig": {
"description": "Configure the `ServiceMonitor` [TLS config](https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#tlsconfig).",
"type": "object"
}
}
},
"shareProcessNamespace": {
"description": "If `true`, the `Pod` will have [process namespace sharing](https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/) enabled.",
"type": "boolean"
},
"sources": {
"description": "_Kubernetes_ resources to monitor for DNS entries.",
"type": "array",
"items": {
"type": "string"
}
},
"terminationGracePeriodSeconds": {
"description": "Termination grace period for the `Pod` in seconds.",
"type": [
"integer",
"null"
]
},
"tolerations": {
"description": "Node taints which will be tolerated for `Pod` [scheduling](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/).",
"type": "array"
},
"topologySpreadConstraints": {
"description": "Topology spread constraints for `Pod` [scheduling](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/). If an explicit label selector is not provided one will be created from the pod selector labels.",
"type": "array"
},
"triggerLoopOnEvent": {
"description": "If `true`, triggers run loop on create/update/delete events in addition of regular interval.",
"type": "boolean"
},
"txtOwnerId": {
"description": "Specify an identifier for this instance of _ExternalDNS_ when using a registry other than `noop`.",
"type": [
"string",
"null"
]
},
"txtPrefix": {
"description": "Specify a prefix for the domain names of TXT records created for the `txt` registry. Mutually exclusive with `txtSuffix`.",
"type": [
"string",
"null"
]
},
"txtSuffix": {
"description": "Specify a suffix for the domain names of TXT records created for the `txt` registry. Mutually exclusive with `txtPrefix`.",
"type": [
"string",
"null"
]
}
},
"additionalProperties": true
}

View File

@ -0,0 +1,322 @@
# Default values for external-dns.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
global:
# -- Global image pull secrets.
imagePullSecrets: [] # @schema item: object
image: # @schema additionalProperties: false
# -- Image repository for the `external-dns` container.
repository: registry.k8s.io/external-dns/external-dns
# -- Image tag for the `external-dns` container, this will default to `.Chart.AppVersion` if not set.
tag: # @schema type:[string, null]
# -- Image pull policy for the `external-dns` container.
pullPolicy: IfNotPresent # @schema enum:[IfNotPresent, Always]
# -- Image pull secrets.
imagePullSecrets: [] # @schema item: object
# -- (string) Override the name of the chart.
nameOverride: # @schema type:[string, null]; default: null
# -- (string) Override the full name of the chart.
fullnameOverride: # @schema type:[string, null]; default: null
# -- Labels to add to all chart resources.
commonLabels: {}
serviceAccount:
# -- If `true`, create a new `ServiceAccount`.
create: true
# -- Labels to add to the service account.
labels: {}
# -- Annotations to add to the service account. Templates are allowed in both the key and the value. Example: `example.com/annotation/{{ .Values.nameOverride }}: {{ .Values.nameOverride }}`
annotations: {}
# -- (string) If this is set and `serviceAccount.create` is `true` this will be used for the created `ServiceAccount` name, if set and `serviceAccount.create` is `false` then this will define an existing `ServiceAccount` to use.
name: # @schema type:[string, null]; default: null
# -- Set this to `false` to [opt out of API credential automounting](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#opt-out-of-api-credential-automounting) for the `ServiceAccount`.
automountServiceAccountToken: true
service:
# -- Service annotations.
annotations: {}
# -- Service HTTP port.
port: 7979 # @schema minimum:0; default:7979
# -- Service IP families (e.g. IPv4 and/or IPv6).
ipFamilies: [] # @schema type: [array, null]; item: string; itemEnum: ["IPv4", "IPv6"]; minItems:0; maxItems:2; uniqueItems: true
# - IPv4
# - IPv6
# -- Service IP family policy.
ipFamilyPolicy: # @schema type: [string, null]; enum:[SingleStack, PreferDualStack, RequireDualStack, null]
rbac: # @schema additionalProperties: true
# -- If `true`, create a `ClusterRole` & `ClusterRoleBinding` with access to the Kubernetes API.
create: true
# -- Additional rules to add to the `ClusterRole`.
additionalPermissions: []
# -- Annotations to add to the `Deployment`.
deploymentAnnotations: {}
# -- Extra containers to add to the `Deployment`.
extraContainers: []
# -- [Deployment Strategy](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy).
deploymentStrategy: # @schema additionalProperties: true
type: Recreate # @schema enum:[Recreate, RollingUpdate]; type:string; default: Recreate
# -- (int) Specify the number of old `ReplicaSets` to retain to allow rollback of the `Deployment``.
revisionHistoryLimit: # @schema type:[integer, null];minimum:0
# -- Labels to add to the `Pod`.
podLabels: {}
# -- Annotations to add to the `Pod`.
podAnnotations: {}
# -- (bool) Set this to `false` to [opt out of API credential automounting](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#opt-out-of-api-credential-automounting) for the `Pod`.
automountServiceAccountToken: true
# -- If `true`, the `Pod` will have [process namespace sharing](https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/) enabled.
shareProcessNamespace: false
# -- [Pod security context](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#podsecuritycontext-v1-core), this supports full customisation.
# @default -- See _values.yaml_
podSecurityContext:
runAsNonRoot: true
fsGroup: 65534
seccompProfile:
type: RuntimeDefault
# -- (string) Priority class name for the `Pod`.
priorityClassName: # @schema type:[string, null]; default: null
# -- (int) Termination grace period for the `Pod` in seconds.
terminationGracePeriodSeconds: # @schema type:[integer, null]
# -- (string) [DNS policy](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy) for the pod, if not set the default will be used.
dnsPolicy: # @schema type:[string, null]; default: null
# -- (object) [DNS config](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-dns-config) for the pod, if not set the default will be used.
dnsConfig: # @schema type:[object, null]; default: null
# -- [Init containers](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) to add to the `Pod` definition.
initContainers: []
# -- [Security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container) for the `external-dns` container.
# @default -- See _values.yaml_
securityContext:
privileged: false
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
capabilities:
drop: ["ALL"]
# -- [Environment variables](https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) for the `external-dns` container.
env: []
# -- [Liveness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) configuration for the `external-dns` container.
# @default -- See _values.yaml_
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 2
successThreshold: 1
# -- [Readiness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) configuration for the `external-dns` container.
# @default -- See _values.yaml_
readinessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
successThreshold: 1
# -- Extra [volumes](https://kubernetes.io/docs/concepts/storage/volumes/) for the `Pod`.
extraVolumes: []
# -- Extra [volume mounts](https://kubernetes.io/docs/concepts/storage/volumes/) for the `external-dns` container.
extraVolumeMounts: []
# -- [Resources](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) for the `external-dns` container.
resources: {}
# -- Node labels to match for `Pod` [scheduling](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/).
nodeSelector: {}
# -- Affinity settings for `Pod` [scheduling](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/). If an explicit label selector is not provided for pod affinity or pod anti-affinity one will be created from the pod selector labels.
affinity: {}
# -- Topology spread constraints for `Pod` [scheduling](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/). If an explicit label selector is not provided one will be created from the pod selector labels.
topologySpreadConstraints: []
# -- Node taints which will be tolerated for `Pod` [scheduling](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/).
tolerations: []
serviceMonitor:
# -- If `true`, create a `ServiceMonitor` resource to support the _Prometheus Operator_.
enabled: false
# -- Additional labels for the `ServiceMonitor`.
additionalLabels: {}
# -- Annotations to add to the `ServiceMonitor`.
annotations: {}
# -- (string) If set create the `ServiceMonitor` in an alternate namespace.
namespace: # @schema type:[string, null]; default: null
# -- (string) If set override the _Prometheus_ default interval.
interval: # @schema type:[string, null]; default: null
# -- (string) If set override the _Prometheus_ default scrape timeout.
scrapeTimeout: # @schema type:[string, null]; default: null
# -- (string) If set overrides the _Prometheus_ default scheme.
scheme: # @schema type:[string, null]; default: null
# -- Configure the `ServiceMonitor` [TLS config](https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#tlsconfig).
tlsConfig: {}
# -- (string) Provide a bearer token file for the `ServiceMonitor`.
bearerTokenFile: # @schema type:[string, null]; default: null
# -- [Relabel configs](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config) to apply to samples before ingestion.
relabelings: []
# -- [Metric relabel configs](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs) to apply to samples before ingestion.
metricRelabelings: []
# -- Provide target labels for the `ServiceMonitor`.
targetLabels: []
# -- Log level.
logLevel: info # @schema enum:[panic, debug, info, warning, error, fatal]; type:string; default: "info"
# -- Log format.
logFormat: text # @schema enum:["text", "json"]; type:string; default: "text"
# -- Interval for DNS updates.
interval: 1m
# -- If `true`, triggers run loop on create/update/delete events in addition of regular interval.
triggerLoopOnEvent: false
# -- if `true`, _ExternalDNS_ will run in a namespaced scope (`Role`` and `Rolebinding`` will be namespaced too).
namespaced: false
# -- _Gateway API_ gateway namespace to watch.
gatewayNamespace: # @schema type:[string, null]; default: null
# -- _Kubernetes_ resources to monitor for DNS entries.
sources:
- service
- ingress
# -- How DNS records are synchronized between sources and providers; available values are `create-only`, `sync`, & `upsert-only`.
policy: upsert-only # @schema enum:[create-only, sync, upsert-only]; type:string; default: "upsert-only"
# -- Specify the registry for storing ownership and labels.
# Valid values are `txt`, `aws-sd`, `dynamodb` & `noop`.
registry: txt # @schema enum:[txt, aws-sd, dynamodb, noop]; default: "txt"
# -- (string) Specify an identifier for this instance of _ExternalDNS_ when using a registry other than `noop`.
txtOwnerId: # @schema type:[string, null]; default: null
# -- (string) Specify a prefix for the domain names of TXT records created for the `txt` registry.
# Mutually exclusive with `txtSuffix`.
txtPrefix: # @schema type:[string, null]; default: null
# -- (string) Specify a suffix for the domain names of TXT records created for the `txt` registry.
# Mutually exclusive with `txtPrefix`.
txtSuffix: # @schema type:[string, null]; default: null
# -- Limit possible target zones by domain suffixes.
domainFilters: []
# -- Intentionally exclude domains from being managed.
excludeDomains: []
# -- Filter resources queried for endpoints by label selector.
labelFilter: # @schema type: [string,null]; default: null
# -- Filter resources queried for endpoints by annotation selector.
annotationFilter: # @schema type: [string,null]; default: null
# -- Annotation prefix for external-dns annotations (useful for split horizon DNS with multiple instances).
annotationPrefix: # @schema type: [string,null]; default: null
# -- Record types to manage (default: A, AAAA, CNAME)
managedRecordTypes: [] # @schema type: [array, null]; item: string; uniqueItems: true
provider: # @schema type: [object, string]
# -- _ExternalDNS_ provider name; for the available providers and how to configure them see [README](https://github.com/kubernetes-sigs/external-dns/blob/master/charts/external-dns/README.md#providers).
name: aws
webhook:
image:
# -- (string) Image repository for the `webhook` container.
repository: # @schema type:[string, null]; default: null
# -- (string) Image tag for the `webhook` container.
tag: # @schema type:[string, null]; default: null
# -- Image pull policy for the `webhook` container.
pullPolicy: IfNotPresent
# -- [Environment variables](https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) for the `webhook` container.
env: []
# -- Extra arguments to provide for the `webhook` container.
args: []
# -- Extra [volume mounts](https://kubernetes.io/docs/concepts/storage/volumes/) for the `webhook` container.
extraVolumeMounts: []
# -- [Resources](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) for the `webhook` container.
resources: {}
# -- [Pod security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container) for the `webhook` container.
# @default -- See _values.yaml_
securityContext: {}
# -- [Liveness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) configuration for the `external-dns` container.
# @default -- See _values.yaml_
livenessProbe:
httpGet:
path: /healthz # @schema type:[string, null]; default: null
port: http-webhook # @schema type:[integer,string]; default: string
initialDelaySeconds: 10 # @schema type:[integer, null]; default: null
periodSeconds: 10 # @schema type:[integer, null]; default: null
timeoutSeconds: 5 # @schema type:[integer, null]; default: null
failureThreshold: 2 # @schema type:[integer, null]; default: null
successThreshold: 1 # @schema type:[integer, null]; default: null
# -- [Readiness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) configuration for the `webhook` container.
# @default -- See _values.yaml_
readinessProbe:
httpGet:
path: /healthz # @schema type:[string, null]; default: null
port: http-webhook # @schema type:[integer,string]; default: string
initialDelaySeconds: 5 # @schema type:[integer, null]; default: null
periodSeconds: 10 # @schema type:[integer, null]; default: null
timeoutSeconds: 5 # @schema type:[integer, null]; default: null
failureThreshold: 6 # @schema type:[integer, null]; default: null
successThreshold: 1 # @schema type:[integer, null]; default: null
service:
# -- Webhook exposed HTTP port for the service.
port: 8080
# -- Optional [Service Monitor](https://prometheus-operator.dev/docs/operator/design/#servicemonitor) configuration for the `webhook` container.
# @default -- See _values.yaml_
serviceMonitor:
interval:
scheme:
tlsConfig: {}
bearerTokenFile:
scrapeTimeout:
metricRelabelings: []
relabelings: []
# -- Extra arguments to provide to _ExternalDNS_.
# An array or map can be used, with maps allowing for value overrides; maps also support slice values to use the same arg multiple times.
extraArgs: {} # @schema type: [array, null, object]; item: string; uniqueItems: true
secretConfiguration:
# -- If `true`, create a `Secret` to store sensitive provider configuration (**DEPRECATED**).
enabled: false
# -- Mount path for the `Secret`, this can be templated.
mountPath: # @schema type:[string, null]; default: null
# -- Sub-path for mounting the `Secret`, this can be templated.
subPath: # @schema type:[string, null]; default: null
# -- `Secret` data.
data: {}
# -- (bool) No effect - reserved for use in sub-charting.
enabled: # @schema type: [boolean, null]; description: No effect - reserved for use in sub-charting

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,97 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
api-approved.kubernetes.io: https://github.com/kubernetes-sigs/external-dns/pull/2007
controller-gen.kubebuilder.io/version: v0.17.2
name: dnsendpoints.externaldns.k8s.io
spec:
group: externaldns.k8s.io
names:
kind: DNSEndpoint
listKind: DNSEndpointList
plural: dnsendpoints
singular: dnsendpoint
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: |-
DNSEndpoint is a contract that a user-specified CRD must implement to be used as a source for external-dns.
The user-specified CRD should also have the status sub-resource.
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: DNSEndpointSpec defines the desired state of DNSEndpoint
properties:
endpoints:
items:
description: Endpoint is a high-level way of a connection between a service and an IP
properties:
dnsName:
description: The hostname of the DNS record
type: string
labels:
additionalProperties:
type: string
description: Labels stores labels defined for the Endpoint
type: object
providerSpecific:
description: ProviderSpecific stores provider specific config
items:
description: ProviderSpecificProperty holds the name and value of a configuration which is specific to individual DNS providers
properties:
name:
type: string
value:
type: string
type: object
type: array
recordTTL:
description: TTL for the record
format: int64
type: integer
recordType:
description: RecordType type of record, e.g. CNAME, A, AAAA, SRV, TXT etc
type: string
setIdentifier:
description: Identifier to distinguish multiple records with the same name and type (e.g. Route53 records with routing policies other than 'simple')
type: string
targets:
description: The targets the DNS record points to
items:
type: string
type: array
type: object
type: array
type: object
status:
description: DNSEndpointStatus defines the observed state of DNSEndpoint
properties:
observedGeneration:
description: The generation observed by the external-dns controller.
format: int64
type: integer
type: object
type: object
served: true
storage: true
subresources:
status: {}

View File

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- controller

View File

@ -0,0 +1,373 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"context"
"errors"
"fmt"
"sync"
"time"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/pkg/events"
"sigs.k8s.io/external-dns/pkg/metrics"
"sigs.k8s.io/external-dns/plan"
"sigs.k8s.io/external-dns/provider"
"sigs.k8s.io/external-dns/registry"
"sigs.k8s.io/external-dns/source"
)
var (
registryErrorsTotal = metrics.NewCounterWithOpts(
prometheus.CounterOpts{
Subsystem: "registry",
Name: "errors_total",
Help: "Number of Registry errors.",
},
)
sourceErrorsTotal = metrics.NewCounterWithOpts(
prometheus.CounterOpts{
Subsystem: "source",
Name: "errors_total",
Help: "Number of Source errors.",
},
)
sourceEndpointsTotal = metrics.NewGaugeWithOpts(
prometheus.GaugeOpts{
Subsystem: "source",
Name: "endpoints_total",
Help: "Number of Endpoints in all sources",
},
)
registryEndpointsTotal = metrics.NewGaugeWithOpts(
prometheus.GaugeOpts{
Subsystem: "registry",
Name: "endpoints_total",
Help: "Number of Endpoints in the registry",
},
)
lastSyncTimestamp = metrics.NewGaugeWithOpts(
prometheus.GaugeOpts{
Subsystem: "controller",
Name: "last_sync_timestamp_seconds",
Help: "Timestamp of last successful sync with the DNS provider",
},
)
lastReconcileTimestamp = metrics.NewGaugeWithOpts(
prometheus.GaugeOpts{
Subsystem: "controller",
Name: "last_reconcile_timestamp_seconds",
Help: "Timestamp of last attempted sync with the DNS provider",
},
)
controllerNoChangesTotal = metrics.NewCounterWithOpts(
prometheus.CounterOpts{
Subsystem: "controller",
Name: "no_op_runs_total",
Help: "Number of reconcile loops ending up with no changes on the DNS provider side.",
},
)
deprecatedRegistryErrors = metrics.NewCounterWithOpts(
prometheus.CounterOpts{
Subsystem: "registry",
Name: "errors_total",
Help: "Number of Registry errors.",
},
)
deprecatedSourceErrors = metrics.NewCounterWithOpts(
prometheus.CounterOpts{
Subsystem: "source",
Name: "errors_total",
Help: "Number of Source errors.",
},
)
registryRecords = metrics.NewGaugedVectorOpts(
prometheus.GaugeOpts{
Subsystem: "registry",
Name: "records",
Help: "Number of registry records partitioned by label name (vector).",
},
[]string{"record_type"},
)
sourceRecords = metrics.NewGaugedVectorOpts(
prometheus.GaugeOpts{
Subsystem: "source",
Name: "records",
Help: "Number of source records partitioned by label name (vector).",
},
[]string{"record_type"},
)
verifiedRecords = metrics.NewGaugedVectorOpts(
prometheus.GaugeOpts{
Subsystem: "controller",
Name: "verified_records",
Help: "Number of DNS records that exists both in source and registry (vector).",
},
[]string{"record_type"},
)
consecutiveSoftErrors = metrics.NewGaugeWithOpts(
prometheus.GaugeOpts{
Subsystem: "controller",
Name: "consecutive_soft_errors",
Help: "Number of consecutive soft errors in reconciliation loop.",
},
)
)
func init() {
metrics.RegisterMetric.MustRegister(registryErrorsTotal)
metrics.RegisterMetric.MustRegister(sourceErrorsTotal)
metrics.RegisterMetric.MustRegister(sourceEndpointsTotal)
metrics.RegisterMetric.MustRegister(registryEndpointsTotal)
metrics.RegisterMetric.MustRegister(lastSyncTimestamp)
metrics.RegisterMetric.MustRegister(lastReconcileTimestamp)
metrics.RegisterMetric.MustRegister(deprecatedRegistryErrors)
metrics.RegisterMetric.MustRegister(deprecatedSourceErrors)
metrics.RegisterMetric.MustRegister(controllerNoChangesTotal)
metrics.RegisterMetric.MustRegister(registryRecords)
metrics.RegisterMetric.MustRegister(sourceRecords)
metrics.RegisterMetric.MustRegister(verifiedRecords)
metrics.RegisterMetric.MustRegister(consecutiveSoftErrors)
}
// Controller is responsible for orchestrating the different components.
// It works in the following way:
// * Ask the DNS provider for the current list of endpoints.
// * Ask the Source for the desired list of endpoints.
// * Take both lists and calculate a Plan to move current towards the desired state.
// * Tell the DNS provider to apply the changes calculated by the Plan.
type Controller struct {
Source source.Source
Registry registry.Registry
// The policy that defines which change to DNS records is allowed
Policy plan.Policy
// The interval between individual synchronizations
Interval time.Duration
// The DomainFilter defines which DNS records to keep or exclude
DomainFilter endpoint.DomainFilterInterface
// The nextRunAt used for throttling and batching reconciliation
nextRunAt time.Time
// The runAtMutex is for atomic updating of nextRunAt and lastRunAt
runAtMutex sync.Mutex
// The lastRunAt used for throttling and batching reconciliation
lastRunAt time.Time
EventEmitter events.EventEmitter
// MangedRecordTypes are DNS record types that will be considered for management.
ManagedRecordTypes []string
// ExcludeRecordTypes are DNS record types that will be excluded from management.
ExcludeRecordTypes []string
// MinEventSyncInterval is used as a window for batching events
MinEventSyncInterval time.Duration
// Old txt-owner value we need to migrate from
TXTOwnerOld string
}
// RunOnce runs a single iteration of a reconciliation loop.
func (c *Controller) RunOnce(ctx context.Context) error {
lastReconcileTimestamp.Gauge.SetToCurrentTime()
c.runAtMutex.Lock()
c.lastRunAt = time.Now()
c.runAtMutex.Unlock()
regMetrics := newMetricsRecorder()
regRecords, err := c.Registry.Records(ctx)
if err != nil {
registryErrorsTotal.Counter.Inc()
deprecatedRegistryErrors.Counter.Inc()
return err
}
registryEndpointsTotal.Gauge.Set(float64(len(regRecords)))
countAddressRecords(regMetrics, regRecords, registryRecords)
ctx = context.WithValue(ctx, provider.RecordsContextKey, regRecords)
sourceEndpoints, err := c.Source.Endpoints(ctx)
if err != nil {
sourceErrorsTotal.Counter.Inc()
deprecatedSourceErrors.Counter.Inc()
return err
}
sourceEndpointsTotal.Gauge.Set(float64(len(sourceEndpoints)))
sourceMetrics := newMetricsRecorder()
countAddressRecords(sourceMetrics, sourceEndpoints, sourceRecords)
vaMetrics := newMetricsRecorder()
countMatchingAddressRecords(vaMetrics, sourceEndpoints, regRecords, verifiedRecords)
endpoints, err := c.Registry.AdjustEndpoints(sourceEndpoints)
if err != nil {
return fmt.Errorf("adjusting endpoints: %w", err)
}
registryFilter := c.Registry.GetDomainFilter()
plan := &plan.Plan{
Policies: []plan.Policy{c.Policy},
Current: regRecords,
Desired: endpoints,
DomainFilter: endpoint.MatchAllDomainFilters{c.DomainFilter, registryFilter},
ManagedRecords: c.ManagedRecordTypes,
ExcludeRecords: c.ExcludeRecordTypes,
OwnerID: c.Registry.OwnerID(),
OldOwnerId: c.TXTOwnerOld,
}
plan = plan.Calculate()
if plan.Changes.HasChanges() {
err = c.Registry.ApplyChanges(ctx, plan.Changes)
if err != nil {
registryErrorsTotal.Counter.Inc()
deprecatedRegistryErrors.Counter.Inc()
return err
} else {
emitChangeEvent(c.EventEmitter, *plan.Changes, events.RecordReady)
}
} else {
controllerNoChangesTotal.Counter.Inc()
log.Info("All records are already up to date")
}
lastSyncTimestamp.Gauge.SetToCurrentTime()
return nil
}
func earliest(r time.Time, times ...time.Time) time.Time {
for _, t := range times {
if t.Before(r) {
r = t
}
}
return r
}
func latest(r time.Time, times ...time.Time) time.Time {
for _, t := range times {
if t.After(r) {
r = t
}
}
return r
}
// Counts the intersections of records in endpoint and registry.
func countMatchingAddressRecords(rec *metricsRecorder, endpoints []*endpoint.Endpoint, registryRecords []*endpoint.Endpoint, metric metrics.GaugeVecMetric) {
recordsMap := make(map[string]map[string]struct{})
for _, regRecord := range registryRecords {
if _, found := recordsMap[regRecord.DNSName]; !found {
recordsMap[regRecord.DNSName] = make(map[string]struct{})
}
recordsMap[regRecord.DNSName][regRecord.RecordType] = struct{}{}
}
for _, sourceRecord := range endpoints {
if _, found := recordsMap[sourceRecord.DNSName]; found {
if _, ok := recordsMap[sourceRecord.DNSName][sourceRecord.RecordType]; ok {
rec.recordEndpointType(sourceRecord.RecordType)
}
}
}
for _, rt := range endpoint.KnownRecordTypes {
metric.SetWithLabels(rec.loadFloat64(rt), rt)
}
}
// countAddressRecords updates the metricsRecorder with the count of each record type
// found in the provided endpoints slice, and sets the corresponding metrics for each
// known DNS record type using the sourceRecords metric.
func countAddressRecords(rec *metricsRecorder, endpoints []*endpoint.Endpoint, metric metrics.GaugeVecMetric) {
// compute the number of records per type
for _, endPoint := range endpoints {
rec.recordEndpointType(endPoint.RecordType)
}
// set metrics for each record type
for _, rt := range endpoint.KnownRecordTypes {
metric.SetWithLabels(rec.loadFloat64(rt), rt)
}
}
// ScheduleRunOnce makes sure execution happens at most once per interval.
func (c *Controller) ScheduleRunOnce(now time.Time) {
c.runAtMutex.Lock()
defer c.runAtMutex.Unlock()
c.nextRunAt = latest(
c.lastRunAt.Add(c.MinEventSyncInterval),
earliest(
now.Add(5*time.Second),
c.nextRunAt,
),
)
}
func (c *Controller) ShouldRunOnce(now time.Time) bool {
c.runAtMutex.Lock()
defer c.runAtMutex.Unlock()
if now.Before(c.nextRunAt) {
return false
}
c.nextRunAt = now.Add(c.Interval)
return true
}
// Run runs RunOnce in a loop with a delay until context is canceled
func (c *Controller) Run(ctx context.Context) {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
var softErrorCount int
for {
if c.ShouldRunOnce(time.Now()) {
if err := c.RunOnce(ctx); err != nil {
if errors.Is(err, provider.SoftError) {
softErrorCount++
consecutiveSoftErrors.Gauge.Set(float64(softErrorCount))
log.Errorf("Failed to do run once: %v (consecutive soft errors: %d)", err, softErrorCount)
} else {
log.Fatalf("Failed to do run once: %v", err) // nolint: gocritic // exitAfterDefer
}
} else {
if softErrorCount > 0 {
log.Infof("Reconciliation succeeded after %d consecutive soft errors", softErrorCount)
}
softErrorCount = 0
consecutiveSoftErrors.Gauge.Set(0)
}
}
select {
case <-ticker.C:
case <-ctx.Done():
log.Info("Terminating main controller loop")
return
}
}
}

View File

@ -0,0 +1,561 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"context"
"errors"
"reflect"
"sort"
"sync"
"testing"
"time"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/internal/testutils"
"sigs.k8s.io/external-dns/pkg/apis/externaldns"
"sigs.k8s.io/external-dns/pkg/events/fake"
"sigs.k8s.io/external-dns/plan"
"sigs.k8s.io/external-dns/provider"
"sigs.k8s.io/external-dns/registry"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// mockProvider returns mock endpoints and validates changes.
type mockProvider struct {
provider.BaseProvider
RecordsStore []*endpoint.Endpoint
ExpectChanges *plan.Changes
}
type filteredMockProvider struct {
provider.BaseProvider
domainFilter *endpoint.DomainFilter
RecordsStore []*endpoint.Endpoint
RecordsCallCount int
ApplyChangesCalls []*plan.Changes
}
type errorMockProvider struct {
mockProvider
}
func (p *filteredMockProvider) GetDomainFilter() endpoint.DomainFilterInterface {
return p.domainFilter
}
// Records returns the desired mock endpoints.
func (p *filteredMockProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
p.RecordsCallCount++
return p.RecordsStore, nil
}
// ApplyChanges stores all calls for later check
func (p *filteredMockProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
p.ApplyChangesCalls = append(p.ApplyChangesCalls, changes)
return nil
}
// Records returns the desired mock endpoints.
func (p *mockProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
return p.RecordsStore, nil
}
func (p *errorMockProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
return nil, errors.New("error for testing")
}
// ApplyChanges validates that the passed in changes satisfy the assumptions.
func (p *mockProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
if err := verifyEndpoints(changes.Create, p.ExpectChanges.Create); err != nil {
return err
}
if err := verifyEndpoints(changes.UpdateNew, p.ExpectChanges.UpdateNew); err != nil {
return err
}
if err := verifyEndpoints(changes.UpdateOld, p.ExpectChanges.UpdateOld); err != nil {
return err
}
if err := verifyEndpoints(changes.Delete, p.ExpectChanges.Delete); err != nil {
return err
}
if !reflect.DeepEqual(ctx.Value(provider.RecordsContextKey), p.RecordsStore) {
return errors.New("context is wrong")
}
return nil
}
func verifyEndpoints(actual, expected []*endpoint.Endpoint) error {
if len(actual) != len(expected) {
return errors.New("number of records is wrong")
}
sort.Slice(actual, func(i, j int) bool {
return actual[i].DNSName < actual[j].DNSName
})
for i := range actual {
if actual[i].DNSName != expected[i].DNSName || !actual[i].Targets.Same(expected[i].Targets) {
return errors.New("record is wrong")
}
}
return nil
}
// newMockProvider creates a new mockProvider returning the given endpoints and validating the desired changes.
func newMockProvider(endpoints []*endpoint.Endpoint, changes *plan.Changes) provider.Provider {
dnsProvider := &mockProvider{
RecordsStore: endpoints,
ExpectChanges: changes,
}
return dnsProvider
}
func getTestSource() *testutils.MockSource {
// Fake some desired endpoints coming from our source.
source := new(testutils.MockSource)
source.On("Endpoints").Return([]*endpoint.Endpoint{
{
DNSName: "create-record",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
{
DNSName: "update-record",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.4.4"},
},
{
DNSName: "create-aaaa-record",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "update-aaaa-record",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
}, nil)
return source
}
func getTestConfig() *externaldns.Config {
cfg := externaldns.NewConfig()
cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME}
return cfg
}
func getTestProvider() provider.Provider {
// Fake some existing records in our DNS provider and validate some desired changes.
return newMockProvider(
[]*endpoint.Endpoint{
{
DNSName: "update-record",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
},
{
DNSName: "delete-record",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"4.3.2.1"},
},
{
DNSName: "update-aaaa-record",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::3"},
},
{
DNSName: "delete-aaaa-record",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::4"},
},
},
&plan.Changes{
Create: []*endpoint.Endpoint{
{DNSName: "create-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1"}},
{DNSName: "create-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
},
UpdateNew: []*endpoint.Endpoint{
{DNSName: "update-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::2"}},
{DNSName: "update-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"8.8.4.4"}},
},
UpdateOld: []*endpoint.Endpoint{
{DNSName: "update-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::3"}},
{DNSName: "update-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"8.8.8.8"}},
},
Delete: []*endpoint.Endpoint{
{DNSName: "delete-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::4"}},
{DNSName: "delete-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"4.3.2.1"}},
},
},
)
}
// TestRunOnce tests that RunOnce correctly orchestrates the different components.
func TestRunOnce(t *testing.T) {
source := getTestSource()
cfg := getTestConfig()
provider := getTestProvider()
emitter := fake.NewFakeEventEmitter()
r, err := registry.NewNoopRegistry(provider)
require.NoError(t, err)
// Run our controller once to trigger the validation.
ctrl := &Controller{
Source: source,
Registry: r,
Policy: &plan.SyncPolicy{},
ManagedRecordTypes: cfg.ManagedDNSRecordTypes,
EventEmitter: emitter,
}
assert.NoError(t, ctrl.RunOnce(context.Background()))
// Validate that the mock source was called.
source.AssertExpectations(t)
// check the verified records
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 1, verifiedRecords.Gauge, map[string]string{"record_type": "a"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 1, verifiedRecords.Gauge, map[string]string{"record_type": "aaaa"})
emitter.AssertNumberOfCalls(t, "Add", 6)
}
// TestRun tests that Run correctly starts and stops
func TestRun(t *testing.T) {
source := getTestSource()
cfg := getTestConfig()
provider := getTestProvider()
r, err := registry.NewNoopRegistry(provider)
require.NoError(t, err)
// Run our controller once to trigger the validation.
ctrl := &Controller{
Source: source,
Registry: r,
Policy: &plan.SyncPolicy{},
ManagedRecordTypes: cfg.ManagedDNSRecordTypes,
}
ctrl.nextRunAt = time.Now().Add(-time.Millisecond)
ctx, cancel := context.WithCancel(context.Background())
stopped := make(chan struct{})
go func() {
ctrl.Run(ctx)
close(stopped)
}()
time.Sleep(1500 * time.Millisecond)
cancel() // start shutdown
<-stopped
// Validate that the mock source was called.
source.AssertExpectations(t)
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 1, verifiedRecords.Gauge, map[string]string{"record_type": "a"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 1, verifiedRecords.Gauge, map[string]string{"record_type": "aaaa"})
}
func TestShouldRunOnce(t *testing.T) {
ctrl := &Controller{Interval: 10 * time.Minute, MinEventSyncInterval: 15 * time.Second}
now := time.Now()
// First run of Run loop should execute RunOnce
assert.True(t, ctrl.ShouldRunOnce(now))
assert.Equal(t, now.Add(10*time.Minute), ctrl.nextRunAt)
// Second run should not
assert.False(t, ctrl.ShouldRunOnce(now))
ctrl.lastRunAt = now
now = now.Add(10 * time.Second)
// Changes happen in ingresses or services
ctrl.ScheduleRunOnce(now)
ctrl.ScheduleRunOnce(now)
// Because we batch changes, ShouldRunOnce returns False at first
assert.False(t, ctrl.ShouldRunOnce(now))
assert.False(t, ctrl.ShouldRunOnce(now.Add(100*time.Microsecond)))
// But after MinInterval we should run reconciliation
now = now.Add(5 * time.Second)
assert.True(t, ctrl.ShouldRunOnce(now))
// But just one time
assert.False(t, ctrl.ShouldRunOnce(now))
// We should wait maximum possible time after last reconciliation started
now = now.Add(10*time.Minute - time.Second)
assert.False(t, ctrl.ShouldRunOnce(now))
// After exactly Interval it's OK again to reconcile
now = now.Add(time.Second)
assert.True(t, ctrl.ShouldRunOnce(now))
// But not two times
assert.False(t, ctrl.ShouldRunOnce(now))
// Multiple ingresses or services changes, closer than MinInterval from each other
ctrl.lastRunAt = now
firstChangeTime := now
secondChangeTime := firstChangeTime.Add(time.Second)
// First change
ctrl.ScheduleRunOnce(firstChangeTime)
// Second change
ctrl.ScheduleRunOnce(secondChangeTime)
// Executions should be spaced by at least MinEventSyncInterval
assert.False(t, ctrl.ShouldRunOnce(now.Add(5*time.Second)))
// Should not postpone the reconciliation further than firstChangeTime + MinInterval
now = now.Add(ctrl.MinEventSyncInterval)
assert.True(t, ctrl.ShouldRunOnce(now))
}
func testControllerFiltersDomains(t *testing.T, configuredEndpoints []*endpoint.Endpoint, domainFilter *endpoint.DomainFilter, providerEndpoints []*endpoint.Endpoint, expectedChanges []*plan.Changes) {
t.Helper()
cfg := externaldns.NewConfig()
cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME}
source := new(testutils.MockSource)
source.On("Endpoints").Return(configuredEndpoints, nil)
// Fake some existing records in our DNS provider and validate some desired changes.
provider := &filteredMockProvider{
RecordsStore: providerEndpoints,
}
r, err := registry.NewNoopRegistry(provider)
require.NoError(t, err)
ctrl := &Controller{
Source: source,
Registry: r,
Policy: &plan.SyncPolicy{},
DomainFilter: domainFilter,
ManagedRecordTypes: cfg.ManagedDNSRecordTypes,
}
assert.NoError(t, ctrl.RunOnce(context.Background()))
assert.Equal(t, 1, provider.RecordsCallCount)
require.Len(t, provider.ApplyChangesCalls, len(expectedChanges))
for i, change := range expectedChanges {
assert.Equal(t, *change, *provider.ApplyChangesCalls[i])
}
}
func TestControllerSkipsEmptyChanges(t *testing.T) {
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "create-record.other.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
},
},
endpoint.NewDomainFilter([]string{"used.tld"}),
[]*endpoint.Endpoint{
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
},
},
[]*plan.Changes{},
)
}
func TestWhenNoFilterControllerConsidersAllComain(t *testing.T) {
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "create-record.other.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
},
},
&endpoint.DomainFilter{},
[]*endpoint.Endpoint{
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
},
},
[]*plan.Changes{
{
Create: []*endpoint.Endpoint{
{
DNSName: "create-record.other.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
},
},
},
)
}
func TestWhenMultipleControllerConsidersAllFilteredComain(t *testing.T) {
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "create-record.other.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.1.1.1"},
},
{
DNSName: "create-record.unused.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
},
endpoint.NewDomainFilter([]string{"used.tld", "other.tld"}),
[]*endpoint.Endpoint{
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
},
},
[]*plan.Changes{
{
Create: []*endpoint.Endpoint{
{
DNSName: "create-record.other.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
},
UpdateOld: []*endpoint.Endpoint{
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
Labels: endpoint.Labels{},
},
},
UpdateNew: []*endpoint.Endpoint{
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.1.1.1"},
Labels: endpoint.Labels{
"owner": "",
},
},
},
},
},
)
}
type toggleRegistry struct {
registry.NoopRegistry
failCount int
failCountMu sync.Mutex // protects failCount
}
const toggleRegistryFailureCount = 3
func (r *toggleRegistry) Records(_ context.Context) ([]*endpoint.Endpoint, error) {
r.failCountMu.Lock()
defer r.failCountMu.Unlock()
if r.failCount < toggleRegistryFailureCount {
r.failCount++
return nil, provider.SoftError
}
return []*endpoint.Endpoint{}, nil
}
func (r *toggleRegistry) ApplyChanges(_ context.Context, changes *plan.Changes) error {
return nil
}
func TestToggleRegistry(t *testing.T) {
source := getTestSource()
cfg := getTestConfig()
r := &toggleRegistry{}
interval := 10 * time.Millisecond
ctrl := &Controller{
Source: source,
Registry: r,
Policy: &plan.SyncPolicy{},
ManagedRecordTypes: cfg.ManagedDNSRecordTypes,
Interval: interval,
}
ctrl.nextRunAt = time.Now().Add(-time.Millisecond)
ctx, cancel := context.WithCancel(context.Background())
stopped := make(chan struct{})
go func() {
ctrl.Run(ctx)
close(stopped)
}()
// Wait up to 1 minute for failCount to reach at least 3
// The timeout serves as a safety net against infinite loops while being
// sufficiently large to accommodate slow CI environments
deadline := time.Now().Add(15 * time.Second)
for {
r.failCountMu.Lock()
count := r.failCount
r.failCountMu.Unlock()
if count >= toggleRegistryFailureCount {
break
}
if time.Now().After(deadline) {
break
}
// Sleep for the controller interval to avoid busy waiting
// since the controller won't run again until the interval passes
time.Sleep(interval)
}
cancel()
<-stopped
r.failCountMu.Lock()
finalCount := r.failCount
r.failCountMu.Unlock()
assert.Equal(t, toggleRegistryFailureCount, finalCount, "failCount should be at least %d", toggleRegistryFailureCount)
}

View File

@ -0,0 +1,40 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"sigs.k8s.io/external-dns/pkg/events"
"sigs.k8s.io/external-dns/plan"
)
// This function emits events for each change in the provided plan.Changes object using the given EventEmitter.
// It handles create, update, and delete changes, assigning appropriate actions and reasons to each event.
// If the emitter is nil, it does nothing.
func emitChangeEvent(e events.EventEmitter, ch plan.Changes, reason events.Reason) {
if e == nil {
return
}
for _, change := range ch.Create {
e.Add(events.NewEvent(change.RefObject(), change.Describe(), events.ActionCreate, reason))
}
for _, change := range ch.UpdateNew {
e.Add(events.NewEvent(change.RefObject(), change.Describe(), events.ActionUpdate, reason))
}
for _, change := range ch.Delete {
e.Add(events.NewEvent(change.RefObject(), change.Describe(), events.ActionDelete, events.RecordDeleted))
}
}

View File

@ -0,0 +1,107 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/pkg/events"
"sigs.k8s.io/external-dns/pkg/events/fake"
"sigs.k8s.io/external-dns/plan"
)
func TestEmit_RecordReady(t *testing.T) {
refObj := &events.ObjectReference{}
tests := []struct {
name string
changes plan.Changes
asserts func(em *fake.EventEmitter, ch plan.Changes)
}{
{
name: "create, update and delete endpoints",
changes: plan.Changes{
Create: []*endpoint.Endpoint{
endpoint.NewEndpoint("one.example.com", endpoint.RecordTypeA, "10.10.10.0").WithRefObject(refObj),
endpoint.NewEndpoint("two.example.com", endpoint.RecordTypeA, "10.10.10.1").WithRefObject(refObj),
},
UpdateNew: []*endpoint.Endpoint{
endpoint.NewEndpoint("three.example.com", endpoint.RecordTypeA, "10.10.10.2").WithRefObject(refObj),
endpoint.NewEndpoint("four.example.com", endpoint.RecordTypeA, "10.10.10.3").WithRefObject(refObj),
},
Delete: []*endpoint.Endpoint{
endpoint.NewEndpoint("five.example.com", endpoint.RecordTypeA, "192.10.10.0").WithRefObject(refObj),
},
},
asserts: func(em *fake.EventEmitter, ch plan.Changes) {
for _, ep := range ch.Create {
em.AssertCalled(t, "Add", events.NewEvent(ep.RefObject(), ep.Describe(), events.ActionCreate, events.RecordReady))
}
for _, ep := range ch.Delete {
em.AssertCalled(t, "Add", events.NewEvent(ep.RefObject(), ep.Describe(), events.ActionDelete, events.RecordDeleted))
}
em.AssertNotCalled(t, "Add", mock.MatchedBy(func(e events.Event) bool {
return e.EventType() == events.EventTypeWarning
}))
em.AssertNumberOfCalls(t, "Add", 5)
},
},
{
name: "delete endpoints",
changes: plan.Changes{
Create: []*endpoint.Endpoint{},
UpdateNew: []*endpoint.Endpoint{},
Delete: []*endpoint.Endpoint{
endpoint.NewEndpoint("five.example.com", endpoint.RecordTypeA, "192.10.10.0").WithRefObject(refObj),
},
},
asserts: func(em *fake.EventEmitter, ch plan.Changes) {
for _, ep := range ch.Delete {
em.AssertCalled(t, "Add", events.NewEvent(ep.RefObject(), ep.Describe(), events.ActionDelete, events.RecordDeleted))
}
em.AssertCalled(t, "Add", mock.MatchedBy(func(e events.Event) bool {
return e.EventType() == events.EventTypeNormal &&
e.Action() == events.ActionDelete &&
e.Reason() == events.RecordDeleted
}))
em.AssertNumberOfCalls(t, "Add", 1)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
emitter := fake.NewFakeEventEmitter()
emitChangeEvent(emitter, tt.changes, events.RecordReady)
tt.asserts(emitter, tt.changes)
mock.AssertExpectationsForObjects(t, emitter)
})
}
}
func TestEmit_NilEmitter(t *testing.T) {
assert.NotPanics(t, func() {
emitChangeEvent(nil, plan.Changes{}, events.RecordError)
})
}

View File

@ -0,0 +1,502 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/route53"
sd "github.com/aws/aws-sdk-go-v2/service/servicediscovery"
"github.com/go-logr/logr"
"github.com/prometheus/client_golang/prometheus/promhttp"
log "github.com/sirupsen/logrus"
"k8s.io/klog/v2"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/pkg/apis/externaldns"
"sigs.k8s.io/external-dns/pkg/apis/externaldns/validation"
"sigs.k8s.io/external-dns/pkg/events"
"sigs.k8s.io/external-dns/pkg/metrics"
"sigs.k8s.io/external-dns/plan"
"sigs.k8s.io/external-dns/provider"
"sigs.k8s.io/external-dns/provider/akamai"
"sigs.k8s.io/external-dns/provider/alibabacloud"
"sigs.k8s.io/external-dns/provider/aws"
"sigs.k8s.io/external-dns/provider/awssd"
"sigs.k8s.io/external-dns/provider/azure"
"sigs.k8s.io/external-dns/provider/civo"
"sigs.k8s.io/external-dns/provider/cloudflare"
"sigs.k8s.io/external-dns/provider/coredns"
"sigs.k8s.io/external-dns/provider/digitalocean"
"sigs.k8s.io/external-dns/provider/dnsimple"
"sigs.k8s.io/external-dns/provider/exoscale"
"sigs.k8s.io/external-dns/provider/gandi"
"sigs.k8s.io/external-dns/provider/godaddy"
"sigs.k8s.io/external-dns/provider/google"
"sigs.k8s.io/external-dns/provider/inmemory"
"sigs.k8s.io/external-dns/provider/linode"
"sigs.k8s.io/external-dns/provider/ns1"
"sigs.k8s.io/external-dns/provider/oci"
"sigs.k8s.io/external-dns/provider/ovh"
"sigs.k8s.io/external-dns/provider/pdns"
"sigs.k8s.io/external-dns/provider/pihole"
"sigs.k8s.io/external-dns/provider/plural"
"sigs.k8s.io/external-dns/provider/rfc2136"
"sigs.k8s.io/external-dns/provider/scaleway"
"sigs.k8s.io/external-dns/provider/transip"
"sigs.k8s.io/external-dns/provider/webhook"
webhookapi "sigs.k8s.io/external-dns/provider/webhook/api"
"sigs.k8s.io/external-dns/registry"
"sigs.k8s.io/external-dns/source"
"sigs.k8s.io/external-dns/source/annotations"
"sigs.k8s.io/external-dns/source/wrappers"
)
func Execute() {
cfg := externaldns.NewConfig()
if err := cfg.ParseFlags(os.Args[1:]); err != nil {
log.Fatalf("flag parsing error: %v", err)
}
log.Infof("config: %s", cfg)
if err := validation.ValidateConfig(cfg); err != nil {
log.Fatalf("config validation failed: %v", err)
}
// Set annotation prefix (required since init() was removed)
annotations.SetAnnotationPrefix(cfg.AnnotationPrefix)
if cfg.AnnotationPrefix != annotations.DefaultAnnotationPrefix {
log.Infof("Using custom annotation prefix: %s", cfg.AnnotationPrefix)
}
configureLogger(cfg)
if cfg.DryRun {
log.Info("running in dry-run mode. No changes to DNS records will be made.")
}
if log.GetLevel() < log.DebugLevel {
// Klog V2 is used by k8s.io/apimachinery/pkg/labels and can throw (a lot) of irrelevant logs
// See https://github.com/kubernetes-sigs/external-dns/issues/2348
defer klog.ClearLogger()
klog.SetLogger(logr.Discard())
}
log.Info(externaldns.Banner())
ctx, cancel := context.WithCancel(context.Background())
go serveMetrics(cfg.MetricsAddress)
go handleSigterm(cancel)
endpointsSource, err := buildSource(ctx, cfg)
if err != nil {
log.Fatal(err) // nolint: gocritic // exitAfterDefer
}
domainFilter := createDomainFilter(cfg)
prvdr, err := buildProvider(ctx, cfg, domainFilter)
if err != nil {
log.Fatal(err)
}
if cfg.WebhookServer {
webhookapi.StartHTTPApi(prvdr, nil, cfg.WebhookProviderReadTimeout, cfg.WebhookProviderWriteTimeout, "127.0.0.1:8888")
os.Exit(0)
}
ctrl, err := buildController(ctx, cfg, endpointsSource, prvdr, domainFilter)
if err != nil {
log.Fatal(err)
}
if cfg.Once {
err := ctrl.RunOnce(ctx)
if err != nil {
log.Fatal(err)
}
os.Exit(0)
}
if cfg.UpdateEvents {
// Add RunOnce as the handler function that will be called when ingress/service sources have changed.
// Note that k8s Informers will perform an initial list operation, which results in the handler
// function initially being called for every Service/Ingress that exists
ctrl.Source.AddEventHandler(ctx, func() { ctrl.ScheduleRunOnce(time.Now()) })
}
ctrl.ScheduleRunOnce(time.Now())
ctrl.Run(ctx)
}
func buildProvider(
ctx context.Context,
cfg *externaldns.Config,
domainFilter *endpoint.DomainFilter,
) (provider.Provider, error) {
var p provider.Provider
var err error
zoneNameFilter := endpoint.NewDomainFilter(cfg.ZoneNameFilter)
zoneIDFilter := provider.NewZoneIDFilter(cfg.ZoneIDFilter)
zoneTypeFilter := provider.NewZoneTypeFilter(cfg.AWSZoneType)
zoneTagFilter := provider.NewZoneTagFilter(cfg.AWSZoneTagFilter)
switch cfg.Provider {
case "akamai":
p, err = akamai.NewAkamaiProvider(
akamai.AkamaiConfig{
DomainFilter: domainFilter,
ZoneIDFilter: zoneIDFilter,
ServiceConsumerDomain: cfg.AkamaiServiceConsumerDomain,
ClientToken: cfg.AkamaiClientToken,
ClientSecret: cfg.AkamaiClientSecret,
AccessToken: cfg.AkamaiAccessToken,
EdgercPath: cfg.AkamaiEdgercPath,
EdgercSection: cfg.AkamaiEdgercSection,
DryRun: cfg.DryRun,
}, nil)
case "alibabacloud":
p, err = alibabacloud.NewAlibabaCloudProvider(cfg.AlibabaCloudConfigFile, domainFilter, zoneIDFilter, cfg.AlibabaCloudZoneType, cfg.DryRun)
case "aws":
configs := aws.CreateV2Configs(cfg)
clients := make(map[string]aws.Route53API, len(configs))
for profile, config := range configs {
clients[profile] = route53.NewFromConfig(config)
}
p, err = aws.NewAWSProvider(
aws.AWSConfig{
DomainFilter: domainFilter,
ZoneIDFilter: zoneIDFilter,
ZoneTypeFilter: zoneTypeFilter,
ZoneTagFilter: zoneTagFilter,
ZoneMatchParent: cfg.AWSZoneMatchParent,
BatchChangeSize: cfg.AWSBatchChangeSize,
BatchChangeSizeBytes: cfg.AWSBatchChangeSizeBytes,
BatchChangeSizeValues: cfg.AWSBatchChangeSizeValues,
BatchChangeInterval: cfg.AWSBatchChangeInterval,
EvaluateTargetHealth: cfg.AWSEvaluateTargetHealth,
PreferCNAME: cfg.AWSPreferCNAME,
DryRun: cfg.DryRun,
ZoneCacheDuration: cfg.AWSZoneCacheDuration,
},
clients,
)
case "aws-sd":
// Check that only compatible Registry is used with AWS-SD
if cfg.Registry != "noop" && cfg.Registry != "aws-sd" {
log.Infof("Registry \"%s\" cannot be used with AWS Cloud Map. Switching to \"aws-sd\".", cfg.Registry)
cfg.Registry = "aws-sd"
}
p, err = awssd.NewAWSSDProvider(domainFilter, cfg.AWSZoneType, cfg.DryRun, cfg.AWSSDServiceCleanup, cfg.TXTOwnerID, cfg.AWSSDCreateTag, sd.NewFromConfig(aws.CreateDefaultV2Config(cfg)))
case "azure-dns", "azure":
p, err = azure.NewAzureProvider(cfg.AzureConfigFile, domainFilter, zoneNameFilter, zoneIDFilter, cfg.AzureSubscriptionID, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.AzureActiveDirectoryAuthorityHost, cfg.AzureZonesCacheDuration, cfg.AzureMaxRetriesCount, cfg.DryRun)
case "azure-private-dns":
p, err = azure.NewAzurePrivateDNSProvider(cfg.AzureConfigFile, domainFilter, zoneNameFilter, zoneIDFilter, cfg.AzureSubscriptionID, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.AzureActiveDirectoryAuthorityHost, cfg.AzureZonesCacheDuration, cfg.AzureMaxRetriesCount, cfg.DryRun)
case "civo":
p, err = civo.NewCivoProvider(domainFilter, cfg.DryRun)
case "cloudflare":
p, err = cloudflare.NewCloudFlareProvider(
domainFilter,
zoneIDFilter,
cfg.CloudflareProxied,
cfg.DryRun,
cloudflare.RegionalServicesConfig{
Enabled: cfg.CloudflareRegionalServices,
RegionKey: cfg.CloudflareRegionKey,
},
cloudflare.CustomHostnamesConfig{
Enabled: cfg.CloudflareCustomHostnames,
MinTLSVersion: cfg.CloudflareCustomHostnamesMinTLSVersion,
CertificateAuthority: cfg.CloudflareCustomHostnamesCertificateAuthority,
},
cloudflare.DNSRecordsConfig{
PerPage: cfg.CloudflareDNSRecordsPerPage,
Comment: cfg.CloudflareDNSRecordsComment,
})
case "google":
p, err = google.NewGoogleProvider(ctx, cfg.GoogleProject, domainFilter, zoneIDFilter, cfg.GoogleBatchChangeSize, cfg.GoogleBatchChangeInterval, cfg.GoogleZoneVisibility, cfg.DryRun)
case "digitalocean":
p, err = digitalocean.NewDigitalOceanProvider(ctx, domainFilter, cfg.DryRun, cfg.DigitalOceanAPIPageSize)
case "ovh":
p, err = ovh.NewOVHProvider(ctx, domainFilter, cfg.OVHEndpoint, cfg.OVHApiRateLimit, cfg.OVHEnableCNAMERelative, cfg.DryRun)
case "linode":
p, err = linode.NewLinodeProvider(domainFilter, cfg.DryRun)
case "dnsimple":
p, err = dnsimple.NewDnsimpleProvider(domainFilter, zoneIDFilter, cfg.DryRun)
case "coredns", "skydns":
p, err = coredns.NewCoreDNSProvider(domainFilter, cfg.CoreDNSPrefix, cfg.DryRun)
case "exoscale":
p, err = exoscale.NewExoscaleProvider(
cfg.ExoscaleAPIEnvironment,
cfg.ExoscaleAPIZone,
cfg.ExoscaleAPIKey,
cfg.ExoscaleAPISecret,
cfg.DryRun,
exoscale.ExoscaleWithDomain(domainFilter),
exoscale.ExoscaleWithLogging(),
)
case "inmemory":
p, err = inmemory.NewInMemoryProvider(inmemory.InMemoryInitZones(cfg.InMemoryZones), inmemory.InMemoryWithDomain(domainFilter), inmemory.InMemoryWithLogging()), nil
case "pdns":
p, err = pdns.NewPDNSProvider(
ctx,
pdns.PDNSConfig{
DomainFilter: domainFilter,
DryRun: cfg.DryRun,
Server: cfg.PDNSServer,
ServerID: cfg.PDNSServerID,
APIKey: cfg.PDNSAPIKey,
TLSConfig: pdns.TLSConfig{
SkipTLSVerify: cfg.PDNSSkipTLSVerify,
CAFilePath: cfg.TLSCA,
ClientCertFilePath: cfg.TLSClientCert,
ClientCertKeyFilePath: cfg.TLSClientCertKey,
},
},
)
case "oci":
var config *oci.OCIConfig
// if the instance-principals flag was set, and a compartment OCID was provided, then ignore the
// OCI config file, and provide a config that uses instance principal authentication.
if cfg.OCIAuthInstancePrincipal {
if len(cfg.OCICompartmentOCID) == 0 {
err = fmt.Errorf("instance principal authentication requested, but no compartment OCID provided")
break
}
authConfig := oci.OCIAuthConfig{UseInstancePrincipal: true}
config = &oci.OCIConfig{Auth: authConfig, CompartmentID: cfg.OCICompartmentOCID}
} else {
if config, err = oci.LoadOCIConfig(cfg.OCIConfigFile); err != nil {
break
}
}
config.ZoneCacheDuration = cfg.OCIZoneCacheDuration
p, err = oci.NewOCIProvider(*config, domainFilter, zoneIDFilter, cfg.OCIZoneScope, cfg.DryRun)
case "rfc2136":
tlsConfig := rfc2136.TLSConfig{
UseTLS: cfg.RFC2136UseTLS,
SkipTLSVerify: cfg.RFC2136SkipTLSVerify,
CAFilePath: cfg.TLSCA,
ClientCertFilePath: cfg.TLSClientCert,
ClientCertKeyFilePath: cfg.TLSClientCertKey,
}
p, err = rfc2136.NewRfc2136Provider(cfg.RFC2136Host, cfg.RFC2136Port, cfg.RFC2136Zone, cfg.RFC2136Insecure, cfg.RFC2136TSIGKeyName, cfg.RFC2136TSIGSecret, cfg.RFC2136TSIGSecretAlg, cfg.RFC2136TAXFR, domainFilter, cfg.DryRun, cfg.RFC2136MinTTL, cfg.RFC2136CreatePTR, cfg.RFC2136GSSTSIG, cfg.RFC2136KerberosUsername, cfg.RFC2136KerberosPassword, cfg.RFC2136KerberosRealm, cfg.RFC2136BatchChangeSize, tlsConfig, cfg.RFC2136LoadBalancingStrategy, nil)
case "ns1":
p, err = ns1.NewNS1Provider(
ns1.NS1Config{
DomainFilter: domainFilter,
ZoneIDFilter: zoneIDFilter,
NS1Endpoint: cfg.NS1Endpoint,
NS1IgnoreSSL: cfg.NS1IgnoreSSL,
DryRun: cfg.DryRun,
MinTTLSeconds: cfg.NS1MinTTLSeconds,
},
)
case "transip":
p, err = transip.NewTransIPProvider(cfg.TransIPAccountName, cfg.TransIPPrivateKeyFile, domainFilter, cfg.DryRun)
case "scaleway":
p, err = scaleway.NewScalewayProvider(ctx, domainFilter, cfg.DryRun)
case "godaddy":
p, err = godaddy.NewGoDaddyProvider(ctx, domainFilter, cfg.GoDaddyTTL, cfg.GoDaddyAPIKey, cfg.GoDaddySecretKey, cfg.GoDaddyOTE, cfg.DryRun)
case "gandi":
p, err = gandi.NewGandiProvider(ctx, domainFilter, cfg.DryRun)
case "pihole":
p, err = pihole.NewPiholeProvider(
pihole.PiholeConfig{
Server: cfg.PiholeServer,
Password: cfg.PiholePassword,
TLSInsecureSkipVerify: cfg.PiholeTLSInsecureSkipVerify,
DomainFilter: domainFilter,
DryRun: cfg.DryRun,
APIVersion: cfg.PiholeApiVersion,
},
)
case "plural":
p, err = plural.NewPluralProvider(cfg.PluralCluster, cfg.PluralProvider)
case "webhook":
p, err = webhook.NewWebhookProvider(cfg.WebhookProviderURL)
default:
err = fmt.Errorf("unknown dns provider: %s", cfg.Provider)
}
if p != nil && cfg.ProviderCacheTime > 0 {
p = provider.NewCachedProvider(
p,
cfg.ProviderCacheTime,
)
}
return p, err
}
func buildController(
ctx context.Context,
cfg *externaldns.Config,
src source.Source,
p provider.Provider,
filter *endpoint.DomainFilter,
) (*Controller, error) {
policy, ok := plan.Policies[cfg.Policy]
if !ok {
return nil, fmt.Errorf("unknown policy: %s", cfg.Policy)
}
reg, err := selectRegistry(cfg, p)
if err != nil {
return nil, err
}
eventsCfg := events.NewConfig(
events.WithKubeConfig(cfg.KubeConfig, cfg.APIServerURL, cfg.RequestTimeout),
events.WithEmitEvents(cfg.EmitEvents),
events.WithDryRun(cfg.DryRun))
var eventEmitter events.EventEmitter
if eventsCfg.IsEnabled() {
eventCtrl, err := events.NewEventController(eventsCfg)
if err != nil {
log.Fatal(err)
}
eventCtrl.Run(ctx)
eventEmitter = eventCtrl
}
return &Controller{
Source: src,
Registry: reg,
Policy: policy,
Interval: cfg.Interval,
DomainFilter: filter,
ManagedRecordTypes: cfg.ManagedDNSRecordTypes,
ExcludeRecordTypes: cfg.ExcludeDNSRecordTypes,
MinEventSyncInterval: cfg.MinEventSyncInterval,
TXTOwnerOld: cfg.TXTOwnerOld,
EventEmitter: eventEmitter,
}, nil
}
// This function configures the logger format and level based on the provided configuration.
func configureLogger(cfg *externaldns.Config) {
if cfg.LogFormat == "json" {
log.SetFormatter(&log.JSONFormatter{})
}
ll, err := log.ParseLevel(cfg.LogLevel)
if err != nil {
log.Fatalf("failed to parse log level: %v", err)
}
log.SetLevel(ll)
}
// selectRegistry selects the appropriate registry implementation based on the configuration in cfg.
// It initializes and returns a registry along with any error encountered during setup.
// Supported registry types include: dynamodb, noop, txt, and aws-sd.
func selectRegistry(cfg *externaldns.Config, p provider.Provider) (registry.Registry, error) {
var r registry.Registry
var err error
switch cfg.Registry {
case "dynamodb":
var dynamodbOpts []func(*dynamodb.Options)
if cfg.AWSDynamoDBRegion != "" {
dynamodbOpts = []func(*dynamodb.Options){
func(opts *dynamodb.Options) {
opts.Region = cfg.AWSDynamoDBRegion
},
}
}
r, err = registry.NewDynamoDBRegistry(p, cfg.TXTOwnerID, dynamodb.NewFromConfig(aws.CreateDefaultV2Config(cfg), dynamodbOpts...), cfg.AWSDynamoDBTable, cfg.TXTPrefix, cfg.TXTSuffix, cfg.TXTWildcardReplacement, cfg.ManagedDNSRecordTypes, cfg.ExcludeDNSRecordTypes, []byte(cfg.TXTEncryptAESKey), cfg.TXTCacheInterval)
case "noop":
r, err = registry.NewNoopRegistry(p)
case "txt":
r, err = registry.NewTXTRegistry(p, cfg.TXTPrefix, cfg.TXTSuffix, cfg.TXTOwnerID, cfg.TXTCacheInterval, cfg.TXTWildcardReplacement, cfg.ManagedDNSRecordTypes, cfg.ExcludeDNSRecordTypes, cfg.TXTEncryptEnabled, []byte(cfg.TXTEncryptAESKey), cfg.TXTOwnerOld)
case "aws-sd":
r, err = registry.NewAWSSDRegistry(p, cfg.TXTOwnerID)
default:
log.Fatalf("unknown registry: %s", cfg.Registry)
}
return r, err
}
// buildSource creates and configures the source(s) for endpoint discovery based on the provided configuration.
// It initializes the source configuration, generates the required sources, and combines them into a single,
// deduplicated source. Returns the combined source or an error if source creation fails.
func buildSource(ctx context.Context, cfg *externaldns.Config) (source.Source, error) {
sourceCfg := source.NewSourceConfig(cfg)
sources, err := source.ByNames(ctx, &source.SingletonClientGenerator{
KubeConfig: cfg.KubeConfig,
APIServerURL: cfg.APIServerURL,
RequestTimeout: func() time.Duration {
if cfg.UpdateEvents {
return 0
}
return cfg.RequestTimeout
}(),
}, cfg.Sources, sourceCfg)
if err != nil {
return nil, err
}
opts := wrappers.NewConfig(
wrappers.WithDefaultTargets(cfg.DefaultTargets),
wrappers.WithForceDefaultTargets(cfg.ForceDefaultTargets),
wrappers.WithNAT64Networks(cfg.NAT64Networks),
wrappers.WithTargetNetFilter(cfg.TargetNetFilter),
wrappers.WithExcludeTargetNets(cfg.ExcludeTargetNets),
wrappers.WithMinTTL(cfg.MinTTL))
return wrappers.WrapSources(sources, opts)
}
// RegexDomainFilter overrides DomainFilter
func createDomainFilter(cfg *externaldns.Config) *endpoint.DomainFilter {
if cfg.RegexDomainFilter != nil && cfg.RegexDomainFilter.String() != "" {
return endpoint.NewRegexDomainFilter(cfg.RegexDomainFilter, cfg.RegexDomainExclusion)
} else {
return endpoint.NewDomainFilterWithExclusions(cfg.DomainFilter, cfg.ExcludeDomains)
}
}
// handleSigterm listens for a SIGTERM signal and triggers the provided cancel function
// to gracefully terminate the application. It logs a message when the signal is received.
func handleSigterm(cancel func()) {
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGTERM)
<-signals
log.Info("Received SIGTERM. Terminating...")
cancel()
}
// serveMetrics starts an HTTP server that serves health and metrics endpoints.
// The /healthz endpoint returns a 200 OK status to indicate the service is healthy.
// The /metrics endpoint serves Prometheus metrics.
// The server listens on the specified address and logs debug information about the endpoints.
func serveMetrics(address string) {
http.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("OK"))
})
log.Debugf("serving 'healthz' on '%s/healthz'", address)
log.Debugf("serving 'metrics' on '%s/metrics'", address)
log.Debugf("registered '%d' metrics", len(metrics.RegisterMetric.Metrics))
http.Handle("/metrics", promhttp.Handler())
log.Fatal(http.ListenAndServe(address, nil))
}

View File

@ -0,0 +1,681 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"bytes"
"context"
"errors"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"reflect"
"regexp"
"testing"
"time"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/pkg/apis/externaldns"
"sigs.k8s.io/external-dns/provider"
fakeprovider "sigs.k8s.io/external-dns/provider/fakes"
)
// Logger
func TestConfigureLogger(t *testing.T) {
tests := []struct {
name string
cfg *externaldns.Config
wantLevel log.Level
wantJSON bool
wantErr bool
wantErrMsg string
}{
{
name: "Default log format and level",
cfg: &externaldns.Config{
LogLevel: "info",
LogFormat: "text",
},
wantLevel: log.InfoLevel,
},
{
name: "JSON log format",
cfg: &externaldns.Config{
LogLevel: "debug",
LogFormat: "json",
},
wantLevel: log.DebugLevel,
wantJSON: true,
},
{
name: "Invalid log level",
cfg: &externaldns.Config{
LogLevel: "invalid",
LogFormat: "text",
},
wantLevel: log.InfoLevel,
wantErr: true,
wantErrMsg: "failed to parse log level",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.wantErr {
// Capture and suppress fatal exit; restore logger after test
logger := log.StandardLogger()
prevOut := logger.Out
prevExit := logger.ExitFunc
b := new(bytes.Buffer)
var captureLogFatal bool
logger.ExitFunc = func(int) { captureLogFatal = true }
logger.SetOutput(b)
t.Cleanup(func() {
logger.SetOutput(prevOut)
logger.ExitFunc = prevExit
})
configureLogger(tt.cfg)
assert.True(t, captureLogFatal)
assert.Contains(t, b.String(), tt.wantErrMsg)
} else {
// Save and restore logger state to avoid leaking between tests
logger := log.StandardLogger()
prevFormatter := logger.Formatter
prevLevel := log.GetLevel()
t.Cleanup(func() {
log.SetLevel(prevLevel)
logger.SetFormatter(prevFormatter)
})
configureLogger(tt.cfg)
assert.Equal(t, tt.wantLevel, log.GetLevel())
if tt.wantJSON {
assert.IsType(t, &log.JSONFormatter{}, log.StandardLogger().Formatter)
} else {
assert.IsType(t, &log.TextFormatter{}, log.StandardLogger().Formatter)
}
}
})
}
}
func TestSelectRegistry(t *testing.T) {
tests := []struct {
name string
cfg *externaldns.Config
provider provider.Provider
wantErr bool
wantType string
}{
{
name: "DynamoDB registry",
cfg: &externaldns.Config{
Registry: "dynamodb",
AWSDynamoDBRegion: "us-west-2",
AWSDynamoDBTable: "test-table",
TXTOwnerID: "owner-id",
TXTWildcardReplacement: "wildcard",
ManagedDNSRecordTypes: []string{"A", "CNAME"},
ExcludeDNSRecordTypes: []string{"TXT"},
TXTCacheInterval: 60,
},
provider: &fakeprovider.MockProvider{},
wantErr: false,
wantType: "DynamoDBRegistry",
},
{
name: "Noop registry",
cfg: &externaldns.Config{
Registry: "noop",
},
provider: &fakeprovider.MockProvider{},
wantErr: false,
wantType: "NoopRegistry",
},
{
name: "TXT registry",
cfg: &externaldns.Config{
Registry: "txt",
TXTPrefix: "prefix",
TXTOwnerID: "owner-id",
TXTCacheInterval: 60,
TXTWildcardReplacement: "wildcard",
ManagedDNSRecordTypes: []string{"A", "CNAME"},
ExcludeDNSRecordTypes: []string{"TXT"},
},
provider: &fakeprovider.MockProvider{},
wantErr: false,
wantType: "TXTRegistry",
},
{
name: "AWS-SD registry",
cfg: &externaldns.Config{
Registry: "aws-sd",
TXTOwnerID: "owner-id",
},
provider: &fakeprovider.MockProvider{},
wantErr: false,
wantType: "AWSSDRegistry",
},
{
name: "Unknown registry",
cfg: &externaldns.Config{
Registry: "unknown",
},
provider: &fakeprovider.MockProvider{},
wantErr: true,
wantType: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.wantErr {
// Capture fatal without exiting; avoid brittle output assertions
logger := log.StandardLogger()
prevOut := logger.Out
prevExit := logger.ExitFunc
var fatalCalled bool
logger.ExitFunc = func(int) { fatalCalled = true }
// Capture log output
b := new(bytes.Buffer)
logger.SetOutput(b)
t.Cleanup(func() {
logger.SetOutput(prevOut)
logger.ExitFunc = prevExit
})
_, err := selectRegistry(tt.cfg, tt.provider)
assert.NoError(t, err)
assert.True(t, fatalCalled)
} else {
reg, err := selectRegistry(tt.cfg, tt.provider)
assert.NoError(t, err)
assert.Contains(t, reflect.TypeOf(reg).String(), tt.wantType)
}
})
}
}
// Provider
func TestBuildProvider(t *testing.T) {
tests := []struct {
name string
cfg *externaldns.Config
expectedType string
expectedError string
}{
{
name: "aws provider",
cfg: &externaldns.Config{
Provider: "aws",
},
expectedType: "*aws.AWSProvider",
},
{
name: "rfc2136 provider",
cfg: &externaldns.Config{
Provider: "rfc2136",
RFC2136TSIGSecretAlg: "hmac-sha256",
},
expectedType: "*rfc2136.rfc2136Provider",
},
{
name: "gandi provider",
cfg: &externaldns.Config{
Provider: "gandi",
},
expectedError: "no environment variable GANDI_KEY or GANDI_PAT provided",
},
{
name: "inmemory provider",
cfg: &externaldns.Config{
Provider: "inmemory",
},
expectedType: "*inmemory.InMemoryProvider",
},
{
name: "inmemory cached provider",
cfg: &externaldns.Config{
Provider: "inmemory",
ProviderCacheTime: 10 * time.Millisecond,
},
expectedType: "*provider.CachedProvider",
},
{
name: "oci provider instance principal without compartment OCID",
cfg: &externaldns.Config{
Provider: "oci",
OCIAuthInstancePrincipal: true,
OCICompartmentOCID: "",
},
expectedError: "instance principal authentication requested, but no compartment OCID provided",
},
{
name: "oci provider without config file",
cfg: &externaldns.Config{
Provider: "oci",
OCIConfigFile: "",
},
expectedError: "reading OCI config file",
},
{
name: "coredns provider",
cfg: &externaldns.Config{
Provider: "coredns",
},
expectedType: "coredns.coreDNSProvider",
},
{
name: "pihole provider",
cfg: &externaldns.Config{
Provider: "pihole",
PiholeApiVersion: "6",
PiholeServer: "http://localhost:8080",
},
expectedType: "*pihole.PiholeProvider",
},
{
name: "dnsimple provider",
cfg: &externaldns.Config{
Provider: "dnsimple",
},
expectedError: "no dnsimple oauth token provided",
},
{
name: "unknown provider",
cfg: &externaldns.Config{
Provider: "unknown",
},
expectedError: "unknown dns provider: unknown",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
domainFilter := endpoint.NewDomainFilter([]string{"example.com"})
p, err := buildProvider(t.Context(), tt.cfg, domainFilter)
if tt.expectedError != "" {
assert.Error(t, err)
assert.ErrorContains(t, err, tt.expectedError)
} else {
assert.NoError(t, err)
assert.NotNil(t, p)
assert.Contains(t, reflect.TypeOf(p).String(), tt.expectedType)
}
})
}
}
func TestCreateDomainFilter(t *testing.T) {
tests := []struct {
name string
cfg *externaldns.Config
expectedDomainFilter *endpoint.DomainFilter
isConfigured bool
}{
{
name: "RegexDomainFilter",
cfg: &externaldns.Config{
RegexDomainFilter: regexp.MustCompile(`example\.com`),
RegexDomainExclusion: regexp.MustCompile(`excluded\.example\.com`),
},
expectedDomainFilter: endpoint.NewRegexDomainFilter(regexp.MustCompile(`example\.com`), regexp.MustCompile(`excluded\.example\.com`)),
isConfigured: true,
},
{
name: "RegexDomainWithoutExclusionFilter",
cfg: &externaldns.Config{
RegexDomainFilter: regexp.MustCompile(`example\.com`),
},
expectedDomainFilter: endpoint.NewRegexDomainFilter(regexp.MustCompile(`example\.com`), nil),
isConfigured: true,
},
{
name: "DomainFilterWithExclusions",
cfg: &externaldns.Config{
DomainFilter: []string{"example.com"},
ExcludeDomains: []string{"excluded.example.com"},
},
expectedDomainFilter: endpoint.NewDomainFilterWithExclusions([]string{"example.com"}, []string{"excluded.example.com"}),
isConfigured: true,
},
{
name: "DomainFilterWithExclusionsOnly",
cfg: &externaldns.Config{
ExcludeDomains: []string{"excluded.example.com"},
},
expectedDomainFilter: endpoint.NewDomainFilterWithExclusions([]string{}, []string{"excluded.example.com"}),
isConfigured: true,
},
{
name: "EmptyDomainFilter",
cfg: &externaldns.Config{
DomainFilter: []string{},
ExcludeDomains: []string{},
},
expectedDomainFilter: endpoint.NewDomainFilterWithExclusions([]string{}, []string{}),
isConfigured: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
filter := createDomainFilter(tt.cfg)
assert.Equal(t, tt.isConfigured, filter.IsConfigured())
assert.Equal(t, tt.expectedDomainFilter, filter)
})
}
}
func TestBuildSource(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
}))
defer svr.Close()
tests := []struct {
name string
cfg *externaldns.Config
expectedError bool
}{
{
name: "Valid configuration with sources",
cfg: &externaldns.Config{
APIServerURL: svr.URL,
Sources: []string{"fake"},
RequestTimeout: 6 * time.Millisecond,
},
expectedError: false,
},
{
name: "Empty sources configuration",
cfg: &externaldns.Config{
APIServerURL: svr.URL,
Sources: []string{},
RequestTimeout: 6 * time.Millisecond,
},
expectedError: false,
},
{
name: "Update events enabled",
cfg: &externaldns.Config{
KubeConfig: "path-to-kubeconfig-not-exists",
APIServerURL: svr.URL,
Sources: []string{"ingress"},
UpdateEvents: true,
},
expectedError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
src, err := buildSource(t.Context(), tt.cfg)
if tt.expectedError {
assert.Error(t, err)
assert.Nil(t, src)
} else {
require.NoError(t, err)
assert.NotNil(t, src)
}
})
}
}
func TestBuildSourceWithWrappers(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
}))
defer svr.Close()
tests := []struct {
name string
cfg *externaldns.Config
asserts func(*testing.T, *externaldns.Config)
}{
{
name: "configuration with target filter wrapper",
cfg: &externaldns.Config{
APIServerURL: svr.URL,
Sources: []string{"fake"},
TargetNetFilter: []string{"10.0.0.0/8"},
},
},
{
name: "configuration with nat64 networks",
cfg: &externaldns.Config{
APIServerURL: svr.URL,
Sources: []string{"fake"},
NAT64Networks: []string{"2001:db8::/96"},
},
},
{
name: "default configuration",
cfg: &externaldns.Config{
APIServerURL: svr.URL,
Sources: []string{"fake"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := buildSource(t.Context(), tt.cfg)
require.NoError(t, err)
})
}
}
// Helper used by runExecuteSubprocess.
func TestHelperProcess(t *testing.T) {
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
return
}
// Parse args after the "--" sentinel.
idx := -1
for i, a := range os.Args {
if a == "--" {
idx = i
break
}
}
var args []string
if idx >= 0 {
args = os.Args[idx+1:]
}
os.Args = append([]string{"external-dns"}, args...)
Execute()
}
// runExecuteSubprocess runs Execute in a separate process and returns exit code and output.
func runExecuteSubprocess(t *testing.T, args []string) (int, string, error) {
t.Helper()
cmdArgs := append([]string{"-test.run=TestHelperProcess", "--"}, args...)
cmd := exec.Command(os.Args[0], cmdArgs...)
cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
var buf bytes.Buffer
cmd.Stdout = &buf
cmd.Stderr = &buf
err := cmd.Run()
output := buf.String()
if err == nil {
return 0, output, nil
}
ee := &exec.ExitError{}
if errors.As(err, &ee) {
return ee.ExitCode(), output, nil
}
return -1, output, err
}
func TestExecuteOnceDryRunExitsZero(t *testing.T) {
// Use :0 for an ephemeral metrics port.
code, _, err := runExecuteSubprocess(t, []string{
"--source", "fake",
"--provider", "inmemory",
"--once",
"--dry-run",
"--metrics-address", ":0",
})
require.NoError(t, err)
assert.Equal(t, 0, code)
}
func TestExecuteUnknownProviderExitsNonZero(t *testing.T) {
code, _, err := runExecuteSubprocess(t, []string{
"--source", "fake",
"--provider", "unknown",
"--metrics-address", ":0",
})
require.NoError(t, err)
assert.NotEqual(t, 0, code)
}
func TestExecuteValidationErrorNoSources(t *testing.T) {
code, _, err := runExecuteSubprocess(t, []string{
"--provider", "inmemory",
"--metrics-address", ":0",
})
require.NoError(t, err)
assert.NotEqual(t, 0, code)
}
func TestExecuteFlagParsingErrorInvalidLogFormat(t *testing.T) {
code, _, err := runExecuteSubprocess(t, []string{
"--log-format", "invalid",
// Provide minimal required flags to keep errors focused on parsing
"--source", "fake",
"--provider", "inmemory",
"--metrics-address", ":0",
})
require.NoError(t, err)
assert.NotEqual(t, 0, code)
}
// Config validation failure triggers log.Fatalf.
func TestExecuteConfigValidationErrorExitsNonZero(t *testing.T) {
code, _, err := runExecuteSubprocess(t, []string{
"--source", "fake",
// Choose a provider with validation that fails without required flags
"--provider", "azure",
// No --azure-config-file provided
"--metrics-address", ":0",
})
require.NoError(t, err)
assert.NotEqual(t, 0, code)
}
// buildSource failure triggers log.Fatal.
func TestExecuteBuildSourceErrorExitsNonZero(t *testing.T) {
// Use a valid source name (ingress) and an invalid kubeconfig path to
// force client creation failure inside buildSource.
code, _, err := runExecuteSubprocess(t, []string{
"--source", "ingress",
"--kubeconfig", "this/path/does/not/exist",
"--provider", "inmemory",
"--metrics-address", ":0",
})
require.NoError(t, err)
assert.NotEqual(t, 0, code)
}
// RunOnce error exits non-zero.
func TestExecuteRunOnceErrorExitsNonZero(t *testing.T) {
// Connector source dials a TCP server; use a closed port to fail.
code, _, err := runExecuteSubprocess(t, []string{
"--source", "connector",
"--connector-source-server", "127.0.0.1:1",
"--provider", "inmemory",
"--once",
"--metrics-address", ":0",
})
require.NoError(t, err)
assert.NotEqual(t, 0, code)
}
// Run loop error exits non-zero.
func TestExecuteRunLoopErrorExitsNonZero(t *testing.T) {
code, _, err := runExecuteSubprocess(t, []string{
"--source", "connector",
"--connector-source-server", "127.0.0.1:1",
"--provider", "inmemory",
"--metrics-address", ":0",
})
require.NoError(t, err)
assert.NotEqual(t, 0, code)
}
// buildController registry-creation failure triggers log.Fatal.
func TestExecuteBuildControllerErrorExitsNonZero(t *testing.T) {
code, _, err := runExecuteSubprocess(t, []string{
"--source", "fake",
"--provider", "inmemory",
"--registry", "dynamodb",
// Force NewDynamoDBRegistry to fail validation by using empty owner id
"--txt-owner-id", "",
"--metrics-address", ":0",
})
require.NoError(t, err)
assert.NotEqual(t, 0, code)
}
// Controller run loop stops on context cancel.
func TestControllerRunCancelContextStopsLoop(t *testing.T) {
// Minimal controller using fake source and inmemory provider.
cfg := &externaldns.Config{
Sources: []string{"fake"},
Provider: "inmemory",
LogLevel: "error",
LogFormat: "text",
Policy: "sync",
Registry: "txt",
TXTOwnerID: "test-owner",
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
src, err := buildSource(ctx, cfg)
require.NoError(t, err)
domainFilter := createDomainFilter(cfg)
p, err := buildProvider(ctx, cfg, domainFilter)
require.NoError(t, err)
ctrl, err := buildController(ctx, cfg, src, p, domainFilter)
require.NoError(t, err)
done := make(chan struct{})
go func() {
ctrl.Run(ctx)
close(done)
}()
cancel()
select {
case <-done:
case <-time.After(2 * time.Second):
t.Fatal("controller did not stop after context cancellation")
}
}

View File

@ -0,0 +1,54 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import "sigs.k8s.io/external-dns/endpoint"
type metricsRecorder struct {
counterPerEndpointType map[string]int
}
func newMetricsRecorder() *metricsRecorder {
return &metricsRecorder{
counterPerEndpointType: map[string]int{
endpoint.RecordTypeA: 0,
endpoint.RecordTypeAAAA: 0,
endpoint.RecordTypeCNAME: 0,
endpoint.RecordTypeTXT: 0,
endpoint.RecordTypeSRV: 0,
endpoint.RecordTypeNS: 0,
endpoint.RecordTypePTR: 0,
endpoint.RecordTypeMX: 0,
endpoint.RecordTypeNAPTR: 0,
},
}
}
func (m *metricsRecorder) recordEndpointType(endpointType string) {
m.counterPerEndpointType[endpointType]++
}
func (m *metricsRecorder) getEndpointTypeCount(endpointType string) int {
if count, ok := m.counterPerEndpointType[endpointType]; ok {
return count
}
return 0
}
func (m *metricsRecorder) loadFloat64(endpointType string) float64 {
return float64(m.getEndpointTypeCount(endpointType))
}

View File

@ -0,0 +1,376 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/internal/testutils"
"sigs.k8s.io/external-dns/pkg/apis/externaldns"
"sigs.k8s.io/external-dns/plan"
"sigs.k8s.io/external-dns/registry"
)
func TestRecordKnownEndpointType(t *testing.T) {
mr := newMetricsRecorder()
// Recording a built-in type should start at 1 and increment
mr.recordEndpointType(endpoint.RecordTypeA)
assert.Equal(t, 1, mr.getEndpointTypeCount(endpoint.RecordTypeA))
mr.recordEndpointType(endpoint.RecordTypeA)
assert.Equal(t, 2, mr.getEndpointTypeCount(endpoint.RecordTypeA))
}
func TestRecordUnknownEndpointType(t *testing.T) {
mr := newMetricsRecorder()
const customType = "CUSTOM"
// Unknown types start at zero
assert.Equal(t, 0, mr.getEndpointTypeCount(customType))
// First record sets to 1
mr.recordEndpointType(customType)
assert.Equal(t, 1, mr.getEndpointTypeCount(customType))
// Subsequent records increment
mr.recordEndpointType(customType)
assert.Equal(t, 2, mr.getEndpointTypeCount(customType))
}
func TestLoadFloat64(t *testing.T) {
mr := newMetricsRecorder()
// loadFloat64 should return the float64 representation of the count
mr.recordEndpointType(endpoint.RecordTypeAAAA)
assert.InDelta(t, float64(1), mr.loadFloat64(endpoint.RecordTypeAAAA), 0.0001)
}
func TestVerifyARecords(t *testing.T) {
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "create-record.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
},
},
endpoint.NewDomainFilter([]string{"used.tld"}),
[]*endpoint.Endpoint{
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
},
{
DNSName: "create-record.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
},
[]*plan.Changes{},
)
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 2, verifiedRecords.Gauge, map[string]string{"record_type": "a"})
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "some-record.1.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
{
DNSName: "some-record.2.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
},
{
DNSName: "some-record.3.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"24.24.24.24"},
},
},
endpoint.NewDomainFilter([]string{"used.tld"}),
[]*endpoint.Endpoint{
{
DNSName: "some-record.1.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
{
DNSName: "some-record.2.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
},
},
[]*plan.Changes{{
Create: []*endpoint.Endpoint{
{
DNSName: "some-record.3.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"24.24.24.24"},
},
},
}},
)
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 2, verifiedRecords.Gauge, map[string]string{"record_type": "a"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 0, verifiedRecords.Gauge, map[string]string{"record_type": "aaaa"})
}
func TestVerifyAAAARecords(t *testing.T) {
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "create-record.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
},
endpoint.NewDomainFilter([]string{"used.tld"}),
[]*endpoint.Endpoint{
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
{
DNSName: "create-record.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
},
[]*plan.Changes{},
)
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 2, verifiedRecords.Gauge, map[string]string{"record_type": "aaaa"})
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "some-record.1.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "some-record.2.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
{
DNSName: "some-record.3.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::3"},
},
},
endpoint.NewDomainFilter([]string{"used.tld"}),
[]*endpoint.Endpoint{
{
DNSName: "some-record.1.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "some-record.2.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
},
[]*plan.Changes{{
Create: []*endpoint.Endpoint{
{
DNSName: "some-record.3.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::3"},
},
},
}},
)
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 0, verifiedRecords.Gauge, map[string]string{"record_type": "a"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 2, verifiedRecords.Gauge, map[string]string{"record_type": "aaaa"})
}
func TestARecords(t *testing.T) {
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "record1.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
{
DNSName: "record2.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
},
{
DNSName: "_mysql-svc._tcp.mysql.used.tld",
RecordType: endpoint.RecordTypeSRV,
Targets: endpoint.Targets{"0 50 30007 mysql.used.tld"},
},
},
endpoint.NewDomainFilter([]string{"used.tld"}),
[]*endpoint.Endpoint{
{
DNSName: "record1.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
{
DNSName: "_mysql-svc._tcp.mysql.used.tld",
RecordType: endpoint.RecordTypeSRV,
Targets: endpoint.Targets{"0 50 30007 mysql.used.tld"},
},
},
[]*plan.Changes{{
Create: []*endpoint.Endpoint{
{
DNSName: "record2.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
},
},
}},
)
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 1, verifiedRecords.Gauge, map[string]string{"record_type": "a"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 0, verifiedRecords.Gauge, map[string]string{"record_type": "aaaa"})
}
func TestAAAARecords(t *testing.T) {
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "record1.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "record2.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
{
DNSName: "_mysql-svc._tcp.mysql.used.tld",
RecordType: endpoint.RecordTypeSRV,
Targets: endpoint.Targets{"0 50 30007 mysql.used.tld"},
},
},
endpoint.NewDomainFilter([]string{"used.tld"}),
[]*endpoint.Endpoint{
{
DNSName: "record1.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "_mysql-svc._tcp.mysql.used.tld",
RecordType: endpoint.RecordTypeSRV,
Targets: endpoint.Targets{"0 50 30007 mysql.used.tld"},
},
},
[]*plan.Changes{{
Create: []*endpoint.Endpoint{
{
DNSName: "record2.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
},
}},
)
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 0, sourceRecords.Gauge, map[string]string{"record_type": "a"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 2, sourceRecords.Gauge, map[string]string{"record_type": "aaaa"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 0, verifiedRecords.Gauge, map[string]string{"record_type": "a"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 1, verifiedRecords.Gauge, map[string]string{"record_type": "aaaa"})
}
func TestGaugeMetricsWithMixedRecords(t *testing.T) {
configuredEndpoints := testutils.GenerateTestEndpointsByType(map[string]int{
endpoint.RecordTypeA: 534,
endpoint.RecordTypeAAAA: 324,
endpoint.RecordTypeCNAME: 2,
endpoint.RecordTypeTXT: 56,
endpoint.RecordTypeSRV: 11,
endpoint.RecordTypeNS: 3,
})
providerEndpoints := testutils.GenerateTestEndpointsByType(map[string]int{
endpoint.RecordTypeA: 5334,
endpoint.RecordTypeAAAA: 324,
endpoint.RecordTypeCNAME: 23,
endpoint.RecordTypeTXT: 6,
endpoint.RecordTypeSRV: 25,
endpoint.RecordTypeNS: 1,
endpoint.RecordTypePTR: 43,
})
cfg := externaldns.NewConfig()
cfg.ManagedDNSRecordTypes = endpoint.KnownRecordTypes
source := new(testutils.MockSource)
source.On("Endpoints").Return(configuredEndpoints, nil)
provider := &filteredMockProvider{
RecordsStore: providerEndpoints,
}
r, err := registry.NewNoopRegistry(provider)
require.NoError(t, err)
ctrl := &Controller{
Source: source,
Registry: r,
Policy: &plan.SyncPolicy{},
DomainFilter: endpoint.NewDomainFilter([]string{}),
ManagedRecordTypes: cfg.ManagedDNSRecordTypes,
}
assert.NoError(t, ctrl.RunOnce(t.Context()))
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 534, sourceRecords.Gauge, map[string]string{"record_type": "a"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 324, sourceRecords.Gauge, map[string]string{"record_type": "aaaa"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 0, sourceRecords.Gauge, map[string]string{"record_type": "cname"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 11, sourceRecords.Gauge, map[string]string{"record_type": "srv"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 5334, registryRecords.Gauge, map[string]string{"record_type": "a"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 324, registryRecords.Gauge, map[string]string{"record_type": "aaaa"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 0, registryRecords.Gauge, map[string]string{"record_type": "mx"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 43, registryRecords.Gauge, map[string]string{"record_type": "ptr"})
}

File diff suppressed because it is too large Load Diff

4
v0.20.0/docs/OWNERS Normal file
View File

@ -0,0 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- docs

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More